From aa443292021eac3ec986c3291c052da19fe641a4 Mon Sep 17 00:00:00 2001 From: therealunull Date: Mon, 14 Dec 2020 07:41:02 -0500 Subject: [PATCH] plugins: add missing compatible plugins --- .../java/net/runelite/api/WallObject.java | 2 +- .../client/plugins/bank/BankConfig.java | 136 - .../client/plugins/bank/BankPlugin.java | 546 ---- .../client/plugins/bank/BankSearch.java | 119 - .../plugins/banktags/BankTagsPlugin.java | 598 ---- .../client/plugins/banktags/TagManager.java | 231 -- .../plugins/banktags/tabs/TabInterface.java | 1198 ------- .../plugins/banktags/tabs/TabManager.java | 167 - .../client/plugins/camera/CameraConfig.java | 183 -- .../client/plugins/camera/CameraPlugin.java | 529 ---- .../client/plugins/corp/CoreOverlay.java | 67 + .../client/plugins/corp/CorpConfig.java | 55 + .../plugins/corp/CorpDamageOverlay.java | 135 + .../client/plugins/corp/CorpPlugin.java | 216 ++ .../crowdsourcing/CrowdsourcingManager.java | 99 + .../crowdsourcing/CrowdsourcingPlugin.java | 113 + .../crowdsourcing/cooking/CookingData.java | 41 + .../cooking/CrowdsourcingCooking.java | 113 + .../dialogue/CrowdsourcingDialogue.java | 90 + .../dialogue/DialogueOptionsData.java | 36 + .../dialogue/NpcDialogueData.java | 37 + .../dialogue/PlayerDialogueData.java | 36 + .../movement/CrowdsourcingMovement.java | 84 + .../crowdsourcing/movement/MovementData.java | 43 + .../music/CrowdsourcingMusic.java | 71 + .../crowdsourcing/music/MusicUnlockData.java | 39 + .../skilling/SkillingEndReason.java} | 13 +- .../crowdsourcing/skilling/SkillingState.java | 34 + .../thieving/CrowdsourcingThieving.java | 126 + .../thieving/PickpocketData.java | 43 + .../woodcutting/CrowdsourcingWoodcutting.java | 233 ++ .../woodcutting/WoodcuttingData.java | 47 + .../crowdsourcing/zmi/CrowdsourcingZMI.java | 141 + .../plugins/crowdsourcing/zmi/ZMIData.java | 42 + .../plugins/customcursor/CustomCursor.java | 59 + .../customcursor/CustomCursorConfig.java | 43 + .../customcursor/CustomCursorPlugin.java | 175 ++ .../dailytaskindicators/DailyTasksConfig.java | 134 + .../dailytaskindicators/DailyTasksPlugin.java | 285 ++ .../defaultworld/DefaultWorldConfig.java | 73 + .../defaultworld/DefaultWorldPlugin.java | 158 + .../plugins/discord/DiscordAreaType.java | 35 + .../client/plugins/discord/DiscordConfig.java | 151 + .../plugins/discord/DiscordGameEventType.java | 624 ++++ .../client/plugins/discord/DiscordPlugin.java | 450 +++ .../client/plugins/discord/DiscordState.java | 279 ++ .../plugins/discord/DiscordUserInfo.java | 37 + .../client/plugins/dpscounter/DpsConfig.java | 77 + .../plugins/dpscounter/DpsCounterPlugin.java | 327 ++ .../client/plugins/dpscounter/DpsMember.java | 99 + .../client/plugins/dpscounter/DpsOverlay.java | 162 + .../client/plugins/dpscounter/DpsUpdate.java | 36 + .../client/plugins/driftnet/DriftNet.java | 65 + .../plugins/driftnet/DriftNetConfig.java | 119 + .../plugins/driftnet/DriftNetOverlay.java | 118 + .../plugins/driftnet/DriftNetPlugin.java | 363 +++ .../plugins/driftnet/DriftNetStatus.java | 59 + .../runelite/client/plugins/emojis/Emoji.java | 126 + .../client/plugins/emojis/EmojiPlugin.java | 203 ++ .../entityhider/EntityHiderConfig.java | 155 + .../entityhider/EntityHiderPlugin.java | 143 + .../client/plugins/examine/CacheKey.java | 71 + .../client/plugins/examine/ExaminePlugin.java | 429 +++ .../client/plugins/examine/ExamineType.java | 33 + .../plugins/examine/PendingExamine.java | 37 + .../plugins/fairyring/FairyRingConfig.java | 43 + .../plugins/fairyring/FairyRingPlugin.java | 367 +++ .../client/plugins/fairyring/FairyRings.java | 103 + .../client/plugins/feed/FeedConfig.java | 42 + .../client/plugins/feed/FeedPanel.java | 348 ++ .../client/plugins/feed/FeedPlugin.java | 154 + .../client/plugins/fps/FpsConfig.java | 98 + .../client/plugins/fps/FpsDrawListener.java | 150 + .../client/plugins/fps/FpsOverlay.java | 116 + .../client/plugins/fps/FpsPlugin.java | 106 + .../plugins/friendlist/FriendListPlugin.java | 129 + .../plugins/friendschat/ActivityType.java | 31 + .../friendschat/FriendsChatConfig.java | 185 ++ .../friendschat/FriendsChatPlugin.java | 722 +++++ .../plugins/friendschat/MemberActivity.java | 38 + .../friendschat/MemberJoinMessage.java | 36 + .../plugins/friendschat/MembersIndicator.java | 58 + .../client/plugins/gpu/SceneUploader.java | 2 +- .../grandexchange/GrandExchangeConfig.java | 138 + .../GrandExchangeInputListener.java | 109 + .../grandexchange/GrandExchangeItemPanel.java | 166 + .../grandexchange/GrandExchangeItems.java | 39 + .../grandexchange/GrandExchangeOfferSlot.java | 289 ++ .../GrandExchangeOffersPanel.java | 184 ++ .../grandexchange/GrandExchangePanel.java | 92 + .../grandexchange/GrandExchangePlugin.java | 964 ++++++ .../GrandExchangeSearchMode.java | 34 + .../GrandExchangeSearchPanel.java | 265 ++ .../plugins/grandexchange/SavedOffer.java | 39 + .../plugins/grounditems/GroundItem.java | 76 + .../grounditems/GroundItemInputListener.java | 137 + .../grounditems/GroundItemsConfig.java | 398 +++ .../grounditems/GroundItemsOverlay.java | 574 ++++ .../grounditems/GroundItemsPlugin.java | 691 ++++ .../plugins/grounditems/ItemThreshold.java | 97 + .../client/plugins/grounditems/LootType.java | 33 + .../MenuEntryWithCount.java} | 24 +- .../plugins/grounditems/NamedQuantity.java | 41 + .../grounditems/WildcardMatchLoader.java | 68 + .../grounditems/config/DespawnTimerMode.java | 33 + .../grounditems/config/HighlightTier.java | 53 + .../grounditems/config/ItemHighlightMode.java | 45 + .../grounditems/config/MenuHighlightMode.java | 45 + .../grounditems/config/PriceDisplayMode.java | 46 + .../config/ValueCalculationMode.java | 46 + .../groundmarkers/ColorTileMarker.java | 44 + .../groundmarkers/GroundMarkerConfig.java | 67 + .../GroundMarkerMinimapOverlay.java | 117 + .../groundmarkers/GroundMarkerOverlay.java | 121 + .../groundmarkers/GroundMarkerPlugin.java | 328 ++ .../groundmarkers/GroundMarkerPoint.java | 48 + .../plugins/herbiboars/HerbiboarConfig.java | 133 + .../herbiboars/HerbiboarMinimapOverlay.java | 81 + .../plugins/herbiboars/HerbiboarOverlay.java | 142 + .../plugins/herbiboars/HerbiboarPlugin.java | 406 +++ .../plugins/herbiboars/HerbiboarRule.java | 110 + .../herbiboars/HerbiboarSearchSpot.java | 180 ++ .../plugins/herbiboars/HerbiboarStart.java | 55 + .../plugins/herbiboars/TrailToSpot.java | 60 + .../client/plugins/hiscore/HiscoreConfig.java | 88 + .../client/plugins/hiscore/HiscorePanel.java | 756 +++++ .../client/plugins/hiscore/HiscorePlugin.java | 220 ++ .../plugins/hiscore/NameAutocompleter.java | 293 ++ .../client/plugins/hunter/HunterConfig.java | 89 + .../client/plugins/hunter/HunterPlugin.java | 396 +++ .../client/plugins/hunter/HunterTrap.java | 121 + .../client/plugins/hunter/TrapOverlay.java | 189 ++ .../client/plugins/implings/Impling.java | 109 + .../implings/ImplingMinimapOverlay.java | 82 + .../client/plugins/implings/ImplingSpawn.java | 89 + .../client/plugins/implings/ImplingType.java | 48 + .../plugins/implings/ImplingsConfig.java | 374 +++ .../plugins/implings/ImplingsOverlay.java | 145 + .../plugins/implings/ImplingsPlugin.java | 235 ++ .../client/plugins/info/InfoPanel.java | 308 ++ .../client/plugins/info/InfoPlugin.java | 70 + .../client/plugins/info/JRichTextPane.java | 98 + .../instancemap/InstanceMapInputListener.java | 148 + .../instancemap/InstanceMapOverlay.java | 287 ++ .../instancemap/InstanceMapPlugin.java | 157 + .../interfacestyles/HealthbarOverride.java | 87 + .../InterfaceStylesConfig.java | 99 + .../InterfaceStylesPlugin.java | 416 +++ .../Skin.java} | 22 +- .../interfacestyles/SpriteOverride.java | 154 + .../plugins/interfacestyles/WidgetOffset.java | 197 ++ .../interfacestyles/WidgetOverride.java | 56 + .../inventorygrid/InventoryGridConfig.java | 75 + .../inventorygrid/InventoryGridOverlay.java | 180 ++ .../inventorygrid/InventoryGridPlugin.java | 66 + .../inventorytags/InventoryTagsConfig.java | 119 + .../inventorytags/InventoryTagsOverlay.java | 78 + .../inventorytags/InventoryTagsPlugin.java | 274 ++ .../InventoryViewerConfig.java | 58 + .../InventoryViewerOverlay.java | 117 + .../InventoryViewerPlugin.java | 84 + .../plugins/itemcharges/ItemChargeConfig.java | 472 +++ .../itemcharges/ItemChargeInfobox.java | 60 + .../itemcharges/ItemChargeOverlay.java | 173 + .../plugins/itemcharges/ItemChargePlugin.java | 728 +++++ .../plugins/itemcharges/ItemChargeType.java | 48 + .../plugins/itemcharges/ItemWithCharge.java | 548 ++++ .../plugins/itemcharges/ItemWithSlot.java | 55 + .../ItemIdentification.java | 310 ++ .../ItemIdentificationConfig.java | 152 + .../ItemIdentificationMode.java | 31 + .../ItemIdentificationOverlay.java | 144 + .../ItemIdentificationPlugin.java | 64 + .../plugins/itemprices/ItemPricesConfig.java | 99 + .../plugins/itemprices/ItemPricesOverlay.java | 302 ++ .../plugins/itemprices/ItemPricesPlugin.java | 65 + .../plugins/itemstats/BoostedStatBoost.java | 51 + .../client/plugins/itemstats/Builders.java | 87 + .../client/plugins/itemstats/Combo.java | 66 + .../client/plugins/itemstats/Effect.java | 32 + .../MenuIndexes.java => itemstats/Food.java} | 36 +- .../client/plugins/itemstats/FoodBase.java | 35 + .../plugins/itemstats/ItemStatChanges.java | 235 ++ .../itemstats/ItemStatChangesService.java | 35 + .../itemstats/ItemStatChangesServiceImpl.java | 46 + .../plugins/itemstats/ItemStatConfig.java | 179 ++ .../plugins/itemstats/ItemStatOverlay.java | 418 +++ .../plugins/itemstats/ItemStatPlugin.java | 430 +++ .../client/plugins/itemstats/Positivity.java | 77 + .../plugins/itemstats/RangeStatBoost.java | 62 + .../plugins/itemstats/RangeStatChange.java | 117 + .../plugins/itemstats/SimpleStatBoost.java | 51 + .../plugins/itemstats/SingleEffect.java | 42 + .../client/plugins/itemstats/StatBoost.java | 96 + .../client/plugins/itemstats/StatChange.java | 87 + .../plugins/itemstats/StatsChanges.java | 49 + .../itemstats/delta/DeltaCalculator.java | 31 + .../itemstats/delta/DeltaPercentage.java | 40 + .../plugins/itemstats/food/Anglerfish.java | 66 + .../itemstats/potions/GauntletPotion.java | 58 + .../itemstats/potions/PotionDuration.java | 120 + .../itemstats/potions/PrayerPotion.java | 105 + .../itemstats/potions/SaradominBrew.java | 78 + .../itemstats/potions/SuperRestore.java | 75 + .../itemstats/special/CastleWarsBandage.java | 85 + .../plugins/itemstats/special/SpicyStew.java | 161 + .../plugins/itemstats/stats/EnergyStat.java | 48 + .../plugins/itemstats/stats/SkillStat.java | 52 + .../client/plugins/itemstats/stats/Stat.java | 58 + .../client/plugins/itemstats/stats/Stats.java | 55 + .../keyremapping/KeyRemappingConfig.java | 301 ++ .../keyremapping/KeyRemappingListener.java | 246 ++ .../keyremapping/KeyRemappingPlugin.java | 217 ++ .../kingdomofmiscellania/KingdomCounter.java | 53 + .../kingdomofmiscellania/KingdomPlugin.java | 141 + .../client/plugins/kourendlibrary/Book.java | 140 + .../plugins/kourendlibrary/BookPanel.java | 90 + .../plugins/kourendlibrary/Bookcase.java | 130 + .../kourendlibrary/KourendLibraryConfig.java | 95 + .../kourendlibrary/KourendLibraryOverlay.java | 247 ++ .../kourendlibrary/KourendLibraryPanel.java | 168 + .../kourendlibrary/KourendLibraryPlugin.java | 430 +++ .../KourendLibraryTutorialOverlay.java | 104 + .../plugins/kourendlibrary/Library.java | 809 +++++ .../plugins/kourendlibrary/SolvedState.java | 32 + .../LoginScreenConfig.java} | 94 +- .../loginscreen/LoginScreenOverride.java | 60 + .../loginscreen/LoginScreenPlugin.java | 321 ++ .../plugins/loottracker/LootReceived.java | 44 + .../plugins/loottracker/LootTrackerBox.java | 382 +++ .../loottracker/LootTrackerConfig.java | 151 + .../LootTrackerItem.java} | 49 +- .../loottracker/LootTrackerMapping.java | 60 + .../plugins/loottracker/LootTrackerPanel.java | 666 ++++ .../loottracker/LootTrackerPlugin.java | 1066 +++++++ .../loottracker/LootTrackerPriceType.java | 31 + .../loottracker/LootTrackerRecord.java | 56 + .../plugins/lowmemory/LowMemoryPlugin.java | 76 + .../client/plugins/minimap/MinimapConfig.java | 104 + .../client/plugins/minimap/MinimapDot.java | 78 + .../client/plugins/minimap/MinimapPlugin.java | 194 ++ .../client/plugins/mining/MiningConfig.java | 56 + .../client/plugins/mining/MiningOverlay.java | 109 + .../client/plugins/mining/MiningPlugin.java | 344 ++ .../plugins/mining/MiningRocksOverlay.java | 142 + .../client/plugins/mining/MiningSession.java | 39 + .../client/plugins/mining/Pickaxe.java | 134 + .../runelite/client/plugins/mining/Rock.java | 141 + .../client/plugins/mining/RockRespawn.java | 46 + .../plugins/motherlode/MotherlodeConfig.java | 146 + .../motherlode/MotherlodeGemOverlay.java | 145 + .../motherlode/MotherlodeOreOverlay.java | 158 + .../plugins/motherlode/MotherlodeOverlay.java | 125 + .../plugins/motherlode/MotherlodePlugin.java | 528 ++++ .../motherlode/MotherlodeSackOverlay.java | 120 + .../motherlode/MotherlodeSceneOverlay.java | 166 + .../plugins/motherlode/MotherlodeSession.java | 186 ++ .../client/plugins/mta/MTAConfig.java | 77 + .../plugins/mta/MTAInventoryOverlay.java | 61 + .../client/plugins/mta/MTAPlugin.java | 104 + .../runelite/client/plugins/mta/MTARoom.java | 52 + .../client/plugins/mta/MTASceneOverlay.java | 61 + .../plugins/mta/alchemy/AlchemyItem.java | 63 + .../plugins/mta/alchemy/AlchemyRoom.java | 474 +++ .../plugins/mta/alchemy/AlchemyRoomTimer.java | 55 + .../client/plugins/mta/alchemy/Cupboard.java | 33 + .../mta/enchantment/EnchantmentRoom.java | 141 + .../mta/graveyard/GraveyardCounter.java | 56 + .../plugins/mta/graveyard/GraveyardRoom.java | 150 + .../client/plugins/mta/telekinetic/Maze.java | 73 + .../mta/telekinetic/TelekineticRoom.java | 526 ++++ .../nightmarezone/AbsorptionCounter.java | 66 + .../nightmarezone/NightmareZoneConfig.java | 163 + .../nightmarezone/NightmareZoneOverlay.java | 169 + .../nightmarezone/NightmareZonePlugin.java | 286 ++ .../client/plugins/notes/NotesConfig.java | 51 + .../client/plugins/notes/NotesPanel.java | 162 + .../client/plugins/notes/NotesPlugin.java | 93 + .../opponentinfo/HitpointsDisplayStyle.java | 32 + .../opponentinfo/OpponentInfoConfig.java | 66 + .../opponentinfo/OpponentInfoOverlay.java | 201 ++ .../opponentinfo/OpponentInfoPlugin.java | 191 ++ .../opponentinfo/PlayerComparisonOverlay.java | 187 ++ .../client/plugins/party/PartyConfig.java | 83 + .../plugins/party/PartyPingOverlay.java | 108 + .../client/plugins/party/PartyPlugin.java | 509 +++ .../plugins/party/PartyPluginService.java | 40 + .../plugins/party/PartyPluginServiceImpl.java | 49 + .../plugins/party/PartyStatsOverlay.java | 141 + .../plugins/party/PartyWorldMapPoint.java | 66 + .../client/plugins/party/data/PartyData.java | 50 + .../plugins/party/data/PartyTilePingData.java | 41 + .../party/messages/LocationUpdate.java | 37 + .../plugins/party/messages/SkillUpdate.java | 39 + .../plugins/party/messages/TilePing.java | 37 + .../client/plugins/pestcontrol/Game.java | 127 + .../pestcontrol/PestControlOverlay.java | 229 ++ .../pestcontrol/PestControlPlugin.java | 126 + .../client/plugins/pestcontrol/Portal.java | 44 + .../plugins/pestcontrol/PortalContext.java | 39 + .../client/plugins/pestcontrol/Rotation.java | 60 + .../PlayerIndicatorsConfig.java | 217 ++ .../PlayerIndicatorsMinimapOverlay.java | 76 + .../PlayerIndicatorsOverlay.java | 148 + .../PlayerIndicatorsPlugin.java | 239 ++ .../PlayerIndicatorsService.java | 91 + .../PlayerIndicatorsTileOverlay.java | 73 + .../playerindicators/PlayerNameLocation.java | 45 + .../client/plugins/poison/PoisonConfig.java | 55 + .../client/plugins/poison/PoisonInfobox.java | 54 + .../client/plugins/poison/PoisonOverlay.java | 88 + .../client/plugins/poison/PoisonPlugin.java | 348 ++ .../plugins/prayer/PrayerBarOverlay.java | 173 + .../client/plugins/prayer/PrayerConfig.java | 143 + .../client/plugins/prayer/PrayerCounter.java | 60 + .../plugins/prayer/PrayerDoseOverlay.java | 172 + .../plugins/prayer/PrayerFlickLocation.java | 35 + .../plugins/prayer/PrayerFlickOverlay.java | 97 + .../client/plugins/prayer/PrayerPlugin.java | 378 +++ .../plugins/prayer/PrayerRestoreType.java | 65 + .../client/plugins/prayer/PrayerType.java | 71 + .../puzzlesolver/PuzzleSolverConfig.java | 64 + .../puzzlesolver/PuzzleSolverOverlay.java | 483 +++ .../puzzlesolver/PuzzleSolverPlugin.java | 283 ++ .../puzzlesolver/VarrockMuseumAnswer.java | 171 + .../puzzlesolver/lightbox/Combination.java | 30 + .../puzzlesolver/lightbox/LightBox.java | 33 + .../lightbox/LightboxSolution.java | 68 + .../puzzlesolver/lightbox/LightboxSolver.java | 96 + .../puzzlesolver/lightbox/LightboxState.java | 58 + .../puzzlesolver/solver/PuzzleSolver.java | 96 + .../puzzlesolver/solver/PuzzleState.java | 220 ++ .../solver/PuzzleSwapPattern.java | 66 + .../solver/heuristics/Heuristic.java | 33 + .../solver/heuristics/ManhattanDistance.java | 122 + .../solver/pathfinding/IDAStar.java | 106 + .../solver/pathfinding/IDAStarMM.java | 716 +++++ .../solver/pathfinding/Pathfinder.java | 47 + .../pyramidplunder/PyramidPlunderConfig.java | 154 + .../pyramidplunder/PyramidPlunderOverlay.java | 168 + .../pyramidplunder/PyramidPlunderPlugin.java | 222 ++ .../pyramidplunder/PyramidPlunderTimer.java | 74 + .../runelite/client/plugins/raids/Raid.java | 195 ++ .../client/plugins/raids/RaidRoom.java | 60 + .../client/plugins/raids/RaidsConfig.java | 200 ++ .../client/plugins/raids/RaidsOverlay.java | 226 ++ .../client/plugins/raids/RaidsPlugin.java | 977 ++++++ .../client/plugins/raids/RaidsTimer.java | 157 + .../client/plugins/raids/RoomType.java | 80 + .../client/plugins/raids/RotationSolver.java | 138 + .../plugins/raids/events/RaidReset.java | 34 + .../plugins/raids/events/RaidScouted.java | 42 + .../client/plugins/raids/solver/Layout.java | 70 + .../plugins/raids/solver/LayoutSolver.java | 222 ++ .../client/plugins/raids/solver/Room.java | 51 + .../randomevents/RandomEventConfig.java | 197 ++ .../randomevents/RandomEventPlugin.java | 214 ++ .../plugins/regenmeter/RegenMeterConfig.java | 72 + .../plugins/regenmeter/RegenMeterOverlay.java | 121 + .../plugins/regenmeter/RegenMeterPlugin.java | 180 ++ .../reportbutton/ReportButtonConfig.java | 53 + .../reportbutton/ReportButtonPlugin.java | 245 ++ .../plugins/reportbutton/TimeFormat.java | 43 + .../plugins/reportbutton/TimeStyle.java | 50 + .../client/plugins/roguesden/Obstacles.java | 53 + .../plugins/roguesden/RoguesDenOverlay.java | 93 + .../plugins/roguesden/RoguesDenPlugin.java | 159 + .../runecraft/AbyssMinimapOverlay.java | 109 + .../plugins/runecraft/AbyssOverlay.java | 124 + .../client/plugins/runecraft/AbyssRifts.java | 91 + .../plugins/runecraft/RunecraftConfig.java | 246 ++ .../plugins/runecraft/RunecraftPlugin.java | 225 ++ .../plugins/runenergy/RunEnergyConfig.java | 43 + .../plugins/runenergy/RunEnergyOverlay.java | 107 + .../plugins/runenergy/RunEnergyPlugin.java | 282 ++ .../plugins/runepouch/RunepouchConfig.java | 68 + .../plugins/runepouch/RunepouchOverlay.java | 179 ++ .../plugins/runepouch/RunepouchPlugin.java | 64 + .../client/plugins/runepouch/Runes.java | 117 + .../config/RunePouchOverlayMode.java | 32 + .../plugins/screenmarkers/ScreenMarker.java | 44 + .../ScreenMarkerCreationOverlay.java | 76 + .../ScreenMarkerMouseListener.java | 116 + .../screenmarkers/ScreenMarkerOverlay.java | 84 + .../screenmarkers/ScreenMarkerPlugin.java | 289 ++ .../screenmarkers/ScreenMarkerRenderable.java | 82 + .../ScreenMarkerWidgetHighlightOverlay.java | 129 + .../ui/ScreenMarkerCreationPanel.java | 150 + .../screenmarkers/ui/ScreenMarkerPanel.java | 577 ++++ .../ui/ScreenMarkerPluginPanel.java | 210 ++ .../plugins/screenshot/ScreenshotConfig.java | 254 ++ .../plugins/screenshot/ScreenshotOverlay.java | 122 + .../plugins/screenshot/ScreenshotPlugin.java | 775 +++++ .../skillcalculator/CacheSkillData.java | 59 + .../skillcalculator/CalculatorType.java | 55 + .../skillcalculator/SkillCalculator.java | 514 +++ .../skillcalculator/SkillCalculatorPanel.java | 101 + .../SkillCalculatorPlugin.java | 85 + .../plugins/skillcalculator/UIActionSlot.java | 188 ++ .../UICalculatorInputArea.java | 142 + .../skillcalculator/UICombinedActionSlot.java | 89 + .../skillcalculator/beans/SkillData.java | 34 + .../skillcalculator/beans/SkillDataBonus.java | 34 + .../skillcalculator/beans/SkillDataEntry.java | 38 + .../plugins/smelting/SmeltingConfig.java | 46 + .../plugins/smelting/SmeltingOverlay.java | 133 + .../plugins/smelting/SmeltingPlugin.java | 140 + .../plugins/smelting/SmeltingSession.java | 53 + .../client/plugins/specialcounter/Boss.java | 70 + .../specialcounter/SpecialCounter.java | 93 + .../specialcounter/SpecialCounterPlugin.java | 349 +++ .../specialcounter/SpecialCounterUpdate.java | 38 + .../plugins/specialcounter/SpecialWeapon.java | 44 + .../plugins/statusbars/BarRenderer.java | 172 + .../plugins/statusbars/StatusBarsConfig.java | 84 + .../plugins/statusbars/StatusBarsOverlay.java | 314 ++ .../plugins/statusbars/StatusBarsPlugin.java | 67 + .../client/plugins/statusbars/Viewport.java | 49 + .../plugins/statusbars/config/BarMode.java | 35 + .../stretchedmode/StretchedModeConfig.java | 76 + .../stretchedmode/StretchedModePlugin.java | 114 + .../stretchedmode/TranslateMouseListener.java | 103 + .../TranslateMouseWheelListener.java | 62 + .../plugins/teamcapes/TeamCapesConfig.java | 44 + .../plugins/teamcapes/TeamCapesOverlay.java | 98 + .../plugins/teamcapes/TeamCapesPlugin.java | 126 + .../tearsofguthix/TearsOfGuthixOverlay.java | 81 + .../tearsofguthix/TearsOfGuthixPlugin.java | 115 + .../tileindicators/TileIndicatorsConfig.java | 98 + .../tileindicators/TileIndicatorsOverlay.java | 110 + .../tileindicators/TileIndicatorsPlugin.java | 65 + .../plugins/timestamp/TimestampConfig.java | 69 + .../plugins/timestamp/TimestampPlugin.java | 147 + .../timetracking/OverviewItemPanel.java | 159 + .../timetracking/OverviewTabPanel.java | 165 + .../plugins/timetracking/SortOrder.java | 32 + .../plugins/timetracking/SummaryState.java | 34 + .../client/plugins/timetracking/Tab.java | 52 + .../plugins/timetracking/TabContentPanel.java | 101 + .../plugins/timetracking/TimeFormatMode.java | 43 + .../timetracking/TimeTrackingConfig.java | 139 + .../timetracking/TimeTrackingPanel.java | 181 ++ .../timetracking/TimeTrackingPlugin.java | 268 ++ .../plugins/timetracking/TimeablePanel.java | 92 + .../plugins/timetracking/clocks/Clock.java | 63 + .../timetracking/clocks/ClockManager.java | 241 ++ .../timetracking/clocks/ClockPanel.java | 289 ++ .../timetracking/clocks/ClockTabPanel.java | 206 ++ .../timetracking/clocks/Stopwatch.java | 107 + .../timetracking/clocks/StopwatchPanel.java | 142 + .../plugins/timetracking/clocks/Timer.java | 114 + .../timetracking/clocks/TimerPanel.java | 82 + .../timetracking/farming/CropState.java | 44 + .../farming/FarmingContractInfoBox.java | 130 + .../farming/FarmingContractManager.java | 353 +++ .../timetracking/farming/FarmingPatch.java | 44 + .../timetracking/farming/FarmingRegion.java | 57 + .../timetracking/farming/FarmingTabPanel.java | 214 ++ .../timetracking/farming/FarmingTracker.java | 330 ++ .../timetracking/farming/FarmingWorld.java | 309 ++ .../farming/PatchImplementation.java | 2790 +++++++++++++++++ .../timetracking/farming/PatchPrediction.java | 37 + .../timetracking/farming/PatchState.java | 53 + .../plugins/timetracking/farming/Produce.java | 246 ++ .../timetracking/hunter/BirdHouse.java | 64 + .../timetracking/hunter/BirdHouseData.java | 38 + .../timetracking/hunter/BirdHouseSpace.java | 42 + .../timetracking/hunter/BirdHouseState.java | 65 + .../hunter/BirdHouseTabPanel.java | 149 + .../timetracking/hunter/BirdHouseTracker.java | 249 ++ .../plugins/tithefarm/TitheFarmPlant.java | 68 + .../tithefarm/TitheFarmPlantOverlay.java | 119 + .../tithefarm/TitheFarmPlantState.java | 58 + .../plugins/tithefarm/TitheFarmPlantType.java | 91 + .../plugins/tithefarm/TitheFarmPlugin.java | 155 + .../tithefarm/TitheFarmPluginConfig.java | 67 + .../client/plugins/twitch/TwitchConfig.java | 58 + .../client/plugins/twitch/TwitchPlugin.java | 242 ++ .../client/plugins/twitch/irc/Message.java | 108 + .../plugins/twitch/irc/TwitchIRCClient.java | 247 ++ .../plugins/twitch/irc/TwitchListener.java | 36 + .../virtuallevels/VirtualLevelsConfig.java | 44 + .../virtuallevels/VirtualLevelsPlugin.java | 151 + .../wintertodt/WintertodtActivity.java | 43 + .../plugins/wintertodt/WintertodtConfig.java | 142 + .../wintertodt/WintertodtInterruptType.java | 45 + .../plugins/wintertodt/WintertodtOverlay.java | 86 + .../plugins/wintertodt/WintertodtPlugin.java | 517 +++ .../config/WintertodtNotifyDamage.java | 45 + .../client/plugins/woodcutting/Axe.java | 103 + .../client/plugins/woodcutting/Tree.java | 114 + .../plugins/woodcutting/TreeRespawn.java | 48 + .../woodcutting/WoodcuttingConfig.java | 90 + .../woodcutting/WoodcuttingOverlay.java | 115 + .../woodcutting/WoodcuttingPlugin.java | 280 ++ .../woodcutting/WoodcuttingSession.java | 42 + .../woodcutting/WoodcuttingTreesOverlay.java | 130 + .../plugins/xptracker/XpProgressBarLabel.java | 2 + .../plugins/xptracker/XpTrackerPlugin.java | 2 +- .../plugins/xpupdater/XpUpdaterConfig.java | 66 + .../plugins/xpupdater/XpUpdaterPlugin.java | 218 ++ .../client/plugins/xtea/XteaPlugin.java | 106 + .../mixins/RSBoundaryObjectMixin.java | 2 +- .../net/runelite/rs/api/RSBoundaryObject.java | 2 +- 504 files changed, 73990 insertions(+), 3845 deletions(-) delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/corp/CoreOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpDamageOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CookingData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CrowdsourcingCooking.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/CrowdsourcingDialogue.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/DialogueOptionsData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/NpcDialogueData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/PlayerDialogueData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/CrowdsourcingMovement.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/MovementData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/CrowdsourcingMusic.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/MusicUnlockData.java rename runelite-client/src/main/java/net/runelite/client/plugins/{bank/ContainerPrices.java => crowdsourcing/skilling/SkillingEndReason.java} (85%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/PickpocketData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/CrowdsourcingWoodcutting.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/WoodcuttingData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/CrowdsourcingZMI.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/ZMIData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNet.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetStatus.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/examine/CacheKey.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/examine/ExamineType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRings.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsDrawListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendlist/FriendListPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendschat/ActivityType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberActivity.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberJoinMessage.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MembersIndicator.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeInputListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java rename runelite-client/src/main/java/net/runelite/client/plugins/{banktags/tabs/TabSprites.java => grounditems/MenuEntryWithCount.java} (75%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/NamedQuantity.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/DespawnTimerMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/HighlightTier.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/MenuHighlightMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/PriceDisplayMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ValueCalculationMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/ColorTileMarker.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPoint.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarMinimapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarRule.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarSearchSpot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarStart.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/TrailToSpot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscoreConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hiscore/NameAutocompleter.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/Impling.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingMinimapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingSpawn.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/info/JRichTextPane.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapInputListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/HealthbarOverride.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java rename runelite-client/src/main/java/net/runelite/client/plugins/{camera/ControlFunction.java => interfacestyles/Skin.java} (80%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/SpriteOverride.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOffset.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOverride.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/BoostedStatBoost.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Builders.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Combo.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Effect.java rename runelite-client/src/main/java/net/runelite/client/plugins/{banktags/tabs/MenuIndexes.java => itemstats/Food.java} (55%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/FoodBase.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesService.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesServiceImpl.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Positivity.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatBoost.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatChange.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/SimpleStatBoost.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/SingleEffect.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatBoost.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatChange.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatsChanges.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/delta/DeltaCalculator.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/delta/DeltaPercentage.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/Anglerfish.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/GauntletPotion.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/PotionDuration.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/PrayerPotion.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/SaradominBrew.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/SuperRestore.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/CastleWarsBandage.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/EnergyStat.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/SkillStat.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/Stat.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/Stats.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kingdomofmiscellania/KingdomCounter.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kingdomofmiscellania/KingdomPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Book.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/BookPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Bookcase.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryTutorialOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Library.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/SolvedState.java rename runelite-client/src/main/java/net/runelite/client/plugins/{banktags/BankTagsConfig.java => loginscreen/LoginScreenConfig.java} (57%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loginscreen/LoginScreenOverride.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loginscreen/LoginScreenPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java rename runelite-client/src/main/java/net/runelite/client/plugins/{banktags/tabs/TagTab.java => loottracker/LootTrackerItem.java} (69%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerMapping.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPriceType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/lowmemory/LowMemoryPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapDot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRocksOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSession.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/Pickaxe.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mining/RockRespawn.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeGemOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOreOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSackOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSceneOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSession.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/MTAConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/MTAInventoryOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/MTAPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/MTARoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/MTASceneOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyItem.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoomTimer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/Cupboard.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/enchantment/EnchantmentRoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/graveyard/GraveyardCounter.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/graveyard/GraveyardRoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/Maze.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/TelekineticRoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/AbsorptionCounter.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZoneConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZoneOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZonePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/HitpointsDisplayStyle.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/PlayerComparisonOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPingOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPluginService.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPluginServiceImpl.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyWorldMapPoint.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/data/PartyData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/data/PartyTilePingData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/messages/LocationUpdate.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/messages/SkillUpdate.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/messages/TilePing.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/Game.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PestControlOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PestControlPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/Portal.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PortalContext.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/Rotation.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsMinimapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsTileOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerNameLocation.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonInfobox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerBarOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerCounter.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerDoseOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerFlickLocation.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerFlickOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerRestoreType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/VarrockMuseumAnswer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/lightbox/Combination.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/lightbox/LightBox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/lightbox/LightboxSolution.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/lightbox/LightboxSolver.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/lightbox/LightboxState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSwapPattern.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/Heuristic.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/ManhattanDistance.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/IDAStar.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/IDAStarMM.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/Pathfinder.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderTimer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/Raid.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidRoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RoomType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RotationSolver.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/events/RaidReset.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/events/RaidScouted.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Layout.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/LayoutSolver.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Room.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/randomevents/RandomEventConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/randomevents/RandomEventPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/reportbutton/ReportButtonConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/reportbutton/ReportButtonPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/reportbutton/TimeFormat.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/reportbutton/TimeStyle.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/roguesden/Obstacles.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssMinimapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssRifts.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runepouch/RunepouchConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runepouch/RunepouchOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runepouch/RunepouchPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runepouch/Runes.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/runepouch/config/RunePouchOverlayMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarker.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerCreationOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerMouseListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerRenderable.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerWidgetHighlightOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ui/ScreenMarkerCreationPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ui/ScreenMarkerPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ui/ScreenMarkerPluginPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/CacheSkillData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/CalculatorType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculator.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculatorPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UIActionSlot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UICalculatorInputArea.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/UICombinedActionSlot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/beans/SkillData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/beans/SkillDataBonus.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/beans/SkillDataEntry.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/smelting/SmeltingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/smelting/SmeltingOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/smelting/SmeltingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/smelting/SmeltingSession.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/Boss.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounter.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterUpdate.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialWeapon.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/statusbars/BarRenderer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/statusbars/Viewport.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/statusbars/config/BarMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/StretchedModeConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/StretchedModePlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/TranslateMouseListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/TranslateMouseWheelListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tearsofguthix/TearsOfGuthixOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tearsofguthix/TearsOfGuthixPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timestamp/TimestampConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timestamp/TimestampPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/SortOrder.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/SummaryState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeFormatMode.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/CropState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingContractInfoBox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingContractManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchPrediction.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouse.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseSpace.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tithefarm/TitheFarmPlant.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tithefarm/TitheFarmPlantOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tithefarm/TitheFarmPlantState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tithefarm/TitheFarmPlantType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tithefarm/TitheFarmPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/tithefarm/TitheFarmPluginConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/twitch/TwitchConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/twitch/TwitchPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/twitch/irc/Message.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/twitch/irc/TwitchIRCClient.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/twitch/irc/TwitchListener.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/wintertodt/WintertodtActivity.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/wintertodt/WintertodtConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/wintertodt/WintertodtInterruptType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/wintertodt/WintertodtOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/wintertodt/WintertodtPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/wintertodt/config/WintertodtNotifyDamage.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Axe.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/TreeRespawn.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingSession.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingTreesOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/xtea/XteaPlugin.java diff --git a/runelite-api/src/main/java/net/runelite/api/WallObject.java b/runelite-api/src/main/java/net/runelite/api/WallObject.java index 0fbc6f7016..94e1d09bc2 100644 --- a/runelite-api/src/main/java/net/runelite/api/WallObject.java +++ b/runelite-api/src/main/java/net/runelite/api/WallObject.java @@ -52,7 +52,7 @@ public interface WallObject extends TileObject */ int getConfig(); - Renderable getRenderable(); + Renderable getRenderable1(); Renderable getRenderable2(); Model getModelA(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java deleted file mode 100644 index 002e01a3d4..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2018, TheLonelyDev - * Copyright (c) 2018, Jeremy Plsek - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.bank; - -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.Keybind; - -@ConfigGroup("bank") -public interface BankConfig extends Config -{ - @ConfigItem( - keyName = "showGE", - name = "Show Grand Exchange price", - description = "Show grand exchange price total (GE)", - position = 1 - ) - default boolean showGE() - { - return true; - } - - @ConfigItem( - keyName = "showHA", - name = "Show high alchemy price", - description = "Show high alchemy price total (HA)", - position = 2 - ) - default boolean showHA() - { - return false; - } - - @ConfigItem( - keyName = "showExact", - name = "Show exact bank value", - description = "Show exact bank value", - position = 3 - ) - default boolean showExact() - { - return false; - } - - @ConfigItem( - keyName = "rightClickBankInventory", - name = "Disable left click bank inventory", - description = "Configures whether the bank inventory button will bank your inventory on left click", - position = 4 - ) - default boolean rightClickBankInventory() - { - return false; - } - - @ConfigItem( - keyName = "rightClickBankEquip", - name = "Disable left click bank equipment", - description = "Configures whether the bank equipment button will bank your equipment on left click", - position = 5 - ) - default boolean rightClickBankEquip() - { - return false; - } - - @ConfigItem( - keyName = "rightClickBankLoot", - name = "Disable left click bank looting bag", - description = "Configures whether the bank looting bag button will bank your looting bag contents on left click", - position = 6 - ) - default boolean rightClickBankLoot() - { - return false; - } - - @ConfigItem( - keyName = "seedVaultValue", - name = "Show seed vault value", - description = "Adds the total value of all seeds inside the seed vault to the title", - position = 7 - ) - default boolean seedVaultValue() - { - return true; - } - - @ConfigItem( - keyName = "bankPinKeyboard", - name = "Keyboard Bankpin", - description = "Allows using the keyboard keys for bank pin input", - position = 8 - ) - default boolean bankPinKeyboard() - { - return false; - } - - @ConfigItem( - keyName = "searchKeybind", - name = "Search Shortcut", - description = "Keyboard shortcut for initiating a bank search", - position = 9 - ) - default Keybind searchKeybind() - { - return new Keybind(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java deleted file mode 100644 index 1741a87015..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java +++ /dev/null @@ -1,546 +0,0 @@ -/* - * Copyright (c) 2018, TheLonelyDev - * Copyright (c) 2018, Jeremy Plsek - * Copyright (c) 2019, Hydrox6 - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.bank; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; -import com.google.inject.Provides; -import java.awt.event.KeyEvent; -import java.text.ParseException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nullable; -import javax.inject.Inject; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.InventoryID; -import net.runelite.api.Item; -import net.runelite.api.ItemComposition; -import net.runelite.api.ItemContainer; -import net.runelite.api.ItemID; -import net.runelite.api.MenuEntry; -import net.runelite.api.ScriptID; -import net.runelite.api.VarClientStr; -import net.runelite.api.events.ItemContainerChanged; -import net.runelite.api.events.MenuEntryAdded; -import net.runelite.api.events.MenuShouldLeftClick; -import net.runelite.api.events.ScriptCallbackEvent; -import net.runelite.api.events.ScriptPostFired; -import net.runelite.api.events.WidgetLoaded; -import net.runelite.api.widgets.JavaScriptCallback; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetID; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.config.Keybind; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.game.ItemManager; -import net.runelite.client.input.KeyListener; -import net.runelite.client.input.KeyManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.util.QuantityFormatter; - -@PluginDescriptor( - name = "Bank", - description = "Modifications to the banking interface", - tags = {"grand", "exchange", "high", "alchemy", "prices", "deposit"} -) -@Slf4j -public class BankPlugin extends Plugin -{ - private static final String DEPOSIT_WORN = "Deposit worn items"; - private static final String DEPOSIT_INVENTORY = "Deposit inventory"; - private static final String DEPOSIT_LOOT = "Deposit loot"; - private static final String SEED_VAULT_TITLE = "Seed Vault"; - - private static final String NUMBER_REGEX = "[0-9]+(\\.[0-9]+)?[kmb]?"; - private static final Pattern VALUE_SEARCH_PATTERN = Pattern.compile("^(?ge|ha|alch)?" + - " *(((?[<>=]|>=|<=) *(?" + NUMBER_REGEX + "))|" + - "((?" + NUMBER_REGEX + ") *- *(?" + NUMBER_REGEX + ")))$", Pattern.CASE_INSENSITIVE); - - @Inject - private Client client; - - @Inject - private ClientThread clientThread; - - @Inject - private ItemManager itemManager; - - @Inject - private BankConfig config; - - @Inject - private BankSearch bankSearch; - - @Inject - private KeyManager keyManager; - - private boolean forceRightClickFlag; - private Multiset itemQuantities; // bank item quantities for bank value search - private String searchString; - - private final KeyListener searchHotkeyListener = new KeyListener() - { - @Override - public void keyTyped(KeyEvent e) - { - } - - @Override - public void keyPressed(KeyEvent e) - { - Keybind keybind = config.searchKeybind(); - if (keybind.matches(e)) - { - Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - if (bankContainer == null || bankContainer.isSelfHidden()) - { - return; - } - - log.debug("Search hotkey pressed"); - - bankSearch.initSearch(); - e.consume(); - } - } - - @Override - public void keyReleased(KeyEvent e) - { - } - }; - - @Provides - BankConfig getConfig(ConfigManager configManager) - { - return configManager.getConfig(BankConfig.class); - } - - @Override - protected void startUp() - { - keyManager.registerKeyListener(searchHotkeyListener); - } - - @Override - protected void shutDown() - { - keyManager.unregisterKeyListener(searchHotkeyListener); - clientThread.invokeLater(() -> bankSearch.reset(false)); - forceRightClickFlag = false; - itemQuantities = null; - searchString = null; - } - - @Subscribe - public void onMenuShouldLeftClick(MenuShouldLeftClick event) - { - if (!forceRightClickFlag) - { - return; - } - - forceRightClickFlag = false; - MenuEntry[] menuEntries = client.getMenuEntries(); - for (MenuEntry entry : menuEntries) - { - if ((entry.getOption().equals(DEPOSIT_WORN) && config.rightClickBankEquip()) - || (entry.getOption().equals(DEPOSIT_INVENTORY) && config.rightClickBankInventory()) - || (entry.getOption().equals(DEPOSIT_LOOT) && config.rightClickBankLoot())) - { - event.setForceRightClick(true); - return; - } - } - } - - @Subscribe - public void onMenuEntryAdded(MenuEntryAdded event) - { - if ((event.getOption().equals(DEPOSIT_WORN) && config.rightClickBankEquip()) - || (event.getOption().equals(DEPOSIT_INVENTORY) && config.rightClickBankInventory()) - || (event.getOption().equals(DEPOSIT_LOOT) && config.rightClickBankLoot())) - { - forceRightClickFlag = true; - } - } - - @Subscribe - public void onScriptCallbackEvent(ScriptCallbackEvent event) - { - int[] intStack = client.getIntStack(); - String[] stringStack = client.getStringStack(); - int intStackSize = client.getIntStackSize(); - int stringStackSize = client.getStringStackSize(); - - switch (event.getEventName()) - { - case "bankSearchFilter": - int itemId = intStack[intStackSize - 1]; - String search = stringStack[stringStackSize - 1]; - - if (valueSearch(itemId, search)) - { - // return true - intStack[intStackSize - 2] = 1; - } - - break; - case "bankpinButtonSetup": - { - if (!config.bankPinKeyboard()) - { - return; - } - - final int compId = intStack[intStackSize - 2]; - final int buttonId = intStack[intStackSize - 1]; - //TODO Implement client.getWidget(compId) - Widget button = null; - Widget buttonRect = button.getChild(0); - - final Object[] onOpListener = buttonRect.getOnOpListener(); - buttonRect.setOnKeyListener((JavaScriptCallback) e -> - { - int typedChar = e.getTypedKeyChar() - '0'; - if (typedChar != buttonId) - { - return; - } - - log.debug("Bank pin keypress"); - - final String input = client.getVar(VarClientStr.CHATBOX_TYPED_TEXT); - clientThread.invokeLater(() -> - { - // reset chatbox input to avoid pin going to chatbox.. - client.setVar(VarClientStr.CHATBOX_TYPED_TEXT, input); - client.runScript(ScriptID.CHAT_PROMPT_INIT); - - client.runScript(onOpListener); - }); - }); - break; - } - } - } - - @Subscribe - public void onWidgetLoaded(WidgetLoaded event) - { - if (event.getGroupId() != WidgetID.SEED_VAULT_GROUP_ID || !config.seedVaultValue()) - { - return; - } - - updateSeedVaultTotal(); - } - - @Subscribe - public void onScriptPostFired(ScriptPostFired event) - { - if (event.getScriptId() == ScriptID.BANKMAIN_BUILD) - { - // Compute bank prices using only the shown items so that we can show bank value during searches - final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - final ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); - final Widget[] children = bankItemContainer.getChildren(); - long geTotal = 0, haTotal = 0; - - if (children != null) - { - log.debug("Computing bank price of {} items", bankContainer.size()); - - // The first components are the bank items, followed by tabs etc. There are always 816 components regardless - // of bank size, but we only need to check up to the bank size. - for (int i = 0; i < bankContainer.size(); ++i) - { - Widget child = children[i]; - if (child != null && !child.isSelfHidden() && child.getItemId() > -1) - { - final int alchPrice = getHaPrice(child.getItemId()); - geTotal += (long) itemManager.getItemPrice(child.getItemId()) * child.getItemQuantity(); - haTotal += (long) alchPrice * child.getItemQuantity(); - } - } - - Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); - bankTitle.setText(bankTitle.getText() + createValueText(geTotal, haTotal)); - } - } - else if (event.getScriptId() == ScriptID.BANKMAIN_SEARCH_REFRESH) - { - // vanilla only lays out the bank every 40 client ticks, so if the search input has changed, - // and the bank wasn't laid out this tick, lay it out early - final String inputText = client.getVar(VarClientStr.INPUT_TEXT); - if (searchString != inputText && client.getGameCycle() % 40 != 0) - { - clientThread.invokeLater(bankSearch::layoutBank); - searchString = inputText; - } - } - } - - @Subscribe - public void onItemContainerChanged(ItemContainerChanged event) - { - int containerId = event.getContainerId(); - - if (containerId == InventoryID.BANK.getId()) - { - itemQuantities = null; - } - else if (containerId == InventoryID.SEED_VAULT.getId() && config.seedVaultValue()) - { - updateSeedVaultTotal(); - } - } - - private String createValueText(long gePrice, long haPrice) - { - StringBuilder stringBuilder = new StringBuilder(); - if (config.showGE() && gePrice != 0) - { - stringBuilder.append(" ("); - - if (config.showHA()) - { - stringBuilder.append("GE: "); - } - - if (config.showExact()) - { - stringBuilder.append(QuantityFormatter.formatNumber(gePrice)); - } - else - { - stringBuilder.append(QuantityFormatter.quantityToStackSize(gePrice)); - } - stringBuilder.append(')'); - } - - if (config.showHA() && haPrice != 0) - { - stringBuilder.append(" ("); - - if (config.showGE()) - { - stringBuilder.append("HA: "); - } - - if (config.showExact()) - { - stringBuilder.append(QuantityFormatter.formatNumber(haPrice)); - } - else - { - stringBuilder.append(QuantityFormatter.quantityToStackSize(haPrice)); - } - stringBuilder.append(')'); - } - - return stringBuilder.toString(); - } - - private void updateSeedVaultTotal() - { - final Widget titleContainer = client.getWidget(WidgetInfo.SEED_VAULT_TITLE_CONTAINER); - if (titleContainer == null) - { - return; - } - - final Widget title = titleContainer.getChild(1); - if (title == null) - { - return; - } - - final ContainerPrices prices = calculate(getSeedVaultItems()); - if (prices == null) - { - return; - } - - final String titleText = createValueText(prices.getGePrice(), prices.getHighAlchPrice()); - title.setText(SEED_VAULT_TITLE + titleText); - } - - private Item[] getSeedVaultItems() - { - final ItemContainer itemContainer = client.getItemContainer(InventoryID.SEED_VAULT); - if (itemContainer == null) - { - return null; - } - - return itemContainer.getItems(); - } - - - @VisibleForTesting - boolean valueSearch(final int itemId, final String str) - { - final Matcher matcher = VALUE_SEARCH_PATTERN.matcher(str); - if (!matcher.matches()) - { - return false; - } - - // Count bank items and remember it for determining item quantity - if (itemQuantities == null) - { - itemQuantities = getBankItemSet(); - } - - final ItemComposition itemComposition = itemManager.getItemComposition(itemId); - final int qty = itemQuantities.count(itemId); - final long gePrice = (long) itemManager.getItemPrice(itemId) * qty; - final long haPrice = (long) itemComposition.getHaPrice() * qty; - - long value = Math.max(gePrice, haPrice); - - final String mode = matcher.group("mode"); - if (mode != null) - { - value = mode.toLowerCase().equals("ge") ? gePrice : haPrice; - } - - final String op = matcher.group("op"); - if (op != null) - { - long compare; - try - { - compare = QuantityFormatter.parseQuantity(matcher.group("num")); - } - catch (ParseException e) - { - return false; - } - - switch (op) - { - case ">": - return value > compare; - case "<": - return value < compare; - case "=": - return value == compare; - case ">=": - return value >= compare; - case "<=": - return value <= compare; - } - } - - final String num1 = matcher.group("num1"); - final String num2 = matcher.group("num2"); - if (num1 != null && num2 != null) - { - long compare1, compare2; - try - { - compare1 = QuantityFormatter.parseQuantity(num1); - compare2 = QuantityFormatter.parseQuantity(num2); - } - catch (ParseException e) - { - return false; - } - - return compare1 <= value && compare2 >= value; - } - - return false; - } - - private Multiset getBankItemSet() - { - ItemContainer itemContainer = client.getItemContainer(InventoryID.BANK); - if (itemContainer == null) - { - return HashMultiset.create(); - } - - Multiset set = HashMultiset.create(); - for (Item item : itemContainer.getItems()) - { - if (item.getId() != ItemID.BANK_FILLER) - { - set.add(item.getId(), item.getQuantity()); - } - } - return set; - } - - @Nullable - ContainerPrices calculate(@Nullable Item[] items) - { - if (items == null) - { - return null; - } - - long ge = 0; - long alch = 0; - - for (final Item item : items) - { - final int qty = item.getQuantity(); - final int id = item.getId(); - - if (id <= 0 || qty == 0) - { - continue; - } - - alch += (long) getHaPrice(id) * qty; - ge += (long) itemManager.getItemPrice(id) * qty; - } - - return new ContainerPrices(ge, alch); - } - - private int getHaPrice(int itemId) - { - switch (itemId) - { - case ItemID.COINS_995: - return 1; - case ItemID.PLATINUM_TOKEN: - return 1000; - default: - return itemManager.getItemComposition(itemId).getHaPrice(); - } - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java deleted file mode 100644 index 73e00b436e..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2018, Ron Young - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package net.runelite.client.plugins.bank; - -import javax.inject.Inject; -import javax.inject.Singleton; -import net.runelite.api.Client; -import net.runelite.api.ScriptID; -import net.runelite.api.VarClientInt; -import net.runelite.api.VarClientStr; -import net.runelite.api.vars.InputType; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.callback.ClientThread; -import org.apache.commons.lang3.ArrayUtils; - -@Singleton -public class BankSearch -{ - private final Client client; - private final ClientThread clientThread; - - @Inject - private BankSearch( - final Client client, - final ClientThread clientThread - ) - { - this.client = client; - this.clientThread = clientThread; - } - - public void layoutBank() - { - Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - if (bankContainer == null || bankContainer.isHidden()) - { - return; - } - - Object[] scriptArgs = bankContainer.getOnInvTransmitListener(); - if (scriptArgs == null) - { - return; - } - - client.runScript(scriptArgs); - } - - public void initSearch() - { - clientThread.invoke(() -> - { - Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - if (bankContainer == null || bankContainer.isHidden()) - { - return; - } - - Object[] bankBuildArgs = bankContainer.getOnInvTransmitListener(); - if (bankBuildArgs == null) - { - return; - } - - // the search toggle script requires 1 as its first argument - Object[] searchToggleArgs = ArrayUtils.insert(1, bankBuildArgs, 1); - searchToggleArgs[0] = ScriptID.BANKMAIN_SEARCH_TOGGLE; - - // reset search to clear tab tags and also allow us to initiate a new search while searching - reset(true); - client.runScript(searchToggleArgs); - }); - } - - public void reset(boolean closeChat) - { - clientThread.invoke(() -> - { - // This ensures that any chatbox input (e.g from search) will not remain visible when - // selecting/changing tab - if (closeChat) - { - // this clears the input text and type, and resets the chatbox to allow input - client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 1, 1); - } - else - { - client.setVar(VarClientInt.INPUT_TYPE, InputType.NONE.getType()); - client.setVar(VarClientStr.INPUT_TEXT, ""); - } - - layoutBank(); - }); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java deleted file mode 100644 index f03f730e6a..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java +++ /dev/null @@ -1,598 +0,0 @@ -/* - * Copyright (c) 2018, Adam - * Copyright (c) 2018, Ron Young - * Copyright (c) 2018, Tomas Slusny - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.banktags; - -import com.google.common.collect.Lists; -import com.google.common.primitives.Shorts; -import com.google.inject.Provides; -import java.awt.event.MouseWheelEvent; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Consumer; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.inject.Inject; -import net.runelite.api.Client; -import net.runelite.api.InventoryID; -import net.runelite.api.Item; -import net.runelite.api.ItemComposition; -import net.runelite.api.ItemContainer; -import net.runelite.api.KeyCode; -import net.runelite.api.MenuAction; -import net.runelite.api.MenuEntry; -import net.runelite.api.ScriptID; -import net.runelite.api.SpriteID; -import net.runelite.api.VarClientStr; -import net.runelite.api.events.DraggingWidgetChanged; -import net.runelite.api.events.GameTick; -import net.runelite.api.events.GrandExchangeSearched; -import net.runelite.api.events.MenuEntryAdded; -import net.runelite.api.events.MenuOptionClicked; -import net.runelite.api.events.ScriptCallbackEvent; -import net.runelite.api.events.ScriptPostFired; -import net.runelite.api.events.ScriptPreFired; -import net.runelite.api.events.WidgetLoaded; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetID; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.events.ConfigChanged; -import net.runelite.client.game.ItemManager; -import net.runelite.client.game.ItemVariationMapping; -import net.runelite.client.game.SpriteManager; -import net.runelite.client.game.chatbox.ChatboxPanelManager; -import net.runelite.client.input.MouseManager; -import net.runelite.client.input.MouseWheelListener; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDependency; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.banktags.tabs.TabInterface; -import static net.runelite.client.plugins.banktags.tabs.TabInterface.FILTERED_CHARS; -import net.runelite.client.plugins.banktags.tabs.TabSprites; -import net.runelite.client.plugins.banktags.tabs.TagTab; -import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; -import net.runelite.client.util.Text; - -@PluginDescriptor( - name = "Bank Tags", - description = "Enable tagging of bank items and searching of bank tags", - tags = {"searching", "tagging"} -) -@PluginDependency(ClueScrollPlugin.class) -public class BankTagsPlugin extends Plugin implements MouseWheelListener -{ - public static final String CONFIG_GROUP = "banktags"; - public static final String TAG_SEARCH = "tag:"; - private static final String EDIT_TAGS_MENU_OPTION = "Edit-tags"; - public static final String ICON_SEARCH = "icon_"; - public static final String TAG_TABS_CONFIG = "tagtabs"; - public static final String VAR_TAG_SUFFIX = "*"; - private static final int ITEMS_PER_ROW = 8; - private static final int ITEM_VERTICAL_SPACING = 36; - private static final int ITEM_HORIZONTAL_SPACING = 48; - private static final int ITEM_ROW_START = 51; - - private static final int MAX_RESULT_COUNT = 250; - - private static final String SEARCH_BANK_INPUT_TEXT = - "Show items whose names or tags contain the following text:
" + - "(To show only tagged items, start your search with 'tag:')"; - private static final String SEARCH_BANK_INPUT_TEXT_FOUND = - "Show items whose names or tags contain the following text: (%d found)
" + - "(To show only tagged items, start your search with 'tag:')"; - - @Inject - private ItemManager itemManager; - - @Inject - private Client client; - - @Inject - private ClientThread clientThread; - - @Inject - private ChatboxPanelManager chatboxPanelManager; - - @Inject - private MouseManager mouseManager; - - @Inject - private BankTagsConfig config; - - @Inject - private TagManager tagManager; - - @Inject - private TabInterface tabInterface; - - @Inject - private SpriteManager spriteManager; - - @Inject - private ConfigManager configManager; - - @Provides - BankTagsConfig getConfig(ConfigManager configManager) - { - return configManager.getConfig(BankTagsConfig.class); - } - - @Override - public void resetConfiguration() - { - List extraKeys = Lists.newArrayList( - CONFIG_GROUP + "." + TagManager.ITEM_KEY_PREFIX, - CONFIG_GROUP + "." + ICON_SEARCH, - CONFIG_GROUP + "." + TAG_TABS_CONFIG - ); - - for (String prefix : extraKeys) - { - List keys = configManager.getConfigurationKeys(prefix); - for (String key : keys) - { - String[] str = key.split("\\.", 2); - if (str.length == 2) - { - configManager.unsetConfiguration(str[0], str[1]); - } - } - } - - clientThread.invokeLater(() -> - { - tabInterface.destroy(); - tabInterface.init(); - }); - } - - - @Override - public void startUp() - { - cleanConfig(); - mouseManager.registerMouseWheelListener(this); - clientThread.invokeLater(tabInterface::init); - spriteManager.addSpriteOverrides(TabSprites.values()); - } - - @Deprecated - private void cleanConfig() - { - removeInvalidTags("tagtabs"); - - List tags = configManager.getConfigurationKeys(CONFIG_GROUP + ".item_"); - tags.forEach(s -> - { - String[] split = s.split("\\.", 2); - removeInvalidTags(split[1]); - }); - - List icons = configManager.getConfigurationKeys(CONFIG_GROUP + ".icon_"); - icons.forEach(s -> - { - String[] split = s.split("\\.", 2); - String replaced = split[1].replaceAll("[<>/]", ""); - if (!split[1].equals(replaced)) - { - String value = configManager.getConfiguration(CONFIG_GROUP, split[1]); - configManager.unsetConfiguration(CONFIG_GROUP, split[1]); - if (replaced.length() > "icon_".length()) - { - configManager.setConfiguration(CONFIG_GROUP, replaced, value); - } - } - }); - } - - @Deprecated - private void removeInvalidTags(final String key) - { - final String value = configManager.getConfiguration(CONFIG_GROUP, key); - if (value == null) - { - return; - } - - String replaced = value.replaceAll("[<>:/]", ""); - if (!value.equals(replaced)) - { - replaced = Text.toCSV(Text.fromCSV(replaced)); - if (replaced.isEmpty()) - { - configManager.unsetConfiguration(CONFIG_GROUP, key); - } - else - { - configManager.setConfiguration(CONFIG_GROUP, key, replaced); - } - } - } - - @Override - public void shutDown() - { - mouseManager.unregisterMouseWheelListener(this); - clientThread.invokeLater(tabInterface::destroy); - spriteManager.removeSpriteOverrides(TabSprites.values()); - } - - @Subscribe - public void onGrandExchangeSearched(GrandExchangeSearched event) - { - final String input = client.getVar(VarClientStr.INPUT_TEXT); - if (!input.startsWith(TAG_SEARCH)) - { - return; - } - - event.consume(); - - final String tag = input.substring(TAG_SEARCH.length()).trim(); - final Set ids = tagManager.getItemsForTag(tag) - .stream() - .mapToInt(Math::abs) - .mapToObj(ItemVariationMapping::getVariations) - .flatMap(Collection::stream) - .distinct() - .filter(i -> itemManager.getItemComposition(i).isTradeable()) - .limit(MAX_RESULT_COUNT) - .collect(Collectors.toCollection(TreeSet::new)); - - client.setGeSearchResultIndex(0); - client.setGeSearchResultCount(ids.size()); - client.setGeSearchResultIds(Shorts.toArray(ids)); - } - - @Subscribe - public void onScriptCallbackEvent(ScriptCallbackEvent event) - { - String eventName = event.getEventName(); - - int[] intStack = client.getIntStack(); - String[] stringStack = client.getStringStack(); - int intStackSize = client.getIntStackSize(); - int stringStackSize = client.getStringStackSize(); - - tabInterface.handleScriptEvent(event); - - switch (eventName) - { - case "setSearchBankInputText": - stringStack[stringStackSize - 1] = SEARCH_BANK_INPUT_TEXT; - break; - case "setSearchBankInputTextFound": - { - int matches = intStack[intStackSize - 1]; - stringStack[stringStackSize - 1] = String.format(SEARCH_BANK_INPUT_TEXT_FOUND, matches); - break; - } - case "bankSearchFilter": - final int itemId = intStack[intStackSize - 1]; - final String searchfilter = stringStack[stringStackSize - 1]; - - // This event only fires when the bank is in search mode. It will fire even if there is no search - // input. We prevent having a tag tab open while also performing a normal search, so if a tag tab - // is active here it must mean we have placed the bank into search mode. See onScriptPostFired(). - TagTab activeTab = tabInterface.getActiveTab(); - String search = activeTab != null ? TAG_SEARCH + activeTab.getTag() : searchfilter; - - if (search.isEmpty()) - { - return; - } - - boolean tagSearch = search.startsWith(TAG_SEARCH); - if (tagSearch) - { - search = search.substring(TAG_SEARCH.length()).trim(); - } - - if (tagManager.findTag(itemId, search)) - { - // return true - intStack[intStackSize - 2] = 1; - } - else if (tagSearch) - { - // if the item isn't tagged we return false to prevent the item matching if the item name happens - // to contain the tag name. - intStack[intStackSize - 2] = 0; - } - break; - case "getSearchingTagTab": - intStack[intStackSize - 1] = tabInterface.isActive() ? 1 : 0; - break; - } - } - - @Subscribe - public void onMenuEntryAdded(MenuEntryAdded event) - { - MenuEntry[] entries = client.getMenuEntries(); - - if (event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() - && event.getOption().equals("Examine")) - { - Widget container = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - Widget item = container.getChild(event.getActionParam()); - int itemID = item.getItemId(); - String text = EDIT_TAGS_MENU_OPTION; - int tagCount = tagManager.getTags(itemID, false).size() + tagManager.getTags(itemID, true).size(); - - if (tagCount > 0) - { - text += " (" + tagCount + ")"; - } - - MenuEntry editTags = new MenuEntry(); - editTags.setActionParam(event.getActionParam()); - editTags.setParam1(event.getActionParam1()); - editTags.setTarget(event.getTarget()); - editTags.setOption(text); - editTags.setType(MenuAction.RUNELITE.getId()); - editTags.setIdentifier(event.getIdentifier()); - entries = Arrays.copyOf(entries, entries.length + 1); - entries[entries.length - 1] = editTags; - client.setMenuEntries(entries); - } - - tabInterface.handleAdd(event); - } - - @Subscribe - public void onMenuOptionClicked(MenuOptionClicked event) - { - if (event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() - && event.getMenuAction() == MenuAction.RUNELITE - && event.getMenuOption().startsWith(EDIT_TAGS_MENU_OPTION)) - { - event.consume(); - int inventoryIndex = event.getActionParam(); - ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); - if (bankContainer == null) - { - return; - } - Item[] items = bankContainer.getItems(); - if (inventoryIndex < 0 || inventoryIndex >= items.length) - { - return; - } - Item item = bankContainer.getItems()[inventoryIndex]; - if (item == null) - { - return; - } - - int itemId = item.getId(); - ItemComposition itemComposition = itemManager.getItemComposition(itemId); - String name = itemComposition.getName(); - - // Get both tags and vartags and append * to end of vartags name - Collection tags = tagManager.getTags(itemId, false); - tagManager.getTags(itemId, true).stream() - .map(i -> i + "*") - .forEach(tags::add); - - String initialValue = Text.toCSV(tags); - - chatboxPanelManager.openTextInput(name + " tags:
(append " + VAR_TAG_SUFFIX + " for variation tag)") - .addCharValidator(FILTERED_CHARS) - .value(initialValue) - .onDone((Consumer) (newValue) -> - clientThread.invoke(() -> - { - // Split inputted tags to vartags (ending with *) and regular tags - final Collection newTags = new ArrayList<>(Text.fromCSV(newValue.toLowerCase())); - final Collection newVarTags = new ArrayList<>(newTags).stream().filter(s -> s.endsWith(VAR_TAG_SUFFIX)).map(s -> - { - newTags.remove(s); - return s.substring(0, s.length() - VAR_TAG_SUFFIX.length()); - }).collect(Collectors.toList()); - - // And save them - tagManager.setTagString(itemId, Text.toCSV(newTags), false); - tagManager.setTagString(itemId, Text.toCSV(newVarTags), true); - - // Check both previous and current tags in case the tag got removed in new tags or in case - // the tag got added in new tags - tabInterface.updateTabIfActive(Text.fromCSV(initialValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), ""))); - tabInterface.updateTabIfActive(Text.fromCSV(newValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), ""))); - })) - .build(); - } - else - { - tabInterface.handleClick(event); - } - } - - @Subscribe - public void onConfigChanged(ConfigChanged configChanged) - { - if (configChanged.getGroup().equals(CONFIG_GROUP) && configChanged.getKey().equals("useTabs")) - { - if (config.tabs()) - { - clientThread.invokeLater(tabInterface::init); - } - else - { - clientThread.invokeLater(tabInterface::destroy); - } - } - } - - @Subscribe - public void onScriptPreFired(ScriptPreFired event) - { - int scriptId = event.getScriptId(); - if (scriptId == ScriptID.BANKMAIN_FINISHBUILDING) - { - // Since we apply tag tab search filters even when the bank is not in search mode, - // bankkmain_build will reset the bank title to "The Bank of Gielinor". So apply our - // own title. - TagTab activeTab = tabInterface.getActiveTab(); - if (tabInterface.isTagTabActive()) - { - // Tag tab tab has its own title since it isn't a real tag - Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); - bankTitle.setText("Tag tab tab"); - } - else if (activeTab != null) - { - Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); - bankTitle.setText("Tag tab " + activeTab.getTag() + ""); - } - } - else if (scriptId == ScriptID.BANKMAIN_SEARCH_TOGGLE) - { - tabInterface.handleSearch(); - } - } - - @Subscribe - public void onScriptPostFired(ScriptPostFired event) - { - if (event.getScriptId() == ScriptID.BANKMAIN_SEARCHING) - { - // The return value of bankmain_searching is on the stack. If we have a tag tab active - // make it return true to put the bank in a searching state. - if (tabInterface.getActiveTab() != null || tabInterface.isTagTabActive()) - { - client.getIntStack()[client.getIntStackSize() - 1] = 1; // true - } - return; - } - - if (event.getScriptId() != ScriptID.BANKMAIN_BUILD || !config.removeSeparators()) - { - return; - } - - if (!tabInterface.isActive()) - { - return; - } - - Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - if (itemContainer == null) - { - return; - } - - int items = 0; - - Widget[] containerChildren = itemContainer.getDynamicChildren(); - - // sort the child array as the items are not in the displayed order - Arrays.sort(containerChildren, Comparator.comparing(Widget::getOriginalY) - .thenComparing(Widget::getOriginalX)); - - for (Widget child : containerChildren) - { - if (child.getItemId() != -1 && !child.isHidden()) - { - // calculate correct item position as if this was a normal tab - int adjYOffset = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING; - int adjXOffset = (items % ITEMS_PER_ROW) * ITEM_HORIZONTAL_SPACING + ITEM_ROW_START; - - if (child.getOriginalY() != adjYOffset) - { - child.setOriginalY(adjYOffset); - child.revalidate(); - } - - if (child.getOriginalX() != adjXOffset) - { - child.setOriginalX(adjXOffset); - child.revalidate(); - } - - items++; - } - - // separator line or tab text - if (child.getSpriteId() == SpriteID.RESIZEABLE_MODE_SIDE_PANEL_BACKGROUND - || child.getText().contains("Tab")) - { - child.setHidden(true); - } - } - - final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - int itemContainerHeight = bankItemContainer.getHeight(); - // add a second row of height here to allow users to scroll down when the last row is partially visible - int adjustedScrollHeight = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING + ITEM_VERTICAL_SPACING; - itemContainer.setScrollHeight(Math.max(adjustedScrollHeight, itemContainerHeight)); - - final int itemContainerScroll = bankItemContainer.getScrollY(); - clientThread.invokeLater(() -> - client.runScript(ScriptID.UPDATE_SCROLLBAR, - WidgetInfo.BANK_SCROLLBAR.getId(), - WidgetInfo.BANK_ITEM_CONTAINER.getId(), - itemContainerScroll)); - - } - - @Subscribe - public void onGameTick(GameTick event) - { - tabInterface.update(); - } - - @Subscribe - public void onDraggingWidgetChanged(DraggingWidgetChanged event) - { - final boolean shiftPressed = client.isKeyPressed(KeyCode.KC_SHIFT); - tabInterface.handleDrag(event.isDraggingWidget(), shiftPressed); - } - - @Subscribe - public void onWidgetLoaded(WidgetLoaded event) - { - if (event.getGroupId() == WidgetID.BANK_GROUP_ID) - { - tabInterface.init(); - } - } - - @Override - public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event) - { - tabInterface.handleWheel(event); - return event; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java deleted file mode 100644 index fe697034d0..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) 2018, Tomas Slusny - * Copyright (c) 2018, Ron Young - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.banktags; - -import com.google.common.base.Strings; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.inject.Singleton; -import net.runelite.api.ItemID; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.game.ItemManager; -import net.runelite.client.game.ItemVariationMapping; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; -import net.runelite.client.plugins.cluescrolls.ClueScrollService; -import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; -import net.runelite.client.plugins.cluescrolls.clues.CoordinateClue; -import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; -import net.runelite.client.plugins.cluescrolls.clues.FairyRingClue; -import net.runelite.client.plugins.cluescrolls.clues.HotColdClue; -import net.runelite.client.plugins.cluescrolls.clues.MapClue; -import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; -import net.runelite.client.util.Text; - -@Singleton -public class TagManager -{ - static final String ITEM_KEY_PREFIX = "item_"; - private final ConfigManager configManager; - private final ItemManager itemManager; - private final ClueScrollService clueScrollService; - - @Inject - private TagManager( - final ItemManager itemManager, - final ConfigManager configManager, - final ClueScrollService clueScrollService) - { - this.itemManager = itemManager; - this.configManager = configManager; - this.clueScrollService = clueScrollService; - } - - String getTagString(int itemId, boolean variation) - { - itemId = getItemId(itemId, variation); - - String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); - if (config == null) - { - return ""; - } - - return config; - } - - Collection getTags(int itemId, boolean variation) - { - return new LinkedHashSet<>(Text.fromCSV(getTagString(itemId, variation).toLowerCase())); - } - - void setTagString(int itemId, String tags, boolean variation) - { - itemId = getItemId(itemId, variation); - - if (Strings.isNullOrEmpty(tags)) - { - configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); - } - else - { - configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, tags); - } - } - - public void addTags(int itemId, final Collection t, boolean variation) - { - final Collection tags = getTags(itemId, variation); - if (tags.addAll(t)) - { - setTags(itemId, tags, variation); - } - } - - public void addTag(int itemId, String tag, boolean variation) - { - final Collection tags = getTags(itemId, variation); - if (tags.add(Text.standardize(tag))) - { - setTags(itemId, tags, variation); - } - } - - private void setTags(int itemId, Collection tags, boolean variation) - { - setTagString(itemId, Text.toCSV(tags), variation); - } - - boolean findTag(int itemId, String search) - { - if (search.equals("clue") && testClue(itemId)) - { - return true; - } - - Collection tags = getTags(itemId, false); - tags.addAll(getTags(itemId, true)); - return tags.stream().anyMatch(tag -> tag.startsWith(Text.standardize(search))); - } - - public List getItemsForTag(String tag) - { - final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; - return configManager.getConfigurationKeys(prefix).stream() - .map(item -> Integer.parseInt(item.replace(prefix, ""))) - .filter(item -> getTags(item, false).contains(tag) || getTags(item, true).contains(tag)) - .collect(Collectors.toList()); - } - - public void removeTag(String tag) - { - final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; - configManager.getConfigurationKeys(prefix).forEach(item -> - { - int id = Integer.parseInt(item.replace(prefix, "")); - removeTag(id, tag); - }); - } - - public void removeTag(int itemId, String tag) - { - Collection tags = getTags(itemId, false); - if (tags.remove(Text.standardize(tag))) - { - setTags(itemId, tags, false); - } - - tags = getTags(itemId, true); - if (tags.remove(Text.standardize(tag))) - { - setTags(itemId, tags, true); - } - } - - public void renameTag(String oldTag, String newTag) - { - List items = getItemsForTag(Text.standardize(oldTag)); - items.forEach(id -> - { - Collection tags = getTags(id, id < 0); - - tags.remove(Text.standardize(oldTag)); - tags.add(Text.standardize(newTag)); - - setTags(id, tags, id < 0); - }); - } - - private int getItemId(int itemId, boolean variation) - { - itemId = Math.abs(itemId); - itemId = itemManager.canonicalize(itemId); - - if (variation) - { - itemId = ItemVariationMapping.map(itemId) * -1; - } - - return itemId; - } - - private boolean testClue(int itemId) - { - ClueScroll c = clueScrollService.getClue(); - - if (c == null) - { - return false; - } - - if (c instanceof EmoteClue) - { - EmoteClue emote = (EmoteClue) c; - - for (ItemRequirement ir : emote.getItemRequirements()) - { - if (ir.fulfilledBy(itemId)) - { - return true; - } - } - } - else if (c instanceof CoordinateClue || c instanceof HotColdClue || c instanceof FairyRingClue) - { - return itemId == ItemID.SPADE; - } - else if (c instanceof MapClue) - { - MapClue mapClue = (MapClue) c; - - return mapClue.getObjectId() == -1 && itemId == ItemID.SPADE; - } - - return false; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java deleted file mode 100644 index d24ef55605..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java +++ /dev/null @@ -1,1198 +0,0 @@ -/* - * Copyright (c) 2018, Tomas Slusny - * Copyright (c) 2018, Ron Young - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.banktags.tabs; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.Runnables; -import java.awt.Color; -import java.awt.Rectangle; -import java.awt.Toolkit; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.awt.event.MouseWheelEvent; -import java.io.IOException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.IntPredicate; -import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.inject.Singleton; -import lombok.Getter; -import net.runelite.api.Client; -import net.runelite.api.Constants; -import net.runelite.api.InventoryID; -import net.runelite.api.Item; -import net.runelite.api.ItemComposition; -import net.runelite.api.ItemContainer; -import net.runelite.api.MenuAction; -import net.runelite.api.MenuEntry; -import net.runelite.api.Point; -import net.runelite.api.ScriptEvent; -import net.runelite.api.ScriptID; -import net.runelite.api.SoundEffectID; -import net.runelite.api.SpriteID; -import net.runelite.api.VarClientInt; -import net.runelite.api.VarClientStr; -import net.runelite.api.Varbits; -import net.runelite.api.events.MenuEntryAdded; -import net.runelite.api.events.MenuOptionClicked; -import net.runelite.api.events.ScriptCallbackEvent; -import net.runelite.api.widgets.ItemQuantityMode; -import net.runelite.api.widgets.JavaScriptCallback; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetConfig; -import net.runelite.api.widgets.WidgetID; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.api.widgets.WidgetSizeMode; -import net.runelite.api.widgets.WidgetType; -import net.runelite.client.Notifier; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.game.ItemManager; -import net.runelite.client.game.chatbox.ChatboxItemSearch; -import net.runelite.client.game.chatbox.ChatboxPanelManager; -import net.runelite.client.plugins.bank.BankSearch; -import net.runelite.client.plugins.banktags.BankTagsConfig; -import net.runelite.client.plugins.banktags.BankTagsPlugin; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_SEARCH; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX; -import net.runelite.client.plugins.banktags.TagManager; -import static net.runelite.client.plugins.banktags.tabs.MenuIndexes.NewTab; -import static net.runelite.client.plugins.banktags.tabs.MenuIndexes.Tab; -import net.runelite.client.ui.JagexColors; -import net.runelite.client.util.ColorUtil; -import net.runelite.client.util.Text; - -@Singleton -public class TabInterface -{ - public static final IntPredicate FILTERED_CHARS = c -> ":".indexOf(c) == -1; - - private static final Color HILIGHT_COLOR = JagexColors.MENU_TARGET; - private static final String SCROLL_UP = "Scroll up"; - private static final String SCROLL_DOWN = "Scroll down"; - private static final String NEW_TAB = "New tag tab"; - private static final String REMOVE_TAB = "Delete tag tab"; - private static final String EXPORT_TAB = "Export tag tab"; - private static final String IMPORT_TAB = "Import tag tab"; - private static final String VIEW_TAB = "View tag tab"; - private static final String RENAME_TAB = "Rename tag tab"; - private static final String CHANGE_ICON = "Change icon"; - private static final String REMOVE_TAG = "Remove-tag"; - private static final String TAG_GEAR = "Tag-equipment"; - private static final String TAG_INVENTORY = "Tag-inventory"; - private static final String TAB_MENU_KEY = "tagtabs"; - private static final String OPEN_TAB_MENU = "View tag tabs"; - private static final String SHOW_WORN = "Show worn items"; - private static final String SHOW_SETTINGS = "Show menu"; - private static final String SHOW_TUTORIAL = "Show tutorial"; - private static final int TAB_HEIGHT = 40; - private static final int TAB_WIDTH = 39; - private static final int BUTTON_HEIGHT = 20; - private static final int MARGIN = 1; - private static final int SCROLL_TICK = 500; - private static final int INCINERATOR_WIDTH = 48; - private static final int INCINERATOR_HEIGHT = 39; - private static final int BANK_ITEM_WIDTH = 36; - private static final int BANK_ITEM_HEIGHT = 32; - private static final int BANK_ITEM_X_PADDING = 12; - private static final int BANK_ITEM_Y_PADDING = 4; - private static final int BANK_ITEMS_PER_ROW = 8; - private static final int BANK_ITEM_START_X = 51; - private static final int BANK_ITEM_START_Y = 0; - - private final Client client; - private final ClientThread clientThread; - private final ItemManager itemManager; - private final TagManager tagManager; - private final TabManager tabManager; - private final ChatboxPanelManager chatboxPanelManager; - private final BankTagsConfig config; - private final Notifier notifier; - private final BankSearch bankSearch; - private final ChatboxItemSearch searchProvider; - private final Rectangle bounds = new Rectangle(); - private final Rectangle canvasBounds = new Rectangle(); - - @Getter - private TagTab activeTab; - @Getter - private boolean tagTabActive; - private int maxTabs; - private int currentTabIndex; - private Instant startScroll = Instant.now(); - - @Getter - private Widget upButton; - - @Getter - private Widget downButton; - - @Getter - private Widget newTab; - - @Getter - private Widget parent; - - @Inject - private TabInterface( - final Client client, - final ClientThread clientThread, - final ItemManager itemManager, - final TagManager tagManager, - final TabManager tabManager, - final ChatboxPanelManager chatboxPanelManager, - final BankTagsConfig config, - final Notifier notifier, - final BankSearch bankSearch, - final ChatboxItemSearch searchProvider) - { - this.client = client; - this.clientThread = clientThread; - this.itemManager = itemManager; - this.tagManager = tagManager; - this.tabManager = tabManager; - this.chatboxPanelManager = chatboxPanelManager; - this.config = config; - this.notifier = notifier; - this.bankSearch = bankSearch; - this.searchProvider = searchProvider; - } - - public boolean isActive() - { - return activeTab != null; - } - - public void init() - { - if (isHidden()) - { - return; - } - - currentTabIndex = config.position(); - parent = client.getWidget(WidgetInfo.BANK_CONTENT_CONTAINER); - - updateBounds(); - - upButton = createGraphic("", TabSprites.UP_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); - upButton.setAction(1, SCROLL_UP); - int clickmask = upButton.getClickMask(); - clickmask |= WidgetConfig.DRAG; - upButton.setClickMask(clickmask); - upButton.setOnOpListener((JavaScriptCallback) (event) -> scrollTab(-1)); - - downButton = createGraphic("", TabSprites.DOWN_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); - downButton.setAction(1, SCROLL_DOWN); - clickmask = downButton.getClickMask(); - clickmask |= WidgetConfig.DRAG; - downButton.setClickMask(clickmask); - downButton.setOnOpListener((JavaScriptCallback) (event) -> scrollTab(1)); - - newTab = createGraphic("", TabSprites.NEW_TAB.getSpriteId(), -1, TAB_WIDTH, 39, bounds.x, 0, true); - newTab.setAction(1, NEW_TAB); - newTab.setAction(2, IMPORT_TAB); - newTab.setAction(3, OPEN_TAB_MENU); - newTab.setOnOpListener((JavaScriptCallback) this::handleNewTab); - - tabManager.clear(); - tabManager.getAllTabs().forEach(this::loadTab); - activateTab(null); - scrollTab(0); - - if (config.rememberTab() && !Strings.isNullOrEmpty(config.tab())) - { - // the server will resync the last opened vanilla tab when the bank is opened - client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); - openTag(config.tab()); - } - - Widget equipmentButton = client.getWidget(WidgetInfo.BANK_EQUIPMENT_BUTTON); - Widget titleBar = client.getWidget(WidgetInfo.BANK_TITLE_BAR); - if (equipmentButton == null || titleBar == null || titleBar.getOriginalX() > 0) - { - // don't keep moving widgets if they have already been moved - return; - } - - equipmentButton.setOriginalX(6); - equipmentButton.setOriginalY(4); - equipmentButton.revalidate(); - - // the bank item count is 3 widgets - for (int child = WidgetInfo.BANK_ITEM_COUNT_TOP.getChildId(); child <= WidgetInfo.BANK_ITEM_COUNT_BOTTOM.getChildId(); child++) - { - Widget widget = client.getWidget(WidgetID.BANK_GROUP_ID, child); - if (widget == null) - { - return; - } - - widget.setOriginalX(widget.getOriginalX() + equipmentButton.getWidth()); - widget.revalidate(); - } - - titleBar.setOriginalX(equipmentButton.getWidth() / 2); - titleBar.setOriginalWidth(titleBar.getWidth() - equipmentButton.getWidth()); - titleBar.revalidate(); - } - - private void handleDeposit(MenuOptionClicked event, Boolean inventory) - { - ItemContainer container = client.getItemContainer(inventory ? InventoryID.INVENTORY : InventoryID.EQUIPMENT); - - if (container == null) - { - return; - } - - List items = Arrays.stream(container.getItems()) - .filter(Objects::nonNull) - .map(Item::getId) - .filter(id -> id != -1) - .collect(Collectors.toList()); - - if (!Strings.isNullOrEmpty(event.getMenuTarget())) - { - if (activeTab != null && Text.removeTags(event.getMenuTarget()).equals(activeTab.getTag())) - { - for (Integer item : items) - { - tagManager.addTag(item, activeTab.getTag(), false); - } - - openTag(activeTab.getTag()); - } - - return; - } - - chatboxPanelManager.openTextInput((inventory ? "Inventory " : "Equipment ") + " tags:") - .addCharValidator(FILTERED_CHARS) - .onDone((Consumer) (newTags) -> - clientThread.invoke(() -> - { - final List tags = Text.fromCSV(newTags.toLowerCase()); - - for (Integer item : items) - { - tagManager.addTags(item, tags, false); - } - - updateTabIfActive(tags); - })) - .build(); - } - - private void handleNewTab(ScriptEvent event) - { - switch (event.getOp()) - { - case NewTab.NEW_TAB: - chatboxPanelManager.openTextInput("Tag name") - .addCharValidator(FILTERED_CHARS) - .onDone((Consumer) (tagName) -> clientThread.invoke(() -> - { - if (!Strings.isNullOrEmpty(tagName)) - { - loadTab(tagName); - tabManager.save(); - scrollTab(0); - } - })) - .build(); - break; - case NewTab.IMPORT_TAB: - try - { - final String dataString = Toolkit - .getDefaultToolkit() - .getSystemClipboard() - .getData(DataFlavor.stringFlavor) - .toString() - .trim(); - - final Iterator dataIter = Text.fromCSV(dataString).iterator(); - String name = dataIter.next(); - StringBuilder sb = new StringBuilder(); - for (char c : name.toCharArray()) - { - if (FILTERED_CHARS.test(c)) - { - sb.append(c); - } - } - - if (sb.length() == 0) - { - notifier.notify("Failed to import tag tab from clipboard, invalid format."); - return; - } - - name = sb.toString(); - - final String icon = dataIter.next(); - tabManager.setIcon(name, icon); - - while (dataIter.hasNext()) - { - final int itemId = Integer.parseInt(dataIter.next()); - tagManager.addTag(itemId, name, itemId < 0); - } - - loadTab(name); - tabManager.save(); - scrollTab(0); - - if (activeTab != null && name.equals(activeTab.getTag())) - { - openTag(activeTab.getTag()); - } - - notifier.notify("Tag tab " + name + " has been imported from your clipboard!"); - } - catch (UnsupportedFlavorException | NoSuchElementException | IOException | NumberFormatException ex) - { - notifier.notify("Failed to import tag tab from clipboard, invalid format."); - } - break; - case NewTab.OPEN_TAB_MENU: - client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); - openTag(TAB_MENU_KEY); - break; - } - } - - private void handleTagTab(ScriptEvent event) - { - switch (event.getOp()) - { - case Tab.OPEN_TAG: - client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); - Widget clicked = event.getSource(); - - TagTab tab = tabManager.find(Text.removeTags(clicked.getName())); - - if (tab.equals(activeTab)) - { - activateTab(null); - bankSearch.reset(true); - } - else - { - openTag(Text.removeTags(clicked.getName())); - // openTag will reset and relayout - } - - client.playSoundEffect(SoundEffectID.UI_BOOP); - break; - case Tab.CHANGE_ICON: - final String tag = Text.removeTags(event.getOpbase()); - searchProvider - .tooltipText(CHANGE_ICON + " (" + tag + ")") - .onItemSelected((itemId) -> - { - TagTab iconToSet = tabManager.find(tag); - if (iconToSet != null) - { - iconToSet.setIconItemId(itemId); - iconToSet.getIcon().setItemId(itemId); - iconToSet.getMenu().setItemId(itemId); - tabManager.setIcon(iconToSet.getTag(), itemId + ""); - } - }) - .build(); - break; - case Tab.DELETE_TAB: - String target = Text.standardize(event.getOpbase()); - chatboxPanelManager.openTextMenuInput("Delete " + target) - .option("1. Tab and tag from all items", () -> - clientThread.invoke(() -> - { - tagManager.removeTag(target); - deleteTab(target); - }) - ) - .option("2. Only tab", () -> clientThread.invoke(() -> deleteTab(target))) - .option("3. Cancel", Runnables::doNothing) - .build(); - break; - case Tab.EXPORT_TAB: - final List data = new ArrayList<>(); - final TagTab tagTab = tabManager.find(Text.removeTags(event.getOpbase())); - data.add(tagTab.getTag()); - data.add(String.valueOf(tagTab.getIconItemId())); - - for (Integer item : tagManager.getItemsForTag(tagTab.getTag())) - { - data.add(String.valueOf(item)); - } - - final StringSelection stringSelection = new StringSelection(Text.toCSV(data)); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); - notifier.notify("Tag tab " + tagTab.getTag() + " has been copied to your clipboard!"); - break; - case Tab.RENAME_TAB: - String renameTarget = Text.standardize(event.getOpbase()); - renameTab(renameTarget); - break; - } - } - - public void destroy() - { - activeTab = null; - currentTabIndex = 0; - maxTabs = 0; - parent = null; - - if (upButton != null) - { - upButton.setHidden(true); - downButton.setHidden(true); - newTab.setHidden(true); - } - - tabManager.clear(); - } - - public void update() - { - if (isHidden()) - { - parent = null; - - saveTab(); - return; - } - - // Don't continue ticking if equipment menu or bank menu is open - if (parent.isSelfHidden()) - { - return; - } - - updateBounds(); - scrollTab(0); - } - - private void saveTab() - { - // If bank window was just hidden, update last active tab position - if (currentTabIndex != config.position()) - { - config.position(currentTabIndex); - } - - // Do the same for last active tab - if (config.rememberTab()) - { - if (activeTab == null && !Strings.isNullOrEmpty(config.tab())) - { - config.tab(""); - } - else if (activeTab != null && !activeTab.getTag().equals(config.tab())) - { - config.tab(activeTab.getTag()); - } - } - else if (!Strings.isNullOrEmpty(config.tab())) - { - config.tab(""); - } - } - - private void setTabMenuVisible(boolean visible) - { - for (TagTab t : tabManager.getTabs()) - { - t.getMenu().setHidden(!visible); - } - } - - private boolean isTabMenuActive() - { - return tagTabActive; - } - - public void handleScriptEvent(final ScriptCallbackEvent event) - { - String eventName = event.getEventName(); - - int[] intStack = client.getIntStack(); - int intStackSize = client.getIntStackSize(); - - switch (eventName) - { - case "setBankScroll": - if (!isTabMenuActive()) - { - setTabMenuVisible(false); - return; - } - - setTabMenuVisible(true); - - // scroll height - intStack[intStackSize - 3] = (((tabManager.getTabs().size() - 1) / BANK_ITEMS_PER_ROW) + 1) * (BANK_ITEM_HEIGHT + BANK_ITEM_Y_PADDING); - - // skip normal bank layout - intStack[intStackSize - 2] = 1; - break; - case "beforeBankLayout": - setTabMenuVisible(false); - break; - } - } - - public void handleWheel(final MouseWheelEvent event) - { - if (parent == null || !canvasBounds.contains(event.getPoint())) - { - return; - } - - event.consume(); - - clientThread.invoke(() -> - { - if (isHidden()) - { - return; - } - - scrollTab(event.getWheelRotation()); - }); - } - - public void handleAdd(MenuEntryAdded event) - { - if (isHidden()) - { - return; - } - - MenuEntry[] entries = client.getMenuEntries(); - - if (activeTab != null - && event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() - && event.getOption().equals("Examine")) - { - entries = createMenuEntry(event, REMOVE_TAG + " (" + activeTab.getTag() + ")", event.getTarget(), entries); - client.setMenuEntries(entries); - } - else if (event.getActionParam1() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId() - && event.getOption().equals("Deposit inventory")) - { - entries = createMenuEntry(event, TAG_INVENTORY, event.getTarget(), entries); - - if (activeTab != null) - { - entries = createMenuEntry(event, TAG_INVENTORY, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR), entries); - } - - client.setMenuEntries(entries); - } - else if (event.getActionParam1() == WidgetInfo.BANK_DEPOSIT_EQUIPMENT.getId() - && event.getOption().equals("Deposit worn items")) - { - entries = createMenuEntry(event, TAG_GEAR, event.getTarget(), entries); - - if (activeTab != null) - { - entries = createMenuEntry(event, TAG_GEAR, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR), entries); - } - - client.setMenuEntries(entries); - } - } - - public void handleClick(MenuOptionClicked event) - { - if (isHidden()) - { - return; - } - - if (chatboxPanelManager.getCurrentInput() != null - && event.getMenuAction() != MenuAction.CANCEL - && !event.getMenuOption().equals(SCROLL_UP) - && !event.getMenuOption().equals(SCROLL_DOWN)) - { - chatboxPanelManager.close(); - } - - if (activeTab != null - && (event.getMenuOption().startsWith("View tab") || event.getMenuOption().equals("View all items"))) - { - activateTab(null); - } - else if (activeTab != null - && event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() - && event.getMenuAction() == MenuAction.RUNELITE - && event.getMenuOption().startsWith(REMOVE_TAG)) - { - // Add "remove" menu entry to all items in bank while tab is selected - event.consume(); - final ItemComposition item = getItem(event.getActionParam()); - final int itemId = item.getId(); - tagManager.removeTag(itemId, activeTab.getTag()); - bankSearch.layoutBank(); // re-layout to filter the removed item out - } - else if (event.getMenuAction() == MenuAction.RUNELITE - && ((event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId() && event.getMenuOption().equals(TAG_INVENTORY)) - || (event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_EQUIPMENT.getId() && event.getMenuOption().equals(TAG_GEAR)))) - { - handleDeposit(event, event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId()); - } - else if (activeTab != null && ((event.getWidgetId() == WidgetInfo.BANK_EQUIPMENT_BUTTON.getId() && event.getMenuOption().equals(SHOW_WORN)) - || (event.getWidgetId() == WidgetInfo.BANK_SETTINGS_BUTTON.getId() && event.getMenuOption().equals(SHOW_SETTINGS)) - || (event.getWidgetId() == WidgetInfo.BANK_TUTORIAL_BUTTON.getId() && event.getMenuOption().equals(SHOW_TUTORIAL)))) - { - saveTab(); - } - } - - public void handleSearch() - { - if (activeTab != null) - { - activateTab(null); - // This ensures that when clicking Search when tab is selected, the search input is opened rather - // than client trying to close it first - client.setVar(VarClientStr.INPUT_TEXT, ""); - client.setVar(VarClientInt.INPUT_TYPE, 0); - } - } - - public void updateTabIfActive(final Collection tags) - { - if (activeTab != null && tags.contains(activeTab.getTag())) - { - openTag(activeTab.getTag()); - } - } - - public void handleDrag(boolean isDragging, boolean shiftDown) - { - if (isHidden()) - { - return; - } - - Widget draggedOn = client.getDraggedOnWidget(); - Widget draggedWidget = client.getDraggedWidget(); - - // Returning early or nulling the drag release listener has no effect. Hence, we need to - // null the draggedOnWidget instead. - if (draggedWidget.getId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() && isActive() - && config.preventTagTabDrags()) - { - client.setDraggedOnWidget(null); - } - - if (!isDragging || draggedOn == null) - { - return; - } - - // is dragging widget and mouse button released - if (client.getMouseCurrentButton() == 0) - { - if (!isTabMenuActive() && draggedWidget.getItemId() > 0 && draggedWidget.getId() != parent.getId()) - { - // Tag an item dragged on a tag tab - if (draggedOn.getId() == parent.getId()) - { - tagManager.addTag(draggedWidget.getItemId(), draggedOn.getName(), shiftDown); - updateTabIfActive(Lists.newArrayList(Text.standardize(draggedOn.getName()))); - } - } - else if ((isTabMenuActive() && draggedWidget.getId() == draggedOn.getId() && draggedOn.getId() != parent.getId()) - || (parent.getId() == draggedOn.getId() && parent.getId() == draggedWidget.getId())) - { - // Reorder tag tabs - moveTagTab(draggedWidget, draggedOn); - } - } - else if (draggedWidget.getItemId() > 0) - { - MenuEntry[] entries = client.getMenuEntries(); - - if (entries.length > 0) - { - MenuEntry entry = entries[entries.length - 1]; - - if (draggedWidget.getItemId() > 0 && entry.getOption().equals(VIEW_TAB) && draggedOn.getId() != draggedWidget.getId()) - { - entry.setOption(TAG_SEARCH + Text.removeTags(entry.getTarget()) + (shiftDown ? VAR_TAG_SUFFIX : "")); - entry.setTarget(draggedWidget.getName()); - client.setMenuEntries(entries); - } - - if (entry.getOption().equals(SCROLL_UP)) - { - scrollTick(-1); - } - else if (entry.getOption().equals(SCROLL_DOWN)) - { - scrollTick(1); - } - } - } - } - - private void moveTagTab(final Widget source, final Widget dest) - { - if (Strings.isNullOrEmpty(dest.getName())) - { - return; - } - - if (client.getVar(Varbits.BANK_REARRANGE_MODE) == 0) - { - tabManager.swap(source.getName(), dest.getName()); - } - else - { - tabManager.insert(source.getName(), dest.getName()); - } - - tabManager.save(); - updateTabs(); - } - - private boolean isHidden() - { - Widget widget = client.getWidget(WidgetInfo.BANK_CONTAINER); - return !config.tabs() || widget == null || widget.isHidden(); - } - - private void addTabActions(Widget w) - { - w.setAction(1, VIEW_TAB); - w.setAction(2, CHANGE_ICON); - w.setAction(3, REMOVE_TAB); - w.setAction(4, EXPORT_TAB); - w.setAction(5, RENAME_TAB); - w.setOnOpListener((JavaScriptCallback) this::handleTagTab); - } - - private void addTabOptions(Widget w) - { - int clickmask = w.getClickMask(); - clickmask |= WidgetConfig.DRAG; - clickmask |= WidgetConfig.DRAG_ON; - w.setClickMask(clickmask); - w.setDragDeadTime(5); - w.setDragDeadZone(5); - w.setItemQuantity(10000); - w.setItemQuantityMode(ItemQuantityMode.NEVER); - } - - private void loadTab(String tag) - { - TagTab tagTab = tabManager.load(tag); - - if (tagTab.getBackground() == null) - { - Widget btn = createGraphic(ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), TabSprites.TAB_BACKGROUND.getSpriteId(), -1, TAB_WIDTH, TAB_HEIGHT, bounds.x, 1, true); - addTabActions(btn); - tagTab.setBackground(btn); - } - - if (tagTab.getIcon() == null) - { - Widget icon = createGraphic( - ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), - -1, - tagTab.getIconItemId(), - Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT, - bounds.x + 3, 1, - false); - addTabOptions(icon); - tagTab.setIcon(icon); - } - - if (tagTab.getMenu() == null) - { - Widget menu = createGraphic( - client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER), - ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), - -1, - tagTab.getIconItemId(), - BANK_ITEM_WIDTH, BANK_ITEM_HEIGHT, - BANK_ITEM_START_X, BANK_ITEM_START_Y, - true); - addTabActions(menu); - addTabOptions(menu); - if (activeTab != null && activeTab.getTag().equals(TAB_MENU_KEY)) - { - menu.setHidden(false); - } - else - { - menu.setHidden(true); - } - tagTab.setMenu(menu); - } - - tabManager.add(tagTab); - } - - private void deleteTab(String tag) - { - if (activeTab != null && activeTab.getTag().equals(tag)) - { - activateTab(null); - bankSearch.reset(true); - } - - tabManager.remove(tag); - tabManager.save(); - - updateBounds(); - scrollTab(0); - } - - private void renameTab(String oldTag) - { - chatboxPanelManager.openTextInput("Enter new tag name for tag \"" + oldTag + "\":") - .addCharValidator(FILTERED_CHARS) - .onDone((Consumer) (newTag) -> clientThread.invoke(() -> - { - if (!Strings.isNullOrEmpty(newTag) && !newTag.equalsIgnoreCase(oldTag)) - { - if (tabManager.find(newTag) == null) - { - TagTab tagTab = tabManager.find(oldTag); - tagTab.setTag(newTag); - - final String coloredName = ColorUtil.wrapWithColorTag(newTag, HILIGHT_COLOR); - tagTab.getIcon().setName(coloredName); - tagTab.getBackground().setName(coloredName); - tagTab.getMenu().setName(coloredName); - - tabManager.removeIcon(oldTag); - tabManager.setIcon(newTag, tagTab.getIconItemId() + ""); - - tabManager.save(); - tagManager.renameTag(oldTag, newTag); - - if (activeTab != null && activeTab.equals(tagTab)) - { - openTag(newTag); - } - } - else - { - chatboxPanelManager.openTextMenuInput("The specified bank tag already exists.") - .option("1. Merge into existing tag \"" + newTag + "\".", () -> - clientThread.invoke(() -> - { - tagManager.renameTag(oldTag, newTag); - final String activeTag = activeTab != null ? activeTab.getTag() : ""; - deleteTab(oldTag); - - if (activeTag.equals(oldTag)) - { - openTag(newTag); - } - }) - ) - .option("2. Choose a different name.", () -> - clientThread.invoke(() -> - renameTab(oldTag)) - ) - .build(); - } - } - })) - .build(); - } - - private void scrollTick(int direction) - { - // This ensures that dragging on scroll buttons do not scrolls too fast - if (startScroll.until(Instant.now(), ChronoUnit.MILLIS) >= SCROLL_TICK) - { - startScroll = Instant.now(); - scrollTab(direction); - } - } - - private void scrollTab(int direction) - { - maxTabs = (bounds.height - BUTTON_HEIGHT * 2 - MARGIN * 2) / TAB_HEIGHT; - - // prevent running into the incinerator - while (bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT * 2 + MARGIN > bounds.y + bounds.height) - { - --maxTabs; - } - - int proposedIndex = currentTabIndex + direction; - int numTabs = tabManager.size(); - - if (proposedIndex >= numTabs || proposedIndex < 0) - { - currentTabIndex = 0; - } - else if (numTabs - proposedIndex >= maxTabs) - { - currentTabIndex = proposedIndex; - } - else if (maxTabs < numTabs && numTabs - proposedIndex < maxTabs) - { - // Edge case when only 1 tab displays instead of up to maxTabs when one is deleted at the end of the list - currentTabIndex = proposedIndex; - scrollTab(-1); - } - - updateTabs(); - } - - private void activateTab(TagTab tagTab) - { - if (activeTab != null && activeTab.equals(tagTab)) - { - return; - } - - if (activeTab != null) - { - Widget tab = activeTab.getBackground(); - tab.setSpriteId(TabSprites.TAB_BACKGROUND.getSpriteId()); - tab.revalidate(); - activeTab = null; - } - - if (tagTab != null) - { - Widget tab = tagTab.getBackground(); - tab.setSpriteId(TabSprites.TAB_BACKGROUND_ACTIVE.getSpriteId()); - tab.revalidate(); - activeTab = tagTab; - } - - tagTabActive = false; - } - - private void updateBounds() - { - Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); - if (itemContainer == null) - { - return; - } - - int height = itemContainer.getHeight(); - - // If player isn't using normal bank tabs - if (itemContainer.getRelativeY() == 0) - { - height -= (TAB_HEIGHT + MARGIN); - } - - bounds.setSize(TAB_WIDTH + MARGIN * 2, height); - bounds.setLocation(MARGIN, TAB_HEIGHT + MARGIN); - - Widget incinerator = client.getWidget(WidgetInfo.BANK_INCINERATOR); - - if (incinerator != null && !incinerator.isHidden()) - { - incinerator.setOriginalHeight(INCINERATOR_HEIGHT); - incinerator.setOriginalWidth(INCINERATOR_WIDTH); - incinerator.setOriginalY(INCINERATOR_HEIGHT); - - Widget child = incinerator.getChild(0); - child.setOriginalHeight(INCINERATOR_HEIGHT); - child.setOriginalWidth(INCINERATOR_WIDTH); - child.setWidthMode(WidgetSizeMode.ABSOLUTE); - child.setHeightMode(WidgetSizeMode.ABSOLUTE); - child.setType(WidgetType.GRAPHIC); - child.setSpriteId(TabSprites.INCINERATOR.getSpriteId()); - incinerator.revalidate(); - - bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight()); - } - - if (upButton != null) - { - Point p = upButton.getCanvasLocation(); - canvasBounds.setBounds(p.getX(), p.getY() + BUTTON_HEIGHT, bounds.width, maxTabs * TAB_HEIGHT + maxTabs * MARGIN); - } - } - - private void updateTabs() - { - int y = bounds.y + MARGIN + BUTTON_HEIGHT; - - if (maxTabs >= tabManager.size()) - { - currentTabIndex = 0; - } - else - { - y -= (currentTabIndex * TAB_HEIGHT + currentTabIndex * MARGIN); - } - - int itemX = BANK_ITEM_START_X; - int itemY = BANK_ITEM_START_Y; - int rowIndex = 0; - - for (TagTab tab : tabManager.getTabs()) - { - updateWidget(tab.getBackground(), y); - updateWidget(tab.getIcon(), y + 4); - - // Edge case where item icon is 1 pixel out of bounds - tab.getIcon().setHidden(tab.getBackground().isHidden()); - - // Keep item widget shown while drag scrolling - if (client.getDraggedWidget() == tab.getIcon()) - { - tab.getIcon().setHidden(false); - } - - y += TAB_HEIGHT + MARGIN; - - Widget item = tab.getMenu(); - item.setOriginalX(itemX); - item.setOriginalY(itemY); - item.revalidate(); - - rowIndex++; - if (rowIndex == BANK_ITEMS_PER_ROW) - { - itemX = BANK_ITEM_START_X; - itemY += BANK_ITEM_Y_PADDING + BANK_ITEM_HEIGHT; - rowIndex = 0; - } - else - { - itemX += BANK_ITEM_X_PADDING + BANK_ITEM_WIDTH; - } - } - - boolean hidden = !(tabManager.size() > 0); - - upButton.setHidden(hidden); - upButton.setOriginalY(bounds.y); - upButton.revalidate(); - - downButton.setHidden(hidden); - downButton.setOriginalY(bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT + MARGIN); - downButton.revalidate(); - } - - private Widget createGraphic(Widget container, String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) - { - Widget widget = container.createChild(-1, WidgetType.GRAPHIC); - widget.setOriginalWidth(width); - widget.setOriginalHeight(height); - widget.setOriginalX(x); - widget.setOriginalY(y); - - widget.setSpriteId(spriteId); - - if (itemId > -1) - { - widget.setItemId(itemId); - widget.setItemQuantity(-1); - widget.setBorderType(1); - } - - if (hasListener) - { - widget.setOnOpListener(ScriptID.NULL); - widget.setHasListener(true); - } - - widget.setName(name); - widget.revalidate(); - - return widget; - } - - private Widget createGraphic(String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) - { - return createGraphic(parent, name, spriteId, itemId, width, height, x, y, hasListener); - } - - private void updateWidget(Widget t, int y) - { - t.setOriginalY(y); - t.setHidden(y < (bounds.y + BUTTON_HEIGHT + MARGIN) || y > (bounds.y + bounds.height - TAB_HEIGHT - MARGIN - BUTTON_HEIGHT)); - t.revalidate(); - } - - private ItemComposition getItem(int idx) - { - ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); - Item item = bankContainer.getItem(idx); - return itemManager.getItemComposition(item.getId()); - } - - private void openTag(final String tag) - { - activateTab(tabManager.find(tag)); - tagTabActive = BankTagsPlugin.TAG_TABS_CONFIG.equals(tag); - bankSearch.reset(true); // clear search dialog & relayout bank for new tab. - - // When searching the button has a script on timer to detect search end, that will set the background back - // and remove the timer. However since we are going from a bank search to our fake search this will not remove - // the timer but instead re-add it and reset the background. So remove the timer and the background. This is the - // same as bankmain_search_setbutton. - Widget searchButtonBackground = client.getWidget(WidgetInfo.BANK_SEARCH_BUTTON_BACKGROUND); - searchButtonBackground.setOnTimerListener((Object[]) null); - searchButtonBackground.setSpriteId(SpriteID.EQUIPMENT_SLOT_TILE); - } - - private static MenuEntry[] createMenuEntry(MenuEntryAdded event, String option, String target, MenuEntry[] entries) - { - final MenuEntry entry = new MenuEntry(); - entry.setActionParam(event.getActionParam()); - entry.setParam1(event.getActionParam1()); - entry.setTarget(target); - entry.setOption(option); - entry.setType(MenuAction.RUNELITE.getId()); - entry.setIdentifier(event.getIdentifier()); - entries = Arrays.copyOf(entries, entries.length + 1); - entries[entries.length - 1] = entry; - return entries; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java deleted file mode 100644 index f3feed8511..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2018, Tomas Slusny - * Copyright (c) 2018, Ron Young - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.banktags.tabs; - -import com.google.common.base.MoreObjects; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.inject.Singleton; -import lombok.Getter; -import net.runelite.api.ItemID; -import net.runelite.client.config.ConfigManager; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_TABS_CONFIG; -import net.runelite.client.util.Text; -import org.apache.commons.lang3.math.NumberUtils; - -@Singleton -class TabManager -{ - @Getter - private final List tabs = new ArrayList<>(); - private final ConfigManager configManager; - - @Inject - private TabManager(ConfigManager configManager) - { - this.configManager = configManager; - } - - void add(TagTab tagTab) - { - if (!contains(tagTab.getTag())) - { - tabs.add(tagTab); - } - } - - void clear() - { - tabs.forEach(t -> t.setHidden(true)); - tabs.clear(); - } - - TagTab find(String tag) - { - Optional first = tabs.stream().filter(t -> t.getTag().equals(Text.standardize(tag))).findAny(); - return first.orElse(null); - } - - List getAllTabs() - { - return Text.fromCSV(MoreObjects.firstNonNull(configManager.getConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG), "")); - } - - TagTab load(String tag) - { - TagTab tagTab = find(tag); - - if (tagTab == null) - { - tag = Text.standardize(tag); - String item = configManager.getConfiguration(CONFIG_GROUP, ICON_SEARCH + tag); - int itemid = NumberUtils.toInt(item, ItemID.SPADE); - tagTab = new TagTab(itemid, tag); - } - - return tagTab; - } - - void swap(String tagToMove, String tagDestination) - { - tagToMove = Text.standardize(tagToMove); - tagDestination = Text.standardize(tagDestination); - - if (contains(tagToMove) && contains(tagDestination)) - { - Collections.swap(tabs, indexOf(tagToMove), indexOf(tagDestination)); - } - } - - void insert(String tagToMove, String tagDestination) - { - tagToMove = Text.standardize(tagToMove); - tagDestination = Text.standardize(tagDestination); - - if (contains(tagToMove) && contains(tagDestination)) - { - tabs.add(indexOf(tagDestination), tabs.remove(indexOf(tagToMove))); - } - } - - void remove(String tag) - { - TagTab tagTab = find(tag); - - if (tagTab != null) - { - tagTab.setHidden(true); - tabs.remove(tagTab); - removeIcon(tag); - } - } - - void save() - { - String tags = Text.toCSV(tabs.stream().map(TagTab::getTag).collect(Collectors.toList())); - configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags); - } - - void removeIcon(final String tag) - { - configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag)); - } - - void setIcon(final String tag, final String icon) - { - configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag), icon); - } - - int size() - { - return tabs.size(); - } - - private boolean contains(String tag) - { - return tabs.stream().anyMatch(t -> t.getTag().equals(tag)); - } - - private int indexOf(TagTab tagTab) - { - return tabs.indexOf(tagTab); - } - - private int indexOf(String tag) - { - return indexOf(find(tag)); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java deleted file mode 100644 index 4c406af095..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2018 Abex - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.camera; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.Range; - -@ConfigGroup("zoom") // using the old plugin's group name -public interface CameraConfig extends Config -{ - int OUTER_LIMIT_MIN = -400; - int OUTER_LIMIT_MAX = 400; - /** - * The largest (most zoomed in) value that can be used without the client crashing. - * - * Larger values trigger an overflow in the engine's fov to scale code. - */ - int INNER_ZOOM_LIMIT = 1004; - - @ConfigItem( - keyName = "inner", - name = "Expand inner zoom limit", - description = "Configures whether or not the inner zoom limit is reduced", - position = 1 - ) - default boolean innerLimit() - { - return false; - } - - @Range( - min = OUTER_LIMIT_MIN, - max = OUTER_LIMIT_MAX - ) - @ConfigItem( - keyName = "outerLimit", - name = "Expand outer zoom limit", - description = "Configures how much the outer zoom limit is adjusted", - position = 2 - ) - default int outerLimit() - { - return 0; - } - - @ConfigItem( - keyName = "relaxCameraPitch", - name = "Vertical camera", - description = "Relax the camera's upper pitch limit", - position = 3 - ) - default boolean relaxCameraPitch() - { - return false; - } - - @ConfigItem( - keyName = "controlFunction", - name = "Control Function", - description = "Configures the zoom function when control is pressed", - position = 4 - ) - default ControlFunction controlFunction() - { - return ControlFunction.NONE; - } - - @ConfigItem( - keyName = "ctrlZoomValue", - name = "Reset zoom position", - description = "Position of zoom when it is reset", - position = 5 - ) - @Range( - min = OUTER_LIMIT_MIN, - max = INNER_ZOOM_LIMIT - ) - default int ctrlZoomValue() - { - return 512; - } - - @ConfigItem( - keyName = "zoomIncrement", - name = "Zoom Speed", - description = "Speed of zoom", - position = 6 - ) - default int zoomIncrement() - { - return 25; - } - - @ConfigItem( - keyName = "rightClickMovesCamera", - name = "Right click moves camera", - description = "Remaps right click to middle mouse click if there are no menu options", - position = 7 - ) - default boolean rightClickMovesCamera() - { - return false; - } - - @ConfigItem( - keyName = "ignoreExamine", - name = "Ignore Examine", - description = "Ignore the Examine menu entry", - position = 8 - ) - default boolean ignoreExamine() - { - return false; - } - - @ConfigItem( - keyName = "middleClickMenu", - name = "Middle-button opens menu", - description = "Middle-mouse button always opens the menu", - position = 9 - ) - default boolean middleClickMenu() - { - return false; - } - - @ConfigItem( - keyName = "compassLook", - name = "Compass options", - description = "Adds Look South, East, and West options to the compass", - position = 10 - ) - default boolean compassLook() - { - return true; - } - - @ConfigItem( - keyName = "invertYaw", - name = "Invert Yaw", - description = "Makes moving the camera horizontally with the mouse backwards", - position = 11 - ) - default boolean invertYaw() - { - return false; - } - - @ConfigItem( - keyName = "invertPitch", - name = "Invert Pitch", - description = "Makes moving the camera vertically with the mouse backwards", - position = 12 - ) - default boolean invertPitch() - { - return false; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java deleted file mode 100644 index 91249c197c..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright (c) 2018 Abex - * Copyright (c) 2018, Adam - * Copyright (c) 2019, Wynadorn - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.camera; - -import com.google.common.primitives.Ints; -import com.google.inject.Provides; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; -import java.util.Arrays; -import javax.inject.Inject; -import javax.swing.SwingUtilities; -import net.runelite.api.Client; -import net.runelite.api.MenuAction; -import net.runelite.api.MenuEntry; -import net.runelite.api.ScriptID; -import net.runelite.api.SettingID; -import net.runelite.api.VarClientInt; -import net.runelite.api.VarPlayer; -import net.runelite.api.events.BeforeRender; -import net.runelite.api.events.ClientTick; -import net.runelite.api.events.FocusChanged; -import net.runelite.api.events.MenuEntryAdded; -import net.runelite.api.events.ScriptCallbackEvent; -import net.runelite.api.events.ScriptPreFired; -import net.runelite.api.events.WidgetLoaded; -import net.runelite.api.widgets.JavaScriptCallback; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetID; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.events.ConfigChanged; -import net.runelite.client.input.KeyListener; -import net.runelite.client.input.KeyManager; -import net.runelite.client.input.MouseListener; -import net.runelite.client.input.MouseManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.tooltip.Tooltip; -import net.runelite.client.ui.overlay.tooltip.TooltipManager; - -@PluginDescriptor( - name = "Camera", - description = "Expands zoom limit, provides vertical camera, and remaps mouse input keys", - tags = {"zoom", "limit", "vertical", "click", "mouse"}, - enabledByDefault = false -) -public class CameraPlugin extends Plugin implements KeyListener, MouseListener -{ - private static final int DEFAULT_ZOOM_INCREMENT = 25; - private static final int DEFAULT_OUTER_ZOOM_LIMIT = 128; - static final int DEFAULT_INNER_ZOOM_LIMIT = 896; - - private static final String LOOK_NORTH = "Look North"; - private static final String LOOK_SOUTH = "Look South"; - private static final String LOOK_EAST = "Look East"; - private static final String LOOK_WEST = "Look West"; - - private boolean controlDown; - // flags used to store the mousedown states - private boolean rightClick; - private boolean middleClick; - /** - * Whether or not the current menu has any non-ignored menu entries - */ - private boolean menuHasEntries; - - @Inject - private Client client; - - @Inject - private ClientThread clientThread; - - @Inject - private CameraConfig config; - - @Inject - private KeyManager keyManager; - - @Inject - private MouseManager mouseManager; - - @Inject - private TooltipManager tooltipManager; - - private Tooltip sliderTooltip; - - @Provides - CameraConfig getConfig(ConfigManager configManager) - { - return configManager.getConfig(CameraConfig.class); - } - - @Override - protected void startUp() - { - rightClick = false; - middleClick = false; - menuHasEntries = false; - copyConfigs(); - keyManager.registerKeyListener(this); - mouseManager.registerMouseListener(this); - clientThread.invoke(() -> - { - Widget sideSlider = client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK); - if (sideSlider != null) - { - addZoomTooltip(sideSlider); - } - - Widget settingsInit = client.getWidget(WidgetInfo.SETTINGS_INIT); - if (settingsInit != null) - { - //TODO: Implement client.createScriptEvent - //client.createScriptEvent(settingsInit.getOnLoadListener()) - //.setSource(settingsInit) - //.run(); - } - }); - } - - @Override - protected void shutDown() - { - client.setCameraPitchRelaxerEnabled(false); - client.setInvertYaw(false); - client.setInvertPitch(false); - keyManager.unregisterKeyListener(this); - mouseManager.unregisterMouseListener(this); - controlDown = false; - - clientThread.invoke(() -> - { - Widget sideSlider = client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK); - if (sideSlider != null) - { - sideSlider.setOnMouseRepeatListener((Object[]) null); - } - - Widget settingsInit = client.getWidget(WidgetInfo.SETTINGS_INIT); - if (settingsInit != null) - { - //TODO: Implement client.createScriptEvent - //client.createScriptEvent(settingsInit.getOnLoadListener()) - //.setSource(settingsInit) - //.run(); - } - }); - } - - void copyConfigs() - { - client.setCameraPitchRelaxerEnabled(config.relaxCameraPitch()); - client.setInvertYaw(config.invertYaw()); - client.setInvertPitch(config.invertPitch()); - } - - @Subscribe - public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded) - { - if (menuEntryAdded.getType() == MenuAction.CC_OP.getId() && menuEntryAdded.getOption().equals(LOOK_NORTH) && config.compassLook()) - { - MenuEntry[] menuEntries = client.getMenuEntries(); - int len = menuEntries.length; - MenuEntry north = menuEntries[len - 1]; - - menuEntries = Arrays.copyOf(menuEntries, len + 3); - - // The handling for these entries is done in ToplevelCompassOp.rs2asm - menuEntries[--len] = createCameraLookEntry(menuEntryAdded, 4, LOOK_WEST); - menuEntries[++len] = createCameraLookEntry(menuEntryAdded, 3, LOOK_EAST); - menuEntries[++len] = createCameraLookEntry(menuEntryAdded, 2, LOOK_SOUTH); - menuEntries[++len] = north; - - client.setMenuEntries(menuEntries); - } - } - - private MenuEntry createCameraLookEntry(MenuEntryAdded lookNorth, int identifier, String option) - { - MenuEntry m = new MenuEntry(); - m.setOption(option); - m.setTarget(lookNorth.getTarget()); - m.setIdentifier(identifier); - m.setType(MenuAction.CC_OP.getId()); - m.setParam0(lookNorth.getActionParam0()); - m.setParam1(lookNorth.getActionParam1()); - return m; - } - - @Subscribe - public void onScriptCallbackEvent(ScriptCallbackEvent event) - { - if (client.getIndexScripts().isOverlayOutdated()) - { - // if any cache overlay fails to load then assume at least one of the zoom scripts is outdated - // and prevent zoom extending entirely. - return; - } - - int[] intStack = client.getIntStack(); - int intStackSize = client.getIntStackSize(); - - if (!controlDown && "scrollWheelZoom".equals(event.getEventName()) && config.controlFunction() == ControlFunction.CONTROL_TO_ZOOM) - { - intStack[intStackSize - 1] = 1; - } - - if ("innerZoomLimit".equals(event.getEventName()) && config.innerLimit()) - { - intStack[intStackSize - 1] = CameraConfig.INNER_ZOOM_LIMIT; - return; - } - - if ("outerZoomLimit".equals(event.getEventName())) - { - int outerLimit = Ints.constrainToRange(config.outerLimit(), CameraConfig.OUTER_LIMIT_MIN, CameraConfig.OUTER_LIMIT_MAX); - int outerZoomLimit = DEFAULT_OUTER_ZOOM_LIMIT - outerLimit; - intStack[intStackSize - 1] = outerZoomLimit; - return; - } - - if ("scrollWheelZoomIncrement".equals(event.getEventName()) && config.zoomIncrement() != DEFAULT_ZOOM_INCREMENT) - { - intStack[intStackSize - 1] = config.zoomIncrement(); - return; - } - - if (config.innerLimit()) - { - // This lets the options panel's slider have an exponential rate - final double exponent = 2.d; - switch (event.getEventName()) - { - case "zoomLinToExp": - { - double range = intStack[intStackSize - 1]; - double value = intStack[intStackSize - 2]; - value = Math.pow(value / range, exponent) * range; - intStack[intStackSize - 2] = (int) value; - break; - } - case "zoomExpToLin": - { - double range = intStack[intStackSize - 1]; - double value = intStack[intStackSize - 2]; - value = Math.pow(value / range, 1.d / exponent) * range; - intStack[intStackSize - 2] = (int) value; - break; - } - } - } - } - - @Subscribe - public void onFocusChanged(FocusChanged event) - { - if (!event.isFocused()) - { - controlDown = false; - } - } - - @Subscribe - public void onConfigChanged(ConfigChanged ev) - { - copyConfigs(); - } - - @Override - public void keyTyped(KeyEvent e) - { - } - - @Override - public void keyPressed(KeyEvent e) - { - if (e.getKeyCode() == KeyEvent.VK_CONTROL) - { - controlDown = true; - } - } - - @Override - public void keyReleased(KeyEvent e) - { - if (e.getKeyCode() == KeyEvent.VK_CONTROL) - { - controlDown = false; - - if (config.controlFunction() == ControlFunction.CONTROL_TO_RESET) - { - final int zoomValue = Ints.constrainToRange(config.ctrlZoomValue(), CameraConfig.OUTER_LIMIT_MIN, CameraConfig.INNER_ZOOM_LIMIT); - clientThread.invokeLater(() -> client.runScript(ScriptID.CAMERA_DO_ZOOM, zoomValue, zoomValue)); - } - } - } - - /** - * Checks if the menu has any non-ignored entries - */ - private boolean hasMenuEntries(MenuEntry[] menuEntries) - { - for (MenuEntry menuEntry : menuEntries) - { - MenuAction action = MenuAction.of(menuEntry.getType()); - switch (action) - { - case CANCEL: - case WALK: - break; - case EXAMINE_OBJECT: - case EXAMINE_NPC: - case EXAMINE_ITEM_GROUND: - case EXAMINE_ITEM: - case CC_OP_LOW_PRIORITY: - if (config.ignoreExamine()) - { - break; - } - default: - return true; - } - } - return false; - } - - /** - * Checks if the menu has any options, because menu entries are built each - * tick and the MouseListener runs on the awt thread - */ - @Subscribe - public void onClientTick(ClientTick event) - { - menuHasEntries = hasMenuEntries(client.getMenuEntries()); - } - - @Subscribe - private void onScriptPreFired(ScriptPreFired ev) - { - if (ev.getScriptId() == ScriptID.SETTINGS_SLIDER_CHOOSE_ONOP) - { - int arg = client.getIntStackSize() - 7; - int[] is = client.getIntStack(); - - if (is[arg] == SettingID.CAMERA_ZOOM) - { - //TODO: Implement client.createScriptEvent - //addZoomTooltip(client.getScriptActiveWidget()); - } - } - } - - @Subscribe - private void onWidgetLoaded(WidgetLoaded ev) - { - if (ev.getGroupId() == WidgetID.SETTINGS_SIDE_GROUP_ID) - { - addZoomTooltip(client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK)); - } - } - - private void addZoomTooltip(Widget w) - { - w.setOnMouseRepeatListener((JavaScriptCallback) ev -> - { - int value = client.getVar(VarClientInt.CAMERA_ZOOM_RESIZABLE_VIEWPORT); - int max = config.innerLimit() ? config.INNER_ZOOM_LIMIT : CameraPlugin.DEFAULT_INNER_ZOOM_LIMIT; - sliderTooltip = new Tooltip("Camera Zoom: " + value + " / " + max); - }); - } - - @Subscribe - private void onBeforeRender(BeforeRender ev) - { - if (sliderTooltip != null) - { - tooltipManager.add(sliderTooltip); - sliderTooltip = null; - } - } - - /** - * The event that is triggered when a mouse button is pressed - * In this method the right click is changed to a middle-click to enable rotating the camera - *

- * This method also provides the config option to enable the middle-mouse button to always open the right click menu - */ - @Override - public MouseEvent mousePressed(MouseEvent mouseEvent) - { - if (SwingUtilities.isRightMouseButton(mouseEvent) && config.rightClickMovesCamera()) - { - boolean oneButton = client.getVar(VarPlayer.MOUSE_BUTTONS) == 1; - // Only move the camera if there is nothing at the menu, or if - // in one-button mode. In one-button mode, left and right click always do the same thing, - // so always treat it as the menu is empty - if (!menuHasEntries || oneButton) - { - // Set the rightClick flag to true so we can release the button in mouseReleased() later - rightClick = true; - // Change the mousePressed() MouseEvent to the middle mouse button - mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), - mouseEvent.getID(), - mouseEvent.getWhen(), - mouseEvent.getModifiersEx(), - mouseEvent.getX(), - mouseEvent.getY(), - mouseEvent.getClickCount(), - mouseEvent.isPopupTrigger(), - MouseEvent.BUTTON2); - } - } - else if (SwingUtilities.isMiddleMouseButton((mouseEvent)) && config.middleClickMenu()) - { - // Set the middleClick flag to true so we can release it later in mouseReleased() - middleClick = true; - // Chance the middle mouse button MouseEvent to a right-click - mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), - mouseEvent.getID(), - mouseEvent.getWhen(), - mouseEvent.getModifiersEx(), - mouseEvent.getX(), - mouseEvent.getY(), - mouseEvent.getClickCount(), - mouseEvent.isPopupTrigger(), - MouseEvent.BUTTON3); - } - return mouseEvent; - } - - /** - * Correct the MouseEvent to release the correct button - */ - @Override - public MouseEvent mouseReleased(MouseEvent mouseEvent) - { - if (rightClick) - { - rightClick = false; - // Change the MouseEvent to button 2 so the middle mouse button will be released - mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), - mouseEvent.getID(), - mouseEvent.getWhen(), - mouseEvent.getModifiersEx(), - mouseEvent.getX(), - mouseEvent.getY(), - mouseEvent.getClickCount(), - mouseEvent.isPopupTrigger(), - MouseEvent.BUTTON2); - - } - if (middleClick) - { - middleClick = false; - // Change the MouseEvent ot button 3 so the right mouse button will be released - mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), - mouseEvent.getID(), - mouseEvent.getWhen(), - mouseEvent.getModifiersEx(), - mouseEvent.getX(), - mouseEvent.getY(), - mouseEvent.getClickCount(), - mouseEvent.isPopupTrigger(), - MouseEvent.BUTTON3); - } - return mouseEvent; - } - - /* - * These methods are unused but required to be present in a MouseListener implementation - */ - // region Unused MouseListener methods - @Override - public MouseEvent mouseDragged(MouseEvent mouseEvent) - { - return mouseEvent; - } - - @Override - public MouseEvent mouseMoved(MouseEvent mouseEvent) - { - return mouseEvent; - } - - @Override - public MouseEvent mouseClicked(MouseEvent mouseEvent) - { - return mouseEvent; - } - - @Override - public MouseEvent mouseEntered(MouseEvent mouseEvent) - { - return mouseEvent; - } - - @Override - public MouseEvent mouseExited(MouseEvent mouseEvent) - { - return mouseEvent; - } - // endregion -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CoreOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CoreOverlay.java new file mode 100644 index 0000000000..7dc5ec8033 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CoreOverlay.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.corp; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Polygon; +import javax.inject.Inject; +import net.runelite.api.NPC; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; + +class CoreOverlay extends Overlay +{ + private final CorpPlugin corpPlugin; + private final CorpConfig config; + + @Inject + private CoreOverlay(CorpPlugin corpPlugin, CorpConfig corpConfig) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.corpPlugin = corpPlugin; + this.config = corpConfig; + } + + @Override + public Dimension render(Graphics2D graphics) + { + NPC core = corpPlugin.getCore(); + if (core != null && config.markDarkCore()) + { + Polygon canvasTilePoly = core.getCanvasTilePoly(); + if (canvasTilePoly != null) + { + OverlayUtil.renderPolygon(graphics, canvasTilePoly, Color.RED.brighter()); + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java new file mode 100644 index 0000000000..e0103ead43 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.corp; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("corp") +public interface CorpConfig extends Config +{ + @ConfigItem( + keyName = "showDamage", + name = "Show damage overlay", + description = "Show total damage overlay", + position = 0 + ) + default boolean showDamage() + { + return true; + } + + @ConfigItem( + keyName = "markDarkCore", + name = "Mark dark core", + description = "Marks the dark energy core.", + position = 1 + ) + default boolean markDarkCore() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpDamageOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpDamageOverlay.java new file mode 100644 index 0000000000..390d7a4270 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpDamageOverlay.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.corp; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import net.runelite.api.NPC; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.client.ui.overlay.OverlayLayer; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.ComponentConstants; +import net.runelite.client.ui.overlay.components.LineComponent; + +class CorpDamageOverlay extends OverlayPanel +{ + private final Client client; + private final CorpPlugin corpPlugin; + private final CorpConfig config; + + @Inject + private CorpDamageOverlay(Client client, CorpPlugin corpPlugin, CorpConfig config) + { + super(corpPlugin); + setPosition(OverlayPosition.TOP_LEFT); + setLayer(OverlayLayer.UNDER_WIDGETS); + setPriority(OverlayPriority.LOW); + this.client = client; + this.corpPlugin = corpPlugin; + this.config = config; + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Corp overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + Widget damageWidget = client.getWidget(WidgetID.CORP_DAMAGE, 0); + if (damageWidget != null) + { + damageWidget.setHidden(true); + } + + NPC corp = corpPlugin.getCorp(); + if (corp == null) + { + return null; + } + + int myDamage = client.getVar(Varbits.CORP_DAMAGE); + int totalDamage = corpPlugin.getTotalDamage(); + int players = corpPlugin.getPlayers().size(); + + // estimate how much damage is required for kill based on number of players + int damageForKill = players != 0 ? totalDamage / players : 0; + + NPC core = corpPlugin.getCore(); + if (core != null) + { + WorldPoint corePoint = core.getWorldLocation(); + WorldPoint myPoint = client.getLocalPlayer().getWorldLocation(); + + String text = null; + + if (core.getInteracting() == client.getLocalPlayer()) + { + text = "The core is targeting you!"; + } + else if (corePoint.distanceTo(myPoint) <= 1) + { + text = "Stay away from the core!"; + } + + if (text != null) + { + final FontMetrics fontMetrics = graphics.getFontMetrics(); + int textWidth = Math.max(ComponentConstants.STANDARD_WIDTH, fontMetrics.stringWidth(text)); + + panelComponent.setPreferredSize(new Dimension(textWidth, 0)); + panelComponent.getChildren().add(LineComponent.builder() + .left(text) + .leftColor(Color.RED) + .build()); + } + } + + if (config.showDamage()) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("Your damage") + .right(Integer.toString(myDamage)) + .rightColor(damageForKill > 0 && myDamage >= damageForKill ? Color.GREEN : Color.RED) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Total damage") + .right(Integer.toString(totalDamage)) + .build()); + } + + return super.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java new file mode 100644 index 0000000000..284e8caea9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.corp; + +import com.google.inject.Provides; +import java.util.HashSet; +import java.util.Set; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.NPC; +import net.runelite.api.NpcID; +import net.runelite.api.Varbits; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.InteractingChanged; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.client.chat.ChatColorType; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Corporeal Beast", + description = "Show damage statistics and highlight dark energy cores", + tags = {"bosses", "combat", "pve", "overlay"} +) +@Slf4j +public class CorpPlugin extends Plugin +{ + @Getter(AccessLevel.PACKAGE) + private NPC corp; + + @Getter(AccessLevel.PACKAGE) + private NPC core; + + private int yourDamage; + + @Getter(AccessLevel.PACKAGE) + private int totalDamage; + + @Getter(AccessLevel.PACKAGE) + private final Set players = new HashSet<>(); + + @Inject + private Client client; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private CorpDamageOverlay corpOverlay; + + @Inject + private CoreOverlay coreOverlay; + + @Provides + CorpConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(CorpConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(corpOverlay); + overlayManager.add(coreOverlay); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(corpOverlay); + overlayManager.remove(coreOverlay); + + corp = core = null; + yourDamage = 0; + totalDamage = 0; + players.clear(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() == GameState.LOADING) + { + players.clear(); + } + } + + @Subscribe + public void onNpcSpawned(NpcSpawned npcSpawned) + { + NPC npc = npcSpawned.getNpc(); + + switch (npc.getId()) + { + case NpcID.CORPOREAL_BEAST: + log.debug("Corporeal beast spawn: {}", npc); + corp = npc; + yourDamage = 0; + totalDamage = 0; + players.clear(); + break; + case NpcID.DARK_ENERGY_CORE: + core = npc; + break; + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + NPC npc = npcDespawned.getNpc(); + + if (npc == corp) + { + log.debug("Corporeal beast despawn: {}", npc); + corp = null; + players.clear(); + + if (npc.isDead()) + { + // Show kill stats + String message = new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append("Corporeal Beast: Your damage: ") + .append(ChatColorType.HIGHLIGHT) + .append(Integer.toString(yourDamage)) + .append(ChatColorType.NORMAL) + .append(", Total damage: ") + .append(ChatColorType.HIGHLIGHT) + .append(Integer.toString(totalDamage)) + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + } + } + else if (npc == core) + { + core = null; + } + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied hitsplatApplied) + { + Actor actor = hitsplatApplied.getActor(); + + if (actor != corp) + { + return; + } + + int myDamage = client.getVar(Varbits.CORP_DAMAGE); + // sometimes hitsplats are applied after the damage counter has been reset + if (myDamage > 0) + { + yourDamage = myDamage; + } + totalDamage += hitsplatApplied.getHitsplat().getAmount(); + } + + @Subscribe + public void onInteractingChanged(InteractingChanged interactingChanged) + { + Actor source = interactingChanged.getSource(); + Actor target = interactingChanged.getTarget(); + + if (corp == null || target != corp) + { + return; + } + + players.add(source); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java new file mode 100644 index 0000000000..661071a786 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing; + +import com.google.gson.Gson; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@Slf4j +@Singleton +public class CrowdsourcingManager +{ + private static final String CROWDSOURCING_BASE = "https://crowdsource.runescape.wiki/runelite"; + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static final Gson GSON = RuneLiteAPI.GSON; + + @Inject + private OkHttpClient okHttpClient; + + private List data = new ArrayList<>(); + + public void storeEvent(Object event) + { + synchronized (this) + { + data.add(event); + } + } + + protected void submitToAPI() + { + List temp; + synchronized (this) + { + if (data.isEmpty()) + { + return; + } + temp = data; + data = new ArrayList<>(); + } + + Request r = new Request.Builder() + .url(CROWDSOURCING_BASE) + .post(RequestBody.create(JSON, GSON.toJson(temp))) + .build(); + + okHttpClient.newCall(r).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + log.debug("Error sending crowdsourcing data", e); + } + + @Override + public void onResponse(Call call, Response response) + { + log.debug("Successfully sent crowdsourcing data"); + response.close(); + } + }); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingPlugin.java new file mode 100644 index 0000000000..5c639b6205 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingPlugin.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing; + +import java.time.temporal.ChronoUnit; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.crowdsourcing.cooking.CrowdsourcingCooking; +import net.runelite.client.plugins.crowdsourcing.dialogue.CrowdsourcingDialogue; +import net.runelite.client.plugins.crowdsourcing.movement.CrowdsourcingMovement; +import net.runelite.client.plugins.crowdsourcing.music.CrowdsourcingMusic; +import net.runelite.client.plugins.crowdsourcing.thieving.CrowdsourcingThieving; +import net.runelite.client.plugins.crowdsourcing.woodcutting.CrowdsourcingWoodcutting; +import net.runelite.client.plugins.crowdsourcing.zmi.CrowdsourcingZMI; +import net.runelite.client.task.Schedule; + +@Slf4j +@PluginDescriptor( + name = "OSRS Wiki Crowdsourcing", + description = "Send data to the wiki to help figure out skilling success rates, burn rates, more. See osrs.wiki/RS:CROWD" +) +public class CrowdsourcingPlugin extends Plugin +{ + // Number of seconds to wait between trying to send data to the wiki. + private static final int SECONDS_BETWEEN_UPLOADS = 300; + + @Inject + private EventBus eventBus; + + @Inject + private CrowdsourcingManager manager; + + @Inject + private CrowdsourcingCooking cooking; + + @Inject + private CrowdsourcingDialogue dialogue; + + @Inject + private CrowdsourcingMovement movement; + + @Inject + private CrowdsourcingMusic music; + + @Inject + private CrowdsourcingThieving thieving; + + @Inject + private CrowdsourcingWoodcutting woodcutting; + + @Inject + private CrowdsourcingZMI zmi; + + @Override + protected void startUp() throws Exception + { + eventBus.register(cooking); + eventBus.register(dialogue); + eventBus.register(movement); + eventBus.register(music); + eventBus.register(thieving); + eventBus.register(woodcutting); + eventBus.register(zmi); + } + + @Override + protected void shutDown() throws Exception + { + eventBus.unregister(cooking); + eventBus.unregister(dialogue); + eventBus.unregister(movement); + eventBus.unregister(music); + eventBus.unregister(thieving); + eventBus.unregister(woodcutting); + eventBus.unregister(zmi); + } + + @Schedule( + period = SECONDS_BETWEEN_UPLOADS, + unit = ChronoUnit.SECONDS, + asynchronous = true + ) + public void submitToAPI() + { + manager.submitToAPI(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CookingData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CookingData.java new file mode 100644 index 0000000000..568361823a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CookingData.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.cooking; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class CookingData +{ + private final String message; + private final boolean hasCookingGauntlets; + private final boolean inHosidiusKitchen; + private final boolean kourendElite; + private final int lastGameObjectClicked; + private final int level; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CrowdsourcingCooking.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CrowdsourcingCooking.java new file mode 100644 index 0000000000..37058b5dbe --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/cooking/CrowdsourcingCooking.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.cooking; + +import javax.inject.Inject; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import net.runelite.api.Player; +import net.runelite.api.Skill; +import net.runelite.api.Varbits; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; + +public class CrowdsourcingCooking +{ + private static final int HOSIDIUS_KITCHEN_REGION = 6712; + + @Inject + private CrowdsourcingManager manager; + + @Inject + private Client client; + + private int lastGameObjectClicked; + + private boolean hasCookingGauntlets() + { + ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT); + if (equipmentContainer == null) + { + return false; + } + + return equipmentContainer.contains(ItemID.COOKING_GAUNTLETS); + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() != ChatMessageType.SPAM) + { + return; + } + + final String message = event.getMessage(); + // Message prefixes taken from CookingPlugin + if (message.startsWith("You successfully cook") + || message.startsWith("You successfully bake") + || message.startsWith("You manage to cook") + || message.startsWith("You roast a") + || message.startsWith("You cook") + || message.startsWith("You accidentally burn") + || message.startsWith("You accidentally spoil")) + { + boolean inHosidiusKitchen = false; + Player local = client.getLocalPlayer(); + if (local != null && local.getWorldLocation().getRegionID() == HOSIDIUS_KITCHEN_REGION) + { + inHosidiusKitchen = true; + } + + int cookingLevel = client.getBoostedSkillLevel(Skill.COOKING); + boolean hasCookingGauntlets = hasCookingGauntlets(); + boolean kourendElite = client.getVar(Varbits.DIARY_KOUREND_ELITE) == 1; + CookingData data = new CookingData(message, hasCookingGauntlets, inHosidiusKitchen, kourendElite, lastGameObjectClicked, cookingLevel); + manager.storeEvent(data); + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) + { + MenuAction action = menuOptionClicked.getMenuAction(); + if (action == MenuAction.ITEM_USE_ON_GAME_OBJECT + || action == MenuAction.GAME_OBJECT_FIRST_OPTION + || action == MenuAction.GAME_OBJECT_SECOND_OPTION + || action == MenuAction.GAME_OBJECT_THIRD_OPTION + || action == MenuAction.GAME_OBJECT_FOURTH_OPTION + || action == MenuAction.GAME_OBJECT_FIFTH_OPTION) + { + lastGameObjectClicked = menuOptionClicked.getId(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/CrowdsourcingDialogue.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/CrowdsourcingDialogue.java new file mode 100644 index 0000000000..9f0a0e72ad --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/CrowdsourcingDialogue.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.dialogue; + +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.events.GameTick; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; + +public class CrowdsourcingDialogue +{ + private static final String USERNAME_TOKEN = "%USERNAME%"; + + @Inject + private Client client; + + @Inject + private CrowdsourcingManager manager; + + private String lastNpcDialogueText = null; + private String lastPlayerDialogueText = null; + private Widget[] dialogueOptions; + + private String sanitize(String dialogue) + { + String username = client.getLocalPlayer().getName(); + return dialogue.replaceAll(username, USERNAME_TOKEN); + } + + @Subscribe + public void onGameTick(GameTick tick) + { + Widget npcDialogueTextWidget = client.getWidget(WidgetInfo.DIALOG_NPC_TEXT); + if (npcDialogueTextWidget != null && !npcDialogueTextWidget.getText().equals(lastNpcDialogueText)) + { + lastNpcDialogueText = npcDialogueTextWidget.getText(); + String npcName = client.getWidget(WidgetInfo.DIALOG_NPC_NAME).getText(); + NpcDialogueData data = new NpcDialogueData(sanitize(lastNpcDialogueText), npcName); + manager.storeEvent(data); + } + + Widget playerDialogueTextWidget = client.getWidget(WidgetID.DIALOG_PLAYER_GROUP_ID, 4); + if (playerDialogueTextWidget != null && !playerDialogueTextWidget.getText().equals(lastPlayerDialogueText)) + { + lastPlayerDialogueText = playerDialogueTextWidget.getText(); + PlayerDialogueData data = new PlayerDialogueData(sanitize(lastPlayerDialogueText)); + manager.storeEvent(data); + } + + Widget playerDialogueOptionsWidget = client.getWidget(WidgetID.DIALOG_OPTION_GROUP_ID, 1); + if (playerDialogueOptionsWidget != null && playerDialogueOptionsWidget.getChildren() != dialogueOptions) + { + dialogueOptions = playerDialogueOptionsWidget.getChildren(); + String[] optionsText = new String[dialogueOptions.length]; + for (int i = 0; i < dialogueOptions.length; i++) + { + optionsText[i] = sanitize(dialogueOptions[i].getText()); + } + DialogueOptionsData data = new DialogueOptionsData(optionsText); + manager.storeEvent(data); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/DialogueOptionsData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/DialogueOptionsData.java new file mode 100644 index 0000000000..141707fc00 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/DialogueOptionsData.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.dialogue; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class DialogueOptionsData +{ + private final String[] options; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/NpcDialogueData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/NpcDialogueData.java new file mode 100644 index 0000000000..18af3f88fa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/NpcDialogueData.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.dialogue; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class NpcDialogueData +{ + private final String message; + private final String name; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/PlayerDialogueData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/PlayerDialogueData.java new file mode 100644 index 0000000000..b78db9407c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/dialogue/PlayerDialogueData.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.dialogue; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class PlayerDialogueData +{ + private final String message; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/CrowdsourcingMovement.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/CrowdsourcingMovement.java new file mode 100644 index 0000000000..7f21c2fda2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/CrowdsourcingMovement.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.movement; + +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; + +public class CrowdsourcingMovement +{ + @Inject + private Client client; + + @Inject + private CrowdsourcingManager manager; + + private WorldPoint lastPoint; + private int ticksStill; + private boolean lastIsInInstance; + private MenuOptionClicked lastClick; + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) + { + if (menuOptionClicked.getMenuAction() != MenuAction.WALK + && !menuOptionClicked.getMenuOption().equals("Message")) + { + lastClick = menuOptionClicked; + } + } + + @Subscribe + public void onGameTick(GameTick tick) + { + LocalPoint local = LocalPoint.fromWorld(client, client.getLocalPlayer().getWorldLocation()); + WorldPoint nextPoint = WorldPoint.fromLocalInstance(client, local); + boolean nextIsInInstance = client.isInInstancedRegion(); + if (lastPoint != null) + { + int distance = nextPoint.distanceTo(lastPoint); + if (distance > 2 || nextIsInInstance != lastIsInInstance) + { + MovementData data = new MovementData(lastPoint, nextPoint, lastIsInInstance, nextIsInInstance, ticksStill, lastClick); + manager.storeEvent(data); + } + if (distance > 0) + { + ticksStill = 0; + } + } + ticksStill++; + lastPoint = nextPoint; + lastIsInInstance = nextIsInInstance; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/MovementData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/MovementData.java new file mode 100644 index 0000000000..1b9c6aef9e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/movement/MovementData.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.movement; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.MenuOptionClicked; + +@Data +@AllArgsConstructor +public class MovementData +{ + private final WorldPoint start; + private final WorldPoint end; + private final boolean fromInstance; + private final boolean toInstance; + private final int ticks; + private MenuOptionClicked lastClick; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/CrowdsourcingMusic.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/CrowdsourcingMusic.java new file mode 100644 index 0000000000..abca2a624f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/CrowdsourcingMusic.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.music; + +import javax.inject.Inject; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; + +public class CrowdsourcingMusic +{ + private static final String MUSIC_UNLOCK_MESSAGE = "You have unlocked a new music track:"; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private CrowdsourcingManager manager; + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() == ChatMessageType.GAMEMESSAGE) + { + String message = event.getMessage(); + if (message.contains(MUSIC_UNLOCK_MESSAGE)) + { + // Need to delay this by a tick because the location is not set until after the message + clientThread.invokeLater(() -> + { + LocalPoint local = LocalPoint.fromWorld(client, client.getLocalPlayer().getWorldLocation()); + WorldPoint location = WorldPoint.fromLocalInstance(client, local); + boolean isInInstance = client.isInInstancedRegion(); + MusicUnlockData data = new MusicUnlockData(location, isInInstance, message); + manager.storeEvent(data); + }); + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/MusicUnlockData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/MusicUnlockData.java new file mode 100644 index 0000000000..55cdb96e62 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/music/MusicUnlockData.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.music; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.runelite.api.coords.WorldPoint; + +@Data +@AllArgsConstructor +public class MusicUnlockData +{ + private final WorldPoint location; + private final boolean isInInstance; + private final String message; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/ContainerPrices.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingEndReason.java similarity index 85% rename from runelite-client/src/main/java/net/runelite/client/plugins/bank/ContainerPrices.java rename to runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingEndReason.java index 5762218e18..7af79389f0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bank/ContainerPrices.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingEndReason.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, TheStonedTurtle + * Copyright (c) 2019, Weird Gloop * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,13 +22,12 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.client.plugins.bank; -import lombok.Value; +package net.runelite.client.plugins.crowdsourcing.skilling; -@Value -class ContainerPrices +public enum SkillingEndReason { - private long gePrice; - private long highAlchPrice; + DEPLETED, + INVENTORY_FULL, + INTERRUPTED } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingState.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingState.java new file mode 100644 index 0000000000..85a4b87866 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/skilling/SkillingState.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.skilling; + +public enum SkillingState +{ + READY, + CLICKED, + CUTTING, + RECOVERING +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java new file mode 100644 index 0000000000..698e359265 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.thieving; + +import java.util.regex.Pattern; +import javax.inject.Inject; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.Skill; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; + +public class CrowdsourcingThieving +{ + private static final String BLACKJACK_SUCCESS = "You smack the bandit over the head and render them unconscious."; + private static final String BLACKJACK_FAIL = "Your blow only glances off the bandit's head."; + private static final Pattern PICKPOCKET_SUCCESS = Pattern.compile("You pick .*'s pocket\\."); + private static final Pattern PICKPOCKET_FAIL = Pattern.compile("You fail to pick .*'s pocket\\."); + + @Inject + private Client client; + + @Inject + private CrowdsourcingManager manager; + + private int lastPickpocketTarget; + + private boolean hasGlovesOfSilence() + { + ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT); + if (equipmentContainer == null) + { + return false; + } + + return equipmentContainer.contains(ItemID.GLOVES_OF_SILENCE); + } + + private boolean hasThievingCape() + { + ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT); + if (equipmentContainer == null) + { + return false; + } + + return equipmentContainer.contains(ItemID.THIEVING_CAPE) || + equipmentContainer.contains(ItemID.THIEVING_CAPET) || + equipmentContainer.contains(ItemID.MAX_CAPE); + } + + private int getArdougneDiary() + { + int easy = client.getVar(Varbits.DIARY_ARDOUGNE_EASY); + int medium = client.getVar(Varbits.DIARY_ARDOUGNE_MEDIUM); + int hard = client.getVar(Varbits.DIARY_ARDOUGNE_HARD); + int elite = client.getVar(Varbits.DIARY_ARDOUGNE_ELITE); + return easy + 2 * medium + 4 * hard + 8 * elite; + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() != ChatMessageType.SPAM) + { + return; + } + + String message = event.getMessage(); + if (BLACKJACK_SUCCESS.equals(message) + || BLACKJACK_FAIL.equals(message) + || PICKPOCKET_FAIL.matcher(message).matches() + || PICKPOCKET_SUCCESS.matcher(message).matches()) + { + WorldPoint location = client.getLocalPlayer().getWorldLocation(); + int ardougneDiary = getArdougneDiary(); + boolean silence = hasGlovesOfSilence(); + boolean thievingCape = hasThievingCape(); + int thievingLevel = client.getBoostedSkillLevel(Skill.THIEVING); + PickpocketData data = new PickpocketData(thievingLevel, lastPickpocketTarget, message, location, silence, thievingCape, ardougneDiary); + manager.storeEvent(data); + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (event.getMenuOption().equals("Pickpocket") || event.getMenuOption().equals("Knock-Out")) + { + NPC[] cachedNPCs = client.getCachedNPCs(); + NPC npc = cachedNPCs[event.getId()]; + lastPickpocketTarget = npc.getId(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/PickpocketData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/PickpocketData.java new file mode 100644 index 0000000000..d45328b1a4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/PickpocketData.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.thieving; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.runelite.api.coords.WorldPoint; + +@Data +@AllArgsConstructor +public class PickpocketData +{ + private final int level; + private final int target; + private final String message; + private final WorldPoint location; + private final boolean silence; + private final boolean thievingCape; + private final int ardougneDiary; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/CrowdsourcingWoodcutting.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/CrowdsourcingWoodcutting.java new file mode 100644 index 0000000000..c8499575cf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/CrowdsourcingWoodcutting.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.woodcutting; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import net.runelite.api.AnimationID; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import net.runelite.api.ObjectID; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameObjectDespawned; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; +import net.runelite.client.plugins.crowdsourcing.skilling.SkillingEndReason; +import net.runelite.client.plugins.crowdsourcing.skilling.SkillingState; + +public class CrowdsourcingWoodcutting +{ + private static final String CHOPPING_MESSAGE = "You swing your axe at the tree."; + private static final String INVENTORY_FULL_MESSAGE = "Your inventory is too full to hold any more logs."; + private static final String NEST_MESSAGE = "A bird's nest falls out of the tree"; + private static final Set TREE_OBJECTS = new ImmutableSet.Builder(). + add(ObjectID.OAK_10820). + add(ObjectID.YEW). + add(ObjectID.TREE). + add(ObjectID.TREE_1278). + add(ObjectID.WILLOW). + add(ObjectID.WILLOW_10829). + add(ObjectID.WILLOW_10831). + add(ObjectID.WILLOW_10833). + add(ObjectID.SCRAPEY_TREE). + add(ObjectID.JUNGLE_TREE_15951). + add(ObjectID.JUNGLE_TREE_15954). + add(ObjectID.JUNGLE_TREE_15948). + add(ObjectID.MAPLE_TREE_10832). + add(ObjectID.MAHOGANY). + add(ObjectID.TEAK). + add(ObjectID.MAGIC_TREE_10834). + add(ObjectID.HOLLOW_TREE_10821). + add(ObjectID.HOLLOW_TREE_10830). + add(ObjectID.ACHEY_TREE). + build(); + + private static final Map AXE_ANIMS = new ImmutableMap.Builder(). + put(AnimationID.WOODCUTTING_BRONZE, ItemID.BRONZE_AXE). + put(AnimationID.WOODCUTTING_IRON, ItemID.IRON_AXE). + put(AnimationID.WOODCUTTING_STEEL, ItemID.STEEL_AXE). + put(AnimationID.WOODCUTTING_BLACK, ItemID.BLACK_AXE). + put(AnimationID.WOODCUTTING_MITHRIL, ItemID.MITHRIL_AXE). + put(AnimationID.WOODCUTTING_ADAMANT, ItemID.ADAMANT_AXE). + put(AnimationID.WOODCUTTING_RUNE, ItemID.RUNE_AXE). + put(AnimationID.WOODCUTTING_DRAGON, ItemID.DRAGON_AXE). + put(AnimationID.WOODCUTTING_INFERNAL, ItemID.INFERNAL_AXE). + put(AnimationID.WOODCUTTING_3A_AXE, ItemID._3RD_AGE_AXE). + put(AnimationID.WOODCUTTING_CRYSTAL, ItemID.CRYSTAL_AXE). + put(AnimationID.WOODCUTTING_TRAILBLAZER, ItemID.TRAILBLAZER_AXE).build(); + + private static final Set SUCCESS_MESSAGES = new ImmutableSet.Builder(). + add("You get some logs."). + add("You get some oak logs."). + add("You get some willow logs."). + add("You get some teak logs."). + add("You get some teak logs and give them to Carpenter Kjallak."). + add("You get some maple logs."). + add("You get some maple logs and give them to Lumberjack Leif."). + add("You get some mahogany logs."). + add("You get some mahogany logs and give them to Carpenter Kjallak."). + add("You get some yew logs."). + add("You get some magic logs."). + add("You get some scrapey tree logs."). + add("You get some bark."). + build(); + + @Inject + private CrowdsourcingManager manager; + + @Inject + private Client client; + + private SkillingState state = SkillingState.RECOVERING; + private int lastExperimentEnd = 0; + private WorldPoint treeLocation; + private int treeId; + private int startTick; + private int axe; + private List chopTicks = new ArrayList<>(); + private List nestTicks = new ArrayList<>(); + + private void endExperiment(SkillingEndReason reason) + { + if (state == SkillingState.CUTTING) + { + int endTick = client.getTickCount(); + int woodcuttingLevel = client.getBoostedSkillLevel(Skill.WOODCUTTING); + WoodcuttingData data = new WoodcuttingData( + woodcuttingLevel, + startTick, + endTick, + chopTicks, + nestTicks, + axe, + treeId, + treeLocation, + reason + ); + manager.storeEvent(data); + + chopTicks = new ArrayList<>(); + nestTicks = new ArrayList<>(); + } + state = SkillingState.RECOVERING; + lastExperimentEnd = client.getTickCount(); + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + final String message = event.getMessage(); + final ChatMessageType type = event.getType(); + if (state == SkillingState.CLICKED && type == ChatMessageType.SPAM && message.equals(CHOPPING_MESSAGE)) + { + startTick = client.getTickCount(); + state = SkillingState.CUTTING; + } + else if (state == SkillingState.CUTTING && type == ChatMessageType.SPAM && SUCCESS_MESSAGES.contains(message)) + { + chopTicks.add(client.getTickCount()); + } + else if (state == SkillingState.CUTTING && type == ChatMessageType.GAMEMESSAGE && message.equals(INVENTORY_FULL_MESSAGE)) + { + endExperiment(SkillingEndReason.INVENTORY_FULL); + } + else if (state == SkillingState.CUTTING && type == ChatMessageType.GAMEMESSAGE && message.contains(NEST_MESSAGE)) + { + nestTicks.add(client.getTickCount()); + } + } + + @Subscribe + public void onGameTick(GameTick tick) + { + int animId = client.getLocalPlayer().getAnimation(); + if (state == SkillingState.CUTTING) + { + if (AXE_ANIMS.containsKey(animId)) + { + axe = AXE_ANIMS.get(animId); + } + else + { + endExperiment(SkillingEndReason.INTERRUPTED); + } + } + else if (animId != -1) + { + endExperiment(SkillingEndReason.INTERRUPTED); + } + else if (state == SkillingState.RECOVERING && client.getTickCount() - lastExperimentEnd >= 4) + { + state = SkillingState.READY; + } + else if (state == SkillingState.CLICKED && client.getTickCount() - lastExperimentEnd >= 20) + { + state = SkillingState.READY; + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) + { + MenuAction menuAction = menuOptionClicked.getMenuAction(); + int id = menuOptionClicked.getId(); + if (state == SkillingState.READY && menuAction == MenuAction.GAME_OBJECT_FIRST_OPTION && TREE_OBJECTS.contains(id)) + { + state = SkillingState.CLICKED; + lastExperimentEnd = client.getTickCount(); + treeId = id; + treeLocation = WorldPoint.fromScene(client, menuOptionClicked.getActionParam(), menuOptionClicked.getWidgetId(), client.getPlane()); + } + else + { + endExperiment(SkillingEndReason.INTERRUPTED); + } + } + + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned event) + { + if (state != SkillingState.CUTTING) + { + return; + } + if (treeId == event.getGameObject().getId() && treeLocation.equals(event.getTile().getWorldLocation())) + { + endExperiment(SkillingEndReason.DEPLETED); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/WoodcuttingData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/WoodcuttingData.java new file mode 100644 index 0000000000..2cb6ccb2f2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/woodcutting/WoodcuttingData.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.woodcutting; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.crowdsourcing.skilling.SkillingEndReason; + +@Data +@AllArgsConstructor +public class WoodcuttingData +{ + private final int level; + private final int startTick; + private final int endTick; + private final List chopTicks; + private final List nestTicks; + private final int axe; + private final int treeId; + private final WorldPoint treeLocation; + private final SkillingEndReason reason; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/CrowdsourcingZMI.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/CrowdsourcingZMI.java new file mode 100644 index 0000000000..2f76cd4741 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/CrowdsourcingZMI.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.zmi; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; +import com.google.common.collect.Multisets; +import java.util.Arrays; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.ItemContainer; +import net.runelite.api.MenuAction; +import net.runelite.api.Skill; +import net.runelite.api.Varbits; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.StatChanged; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.crowdsourcing.CrowdsourcingManager; + +public class CrowdsourcingZMI +{ + private static final String CHAT_MESSAGE_ZMI = "You bind the temple's power into runes."; + + @Inject + private CrowdsourcingManager manager; + + @Inject + private Client client; + + private int gameTickZMI = -1; + private int illegalActionTick = -1; + private int previousRunecraftXp = 0; + private int runecraftXpGained = 0; + private Multiset previousInventorySnapshot; + + private Multiset getInventorySnapshot() + { + final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); + Multiset inventorySnapshot = HashMultiset.create(); + + if (inventory != null) + { + Arrays.stream(inventory.getItems()) + .forEach(item -> inventorySnapshot.add(item.getId(), item.getQuantity())); + } + + return inventorySnapshot; + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) + { + MenuAction action = menuOptionClicked.getMenuAction(); + + switch (action) + { + case ITEM_FIRST_OPTION: + case ITEM_SECOND_OPTION: + case ITEM_THIRD_OPTION: + case ITEM_FOURTH_OPTION: + case ITEM_FIFTH_OPTION: + case GROUND_ITEM_FIRST_OPTION: + case GROUND_ITEM_SECOND_OPTION: + case GROUND_ITEM_THIRD_OPTION: + case GROUND_ITEM_FOURTH_OPTION: + case GROUND_ITEM_FIFTH_OPTION: + case ITEM_DROP: + illegalActionTick = client.getTickCount(); + break; + } + } + + @Subscribe + public void onChatMessage(ChatMessage chatMessage) + { + if (chatMessage.getMessage().equals(CHAT_MESSAGE_ZMI)) + { + gameTickZMI = client.getTickCount(); + previousRunecraftXp = client.getSkillExperience(Skill.RUNECRAFT); + previousInventorySnapshot = getInventorySnapshot(); + } + } + + @Subscribe + public void onStatChanged(StatChanged statChanged) + { + if (gameTickZMI == client.getTickCount()) + { + int currentRunecraftXp = statChanged.getXp(); + runecraftXpGained = currentRunecraftXp - previousRunecraftXp; + } + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + int itemContainerChangedTick = client.getTickCount(); + + if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY) || gameTickZMI != itemContainerChangedTick) + { + return; + } + + int tickDelta = itemContainerChangedTick - illegalActionTick; + boolean ardougneMedium = client.getVar(Varbits.DIARY_ARDOUGNE_MEDIUM) == 1; + int runecraftBoostedLevel = client.getBoostedSkillLevel(Skill.RUNECRAFT); + Multiset currentInventorySnapshot = getInventorySnapshot(); + final Multiset itemsReceived = Multisets.difference(currentInventorySnapshot, previousInventorySnapshot); + final Multiset itemsRemoved = Multisets.difference(previousInventorySnapshot, currentInventorySnapshot); + + ZMIData data = new ZMIData(tickDelta, ardougneMedium, runecraftBoostedLevel, runecraftXpGained, itemsReceived, itemsRemoved); + manager.storeEvent(data); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/ZMIData.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/ZMIData.java new file mode 100644 index 0000000000..5c51b4d8aa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/zmi/ZMIData.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019, Weird Gloop + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.crowdsourcing.zmi; + +import com.google.common.collect.Multiset; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ZMIData +{ + private final int tickDelta; + private final boolean ardougneMedium; + private final int level; + private final int xpGained; + private final Multiset itemsReceived; + private final Multiset itemsRemoved; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java new file mode 100644 index 0000000000..37208ddd23 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, Kruithne + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.customcursor; + +import java.awt.image.BufferedImage; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.client.util.ImageUtil; + +@Getter +public enum CustomCursor +{ + RS3_GOLD("RS3 Gold", "cursor-rs3-gold.png"), + RS3_SILVER("RS3 Silver", "cursor-rs3-silver.png"), + DRAGON_DAGGER("Dragon Dagger", "cursor-dragon-dagger.png"), + DRAGON_DAGGER_POISON("Dragon Dagger (p)", "cursor-dragon-dagger-p.png"), + TROUT("Trout", "cursor-trout.png"), + DRAGON_SCIMITAR("Dragon Scimitar", "cursor-dragon-scimitar.png"), + EQUIPPED_WEAPON("Equipped Weapon"), + CUSTOM_IMAGE("Custom Image"); + + private final String name; + @Nullable + private final BufferedImage cursorImage; + + CustomCursor(String name) + { + this.name = name; + this.cursorImage = null; + } + + CustomCursor(String name, String icon) + { + this.name = name; + this.cursorImage = ImageUtil.getResourceStreamFromClass(CustomCursorPlugin.class, icon); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorConfig.java new file mode 100644 index 0000000000..cf7075a393 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018, Kruithne + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.customcursor; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("customcursor") +public interface CustomCursorConfig extends Config +{ + @ConfigItem( + keyName = "cursorStyle", + name = "Cursor", + description = "Select which cursor you wish to use" + ) + default CustomCursor selectedCursor() + { + return CustomCursor.RS3_GOLD; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java new file mode 100644 index 0000000000..dfe3f23d60 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018, Kruithne + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.customcursor; + +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.io.File; +import javax.imageio.ImageIO; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.client.RuneLite; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.ClientUI; + +@PluginDescriptor( + name = "Custom Cursor", + description = "Replaces your mouse cursor image", + enabledByDefault = false +) +@Slf4j +public class CustomCursorPlugin extends Plugin +{ + private static final File CUSTOM_IMAGE_FILE = new File(RuneLite.RUNELITE_DIR, "cursor.png"); + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ClientUI clientUI; + + @Inject + private CustomCursorConfig config; + + @Inject + private ItemManager itemManager; + + @Provides + CustomCursorConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(CustomCursorConfig.class); + } + + @Override + protected void startUp() + { + updateCursor(); + } + + @Override + protected void shutDown() + { + clientUI.resetCursor(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("customcursor") && event.getKey().equals("cursorStyle")) + { + updateCursor(); + } + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + if (config.selectedCursor() == CustomCursor.EQUIPPED_WEAPON && event.getContainerId() == InventoryID.EQUIPMENT.getId()) + { + updateCursor(); + } + } + + private void updateCursor() + { + CustomCursor selectedCursor = config.selectedCursor(); + + if (selectedCursor == CustomCursor.CUSTOM_IMAGE) + { + if (CUSTOM_IMAGE_FILE.exists()) + { + try + { + BufferedImage image; + synchronized (ImageIO.class) + { + image = ImageIO.read(CUSTOM_IMAGE_FILE); + } + clientUI.setCursor(image, selectedCursor.getName()); + } + catch (Exception e) + { + log.error("error setting custom cursor", e); + clientUI.resetCursor(); + } + } + else + { + clientUI.resetCursor(); + } + } + else if (selectedCursor == CustomCursor.EQUIPPED_WEAPON) + { + clientThread.invokeLater(() -> + { + final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT); + + if (equipment == null) + { + clientUI.resetCursor(); + return; + } + + Item weapon = equipment.getItem(EquipmentInventorySlot.WEAPON.getSlotIdx()); + if (weapon == null) + { + clientUI.resetCursor(); + return; + } + + final BufferedImage image = itemManager.getImage(weapon.getId()); + + if (weapon.getQuantity() > 0) + { + clientUI.setCursor(image, selectedCursor.getName()); + } + else + { + clientUI.resetCursor(); + } + }); + } + else + { + assert selectedCursor.getCursorImage() != null; + clientUI.setCursor(selectedCursor.getCursorImage(), selectedCursor.getName()); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksConfig.java new file mode 100644 index 0000000000..4ff86534be --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksConfig.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018, Infinitay + * Copyright (c) 2018, Shaun Dreclin + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.dailytaskindicators; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("dailytaskindicators") +public interface DailyTasksConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "showHerbBoxes", + name = "Show Herb Boxes", + description = "Show a message when you can collect your daily herb boxes at NMZ." + ) + default boolean showHerbBoxes() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "showStaves", + name = "Show Claimable Staves", + description = "Show a message when you can collect your daily battlestaves from Zaff." + ) + default boolean showStaves() + { + return true; + } + + @ConfigItem( + position = 3, + keyName = "showEssence", + name = "Show Claimable Essence", + description = "Show a message when you can collect your daily pure essence from Wizard Cromperty." + ) + default boolean showEssence() + { + return false; + } + + @ConfigItem( + position = 4, + keyName = "showRunes", + name = "Show Claimable Random Runes", + description = "Show a message when you can collect your daily random runes from Lundail." + ) + default boolean showRunes() + { + return false; + } + + @ConfigItem( + position = 5, + keyName = "showSand", + name = "Show Claimable Sand", + description = "Show a message when you can collect your daily sand from Bert." + ) + default boolean showSand() + { + return false; + } + + @ConfigItem( + position = 6, + keyName = "showFlax", + name = "Show Claimable Bow Strings", + description = "Show a message when you can convert noted flax to bow strings with the Flax keeper." + ) + default boolean showFlax() + { + return false; + } + + @ConfigItem( + position = 7, + keyName = "showBonemeal", + name = "Show Claimable Bonemeal & Slime", + description = "Show a message when you can collect bonemeal & slime from Robin." + ) + default boolean showBonemeal() + { + return false; + } + + @ConfigItem( + position = 8, + keyName = "showDynamite", + name = "Show Claimable Dynamite", + description = "Show a message when you can collect Dynamite from Thirus." + ) + default boolean showDynamite() + { + return false; + } + + @ConfigItem( + position = 9, + keyName = "showArrows", + name = "Show Claimable Ogre Arrows", + description = "Show a message when you can collect ogre arrows from Rantz." + ) + default boolean showArrows() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksPlugin.java new file mode 100644 index 0000000000..8e1757b26b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dailytaskindicators/DailyTasksPlugin.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2018, Infinitay + * Copyright (c) 2018-2019, Shaun Dreclin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.dailytaskindicators; + +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarPlayer; +import net.runelite.api.Varbits; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.vars.AccountType; +import net.runelite.client.chat.ChatColorType; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Daily Task Indicator", + description = "Show chat notifications for daily tasks upon login" +) +public class DailyTasksPlugin extends Plugin +{ + private static final int ONE_DAY = 86400000; + + private static final String HERB_BOX_MESSAGE = "You have herb boxes waiting to be collected at NMZ."; + private static final int HERB_BOX_MAX = 15; + private static final int HERB_BOX_COST = 9500; + private static final String STAVES_MESSAGE = "You have battlestaves waiting to be collected from Zaff."; + private static final String ESSENCE_MESSAGE = "You have essence waiting to be collected from Wizard Cromperty."; + private static final String RUNES_MESSAGE = "You have random runes waiting to be collected from Lundail."; + private static final String SAND_MESSAGE = "You have sand waiting to be collected from Bert."; + private static final int SAND_QUEST_COMPLETE = 160; + private static final String FLAX_MESSAGE = "You have bowstrings waiting to be converted from flax from the Flax keeper."; + private static final String ARROWS_MESSAGE = "You have ogre arrows waiting to be collected from Rantz."; + private static final String BONEMEAL_MESSAGE = "You have bonemeal and slime waiting to be collected from Robin."; + private static final int BONEMEAL_PER_DIARY = 13; + private static final String DYNAMITE_MESSAGE = "You have dynamite waiting to be collected from Thirus."; + + @Inject + private Client client; + + @Inject + private DailyTasksConfig config; + + @Inject + private ChatMessageManager chatMessageManager; + + private long lastReset; + private boolean loggingIn; + + @Provides + DailyTasksConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(DailyTasksConfig.class); + } + + @Override + public void startUp() + { + loggingIn = true; + } + + @Override + public void shutDown() + { + lastReset = 0L; + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGING_IN) + { + loggingIn = true; + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + long currentTime = System.currentTimeMillis(); + boolean dailyReset = !loggingIn && currentTime - lastReset > ONE_DAY; + + if ((dailyReset || loggingIn) + && client.getVar(VarClientInt.MEMBERSHIP_STATUS) == 1) + { + // Round down to the nearest day + lastReset = (long) Math.floor(currentTime / ONE_DAY) * ONE_DAY; + loggingIn = false; + + if (config.showHerbBoxes()) + { + checkHerbBoxes(dailyReset); + } + + if (config.showStaves()) + { + checkStaves(dailyReset); + } + + if (config.showEssence()) + { + checkEssence(dailyReset); + } + + if (config.showRunes()) + { + checkRunes(dailyReset); + } + + if (config.showSand()) + { + checkSand(dailyReset); + } + + if (config.showFlax()) + { + checkFlax(dailyReset); + } + + if (config.showBonemeal()) + { + checkBonemeal(dailyReset); + } + + if (config.showDynamite()) + { + checkDynamite(dailyReset); + } + + if (config.showArrows()) + { + checkArrows(dailyReset); + } + } + } + + private void checkHerbBoxes(boolean dailyReset) + { + if (client.getAccountType() == AccountType.NORMAL + && client.getVar(VarPlayer.NMZ_REWARD_POINTS) >= HERB_BOX_COST + && (client.getVar(Varbits.DAILY_HERB_BOXES_COLLECTED) < HERB_BOX_MAX + || dailyReset)) + { + sendChatMessage(HERB_BOX_MESSAGE); + } + } + + private void checkStaves(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_VARROCK_EASY) == 1 + && (client.getVar(Varbits.DAILY_STAVES_COLLECTED) == 0 + || dailyReset)) + { + sendChatMessage(STAVES_MESSAGE); + } + } + + private void checkEssence(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_ARDOUGNE_MEDIUM) == 1 + && (client.getVar(Varbits.DAILY_ESSENCE_COLLECTED) == 0 + || dailyReset)) + { + sendChatMessage(ESSENCE_MESSAGE); + } + } + + private void checkRunes(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_WILDERNESS_EASY) == 1 + && (client.getVar(Varbits.DAILY_RUNES_COLLECTED) == 0 + || dailyReset)) + { + sendChatMessage(RUNES_MESSAGE); + } + } + + private void checkSand(boolean dailyReset) + { + if (client.getVar(Varbits.QUEST_THE_HAND_IN_THE_SAND) >= SAND_QUEST_COMPLETE + && (client.getVar(Varbits.DAILY_SAND_COLLECTED) == 0 + || dailyReset)) + { + sendChatMessage(SAND_MESSAGE); + } + } + + private void checkFlax(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_KANDARIN_EASY) == 1 + && (client.getVar(Varbits.DAILY_FLAX_STATE) == 0 + || dailyReset)) + { + sendChatMessage(FLAX_MESSAGE); + } + } + + private void checkArrows(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_WESTERN_EASY) == 1 + && (client.getVar(Varbits.DAILY_ARROWS_STATE) == 0 + || dailyReset)) + { + sendChatMessage(ARROWS_MESSAGE); + } + } + + private void checkBonemeal(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_MORYTANIA_MEDIUM) == 1) + { + int collected = client.getVar(Varbits.DAILY_BONEMEAL_STATE); + int max = BONEMEAL_PER_DIARY; + if (client.getVar(Varbits.DIARY_MORYTANIA_HARD) == 1) + { + max += BONEMEAL_PER_DIARY; + if (client.getVar(Varbits.DIARY_MORYTANIA_ELITE) == 1) + { + max += BONEMEAL_PER_DIARY; + } + } + if (collected < max || dailyReset) + { + sendChatMessage(BONEMEAL_MESSAGE); + } + } + } + + private void checkDynamite(boolean dailyReset) + { + if (client.getVar(Varbits.DIARY_KOUREND_MEDIUM) == 1 + && (client.getVar(Varbits.DAILY_DYNAMITE_COLLECTED) == 0 + || dailyReset)) + { + sendChatMessage(DYNAMITE_MESSAGE); + } + } + + private void sendChatMessage(String chatMessage) + { + final String message = new ChatMessageBuilder() + .append(ChatColorType.HIGHLIGHT) + .append(chatMessage) + .build(); + + chatMessageManager.queue( + QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldConfig.java new file mode 100644 index 0000000000..d53ede7a7b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldConfig.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.defaultworld; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(DefaultWorldConfig.GROUP) +public interface DefaultWorldConfig extends Config +{ + String GROUP = "defaultworld"; + + @ConfigItem( + keyName = "defaultWorld", + name = "Default world", + description = "World to use as default one" + ) + default int getWorld() + { + return 0; + } + + @ConfigItem( + keyName = "useLastWorld", + name = "Use Last World", + description = "Use the last world you used as the default" + ) + default boolean useLastWorld() + { + return false; + } + + @ConfigItem( + keyName = "lastWorld", + name = "", + description = "", + hidden = true + ) + default int lastWorld() + { + return 0; + } + + @ConfigItem( + keyName = "lastWorld", + name = "", + description = "" + ) + void lastWorld(int lastWorld); +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java new file mode 100644 index 0000000000..9010f9a8d0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.defaultworld; + +import com.google.inject.Provides; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.events.GameStateChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.SessionOpen; +import net.runelite.client.game.WorldService; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.WorldUtil; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldResult; + +@PluginDescriptor( + name = "Default World", + description = "Enable a default world to be selected when launching the client", + tags = {"home"} +) +@Slf4j +public class DefaultWorldPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private DefaultWorldConfig config; + + @Inject + private WorldService worldService; + + private int worldCache; + private boolean worldChangeRequired; + + @Override + protected void startUp() throws Exception + { + worldChangeRequired = true; + applyWorld(); + } + + @Override + protected void shutDown() throws Exception + { + worldChangeRequired = true; + changeWorld(worldCache); + } + + @Provides + DefaultWorldConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(DefaultWorldConfig.class); + } + + @Subscribe + public void onSessionOpen(SessionOpen event) + { + worldChangeRequired = true; + applyWorld(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGED_IN) + { + config.lastWorld(client.getWorld()); + } + + applyWorld(); + } + + private void changeWorld(int newWorld) + { + if (!worldChangeRequired || client.getGameState() != GameState.LOGIN_SCREEN) + { + return; + } + + worldChangeRequired = false; + int correctedWorld = newWorld < 300 ? newWorld + 300 : newWorld; + + // Old School RuneScape worlds start on 301 so don't even bother trying to find lower id ones + // and also do not try to set world if we are already on it + if (correctedWorld <= 300 || client.getWorld() == correctedWorld) + { + return; + } + + final WorldResult worldResult = worldService.getWorlds(); + + if (worldResult == null) + { + log.warn("Failed to lookup worlds."); + return; + } + + final World world = worldResult.findWorld(correctedWorld); + + if (world != null) + { + final net.runelite.api.World rsWorld = client.createWorld(); + rsWorld.setActivity(world.getActivity()); + rsWorld.setAddress(world.getAddress()); + rsWorld.setId(world.getId()); + rsWorld.setPlayerCount(world.getPlayers()); + rsWorld.setLocation(world.getLocation()); + rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes())); + + client.changeWorld(rsWorld); + log.debug("Applied new world {}", correctedWorld); + } + else + { + log.warn("World {} not found.", correctedWorld); + } + } + + private void applyWorld() + { + if (worldCache == 0) + { + worldCache = client.getWorld(); + log.debug("Stored old world {}", worldCache); + } + + final int newWorld = !config.useLastWorld() ? config.getWorld() : config.lastWorld(); + changeWorld(newWorld); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java new file mode 100644 index 0000000000..743a04d6d3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordAreaType.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, PandahRS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +enum DiscordAreaType +{ + BOSSES, + CITIES, + DUNGEONS, + MINIGAMES, + RAIDS, + REGIONS +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java new file mode 100644 index 0000000000..770fe49cde --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordConfig.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +import lombok.AllArgsConstructor; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; + +@ConfigGroup("discord") +public interface DiscordConfig extends Config +{ + @AllArgsConstructor + enum ElapsedTimeType + { + TOTAL("Total elapsed time"), + ACTIVITY("Per activity"), + HIDDEN("Hide elapsed time"); + + private final String value; + + @Override + public String toString() + { + return value; + } + } + + @ConfigItem( + keyName = "elapsedTime", + name = "Elapsed Time", + description = "Configures elapsed time shown.", + position = 1 + ) + default ElapsedTimeType elapsedTimeType() + { + return ElapsedTimeType.ACTIVITY; + } + + @ConfigItem( + keyName = "actionTimeout", + name = "Activity timeout", + description = "Configures after how long of not updating activity will be reset (in minutes)", + position = 2 + ) + @Units(Units.MINUTES) + default int actionTimeout() + { + return 5; + } + + @ConfigItem( + keyName = "showSkillActivity", + name = "Skilling", + description = "Show your activity while training skills", + position = 3 + ) + default boolean showSkillingActivity() + { + return true; + } + + @ConfigItem( + keyName = "showBossActivity", + name = "Bosses", + description = "Show your activity and location while at bosses", + position = 4 + ) + default boolean showBossActivity() + { + return true; + } + + @ConfigItem( + keyName = "showCityActivity", + name = "Cities", + description = "Show your activity and location while in cities", + position = 5 + ) + default boolean showCityActivity() + { + return true; + } + + @ConfigItem( + keyName = "showDungeonActivity", + name = "Dungeons", + description = "Show your activity and location while in dungeons", + position = 6 + ) + default boolean showDungeonActivity() + { + return true; + } + + @ConfigItem( + keyName = "showMinigameActivity", + name = "Minigames", + description = "Show your activity and location while in minigames", + position = 7 + ) + default boolean showMinigameActivity() + { + return true; + } + + @ConfigItem( + keyName = "showRaidingActivity", + name = "Raids", + description = "Show your activity and location while in Raids", + position = 8 + ) + default boolean showRaidingActivity() + { + return true; + } + + @ConfigItem( + keyName = "showRegionsActivity", + name = "Regions", + description = "Show your activity and location while in other regions", + position = 9 + ) + default boolean showRegionsActivity() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java new file mode 100644 index 0000000000..d5a728a368 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, PandahRS + * Copyright (c) 2020, Brooklyn + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.Skill; +import net.runelite.api.Varbits; + +@AllArgsConstructor +@Getter +enum DiscordGameEventType +{ + + IN_MENU("In Menu", -3, true, true, true, false, true), + IN_GAME("In Game", -3, true, false, false, false, true), + PLAYING_DEADMAN("Playing Deadman Mode", -3), + PLAYING_PVP("Playing in a PVP world", -3), + TRAINING_ATTACK(Skill.ATTACK), + TRAINING_DEFENCE(Skill.DEFENCE), + TRAINING_STRENGTH(Skill.STRENGTH), + TRAINING_HITPOINTS(Skill.HITPOINTS, -1), + TRAINING_SLAYER(Skill.SLAYER, 1), + TRAINING_RANGED(Skill.RANGED), + TRAINING_MAGIC(Skill.MAGIC), + TRAINING_PRAYER(Skill.PRAYER), + TRAINING_COOKING(Skill.COOKING), + TRAINING_WOODCUTTING(Skill.WOODCUTTING), + TRAINING_FLETCHING(Skill.FLETCHING), + TRAINING_FISHING(Skill.FISHING, 1), + TRAINING_FIREMAKING(Skill.FIREMAKING), + TRAINING_CRAFTING(Skill.CRAFTING), + TRAINING_SMITHING(Skill.SMITHING), + TRAINING_MINING(Skill.MINING), + TRAINING_HERBLORE(Skill.HERBLORE), + TRAINING_AGILITY(Skill.AGILITY), + TRAINING_THIEVING(Skill.THIEVING), + TRAINING_FARMING(Skill.FARMING), + TRAINING_RUNECRAFT(Skill.RUNECRAFT), + TRAINING_HUNTER(Skill.HUNTER), + TRAINING_CONSTRUCTION(Skill.CONSTRUCTION), + + // Bosses + BOSS_ABYSSAL_SIRE("Abyssal Sire", DiscordAreaType.BOSSES, 11851, 11850, 12363, 12362), + BOSS_CERBERUS("Cerberus", DiscordAreaType.BOSSES, 4883, 5140, 5395), + BOSS_COMMANDER_ZILYANA("Commander Zilyana", DiscordAreaType.BOSSES, 11602), + BOSS_CORP("Corporeal Beast", DiscordAreaType.BOSSES, 11842, 11844), + BOSS_DKS("Dagannoth Kings", DiscordAreaType.BOSSES, 11588, 11589), + BOSS_GENERAL_GRAARDOR("General Graardor", DiscordAreaType.BOSSES, 11347), + BOSS_GIANT_MOLE("Giant Mole", DiscordAreaType.BOSSES, 6993, 6992), + BOSS_GROTESQUE_GUARDIANS("Grotesque Guardians", DiscordAreaType.BOSSES, 6727), + BOSS_HESPORI("Hespori", DiscordAreaType.BOSSES, 5021), + BOSS_HYDRA("Alchemical Hydra", DiscordAreaType.BOSSES, 5536), + BOSS_KQ("Kalphite Queen", DiscordAreaType.BOSSES, 13972), + BOSS_KRAKEN("Kraken", DiscordAreaType.BOSSES, 9116), + BOSS_KREEARRA("Kree'arra", DiscordAreaType.BOSSES, 11346), + BOSS_KRIL_TSUTSAROTH("K'ril Tsutsaroth", DiscordAreaType.BOSSES, 11603), + BOSS_NIGHTMARE("Nightmare of Ashihama", DiscordAreaType.BOSSES, 15515), + BOSS_SARACHNIS("Sarachnis", DiscordAreaType.BOSSES, 7322), + BOSS_SKOTIZO("Skotizo", DiscordAreaType.BOSSES, 6810), + BOSS_SMOKE_DEVIL("Thermonuclear smoke devil", DiscordAreaType.BOSSES, 9363, 9619), + BOSS_VORKATH("Vorkath", DiscordAreaType.BOSSES, 9023), + BOSS_WINTERTODT("Wintertodt", DiscordAreaType.BOSSES, 6462), + BOSS_ZALCANO("Zalcano", DiscordAreaType.BOSSES, 12126), + BOSS_ZULRAH("Zulrah", DiscordAreaType.BOSSES, 9007), + + // Cities + CITY_AL_KHARID("Al Kharid" , DiscordAreaType.CITIES, 13105, 13106), + CITY_ARCEUUS_HOUSE("Arceuus" , DiscordAreaType.CITIES, 6458, 6459, 6460, 6714, 6715), + CITY_ARDOUGNE("Ardougne" , DiscordAreaType.CITIES, 9779, 9780, 10035, 10036, 10291, 10292, 10547, 10548), + CITY_BANDIT_CAMP("Bandit Camp" , DiscordAreaType.CITIES, 12590), + CITY_BARBARIAN_OUTPOST("Barbarian Outpost", DiscordAreaType.CITIES, 10039), + CITY_BARBARIAN_VILLAGE("Barbarian Village" , DiscordAreaType.CITIES, 12341), + CITY_BEDABIN_CAMP("Bedabin Camp" , DiscordAreaType.CITIES, 12591), + CITY_BRIMHAVEN("Brimhaven" , DiscordAreaType.CITIES, 11057, 11058), + CITY_BURGH_DE_ROTT("Burgh de Rott" , DiscordAreaType.CITIES, 13874, 13873, 14130, 14129), + CITY_BURTHORPE("Burthorpe" , DiscordAreaType.CITIES, 11319, 11575), + CITY_CANIFIS("Canifis" , DiscordAreaType.CITIES, 13878), + CITY_CATHERBY("Catherby" , DiscordAreaType.CITIES, 11317, 11318, 11061), + CITY_CORSAIR_COVE("Corsair Cove" , DiscordAreaType.CITIES, 10028, 10284), + CITY_DARKMEYER("Darkmeyer", DiscordAreaType.CITIES, 14388, 14644), + CITY_DORGESH_KAAN("Dorgesh-Kaan" , DiscordAreaType.CITIES, 10835, 10834), + CITY_DRAYNOR("Draynor" , DiscordAreaType.CITIES, 12338, 12339), + CITY_EDGEVILLE("Edgeville" , DiscordAreaType.CITIES, 12342), + CITY_ENTRANA("Entrana" , DiscordAreaType.CITIES, 11060, 11316), + CITY_ETCETERIA("Etceteria", DiscordAreaType.CITIES, 10300), + CITY_FALADOR("Falador" , DiscordAreaType.CITIES, 11828, 11572, 11827, 12084), + CITY_GUTANOTH("Gu'Tanoth" , DiscordAreaType.CITIES, 10031), + CITY_GWENITH("Gwenith", DiscordAreaType.CITIES, 8757), + CITY_HOSIDIUS_HOUSE("Hosidius" , DiscordAreaType.CITIES, 6710, 6711, 6712, 6455, 6456, 6966, 6967, 6968, 7221, 7223, 7224, 7478, 7479), + CITY_JATIZSO("Jatizso" , DiscordAreaType.CITIES, 9531), + CITY_KELDAGRIM("Keldagrim" , DiscordAreaType.CITIES, 11423, 11422, 11679, 11678), + CITY_LANDS_END("Land's End", DiscordAreaType.CITIES, 5941), + CITY_LLETYA("Lletya" , DiscordAreaType.CITIES, 9265), + CITY_LOVAKENGJ_HOUSE("Lovakengj" , DiscordAreaType.CITIES, 5692, 5691, 5947, 6203, 6202, 5690, 5946), + CITY_LUMBRIDGE("Lumbridge" , DiscordAreaType.CITIES, 12850), + CITY_LUNAR_ISLE("Lunar Isle" , DiscordAreaType.CITIES, 8253, 8252, 8509, 8508), + CITY_MARIM("Marim", DiscordAreaType.REGIONS, 11051), + CITY_MEIYERDITCH("Meiyerditch" , DiscordAreaType.CITIES, 14132, 14387, 14386, 14385), + CITY_MISCELLANIA("Miscellania" , DiscordAreaType.CITIES, 10044), + CITY_MOR_UL_REK("Mor Ul Rek" , DiscordAreaType.CITIES, 9808, 9807, 10064, 10063), + CITY_MORTTON("Mort'ton" , DiscordAreaType.CITIES, 13875), + CITY_MOS_LE_HARMLESS("Mos Le'Harmless" , DiscordAreaType.CITIES, 14638, 14639, 14894, 14895, 15151, 15406, 15407), + CITY_MOUNT_KARUULM("Mount Karuulm", DiscordAreaType.CITIES, 5179, 4923, 5180), + CITY_MOUNTAIN_CAMP("Mountain Camp", DiscordAreaType.CITIES, 11065), + CITY_MYNYDD("Mynydd", DiscordAreaType.CITIES, 8501), + CITY_NARDAH("Nardah" , DiscordAreaType.CITIES, 13613), + CITY_NEITIZNOT("Neitiznot" , DiscordAreaType.CITIES, 9275), + CITY_PISCARILIUS_HOUSE("Port Piscarilius" , DiscordAreaType.CITIES, 6971, 7227, 6970, 7226), + CITY_PISCATORIS("Piscatoris" , DiscordAreaType.CITIES, 9273), + CITY_POLLNIVNEACH("Pollnivneach" , DiscordAreaType.CITIES, 13358), + CITY_PORT_KHAZARD("Port Khazard" , DiscordAreaType.CITIES, 10545), + CITY_PORT_PHASMATYS("Port Phasmatys" , DiscordAreaType.CITIES, 14646), + CITY_PORT_SARIM("Port Sarim" , DiscordAreaType.CITIES, 12081, 12082), + CITY_PRIFDDINAS("Prifddinas", DiscordAreaType.CITIES, 8499, 8500, 8755, 8756, 9011, 9012, 9013, 12894, 12895, 13150, 13151), + CITY_RELLEKKA("Rellekka" , DiscordAreaType.CITIES, 10297, 10553), + CITY_RIMMINGTON("Rimmington" , DiscordAreaType.CITIES, 11826, 11570), + CITY_SEERS_VILLAGE("Seers' Village" , DiscordAreaType.CITIES, 10806), + CITY_SHAYZIEN_HOUSE("Shayzien" , DiscordAreaType.CITIES, 5944, 5943, 6200, 6199, 5686, 5687, 5688, 5689, 5945), + CITY_SHILO_VILLAGE("Shilo Village" , DiscordAreaType.CITIES, 11310), + CITY_SLEPE("Slepe", DiscordAreaType.CITIES, 14643, 14899, 14900, 14901), + CITY_SOPHANEM("Sophanem" , DiscordAreaType.CITIES, 13099), + CITY_TAI_BWO_WANNAI("Tai Bwo Wannai" , DiscordAreaType.CITIES, 11056, 11055), + CITY_TAVERLEY("Taverley" , DiscordAreaType.CITIES, 11574, 11573), + CITY_TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold" , DiscordAreaType.CITIES, 9525, 9526, 9782, 9781), + CITY_TREE_GNOME_VILLAGE("Tree Gnome Village" , DiscordAreaType.CITIES, 10033), + CITY_TROLL_STRONGHOLD("Troll Stronghold" , DiscordAreaType.CITIES, 11321, 11421), + CITY_UZER("Uzer" , DiscordAreaType.CITIES, 13872), + CITY_VARROCK("Varrock" , DiscordAreaType.CITIES, 12596, 12597, 12852, 12853, 12854, 13108, 13109, 13110), + CITY_VER_SINHAZA("Ver Sinhaza", DiscordAreaType.CITIES, 14642), + CITY_VOID_OUTPOST("Void Knights' Outpost", DiscordAreaType.CITIES, 10537), + CITY_WEISS("Weiss", DiscordAreaType.CITIES, 11325, 11581), + CITY_WITCHHAVEN("Witchaven" , DiscordAreaType.CITIES, 10803), + CITY_YANILLE("Yanille" , DiscordAreaType.CITIES, 10288, 10032), + CITY_ZANARIS("Zanaris" , DiscordAreaType.CITIES, 9285, 9541, 9540, 9797), + CITY_ZULANDRA("Zul-Andra" , DiscordAreaType.CITIES, 8495, 8751), + + // Dungeons + DUNGEON_ABANDONED_MINE("Abandoned Mine", DiscordAreaType.DUNGEONS, 13718, 11079, 11078, 11077, 10823, 10822, 10821), + DUNGEON_AH_ZA_RHOON("Ah Za Rhoon", DiscordAreaType.DUNGEONS, 11666), + DUNGEON_ANCIENT_CAVERN("Ancient Cavern", DiscordAreaType.DUNGEONS, 6483, 6995), + DUNGEON_APE_ATOLL("Ape Atoll Dungeon", DiscordAreaType.DUNGEONS, 11150, 10894), + DUNGEON_APE_ATOLL_BANANA_PLANTATION("Ape Atoll Banana Plantation", DiscordAreaType.DUNGEONS, 10895), + DUNGEON_ARDY_SEWERS("Ardougne Sewers", DiscordAreaType.DUNGEONS, 10136, 10647), + DUNGEON_ASGARNIAN_ICE_CAVES("Asgarnian Ice Caves", DiscordAreaType.DUNGEONS, 11925, 12181), + DUNGEON_BERVIRIUS_TOMB("Tomb of Bervirius", DiscordAreaType.DUNGEONS, 11154), + DUNGEON_BRIMHAVEN("Brimhaven Dungeon", DiscordAreaType.DUNGEONS, 10901, 10900, 10899, 10645, 10644, 10643), + DUNGEON_BRINE_RAT_CAVERN("Brine Rat Cavern", DiscordAreaType.DUNGEONS, 10910), + DUNGEON_CATACOMBS_OF_KOUREND("Catacombs of Kourend", DiscordAreaType.DUNGEONS, 6557, 6556, 6813, 6812), + DUNGEON_CHAMPIONS_CHALLENGE("Champions' Challenge", DiscordAreaType.DUNGEONS, 12696), + DUNGEON_CHAOS_DRUID_TOWER("Chaos Druid Tower", DiscordAreaType.DUNGEONS, 10392), + DUNGEON_CHASM_OF_FIRE("Chasm of Fire", DiscordAreaType.DUNGEONS, 5789), + DUNGEON_CHASM_OF_TEARS("Chasm of Tears", DiscordAreaType.DUNGEONS, 12948), + DUNGEON_CHINCHOMPA("Chinchompa Hunting Ground", DiscordAreaType.DUNGEONS, 10129), + DUNGEON_CLOCK_TOWER("Clock Tower Basement", DiscordAreaType.DUNGEONS, 10390), + DUNGEON_CORSAIR_COVE("Corsair Cove Dungeon", DiscordAreaType.DUNGEONS, 8076, 8332), + DUNGEON_CRABCLAW_CAVES("Crabclaw Caves", DiscordAreaType.DUNGEONS, 6553, 6809), + DUNGEON_CRANDOR("Crandor Dungeon", DiscordAreaType.DUNGEONS, 11414), + DUNGEON_CRASH_SITE_CAVERN("Crash Site Cavern", DiscordAreaType.DUNGEONS, 8280, 8536), + DUNGEON_DAEYALT_ESSENCE_MINE("Daeyalt Essence Mine", DiscordAreaType.DUNGEONS, 14744), + DUNGEON_DIGSITE("Digsite Dungeon", DiscordAreaType.DUNGEONS, 13464, 13465), + DUNGEON_DORGESHKAAN("Dorgesh-Kaan South Dungeon", DiscordAreaType.DUNGEONS, 10833), + DUNGEON_DORGESHUUN_MINES("Dorgeshuun Mines", DiscordAreaType.DUNGEONS, 12950, 13206), + DUNGEON_DRAYNOR_SEWERS("Draynor Sewers", DiscordAreaType.DUNGEONS, 12439, 12438), + DUNGEON_DWARVEN_MINES("Dwarven Mines", DiscordAreaType.DUNGEONS, 12185, 12184, 12183), + DUNGEON_EAGLES_PEAK("Eagles' Peak Dungeon", DiscordAreaType.DUNGEONS, 8013), + DUNGEON_ECTOFUNTUS("Ectofuntus", DiscordAreaType.DUNGEONS, 14746), + DUNGEON_EDGEVILLE("Edgeville Dungeon", DiscordAreaType.DUNGEONS, 12441, 12442, 12443, 12698), + DUNGEON_ELEMENTAL_WORKSHOP("Elemental Workshop", DiscordAreaType.DUNGEONS, 10906, 7760), + DUNGEON_ENAKHRAS_TEMPLE("Enakhra's Temple", DiscordAreaType.DUNGEONS, 12423), + DUNGEON_EVIL_CHICKENS_LAIR("Evil Chicken's Lair", DiscordAreaType.DUNGEONS, 9796), + DUNGEON_EXPERIMENT_CAVE("Experiment Cave", DiscordAreaType.DUNGEONS, 14235, 13979), + DUNGEON_FEROX_ENCLAVE("Ferox Enclave Dungeon", DiscordAreaType.DUNGEONS, 12700), + DUNGEON_FORTHOS("Forthos Dungeon", DiscordAreaType.DUNGEONS, 7323), + DUNGEON_FREMENNIK_SLAYER("Fremennik Slayer Dungeon", DiscordAreaType.DUNGEONS, 10907, 10908, 11164), + DUNGEON_GLARIALS_TOMB("Glarial's Tomb", DiscordAreaType.DUNGEONS, 10137), + DUNGEON_GOBLIN_CAVE("Goblin Cave", DiscordAreaType.DUNGEONS, 10393), + DUNGEON_GRAND_TREE_TUNNELS("Grand Tree Tunnels", DiscordAreaType.DUNGEONS, 9882), + DUNGEON_HAM_HIDEOUT("H.A.M. Hideout", DiscordAreaType.DUNGEONS, 12694), + DUNGEON_HAM_STORE_ROOM("H.A.M. Store room", DiscordAreaType.DUNGEONS, 10321), + DUNGEON_HEROES_GUILD("Heroes' Guild Mine", DiscordAreaType.DUNGEONS, 11674), + DUNGEON_IORWERTH("Iorwerth Dungeon", DiscordAreaType.DUNGEONS, 12737, 12738, 12993, 12994), + DUNGEON_JATIZSO_MINES("Jatizso Mines", DiscordAreaType.DUNGEONS, 9631), + DUNGEON_JIGGIG_BURIAL_TOMB("Jiggig Burial Tomb", DiscordAreaType.DUNGEONS, 9875, 9874), + DUNGEON_JOGRE("Jogre Dungeon", DiscordAreaType.DUNGEONS, 11412), + DUNGEON_KARAMJA("Karamja Dungeon", DiscordAreaType.DUNGEONS, 11413), + DUNGEON_KARUULM("Karuulm Slayer Dungeon", DiscordAreaType.DUNGEONS, 5280, 5279, 5023, 5535, 5022, 4766, 4510, 4511, 4767, 4768, 4512), + DUNGEON_KGP_HEADQUARTERS("KGP Headquarters", DiscordAreaType.DUNGEONS, 10658), + DUNGEON_KRUK("Kruk's Dungeon", DiscordAreaType.DUNGEONS, 9358, 9359, 9360, 9615, 9616, 9871, 10125, 10126, 10127, 10128, 10381, 10382, 10383, 10384, 10637, 10638, 10639, 10640), + DUNGEON_LEGENDS_GUILD("Legends' Guild Dungeon", DiscordAreaType.DUNGEONS, 10904), + DUNGEON_LIGHTHOUSE("Lighthouse", DiscordAreaType.DUNGEONS, 10140), + DUNGEON_LIZARDMAN_CAVES("Lizardman Caves", DiscordAreaType.DUNGEONS, 5275), + DUNGEON_LIZARDMAN_TEMPLE("Lizardman Temple", DiscordAreaType.DUNGEONS, 5277), + DUNGEON_LUMBRIDGE_SWAMP_CAVES("Lumbridge Swamp Caves", DiscordAreaType.DUNGEONS, 12693, 12949), + DUNGEON_LUNAR_ISLE_MINE("Lunar Isle Mine", DiscordAreaType.DUNGEONS, 9377), + DUNGEON_MANIACAL_HUNTER("Maniacal Monkey Hunter Area", DiscordAreaType.DUNGEONS, 11662), + DUNGEON_MEIYERDITCH_MINE("Meiyerditch Mine", DiscordAreaType.DUNGEONS, 9544), + DUNGEON_MISCELLANIA("Miscellania Dungeon", DiscordAreaType.DUNGEONS, 10144, 10400), + DUNGEON_MOGRE_CAMP("Mogre Camp", DiscordAreaType.DUNGEONS, 11924), + DUNGEON_MOS_LE_HARMLESS_CAVES("Mos Le'Harmless Caves", DiscordAreaType.DUNGEONS, 14994, 14995, 15251), + DUNGEON_MOTHERLODE_MINE("Motherlode Mine", DiscordAreaType.DUNGEONS, 14679, 14680, 14681, 14935, 14936, 14937, 15191, 15192, 15193), + DUNGEON_MOURNER_TUNNELS("Mourner Tunnels", DiscordAreaType.DUNGEONS, 7752, 8008), + DUNGEON_MOUSE_HOLE("Mouse Hole", DiscordAreaType.DUNGEONS, 9046), + DUNGEON_MYREDITCH_LABORATORIES("Myreditch Laboratories", DiscordAreaType.DUNGEONS, 14232, 14233, 14487, 14488), + DUNGEON_MYREQUE("Myreque Hideout", DiscordAreaType.DUNGEONS, 13721, 13974, 13977, 13978), + DUNGEON_MYTHS_GUILD("Myths' Guild Dungeon", DiscordAreaType.DUNGEONS, 7564, 7820, 7821), + DUNGEON_OBSERVATORY("Observatory Dungeon", DiscordAreaType.DUNGEONS, 9362), + DUNGEON_OGRE_ENCLAVE("Ogre Enclave", DiscordAreaType.DUNGEONS, 10387), + DUNGEON_OURANIA("Ourania Cave", DiscordAreaType.DUNGEONS, 12119), + DUNGEON_QUIDAMORTEM_CAVE("Quidamortem Cave", DiscordAreaType.DUNGEONS, 4763), + DUNGEON_RASHILIYIAS_TOMB("Rashiliyta's Tomb", DiscordAreaType.DUNGEONS, 11668), + DUNGEON_SARADOMINSHRINE("Saradomin Shrine (Paterdomus)", DiscordAreaType.DUNGEONS, 13722), + DUNGEON_SHADE_CATACOMBS("Shade Catacombs", DiscordAreaType.DUNGEONS, 13975), + DUNGEON_SHADOW("Shadow Dungeon", DiscordAreaType.DUNGEONS, 10575, 10831), + DUNGEON_SHAYZIEN_CRYPTS("Shayzien Crypts", DiscordAreaType.DUNGEONS, 6043), + DUNGEON_SISTERHOOD_SANCTUARY("Sisterhood Sanctuary", DiscordAreaType.DUNGEONS, 14999, 15000, 15001, 15255, 15256, 15257, 15511, 15512, 15513), + DUNGEON_SMOKE("Smoke Dungeon", DiscordAreaType.DUNGEONS, 12946, 13202), + DUNGEON_SOPHANEM("Sophanem Dungeon", DiscordAreaType.DUNGEONS, 13200), + DUNGEON_SOURHOG_CAVE("Sourhog Cave", DiscordAreaType.DUNGEONS, 12695), + DUNGEON_STRONGHOLD_SECURITY("Stronghold of Security", DiscordAreaType.DUNGEONS, 7505, 8017, 8530, 9297), + DUNGEON_STRONGHOLD_SLAYER("Stronghold Slayer Cave", DiscordAreaType.DUNGEONS, 9624, 9625, 9880, 9881), + DUNGEON_TARNS_LAIR("Tarn's Lair", DiscordAreaType.DUNGEONS, 12616, 12615), + DUNGEON_TAVERLEY("Taverley Dungeon", DiscordAreaType.DUNGEONS, 11416, 11417, 11671, 11672, 11673, 11928, 11929), + DUNGEON_TEMPLE_OF_IKOV("Temple of Ikov", DiscordAreaType.DUNGEONS, 10649, 10905, 10650), + DUNGEON_TEMPLE_OF_LIGHT("Temple of Light", DiscordAreaType.DUNGEONS, 7496), + DUNGEON_TEMPLE_OF_MARIMBO("Temple of Marimbo", DiscordAreaType.DUNGEONS, 11151), + DUNGEON_THE_WARRENS("The Warrens", DiscordAreaType.DUNGEONS, 7070, 7326), + DUNGEON_TOLNA("Dungeon of Tolna", DiscordAreaType.DUNGEONS, 13209), + DUNGEON_TOWER_OF_LIFE("Tower of Life Basement", DiscordAreaType.DUNGEONS, 12100), + DUNGEON_TRAHAEARN_MINE("Trahaearn Mine", DiscordAreaType.DUNGEONS, 13250), + DUNGEON_TUNNEL_OF_CHAOS("Tunnel of Chaos", DiscordAreaType.DUNGEONS, 12625), + DUNGEON_UNDERGROUND_PASS("Underground Pass", DiscordAreaType.DUNGEONS, 9369, 9370), + DUNGEON_VARROCKSEWERS("Varrock Sewers", DiscordAreaType.DUNGEONS, 12954, 13210), + DUNGEON_VIYELDI_CAVES("Viyeldi Caves", DiscordAreaType.DUNGEONS, 9545, 11153), + DUNGEON_WARRIORS_GUILD("Warriors' Guild Basement", DiscordAreaType.DUNGEONS, 11675), + DUNGEON_WATER_RAVINE("Water Ravine", DiscordAreaType.DUNGEONS, 13461), + DUNGEON_WATERBIRTH("Waterbirth Dungeon", DiscordAreaType.DUNGEONS, 9886, 10142, 7492, 7748), + DUNGEON_WATERFALL("Waterfall Dungeon", DiscordAreaType.DUNGEONS, 10394), + DUNGEON_WEREWOLF_AGILITY("Werewolf Agility Course", DiscordAreaType.DUNGEONS, 14234), + DUNGEON_WHITE_WOLF_MOUNTAIN_CAVES("White Wolf Mountain Caves", DiscordAreaType.DUNGEONS, 11418, 11419), + DUNGEON_WITCHAVEN_SHRINE("Witchhaven Shrine Dungeon", DiscordAreaType.DUNGEONS, 10903), + DUNGEON_WIZARDS_TOWER("Wizards' Tower Basement", DiscordAreaType.DUNGEONS, 12437), + DUNGEON_WOODCUTTING_GUILD("Woodcutting Guild Dungeon", DiscordAreaType.DUNGEONS, 6298), + DUNGEON_WYVERN_CAVE("Wyvern Cave", DiscordAreaType.DUNGEONS, 14495, 14496), + DUNGEON_YANILLE_AGILITY("Yanille Agility Dungeon", DiscordAreaType.DUNGEONS, 10388), + + // Minigames + MG_ARDOUGNE_RAT_PITS("Ardougne Rat Pits", DiscordAreaType.MINIGAMES, 10646), + MG_BARBARIAN_ASSAULT("Barbarian Assault", DiscordAreaType.MINIGAMES, 7508, 7509, 10322), + MG_BARROWS("Barrows", DiscordAreaType.MINIGAMES, 14131, 14231), + MG_BLAST_FURNACE("Blast Furnace", DiscordAreaType.MINIGAMES, 7757), + MG_BRIMHAVEN_AGILITY_ARENA("Brimhaven Agility Arena", DiscordAreaType.MINIGAMES, 11157), + MG_BURTHORPE_GAMES_ROOM("Burthorpe Games Room", DiscordAreaType.MINIGAMES, 8781), + MG_CASTLE_WARS("Castle Wars", DiscordAreaType.MINIGAMES, 9520, 9620), + MG_CLAN_WARS("Clan Wars", DiscordAreaType.MINIGAMES, 12621, 12622, 12623, 13130, 13131, 13133, 13134, 13135, 13386, 13387, 13390, 13641, 13642, 13643, 13644, 13645, 13646, 13647, 13899, 13900, 14155, 14156), + MG_DUEL_ARENA("Duel Arena", DiscordAreaType.MINIGAMES, 13362, 13363), + MG_FISHING_TRAWLER("Fishing Trawler", DiscordAreaType.MINIGAMES, 7499), + MG_GAUNTLET("The Gauntlet", DiscordAreaType.MINIGAMES, 12127, 7512, 7768), + MG_HALLOWED_SEPULCHRE("Hallowed Sepulchre", DiscordAreaType.MINIGAMES, 8797, 9051, 9052, 9053, 9054, 9309, 9563, 9565, 9821, 10074, 10075, 10077), + MG_INFERNO("The Inferno", DiscordAreaType.MINIGAMES, 9043), + MG_KELDAGRIM_RAT_PITS("Keldagrim Rat Pits", DiscordAreaType.MINIGAMES, 7753), + MG_LAST_MAN_STANDING("Last Man Standing", DiscordAreaType.MINIGAMES, 13660, 13659, 13658, 13916, 13915, 13914), + MG_MAGE_TRAINING_ARENA("Mage Training Arena", DiscordAreaType.MINIGAMES, 13462, 13463), + MG_NIGHTMARE_ZONE("Nightmare Zone", DiscordAreaType.MINIGAMES, 9033), + MG_PEST_CONTROL("Pest Control", DiscordAreaType.MINIGAMES, 10536), + MG_PORT_SARIM_RAT_PITS("Port Sarim Rat Pits", DiscordAreaType.MINIGAMES, 11926), + MG_PYRAMID_PLUNDER("Pyramid Plunder", DiscordAreaType.MINIGAMES, 7749), + MG_ROGUES_DEN("Rogues' Den", DiscordAreaType.MINIGAMES, 11855, 11854, 12111, 12110), + MG_SORCERESS_GARDEN("Sorceress's Garden", DiscordAreaType.MINIGAMES, 11605), + MG_TEMPLE_TREKKING("Temple Trekking", DiscordAreaType.MINIGAMES, 8014, 8270, 8256, 8782, 9038, 9294, 9550, 9806), + MG_TITHE_FARM("Tithe Farm", DiscordAreaType.MINIGAMES, 7222), + MG_TROUBLE_BREWING("Trouble Brewing", DiscordAreaType.MINIGAMES, 15150), + MG_TZHAAR_FIGHT_CAVES("Tzhaar Fight Caves", DiscordAreaType.MINIGAMES, 9551), + MG_TZHAAR_FIGHT_PITS("Tzhaar Fight Pits", DiscordAreaType.MINIGAMES, 9552), + MG_VARROCK_RAT_PITS("Varrock Rat Pits", DiscordAreaType.MINIGAMES, 11599), + MG_VOLCANIC_MINE("Volcanic Mine", DiscordAreaType.MINIGAMES, 15263, 15262), + + // Raids + RAIDS_CHAMBERS_OF_XERIC("Chambers of Xeric", DiscordAreaType.RAIDS, Varbits.IN_RAID), + RAIDS_THEATRE_OF_BLOOD("Theatre of Blood", DiscordAreaType.RAIDS, Varbits.THEATRE_OF_BLOOD), + + // Other + REGION_ABYSSAL_AREA("Abyssal Area", DiscordAreaType.REGIONS, 12108), + REGION_ABYSSAL_NEXUS("Abyssal Nexus", DiscordAreaType.REGIONS, 12106), + REGION_AGILITY_PYRAMID("Agility Pyramid", DiscordAreaType.REGIONS, 12105, 13356), + REGION_AIR_ALTAR("Air Altar", DiscordAreaType.REGIONS, 11339), + REGION_AL_KHARID_MINE("Al Kharid Mine", DiscordAreaType.REGIONS, 13107), + REGION_APE_ATOLL("Ape Atoll" , DiscordAreaType.REGIONS, 10795, 10974, 11050), + REGION_ARANDAR("Arandar", DiscordAreaType.REGIONS, 9266, 9267, 9523), + REGION_ASGARNIA("Asgarnia", DiscordAreaType.REGIONS, 11825, 11829, 11830, 12085, 12086), + REGION_BATTLEFIELD("Battlefield", DiscordAreaType.REGIONS, 10034), + REGION_BATTLEFRONT("Battlefront", DiscordAreaType.REGIONS, 5433, 5434), + REGION_BLAST_MINE("Blast Mine", DiscordAreaType.REGIONS, 5948), + REGION_BODY_ALTAR("Body Altar", DiscordAreaType.REGIONS, 10059), + REGION_CHAOS_ALTAR("Chaos Altar", DiscordAreaType.REGIONS, 9035), + REGION_COSMIC_ALTAR("Cosmic Altar", DiscordAreaType.REGIONS, 8523), + REGION_COSMIC_ENTITYS_PLANE("Cosmic Entity's Plane", DiscordAreaType.REGIONS, 8267), + REGION_CRABCLAW_ISLE("Crabclaw Isle", DiscordAreaType.REGIONS, 6965), + REGION_CRAFTING_GUILD("Crafting Guild", DiscordAreaType.REGIONS, 11571), + REGION_CRANDOR("Crandor", DiscordAreaType.REGIONS, 11314, 11315), + REGION_CRASH_ISLAND("Crash Island", DiscordAreaType.REGIONS, 11562), + REGION_DARK_ALTAR("Dark Altar", DiscordAreaType.REGIONS, 6716), + REGION_DEATH_ALTAR("Death Altar", DiscordAreaType.REGIONS, 8779), + REGION_DEATH_PLATEAU("Death Plateau", DiscordAreaType.REGIONS, 11320), + REGION_DENSE_ESSENCE("Dense Essence Mine", DiscordAreaType.REGIONS, 6972), + REGION_DIGSITE("Digsite", DiscordAreaType.REGIONS, 13365), + REGION_DRAGONTOOTH("Dragontooth Island", DiscordAreaType.REGIONS, 15159), + REGION_DRAYNOR_MANOR("Draynor Manor", DiscordAreaType.REGIONS, 12340), + REGION_DRILL_SERGEANT("Drill Sergeant's Training Camp", DiscordAreaType.REGIONS, 12619), + REGION_EAGLES_PEAK("Eagles' Peak", DiscordAreaType.REGIONS, 9270), + REGION_EARTH_ALTAR("Earth Altar", DiscordAreaType.REGIONS, 10571), + REGION_ENCHANTED_VALLEY("Enchanted Valley", DiscordAreaType.REGIONS, 12102), + REGION_EVIL_TWIN("Evil Twin Crane Room", DiscordAreaType.REGIONS, 7504), + REGION_EXAM_CENTRE("Exam Centre", DiscordAreaType.REGIONS, 13364), + REGION_FALADOR_FARM("Falador Farm", DiscordAreaType.REGIONS, 12083), + REGION_FARMING_GUILD("Farming Guild", DiscordAreaType.REGIONS, 4922), + REGION_FELDIP_HILLS("Feldip Hills", DiscordAreaType.REGIONS, 9773, 9774, 10029, 10030, 10285, 10286, 10287, 10542, 10543), + REGION_FENKENSTRAIN("Fenkenstrain's Castle", DiscordAreaType.REGIONS, 14135), + REGION_FIRE_ALTAR("Fire Altar", DiscordAreaType.REGIONS, 10315), + REGION_FISHER_REALM("Fisher Realm", DiscordAreaType.REGIONS, 10569), + REGION_FISHING_GUILD("Fishing Guild", DiscordAreaType.REGIONS, 10293), + REGION_FISHING_PLATFORM("Fishing Platform", DiscordAreaType.REGIONS, 11059), + REGION_FORSAKEN_TOWER("The Forsaken Tower", DiscordAreaType.REGIONS, 5435), + REGION_FOSSIL_ISLAND("Fossil Island", DiscordAreaType.REGIONS, 14650, 14651, 14652, 14906, 14907, 14908, 15162, 15163, 15164), + REGION_FREAKY_FORESTER("Freaky Forester's Clearing", DiscordAreaType.REGIONS, 10314), + REGION_FREMENNIK("Fremennik Province", DiscordAreaType.REGIONS, 10296, 10552, 10808, 10809, 10810, 10811, 11064), + REGION_FREMENNIK_ISLES("Fremennik Isles", DiscordAreaType.REGIONS, 9276, 9532), + REGION_FROGLAND("Frogland", DiscordAreaType.REGIONS, 9802), + REGION_GALVEK_SHIPWRECKS("Galvek Shipwrecks", DiscordAreaType.REGIONS, 6486, 6487, 6488, 6489, 6742, 6743, 6744, 6745), + REGION_GORAKS_PLANE("Gorak's Plane", DiscordAreaType.REGIONS, 12115), + REGION_GRAND_EXCHANGE("Grand Exchange", DiscordAreaType.REGIONS, 12598), + REGION_GWD("God Wars Dungeon", DiscordAreaType.REGIONS, 11578), + REGION_HARMONY("Harmony Island", DiscordAreaType.REGIONS, 15148), + REGION_ICE_PATH("Ice Path", DiscordAreaType.REGIONS, 11322, 11323), + REGION_ICEBERG("Iceberg", DiscordAreaType.REGIONS, 10558, 10559), + REGION_ICYENE_GRAVEYARD("Icyene Graveyard", DiscordAreaType.REGIONS, 14641, 14897, 14898), + REGION_ISAFDAR("Isafdar", DiscordAreaType.REGIONS, 8497, 8753, 8754, 9009, 9010), + REGION_ISLAND_OF_STONE("Island of Stone", DiscordAreaType.REGIONS, 9790), + REGION_JIGGIG("Jiggig" , DiscordAreaType.REGIONS, 9775), + REGION_KANDARIN("Kandarin", DiscordAreaType.REGIONS, 9014, 9263, 9264, 9519, 9524, 9527, 9776, 9783, 10037, 10290, 10294, 10546, 10551, 10805), + REGION_KARAMJA("Karamja" , DiscordAreaType.REGIONS, 10801, 10802, 11054, 11311, 11312, 11313, 11566, 11567, 11568, 11569, 11822), + REGION_KEBOS_LOWLANDS("Kebos Lowlands", DiscordAreaType.REGIONS, 4665, 4666, 4921, 5178), + REGION_KEBOS_SWAMP("Kebos Swamp", DiscordAreaType.REGIONS, 4664, 4920, 5174, 5175, 5176, 5430, 5431), + REGION_KHARAZI_JUNGLE("Kharazi Jungle", DiscordAreaType.REGIONS, 11053, 11309, 11565, 11821), + REGION_KHARIDIAN_DESERT("Kharidian Desert", DiscordAreaType.REGIONS, 12844, 12845, 12846, 12847, 12848, 13100, 13101, 13102, 13103, 13104, 13357, 13359, 13360, 13614, 13615, 13616), + REGION_KILLERWATT_PLANE("Killerwatt Plane", DiscordAreaType.REGIONS, 10577), + REGION_KOUREND("Great Kourend", DiscordAreaType.REGIONS, 6201, 6457, 6713), + REGION_KOUREND_WOODLAND("Kourend Woodland", DiscordAreaType.REGIONS, 5942, 6197, 6453), + REGION_LAW_ALTAR("Law Altar", DiscordAreaType.REGIONS, 9803), + REGION_LEGENDS_GUILD("Legends' Guild", DiscordAreaType.REGIONS, 10804), + REGION_LIGHTHOUSE("Lighthouse", DiscordAreaType.REGIONS, 10040), + REGION_LITHKREN("Lithkren", DiscordAreaType.REGIONS, 14142, 14398), + REGION_LUMBRIDGE_SWAMP("Lumbridge Swamp", DiscordAreaType.REGIONS, 12593, 12849), + REGION_MAX_ISLAND("Max Island", DiscordAreaType.REGIONS, 11063), + REGION_MCGRUBORS_WOOD("McGrubor's Wood", DiscordAreaType.REGIONS, 10550), + REGION_MIME_STAGE("Mime's Stage", DiscordAreaType.REGIONS, 8010), + REGION_MIND_ALTAR("Mind Altar", DiscordAreaType.REGIONS, 11083), + REGION_MISTHALIN("Misthalin", DiscordAreaType.REGIONS, 12594, 12595), + REGION_MOLCH("Molch", DiscordAreaType.REGIONS, 5177), + REGION_MOLCH_ISLAND("Molch Island", DiscordAreaType.REGIONS, 5432), + REGION_MORYTANIA("Morytania", DiscordAreaType.REGIONS, 13619, 13620, 13621, 13622, 13876, 13877, 13879, 14133, 14134, 14389, 14390, 14391, 14645, 14647), + REGION_MOUNT_QUIDAMORTEM("Mount Quidamortem", DiscordAreaType.REGIONS, 4662, 4663, 4918, 4919), + REGION_MR_MORDAUTS_CLASSROOM("Mr. Mordaut's Classroom", DiscordAreaType.REGIONS, 7502), + REGION_MUDSKIPPER("Mudskipper Point", DiscordAreaType.REGIONS, 11824), + REGION_MYSTERIOUS_OLD_MAN_MAZE("Mysterious Old Man's Maze", DiscordAreaType.REGIONS, 11590, 11591, 11846, 11847), + REGION_MYTHS_GUILD("Myths' Guild", DiscordAreaType.REGIONS, 9772), + REGION_NATURE_ALTAR("Nature Altar", DiscordAreaType.REGIONS, 9547), + REGION_NORTHERN_TUNDRAS("Northern Tundras", DiscordAreaType.REGIONS, 6204, 6205, 6717), + REGION_OBSERVATORY("Observatory", DiscordAreaType.REGIONS, 9777), + REGION_ODD_ONE_OUT("Odd One Out", DiscordAreaType.REGIONS, 7754), + REGION_OTTOS_GROTTO("Otto's Grotto", DiscordAreaType.REGIONS, 10038), + REGION_OURANIA_HUNTER("Ourania Hunter Area", DiscordAreaType.REGIONS, 9778), + REGION_PIRATES_COVE("Pirates' Cove", DiscordAreaType.REGIONS, 8763), + REGION_PISCATORIS_HUNTER_AREA("Piscatoris Hunter Area", DiscordAreaType.REGIONS, 9015, 9016, 9271, 9272, 9528), + REGION_POH("Player Owned House", DiscordAreaType.REGIONS, 7513, 7514, 7769, 7770), + REGION_POISON_WASTE("Poison Waste", DiscordAreaType.REGIONS, 8752, 9008), + REGION_PORT_TYRAS("Port Tyras", DiscordAreaType.REGIONS, 8496), + REGION_PURO_PURO("Puro Puro", DiscordAreaType.REGIONS, 10307), + REGION_QUARRY("Quarry", DiscordAreaType.REGIONS, 12589), + REGION_RANGING_GUILD("Ranging Guild", DiscordAreaType.REGIONS, 10549), + REGION_RATCATCHERS_MANSION("Ratcatchers Mansion", DiscordAreaType.REGIONS, 11343), + REGION_RUNE_ESSENCE_MINE("Rune Essence Mine", DiscordAreaType.REGIONS, 11595), + // The Beekeper, Pinball, and Gravedigger randoms share a region (7758), and although they are not technically ScapeRune, that name is most commonly + // associated with random events, so those three have been denoted ScapeRune to avoid leaving multiple random event regions without an assigned name. + REGION_SCAPERUNE("ScapeRune", DiscordAreaType.REGIONS, 10058, 7758, 8261), + REGION_SHIP_YARD("Ship Yard", DiscordAreaType.REGIONS, 11823), + REGION_SILVAREA("Silvarea", DiscordAreaType.REGIONS, 13366), + REGION_SINCLAR_MANSION("Sinclair Mansion", DiscordAreaType.REGIONS, 10807), + REGION_SLAYER_TOWER("Slayer Tower", DiscordAreaType.REGIONS, 13623, 13723), + REGION_SOUL_ALTAR("Soul Altar", DiscordAreaType.REGIONS, 7228), + REGION_TROLL_ARENA("Troll Arena", DiscordAreaType.REGIONS, 11576), + REGION_TROLLHEIM("Trollheim", DiscordAreaType.REGIONS, 11577), + REGION_TROLLWEISS_MTN("Trollweiss Mountain", DiscordAreaType.REGIONS, 11066, 11067, 11068), + REGION_UNDERWATER("Underwater", DiscordAreaType.REGIONS, 15008, 15264), + REGION_WATER_ALTAR("Water Altar", DiscordAreaType.REGIONS, 10827), + REGION_WINTERTODT_CAMP("Wintertodt Camp", DiscordAreaType.REGIONS, 6461), + REGION_WIZARDS_TOWER("Wizards' Tower", DiscordAreaType.REGIONS, 12337), + REGION_WOODCUTTING_GUILD("Woodcutting Guild", DiscordAreaType.REGIONS, 6198, 6454), + REGION_WRATH_ALTAR("Wrath Altar", DiscordAreaType.REGIONS, 9291); + + private static final Map FROM_REGION; + private static final List FROM_VARBITS; + + static + { + ImmutableMap.Builder regionMapBuilder = new ImmutableMap.Builder<>(); + ImmutableList.Builder fromVarbitsBuilder = ImmutableList.builder(); + for (DiscordGameEventType discordGameEventType : DiscordGameEventType.values()) + { + if (discordGameEventType.getVarbits() != null) + { + fromVarbitsBuilder.add(discordGameEventType); + continue; + } + + if (discordGameEventType.getRegionIds() == null) + { + continue; + } + + for (int region : discordGameEventType.getRegionIds()) + { + regionMapBuilder.put(region, discordGameEventType); + } + } + FROM_REGION = regionMapBuilder.build(); + FROM_VARBITS = fromVarbitsBuilder.build(); + } + + @Nullable + private String imageKey; + + @Nullable + private String state; + + @Nullable + private String details; + + private int priority; + + /** + * Marks this event as root event, e.g event that should be used for total time tracking + */ + private boolean root; + + /** + * Determines if event should clear other clearable events when triggered + */ + private boolean shouldClear; + + /** + * Determines if event should be processed when it timeouts based on action timeout + */ + private boolean shouldTimeout; + + /** + * Determines if event start time should be reset when processed + */ + private boolean shouldRestart; + + /** + * Determines if event should be cleared when processed + */ + private boolean shouldBeCleared = true; + + @Nullable + private DiscordAreaType discordAreaType; + + @Nullable + private Varbits varbits; + + @Nullable + private int[] regionIds; + + DiscordGameEventType(Skill skill) + { + this(skill, 0); + } + + DiscordGameEventType(Skill skill, int priority) + { + this.details = training(skill); + this.priority = priority; + this.imageKey = imageKeyOf(skill); + this.shouldTimeout = true; + } + + DiscordGameEventType(String areaName, DiscordAreaType areaType, int... regionIds) + { + this.state = exploring(areaType, areaName); + this.priority = -2; + this.discordAreaType = areaType; + this.regionIds = regionIds; + this.shouldClear = true; + } + + DiscordGameEventType(String state, int priority, boolean shouldClear, boolean shouldTimeout, boolean shouldRestart, boolean shouldBeCleared, boolean root) + { + this.state = state; + this.priority = priority; + this.shouldClear = shouldClear; + this.shouldTimeout = shouldTimeout; + this.shouldRestart = shouldRestart; + this.shouldBeCleared = shouldBeCleared; + this.root = root; + } + + DiscordGameEventType(String state, int priority) + { + this(state, priority, true, false, false, true, false); + } + + DiscordGameEventType(String areaName, DiscordAreaType areaType, Varbits varbits) + { + this.state = exploring(areaType, areaName); + this.priority = -2; + this.discordAreaType = areaType; + this.varbits = varbits; + this.shouldClear = true; + } + + private static String training(final Skill skill) + { + return training(skill.getName()); + } + + private static String training(final String what) + { + return "Training: " + what; + } + + private static String imageKeyOf(final Skill skill) + { + return imageKeyOf(skill.getName().toLowerCase()); + } + + private static String imageKeyOf(final String what) + { + return "icon_" + what; + } + + private static String exploring(DiscordAreaType areaType, String areaName) + { + return areaName; + } + + public static DiscordGameEventType fromSkill(final Skill skill) + { + switch (skill) + { + case ATTACK: return TRAINING_ATTACK; + case DEFENCE: return TRAINING_DEFENCE; + case STRENGTH: return TRAINING_STRENGTH; + case RANGED: return TRAINING_RANGED; + case PRAYER: return TRAINING_PRAYER; + case MAGIC: return TRAINING_MAGIC; + case COOKING: return TRAINING_COOKING; + case WOODCUTTING: return TRAINING_WOODCUTTING; + case FLETCHING: return TRAINING_FLETCHING; + case FISHING: return TRAINING_FISHING; + case FIREMAKING: return TRAINING_FIREMAKING; + case CRAFTING: return TRAINING_CRAFTING; + case SMITHING: return TRAINING_SMITHING; + case MINING: return TRAINING_MINING; + case HERBLORE: return TRAINING_HERBLORE; + case AGILITY: return TRAINING_AGILITY; + case THIEVING: return TRAINING_THIEVING; + case SLAYER: return TRAINING_SLAYER; + case FARMING: return TRAINING_FARMING; + case RUNECRAFT: return TRAINING_RUNECRAFT; + case HUNTER: return TRAINING_HUNTER; + case CONSTRUCTION: return TRAINING_CONSTRUCTION; + default: return null; + } + } + + public static DiscordGameEventType fromRegion(final int regionId) + { + return FROM_REGION.get(regionId); + } + + public static DiscordGameEventType fromVarbit(final Client client) + { + for (DiscordGameEventType fromVarbit : FROM_VARBITS) + { + if (client.getVar(fromVarbit.getVarbits()) != 0) + { + return fromVarbit; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java new file mode 100644 index 0000000000..f4cdaa34a5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, PandahRS + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +import com.google.common.base.Strings; +import com.google.inject.Inject; +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.time.temporal.ChronoUnit; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import javax.imageio.ImageIO; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Skill; +import net.runelite.api.WorldType; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.StatChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.discord.DiscordService; +import net.runelite.client.discord.events.DiscordJoinGame; +import net.runelite.client.discord.events.DiscordJoinRequest; +import net.runelite.client.discord.events.DiscordReady; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.PartyChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.task.Schedule; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; +import net.runelite.client.ws.PartyMember; +import net.runelite.client.ws.PartyService; +import net.runelite.client.ws.WSClient; +import net.runelite.http.api.ws.messages.party.UserJoin; +import net.runelite.http.api.ws.messages.party.UserPart; +import net.runelite.http.api.ws.messages.party.UserSync; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +@PluginDescriptor( + name = "Discord", + description = "Show your status and activity in the Discord user panel", + tags = {"action", "activity", "external", "integration", "status"} +) +@Slf4j +public class DiscordPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private DiscordConfig config; + + @Inject + private ClientToolbar clientToolbar; + + @Inject + private DiscordState discordState; + + @Inject + private PartyService partyService; + + @Inject + private DiscordService discordService; + + @Inject + private WSClient wsClient; + + @Inject + private OkHttpClient okHttpClient; + + private final Map skillExp = new HashMap<>(); + private NavigationButton discordButton; + private boolean loginFlag; + + @Provides + private DiscordConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(DiscordConfig.class); + } + + @Override + protected void startUp() throws Exception + { + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "discord.png"); + + discordButton = NavigationButton.builder() + .tab(false) + .tooltip("Join Discord") + .icon(icon) + .onClick(() -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite())) + .build(); + + clientToolbar.addNavigation(discordButton); + resetState(); + checkForGameStateUpdate(); + checkForAreaUpdate(); + + if (discordService.getCurrentUser() != null) + { + partyService.setUsername(discordService.getCurrentUser().username + "#" + discordService.getCurrentUser().discriminator); + } + + wsClient.registerMessage(DiscordUserInfo.class); + } + + @Override + protected void shutDown() throws Exception + { + clientToolbar.removeNavigation(discordButton); + resetState(); + partyService.changeParty(null); + wsClient.unregisterMessage(DiscordUserInfo.class); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + case LOGIN_SCREEN: + resetState(); + checkForGameStateUpdate(); + return; + case LOGGING_IN: + loginFlag = true; + break; + case LOGGED_IN: + if (loginFlag) + { + loginFlag = false; + resetState(); + checkForGameStateUpdate(); + } + break; + } + + checkForAreaUpdate(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equalsIgnoreCase("discord")) + { + resetState(); + checkForGameStateUpdate(); + checkForAreaUpdate(); + } + } + + @Subscribe + public void onStatChanged(StatChanged statChanged) + { + final Skill skill = statChanged.getSkill(); + final int exp = statChanged.getXp(); + final Integer previous = skillExp.put(skill, exp); + + if (previous == null || previous >= exp) + { + return; + } + + final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromSkill(skill); + + if (discordGameEventType != null && config.showSkillingActivity()) + { + discordState.triggerEvent(discordGameEventType); + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + if (!config.showRaidingActivity()) + { + return; + } + + final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromVarbit(client); + + if (discordGameEventType != null) + { + discordState.triggerEvent(discordGameEventType); + } + } + + @Subscribe + public void onDiscordReady(DiscordReady event) + { + partyService.setUsername(event.getUsername() + "#" + event.getDiscriminator()); + } + + @Subscribe + public void onDiscordJoinRequest(DiscordJoinRequest request) + { + // In order for the "Invite to join" message to work we need to have a valid party in Discord presence. + // We lazily create the party here in order to avoid the (1 of 15) being permanently in the Discord status. + if (!partyService.isInParty()) + { + // Change to my party id, which is advertised in the Discord presence secret. This will open the socket, + // send a join, and cause a UserJoin later for me, which will then update the presence and allow the + // "Invite to join" to continue. + partyService.changeParty(partyService.getLocalPartyId()); + } + } + + @Subscribe + public void onDiscordJoinGame(DiscordJoinGame joinGame) + { + UUID partyId = UUID.fromString(joinGame.getJoinSecret()); + partyService.changeParty(partyId); + updatePresence(); + } + + @Subscribe + public void onDiscordUserInfo(final DiscordUserInfo event) + { + final PartyMember memberById = partyService.getMemberById(event.getMemberId()); + + if (memberById == null || memberById.getAvatar() != null) + { + return; + } + + String url = "https://cdn.discordapp.com/avatars/" + event.getUserId() + "/" + event.getAvatarId() + ".png"; + + if (Strings.isNullOrEmpty(event.getAvatarId())) + { + final String[] split = memberById.getName().split("#", 2); + + if (split.length == 2) + { + int disc = Integer.valueOf(split[1]); + int avatarId = disc % 5; + url = "https://cdn.discordapp.com/embed/avatars/" + avatarId + ".png"; + } + } + + log.debug("Got user avatar {}", url); + + final Request request = new Request.Builder() + .url(url) + .build(); + + okHttpClient.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + + } + + @Override + public void onResponse(Call call, Response response) throws IOException + { + try // NOPMD: UseTryWithResources + { + if (!response.isSuccessful()) + { + throw new IOException("Unexpected code " + response); + } + + final InputStream inputStream = response.body().byteStream(); + final BufferedImage image; + synchronized (ImageIO.class) + { + image = ImageIO.read(inputStream); + } + memberById.setAvatar(image); + } + finally + { + response.close(); + } + } + }); + } + + @Subscribe + public void onUserJoin(final UserJoin event) + { + updatePresence(); + } + + @Subscribe + public void onUserSync(final UserSync event) + { + final PartyMember localMember = partyService.getLocalMember(); + + if (localMember != null) + { + if (discordService.getCurrentUser() != null) + { + final DiscordUserInfo userInfo = new DiscordUserInfo( + discordService.getCurrentUser().userId, + discordService.getCurrentUser().avatar); + + userInfo.setMemberId(localMember.getMemberId()); + wsClient.send(userInfo); + } + } + } + + @Subscribe + public void onUserPart(final UserPart event) + { + updatePresence(); + } + + @Subscribe + public void onPartyChanged(final PartyChanged event) + { + updatePresence(); + } + + @Schedule( + period = 1, + unit = ChronoUnit.MINUTES + ) + public void checkForValidStatus() + { + discordState.checkForTimeout(); + } + + private void updatePresence() + { + discordState.refresh(); + } + + private void resetState() + { + discordState.reset(); + } + + private void checkForGameStateUpdate() + { + discordState.triggerEvent(client.getGameState() == GameState.LOGGED_IN + ? DiscordGameEventType.IN_GAME + : DiscordGameEventType.IN_MENU); + } + + private void checkForAreaUpdate() + { + if (client.getLocalPlayer() == null) + { + return; + } + + final int playerRegionID = WorldPoint.fromLocalInstance(client, client.getLocalPlayer().getLocalLocation()).getRegionID(); + + if (playerRegionID == 0) + { + return; + } + + final EnumSet worldType = client.getWorldType(); + + if (worldType.contains(WorldType.DEADMAN)) + { + discordState.triggerEvent(DiscordGameEventType.PLAYING_DEADMAN); + return; + } + else if (WorldType.isPvpWorld(worldType)) + { + discordState.triggerEvent(DiscordGameEventType.PLAYING_PVP); + return; + } + + DiscordGameEventType discordGameEventType = DiscordGameEventType.fromRegion(playerRegionID); + + // NMZ uses the same region ID as KBD. KBD is always on plane 0 and NMZ is always above plane 0 + // Since KBD requires going through the wilderness there is no EventType for it + if (DiscordGameEventType.MG_NIGHTMARE_ZONE == discordGameEventType + && client.getLocalPlayer().getWorldLocation().getPlane() == 0) + { + discordGameEventType = null; + } + + if (discordGameEventType == null) + { + // Unknown region, reset to default in-game + discordState.triggerEvent(DiscordGameEventType.IN_GAME); + return; + } + + if (!showArea(discordGameEventType)) + { + return; + } + + discordState.triggerEvent(discordGameEventType); + } + + private boolean showArea(final DiscordGameEventType event) + { + if (event == null) + { + return false; + } + + switch (event.getDiscordAreaType()) + { + case BOSSES: return config.showBossActivity(); + case CITIES: return config.showCityActivity(); + case DUNGEONS: return config.showDungeonActivity(); + case MINIGAMES: return config.showMinigameActivity(); + case REGIONS: return config.showRegionsActivity(); + } + + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java new file mode 100644 index 0000000000..671107833e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ComparisonChain; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.Data; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.discord.DiscordPresence; +import net.runelite.client.discord.DiscordService; +import net.runelite.client.ws.PartyService; +import static net.runelite.client.ws.PartyService.PARTY_MAX; + +/** + * This class contains data about currently active discord state. + */ +class DiscordState +{ + @Data + private static class EventWithTime + { + private final DiscordGameEventType type; + private Instant start; + private Instant updated; + } + + private final UUID partyId = UUID.randomUUID(); + private final List events = new ArrayList<>(); + private final DiscordService discordService; + private final DiscordConfig config; + private final PartyService party; + private DiscordPresence lastPresence; + + @Inject + private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party) + { + this.discordService = discordService; + this.config = config; + this.party = party; + } + + /** + * Reset state. + */ + void reset() + { + discordService.clearPresence(); + events.clear(); + lastPresence = null; + } + + /** + * Force refresh discord presence + */ + void refresh() + { + if (lastPresence == null) + { + return; + } + + final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder() + .state(lastPresence.getState()) + .details(lastPresence.getDetails()) + .largeImageText(lastPresence.getLargeImageText()) + .startTimestamp(lastPresence.getStartTimestamp()) + .smallImageKey(lastPresence.getSmallImageKey()) + .partyMax(lastPresence.getPartyMax()) + .partySize(party.getMembers().size()); + + if (!party.isInParty() || party.isPartyOwner()) + { + // This is only used to identify the invites on Discord's side. Our party ids are the secret. + presenceBuilder.partyId(partyId.toString()); + presenceBuilder.joinSecret(party.getLocalPartyId().toString()); + } + + discordService.updatePresence(presenceBuilder.build()); + } + + /** + * Trigger new discord state update. + * + * @param eventType discord event type + */ + void triggerEvent(final DiscordGameEventType eventType) + { + final Optional foundEvent = events.stream().filter(e -> e.type == eventType).findFirst(); + final EventWithTime event; + + if (foundEvent.isPresent()) + { + event = foundEvent.get(); + } + else + { + event = new EventWithTime(eventType); + event.setStart(Instant.now()); + events.add(event); + } + + event.setUpdated(Instant.now()); + + if (event.getType().isShouldClear()) + { + events.removeIf(e -> e.getType() != eventType && e.getType().isShouldBeCleared()); + } + + if (event.getType().isShouldRestart()) + { + event.setStart(Instant.now()); + } + + events.sort((a, b) -> ComparisonChain.start() + .compare(b.getType().getPriority(), a.getType().getPriority()) + .compare(b.getUpdated(), a.getUpdated()) + .result()); + + updatePresenceWithLatestEvent(); + } + + private void updatePresenceWithLatestEvent() + { + if (events.isEmpty()) + { + reset(); + return; + } + + final EventWithTime event = events.get(0); + + String imageKey = null; + String state = null; + String details = null; + + for (EventWithTime eventWithTime : events) + { + if (imageKey == null) + { + imageKey = eventWithTime.getType().getImageKey(); + } + + if (details == null) + { + details = eventWithTime.getType().getDetails(); + } + + if (state == null) + { + state = eventWithTime.getType().getState(); + } + + if (imageKey != null && details != null && state != null) + { + break; + } + } + + // Replace snapshot with + to make tooltip shorter (so it will span only 1 line) + final String versionShortHand = RuneLiteProperties.getVersion().replace("-SNAPSHOT", "+"); + + final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder() + .state(MoreObjects.firstNonNull(state, "")) + .details(MoreObjects.firstNonNull(details, "")) + .largeImageText(RuneLiteProperties.getTitle() + " v" + versionShortHand) + .smallImageKey(imageKey) + .partyMax(PARTY_MAX) + .partySize(party.getMembers().size()); + + final Instant startTime; + switch (config.elapsedTimeType()) + { + case HIDDEN: + startTime = null; + break; + case TOTAL: + // We are tracking total time spent instead of per activity time so try to find + // root event as this indicates start of tracking and find last updated one + // to determine correct state we are in + startTime = events.stream() + .filter(e -> e.getType().isRoot()) + .sorted((a, b) -> b.getUpdated().compareTo(a.getUpdated())) + .map(EventWithTime::getStart) + .findFirst() + .orElse(event.getStart()); + break; + case ACTIVITY: + default: + startTime = event.getStart(); + break; + } + + presenceBuilder.startTimestamp(startTime); + + if (!party.isInParty() || party.isPartyOwner()) + { + presenceBuilder.partyId(partyId.toString()); + presenceBuilder.joinSecret(party.getLocalPartyId().toString()); + } + + final DiscordPresence presence = presenceBuilder.build(); + + // This is to reduce amount of RPC calls + if (!presence.equals(lastPresence)) + { + lastPresence = presence; + discordService.updatePresence(presence); + } + } + + /** + * Check for current state timeout and act upon it. + */ + void checkForTimeout() + { + if (events.isEmpty()) + { + return; + } + + final Duration actionTimeout = Duration.ofMinutes(config.actionTimeout()); + final Instant now = Instant.now(); + final AtomicBoolean updatedAny = new AtomicBoolean(); + + final boolean removedAny = events.removeAll(events.stream() + // Find only events that should time out + .filter(event -> event.getType().isShouldTimeout() && now.isAfter(event.getUpdated().plus(actionTimeout))) + // Reset start times on timed events that should restart + .peek(event -> + { + if (event.getType().isShouldRestart()) + { + event.setStart(null); + updatedAny.set(true); + } + }) + // Now filter out events that should restart as we do not want to remove them + .filter(event -> !event.getType().isShouldRestart()) + .filter(event -> event.getType().isShouldBeCleared()) + .collect(Collectors.toList()) + ); + + if (removedAny || updatedAny.get()) + { + updatePresenceWithLatestEvent(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java new file mode 100644 index 0000000000..360d58652e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.discord; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import net.runelite.http.api.ws.messages.party.PartyMemberMessage; + +@Value +@EqualsAndHashCode(callSuper = true) +class DiscordUserInfo extends PartyMemberMessage +{ + private final String userId; + private final String avatarId; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java new file mode 100644 index 0000000000..f730dd2c8f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.dpscounter; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("dpscounter") +public interface DpsConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "showDamage", + name = "Show damage", + description = "Show total damage instead of DPS" + ) + default boolean showDamage() + { + return false; + } + + @ConfigItem( + position = 1, + keyName = "autopause", + name = "Auto pause", + description = "Pause the DPS tracker when a boss dies" + ) + default boolean autopause() + { + return false; + } + + @ConfigItem( + position = 2, + keyName = "autoreset", + name = "Auto reset", + description = "Reset the DPS tracker when a boss dies" + ) + default boolean autoreset() + { + return false; + } + + @ConfigItem( + position = 3, + keyName = "bossDamage", + name = "Only boss damage", + description = "Only count damage done to the boss, and not to other NPCs" + ) + default boolean bossDamage() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java new file mode 100644 index 0000000000..54887b7223 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2020 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.dpscounter; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Provides; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.Hitsplat; +import net.runelite.api.NPC; +import static net.runelite.api.NpcID.*; +import net.runelite.api.Player; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.NpcDespawned; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.events.PartyChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ws.PartyMember; +import net.runelite.client.ws.PartyService; +import net.runelite.client.ws.WSClient; + +@PluginDescriptor( + name = "DPS Counter", + description = "Counts damage (per second) by a party", + enabledByDefault = false +) +@Slf4j +public class DpsCounterPlugin extends Plugin +{ + private static final ImmutableSet BOSSES = ImmutableSet.of( + ABYSSAL_SIRE, ABYSSAL_SIRE_5887, ABYSSAL_SIRE_5888, ABYSSAL_SIRE_5889, ABYSSAL_SIRE_5890, ABYSSAL_SIRE_5891, ABYSSAL_SIRE_5908, + ALCHEMICAL_HYDRA, ALCHEMICAL_HYDRA_8616, ALCHEMICAL_HYDRA_8617, ALCHEMICAL_HYDRA_8618, ALCHEMICAL_HYDRA_8619, ALCHEMICAL_HYDRA_8620, ALCHEMICAL_HYDRA_8621, ALCHEMICAL_HYDRA_8622, + AHRIM_THE_BLIGHTED, DHAROK_THE_WRETCHED, GUTHAN_THE_INFESTED, KARIL_THE_TAINTED, TORAG_THE_CORRUPTED, VERAC_THE_DEFILED, + BRYOPHYTA, + CALLISTO, CALLISTO_6609, + CERBERUS, CERBERUS_5863, CERBERUS_5866, + CHAOS_ELEMENTAL, CHAOS_ELEMENTAL_6505, + CHAOS_FANATIC, + COMMANDER_ZILYANA, COMMANDER_ZILYANA_6493, + CORPOREAL_BEAST, + CRAZY_ARCHAEOLOGIST, + CRYSTALLINE_HUNLLEF, CRYSTALLINE_HUNLLEF_9022, CRYSTALLINE_HUNLLEF_9023, CRYSTALLINE_HUNLLEF_9024, + DAGANNOTH_SUPREME, DAGANNOTH_PRIME, DAGANNOTH_REX, DAGANNOTH_SUPREME_6496, DAGANNOTH_PRIME_6497, DAGANNOTH_REX_6498, + DUSK, DAWN, DUSK_7851, DAWN_7852, DAWN_7853, DUSK_7854, DUSK_7855, + GENERAL_GRAARDOR, GENERAL_GRAARDOR_6494, + GIANT_MOLE, GIANT_MOLE_6499, + HESPORI, + KALPHITE_QUEEN, KALPHITE_QUEEN_963, KALPHITE_QUEEN_965, KALPHITE_QUEEN_4303, KALPHITE_QUEEN_4304, KALPHITE_QUEEN_6500, KALPHITE_QUEEN_6501, + KING_BLACK_DRAGON, KING_BLACK_DRAGON_2642, KING_BLACK_DRAGON_6502, + KRAKEN, KRAKEN_6640, KRAKEN_6656, + KREEARRA, KREEARRA_6492, + KRIL_TSUTSAROTH, KRIL_TSUTSAROTH_6495, + THE_MIMIC, THE_MIMIC_8633, + THE_NIGHTMARE, THE_NIGHTMARE_9426, THE_NIGHTMARE_9427, THE_NIGHTMARE_9428, THE_NIGHTMARE_9429, THE_NIGHTMARE_9430, THE_NIGHTMARE_9431, THE_NIGHTMARE_9432, THE_NIGHTMARE_9433, + OBOR, + SARACHNIS, + SCORPIA, + SKOTIZO, + THERMONUCLEAR_SMOKE_DEVIL, + TZKALZUK, + TZTOKJAD, TZTOKJAD_6506, + VENENATIS, VENENATIS_6610, + VETION, VETION_REBORN, + VORKATH, VORKATH_8058, VORKATH_8059, VORKATH_8060, VORKATH_8061, + ZALCANO, ZALCANO_9050, + ZULRAH, ZULRAH_2043, ZULRAH_2044, + + // ToB + THE_MAIDEN_OF_SUGADINTI, THE_MAIDEN_OF_SUGADINTI_8361, THE_MAIDEN_OF_SUGADINTI_8362, THE_MAIDEN_OF_SUGADINTI_8363, THE_MAIDEN_OF_SUGADINTI_8364, THE_MAIDEN_OF_SUGADINTI_8365, + PESTILENT_BLOAT, + NYLOCAS_VASILIAS, NYLOCAS_VASILIAS_8355, NYLOCAS_VASILIAS_8356, NYLOCAS_VASILIAS_8357, + SOTETSEG, SOTETSEG_8388, + XARPUS_8340, XARPUS_8341, + VERZIK_VITUR_8370, + VERZIK_VITUR_8372, + VERZIK_VITUR_8374, + + // CoX + TEKTON, TEKTON_7541, TEKTON_7542, TEKTON_ENRAGED, TEKTON_ENRAGED_7544, TEKTON_7545, + VESPULA, VESPULA_7531, VESPULA_7532, ABYSSAL_PORTAL, + VANGUARD, VANGUARD_7526, VANGUARD_7527, VANGUARD_7528, VANGUARD_7529, + GREAT_OLM, GREAT_OLM_LEFT_CLAW, GREAT_OLM_RIGHT_CLAW_7553, GREAT_OLM_7554, GREAT_OLM_LEFT_CLAW_7555, + DEATHLY_RANGER, DEATHLY_MAGE, + MUTTADILE, MUTTADILE_7562, MUTTADILE_7563, + VASA_NISTIRIO, VASA_NISTIRIO_7567, + GUARDIAN, GUARDIAN_7570, GUARDIAN_7571, GUARDIAN_7572, + LIZARDMAN_SHAMAN_7573, LIZARDMAN_SHAMAN_7574, + ICE_DEMON, ICE_DEMON_7585, + SKELETAL_MYSTIC, SKELETAL_MYSTIC_7605, SKELETAL_MYSTIC_7606 + ); + + @Inject + private Client client; + + @Inject + private OverlayManager overlayManager; + + @Inject + private PartyService partyService; + + @Inject + private WSClient wsClient; + + @Inject + private DpsOverlay dpsOverlay; + + @Inject + private DpsConfig dpsConfig; + + @Getter(AccessLevel.PACKAGE) + private final Map members = new ConcurrentHashMap<>(); + @Getter(AccessLevel.PACKAGE) + private final DpsMember total = new DpsMember("Total"); + + @Provides + DpsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(DpsConfig.class); + } + + @Override + protected void startUp() + { + total.reset(); + overlayManager.add(dpsOverlay); + wsClient.registerMessage(DpsUpdate.class); + } + + @Override + protected void shutDown() + { + wsClient.unregisterMessage(DpsUpdate.class); + overlayManager.remove(dpsOverlay); + members.clear(); + } + + @Subscribe + public void onPartyChanged(PartyChanged partyChanged) + { + members.clear(); + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied hitsplatApplied) + { + Player player = client.getLocalPlayer(); + Actor actor = hitsplatApplied.getActor(); + if (!(actor instanceof NPC)) + { + return; + } + + Hitsplat hitsplat = hitsplatApplied.getHitsplat(); + + if (hitsplat.isMine()) + { + final int npcId = ((NPC) actor).getId(); + boolean isBoss = BOSSES.contains(npcId); + if (dpsConfig.bossDamage() && !isBoss) + { + return; + } + + int hit = hitsplat.getAmount(); + // Update local member + PartyMember localMember = partyService.getLocalMember(); + // If not in a party, user local player name + final String name = localMember == null ? player.getName() : localMember.getName(); + DpsMember dpsMember = members.computeIfAbsent(name, DpsMember::new); + dpsMember.addDamage(hit); + + // broadcast damage + if (localMember != null) + { + final DpsUpdate specialCounterUpdate = new DpsUpdate(hit); + specialCounterUpdate.setMemberId(localMember.getMemberId()); + wsClient.send(specialCounterUpdate); + } + // apply to total + } + else if (hitsplat.isOthers()) + { + final int npcId = ((NPC) actor).getId(); + boolean isBoss = BOSSES.contains(npcId); + if ((dpsConfig.bossDamage() || actor != player.getInteracting()) && !isBoss) + { + // only track damage to npcs we are attacking, or is a nearby common boss + return; + } + // apply to total + } + else + { + return; + } + + unpause(); + total.addDamage(hitsplat.getAmount()); + } + + @Subscribe + public void onDpsUpdate(DpsUpdate dpsUpdate) + { + if (partyService.getLocalMember().getMemberId().equals(dpsUpdate.getMemberId())) + { + return; + } + + String name = partyService.getMemberById(dpsUpdate.getMemberId()).getName(); + if (name == null) + { + return; + } + + unpause(); + + DpsMember dpsMember = members.computeIfAbsent(name, DpsMember::new); + dpsMember.addDamage(dpsUpdate.getHit()); + } + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked event) + { + if (event.getEntry() == DpsOverlay.RESET_ENTRY) + { + members.clear(); + total.reset(); + } + else if (event.getEntry() == DpsOverlay.UNPAUSE_ENTRY) + { + unpause(); + } + else if (event.getEntry() == DpsOverlay.PAUSE_ENTRY) + { + pause(); + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + NPC npc = npcDespawned.getNpc(); + + if (npc.isDead() && BOSSES.contains(npc.getId())) + { + log.debug("Boss has died!"); + + if (dpsConfig.autoreset()) + { + members.values().forEach(DpsMember::reset); + total.reset(); + } + else if (dpsConfig.autopause()) + { + pause(); + } + } + } + + private void pause() + { + if (total.isPaused()) + { + return; + } + + log.debug("Pausing"); + + for (DpsMember dpsMember : members.values()) + { + dpsMember.pause(); + } + total.pause(); + + dpsOverlay.setPaused(true); + } + + private void unpause() + { + if (!total.isPaused()) + { + return; + } + + log.debug("Unpausing"); + + for (DpsMember dpsMember : members.values()) + { + dpsMember.unpause(); + } + total.unpause(); + + dpsOverlay.setPaused(false); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java new file mode 100644 index 0000000000..e35bbedbd2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.dpscounter; + +import java.time.Duration; +import java.time.Instant; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +class DpsMember +{ + private final String name; + private Instant start; + private Instant end; + private int damage; + + void addDamage(int amount) + { + if (start == null) + { + start = Instant.now(); + } + + damage += amount; + } + + float getDps() + { + if (start == null) + { + return 0; + } + + Instant now = end == null ? Instant.now() : end; + int diff = (int) (now.toEpochMilli() - start.toEpochMilli()) / 1000; + if (diff == 0) + { + return 0; + } + + return (float) damage / (float) diff; + } + + void pause() + { + end = Instant.now(); + } + + boolean isPaused() + { + return start == null || end != null; + } + + void unpause() + { + if (end == null) + { + return; + } + + start = start.plus(Duration.between(end, Instant.now())); + end = null; + } + + void reset() + { + damage = 0; + start = end = Instant.now(); + } + + Duration elapsed() + { + return Duration.between(start, end == null ? Instant.now() : end); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java new file mode 100644 index 0000000000..ead4ded77e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2020 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.dpscounter; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.text.DecimalFormat; +import java.time.Duration; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY; +import net.runelite.api.Player; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.components.ComponentConstants; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.ws.PartyService; + +class DpsOverlay extends OverlayPanel +{ + private static final DecimalFormat DPS_FORMAT = new DecimalFormat("#0.0"); + private static final int PANEL_WIDTH_OFFSET = 10; // assumes 8 for panel component border + 2px between left and right + + static final OverlayMenuEntry RESET_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Reset", "DPS counter"); + static final OverlayMenuEntry PAUSE_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Pause", "DPS counter"); + static final OverlayMenuEntry UNPAUSE_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Unpause", "DPS counter"); + + private final DpsCounterPlugin dpsCounterPlugin; + private final DpsConfig dpsConfig; + private final PartyService partyService; + private final Client client; + private final TooltipManager tooltipManager; + + @Inject + DpsOverlay(DpsCounterPlugin dpsCounterPlugin, DpsConfig dpsConfig, PartyService partyService, Client client, + TooltipManager tooltipManager) + { + super(dpsCounterPlugin); + this.dpsCounterPlugin = dpsCounterPlugin; + this.dpsConfig = dpsConfig; + this.partyService = partyService; + this.client = client; + this.tooltipManager = tooltipManager; + getMenuEntries().add(RESET_ENTRY); + setPaused(false); + } + + @Override + public void onMouseOver() + { + DpsMember total = dpsCounterPlugin.getTotal(); + Duration elapsed = total.elapsed(); + long s = elapsed.getSeconds(); + String format; + if (s >= 3600) + { + format = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60)); + } + else + { + format = String.format("%d:%02d", s / 60, (s % 60)); + } + tooltipManager.add(new Tooltip("Elapsed time: " + format)); + } + + @Override + public Dimension render(Graphics2D graphics) + { + Map dpsMembers = dpsCounterPlugin.getMembers(); + if (dpsMembers.isEmpty()) + { + return null; + } + + boolean inParty = !partyService.getMembers().isEmpty(); + boolean showDamage = dpsConfig.showDamage(); + DpsMember total = dpsCounterPlugin.getTotal(); + boolean paused = total.isPaused(); + + final String title = (inParty ? "Party " : "") + (showDamage ? "Damage" : "DPS") + (paused ? " (paused)" : ""); + panelComponent.getChildren().add( + TitleComponent.builder() + .text(title) + .build()); + + int maxWidth = ComponentConstants.STANDARD_WIDTH; + FontMetrics fontMetrics = graphics.getFontMetrics(); + + for (DpsMember dpsMember : dpsMembers.values()) + { + String left = dpsMember.getName(); + String right = showDamage ? QuantityFormatter.formatNumber(dpsMember.getDamage()) : DPS_FORMAT.format(dpsMember.getDps()); + maxWidth = Math.max(maxWidth, fontMetrics.stringWidth(left) + fontMetrics.stringWidth(right)); + panelComponent.getChildren().add( + LineComponent.builder() + .left(left) + .right(right) + .build()); + } + + panelComponent.setPreferredSize(new Dimension(maxWidth + PANEL_WIDTH_OFFSET, 0)); + + if (!inParty) + { + Player player = client.getLocalPlayer(); + if (player.getName() != null) + { + DpsMember self = dpsMembers.get(player.getName()); + + if (self != null && total.getDamage() > self.getDamage()) + { + panelComponent.getChildren().add( + LineComponent.builder() + .left(total.getName()) + .right(showDamage ? Integer.toString(total.getDamage()) : DPS_FORMAT.format(total.getDps())) + .build()); + } + } + } + + return super.render(graphics); + } + + void setPaused(boolean paused) + { + OverlayMenuEntry remove = paused ? PAUSE_ENTRY : UNPAUSE_ENTRY; + OverlayMenuEntry add = paused ? UNPAUSE_ENTRY : PAUSE_ENTRY; + getMenuEntries().remove(remove); + if (!getMenuEntries().contains(add)) + { + getMenuEntries().add(add); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java new file mode 100644 index 0000000000..81e5859b38 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.dpscounter; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import net.runelite.http.api.ws.messages.party.PartyMemberMessage; + +@Value +@EqualsAndHashCode(callSuper = true) +public class DpsUpdate extends PartyMemberMessage +{ + private int hit; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNet.java b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNet.java new file mode 100644 index 0000000000..99dd0356e1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNet.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.driftnet; + +import java.util.Set; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.runelite.api.GameObject; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; + +@Data +@RequiredArgsConstructor +class DriftNet +{ + private final int objectId; + private final Varbits statusVarbit; + private final Varbits countVarbit; + private final Set adjacentTiles; + + private GameObject net; + private DriftNetStatus status; + private int count; + @Setter + private DriftNetStatus prevTickStatus; + + // Nets that are not accepting fish are those currently not accepting, or those which were not + // accepting in the previous tick. (When a fish shoal is 2 tiles adjacent to a drift net and is + // moving to a net that is just being setup it will be denied even though the net is currently + // in the CATCHING status) + boolean isNotAcceptingFish() + { + return (status != DriftNetStatus.CATCH && status != DriftNetStatus.SET) || + (prevTickStatus != DriftNetStatus.CATCH && prevTickStatus != DriftNetStatus.SET); + } + + String getFormattedCountText() + { + return status != DriftNetStatus.UNSET ? count + "/10" : ""; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetConfig.java new file mode 100644 index 0000000000..5ca1f29194 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetConfig.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.driftnet; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Range; +import net.runelite.client.config.Units; + +@ConfigGroup(DriftNetPlugin.CONFIG_GROUP) +public interface DriftNetConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "showNetStatus", + name = "Show net status", + description = "Show net status and fish count" + ) + default boolean showNetStatus() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "countColor", + name = "Fish count color", + description = "Color of the fish count text" + ) + default Color countColor() + { + return Color.WHITE; + } + + @ConfigItem( + position = 3, + keyName = "highlightUntaggedFish", + name = "Highlight untagged fish", + description = "Highlight the untagged fish" + ) + default boolean highlightUntaggedFish() + { + return true; + } + + @ConfigItem( + position = 4, + keyName = "timeoutDelay", + name = "Tagged timeout", + description = "Time required for a tag to expire" + ) + @Range( + min = 1, + max = 100 + ) + @Units(Units.TICKS) + default int timeoutDelay() + { + return 60; + } + + @ConfigItem( + keyName = "untaggedFishColor", + name = "Untagged fish color", + description = "Color of untagged fish", + position = 5 + ) + default Color untaggedFishColor() + { + return Color.CYAN; + } + + @ConfigItem( + keyName = "tagAnnette", + name = "Tag Annette when no nets in inventory", + description = "Tag Annette when no nets in inventory", + position = 6 + ) + default boolean tagAnnetteWhenNoNets() + { + return true; + } + + @ConfigItem( + keyName = "annetteTagColor", + name = "Annette tag color", + description = "Color of Annette tag", + position = 7 + ) + default Color annetteTagColor() + { + return Color.RED; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetOverlay.java new file mode 100644 index 0000000000..1ff9e117f8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetOverlay.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.driftnet; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Shape; +import javax.inject.Inject; +import net.runelite.api.GameObject; +import net.runelite.api.NPC; +import net.runelite.api.Point; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.OverlayUtil; + +class DriftNetOverlay extends Overlay +{ + private final DriftNetConfig config; + private final DriftNetPlugin plugin; + + @Inject + private DriftNetOverlay(DriftNetConfig config, DriftNetPlugin plugin) + { + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setPriority(OverlayPriority.LOW); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.isInDriftNetArea()) + { + return null; + } + + if (config.highlightUntaggedFish()) + { + renderFish(graphics); + } + if (config.showNetStatus()) + { + renderNets(graphics); + } + if (config.tagAnnetteWhenNoNets()) + { + renderAnnette(graphics); + } + + return null; + } + + private void renderFish(Graphics2D graphics) + { + for (NPC fish : plugin.getFish()) + { + if (!plugin.getTaggedFish().containsKey(fish)) + { + OverlayUtil.renderActorOverlay(graphics, fish, "", config.untaggedFishColor()); + } + } + } + + private void renderNets(Graphics2D graphics) + { + for (DriftNet net : plugin.getNETS()) + { + final Shape polygon = net.getNet().getConvexHull(); + + if (polygon != null) + { + OverlayUtil.renderPolygon(graphics, polygon, net.getStatus().getColor()); + } + + String text = net.getFormattedCountText(); + Point textLocation = net.getNet().getCanvasTextLocation(graphics, text, 0); + if (textLocation != null) + { + OverlayUtil.renderTextLocation(graphics, textLocation, text, config.countColor()); + } + } + } + + private void renderAnnette(Graphics2D graphics) + { + GameObject annette = plugin.getAnnette(); + if (annette != null && !plugin.isDriftNetsInInventory()) + { + OverlayUtil.renderPolygon(graphics, annette.getConvexHull(), config.annetteTagColor()); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetPlugin.java new file mode 100644 index 0000000000..b7eecf2545 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetPlugin.java @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.driftnet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Provides; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.Getter; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.GameState; +import net.runelite.api.InventoryID; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.NpcID; +import net.runelite.api.NullObjectID; +import net.runelite.api.ObjectID; +import net.runelite.api.Player; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameObjectDespawned; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.InteractingChanged; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.events.VarbitChanged; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Drift Net", + description = "Display information about drift nets", + tags = {"hunter", "fishing", "drift", "net"}, + enabledByDefault = false +) +public class DriftNetPlugin extends Plugin +{ + static final String CONFIG_GROUP = "driftnet"; + private static final int UNDERWATER_REGION = 15008; + private static final String CHAT_PRODDING_FISH = "You prod at the shoal of fish to scare it."; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private DriftNetConfig config; + + @Inject + private OverlayManager overlayManager; + + @Inject + private DriftNetOverlay overlay; + + @Getter + private Set fish = new HashSet<>(); + @Getter + private Map taggedFish = new HashMap<>(); + @Getter + private final List NETS = ImmutableList.of( + new DriftNet(NullObjectID.NULL_31433, Varbits.NORTH_NET_STATUS, Varbits.NORTH_NET_CATCH_COUNT, ImmutableSet.of( + new WorldPoint(3746, 10297, 1), + new WorldPoint(3747, 10297, 1), + new WorldPoint(3748, 10297, 1), + new WorldPoint(3749, 10297, 1) + )), + new DriftNet(NullObjectID.NULL_31434, Varbits.SOUTH_NET_STATUS, Varbits.SOUTH_NET_CATCH_COUNT, ImmutableSet.of( + new WorldPoint(3742, 10288, 1), + new WorldPoint(3742, 10289, 1), + new WorldPoint(3742, 10290, 1), + new WorldPoint(3742, 10291, 1), + new WorldPoint(3742, 10292, 1) + ))); + + @Getter + private boolean inDriftNetArea; + private boolean armInteraction; + + @Getter + private boolean driftNetsInInventory; + + @Getter + private GameObject annette; + + @Provides + DriftNetConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(DriftNetConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + + if (client.getGameState() == GameState.LOGGED_IN) + { + clientThread.invokeLater(() -> + { + inDriftNetArea = checkArea(); + updateDriftNetVarbits(); + }); + } + } + + @Override + protected void shutDown() + { + overlayManager.remove(overlay); + reset(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() != GameState.LOGGED_IN) + { + annette = null; + } + switch (event.getGameState()) + { + case LOGIN_SCREEN: + case HOPPING: + case LOADING: + reset(); + break; + case LOGGED_IN: + inDriftNetArea = checkArea(); + updateDriftNetVarbits(); + break; + } + } + + private void reset() + { + fish.clear(); + taggedFish.clear(); + armInteraction = false; + inDriftNetArea = false; + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + updateDriftNetVarbits(); + } + + private void updateDriftNetVarbits() + { + if (!inDriftNetArea) + { + return; + } + + for (DriftNet net : NETS) + { + DriftNetStatus status = DriftNetStatus.of(client.getVar(net.getStatusVarbit())); + int count = client.getVar(net.getCountVarbit()); + + net.setStatus(status); + net.setCount(count); + } + } + + @Subscribe + public void onInteractingChanged(InteractingChanged event) + { + if (armInteraction + && event.getSource() == client.getLocalPlayer() + && event.getTarget() instanceof NPC + && ((NPC) event.getTarget()).getId() == NpcID.FISH_SHOAL) + { + tagFish(event.getTarget()); + armInteraction = false; + } + } + + private boolean isFishNextToNet(NPC fish, Collection nets) + { + final WorldPoint fishTile = WorldPoint.fromLocalInstance(client, fish.getLocalLocation()); + return nets.stream().anyMatch(net -> net.getAdjacentTiles().contains(fishTile)); + } + + private boolean isTagExpired(Integer tick) + { + return tick + config.timeoutDelay() < client.getTickCount(); + } + + @Subscribe + public void onGameTick(GameTick tick) + { + if (!inDriftNetArea) + { + return; + } + + List closedNets = NETS.stream() + .filter(DriftNet::isNotAcceptingFish) + .collect(Collectors.toList()); + + taggedFish.entrySet().removeIf(entry -> + isTagExpired(entry.getValue()) || + isFishNextToNet(entry.getKey(), closedNets) + ); + + NETS.forEach(net -> net.setPrevTickStatus(net.getStatus())); + + armInteraction = false; + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (!inDriftNetArea) + { + return; + } + + if (event.getType() == ChatMessageType.SPAM && event.getMessage().equals(CHAT_PRODDING_FISH)) + { + Actor target = client.getLocalPlayer().getInteracting(); + + if (target instanceof NPC && ((NPC) target).getId() == NpcID.FISH_SHOAL) + { + tagFish(target); + } + else + { + // If the fish is on an adjacent tile, the interaction change happens after + // the chat message is sent, so we arm it + armInteraction = true; + } + } + } + + private void tagFish(Actor fish) + { + NPC fishTarget = (NPC) fish; + taggedFish.put(fishTarget, client.getTickCount()); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned event) + { + final NPC npc = event.getNpc(); + if (npc.getId() == NpcID.FISH_SHOAL) + { + fish.add(npc); + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned event) + { + final NPC npc = event.getNpc(); + fish.remove(npc); + taggedFish.remove(npc); + } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) + { + GameObject object = event.getGameObject(); + if (object.getId() == ObjectID.ANNETTE) + { + annette = object; + } + + for (DriftNet net : NETS) + { + if (net.getObjectId() == object.getId()) + { + net.setNet(object); + } + } + } + + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned event) + { + GameObject object = event.getGameObject(); + if (object == annette) + { + annette = null; + } + + for (DriftNet net : NETS) + { + if (net.getObjectId() == object.getId()) + { + net.setNet(null); + } + } + } + + @Subscribe + public void onItemContainerChanged(final ItemContainerChanged event) + { + final ItemContainer itemContainer = event.getItemContainer(); + if (itemContainer != client.getItemContainer(InventoryID.INVENTORY)) + { + return; + } + + driftNetsInInventory = itemContainer.contains(ItemID.DRIFT_NET); + } + + private boolean checkArea() + { + final Player localPlayer = client.getLocalPlayer(); + if (localPlayer == null || !client.isInInstancedRegion()) + { + return false; + } + + final WorldPoint point = WorldPoint.fromLocalInstance(client, localPlayer.getLocalLocation()); + return point.getRegionID() == UNDERWATER_REGION; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetStatus.java b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetStatus.java new file mode 100644 index 0000000000..e6f7d5699e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/driftnet/DriftNetStatus.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.driftnet; + +import java.awt.Color; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +enum DriftNetStatus +{ + UNSET(Color.YELLOW), + SET(Color.GREEN), + CATCH(Color.GREEN), + FULL(Color.RED); + + private final Color color; + + static DriftNetStatus of(int varbitValue) + { + switch (varbitValue) + { + case 0: + return UNSET; + case 1: + return SET; + case 2: + return CATCH; + case 3: + return FULL; + default: + return null; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java new file mode 100644 index 0000000000..acee57ab0f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019, Lotto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.emojis; + +import com.google.common.collect.ImmutableMap; +import java.awt.image.BufferedImage; +import java.util.Map; +import net.runelite.client.util.ImageUtil; + +enum Emoji +{ + SLIGHT_SMILE(":)"), + JOY("=')"), + COWBOY("3:)"), + BLUSH("^_^"), + SMILE(":D"), + GRINNING("=D"), + WINK(";)"), + STUCK_OUT_TONGUE_CLOSED_EYES("X-P"), + STUCK_OUT_TONGUE(":P"), + YUM("=P~"), + HUGGING(":D"), // >:D< + TRIUMPH(":"), // :> + THINKING(":-?"), + CONFUSED(":/"), + NEUTRAL_FACE("=|"), + EXPRESSIONLESS(":|"), + UNAMUSED(":-|"), + SLIGHT_FROWN(":("), + FROWNING2("=("), + CRY(":'("), + SOB(":_("), + FLUSHED(":$"), + ZIPPER_MOUTH(":-#"), + PERSEVERE("_"), // >_< + SUNGLASSES("8-)"), + INNOCENT("O:)"), + SMILING_IMP(":)"), // >:) + RAGE(":("), // >:( + HUSHED(":-O"), + OPEN_MOUTH(":O"), + SCREAM(":-@"), + SEE_NO_EVIL("X_X"), + DANCER("\\:D/"), + OK_HAND("(Ok)"), + THUMBSUP("(Y)"), + THUMBSDOWN("(N)"), + HEARTS("3"), // <3 + BROKEN_HEART("/3"), // "), // <>< + CAT(":3"), + DOG("=3"), + CRAB("V(;,;)V"), + FORK_AND_KNIFE("--E"), + COOKING("--(o)"), + PARTY_POPPER("@@@"), + EYES("O.O"), + SWEAT(";;"), + PILE_OF_POO("~@~"), + FIRE("(/\\)"), + ALIEN("(@.@)"), + EGGPLANT("8=D"), + WAVE("(^_^)/"), + HEART_EYES("(*.*)"), + FACEPALM("M-)"), + PENSIVE("V_V"), + ACORN("D~"), // emojiMap; + + private final String trigger; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (final Emoji emoji : values()) + { + builder.put(emoji.trigger, emoji); + } + + emojiMap = builder.build(); + } + + Emoji(String trigger) + { + this.trigger = trigger; + } + + BufferedImage loadImage() + { + return ImageUtil.getResourceStreamFromClass(getClass(), this.name().toLowerCase() + ".png"); + } + + static Emoji getEmoji(String trigger) + { + return emojiMap.get(trigger); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java new file mode 100644 index 0000000000..e63a97c3cc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2019, Lotto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.emojis; + +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import javax.inject.Inject; +import joptsimple.internal.Strings; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.IndexedSprite; +import net.runelite.api.MessageNode; +import net.runelite.api.Player; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.OverheadTextChanged; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Emojis", + description = "Replaces common emoticons such as :) with their corresponding emoji in the chat", + enabledByDefault = false +) +@Slf4j +public class EmojiPlugin extends Plugin +{ + private static final Pattern WHITESPACE_REGEXP = Pattern.compile("[\\s\\u00A0]"); + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ChatMessageManager chatMessageManager; + + private int modIconsStart = -1; + + @Override + protected void startUp() + { + clientThread.invoke(this::loadEmojiIcons); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() == GameState.LOGGED_IN) + { + loadEmojiIcons(); + } + } + + private void loadEmojiIcons() + { + final IndexedSprite[] modIcons = client.getModIcons(); + if (modIconsStart != -1 || modIcons == null) + { + return; + } + + final Emoji[] emojis = Emoji.values(); + final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + emojis.length); + modIconsStart = modIcons.length; + + for (int i = 0; i < emojis.length; i++) + { + final Emoji emoji = emojis[i]; + + try + { + final BufferedImage image = emoji.loadImage(); + final IndexedSprite sprite = ImageUtil.getImageIndexedSprite(image, client); + newModIcons[modIconsStart + i] = sprite; + } + catch (Exception ex) + { + log.warn("Failed to load the sprite for emoji " + emoji, ex); + } + } + + log.debug("Adding emoji icons"); + client.setModIcons(newModIcons); + } + + @Subscribe + public void onChatMessage(ChatMessage chatMessage) + { + if (client.getGameState() != GameState.LOGGED_IN || modIconsStart == -1) + { + return; + } + + switch (chatMessage.getType()) + { + case PUBLICCHAT: + case MODCHAT: + case FRIENDSCHAT: + case PRIVATECHAT: + case PRIVATECHATOUT: + case MODPRIVATECHAT: + break; + default: + return; + } + + final MessageNode messageNode = chatMessage.getMessageNode(); + final String message = messageNode.getValue(); + final String updatedMessage = updateMessage(message); + + if (updatedMessage == null) + { + return; + } + + messageNode.setRuneLiteFormatMessage(updatedMessage); + chatMessageManager.update(messageNode); + client.refreshChat(); + } + + @Subscribe + public void onOverheadTextChanged(final OverheadTextChanged event) + { + if (!(event.getActor() instanceof Player)) + { + return; + } + + final String message = event.getOverheadText(); + final String updatedMessage = updateMessage(message); + + if (updatedMessage == null) + { + return; + } + + event.getActor().setOverheadText(updatedMessage); + } + + @Nullable + String updateMessage(final String message) + { + final String[] messageWords = WHITESPACE_REGEXP.split(message); + + boolean editedMessage = false; + for (int i = 0; i < messageWords.length; i++) + { + // Remove tags except for and + final String trigger = Text.removeFormattingTags(messageWords[i]); + final Emoji emoji = Emoji.getEmoji(trigger); + + if (emoji == null) + { + continue; + } + + final int emojiId = modIconsStart + emoji.ordinal(); + + messageWords[i] = messageWords[i].replace(trigger, ""); + editedMessage = true; + } + + // If we haven't edited the message any, don't update it. + if (!editedMessage) + { + return null; + } + + return Strings.join(messageWords, " "); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java new file mode 100644 index 0000000000..d3ee118b8c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018, Lotto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.entityhider; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("entityhider") +public interface EntityHiderConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "hidePlayers", + name = "Hide Players", + description = "Configures whether or not players are hidden" + ) + default boolean hidePlayers() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "hidePlayers2D", + name = "Hide Players 2D", + description = "Configures whether or not players 2D elements are hidden" + ) + default boolean hidePlayers2D() + { + return true; + } + + @ConfigItem( + position = 3, + keyName = "hideFriends", + name = "Hide Friends", + description = "Configures whether or not friends are hidden" + ) + default boolean hideFriends() + { + return false; + } + + @ConfigItem( + position = 4, + keyName = "hideClanMates", + name = "Hide Friends Chat members", + description = "Configures whether or not friends chat members are hidden" + ) + default boolean hideFriendsChatMembers() + { + return false; + } + + @ConfigItem( + position = 5, + keyName = "hideLocalPlayer", + name = "Hide Local Player", + description = "Configures whether or not the local player is hidden" + ) + default boolean hideLocalPlayer() + { + return false; + } + + @ConfigItem( + position = 6, + keyName = "hideLocalPlayer2D", + name = "Hide Local Player 2D", + description = "Configures whether or not the local player's 2D elements are hidden" + ) + default boolean hideLocalPlayer2D() + { + return false; + } + + @ConfigItem( + position = 7, + keyName = "hideNPCs", + name = "Hide NPCs", + description = "Configures whether or not NPCs are hidden" + ) + default boolean hideNPCs() + { + return false; + } + + @ConfigItem( + position = 8, + keyName = "hideNPCs2D", + name = "Hide NPCs 2D", + description = "Configures whether or not NPCs 2D elements are hidden" + ) + default boolean hideNPCs2D() + { + return false; + } + + @ConfigItem( + position = 9, + keyName = "hidePets", + name = "Hide Pets", + description = "Configures whether or not other player pets are hidden" + ) + default boolean hidePets() + { + return false; + } + + @ConfigItem( + position = 10, + keyName = "hideAttackers", + name = "Hide Attackers", + description = "Configures whether or not NPCs/players attacking you are hidden" + ) + default boolean hideAttackers() + { + return false; + } + + @ConfigItem( + position = 11, + keyName = "hideProjectiles", + name = "Hide Projectiles", + description = "Configures whether or not projectiles are hidden" + ) + default boolean hideProjectiles() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java new file mode 100644 index 0000000000..155bc6cc38 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018, Lotto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.entityhider; + +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Entity Hider", + description = "Hide players, NPCs, and/or projectiles", + tags = {"npcs", "players", "projectiles"}, + enabledByDefault = false +) +public class EntityHiderPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private EntityHiderConfig config; + + @Provides + EntityHiderConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(EntityHiderConfig.class); + } + + @Override + protected void startUp() + { + updateConfig(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged e) + { + updateConfig(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGED_IN) + { + client.setIsHidingEntities(isPlayerRegionAllowed()); + } + } + + private void updateConfig() + { + client.setIsHidingEntities(isPlayerRegionAllowed()); + + client.setPlayersHidden(config.hidePlayers()); + client.setPlayersHidden2D(config.hidePlayers2D()); + + client.setFriendsHidden(config.hideFriends()); + client.setFriendsChatMembersHidden(config.hideFriendsChatMembers()); + + client.setLocalPlayerHidden(config.hideLocalPlayer()); + client.setLocalPlayerHidden2D(config.hideLocalPlayer2D()); + + client.setNPCsHidden(config.hideNPCs()); + client.setNPCsHidden2D(config.hideNPCs2D()); + + client.setPetsHidden(config.hidePets()); + + client.setAttackersHidden(config.hideAttackers()); + + client.setProjectilesHidden(config.hideProjectiles()); + } + + @Override + protected void shutDown() throws Exception + { + client.setIsHidingEntities(false); + + client.setPlayersHidden(false); + client.setPlayersHidden2D(false); + + client.setFriendsHidden(false); + client.setFriendsChatMembersHidden(false); + + client.setLocalPlayerHidden(false); + client.setLocalPlayerHidden2D(false); + + client.setNPCsHidden(false); + client.setNPCsHidden2D(false); + + client.setPetsHidden(false); + + client.setAttackersHidden(false); + + client.setProjectilesHidden(false); + } + + private boolean isPlayerRegionAllowed() + { + final Player localPlayer = client.getLocalPlayer(); + + if (localPlayer == null) + { + return true; + } + + final int playerRegionID = WorldPoint.fromLocalInstance(client, localPlayer.getLocalLocation()).getRegionID(); + + // 9520 = Castle Wars + return playerRegionID != 9520; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/CacheKey.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/CacheKey.java new file mode 100644 index 0000000000..8cd0340b69 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/CacheKey.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.examine; + +import java.util.Objects; + +class CacheKey +{ + private final ExamineType type; + private final int id; + + public CacheKey(ExamineType type, int id) + { + this.type = type; + this.id = id; + } + + @Override + public int hashCode() + { + int hash = 3; + hash = 23 * hash + Objects.hashCode(this.type); + hash = 23 * hash + this.id; + return hash; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + final CacheKey other = (CacheKey) obj; + if (this.id != other.id) + { + return false; + } + return this.type == other.type; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java new file mode 100644 index 0000000000..976082dcd7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.examine; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.inject.Provides; +import java.time.Instant; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.regex.Pattern; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemID; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import static net.runelite.api.widgets.WidgetInfo.SEED_VAULT_ITEM_CONTAINER; +import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; +import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.chat.ChatColorType; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.util.Text; +import net.runelite.http.api.examine.ExamineClient; +import okhttp3.OkHttpClient; + +/** + * Submits examine info to the api + * + * @author Adam + */ +@PluginDescriptor( + name = "Examine", + description = "Send examine information to the API", + tags = {"npcs", "items", "inventory", "objects"} +) +@Slf4j +public class ExaminePlugin extends Plugin +{ + private static final Pattern X_PATTERN = Pattern.compile("^\\d+ x "); + + private final Deque pending = new ArrayDeque<>(); + private final Cache cache = CacheBuilder.newBuilder() + .maximumSize(128L) + .build(); + + @Inject + private ExamineClient examineClient; + + @Inject + private Client client; + + @Inject + private ItemManager itemManager; + + @Inject + private ChatMessageManager chatMessageManager; + + @Provides + ExamineClient provideExamineClient(OkHttpClient okHttpClient) + { + return new ExamineClient(okHttpClient); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + pending.clear(); + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (!Text.removeTags(event.getMenuOption()).equals("Examine")) + { + return; + } + + ExamineType type; + int id, quantity = -1; + switch (event.getMenuAction()) + { + case EXAMINE_ITEM: + { + type = ExamineType.ITEM; + id = event.getId(); + + int widgetId = event.getWidgetId(); + int widgetGroup = TO_GROUP(widgetId); + int widgetChild = TO_CHILD(widgetId); + Widget widget = client.getWidget(widgetGroup, widgetChild); + WidgetItem widgetItem = widget.getWidgetItem(event.getActionParam()); + quantity = widgetItem != null && widgetItem.getId() >= 0 ? widgetItem.getQuantity() : 1; + + // Examine on inventory items with more than 100000 quantity is handled locally and shows the item stack + // count, instead of sending the examine packet, so that you can see how many items are in the stack. + // Replace that message with one that formats the quantity using the quantity formatter instead. + if (quantity >= 100_000) + { + int itemId = event.getId(); + final ChatMessageBuilder message = new ChatMessageBuilder() + .append(QuantityFormatter.formatNumber(quantity)).append(" x ").append(itemManager.getItemComposition(itemId).getName()); + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.ITEM_EXAMINE) + .runeLiteFormattedMessage(message.build()) + .build()); + event.consume(); + } + break; + } + case EXAMINE_ITEM_GROUND: + type = ExamineType.ITEM; + id = event.getId(); + break; + case CC_OP_LOW_PRIORITY: + { + type = ExamineType.ITEM_BANK_EQ; + int[] qi = findItemFromWidget(event.getWidgetId(), event.getActionParam()); + if (qi == null) + { + log.debug("Examine for item with unknown widget: {}", event); + return; + } + quantity = qi[0]; + id = qi[1]; + break; + } + case EXAMINE_OBJECT: + type = ExamineType.OBJECT; + id = event.getId(); + break; + case EXAMINE_NPC: + type = ExamineType.NPC; + id = event.getId(); + break; + default: + return; + } + + PendingExamine pendingExamine = new PendingExamine(); + pendingExamine.setType(type); + pendingExamine.setId(id); + pendingExamine.setQuantity(quantity); + pendingExamine.setCreated(Instant.now()); + pending.push(pendingExamine); + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + ExamineType type; + switch (event.getType()) + { + case ITEM_EXAMINE: + type = ExamineType.ITEM; + break; + case OBJECT_EXAMINE: + type = ExamineType.OBJECT; + break; + case NPC_EXAMINE: + type = ExamineType.NPC; + break; + case GAMEMESSAGE: + type = ExamineType.ITEM_BANK_EQ; + break; + default: + return; + } + + if (pending.isEmpty()) + { + log.debug("Got examine without a pending examine?"); + return; + } + + PendingExamine pendingExamine = pending.pop(); + + if (pendingExamine.getType() != type) + { + log.debug("Type mismatch for pending examine: {} != {}", pendingExamine.getType(), type); + pending.clear(); // eh + return; + } + + log.debug("Got examine for {} {}: {}", pendingExamine.getType(), pendingExamine.getId(), event.getMessage()); + + // If it is an item, show the price of it + final ItemComposition itemComposition; + if (pendingExamine.getType() == ExamineType.ITEM || pendingExamine.getType() == ExamineType.ITEM_BANK_EQ) + { + final int itemId = pendingExamine.getId(); + final int itemQuantity = pendingExamine.getQuantity(); + + if (itemId == ItemID.COINS_995) + { + return; + } + + itemComposition = itemManager.getItemComposition(itemId); + getItemPrice(itemComposition.getId(), itemComposition, itemQuantity); + } + else + { + itemComposition = null; + } + + // Don't submit examine info for tradeable items, which we already have from the RS item api + if (itemComposition != null && itemComposition.isTradeable()) + { + return; + } + + // Large quantities of items show eg. 100000 x Coins + if (type == ExamineType.ITEM && X_PATTERN.matcher(event.getMessage()).lookingAt()) + { + return; + } + + CacheKey key = new CacheKey(type, pendingExamine.getId()); + Boolean cached = cache.getIfPresent(key); + if (cached != null) + { + return; + } + + cache.put(key, Boolean.TRUE); + submitExamine(pendingExamine, event.getMessage()); + } + + private int[] findItemFromWidget(int widgetId, int actionParam) + { + int widgetGroup = TO_GROUP(widgetId); + int widgetChild = TO_CHILD(widgetId); + Widget widget = client.getWidget(widgetGroup, widgetChild); + + if (widget == null) + { + return null; + } + + if (WidgetInfo.EQUIPMENT.getGroupId() == widgetGroup) + { + Widget widgetItem = widget.getChild(1); + if (widgetItem != null) + { + return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()}; + } + } + else if (WidgetInfo.SMITHING_INVENTORY_ITEMS_CONTAINER.getGroupId() == widgetGroup) + { + Widget widgetItem = widget.getChild(2); + if (widgetItem != null) + { + return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()}; + } + } + else if (WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getGroupId() == widgetGroup + || WidgetInfo.RUNE_POUCH_ITEM_CONTAINER.getGroupId() == widgetGroup) + { + Widget widgetItem = widget.getChild(actionParam); + if (widgetItem != null) + { + return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()}; + } + } + else if (WidgetInfo.BANK_ITEM_CONTAINER.getGroupId() == widgetGroup + || WidgetInfo.CLUE_SCROLL_REWARD_ITEM_CONTAINER.getGroupId() == widgetGroup + || WidgetInfo.LOOTING_BAG_CONTAINER.getGroupId() == widgetGroup + || WidgetID.SEED_VAULT_INVENTORY_GROUP_ID == widgetGroup + || WidgetID.SEED_BOX_GROUP_ID == widgetGroup + || WidgetID.PLAYER_TRADE_SCREEN_GROUP_ID == widgetGroup + || WidgetID.PLAYER_TRADE_INVENTORY_GROUP_ID == widgetGroup) + { + Widget widgetItem = widget.getChild(actionParam); + if (widgetItem != null) + { + return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()}; + } + } + else if (WidgetInfo.SHOP_ITEMS_CONTAINER.getGroupId() == widgetGroup) + { + Widget widgetItem = widget.getChild(actionParam); + if (widgetItem != null) + { + return new int[]{1, widgetItem.getItemId()}; + } + } + else if (WidgetID.SEED_VAULT_GROUP_ID == widgetGroup) + { + Widget widgetItem = client.getWidget(SEED_VAULT_ITEM_CONTAINER).getChild(actionParam); + if (widgetItem != null) + { + return new int[]{widgetItem.getItemQuantity(), widgetItem.getItemId()}; + } + } + + return null; + } + + @VisibleForTesting + void getItemPrice(int id, ItemComposition itemComposition, int quantity) + { + // quantity is at least 1 + quantity = Math.max(1, quantity); + final int gePrice = itemManager.getItemPrice(id); + final int alchPrice = itemComposition.getHaPrice(); + + if (gePrice > 0 || alchPrice > 0) + { + final ChatMessageBuilder message = new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append("Price of ") + .append(ChatColorType.HIGHLIGHT); + + if (quantity > 1) + { + message + .append(QuantityFormatter.formatNumber(quantity)) + .append(" x "); + } + + message + .append(itemComposition.getName()) + .append(ChatColorType.NORMAL) + .append(":"); + + if (gePrice > 0) + { + message + .append(ChatColorType.NORMAL) + .append(" GE average ") + .append(ChatColorType.HIGHLIGHT) + .append(QuantityFormatter.formatNumber((long) gePrice * quantity)); + + if (quantity > 1) + { + message + .append(ChatColorType.NORMAL) + .append(" (") + .append(ChatColorType.HIGHLIGHT) + .append(QuantityFormatter.formatNumber(gePrice)) + .append(ChatColorType.NORMAL) + .append("ea)"); + } + } + + if (alchPrice > 0) + { + message + .append(ChatColorType.NORMAL) + .append(" HA value ") + .append(ChatColorType.HIGHLIGHT) + .append(QuantityFormatter.formatNumber((long) alchPrice * quantity)); + + if (quantity > 1) + { + message + .append(ChatColorType.NORMAL) + .append(" (") + .append(ChatColorType.HIGHLIGHT) + .append(QuantityFormatter.formatNumber(alchPrice)) + .append(ChatColorType.NORMAL) + .append("ea)"); + } + } + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.ITEM_EXAMINE) + .runeLiteFormattedMessage(message.build()) + .build()); + } + } + + private void submitExamine(PendingExamine examine, String text) + { + int id = examine.getId(); + + switch (examine.getType()) + { + case ITEM: + examineClient.submitItem(id, text); + break; + case OBJECT: + examineClient.submitObject(id, text); + break; + case NPC: + examineClient.submitNpc(id, text); + break; + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExamineType.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExamineType.java new file mode 100644 index 0000000000..790d0f1d77 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExamineType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.examine; + +public enum ExamineType +{ + ITEM, + ITEM_BANK_EQ, + NPC, + OBJECT; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java new file mode 100644 index 0000000000..96b7389e06 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.examine; + +import java.time.Instant; +import lombok.Data; + +@Data +class PendingExamine +{ + private ExamineType type; + private int id; + private int quantity; + private Instant created; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingConfig.java new file mode 100644 index 0000000000..131e36e6c1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.fairyring; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("fairyrings") +public interface FairyRingConfig extends Config +{ + @ConfigItem( + keyName = "autoOpen", + name = "Open search automatically", + description = "Open the search widget every time you enter a fairy ring" + ) + default boolean autoOpen() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingPlugin.java new file mode 100644 index 0000000000..28e480ba34 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRingPlugin.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2017, Tyler + * Copyright (c) 2018, Yoav Ram + * Copyright (c) 2018, Infinitay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.fairyring; + +import com.google.common.base.Strings; +import com.google.inject.Provides; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import javax.annotation.Nullable; +import javax.inject.Inject; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.ScriptEvent; +import net.runelite.api.ScriptID; +import net.runelite.api.SoundEffectID; +import net.runelite.api.SpriteID; +import net.runelite.api.Varbits; +import net.runelite.api.widgets.WidgetType; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.game.chatbox.ChatboxTextInput; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.Text; + +@Slf4j +@PluginDescriptor( + name = "Fairy Rings", + description = "Show the location of the fairy ring teleport", + tags = {"teleportation"} +) +public class FairyRingPlugin extends Plugin +{ + private static final String[] leftDial = {"A", "D", "C", "B"}; + private static final String[] middleDial = {"I", "L", "K", "J"}; + private static final String[] rightDial = {"P", "S", "R", "Q"}; + + private static final int ENTRY_PADDING = 3; + + private static final String MENU_OPEN = "Open"; + private static final String MENU_CLOSE = "Close"; + + @Inject + private Client client; + + @Inject + private FairyRingConfig config; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + @Inject + private ClientThread clientThread; + + private ChatboxTextInput searchInput = null; + private Widget searchBtn; + private Collection codes = null; + + @Data + private static class CodeWidgets + { + // The fairy hideout has both of these null, because its not the same as the rest of them + @Nullable + private Widget favorite; + + @Nullable + private Widget code; + + private Widget description; + } + + @Provides + FairyRingConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(FairyRingConfig.class); + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + setWidgetTextToDestination(); + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded widgetLoaded) + { + if (widgetLoaded.getGroupId() == WidgetID.FAIRY_RING_PANEL_GROUP_ID) + { + setWidgetTextToDestination(); + + Widget header = client.getWidget(WidgetInfo.FAIRY_RING_HEADER); + if (header != null) + { + searchBtn = header.createChild(-1, WidgetType.GRAPHIC); + searchBtn.setSpriteId(SpriteID.GE_SEARCH); + searchBtn.setOriginalWidth(17); + searchBtn.setOriginalHeight(17); + searchBtn.setOriginalX(11); + searchBtn.setOriginalY(11); + searchBtn.setHasListener(true); + searchBtn.setAction(1, MENU_OPEN); + searchBtn.setOnOpListener((JavaScriptCallback) this::menuOpen); + searchBtn.setName("Search"); + searchBtn.revalidate(); + + codes = null; + + if (config.autoOpen()) + { + openSearch(); + } + } + } + } + + private void menuOpen(ScriptEvent e) + { + openSearch(); + client.playSoundEffect(SoundEffectID.UI_BOOP); + } + + private void menuClose(ScriptEvent e) + { + updateFilter(""); + chatboxPanelManager.close(); + client.playSoundEffect(SoundEffectID.UI_BOOP); + } + + private void setWidgetTextToDestination() + { + Widget fairyRingTeleportButton = client.getWidget(WidgetInfo.FAIRY_RING_TELEPORT_BUTTON); + if (fairyRingTeleportButton != null && !fairyRingTeleportButton.isHidden()) + { + String destination; + try + { + FairyRings fairyRingDestination = getFairyRingDestination(client.getVar(Varbits.FAIRY_RING_DIAL_ADCB), + client.getVar(Varbits.FAIRY_RIGH_DIAL_ILJK), client.getVar(Varbits.FAIRY_RING_DIAL_PSRQ)); + destination = fairyRingDestination.getDestination(); + } + catch (IllegalArgumentException ex) + { + destination = "Invalid location"; + } + + fairyRingTeleportButton.setText(destination); + } + } + + private FairyRings getFairyRingDestination(int varbitValueDialLeft, int varbitValueDialMiddle, int varbitValueDialRight) + { + return FairyRings.valueOf(leftDial[varbitValueDialLeft] + middleDial[varbitValueDialMiddle] + rightDial[varbitValueDialRight]); + } + + private void openSearch() + { + updateFilter(""); + searchBtn.setAction(1, MENU_CLOSE); + searchBtn.setOnOpListener((JavaScriptCallback) this::menuClose); + searchInput = chatboxPanelManager.openTextInput("Filter fairy rings") + .onChanged(s -> clientThread.invokeLater(() -> updateFilter(s))) + .onDone(s -> false) + .onClose(() -> + { + clientThread.invokeLater(() -> updateFilter("")); + searchBtn.setOnOpListener((JavaScriptCallback) this::menuOpen); + searchBtn.setAction(1, MENU_OPEN); + }) + .build(); + } + + @Subscribe + public void onGameTick(GameTick t) + { + // This has to happen because the only widget that gets hidden is the tli one + Widget fairyRingTeleportButton = client.getWidget(WidgetInfo.FAIRY_RING_TELEPORT_BUTTON); + boolean fairyRingWidgetOpen = fairyRingTeleportButton != null && !fairyRingTeleportButton.isHidden(); + boolean chatboxOpen = searchInput != null && chatboxPanelManager.getCurrentInput() == searchInput; + + if (!fairyRingWidgetOpen && chatboxOpen) + { + chatboxPanelManager.close(); + } + } + + private void updateFilter(String filter) + { + filter = filter.toLowerCase(); + final Widget list = client.getWidget(WidgetInfo.FAIRY_RING_LIST); + final Widget favorites = client.getWidget(WidgetInfo.FAIRY_RING_FAVORITES); + + if (list == null) + { + return; + } + + if (codes != null) + { + // Check to make sure the list hasn't been rebuild since we were last her + // Do this by making sure the list's dynamic children are the same as when we last saw them + if (codes.stream().noneMatch(w -> + { + Widget codeWidget = w.getCode(); + if (codeWidget == null) + { + return false; + } + return list.getChild(codeWidget.getIndex()) == codeWidget; + })) + { + codes = null; + } + } + + if (codes == null) + { + // Find all of the widgets that we care about, grouping by their Y value + Map codeMap = new TreeMap<>(); + + for (Widget w : list.getStaticChildren()) + { + if (w.isSelfHidden()) + { + continue; + } + + if (w.getSpriteId() != -1) + { + codeMap.computeIfAbsent(w.getRelativeY(), k -> new CodeWidgets()).setFavorite(w); + } + else if (!Strings.isNullOrEmpty(w.getText())) + { + codeMap.computeIfAbsent(w.getRelativeY(), k -> new CodeWidgets()).setDescription(w); + } + } + + for (Widget w : list.getDynamicChildren()) + { + if (w.isSelfHidden()) + { + continue; + } + + CodeWidgets c = codeMap.computeIfAbsent(w.getRelativeY(), k -> new CodeWidgets()); + c.setCode(w); + } + + codes = codeMap.values(); + } + + // Relayout the panel + int y = 0; + + if (favorites != null) + { + boolean hide = !filter.isEmpty(); + favorites.setHidden(hide); + if (!hide) + { + y += favorites.getOriginalHeight() + ENTRY_PADDING; + } + } + + for (CodeWidgets c : codes) + { + String code = Text.removeTags(c.getDescription().getName()).replaceAll(" ", ""); + String tags = null; + + if (!code.isEmpty()) + { + try + { + FairyRings ring = FairyRings.valueOf(code); + tags = ring.getTags(); + } + catch (IllegalArgumentException e) + { + log.warn("Unable to find ring with code '{}'", code, e); + } + } + + boolean hidden = !(filter.isEmpty() + || Text.removeTags(c.getDescription().getText()).toLowerCase().contains(filter) + || code.toLowerCase().contains(filter) + || tags != null && tags.contains(filter)); + + if (c.getCode() != null) + { + c.getCode().setHidden(hidden); + c.getCode().setOriginalY(y); + } + + if (c.getFavorite() != null) + { + c.getFavorite().setHidden(hidden); + c.getFavorite().setOriginalY(y); + } + + c.getDescription().setHidden(hidden); + c.getDescription().setOriginalY(y); + + if (!hidden) + { + y += c.getDescription().getHeight() + ENTRY_PADDING; + } + } + + y -= ENTRY_PADDING; + + if (y < 0) + { + y = 0; + } + + int newHeight = 0; + if (list.getScrollHeight() > 0) + { + newHeight = (list.getScrollY() * y) / list.getScrollHeight(); + } + + list.setScrollHeight(y); + list.revalidateScroll(); + client.runScript( + ScriptID.UPDATE_SCROLLBAR, + WidgetInfo.FAIRY_RING_LIST_SCROLLBAR.getId(), + WidgetInfo.FAIRY_RING_LIST.getId(), + newHeight + ); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRings.java b/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRings.java new file mode 100644 index 0000000000..0f1db7d044 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fairyring/FairyRings.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018, Yoav Ram + * Copyright (c) 2018, Infinitay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.fairyring; + +import lombok.Getter; + +public enum FairyRings +{ + // A + AIQ("Mudskipper Point"), + AIR("(Island) South-east of Ardougne"), + AJQ("Cave south of Dorgesh-Kaan"), + AJR("Slayer cave"), + AJS("Penguins near Miscellania"), + AKQ("Piscatoris Hunter area"), + AKS("Feldip Hunter area"), + ALP("(Island) Lighthouse"), + ALQ("Haunted Woods east of Canifis"), + ALR("Abyssal Area"), + ALS("McGrubor's Wood"), + + // B + BIP("(Island) South-west of Mort Myre"), + BIQ("Kalphite Hive"), + BIS("Ardougne Zoo - Unicorns"), + BJR("Realm of the Fisher King"), + BJS("(Island) Near Zul-Andra", "zulrah"), + BKP("South of Castle Wars"), + BKQ("Enchanted Valley"), + BKR("Mort Myre Swamp, south of Canifis"), + BKS("Zanaris"), + BLP("TzHaar area"), + BLR("Legends' Guild"), + + // C + CIP("(Island) Miscellania"), + CIQ("North-west of Yanille"), + CIS("North of the Arceuus Library"), + CIR("North-east of the Farming Guild", "mount karuulm konar"), + CJR("Sinclair Mansion", "falo bard"), + CKP("Cosmic entity's plane"), + CKR("South of Tai Bwo Wannai Village"), + CKS("Canifis"), + CLP("(Island) South of Draynor Village"), + CLR("(Island) Ape Atoll"), + CLS("(Island) Hazelmere's home"), + + // D + DIP("(Sire Boss) Abyssal Nexus"), + DIR("Gorak's Plane"), + DIQ("Player-owned house", "poh home"), + DIS("Wizards' Tower"), + DJP("Tower of Life"), + DJR("Chasm of Fire"), + DKP("South of Musa Point"), + DKR("Edgeville, Grand Exchange"), + DKS("Polar Hunter area"), + DLQ("North of Nardah"), + DLR("(Island) Poison Waste south of Isafdar"), + DLS("Myreque hideout under The Hollows"); + + @Getter + private final String destination; + + @Getter + private final String tags; + + FairyRings(String destination) + { + this(destination, ""); + } + + FairyRings(String destination, String tags) + { + this.destination = destination; + this.tags = tags.toLowerCase() + " " + destination.toLowerCase(); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedConfig.java new file mode 100644 index 0000000000..74ed8cf97c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedConfig.java @@ -0,0 +1,42 @@ +package net.runelite.client.plugins.feed; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("feed") +public interface FeedConfig extends Config +{ + @ConfigItem( + keyName = "includeBlogPosts", + name = "Include Blog Posts", + description = "Configures whether blog posts are displayed", + position = 0 + ) + default boolean includeBlogPosts() + { + return true; + } + + @ConfigItem( + keyName = "includeTweets", + name = "Include Tweets", + description = "Configures whether tweets are displayed", + position = 1 + ) + default boolean includeTweets() + { + return true; + } + + @ConfigItem( + keyName = "includeOsrsNews", + name = "Include OSRS News", + description = "Configures whether OSRS news are displayed", + position = 2 + ) + default boolean includeOsrsNews() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java new file mode 100644 index 0000000000..7b1d987c4e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.feed; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.font.FontRenderContext; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.Comparator; +import java.util.function.Supplier; +import javax.imageio.ImageIO; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; +import net.runelite.http.api.feed.FeedItem; +import net.runelite.http.api.feed.FeedItemType; +import net.runelite.http.api.feed.FeedResult; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +@Slf4j +@Singleton +class FeedPanel extends PluginPanel +{ + private static final ImageIcon RUNELITE_ICON; + private static final ImageIcon OSRS_ICON; + + private static final Color TWEET_BACKGROUND = new Color(15, 15, 15); + private static final Color OSRS_NEWS_BACKGROUND = new Color(36, 30, 19); + private static final Color BLOG_POST_BACKGROUND = new Color(11, 30, 41); + + private static final int MAX_CONTENT_LINES = 3; + private static final int CONTENT_WIDTH = 148; + private static final int TIME_WIDTH = 20; + + private static final Comparator FEED_ITEM_COMPARATOR = (o1, o2) -> + { + if (o1.getType() != o2.getType()) + { + if (o1.getType() == FeedItemType.BLOG_POST) + { + return -1; + } + else if (o2.getType() == FeedItemType.BLOG_POST) + { + return 1; + } + } + + return -Long.compare(o1.getTimestamp(), o2.getTimestamp()); + }; + + static + { + RUNELITE_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(FeedPanel.class, "runelite.png")); + OSRS_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(FeedPanel.class, "osrs.png")); + } + + private final FeedConfig config; + private final Supplier feedSupplier; + private final OkHttpClient okHttpClient; + + @Inject + FeedPanel(FeedConfig config, Supplier feedSupplier, OkHttpClient okHttpClient) + { + super(true); + this.config = config; + this.feedSupplier = feedSupplier; + this.okHttpClient = okHttpClient; + + setBorder(new EmptyBorder(10, 10, 10, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setLayout(new GridLayout(0, 1, 0, 4)); + } + + void rebuildFeed() + { + FeedResult feed = feedSupplier.get(); + + if (feed == null) + { + return; + } + + SwingUtilities.invokeLater(() -> + { + removeAll(); + + feed.getItems() + .stream() + .filter(f -> f.getType() != FeedItemType.BLOG_POST || config.includeBlogPosts()) + .filter(f -> f.getType() != FeedItemType.TWEET || config.includeTweets()) + .filter(f -> f.getType() != FeedItemType.OSRS_NEWS || config.includeOsrsNews()) + .sorted(FEED_ITEM_COMPARATOR) + .forEach(this::addItemToPanel); + }); + } + + private void addItemToPanel(FeedItem item) + { + JPanel avatarAndRight = new JPanel(new BorderLayout()); + avatarAndRight.setPreferredSize(new Dimension(0, 56)); + + JLabel avatar = new JLabel(); + // width = 48+4 to compensate for the border + avatar.setPreferredSize(new Dimension(52, 48)); + avatar.setBorder(new EmptyBorder(0, 4, 0, 0)); + + switch (item.getType()) + { + case TWEET: + try + { + Request request = new Request.Builder() + .url(item.getAvatar()) + .build(); + + okHttpClient.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + log.warn(null, e); + } + + @Override + public void onResponse(Call call, Response response) throws IOException + { + try (ResponseBody responseBody = response.body()) + { + if (!response.isSuccessful()) + { + log.warn("Failed to download image " + item.getAvatar()); + return; + } + + BufferedImage icon; + synchronized (ImageIO.class) + { + icon = ImageIO.read(responseBody.byteStream()); + } + avatar.setIcon(new ImageIcon(icon)); + } + } + }); + } + catch (IllegalArgumentException | NullPointerException e) + { + log.warn(null, e); + } + avatarAndRight.setBackground(TWEET_BACKGROUND); + break; + case OSRS_NEWS: + if (OSRS_ICON != null) + { + avatar.setIcon(OSRS_ICON); + } + avatarAndRight.setBackground(OSRS_NEWS_BACKGROUND); + break; + default: + if (RUNELITE_ICON != null) + { + avatar.setIcon(RUNELITE_ICON); + } + avatarAndRight.setBackground(BLOG_POST_BACKGROUND); + break; + } + + JPanel upAndContent = new JPanel(); + upAndContent.setLayout(new BoxLayout(upAndContent, BoxLayout.Y_AXIS)); + upAndContent.setBorder(new EmptyBorder(4, 8, 4, 4)); + upAndContent.setBackground(null); + + JPanel titleAndTime = new JPanel(); + titleAndTime.setLayout(new BorderLayout()); + titleAndTime.setBackground(null); + + Color darkerForeground = UIManager.getColor("Label.foreground").darker(); + + JLabel titleLabel = new JLabel(item.getTitle()); + titleLabel.setFont(FontManager.getRunescapeSmallFont()); + titleLabel.setBackground(null); + titleLabel.setForeground(darkerForeground); + titleLabel.setPreferredSize(new Dimension(CONTENT_WIDTH - TIME_WIDTH, 0)); + + Duration duration = Duration.between(Instant.ofEpochMilli(item.getTimestamp()), Instant.now()); + JLabel timeLabel = new JLabel(durationToString(duration)); + timeLabel.setFont(FontManager.getRunescapeSmallFont()); + timeLabel.setForeground(darkerForeground); + + titleAndTime.add(titleLabel, BorderLayout.WEST); + titleAndTime.add(timeLabel, BorderLayout.EAST); + + JPanel content = new JPanel(new BorderLayout()); + content.setBackground(null); + + JLabel contentLabel = new JLabel(lineBreakText(item.getContent(), FontManager.getRunescapeSmallFont())); + contentLabel.setBorder(new EmptyBorder(2, 0, 0, 0)); + contentLabel.setFont(FontManager.getRunescapeSmallFont()); + contentLabel.setForeground(darkerForeground); + + content.add(contentLabel, BorderLayout.CENTER); + + upAndContent.add(titleAndTime); + upAndContent.add(content); + upAndContent.add(new Box.Filler(new Dimension(0, 0), + new Dimension(0, Short.MAX_VALUE), + new Dimension(0, Short.MAX_VALUE))); + + avatarAndRight.add(avatar, BorderLayout.WEST); + avatarAndRight.add(upAndContent, BorderLayout.CENTER); + + Color backgroundColor = avatarAndRight.getBackground(); + Color hoverColor = backgroundColor.brighter().brighter(); + Color pressedColor = hoverColor.brighter(); + + avatarAndRight.addMouseListener(new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent e) + { + avatarAndRight.setBackground(hoverColor); + avatarAndRight.setCursor(new Cursor(Cursor.HAND_CURSOR)); + } + + @Override + public void mouseExited(MouseEvent e) + { + avatarAndRight.setBackground(backgroundColor); + avatarAndRight.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + + @Override + public void mousePressed(MouseEvent e) + { + avatarAndRight.setBackground(pressedColor); + } + + @Override + public void mouseReleased(MouseEvent e) + { + avatarAndRight.setBackground(hoverColor); + LinkBrowser.browse(item.getUrl()); + } + }); + + add(avatarAndRight); + } + + private String durationToString(Duration duration) + { + if (duration.getSeconds() >= 60 * 60 * 24) + { + return (int) (duration.getSeconds() / (60 * 60 * 24)) + "d"; + } + else if (duration.getSeconds() >= 60 * 60) + { + return (int) (duration.getSeconds() / (60 * 60)) + "h"; + } + return (int) (duration.getSeconds() / 60) + "m"; + } + + private String lineBreakText(String text, Font font) + { + StringBuilder newText = new StringBuilder(""); + + FontRenderContext fontRenderContext = new FontRenderContext(font.getTransform(), + true, true); + + int lines = 0; + int pos = 0; + String[] words = text.split(" "); + String line = ""; + + while (lines < MAX_CONTENT_LINES && pos < words.length) + { + String newLine = pos > 0 ? line + " " + words[pos] : words[pos]; + double width = font.getStringBounds(newLine, fontRenderContext).getWidth(); + + if (width >= CONTENT_WIDTH) + { + newText.append(line); + newText.append("
"); + line = ""; + lines++; + } + else + { + line = newLine; + pos++; + } + } + + newText.append(line); + newText.append(""); + + return newText.toString(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java new file mode 100644 index 0000000000..5cc4ea0bd9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2018, Lotto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.feed; + +import com.google.common.base.Suppliers; +import com.google.inject.Binder; +import com.google.inject.Provides; +import com.google.inject.TypeLiteral; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.task.Schedule; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.util.ImageUtil; +import net.runelite.http.api.feed.FeedClient; +import net.runelite.http.api.feed.FeedResult; +import okhttp3.OkHttpClient; + +@PluginDescriptor( + name = "News Feed", + description = "Show the latest RuneLite blog posts, OSRS news, and JMod Twitter posts", + tags = {"external", "integration", "panel", "twitter"}, + loadWhenOutdated = true +) +@Slf4j +public class FeedPlugin extends Plugin +{ + @Inject + private ClientToolbar clientToolbar; + + @Inject + private ScheduledExecutorService executorService; + + @Inject + private FeedClient feedClient; + + private FeedPanel feedPanel; + private NavigationButton navButton; + + private final Supplier feedSupplier = Suppliers.memoizeWithExpiration(() -> + { + try + { + return feedClient.lookupFeed(); + } + catch (IOException e) + { + log.warn(null, e); + } + return null; + }, 10, TimeUnit.MINUTES); + + @Override + public void configure(Binder binder) + { + // CHECKSTYLE:OFF + binder.bind(new TypeLiteral>(){}) + .toInstance(feedSupplier); + // CHECKSTYLE:ON + } + + @Override + protected void startUp() throws Exception + { + feedPanel = injector.getInstance(FeedPanel.class); + + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "icon.png"); + + navButton = NavigationButton.builder() + .tooltip("News Feed") + .icon(icon) + .priority(8) + .panel(feedPanel) + .build(); + + clientToolbar.addNavigation(navButton); + executorService.submit(this::updateFeed); + } + + @Override + protected void shutDown() throws Exception + { + clientToolbar.removeNavigation(navButton); + } + + private void updateFeed() + { + feedPanel.rebuildFeed(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("feed")) + { + executorService.submit(this::updateFeed); + } + } + + @Schedule( + period = 10, + unit = ChronoUnit.MINUTES, + asynchronous = true + ) + public void updateFeedTask() + { + updateFeed(); + } + + @Provides + FeedConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(FeedConfig.class); + } + + @Provides + FeedClient provideFeedClient(OkHttpClient okHttpClient) + { + return new FeedClient(okHttpClient); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsConfig.java new file mode 100644 index 0000000000..feba6256cb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsConfig.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017, Levi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.fps; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Range; + +@ConfigGroup(FpsPlugin.CONFIG_GROUP_KEY) +public interface FpsConfig extends Config +{ + @ConfigItem( + keyName = "limitFps", + name = "Limit Global FPS", + description = "Global FPS limit in effect regardless of
" + + "whether window is in focus or not", + position = 1 + ) + default boolean limitFps() + { + return false; + } + + @Range( + min = 1, + max = 50 + ) + @ConfigItem( + keyName = "maxFps", + name = "Global FPS target", + description = "Desired max global frames per second", + position = 2 + ) + default int maxFps() + { + return 50; + } + + @ConfigItem( + keyName = "limitFpsUnfocused", + name = "Limit FPS unfocused", + description = "FPS limit while window is out of focus", + position = 3 + ) + default boolean limitFpsUnfocused() + { + return false; + } + + @Range( + min = 1, + max = 50 + ) + @ConfigItem( + keyName = "maxFpsUnfocused", + name = "Unfocused FPS target", + description = "Desired max frames per second for unfocused", + position = 4 + ) + default int maxFpsUnfocused() + { + return 50; + } + + @ConfigItem( + keyName = "drawFps", + name = "Draw FPS indicator", + description = "Show a number in the corner for the current FPS", + position = 5 + ) + default boolean drawFps() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsDrawListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsDrawListener.java new file mode 100644 index 0000000000..ca521b6b1c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsDrawListener.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2017, Levi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.fps; + +import javax.inject.Inject; +import net.runelite.api.events.FocusChanged; + +/** + * FPS Draw Listener runs after the canvas has been painted with the buffered image (unused in this plugin) + * It specifically is designed only to pause when RS decides to paint the canvas, after the canvas has been painted. + * The RS client operates at 50 cycles happen per second in the RS Client with higher priority than draws per second. + * For high powered computers, drawing is limited by client cycles, so 50 is the max FPS observed. + * After running a game cycle and a draw operation, the RS client burns the rest of the time with a nano timer. + * For low powered computers, the RS client is often throttled by the hardware or OS and draws at 25-30 fps. + * The nano timer is not used in this scenario. + * Instead to catch up the RS client runs several cycles before drawing, thus maintaining 50 cycles / second. + *

+ * Enforcing FPS in the draw code does not impact the client engine's ability to run including its audio, + * even when forced to 1 FPS with this plugin. + */ +public class FpsDrawListener implements Runnable +{ + private static final int SAMPLE_SIZE = 4; + + private final FpsConfig config; + + private long targetDelay = 0; + + // Often changing values + private boolean isFocused = true; + + // Working set + private long lastMillis = 0; + private long[] lastDelays = new long[SAMPLE_SIZE]; + private int lastDelayIndex = 0; + private long sleepDelay = 0; + + @Inject + private FpsDrawListener(FpsConfig config) + { + this.config = config; + reloadConfig(); + } + + void reloadConfig() + { + lastMillis = System.currentTimeMillis(); + + int fps = config.limitFpsUnfocused() && !isFocused + ? config.maxFpsUnfocused() + : config.maxFps(); + + targetDelay = 1000 / Math.max(1, fps); + + sleepDelay = targetDelay; + + for (int i = 0; i < SAMPLE_SIZE; i++) + { + lastDelays[i] = targetDelay; + } + } + + void onFocusChanged(FocusChanged event) + { + this.isFocused = event.isFocused(); + reloadConfig(); // load new delay + } + + private boolean isEnforced() + { + return config.limitFps() + || (config.limitFpsUnfocused() && !isFocused); + } + + @Override + public void run() + { + if (!isEnforced()) + { + return; + } + + // We can't trust client.getFPS to get frame-perfect FPS knowledge + // If we do try to use client.getFPS, we will end up oscillating + // So we rely on currentTimeMillis which is occasionally cached by the JVM unlike nanotime + // Its caching will not cause oscillation as it is granular enough for our uses here + final long before = lastMillis; + final long now = System.currentTimeMillis(); + + lastMillis = now; + lastDelayIndex = (lastDelayIndex + 1) % SAMPLE_SIZE; + lastDelays[lastDelayIndex] = now - before; + + // We take a sampling approach because sometimes the game client seems to repaint + // after only running 1 game cycle, and then runs repaint again after running 30 cycles + long averageDelay = 0; + for (int i = 0; i < SAMPLE_SIZE; i++) + { + averageDelay += lastDelays[i]; + } + averageDelay /= lastDelays.length; + + // Sleep delay is a moving target due to the nature of how and when the engine + // decides to run cycles. + // This will also keep us safe from time spent in plugins conditionally + // as some plugins and overlays are only appropriate in some game areas + if (averageDelay > targetDelay) + { + sleepDelay--; + } + else if (averageDelay < targetDelay) + { + sleepDelay++; + } + + if (sleepDelay > 0) + { + try + { + Thread.sleep(sleepDelay); + } + catch (InterruptedException e) + { + // Can happen on shutdown + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsOverlay.java new file mode 100644 index 0000000000..76b8d18908 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsOverlay.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, Levi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.fps; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Point; +import net.runelite.api.events.FocusChanged; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.OverlayUtil; + +/** + * The built in FPS overlay has a few problems that this one does not have, most of all: it is distracting. + * 1. The built in one also shows memory, which constantly fluctuates and garbage collects. + * It is useful for devs profiling memory. + * 2. The built in one shifts around constantly because it is not monospace. + * This locks "FPS:" into one position (the far top right corner of the canvas), + * along with a locked position for the FPS value. + */ +public class FpsOverlay extends Overlay +{ + private static final int Y_OFFSET = 1; + private static final int X_OFFSET = 1; + private static final String FPS_STRING = " FPS"; + + // Local dependencies + private final FpsConfig config; + private final Client client; + + // Often changing values + private boolean isFocused = true; + + @Inject + private FpsOverlay(FpsConfig config, Client client) + { + this.config = config; + this.client = client; + setLayer(OverlayLayer.ABOVE_WIDGETS); + setPriority(OverlayPriority.HIGH); + setPosition(OverlayPosition.DYNAMIC); + } + + void onFocusChanged(FocusChanged event) + { + isFocused = event.isFocused(); + } + + private boolean isEnforced() + { + return config.limitFps() + || (config.limitFpsUnfocused() && !isFocused); + } + + private Color getFpsValueColor() + { + return isEnforced() ? Color.red : Color.yellow; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.drawFps()) + { + return null; + } + + // On resizable bottom line mode the logout button is at the top right, so offset the overlay + // to account for it + Widget logoutButton = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_LOGOUT_BUTTON); + int xOffset = X_OFFSET; + if (logoutButton != null && !logoutButton.isHidden()) + { + xOffset += logoutButton.getWidth(); + } + + final String text = client.getFPS() + FPS_STRING; + final int textWidth = graphics.getFontMetrics().stringWidth(text); + final int textHeight = graphics.getFontMetrics().getAscent() - graphics.getFontMetrics().getDescent(); + + final int width = (int) client.getRealDimensions().getWidth(); + final Point point = new Point(width - textWidth - xOffset, textHeight + Y_OFFSET); + OverlayUtil.renderTextLocation(graphics, point, text, getFpsValueColor()); + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsPlugin.java new file mode 100644 index 0000000000..9a42e56c29 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fps/FpsPlugin.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017, Levi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.fps; + +import com.google.inject.Inject; +import com.google.inject.Provides; +import net.runelite.client.events.ConfigChanged; +import net.runelite.api.events.FocusChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.DrawManager; +import net.runelite.client.ui.overlay.OverlayManager; + +/** + * FPS Control has two primary areas, this plugin class just keeps those areas up to date and handles setup / teardown. + * + *

Overlay paints the current FPS, the color depends on whether or not FPS is being enforced. + * The overlay is lightweight and is merely and indicator. + * + *

Draw Listener, sleeps a calculated amount after each canvas paint operation. + * This is the heart of the plugin, the amount of sleep taken is regularly adjusted to account varying + * game and system load, it usually finds the sweet spot in about two seconds. + */ +@PluginDescriptor( + name = "FPS Control", + description = "Show current FPS and/or set an FPS limit", + tags = {"frames", "framerate", "limit", "overlay"}, + enabledByDefault = false +) +public class FpsPlugin extends Plugin +{ + static final String CONFIG_GROUP_KEY = "fpscontrol"; + + @Inject + private OverlayManager overlayManager; + + @Inject + private FpsOverlay overlay; + + @Inject + private FpsDrawListener drawListener; + + @Inject + private DrawManager drawManager; + + @Provides + FpsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(FpsConfig.class); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(CONFIG_GROUP_KEY)) + { + drawListener.reloadConfig(); + } + } + + @Subscribe + public void onFocusChanged(FocusChanged event) + { + drawListener.onFocusChanged(event); + overlay.onFocusChanged(event); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + drawManager.registerEveryFrameListener(drawListener); + drawListener.reloadConfig(); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + drawManager.unregisterEveryFrameListener(drawListener); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendlist/FriendListPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendlist/FriendListPlugin.java new file mode 100644 index 0000000000..da491c4c76 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendlist/FriendListPlugin.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018, Connor + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendlist; + +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Friend; +import net.runelite.api.Ignore; +import net.runelite.api.NameableContainer; +import net.runelite.api.ScriptID; +import net.runelite.api.VarPlayer; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Friend List", + description = "Add extra information to the friend and ignore lists" +) +public class FriendListPlugin extends Plugin +{ + private static final int MAX_FRIENDS_P2P = 400; + private static final int MAX_FRIENDS_F2P = 200; + + private static final int MAX_IGNORES_P2P = 400; + private static final int MAX_IGNORES_F2P = 100; + + @Inject + private Client client; + + @Override + protected void shutDown() + { + final int world = client.getWorld(); + setFriendsListTitle("Friends List - World " + world); + setIgnoreListTitle("Ignore List - World " + world); + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + if (event.getScriptId() == ScriptID.FRIENDS_UPDATE) + { + final int world = client.getWorld(); + final boolean isMember = client.getVar(VarPlayer.MEMBERSHIP_DAYS) > 0; + final NameableContainer friendContainer = client.getFriendContainer(); + final int friendCount = friendContainer.getCount(); + if (friendCount >= 0) + { + final int limit = isMember ? MAX_FRIENDS_P2P : MAX_FRIENDS_F2P; + + final String title = "Friends - W" + + world + + " (" + + friendCount + + "/" + + limit + + ")"; + + setFriendsListTitle(title); + } + } + else if (event.getScriptId() == ScriptID.IGNORE_UPDATE) + { + final int world = client.getWorld(); + final boolean isMember = client.getVar(VarPlayer.MEMBERSHIP_DAYS) > 0; + final NameableContainer ignoreContainer = client.getIgnoreContainer(); + final int ignoreCount = ignoreContainer.getCount(); + if (ignoreCount >= 0) + { + final int limit = isMember ? MAX_IGNORES_P2P : MAX_IGNORES_F2P; + + final String title = "Ignores - W" + + world + + " (" + + ignoreCount + + "/" + + limit + + ")"; + + setIgnoreListTitle(title); + } + } + } + + private void setFriendsListTitle(final String title) + { + Widget friendListTitleWidget = client.getWidget(WidgetInfo.FRIEND_CHAT_TITLE); + if (friendListTitleWidget != null) + { + friendListTitleWidget.setText(title); + } + } + + private void setIgnoreListTitle(final String title) + { + Widget ignoreTitleWidget = client.getWidget(WidgetInfo.IGNORE_TITLE); + if (ignoreTitleWidget != null) + { + ignoreTitleWidget.setText(title); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/ActivityType.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/ActivityType.java new file mode 100644 index 0000000000..242090392f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/ActivityType.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018, trimbe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendschat; + +enum ActivityType +{ + JOINED, + LEFT +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatConfig.java new file mode 100644 index 0000000000..17ce7b0098 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatConfig.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2017, Ron + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendschat; + +import java.awt.Color; +import net.runelite.api.FriendsChatRank; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("clanchat") // group name from the old plugin +public interface FriendsChatConfig extends Config +{ + @ConfigItem( + keyName = "clanChatIcons", + name = "Chat Icons", + description = "Show rank icons next to friends chat members.", + position = 1 + ) + default boolean chatIcons() + { + return true; + } + + @ConfigItem( + keyName = "recentChats", + name = "Recent Chats", + description = "Show recent friends chats.", + position = 2 + ) + default boolean recentChats() + { + return true; + } + + @ConfigItem( + keyName = "clanCounter", + name = "Members Counter", + description = "Show the amount of friends chat members near you.", + position = 3 + ) + default boolean showCounter() + { + return false; + } + + @ConfigItem( + keyName = "chatsData", + name = "", + description = "", + hidden = true + ) + default String chatsData() + { + return ""; + } + + @ConfigItem( + keyName = "chatsData", + name = "", + description = "" + ) + void chatsData(String str); + + @ConfigItem( + keyName = "showJoinLeave", + name = "Show Join/Leave", + description = "Adds a temporary message notifying when a member joins or leaves.", + position = 4 + ) + default boolean showJoinLeave() + { + return false; + } + + @ConfigItem( + keyName = "joinLeaveRank", + name = "Join/Leave rank", + description = "Only show join/leave messages for members at or above this rank.", + position = 5 + ) + default FriendsChatRank joinLeaveRank() + { + return FriendsChatRank.UNRANKED; + } + + @ConfigItem( + keyName = "joinLeaveTimeout", + name = "Join/Leave timeout", + description = "Set the timeout duration of join/leave messages. A value of 0 will make the messages permanent.", + position = 6 + ) + default int joinLeaveTimeout() + { + return 20; + } + + @ConfigItem( + keyName = "privateMessageIcons", + name = "Private Message Icons", + description = "Add rank icons to private messages received from members.", + position = 7 + ) + default boolean privateMessageIcons() + { + return false; + } + + @ConfigItem( + keyName = "publicChatIcons", + name = "Public Chat Icons", + description = "Add rank icons to public chat messages from members.", + position = 8 + ) + default boolean publicChatIcons() + { + return false; + } + + @ConfigItem( + keyName = "clanTabChat", + name = "Tab Chat", + description = "Message friends chat without appending '/' when the friends chat tab is selected.", + position = 9 + ) + default boolean friendsChatTabChat() + { + return false; + } + + @ConfigItem( + keyName = "confirmKicks", + name = "Confirm Kicks", + description = "Shows a chat prompt to confirm kicks", + position = 10 + ) + default boolean confirmKicks() + { + return false; + } + + @ConfigItem( + keyName = "showIgnores", + name = "Recolor ignored players", + description = "Recolor members who are on your ignore list", + position = 11 + ) + default boolean showIgnores() + { + return true; + } + + @ConfigItem( + keyName = "showIgnoresColor", + name = "Ignored color", + description = "Allows you to change the color of the ignored players in your friends chat", + position = 12 + ) + default Color showIgnoresColor() + { + return Color.RED; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java new file mode 100644 index 0000000000..8413c225e6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java @@ -0,0 +1,722 @@ +/* + * Copyright (c) 2017, Devin French + * Copyright (c) 2019, Adam + * Copyright (c) 2018, trimbe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendschat; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Runnables; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.ChatLineBuffer; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.FriendsChatManager; +import net.runelite.api.FriendsChatMember; +import net.runelite.api.FriendsChatRank; +import net.runelite.api.GameState; +import net.runelite.api.Ignore; +import net.runelite.api.MessageNode; +import net.runelite.api.NameableContainer; +import net.runelite.api.Player; +import net.runelite.api.ScriptID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarClientStr; +import net.runelite.api.Varbits; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.FriendsChatChanged; +import net.runelite.api.events.FriendsChatMemberJoined; +import net.runelite.api.events.FriendsChatMemberLeft; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.api.events.PlayerSpawned; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.VarClientStrChanged; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.FriendChatManager; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import static net.runelite.client.ui.JagexColors.CHAT_FC_NAME_OPAQUE_BACKGROUND; +import static net.runelite.client.ui.JagexColors.CHAT_FC_NAME_TRANSPARENT_BACKGROUND; +import static net.runelite.client.ui.JagexColors.CHAT_FC_TEXT_OPAQUE_BACKGROUND; +import static net.runelite.client.ui.JagexColors.CHAT_FC_TEXT_TRANSPARENT_BACKGROUND; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Friends Chat", + description = "Add rank icons to users talking in friends chat", + tags = {"icons", "rank", "recent", "clan"} +) +public class FriendsChatPlugin extends Plugin +{ + private static final int MAX_CHATS = 10; + private static final String TITLE = "FC"; + private static final String RECENT_TITLE = "Recent FCs"; + private static final int MESSAGE_DELAY = 10; + + @Inject + private Client client; + + @Inject + private FriendChatManager friendChatManager; + + @Inject + private FriendsChatConfig config; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private SpriteManager spriteManager; + + @Inject + private ClientThread clientThread; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + private List chats = new ArrayList<>(); + private final List members = new ArrayList<>(); + private MembersIndicator membersIndicator; + /** + * queue of temporary messages added to the client + */ + private final Deque joinMessages = new ArrayDeque<>(); + private final Map activityBuffer = new HashMap<>(); + private int joinedTick; + + private boolean kickConfirmed = false; + + @Provides + FriendsChatConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(FriendsChatConfig.class); + } + + @Override + public void startUp() + { + chats = new ArrayList<>(Text.fromCSV(config.chatsData())); + + if (config.showIgnores()) + { + clientThread.invoke(() -> colorIgnoredPlayers(config.showIgnoresColor())); + } + } + + @Override + public void shutDown() + { + clientThread.invoke(() -> colorIgnoredPlayers(Color.WHITE)); + members.clear(); + resetCounter(); + resetChats(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (configChanged.getGroup().equals("clanchat")) + { + if (!config.recentChats()) + { + resetChats(); + } + + if (config.showCounter()) + { + clientThread.invoke(this::addCounter); + } + else + { + resetCounter(); + } + + Color ignoreColor = config.showIgnores() ? config.showIgnoresColor() : Color.WHITE; + clientThread.invoke(() -> colorIgnoredPlayers(ignoreColor)); + } + } + + @Subscribe + public void onFriendsChatMemberJoined(FriendsChatMemberJoined event) + { + final FriendsChatMember member = event.getMember(); + + if (member.getWorld() == client.getWorld()) + { + final Player local = client.getLocalPlayer(); + final String memberName = Text.toJagexName(member.getName()); + + for (final Player player : client.getPlayers()) + { + if (player != null && player != local && memberName.equals(Text.toJagexName(player.getName()))) + { + members.add(player); + addCounter(); + break; + } + } + } + + // members getting initialized isn't relevant + if (joinedTick == client.getTickCount()) + { + return; + } + + if (!config.showJoinLeave() || + member.getRank().getValue() < config.joinLeaveRank().getValue()) + { + return; + } + + // attempt to filter out world hopping joins + if (!activityBuffer.containsKey(member.getName())) + { + MemberActivity joinActivity = new MemberActivity(ActivityType.JOINED, + member, client.getTickCount()); + activityBuffer.put(member.getName(), joinActivity); + } + else + { + activityBuffer.remove(member.getName()); + } + } + + @Subscribe + public void onFriendsChatMemberLeft(FriendsChatMemberLeft event) + { + final FriendsChatMember member = event.getMember(); + + if (member.getWorld() == client.getWorld()) + { + final String memberName = Text.toJagexName(member.getName()); + final Iterator each = members.iterator(); + + while (each.hasNext()) + { + if (memberName.equals(Text.toJagexName(each.next().getName()))) + { + each.remove(); + + if (members.isEmpty()) + { + resetCounter(); + } + + break; + } + } + } + + if (!config.showJoinLeave() || + member.getRank().getValue() < config.joinLeaveRank().getValue()) + { + return; + } + + if (!activityBuffer.containsKey(member.getName())) + { + MemberActivity leaveActivity = new MemberActivity(ActivityType.LEFT, + member, client.getTickCount()); + activityBuffer.put(member.getName(), leaveActivity); + } + else + { + activityBuffer.remove(member.getName()); + } + } + + @Subscribe + public void onGameTick(GameTick gameTick) + { + if (client.getGameState() != GameState.LOGGED_IN) + { + return; + } + + Widget chatTitleWidget = client.getWidget(WidgetInfo.FRIENDS_CHAT_TITLE); + if (chatTitleWidget != null) + { + Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST); + Widget owner = client.getWidget(WidgetInfo.FRIENDS_CHAT_OWNER); + FriendsChatManager friendsChatManager = client.getFriendsChatManager(); + if (friendsChatManager != null && friendsChatManager.getCount() > 0) + { + chatTitleWidget.setText(TITLE + " (" + friendsChatManager.getCount() + "/100)"); + } + else if (config.recentChats() && chatList.getChildren() == null && !Strings.isNullOrEmpty(owner.getText())) + { + chatTitleWidget.setText(RECENT_TITLE); + + loadFriendsChats(); + } + } + + if (!config.showJoinLeave()) + { + return; + } + + timeoutMessages(); + + addActivityMessages(); + } + + private void timeoutMessages() + { + if (joinMessages.isEmpty()) + { + return; + } + + final int joinLeaveTimeout = config.joinLeaveTimeout(); + if (joinLeaveTimeout == 0) + { + return; + } + + boolean removed = false; + + for (Iterator it = joinMessages.iterator(); it.hasNext(); ) + { + MemberJoinMessage joinMessage = it.next(); + MessageNode messageNode = joinMessage.getMessageNode(); + final int createdTick = joinMessage.getTick(); + + if (client.getTickCount() > createdTick + joinLeaveTimeout) + { + it.remove(); + + // If this message has been reused since, it will get a different id + if (joinMessage.getGetMessageId() == messageNode.getId()) + { + ChatLineBuffer ccInfoBuffer = client.getChatLineMap().get(ChatMessageType.FRIENDSCHATNOTIFICATION.getType()); + if (ccInfoBuffer != null) + { + ccInfoBuffer.removeMessageNode(messageNode); + removed = true; + } + } + } + else + { + // Everything else in the deque is newer + break; + } + } + + if (removed) + { + clientThread.invoke(() -> client.runScript(ScriptID.BUILD_CHATBOX)); + } + } + + private void addActivityMessages() + { + FriendsChatManager friendsChatManager = client.getFriendsChatManager(); + if (friendsChatManager == null || activityBuffer.isEmpty()) + { + return; + } + + Iterator activityIt = activityBuffer.values().iterator(); + + while (activityIt.hasNext()) + { + MemberActivity activity = activityIt.next(); + + if (activity.getTick() < client.getTickCount() - MESSAGE_DELAY) + { + activityIt.remove(); + addActivityMessage(friendsChatManager, activity.getMember(), activity.getActivityType()); + } + } + } + + private void addActivityMessage(FriendsChatManager friendsChatManager, FriendsChatMember member, ActivityType activityType) + { + final String activityMessage = activityType == ActivityType.JOINED ? " has joined." : " has left."; + final FriendsChatRank rank = member.getRank(); + Color textColor = CHAT_FC_TEXT_OPAQUE_BACKGROUND; + Color channelColor = CHAT_FC_NAME_OPAQUE_BACKGROUND; + int rankIcon = -1; + + if (client.isResized() && client.getVar(Varbits.TRANSPARENT_CHATBOX) == 1) + { + textColor = CHAT_FC_TEXT_TRANSPARENT_BACKGROUND; + channelColor = CHAT_FC_NAME_TRANSPARENT_BACKGROUND; + } + + if (config.chatIcons() && rank != null && rank != FriendsChatRank.UNRANKED) + { + rankIcon = friendChatManager.getIconNumber(rank); + } + + ChatMessageBuilder message = new ChatMessageBuilder() + .append("[") + .append(channelColor, friendsChatManager.getName()); + if (rankIcon > -1) + { + message + .append(" ") + .img(rankIcon); + } + message + .append("] ") + .append(textColor, member.getName() + activityMessage); + + final String messageString = message.build(); + client.addChatMessage(ChatMessageType.FRIENDSCHATNOTIFICATION, "", messageString, ""); + + final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(ChatMessageType.FRIENDSCHATNOTIFICATION.getType()); + final MessageNode[] lines = chatLineBuffer.getLines(); + final MessageNode line = lines[0]; + + MemberJoinMessage joinMessage = new MemberJoinMessage(line, line.getId(), client.getTickCount()); + joinMessages.addLast(joinMessage); + } + + @Subscribe + public void onVarClientStrChanged(VarClientStrChanged strChanged) + { + if (strChanged.getIndex() == VarClientStr.RECENT_FRIENDS_CHAT.getIndex() && config.recentChats()) + { + updateRecentChat(client.getVar(VarClientStr.RECENT_FRIENDS_CHAT)); + } + } + + @Subscribe + public void onChatMessage(ChatMessage chatMessage) + { + if (client.getGameState() != GameState.LOADING && client.getGameState() != GameState.LOGGED_IN) + { + return; + } + + FriendsChatManager friendsChatManager = client.getFriendsChatManager(); + if (friendsChatManager == null || friendsChatManager.getCount() == 0) + { + return; + } + + switch (chatMessage.getType()) + { + case PRIVATECHAT: + case MODPRIVATECHAT: + if (!config.privateMessageIcons()) + { + return; + } + break; + case PUBLICCHAT: + case MODCHAT: + if (!config.publicChatIcons()) + { + return; + } + break; + case FRIENDSCHAT: + if (!config.chatIcons()) + { + return; + } + break; + default: + return; + } + + insertRankIcon(chatMessage); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged state) + { + GameState gameState = state.getGameState(); + + if (gameState == GameState.LOGIN_SCREEN || gameState == GameState.CONNECTION_LOST || gameState == GameState.HOPPING) + { + members.clear(); + resetCounter(); + + joinMessages.clear(); + } + } + + @Subscribe + public void onPlayerSpawned(PlayerSpawned event) + { + final Player local = client.getLocalPlayer(); + final Player player = event.getPlayer(); + + if (player != local && player.isFriendsChatMember()) + { + members.add(player); + addCounter(); + } + } + + @Subscribe + public void onPlayerDespawned(PlayerDespawned event) + { + if (members.remove(event.getPlayer()) && members.isEmpty()) + { + resetCounter(); + } + } + + @Subscribe + public void onFriendsChatChanged(FriendsChatChanged event) + { + if (event.isJoined()) + { + joinedTick = client.getTickCount(); + } + else + { + members.clear(); + resetCounter(); + } + + activityBuffer.clear(); + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent scriptCallbackEvent) + { + switch (scriptCallbackEvent.getEventName()) + { + case "friendsChatInput": + { + final int[] intStack = client.getIntStack(); + final int size = client.getIntStackSize(); + // If the user accidentally adds a / when the config and the friends chat chat tab is active, handle it like a normal message + boolean alterDispatch = config.friendsChatTabChat() && !client.getVar(VarClientStr.CHATBOX_TYPED_TEXT).startsWith("/"); + intStack[size - 1] = alterDispatch ? 1 : 0; + break; + } + case "confirmFriendsChatKick": + { + if (!config.confirmKicks() || kickConfirmed) + { + break; + } + + // Set a flag so the script doesn't instantly kick them + final int[] intStack = client.getIntStack(); + final int size = client.getIntStackSize(); + intStack[size - 1] = 1; + + // Get name of player we are trying to kick + final String[] stringStack = client.getStringStack(); + final int stringSize = client.getStringStackSize(); + final String kickPlayerName = stringStack[stringSize - 1]; + + // Show a chatbox panel confirming the kick + clientThread.invokeLater(() -> confirmKickPlayer(kickPlayerName)); + break; + } + } + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + if (event.getScriptId() == ScriptID.FRIENDS_CHAT_CHANNEL_REBUILD && config.showIgnores()) + { + colorIgnoredPlayers(config.showIgnoresColor()); + } + } + + int getMembersSize() + { + return members.size(); + } + + private void insertRankIcon(final ChatMessage message) + { + final FriendsChatRank rank = friendChatManager.getRank(message.getName()); + + if (rank != null && rank != FriendsChatRank.UNRANKED) + { + int iconNumber = friendChatManager.getIconNumber(rank); + final String img = ""; + if (message.getType() == ChatMessageType.FRIENDSCHAT) + { + message.getMessageNode() + .setSender(message.getMessageNode().getSender() + " " + img); + } + else + { + message.getMessageNode() + .setName(img + message.getMessageNode().getName()); + } + client.refreshChat(); + } + } + + private void resetChats() + { + Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST); + Widget chatTitleWidget = client.getWidget(WidgetInfo.FRIENDS_CHAT_TITLE); + + if (chatList == null) + { + return; + } + + FriendsChatManager friendsChatManager = client.getFriendsChatManager(); + if (friendsChatManager == null || friendsChatManager.getCount() == 0) + { + chatList.setChildren(null); + } + + chatTitleWidget.setText(TITLE); + } + + private void loadFriendsChats() + { + Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST); + if (chatList == null) + { + return; + } + + int y = 2; + chatList.setChildren(null); + for (String chat : Lists.reverse(chats)) + { + Widget widget = chatList.createChild(-1, WidgetType.TEXT); + widget.setFontId(494); + widget.setTextColor(0xffffff); + widget.setText(chat); + widget.setOriginalHeight(14); + widget.setOriginalWidth(142); + widget.setOriginalY(y); + widget.setOriginalX(20); + widget.revalidate(); + + y += 14; + } + } + + private void updateRecentChat(String s) + { + if (Strings.isNullOrEmpty(s)) + { + return; + } + + s = Text.toJagexName(s); + + chats.removeIf(s::equalsIgnoreCase); + chats.add(s); + + while (chats.size() > MAX_CHATS) + { + chats.remove(0); + } + + config.chatsData(Text.toCSV(chats)); + } + + private void resetCounter() + { + infoBoxManager.removeInfoBox(membersIndicator); + membersIndicator = null; + } + + private void addCounter() + { + if (!config.showCounter() || membersIndicator != null || members.isEmpty()) + { + return; + } + + final BufferedImage image = spriteManager.getSprite(SpriteID.TAB_FRIENDS_CHAT, 0); + membersIndicator = new MembersIndicator(image, this); + infoBoxManager.addInfoBox(membersIndicator); + } + + private void confirmKickPlayer(final String kickPlayerName) + { + chatboxPanelManager.openTextMenuInput("Attempting to kick: " + kickPlayerName) + .option("1. Confirm kick", () -> + clientThread.invoke(() -> + { + kickConfirmed = true; + client.runScript(ScriptID.FRIENDS_CHAT_SEND_KICK, kickPlayerName); + kickConfirmed = false; + }) + ) + .option("2. Cancel", Runnables::doNothing) + .build(); + } + + private void colorIgnoredPlayers(Color ignoreColor) + { + Widget chatList = client.getWidget(WidgetInfo.FRIENDS_CHAT_LIST); + if (chatList == null || chatList.getChildren() == null) + { + return; + } + + NameableContainer ignoreContainer = client.getIgnoreContainer(); + // Iterate every 3 widgets, since the order of widgets is name, world, icon + for (int i = 0; i < chatList.getChildren().length; i += 3) + { + Widget listWidget = chatList.getChild(i); + String memberName = listWidget.getText(); + if (memberName.isEmpty() || ignoreContainer.findByName(memberName) == null) + { + continue; + } + + listWidget.setTextColor(ignoreColor.getRGB()); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberActivity.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberActivity.java new file mode 100644 index 0000000000..8e3135b99d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberActivity.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, trimbe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendschat; + +import lombok.AllArgsConstructor; +import lombok.Value; +import net.runelite.api.FriendsChatMember; + +@Value +@AllArgsConstructor +class MemberActivity +{ + private ActivityType activityType; + private FriendsChatMember member; + private Integer tick; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberJoinMessage.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberJoinMessage.java new file mode 100644 index 0000000000..e90905d15c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MemberJoinMessage.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendschat; + +import lombok.Value; +import net.runelite.api.MessageNode; + +@Value +class MemberJoinMessage +{ + private final MessageNode messageNode; + private final int getMessageId; + private final int tick; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MembersIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MembersIndicator.java new file mode 100644 index 0000000000..99b488fd90 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/MembersIndicator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Sebastiaan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.friendschat; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import net.runelite.client.ui.overlay.infobox.Counter; + +class MembersIndicator extends Counter +{ + private final FriendsChatPlugin plugin; + + MembersIndicator(BufferedImage image, FriendsChatPlugin plugin) + { + super(image, plugin, plugin.getMembersSize()); + this.plugin = plugin; + } + + @Override + public int getCount() + { + return plugin.getMembersSize(); + } + + @Override + public String getTooltip() + { + return plugin.getMembersSize() + " friends chat member(s) near you"; + } + + @Override + public Color getTextColor() + { + return Color.WHITE; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java index 3d1585e625..548eca7589 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java @@ -144,7 +144,7 @@ class SceneUploader WallObject wallObject = tile.getWallObject(); if (wallObject != null) { - Renderable renderable1 = wallObject.getRenderable(); + Renderable renderable1 = wallObject.getRenderable1(); if (renderable1 instanceof Model) { uploadModel((Model) renderable1, vertexBuffer, uvBuffer); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java new file mode 100644 index 0000000000..9cb66911af --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018, Seth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(GrandExchangeConfig.CONFIG_GROUP) +public interface GrandExchangeConfig extends Config +{ + String CONFIG_GROUP = "grandexchange"; + + @ConfigItem( + position = 1, + keyName = "quickLookup", + name = "Hotkey lookup (Alt + Left click)", + description = "Configures whether to enable the hotkey lookup for ge searches" + ) + default boolean quickLookup() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "enableNotifications", + name = "Enable Notifications", + description = "Configures whether to enable notifications when an offer updates" + ) + default boolean enableNotifications() + { + return true; + } + + @ConfigItem( + position = 3, + keyName = "enableOsbPrices", + name = "Enable OSB actively traded prices", + description = "Shows the OSBuddy actively traded price at the GE" + ) + default boolean enableOsbPrices() + { + return false; + } + + @ConfigItem( + position = 4, + keyName = "enableGeLimits", + name = "Enable GE Limits on GE", + description = "Shows the GE Limits on the GE" + ) + default boolean enableGELimits() + { + return true; + } + + @ConfigItem( + position = 5, + keyName = "enableGELimitReset", + name = "Enable GE Limit Reset Timer", + description = "Shows when GE Trade limits reset (H:MM)" + ) + + default boolean enableGELimitReset() + { + return true; + } + + @ConfigItem( + position = 6, + keyName = "showTotal", + name = "Show grand exchange total", + description = "Show grand exchange total" + ) + default boolean showTotal() + { + return true; + } + + @ConfigItem( + position = 7, + keyName = "showExact", + name = "Show exact total value", + description = "Show exact total value" + ) + default boolean showExact() + { + return false; + } + + @ConfigItem( + position = 8, + keyName = "highlightSearchMatch", + name = "Highlight Search Match", + description = "Highlights the search match with an underline" + ) + default boolean highlightSearchMatch() + { + return true; + } + + @ConfigItem( + position = 9, + keyName = "geSearchMode", + name = "Search Mode", + description = "The search mode to use for the GE
" + + "Default - Matches exact text only
" + + "Fuzzy Only - Matches inexact text such as 'sara sword'
" + + "Fuzzy Fallback - Uses default search, falling back to fuzzy search if no results were found" + ) + default GrandExchangeSearchMode geSearchMode() + { + return GrandExchangeSearchMode.DEFAULT; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeInputListener.java new file mode 100644 index 0000000000..c2533d59e4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeInputListener.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import net.runelite.api.Client; +import net.runelite.api.MenuEntry; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.MouseAdapter; +import static net.runelite.client.plugins.grandexchange.GrandExchangePlugin.SEARCH_GRAND_EXCHANGE; +import net.runelite.client.util.Text; + +public class GrandExchangeInputListener extends MouseAdapter implements KeyListener +{ + private final Client client; + private final GrandExchangePlugin plugin; + + @Inject + private GrandExchangeInputListener(Client client, GrandExchangePlugin plugin) + { + this.client = client; + this.plugin = plugin; + } + + @Override + public MouseEvent mouseClicked(MouseEvent e) + { + // Check if left click + alt + if (e.getButton() == MouseEvent.BUTTON1 && e.isAltDown()) + { + final MenuEntry[] menuEntries = client.getMenuEntries(); + for (final MenuEntry menuEntry : menuEntries) + { + if (menuEntry.getOption().equals(SEARCH_GRAND_EXCHANGE)) + { + search(Text.removeTags(menuEntry.getTarget())); + e.consume(); + break; + } + } + } + + return super.mouseClicked(e); + } + + private void search(final String itemName) + { + SwingUtilities.invokeLater(() -> + { + plugin.getPanel().showSearch(); + + if (!plugin.getButton().isSelected()) + { + plugin.getButton().getOnSelect().run(); + } + + plugin.getPanel().getSearchPanel().priceLookup(itemName); + }); + } + + @Override + public void keyTyped(KeyEvent e) + { + + } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.isAltDown()) + { + plugin.setHotKeyPressed(true); + } + } + + @Override + public void keyReleased(KeyEvent e) + { + if (!e.isAltDown()) + { + plugin.setHotKeyPressed(false); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java new file mode 100644 index 0000000000..b833c4ab41 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018, Seth + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.util.AsyncBufferedImage; +import net.runelite.client.util.QuantityFormatter; + +/** + * This panel displays an individual item result in the + * Grand Exchange search plugin. + */ +class GrandExchangeItemPanel extends JPanel +{ + private static final Dimension ICON_SIZE = new Dimension(32, 32); + + GrandExchangeItemPanel(AsyncBufferedImage icon, String name, int itemID, int gePrice, Double + haPrice, int geItemLimit) + { + BorderLayout layout = new BorderLayout(); + layout.setHgap(5); + setLayout(layout); + setToolTipText(name); + setBackground(ColorScheme.DARKER_GRAY_COLOR); + + Color background = getBackground(); + List panels = new ArrayList<>(); + panels.add(this); + + MouseAdapter itemPanelMouseListener = new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent e) + { + for (JPanel panel : panels) + { + matchComponentBackground(panel, ColorScheme.DARK_GRAY_HOVER_COLOR); + } + setCursor(new Cursor(Cursor.HAND_CURSOR)); + } + + @Override + public void mouseExited(MouseEvent e) + { + for (JPanel panel : panels) + { + matchComponentBackground(panel, background); + } + setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + + @Override + public void mouseReleased(MouseEvent e) + { + GrandExchangePlugin.openGeLink(name, itemID); + } + }; + + addMouseListener(itemPanelMouseListener); + + setBorder(new EmptyBorder(5, 5, 5, 0)); + + // Icon + JLabel itemIcon = new JLabel(); + itemIcon.setPreferredSize(ICON_SIZE); + if (icon != null) + { + icon.addTo(itemIcon); + } + add(itemIcon, BorderLayout.LINE_START); + + // Item details panel + JPanel rightPanel = new JPanel(new GridLayout(3, 1)); + panels.add(rightPanel); + rightPanel.setBackground(background); + + // Item name + JLabel itemName = new JLabel(); + itemName.setForeground(Color.WHITE); + itemName.setMaximumSize(new Dimension(0, 0)); // to limit the label's size for + itemName.setPreferredSize(new Dimension(0, 0)); // items with longer names + itemName.setText(name); + rightPanel.add(itemName); + + // Ge price + JLabel gePriceLabel = new JLabel(); + if (gePrice > 0) + { + gePriceLabel.setText(QuantityFormatter.formatNumber(gePrice) + " gp"); + } + else + { + gePriceLabel.setText("N/A"); + } + gePriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_PRICE); + rightPanel.add(gePriceLabel); + + JPanel alchAndLimitPanel = new JPanel(new BorderLayout()); + panels.add(alchAndLimitPanel); + alchAndLimitPanel.setBackground(background); + + // Alch price + JLabel haPriceLabel = new JLabel(); + haPriceLabel.setText(QuantityFormatter.formatNumber(haPrice.intValue()) + " alch"); + haPriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_ALCH); + alchAndLimitPanel.add(haPriceLabel, BorderLayout.WEST); + + // GE Limit + JLabel geLimitLabel = new JLabel(); + String limitLabelText = geItemLimit == 0 ? "" : "Limit " + QuantityFormatter.formatNumber(geItemLimit); + geLimitLabel.setText(limitLabelText); + geLimitLabel.setForeground(ColorScheme.GRAND_EXCHANGE_LIMIT); + geLimitLabel.setBorder(new CompoundBorder(geLimitLabel.getBorder(), new EmptyBorder(0, 0, 0, 7))); + alchAndLimitPanel.add(geLimitLabel, BorderLayout.EAST); + + rightPanel.add(alchAndLimitPanel); + + add(rightPanel, BorderLayout.CENTER); + } + + private void matchComponentBackground(JPanel panel, Color color) + { + panel.setBackground(color); + for (Component c : panel.getComponents()) + { + c.setBackground(color); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java new file mode 100644 index 0000000000..c56472d494 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Seth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import lombok.Value; +import net.runelite.client.util.AsyncBufferedImage; + +@Value +class GrandExchangeItems +{ + private final AsyncBufferedImage icon; + private final String name; + private final int itemId; + private final int gePrice; + private final double haPrice; + private final int geItemLimit; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java new file mode 100644 index 0000000000..13405426b9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOfferSlot.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.grandexchange; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.image.BufferedImage; +import javax.annotation.Nullable; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import net.runelite.api.GrandExchangeOffer; +import static net.runelite.api.GrandExchangeOfferState.BOUGHT; +import static net.runelite.api.GrandExchangeOfferState.BUYING; +import static net.runelite.api.GrandExchangeOfferState.CANCELLED_BUY; +import static net.runelite.api.GrandExchangeOfferState.CANCELLED_SELL; +import static net.runelite.api.GrandExchangeOfferState.EMPTY; +import net.runelite.api.ItemComposition; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.ThinProgressBar; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.QuantityFormatter; + +public class GrandExchangeOfferSlot extends JPanel +{ + private static final String FACE_CARD = "FACE_CARD"; + private static final String DETAILS_CARD = "DETAILS_CARD"; + + private static final ImageIcon RIGHT_ARROW_ICON; + private static final ImageIcon LEFT_ARROW_ICON; + + private final JPanel container = new JPanel(); + private final CardLayout cardLayout = new CardLayout(); + + private final JLabel itemIcon = new JLabel(); + private final JLabel itemName = new JLabel(); + private final JLabel offerInfo = new JLabel(); + + private final JLabel itemPrice = new JLabel(); + private final JLabel offerSpent = new JLabel(); + + private final ThinProgressBar progressBar = new ThinProgressBar(); + + private boolean showingFace = true; + + static + { + final BufferedImage rightArrow = ImageUtil.alphaOffset(ImageUtil.getResourceStreamFromClass(GrandExchangeOfferSlot.class, "/util/arrow_right.png"), 0.25f); + RIGHT_ARROW_ICON = new ImageIcon(rightArrow); + LEFT_ARROW_ICON = new ImageIcon(ImageUtil.flipImage(rightArrow, true, false)); + } + + /** + * This (sub)panel is used for each GE slot displayed + * in the sidebar + */ + GrandExchangeOfferSlot() + { + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setBorder(new EmptyBorder(7, 0, 0, 0)); + + final MouseListener ml = new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (SwingUtilities.isLeftMouseButton(mouseEvent)) + { + switchPanel(); + } + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + container.setBackground(ColorScheme.DARKER_GRAY_HOVER_COLOR); + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + } + }; + + container.setLayout(cardLayout); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + JPanel faceCard = new JPanel(); + faceCard.setBackground(ColorScheme.DARKER_GRAY_COLOR); + faceCard.setLayout(new BorderLayout()); + faceCard.addMouseListener(ml); + + itemIcon.setVerticalAlignment(JLabel.CENTER); + itemIcon.setHorizontalAlignment(JLabel.CENTER); + itemIcon.setPreferredSize(new Dimension(45, 45)); + + itemName.setForeground(Color.WHITE); + itemName.setVerticalAlignment(JLabel.BOTTOM); + itemName.setFont(FontManager.getRunescapeSmallFont()); + + offerInfo.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + offerInfo.setVerticalAlignment(JLabel.TOP); + offerInfo.setFont(FontManager.getRunescapeSmallFont()); + + JLabel switchFaceViewIcon = new JLabel(); + switchFaceViewIcon.setIcon(RIGHT_ARROW_ICON); + switchFaceViewIcon.setVerticalAlignment(JLabel.CENTER); + switchFaceViewIcon.setHorizontalAlignment(JLabel.CENTER); + switchFaceViewIcon.setPreferredSize(new Dimension(30, 45)); + + JPanel offerFaceDetails = new JPanel(); + offerFaceDetails.setBackground(ColorScheme.DARKER_GRAY_COLOR); + offerFaceDetails.setLayout(new GridLayout(2, 1, 0, 2)); + + offerFaceDetails.add(itemName); + offerFaceDetails.add(offerInfo); + + faceCard.add(offerFaceDetails, BorderLayout.CENTER); + faceCard.add(itemIcon, BorderLayout.WEST); + faceCard.add(switchFaceViewIcon, BorderLayout.EAST); + + JPanel detailsCard = new JPanel(); + detailsCard.setBackground(ColorScheme.DARKER_GRAY_COLOR); + detailsCard.setLayout(new BorderLayout()); + detailsCard.setBorder(new EmptyBorder(0, 15, 0, 0)); + detailsCard.addMouseListener(ml); + + itemPrice.setForeground(Color.WHITE); + itemPrice.setVerticalAlignment(JLabel.BOTTOM); + itemPrice.setFont(FontManager.getRunescapeSmallFont()); + + offerSpent.setForeground(Color.WHITE); + offerSpent.setVerticalAlignment(JLabel.TOP); + offerSpent.setFont(FontManager.getRunescapeSmallFont()); + + JLabel switchDetailsViewIcon = new JLabel(); + switchDetailsViewIcon.setIcon(LEFT_ARROW_ICON); + switchDetailsViewIcon.setVerticalAlignment(JLabel.CENTER); + switchDetailsViewIcon.setHorizontalAlignment(JLabel.CENTER); + switchDetailsViewIcon.setPreferredSize(new Dimension(30, 45)); + + JPanel offerDetails = new JPanel(); + offerDetails.setBackground(ColorScheme.DARKER_GRAY_COLOR); + offerDetails.setLayout(new GridLayout(2, 1)); + + offerDetails.add(itemPrice); + offerDetails.add(offerSpent); + + detailsCard.add(offerDetails, BorderLayout.CENTER); + detailsCard.add(switchDetailsViewIcon, BorderLayout.EAST); + + container.add(faceCard, FACE_CARD); + container.add(detailsCard, DETAILS_CARD); + + cardLayout.show(container, FACE_CARD); + + add(container, BorderLayout.CENTER); + add(progressBar, BorderLayout.SOUTH); + } + + void updateOffer(ItemComposition offerItem, BufferedImage itemImage, @Nullable GrandExchangeOffer newOffer) + { + if (newOffer == null || newOffer.getState() == EMPTY) + { + return; + } + else + { + cardLayout.show(container, FACE_CARD); + + itemName.setText(offerItem.getName()); + itemIcon.setIcon(new ImageIcon(itemImage)); + + boolean buying = newOffer.getState() == BOUGHT + || newOffer.getState() == BUYING + || newOffer.getState() == CANCELLED_BUY; + + String offerState = (buying ? "Bought " : "Sold ") + + QuantityFormatter.quantityToRSDecimalStack(newOffer.getQuantitySold()) + " / " + + QuantityFormatter.quantityToRSDecimalStack(newOffer.getTotalQuantity()); + + offerInfo.setText(offerState); + + itemPrice.setText(htmlLabel("Price each: ", QuantityFormatter.formatNumber(newOffer.getPrice()))); + + String action = buying ? "Spent: " : "Received: "; + + offerSpent.setText(htmlLabel(action, QuantityFormatter.formatNumber(newOffer.getSpent()) + " / " + + QuantityFormatter.formatNumber(newOffer.getPrice() * newOffer.getTotalQuantity()))); + + progressBar.setForeground(getProgressColor(newOffer)); + progressBar.setMaximumValue(newOffer.getTotalQuantity()); + progressBar.setValue(newOffer.getQuantitySold()); + + final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); + + final JMenuItem openGeLink = new JMenuItem("Open Grand Exchange website"); + openGeLink.addActionListener(e -> GrandExchangePlugin.openGeLink(offerItem.getName(), offerItem.getId())); + popupMenu.add(openGeLink); + + /* Couldn't set the tooltip for the container panel as the children override it, so I'm setting + * the tooltips on the children instead. */ + for (Component c : container.getComponents()) + { + if (c instanceof JPanel) + { + JPanel panel = (JPanel) c; + panel.setToolTipText(htmlTooltip(((int) progressBar.getPercentage()) + "%")); + panel.setComponentPopupMenu(popupMenu); + } + } + } + + revalidate(); + repaint(); + } + + private String htmlTooltip(String value) + { + return "Progress: " + value + ""; + } + + private String htmlLabel(String key, String value) + { + return "" + key + "" + value + ""; + } + + private void switchPanel() + { + this.showingFace = !this.showingFace; + cardLayout.show(container, showingFace ? FACE_CARD : DETAILS_CARD); + } + + private Color getProgressColor(GrandExchangeOffer offer) + { + if (offer.getState() == CANCELLED_BUY || offer.getState() == CANCELLED_SELL) + { + return ColorScheme.PROGRESS_ERROR_COLOR; + } + + if (offer.getQuantitySold() == offer.getTotalQuantity()) + { + return ColorScheme.PROGRESS_COMPLETE_COLOR; + } + + return ColorScheme.PROGRESS_INPROGRESS_COLOR; + } +} + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java new file mode 100644 index 0000000000..a64f4b1474 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.image.BufferedImage; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; +import net.runelite.api.ItemComposition; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.components.PluginErrorPanel; + +public class GrandExchangeOffersPanel extends JPanel +{ + private static final String ERROR_PANEL = "ERROR_PANEL"; + private static final String OFFERS_PANEL = "OFFERS_PANEL"; + + private static final int MAX_OFFERS = 8; + + private final GridBagConstraints constraints = new GridBagConstraints(); + private final CardLayout cardLayout = new CardLayout(); + + /* The offers container, this will hold all the individual ge offers panels */ + private final JPanel offerPanel = new JPanel(); + + /* The center panel, this holds either the error panel or the offers container */ + private final JPanel container = new JPanel(cardLayout); + + private final GrandExchangeOfferSlot[] offerSlotPanels = new GrandExchangeOfferSlot[MAX_OFFERS]; + + GrandExchangeOffersPanel() + { + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.weightx = 1; + constraints.gridx = 0; + constraints.gridy = 0; + + /* This panel wraps the offers panel and limits its height */ + JPanel offersWrapper = new JPanel(new BorderLayout()); + offersWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + offersWrapper.add(offerPanel, BorderLayout.NORTH); + + offerPanel.setLayout(new GridBagLayout()); + offerPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + offerPanel.setBackground(ColorScheme.DARK_GRAY_COLOR); + + /* This panel wraps the error panel and limits its height */ + JPanel errorWrapper = new JPanel(new BorderLayout()); + errorWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + /* The error panel, this displays an error message */ + PluginErrorPanel errorPanel = new PluginErrorPanel(); + errorWrapper.add(errorPanel, BorderLayout.NORTH); + + errorPanel.setBorder(new EmptyBorder(50, 20, 20, 20)); + errorPanel.setContent("No offers detected", "No grand exchange offers were found on your account."); + + container.add(offersWrapper, OFFERS_PANEL); + container.add(errorWrapper, ERROR_PANEL); + + add(container, BorderLayout.CENTER); + + resetOffers(); + } + + void resetOffers() + { + offerPanel.removeAll(); + for (int i = 0; i < offerSlotPanels.length; i++) + { + offerSlotPanels[i] = null; + } + updateEmptyOffersPanel(); + } + + void updateOffer(ItemComposition item, BufferedImage itemImage, GrandExchangeOffer newOffer, int slot) + { + /* If slot was previously filled, and is now empty, remove it from the list */ + if (newOffer == null || newOffer.getState() == GrandExchangeOfferState.EMPTY) + { + if (offerSlotPanels[slot] != null) + { + offerPanel.remove(offerSlotPanels[slot]); + offerSlotPanels[slot] = null; + revalidate(); + repaint(); + } + + removeTopMargin(); + updateEmptyOffersPanel(); + return; + } + + /* If slot was empty, and is now filled, add it to the list */ + if (offerSlotPanels[slot] == null) + { + GrandExchangeOfferSlot newSlot = new GrandExchangeOfferSlot(); + offerSlotPanels[slot] = newSlot; + offerPanel.add(newSlot, constraints); + constraints.gridy++; + } + + offerSlotPanels[slot].updateOffer(item, itemImage, newOffer); + + removeTopMargin(); + + revalidate(); + repaint(); + + updateEmptyOffersPanel(); + } + + /** + * Reset the border for the first offer slot. + */ + private void removeTopMargin() + { + + if (offerPanel.getComponentCount() <= 0) + { + return; + } + + JPanel firstItem = (JPanel) offerPanel.getComponent(0); + firstItem.setBorder(null); + } + + /** + * This method calculates the amount of empty ge offer slots, if all slots are empty, + * it shows the error panel. + */ + private void updateEmptyOffersPanel() + { + int nullCount = 0; + for (GrandExchangeOfferSlot slot : offerSlotPanels) + { + if (slot == null) + { + nullCount++; + } + } + + if (nullCount == MAX_OFFERS) + { + offerPanel.removeAll(); + cardLayout.show(container, ERROR_PANEL); + } + else + { + cardLayout.show(container, OFFERS_PANEL); + } + + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java new file mode 100644 index 0000000000..0b59270de5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.grandexchange; + +import java.awt.BorderLayout; +import java.util.concurrent.ScheduledExecutorService; +import javax.inject.Inject; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import lombok.Getter; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.materialtabs.MaterialTab; +import net.runelite.client.ui.components.materialtabs.MaterialTabGroup; + +class GrandExchangePanel extends PluginPanel +{ + + // this panel will hold either the ge search panel or the ge offers panel + private final JPanel display = new JPanel(); + + private final MaterialTabGroup tabGroup = new MaterialTabGroup(display); + private final MaterialTab searchTab; + + @Getter + private GrandExchangeSearchPanel searchPanel; + @Getter + private GrandExchangeOffersPanel offersPanel; + + @Inject + private GrandExchangePanel(ClientThread clientThread, ItemManager itemManager, ScheduledExecutorService executor) + { + super(false); + + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + // Search Panel + searchPanel = new GrandExchangeSearchPanel(clientThread, itemManager, executor); + + //Offers Panel + offersPanel = new GrandExchangeOffersPanel(); + + MaterialTab offersTab = new MaterialTab("Offers", tabGroup, offersPanel); + searchTab = new MaterialTab("Search", tabGroup, searchPanel); + + tabGroup.setBorder(new EmptyBorder(5, 0, 0, 0)); + tabGroup.addTab(offersTab); + tabGroup.addTab(searchTab); + tabGroup.select(offersTab); // selects the default selected tab + + add(tabGroup, BorderLayout.NORTH); + add(display, BorderLayout.CENTER); + } + + void showSearch() + { + if (searchPanel.isShowing()) + { + return; + } + + tabGroup.select(searchTab); + revalidate(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java new file mode 100644 index 0000000000..240ea3b8d4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2019, Adam + * Copyright (c) 2017, Robbie + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2020, Dennis + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.grandexchange; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import com.google.common.primitives.Shorts; +import com.google.gson.Gson; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; +import net.runelite.api.ItemComposition; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.ScriptID; +import net.runelite.api.VarClientStr; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GrandExchangeOfferChanged; +import net.runelite.api.events.GrandExchangeSearched; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.Notifier; +import net.runelite.client.account.AccountSession; +import net.runelite.client.account.SessionManager; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.SessionClose; +import net.runelite.client.events.SessionOpen; +import net.runelite.client.game.ItemManager; +import net.runelite.client.input.KeyManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; +import net.runelite.client.util.OSType; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.util.Text; +import net.runelite.http.api.ge.GrandExchangeClient; +import net.runelite.http.api.ge.GrandExchangeTrade; +import net.runelite.http.api.item.ItemStats; +import net.runelite.http.api.osbuddy.OSBGrandExchangeClient; +import net.runelite.http.api.osbuddy.OSBGrandExchangeResult; +import net.runelite.http.api.worlds.WorldType; +import okhttp3.OkHttpClient; +import org.apache.commons.lang3.time.DurationFormatUtils; +import org.apache.commons.text.similarity.FuzzyScore; + +@PluginDescriptor( + name = "Grand Exchange", + description = "Provide additional and/or easier access to Grand Exchange information", + tags = {"external", "integration", "notifications", "panel", "prices", "trade"} +) +@Slf4j +public class GrandExchangePlugin extends Plugin +{ + private static final int GE_SLOTS = 8; + private static final int OFFER_CONTAINER_ITEM = 21; + private static final int OFFER_DEFAULT_ITEM_ID = 6512; + private static final String OSB_GE_TEXT = "
OSBuddy Actively traded price: "; + + private static final String BUY_LIMIT_GE_TEXT = "
Buy limit: "; + private static final String BUY_LIMIT_KEY = "buylimit"; + private static final Gson GSON = new Gson(); + private static final Duration BUY_LIMIT_RESET = Duration.ofHours(4); + + static final String SEARCH_GRAND_EXCHANGE = "Search Grand Exchange"; + + private static final int MAX_RESULT_COUNT = 250; + + private static final FuzzyScore FUZZY = new FuzzyScore(Locale.ENGLISH); + + private static final Color FUZZY_HIGHLIGHT_COLOR = new Color(0x800000); + + @Getter(AccessLevel.PACKAGE) + private NavigationButton button; + + @Getter(AccessLevel.PACKAGE) + private GrandExchangePanel panel; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private boolean hotKeyPressed; + + @Inject + private GrandExchangeInputListener inputListener; + + @Inject + private ItemManager itemManager; + + @Inject + private MouseManager mouseManager; + + @Inject + private KeyManager keyManager; + + @Inject + private Client client; + + @Inject + private ClientToolbar clientToolbar; + + @Inject + private GrandExchangeConfig config; + + @Inject + private Notifier notifier; + + @Inject + private ScheduledExecutorService executorService; + + @Inject + private SessionManager sessionManager; + + @Inject + private ConfigManager configManager; + + private Widget grandExchangeText; + private Widget grandExchangeItem; + private String grandExchangeExamine; + + private int osbItem; + private OSBGrandExchangeResult osbGrandExchangeResult; + + @Inject + private GrandExchangeClient grandExchangeClient; + private boolean loginBurstGeUpdates; + + @Inject + private OSBGrandExchangeClient osbGrandExchangeClient; + + private boolean wasFuzzySearch; + + private String machineUuid; + private String lastUsername; + + /** + * Logic from {@link org.apache.commons.text.similarity.FuzzyScore} + */ + @VisibleForTesting + static List findFuzzyIndices(String term, String query) + { + List indices = new ArrayList<>(); + + // fuzzy logic is case insensitive. We normalize the Strings to lower + // case right from the start. Turning characters to lower case + // via Character.toLowerCase(char) is unfortunately insufficient + // as it does not accept a locale. + final String termLowerCase = term.toLowerCase(); + final String queryLowerCase = query.toLowerCase(); + + // the position in the term which will be scanned next for potential + // query character matches + int termIndex = 0; + + for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) + { + final char queryChar = queryLowerCase.charAt(queryIndex); + + boolean termCharacterMatchFound = false; + for (; termIndex < termLowerCase.length() + && !termCharacterMatchFound; termIndex++) + { + final char termChar = termLowerCase.charAt(termIndex); + + if (queryChar == termChar) + { + indices.add(termIndex); + + // we can leave the nested loop. Every character in the + // query can match at most one character in the term. + termCharacterMatchFound = true; + } + } + } + + return indices; + } + + private SavedOffer getOffer(int slot) + { + String offer = configManager.getRSProfileConfiguration("geoffer", Integer.toString(slot)); + if (offer == null) + { + return null; + } + return GSON.fromJson(offer, SavedOffer.class); + } + + private void setOffer(int slot, SavedOffer offer) + { + configManager.setRSProfileConfiguration("geoffer", Integer.toString(slot), GSON.toJson(offer)); + } + + private void deleteOffer(int slot) + { + configManager.unsetRSProfileConfiguration("geoffer", Integer.toString(slot)); + } + + @Provides + GrandExchangeConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(GrandExchangeConfig.class); + } + + @Provides + OSBGrandExchangeClient provideOsbGrandExchangeClient(OkHttpClient okHttpClient) + { + return new OSBGrandExchangeClient(okHttpClient); + } + + @Provides + GrandExchangeClient provideGrandExchangeClient(OkHttpClient okHttpClient) + { + return new GrandExchangeClient(okHttpClient); + } + + @Override + protected void startUp() + { + panel = injector.getInstance(GrandExchangePanel.class); + + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "ge_icon.png"); + + button = NavigationButton.builder() + .tooltip("Grand Exchange") + .icon(icon) + .priority(3) + .panel(panel) + .build(); + + clientToolbar.addNavigation(button); + + if (config.quickLookup()) + { + mouseManager.registerMouseListener(inputListener); + keyManager.registerKeyListener(inputListener); + } + + AccountSession accountSession = sessionManager.getAccountSession(); + if (accountSession != null) + { + grandExchangeClient.setUuid(accountSession.getUuid()); + } + else + { + grandExchangeClient.setUuid(null); + } + + osbItem = -1; + osbGrandExchangeResult = null; + } + + @Override + protected void shutDown() + { + clientToolbar.removeNavigation(button); + mouseManager.unregisterMouseListener(inputListener); + keyManager.unregisterKeyListener(inputListener); + grandExchangeText = null; + grandExchangeItem = null; + lastUsername = machineUuid = null; + } + + @Subscribe + public void onSessionOpen(SessionOpen sessionOpen) + { + AccountSession accountSession = sessionManager.getAccountSession(); + grandExchangeClient.setUuid(accountSession.getUuid()); + } + + @Subscribe + public void onSessionClose(SessionClose sessionClose) + { + grandExchangeClient.setUuid(null); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(GrandExchangeConfig.CONFIG_GROUP)) + { + if (event.getKey().equals("quickLookup")) + { + if (config.quickLookup()) + { + mouseManager.registerMouseListener(inputListener); + keyManager.registerKeyListener(inputListener); + } + else + { + mouseManager.unregisterMouseListener(inputListener); + keyManager.unregisterKeyListener(inputListener); + } + } + } + } + + @Subscribe + public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent) + { + final int slot = offerEvent.getSlot(); + final GrandExchangeOffer offer = offerEvent.getOffer(); + + if (offer.getState() == GrandExchangeOfferState.EMPTY && client.getGameState() != GameState.LOGGED_IN) + { + // Trades are cleared by the client during LOGIN_SCREEN/HOPPING/LOGGING_IN, ignore those so we don't + // zero and re-submit the trade on login as an update + return; + } + + log.debug("GE offer updated: state: {}, slot: {}, item: {}, qty: {}, login: {}", + offer.getState(), slot, offer.getItemId(), offer.getQuantitySold(), loginBurstGeUpdates); + + ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId()); + boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1; + BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack); + SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offer, slot)); + + submitTrade(slot, offer); + + updateConfig(slot, offer); + + if (loginBurstGeUpdates && slot == GE_SLOTS - 1) // slots are sent sequentially on login; this is the last one + { + loginBurstGeUpdates = false; + } + } + + @VisibleForTesting + void submitTrade(int slot, GrandExchangeOffer offer) + { + GrandExchangeOfferState state = offer.getState(); + + if (state != GrandExchangeOfferState.CANCELLED_BUY && state != GrandExchangeOfferState.CANCELLED_SELL && state != GrandExchangeOfferState.BUYING && state != GrandExchangeOfferState.SELLING) + { + return; + } + + SavedOffer savedOffer = getOffer(slot); + if (savedOffer == null && (state == GrandExchangeOfferState.BUYING || state == GrandExchangeOfferState.SELLING) && offer.getQuantitySold() == 0) + { + // new offer + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(state == GrandExchangeOfferState.BUYING); + grandExchangeTrade.setItemId(offer.getItemId()); + grandExchangeTrade.setTotal(offer.getTotalQuantity()); + grandExchangeTrade.setOffer(offer.getPrice()); + grandExchangeTrade.setSlot(slot); + grandExchangeTrade.setWorldType(getGeWorldType()); + grandExchangeTrade.setLogin(loginBurstGeUpdates); + + log.debug("Submitting new trade: {}", grandExchangeTrade); + grandExchangeClient.submit(grandExchangeTrade); + return; + } + + if (savedOffer == null || savedOffer.getItemId() != offer.getItemId() || savedOffer.getPrice() != offer.getPrice() || savedOffer.getTotalQuantity() != offer.getTotalQuantity()) + { + // desync + return; + } + + if (savedOffer.getState() == offer.getState() && savedOffer.getQuantitySold() == offer.getQuantitySold()) + { + // no change + return; + } + + if (state == GrandExchangeOfferState.CANCELLED_BUY || state == GrandExchangeOfferState.CANCELLED_SELL) + { + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(state == GrandExchangeOfferState.CANCELLED_BUY); + grandExchangeTrade.setCancel(true); + grandExchangeTrade.setItemId(offer.getItemId()); + grandExchangeTrade.setQty(offer.getQuantitySold()); + grandExchangeTrade.setTotal(offer.getTotalQuantity()); + grandExchangeTrade.setSpent(offer.getSpent()); + grandExchangeTrade.setOffer(offer.getPrice()); + grandExchangeTrade.setSlot(slot); + grandExchangeTrade.setWorldType(getGeWorldType()); + grandExchangeTrade.setLogin(loginBurstGeUpdates); + + log.debug("Submitting cancelled: {}", grandExchangeTrade); + grandExchangeClient.submit(grandExchangeTrade); + return; + } + + final int qty = offer.getQuantitySold() - savedOffer.getQuantitySold(); + final int dspent = offer.getSpent() - savedOffer.getSpent(); + if (qty <= 0 || dspent <= 0) + { + return; + } + + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(state == GrandExchangeOfferState.BUYING); + grandExchangeTrade.setItemId(offer.getItemId()); + grandExchangeTrade.setQty(offer.getQuantitySold()); + grandExchangeTrade.setDqty(qty); + grandExchangeTrade.setTotal(offer.getTotalQuantity()); + grandExchangeTrade.setDspent(dspent); + grandExchangeTrade.setSpent(offer.getSpent()); + grandExchangeTrade.setOffer(offer.getPrice()); + grandExchangeTrade.setSlot(slot); + grandExchangeTrade.setWorldType(getGeWorldType()); + grandExchangeTrade.setLogin(loginBurstGeUpdates); + + log.debug("Submitting trade: {}", grandExchangeTrade); + grandExchangeClient.submit(grandExchangeTrade); + } + + private WorldType getGeWorldType() + { + EnumSet worldTypes = client.getWorldType(); + if (worldTypes.contains(net.runelite.api.WorldType.DEADMAN)) + { + return WorldType.DEADMAN; + } + else if (worldTypes.contains(net.runelite.api.WorldType.DEADMAN_TOURNAMENT)) + { + return WorldType.DEADMAN_TOURNAMENT; + } + else + { + return null; + } + } + + private void updateConfig(int slot, GrandExchangeOffer offer) + { + if (offer.getState() == GrandExchangeOfferState.EMPTY) + { + deleteOffer(slot); + } + else + { + SavedOffer savedOffer = new SavedOffer(); + savedOffer.setItemId(offer.getItemId()); + savedOffer.setQuantitySold(offer.getQuantitySold()); + savedOffer.setTotalQuantity(offer.getTotalQuantity()); + savedOffer.setPrice(offer.getPrice()); + savedOffer.setSpent(offer.getSpent()); + savedOffer.setState(offer.getState()); + setOffer(slot, savedOffer); + + updateLimitTimer(offer); + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (!this.config.enableNotifications() || event.getType() != ChatMessageType.GAMEMESSAGE) + { + return; + } + + String message = Text.removeTags(event.getMessage()); + + if (message.startsWith("Grand Exchange:")) + { + this.notifier.notify(message); + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN) + { + panel.getOffersPanel().resetOffers(); + loginBurstGeUpdates = true; + } + else if (gameStateChanged.getGameState() == GameState.LOGGED_IN) + { + grandExchangeClient.setMachineId(getMachineUuid()); + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + // At the moment, if the user disables quick lookup, the input listener gets disabled. Thus, isHotKeyPressed() + // should always return false when quick lookup is disabled. + // Replace the default option with "Search ..." when holding alt + if (client.getGameState() != GameState.LOGGED_IN || !hotKeyPressed) + { + return; + } + + final MenuEntry[] entries = client.getMenuEntries(); + final MenuEntry menuEntry = entries[entries.length - 1]; + final int widgetId = menuEntry.getParam1(); + final int groupId = WidgetInfo.TO_GROUP(widgetId); + + switch (groupId) + { + case WidgetID.BANK_GROUP_ID: + // Don't show for view tabs and such + if (WidgetInfo.TO_CHILD(widgetId) != WidgetInfo.BANK_ITEM_CONTAINER.getChildId()) + { + break; + } + case WidgetID.INVENTORY_GROUP_ID: + case WidgetID.BANK_INVENTORY_GROUP_ID: + case WidgetID.GRAND_EXCHANGE_INVENTORY_GROUP_ID: + case WidgetID.SHOP_INVENTORY_GROUP_ID: + menuEntry.setOption(SEARCH_GRAND_EXCHANGE); + menuEntry.setType(MenuAction.RUNELITE.getId()); + client.setMenuEntries(entries); + } + } + + @Subscribe + public void onFocusChanged(FocusChanged focusChanged) + { + if (!focusChanged.isFocused()) + { + setHotKeyPressed(false); + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + switch (event.getGroupId()) + { + // Grand exchange was opened. + case WidgetID.GRAND_EXCHANGE_GROUP_ID: + Widget grandExchangeOffer = client.getWidget(WidgetInfo.GRAND_EXCHANGE_OFFER_CONTAINER); + grandExchangeText = client.getWidget(WidgetInfo.GRAND_EXCHANGE_OFFER_TEXT); + grandExchangeItem = grandExchangeOffer.getChild(OFFER_CONTAINER_ITEM); + break; + // Grand exchange was closed (if it was open before). + case WidgetID.INVENTORY_GROUP_ID: + grandExchangeText = null; + grandExchangeItem = null; + break; + } + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + // GE offers setup init + if (event.getScriptId() == ScriptID.GE_OFFERS_SETUP_BUILD) + { + rebuildGeText(); + } + else if (event.getScriptId() == ScriptID.GE_ITEM_SEARCH && config.highlightSearchMatch()) + { + highlightSearchMatches(); + } + } + + private void highlightSearchMatches() + { + if (!wasFuzzySearch) + { + return; + } + String input = client.getVar(VarClientStr.INPUT_TEXT); + + String underlineTag = ""; + + Widget results = client.getWidget(WidgetInfo.CHATBOX_GE_SEARCH_RESULTS); + Widget[] children = results.getDynamicChildren(); + int resultCount = children.length / 3; + + for (int i = 0; i < resultCount; i++) + { + Widget itemNameWidget = children[i * 3 + 1]; + String itemName = itemNameWidget.getText(); + + List indices; + String otherName = itemName.replace('-', ' '); + if (!itemName.contains("-") || FUZZY.fuzzyScore(itemName, input) >= FUZZY.fuzzyScore(otherName, input)) + { + indices = findFuzzyIndices(itemName, input); + } + else + { + indices = findFuzzyIndices(otherName, input); + } + Collections.reverse(indices); + + StringBuilder newItemName = new StringBuilder(itemName); + for (int index : indices) + { + if (itemName.charAt(index) == ' ' || itemName.charAt(index) == '-') + { + continue; + } + + newItemName.insert(index + 1, ""); + newItemName.insert(index, underlineTag); + } + + itemNameWidget.setText(newItemName.toString()); + } + } + + @Subscribe + public void onGrandExchangeSearched(GrandExchangeSearched event) + { + wasFuzzySearch = false; + + GrandExchangeSearchMode searchMode = config.geSearchMode(); + final String input = client.getVar(VarClientStr.INPUT_TEXT); + if (searchMode == GrandExchangeSearchMode.DEFAULT || input.isEmpty()) + { + return; + } + + event.consume(); + + client.setGeSearchResultIndex(0); + + int resultCount = 0; + if (searchMode == GrandExchangeSearchMode.FUZZY_FALLBACK) + { + List ids = IntStream.range(0, client.getItemCount()) + .mapToObj(itemManager::getItemComposition) + .filter(item -> item.isTradeable() && item.getNote() == -1 + && item.getName().toLowerCase().contains(input)) + .limit(MAX_RESULT_COUNT + 1) + .sorted(Comparator.comparing(ItemComposition::getName)) + .map(ItemComposition::getId) + .collect(Collectors.toList()); + if (ids.size() > MAX_RESULT_COUNT) + { + client.setGeSearchResultCount(-1); + client.setGeSearchResultIds(null); + } + else + { + resultCount = ids.size(); + client.setGeSearchResultCount(resultCount); + client.setGeSearchResultIds(Shorts.toArray(ids)); + } + } + + if (resultCount == 0) + { + // We do this so that for example the items "Anti-venom ..." are still at the top + // when searching "anti venom" + ToIntFunction getScore = item -> + { + int score = FUZZY.fuzzyScore(item.getName(), input); + if (item.getName().contains("-")) + { + return Math.max(FUZZY.fuzzyScore(item.getName().replace('-', ' '), input), score); + } + return score; + }; + + List ids = IntStream.range(0, client.getItemCount()) + .mapToObj(itemManager::getItemComposition) + .filter(item -> item.isTradeable() && item.getNote() == -1) + .filter(item -> getScore.applyAsInt(item) > 0) + .sorted(Comparator.comparingInt(getScore).reversed() + .thenComparing(ItemComposition::getName)) + .limit(MAX_RESULT_COUNT) + .map(ItemComposition::getId) + .collect(Collectors.toList()); + + client.setGeSearchResultCount(ids.size()); + client.setGeSearchResultIds(Shorts.toArray(ids)); + + wasFuzzySearch = true; + } + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + if (!event.getEventName().equals("setGETitle") || !config.showTotal()) + { + return; + } + + long total = 0; + GrandExchangeOffer[] offers = client.getGrandExchangeOffers(); + for (GrandExchangeOffer offer : offers) + { + if (offer != null) + { + total += offer.getPrice() * offer.getTotalQuantity(); + } + } + + if (total == 0L) + { + return; + } + + StringBuilder titleBuilder = new StringBuilder(" ("); + + if (config.showExact()) + { + titleBuilder.append(QuantityFormatter.formatNumber(total)); + } + else + { + titleBuilder.append(QuantityFormatter.quantityToStackSize(total)); + } + + titleBuilder.append(')'); + + // Append to title + String[] stringStack = client.getStringStack(); + int stringStackSize = client.getStringStackSize(); + + stringStack[stringStackSize - 1] += titleBuilder.toString(); + } + + private void setLimitResetTime(int itemId) + { + Instant lastDateTime = configManager.getRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, + BUY_LIMIT_KEY + "." + itemId, Instant.class); + if (lastDateTime == null || lastDateTime.isBefore(Instant.now())) + { + configManager.setRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + "." + itemId, + Instant.now().plus(BUY_LIMIT_RESET)); + } + } + + private Instant getLimitResetTime(int itemId) + { + Instant lastDateTime = configManager.getRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, + BUY_LIMIT_KEY + "." + itemId, Instant.class); + if (lastDateTime == null) + { + return null; + } + + if (lastDateTime.isBefore(Instant.now())) + { + configManager.unsetRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + "." + itemId); + return null; + } + + return lastDateTime; + } + + private void updateLimitTimer(GrandExchangeOffer offer) + { + if (offer.getState() == GrandExchangeOfferState.BOUGHT || + (offer.getQuantitySold() > 0 && + offer.getState() == GrandExchangeOfferState.BUYING)) + { + setLimitResetTime(offer.getItemId()); + } + } + + private void rebuildGeText() + { + if (grandExchangeText == null || grandExchangeItem == null || grandExchangeItem.isHidden()) + { + return; + } + + final Widget geText = grandExchangeText; + final int itemId = grandExchangeItem.getItemId(); + + if (itemId == OFFER_DEFAULT_ITEM_ID || itemId == -1) + { + // This item is invalid/nothing has been searched for + return; + } + + if (geText.getText() == grandExchangeExamine) + { + // if we've already set the text, don't set it again + return; + } + + String text = geText.getText(); + + if (config.enableGELimits()) + { + final ItemStats itemStats = itemManager.getItemStats(itemId, false); + + // If we have item buy limit, append it + if (itemStats != null && itemStats.getGeLimit() > 0) + { + text += BUY_LIMIT_GE_TEXT + QuantityFormatter.formatNumber(itemStats.getGeLimit()); + } + } + + if (config.enableGELimitReset()) + { + Instant resetTime = getLimitResetTime(itemId); + if (resetTime != null) + { + Duration remaining = Duration.between(Instant.now(), resetTime); + text += " (" + DurationFormatUtils.formatDuration(remaining.toMillis(), "H:mm") + ")"; + } + } + + grandExchangeExamine = text; + geText.setText(text); + + if (!config.enableOsbPrices()) + { + return; + } + + // If we already have the result, use it + if (osbGrandExchangeResult != null && osbGrandExchangeResult.getItem_id() == itemId && osbGrandExchangeResult.getOverall_average() > 0) + { + grandExchangeExamine = text + OSB_GE_TEXT + QuantityFormatter.formatNumber(osbGrandExchangeResult.getOverall_average()); + geText.setText(grandExchangeExamine); + } + + if (osbItem == itemId) + { + // avoid starting duplicate lookups + return; + } + + osbItem = itemId; + + log.debug("Looking up OSB item price {}", itemId); + + final String start = text; + executorService.submit(() -> + { + try + { + final OSBGrandExchangeResult result = osbGrandExchangeClient.lookupItem(itemId); + if (result != null && result.getOverall_average() > 0) + { + osbGrandExchangeResult = result; + // Update the text on the widget too + grandExchangeExamine = start + OSB_GE_TEXT + QuantityFormatter.formatNumber(result.getOverall_average()); + geText.setText(grandExchangeExamine); + } + } + catch (IOException e) + { + log.debug("Error getting price of item {}", itemId, e); + } + }); + } + + static void openGeLink(String name, int itemId) + { + final String url = "https://services.runescape.com/m=itemdb_oldschool/" + + name.replaceAll(" ", "+") + + "/viewitem?obj=" + + itemId; + LinkBrowser.browse(url); + } + + private String getMachineUuid() + { + String username = client.getUsername(); + if (lastUsername == username) + { + return machineUuid; + } + + lastUsername = username; + + try + { + Hasher hasher = Hashing.sha256().newHasher(); + Runtime runtime = Runtime.getRuntime(); + + hasher.putByte((byte) OSType.getOSType().ordinal()); + hasher.putByte((byte) runtime.availableProcessors()); + hasher.putUnencodedChars(System.getProperty("os.arch", "")); + hasher.putUnencodedChars(System.getProperty("os.version", "")); + hasher.putUnencodedChars(System.getProperty("user.name", "")); + + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) + { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + byte[] hardwareAddress = networkInterface.getHardwareAddress(); + if (hardwareAddress != null) + { + hasher.putBytes(hardwareAddress); + } + } + hasher.putUnencodedChars(username); + machineUuid = hasher.hash().toString(); + return machineUuid; + } + catch (SocketException ex) + { + log.debug("unable to generate machine id", ex); + machineUuid = null; + return null; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchMode.java new file mode 100644 index 0000000000..6c09e4ddcf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchMode.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, Dennis + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +public enum GrandExchangeSearchMode +{ + DEFAULT, + + FUZZY_FALLBACK, + + FUZZY_ONLY +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java new file mode 100644 index 0000000000..723d9f697a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2018, Seth + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import com.google.common.base.Strings; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import net.runelite.api.ItemComposition; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.components.IconTextField; +import net.runelite.client.ui.components.PluginErrorPanel; +import net.runelite.client.util.AsyncBufferedImage; +import net.runelite.http.api.item.ItemPrice; +import net.runelite.http.api.item.ItemStats; + +/** + * This panel holds the search section of the Grand Exchange Plugin. + * It should display a search bar and either item results or a error panel. + */ +class GrandExchangeSearchPanel extends JPanel +{ + private static final String ERROR_PANEL = "ERROR_PANEL"; + private static final String RESULTS_PANEL = "RESULTS_PANEL"; + private static final int MAX_SEARCH_ITEMS = 100; + + private final GridBagConstraints constraints = new GridBagConstraints(); + private final CardLayout cardLayout = new CardLayout(); + + private final ClientThread clientThread; + private final ItemManager itemManager; + private final ScheduledExecutorService executor; + + private final IconTextField searchBar = new IconTextField(); + + /* The results container, this will hold all the individual ge item panels */ + private final JPanel searchItemsPanel = new JPanel(); + + /* The center panel, this holds either the error panel or the results container */ + private final JPanel centerPanel = new JPanel(cardLayout); + + /* The error panel, this displays an error message */ + private final PluginErrorPanel errorPanel = new PluginErrorPanel(); + + private final List itemsList = new ArrayList<>(); + + GrandExchangeSearchPanel(ClientThread clientThread, ItemManager itemManager, ScheduledExecutorService executor) + { + this.clientThread = clientThread; + this.itemManager = itemManager; + this.executor = executor; + + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + /* The main container, this holds the search bar and the center panel */ + JPanel container = new JPanel(); + container.setLayout(new BorderLayout(5, 5)); + container.setBorder(new EmptyBorder(10, 10, 10, 10)); + container.setBackground(ColorScheme.DARK_GRAY_COLOR); + + searchBar.setIcon(IconTextField.Icon.SEARCH); + searchBar.setPreferredSize(new Dimension(100, 30)); + searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + searchBar.addActionListener(e -> executor.execute(() -> priceLookup(false))); + searchBar.addClearListener(this::updateSearch); + + searchItemsPanel.setLayout(new GridBagLayout()); + searchItemsPanel.setBackground(ColorScheme.DARK_GRAY_COLOR); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.weightx = 1; + constraints.gridx = 0; + constraints.gridy = 0; + + /* This panel wraps the results panel and guarantees the scrolling behaviour */ + JPanel wrapper = new JPanel(new BorderLayout()); + wrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + wrapper.add(searchItemsPanel, BorderLayout.NORTH); + + /* The results wrapper, this scrolling panel wraps the results container */ + JScrollPane resultsWrapper = new JScrollPane(wrapper); + resultsWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + resultsWrapper.getVerticalScrollBar().setPreferredSize(new Dimension(12, 0)); + resultsWrapper.getVerticalScrollBar().setBorder(new EmptyBorder(0, 5, 0, 0)); + resultsWrapper.setVisible(false); + + /* This panel wraps the error panel and limits its height */ + JPanel errorWrapper = new JPanel(new BorderLayout()); + errorWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + errorWrapper.add(errorPanel, BorderLayout.NORTH); + + errorPanel.setContent("Grand Exchange Search", + "Here you can search for an item by its name to find price information."); + + centerPanel.add(resultsWrapper, RESULTS_PANEL); + centerPanel.add(errorWrapper, ERROR_PANEL); + + cardLayout.show(centerPanel, ERROR_PANEL); + + container.add(searchBar, BorderLayout.NORTH); + container.add(centerPanel, BorderLayout.CENTER); + + add(container, BorderLayout.CENTER); + } + + void priceLookup(String item) + { + searchBar.setText(item); + executor.execute(() -> priceLookup(true)); + } + + private boolean updateSearch() + { + String lookup = searchBar.getText(); + + if (Strings.isNullOrEmpty(lookup)) + { + searchItemsPanel.removeAll(); + SwingUtilities.invokeLater(searchItemsPanel::updateUI); + return false; + } + + // Input is not empty, add searching label + searchItemsPanel.removeAll(); + searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + searchBar.setEditable(false); + searchBar.setIcon(IconTextField.Icon.LOADING); + return true; + } + + private void priceLookup(boolean exactMatch) + { + if (!updateSearch()) + { + return; + } + + List result = itemManager.search(searchBar.getText()); + if (result.isEmpty()) + { + searchBar.setIcon(IconTextField.Icon.ERROR); + errorPanel.setContent("No results found.", "No items were found with that name, please try again."); + cardLayout.show(centerPanel, ERROR_PANEL); + searchBar.setEditable(true); + return; + } + + // move to client thread to lookup item composition + clientThread.invokeLater(() -> processResult(result, searchBar.getText(), exactMatch)); + } + + private void processResult(List result, String lookup, boolean exactMatch) + { + itemsList.clear(); + + cardLayout.show(centerPanel, RESULTS_PANEL); + + int count = 0; + + for (ItemPrice item : result) + { + if (count++ > MAX_SEARCH_ITEMS) + { + // Cap search + break; + } + + int itemId = item.getId(); + + ItemComposition itemComp = itemManager.getItemComposition(itemId); + ItemStats itemStats = itemManager.getItemStats(itemId, false); + + int itemPrice = item.getPrice(); + int itemLimit = itemStats != null ? itemStats.getGeLimit() : 0; + AsyncBufferedImage itemImage = itemManager.getImage(itemId); + + itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit)); + + // If using hotkey to lookup item, stop after finding match. + if (exactMatch && item.getName().equalsIgnoreCase(lookup)) + { + break; + } + } + + SwingUtilities.invokeLater(() -> + { + int index = 0; + for (GrandExchangeItems item : itemsList) + { + GrandExchangeItemPanel panel = new GrandExchangeItemPanel(item.getIcon(), item.getName(), + item.getItemId(), item.getGePrice(), item.getHaPrice(), item.getGeItemLimit()); + + /* + Add the first item directly, wrap the rest with margin. This margin hack is because + gridbaglayout does not support inter-element margins. + */ + if (index++ > 0) + { + JPanel marginWrapper = new JPanel(new BorderLayout()); + marginWrapper.setBackground(ColorScheme.DARK_GRAY_COLOR); + marginWrapper.setBorder(new EmptyBorder(5, 0, 0, 0)); + marginWrapper.add(panel, BorderLayout.NORTH); + searchItemsPanel.add(marginWrapper, constraints); + } + else + { + searchItemsPanel.add(panel, constraints); + } + + constraints.gridy++; + } + + // if exactMatch was set, then it came from the applet, so don't lose focus + if (!exactMatch) + { + searchItemsPanel.requestFocusInWindow(); + } + searchBar.setEditable(true); + + // Remove searching label after search is complete + if (!itemsList.isEmpty()) + { + searchBar.setIcon(IconTextField.Icon.SEARCH); + } + }); + } + +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java new file mode 100644 index 0000000000..58a4055fed --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grandexchange; + +import lombok.Data; +import net.runelite.api.GrandExchangeOfferState; + +@Data +class SavedOffer +{ + private int itemId; + private int quantitySold; + private int totalQuantity; + private int price; + private int spent; + private GrandExchangeOfferState state; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java new file mode 100644 index 0000000000..0b926d30e0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import java.time.Instant; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Data; +import lombok.Value; +import net.runelite.api.coords.WorldPoint; + +@Data +@Builder +class GroundItem +{ + private int id; + private int itemId; + private String name; + private int quantity; + private WorldPoint location; + private int height; + private int haPrice; + private int gePrice; + private int offset; + private boolean tradeable; + @Nonnull + private LootType lootType; + @Nullable + private Instant spawnTime; + private boolean stackable; + + int getHaPrice() + { + return haPrice * quantity; + } + + int getGePrice() + { + return gePrice * quantity; + } + + boolean isMine() + { + return lootType != LootType.UNKNOWN; + } + + @Value + static class GroundItemKey + { + private int itemId; + private WorldPoint location; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java new file mode 100644 index 0000000000..da6c3a80c5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018, Seth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import java.awt.Point; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.MouseAdapter; + +public class GroundItemInputListener extends MouseAdapter implements KeyListener +{ + private static final int HOTKEY = KeyEvent.VK_ALT; + + private Instant lastPress; + + @Inject + private GroundItemsPlugin plugin; + + @Inject + private GroundItemsConfig config; + + @Override + public void keyTyped(KeyEvent e) + { + + } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() == HOTKEY) + { + if (plugin.isHideAll()) + { + plugin.setHideAll(false); + plugin.setHotKeyPressed(true); + lastPress = null; + } + else if (lastPress != null && !plugin.isHotKeyPressed() && config.doubleTapDelay() > 0 && Duration.between(lastPress, Instant.now()).compareTo(Duration.ofMillis(config.doubleTapDelay())) < 0) + { + plugin.setHideAll(true); + lastPress = null; + } + else + { + plugin.setHotKeyPressed(true); + lastPress = Instant.now(); + } + } + } + + @Override + public void keyReleased(KeyEvent e) + { + if (e.getKeyCode() == HOTKEY) + { + plugin.setHotKeyPressed(false); + plugin.setTextBoxBounds(null); + plugin.setHiddenBoxBounds(null); + plugin.setHighlightBoxBounds(null); + } + } + + @Override + public MouseEvent mousePressed(MouseEvent e) + { + final Point mousePos = e.getPoint(); + + if (plugin.isHotKeyPressed()) + { + if (SwingUtilities.isLeftMouseButton(e)) + { + // Process both click boxes for hidden and highlighted items + if (plugin.getHiddenBoxBounds() != null && plugin.getHiddenBoxBounds().getKey().contains(mousePos)) + { + plugin.updateList(plugin.getHiddenBoxBounds().getValue().getName(), true); + e.consume(); + return e; + } + + if (plugin.getHighlightBoxBounds() != null && plugin.getHighlightBoxBounds().getKey().contains(mousePos)) + { + plugin.updateList(plugin.getHighlightBoxBounds().getValue().getName(), false); + e.consume(); + return e; + } + + // There is one name click box for left click and one for right click + if (plugin.getTextBoxBounds() != null && plugin.getTextBoxBounds().getKey().contains(mousePos)) + { + plugin.updateList(plugin.getTextBoxBounds().getValue().getName(), false); + e.consume(); + return e; + } + } + else if (SwingUtilities.isRightMouseButton(e)) + { + if (plugin.getTextBoxBounds() != null && plugin.getTextBoxBounds().getKey().contains(mousePos)) + { + plugin.updateList(plugin.getTextBoxBounds().getValue().getName(), true); + e.consume(); + return e; + } + } + } + + return e; + } +} + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java new file mode 100644 index 0000000000..43ce88dd2e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2017, Aria + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.grounditems; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.plugins.grounditems.config.DespawnTimerMode; +import net.runelite.client.plugins.grounditems.config.HighlightTier; +import net.runelite.client.plugins.grounditems.config.ItemHighlightMode; +import net.runelite.client.plugins.grounditems.config.MenuHighlightMode; +import net.runelite.client.plugins.grounditems.config.PriceDisplayMode; +import net.runelite.client.plugins.grounditems.config.ValueCalculationMode; + +@ConfigGroup("grounditems") +public interface GroundItemsConfig extends Config +{ + @ConfigSection( + name = "Item Lists", + description = "The highlighted and hidden item lists", + position = 0, + closedByDefault = true + ) + String itemLists = "itemLists"; + + @ConfigItem( + keyName = "highlightedItems", + name = "Highlighted Items", + description = "Configures specifically highlighted ground items. Format: (item), (item)", + position = 0, + section = itemLists + ) + default String getHighlightItems() + { + return ""; + } + + @ConfigItem( + keyName = "highlightedItems", + name = "", + description = "" + ) + void setHighlightedItem(String key); + + @ConfigItem( + keyName = "hiddenItems", + name = "Hidden Items", + description = "Configures hidden ground items. Format: (item), (item)", + position = 1, + section = itemLists + ) + default String getHiddenItems() + { + return "Vial, Ashes, Coins, Bones, Bucket, Jug, Seaweed"; + } + + @ConfigItem( + keyName = "hiddenItems", + name = "", + description = "" + ) + void setHiddenItems(String key); + + @ConfigItem( + keyName = "showHighlightedOnly", + name = "Show Highlighted items only", + description = "Configures whether or not to draw items only on your highlighted list", + position = 2 + ) + default boolean showHighlightedOnly() + { + return false; + } + + @ConfigItem( + keyName = "dontHideUntradeables", + name = "Do not hide untradeables", + description = "Configures whether or not untradeable items ignore hiding under settings", + position = 3 + ) + default boolean dontHideUntradeables() + { + return true; + } + + @ConfigItem( + keyName = "showMenuItemQuantities", + name = "Show Menu Item Quantities", + description = "Configures whether or not to show the item quantities in the menu", + position = 4 + ) + default boolean showMenuItemQuantities() + { + return true; + } + + @ConfigItem( + keyName = "recolorMenuHiddenItems", + name = "Recolor Menu Hidden Items", + description = "Configures whether or not hidden items in right-click menu will be recolored", + position = 5 + ) + default boolean recolorMenuHiddenItems() + { + return false; + } + + @ConfigItem( + keyName = "highlightTiles", + name = "Highlight Tiles", + description = "Configures whether or not to highlight tiles containing ground items", + position = 6 + ) + default boolean highlightTiles() + { + return false; + } + + @ConfigItem( + keyName = "notifyHighlightedDrops", + name = "Notify for Highlighted drops", + description = "Configures whether or not to notify for drops on your highlighted list", + position = 7 + ) + default boolean notifyHighlightedDrops() + { + return false; + } + + @ConfigItem( + keyName = "notifyTier", + name = "Notify >= Tier", + description = "Configures which price tiers will trigger a notification on drop", + position = 8 + ) + default HighlightTier notifyTier() + { + return HighlightTier.OFF; + } + + @ConfigItem( + keyName = "priceDisplayMode", + name = "Price Display Mode", + description = "Configures which price types are shown alongside ground item name", + position = 9 + ) + default PriceDisplayMode priceDisplayMode() + { + return PriceDisplayMode.BOTH; + } + + @ConfigItem( + keyName = "itemHighlightMode", + name = "Item Highlight Mode", + description = "Configures how ground items will be highlighted", + position = 10 + ) + default ItemHighlightMode itemHighlightMode() + { + return ItemHighlightMode.BOTH; + } + + @ConfigItem( + keyName = "menuHighlightMode", + name = "Menu Highlight Mode", + description = "Configures what to highlight in right-click menu", + position = 11 + ) + default MenuHighlightMode menuHighlightMode() + { + return MenuHighlightMode.NAME; + } + + @ConfigItem( + keyName = "highlightValueCalculation", + name = "Highlight Value Calculation", + description = "Configures which coin value is used to determine highlight color", + position = 12 + ) + default ValueCalculationMode valueCalculationMode() + { + return ValueCalculationMode.HIGHEST; + } + + @ConfigItem( + keyName = "hideUnderValue", + name = "Hide < Value", + description = "Configures hidden ground items under both GE and HA value", + position = 13 + ) + default int getHideUnderValue() + { + return 0; + } + + @ConfigItem( + keyName = "defaultColor", + name = "Default items color", + description = "Configures the color for default, non-highlighted items", + position = 14 + ) + default Color defaultColor() + { + return Color.WHITE; + } + + @ConfigItem( + keyName = "highlightedColor", + name = "Highlighted items color", + description = "Configures the color for highlighted items", + position = 15 + ) + default Color highlightedColor() + { + return Color.decode("#AA00FF"); + } + + @ConfigItem( + keyName = "hiddenColor", + name = "Hidden items color", + description = "Configures the color for hidden items in right-click menu and when holding ALT", + position = 16 + ) + default Color hiddenColor() + { + return Color.GRAY; + } + + @ConfigItem( + keyName = "lowValueColor", + name = "Low value items color", + description = "Configures the color for low value items", + position = 17 + ) + default Color lowValueColor() + { + return Color.decode("#66B2FF"); + } + + @ConfigItem( + keyName = "lowValuePrice", + name = "Low value price", + description = "Configures the start price for low value items", + position = 18 + ) + default int lowValuePrice() + { + return 20000; + } + + @ConfigItem( + keyName = "mediumValueColor", + name = "Medium value items color", + description = "Configures the color for medium value items", + position = 19 + ) + default Color mediumValueColor() + { + return Color.decode("#99FF99"); + } + + @ConfigItem( + keyName = "mediumValuePrice", + name = "Medium value price", + description = "Configures the start price for medium value items", + position = 20 + ) + default int mediumValuePrice() + { + return 100000; + } + + @ConfigItem( + keyName = "highValueColor", + name = "High value items color", + description = "Configures the color for high value items", + position = 21 + ) + default Color highValueColor() + { + return Color.decode("#FF9600"); + } + + @ConfigItem( + keyName = "highValuePrice", + name = "High value price", + description = "Configures the start price for high value items", + position = 22 + ) + default int highValuePrice() + { + return 1000000; + } + + @ConfigItem( + keyName = "insaneValueColor", + name = "Insane value items color", + description = "Configures the color for insane value items", + position = 23 + ) + default Color insaneValueColor() + { + return Color.decode("#FF66B2"); + } + + @ConfigItem( + keyName = "insaneValuePrice", + name = "Insane value price", + description = "Configures the start price for insane value items", + position = 24 + ) + default int insaneValuePrice() + { + return 10000000; + } + + @ConfigItem( + keyName = "onlyShowLoot", + name = "Only show loot", + description = "Only shows drops from NPCs and players", + position = 25 + ) + default boolean onlyShowLoot() + { + return false; + } + + @ConfigItem( + keyName = "doubleTapDelay", + name = "Delay for double-tap ALT to hide", + description = "Decrease this number if you accidentally hide ground items often. (0 = Disabled)", + position = 26 + ) + @Units(Units.MILLISECONDS) + default int doubleTapDelay() + { + return 250; + } + + @ConfigItem( + keyName = "collapseEntries", + name = "Collapse ground item menu entries", + description = "Collapses ground item menu entries together and appends count", + position = 27 + ) + default boolean collapseEntries() + { + return false; + } + + @ConfigItem( + keyName = "groundItemTimers", + name = "Despawn timer", + description = "Shows despawn timers for items you've dropped and received as loot", + position = 28 + ) + default DespawnTimerMode groundItemTimers() + { + return DespawnTimerMode.OFF; + } + + @ConfigItem( + keyName = "textOutline", + name = "Text Outline", + description = "Use an outline around text instead of a text shadow", + position = 29 + ) + default boolean textOutline() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java new file mode 100644 index 0000000000..ec1d0527c2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -0,0 +1,574 @@ +/* + * Copyright (c) 2017, Aria + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Perspective; +import net.runelite.api.Player; +import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.grounditems.GroundItemsPlugin.MAX_QUANTITY; +import net.runelite.client.plugins.grounditems.config.DespawnTimerMode; +import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.MENU; +import net.runelite.client.plugins.grounditems.config.PriceDisplayMode; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.BackgroundComponent; +import net.runelite.client.ui.overlay.components.ProgressPieComponent; +import net.runelite.client.ui.overlay.components.TextComponent; +import net.runelite.client.util.QuantityFormatter; +import org.apache.commons.lang3.ArrayUtils; + +public class GroundItemsOverlay extends Overlay +{ + private static final int MAX_DISTANCE = 2500; + // We must offset the text on the z-axis such that + // it doesn't obscure the ground items below it. + private static final int OFFSET_Z = 20; + // The 15 pixel gap between each drawn ground item. + private static final int STRING_GAP = 15; + // Size of the hidden/highlight boxes + private static final int RECTANGLE_SIZE = 8; + private static final Color PUBLIC_TIMER_COLOR = Color.YELLOW; + private static final Color PRIVATE_TIMER_COLOR = Color.GREEN; + private static final int TIMER_OVERLAY_DIAMETER = 10; + private static final Duration DESPAWN_TIME_INSTANCE = Duration.ofMinutes(30); + private static final Duration DESPAWN_TIME_LOOT = Duration.ofMinutes(2); + private static final Duration DESPAWN_TIME_DROP = Duration.ofMinutes(3); + private static final int KRAKEN_REGION = 9116; + private static final int KBD_NMZ_REGION = 9033; + + private final Client client; + private final GroundItemsPlugin plugin; + private final GroundItemsConfig config; + private final StringBuilder itemStringBuilder = new StringBuilder(); + private final BackgroundComponent backgroundComponent = new BackgroundComponent(); + private final TextComponent textComponent = new TextComponent(); + private final ProgressPieComponent progressPieComponent = new ProgressPieComponent(); + private final Map offsetMap = new HashMap<>(); + + @Inject + private GroundItemsOverlay(Client client, GroundItemsPlugin plugin, GroundItemsConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.client = client; + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics) + { + final boolean dontShowOverlay = (config.itemHighlightMode() == MENU || plugin.isHideAll()) && !plugin.isHotKeyPressed(); + + if (dontShowOverlay && !config.highlightTiles()) + { + return null; + } + + final FontMetrics fm = graphics.getFontMetrics(); + final Player player = client.getLocalPlayer(); + + if (player == null || client.getViewportWidget() == null) + { + return null; + } + + offsetMap.clear(); + final LocalPoint localLocation = player.getLocalLocation(); + final Point mousePos = client.getMouseCanvasPosition(); + Collection groundItemList = plugin.getCollectedGroundItems().values(); + GroundItem topGroundItem = null; + + if (plugin.isHotKeyPressed()) + { + // Make copy of ground items because we are going to modify them here, and the array list supports our + // desired behaviour here + groundItemList = new ArrayList<>(groundItemList); + final java.awt.Point awtMousePos = new java.awt.Point(mousePos.getX(), mousePos.getY()); + GroundItem groundItem = null; + + for (GroundItem item : groundItemList) + { + item.setOffset(offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0)); + + if (groundItem != null) + { + continue; + } + + if (plugin.getTextBoxBounds() != null + && item.equals(plugin.getTextBoxBounds().getValue()) + && plugin.getTextBoxBounds().getKey().contains(awtMousePos)) + { + groundItem = item; + continue; + } + + if (plugin.getHiddenBoxBounds() != null + && item.equals(plugin.getHiddenBoxBounds().getValue()) + && plugin.getHiddenBoxBounds().getKey().contains(awtMousePos)) + { + groundItem = item; + continue; + } + + if (plugin.getHighlightBoxBounds() != null + && item.equals(plugin.getHighlightBoxBounds().getValue()) + && plugin.getHighlightBoxBounds().getKey().contains(awtMousePos)) + { + groundItem = item; + } + } + + if (groundItem != null) + { + groundItemList.remove(groundItem); + groundItemList.add(groundItem); + topGroundItem = groundItem; + } + } + + plugin.setTextBoxBounds(null); + plugin.setHiddenBoxBounds(null); + plugin.setHighlightBoxBounds(null); + + final boolean onlyShowLoot = config.onlyShowLoot(); + final DespawnTimerMode groundItemTimers = config.groundItemTimers(); + final boolean outline = config.textOutline(); + + for (GroundItem item : groundItemList) + { + final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation()); + + if (groundPoint == null || localLocation.distanceTo(groundPoint) > MAX_DISTANCE + || (onlyShowLoot && !item.isMine())) + { + continue; + } + + final Color highlighted = plugin.getHighlighted(new NamedQuantity(item), item.getGePrice(), item.getHaPrice()); + final Color hidden = plugin.getHidden(new NamedQuantity(item), item.getGePrice(), item.getHaPrice(), item.isTradeable()); + + if (highlighted == null && !plugin.isHotKeyPressed()) + { + // Do not display hidden items + if (hidden != null) + { + continue; + } + + // Do not display non-highlighted items + if (config.showHighlightedOnly()) + { + continue; + } + } + + final Color color = plugin.getItemColor(highlighted, hidden); + + if (config.highlightTiles()) + { + final Polygon poly = Perspective.getCanvasTilePoly(client, groundPoint, item.getHeight()); + + if (poly != null) + { + OverlayUtil.renderPolygon(graphics, poly, color); + } + } + + if (dontShowOverlay) + { + continue; + } + + itemStringBuilder.append(item.getName()); + + if (item.getQuantity() > 1) + { + if (item.getQuantity() >= MAX_QUANTITY) + { + itemStringBuilder.append(" (Lots!)"); + } + else + { + itemStringBuilder.append(" (") + .append(QuantityFormatter.quantityToStackSize(item.getQuantity())) + .append(")"); + } + } + + if (config.priceDisplayMode() == PriceDisplayMode.BOTH) + { + if (item.getGePrice() > 0) + { + itemStringBuilder.append(" (GE: ") + .append(QuantityFormatter.quantityToStackSize(item.getGePrice())) + .append(" gp)"); + } + + if (item.getHaPrice() > 0) + { + itemStringBuilder.append(" (HA: ") + .append(QuantityFormatter.quantityToStackSize(item.getHaPrice())) + .append(" gp)"); + } + } + else if (config.priceDisplayMode() != PriceDisplayMode.OFF) + { + final int price = config.priceDisplayMode() == PriceDisplayMode.GE + ? item.getGePrice() + : item.getHaPrice(); + + if (price > 0) + { + itemStringBuilder + .append(" (") + .append(QuantityFormatter.quantityToStackSize(price)) + .append(" gp)"); + } + } + + final String itemString = itemStringBuilder.toString(); + itemStringBuilder.setLength(0); + + final Point textPoint = Perspective.getCanvasTextLocation(client, + graphics, + groundPoint, + itemString, + item.getHeight() + OFFSET_Z); + + if (textPoint == null) + { + continue; + } + + final int offset = plugin.isHotKeyPressed() + ? item.getOffset() + : offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0); + + final int textX = textPoint.getX(); + final int textY = textPoint.getY() - (STRING_GAP * offset); + + if (plugin.isHotKeyPressed()) + { + final int stringWidth = fm.stringWidth(itemString); + final int stringHeight = fm.getHeight(); + + // Item bounds + int x = textX - 2; + int y = textY - stringHeight - 2; + int width = stringWidth + 4; + int height = stringHeight + 4; + final Rectangle itemBounds = new Rectangle(x, y, width, height); + + // Hidden box + x += width + 2; + y = textY - (RECTANGLE_SIZE + stringHeight) / 2; + width = height = RECTANGLE_SIZE; + final Rectangle itemHiddenBox = new Rectangle(x, y, width, height); + + // Highlight box + x += width + 2; + final Rectangle itemHighlightBox = new Rectangle(x, y, width, height); + + boolean mouseInBox = itemBounds.contains(mousePos.getX(), mousePos.getY()); + boolean mouseInHiddenBox = itemHiddenBox.contains(mousePos.getX(), mousePos.getY()); + boolean mouseInHighlightBox = itemHighlightBox.contains(mousePos.getX(), mousePos.getY()); + + if (mouseInBox) + { + plugin.setTextBoxBounds(new SimpleEntry<>(itemBounds, item)); + } + else if (mouseInHiddenBox) + { + plugin.setHiddenBoxBounds(new SimpleEntry<>(itemHiddenBox, item)); + + } + else if (mouseInHighlightBox) + { + plugin.setHighlightBoxBounds(new SimpleEntry<>(itemHighlightBox, item)); + } + + boolean topItem = topGroundItem == item; + + // Draw background if hovering + if (topItem && (mouseInBox || mouseInHiddenBox || mouseInHighlightBox)) + { + backgroundComponent.setRectangle(itemBounds); + backgroundComponent.render(graphics); + } + + // Draw hidden box + drawRectangle(graphics, itemHiddenBox, topItem && mouseInHiddenBox ? Color.RED : color, hidden != null, true); + + // Draw highlight box + drawRectangle(graphics, itemHighlightBox, topItem && mouseInHighlightBox ? Color.GREEN : color, highlighted != null, false); + } + + // When the hotkey is pressed the hidden/highlight boxes are drawn to the right of the text, + // so always draw the pie since it is on the left hand side. + if (groundItemTimers == DespawnTimerMode.PIE || plugin.isHotKeyPressed()) + { + drawTimerPieOverlay(graphics, textX, textY, item); + } + else if (groundItemTimers == DespawnTimerMode.SECONDS || groundItemTimers == DespawnTimerMode.TICKS) + { + Instant despawnTime = calculateDespawnTime(item); + Color timerColor = getItemTimerColor(item); + if (despawnTime != null && timerColor != null) + { + long despawnTimeMillis = despawnTime.toEpochMilli() - Instant.now().toEpochMilli(); + final String timerText; + if (groundItemTimers == DespawnTimerMode.SECONDS) + { + timerText = String.format(" - %.1f", despawnTimeMillis / 1000f); + } + else // TICKS + { + timerText = String.format(" - %d", despawnTimeMillis / 600); + } + + // The timer text is drawn separately to have its own color, and is intentionally not included + // in the getCanvasTextLocation() call because the timer text can change per frame and we do not + // use a monospaced font, which causes the text location on screen to jump around slightly each frame. + textComponent.setText(timerText); + textComponent.setColor(timerColor); + textComponent.setOutline(outline); + textComponent.setPosition(new java.awt.Point(textX + fm.stringWidth(itemString), textY)); + textComponent.render(graphics); + } + } + + textComponent.setText(itemString); + textComponent.setColor(color); + textComponent.setOutline(outline); + textComponent.setPosition(new java.awt.Point(textX, textY)); + textComponent.render(graphics); + } + + return null; + } + + private Instant calculateDespawnTime(GroundItem groundItem) + { + // We can only accurately guess despawn times for our own pvm loot and dropped items + if (groundItem.getLootType() != LootType.PVM && groundItem.getLootType() != LootType.DROPPED) + { + return null; + } + + // Loot appears to others after 1 minute, and despawns after 2 minutes + // Dropped items appear to others after 1 minute, and despawns after 3 minutes + // Items in instances never appear to anyone and despawn after 30 minutes + + Instant spawnTime = groundItem.getSpawnTime(); + if (spawnTime == null) + { + return null; + } + + Instant despawnTime; + Instant now = Instant.now(); + if (client.isInInstancedRegion()) + { + // Items in the Kraken instance appear to never despawn? + if (isInKraken()) + { + return null; + } + else if (isInKBDorNMZ()) + { + // NMZ and the KBD lair uses the same region ID but NMZ uses planes 1-3 and KBD uses plane 0 + if (client.getLocalPlayer().getWorldLocation().getPlane() == 0) + { + // Items in the KBD instance use the standard despawn timer + if (groundItem.getLootType() == LootType.DROPPED) + { + despawnTime = spawnTime.plus(DESPAWN_TIME_DROP); + } + else + { + despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT); + } + } + else + { + // Dropped items in the NMZ instance appear to never despawn? + if (groundItem.getLootType() == LootType.DROPPED) + { + return null; + } + else + { + despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT); + } + } + } + else + { + despawnTime = spawnTime.plus(DESPAWN_TIME_INSTANCE); + } + } + else + { + if (groundItem.getLootType() == LootType.DROPPED) + { + despawnTime = spawnTime.plus(DESPAWN_TIME_DROP); + } + else + { + despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT); + } + } + + if (now.isBefore(spawnTime) || now.isAfter(despawnTime)) + { + // that's weird + return null; + } + + return despawnTime; + } + + private Color getItemTimerColor(GroundItem groundItem) + { + // We can only accurately guess despawn times for our own pvm loot and dropped items + if (groundItem.getLootType() != LootType.PVM && groundItem.getLootType() != LootType.DROPPED) + { + return null; + } + + final Instant spawnTime = groundItem.getSpawnTime(); + if (spawnTime == null) + { + return null; + } + + final Instant now = Instant.now(); + + // If it has not yet been a minute, the item is private + if (client.isInInstancedRegion() || spawnTime.plus(1, ChronoUnit.MINUTES).isAfter(now)) + { + return PRIVATE_TIMER_COLOR; + } + else + { + return PUBLIC_TIMER_COLOR; + } + } + + private void drawTimerPieOverlay(Graphics2D graphics, int textX, int textY, GroundItem groundItem) + { + Instant now = Instant.now(); + Instant spawnTime = groundItem.getSpawnTime(); + Instant despawnTime = calculateDespawnTime(groundItem); + Color fillColor = getItemTimerColor(groundItem); + + if (spawnTime == null || despawnTime == null || fillColor == null) + { + return; + } + + float percent = (float) (now.toEpochMilli() - spawnTime.toEpochMilli()) / (despawnTime.toEpochMilli() - spawnTime.toEpochMilli()); + + progressPieComponent.setDiameter(TIMER_OVERLAY_DIAMETER); + // Shift over to not be on top of the text + int x = textX - TIMER_OVERLAY_DIAMETER; + int y = textY - TIMER_OVERLAY_DIAMETER / 2; + progressPieComponent.setPosition(new Point(x, y)); + progressPieComponent.setFill(fillColor); + progressPieComponent.setBorderColor(fillColor); + progressPieComponent.setProgress(1 - percent); // inverse so pie drains over time + progressPieComponent.render(graphics); + } + + private void drawRectangle(Graphics2D graphics, Rectangle rect, Color color, boolean inList, boolean hiddenBox) + { + graphics.setColor(Color.BLACK); + graphics.drawRect(rect.x + 1, rect.y + 1, rect.width, rect.height); + + graphics.setColor(color); + graphics.draw(rect); + + if (inList) + { + graphics.fill(rect); + } + + graphics.setColor(Color.WHITE); + // Minus symbol + graphics.drawLine + ( + rect.x + 2, + rect.y + (rect.height / 2), + rect.x + rect.width - 2, + rect.y + (rect.height / 2) + ); + + if (!hiddenBox) + { + // Plus symbol + graphics.drawLine + ( + rect.x + (rect.width / 2), + rect.y + 2, + rect.x + (rect.width / 2), + rect.y + rect.height - 2 + ); + } + + } + + private boolean isInKraken() + { + return ArrayUtils.contains(client.getMapRegions(), KRAKEN_REGION); + } + + private boolean isInKBDorNMZ() + { + return ArrayUtils.contains(client.getMapRegions(), KBD_NMZ_REGION); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java new file mode 100644 index 0000000000..4bedc5c28e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2017, Aria + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.EvictingQueue; +import com.google.common.collect.ImmutableList; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.Rectangle; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.Value; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Player; +import net.runelite.api.Tile; +import net.runelite.api.TileItem; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ClientTick; +import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.ItemDespawned; +import net.runelite.api.events.ItemQuantityChanged; +import net.runelite.api.events.ItemSpawned; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.Notifier; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.NpcLootReceived; +import net.runelite.client.events.PlayerLootReceived; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemStack; +import net.runelite.client.input.KeyManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.grounditems.config.HighlightTier; +import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.OVERLAY; +import net.runelite.client.plugins.grounditems.config.MenuHighlightMode; +import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.BOTH; +import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.NAME; +import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.OPTION; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Ground Items", + description = "Highlight ground items and/or show price information", + tags = {"grand", "exchange", "high", "alchemy", "prices", "highlight", "overlay"} +) +public class GroundItemsPlugin extends Plugin +{ + @Value + static class PriceHighlight + { + private final int price; + private final Color color; + } + + // The game won't send anything higher than this value to the plugin - + // so we replace any item quantity higher with "Lots" instead. + static final int MAX_QUANTITY = 65535; + // ItemID for coins + private static final int COINS = ItemID.COINS_995; + // Ground item menu options + private static final int FIRST_OPTION = MenuAction.GROUND_ITEM_FIRST_OPTION.getId(); + private static final int SECOND_OPTION = MenuAction.GROUND_ITEM_SECOND_OPTION.getId(); + private static final int THIRD_OPTION = MenuAction.GROUND_ITEM_THIRD_OPTION.getId(); // this is Take + private static final int FOURTH_OPTION = MenuAction.GROUND_ITEM_FOURTH_OPTION.getId(); + private static final int FIFTH_OPTION = MenuAction.GROUND_ITEM_FIFTH_OPTION.getId(); + private static final int EXAMINE_ITEM = MenuAction.EXAMINE_ITEM_GROUND.getId(); + private static final int CAST_ON_ITEM = MenuAction.SPELL_CAST_ON_GROUND_ITEM.getId(); + + private static final String TELEGRAB_TEXT = ColorUtil.wrapWithColorTag("Telekinetic Grab", Color.GREEN) + ColorUtil.prependColorTag(" -> ", Color.WHITE); + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private Map.Entry textBoxBounds; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private Map.Entry hiddenBoxBounds; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private Map.Entry highlightBoxBounds; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private boolean hotKeyPressed; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private boolean hideAll; + + private List hiddenItemList = new CopyOnWriteArrayList<>(); + private List highlightedItemsList = new CopyOnWriteArrayList<>(); + + @Inject + private GroundItemInputListener inputListener; + + @Inject + private MouseManager mouseManager; + + @Inject + private KeyManager keyManager; + + @Inject + private Client client; + + @Inject + private ItemManager itemManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private GroundItemsConfig config; + + @Inject + private GroundItemsOverlay overlay; + + @Inject + private Notifier notifier; + + @Inject + private ScheduledExecutorService executor; + + @Getter + private final Map collectedGroundItems = new LinkedHashMap<>(); + private List priceChecks = ImmutableList.of(); + private LoadingCache highlightedItems; + private LoadingCache hiddenItems; + private final Queue droppedItemQueue = EvictingQueue.create(16); // recently dropped items + + @Provides + GroundItemsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(GroundItemsConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + mouseManager.registerMouseListener(inputListener); + keyManager.registerKeyListener(inputListener); + executor.execute(this::reset); + } + + @Override + protected void shutDown() + { + overlayManager.remove(overlay); + mouseManager.unregisterMouseListener(inputListener); + keyManager.unregisterKeyListener(inputListener); + highlightedItems.invalidateAll(); + highlightedItems = null; + hiddenItems.invalidateAll(); + hiddenItems = null; + hiddenItemList = null; + highlightedItemsList = null; + collectedGroundItems.clear(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("grounditems")) + { + executor.execute(this::reset); + } + } + + @Subscribe + public void onGameStateChanged(final GameStateChanged event) + { + if (event.getGameState() == GameState.LOADING) + { + collectedGroundItems.clear(); + } + } + + @Subscribe + public void onItemSpawned(ItemSpawned itemSpawned) + { + TileItem item = itemSpawned.getItem(); + Tile tile = itemSpawned.getTile(); + + GroundItem groundItem = buildGroundItem(tile, item); + + GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation()); + GroundItem existing = collectedGroundItems.putIfAbsent(groundItemKey, groundItem); + if (existing != null) + { + existing.setQuantity(existing.getQuantity() + groundItem.getQuantity()); + // The spawn time remains set at the oldest spawn + } + + if (!config.onlyShowLoot()) + { + notifyHighlightedItem(groundItem); + } + } + + @Subscribe + public void onItemDespawned(ItemDespawned itemDespawned) + { + TileItem item = itemDespawned.getItem(); + Tile tile = itemDespawned.getTile(); + + GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation()); + GroundItem groundItem = collectedGroundItems.get(groundItemKey); + if (groundItem == null) + { + return; + } + + if (groundItem.getQuantity() <= item.getQuantity()) + { + collectedGroundItems.remove(groundItemKey); + } + else + { + groundItem.setQuantity(groundItem.getQuantity() - item.getQuantity()); + // When picking up an item when multiple stacks appear on the ground, + // it is not known which item is picked up, so we invalidate the spawn + // time + groundItem.setSpawnTime(null); + } + } + + @Subscribe + public void onItemQuantityChanged(ItemQuantityChanged itemQuantityChanged) + { + TileItem item = itemQuantityChanged.getItem(); + Tile tile = itemQuantityChanged.getTile(); + int oldQuantity = itemQuantityChanged.getOldQuantity(); + int newQuantity = itemQuantityChanged.getNewQuantity(); + + int diff = newQuantity - oldQuantity; + GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation()); + GroundItem groundItem = collectedGroundItems.get(groundItemKey); + if (groundItem != null) + { + groundItem.setQuantity(groundItem.getQuantity() + diff); + } + } + + @Subscribe + public void onNpcLootReceived(NpcLootReceived npcLootReceived) + { + Collection items = npcLootReceived.getItems(); + lootReceived(items, LootType.PVM); + } + + @Subscribe + public void onPlayerLootReceived(PlayerLootReceived playerLootReceived) + { + Collection items = playerLootReceived.getItems(); + lootReceived(items, LootType.PVP); + } + + @Subscribe + public void onClientTick(ClientTick event) + { + if (!config.collapseEntries()) + { + return; + } + + final MenuEntry[] menuEntries = client.getMenuEntries(); + final List newEntries = new ArrayList<>(menuEntries.length); + + outer: + for (int i = menuEntries.length - 1; i >= 0; i--) + { + MenuEntry menuEntry = menuEntries[i]; + + int menuType = menuEntry.getType(); + if (menuType == FIRST_OPTION || menuType == SECOND_OPTION || menuType == THIRD_OPTION + || menuType == FOURTH_OPTION || menuType == FIFTH_OPTION || menuType == EXAMINE_ITEM) + { + for (MenuEntryWithCount entryWCount : newEntries) + { + if (entryWCount.getEntry().equals(menuEntry)) + { + entryWCount.increment(); + continue outer; + } + } + } + + newEntries.add(new MenuEntryWithCount(menuEntry)); + } + + Collections.reverse(newEntries); + + client.setMenuEntries(newEntries.stream().map(e -> + { + final MenuEntry entry = e.getEntry(); + final int count = e.getCount(); + if (count > 1) + { + entry.setTarget(entry.getTarget() + " x " + count); + } + + return entry; + }).toArray(MenuEntry[]::new)); + } + + private void lootReceived(Collection items, LootType lootType) + { + for (ItemStack itemStack : items) + { + WorldPoint location = WorldPoint.fromLocal(client, itemStack.getLocation()); + GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(itemStack.getId(), location); + GroundItem groundItem = collectedGroundItems.get(groundItemKey); + if (groundItem != null) + { + groundItem.setLootType(lootType); + + if (config.onlyShowLoot()) + { + notifyHighlightedItem(groundItem); + } + } + } + } + + private GroundItem buildGroundItem(final Tile tile, final TileItem item) + { + // Collect the data for the item + final int itemId = item.getId(); + final ItemComposition itemComposition = itemManager.getItemComposition(itemId); + final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemId; + final int alchPrice = itemComposition.getHaPrice(); + final boolean dropped = tile.getWorldLocation().equals(client.getLocalPlayer().getWorldLocation()) && droppedItemQueue.remove(itemId); + + final GroundItem groundItem = GroundItem.builder() + .id(itemId) + .location(tile.getWorldLocation()) + .itemId(realItemId) + .quantity(item.getQuantity()) + .name(itemComposition.getName()) + .haPrice(alchPrice) + .height(tile.getItemLayer().getHeight()) + .tradeable(itemComposition.isTradeable()) + .lootType(dropped ? LootType.DROPPED : LootType.UNKNOWN) + .spawnTime(Instant.now()) + .stackable(itemComposition.isStackable()) + .build(); + + + // Update item price in case it is coins + if (realItemId == COINS) + { + groundItem.setHaPrice(1); + groundItem.setGePrice(1); + } + else + { + groundItem.setGePrice(itemManager.getItemPrice(realItemId)); + } + + return groundItem; + } + + private void reset() + { + // gets the hidden items from the text box in the config + hiddenItemList = Text.fromCSV(config.getHiddenItems()); + + // gets the highlighted items from the text box in the config + highlightedItemsList = Text.fromCSV(config.getHighlightItems()); + + highlightedItems = CacheBuilder.newBuilder() + .maximumSize(512L) + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(new WildcardMatchLoader(highlightedItemsList)); + + hiddenItems = CacheBuilder.newBuilder() + .maximumSize(512L) + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(new WildcardMatchLoader(hiddenItemList)); + + // Cache colors + ImmutableList.Builder priceCheckBuilder = ImmutableList.builder(); + + if (config.insaneValuePrice() > 0) + { + priceCheckBuilder.add(new PriceHighlight(config.insaneValuePrice(), config.insaneValueColor())); + } + + if (config.highValuePrice() > 0) + { + priceCheckBuilder.add(new PriceHighlight(config.highValuePrice(), config.highValueColor())); + } + + if (config.mediumValuePrice() > 0) + { + priceCheckBuilder.add(new PriceHighlight(config.mediumValuePrice(), config.mediumValueColor())); + } + + if (config.lowValuePrice() > 0) + { + priceCheckBuilder.add(new PriceHighlight(config.lowValuePrice(), config.lowValueColor())); + } + + priceChecks = priceCheckBuilder.build(); + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + if (config.itemHighlightMode() != OVERLAY) + { + final boolean telegrabEntry = event.getOption().equals("Cast") && event.getTarget().startsWith(TELEGRAB_TEXT) && event.getType() == CAST_ON_ITEM; + if (!(event.getOption().equals("Take") && event.getType() == THIRD_OPTION) && !telegrabEntry) + { + return; + } + + final int itemId = event.getIdentifier(); + final int sceneX = event.getActionParam0(); + final int sceneY = event.getActionParam1(); + + MenuEntry[] menuEntries = client.getMenuEntries(); + MenuEntry lastEntry = menuEntries[menuEntries.length - 1]; + + final WorldPoint worldPoint = WorldPoint.fromScene(client, sceneX, sceneY, client.getPlane()); + GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(itemId, worldPoint); + GroundItem groundItem = collectedGroundItems.get(groundItemKey); + int quantity = groundItem.getQuantity(); + + final int gePrice = groundItem.getGePrice(); + final int haPrice = groundItem.getHaPrice(); + final Color hidden = getHidden(new NamedQuantity(groundItem.getName(), quantity), gePrice, haPrice, groundItem.isTradeable()); + final Color highlighted = getHighlighted(new NamedQuantity(groundItem.getName(), quantity), gePrice, haPrice); + final Color color = getItemColor(highlighted, hidden); + final boolean canBeRecolored = highlighted != null || (hidden != null && config.recolorMenuHiddenItems()); + + if (color != null && canBeRecolored && !color.equals(config.defaultColor())) + { + final MenuHighlightMode mode = config.menuHighlightMode(); + + if (mode == BOTH || mode == OPTION) + { + final String optionText = telegrabEntry ? "Cast" : "Take"; + lastEntry.setOption(ColorUtil.prependColorTag(optionText, color)); + } + + if (mode == BOTH || mode == NAME) + { + String target = lastEntry.getTarget(); + + if (telegrabEntry) + { + target = target.substring(TELEGRAB_TEXT.length()); + } + + target = ColorUtil.prependColorTag(target.substring(target.indexOf('>') + 1), color); + + if (telegrabEntry) + { + target = TELEGRAB_TEXT + target; + } + + lastEntry.setTarget(target); + } + } + + if (config.showMenuItemQuantities() && groundItem.isStackable() && quantity > 1) + { + lastEntry.setTarget(lastEntry.getTarget() + " (" + quantity + ")"); + } + + client.setMenuEntries(menuEntries); + } + } + + void updateList(String item, boolean hiddenList) + { + final List hiddenItemSet = new ArrayList<>(hiddenItemList); + final List highlightedItemSet = new ArrayList<>(highlightedItemsList); + + if (hiddenList) + { + highlightedItemSet.removeIf(item::equalsIgnoreCase); + } + else + { + hiddenItemSet.removeIf(item::equalsIgnoreCase); + } + + final List items = hiddenList ? hiddenItemSet : highlightedItemSet; + + if (!items.removeIf(item::equalsIgnoreCase)) + { + items.add(item); + } + + config.setHiddenItems(Text.toCSV(hiddenItemSet)); + config.setHighlightedItem(Text.toCSV(highlightedItemSet)); + } + + Color getHighlighted(NamedQuantity item, int gePrice, int haPrice) + { + if (TRUE.equals(highlightedItems.getUnchecked(item))) + { + return config.highlightedColor(); + } + + // Explicit hide takes priority over implicit highlight + if (TRUE.equals(hiddenItems.getUnchecked(item))) + { + return null; + } + + final int price = getValueByMode(gePrice, haPrice); + for (PriceHighlight highlight : priceChecks) + { + if (price > highlight.getPrice()) + { + return highlight.getColor(); + } + } + + return null; + } + + Color getHidden(NamedQuantity item, int gePrice, int haPrice, boolean isTradeable) + { + final boolean isExplicitHidden = TRUE.equals(hiddenItems.getUnchecked(item)); + final boolean isExplicitHighlight = TRUE.equals(highlightedItems.getUnchecked(item)); + final boolean canBeHidden = gePrice > 0 || isTradeable || !config.dontHideUntradeables(); + final boolean underGe = gePrice < config.getHideUnderValue(); + final boolean underHa = haPrice < config.getHideUnderValue(); + + // Explicit highlight takes priority over implicit hide + return isExplicitHidden || (!isExplicitHighlight && canBeHidden && underGe && underHa) + ? config.hiddenColor() + : null; + } + + Color getItemColor(Color highlighted, Color hidden) + { + if (highlighted != null) + { + return highlighted; + } + + if (hidden != null) + { + return hidden; + } + + return config.defaultColor(); + } + + @Subscribe + public void onFocusChanged(FocusChanged focusChanged) + { + if (!focusChanged.isFocused()) + { + setHotKeyPressed(false); + } + } + + private void notifyHighlightedItem(GroundItem item) + { + final boolean shouldNotifyHighlighted = config.notifyHighlightedDrops() && + TRUE.equals(highlightedItems.getUnchecked(new NamedQuantity(item))); + + final boolean shouldNotifyTier = config.notifyTier() != HighlightTier.OFF && + getValueByMode(item.getGePrice(), item.getHaPrice()) > config.notifyTier().getValueFromTier(config) && + FALSE.equals(hiddenItems.getUnchecked(new NamedQuantity(item))); + + final String dropType; + if (shouldNotifyHighlighted) + { + dropType = "highlighted"; + } + else if (shouldNotifyTier) + { + dropType = "valuable"; + } + else + { + return; + } + + final Player local = client.getLocalPlayer(); + final StringBuilder notificationStringBuilder = new StringBuilder() + .append('[') + .append(local.getName()) + .append("] received a ") + .append(dropType) + .append(" drop: ") + .append(item.getName()); + + if (item.getQuantity() > 1) + { + if (item.getQuantity() >= MAX_QUANTITY) + { + notificationStringBuilder.append(" (Lots!)"); + } + else + { + notificationStringBuilder.append(" (") + .append(QuantityFormatter.quantityToStackSize(item.getQuantity())) + .append(")"); + } + } + + notifier.notify(notificationStringBuilder.toString()); + } + + private int getValueByMode(int gePrice, int haPrice) + { + switch (config.valueCalculationMode()) + { + case GE: + return gePrice; + case HA: + return haPrice; + default: // Highest + return Math.max(gePrice, haPrice); + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) + { + if (menuOptionClicked.getMenuAction() == MenuAction.ITEM_DROP) + { + int itemId = menuOptionClicked.getId(); + // Keep a queue of recently dropped items to better detect + // item spawns that are drops + droppedItemQueue.add(itemId); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java new file mode 100644 index 0000000000..d19473d47d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import com.google.common.base.Strings; +import lombok.Value; + +@Value +class ItemThreshold +{ + enum Inequality + { + LESS_THAN, + MORE_THAN + } + + private final String itemName; + private final int quantity; + private final Inequality inequality; + + static ItemThreshold fromConfigEntry(String entry) + { + if (Strings.isNullOrEmpty(entry)) + { + return null; + } + + Inequality operator = Inequality.MORE_THAN; + int qty = 0; + + for (int i = entry.length() - 1; i >= 0; i--) + { + char c = entry.charAt(i); + if (c >= '0' && c <= '9' || Character.isWhitespace(c)) + { + continue; + } + switch (c) + { + case '<': + operator = Inequality.LESS_THAN; + // fallthrough + case '>': + if (i + 1 < entry.length()) + { + try + { + qty = Integer.parseInt(entry.substring(i + 1).trim()); + } + catch (NumberFormatException e) + { + qty = 0; + operator = Inequality.MORE_THAN; + } + entry = entry.substring(0, i); + } + } + break; + } + + return new ItemThreshold(entry.trim(), qty, operator); + } + + boolean quantityHolds(int itemCount) + { + if (inequality == Inequality.LESS_THAN) + { + return itemCount < quantity; + } + else + { + return itemCount > quantity; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java new file mode 100644 index 0000000000..b434298faf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +enum LootType +{ + UNKNOWN, + DROPPED, + PVP, + PVM; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/MenuEntryWithCount.java similarity index 75% rename from runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java rename to runelite-client/src/main/java/net/runelite/client/plugins/grounditems/MenuEntryWithCount.java index 20f9d0dfb6..a8539921c0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/MenuEntryWithCount.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2018, Tomas Slusny - * Copyright (c) 2018, Ron Young * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,25 +22,24 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.client.plugins.banktags.tabs; +package net.runelite.client.plugins.grounditems; import lombok.Getter; import lombok.RequiredArgsConstructor; -import net.runelite.client.game.SpriteOverride; +import net.runelite.api.MenuEntry; @RequiredArgsConstructor -public enum TabSprites implements SpriteOverride +class MenuEntryWithCount { - INCINERATOR(-200, "incinerator.png"), - TAB_BACKGROUND(-201, "tag-tab.png"), - TAB_BACKGROUND_ACTIVE(-202, "tag-tab-active.png"), - UP_ARROW(-203, "up-arrow.png"), - DOWN_ARROW(-204, "down-arrow.png"), - NEW_TAB(-205, "new-tab.png"); + @Getter + private final MenuEntry entry; @Getter - private final int spriteId; + private int count = 1; - @Getter - private final String fileName; + void increment() + { + count++; + } } + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/NamedQuantity.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/NamedQuantity.java new file mode 100644 index 0000000000..1d834089fc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/NamedQuantity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import lombok.RequiredArgsConstructor; +import lombok.Value; + +@Value +@RequiredArgsConstructor +class NamedQuantity +{ + private final String name; + private final int quantity; + + NamedQuantity(GroundItem groundItem) + { + this(groundItem.getName(), groundItem.getQuantity()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java new file mode 100644 index 0000000000..4a693a9ac5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheLoader; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import net.runelite.client.util.WildcardMatcher; + +class WildcardMatchLoader extends CacheLoader +{ + private final List itemThresholds; + + WildcardMatchLoader(List configEntries) + { + this.itemThresholds = configEntries.stream() + .map(ItemThreshold::fromConfigEntry) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public Boolean load(@Nonnull final NamedQuantity key) + { + if (Strings.isNullOrEmpty(key.getName())) + { + return false; + } + + final String filteredName = key.getName().trim(); + + for (final ItemThreshold entry : itemThresholds) + { + if (WildcardMatcher.matches(entry.getItemName(), filteredName) + && entry.quantityHolds(key.getQuantity())) + { + return true; + } + } + + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/DespawnTimerMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/DespawnTimerMode.java new file mode 100644 index 0000000000..858bc48754 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/DespawnTimerMode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems.config; + +public enum DespawnTimerMode +{ + OFF, + PIE, + TICKS, + SECONDS +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/HighlightTier.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/HighlightTier.java new file mode 100644 index 0000000000..1764acf42c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/HighlightTier.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems.config; + +import net.runelite.client.plugins.grounditems.GroundItemsConfig; + +public enum HighlightTier +{ + OFF, + LOW, + MEDIUM, + HIGH, + INSANE; + + public int getValueFromTier(GroundItemsConfig config) + { + switch (this) + { + case LOW: + return config.lowValuePrice(); + case MEDIUM: + return config.mediumValuePrice(); + case HIGH: + return config.highValuePrice(); + case INSANE: + return config.insaneValuePrice(); + default: + throw new UnsupportedOperationException(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java new file mode 100644 index 0000000000..9b873a639e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ItemHighlightMode +{ + OVERLAY("Overlay"), + MENU("Right-click menu"), + BOTH("Both"); + + private final String name; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/MenuHighlightMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/MenuHighlightMode.java new file mode 100644 index 0000000000..705f322cc0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/MenuHighlightMode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum MenuHighlightMode +{ + OPTION("Menu option"), + NAME("Menu item name"), + BOTH("Both"); + + private final String name; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/PriceDisplayMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/PriceDisplayMode.java new file mode 100644 index 0000000000..d98fecddeb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/PriceDisplayMode.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PriceDisplayMode +{ + HA("High Alchemy"), + GE("Grand Exchange"), + BOTH("Both"), + OFF("Off"); + + private final String name; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ValueCalculationMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ValueCalculationMode.java new file mode 100644 index 0000000000..197e281f46 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ValueCalculationMode.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019, Abel Briggs + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.client.plugins.grounditems.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ValueCalculationMode +{ + HA("High Alchemy"), // calc highlight by HA value + GE("Grand Exchange"), // calc by GE + HIGHEST("Highest"); // use whatever is highest. + + private final String name; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/ColorTileMarker.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/ColorTileMarker.java new file mode 100644 index 0000000000..1b5891d138 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/ColorTileMarker.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019, Jordan Atwood + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.groundmarkers; + +import java.awt.Color; +import javax.annotation.Nullable; +import lombok.Value; +import net.runelite.api.coords.WorldPoint; + +/** + * Used to denote marked tiles and their colors. + * Note: This is not used for serialization of ground markers; see {@link GroundMarkerPoint} + */ +@Value +class ColorTileMarker +{ + private WorldPoint worldPoint; + @Nullable + private Color color; + @Nullable + private String label; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerConfig.java new file mode 100644 index 0000000000..bc2a65f0d9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018, TheLonelyDev + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.groundmarkers; + +import java.awt.Color; +import net.runelite.client.config.Alpha; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("groundMarker") +public interface GroundMarkerConfig extends Config +{ + @Alpha + @ConfigItem( + keyName = "markerColor", + name = "Color of the tile", + description = "Configures the color of marked tile" + ) + default Color markerColor() + { + return Color.YELLOW; + } + + @ConfigItem( + keyName = "rememberTileColors", + name = "Remember color per tile", + description = "Color tiles using the color from time of placement" + ) + default boolean rememberTileColors() + { + return false; + } + + @ConfigItem( + keyName = "drawOnMinimap", + name = "Draw tiles on minimap", + description = "Configures whether marked tiles should be drawn on minimap" + ) + default boolean drawTileOnMinimmap() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java new file mode 100644 index 0000000000..04a762242e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019, Benjamin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.groundmarkers; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.Collection; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Perspective; +import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.OverlayUtil; + +class GroundMarkerMinimapOverlay extends Overlay +{ + private static final int MAX_DRAW_DISTANCE = 16; + private static final int TILE_WIDTH = 4; + private static final int TILE_HEIGHT = 4; + + private final Client client; + private final GroundMarkerConfig config; + private final GroundMarkerPlugin plugin; + + @Inject + private GroundMarkerMinimapOverlay(Client client, GroundMarkerConfig config, GroundMarkerPlugin plugin) + { + this.client = client; + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setPriority(OverlayPriority.LOW); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.drawTileOnMinimmap()) + { + return null; + } + + final Collection points = plugin.getPoints(); + for (final ColorTileMarker point : points) + { + WorldPoint worldPoint = point.getWorldPoint(); + if (worldPoint.getPlane() != client.getPlane()) + { + continue; + } + + Color tileColor = point.getColor(); + if (tileColor == null || !config.rememberTileColors()) + { + // If this is an old tile which has no color, or rememberTileColors is off, use marker color + tileColor = config.markerColor(); + } + + drawOnMinimap(graphics, worldPoint, tileColor); + } + + return null; + } + + private void drawOnMinimap(Graphics2D graphics, WorldPoint point, Color color) + { + WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation(); + + if (point.distanceTo(playerLocation) >= MAX_DRAW_DISTANCE) + { + return; + } + + LocalPoint lp = LocalPoint.fromWorld(client, point); + if (lp == null) + { + return; + } + + Point posOnMinimap = Perspective.localToMinimap(client, lp); + if (posOnMinimap == null) + { + return; + } + + OverlayUtil.renderMinimapRect(client, graphics, posOnMinimap, TILE_WIDTH, TILE_HEIGHT, color); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java new file mode 100644 index 0000000000..31d3e1bf08 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018, TheLonelyDev + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.groundmarkers; + +import com.google.common.base.Strings; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.util.Collection; +import javax.annotation.Nullable; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Perspective; +import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.OverlayUtil; + +public class GroundMarkerOverlay extends Overlay +{ + private static final int MAX_DRAW_DISTANCE = 32; + + private final Client client; + private final GroundMarkerConfig config; + private final GroundMarkerPlugin plugin; + + @Inject + private GroundMarkerOverlay(Client client, GroundMarkerConfig config, GroundMarkerPlugin plugin) + { + this.client = client; + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setPriority(OverlayPriority.LOW); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics) + { + final Collection points = plugin.getPoints(); + for (final ColorTileMarker point : points) + { + WorldPoint worldPoint = point.getWorldPoint(); + if (worldPoint.getPlane() != client.getPlane()) + { + continue; + } + + Color tileColor = point.getColor(); + if (tileColor == null || !config.rememberTileColors()) + { + // If this is an old tile which has no color, or rememberTileColors is off, use marker color + tileColor = config.markerColor(); + } + + drawTile(graphics, worldPoint, tileColor, point.getLabel()); + } + + return null; + } + + private void drawTile(Graphics2D graphics, WorldPoint point, Color color, @Nullable String label) + { + WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation(); + + if (point.distanceTo(playerLocation) >= MAX_DRAW_DISTANCE) + { + return; + } + + LocalPoint lp = LocalPoint.fromWorld(client, point); + if (lp == null) + { + return; + } + + Polygon poly = Perspective.getCanvasTilePoly(client, lp); + if (poly != null) + { + OverlayUtil.renderPolygon(graphics, poly, color); + } + + if (!Strings.isNullOrEmpty(label)) + { + Point canvasTextLocation = Perspective.getCanvasTextLocation(client, graphics, lp, label, 0); + if (canvasTextLocation != null) + { + OverlayUtil.renderTextLocation(graphics, canvasTextLocation, label, color); + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java new file mode 100644 index 0000000000..73e93f86ad --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2018, TheLonelyDev + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.groundmarkers; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Provides; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.KeyCode; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@Slf4j +@PluginDescriptor( + name = "Ground Markers", + description = "Enable marking of tiles using the Shift key", + tags = {"overlay", "tiles"} +) +public class GroundMarkerPlugin extends Plugin +{ + private static final String CONFIG_GROUP = "groundMarker"; + private static final String MARK = "Mark tile"; + private static final String UNMARK = "Unmark tile"; + private static final String LABEL = "Label tile"; + private static final String WALK_HERE = "Walk here"; + private static final String REGION_PREFIX = "region_"; + + private static final Gson GSON = new Gson(); + + @Getter(AccessLevel.PACKAGE) + private final List points = new ArrayList<>(); + + @Inject + private Client client; + + @Inject + private GroundMarkerConfig config; + + @Inject + private ConfigManager configManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private GroundMarkerOverlay overlay; + + @Inject + private GroundMarkerMinimapOverlay minimapOverlay; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + private void savePoints(int regionId, Collection points) + { + if (points == null || points.isEmpty()) + { + configManager.unsetConfiguration(CONFIG_GROUP, REGION_PREFIX + regionId); + return; + } + + String json = GSON.toJson(points); + configManager.setConfiguration(CONFIG_GROUP, REGION_PREFIX + regionId, json); + } + + private Collection getPoints(int regionId) + { + String json = configManager.getConfiguration(CONFIG_GROUP, REGION_PREFIX + regionId); + if (Strings.isNullOrEmpty(json)) + { + return Collections.emptyList(); + } + + // CHECKSTYLE:OFF + return GSON.fromJson(json, new TypeToken>(){}.getType()); + // CHECKSTYLE:ON + } + + @Provides + GroundMarkerConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(GroundMarkerConfig.class); + } + + private void loadPoints() + { + points.clear(); + + int[] regions = client.getMapRegions(); + + if (regions == null) + { + return; + } + + for (int regionId : regions) + { + // load points for region + log.debug("Loading points for region {}", regionId); + Collection regionPoints = getPoints(regionId); + Collection colorTileMarkers = translateToColorTileMarker(regionPoints); + points.addAll(colorTileMarkers); + } + } + + /** + * Translate a collection of ground marker points to color tile markers, accounting for instances + * + * @param points {@link GroundMarkerPoint}s to be converted to {@link ColorTileMarker}s + * @return A collection of color tile markers, converted from the passed ground marker points, accounting for local + * instance points. See {@link WorldPoint#toLocalInstance(Client, WorldPoint)} + */ + private Collection translateToColorTileMarker(Collection points) + { + if (points.isEmpty()) + { + return Collections.emptyList(); + } + + return points.stream() + .map(point -> new ColorTileMarker( + WorldPoint.fromRegion(point.getRegionId(), point.getRegionX(), point.getRegionY(), point.getZ()), + point.getColor(), point.getLabel())) + .flatMap(colorTile -> + { + final Collection localWorldPoints = WorldPoint.toLocalInstance(client, colorTile.getWorldPoint()); + return localWorldPoints.stream().map(wp -> new ColorTileMarker(wp, colorTile.getColor(), colorTile.getLabel())); + }) + .collect(Collectors.toList()); + } + + @Override + public void startUp() + { + overlayManager.add(overlay); + overlayManager.add(minimapOverlay); + loadPoints(); + } + + @Override + public void shutDown() + { + overlayManager.remove(overlay); + overlayManager.remove(minimapOverlay); + points.clear(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() != GameState.LOGGED_IN) + { + return; + } + + // map region has just been updated + loadPoints(); + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + final boolean hotKeyPressed = client.isKeyPressed(KeyCode.KC_SHIFT); + if (hotKeyPressed && event.getOption().equals(WALK_HERE)) + { + final Tile selectedSceneTile = client.getSelectedSceneTile(); + + if (selectedSceneTile == null) + { + return; + } + + final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, selectedSceneTile.getLocalLocation()); + final int regionId = worldPoint.getRegionID(); + final GroundMarkerPoint point = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), null, null); + final boolean exists = getPoints(regionId).contains(point); + + MenuEntry[] menuEntries = client.getMenuEntries(); + menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + (exists ? 2 : 1)); + + MenuEntry mark = menuEntries[menuEntries.length - 1] = new MenuEntry(); + mark.setOption(exists ? UNMARK : MARK); + mark.setTarget(event.getTarget()); + mark.setType(MenuAction.RUNELITE.getId()); + + if (exists) + { + MenuEntry label = menuEntries[menuEntries.length - 2] = new MenuEntry(); + label.setOption(LABEL); + label.setTarget(event.getTarget()); + label.setType(MenuAction.RUNELITE.getId()); + } + + client.setMenuEntries(menuEntries); + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (event.getMenuAction().getId() != MenuAction.RUNELITE.getId()) + { + return; + } + + Tile target = client.getSelectedSceneTile(); + if (target == null) + { + return; + } + + final String option = event.getMenuOption(); + if (option.equals(MARK) || option.equals(UNMARK)) + { + markTile(target.getLocalLocation()); + } + else if (option.equals(LABEL)) + { + labelTile(target); + } + } + + private void markTile(LocalPoint localPoint) + { + if (localPoint == null) + { + return; + } + + WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, localPoint); + + int regionId = worldPoint.getRegionID(); + GroundMarkerPoint point = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), config.markerColor(), null); + log.debug("Updating point: {} - {}", point, worldPoint); + + List groundMarkerPoints = new ArrayList<>(getPoints(regionId)); + if (groundMarkerPoints.contains(point)) + { + groundMarkerPoints.remove(point); + } + else + { + groundMarkerPoints.add(point); + } + + savePoints(regionId, groundMarkerPoints); + + loadPoints(); + } + + private void labelTile(Tile tile) + { + LocalPoint localPoint = tile.getLocalLocation(); + WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, localPoint); + final int regionId = worldPoint.getRegionID(); + + chatboxPanelManager.openTextInput("Tile label") + .onDone((input) -> + { + input = Strings.emptyToNull(input); + + GroundMarkerPoint searchPoint = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), null, null); + Collection points = getPoints(regionId); + GroundMarkerPoint existing = points.stream() + .filter(p -> p.equals(searchPoint)) + .findFirst().orElse(null); + if (existing == null) + { + return; + } + + GroundMarkerPoint newPoint = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), existing.getColor(), input); + points.remove(searchPoint); + points.add(newPoint); + savePoints(regionId, points); + + loadPoints(); + }) + .build(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPoint.java new file mode 100644 index 0000000000..29d7d6f92c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPoint.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, TheLonelyDev + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.groundmarkers; + +import java.awt.Color; +import javax.annotation.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Value; + +/** + * Used for serialization of ground marker points. + */ +@Value +@EqualsAndHashCode(exclude = { "color", "label" }) +class GroundMarkerPoint +{ + private int regionId; + private int regionX; + private int regionY; + private int z; + @Nullable + private Color color; + @Nullable + private String label; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarConfig.java new file mode 100644 index 0000000000..15b27bf79f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarConfig.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2017, Tyler + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("herbiboar") +public interface HerbiboarConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "showStart", + name = "Show Start Objects", + description = "Show highlights for starting rocks and logs" + ) + default boolean isStartShown() + { + return true; + } + + @ConfigItem( + position = 1, + keyName = "showClickboxes", + name = "Show Clickboxes", + description = "Show clickboxes on trail objects and tunnels instead of tiles" + ) + default boolean showClickBoxes() + { + return false; + } + + @ConfigItem( + position = 2, + keyName = "colorStart", + name = "Start Color", + description = "Color for rocks that start the trails" + ) + default Color getStartColor() + { + return Color.CYAN; + } + + @ConfigItem( + position = 3, + keyName = "showTunnel", + name = "Show End Tunnels", + description = "Show highlights for tunnels with herbiboars" + ) + default boolean isTunnelShown() + { + return true; + } + + @ConfigItem( + position = 4, + keyName = "colorTunnel", + name = "Tunnel Color", + description = "Color for tunnels with herbiboars" + ) + default Color getTunnelColor() + { + return Color.GREEN; + } + + @ConfigItem( + position = 5, + keyName = "showObject", + name = "Show Trail Objects", + description = "Show highlights for mushrooms, mud, seaweed, etc" + ) + default boolean isObjectShown() + { + return true; + } + + @ConfigItem( + position = 6, + keyName = "colorGameObject", + name = "Trail Object Color", + description = "Color for mushrooms, mud, seaweed, etc" + ) + default Color getObjectColor() + { + return Color.CYAN; + } + + @ConfigItem( + position = 7, + keyName = "showTrail", + name = "Show Trail", + description = "Show highlights for trail prints" + ) + default boolean isTrailShown() + { + return true; + } + + @ConfigItem( + position = 8, + keyName = "colorTrail", + name = "Trail Color", + description = "Color for mushrooms, mud, seaweed, etc" + ) + default Color getTrailColor() + { + return Color.WHITE; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarMinimapOverlay.java new file mode 100644 index 0000000000..acbdbdfb89 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarMinimapOverlay.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018, Kamiel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import com.google.inject.Inject; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.Set; +import net.runelite.api.Point; +import net.runelite.api.TileObject; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; + +class HerbiboarMinimapOverlay extends Overlay +{ + private final HerbiboarPlugin plugin; + private final HerbiboarConfig config; + + @Inject + public HerbiboarMinimapOverlay(HerbiboarPlugin plugin, HerbiboarConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.isTrailShown() || !plugin.isInHerbiboarArea()) + { + return null; + } + + TrailToSpot nextTrail = plugin.getNextTrail(); + int finishId = plugin.getFinishId(); + Set shownTrailIds = plugin.getShownTrails(); + + for (TileObject tileObject : plugin.getTrails().values()) + { + int id = tileObject.getId(); + Point minimapLocation = tileObject.getMinimapLocation(); + + if (minimapLocation == null) + { + continue; + } + + if (shownTrailIds.contains(id) && (finishId > 0 || nextTrail != null && !nextTrail.getFootprintIds().contains(id))) + { + OverlayUtil.renderMinimapLocation(graphics, minimapLocation, config.getTrailColor()); + } + } + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java new file mode 100644 index 0000000000..5389cd6fd0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2017, Tyler + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.util.Set; +import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; + +class HerbiboarOverlay extends Overlay +{ + private final HerbiboarPlugin plugin; + private final HerbiboarConfig config; + + @Inject + public HerbiboarOverlay(HerbiboarPlugin plugin, HerbiboarConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.isInHerbiboarArea()) + { + return null; + } + + HerbiboarSearchSpot.Group currentGroup = plugin.getCurrentGroup(); + TrailToSpot nextTrail = plugin.getNextTrail(); + int finishId = plugin.getFinishId(); + + // Draw start objects + if (config.isStartShown() && (currentGroup == null && finishId == 0)) + { + plugin.getStarts().values().forEach((obj) -> OverlayUtil.renderTileOverlay(graphics, obj, "", config.getStartColor())); + } + + // Draw trails + if (config.isTrailShown()) + { + Set shownTrailIds = plugin.getShownTrails(); + plugin.getTrails().values().forEach((x) -> + { + int id = x.getId(); + if (shownTrailIds.contains(id) && (finishId > 0 || nextTrail != null && !nextTrail.getFootprintIds().contains(id))) + { + OverlayUtil.renderTileOverlay(graphics, x, "", config.getTrailColor()); + } + }); + } + + // Draw trail objects (mushrooms, mud, etc) + if (config.isObjectShown() && !(finishId > 0 || currentGroup == null)) + { + if (plugin.isRuleApplicable()) + { + WorldPoint correct = Iterables.getLast(plugin.getCurrentPath()).getLocation(); + TileObject object = plugin.getTrailObjects().get(correct); + drawObjectLocation(graphics, object, config.getObjectColor()); + } + else + { + for (WorldPoint trailLoc : HerbiboarSearchSpot.getGroupLocations(plugin.getCurrentGroup())) + { + TileObject object = plugin.getTrailObjects().get(trailLoc); + drawObjectLocation(graphics, object, config.getObjectColor()); + } + } + } + + // Draw finish tunnels + if (config.isTunnelShown() && finishId > 0) + { + WorldPoint finishLoc = plugin.getEndLocations().get(finishId - 1); + TileObject object = plugin.getTunnels().get(finishLoc); + drawObjectLocation(graphics, object, config.getTunnelColor()); + } + + return null; + } + + private void drawObjectLocation(Graphics2D graphics, TileObject object, Color color) + { + if (object == null) + { + return; + } + + if (config.showClickBoxes()) + { + Shape clickbox = object.getClickbox(); + if (clickbox != null) + { + Color clickBoxColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), 20); + + graphics.setColor(color); + graphics.draw(clickbox); + graphics.setColor(clickBoxColor); + graphics.fill(clickbox); + } + } + else + { + OverlayUtil.renderTileOverlay(graphics, object, "", color); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarPlugin.java new file mode 100644 index 0000000000..02288075c5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarPlugin.java @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2017, Tyler + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.Provides; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.MenuAction; +import static net.runelite.api.ObjectID.DRIFTWOOD_30523; +import static net.runelite.api.ObjectID.MUSHROOM_30520; +import static net.runelite.api.ObjectID.ROCK_30519; +import static net.runelite.api.ObjectID.ROCK_30521; +import static net.runelite.api.ObjectID.ROCK_30522; +import net.runelite.api.TileObject; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameObjectChanged; +import net.runelite.api.events.GameObjectDespawned; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GroundObjectChanged; +import net.runelite.api.events.GroundObjectDespawned; +import net.runelite.api.events.GroundObjectSpawned; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.VarbitChanged; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.ArrayUtils; + +@PluginDescriptor( + name = "Herbiboar", + description = "Highlight starting rocks, trails, and the objects to search at the end of each trail", + tags = {"herblore", "hunter", "skilling", "overlay"} +) +@Slf4j +@Getter +public class HerbiboarPlugin extends Plugin +{ + private static final List END_LOCATIONS = ImmutableList.of( + new WorldPoint(3693, 3798, 0), + new WorldPoint(3702, 3808, 0), + new WorldPoint(3703, 3826, 0), + new WorldPoint(3710, 3881, 0), + new WorldPoint(3700, 3877, 0), + new WorldPoint(3715, 3840, 0), + new WorldPoint(3751, 3849, 0), + new WorldPoint(3685, 3869, 0), + new WorldPoint(3681, 3863, 0) + ); + + private static final Set START_OBJECT_IDS = ImmutableSet.of( + ROCK_30519, + MUSHROOM_30520, + ROCK_30521, + ROCK_30522, + DRIFTWOOD_30523 + ); + + private static final List HERBIBOAR_REGIONS = ImmutableList.of( + 14652, + 14651, + 14908, + 14907 + ); + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private OverlayManager overlayManager; + + @Inject + private HerbiboarOverlay overlay; + + @Inject + private HerbiboarMinimapOverlay minimapOverlay; + + /** + * Objects which appear at the beginning of Herbiboar hunting trails + */ + private final Map starts = new HashMap<>(); + /** + * Herbiboar hunting "footstep" trail objects + */ + private final Map trails = new HashMap<>(); + /** + * Objects which trigger next trail (mushrooms, mud, seaweed, etc) + */ + private final Map trailObjects = new HashMap<>(); + /** + * Tunnel where the Herbiboar is hiding at the end of a trail + */ + private final Map tunnels = new HashMap<>(); + /** + * Trail object IDs which should be highlighted + */ + private final Set shownTrails = new HashSet<>(); + /** + * Sequence of herbiboar spots searched along the current trail + */ + private final List currentPath = Lists.newArrayList(); + + private boolean inHerbiboarArea; + private TrailToSpot nextTrail; + private HerbiboarSearchSpot.Group currentGroup; + private int finishId; + + private boolean started; + private WorldPoint startPoint; + private HerbiboarStart startSpot; + private boolean ruleApplicable; + + @Provides + HerbiboarConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(HerbiboarConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + overlayManager.add(minimapOverlay); + + if (client.getGameState() == GameState.LOGGED_IN) + { + clientThread.invokeLater(() -> + { + inHerbiboarArea = checkArea(); + updateTrailData(); + }); + } + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + overlayManager.remove(minimapOverlay); + resetTrailData(); + clearCache(); + inHerbiboarArea = false; + } + + private void updateTrailData() + { + if (!isInHerbiboarArea()) + { + return; + } + + boolean pathActive = false; + boolean wasStarted = started; + + // Get trail data + for (HerbiboarSearchSpot spot : HerbiboarSearchSpot.values()) + { + for (TrailToSpot trail : spot.getTrails()) + { + int value = client.getVar(trail.getVarbit()); + + if (value == trail.getValue()) + { + // The trail after you have searched the spot + currentGroup = spot.getGroup(); + nextTrail = trail; + + // You never visit the same spot twice + if (!currentPath.contains(spot)) + { + currentPath.add(spot); + } + } + else if (value > 0) + { + // The current trail + shownTrails.addAll(trail.getFootprintIds()); + pathActive = true; + } + } + } + + finishId = client.getVar(Varbits.HB_FINISH); + + // The started varbit doesn't get set until the first spot of the rotation has been searched + // so we need to use the current group as an indicator of the rotation being started + started = client.getVar(Varbits.HB_STARTED) > 0 || currentGroup != null; + boolean finished = !pathActive && started; + + if (!wasStarted && started) + { + startSpot = HerbiboarStart.from(startPoint); + } + + ruleApplicable = HerbiboarRule.canApplyRule(startSpot, currentPath); + + if (finished) + { + resetTrailData(); + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menuOpt) + { + if (!inHerbiboarArea || started || MenuAction.GAME_OBJECT_FIRST_OPTION != menuOpt.getMenuAction()) + { + return; + } + + switch (Text.removeTags(menuOpt.getMenuTarget())) + { + case "Rock": + case "Mushroom": + case "Driftwood": + startPoint = WorldPoint.fromScene(client, menuOpt.getActionParam(), menuOpt.getWidgetId(), client.getPlane()); + } + } + + private void resetTrailData() + { + log.debug("Reset trail data"); + shownTrails.clear(); + currentPath.clear(); + nextTrail = null; + currentGroup = null; + finishId = 0; + started = false; + startPoint = null; + startSpot = null; + ruleApplicable = false; + } + + private void clearCache() + { + starts.clear(); + trails.clear(); + trailObjects.clear(); + tunnels.clear(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + case HOPPING: + case LOGGING_IN: + resetTrailData(); + break; + case LOADING: + clearCache(); + inHerbiboarArea = checkArea(); + break; + default: + break; + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + updateTrailData(); + } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) + { + onTileObject(null, event.getGameObject()); + } + + @Subscribe + public void onGameObjectChanged(GameObjectChanged event) + { + onTileObject(event.getPrevious(), event.getGameObject()); + } + + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned event) + { + onTileObject(event.getGameObject(), null); + } + + @Subscribe + public void onGroundObjectSpawned(GroundObjectSpawned event) + { + onTileObject(null, event.getGroundObject()); + } + + @Subscribe + public void onGroundObjectChanged(GroundObjectChanged event) + { + onTileObject(event.getPrevious(), event.getGroundObject()); + } + + @Subscribe + public void onGroundObjectDespawned(GroundObjectDespawned event) + { + onTileObject(event.getGroundObject(), null); + } + + // Store relevant GameObjects (starts, tracks on trails, objects used to trigger next trails, and tunnels) + private void onTileObject(TileObject oldObject, TileObject newObject) + { + if (oldObject != null) + { + WorldPoint oldLocation = oldObject.getWorldLocation(); + starts.remove(oldLocation); + trails.remove(oldLocation); + trailObjects.remove(oldLocation); + tunnels.remove(oldLocation); + } + + if (newObject == null) + { + return; + } + + // Starts + if (START_OBJECT_IDS.contains(newObject.getId())) + { + starts.put(newObject.getWorldLocation(), newObject); + return; + } + + // Trails + if (HerbiboarSearchSpot.isTrail(newObject.getId())) + { + trails.put(newObject.getWorldLocation(), newObject); + return; + } + + // GameObject to trigger next trail (mushrooms, mud, seaweed, etc) + if (HerbiboarSearchSpot.isSearchSpot(newObject.getWorldLocation())) + { + trailObjects.put(newObject.getWorldLocation(), newObject); + return; + } + + // Herbiboar tunnel + if (END_LOCATIONS.contains(newObject.getWorldLocation())) + { + tunnels.put(newObject.getWorldLocation(), newObject); + } + } + + private boolean checkArea() + { + final int[] mapRegions = client.getMapRegions(); + for (int region : HERBIBOAR_REGIONS) + { + if (ArrayUtils.contains(mapRegions, region)) + { + return true; + } + } + return false; + } + + List getEndLocations() + { + return END_LOCATIONS; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarRule.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarRule.java new file mode 100644 index 0000000000..b904e05d3a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarRule.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020, dekvall + * Copyright (c) 2020, Jordan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import java.util.List; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +enum HerbiboarRule +{ + A_SOUTH(HerbiboarSearchSpot.Group.A, HerbiboarStart.MIDDLE), + C_WEST(HerbiboarSearchSpot.Group.C, HerbiboarStart.MIDDLE), + D_WEST_1(HerbiboarSearchSpot.Group.D, HerbiboarStart.MIDDLE), + D_WEST_2(HerbiboarSearchSpot.Group.D, HerbiboarSearchSpot.Group.C), + E_NORTH(HerbiboarSearchSpot.Group.E, HerbiboarSearchSpot.Group.A), + F_EAST(HerbiboarSearchSpot.Group.F, HerbiboarSearchSpot.Group.G), + G_NORTH(HerbiboarSearchSpot.Group.G, HerbiboarSearchSpot.Group.F), + H_NORTH(HerbiboarSearchSpot.Group.H, HerbiboarSearchSpot.Group.D), + H_EAST(HerbiboarSearchSpot.Group.H, HerbiboarStart.DRIFTWOOD), + I_EAST(HerbiboarSearchSpot.Group.I, HerbiboarStart.LEPRECHAUN), + I_SOUTH_1(HerbiboarSearchSpot.Group.I, HerbiboarStart.GHOST_MUSHROOM), + I_SOUTH_2(HerbiboarSearchSpot.Group.I, HerbiboarStart.CAMP_ENTRANCE), + I_WEST(HerbiboarSearchSpot.Group.I, HerbiboarSearchSpot.Group.E), + ; + + private final HerbiboarSearchSpot.Group to; + private final HerbiboarStart fromStart; + private final HerbiboarSearchSpot.Group fromGroup; + + HerbiboarRule(HerbiboarSearchSpot.Group to, HerbiboarSearchSpot.Group from) + { + this(to, null, from); + } + + HerbiboarRule(HerbiboarSearchSpot.Group to, HerbiboarStart fromStart) + { + this(to, fromStart, null); + } + + /** + * Returns whether the next {@link HerbiboarSearchSpot} can be deterministically selected based on the starting + * location and the path taken so far, based on the rules defined on the OSRS wiki. + * + * {@see https://oldschool.runescape.wiki/w/Herbiboar#Guaranteed_tracks} + * + * @param start Herbiboar's starting spot where the tracking path begins + * @param currentPath A list of {@link HerbiboarSearchSpot}s which have been searched thus far, and the next one to search + * @return {@code true} if a rule can be applied, {@code false} otherwise + */ + static boolean canApplyRule(HerbiboarStart start, List currentPath) + { + if (start == null || currentPath.isEmpty()) + { + return false; + } + + int lastIndex = currentPath.size() - 1; + HerbiboarSearchSpot.Group goingTo = currentPath.get(lastIndex).getGroup(); + + for (HerbiboarRule rule : values()) + { + if (lastIndex > 0 && rule.matches(currentPath.get(lastIndex - 1).getGroup(), goingTo) + || lastIndex == 0 && rule.matches(start, goingTo)) + { + return true; + } + } + + return false; + } + + boolean matches(HerbiboarStart from, HerbiboarSearchSpot.Group to) + { + return this.matches(from, null, to); + } + + boolean matches(HerbiboarSearchSpot.Group from, HerbiboarSearchSpot.Group to) + { + return this.matches(null, from, to); + } + + boolean matches(HerbiboarStart fromStart, HerbiboarSearchSpot.Group fromGroup, HerbiboarSearchSpot.Group to) + { + return this.to == to + && (fromStart != null && this.fromStart == fromStart || fromGroup != null && this.fromGroup == fromGroup); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarSearchSpot.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarSearchSpot.java new file mode 100644 index 0000000000..9e324f5a2b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarSearchSpot.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, dekvall + * Copyright (c) 2020, Jordan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.Getter; +import static net.runelite.api.NullObjectID.*; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; + +@Getter +enum HerbiboarSearchSpot +{ + // Wiki A location + A_MUSHROOM(Group.A, new WorldPoint(3670, 3889, 0), + new TrailToSpot(Varbits.HB_TRAIL_31318, 1, NULL_31318), + new TrailToSpot(Varbits.HB_TRAIL_31321, 1, NULL_31321)), + A_PATCH(Group.A, new WorldPoint(3672, 3890, 0), + new TrailToSpot(Varbits.HB_TRAIL_31306, 2, NULL_31306)), + + // Wiki B location + B_SEAWEED(Group.B, new WorldPoint(3728, 3893, 0), + new TrailToSpot(Varbits.HB_TRAIL_31315, 2, NULL_31315), + new TrailToSpot(Varbits.HB_TRAIL_31318, 2, NULL_31318), + new TrailToSpot(Varbits.HB_TRAIL_31336, 1, NULL_31336), + new TrailToSpot(Varbits.HB_TRAIL_31339, 1, NULL_31339)), + + // Wiki C location + C_MUSHROOM(Group.C, new WorldPoint(3697, 3875, 0), + new TrailToSpot(Varbits.HB_TRAIL_31303, 2, NULL_31303)), + C_PATCH(Group.C, new WorldPoint(3699, 3875, 0), + new TrailToSpot(Varbits.HB_TRAIL_31312, 1, NULL_31312), + new TrailToSpot(Varbits.HB_TRAIL_31315, 1, NULL_31315)), + + // Wiki D location + D_PATCH(Group.D, new WorldPoint(3708, 3876, 0), + new TrailToSpot(Varbits.HB_TRAIL_31330, 1, NULL_31330), + new TrailToSpot(Varbits.HB_TRAIL_31333, 1, NULL_31333)), + D_SEAWEED(Group.D, new WorldPoint(3710, 3877, 0), + new TrailToSpot(Varbits.HB_TRAIL_31312, 2, NULL_31312), + new TrailToSpot(Varbits.HB_TRAIL_31339, 2, NULL_31339)), + + // Wiki E location + E_MUSHROOM(Group.E, new WorldPoint(3668, 3865, 0), + new TrailToSpot(Varbits.HB_TRAIL_31342, 1, NULL_31342), + new TrailToSpot(Varbits.HB_TRAIL_31345, 1, NULL_31345)), + E_PATCH(Group.E, new WorldPoint(3667, 3862, 0), + new TrailToSpot(Varbits.HB_TRAIL_31321, 2, NULL_31321)), + + // Wiki F location + F_MUSHROOM(Group.F, new WorldPoint(3681, 3860, 0), + new TrailToSpot(Varbits.HB_TRAIL_31324, 1, NULL_31324), + new TrailToSpot(Varbits.HB_TRAIL_31327, 1, NULL_31327), + new TrailToSpot(Varbits.HB_TRAIL_31342, 2, NULL_31342)), + F_PATCH(Group.F, new WorldPoint(3681, 3859, 0), + new TrailToSpot(Varbits.HB_TRAIL_31309, 2, NULL_31309)), + + // Wiki G location + G_MUSHROOM(Group.G, new WorldPoint(3694, 3847, 0), + new TrailToSpot(Varbits.HB_TRAIL_31333, 2, NULL_31333), + new TrailToSpot(Varbits.HB_TRAIL_31354, 1, NULL_31354)), + G_PATCH(Group.G, new WorldPoint(3698, 3847, 0), + new TrailToSpot(Varbits.HB_TRAIL_31327, 2, NULL_31327)), + + // Wiki H location + H_SEAWEED_EAST(Group.H, new WorldPoint(3715, 3851, 0), + new TrailToSpot(Varbits.HB_TRAIL_31357, 1, NULL_31357), + new TrailToSpot(Varbits.HB_TRAIL_31360, 1, NULL_31360)), + H_SEAWEED_WEST(Group.H, new WorldPoint(3713, 3850, 0), + new TrailToSpot(Varbits.HB_TRAIL_31330, 2, NULL_31330), + new TrailToSpot(Varbits.HB_TRAIL_31363, 1, NULL_31363)), + + // Wiki I location + I_MUSHROOM(Group.I, new WorldPoint(3680, 3838, 0), + new TrailToSpot(Varbits.HB_TRAIL_31348, 1, NULL_31348), + new TrailToSpot(Varbits.HB_TRAIL_31351, 1, NULL_31351)), + I_PATCH(Group.I, new WorldPoint(3680, 3836, 0), + new TrailToSpot(Varbits.HB_TRAIL_31324, 2, NULL_31324), + new TrailToSpot(Varbits.HB_TRAIL_31345, 2, NULL_31345)), + + // Wiki J location + J_PATCH(Group.J, new WorldPoint(3713, 3840, 0), + new TrailToSpot(Varbits.HB_TRAIL_31357, 2, NULL_31357), + new TrailToSpot(Varbits.HB_TRAIL_31372, 1, NULL_31372)), + + // Wiki K location + K_PATCH(Group.K, new WorldPoint(3706, 3811, 0), + new TrailToSpot(Varbits.HB_TRAIL_31348, 2, NULL_31348), + new TrailToSpot(Varbits.HB_TRAIL_31366, 1, NULL_31366), + new TrailToSpot(Varbits.HB_TRAIL_31369, 1, NULL_31369)), + ; + + private static final ImmutableMultimap GROUPS; + private static final Set SPOTS; + private static final Set TRAILS; + + static + { + ImmutableMultimap.Builder groupBuilder = new ImmutableMultimap.Builder<>(); + ImmutableSet.Builder spotBuilder = new ImmutableSet.Builder<>(); + ImmutableSet.Builder trailBuilder = new ImmutableSet.Builder<>(); + + for (HerbiboarSearchSpot spot : values()) + { + groupBuilder.put(spot.getGroup(), spot); + spotBuilder.add(spot.getLocation()); + + for (TrailToSpot trail : spot.getTrails()) + { + trailBuilder.addAll(trail.getFootprintIds()); + } + } + + GROUPS = groupBuilder.build(); + SPOTS = spotBuilder.build(); + TRAILS = trailBuilder.build(); + } + + private final Group group; + private final WorldPoint location; + private final List trails; + + HerbiboarSearchSpot(Group group, WorldPoint location, TrailToSpot... trails) + { + this.group = group; + this.location = location; + this.trails = ImmutableList.copyOf(trails); + } + + /** + * Spots are placed in groups of two + */ + enum Group + { + A, B, C, D, E, F, G, H, I, J, K + } + + static boolean isTrail(int id) + { + return TRAILS.contains(id); + } + + static boolean isSearchSpot(WorldPoint location) + { + return SPOTS.contains(location); + } + + static List getGroupLocations(Group group) + { + return GROUPS.get(group).stream().map(HerbiboarSearchSpot::getLocation).collect(Collectors.toList()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarStart.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarStart.java new file mode 100644 index 0000000000..3aa6485450 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarStart.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020, dekvall + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.coords.WorldPoint; + +@Getter +@RequiredArgsConstructor +enum HerbiboarStart +{ + MIDDLE(new WorldPoint(3686, 3870, 0)), + LEPRECHAUN(new WorldPoint(3705, 3830, 0)), + CAMP_ENTRANCE(new WorldPoint(3704, 3810, 0)), + GHOST_MUSHROOM(new WorldPoint(3695, 3800, 0)), + DRIFTWOOD(new WorldPoint(3751, 3850, 0)), + ; + + private final WorldPoint location; + + static HerbiboarStart from(WorldPoint location) + { + for (final HerbiboarStart start : values()) + { + if (start.getLocation().equals(location)) + { + return start; + } + } + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/TrailToSpot.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/TrailToSpot.java new file mode 100644 index 0000000000..17c0b923cb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/TrailToSpot.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, dekvall + * Copyright (c) 2020, Jordan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.herbiboars; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import lombok.Value; +import net.runelite.api.Varbits; + +/** + * A representation of a trail of footsteps which appears when hunting for the Herbiboar. + */ +@Value +class TrailToSpot +{ + /** + * The Varbit associated with the trail. When inactive, this Varbit's value should be less than + * {@link TrailToSpot#getValue()}. When this trail appears after searching a spot, this Varbit's value should be + * equal to that of {@link TrailToSpot#getValue()}. Once the next object along the trail has been searched, this + * Varbit's value will be greater than that of {@link TrailToSpot#getValue()}. + */ + private final Varbits varbit; + /** + * The cutoff reference value to compare against the value of {@link TrailToSpot#getVarbit()} to determine its state + * along the current trail. + */ + private final int value; + /** + * The object ID of the footprints which appear when the trail is made visible. + */ + private final int footprint; + + Set getFootprintIds() + { + return ImmutableSet.of(footprint, footprint + 1); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscoreConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscoreConfig.java new file mode 100644 index 0000000000..8ba792f5e6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscoreConfig.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hiscore; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("hiscore") +public interface HiscoreConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "playerOption", + name = "Player option", + description = "Add Lookup option to players" + ) + default boolean playerOption() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "menuOption", + name = "Menu option", + description = "Show Lookup option in menus" + ) + default boolean menuOption() + { + return true; + } + + @ConfigItem( + position = 3, + keyName = "virtualLevels", + name = "Display virtual levels", + description = "Display levels over 99 in the hiscore panel" + ) + default boolean virtualLevels() + { + return true; + } + + @ConfigItem( + position = 4, + keyName = "autocomplete", + name = "Autocomplete", + description = "Predict names when typing a name to lookup" + ) + default boolean autocomplete() + { + return true; + } + + @ConfigItem( + position = 5, + keyName = "bountylookup", + name = "Bounty lookup", + description = "Automatically lookup the stats of your bounty hunter target" + ) + default boolean bountylookup() + { + return false; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java new file mode 100644 index 0000000000..7876289da6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java @@ -0,0 +1,756 @@ +/* + * Copyright (c) 2017, Adam + * Copyright (c) 2018, Psikoi + * Copyright (c) 2019, Bram91 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hiscore; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.Experience; +import net.runelite.api.Player; +import net.runelite.api.WorldType; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.IconTextField; +import net.runelite.client.ui.components.materialtabs.MaterialTab; +import net.runelite.client.ui.components.materialtabs.MaterialTabGroup; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.http.api.hiscore.HiscoreClient; +import net.runelite.http.api.hiscore.HiscoreEndpoint; +import net.runelite.http.api.hiscore.HiscoreResult; +import net.runelite.http.api.hiscore.HiscoreSkill; +import static net.runelite.http.api.hiscore.HiscoreSkill.*; +import net.runelite.http.api.hiscore.HiscoreSkillType; +import net.runelite.http.api.hiscore.Skill; +import okhttp3.OkHttpClient; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +@Slf4j +public class HiscorePanel extends PluginPanel +{ + /* The maximum allowed username length in RuneScape accounts */ + private static final int MAX_USERNAME_LENGTH = 12; + + /** + * Real skills, ordered in the way they should be displayed in the panel. + */ + private static final List SKILLS = ImmutableList.of( + ATTACK, HITPOINTS, MINING, + STRENGTH, AGILITY, SMITHING, + DEFENCE, HERBLORE, FISHING, + RANGED, THIEVING, COOKING, + PRAYER, CRAFTING, FIREMAKING, + MAGIC, FLETCHING, WOODCUTTING, + RUNECRAFT, SLAYER, FARMING, + CONSTRUCTION, HUNTER + ); + + /** + * Bosses, ordered in the way they should be displayed in the panel. + */ + private static final List BOSSES = ImmutableList.of( + ABYSSAL_SIRE, ALCHEMICAL_HYDRA, BARROWS_CHESTS, + BRYOPHYTA, CALLISTO, CERBERUS, + CHAMBERS_OF_XERIC, CHAMBERS_OF_XERIC_CHALLENGE_MODE, CHAOS_ELEMENTAL, + CHAOS_FANATIC, COMMANDER_ZILYANA, CORPOREAL_BEAST, + DAGANNOTH_PRIME, DAGANNOTH_REX, DAGANNOTH_SUPREME, + CRAZY_ARCHAEOLOGIST, DERANGED_ARCHAEOLOGIST, GENERAL_GRAARDOR, + GIANT_MOLE, GROTESQUE_GUARDIANS, HESPORI, + KALPHITE_QUEEN, KING_BLACK_DRAGON, KRAKEN, + KREEARRA, KRIL_TSUTSAROTH, MIMIC, + NIGHTMARE, OBOR, SARACHNIS, + SCORPIA, SKOTIZO, THE_GAUNTLET, + THE_CORRUPTED_GAUNTLET, THEATRE_OF_BLOOD, THERMONUCLEAR_SMOKE_DEVIL, + TZKAL_ZUK, TZTOK_JAD, VENENATIS, + VETION, VORKATH, WINTERTODT, + ZALCANO, ZULRAH + ); + + private static final HiscoreEndpoint[] ENDPOINTS = { + HiscoreEndpoint.NORMAL, HiscoreEndpoint.IRONMAN, HiscoreEndpoint.HARDCORE_IRONMAN, HiscoreEndpoint.ULTIMATE_IRONMAN, HiscoreEndpoint.DEADMAN, HiscoreEndpoint.LEAGUE + }; + + private final Client client; + private final HiscoreConfig config; + private final NameAutocompleter nameAutocompleter; + private final HiscoreClient hiscoreClient; + + private final IconTextField searchBar; + + // Not an enummap because we need null keys for combat + private final Map skillLabels = new HashMap<>(); + + /* Container of all the selectable endpoints (ironman, deadman, etc) */ + private final MaterialTabGroup tabGroup; + + /* The currently selected endpoint */ + private HiscoreEndpoint selectedEndPoint; + + /* Used to prevent users from switching endpoint tabs while the results are loading */ + private boolean loading = false; + + @Inject + public HiscorePanel(@Nullable Client client, + HiscoreConfig config, NameAutocompleter nameAutocompleter, OkHttpClient okHttpClient) + { + this.client = client; + this.config = config; + this.nameAutocompleter = nameAutocompleter; + this.hiscoreClient = new HiscoreClient(okHttpClient); + + // The layout seems to be ignoring the top margin and only gives it + // a 2-3 pixel margin, so I set the value to 18 to compensate + // TODO: Figure out why this layout is ignoring most of the top margin + setBorder(new EmptyBorder(18, 10, 0, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setLayout(new GridBagLayout()); + + // Expand sub items to fit width of panel, align to top of panel + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 0; + c.weightx = 1; + c.weighty = 0; + c.insets = new Insets(0, 0, 10, 0); + + searchBar = new IconTextField(); + searchBar.setIcon(IconTextField.Icon.SEARCH); + searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30)); + searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + searchBar.setMinimumSize(new Dimension(0, 30)); + searchBar.addActionListener(e -> lookup()); + searchBar.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + if (e.getClickCount() != 2) + { + return; + } + if (client == null) + { + return; + } + + Player localPlayer = client.getLocalPlayer(); + + if (localPlayer != null) + { + lookup(localPlayer.getName()); + } + } + }); + searchBar.addClearListener(() -> + { + searchBar.setIcon(IconTextField.Icon.SEARCH); + searchBar.setEditable(true); + loading = false; + }); + + add(searchBar, c); + c.gridy++; + + tabGroup = new MaterialTabGroup(); + tabGroup.setLayout(new GridLayout(1, 5, 7, 7)); + + for (HiscoreEndpoint endpoint : ENDPOINTS) + { + final BufferedImage iconImage = ImageUtil.getResourceStreamFromClass(getClass(), endpoint.name().toLowerCase() + ".png"); + + MaterialTab tab = new MaterialTab(new ImageIcon(iconImage), tabGroup, null); + tab.setToolTipText(endpoint.getName() + " Hiscores"); + tab.setOnSelectEvent(() -> + { + if (loading) + { + return false; + } + + selectedEndPoint = endpoint; + return true; + }); + + // Adding the lookup method to a mouseListener instead of the above onSelectedEvent + // Because sometimes you might want to switch the tab, without calling for lookup + // Ex: selecting the normal hiscores as default + tab.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (loading) + { + return; + } + + lookup(); + } + }); + + tabGroup.addTab(tab); + } + + // Default selected tab is normal hiscores + resetEndpoints(); + + add(tabGroup, c); + c.gridy++; + + // Panel that holds skill icons + JPanel statsPanel = new JPanel(); + statsPanel.setLayout(new GridLayout(8, 3)); + statsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + statsPanel.setBorder(new EmptyBorder(5, 0, 5, 0)); + + // For each skill on the ingame skill panel, create a Label and add it to the UI + for (HiscoreSkill skill : SKILLS) + { + JPanel panel = makeHiscorePanel(skill); + statsPanel.add(panel); + } + + add(statsPanel, c); + c.gridy++; + + JPanel totalPanel = new JPanel(); + totalPanel.setLayout(new GridLayout(1, 2)); + totalPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + totalPanel.add(makeHiscorePanel(null)); //combat has no hiscore skill, referred to as null + totalPanel.add(makeHiscorePanel(OVERALL)); + + add(totalPanel, c); + c.gridy++; + + JPanel minigamePanel = new JPanel(); + // These aren't all on one row because when there's a label with four or more digits it causes the details + // panel to change its size for some reason... + minigamePanel.setLayout(new GridLayout(2, 3)); + minigamePanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + minigamePanel.add(makeHiscorePanel(CLUE_SCROLL_ALL)); + minigamePanel.add(makeHiscorePanel(LEAGUE_POINTS)); + minigamePanel.add(makeHiscorePanel(LAST_MAN_STANDING)); + minigamePanel.add(makeHiscorePanel(BOUNTY_HUNTER_ROGUE)); + minigamePanel.add(makeHiscorePanel(BOUNTY_HUNTER_HUNTER)); + + add(minigamePanel, c); + c.gridy++; + + JPanel bossPanel = new JPanel(); + bossPanel.setLayout(new GridLayout(0, 3)); + bossPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + // For each boss on the hi-scores, create a Label and add it to the UI + for (HiscoreSkill skill : BOSSES) + { + JPanel panel = makeHiscorePanel(skill); + bossPanel.add(panel); + } + + add(bossPanel, c); + c.gridy++; + + addInputKeyListener(nameAutocompleter); + } + + void shutdown() + { + removeInputKeyListener(nameAutocompleter); + } + + @Override + public void onActivate() + { + super.onActivate(); + searchBar.requestFocusInWindow(); + } + + /* Builds a JPanel displaying an icon and level/number associated with it */ + private JPanel makeHiscorePanel(HiscoreSkill skill) + { + HiscoreSkillType skillType = skill == null ? HiscoreSkillType.SKILL : skill.getType(); + + JLabel label = new JLabel(); + label.setToolTipText(skill == null ? "Combat" : skill.getName()); + label.setFont(FontManager.getRunescapeSmallFont()); + label.setText(pad("--", skillType)); + + String directory; + if (skill == null || skill == OVERALL) + { + directory = "/skill_icons/"; + } + else if (skill.getType() == HiscoreSkillType.BOSS) + { + directory = "bosses/"; + } + else + { + directory = "/skill_icons_small/"; + } + + String skillName = (skill == null ? "combat" : skill.name().toLowerCase()); + String skillIcon = directory + skillName + ".png"; + log.debug("Loading skill icon from {}", skillIcon); + + label.setIcon(new ImageIcon(ImageUtil.getResourceStreamFromClass(getClass(), skillIcon))); + + boolean totalLabel = skill == OVERALL || skill == null; //overall or combat + label.setIconTextGap(totalLabel ? 10 : 4); + + JPanel skillPanel = new JPanel(); + skillPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + skillPanel.setBorder(new EmptyBorder(2, 0, 2, 0)); + skillLabels.put(skill, label); + skillPanel.add(label); + + return skillPanel; + } + + public void lookup(String username) + { + searchBar.setText(username); + resetEndpoints(); + lookup(); + } + + private void lookup() + { + final String lookup = sanitize(searchBar.getText()); + + if (Strings.isNullOrEmpty(lookup)) + { + return; + } + + /* RuneScape usernames can't be longer than 12 characters long */ + if (lookup.length() > MAX_USERNAME_LENGTH) + { + searchBar.setIcon(IconTextField.Icon.ERROR); + loading = false; + return; + } + + searchBar.setEditable(false); + searchBar.setIcon(IconTextField.Icon.LOADING_DARKER); + loading = true; + + for (Map.Entry entry : skillLabels.entrySet()) + { + HiscoreSkill skill = entry.getKey(); + JLabel label = entry.getValue(); + HiscoreSkillType skillType = skill == null ? HiscoreSkillType.SKILL : skill.getType(); + + label.setText(pad("--", skillType)); + label.setToolTipText(skill == null ? "Combat" : skill.getName()); + } + + // if for some reason no endpoint was selected, default to normal + if (selectedEndPoint == null) + { + selectedEndPoint = HiscoreEndpoint.NORMAL; + } + + hiscoreClient.lookupAsync(lookup, selectedEndPoint).whenCompleteAsync((result, ex) -> + SwingUtilities.invokeLater(() -> + { + if (!sanitize(searchBar.getText()).equals(lookup)) + { + // search has changed in the meantime + return; + } + + if (result == null || ex != null) + { + if (ex != null) + { + log.warn("Error fetching Hiscore data " + ex.getMessage()); + } + + searchBar.setIcon(IconTextField.Icon.ERROR); + searchBar.setEditable(true); + loading = false; + return; + } + + //successful player search + searchBar.setIcon(IconTextField.Icon.SEARCH); + searchBar.setEditable(true); + loading = false; + + applyHiscoreResult(result); + })); + } + + private void applyHiscoreResult(HiscoreResult result) + { + assert SwingUtilities.isEventDispatchThread(); + + nameAutocompleter.addToSearchHistory(result.getPlayer().toLowerCase()); + + for (Map.Entry entry : skillLabels.entrySet()) + { + HiscoreSkill skill = entry.getKey(); + JLabel label = entry.getValue(); + Skill s; + + if (skill == null) + { + if (result.getPlayer() != null) + { + int combatLevel = Experience.getCombatLevel( + result.getAttack().getLevel(), + result.getStrength().getLevel(), + result.getDefence().getLevel(), + result.getHitpoints().getLevel(), + result.getMagic().getLevel(), + result.getRanged().getLevel(), + result.getPrayer().getLevel() + ); + label.setText(Integer.toString(combatLevel)); + } + } + else if ((s = result.getSkill(skill)) != null) + { + final long exp = s.getExperience(); + final boolean isSkill = skill.getType() == HiscoreSkillType.SKILL; + int level = -1; + if (config.virtualLevels() && isSkill && exp > -1L) + { + level = Experience.getLevelForXp((int) exp); + } + else if (!isSkill || exp != -1L) + { + // for skills, level is only valid if exp is not -1 + // otherwise level is always valid + level = s.getLevel(); + } + + if (level != -1) + { + label.setText(pad(formatLevel(level), skill.getType())); + } + } + + label.setToolTipText(detailsHtml(result, skill)); + } + } + + void addInputKeyListener(KeyListener l) + { + this.searchBar.addKeyListener(l); + } + + void removeInputKeyListener(KeyListener l) + { + this.searchBar.removeKeyListener(l); + } + + /* + Builds a html string to display on tooltip (when hovering a skill). + */ + private String detailsHtml(HiscoreResult result, HiscoreSkill skill) + { + String openingTags = ""; + String closingTags = ""; + + String content = ""; + + if (skill == null) + { + double combatLevel = Experience.getCombatLevelPrecise( + result.getAttack().getLevel(), + result.getStrength().getLevel(), + result.getDefence().getLevel(), + result.getHitpoints().getLevel(), + result.getMagic().getLevel(), + result.getRanged().getLevel(), + result.getPrayer().getLevel() + ); + + double combatExperience = result.getAttack().getExperience() + + result.getStrength().getExperience() + result.getDefence().getExperience() + + result.getHitpoints().getExperience() + result.getMagic().getExperience() + + result.getRanged().getExperience() + result.getPrayer().getExperience(); + + content += "

Combat

"; + content += "

Exact Combat Level: " + QuantityFormatter.formatNumber(combatLevel) + "

"; + content += "

Experience: " + QuantityFormatter.formatNumber(combatExperience) + "

"; + } + else + { + switch (skill) + { + case CLUE_SCROLL_ALL: + { + String allRank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollAll().getRank()); + String beginnerRank = (result.getClueScrollBeginner().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollBeginner().getRank()); + String easyRank = (result.getClueScrollEasy().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollEasy().getRank()); + String mediumRank = (result.getClueScrollMedium().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollMedium().getRank()); + String hardRank = (result.getClueScrollHard().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollHard().getRank()); + String eliteRank = (result.getClueScrollElite().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollElite().getRank()); + String masterRank = (result.getClueScrollMaster().getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(result.getClueScrollMaster().getRank()); + String all = (result.getClueScrollAll().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollAll().getLevel())); + String beginner = (result.getClueScrollBeginner().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollBeginner().getLevel())); + String easy = (result.getClueScrollEasy().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollEasy().getLevel())); + String medium = (result.getClueScrollMedium().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollMedium().getLevel())); + String hard = (result.getClueScrollHard().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollHard().getLevel())); + String elite = (result.getClueScrollElite().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollElite().getLevel())); + String master = (result.getClueScrollMaster().getLevel() == -1 ? "0" : QuantityFormatter.formatNumber(result.getClueScrollMaster().getLevel())); + content += "

Clues

"; + content += "

All: " + all + " Rank: " + allRank + "

"; + content += "

Beginner: " + beginner + " Rank: " + beginnerRank + "

"; + content += "

Easy: " + easy + " Rank: " + easyRank + "

"; + content += "

Medium: " + medium + " Rank: " + mediumRank + "

"; + content += "

Hard: " + hard + " Rank: " + hardRank + "

"; + content += "

Elite: " + elite + " Rank: " + eliteRank + "

"; + content += "

Master: " + master + " Rank: " + masterRank + "

"; + break; + } + case BOUNTY_HUNTER_ROGUE: + { + Skill bountyHunterRogue = result.getBountyHunterRogue(); + String rank = (bountyHunterRogue.getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(bountyHunterRogue.getRank()); + content += "

Bounty Hunter - Rogue

"; + content += "

Rank: " + rank + "

"; + if (bountyHunterRogue.getLevel() > -1) + { + content += "

Score: " + QuantityFormatter.formatNumber(bountyHunterRogue.getLevel()) + "

"; + } + break; + } + case BOUNTY_HUNTER_HUNTER: + { + Skill bountyHunterHunter = result.getBountyHunterHunter(); + String rank = (bountyHunterHunter.getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(bountyHunterHunter.getRank()); + content += "

Bounty Hunter - Hunter

"; + content += "

Rank: " + rank + "

"; + if (bountyHunterHunter.getLevel() > -1) + { + content += "

Score: " + QuantityFormatter.formatNumber(bountyHunterHunter.getLevel()) + "

"; + } + break; + } + case LAST_MAN_STANDING: + { + Skill lastManStanding = result.getLastManStanding(); + String rank = (lastManStanding.getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(lastManStanding.getRank()); + content += "

Last Man Standing

"; + content += "

Rank: " + rank + "

"; + if (lastManStanding.getLevel() > -1) + { + content += "

Score: " + QuantityFormatter.formatNumber(lastManStanding.getLevel()) + "

"; + } + break; + } + case LEAGUE_POINTS: + { + Skill leaguePoints = result.getLeaguePoints(); + String rank = (leaguePoints.getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(leaguePoints.getRank()); + content += "

League Points

"; + content += "

Rank: " + rank + "

"; + if (leaguePoints.getLevel() > -1) + { + content += "

Points: " + QuantityFormatter.formatNumber(leaguePoints.getLevel()) + "

"; + } + break; + } + case OVERALL: + { + Skill requestedSkill = result.getSkill(skill); + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(requestedSkill.getRank()); + String exp = (requestedSkill.getExperience() == -1L) ? "Unranked" : QuantityFormatter.formatNumber(requestedSkill.getExperience()); + content += "

" + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + content += "

Experience: " + exp + "

"; + break; + } + default: + { + if (skill.getType() == HiscoreSkillType.BOSS) + { + String rank = "Unranked"; + String lvl = null; + Skill requestedSkill = result.getSkill(skill); + if (requestedSkill != null) + { + if (requestedSkill.getRank() > -1) + { + rank = QuantityFormatter.formatNumber(requestedSkill.getRank()); + } + if (requestedSkill.getLevel() > -1) + { + lvl = QuantityFormatter.formatNumber(requestedSkill.getLevel()); + } + } + + content += "

Boss: " + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + if (lvl != null) + { + content += "

KC: " + lvl + "

"; + } + } + else + { + Skill requestedSkill = result.getSkill(skill); + final long experience = requestedSkill.getExperience(); + + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : QuantityFormatter.formatNumber(requestedSkill.getRank()); + String exp = (experience == -1L) ? "Unranked" : QuantityFormatter.formatNumber(experience); + String remainingXp; + if (experience == -1L) + { + remainingXp = "Unranked"; + } + else + { + int currentLevel = Experience.getLevelForXp((int) experience); + remainingXp = (currentLevel + 1 <= Experience.MAX_VIRT_LEVEL) ? QuantityFormatter.formatNumber(Experience.getXpForLevel(currentLevel + 1) - experience) : "0"; + } + + content += "

Skill: " + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + content += "

Experience: " + exp + "

"; + content += "

Remaining XP: " + remainingXp + "

"; + } + break; + } + } + } + + // Add a html progress bar to the hover information + if (skill != null && skill.getType() == HiscoreSkillType.SKILL) + { + long experience = result.getSkill(skill).getExperience(); + if (experience >= 0) + { + int currentXp = (int) experience; + int currentLevel = Experience.getLevelForXp(currentXp); + int xpForCurrentLevel = Experience.getXpForLevel(currentLevel); + int xpForNextLevel = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1; + + double xpGained = currentXp - xpForCurrentLevel; + double xpGoal = xpForNextLevel != -1 ? xpForNextLevel - xpForCurrentLevel : 100; + int progress = (int) ((xpGained / xpGoal) * 100f); + + // had to wrap the bar with an empty div, if i added the margin directly to the bar, it would mess up + content += "
" + + "
" + + "
" + + "
" + + "
" + + "
"; + } + } + + return openingTags + content + closingTags; + } + + private static String sanitize(String lookup) + { + return lookup.replace('\u00A0', ' '); + } + + private void resetEndpoints() + { + // Select the correct tab based on the world type. + HiscoreEndpoint endpoint = selectWorldEndpoint(); + int idx = ArrayUtils.indexOf(ENDPOINTS, endpoint); + tabGroup.select(tabGroup.getTab(idx)); + } + + private HiscoreEndpoint selectWorldEndpoint() + { + if (client != null) + { + EnumSet wTypes = client.getWorldType(); + + if (wTypes.contains(WorldType.DEADMAN_TOURNAMENT)) + { + return HiscoreEndpoint.TOURNAMENT; + } + else if (wTypes.contains(WorldType.DEADMAN)) + { + return HiscoreEndpoint.DEADMAN; + } + else if (wTypes.contains(WorldType.LEAGUE)) + { + return HiscoreEndpoint.LEAGUE; + } + } + return HiscoreEndpoint.NORMAL; + } + + @VisibleForTesting + static String formatLevel(int level) + { + if (level < 10000) + { + return Integer.toString(level); + } + else + { + return (level / 1000) + "k"; + } + } + + private static String pad(String str, HiscoreSkillType type) + { + // Left pad label text to keep labels aligned + int pad = type == HiscoreSkillType.BOSS ? 4 : 2; + return StringUtils.leftPad(str, pad); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java new file mode 100644 index 0000000000..75e41f6018 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hiscore; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ObjectArrays; +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.swing.SwingUtilities; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.PlayerMenuOptionClicked; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.menus.MenuManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.ArrayUtils; + +@PluginDescriptor( + name = "HiScore", + description = "Enable the HiScore panel and an optional Lookup option on players", + tags = {"panel", "players"}, + loadWhenOutdated = true +) +public class HiscorePlugin extends Plugin +{ + private static final String LOOKUP = "Lookup"; + private static final String KICK_OPTION = "Kick"; + private static final ImmutableList AFTER_OPTIONS = ImmutableList.of("Message", "Add ignore", "Remove friend", "Delete", KICK_OPTION); + private static final Pattern BOUNTY_PATTERN = Pattern.compile("You've been assigned a target: (.*)"); + + @Inject + @Nullable + private Client client; + + @Inject + private Provider menuManager; + + @Inject + private ClientToolbar clientToolbar; + + @Inject + private HiscoreConfig config; + + private NavigationButton navButton; + private HiscorePanel hiscorePanel; + + @Provides + HiscoreConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(HiscoreConfig.class); + } + + @Override + protected void startUp() throws Exception + { + hiscorePanel = injector.getInstance(HiscorePanel.class); + + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "normal.png"); + + navButton = NavigationButton.builder() + .tooltip("Hiscore") + .icon(icon) + .priority(5) + .panel(hiscorePanel) + .build(); + + clientToolbar.addNavigation(navButton); + + if (config.playerOption() && client != null) + { + menuManager.get().addPlayerMenuItem(LOOKUP); + } + } + + @Override + protected void shutDown() throws Exception + { + hiscorePanel.shutdown(); + clientToolbar.removeNavigation(navButton); + + if (client != null) + { + menuManager.get().removePlayerMenuItem(LOOKUP); + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("hiscore")) + { + if (client != null) + { + menuManager.get().removePlayerMenuItem(LOOKUP); + + if (config.playerOption()) + { + menuManager.get().addPlayerMenuItem(LOOKUP); + } + } + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + if (!config.menuOption()) + { + return; + } + + int groupId = WidgetInfo.TO_GROUP(event.getActionParam1()); + String option = event.getOption(); + + if (groupId == WidgetInfo.FRIENDS_LIST.getGroupId() || groupId == WidgetInfo.FRIENDS_CHAT.getGroupId() || + groupId == WidgetInfo.CHATBOX.getGroupId() && !KICK_OPTION.equals(option) || //prevent from adding for Kick option (interferes with the raiding party one) + groupId == WidgetInfo.RAIDING_PARTY.getGroupId() || groupId == WidgetInfo.PRIVATE_CHAT_MESSAGE.getGroupId() || + groupId == WidgetInfo.IGNORE_LIST.getGroupId()) + { + if (!AFTER_OPTIONS.contains(option) || (option.equals("Delete") && groupId != WidgetInfo.IGNORE_LIST.getGroupId())) + { + return; + } + + final MenuEntry lookup = new MenuEntry(); + lookup.setOption(LOOKUP); + lookup.setTarget(event.getTarget()); + lookup.setType(MenuAction.RUNELITE.getId()); + lookup.setParam0(event.getActionParam0()); + lookup.setParam1(event.getActionParam1()); + lookup.setIdentifier(event.getIdentifier()); + + insertMenuEntry(lookup, client.getMenuEntries()); + } + } + + @Subscribe + public void onPlayerMenuOptionClicked(PlayerMenuOptionClicked event) + { + if (event.getMenuOption().equals(LOOKUP)) + { + lookupPlayer(Text.removeTags(event.getMenuTarget())); + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (!config.bountylookup() || !event.getType().equals(ChatMessageType.GAMEMESSAGE)) + { + return; + } + + String message = event.getMessage(); + Matcher m = BOUNTY_PATTERN.matcher(message); + if (m.matches()) + { + lookupPlayer(m.group(1)); + } + } + + private void insertMenuEntry(MenuEntry newEntry, MenuEntry[] entries) + { + MenuEntry[] newMenu = ObjectArrays.concat(entries, newEntry); + int menuEntryCount = newMenu.length; + ArrayUtils.swap(newMenu, menuEntryCount - 1, menuEntryCount - 2); + client.setMenuEntries(newMenu); + } + + private void lookupPlayer(String playerName) + { + SwingUtilities.invokeLater(() -> + { + if (!navButton.isSelected()) + { + navButton.getOnSelect().run(); + } + hiscorePanel.lookup(playerName); + }); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/NameAutocompleter.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/NameAutocompleter.java new file mode 100644 index 0000000000..00cadc1e22 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/NameAutocompleter.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2018, John Pettenger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hiscore; + +import com.google.common.collect.EvictingQueue; +import com.google.inject.Inject; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import javax.inject.Singleton; +import javax.swing.SwingUtilities; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.FriendsChatManager; +import net.runelite.api.Client; +import net.runelite.api.Friend; +import net.runelite.api.Nameable; +import net.runelite.api.NameableContainer; +import net.runelite.api.Player; + +@Slf4j +@Singleton +class NameAutocompleter implements KeyListener +{ + /** + * Non-breaking space character. + */ + private static final String NBSP = Character.toString((char)160); + + /** + * Character class for characters that cannot be in an RSN. + */ + private static final Pattern INVALID_CHARS = Pattern.compile("[^a-zA-Z0-9_ -]"); + + private static final int MAX_SEARCH_HISTORY = 25; + + private final Client client; + private final HiscoreConfig hiscoreConfig; + + private final EvictingQueue searchHistory = EvictingQueue.create(MAX_SEARCH_HISTORY); + + /** + * The name currently being autocompleted. + */ + private String autocompleteName; + + /** + * Pattern for the name currently being autocompleted. + */ + private Pattern autocompleteNamePattern; + + @Inject + private NameAutocompleter(@Nullable Client client, HiscoreConfig hiscoreConfig) + { + this.client = client; + this.hiscoreConfig = hiscoreConfig; + } + + @Override + public void keyPressed(KeyEvent e) + { + + } + + @Override + public void keyReleased(KeyEvent e) + { + + } + + @Override + public void keyTyped(KeyEvent e) + { + if (!hiscoreConfig.autocomplete()) + { + return; + } + + final JTextComponent input = (JTextComponent)e.getSource(); + final String inputText = input.getText(); + + // Only autocomplete if the selection end is at the end of the text. + if (input.getSelectionEnd() != inputText.length()) + { + return; + } + + // Character to be inserted at the selection start. + final String charToInsert = Character.toString(e.getKeyChar()); + + // Don't attempt to autocomplete if the name is invalid. + // This condition is also true when the user presses a key like backspace. + if (INVALID_CHARS.matcher(charToInsert).find() + || INVALID_CHARS.matcher(inputText).find()) + { + return; + } + + // Check if we are already autocompleting. + if (autocompleteName != null && autocompleteNamePattern.matcher(inputText).matches()) + { + if (isExpectedNext(input, charToInsert)) + { + try + { + // Insert the character and move the selection. + final int insertIndex = input.getSelectionStart(); + Document doc = input.getDocument(); + doc.remove(insertIndex, 1); + doc.insertString(insertIndex, charToInsert, null); + input.select(insertIndex + 1, input.getSelectionEnd()); + } + catch (BadLocationException ex) + { + log.warn("Could not insert character.", ex); + } + + // Prevent default behavior. + e.consume(); + } + else // Character to insert does not match current autocompletion. Look for another name. + { + newAutocomplete(e); + } + } + else // Search for a name to autocomplete + { + newAutocomplete(e); + } + } + + private void newAutocomplete(KeyEvent e) + { + final JTextComponent input = (JTextComponent)e.getSource(); + final String inputText = input.getText(); + final String nameStart = inputText.substring(0, input.getSelectionStart()) + e.getKeyChar(); + + if (findAutocompleteName(nameStart)) + { + // Assert this.autocompleteName != null + final String name = this.autocompleteName; + SwingUtilities.invokeLater(() -> + { + try + { + input.getDocument().insertString( + nameStart.length(), + name.substring(nameStart.length()), + null); + input.select(nameStart.length(), name.length()); + } + catch (BadLocationException ex) + { + log.warn("Could not autocomplete name.", ex); + } + }); + } + } + + private boolean findAutocompleteName(String nameStart) + { + final Pattern pattern; + Optional autocompleteName; + + // Pattern to match names that start with nameStart. + // Allows spaces to be represented as common whitespaces, underscores, + // hyphens, or non-breaking spaces. + // Matching non-breaking spaces is necessary because the API + // returns non-breaking spaces when a name has whitespace. + pattern = Pattern.compile( + "(?i)^" + nameStart.replaceAll("[ _-]", "[ _" + NBSP + "-]") + ".+?"); + + if (client == null) + { + return false; + } + + // Search all previous successful queries + autocompleteName = searchHistory.stream() + .filter(n -> pattern.matcher(n).matches()) + .findFirst(); + + // Search friends if previous searches weren't matched + if (!autocompleteName.isPresent()) + { + NameableContainer friendContainer = client.getFriendContainer(); + if (friendContainer != null) + { + autocompleteName = Arrays.stream(friendContainer.getMembers()) + .map(Nameable::getName) + .filter(n -> pattern.matcher(n).matches()) + .findFirst(); + } + } + + // Search friends chat if a friend wasn't found + if (!autocompleteName.isPresent()) + { + final FriendsChatManager friendsChatManager = client.getFriendsChatManager(); + if (friendsChatManager != null) + { + autocompleteName = Arrays.stream(friendsChatManager.getMembers()) + .map(Nameable::getName) + .filter(n -> pattern.matcher(n).matches()) + .findFirst(); + } + } + + // Search cached players if a friend wasn't found + if (!autocompleteName.isPresent()) + { + final Player[] cachedPlayers = client.getCachedPlayers(); + autocompleteName = Arrays.stream(cachedPlayers) + .filter(Objects::nonNull) + .map(Player::getName) + .filter(n -> pattern.matcher(n).matches()) + .findFirst(); + } + + if (autocompleteName.isPresent()) + { + this.autocompleteName = autocompleteName.get().replace(NBSP, " "); + this.autocompleteNamePattern = Pattern.compile( + "(?i)^" + this.autocompleteName.replaceAll("[ _-]", "[ _-]") + "$"); + } + else + { + this.autocompleteName = null; + this.autocompleteNamePattern = null; + } + + return autocompleteName.isPresent(); + } + + void addToSearchHistory(@NonNull String name) + { + if (!searchHistory.contains(name)) + { + searchHistory.offer(name); + } + } + + private boolean isExpectedNext(JTextComponent input, String nextChar) + { + String expected; + if (input.getSelectionStart() < input.getSelectionEnd()) + { + try + { + expected = input.getText(input.getSelectionStart(), 1); + } + catch (BadLocationException ex) + { + log.warn("Could not get first character from input selection.", ex); + return false; + } + } + else + { + expected = ""; + } + return nextChar.equalsIgnoreCase(expected); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java new file mode 100644 index 0000000000..87df9baadd --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017, Robin Weymans + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hunter; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("hunterplugin") +public interface HunterConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "hexColorOpenTrap", + name = "Open trap", + description = "Color of open trap timer" + ) + default Color getOpenTrapColor() + { + return Color.YELLOW; + } + + @ConfigItem( + position = 2, + keyName = "hexColorFullTrap", + name = "Full trap", + description = "Color of full trap timer" + ) + default Color getFullTrapColor() + { + return Color.GREEN; + } + + @ConfigItem( + position = 3, + keyName = "hexColorEmptyTrap", + name = "Empty trap", + description = "Color of empty trap timer" + ) + default Color getEmptyTrapColor() + { + return Color.RED; + } + + @ConfigItem( + position = 4, + keyName = "hexColorTransTrap", + name = "Transitioning trap", + description = "Color of transitioning trap timer" + ) + default Color getTransTrapColor() + { + return Color.ORANGE; + } + + @ConfigItem( + position = 5, + keyName = "maniacalMonkeyNotify", + name = "Maniacal monkey notification", + description = "Send notification when maniacal monkey is caught or you fail to catch." + ) + default boolean maniacalMonkeyNotify() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java new file mode 100644 index 0000000000..8a6fc7e2ad --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2017, Robin Weymans + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hunter; + +import com.google.inject.Provides; +import java.time.Instant; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.ObjectID; +import net.runelite.api.Player; +import net.runelite.api.Tile; +import net.runelite.api.coords.Direction; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.events.ConfigChanged; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameTick; +import net.runelite.client.Notifier; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@Slf4j +@PluginDescriptor( + name = "Hunter", + description = "Show the state of your traps", + tags = {"overlay", "skilling", "timers"} +) +public class HunterPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private OverlayManager overlayManager; + + @Inject + private TrapOverlay overlay; + + @Inject + private Notifier notifier; + + @Inject + private HunterConfig config; + + @Getter + private final Map traps = new HashMap<>(); + + @Getter + private Instant lastActionTime = Instant.ofEpochMilli(0); + + private WorldPoint lastTickLocalPlayerLocation; + + @Provides + HunterConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(HunterConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + overlay.updateConfig(); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + lastActionTime = Instant.ofEpochMilli(0); + traps.clear(); + } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) + { + final GameObject gameObject = event.getGameObject(); + final WorldPoint trapLocation = gameObject.getWorldLocation(); + final HunterTrap myTrap = traps.get(trapLocation); + final Player localPlayer = client.getLocalPlayer(); + + switch (gameObject.getId()) + { + /* + * ------------------------------------------------------------------------------ + * Placing traps + * ------------------------------------------------------------------------------ + */ + case ObjectID.DEADFALL: // Deadfall trap placed + if (localPlayer.getWorldLocation().distanceTo(trapLocation) <= 2) + { + log.debug("Trap placed by \"{}\" on {}", localPlayer.getName(), trapLocation); + traps.put(trapLocation, new HunterTrap(gameObject)); + lastActionTime = Instant.now(); + } + break; + + case ObjectID.MONKEY_TRAP: // Maniacal monkey trap placed + // If player is right next to "object" trap assume that player placed the trap + if (localPlayer.getWorldLocation().distanceTo(trapLocation) <= 2) + { + log.debug("Trap placed by \"{}\" on {}", localPlayer.getName(), trapLocation); + traps.put(trapLocation, new HunterTrap(gameObject)); + lastActionTime = Instant.now(); + } + break; + + case ObjectID.MAGIC_BOX: // Imp box placed + case ObjectID.BOX_TRAP_9380: // Box trap placed + case ObjectID.BIRD_SNARE_9345: // Bird snare placed + // If the player is on that tile, assume he is the one that placed the trap + // Note that a player can move and set up a trap in the same tick, and this + // event runs after the player movement has been updated, so we need to + // compare to the trap location to the last location of the player. + if (lastTickLocalPlayerLocation != null + && trapLocation.distanceTo(lastTickLocalPlayerLocation) == 0) + { + log.debug("Trap placed by \"{}\" on {}", localPlayer.getName(), localPlayer.getWorldLocation()); + traps.put(trapLocation, new HunterTrap(gameObject)); + lastActionTime = Instant.now(); + } + break; + + case ObjectID.NET_TRAP_9343: // Net trap placed at green sallys + case ObjectID.NET_TRAP: // Net trap placed at orange sallys + case ObjectID.NET_TRAP_8992: // Net trap placed at red sallys + case ObjectID.NET_TRAP_9002: // Net trap placed at black sallys + if (lastTickLocalPlayerLocation != null + && trapLocation.distanceTo(lastTickLocalPlayerLocation) == 0) + { + // Net traps facing to the north and east must have their tile translated. + // As otherwise, the wrong tile is stored. + Direction trapOrientation = gameObject.getOrientation().getNearestDirection(); + WorldPoint translatedTrapLocation = trapLocation; + + switch (trapOrientation) + { + case NORTH: + translatedTrapLocation = trapLocation.dy(1); + break; + case EAST: + translatedTrapLocation = trapLocation.dx(1); + break; + } + + log.debug("Trap placed by \"{}\" on {}", localPlayer.getName(), translatedTrapLocation); + traps.put(translatedTrapLocation, new HunterTrap(gameObject)); + lastActionTime = Instant.now(); + } + break; + + /* + * ------------------------------------------------------------------------------ + * Catching stuff + * ------------------------------------------------------------------------------ + */ + case ObjectID.MAGIC_BOX_19226: // Imp caught + case ObjectID.SHAKING_BOX: // Black chinchompa caught + case ObjectID.SHAKING_BOX_9382: // Grey chinchompa caught + case ObjectID.SHAKING_BOX_9383: // Red chinchompa caught + case ObjectID.SHAKING_BOX_9384: // Ferret caught + case ObjectID.BOULDER_20648: // Prickly kebbit caught + case ObjectID.BOULDER_20649: // Sabre-tooth kebbit caught + case ObjectID.BOULDER_20650: // Barb-tailed kebbit caught + case ObjectID.BOULDER_20651: // Wild kebbit caught + case ObjectID.BIRD_SNARE_9373: // Crimson swift caught + case ObjectID.BIRD_SNARE_9375: // Cerulean twitch caught + case ObjectID.BIRD_SNARE_9377: // Golden warbler caught + case ObjectID.BIRD_SNARE_9379: // Copper longtail caught + case ObjectID.BIRD_SNARE_9348: // Tropical wagtail caught + case ObjectID.NET_TRAP_9004: // Green sally caught + case ObjectID.NET_TRAP_8986: // Red sally caught + case ObjectID.NET_TRAP_8734: // Orange sally caught + case ObjectID.NET_TRAP_8996: // Black sally caught + case ObjectID.LARGE_BOULDER_28830: // Maniacal monkey tail obtained + case ObjectID.LARGE_BOULDER_28831: // Maniacal monkey tail obtained + if (myTrap != null) + { + myTrap.setState(HunterTrap.State.FULL); + myTrap.resetTimer(); + lastActionTime = Instant.now(); + + if (config.maniacalMonkeyNotify() && myTrap.getObjectId() == ObjectID.MONKEY_TRAP) + { + notifier.notify("You've caught part of a monkey's tail."); + } + } + + break; + /* + * ------------------------------------------------------------------------------ + * Failed catch + * ------------------------------------------------------------------------------ + */ + case ObjectID.MAGIC_BOX_FAILED: //Empty imp box + case ObjectID.BOX_TRAP_9385: //Empty box trap + case ObjectID.BIRD_SNARE: //Empty box trap + if (myTrap != null) + { + myTrap.setState(HunterTrap.State.EMPTY); + myTrap.resetTimer(); + lastActionTime = Instant.now(); + } + + break; + /* + * ------------------------------------------------------------------------------ + * Transitions + * ------------------------------------------------------------------------------ + */ + // Imp entering box + case ObjectID.MAGIC_BOX_19225: + + // Black chin shaking box + case ObjectID.BOX_TRAP: + case ObjectID.BOX_TRAP_2026: + case ObjectID.BOX_TRAP_2028: + case ObjectID.BOX_TRAP_2029: + + // Red chin shaking box + case ObjectID.BOX_TRAP_9381: + case ObjectID.BOX_TRAP_9390: + case ObjectID.BOX_TRAP_9391: + case ObjectID.BOX_TRAP_9392: + case ObjectID.BOX_TRAP_9393: + + // Grey chin shaking box + case ObjectID.BOX_TRAP_9386: + case ObjectID.BOX_TRAP_9387: + case ObjectID.BOX_TRAP_9388: + + // Ferret shaking box + case ObjectID.BOX_TRAP_9394: + case ObjectID.BOX_TRAP_9396: + case ObjectID.BOX_TRAP_9397: + + // Bird traps + case ObjectID.BIRD_SNARE_9346: + case ObjectID.BIRD_SNARE_9347: + case ObjectID.BIRD_SNARE_9349: + case ObjectID.BIRD_SNARE_9374: + case ObjectID.BIRD_SNARE_9376: + case ObjectID.BIRD_SNARE_9378: + + // Deadfall trap + case ObjectID.DEADFALL_19218: + case ObjectID.DEADFALL_19851: + case ObjectID.DEADFALL_20128: + case ObjectID.DEADFALL_20129: + case ObjectID.DEADFALL_20130: + case ObjectID.DEADFALL_20131: + + // Net trap + case ObjectID.NET_TRAP_9003: + case ObjectID.NET_TRAP_9005: + case ObjectID.NET_TRAP_8972: + case ObjectID.NET_TRAP_8974: + case ObjectID.NET_TRAP_8985: + case ObjectID.NET_TRAP_8987: + case ObjectID.NET_TRAP_8993: + case ObjectID.NET_TRAP_8997: + + // Maniacal monkey boulder trap + case ObjectID.MONKEY_TRAP_28828: + case ObjectID.MONKEY_TRAP_28829: + if (myTrap != null) + { + myTrap.setState(HunterTrap.State.TRANSITION); + } + break; + } + } + + /** + * Iterates over all the traps that were placed by the local player and + * checks if the trap is still there. If the trap is gone, it removes + * the trap from the local players trap collection. + */ + @Subscribe + public void onGameTick(GameTick event) + { + // Check if all traps are still there, and remove the ones that are not. + Iterator> it = traps.entrySet().iterator(); + Tile[][][] tiles = client.getScene().getTiles(); + + Instant expire = Instant.now().minus(HunterTrap.TRAP_TIME.multipliedBy(2)); + + while (it.hasNext()) + { + Map.Entry entry = it.next(); + HunterTrap trap = entry.getValue(); + WorldPoint world = entry.getKey(); + LocalPoint local = LocalPoint.fromWorld(client, world); + + // Not within the client's viewport + if (local == null) + { + // Cull very old traps + if (trap.getPlacedOn().isBefore(expire)) + { + log.debug("Trap removed from personal trap collection due to timeout, {} left", traps.size()); + it.remove(); + continue; + } + continue; + } + + Tile tile = tiles[world.getPlane()][local.getSceneX()][local.getSceneY()]; + GameObject[] objects = tile.getGameObjects(); + + boolean containsBoulder = false; + boolean containsAnything = false; + boolean containsYoungTree = false; + for (GameObject object : objects) + { + if (object != null) + { + containsAnything = true; + if (object.getId() == ObjectID.BOULDER_19215 || object.getId() == ObjectID.LARGE_BOULDER) + { + containsBoulder = true; + break; + } + + // Check for young trees (used while catching salamanders) in the tile. + // Otherwise, hunter timers will never disappear after a trap is dismantled + if (object.getId() == ObjectID.YOUNG_TREE_8732 || object.getId() == ObjectID.YOUNG_TREE_8990 || + object.getId() == ObjectID.YOUNG_TREE_9000 || object.getId() == ObjectID.YOUNG_TREE_9341) + { + containsYoungTree = true; + } + } + } + + if (!containsAnything || containsYoungTree) + { + it.remove(); + log.debug("Trap removed from personal trap collection, {} left", traps.size()); + } + else if (containsBoulder) // For traps like deadfalls. This is different because when the trap is gone, there is still a GameObject (boulder) + { + it.remove(); + log.debug("Special trap removed from personal trap collection, {} left", traps.size()); + + // Case we have notifications enabled and the action was not manual, throw notification + if (config.maniacalMonkeyNotify() && trap.getObjectId() == ObjectID.MONKEY_TRAP && + !trap.getState().equals(HunterTrap.State.FULL) && !trap.getState().equals(HunterTrap.State.OPEN)) + { + notifier.notify("The monkey escaped."); + } + } + } + + lastTickLocalPlayerLocation = client.getLocalPlayer().getWorldLocation(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("hunterplugin")) + { + overlay.updateConfig(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java new file mode 100644 index 0000000000..4a28288994 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017, Robin Weymans + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hunter; + +import java.time.Duration; +import java.time.Instant; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.GameObject; +import net.runelite.api.coords.WorldPoint; + +/** + * Wrapper class for a GameObject that represents a hunter trap. + */ +class HunterTrap +{ + /** + * A hunter trap stays up 1 minute before collapsing. + */ + static final Duration TRAP_TIME = Duration.ofMinutes(1); + + /** + * The time in milliseconds when the trap was placed. + */ + @Getter + private Instant placedOn; + + /** + * The state of the trap. + */ + @Getter + @Setter + private State state; + + /** + * The ID of the game object this is representing + */ + @Getter + private int objectId; + + @Getter + private WorldPoint worldLocation; + + /** + * The states a trap can be in. + */ + enum State + { + /** + * A laid out trap. + */ + OPEN, + /** + * A trap that is empty. + */ + EMPTY, + /** + * A trap that caught something. + */ + FULL, + /** + * A trap that is closing. + */ + TRANSITION + } + + /** + * Constructor for a HunterTrap object + * + * @param gameObject The gameobject thats corresponds with this trap. + */ + HunterTrap(GameObject gameObject) + { + this.state = State.OPEN; + this.placedOn = Instant.now(); + this.objectId = gameObject.getId(); + this.worldLocation = gameObject.getWorldLocation(); + } + + /** + * Calculates how much time is left before the trap is collapsing. + * + * @return Value between 0 and 1. 0 means the trap was laid moments ago. + * 1 is a trap that's about to collapse. + */ + public double getTrapTimeRelative() + { + Duration duration = Duration.between(placedOn, Instant.now()); + return duration.compareTo(TRAP_TIME) < 0 ? (double) duration.toMillis() / TRAP_TIME.toMillis() : 1; + } + + /** + * Resets the time value when the trap was placed. + */ + public void resetTimer() + { + placedOn = Instant.now(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java new file mode 100644 index 0000000000..1e0f23b358 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2017, Robin Weymans + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.hunter; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Perspective; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.ProgressPieComponent; + +/** + * Represents the overlay that shows timers on traps that are placed by the + * player. + */ +public class TrapOverlay extends Overlay +{ + /** + * The timer is low when only 25% is left. + */ + private static final double TIMER_LOW = 0.25; // When the timer is under a quarter left, if turns red. + + private final Client client; + private final HunterPlugin plugin; + private final HunterConfig config; + + private Color colorOpen, colorOpenBorder; + private Color colorEmpty, colorEmptyBorder; + private Color colorFull, colorFullBorder; + private Color colorTrans, colorTransBorder; + + @Inject + TrapOverlay(Client client, HunterPlugin plugin, HunterConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.plugin = plugin; + this.config = config; + this.client = client; + } + + @Override + public Dimension render(Graphics2D graphics) + { + drawTraps(graphics); + return null; + } + + /** + * Updates the timer colors. + */ + public void updateConfig() + { + colorEmptyBorder = config.getEmptyTrapColor(); + colorEmpty = new Color(colorEmptyBorder.getRed(), colorEmptyBorder.getGreen(), colorEmptyBorder.getBlue(), 100); + colorFullBorder = config.getFullTrapColor(); + colorFull = new Color(colorFullBorder.getRed(), colorFullBorder.getGreen(), colorFullBorder.getBlue(), 100); + colorOpenBorder = config.getOpenTrapColor(); + colorOpen = new Color(colorOpenBorder.getRed(), colorOpenBorder.getGreen(), colorOpenBorder.getBlue(), 100); + colorTransBorder = config.getTransTrapColor(); + colorTrans = new Color(colorTransBorder.getRed(), colorTransBorder.getGreen(), colorTransBorder.getBlue(), 100); + } + + /** + * Iterates over all the traps that were placed by the local player, and + * draws a circle or a timer on the trap, depending on the trap state. + * + * @param graphics + */ + private void drawTraps(Graphics2D graphics) + { + for (Map.Entry entry : plugin.getTraps().entrySet()) + { + HunterTrap trap = entry.getValue(); + + switch (trap.getState()) + { + case OPEN: + drawTimerOnTrap(graphics, trap, colorOpen, colorOpenBorder, colorEmpty, colorOpenBorder); + break; + case EMPTY: + drawTimerOnTrap(graphics, trap, colorEmpty, colorEmptyBorder, colorEmpty, colorEmptyBorder); + break; + case FULL: + drawTimerOnTrap(graphics, trap, colorFull, colorFullBorder, colorFull, colorFullBorder); + break; + case TRANSITION: + drawCircleOnTrap(graphics, trap, colorTrans, colorTransBorder); + break; + } + } + } + + /** + * Draws a timer on a given trap. + * + * @param graphics + * @param trap The trap on which the timer needs to be drawn + * @param fill The fill color of the timer + * @param border The border color of the timer + * @param fillTimeLow The fill color of the timer when it is low + * @param borderTimeLow The border color of the timer when it is low + */ + private void drawTimerOnTrap(Graphics2D graphics, HunterTrap trap, Color fill, Color border, Color fillTimeLow, Color borderTimeLow) + { + if (trap.getWorldLocation().getPlane() != client.getPlane()) + { + return; + } + LocalPoint localLoc = LocalPoint.fromWorld(client, trap.getWorldLocation()); + if (localLoc == null) + { + return; + } + net.runelite.api.Point loc = Perspective.localToCanvas(client, localLoc, client.getPlane()); + + if (loc == null) + { + return; + } + + double timeLeft = 1 - trap.getTrapTimeRelative(); + + ProgressPieComponent pie = new ProgressPieComponent(); + pie.setFill(timeLeft > TIMER_LOW ? fill : fillTimeLow); + pie.setBorderColor(timeLeft > TIMER_LOW ? border : borderTimeLow); + pie.setPosition(loc); + pie.setProgress(timeLeft); + pie.render(graphics); + } + + /** + * Draws a timer on a given trap. + * + * @param graphics + * @param trap The trap on which the timer needs to be drawn + * @param fill The fill color of the timer + * @param border The border color of the timer + */ + private void drawCircleOnTrap(Graphics2D graphics, HunterTrap trap, Color fill, Color border) + { + if (trap.getWorldLocation().getPlane() != client.getPlane()) + { + return; + } + LocalPoint localLoc = LocalPoint.fromWorld(client, trap.getWorldLocation()); + if (localLoc == null) + { + return; + } + net.runelite.api.Point loc = Perspective.localToCanvas(client, localLoc, client.getPlane()); + + ProgressPieComponent pie = new ProgressPieComponent(); + pie.setFill(fill); + pie.setBorderColor(border); + pie.setPosition(loc); + pie.setProgress(1); + pie.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/Impling.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/Impling.java new file mode 100644 index 0000000000..69aa92f625 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/Impling.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.NpcID; + +@AllArgsConstructor +@Getter +enum Impling +{ + BABY(ImplingType.BABY, NpcID.BABY_IMPLING), + BABY_2(ImplingType.BABY, NpcID.BABY_IMPLING_1645), + + YOUNG(ImplingType.YOUNG, NpcID.YOUNG_IMPLING), + YOUNG_2(ImplingType.YOUNG, NpcID.YOUNG_IMPLING_1646), + + GOURMET(ImplingType.GOURMET, NpcID.GOURMET_IMPLING), + GOURMET_2(ImplingType.GOURMET, NpcID.GOURMET_IMPLING_1647), + + EARTH(ImplingType.EARTH, NpcID.EARTH_IMPLING), + EARTH_2(ImplingType.EARTH, NpcID.EARTH_IMPLING_1648), + + ESSENCE(ImplingType.ESSENCE, NpcID.ESSENCE_IMPLING), + ESSENCE_2(ImplingType.ESSENCE, NpcID.ESSENCE_IMPLING_1649), + + ECLECTIC(ImplingType.ECLECTIC, NpcID.ECLECTIC_IMPLING), + ECLECTIC_2(ImplingType.ECLECTIC, NpcID.ECLECTIC_IMPLING_1650), + + NATURE(ImplingType.NATURE, NpcID.NATURE_IMPLING), + NATURE_2(ImplingType.NATURE, NpcID.NATURE_IMPLING_1651), + + MAGPIE(ImplingType.MAGPIE, NpcID.MAGPIE_IMPLING), + MAGPIE_2(ImplingType.MAGPIE, NpcID.MAGPIE_IMPLING_1652), + + NINJA(ImplingType.NINJA, NpcID.NINJA_IMPLING), + NINJA_2(ImplingType.NINJA, NpcID.NINJA_IMPLING_1653), + + CRYSTAL(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING), + CRYSTAL_2(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8742), + CRYSTAL_3(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8743), + CRYSTAL_4(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8744), + CRYSTAL_5(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8745), + CRYSTAL_6(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8746), + CRYSTAL_7(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8747), + CRYSTAL_8(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8748), + CRYSTAL_9(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8749), + CRYSTAL_10(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8750), + CRYSTAL_11(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8751), + CRYSTAL_12(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8752), + CRYSTAL_13(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8753), + CRYSTAL_14(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8754), + CRYSTAL_15(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8755), + CRYSTAL_16(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8756), + CRYSTAL_17(ImplingType.CRYSTAL, NpcID.CRYSTAL_IMPLING_8757), + + DRAGON(ImplingType.DRAGON, NpcID.DRAGON_IMPLING), + DRAGON_2(ImplingType.DRAGON, NpcID.DRAGON_IMPLING_1654), + + LUCKY(ImplingType.LUCKY, NpcID.LUCKY_IMPLING), + LUCKY_2(ImplingType.LUCKY, NpcID.LUCKY_IMPLING_7302); + + private ImplingType implingType; + private final int npcId; + + private static final Map IMPLINGS; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (Impling impling : values()) + { + builder.put(impling.npcId, impling); + } + + IMPLINGS = builder.build(); + } + + static Impling findImpling(int npcId) + { + return IMPLINGS.get(npcId); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingMinimapOverlay.java new file mode 100644 index 0000000000..ab35f9f945 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingMinimapOverlay.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018, Seth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.NPC; +import net.runelite.api.Point; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; + +public class ImplingMinimapOverlay extends Overlay +{ + private final ImplingsPlugin plugin; + private final ImplingsConfig config; + + @Inject + private ImplingMinimapOverlay(ImplingsPlugin plugin, ImplingsConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics) + { + List imps = plugin.getImplings(); + if (imps.isEmpty()) + { + return null; + } + + for (NPC imp : imps) + { + Point impLocation = imp.getMinimapLocation(); + Color color = plugin.npcToColor(imp); + if (!plugin.showNpc(imp) || impLocation == null || color == null) + { + continue; + } + + OverlayUtil.renderMinimapLocation(graphics, impLocation, color); + + if (config.showName()) + { + Point textLocation = new Point(impLocation.getX() + 1, impLocation.getY()); + OverlayUtil.renderTextLocation(graphics, textLocation, imp.getName(), color); + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingSpawn.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingSpawn.java new file mode 100644 index 0000000000..d624c02682 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingSpawn.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018, Mantautas Jurksa + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.coords.WorldPoint; + +/** + * @author Juzzed + */ +@AllArgsConstructor +@Getter +enum ImplingSpawn +{ + //Baby spawns + SPAWN_BABY1(new WorldPoint(2563, 4291, 0), ImplingType.BABY), + SPAWN_BABY2(new WorldPoint(2563, 4348, 0), ImplingType.BABY), + SPAWN_BABY3(new WorldPoint(2569, 4323, 0), ImplingType.BABY), + SPAWN_BABY4(new WorldPoint(2571, 4305, 0), ImplingType.BABY), + SPAWN_BABY5(new WorldPoint(2581, 4300, 0), ImplingType.BABY), + SPAWN_BABY6(new WorldPoint(2596, 4296, 0), ImplingType.BABY), + SPAWN_BABY7(new WorldPoint(2609, 4339, 0), ImplingType.BABY), + SPAWN_BABY8(new WorldPoint(2610, 4304, 0), ImplingType.BABY), + SPAWN_BABY9(new WorldPoint(2615, 4322, 0), ImplingType.BABY), + SPAWN_BABY10(new WorldPoint(2620, 4291, 0), ImplingType.BABY), + SPAWN_BABY11(new WorldPoint(2620, 4348, 0), ImplingType.BABY), + + //Young spawns + SPAWN_YOUNG1(new WorldPoint(2564, 4321, 0), ImplingType.YOUNG), + SPAWN_YOUNG2(new WorldPoint(2573, 4330, 0), ImplingType.YOUNG), + SPAWN_YOUNG3(new WorldPoint(2574, 4321, 0), ImplingType.YOUNG), + SPAWN_YOUNG4(new WorldPoint(2590, 4348, 0), ImplingType.YOUNG), + SPAWN_YOUNG5(new WorldPoint(2592, 4291, 0), ImplingType.YOUNG), + SPAWN_YOUNG6(new WorldPoint(2595, 4343, 0), ImplingType.YOUNG), + SPAWN_YOUNG7(new WorldPoint(2612, 4327, 0), ImplingType.YOUNG), + SPAWN_YOUNG8(new WorldPoint(2612, 4309, 0), ImplingType.YOUNG), + SPAWN_YOUNG9(new WorldPoint(2619, 4322, 0), ImplingType.YOUNG), + SPAWN_YOUNG10(new WorldPoint(2587, 4300, 0), ImplingType.YOUNG), + + //Gourmet spawns + SPAWN_GOURMET1(new WorldPoint(2568, 4296, 0), ImplingType.GOURMET), + SPAWN_GOURMET2(new WorldPoint(2569, 4327, 0), ImplingType.GOURMET), + SPAWN_GOURMET3(new WorldPoint(2574, 4311, 0), ImplingType.GOURMET), + SPAWN_GOURMET4(new WorldPoint(2574, 4311, 0), ImplingType.GOURMET), + SPAWN_GOURMET5(new WorldPoint(2585, 4296, 0), ImplingType.GOURMET), + SPAWN_GOURMET6(new WorldPoint(2597, 4293, 0), ImplingType.GOURMET), + SPAWN_GOURMET7(new WorldPoint(2609, 4317, 0), ImplingType.GOURMET), + SPAWN_GOURMET8(new WorldPoint(2615, 4298, 0), ImplingType.GOURMET), + SPAWN_GOURMET9(new WorldPoint(2618, 4321, 0), ImplingType.GOURMET), + + //Earth spawns + SPAWN_EARTH1(new WorldPoint(2570, 4330, 0), ImplingType.EARTH), + SPAWN_EARTH2(new WorldPoint(2598, 4340, 0), ImplingType.EARTH), + SPAWN_EARTH3(new WorldPoint(2587, 4342, 0), ImplingType.EARTH), + SPAWN_EARTH4(new WorldPoint(2612, 4310, 0), ImplingType.EARTH), + SPAWN_EARTH5(new WorldPoint(2611, 4334, 0), ImplingType.EARTH), + + //Eclectic spawns + SPAWN_ECLECTIC1(new WorldPoint(2567, 4319, 0), ImplingType.ECLECTIC), + SPAWN_ECLECTIC2(new WorldPoint(2591, 4340, 0), ImplingType.ECLECTIC), + SPAWN_ECLECTIC3(new WorldPoint(2591, 4295, 0), ImplingType.ECLECTIC), + SPAWN_ECLECTIC4(new WorldPoint(2615, 4326, 0), ImplingType.ECLECTIC); + + private final WorldPoint spawnLocation; + private final ImplingType type; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingType.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingType.java new file mode 100644 index 0000000000..f1ae90ebbd --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingType.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +enum ImplingType +{ + BABY("Baby"), + YOUNG("Young"), + GOURMET("Gourmet"), + EARTH("Earth"), + ESSENCE("Essence"), + ECLECTIC("Eclectic"), + NATURE("Nature"), + MAGPIE("Magpie"), + NINJA("Ninja"), + CRYSTAL("Crystal"), + DRAGON("Dragon"), + LUCKY("Lucky"); + + private final String name; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsConfig.java new file mode 100644 index 0000000000..ac88a11214 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsConfig.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2017, Robin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; + +/** + * + * @author robin + */ +@ConfigGroup("implings") +public interface ImplingsConfig extends Config +{ + enum ImplingMode + { + NONE, + HIGHLIGHT, + NOTIFY + } + + @ConfigSection( + name = "Impling Type Settings", + description = "Configuration for each type of impling", + position = 99 + ) + String implingSection = "implings"; + + @ConfigItem( + position = 1, + keyName = "showbaby", + name = "Show Baby implings", + description = "Configures whether or not Baby impling tags are displayed", + section = implingSection + ) + default ImplingMode showBaby() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 2, + keyName = "babyColor", + name = "Baby impling color", + description = "Text color for Baby implings", + section = implingSection + ) + default Color getBabyColor() + { + return new Color(177, 143, 179); + } + + @ConfigItem( + position = 3, + keyName = "showyoung", + name = "Show Young implings", + description = "Configures whether or not Young impling tags are displayed", + section = implingSection + ) + default ImplingMode showYoung() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 4, + keyName = "youngColor", + name = "Young impling color", + description = "Text color for Young implings", + section = implingSection + ) + default Color getYoungColor() + { + return new Color(175, 164, 136); + } + + @ConfigItem( + position = 5, + keyName = "showgourmet", + name = "Show Gourmet implings", + description = "Configures whether or not Gourmet impling tags are displayed", + section = implingSection + ) + default ImplingMode showGourmet() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 6, + keyName = "gourmetColor", + name = "Gourmet impling color", + description = "Text color for Gourmet implings", + section = implingSection + ) + default Color getGourmetColor() + { + return new Color(169, 131, 98); + } + + @ConfigItem( + position = 7, + keyName = "showearth", + name = "Show Earth implings", + description = "Configures whether or not Earth impling tags are displayed", + section = implingSection + ) + default ImplingMode showEarth() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 8, + keyName = "earthColor", + name = "Earth impling color", + description = "Text color for Earth implings", + section = implingSection + ) + default Color getEarthColor() + { + return new Color(62, 86, 64); + } + + @ConfigItem( + position = 9, + keyName = "showessence", + name = "Show Essence implings", + description = "Configures whether or not Essence impling tags are displayed", + section = implingSection + ) + default ImplingMode showEssence() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 10, + keyName = "essenceColor", + name = "Essence impling color", + description = "Text color for Essence implings", + section = implingSection + ) + default Color getEssenceColor() + { + return new Color(32, 89, 90); + } + + @ConfigItem( + position = 11, + keyName = "showeclectic", + name = "Show Eclectic implings", + description = "Configures whether or not Eclectic impling tags are displayed", + section = implingSection + ) + default ImplingMode showEclectic() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 12, + keyName = "eclecticColor", + name = "Eclectic impling color", + description = "Text color for Eclectic implings", + section = implingSection + ) + default Color getEclecticColor() + { + return new Color(145, 155, 69); + } + + @ConfigItem( + position = 13, + keyName = "shownature", + name = "Show Nature implings", + description = "Configures whether or not Nature impling tags are displayed", + section = implingSection + ) + default ImplingMode showNature() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 14, + keyName = "natureColor", + name = "Nature impling color", + description = "Text color for Nature implings", + section = implingSection + ) + default Color getNatureColor() + { + return new Color(92, 138, 95); + } + + @ConfigItem( + position = 15, + keyName = "showmagpie", + name = "Show Magpie implings", + description = "Configures whether or not Magpie impling tags are displayed", + section = implingSection + ) + default ImplingMode showMagpie() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 16, + keyName = "magpieColor", + name = "Magpie impling color", + description = "Text color for Magpie implings", + section = implingSection + ) + default Color getMagpieColor() + { + return new Color(142, 142, 19); + } + + @ConfigItem( + position = 17, + keyName = "showninja", + name = "Show Ninja implings", + description = "Configures whether or not Ninja impling tags are displayed", + section = implingSection + ) + default ImplingMode showNinja() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 18, + keyName = "ninjaColor", + name = "Ninja impling color", + description = "Text color for Ninja implings", + section = implingSection + ) + default Color getNinjaColor() + { + return new Color(71, 70, 75); + } + + @ConfigItem( + position = 19, + keyName = "showCrystal", + name = "Show Crystal implings", + description = "Configures whether or not Crystal implings are displayed", + section = implingSection + ) + default ImplingMode showCrystal() + { + return ImplingMode.NONE; + } + + @ConfigItem( + position = 20, + keyName = "crystalColor", + name = "Crystal impling color", + description = "Text color for Crystal implings", + section = implingSection + ) + default Color getCrystalColor() + { + return new Color(93, 188, 210); + } + + @ConfigItem( + position = 21, + keyName = "showdragon", + name = "Show Dragon implings", + description = "Configures whether or not Dragon impling tags are displayed", + section = implingSection + ) + default ImplingMode showDragon() + { + return ImplingMode.HIGHLIGHT; + } + + @ConfigItem( + position = 22, + keyName = "dragonColor", + name = "Dragon impling color", + description = "Text color for Dragon implings", + section = implingSection + ) + default Color getDragonColor() + { + return new Color(210, 85, 75); + } + + @ConfigItem( + position = 23, + keyName = "showlucky", + name = "Show Lucky implings", + description = "Configures whether or not Lucky impling tags are displayed", + section = implingSection + ) + default ImplingMode showLucky() + { + return ImplingMode.HIGHLIGHT; + } + + @ConfigItem( + position = 24, + keyName = "luckyColor", + name = "Lucky impling color", + description = "Text color for Lucky implings", + section = implingSection + ) + default Color getLuckyColor() + { + return new Color(102, 7, 101); + } + + @ConfigItem( + position = 25, + keyName = "showspawn", + name = "Show Spawn locations", + description = "Configures whether or not spawn locations are displayed in Puro Puro" + ) + default boolean showSpawn() + { + return false; + } + + @ConfigItem( + position = 26, + keyName = "spawnColor", + name = "Impling spawn color", + description = "Text color for impling spawns in Puro Puro" + ) + default Color getSpawnColor() + { + return Color.WHITE; + } + + @ConfigItem( + position = 27, + keyName = "showname", + name = "Show name on minimap", + description = "Configures whether or not impling names are displayed on minimap" + ) + default boolean showName() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java new file mode 100644 index 0000000000..8f58fbe7f2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2017, Robin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Point; +import net.runelite.api.Perspective; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; + +/** + * @author robin + */ +public class ImplingsOverlay extends Overlay +{ + private final Client client; + private final ImplingsConfig config; + private final ImplingsPlugin plugin; + + @Inject + private ImplingsOverlay(Client client, ImplingsConfig config, ImplingsPlugin plugin) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.config = config; + this.client = client; + this.plugin = plugin; + } + + @Override + public Dimension render(Graphics2D graphics) + { + List implings = plugin.getImplings(); + + if (implings.isEmpty()) + { + return null; + } + + for (NPC imp : implings) + { + Color color = plugin.npcToColor(imp); + if (!plugin.showNpc(imp) || color == null) + { + continue; + } + + drawImp(graphics, imp, imp.getName(), color); + } + + //Draw static spawns + if (config.showSpawn()) + { + for (ImplingSpawn spawn : ImplingSpawn.values()) + { + if (plugin.showImplingType(spawn.getType()) == ImplingsConfig.ImplingMode.NONE) + { + continue; + } + + String impName = spawn.getType().getName(); + drawSpawn(graphics, spawn.getSpawnLocation(), impName, config.getSpawnColor()); + } + } + + return null; + } + + private void drawSpawn(Graphics2D graphics, WorldPoint point, String text, Color color) + { + //Don't draw spawns if Player is not in range + if (point.distanceTo(client.getLocalPlayer().getWorldLocation()) >= 32) + { + return; + } + + LocalPoint localPoint = LocalPoint.fromWorld(client, point); + if (localPoint == null) + { + return; + } + + Polygon poly = Perspective.getCanvasTilePoly(client, localPoint); + if (poly != null) + { + OverlayUtil.renderPolygon(graphics, poly, color); + } + + Point textPoint = Perspective.getCanvasTextLocation(client, graphics, localPoint, text, 0); + if (textPoint != null) + { + OverlayUtil.renderTextLocation(graphics, textPoint, text, color); + } + } + + private void drawImp(Graphics2D graphics, Actor actor, String text, Color color) + { + Polygon poly = actor.getCanvasTilePoly(); + if (poly != null) + { + OverlayUtil.renderPolygon(graphics, poly, color); + } + + Point textLocation = actor.getCanvasTextLocation(graphics, text, actor.getLogicalHeight()); + if (textLocation != null) + { + OverlayUtil.renderTextLocation(graphics, textLocation, text, color); + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsPlugin.java new file mode 100644 index 0000000000..f2c25bcbda --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsPlugin.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2017, Robin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.implings; + +import com.google.inject.Provides; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.api.GameState; +import net.runelite.api.NPC; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.NpcChanged; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.client.Notifier; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Implings", + description = "Highlight nearby implings on the minimap and on-screen", + tags = {"hunter", "minimap", "overlay", "imp"} +) +public class ImplingsPlugin extends Plugin +{ + @Getter(AccessLevel.PACKAGE) + private final List implings = new ArrayList<>(); + + @Inject + private OverlayManager overlayManager; + + @Inject + private ImplingsOverlay overlay; + + @Inject + private ImplingMinimapOverlay minimapOverlay; + + @Inject + private ImplingsConfig config; + + @Inject + private Notifier notifier; + + @Provides + ImplingsConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(ImplingsConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + overlayManager.add(minimapOverlay); + } + + @Override + protected void shutDown() + { + implings.clear(); + overlayManager.remove(overlay); + overlayManager.remove(minimapOverlay); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned npcSpawned) + { + NPC npc = npcSpawned.getNpc(); + Impling impling = Impling.findImpling(npc.getId()); + + if (impling != null) + { + if (showImplingType(impling.getImplingType()) == ImplingsConfig.ImplingMode.NOTIFY) + { + notifier.notify(impling.getImplingType().getName() + " impling is in the area"); + } + + implings.add(npc); + } + } + + @Subscribe + public void onNpcChanged(NpcChanged npcCompositionChanged) + { + NPC npc = npcCompositionChanged.getNpc(); + Impling impling = Impling.findImpling(npc.getId()); + + if (impling != null) + { + if (showImplingType(impling.getImplingType()) == ImplingsConfig.ImplingMode.NOTIFY) + { + notifier.notify(impling.getImplingType().getName() + " impling is in the area"); + } + + if (!implings.contains(npc)) + { + implings.add(npc); + } + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGIN_SCREEN || event.getGameState() == GameState.HOPPING) + { + implings.clear(); + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + if (implings.isEmpty()) + { + return; + } + + NPC npc = npcDespawned.getNpc(); + implings.remove(npc); + } + + boolean showNpc(NPC npc) + { + Impling impling = Impling.findImpling(npc.getId()); + if (impling == null) + { + return false; + } + + ImplingsConfig.ImplingMode impMode = showImplingType(impling.getImplingType()); + return impMode == ImplingsConfig.ImplingMode.HIGHLIGHT || impMode == ImplingsConfig.ImplingMode.NOTIFY; + } + + ImplingsConfig.ImplingMode showImplingType(ImplingType implingType) + { + switch (implingType) + { + case BABY: + return config.showBaby(); + case YOUNG: + return config.showYoung(); + case GOURMET: + return config.showGourmet(); + case EARTH: + return config.showEarth(); + case ESSENCE: + return config.showEssence(); + case ECLECTIC: + return config.showEclectic(); + case NATURE: + return config.showNature(); + case MAGPIE: + return config.showMagpie(); + case NINJA: + return config.showNinja(); + case CRYSTAL: + return config.showCrystal(); + case DRAGON: + return config.showDragon(); + case LUCKY: + return config.showLucky(); + default: + return ImplingsConfig.ImplingMode.NONE; + } + } + + Color npcToColor(NPC npc) + { + Impling impling = Impling.findImpling(npc.getId()); + if (impling == null) + { + return null; + } + + switch (impling.getImplingType()) + { + + case BABY: + return config.getBabyColor(); + case YOUNG: + return config.getYoungColor(); + case GOURMET: + return config.getGourmetColor(); + case EARTH: + return config.getEarthColor(); + case ESSENCE: + return config.getEssenceColor(); + case ECLECTIC: + return config.getEclecticColor(); + case NATURE: + return config.getNatureColor(); + case MAGPIE: + return config.getMagpieColor(); + case NINJA: + return config.getNinjaColor(); + case CRYSTAL: + return config.getCrystalColor(); + case DRAGON: + return config.getDragonColor(); + case LUCKY: + return config.getLuckyColor(); + default: + return null; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java new file mode 100644 index 0000000000..d691cef668 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2018, Psikoi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.info; + +import com.google.common.base.MoreObjects; +import com.google.inject.Inject; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.concurrent.ScheduledExecutorService; +import javax.annotation.Nullable; +import javax.inject.Singleton; +import javax.swing.Box; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import javax.swing.event.HyperlinkEvent; +import net.runelite.api.Client; +import net.runelite.client.events.SessionClose; +import net.runelite.client.events.SessionOpen; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.account.SessionManager; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; + +@Singleton +public class InfoPanel extends PluginPanel +{ + private static final String RUNELITE_LOGIN = "https://runelite_login/"; + + private static final ImageIcon ARROW_RIGHT_ICON; + private static final ImageIcon GITHUB_ICON; + private static final ImageIcon DISCORD_ICON; + private static final ImageIcon PATREON_ICON; + private static final ImageIcon WIKI_ICON; + private static final ImageIcon IMPORT_ICON; + + private final JLabel loggedLabel = new JLabel(); + private final JRichTextPane emailLabel = new JRichTextPane(); + private JPanel syncPanel; + private JPanel actionsContainer; + + @Inject + @Nullable + private Client client; + + @Inject + private EventBus eventBus; + + @Inject + private SessionManager sessionManager; + + @Inject + private ScheduledExecutorService executor; + + @Inject + private ConfigManager configManager; + + static + { + ARROW_RIGHT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "/util/arrow_right.png")); + GITHUB_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "github_icon.png")); + DISCORD_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "discord_icon.png")); + PATREON_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "patreon_icon.png")); + WIKI_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "wiki_icon.png")); + IMPORT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "import_icon.png")); + } + + void init() + { + setLayout(new BorderLayout()); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setBorder(new EmptyBorder(10, 10, 10, 10)); + + JPanel versionPanel = new JPanel(); + versionPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + versionPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + versionPanel.setLayout(new GridLayout(0, 1)); + + final Font smallFont = FontManager.getRunescapeSmallFont(); + + JLabel version = new JLabel(htmlLabel("RuneLite version: ", RuneLiteProperties.getVersion())); + version.setFont(smallFont); + + JLabel revision = new JLabel(); + revision.setFont(smallFont); + + String engineVer = "Unknown"; + if (client != null) + { + engineVer = String.format("Rev %d", client.getRevision()); + } + + revision.setText(htmlLabel("Oldschool revision: ", engineVer)); + + JLabel launcher = new JLabel(htmlLabel("Launcher version: ", MoreObjects + .firstNonNull(RuneLiteProperties.getLauncherVersion(), "Unknown"))); + launcher.setFont(smallFont); + + loggedLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + loggedLabel.setFont(smallFont); + + emailLabel.setForeground(Color.WHITE); + emailLabel.setFont(smallFont); + emailLabel.enableAutoLinkHandler(false); + emailLabel.addHyperlinkListener(e -> + { + if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType()) && e.getURL() != null) + { + if (e.getURL().toString().equals(RUNELITE_LOGIN)) + { + executor.execute(sessionManager::login); + } + } + }); + + versionPanel.add(version); + versionPanel.add(revision); + versionPanel.add(launcher); + versionPanel.add(Box.createGlue()); + versionPanel.add(loggedLabel); + versionPanel.add(emailLabel); + + actionsContainer = new JPanel(); + actionsContainer.setBorder(new EmptyBorder(10, 0, 0, 0)); + actionsContainer.setLayout(new GridLayout(0, 1, 0, 10)); + + syncPanel = buildLinkPanel(IMPORT_ICON, "Import local settings", "to remote RuneLite account", () -> + { + final int result = JOptionPane.showOptionDialog(syncPanel, + "This will replace your current RuneLite account settings with settings from your local profile.", + "Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, + null, new String[]{"Yes", "No"}, "No"); + + if (result == JOptionPane.YES_OPTION) + { + configManager.importLocal(); + } + }); + + actionsContainer.add(buildLinkPanel(GITHUB_ICON, "Report an issue or", "make a suggestion", RuneLiteProperties.getGithubLink())); + actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "Discord server", RuneLiteProperties.getDiscordInvite())); + actionsContainer.add(buildLinkPanel(PATREON_ICON, "Become a patron to", "help support RuneLite", RuneLiteProperties.getPatreonLink())); + actionsContainer.add(buildLinkPanel(WIKI_ICON, "Information about", "RuneLite and plugins", RuneLiteProperties.getWikiLink())); + + add(versionPanel, BorderLayout.NORTH); + add(actionsContainer, BorderLayout.CENTER); + + updateLoggedIn(); + eventBus.register(this); + } + + /** + * Builds a link panel with a given icon, text and url to redirect to. + */ + private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, String url) + { + return buildLinkPanel(icon, topText, bottomText, () -> LinkBrowser.browse(url)); + } + + /** + * Builds a link panel with a given icon, text and callable to call. + */ + private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, Runnable callback) + { + JPanel container = new JPanel(); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + container.setLayout(new BorderLayout()); + container.setBorder(new EmptyBorder(10, 10, 10, 10)); + + final Color hoverColor = ColorScheme.DARKER_GRAY_HOVER_COLOR; + final Color pressedColor = ColorScheme.DARKER_GRAY_COLOR.brighter(); + + JLabel iconLabel = new JLabel(icon); + container.add(iconLabel, BorderLayout.WEST); + + JPanel textContainer = new JPanel(); + textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + textContainer.setLayout(new GridLayout(2, 1)); + textContainer.setBorder(new EmptyBorder(5, 10, 5, 10)); + + container.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + container.setBackground(pressedColor); + textContainer.setBackground(pressedColor); + } + + @Override + public void mouseReleased(MouseEvent e) + { + callback.run(); + container.setBackground(hoverColor); + textContainer.setBackground(hoverColor); + } + + @Override + public void mouseEntered(MouseEvent e) + { + container.setBackground(hoverColor); + textContainer.setBackground(hoverColor); + container.setCursor(new Cursor(Cursor.HAND_CURSOR)); + } + + @Override + public void mouseExited(MouseEvent e) + { + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + container.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + JLabel topLine = new JLabel(topText); + topLine.setForeground(Color.WHITE); + topLine.setFont(FontManager.getRunescapeSmallFont()); + + JLabel bottomLine = new JLabel(bottomText); + bottomLine.setForeground(Color.WHITE); + bottomLine.setFont(FontManager.getRunescapeSmallFont()); + + textContainer.add(topLine); + textContainer.add(bottomLine); + + container.add(textContainer, BorderLayout.CENTER); + + JLabel arrowLabel = new JLabel(ARROW_RIGHT_ICON); + container.add(arrowLabel, BorderLayout.EAST); + + return container; + } + + private void updateLoggedIn() + { + final String name = sessionManager.getAccountSession() != null + ? sessionManager.getAccountSession().getUsername() + : null; + + if (name != null) + { + emailLabel.setContentType("text/plain"); + emailLabel.setText(name); + loggedLabel.setText("Logged in as"); + actionsContainer.add(syncPanel, 0); + } + else + { + emailLabel.setContentType("text/html"); + emailLabel.setText("Login to sync settings to the cloud."); + loggedLabel.setText("Not logged in"); + actionsContainer.remove(syncPanel); + } + } + + private static String htmlLabel(String key, String value) + { + return "" + key + "" + value + ""; + } + + @Subscribe + public void onSessionOpen(SessionOpen sessionOpen) + { + updateLoggedIn(); + } + + @Subscribe + public void onSessionClose(SessionClose e) + { + updateLoggedIn(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java new file mode 100644 index 0000000000..f986a28ffa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.info; + +import java.awt.image.BufferedImage; +import javax.inject.Inject; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.util.ImageUtil; + +@PluginDescriptor( + name = "Info Panel", + description = "Enable the Info panel", + loadWhenOutdated = true +) +public class InfoPlugin extends Plugin +{ + @Inject + private ClientToolbar clientToolbar; + + private NavigationButton navButton; + + @Override + protected void startUp() throws Exception + { + final InfoPanel panel = injector.getInstance(InfoPanel.class); + panel.init(); + + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "info_icon.png"); + + navButton = NavigationButton.builder() + .tooltip("Info") + .icon(icon) + .priority(9) + .panel(panel) + .build(); + + clientToolbar.addNavigation(navButton); + } + + @Override + protected void shutDown() + { + clientToolbar.removeNavigation(navButton); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/JRichTextPane.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/JRichTextPane.java new file mode 100644 index 0000000000..f24df49e17 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/JRichTextPane.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.info; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.URISyntaxException; +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLEditorKit; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class JRichTextPane extends JEditorPane +{ + private HyperlinkListener linkHandler; + + public JRichTextPane() + { + super(); + setHighlighter(null); + setEditable(false); + setOpaque(false); + enableAutoLinkHandler(true); + setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + HTMLEditorKit ek = (HTMLEditorKit) getEditorKitForContentType("text/html"); + ek.getStyleSheet().addRule("a {color: #DDDDDD }"); + } + + public JRichTextPane(String type, String text) + { + this(); + setContentType(type); + setText(text); + } + + public void enableAutoLinkHandler(boolean enable) + { + if (enable == (linkHandler == null)) + { + if (enable) + { + linkHandler = e -> + { + if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType()) && e.getURL() != null) + { + if (Desktop.isDesktopSupported()) + { + try + { + Desktop.getDesktop().browse(e.getURL().toURI()); + } + catch (URISyntaxException | IOException ex) + { + log.warn("Error opening link", ex); + } + } + } + }; + addHyperlinkListener(linkHandler); + } + else + { + removeHyperlinkListener(linkHandler); + linkHandler = null; + } + } + } + + public boolean getAutoLinkHandlerEnabled() + { + return linkHandler != null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapInputListener.java new file mode 100644 index 0000000000..ae75a638ac --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapInputListener.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2018, Kamiel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.instancemap; + +import java.awt.Point; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.MouseAdapter; +import net.runelite.client.input.MouseWheelListener; + +public class InstanceMapInputListener extends MouseAdapter implements KeyListener, MouseWheelListener +{ + @Inject + private InstanceMapPlugin plugin; + + @Inject + private InstanceMapOverlay overlay; + + @Override + public void keyTyped(KeyEvent event) + { + + } + + @Override + public void keyPressed(KeyEvent event) + { + if (!overlay.isMapShown()) + { + return; + } + + if (event.getKeyCode() == KeyEvent.VK_ESCAPE) + { + plugin.closeMap(); + event.consume(); + } + } + + @Override + public void keyReleased(KeyEvent event) + { + + } + + @Override + public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event) + { + if (!overlay.isMapShown() || isNotWithinOverlay(event.getPoint())) + { + return event; + } + + int direction = event.getWheelRotation(); + + if (direction > 0) + { + plugin.ascendMap(); + } + else + { + plugin.descendMap(); + } + + event.consume(); + return event; + } + + @Override + public MouseEvent mouseClicked(MouseEvent event) + { + if (!overlay.isMapShown() || isNotWithinOverlay(event.getPoint())) + { + return event; + } + + event.consume(); + return event; + } + + @Override + public MouseEvent mousePressed(MouseEvent event) + { + if (!overlay.isMapShown() || isNotWithinOverlay(event.getPoint())) + { + return event; + } + + if (SwingUtilities.isLeftMouseButton(event) && isWithinCloseButton(event.getPoint())) + { + plugin.closeMap(); + } + + event.consume(); + return event; + } + + @Override + public MouseEvent mouseMoved(MouseEvent event) + { + if (overlay.isMapShown()) + { + overlay.setCloseButtonHovered(isWithinCloseButton(event.getPoint())); + } + + return event; + } + + private boolean isNotWithinOverlay(final Point point) + { + return !overlay.getBounds().contains(point); + } + + private boolean isWithinCloseButton(final Point point) + { + Point overlayPoint = new Point(point.x - (int) overlay.getBounds().getX(), + point.y - (int) overlay.getBounds().getY()); + + return overlay.getCloseButtonBounds() != null + && overlay.getCloseButtonBounds().contains(overlayPoint); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapOverlay.java new file mode 100644 index 0000000000..6a3492a425 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapOverlay.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.instancemap; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.Client; +import net.runelite.api.Player; +import net.runelite.api.SpritePixels; +import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.events.GameStateChanged; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.BackgroundComponent; +import static net.runelite.api.SpriteID.WINDOW_CLOSE_BUTTON_RED_X; +import static net.runelite.api.SpriteID.WINDOW_CLOSE_BUTTON_RED_X_HOVERED; + +@Singleton +class InstanceMapOverlay extends Overlay +{ + /** + * The size of tiles on the map. The way the client renders requires + * this value to be 4. Changing this will break the method for rendering + * complex tiles + */ + static final int TILE_SIZE = 4; + + /** + * The size of the player's position marker on the map + */ + private static final int PLAYER_MARKER_SIZE = 4; + + private static final int MAX_PLANE = 3; + private static final int MIN_PLANE = 0; + + /** + * The plane to render on the instance map. When the map is opened this + * defaults to the current plane. The ascend and descend buttons raise + * and lower this This is used to render parts of an instance below or + * above the local player's current plane. + */ + private int viewedPlane = 0; + + private final Client client; + private final SpriteManager spriteManager; + + /** + * Saved image of the scene, no reason to draw the whole thing every + * frame. + */ + private volatile BufferedImage mapImage; + private volatile boolean showMap = false; + private final BackgroundComponent backgroundComponent = new BackgroundComponent(); + + @Setter + private boolean isCloseButtonHovered; + + @Getter + private Rectangle closeButtonBounds; + + private BufferedImage closeButtonImage; + private BufferedImage closeButtonHoveredImage; + + @Inject + InstanceMapOverlay(Client client, SpriteManager spriteManager) + { + this.client = client; + this.spriteManager = spriteManager; + setPriority(OverlayPriority.HIGH); + setPosition(OverlayPosition.TOP_LEFT); + setLayer(OverlayLayer.ABOVE_WIDGETS); + backgroundComponent.setFill(false); + } + + public boolean isMapShown() + { + return showMap; + } + + /** + * Setter for showing the map. When the map is set to show, the map is + * re-rendered + * + * @param show Whether or not the map should be shown. + */ + public synchronized void setShowMap(boolean show) + { + showMap = show; + if (showMap) + { + //When we open the map show the current plane + viewedPlane = client.getPlane(); + } + mapImage = null; + } + + /** + * Increases the viewed plane. The maximum viewedPlane is 3 + */ + public synchronized void onAscend() + { + if (viewedPlane >= MAX_PLANE) + { + return; + } + + viewedPlane++;//Increment plane + mapImage = null; + } + + /** + * Decreases the viewed plane. The minimum viewedPlane is 0 + */ + public synchronized void onDescend() + { + if (viewedPlane <= MIN_PLANE) + { + return; + } + + viewedPlane--; + mapImage = null; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!showMap) + { + return null; + } + + // avoid locking on fast path by creating a local ref + BufferedImage image = mapImage; + + if (image == null) + { + SpritePixels map = client.drawInstanceMap(viewedPlane); + image = minimapToBufferedImage(map); + synchronized (this) + { + if (showMap) + { + mapImage = image; + } + } + } + + BufferedImage closeButton = getCloseButtonImage(); + BufferedImage closeButtonHover = getCloseButtonHoveredImage(); + if (closeButton != null && closeButtonBounds == null) + { + closeButtonBounds = new Rectangle(image.getWidth() - closeButton.getWidth() - 5, 6, + closeButton.getWidth(), closeButton.getHeight()); + } + + graphics.drawImage(image, 0, 0, null); + backgroundComponent.setRectangle(new Rectangle(0, 0, image.getWidth(), image.getHeight())); + backgroundComponent.render(graphics); + + if (client.getPlane() == viewedPlane)//If we are not viewing the plane we are on, don't show player's position + { + drawPlayerDot(graphics, client.getLocalPlayer(), Color.white, Color.black); + } + + if (isCloseButtonHovered) + { + closeButton = closeButtonHover; + } + + if (closeButton != null) + { + graphics.drawImage(closeButton, (int) closeButtonBounds.getX(), (int) closeButtonBounds.getY(), null); + } + + return new Dimension(image.getWidth(), image.getHeight()); + } + + /** + * Get the files for the current viewed plane + * + * @return + */ + private Tile[][] getTiles() + { + Tile[][][] sceneTiles = client.getScene().getTiles(); + return sceneTiles[viewedPlane]; + } + + /** + * Draws the players position as a dot on the map. + * + * @param graphics graphics to be drawn to + */ + private void drawPlayerDot(Graphics2D graphics, Player player, + Color dotColor, Color outlineColor) + { + LocalPoint playerLoc = player.getLocalLocation(); + + Tile[][] tiles = getTiles(); + int tileX = playerLoc.getSceneX(); + int tileY = (tiles[0].length - 1) - playerLoc.getSceneY(); // flip the y value + + int x = tileX * TILE_SIZE; + int y = tileY * TILE_SIZE; + graphics.setColor(dotColor); + graphics.fillRect(x, y, PLAYER_MARKER_SIZE, PLAYER_MARKER_SIZE);//draw the players point on the map + graphics.setColor(outlineColor); + graphics.drawRect(x, y, PLAYER_MARKER_SIZE, PLAYER_MARKER_SIZE);//outline + } + + /** + * Handles game state changes and re-draws the map + * + * @param event The game state change event + */ + public void onGameStateChange(GameStateChanged event) + { + mapImage = null; + } + + private static BufferedImage minimapToBufferedImage(SpritePixels spritePixels) + { + int width = spritePixels.getWidth(); + int height = spritePixels.getHeight(); + int[] pixels = spritePixels.getPixels(); + BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + img.setRGB(0, 0, width, height, pixels, 0, width); + // 24624 / 512 and 24624 % 512 are both 48 + img = img.getSubimage(48, 48, TILE_SIZE * 104, TILE_SIZE * 104); + return img; + } + + @Nullable + private BufferedImage getCloseButtonImage() + { + if (closeButtonImage == null) + { + closeButtonImage = spriteManager.getSprite(WINDOW_CLOSE_BUTTON_RED_X, 0); + } + return closeButtonImage; + } + + @Nullable + private BufferedImage getCloseButtonHoveredImage() + { + if (closeButtonHoveredImage == null) + { + closeButtonHoveredImage = spriteManager.getSprite(WINDOW_CLOSE_BUTTON_RED_X_HOVERED, 0); + } + return closeButtonHoveredImage; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapPlugin.java new file mode 100644 index 0000000000..003a5bbc2e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/instancemap/InstanceMapPlugin.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.instancemap; + +import com.google.inject.Binder; +import javax.inject.Inject; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.WidgetMenuOptionClicked; +import static net.runelite.api.widgets.WidgetInfo.WORLD_MAP_OPTION; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.input.KeyManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.menus.MenuManager; +import net.runelite.client.menus.WidgetMenuOption; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Instance Map", + description = "Add an instanced map, accessible by right-clicking the map button" +) +public class InstanceMapPlugin extends Plugin +{ + private final WidgetMenuOption openMapOption = new WidgetMenuOption("Show", "Instance Map", WORLD_MAP_OPTION); + + @Inject + private InstanceMapInputListener inputListener; + + @Inject + private OverlayManager overlayManager; + + @Inject + private InstanceMapOverlay overlay; + + @Inject + private MenuManager menuManager; + + @Inject + private KeyManager keyManager; + + @Inject + private MouseManager mouseManager; + + @Override + public void configure(Binder binder) + { + binder.bind(InstanceMapInputListener.class); + } + + private void addCustomOptions() + { + menuManager.addManagedCustomMenu(openMapOption); + } + + private void removeCustomOptions() + { + menuManager.removeManagedCustomMenu(openMapOption); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + addCustomOptions(); + keyManager.registerKeyListener(inputListener); + mouseManager.registerMouseListener(inputListener); + mouseManager.registerMouseWheelListener(inputListener); + } + + @Override + protected void shutDown() throws Exception + { + overlay.setShowMap(false); + overlayManager.remove(overlay); + removeCustomOptions(); + keyManager.unregisterKeyListener(inputListener); + mouseManager.unregisterMouseListener(inputListener); + mouseManager.unregisterMouseWheelListener(inputListener); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + overlay.onGameStateChange(event); + } + + private boolean clickedOptionEquals(WidgetMenuOptionClicked event, WidgetMenuOption widgetMenuOption) + { + return event.getMenuOption().equals(widgetMenuOption.getMenuOption()) && event.getMenuTarget().equals(widgetMenuOption.getMenuTarget()); + } + + @Subscribe + public void onWidgetMenuOptionClicked(WidgetMenuOptionClicked event) + { + if (event.getWidget() != WORLD_MAP_OPTION) + { + return; + } + + if (clickedOptionEquals(event, openMapOption)) + { + if (overlay.isMapShown()) + { + closeMap(); + } + else + { + showMap(); + } + } + } + + public void showMap() + { + overlay.setShowMap(true); + openMapOption.setMenuOption("Hide"); + } + + public void closeMap() + { + overlay.setShowMap(false); + openMapOption.setMenuOption("Show"); + } + + public void ascendMap() + { + overlay.onAscend(); + } + + public void descendMap() + { + overlay.onDescend(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/HealthbarOverride.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/HealthbarOverride.java new file mode 100644 index 0000000000..c0e844939a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/HealthbarOverride.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.interfacestyles; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import static net.runelite.api.SpriteID.*; +import net.runelite.client.game.SpriteOverride; + +@RequiredArgsConstructor +enum HealthbarOverride implements SpriteOverride +{ + BACK_30PX(HEALTHBAR_DEFAULT_BACK_30PX, "back_30px.png"), + BACK_50PX(HEALTHBAR_DEFAULT_BACK_50PX, "back_30px.png"), + BACK_60PX(HEALTHBAR_DEFAULT_BACK_60PX, "back_30px.png"), + BACK_80PX(HEALTHBAR_DEFAULT_BACK_80PX, "back_90px.png"), + BACK_100PX(HEALTHBAR_DEFAULT_BACK_100PX, "back_90px.png"), + BACK_120PX(HEALTHBAR_DEFAULT_BACK_120PX, "back_90px.png"), + BACK_140PX(HEALTHBAR_DEFAULT_BACK_140PX, "back_90px.png"), + BACK_160PX(HEALTHBAR_DEFAULT_BACK_160PX, "back_90px.png"), + + FRONT_30PX(HEALTHBAR_DEFAULT_FRONT_30PX, "front_30px.png"), + FRONT_50PX(HEALTHBAR_DEFAULT_FRONT_50PX, "front_30px.png"), + FRONT_60PX(HEALTHBAR_DEFAULT_FRONT_60PX, "front_30px.png"), + FRONT_80PX(HEALTHBAR_DEFAULT_FRONT_80PX, "front_90px.png"), + FRONT_100PX(HEALTHBAR_DEFAULT_FRONT_100PX, "front_90px.png"), + FRONT_120PX(HEALTHBAR_DEFAULT_FRONT_120PX, "front_90px.png"), + FRONT_140PX(HEALTHBAR_DEFAULT_FRONT_140PX, "front_90px.png"), + FRONT_160PX(HEALTHBAR_DEFAULT_FRONT_160PX, "front_90px.png"); + + @Getter + private final int spriteId; + + private final String fileName; + + @Getter + private int padding = 1; + + private static final Map MAP; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (HealthbarOverride override : values()) + { + builder.put(override.spriteId, override); + } + + MAP = builder.build(); + } + + static HealthbarOverride get(int spriteID) + { + return MAP.get(spriteID); + } + + @Override + public String getFileName() + { + return Skin.AROUND_2010.toString() + "/healthbar/" + this.fileName; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesConfig.java new file mode 100644 index 0000000000..3c14155710 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesConfig.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Raqes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.interfacestyles; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Range; + +@ConfigGroup("interfaceStyles") +public interface InterfaceStylesConfig extends Config +{ + @ConfigItem( + keyName = "gameframe", + name = "Gameframe", + description = "The gameframe to use for the interface" + ) + default Skin skin() + { + return Skin.AROUND_2010; + } + + @ConfigItem( + keyName = "hdHealthBars", + name = "High Detail health bars", + description = "Replaces health bars with the RuneScape High Detail mode design" + ) + default boolean hdHealthBars() + { + return false; + } + + @ConfigItem( + keyName = "hdMenu", + name = "High Detail menu", + description = "Replaces game menu with the RuneScape High Detail mode design" + ) + default boolean hdMenu() + { + return false; + } + + @ConfigItem( + keyName = "rsCrossSprites", + name = "RuneScape cross sprites", + description = "Replaces left-click cross sprites with the ones in RuneScape" + ) + default boolean rsCrossSprites() + { + return false; + } + + @ConfigItem( + keyName = "alwaysStack", + name = "Always stack bottom bar", + description = "Always stack the bottom bar in resizable" + ) + default boolean alwaysStack() + { + return false; + } + + @Range( + max = 255 + ) + @ConfigItem( + keyName = "menuAlpha", + name = "Menu alpha", + description = "Configures the transparency of the right-click menu" + ) + default int menuAlpha() + { + return 255; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java new file mode 100644 index 0000000000..48ee9313fa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Raqes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.interfacestyles; + +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.HealthBar; +import net.runelite.api.SpriteID; +import net.runelite.api.SpritePixels; +import net.runelite.api.events.BeforeMenuRender; +import net.runelite.api.events.BeforeRender; +import net.runelite.client.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.PostHealthBar; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.ImageUtil; + +@Slf4j +@PluginDescriptor( + name = "Interface Styles", + description = "Change the interface style to the 2005/2010 interface", + tags = {"2005", "2010", "skin", "theme", "ui"}, + enabledByDefault = false +) +public class InterfaceStylesPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private InterfaceStylesConfig config; + + @Inject + private SpriteManager spriteManager; + + private SpritePixels[] defaultCrossSprites; + + @Provides + InterfaceStylesConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(InterfaceStylesConfig.class); + } + + @Override + protected void startUp() throws Exception + { + clientThread.invoke(this::updateAllOverrides); + } + + @Override + protected void shutDown() throws Exception + { + clientThread.invoke(() -> + { + restoreWidgetDimensions(); + removeGameframe(); + restoreHealthBars(); + restoreCrossSprites(); + }); + } + + @Subscribe + public void onConfigChanged(ConfigChanged config) + { + if (config.getGroup().equals("interfaceStyles")) + { + clientThread.invoke(this::updateAllOverrides); + } + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + if ("forceStackStones".equals(event.getEventName()) && config.alwaysStack()) + { + int[] intStack = client.getIntStack(); + int intStackSize = client.getIntStackSize(); + intStack[intStackSize - 1] = 1; + } + } + + @Subscribe + public void onBeforeRender(BeforeRender event) + { + adjustWidgetDimensions(); + } + + @Subscribe + public void onPostHealthBar(PostHealthBar postHealthBar) + { + if (!config.hdHealthBars()) + { + return; + } + + HealthBar healthBar = postHealthBar.getHealthBar(); + HealthbarOverride override = HealthbarOverride.get(healthBar.getHealthBarFrontSpriteId()); + + // Check if this is the health bar we are replacing + if (override != null) + { + // Increase padding to show some more green at very low hp percentages + healthBar.setPadding(override.getPadding()); + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() != GameState.LOGIN_SCREEN) + { + return; + } + + /* + * The cross sprites aren't loaded yet when the initial config change event is received. + * So run the overriding for cross sprites when we reach the login screen, + * at which point the cross sprites will have been loaded. + */ + overrideCrossSprites(); + } + + private void updateAllOverrides() + { + removeGameframe(); + overrideSprites(); + overrideWidgetSprites(); + restoreWidgetDimensions(); + adjustWidgetDimensions(); + overrideHealthBars(); + overrideCrossSprites(); + } + + @Subscribe + public void onBeforeMenuRender(BeforeMenuRender event) + { + if (config.hdMenu()) + { + client.draw2010Menu(config.menuAlpha()); + event.consume(); + } + else if (config.menuAlpha() != 255) + { + client.drawOriginalMenu(config.menuAlpha()); + event.consume(); + } + } + + private void overrideSprites() + { + final Skin configuredSkin = config.skin(); + for (SpriteOverride spriteOverride : SpriteOverride.values()) + { + for (Skin skin : spriteOverride.getSkin()) + { + if (skin == configuredSkin) + { + final String configSkin = skin.getExtendSkin() != null + ? skin.getExtendSkin().toString() + : skin.toString(); + String file = configSkin + "/" + spriteOverride.getSpriteID() + ".png"; + SpritePixels spritePixels = getFileSpritePixels(file); + + if (spriteOverride.getSpriteID() == SpriteID.COMPASS_TEXTURE) + { + client.setCompass(spritePixels); + } + else + { + client.getSpriteOverrides().put(spriteOverride.getSpriteID(), spritePixels); + } + } + } + } + } + + private void restoreSprites() + { + client.getWidgetSpriteCache().reset(); + + for (SpriteOverride spriteOverride : SpriteOverride.values()) + { + client.getSpriteOverrides().remove(spriteOverride.getSpriteID()); + } + } + + private void overrideWidgetSprites() + { + final Skin configuredSkin = config.skin(); + for (WidgetOverride widgetOverride : WidgetOverride.values()) + { + if (widgetOverride.getSkin() == configuredSkin + || widgetOverride.getSkin() == configuredSkin.getExtendSkin()) + { + final String configSkin = configuredSkin.getExtendSkin() != null + ? configuredSkin.getExtendSkin().toString() + : configuredSkin.toString(); + String file = configSkin + "/widget/" + widgetOverride.getName() + ".png"; + SpritePixels spritePixels = getFileSpritePixels(file); + + if (spritePixels != null) + { + for (WidgetInfo widgetInfo : widgetOverride.getWidgetInfo()) + { + client.getWidgetSpriteOverrides().put(widgetInfo.getPackedId(), spritePixels); + } + } + } + } + } + + private void restoreWidgetSprites() + { + for (WidgetOverride widgetOverride : WidgetOverride.values()) + { + for (WidgetInfo widgetInfo : widgetOverride.getWidgetInfo()) + { + client.getWidgetSpriteOverrides().remove(widgetInfo.getPackedId()); + } + } + } + + private SpritePixels getFileSpritePixels(String file) + { + try + { + log.debug("Loading: {}", file); + BufferedImage image = ImageUtil.getResourceStreamFromClass(this.getClass(), file); + return ImageUtil.getImageSpritePixels(image, client); + } + catch (RuntimeException ex) + { + log.debug("Unable to load image: ", ex); + } + + return null; + } + + private void adjustWidgetDimensions() + { + for (WidgetOffset widgetOffset : WidgetOffset.values()) + { + if (widgetOffset.getSkin() != config.skin()) + { + continue; + } + + Widget widget = client.getWidget(widgetOffset.getWidgetInfo()); + + if (widget != null) + { + if (widgetOffset.getOffsetX() != null) + { + widget.setRelativeX(widgetOffset.getOffsetX()); + } + + if (widgetOffset.getOffsetY() != null) + { + widget.setRelativeY(widgetOffset.getOffsetY()); + } + + if (widgetOffset.getWidth() != null) + { + widget.setWidth(widgetOffset.getWidth()); + } + + if (widgetOffset.getHeight() != null) + { + widget.setHeight(widgetOffset.getHeight()); + } + } + } + } + + private void overrideHealthBars() + { + if (config.hdHealthBars()) + { + spriteManager.addSpriteOverrides(HealthbarOverride.values()); + // Reset health bar caches to apply the override + clientThread.invokeLater(client::resetHealthBarCaches); + } + else + { + restoreHealthBars(); + } + } + + private void restoreHealthBars() + { + spriteManager.removeSpriteOverrides(HealthbarOverride.values()); + clientThread.invokeLater(client::resetHealthBarCaches); + } + + private void overrideCrossSprites() + { + if (config.rsCrossSprites()) + { + // If we've already replaced them, + // we don't need to replace them again + if (defaultCrossSprites != null) + { + return; + } + + SpritePixels[] crossSprites = client.getCrossSprites(); + + if (crossSprites == null) + { + return; + } + + defaultCrossSprites = new SpritePixels[crossSprites.length]; + System.arraycopy(crossSprites, 0, defaultCrossSprites, 0, defaultCrossSprites.length); + + for (int i = 0; i < crossSprites.length; i++) + { + SpritePixels newSprite = getFileSpritePixels("rs3/cross_sprites/" + i + ".png"); + + if (newSprite == null) + { + continue; + } + + crossSprites[i] = newSprite; + } + } + else + { + restoreCrossSprites(); + } + } + + private void restoreCrossSprites() + { + if (defaultCrossSprites == null) + { + return; + } + + SpritePixels[] crossSprites = client.getCrossSprites(); + + if (crossSprites != null && defaultCrossSprites.length == crossSprites.length) + { + System.arraycopy(defaultCrossSprites, 0, crossSprites, 0, defaultCrossSprites.length); + } + + defaultCrossSprites = null; + } + + private void restoreWidgetDimensions() + { + for (WidgetOffset widgetOffset : WidgetOffset.values()) + { + Widget widget = client.getWidget(widgetOffset.getWidgetInfo()); + + if (widget != null) + { + widget.revalidate(); + } + } + } + + private void removeGameframe() + { + restoreSprites(); + restoreWidgetSprites(); + + BufferedImage compassImage = spriteManager.getSprite(SpriteID.COMPASS_TEXTURE, 0); + + if (compassImage != null) + { + SpritePixels compass = ImageUtil.getImageSpritePixels(compassImage, client); + client.setCompass(compass); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/ControlFunction.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/Skin.java similarity index 80% rename from runelite-client/src/main/java/net/runelite/client/plugins/camera/ControlFunction.java rename to runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/Skin.java index 07fecfd5ba..7e45fd6a64 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/camera/ControlFunction.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/Skin.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2019, Jacob M + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Raqes * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,20 +24,27 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.client.plugins.camera; +package net.runelite.client.plugins.interfacestyles; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor -public enum ControlFunction +public enum Skin { - NONE("None"), - CONTROL_TO_ZOOM("Hold to zoom"), - CONTROL_TO_RESET("Reset zoom"); + DEFAULT("Default"), + AROUND_2005("2005"), + AROUND_2006("2006", AROUND_2005), + AROUND_2010("2010"); - private final String name; + private String name; + private Skin extendSkin; + + Skin(String name) + { + this(name, null); + } @Override public String toString() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/SpriteOverride.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/SpriteOverride.java new file mode 100644 index 0000000000..4f0153ee2c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/SpriteOverride.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Raqes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.interfacestyles; + +import lombok.Getter; +import net.runelite.api.SpriteID; +import static net.runelite.client.plugins.interfacestyles.Skin.AROUND_2005; +import static net.runelite.client.plugins.interfacestyles.Skin.AROUND_2006; +import static net.runelite.client.plugins.interfacestyles.Skin.AROUND_2010; + +@Getter +enum SpriteOverride +{ + TAB_COMBAT(SpriteID.TAB_COMBAT, AROUND_2005, AROUND_2010), + TAB_STATS(SpriteID.TAB_STATS, AROUND_2005, AROUND_2010), + TAB_QUESTS(SpriteID.TAB_QUESTS, AROUND_2005), + TAB_QUESTS_PURPLE_KOUREND_1299(SpriteID.TAB_QUESTS_PURPLE_KOUREND, AROUND_2005), + TAB_QUESTS_RED_MINIGAMES(SpriteID.TAB_QUESTS_RED_MINIGAMES, AROUND_2005), + TAB_QUESTS_GREEN_ACHIEVEMENT_DIARIES(SpriteID.TAB_QUESTS_GREEN_ACHIEVEMENT_DIARIES, AROUND_2005), + TAB_INVENTORY(SpriteID.TAB_INVENTORY, AROUND_2005, AROUND_2010), + TAB_EQUIPMENT(SpriteID.TAB_EQUIPMENT, AROUND_2005, AROUND_2010), + TAB_PRAYER(SpriteID.TAB_PRAYER, AROUND_2005, AROUND_2010), + TAB_MAGIC(SpriteID.TAB_MAGIC, AROUND_2005, AROUND_2010), + TAB_MAGIC_SPELLBOOK_ANCIENT_MAGICKS(SpriteID.TAB_MAGIC_SPELLBOOK_ANCIENT_MAGICKS, AROUND_2005), + TAB_MAGIC_SPELLBOOK_LUNAR(SpriteID.TAB_MAGIC_SPELLBOOK_LUNAR, AROUND_2005), + TAB_MAGIC_SPELLBOOK_ARCEUUS(SpriteID.TAB_MAGIC_SPELLBOOK_ARCEUUS, AROUND_2005), + TAB_CLAN_CHAT(SpriteID.TAB_FRIENDS_CHAT, AROUND_2005, AROUND_2010), + TAB_FRIENDS(SpriteID.TAB_FRIENDS, AROUND_2005, AROUND_2010), + TAB_IGNORES(SpriteID.TAB_IGNORES, AROUND_2005, AROUND_2010), + TAB_LOGOUT(SpriteID.TAB_LOGOUT, AROUND_2005, AROUND_2010), + TAB_OPTIONS(SpriteID.TAB_OPTIONS, AROUND_2005, AROUND_2010), + TAB_EMOTES(SpriteID.TAB_EMOTES, AROUND_2005, AROUND_2010), + TAB_MUSIC(SpriteID.TAB_MUSIC, AROUND_2005, AROUND_2010), + TAB_CHATBOX(SpriteID.CHATBOX, AROUND_2005), + + BUTTON_FRIENDS(SpriteID.BUTTON_FRIENDS, AROUND_2005), + BUTTON_IGNORES(SpriteID.BUTTON_IGNORES, AROUND_2005), + + SKILL_ATTACK(SpriteID.SKILL_ATTACK, AROUND_2010), + SKILL_STRENGTH(SpriteID.SKILL_STRENGTH, AROUND_2010), + SKILL_DEFENCE(SpriteID.SKILL_DEFENCE, AROUND_2010), + SKILL_RANGED(SpriteID.SKILL_RANGED, AROUND_2010), + SKILL_PRAYER(SpriteID.SKILL_PRAYER, AROUND_2005, AROUND_2010), + SKILL_MAGIC(SpriteID.SKILL_MAGIC, AROUND_2010), + SKILL_HITPOINTS(SpriteID.SKILL_HITPOINTS, AROUND_2010), + SKILL_AGILITY(SpriteID.SKILL_AGILITY, AROUND_2010), + SKILL_HERBLORE(SpriteID.SKILL_HERBLORE, AROUND_2010), + SKILL_THIEVING(SpriteID.SKILL_THIEVING, AROUND_2010), + SKILL_CRAFTING(SpriteID.SKILL_CRAFTING, AROUND_2010), + SKILL_FLETCHING(SpriteID.SKILL_FLETCHING, AROUND_2010), + SKILL_MINING(SpriteID.SKILL_MINING, AROUND_2010), + SKILL_SMITHING(SpriteID.SKILL_SMITHING, AROUND_2010), + SKILL_FISHING(SpriteID.SKILL_FISHING, AROUND_2010), + SKILL_COOKING(SpriteID.SKILL_COOKING, AROUND_2010), + SKILL_FIREMAKING(SpriteID.SKILL_FIREMAKING, AROUND_2010), + SKILL_WOODCUTTING(SpriteID.SKILL_WOODCUTTING, AROUND_2010), + SKILL_RUNECRAFT(SpriteID.SKILL_RUNECRAFT, AROUND_2010), + SKILL_SLAYER(SpriteID.SKILL_SLAYER, AROUND_2010), + SKILL_HUNTER(SpriteID.SKILL_HUNTER, AROUND_2010), + SKILL_CONSTRUCTION(SpriteID.SKILL_CONSTRUCTION, AROUND_2010), + + COMPASS(SpriteID.COMPASS_TEXTURE, AROUND_2005), + WINDOW_CLOSE_BUTTON_RED_X(SpriteID.WINDOW_CLOSE_BUTTON_RED_X, AROUND_2010), + WINDOW_CLOSE_BUTTON_RED_X_HOVERED(SpriteID.WINDOW_CLOSE_BUTTON_RED_X_HOVERED, AROUND_2010), + WINDOW_CLOSE_BUTTON_BROWN_X(SpriteID.WINDOW_CLOSE_BUTTON_BROWN_X, AROUND_2010), + WINDOW_CLOSE_BUTTON_BROWN_X_HOVERED(SpriteID.WINDOW_CLOSE_BUTTON_BROWN_X_HOVERED, AROUND_2010), + MINIMAP_ORB_FRAME(SpriteID.MINIMAP_ORB_FRAME, AROUND_2010), + MINIMAP_ORB_FRAME_HOVERED(SpriteID.MINIMAP_ORB_FRAME_HOVERED, AROUND_2010), + MINIMAP_ORB_XP(SpriteID.MINIMAP_ORB_XP, AROUND_2010), + MINIMAP_ORB_XP_ACTIVATED(SpriteID.MINIMAP_ORB_XP_ACTIVATED, AROUND_2010), + MINIMAP_ORB_XP_HOVERED(SpriteID.MINIMAP_ORB_XP_HOVERED, AROUND_2010), + MINIMAP_ORB_XP_ACTIVATED_HOVERED(SpriteID.MINIMAP_ORB_XP_ACTIVATED_HOVERED, AROUND_2010), + MINIMAP_ORB_WORLD_MAP_FRAME(SpriteID.MINIMAP_ORB_WORLD_MAP_FRAME, AROUND_2010), + MINIMAP_ORB_WORLD_MAP_PLANET(SpriteID.MINIMAP_ORB_WORLD_MAP_PLANET, AROUND_2010), + + //CHATBOX(SpriteID.CHATBOX, AROUND_2005, AROUND_2006), + CHATBOX_BUTTONS_BACKGROUND_STONES(SpriteID.CHATBOX_BUTTONS_BACKGROUND_STONES, AROUND_2005, AROUND_2006), + CHATBOX_BUTTON(SpriteID.CHATBOX_BUTTON, AROUND_2005, AROUND_2006), + CHATBOX_BUTTON_HOVERED(SpriteID.CHATBOX_BUTTON_HOVERED, AROUND_2005, AROUND_2006), + CHATBOX_BUTTON_NEW_MESSAGES( SpriteID.CHATBOX_BUTTON_NEW_MESSAGES, AROUND_2005, AROUND_2006), + CHATBOX_BUTTON_SELECTED(SpriteID.CHATBOX_BUTTON_SELECTED, AROUND_2005, AROUND_2006), + CHATBOX_BUTTON_SELECTED_HOVERED(SpriteID.CHATBOX_BUTTON_SELECTED_HOVERED, AROUND_2005, AROUND_2006), + CHATBOX_REPORT_BUTTON(SpriteID.CHATBOX_REPORT_BUTTON, AROUND_2005, AROUND_2006), + CHATBOX_REPORT_BUTTON_HOVERED(SpriteID.CHATBOX_REPORT_BUTTON_HOVERED, AROUND_2005, AROUND_2006), + + SCROLLBAR_ARROW_UP(SpriteID.SCROLLBAR_ARROW_UP, AROUND_2005), + SCROLLBAR_ARROW_DOWN(SpriteID.SCROLLBAR_ARROW_DOWN, AROUND_2005), + SCROLLBAR_THUMB_TOP(SpriteID.SCROLLBAR_THUMB_TOP, AROUND_2005), + SCROLLBAR_THUMB_MIDDLE(SpriteID.SCROLLBAR_THUMB_MIDDLE, AROUND_2005), + SCROLLBAR_THUMB_BOTTOM(SpriteID.SCROLLBAR_THUMB_BOTTOM, AROUND_2005), + SCROLLBAR_THUMB_MIDDLE_DARK(SpriteID.SCROLLBAR_THUMB_MIDDLE_DARK, AROUND_2005), + + TAB_STONE_TOP_LEFT_SELECTED(SpriteID.TAB_STONE_TOP_LEFT_SELECTED, AROUND_2010), + TAB_STONE_TOP_RIGHT_SELECTED(SpriteID.TAB_STONE_TOP_RIGHT_SELECTED, AROUND_2010), + TAB_STONE_BOTTOM_LEFT_SELECTED(SpriteID.TAB_STONE_BOTTOM_LEFT_SELECTED, AROUND_2010), + TAB_STONE_BOTTOM_RIGHT_SELECTED(SpriteID.TAB_STONE_BOTTOM_RIGHT_SELECTED, AROUND_2010), + TAB_STONE_MIDDLE_SELECTED(SpriteID.TAB_STONE_MIDDLE_SELECTED, AROUND_2010), + + FIXED_MODE_SIDE_PANEL_BACKGROUND(SpriteID.FIXED_MODE_SIDE_PANEL_BACKGROUND, AROUND_2005, AROUND_2006), + FIXED_MODE_TABS_ROW_BOTTOM(SpriteID.FIXED_MODE_TABS_ROW_BOTTOM, AROUND_2005, AROUND_2006, AROUND_2010), + + OLD_SCHOOl_MODE_SIDE_PANEL_EDGE_LEFT_UPPER(SpriteID.OLD_SCHOOl_MODE_SIDE_PANEL_EDGE_LEFT_UPPER, AROUND_2005, AROUND_2006, AROUND_2010), + OLD_SCHOOl_MODE_SIDE_PANEL_EDGE_LEFT_LOWER(SpriteID.OLD_SCHOOl_MODE_SIDE_PANEL_EDGE_LEFT_LOWER, AROUND_2005, AROUND_2006, AROUND_2010), + OLD_SCHOOl_MODE_SIDE_PANEL_EDGE_RIGHT(SpriteID.OLD_SCHOOl_MODE_SIDE_PANEL_EDGE_RIGHT, AROUND_2005, AROUND_2006, AROUND_2010), + + FIXED_MODE_TABS_TOP_ROW(SpriteID.FIXED_MODE_TABS_TOP_ROW, AROUND_2005, AROUND_2006, AROUND_2010), + FIXED_MODE_MINIMAP_LEFT_EDGE(SpriteID.FIXED_MODE_MINIMAP_LEFT_EDGE, AROUND_2005, AROUND_2006, AROUND_2010), + FIXED_MODE_MINIMAP_RIGHT_EDGE(SpriteID.FIXED_MODE_MINIMAP_RIGHT_EDGE, AROUND_2005, AROUND_2006, AROUND_2010), + FIXED_MODE_WINDOW_FRAME_EDGE_TOP(SpriteID.FIXED_MODE_WINDOW_FRAME_EDGE_TOP, AROUND_2005, AROUND_2006, AROUND_2010), + FIXED_MODE_MINIMAP_AND_COMPASS_FRAME(SpriteID.FIXED_MODE_MINIMAP_AND_COMPASS_FRAME, AROUND_2005, AROUND_2006, AROUND_2010), + FIXED_MODE_MINIMAP_FRAME_BOTTOM(SpriteID.FIXED_MODE_MINIMAP_FRAME_BOTTOM, AROUND_2005, AROUND_2006), + FIXED_MODE_TOP_RIGHT_CORNER(SpriteID.FIXED_MODE_TOP_RIGHT_CORNER, AROUND_2005, AROUND_2006), + + RESIZEABLE_MODE_TABS_TOP_ROW(SpriteID.RESIZEABLE_MODE_TABS_TOP_ROW, AROUND_2010), + RESIZEABLE_MODE_TABS_BOTTOM_ROW(SpriteID.RESIZEABLE_MODE_TABS_BOTTOM_ROW, AROUND_2010), + RESIZEABLE_MODE_SIDE_PANEL_EDGE_LEFT(SpriteID.RESIZEABLE_MODE_SIDE_PANEL_EDGE_LEFT, AROUND_2010), + RESIZEABLE_MODE_SIDE_PANEL_EDGE_RIGHT(SpriteID.RESIZEABLE_MODE_SIDE_PANEL_EDGE_RIGHT, AROUND_2010), + RESIZEABLE_MODE_MINIMAP_AND_COMPASS_FRAME(SpriteID.RESIZEABLE_MODE_MINIMAP_AND_COMPASS_FRAME, AROUND_2010), + RESIZEABLE_MODE_TAB_STONE_MIDDLE(SpriteID.RESIZEABLE_MODE_TAB_STONE_MIDDLE, AROUND_2010), + RESIZEABLE_MODE_TAB_STONE_MIDDLE_SELECTED(SpriteID.RESIZEABLE_MODE_TAB_STONE_MIDDLE_SELECTED, AROUND_2010); + + private int spriteID; + private Skin[] skin; + + SpriteOverride(int spriteID, Skin... skin) + { + this.spriteID = spriteID; + this.skin = skin; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOffset.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOffset.java new file mode 100644 index 0000000000..8b189e57eb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOffset.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Raqes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.interfacestyles; + +import lombok.Getter; +import net.runelite.api.widgets.WidgetInfo; + +@Getter +enum WidgetOffset +{ + RESIZABLE_2010_COMBAT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_COMBAT_TAB, -4, 1, null, null), + RESIZABLE_2010_COMBAT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_COMBAT_ICON, 5, null, null, null), + RESIZABLE_2010_STATS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_STATS_TAB, 35, 1, null, null), + RESIZABLE_2010_STATS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_STATS_ICON, 35, null, null, null), + RESIZABLE_2010_QUESTS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_QUESTS_TAB, 69, 1, 33, null), + RESIZABLE_2010_QUESTS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_QUESTS_ICON, 70, 1, 33, null), + RESIZABLE_2010_INVENTORY_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB, 103, 1, null, null), + RESIZABLE_2010_INVENTORY_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_ICON, null, null, null, null), + RESIZABLE_2010_EQUIPMENT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_EQUIPMENT_ICON, null, 3, null, null), + RESIZABLE_2010_EQUIPMENT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_EQUIPMENT_TAB, null, 1, null, null), + RESIZABLE_2010_PRAYER_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_PRAYER_TAB, 171, 1, null, null), + RESIZABLE_2010_PRAYER_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_PRAYER_ICON, 172, 1, null, null), + RESIZABLE_2010_MAGIC_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_MAGIC_TAB, 205, 1, null, null), + RESIZABLE_2010_MAGIC_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_MAGIC_ICON, 206, null, null, null), + RESIZABLE_2010_FRENDS_CHAT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_FRIENDS_CHAT_TAB, -4, 1, null, null), + RESIZABLE_2010_FRIENDS_CHAT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_FRIENDS_CHAT_ICON, 2, 1, null, null), + RESIZABLE_2010_FRIENDS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_FRIENDS_TAB, 35, 1, null, null), + RESIZABLE_2010_FRIENDS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_FRIENDS_ICON, 37, 1, null, null), + RESIZABLE_2010_IGNORES_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_IGNORES_TAB, 69, 1, null, null), + RESIZABLE_2010_IGNORES_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_IGNORES_ICON, 71, null, null, null), + RESIZABLE_2010_LOGOUT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_LOGOUT_TAB, 103, 1, null, null), + RESIZABLE_2010_LOGOUT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_LOGOUT_ICON, 104, 2, null, null), + RESIZABLE_2010_OPTIONS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_OPTIONS_TAB, null, 1, null, null), + RESIZABLE_2010_OPTIONS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_OPTIONS_ICON, 138, null, null, null), + RESIZABLE_2010_EMOTES_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_EMOTES_TAB, 171, 1, null, null), + RESIZABLE_2010_EMOTES_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_EMOTES_ICON, 172, 2, null, null), + RESIZABLE_2010_MUSIC_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_MUSIC_TAB, 205, 1, null, null), + RESIZABLE_2010_MUSIC_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_MUSIC_ICON, 204, 3, null, null), + + RESIZABLE_BOTTOM_2010_COMBAT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_COMBAT_ICON, 2, null, null, null), + RESIZABLE_BOTTOM_2010_STATS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_STATS_ICON, 32, null, null, null), + RESIZABLE_BOTTOM_2010_QUESTS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_QUESTS_ICON, null, 1, null, null), + RESIZABLE_BOTTOM_2010_EQUIPMENT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_EQUIPMENT_ICON, 132, 3, null, null), + RESIZABLE_BOTTOM_2010_PRAYERS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_PRAYER_ICON, 165, 1, null, null), + RESIZABLE_BOTTOM_2010_LOGOUT_BUTTON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_LOGOUT_BUTTON, 185, null, null, null), + RESIZABLE_BOTTOM_2010_MAGIC_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_MAGIC_ICON, null, 2, null, null), + RESIZABLE_BOTTOM_2010_FRIEND_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_FRIEND_ICON, null, 3, null, null), + RESIZABLE_BOTTOM_2010_FRIEND_CHAT_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_FRIEND_CHAT_ICON, null, 2, null, null), + RESIZABLE_BOTTOM_2010_OPTIONS_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_OPTIONS_ICON, null, 2, null, null), + RESIZABLE_BOTTOM_2010_EMOTES_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_EMOTES_ICON, null, 3, null, null), + RESIZABLE_BOTTOM_2010_MUSIC_ICON(Skin.AROUND_2010, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_MUSIC_ICON, null, 3, null, null), + + FIXED_2010_COMBAT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_COMBAT_TAB, 2, 1, null, null), + FIXED_2010_COMBAT_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_COMBAT_ICON, 11, null, null, null), + FIXED_2010_STATS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_STATS_TAB, 41, 1, null, null), + FIXED_2010_STATS_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_STATS_ICON, 41, null, null, null), + FIXED_2010_QUESTS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_QUESTS_TAB, 75, 1, 33, null), + FIXED_2010_QUESTS_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_QUESTS_ICON, 75, 0, null, null), + FIXED_2010_INVENTORY_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB, 109, 1, null, null), + FIXED_2010_INVENTORY_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_INVENTORY_ICON, 111, -1, null, null), + FIXED_2010_EQUIPMENT_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_EQUIPMENT_ICON, 143, 2, null, null), + FIXED_2010_PRAYER_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_PRAYER_TAB, 177, 1, null, null), + FIXED_2010_PRAYER_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_PRAYER_ICON, 178, 1, null, null), + FIXED_2010_MAGIC_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_MAGIC_TAB, 211, 1, null, null), + FIXED_2010_MAGIC_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_MAGIC_ICON, 212, 1, null, null), + FIXED_2010_FRIENDS_CHAT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_TAB, 0, 1, null, null), + FIXED_2010_FRIENDS_CHAT_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_ICON, 5, null, null, null), + FIXED_2010_FRIENDS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_FRIENDS_TAB, 38, 1, 33, null), + FIXED_2010_FRIENDS_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_FRIENDS_ICON, 40, null, null, null), + FIXED_2010_IGNORES_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_IGNORES_TAB, 72, 1, null, null), + FIXED_2010_IGNORES_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_IGNORES_ICON, 74, null, null, null), + FIXED_2010_LOGOUT_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_LOGOUT_TAB, 106, 1, null, null), + FIXED_2010_LOGOUT_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_LOGOUT_ICON, 107, 2, null, null), + FIXED_2010_OPTIONS_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_OPTIONS_TAB, 140, 1, null, null), + FIXED_2010_OPTIONS_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_OPTIONS_ICON, 143, null, null, null), + FIXED_2010_EMOTES_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_EMOTES_TAB, 174, 1, null, null), + FIXED_2010_EMOTES_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_EMOTES_ICON, 177, 2, null, null), + FIXED_2010_MUSIC_HIGHLIGHT(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_MUSIC_TAB, 208, 1, null, null), + FIXED_2010_MUSIC_ICON(Skin.AROUND_2010, WidgetInfo.FIXED_VIEWPORT_MUSIC_ICON, 209, 2, null, null), + + RESIZABLE_2005_QUESTS_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_QUESTS_ICON, 72, 0, null, null), + RESIZABLE_2005_LOGOUT_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_LOGOUT_ICON, null, null, null, null), + RESIZABLE_2005_OPTIONS_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_OPTIONS_ICON, 137, null, null, null), + RESIZABLE_2005_EMOTE_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_EMOTES_ICON, 173, 1, null, null), + RESIZABLE_2005_INVENTORY_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_ICON, null, -2, null, null), + RESIZABLE_2005_EQUIPMENT_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_EQUIPMENT_ICON, null, 2, null, null), + RESIZABLE_2005_MUSIC_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_MUSIC_ICON, null, 3, null, null), + + RESIZABLE_BOTTOM_2005_INVENTORY_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_ICON, 98, 2, null, null), + RESIZABLE_BOTTOM_2005_QUESTS_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_QUESTS_ICON, 67, 0, null, null), + RESIZABLE_BOTTOM_2005_EQUIPMENT_ICON(Skin.AROUND_2005, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_EQUIPMENT_ICON, 132, 2, null, null), + + FIXED_2005_ROOT_INTERFACE_CONTAINER(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_ROOT_INTERFACE_CONTAINER, null, null, 197, null), + FIXED_2005_INTERFACE_CONTAINER(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_INTERFACE_CONTAINER, 7, null, null, null), + FIXED_2005_BANK_CONTAINER(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_BANK_CONTAINER, 7, null, null, null), + FIXED_2005_COMBAT_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_COMBAT_TAB, 19, 2, null, null), + FIXED_2005_COMBAT_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_COMBAT_ICON, 28, 1, null, null), + FIXED_2005_STATS_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_STATS_TAB, 55, null, 30, null), + FIXED_2005_STATS_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_STATS_ICON, 51, null, null, null), + FIXED_2005_QUESTS_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_QUESTS_TAB, 82, 1, 30, null), + FIXED_2005_QUESTS_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_QUESTS_ICON, 80, null, null, null), + FIXED_2005_INVENTORY_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB, null, null, 45, null), + FIXED_2005_INVENTORY_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_INVENTORY_ICON, 113, 1, null, null), + FIXED_2005_EQUIPMENT_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_EQUIPMENT_TAB, 153, 1, 30, null), + FIXED_2005_EQUIPMENT_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_EQUIPMENT_ICON, 151, 4, null, null), + FIXED_2005_PRAYER_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_PRAYER_TAB, 181, null, 30, null), + FIXED_2005_PRAYER_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_PRAYER_ICON, 178, null, null, null), + FIXED_2005_MAGIC_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_MAGIC_TAB, 209, 1, 30, null), + FIXED_2005_MAGIC_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_MAGIC_ICON, 206, 2, null, null), + FIXED_2005_FRIENDS_CHAT_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_TAB, 15, null, null, null), + FIXED_2005_FRIENDS_CHAT_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_ICON, 22, 0, null, null), + FIXED_2005_FRIENDS_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_FRIENDS_TAB, 51, null, 30, null), + FIXED_2005_FRIENDS_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_FRIENDS_ICON, 49, -1, null, null), + FIXED_2005_IGNORES_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_IGNORES_TAB, 79, null, 30, null), + FIXED_2005_IGNORES_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_IGNORES_ICON, 78, null, null, null), + FIXED_2005_LOGOUT_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_LOGOUT_TAB, 107, 1, 45, null), + FIXED_2005_LOGOUT_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_LOGOUT_ICON, 112, null, null, null), + FIXED_2005_OPTIONS_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_OPTIONS_TAB, 150, null, 30, null), + FIXED_2005_OPTIONS_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_OPTIONS_ICON, 148, -1, null, null), + FIXED_2005_EMOTES_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_EMOTES_TAB, 178, null, 30, null), + FIXED_2005_EMOTES_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_EMOTES_ICON, 178, 1, null, null), + FIXED_2005_MUSIC_HIGHLIGHT(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_MUSIC_TAB, 206, null, 30, null), + FIXED_2005_MUSIC_ICON(Skin.AROUND_2005, WidgetInfo.FIXED_VIEWPORT_MUSIC_ICON, 202, 5, null, null), + + FIXED_2006_ROOT_INTERFACE_CONTAINER(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_ROOT_INTERFACE_CONTAINER, null, null, 197, null), + FIXED_2006_INTERFACE_CONTAINER(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_INTERFACE_CONTAINER, 7, null, null, null), + FIXED_2006_BANK_CONTAINER(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_BANK_CONTAINER, 7, null, null, null), + FIXED_2006_COMBAT_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_COMBAT_TAB, 19, 2, null, null), + FIXED_2006_COMBAT_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_COMBAT_ICON, 26, 1, null, null), + FIXED_2006_STATS_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_STATS_TAB, 55, null, 30, null), + FIXED_2006_STATS_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_STATS_ICON, 54, null, null, null), + FIXED_2006_QUESTS_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_QUESTS_TAB, 82, 1, 30, null), + FIXED_2006_QUESTS_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_QUESTS_ICON, 81, null, null, null), + FIXED_2006_INVENTORY_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB, null, null, 45, null), + FIXED_2006_INVENTORY_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_INVENTORY_ICON, 114, 2, null, null), + FIXED_2006_EQUIPMENT_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_EQUIPMENT_TAB, 153, 1, 30, null), + FIXED_2006_EQUIPMENT_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_EQUIPMENT_ICON, 152, 2, null, null), + FIXED_2006_PRAYER_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_PRAYER_TAB, 181, null, 30, null), + FIXED_2006_PRAYER_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_PRAYER_ICON, 180, 2, null, null), + FIXED_2006_MAGIC_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_MAGIC_TAB, 209, 1, 30, null), + FIXED_2006_MAGIC_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_MAGIC_ICON, 207, 4, null, null), + FIXED_2006_FRIENDS_CHAT_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_TAB, 15, null, null, null), + FIXED_2006_FRIENDS_CHAT_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_ICON, 22, -1, null, null), + FIXED_2006_FRIENDS_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_FRIENDS_TAB, 51, null, 30, null), + FIXED_2006_FRIENDS_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_FRIENDS_ICON, 49, 1, null, null), + FIXED_2006_IGNORES_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_IGNORES_TAB, 79, null, 30, null), + FIXED_2006_IGNORES_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_IGNORES_ICON, 76, null, null, null), + FIXED_2006_LOGOUT_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_LOGOUT_TAB, 107, 1, 45, null), + FIXED_2006_LOGOUT_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_LOGOUT_ICON, 113, 2, null, null), + FIXED_2006_OPTIONS_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_OPTIONS_TAB, 150, null, 30, null), + FIXED_2006_OPTIONS_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_OPTIONS_ICON, 147, null, null, null), + FIXED_2006_EMOTES_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_EMOTES_TAB, 178, null, 30, null), + FIXED_2006_EMOTES_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_EMOTES_ICON, 177, null, null, null), + FIXED_2006_MUSIC_HIGHLIGHT(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_MUSIC_TAB, 206, null, 30, null), + FIXED_2006_MUSIC_ICON(Skin.AROUND_2006, WidgetInfo.FIXED_VIEWPORT_MUSIC_ICON, 202, -1, null, null); + + private Skin skin; + private WidgetInfo widgetInfo; + private Integer offsetX; + private Integer offsetY; + private Integer width; + private Integer height; + + WidgetOffset(Skin skin, WidgetInfo widgetInfo, Integer offsetX, Integer offsetY, Integer width, Integer height) + { + this.skin = skin; + this.widgetInfo = widgetInfo; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.width = width; + this.height = height; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOverride.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOverride.java new file mode 100644 index 0000000000..1dfbf80766 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/WidgetOverride.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Raqes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.interfacestyles; + +import lombok.Getter; +import net.runelite.api.widgets.WidgetInfo; + +@Getter +enum WidgetOverride +{ + FIXED_CORNER_TOP_LEFT_2005(Skin.AROUND_2005, "1026", WidgetInfo.FIXED_VIEWPORT_COMBAT_TAB), + FIXED_CORNER_TOP_RIGHT_2005(Skin.AROUND_2005, "1027", WidgetInfo.FIXED_VIEWPORT_MAGIC_TAB), + FIXED_CORNER_BOTTOM_LEFT_2005(Skin.AROUND_2005, "1028", WidgetInfo.FIXED_VIEWPORT_FRIENDS_CHAT_TAB), + FIXED_CORNER_BOTTOM_RIGHT_2005(Skin.AROUND_2005, "1029", WidgetInfo.FIXED_VIEWPORT_MUSIC_TAB), + FIXED_TOP_LEFT_2005(Skin.AROUND_2005, "1030_top_left", WidgetInfo.FIXED_VIEWPORT_STATS_TAB, WidgetInfo.FIXED_VIEWPORT_QUESTS_TAB), + FIXED_TOP_RIGHT_2005(Skin.AROUND_2005, "1030_top_right", WidgetInfo.FIXED_VIEWPORT_EQUIPMENT_TAB, WidgetInfo.FIXED_VIEWPORT_PRAYER_TAB), + FIXED_TOP_MIDDLE_2005(Skin.AROUND_2005, "1030_top_middle", WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB), + FIXED_BOTTOM_LEFT_2005(Skin.AROUND_2005, "1030_bottom_left", WidgetInfo.FIXED_VIEWPORT_FRIENDS_TAB, WidgetInfo.FIXED_VIEWPORT_IGNORES_TAB), + FIXED_BOTTOM_RIGHT_2005(Skin.AROUND_2005, "1030_bottom_middle", WidgetInfo.FIXED_VIEWPORT_LOGOUT_TAB), + FIXED_BOTTOM_MIDDLE_2005(Skin.AROUND_2005, "1030_bottom_right", WidgetInfo.FIXED_VIEWPORT_OPTIONS_TAB, WidgetInfo.FIXED_VIEWPORT_EMOTES_TAB); + + private Skin skin; + private String name; + private WidgetInfo[] widgetInfo; + + WidgetOverride(Skin skin, String name, WidgetInfo... widgetInfo) + { + this.skin = skin; + this.name = name; + this.widgetInfo = widgetInfo; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridConfig.java new file mode 100644 index 0000000000..45551025b7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventorygrid; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; + +@ConfigGroup("inventorygrid") +public interface InventoryGridConfig extends Config +{ + @ConfigItem( + keyName = "showItem", + name = "Show item", + description = "Show a preview of the item in the new slot" + ) + default boolean showItem() + { + return true; + } + + @ConfigItem( + keyName = "showGrid", + name = "Show grid", + description = "Show a grid on the inventory while dragging" + ) + default boolean showGrid() + { + return true; + } + + @ConfigItem( + keyName = "showHighlight", + name = "Highlight background", + description = "Show a green background highlight on the new slot" + ) + default boolean showHighlight() + { + return true; + } + + @ConfigItem( + keyName = "dragDelay", + name = "Drag delay", + description = "Time to wait after an item press before the overlay is enabled" + ) + @Units(Units.MILLISECONDS) + default int dragDelay() + { + return 0; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridOverlay.java new file mode 100644 index 0000000000..3d606d8d6a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridOverlay.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventorygrid; + +import com.google.inject.Inject; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +class InventoryGridOverlay extends Overlay +{ + private static final int INVENTORY_SIZE = 28; + private static final int DISTANCE_TO_ACTIVATE_HOVER = 5; + + private static final Color HIGHLIGHT = new Color(0, 255, 0, 45); + private static final Color GRID = new Color(255, 255, 255, 45); + + private final InventoryGridConfig config; + private final Client client; + private final ItemManager itemManager; + + private Point initialMousePoint; + private boolean hoverActive = false; + + @Inject + private InventoryGridOverlay(InventoryGridConfig config, Client client, ItemManager itemManager) + { + this.itemManager = itemManager; + this.client = client; + this.config = config; + + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) + { + final Widget draggingWidget = getDraggedWidget(); + if (draggingWidget == null) + { + initialMousePoint = null; + hoverActive = false; + // not dragging + return null; + } + + // grid is only supported on bank inventory and inventory + Widget inventoryWidget = draggingWidget.isIf3() ? + client.getWidget(WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER) : + client.getWidget(WidgetInfo.INVENTORY); + + // with if3 the dragged widget is a child of the inventory, with if1 it is an item of the inventory (and the same widget) + if (inventoryWidget == null || (draggingWidget.isIf3() ? draggingWidget.getParent() != inventoryWidget : draggingWidget != inventoryWidget)) + { + return null; + } + + final net.runelite.api.Point mouse = client.getMouseCanvasPosition(); + final Point mousePoint = new Point(mouse.getX(), mouse.getY()); + final int draggedItemIndex = draggingWidget.isIf3() ? draggingWidget.getIndex() : client.getIf1DraggedItemIndex(); + final WidgetItem draggedItem = getWidgetItem(inventoryWidget, draggedItemIndex); + final Rectangle initialBounds = draggedItem.getCanvasBounds(false); + + if (initialMousePoint == null) + { + initialMousePoint = mousePoint; + } + + if (draggedItem.getId() == -1 + || client.getItemPressedDuration() < config.dragDelay() / Constants.CLIENT_TICK_LENGTH + || !hoverActive && initialMousePoint.distance(mousePoint) < DISTANCE_TO_ACTIVATE_HOVER) + { + return null; + } + + hoverActive = true; + + for (int i = 0; i < INVENTORY_SIZE; ++i) + { + final WidgetItem targetWidgetItem = getWidgetItem(inventoryWidget, i); + final Rectangle bounds = targetWidgetItem.getCanvasBounds(false); + boolean inBounds = bounds.contains(mousePoint); + + if (config.showItem() && inBounds) + { + drawItem(graphics, bounds, draggedItem); + drawItem(graphics, initialBounds, targetWidgetItem); + } + + if (config.showHighlight() && inBounds) + { + graphics.setColor(HIGHLIGHT); + graphics.fill(bounds); + } + else if (config.showGrid()) + { + graphics.setColor(GRID); + graphics.fill(bounds); + } + } + + return null; + } + + private Widget getDraggedWidget() + { + Widget widget = client.getIf1DraggedWidget(); // if1 drag + if (widget != null) + { + return widget; + } + return client.getDraggedWidget(); // if3 drag + } + + private static WidgetItem getWidgetItem(Widget parentWidget, int idx) + { + if (parentWidget.isIf3()) + { + Widget wi = parentWidget.getChild(idx); + return new WidgetItem(wi.getItemId(), wi.getItemQuantity(), -1, wi.getBounds(), parentWidget, wi.getBounds()); + } + else + { + return parentWidget.getWidgetItem(idx); + } + } + + private void drawItem(Graphics2D graphics, Rectangle bounds, WidgetItem item) + { + if (item.getId() == -1) + { + return; + } + + final BufferedImage draggedItemImage = itemManager.getImage(item.getId(), item.getQuantity(), false); + final int x = (int) bounds.getX(); + final int y = (int) bounds.getY(); + + graphics.setComposite(AlphaComposite.SrcOver.derive(0.3f)); + graphics.drawImage(draggedItemImage, x, y, null); + graphics.setComposite(AlphaComposite.SrcOver); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridPlugin.java new file mode 100644 index 0000000000..dc6b0ae372 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorygrid/InventoryGridPlugin.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventorygrid; + +import com.google.inject.Inject; +import com.google.inject.Provides; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Inventory Grid", + description = "Shows a grid over the inventory and a preview of where items will be dragged", + tags = {"items", "overlay"}, + enabledByDefault = false +) +public class InventoryGridPlugin extends Plugin +{ + @Inject + private InventoryGridOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Override + public void startUp() + { + overlayManager.add(overlay); + } + + @Override + public void shutDown() + { + overlayManager.remove(overlay); + } + + @Provides + InventoryGridConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(InventoryGridConfig.class); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java new file mode 100644 index 0000000000..745d64594b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018 kulers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventorytags; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("inventorytags") +public interface InventoryTagsConfig extends Config +{ + enum DisplayMode + { + OUTLINE, + UNDERLINE + } + + String GROUP = "inventorytags"; + + @ConfigItem( + position = 0, + keyName = "groupColor1", + name = "Group 1 Color", + description = "Color of the Tag" + ) + default Color getGroup1Color() + { + return new Color(255, 0, 0); + } + + @ConfigItem( + position = 1, + keyName = "groupColor2", + name = "Group 2 Color", + description = "Color of the Tag" + ) + default Color getGroup2Color() + { + return new Color(0, 255, 0); + } + + @ConfigItem( + position = 2, + keyName = "groupColor3", + name = "Group 3 Color", + description = "Color of the Tag" + ) + default Color getGroup3Color() + { + return new Color(0, 0, 255); + } + + @ConfigItem( + position = 3, + keyName = "groupColor4", + name = "Group 4 Color", + description = "Color of the Tag" + ) + default Color getGroup4Color() + { + return new Color(255, 0, 255); + } + + @ConfigItem( + position = 4, + keyName = "groupColor5", + name = "Group 5 Color", + description = "Color of the Tag" + ) + default Color getGroup5Color() + { + return new Color(255, 255, 0); + } + + @ConfigItem( + position = 5, + keyName = "groupColor6", + name = "Group 6 Color", + description = "Color of the Tag" + ) + default Color getGroup6Color() + { + return new Color(0, 255, 255); + } + + @ConfigItem( + position = 6, + keyName = "displayMode", + name = "Display mode", + description = "How tags are displayed in the inventory" + ) + default DisplayMode getDisplayMode() + { + return DisplayMode.OUTLINE; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java new file mode 100644 index 0000000000..c6fafc9466 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018 kulers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventorytags; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import javax.inject.Inject; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.inventorytags.InventoryTagsConfig.DisplayMode; +import net.runelite.client.ui.overlay.WidgetItemOverlay; + +public class InventoryTagsOverlay extends WidgetItemOverlay +{ + private final ItemManager itemManager; + private final InventoryTagsPlugin plugin; + private final InventoryTagsConfig config; + + @Inject + private InventoryTagsOverlay(ItemManager itemManager, InventoryTagsPlugin plugin, InventoryTagsConfig config) + { + this.itemManager = itemManager; + this.plugin = plugin; + this.config = config; + showOnEquipment(); + showOnInventory(); + } + + @Override + public void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget) + { + final String group = plugin.getTag(itemId); + if (group != null) + { + final Color color = plugin.getGroupNameColor(group); + final DisplayMode displayMode = config.getDisplayMode(); + if (color != null) + { + Rectangle bounds = itemWidget.getCanvasBounds(); + if (displayMode == DisplayMode.OUTLINE) + { + final BufferedImage outline = itemManager.getItemOutline(itemId, itemWidget.getQuantity(), color); + graphics.drawImage(outline, (int) bounds.getX(), (int) bounds.getY(), null); + } + else + { + int heightOffSet = (int) bounds.getY() + (int) bounds.getHeight() + 2; + graphics.setColor(color); + graphics.drawLine((int) bounds.getX(), heightOffSet, (int) bounds.getX() + (int) bounds.getWidth(), heightOffSet); + } + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java new file mode 100644 index 0000000000..77a0548287 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2018 kulers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventorytags; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.inject.Provides; +import java.awt.Color; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.events.MenuOpened; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.WidgetMenuOptionClicked; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.menus.MenuManager; +import net.runelite.client.menus.WidgetMenuOption; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Inventory Tags", + description = "Add the ability to tag items in your inventory", + tags = {"highlight", "items", "overlay", "tagging"}, + enabledByDefault = false +) +public class InventoryTagsPlugin extends Plugin +{ + private static final String ITEM_KEY_PREFIX = "item_"; + + private static final String SETNAME_GROUP_1 = "Group 1"; + private static final String SETNAME_GROUP_2 = "Group 2"; + private static final String SETNAME_GROUP_3 = "Group 3"; + private static final String SETNAME_GROUP_4 = "Group 4"; + private static final String SETNAME_GROUP_5 = "Group 5"; + private static final String SETNAME_GROUP_6 = "Group 6"; + + private static final String CONFIGURE = "Configure"; + private static final String SAVE = "Save"; + private static final String MENU_TARGET = "Inventory Tags"; + private static final String MENU_SET = "Mark"; + private static final String MENU_REMOVE = "Remove"; + + private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE, + MENU_TARGET, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB); + private static final WidgetMenuOption FIXED_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE, + MENU_TARGET, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB); + private static final WidgetMenuOption RESIZABLE_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB); + private static final WidgetMenuOption RESIZABLE_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB); + private static final WidgetMenuOption RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB); + private static final WidgetMenuOption RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB); + + private static final List GROUPS = ImmutableList.of(SETNAME_GROUP_6, SETNAME_GROUP_5, SETNAME_GROUP_4, SETNAME_GROUP_3, SETNAME_GROUP_2, SETNAME_GROUP_1); + + @Inject + private Client client; + + @Inject + private ConfigManager configManager; + + @Inject + private InventoryTagsConfig config; + + @Inject + private MenuManager menuManager; + + @Inject + private InventoryTagsOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + private boolean editorMode; + + @Provides + InventoryTagsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(InventoryTagsConfig.class); + } + + String getTag(int itemId) + { + String tag = configManager.getConfiguration(InventoryTagsConfig.GROUP, ITEM_KEY_PREFIX + itemId); + if (tag == null || tag.isEmpty()) + { + return null; + } + + return tag; + } + + private void setTag(int itemId, String tag) + { + configManager.setConfiguration(InventoryTagsConfig.GROUP, ITEM_KEY_PREFIX + itemId, tag); + } + + private void unsetTag(int itemId) + { + configManager.unsetConfiguration(InventoryTagsConfig.GROUP, ITEM_KEY_PREFIX + itemId); + } + + @Override + protected void startUp() throws Exception + { + refreshInventoryMenuOptions(); + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + removeInventoryMenuOptions(); + overlayManager.remove(overlay); + editorMode = false; + } + + @Subscribe + public void onWidgetMenuOptionClicked(final WidgetMenuOptionClicked event) + { + if (event.getWidget() == WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB + || event.getWidget() == WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB + || event.getWidget() == WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB) + { + editorMode = event.getMenuOption().equals(CONFIGURE) && Text.removeTags(event.getMenuTarget()).equals(MENU_TARGET); + refreshInventoryMenuOptions(); + } + } + + @Subscribe + public void onMenuOptionClicked(final MenuOptionClicked event) + { + if (event.getMenuAction() != MenuAction.RUNELITE) + { + return; + } + + final String selectedMenu = Text.removeTags(event.getMenuTarget()); + + if (event.getMenuOption().equals(MENU_SET)) + { + setTag(event.getId(), selectedMenu); + } + else if (event.getMenuOption().equals(MENU_REMOVE)) + { + unsetTag(event.getId()); + } + } + + @Subscribe + public void onMenuOpened(final MenuOpened event) + { + final MenuEntry firstEntry = event.getFirstEntry(); + + if (firstEntry == null) + { + return; + } + + final int widgetId = firstEntry.getParam1(); + + // Inventory item menu + if (widgetId == WidgetInfo.INVENTORY.getId() && editorMode) + { + int itemId = firstEntry.getIdentifier(); + + if (itemId == -1) + { + return; + } + + MenuEntry[] menuList = new MenuEntry[GROUPS.size() + 1]; + int num = 0; + + // preserve the 'Cancel' option as the client will reuse the first entry for Cancel and only resets option/action + menuList[num++] = event.getMenuEntries()[0]; + + for (final String groupName : GROUPS) + { + final String group = getTag(itemId); + final MenuEntry newMenu = new MenuEntry(); + final Color color = getGroupNameColor(groupName); + newMenu.setOption(groupName.equals(group) ? MENU_REMOVE : MENU_SET); + newMenu.setTarget(ColorUtil.prependColorTag(groupName, MoreObjects.firstNonNull(color, Color.WHITE))); + newMenu.setIdentifier(itemId); + newMenu.setParam1(widgetId); + newMenu.setType(MenuAction.RUNELITE.getId()); + menuList[num++] = newMenu; + } + + client.setMenuEntries(menuList); + } + } + + Color getGroupNameColor(final String name) + { + switch (name) + { + case SETNAME_GROUP_1: + return config.getGroup1Color(); + case SETNAME_GROUP_2: + return config.getGroup2Color(); + case SETNAME_GROUP_3: + return config.getGroup3Color(); + case SETNAME_GROUP_4: + return config.getGroup4Color(); + case SETNAME_GROUP_5: + return config.getGroup5Color(); + case SETNAME_GROUP_6: + return config.getGroup6Color(); + } + + return null; + } + + private void removeInventoryMenuOptions() + { + menuManager.removeManagedCustomMenu(FIXED_INVENTORY_TAB_CONFIGURE); + menuManager.removeManagedCustomMenu(FIXED_INVENTORY_TAB_SAVE); + menuManager.removeManagedCustomMenu(RESIZABLE_INVENTORY_TAB_CONFIGURE); + menuManager.removeManagedCustomMenu(RESIZABLE_INVENTORY_TAB_SAVE); + menuManager.removeManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE); + menuManager.removeManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE); + } + + private void refreshInventoryMenuOptions() + { + removeInventoryMenuOptions(); + if (editorMode) + { + menuManager.addManagedCustomMenu(FIXED_INVENTORY_TAB_SAVE); + menuManager.addManagedCustomMenu(RESIZABLE_INVENTORY_TAB_SAVE); + menuManager.addManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE); + } + else + { + menuManager.addManagedCustomMenu(FIXED_INVENTORY_TAB_CONFIGURE); + menuManager.addManagedCustomMenu(RESIZABLE_INVENTORY_TAB_CONFIGURE); + menuManager.addManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java new file mode 100644 index 0000000000..2c3dc07102 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, Matthew C + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventoryviewer; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Keybind; + +@ConfigGroup(InventoryViewerConfig.GROUP) +public interface InventoryViewerConfig extends Config +{ + String GROUP = "inventoryViewer"; + + @ConfigItem( + keyName = "toggleKeybind", + name = "Toggle Overlay", + description = "Binds a key (combination) to toggle the overlay.", + position = 0 + ) + default Keybind toggleKeybind() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "hiddenDefault", + name = "Hidden by default", + description = "Whether or not the overlay is hidden by default.", + position = 1 + ) + default boolean hiddenDefault() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java new file mode 100644 index 0000000000..2896a9cf65 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 AWPH-I + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventoryviewer; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.image.BufferedImage; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.ComponentOrientation; +import net.runelite.client.ui.overlay.components.ImageComponent; + +class InventoryViewerOverlay extends OverlayPanel +{ + private static final int INVENTORY_SIZE = 28; + private static final ImageComponent PLACEHOLDER_IMAGE = new ImageComponent( + new BufferedImage(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR)); + + private final Client client; + private final ItemManager itemManager; + private boolean hidden; + + @Inject + private InventoryViewerOverlay(Client client, ItemManager itemManager, InventoryViewerConfig config) + { + setPosition(OverlayPosition.BOTTOM_RIGHT); + panelComponent.setWrap(true); + panelComponent.setGap(new Point(6, 4)); + panelComponent.setPreferredSize(new Dimension(4 * (Constants.ITEM_SPRITE_WIDTH + 6), 0)); + panelComponent.setOrientation(ComponentOrientation.HORIZONTAL); + this.itemManager = itemManager; + this.client = client; + this.hidden = config.hiddenDefault(); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (hidden) + { + return null; + } + + final ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY); + + if (itemContainer == null) + { + return null; + } + + final Item[] items = itemContainer.getItems(); + + for (int i = 0; i < INVENTORY_SIZE; i++) + { + if (i < items.length) + { + final Item item = items[i]; + if (item.getQuantity() > 0) + { + final BufferedImage image = getImage(item); + if (image != null) + { + panelComponent.getChildren().add(new ImageComponent(image)); + continue; + } + } + } + + // put a placeholder image so each item is aligned properly and the panel is not resized + panelComponent.getChildren().add(PLACEHOLDER_IMAGE); + } + + return super.render(graphics); + } + + private BufferedImage getImage(Item item) + { + ItemComposition itemComposition = itemManager.getItemComposition(item.getId()); + return itemManager.getImage(item.getId(), item.getQuantity(), itemComposition.isStackable()); + } + + protected void toggle() + { + hidden = !hidden; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java new file mode 100644 index 0000000000..434821522e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 AWPH-I + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.inventoryviewer; + +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.input.KeyManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.HotkeyListener; + +@PluginDescriptor( + name = "Inventory Viewer", + description = "Add an overlay showing the contents of your inventory", + tags = {"alternate", "items", "overlay", "second"}, + enabledByDefault = false +) +public class InventoryViewerPlugin extends Plugin +{ + @Inject + private InventoryViewerConfig config; + + @Inject + private InventoryViewerOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private KeyManager keyManager; + + @Provides + InventoryViewerConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(InventoryViewerConfig.class); + } + + @Override + public void startUp() + { + overlayManager.add(overlay); + keyManager.registerKeyListener(hotkeyListener); + } + + @Override + public void shutDown() + { + overlayManager.remove(overlay); + keyManager.unregisterKeyListener(hotkeyListener); + } + + private final HotkeyListener hotkeyListener = new HotkeyListener(() -> config.toggleKeybind()) + { + @Override + public void hotkeyPressed() + { + overlay.toggle(); + } + }; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java new file mode 100644 index 0000000000..0e8a4569fe --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2017, Devin French + * Copyright (c) 2019, Aleios + * Copyright (c) 2020, Unmoon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; + +@ConfigGroup("itemCharge") +public interface ItemChargeConfig extends Config +{ + @ConfigSection( + name = "Charge Settings", + description = "Configuration for which charges should be displayed", + position = 98 + ) + String chargesSection = "charges"; + + @ConfigSection( + name = "Notification Settings", + description = "Configuration for notifications", + position = 99 + ) + String notificationSection = "notifications"; + + @ConfigItem( + keyName = "veryLowWarningColor", + name = "Very Low Warning Color", + description = "The color of the overlay when charges are very low", + position = 1 + ) + default Color veryLowWarningColor() + { + return Color.RED; + } + + @ConfigItem( + keyName = "lowWarningColor", + name = "Low Warning Color", + description = "The color of the overlay when charges are low", + position = 2 + ) + default Color lowWarningolor() + { + return Color.YELLOW; + } + + @ConfigItem( + keyName = "veryLowWarning", + name = "Very Low Warning", + description = "The charge count for the very low warning color", + position = 3 + ) + default int veryLowWarning() + { + return 1; + } + + @ConfigItem( + keyName = "lowWarning", + name = "Low Warning", + description = "The charge count for the low warning color", + position = 4 + ) + default int lowWarning() + { + return 2; + } + + @ConfigItem( + keyName = "showTeleportCharges", + name = "Show Teleport Charges", + description = "Show teleport item charge counts", + position = 5, + section = chargesSection + ) + default boolean showTeleportCharges() + { + return true; + } + + @ConfigItem( + keyName = "showDodgyCount", + name = "Dodgy Necklace Count", + description = "Show Dodgy necklace charges", + position = 6, + section = chargesSection + ) + default boolean showDodgyCount() + { + return true; + } + + @ConfigItem( + keyName = "dodgyNotification", + name = "Dodgy Necklace Notification", + description = "Send a notification when a Dodgy necklace breaks", + position = 7, + section = notificationSection + ) + default boolean dodgyNotification() + { + return true; + } + + @ConfigItem( + keyName = "dodgyNecklace", + name = "", + description = "", + hidden = true + ) + default int dodgyNecklace() + { + return -1; + } + + @ConfigItem( + keyName = "dodgyNecklace", + name = "", + description = "" + ) + void dodgyNecklace(int dodgyNecklace); + + @ConfigItem( + keyName = "showImpCharges", + name = "Show Imp-in-a-box charges", + description = "Show Imp-in-a-box item charges", + position = 8, + section = chargesSection + ) + default boolean showImpCharges() + { + return true; + } + + @ConfigItem( + keyName = "showFungicideCharges", + name = "Show Fungicide Charges", + description = "Show Fungicide item charges", + position = 9, + section = chargesSection + ) + default boolean showFungicideCharges() + { + return true; + } + + @ConfigItem( + keyName = "showWateringCanCharges", + name = "Show Watering Can Charges", + description = "Show Watering can item charges", + position = 10, + section = chargesSection + ) + default boolean showWateringCanCharges() + { + return true; + } + + @ConfigItem( + keyName = "showWaterskinCharges", + name = "Show Waterskin Charges", + description = "Show Waterskin dose counts", + position = 11, + section = chargesSection + ) + default boolean showWaterskinCharges() + { + return true; + } + + @ConfigItem( + keyName = "showBellowCharges", + name = "Show Bellows Charges", + description = "Show Ogre bellows item charges", + position = 12, + section = chargesSection + ) + default boolean showBellowCharges() + { + return true; + } + + @ConfigItem( + keyName = "showBasketCharges", + name = "Show Basket Charges", + description = "Show Fruit basket item counts", + position = 13, + section = chargesSection + ) + default boolean showBasketCharges() + { + return true; + } + + @ConfigItem( + keyName = "showSackCharges", + name = "Show Sack Charges", + description = "Show Sack item counts", + position = 14, + section = chargesSection + ) + default boolean showSackCharges() + { + return true; + } + + @ConfigItem( + keyName = "showAbyssalBraceletCharges", + name = "Show Abyssal Bracelet Charges", + description = "Show Abyssal bracelet item charges", + position = 15, + section = chargesSection + ) + default boolean showAbyssalBraceletCharges() + { + return true; + } + + @ConfigItem( + keyName = "showAmuletOfChemistryCharges", + name = "Show Amulet of Chemistry Charges", + description = "Show Amulet of chemistry item charges", + position = 16, + section = chargesSection + ) + default boolean showAmuletOfChemistryCharges() + { + return true; + } + + @ConfigItem( + keyName = "amuletOfChemistry", + name = "", + description = "", + hidden = true + ) + default int amuletOfChemistry() + { + return -1; + } + + @ConfigItem( + keyName = "amuletOfChemistry", + name = "", + description = "" + ) + void amuletOfChemistry(int amuletOfChemistry); + + @ConfigItem( + keyName = "showAmuletOfBountyCharges", + name = "Show Amulet of Bounty Charges", + description = "Show Amulet of bounty item charges", + position = 17, + section = chargesSection + ) + default boolean showAmuletOfBountyCharges() + { + return true; + } + + @ConfigItem( + keyName = "amuletOfBounty", + name = "", + description = "", + hidden = true + ) + default int amuletOfBounty() + { + return -1; + } + + @ConfigItem( + keyName = "amuletOfBounty", + name = "", + description = "" + ) + void amuletOfBounty(int amuletOfBounty); + + @ConfigItem( + keyName = "recoilNotification", + name = "Ring of Recoil Notification", + description = "Send a notification when a Ring of recoil breaks", + position = 18, + section = notificationSection + ) + default boolean recoilNotification() + { + return false; + } + + @ConfigItem( + keyName = "showBindingNecklaceCharges", + name = "Show Binding Necklace Charges", + description = "Show Binding necklace item charges", + position = 19, + section = chargesSection + ) + default boolean showBindingNecklaceCharges() + { + return true; + } + + @ConfigItem( + keyName = "bindingNecklace", + name = "", + description = "", + hidden = true + ) + default int bindingNecklace() + { + return -1; + } + + @ConfigItem( + keyName = "bindingNecklace", + name = "", + description = "" + ) + void bindingNecklace(int bindingNecklace); + + @ConfigItem( + keyName = "bindingNotification", + name = "Binding Necklace Notification", + description = "Send a notification when a Binding necklace breaks", + position = 20, + section = notificationSection + ) + default boolean bindingNotification() + { + return true; + } + + @ConfigItem( + keyName = "showExplorerRingCharges", + name = "Show Explorer's Ring Alch Charges", + description = "Show Explorer's ring alchemy charges", + position = 21, + section = chargesSection + ) + default boolean showExplorerRingCharges() + { + return true; + } + + @ConfigItem( + keyName = "explorerRing", + name = "", + description = "", + hidden = true + ) + default int explorerRing() + { + return -1; + } + + @ConfigItem( + keyName = "explorerRing", + name = "", + description = "" + ) + void explorerRing(int explorerRing); + + @ConfigItem( + keyName = "showRingOfForgingCount", + name = "Show Ring of Forging Charges", + description = "Show Ring of forging item charges", + position = 22, + section = chargesSection + ) + default boolean showRingOfForgingCount() + { + return true; + } + + @ConfigItem( + keyName = "ringOfForging", + name = "", + description = "", + hidden = true + ) + default int ringOfForging() + { + return -1; + } + + @ConfigItem( + keyName = "ringOfForging", + name = "", + description = "" + ) + void ringOfForging(int ringOfForging); + + @ConfigItem( + keyName = "ringOfForgingNotification", + name = "Ring of Forging Notification", + description = "Send a notification when a Ring of forging breaks", + position = 23, + section = notificationSection + ) + default boolean ringOfForgingNotification() + { + return true; + } + + @ConfigItem( + keyName = "showInfoboxes", + name = "Show Infoboxes", + description = "Show an infobox with remaining charges for equipped items", + position = 24 + ) + default boolean showInfoboxes() + { + return false; + } + + @ConfigItem( + keyName = "showPotionDoseCount", + name = "Show Potion Doses", + description = "Show remaining potion doses", + position = 25, + section = chargesSection + ) + default boolean showPotionDoseCount() + { + return false; + } + + @ConfigItem( + keyName = "chronicle", + name = "", + description = "", + hidden = true + ) + default int chronicle() + { + return -1; + } + + @ConfigItem( + keyName = "chronicle", + name = "", + description = "" + ) + void chronicle(int chronicle); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java new file mode 100644 index 0000000000..7ee70d44b5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import lombok.Getter; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.client.ui.overlay.infobox.Counter; + +@Getter +class ItemChargeInfobox extends Counter +{ + private final ItemChargePlugin plugin; + private final ItemWithSlot item; + private final EquipmentInventorySlot slot; + + ItemChargeInfobox( + ItemChargePlugin plugin, + BufferedImage image, + String name, + int charges, + ItemWithSlot item, + EquipmentInventorySlot slot) + { + super(image, plugin, charges); + setTooltip(name); + this.plugin = plugin; + this.item = item; + this.slot = slot; + } + + @Override + public Color getTextColor() + { + return getPlugin().getColor(getCount()); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java new file mode 100644 index 0000000000..fc91487230 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2017, Seth + * Copyright (c) 2019, Aleios + * Copyright (c) 2020, Unmoon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import javax.inject.Inject; +import net.runelite.api.ItemID; +import net.runelite.api.widgets.WidgetItem; +import static net.runelite.client.plugins.itemcharges.ItemChargeType.*; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.WidgetItemOverlay; +import net.runelite.client.ui.overlay.components.TextComponent; + +class ItemChargeOverlay extends WidgetItemOverlay +{ + private final ItemChargePlugin itemChargePlugin; + private final ItemChargeConfig config; + + @Inject + ItemChargeOverlay(ItemChargePlugin itemChargePlugin, ItemChargeConfig config) + { + this.itemChargePlugin = itemChargePlugin; + this.config = config; + showOnInventory(); + showOnEquipment(); + } + + @Override + public void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget) + { + if (!displayOverlay()) + { + return; + } + + graphics.setFont(FontManager.getRunescapeSmallFont()); + + int charges; + if (itemId == ItemID.DODGY_NECKLACE) + { + if (!config.showDodgyCount()) + { + return; + } + + charges = config.dodgyNecklace(); + } + else if (itemId == ItemID.BINDING_NECKLACE) + { + if (!config.showBindingNecklaceCharges()) + { + return; + } + + charges = config.bindingNecklace(); + } + else if (itemId >= ItemID.EXPLORERS_RING_1 && itemId <= ItemID.EXPLORERS_RING_4) + { + if (!config.showExplorerRingCharges()) + { + return; + } + + charges = config.explorerRing(); + } + else if (itemId == ItemID.RING_OF_FORGING) + { + if (!config.showRingOfForgingCount()) + { + return; + } + + charges = config.ringOfForging(); + } + else if (itemId == ItemID.AMULET_OF_CHEMISTRY) + { + if (!config.showAmuletOfChemistryCharges()) + { + return; + } + + charges = config.amuletOfChemistry(); + } + else if (itemId == ItemID.AMULET_OF_BOUNTY) + { + if (!config.showAmuletOfBountyCharges()) + { + return; + } + + charges = config.amuletOfBounty(); + } + else if (itemId == ItemID.CHRONICLE) + { + if (!config.showTeleportCharges()) + { + return; + } + + charges = config.chronicle(); + } + else + { + ItemWithCharge chargeItem = ItemWithCharge.findItem(itemId); + if (chargeItem == null) + { + return; + } + + ItemChargeType type = chargeItem.getType(); + if ((type == TELEPORT && !config.showTeleportCharges()) + || (type == FUNGICIDE_SPRAY && !config.showFungicideCharges()) + || (type == IMPBOX && !config.showImpCharges()) + || (type == WATERCAN && !config.showWateringCanCharges()) + || (type == WATERSKIN && !config.showWaterskinCharges()) + || (type == BELLOWS && !config.showBellowCharges()) + || (type == FRUIT_BASKET && !config.showBasketCharges()) + || (type == SACK && !config.showSackCharges()) + || (type == ABYSSAL_BRACELET && !config.showAbyssalBraceletCharges()) + || (type == AMULET_OF_CHEMISTRY && !config.showAmuletOfChemistryCharges()) + || (type == AMULET_OF_BOUNTY && !config.showAmuletOfBountyCharges()) + || (type == POTION && !config.showPotionDoseCount())) + { + return; + } + + charges = chargeItem.getCharges(); + } + + final Rectangle bounds = itemWidget.getCanvasBounds(); + final TextComponent textComponent = new TextComponent(); + textComponent.setPosition(new Point(bounds.x - 1, bounds.y + 15)); + textComponent.setText(charges < 0 ? "?" : String.valueOf(charges)); + textComponent.setColor(itemChargePlugin.getColor(charges)); + textComponent.render(graphics); + } + + private boolean displayOverlay() + { + return config.showTeleportCharges() || config.showDodgyCount() || config.showFungicideCharges() + || config.showImpCharges() || config.showWateringCanCharges() || config.showWaterskinCharges() + || config.showBellowCharges() || config.showBasketCharges() || config.showSackCharges() + || config.showAbyssalBraceletCharges() || config.showExplorerRingCharges() || config.showRingOfForgingCount() + || config.showAmuletOfChemistryCharges() || config.showAmuletOfBountyCharges() || config.showPotionDoseCount(); + + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java new file mode 100644 index 0000000000..bc4ef469ef --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2017, Seth + * Copyright (c) 2018, Hydrox6 + * Copyright (c) 2019, Aleios + * Copyright (c) 2020, Unmoon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +import com.google.common.primitives.Ints; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.Varbits; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.Notifier; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Item Charges", + description = "Show number of item charges remaining", + tags = {"inventory", "notifications", "overlay"} +) +@Slf4j +public class ItemChargePlugin extends Plugin +{ + private static final Pattern DODGY_CHECK_PATTERN = Pattern.compile( + "Your dodgy necklace has (\\d+) charges? left\\."); + private static final Pattern DODGY_PROTECT_PATTERN = Pattern.compile( + "Your dodgy necklace protects you\\..*It has (\\d+) charges? left\\."); + private static final Pattern DODGY_BREAK_PATTERN = Pattern.compile( + "Your dodgy necklace protects you\\..*It then crumbles to dust\\."); + private static final String RING_OF_RECOIL_BREAK_MESSAGE = "Your Ring of Recoil has shattered."; + private static final Pattern BINDING_CHECK_PATTERN = Pattern.compile( + "You have ([0-9]+|one) charges? left before your Binding necklace disintegrates\\."); + private static final Pattern BINDING_USED_PATTERN = Pattern.compile( + "You bind the temple's power into (mud|lava|steam|dust|smoke|mist) runes\\."); + private static final String BINDING_BREAK_TEXT = "Your Binding necklace has disintegrated."; + private static final Pattern RING_OF_FORGING_CHECK_PATTERN = Pattern.compile( + "You can smelt ([0-9]+|one) more pieces? of iron ore before a ring melts\\."); + private static final String RING_OF_FORGING_USED_TEXT = "You retrieve a bar of iron."; + private static final String RING_OF_FORGING_BREAK_TEXT = "Your Ring of Forging has melted."; + private static final Pattern AMULET_OF_CHEMISTRY_CHECK_PATTERN = Pattern.compile( + "Your amulet of chemistry has (\\d) charges? left\\." + ); + private static final Pattern AMULET_OF_CHEMISTRY_USED_PATTERN = Pattern.compile( + "Your amulet of chemistry helps you create a \\d-dose potion\\. It has (\\d|one) charges? left\\." + ); + private static final Pattern AMULET_OF_CHEMISTRY_BREAK_PATTERN = Pattern.compile( + "Your amulet of chemistry helps you create a \\d-dose potion\\. It then crumbles to dust\\." + ); + private static final Pattern AMULET_OF_BOUNTY_CHECK_PATTERN = Pattern.compile( + "Your amulet of bounty has (\\d+) charges? left\\." + ); + private static final Pattern AMULET_OF_BOUNTY_USED_PATTERN = Pattern.compile( + "Your amulet of bounty saves some seeds for you\\. It has (\\d) charges? left\\." + ); + private static final String AMULET_OF_BOUNTY_BREAK_TEXT = "Your amulet of bounty saves some seeds for you. It then crumbles to dust."; + private static final Pattern CHRONICLE_ADD_PATTERN = Pattern.compile( + "You add (?:\\d+|a single) charges? to your book\\. It now has (\\d+|one) charges?\\." + ); + private static final Pattern CHRONICLE_USE_AND_CHECK_PATTERN = Pattern.compile( + "Your book has (\\d+) charges left\\." + ); + private static final String CHRONICLE_FULL_TEXT = "Your book is fully charged! It has 1,000 charges already."; + private static final String CHRONICLE_ONE_CHARGE_TEXT = "You have one charge left in your book."; + private static final String CHRONICLE_EMPTY_TEXT = "Your book has run out of charges."; + private static final String CHRONICLE_NO_CHARGES_TEXT = "Your book does not have any charges. Purchase some Teleport Cards from Diango."; + + private static final int MAX_DODGY_CHARGES = 10; + private static final int MAX_BINDING_CHARGES = 16; + private static final int MAX_EXPLORER_RING_CHARGES = 30; + private static final int MAX_RING_OF_FORGING_CHARGES = 140; + private static final int MAX_AMULET_OF_CHEMISTRY_CHARGES = 5; + private static final int MAX_AMULET_OF_BOUNTY_CHARGES = 10; + + private int lastExplorerRingCharge = -1; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ItemChargeOverlay overlay; + + @Inject + private ItemManager itemManager; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private Notifier notifier; + + @Inject + private ItemChargeConfig config; + + // Limits destroy callback to once per tick + private int lastCheckTick; + + @Provides + ItemChargeConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(ItemChargeConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + infoBoxManager.removeIf(ItemChargeInfobox.class::isInstance); + lastCheckTick = -1; + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (!event.getGroup().equals("itemCharge")) + { + return; + } + + if (!config.showInfoboxes()) + { + infoBoxManager.removeIf(ItemChargeInfobox.class::isInstance); + return; + } + + if (!config.showTeleportCharges()) + { + removeInfobox(ItemWithSlot.TELEPORT); + } + + if (!config.showAmuletOfChemistryCharges()) + { + removeInfobox(ItemWithSlot.AMULET_OF_CHEMISTY); + } + + if (!config.showAmuletOfBountyCharges()) + { + removeInfobox(ItemWithSlot.AMULET_OF_BOUNTY); + } + + if (!config.showAbyssalBraceletCharges()) + { + removeInfobox(ItemWithSlot.ABYSSAL_BRACELET); + } + + if (!config.showDodgyCount()) + { + removeInfobox(ItemWithSlot.DODGY_NECKLACE); + } + + if (!config.showBindingNecklaceCharges()) + { + removeInfobox(ItemWithSlot.BINDING_NECKLACE); + } + + if (!config.showExplorerRingCharges()) + { + removeInfobox(ItemWithSlot.EXPLORER_RING); + } + + if (!config.showRingOfForgingCount()) + { + removeInfobox(ItemWithSlot.RING_OF_FORGING); + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() == ChatMessageType.GAMEMESSAGE || event.getType() == ChatMessageType.SPAM) + { + String message = Text.removeTags(event.getMessage()); + Matcher dodgyCheckMatcher = DODGY_CHECK_PATTERN.matcher(message); + Matcher dodgyProtectMatcher = DODGY_PROTECT_PATTERN.matcher(message); + Matcher dodgyBreakMatcher = DODGY_BREAK_PATTERN.matcher(message); + Matcher bindingNecklaceCheckMatcher = BINDING_CHECK_PATTERN.matcher(message); + Matcher bindingNecklaceUsedMatcher = BINDING_USED_PATTERN.matcher(message); + Matcher ringOfForgingCheckMatcher = RING_OF_FORGING_CHECK_PATTERN.matcher(message); + Matcher amuletOfChemistryCheckMatcher = AMULET_OF_CHEMISTRY_CHECK_PATTERN.matcher(message); + Matcher amuletOfChemistryUsedMatcher = AMULET_OF_CHEMISTRY_USED_PATTERN.matcher(message); + Matcher amuletOfChemistryBreakMatcher = AMULET_OF_CHEMISTRY_BREAK_PATTERN.matcher(message); + Matcher amuletOfBountyCheckMatcher = AMULET_OF_BOUNTY_CHECK_PATTERN.matcher(message); + Matcher amuletOfBountyUsedMatcher = AMULET_OF_BOUNTY_USED_PATTERN.matcher(message); + Matcher chronicleAddMatcher = CHRONICLE_ADD_PATTERN.matcher(message); + Matcher chronicleUseAndCheckMatcher = CHRONICLE_USE_AND_CHECK_PATTERN.matcher(message); + + if (config.recoilNotification() && message.contains(RING_OF_RECOIL_BREAK_MESSAGE)) + { + notifier.notify("Your Ring of Recoil has shattered"); + } + else if (dodgyBreakMatcher.find()) + { + if (config.dodgyNotification()) + { + notifier.notify("Your dodgy necklace has crumbled to dust."); + } + + updateDodgyNecklaceCharges(MAX_DODGY_CHARGES); + } + else if (dodgyCheckMatcher.find()) + { + updateDodgyNecklaceCharges(Integer.parseInt(dodgyCheckMatcher.group(1))); + } + else if (dodgyProtectMatcher.find()) + { + updateDodgyNecklaceCharges(Integer.parseInt(dodgyProtectMatcher.group(1))); + } + else if (amuletOfChemistryCheckMatcher.find()) + { + updateAmuletOfChemistryCharges(Integer.parseInt(amuletOfChemistryCheckMatcher.group(1))); + } + else if (amuletOfChemistryUsedMatcher.find()) + { + final String match = amuletOfChemistryUsedMatcher.group(1); + + int charges = 1; + if (!match.equals("one")) + { + charges = Integer.parseInt(match); + } + + updateAmuletOfChemistryCharges(charges); + } + else if (amuletOfChemistryBreakMatcher.find()) + { + updateAmuletOfChemistryCharges(MAX_AMULET_OF_CHEMISTRY_CHARGES); + } + else if (amuletOfBountyCheckMatcher.find()) + { + updateAmuletOfBountyCharges(Integer.parseInt(amuletOfBountyCheckMatcher.group(1))); + } + else if (amuletOfBountyUsedMatcher.find()) + { + updateAmuletOfBountyCharges(Integer.parseInt(amuletOfBountyUsedMatcher.group(1))); + } + else if (message.equals(AMULET_OF_BOUNTY_BREAK_TEXT)) + { + updateAmuletOfBountyCharges(MAX_AMULET_OF_BOUNTY_CHARGES); + } + else if (message.contains(BINDING_BREAK_TEXT)) + { + if (config.bindingNotification()) + { + notifier.notify(BINDING_BREAK_TEXT); + } + + // This chat message triggers before the used message so add 1 to the max charges to ensure proper sync + updateBindingNecklaceCharges(MAX_BINDING_CHARGES + 1); + } + else if (bindingNecklaceUsedMatcher.find()) + { + updateBindingNecklaceCharges(config.bindingNecklace() - 1); + } + else if (bindingNecklaceCheckMatcher.find()) + { + final String match = bindingNecklaceCheckMatcher.group(1); + + int charges = 1; + if (!match.equals("one")) + { + charges = Integer.parseInt(match); + } + + updateBindingNecklaceCharges(charges); + } + else if (ringOfForgingCheckMatcher.find()) + { + final String match = ringOfForgingCheckMatcher.group(1); + + int charges = 1; + if (!match.equals("one")) + { + charges = Integer.parseInt(match); + } + updateRingOfForgingCharges(charges); + } + else if (message.equals(RING_OF_FORGING_USED_TEXT)) + { + final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT); + + // Determine if the player smelted with a Ring of Forging equipped. + if (equipment == null) + { + return; + } + + if (equipment.contains(ItemID.RING_OF_FORGING)) + { + int charges = Ints.constrainToRange(config.ringOfForging() - 1, 0, MAX_RING_OF_FORGING_CHARGES); + updateRingOfForgingCharges(charges); + } + } + else if (message.equals(RING_OF_FORGING_BREAK_TEXT)) + { + if (config.ringOfForgingNotification()) + { + notifier.notify("Your ring of forging has melted."); + } + + updateRingOfForgingCharges(MAX_RING_OF_FORGING_CHARGES); + } + else if (chronicleAddMatcher.find()) + { + final String match = chronicleAddMatcher.group(1); + + if (match.equals("one")) + { + config.chronicle(1); + } + else + { + config.chronicle(Integer.parseInt(match)); + } + } + else if (chronicleUseAndCheckMatcher.find()) + { + config.chronicle(Integer.parseInt(chronicleUseAndCheckMatcher.group(1))); + } + else if (message.equals(CHRONICLE_ONE_CHARGE_TEXT)) + { + config.chronicle(1); + } + else if (message.equals(CHRONICLE_EMPTY_TEXT) || message.equals(CHRONICLE_NO_CHARGES_TEXT)) + { + config.chronicle(0); + } + else if (message.equals(CHRONICLE_FULL_TEXT)) + { + config.chronicle(1000); + } + } + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + if (event.getItemContainer() != client.getItemContainer(InventoryID.EQUIPMENT) || !config.showInfoboxes()) + { + return; + } + + final Item[] items = event.getItemContainer().getItems(); + + if (config.showTeleportCharges()) + { + updateJewelleryInfobox(ItemWithSlot.TELEPORT, items); + } + + if (config.showDodgyCount()) + { + updateJewelleryInfobox(ItemWithSlot.DODGY_NECKLACE, items); + } + + if (config.showAbyssalBraceletCharges()) + { + updateJewelleryInfobox(ItemWithSlot.ABYSSAL_BRACELET, items); + } + + if (config.showBindingNecklaceCharges()) + { + updateJewelleryInfobox(ItemWithSlot.BINDING_NECKLACE, items); + } + + if (config.showExplorerRingCharges()) + { + updateJewelleryInfobox(ItemWithSlot.EXPLORER_RING, items); + } + + if (config.showRingOfForgingCount()) + { + updateJewelleryInfobox(ItemWithSlot.RING_OF_FORGING, items); + } + + if (config.showAmuletOfChemistryCharges()) + { + updateJewelleryInfobox(ItemWithSlot.AMULET_OF_CHEMISTY, items); + } + + if (config.showAmuletOfBountyCharges()) + { + updateJewelleryInfobox(ItemWithSlot.AMULET_OF_BOUNTY, items); + } + } + + @Subscribe + private void onScriptCallbackEvent(ScriptCallbackEvent event) + { + if (!"destroyOnOpKey".equals(event.getEventName())) + { + return; + } + + final int yesOption = client.getIntStack()[client.getIntStackSize() - 1]; + if (yesOption == 1) + { + checkDestroyWidget(); + } + } + + @Subscribe + private void onVarbitChanged(VarbitChanged event) + { + int explorerRingCharge = client.getVar(Varbits.EXPLORER_RING_ALCHS); + if (lastExplorerRingCharge != explorerRingCharge) + { + lastExplorerRingCharge = explorerRingCharge; + updateExplorerRingCharges(explorerRingCharge); + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded widgetLoaded) + { + if (widgetLoaded.getGroupId() == WidgetID.DIALOG_SPRITE_GROUP_ID) + { + clientThread.invokeLater(() -> + { + Widget sprite = client.getWidget(WidgetInfo.DIALOG_SPRITE_SPRITE); + if (sprite != null) + { + switch (sprite.getItemId()) + { + case ItemID.DODGY_NECKLACE: + log.debug("Reset dodgy necklace"); + updateDodgyNecklaceCharges(MAX_DODGY_CHARGES); + break; + case ItemID.RING_OF_FORGING: + log.debug("Reset ring of forging"); + updateRingOfForgingCharges(MAX_RING_OF_FORGING_CHARGES); + break; + case ItemID.AMULET_OF_CHEMISTRY: + log.debug("Reset amulet of chemistry"); + updateAmuletOfChemistryCharges(MAX_AMULET_OF_CHEMISTRY_CHARGES); + break; + } + } + }); + } + } + + private void updateDodgyNecklaceCharges(final int value) + { + config.dodgyNecklace(value); + + if (config.showInfoboxes() && config.showDodgyCount()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.DODGY_NECKLACE, itemContainer.getItems()); + } + } + + private void updateAmuletOfChemistryCharges(final int value) + { + config.amuletOfChemistry(value); + + if (config.showInfoboxes() && config.showAmuletOfChemistryCharges()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.AMULET_OF_CHEMISTY, itemContainer.getItems()); + } + } + + private void updateAmuletOfBountyCharges(final int value) + { + config.amuletOfBounty(value); + + if (config.showInfoboxes() && config.showAmuletOfBountyCharges()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.AMULET_OF_BOUNTY, itemContainer.getItems()); + } + } + + private void updateBindingNecklaceCharges(final int value) + { + config.bindingNecklace(value); + + if (config.showInfoboxes() && config.showBindingNecklaceCharges()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.BINDING_NECKLACE, itemContainer.getItems()); + } + } + + private void updateExplorerRingCharges(final int value) + { + // Note: Varbit counts upwards. We count down from the maximum charges. + config.explorerRing(MAX_EXPLORER_RING_CHARGES - value); + + if (config.showInfoboxes() && config.showExplorerRingCharges()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.EXPLORER_RING, itemContainer.getItems()); + } + } + + private void updateRingOfForgingCharges(final int value) + { + config.ringOfForging(value); + + if (config.showInfoboxes() && config.showRingOfForgingCount()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.RING_OF_FORGING, itemContainer.getItems()); + } + } + + private void checkDestroyWidget() + { + final int currentTick = client.getTickCount(); + if (lastCheckTick == currentTick) + { + return; + } + lastCheckTick = currentTick; + + final Widget widgetDestroyItemName = client.getWidget(WidgetInfo.DESTROY_ITEM_NAME); + if (widgetDestroyItemName == null) + { + return; + } + + if (widgetDestroyItemName.getText().equals("Binding necklace")) + { + log.debug("Reset binding necklace"); + updateBindingNecklaceCharges(MAX_BINDING_CHARGES); + } + } + + private void updateJewelleryInfobox(ItemWithSlot item, Item[] items) + { + for (final EquipmentInventorySlot equipmentInventorySlot : item.getSlots()) + { + updateJewelleryInfobox(item, items, equipmentInventorySlot); + } + } + + private void updateJewelleryInfobox(ItemWithSlot type, Item[] items, EquipmentInventorySlot slot) + { + removeInfobox(type, slot); + + if (slot.getSlotIdx() >= items.length) + { + return; + } + + final int id = items[slot.getSlotIdx()].getId(); + if (id < 0) + { + return; + } + + final ItemWithCharge itemWithCharge = ItemWithCharge.findItem(id); + int charges = -1; + + if (itemWithCharge == null) + { + if (id == ItemID.DODGY_NECKLACE && type == ItemWithSlot.DODGY_NECKLACE) + { + charges = config.dodgyNecklace(); + } + else if (id == ItemID.BINDING_NECKLACE && type == ItemWithSlot.BINDING_NECKLACE) + { + charges = config.bindingNecklace(); + } + else if ((id >= ItemID.EXPLORERS_RING_1 && id <= ItemID.EXPLORERS_RING_4) && type == ItemWithSlot.EXPLORER_RING) + { + charges = config.explorerRing(); + } + else if (id == ItemID.RING_OF_FORGING && type == ItemWithSlot.RING_OF_FORGING) + { + charges = config.ringOfForging(); + } + else if (id == ItemID.AMULET_OF_CHEMISTRY && type == ItemWithSlot.AMULET_OF_CHEMISTY) + { + charges = config.amuletOfChemistry(); + } + else if (id == ItemID.AMULET_OF_BOUNTY && type == ItemWithSlot.AMULET_OF_BOUNTY) + { + charges = config.amuletOfBounty(); + } + } + else if (itemWithCharge.getType() == type.getType()) + { + charges = itemWithCharge.getCharges(); + } + + if (charges <= 0) + { + return; + } + + final String name = itemManager.getItemComposition(id).getName(); + final BufferedImage image = itemManager.getImage(id); + final ItemChargeInfobox infobox = new ItemChargeInfobox(this, image, name, charges, type, slot); + infoBoxManager.addInfoBox(infobox); + } + + private void removeInfobox(final ItemWithSlot item) + { + infoBoxManager.removeIf(t -> t instanceof ItemChargeInfobox && ((ItemChargeInfobox) t).getItem() == item); + } + + private void removeInfobox(final ItemWithSlot item, final EquipmentInventorySlot slot) + { + infoBoxManager.removeIf(t -> + { + if (!(t instanceof ItemChargeInfobox)) + { + return false; + } + + final ItemChargeInfobox i = (ItemChargeInfobox) t; + return i.getItem() == item && i.getSlot() == slot; + }); + } + + Color getColor(int charges) + { + Color color = Color.WHITE; + if (charges <= config.veryLowWarning()) + { + color = config.veryLowWarningColor(); + } + else if (charges <= config.lowWarning()) + { + color = config.lowWarningolor(); + } + return color; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java new file mode 100644 index 0000000000..a3f5fb7a78 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, Mitchell + * Copyright (c) 2020, Unmoon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +enum ItemChargeType +{ + ABYSSAL_BRACELET, + AMULET_OF_CHEMISTRY, + AMULET_OF_BOUNTY, + BELLOWS, + FUNGICIDE_SPRAY, + IMPBOX, + TELEPORT, + WATERCAN, + WATERSKIN, + DODGY_NECKLACE, + BINDING_NECKLACE, + EXPLORER_RING, + FRUIT_BASKET, + SACK, + RING_OF_FORGING, + GUTHIX_REST, + CHRONICLE, + POTION, +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java new file mode 100644 index 0000000000..b9c1124423 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2017, Seth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import static net.runelite.api.ItemID.*; +import static net.runelite.client.plugins.itemcharges.ItemChargeType.POTION; +import static net.runelite.client.plugins.itemcharges.ItemChargeType.*; + +@AllArgsConstructor +@Getter +enum ItemWithCharge +{ + ABRACE1(ABYSSAL_BRACELET, ABYSSAL_BRACELET1, 1), + ABRACE2(ABYSSAL_BRACELET, ABYSSAL_BRACELET2, 2), + ABRACE3(ABYSSAL_BRACELET, ABYSSAL_BRACELET3, 3), + ABRACE4(ABYSSAL_BRACELET, ABYSSAL_BRACELET4, 4), + ABRACE5(ABYSSAL_BRACELET, ABYSSAL_BRACELET5, 5), + ABSORPTION1(POTION, ABSORPTION_1, 1), + ABSORPTION2(POTION, ABSORPTION_2, 2), + ABSORPTION3(POTION, ABSORPTION_3, 3), + ABSORPTION4(POTION, ABSORPTION_4, 4), + AGILITY1(POTION, AGILITY_POTION1, 1), + AGILITY2(POTION, AGILITY_POTION2, 2), + AGILITY3(POTION, AGILITY_POTION3, 3), + AGILITY4(POTION, AGILITY_POTION4, 4), + ANTI1(POTION, ANTIPOISON1, 1), + ANTI2(POTION, ANTIPOISON2, 2), + ANTI3(POTION, ANTIPOISON3, 3), + ANTI4(POTION, ANTIPOISON4, 4), + ANTIDOTE_P1(POTION, ANTIDOTE1, 1), + ANTIDOTE_P2(POTION, ANTIDOTE2, 2), + ANTIDOTE_P3(POTION, ANTIDOTE3, 3), + ANTIDOTE_P4(POTION, ANTIDOTE4, 4), + ANTIDOTE_PP1(POTION, ANTIDOTE1_5958, 1), + ANTIDOTE_PP2(POTION, ANTIDOTE2_5956, 2), + ANTIDOTE_PP3(POTION, ANTIDOTE3_5954, 3), + ANTIDOTE_PP4(POTION, ANTIDOTE4_5952, 4), + ANTIFIRE1(POTION, ANTIFIRE_POTION1, 1), + ANTIFIRE2(POTION, ANTIFIRE_POTION2, 2), + ANTIFIRE3(POTION, ANTIFIRE_POTION3, 3), + ANTIFIRE4(POTION, ANTIFIRE_POTION4, 4), + ANTIVEN1(POTION, ANTIVENOM1, 1), + ANTIVEN2(POTION, ANTIVENOM2, 2), + ANTIVEN3(POTION, ANTIVENOM3, 3), + ANTIVEN4(POTION, ANTIVENOM4, 4), + ANTIVENOM_P1(POTION, ANTIVENOM1_12919, 1), + ANTIVENOM_P2(POTION, ANTIVENOM2_12917, 2), + ANTIVENOM_P3(POTION, ANTIVENOM3_12915, 3), + ANTIVENOM_P4(POTION, ANTIVENOM4_12913, 4), + ATTACK1(POTION, ATTACK_POTION1, 1), + ATTACK2(POTION, ATTACK_POTION2, 2), + ATTACK3(POTION, ATTACK_POTION3, 3), + ATTACK4(POTION, ATTACK_POTION4, 4), + BASKET_APPLES1(FRUIT_BASKET, APPLES1, 1), + BASKET_APPLES2(FRUIT_BASKET, APPLES2, 2), + BASKET_APPLES3(FRUIT_BASKET, APPLES3, 3), + BASKET_APPLES4(FRUIT_BASKET, APPLES4, 4), + BASKET_APPLES5(FRUIT_BASKET, APPLES5, 5), + BASKET_BANANAS1(FRUIT_BASKET, BANANAS1, 1), + BASKET_BANANAS2(FRUIT_BASKET, BANANAS2, 2), + BASKET_BANANAS3(FRUIT_BASKET, BANANAS3, 3), + BASKET_BANANAS4(FRUIT_BASKET, BANANAS4, 4), + BASKET_BANANAS5(FRUIT_BASKET, BANANAS5, 5), + BASKET_ORANGES1(FRUIT_BASKET, ORANGES1, 1), + BASKET_ORANGES2(FRUIT_BASKET, ORANGES2, 2), + BASKET_ORANGES3(FRUIT_BASKET, ORANGES3, 3), + BASKET_ORANGES4(FRUIT_BASKET, ORANGES4, 4), + BASKET_ORANGES5(FRUIT_BASKET, ORANGES5, 5), + BASKET_STRAWBERRIES1(FRUIT_BASKET, STRAWBERRIES1, 1), + BASKET_STRAWBERRIES2(FRUIT_BASKET, STRAWBERRIES2, 2), + BASKET_STRAWBERRIES3(FRUIT_BASKET, STRAWBERRIES3, 3), + BASKET_STRAWBERRIES4(FRUIT_BASKET, STRAWBERRIES4, 4), + BASKET_STRAWBERRIES5(FRUIT_BASKET, STRAWBERRIES5, 5), + BASKET_TOMATOES1(FRUIT_BASKET, TOMATOES1, 1), + BASKET_TOMATOES2(FRUIT_BASKET, TOMATOES2, 2), + BASKET_TOMATOES3(FRUIT_BASKET, TOMATOES3, 3), + BASKET_TOMATOES4(FRUIT_BASKET, TOMATOES4, 4), + BASKET_TOMATOES5(FRUIT_BASKET, TOMATOES5, 5), + BASTION1(POTION, BASTION_POTION1, 1), + BASTION2(POTION, BASTION_POTION2, 2), + BASTION3(POTION, BASTION_POTION3, 3), + BASTION4(POTION, BASTION_POTION4, 4), + BATTLEMAGE1(POTION, BATTLEMAGE_POTION1, 1), + BATTLEMAGE2(POTION, BATTLEMAGE_POTION2, 2), + BATTLEMAGE3(POTION, BATTLEMAGE_POTION3, 3), + BATTLEMAGE4(POTION, BATTLEMAGE_POTION4, 4), + BELLOWS0(BELLOWS, OGRE_BELLOWS, 0), + BELLOWS1(BELLOWS, OGRE_BELLOWS_1, 1), + BELLOWS2(BELLOWS, OGRE_BELLOWS_2, 2), + BELLOWS3(BELLOWS, OGRE_BELLOWS_3, 3), + BLIGHTED_SUPER_REST1(POTION, BLIGHTED_SUPER_RESTORE1, 1), + BLIGHTED_SUPER_REST2(POTION, BLIGHTED_SUPER_RESTORE2, 2), + BLIGHTED_SUPER_REST3(POTION, BLIGHTED_SUPER_RESTORE3, 3), + BLIGHTED_SUPER_REST4(POTION, BLIGHTED_SUPER_RESTORE4, 4), + BURNING1(TELEPORT, BURNING_AMULET1, 1), + BURNING2(TELEPORT, BURNING_AMULET2, 2), + BURNING3(TELEPORT, BURNING_AMULET3, 3), + BURNING4(TELEPORT, BURNING_AMULET4, 4), + BURNING5(TELEPORT, BURNING_AMULET5, 5), + CBRACE1(TELEPORT, COMBAT_BRACELET1, 1), + CBRACE2(TELEPORT, COMBAT_BRACELET2, 2), + CBRACE3(TELEPORT, COMBAT_BRACELET3, 3), + CBRACE4(TELEPORT, COMBAT_BRACELET4, 4), + CBRACE5(TELEPORT, COMBAT_BRACELET5, 5), + CBRACE6(TELEPORT, COMBAT_BRACELET6, 6), + COMBAT1(POTION, COMBAT_POTION1, 1), + COMBAT2(POTION, COMBAT_POTION2, 2), + COMBAT3(POTION, COMBAT_POTION3, 3), + COMBAT4(POTION, COMBAT_POTION4, 4), + COMPOST1(POTION, COMPOST_POTION1, 1), + COMPOST2(POTION, COMPOST_POTION2, 2), + COMPOST3(POTION, COMPOST_POTION3, 3), + COMPOST4(POTION, COMPOST_POTION4, 4), + DEFENCE1(POTION, DEFENCE_POTION1, 1), + DEFENCE2(POTION, DEFENCE_POTION2, 2), + DEFENCE3(POTION, DEFENCE_POTION3, 3), + DEFENCE4(POTION, DEFENCE_POTION4, 4), + DIGSITE1(TELEPORT, DIGSITE_PENDANT_1, 1), + DIGSITE2(TELEPORT, DIGSITE_PENDANT_2, 2), + DIGSITE3(TELEPORT, DIGSITE_PENDANT_3, 3), + DIGSITE4(TELEPORT, DIGSITE_PENDANT_4, 4), + DIGSITE5(TELEPORT, DIGSITE_PENDANT_5, 5), + DIVINE_BASTION1(POTION, DIVINE_BASTION_POTION1, 1), + DIVINE_BASTION2(POTION, DIVINE_BASTION_POTION2, 2), + DIVINE_BASTION3(POTION, DIVINE_BASTION_POTION3, 3), + DIVINE_BASTION4(POTION, DIVINE_BASTION_POTION4, 4), + DIVINE_BATTLEMAGE1(POTION, DIVINE_BATTLEMAGE_POTION1, 1), + DIVINE_BATTLEMAGE2(POTION, DIVINE_BATTLEMAGE_POTION2, 2), + DIVINE_BATTLEMAGE3(POTION, DIVINE_BATTLEMAGE_POTION3, 3), + DIVINE_BATTLEMAGE4(POTION, DIVINE_BATTLEMAGE_POTION4, 4), + DIVINE_MAGIC1(POTION, DIVINE_MAGIC_POTION1, 1), + DIVINE_MAGIC2(POTION, DIVINE_MAGIC_POTION2, 2), + DIVINE_MAGIC3(POTION, DIVINE_MAGIC_POTION3, 3), + DIVINE_MAGIC4(POTION, DIVINE_MAGIC_POTION4, 4), + DIVINE_RANGING1(POTION, DIVINE_RANGING_POTION1, 1), + DIVINE_RANGING2(POTION, DIVINE_RANGING_POTION2, 2), + DIVINE_RANGING3(POTION, DIVINE_RANGING_POTION3, 3), + DIVINE_RANGING4(POTION, DIVINE_RANGING_POTION4, 4), + DIVINE_SUPER_ATTACK1(POTION, DIVINE_SUPER_ATTACK_POTION1, 1), + DIVINE_SUPER_ATTACK2(POTION, DIVINE_SUPER_ATTACK_POTION2, 2), + DIVINE_SUPER_ATTACK3(POTION, DIVINE_SUPER_ATTACK_POTION3, 3), + DIVINE_SUPER_ATTACK4(POTION, DIVINE_SUPER_ATTACK_POTION4, 4), + DIVINE_SUPER_COMBAT1(POTION, DIVINE_SUPER_COMBAT_POTION1, 1), + DIVINE_SUPER_COMBAT2(POTION, DIVINE_SUPER_COMBAT_POTION2, 2), + DIVINE_SUPER_COMBAT3(POTION, DIVINE_SUPER_COMBAT_POTION3, 3), + DIVINE_SUPER_COMBAT4(POTION, DIVINE_SUPER_COMBAT_POTION4, 4), + DIVINE_SUPER_DEFENCE1(POTION, DIVINE_SUPER_DEFENCE_POTION1, 1), + DIVINE_SUPER_DEFENCE2(POTION, DIVINE_SUPER_DEFENCE_POTION2, 2), + DIVINE_SUPER_DEFENCE3(POTION, DIVINE_SUPER_DEFENCE_POTION3, 3), + DIVINE_SUPER_DEFENCE4(POTION, DIVINE_SUPER_DEFENCE_POTION4, 4), + DIVINE_SUPER_STRENGTH1(POTION, DIVINE_SUPER_STRENGTH_POTION1, 1), + DIVINE_SUPER_STRENGTH2(POTION, DIVINE_SUPER_STRENGTH_POTION2, 2), + DIVINE_SUPER_STRENGTH3(POTION, DIVINE_SUPER_STRENGTH_POTION3, 3), + DIVINE_SUPER_STRENGTH4(POTION, DIVINE_SUPER_STRENGTH_POTION4, 4), + ELYRE1(TELEPORT, ENCHANTED_LYRE1, 1), + ELYRE2(TELEPORT, ENCHANTED_LYRE2, 2), + ELYRE3(TELEPORT, ENCHANTED_LYRE3, 3), + ELYRE4(TELEPORT, ENCHANTED_LYRE4, 4), + ELYRE5(TELEPORT, ENCHANTED_LYRE5, 5), + ENERGY1(POTION, ENERGY_POTION1, 1), + ENERGY2(POTION, ENERGY_POTION2, 2), + ENERGY3(POTION, ENERGY_POTION3, 3), + ENERGY4(POTION, ENERGY_POTION4, 4), + EXTENDED_ANTIFI1(POTION, EXTENDED_ANTIFIRE1, 1), + EXTENDED_ANTIFI2(POTION, EXTENDED_ANTIFIRE2, 2), + EXTENDED_ANTIFI3(POTION, EXTENDED_ANTIFIRE3, 3), + EXTENDED_ANTIFI4(POTION, EXTENDED_ANTIFIRE4, 4), + EXTENDED_SUPER_ANTI1(POTION, EXTENDED_SUPER_ANTIFIRE1, 1), + EXTENDED_SUPER_ANTI2(POTION, EXTENDED_SUPER_ANTIFIRE2, 2), + EXTENDED_SUPER_ANTI3(POTION, EXTENDED_SUPER_ANTIFIRE3, 3), + EXTENDED_SUPER_ANTI4(POTION, EXTENDED_SUPER_ANTIFIRE4, 4), + FISHING1(POTION, FISHING_POTION1, 1), + FISHING2(POTION, FISHING_POTION2, 2), + FISHING3(POTION, FISHING_POTION3, 3), + FISHING4(POTION, FISHING_POTION4, 4), + FUNGICIDE0(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_0, 0), + FUNGICIDE1(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_1, 1), + FUNGICIDE2(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_2, 2), + FUNGICIDE3(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_3, 3), + FUNGICIDE4(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_4, 4), + FUNGICIDE5(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_5, 5), + FUNGICIDE6(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_6, 6), + FUNGICIDE7(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_7, 7), + FUNGICIDE8(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_8, 8), + FUNGICIDE9(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_9, 9), + FUNGICIDE10(FUNGICIDE_SPRAY, FUNGICIDE_SPRAY_10, 10), + GAMES1(TELEPORT, GAMES_NECKLACE1, 1), + GAMES2(TELEPORT, GAMES_NECKLACE2, 2), + GAMES3(TELEPORT, GAMES_NECKLACE3, 3), + GAMES4(TELEPORT, GAMES_NECKLACE4, 4), + GAMES5(TELEPORT, GAMES_NECKLACE5, 5), + GAMES6(TELEPORT, GAMES_NECKLACE6, 6), + GAMES7(TELEPORT, GAMES_NECKLACE7, 7), + GAMES8(TELEPORT, GAMES_NECKLACE8, 8), + GLORY1(TELEPORT, AMULET_OF_GLORY1, 1), + GLORY2(TELEPORT, AMULET_OF_GLORY2, 2), + GLORY3(TELEPORT, AMULET_OF_GLORY3, 3), + GLORY4(TELEPORT, AMULET_OF_GLORY4, 4), + GLORY5(TELEPORT, AMULET_OF_GLORY5, 5), + GLORY6(TELEPORT, AMULET_OF_GLORY6, 6), + GLORYT1(TELEPORT, AMULET_OF_GLORY_T1, 1), + GLORYT2(TELEPORT, AMULET_OF_GLORY_T2, 2), + GLORYT3(TELEPORT, AMULET_OF_GLORY_T3, 3), + GLORYT4(TELEPORT, AMULET_OF_GLORY_T4, 4), + GLORYT5(TELEPORT, AMULET_OF_GLORY_T5, 5), + GLORYT6(TELEPORT, AMULET_OF_GLORY_T6, 6), + GREST1(GUTHIX_REST, GUTHIX_REST1, 1), + GREST2(GUTHIX_REST, GUTHIX_REST2, 2), + GREST3(GUTHIX_REST, GUTHIX_REST3, 3), + GREST4(GUTHIX_REST, GUTHIX_REST4, 4), + GUTHIX_BAL1(POTION, GUTHIX_BALANCE1, 1), + GUTHIX_BAL2(POTION, GUTHIX_BALANCE2, 2), + GUTHIX_BAL3(POTION, GUTHIX_BALANCE3, 3), + GUTHIX_BAL4(POTION, GUTHIX_BALANCE4, 4), + HUNTER1(POTION, HUNTER_POTION1, 1), + HUNTER2(POTION, HUNTER_POTION2, 2), + HUNTER3(POTION, HUNTER_POTION3, 3), + HUNTER4(POTION, HUNTER_POTION4, 4), + IMP_IN_A_BOX1(IMPBOX, IMPINABOX1, 1), + IMP_IN_A_BOX2(IMPBOX, IMPINABOX2, 2), + MAGIC1(POTION, MAGIC_POTION1, 1), + MAGIC2(POTION, MAGIC_POTION2, 2), + MAGIC3(POTION, MAGIC_POTION3, 3), + MAGIC4(POTION, MAGIC_POTION4, 4), + MAGIC_ESS1(POTION, MAGIC_ESSENCE1, 1), + MAGIC_ESS2(POTION, MAGIC_ESSENCE2, 2), + MAGIC_ESS3(POTION, MAGIC_ESSENCE3, 3), + MAGIC_ESS4(POTION, MAGIC_ESSENCE4, 4), + OVERLOAD1(POTION, OVERLOAD_1, 1), + OVERLOAD2(POTION, OVERLOAD_2, 2), + OVERLOAD3(POTION, OVERLOAD_3, 3), + OVERLOAD4(POTION, OVERLOAD_4, 4), + PASSAGE1(TELEPORT, NECKLACE_OF_PASSAGE1, 1), + PASSAGE2(TELEPORT, NECKLACE_OF_PASSAGE2, 2), + PASSAGE3(TELEPORT, NECKLACE_OF_PASSAGE3, 3), + PASSAGE4(TELEPORT, NECKLACE_OF_PASSAGE4, 4), + PASSAGE5(TELEPORT, NECKLACE_OF_PASSAGE5, 5), + PHARAO1(TELEPORT, PHARAOHS_SCEPTRE_1, 1), + PHARAO2(TELEPORT, PHARAOHS_SCEPTRE_2, 2), + PHARAO3(TELEPORT, PHARAOHS_SCEPTRE_3, 3), + PHARAO4(TELEPORT, PHARAOHS_SCEPTRE_4, 4), + PHARAO5(TELEPORT, PHARAOHS_SCEPTRE_5, 5), + PHARAO6(TELEPORT, PHARAOHS_SCEPTRE_6, 6), + PHARAO7(TELEPORT, PHARAOHS_SCEPTRE_7, 7), + PHARAO8(TELEPORT, PHARAOHS_SCEPTRE_8, 8), + PRAYER1(POTION, PRAYER_POTION1, 1), + PRAYER2(POTION, PRAYER_POTION2, 2), + PRAYER3(POTION, PRAYER_POTION3, 3), + PRAYER4(POTION, PRAYER_POTION4, 4), + RANGING1(POTION, RANGING_POTION1, 1), + RANGING2(POTION, RANGING_POTION2, 2), + RANGING3(POTION, RANGING_POTION3, 3), + RANGING4(POTION, RANGING_POTION4, 4), + RELICYMS1(POTION, RELICYMS_BALM1, 1), + RELICYMS2(POTION, RELICYMS_BALM2, 2), + RELICYMS3(POTION, RELICYMS_BALM3, 3), + RELICYMS4(POTION, RELICYMS_BALM4, 4), + RESTORE1(POTION, RESTORE_POTION1, 1), + RESTORE2(POTION, RESTORE_POTION2, 2), + RESTORE3(POTION, RESTORE_POTION3, 3), + RESTORE4(POTION, RESTORE_POTION4, 4), + RETURNING1(TELEPORT, RING_OF_RETURNING1, 1), + RETURNING2(TELEPORT, RING_OF_RETURNING2, 2), + RETURNING3(TELEPORT, RING_OF_RETURNING3, 3), + RETURNING4(TELEPORT, RING_OF_RETURNING4, 4), + RETURNING5(TELEPORT, RING_OF_RETURNING5, 5), + ROD1(TELEPORT, RING_OF_DUELING1, 1), + ROD2(TELEPORT, RING_OF_DUELING2, 2), + ROD3(TELEPORT, RING_OF_DUELING3, 3), + ROD4(TELEPORT, RING_OF_DUELING4, 4), + ROD5(TELEPORT, RING_OF_DUELING5, 5), + ROD6(TELEPORT, RING_OF_DUELING6, 6), + ROD7(TELEPORT, RING_OF_DUELING7, 7), + ROD8(TELEPORT, RING_OF_DUELING8, 8), + ROS1(TELEPORT, SLAYER_RING_1, 1), + ROS2(TELEPORT, SLAYER_RING_2, 2), + ROS3(TELEPORT, SLAYER_RING_3, 3), + ROS4(TELEPORT, SLAYER_RING_4, 4), + ROS5(TELEPORT, SLAYER_RING_5, 5), + ROS6(TELEPORT, SLAYER_RING_6, 6), + ROS7(TELEPORT, SLAYER_RING_7, 7), + ROS8(TELEPORT, SLAYER_RING_8, 8), + ROW1(TELEPORT, RING_OF_WEALTH_1, 1), + ROW2(TELEPORT, RING_OF_WEALTH_2, 2), + ROW3(TELEPORT, RING_OF_WEALTH_3, 3), + ROW4(TELEPORT, RING_OF_WEALTH_4, 4), + ROW5(TELEPORT, RING_OF_WEALTH_5, 5), + SACK_CABBAGES1(SACK, CABBAGES1, 1), + SACK_CABBAGES2(SACK, CABBAGES2, 2), + SACK_CABBAGES3(SACK, CABBAGES3, 3), + SACK_CABBAGES4(SACK, CABBAGES4, 4), + SACK_CABBAGES5(SACK, CABBAGES5, 5), + SACK_CABBAGES6(SACK, CABBAGES6, 6), + SACK_CABBAGES7(SACK, CABBAGES7, 7), + SACK_CABBAGES8(SACK, CABBAGES8, 8), + SACK_CABBAGES9(SACK, CABBAGES9, 9), + SACK_CABBAGES10(SACK, CABBAGES10, 10), + SACK_ONIONS1(SACK, ONIONS1, 1), + SACK_ONIONS2(SACK, ONIONS2, 2), + SACK_ONIONS3(SACK, ONIONS3, 3), + SACK_ONIONS4(SACK, ONIONS4, 4), + SACK_ONIONS5(SACK, ONIONS5, 5), + SACK_ONIONS6(SACK, ONIONS6, 6), + SACK_ONIONS7(SACK, ONIONS7, 7), + SACK_ONIONS8(SACK, ONIONS8, 8), + SACK_ONIONS9(SACK, ONIONS9, 9), + SACK_ONIONS10(SACK, ONIONS10, 10), + SACK_POTATOES1(SACK, POTATOES1, 1), + SACK_POTATOES2(SACK, POTATOES2, 2), + SACK_POTATOES3(SACK, POTATOES3, 3), + SACK_POTATOES4(SACK, POTATOES4, 4), + SACK_POTATOES5(SACK, POTATOES5, 5), + SACK_POTATOES6(SACK, POTATOES6, 6), + SACK_POTATOES7(SACK, POTATOES7, 7), + SACK_POTATOES8(SACK, POTATOES8, 8), + SACK_POTATOES9(SACK, POTATOES9, 9), + SACK_POTATOES10(SACK, POTATOES10, 10), + SANFEW1(POTION, SANFEW_SERUM1, 1), + SANFEW2(POTION, SANFEW_SERUM2, 2), + SANFEW3(POTION, SANFEW_SERUM3, 3), + SANFEW4(POTION, SANFEW_SERUM4, 4), + SARADOMIN_BR1(POTION, SARADOMIN_BREW1, 1), + SARADOMIN_BR2(POTION, SARADOMIN_BREW2, 2), + SARADOMIN_BR3(POTION, SARADOMIN_BREW3, 3), + SARADOMIN_BR4(POTION, SARADOMIN_BREW4, 4), + SERUM_2071(POTION, SERUM_207_1, 1), + SERUM_2072(POTION, SERUM_207_2, 2), + SERUM_2073(POTION, SERUM_207_3, 3), + SERUM_2074(POTION, SERUM_207_4, 4), + SERUM_2081(POTION, SERUM_208_1, 1), + SERUM_2082(POTION, SERUM_208_2, 2), + SERUM_2083(POTION, SERUM_208_3, 3), + SERUM_2084(POTION, SERUM_208_4, 4), + SKILLS1(TELEPORT, SKILLS_NECKLACE1, 1), + SKILLS2(TELEPORT, SKILLS_NECKLACE2, 2), + SKILLS3(TELEPORT, SKILLS_NECKLACE3, 3), + SKILLS4(TELEPORT, SKILLS_NECKLACE4, 4), + SKILLS5(TELEPORT, SKILLS_NECKLACE5, 5), + SKILLS6(TELEPORT, SKILLS_NECKLACE6, 6), + STAMINA1(POTION, STAMINA_POTION1, 1), + STAMINA2(POTION, STAMINA_POTION2, 2), + STAMINA3(POTION, STAMINA_POTION3, 3), + STAMINA4(POTION, STAMINA_POTION4, 4), + STRENGTH1(POTION, STRENGTH_POTION1, 1), + STRENGTH2(POTION, STRENGTH_POTION2, 2), + STRENGTH3(POTION, STRENGTH_POTION3, 3), + STRENGTH4(POTION, STRENGTH_POTION4, 4), + SUPERANTI1(POTION, SUPERANTIPOISON1, 1), + SUPERANTI2(POTION, SUPERANTIPOISON2, 2), + SUPERANTI3(POTION, SUPERANTIPOISON3, 3), + SUPERANTI4(POTION, SUPERANTIPOISON4, 4), + SUPER_ANTIFIRE1(POTION, SUPER_ANTIFIRE_POTION1, 1), + SUPER_ANTIFIRE2(POTION, SUPER_ANTIFIRE_POTION2, 2), + SUPER_ANTIFIRE3(POTION, SUPER_ANTIFIRE_POTION3, 3), + SUPER_ANTIFIRE4(POTION, SUPER_ANTIFIRE_POTION4, 4), + SUPER_ATT1(POTION, SUPER_ATTACK1, 1), + SUPER_ATT2(POTION, SUPER_ATTACK2, 2), + SUPER_ATT3(POTION, SUPER_ATTACK3, 3), + SUPER_ATT4(POTION, SUPER_ATTACK4, 4), + SUPER_COMB1(POTION, SUPER_COMBAT_POTION1, 1), + SUPER_COMB2(POTION, SUPER_COMBAT_POTION2, 2), + SUPER_COMB3(POTION, SUPER_COMBAT_POTION3, 3), + SUPER_COMB4(POTION, SUPER_COMBAT_POTION4, 4), + SUPER_DEF1(POTION, SUPER_DEFENCE1, 1), + SUPER_DEF2(POTION, SUPER_DEFENCE2, 2), + SUPER_DEF3(POTION, SUPER_DEFENCE3, 3), + SUPER_DEF4(POTION, SUPER_DEFENCE4, 4), + SUPER_ENERG1(POTION, SUPER_ENERGY1, 1), + SUPER_ENERG2(POTION, SUPER_ENERGY2, 2), + SUPER_ENERG3(POTION, SUPER_ENERGY3, 3), + SUPER_ENERG4(POTION, SUPER_ENERGY4, 4), + SUPER_MAG1(POTION, SUPER_MAGIC_POTION_1, 1), + SUPER_MAG2(POTION, SUPER_MAGIC_POTION_2, 2), + SUPER_MAG3(POTION, SUPER_MAGIC_POTION_3, 3), + SUPER_MAG4(POTION, SUPER_MAGIC_POTION_4, 4), + SUPER_RANG1(POTION, SUPER_RANGING_1, 1), + SUPER_RANG2(POTION, SUPER_RANGING_2, 2), + SUPER_RANG3(POTION, SUPER_RANGING_3, 3), + SUPER_RANG4(POTION, SUPER_RANGING_4, 4), + SUPER_REST1(POTION, SUPER_RESTORE1, 1), + SUPER_REST2(POTION, SUPER_RESTORE2, 2), + SUPER_REST3(POTION, SUPER_RESTORE3, 3), + SUPER_REST4(POTION, SUPER_RESTORE4, 4), + SUPER_STR1(POTION, SUPER_STRENGTH1, 1), + SUPER_STR2(POTION, SUPER_STRENGTH2, 2), + SUPER_STR3(POTION, SUPER_STRENGTH3, 3), + SUPER_STR4(POTION, SUPER_STRENGTH4, 4), + TCRYSTAL1(TELEPORT, TELEPORT_CRYSTAL_1, 1), + TCRYSTAL2(TELEPORT, TELEPORT_CRYSTAL_2, 2), + TCRYSTAL3(TELEPORT, TELEPORT_CRYSTAL_3, 3), + TCRYSTAL4(TELEPORT, TELEPORT_CRYSTAL_4, 4), + TCRYSTAL5(TELEPORT, TELEPORT_CRYSTAL_5, 5), + WCAN0(WATERCAN, WATERING_CAN, 0), + WCAN1(WATERCAN, WATERING_CAN1, 1), + WCAN2(WATERCAN, WATERING_CAN2, 2), + WCAN3(WATERCAN, WATERING_CAN3, 3), + WCAN4(WATERCAN, WATERING_CAN4, 4), + WCAN5(WATERCAN, WATERING_CAN5, 5), + WCAN6(WATERCAN, WATERING_CAN6, 6), + WCAN7(WATERCAN, WATERING_CAN7, 7), + WCAN8(WATERCAN, WATERING_CAN8, 8), + WSKIN0(WATERSKIN, WATERSKIN0, 0), + WSKIN1(WATERSKIN, WATERSKIN1, 1), + WSKIN2(WATERSKIN, WATERSKIN2, 2), + WSKIN3(WATERSKIN, WATERSKIN3, 3), + WSKIN4(WATERSKIN, WATERSKIN4, 4), + ZAMORAK_BR1(POTION, ZAMORAK_BREW1, 1), + ZAMORAK_BR2(POTION, ZAMORAK_BREW2, 2), + ZAMORAK_BR3(POTION, ZAMORAK_BREW3, 3), + ZAMORAK_BR4(POTION, ZAMORAK_BREW4, 4), + ELDER_MIN1(POTION, ELDER_1, 1), + ELDER_MIN2(POTION, ELDER_2, 2), + ELDER_MIN3(POTION, ELDER_3, 3), + ELDER_MIN4(POTION, ELDER_4, 4), + ELDER1(POTION, ELDER_POTION_1, 1), + ELDER2(POTION, ELDER_POTION_2, 2), + ELDER3(POTION, ELDER_POTION_3, 3), + ELDER4(POTION, ELDER_POTION_4, 4), + ELDER_MAX1(POTION, ELDER_1_20921, 1), + ELDER_MAX2(POTION, ELDER_2_20922, 2), + ELDER_MAX3(POTION, ELDER_3_20923, 3), + ELDER_MAX4(POTION, ELDER_4_20924, 4), + KODAI_MIN1(POTION, KODAI_1, 1), + KODAI_MIN2(POTION, KODAI_2, 2), + KODAI_MIN3(POTION, KODAI_3, 3), + KODAI_MIN4(POTION, KODAI_4, 4), + KODAI1(POTION, KODAI_POTION_1, 1), + KODAI2(POTION, KODAI_POTION_2, 2), + KODAI3(POTION, KODAI_POTION_3, 3), + KODAI4(POTION, KODAI_POTION_4, 4), + KODAI_MAX1(POTION, KODAI_1_20945, 1), + KODAI_MAX2(POTION, KODAI_2_20946, 2), + KODAI_MAX3(POTION, KODAI_3_20947, 3), + KODAI_MAX4(POTION, KODAI_4_20948, 4), + TWISTED_MIN1(POTION, TWISTED_1, 1), + TWISTED_MIN2(POTION, TWISTED_2, 2), + TWISTED_MIN3(POTION, TWISTED_3, 3), + TWISTED_MIN4(POTION, TWISTED_4, 4), + TWISTED1(POTION, TWISTED_POTION_1, 1), + TWISTED2(POTION, TWISTED_POTION_2, 2), + TWISTED3(POTION, TWISTED_POTION_3, 3), + TWISTED4(POTION, TWISTED_POTION_4, 4), + TWISTED_MAX1(POTION, TWISTED_1_20933, 1), + TWISTED_MAX2(POTION, TWISTED_2_20934, 2), + TWISTED_MAX3(POTION, TWISTED_3_20935, 3), + TWISTED_MAX4(POTION, TWISTED_4_20936, 4), + REVITALISATION_MIN1(POTION, REVITALISATION_1, 1), + REVITALISATION_MIN2(POTION, REVITALISATION_2, 2), + REVITALISATION_MIN3(POTION, REVITALISATION_3, 3), + REVITALISATION_MIN4(POTION, REVITALISATION_4, 4), + REVITALISATION1(POTION, REVITALISATION_POTION_1, 1), + REVITALISATION2(POTION, REVITALISATION_POTION_2, 2), + REVITALISATION3(POTION, REVITALISATION_POTION_3, 3), + REVITALISATION4(POTION, REVITALISATION_POTION_4, 4), + REVITALISATION_MAX1(POTION, REVITALISATION_1_20957, 1), + REVITALISATION_MAX2(POTION, REVITALISATION_2_20958, 2), + REVITALISATION_MAX3(POTION, REVITALISATION_3_20959, 3), + REVITALISATION_MAX4(POTION, REVITALISATION_4_20960, 4), + XERICS_AID_MIN1(POTION, XERICS_AID_1, 1), + XERICS_AID_MIN2(POTION, XERICS_AID_2, 2), + XERICS_AID_MIN3(POTION, XERICS_AID_3, 3), + XERICS_AID_MIN4(POTION, XERICS_AID_4, 4), + XERICS_AID1(POTION, XERICS_AID_1_20977, 1), + XERICS_AID2(POTION, XERICS_AID_2_20978, 2), + XERICS_AID3(POTION, XERICS_AID_3_20979, 3), + XERICS_AID4(POTION, XERICS_AID_4_20980, 4), + XERICS_AID_MAX1(POTION, XERICS_AID_1_20981, 1), + XERICS_AID_MAX2(POTION, XERICS_AID_2_20982, 2), + XERICS_AID_MAX3(POTION, XERICS_AID_3_20983, 3), + XERICS_AID_MAX4(POTION, XERICS_AID_4_20984, 4), + PRAYER_ENHANCE_MIN1(POTION, PRAYER_ENHANCE_1, 1), + PRAYER_ENHANCE_MIN2(POTION, PRAYER_ENHANCE_2, 2), + PRAYER_ENHANCE_MIN3(POTION, PRAYER_ENHANCE_3, 3), + PRAYER_ENHANCE_MIN4(POTION, PRAYER_ENHANCE_4, 4), + PRAYER_ENHANCE1(POTION, PRAYER_ENHANCE_1_20965, 1), + PRAYER_ENHANCE2(POTION, PRAYER_ENHANCE_2_20966, 2), + PRAYER_ENHANCE3(POTION, PRAYER_ENHANCE_3_20967, 3), + PRAYER_ENHANCE4(POTION, PRAYER_ENHANCE_4_20968, 4), + PRAYER_ENHANCE_MAX1(POTION, PRAYER_ENHANCE_1_20969, 1), + PRAYER_ENHANCE_MAX2(POTION, PRAYER_ENHANCE_2_20970, 2), + PRAYER_ENHANCE_MAX3(POTION, PRAYER_ENHANCE_3_20971, 3), + PRAYER_ENHANCE_MAX4(POTION, PRAYER_ENHANCE_4_20972, 4), + COX_OVERLOAD_MIN1(POTION, OVERLOAD_1_20985, 1), + COX_OVERLOAD_MIN2(POTION, OVERLOAD_2_20986, 2), + COX_OVERLOAD_MIN3(POTION, OVERLOAD_3_20987, 3), + COX_OVERLOAD_MIN4(POTION, OVERLOAD_4_20988, 4), + COX_OVERLOAD1(POTION, OVERLOAD_1_20989, 1), + COX_OVERLOAD2(POTION, OVERLOAD_2_20990, 2), + COX_OVERLOAD3(POTION, OVERLOAD_3_20991, 3), + COX_OVERLOAD4(POTION, OVERLOAD_4_20992, 4), + COX_OVERLOAD_MAX1(POTION, OVERLOAD_1_20993, 1), + COX_OVERLOAD_MAX2(POTION, OVERLOAD_2_20994, 2), + COX_OVERLOAD_MAX3(POTION, OVERLOAD_3_20995, 3), + COX_OVERLOAD_MAX4(POTION, OVERLOAD_4_20996, 4), + ; + + private final ItemChargeType type; + private final int id; + private final int charges; + + private static final Map ID_MAP; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (ItemWithCharge itemCharge : values()) + { + builder.put(itemCharge.getId(), itemCharge); + } + + ID_MAP = builder.build(); + } + + @Nullable + static ItemWithCharge findItem(int itemId) + { + return ID_MAP.get(itemId); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java new file mode 100644 index 0000000000..c59741ac52 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019, Tomas Slusny + * Copyright (c) 2019, Aleios + * Copyright (c) 2020, Unmoon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemcharges; + +import com.google.common.collect.Sets; +import java.util.Set; +import lombok.Getter; +import net.runelite.api.EquipmentInventorySlot; + +@Getter +enum ItemWithSlot +{ + ABYSSAL_BRACELET(ItemChargeType.ABYSSAL_BRACELET, EquipmentInventorySlot.GLOVES), + AMULET_OF_CHEMISTY(ItemChargeType.AMULET_OF_CHEMISTRY, EquipmentInventorySlot.AMULET), + AMULET_OF_BOUNTY(ItemChargeType.AMULET_OF_BOUNTY, EquipmentInventorySlot.AMULET), + DODGY_NECKLACE(ItemChargeType.DODGY_NECKLACE, EquipmentInventorySlot.AMULET), + BINDING_NECKLACE(ItemChargeType.BINDING_NECKLACE, EquipmentInventorySlot.AMULET), + EXPLORER_RING(ItemChargeType.EXPLORER_RING, EquipmentInventorySlot.RING), + RING_OF_FORGING(ItemChargeType.RING_OF_FORGING, EquipmentInventorySlot.RING), + CHRONICLE(ItemChargeType.CHRONICLE, EquipmentInventorySlot.SHIELD), + TELEPORT(ItemChargeType.TELEPORT, EquipmentInventorySlot.WEAPON, EquipmentInventorySlot.AMULET, EquipmentInventorySlot.GLOVES, EquipmentInventorySlot.RING); + + private final ItemChargeType type; + private final Set slots; + + ItemWithSlot(final ItemChargeType type, final EquipmentInventorySlot... slots) + { + this.type = type; + this.slots = Sets.newHashSet(slots); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java new file mode 100644 index 0000000000..0e14e2dd85 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2019, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemidentification; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import net.runelite.api.ItemID; + +enum ItemIdentification +{ + //Seeds + GUAM_SEED(Type.SEED, "Guam", "G", ItemID.GUAM_SEED), + MARRENTILL_SEED(Type.SEED, "Marren", "M", ItemID.MARRENTILL_SEED), + TARROMIN_SEED(Type.SEED, "Tarro", "TAR", ItemID.TARROMIN_SEED), + HARRALANDER_SEED(Type.SEED, "Harra", "H", ItemID.HARRALANDER_SEED), + RANARR_SEED(Type.SEED, "Ranarr", "R", ItemID.RANARR_SEED), + TOADFLAX_SEED(Type.SEED, "Toad", "TOA", ItemID.TOADFLAX_SEED), + IRIT_SEED(Type.SEED, "Irit", "I", ItemID.IRIT_SEED), + AVANTOE_SEED(Type.SEED, "Avan", "A", ItemID.AVANTOE_SEED), + KWUARM_SEED(Type.SEED, "Kwuarm", "K", ItemID.KWUARM_SEED), + SNAPDRAGON_SEED(Type.SEED, "Snap", "S", ItemID.SNAPDRAGON_SEED), + CADANTINE_SEED(Type.SEED, "Cadan", "C", ItemID.CADANTINE_SEED), + LANTADYME_SEED(Type.SEED, "Lanta", "L", ItemID.LANTADYME_SEED), + DWARF_WEED_SEED(Type.SEED, "Dwarf", "D", ItemID.DWARF_WEED_SEED), + TORSTOL_SEED(Type.SEED, "Torstol", "TOR", ItemID.TORSTOL_SEED), + POISON_IVY_SEED(Type.SEED, "Ivy", "I", ItemID.POISON_IVY_SEED), + WHITEBERRY_SEED(Type.SEED, "White", "W", ItemID.WHITEBERRY_SEED), + + //Herbs + GUAM(Type.HERB, "Guam", "G", ItemID.GUAM_LEAF, ItemID.GRIMY_GUAM_LEAF), + MARRENTILL(Type.HERB, "Marren", "M", ItemID.MARRENTILL, ItemID.GRIMY_MARRENTILL), + TARROMIN(Type.HERB, "Tarro", "TAR", ItemID.TARROMIN, ItemID.GRIMY_TARROMIN), + HARRALANDER(Type.HERB, "Harra", "H", ItemID.HARRALANDER, ItemID.GRIMY_HARRALANDER), + RANARR(Type.HERB, "Ranarr", "R", ItemID.RANARR_WEED, ItemID.GRIMY_RANARR_WEED), + TOADFLAX(Type.HERB, "Toad", "TOA", ItemID.TOADFLAX, ItemID.GRIMY_TOADFLAX), + IRIT(Type.HERB, "Irit", "I", ItemID.IRIT_LEAF, ItemID.GRIMY_IRIT_LEAF), + AVANTOE(Type.HERB, "Avan", "A", ItemID.AVANTOE, ItemID.GRIMY_AVANTOE), + KWUARM(Type.HERB, "Kwuarm", "K", ItemID.KWUARM, ItemID.GRIMY_KWUARM), + SNAPDRAGON(Type.HERB, "Snap", "S", ItemID.SNAPDRAGON, ItemID.GRIMY_SNAPDRAGON), + CADANTINE(Type.HERB, "Cadan", "C", ItemID.CADANTINE, ItemID.GRIMY_CADANTINE), + LANTADYME(Type.HERB, "Lanta", "L", ItemID.LANTADYME, ItemID.GRIMY_LANTADYME), + DWARF_WEED(Type.HERB, "Dwarf", "D", ItemID.DWARF_WEED, ItemID.GRIMY_DWARF_WEED), + TORSTOL(Type.HERB, "Torstol", "TOR", ItemID.TORSTOL, ItemID.GRIMY_TORSTOL), + + //Saplings + OAK_SAPLING(Type.SAPLING, "Oak", "OAK", ItemID.OAK_SAPLING, ItemID.OAK_SEEDLING, ItemID.OAK_SEEDLING_W), + WILLOW_SAPLING(Type.SAPLING, "Willow", "WIL", ItemID.WILLOW_SAPLING, ItemID.WILLOW_SEEDLING, ItemID.WILLOW_SEEDLING_W), + MAPLE_SAPLING(Type.SAPLING, "Maple", "MAP", ItemID.MAPLE_SAPLING, ItemID.MAPLE_SEEDLING, ItemID.MAPLE_SEEDLING_W), + YEW_SAPLING(Type.SAPLING, "Yew", "YEW", ItemID.YEW_SAPLING, ItemID.YEW_SEEDLING, ItemID.YEW_SEEDLING_W), + MAGIC_SAPLING(Type.SAPLING, "Magic", "MAG", ItemID.MAGIC_SAPLING, ItemID.MAGIC_SEEDLING, ItemID.MAGIC_SEEDLING_W), + REDWOOD_SAPLING(Type.SAPLING, "Red", "RED", ItemID.REDWOOD_SAPLING, ItemID.REDWOOD_SEEDLING, ItemID.REDWOOD_SEEDLING_W), + SPIRIT_SAPLING(Type.SAPLING, "Spirit", "SPI", ItemID.SPIRIT_SAPLING, ItemID.SPIRIT_SEEDLING, ItemID.SPIRIT_SEEDLING_W), + CRYSTAL_SAPLING(Type.SAPLING, "Crystal", "CRY", ItemID.CRYSTAL_SAPLING, ItemID.CRYSTAL_SEEDLING, ItemID.CRYSTAL_SEEDLING_W), + + APPLE_SAPLING(Type.SAPLING, "Apple", "APP", ItemID.APPLE_SAPLING, ItemID.APPLE_SEEDLING, ItemID.APPLE_SEEDLING_W), + BANANA_SAPLING(Type.SAPLING, "Banana", "BAN", ItemID.BANANA_SAPLING, ItemID.BANANA_SEEDLING, ItemID.BANANA_SEEDLING_W), + ORANGE_SAPLING(Type.SAPLING, "Orange", "ORA", ItemID.ORANGE_SAPLING, ItemID.ORANGE_SEEDLING, ItemID.ORANGE_SEEDLING_W), + CURRY_SAPLING(Type.SAPLING, "Curry", "CUR", ItemID.CURRY_SAPLING, ItemID.CURRY_SEEDLING, ItemID.CURRY_SEEDLING_W), + PINEAPPLE_SAPLING(Type.SAPLING, "Pine", "PINE", ItemID.PINEAPPLE_SAPLING, ItemID.PINEAPPLE_SEEDLING, ItemID.PINEAPPLE_SEEDLING_W), + PAPAYA_SAPLING(Type.SAPLING, "Papaya", "PAP", ItemID.PAPAYA_SAPLING, ItemID.PAPAYA_SEEDLING, ItemID.PAPAYA_SEEDLING_W), + PALM_SAPLING(Type.SAPLING, "Palm", "PALM", ItemID.PALM_SAPLING, ItemID.PALM_SEEDLING, ItemID.PALM_SEEDLING_W), + DRAGONFRUIT_SAPLING(Type.SAPLING, "Dragon", "DRAG", ItemID.DRAGONFRUIT_SAPLING, ItemID.DRAGONFRUIT_SEEDLING, ItemID.DRAGONFRUIT_SEEDLING_W), + + TEAK_SAPLING(Type.SAPLING, "Teak", "TEAK", ItemID.TEAK_SAPLING, ItemID.TEAK_SEEDLING, ItemID.TEAK_SEEDLING_W), + MAHOGANY_SAPLING(Type.SAPLING, "Mahog", "MAHOG", ItemID.MAHOGANY_SAPLING, ItemID.MAHOGANY_SEEDLING, ItemID.MAHOGANY_SEEDLING_W), + CALQUAT_SAPLING(Type.SAPLING, "Calquat", "CALQ", ItemID.CALQUAT_SAPLING, ItemID.CALQUAT_SEEDLING, ItemID.CALQUAT_SEEDLING_W), + CELASTRUS_SAPLING(Type.SAPLING, "Celas", "CEL", ItemID.CELASTRUS_SAPLING, ItemID.CELASTRUS_SEEDLING, ItemID.CELASTRUS_SEEDLING_W), + + //Ores + COPPER_ORE(Type.ORE, "Copper", "COP", ItemID.COPPER_ORE), + TIN_ORE(Type.ORE, "Tin", "TIN", ItemID.TIN_ORE), + IRON_ORE(Type.ORE, "Iron", "IRO", ItemID.IRON_ORE), + SILVER_ORE(Type.ORE, "Silver", "SIL", ItemID.SILVER_ORE), + COAL_ORE(Type.ORE, "Coal", "COA", ItemID.COAL), + GOLD_ORE(Type.ORE, "Gold", "GOL", ItemID.GOLD_ORE), + MITHRIL_ORE(Type.ORE, "Mithril", "MIT", ItemID.MITHRIL_ORE), + ADAMANTITE_ORE(Type.ORE, "Adaman", "ADA", ItemID.ADAMANTITE_ORE), + RUNITE_ORE(Type.ORE, "Runite", "RUN", ItemID.RUNITE_ORE), + + RUNE_ESSENCE(Type.ORE, "R.Ess", "R.E.", ItemID.RUNE_ESSENCE), + PURE_ESSENCE(Type.ORE, "P.Ess", "P.E.", ItemID.PURE_ESSENCE), + + PAYDIRT(Type.ORE, "Paydirt", "PAY", ItemID.PAYDIRT), + AMETHYST(Type.ORE, "Amethy", "AME", ItemID.AMETHYST), + LOVAKITE_ORE(Type.ORE, "Lovakit", "LOV", ItemID.LOVAKITE_ORE), + BLURITE_ORE(Type.ORE, "Blurite", "BLU", ItemID.BLURITE_ORE), + ELEMENTAL_ORE(Type.ORE, "Element", "ELE", ItemID.ELEMENTAL_ORE), + DAEYALT_ORE(Type.ORE, "Daeyalt", "DAE", ItemID.DAEYALT_ORE), + LUNAR_ORE(Type.ORE, "Lunar", "LUN", ItemID.LUNAR_ORE), + + //Gems + SAPPHIRE(Type.GEM, "Sapphir", "S", ItemID.UNCUT_SAPPHIRE, ItemID.SAPPHIRE), + EMERALD(Type.GEM, "Emerald", "E", ItemID.UNCUT_EMERALD, ItemID.EMERALD), + RUBY(Type.GEM, "Ruby", "R", ItemID.UNCUT_RUBY, ItemID.RUBY), + DIAMOND(Type.GEM, "Diamon", "DI", ItemID.UNCUT_DIAMOND, ItemID.DIAMOND), + OPAL(Type.GEM, "Opal", "OP", ItemID.UNCUT_OPAL, ItemID.OPAL), + JADE(Type.GEM, "Jade", "J", ItemID.UNCUT_JADE, ItemID.JADE), + RED_TOPAZ(Type.GEM, "Topaz", "T", ItemID.UNCUT_RED_TOPAZ, ItemID.RED_TOPAZ), + DRAGONSTONE(Type.GEM, "Dragon", "DR", ItemID.UNCUT_DRAGONSTONE, ItemID.DRAGONSTONE), + ONYX(Type.GEM, "Onyx", "ON", ItemID.UNCUT_ONYX, ItemID.ONYX), + ZENYTE(Type.GEM, "Zenyte", "Z", ItemID.UNCUT_ZENYTE, ItemID.ZENYTE), + + // Potions + ATTACK(Type.POTION, "Att", "A", ItemID.ATTACK_POTION4, ItemID.ATTACK_POTION3, ItemID.ATTACK_POTION2, ItemID.ATTACK_POTION1), + STRENGTH(Type.POTION, "Str", "S", ItemID.STRENGTH_POTION4, ItemID.STRENGTH_POTION3, ItemID.STRENGTH_POTION2, ItemID.STRENGTH_POTION1), + DEFENCE(Type.POTION, "Def", "D", ItemID.DEFENCE_POTION4, ItemID.DEFENCE_POTION3, ItemID.DEFENCE_POTION2, ItemID.DEFENCE_POTION1), + COMBAT(Type.POTION, "Com", "C", ItemID.COMBAT_POTION4, ItemID.COMBAT_POTION3, ItemID.COMBAT_POTION2, ItemID.COMBAT_POTION1), + MAGIC(Type.POTION, "Magic", "M", ItemID.MAGIC_POTION4, ItemID.MAGIC_POTION3, ItemID.MAGIC_POTION2, ItemID.MAGIC_POTION1), + RANGING(Type.POTION, "Range", "R", ItemID.RANGING_POTION4, ItemID.RANGING_POTION3, ItemID.RANGING_POTION2, ItemID.RANGING_POTION1), + BASTION(Type.POTION, "Bastion", "B", ItemID.BASTION_POTION4, ItemID.BASTION_POTION3, ItemID.BASTION_POTION2, ItemID.BASTION_POTION1), + BATTLEMAGE(Type.POTION, "BatMage", "B.M", ItemID.BATTLEMAGE_POTION4, ItemID.BATTLEMAGE_POTION3, ItemID.BATTLEMAGE_POTION2, ItemID.BATTLEMAGE_POTION1), + + SUPER_ATTACK(Type.POTION, "S.Att", "S.A", ItemID.SUPER_ATTACK4, ItemID.SUPER_ATTACK3, ItemID.SUPER_ATTACK2, ItemID.SUPER_ATTACK1), + SUPER_STRENGTH(Type.POTION, "S.Str", "S.S", ItemID.SUPER_STRENGTH4, ItemID.SUPER_STRENGTH3, ItemID.SUPER_STRENGTH2, ItemID.SUPER_STRENGTH1), + SUPER_DEFENCE(Type.POTION, "S.Def", "S.D", ItemID.SUPER_DEFENCE4, ItemID.SUPER_DEFENCE3, ItemID.SUPER_DEFENCE2, ItemID.SUPER_DEFENCE1), + SUPER_COMBAT(Type.POTION, "S.Com", "S.C", ItemID.SUPER_COMBAT_POTION4, ItemID.SUPER_COMBAT_POTION3, ItemID.SUPER_COMBAT_POTION2, ItemID.SUPER_COMBAT_POTION1), + SUPER_RANGING(Type.POTION, "S.Range", "S.Ra", ItemID.SUPER_RANGING_4, ItemID.SUPER_RANGING_3, ItemID.SUPER_RANGING_2, ItemID.SUPER_RANGING_1), + SUPER_MAGIC(Type.POTION, "S.Magic", "S.M", ItemID.SUPER_MAGIC_POTION_4, ItemID.SUPER_MAGIC_POTION_3, ItemID.SUPER_MAGIC_POTION_2, ItemID.SUPER_MAGIC_POTION_1), + + DIVINE_SUPER_ATTACK(Type.POTION, "S.Att", "S.A", ItemID.DIVINE_SUPER_ATTACK_POTION4, ItemID.DIVINE_SUPER_ATTACK_POTION3, ItemID.DIVINE_SUPER_ATTACK_POTION2, ItemID.DIVINE_SUPER_ATTACK_POTION1), + DIVINE_SUPER_DEFENCE(Type.POTION, "S.Def", "S.D", ItemID.DIVINE_SUPER_DEFENCE_POTION4, ItemID.DIVINE_SUPER_DEFENCE_POTION3, ItemID.DIVINE_SUPER_DEFENCE_POTION2, ItemID.DIVINE_SUPER_DEFENCE_POTION1), + DIVINE_SUPER_STRENGTH(Type.POTION, "S.Str", "S.S", ItemID.DIVINE_SUPER_STRENGTH_POTION4, ItemID.DIVINE_SUPER_STRENGTH_POTION3, ItemID.DIVINE_SUPER_STRENGTH_POTION2, ItemID.DIVINE_SUPER_STRENGTH_POTION1), + DIVINE_SUPER_COMBAT(Type.POTION, "S.Com", "S.C", ItemID.DIVINE_SUPER_COMBAT_POTION4, ItemID.DIVINE_SUPER_COMBAT_POTION3, ItemID.DIVINE_SUPER_COMBAT_POTION2, ItemID.DIVINE_SUPER_COMBAT_POTION1), + DIVINE_RANGING(Type.POTION, "Range", "R", ItemID.DIVINE_RANGING_POTION4, ItemID.DIVINE_RANGING_POTION3, ItemID.DIVINE_RANGING_POTION2, ItemID.DIVINE_RANGING_POTION1), + DIVINE_MAGIC(Type.POTION, "Magic", "M", ItemID.DIVINE_MAGIC_POTION4, ItemID.DIVINE_MAGIC_POTION3, ItemID.DIVINE_MAGIC_POTION2, ItemID.DIVINE_MAGIC_POTION1), + DIVINE_BASTION(Type.POTION, "Bastion", "B", ItemID.DIVINE_BASTION_POTION4, ItemID.DIVINE_BASTION_POTION3, ItemID.DIVINE_BASTION_POTION2, ItemID.DIVINE_BASTION_POTION1), + DIVINE_BATTLEMAGE(Type.POTION, "BatMage", "B.M", ItemID.DIVINE_BATTLEMAGE_POTION4, ItemID.DIVINE_BATTLEMAGE_POTION3, ItemID.DIVINE_BATTLEMAGE_POTION2, ItemID.DIVINE_BATTLEMAGE_POTION1), + + RESTORE(Type.POTION, "Restore", "Re", ItemID.RESTORE_POTION4, ItemID.RESTORE_POTION3, ItemID.RESTORE_POTION2, ItemID.RESTORE_POTION1), + GUTHIX_BALANCE(Type.POTION, "GuthBal", "G.B.", ItemID.GUTHIX_BALANCE4, ItemID.GUTHIX_BALANCE3, ItemID.GUTHIX_BALANCE2, ItemID.GUTHIX_BALANCE1), + SUPER_RESTORE(Type.POTION, "S.Rest", "S.Re", ItemID.SUPER_RESTORE4, ItemID.SUPER_RESTORE3, ItemID.SUPER_RESTORE2, ItemID.SUPER_RESTORE1), + PRAYER(Type.POTION, "Prayer", "P", ItemID.PRAYER_POTION4, ItemID.PRAYER_POTION3, ItemID.PRAYER_POTION2, ItemID.PRAYER_POTION1), + ENERGY(Type.POTION, "Energy", "En", ItemID.ENERGY_POTION4, ItemID.ENERGY_POTION3, ItemID.ENERGY_POTION2, ItemID.ENERGY_POTION1), + SUPER_ENERGY(Type.POTION, "S.Energ", "S.En", ItemID.SUPER_ENERGY4, ItemID.SUPER_ENERGY3, ItemID.SUPER_ENERGY2, ItemID.SUPER_ENERGY1), + STAMINA(Type.POTION, "Stamina", "St", ItemID.STAMINA_POTION4, ItemID.STAMINA_POTION3, ItemID.STAMINA_POTION2, ItemID.STAMINA_POTION1), + OVERLOAD(Type.POTION, "Overloa", "OL", ItemID.OVERLOAD_4, ItemID.OVERLOAD_3, ItemID.OVERLOAD_2, ItemID.OVERLOAD_1), + ABSORPTION(Type.POTION, "Absorb", "Ab", ItemID.ABSORPTION_4, ItemID.ABSORPTION_3, ItemID.ABSORPTION_2, ItemID.ABSORPTION_1), + + ZAMORAK_BREW(Type.POTION, "ZammyBr", "Za", ItemID.ZAMORAK_BREW4, ItemID.ZAMORAK_BREW3, ItemID.ZAMORAK_BREW2, ItemID.ZAMORAK_BREW1), + SARADOMIN_BREW(Type.POTION, "SaraBr", "Sa", ItemID.SARADOMIN_BREW4, ItemID.SARADOMIN_BREW3, ItemID.SARADOMIN_BREW2, ItemID.SARADOMIN_BREW1), + + ANTIPOISON(Type.POTION, "AntiP", "AP", ItemID.ANTIPOISON4, ItemID.ANTIPOISON3, ItemID.ANTIPOISON2, ItemID.ANTIPOISON1), + SUPERANTIPOISON(Type.POTION, "S.AntiP", "S.AP", ItemID.SUPERANTIPOISON4, ItemID.SUPERANTIPOISON3, ItemID.SUPERANTIPOISON2, ItemID.SUPERANTIPOISON1), + ANTIDOTE_P(Type.POTION, "Antid+", "A+", ItemID.ANTIDOTE4, ItemID.ANTIDOTE3, ItemID.ANTIDOTE2, ItemID.ANTIDOTE1), + ANTIDOTE_PP(Type.POTION, "Antid++", "A++", ItemID.ANTIDOTE4_5952, ItemID.ANTIDOTE3_5954, ItemID.ANTIDOTE2_5956, ItemID.ANTIDOTE1_5958), + ANTIVENOM(Type.POTION, "Anti-V", "AV", ItemID.ANTIVENOM4, ItemID.ANTIVENOM3, ItemID.ANTIVENOM2, ItemID.ANTIVENOM1), + ANTIVENOM_P(Type.POTION, "Anti-V+", "AV+", ItemID.ANTIVENOM4_12913, ItemID.ANTIVENOM3_12915, ItemID.ANTIVENOM2_12917, ItemID.ANTIVENOM1_12919), + + RELICYMS_BALM(Type.POTION, "Relicym", "R.B", ItemID.RELICYMS_BALM4, ItemID.RELICYMS_BALM3, ItemID.RELICYMS_BALM2, ItemID.RELICYMS_BALM1), + SANFEW_SERUM(Type.POTION, "Sanfew", "Sf", ItemID.SANFEW_SERUM4, ItemID.SANFEW_SERUM3, ItemID.SANFEW_SERUM2, ItemID.SANFEW_SERUM1), + ANTIFIRE(Type.POTION, "Antif", "Af", ItemID.ANTIFIRE_POTION4, ItemID.ANTIFIRE_POTION3, ItemID.ANTIFIRE_POTION2, ItemID.ANTIFIRE_POTION1), + EXTENDED_ANTIFIRE(Type.POTION, "E.Antif", "E.Af", ItemID.EXTENDED_ANTIFIRE4, ItemID.EXTENDED_ANTIFIRE3, ItemID.EXTENDED_ANTIFIRE2, ItemID.EXTENDED_ANTIFIRE1), + SUPER_ANTIFIRE(Type.POTION, "S.Antif", "S.Af", ItemID.SUPER_ANTIFIRE_POTION4, ItemID.SUPER_ANTIFIRE_POTION3, ItemID.SUPER_ANTIFIRE_POTION2, ItemID.SUPER_ANTIFIRE_POTION1), + EXTENDED_SUPER_ANTIFIRE(Type.POTION, "ES.Antif", "ES.Af", ItemID.EXTENDED_SUPER_ANTIFIRE4, ItemID.EXTENDED_SUPER_ANTIFIRE3, ItemID.EXTENDED_SUPER_ANTIFIRE2, ItemID.EXTENDED_SUPER_ANTIFIRE1), + + SERUM_207(Type.POTION, "Ser207", "S7", ItemID.SERUM_207_4, ItemID.SERUM_207_3, ItemID.SERUM_207_2, ItemID.SERUM_207_1), + SERUM_208(Type.POTION, "Ser208", "S8", ItemID.SERUM_208_4, ItemID.SERUM_208_3, ItemID.SERUM_208_2, ItemID.SERUM_208_1), + COMPOST(Type.POTION, "Compost", "Cp", ItemID.COMPOST_POTION4, ItemID.COMPOST_POTION3, ItemID.COMPOST_POTION2, ItemID.COMPOST_POTION1), + + AGILITY(Type.POTION, "Agility", "Ag", ItemID.AGILITY_POTION4, ItemID.AGILITY_POTION3, ItemID.AGILITY_POTION2, ItemID.AGILITY_POTION1), + FISHING(Type.POTION, "Fishing", "Fi", ItemID.FISHING_POTION4, ItemID.FISHING_POTION3, ItemID.FISHING_POTION2, ItemID.FISHING_POTION1), + HUNTER(Type.POTION, "Hunter", "Hu", ItemID.HUNTER_POTION4, ItemID.HUNTER_POTION3, ItemID.HUNTER_POTION2, ItemID.HUNTER_POTION1), + + // Unfinished Potions + GUAM_POTION(Type.POTION, "Guam", "G", ItemID.GUAM_POTION_UNF), + MARRENTILL_POTION(Type.POTION, "Marren", "M", ItemID.MARRENTILL_POTION_UNF), + TARROMIN_POTION(Type.POTION, "Tarro", "TAR", ItemID.TARROMIN_POTION_UNF), + HARRALANDER_POTION(Type.POTION, "Harra", "H", ItemID.HARRALANDER_POTION_UNF), + RANARR_POTION(Type.POTION, "Ranarr", "R", ItemID.RANARR_POTION_UNF), + TOADFLAX_POTION(Type.POTION, "Toad", "TOA", ItemID.TOADFLAX_POTION_UNF), + IRIT_POTION(Type.POTION, "Irit", "I", ItemID.IRIT_POTION_UNF), + AVANTOE_POTION(Type.POTION, "Avan", "A", ItemID.AVANTOE_POTION_UNF), + KWUARM_POTION(Type.POTION, "Kwuarm", "K", ItemID.KWUARM_POTION_UNF), + SNAPDRAGON_POTION(Type.POTION, "Snap", "S", ItemID.SNAPDRAGON_POTION_UNF), + CADANTINE_POTION(Type.POTION, "Cadan", "C", ItemID.CADANTINE_POTION_UNF), + LANTADYME_POTION(Type.POTION, "Lanta", "L", ItemID.LANTADYME_POTION_UNF), + DWARF_WEED_POTION(Type.POTION, "Dwarf", "D", ItemID.DWARF_WEED_POTION_UNF), + TORSTOL_POTION(Type.POTION, "Torstol", "TOR", ItemID.TORSTOL_POTION_UNF), + + // Impling jars + BABY_IMPLING(Type.IMPLING_JAR, "Baby", "B", ItemID.BABY_IMPLING_JAR), + YOUNG_IMPLING(Type.IMPLING_JAR, "Young", "Y", ItemID.YOUNG_IMPLING_JAR), + GOURMET_IMPLING(Type.IMPLING_JAR, "Gourmet", "G", ItemID.GOURMET_IMPLING_JAR), + EARTH_IMPLING(Type.IMPLING_JAR, "Earth", "EAR", ItemID.EARTH_IMPLING_JAR), + ESSENCE_IMPLING(Type.IMPLING_JAR, "Essen", "ESS", ItemID.ESSENCE_IMPLING_JAR), + ECLECTIC_IMPLING(Type.IMPLING_JAR, "Eclect", "ECL", ItemID.ECLECTIC_IMPLING_JAR), + NATURE_IMPLING(Type.IMPLING_JAR, "Nature", "NAT", ItemID.NATURE_IMPLING_JAR), + MAGPIE_IMPLING(Type.IMPLING_JAR, "Magpie", "M", ItemID.MAGPIE_IMPLING_JAR), + NINJA_IMPLING(Type.IMPLING_JAR, "Ninja", "NIN", ItemID.NINJA_IMPLING_JAR), + CRYSTAL_IMPLING(Type.IMPLING_JAR, "Crystal", "C", ItemID.CRYSTAL_IMPLING_JAR), + DRAGON_IMPLING(Type.IMPLING_JAR, "Dragon", "D", ItemID.DRAGON_IMPLING_JAR), + LUCKY_IMPLING(Type.IMPLING_JAR, "Lucky", "L", ItemID.LUCKY_IMPLING_JAR), + + // Tablets + VARROCK_TELEPORT(Type.TABLET, "Varro", "VAR", ItemID.VARROCK_TELEPORT), + LUMBRIDGE_TELEPORT(Type.TABLET, "Lumbr", "LUM", ItemID.LUMBRIDGE_TELEPORT), + FALADOR_TELEPORT(Type.TABLET, "Fala", "FAL", ItemID.FALADOR_TELEPORT), + CAMELOT_TELEPORT(Type.TABLET, "Cammy", "CAM", ItemID.CAMELOT_TELEPORT), + ARDOUGNE_TELEPORT(Type.TABLET, "Ardoug", "ARD", ItemID.ARDOUGNE_TELEPORT), + WATCHTOWER_TELEPORT(Type.TABLET, "W.tow", "WT", ItemID.WATCHTOWER_TELEPORT), + TELEPORT_TO_HOUSE(Type.TABLET, "House", "POH", ItemID.TELEPORT_TO_HOUSE), + ENCHANT_SAPPHIRE_OR_OPAL(Type.TABLET, "E.Saph", "E SO", ItemID.ENCHANT_SAPPHIRE_OR_OPAL), + ENCHANT_EMERALD_OR_JADE(Type.TABLET, "E.Emer", "E EJ", ItemID.ENCHANT_EMERALD_OR_JADE), + ENCHANT_RUBY_OR_TOPAZ(Type.TABLET, "E.Ruby", "E RT", ItemID.ENCHANT_RUBY_OR_TOPAZ), + ENCHANT_DIAMOND(Type.TABLET, "E.Diam", "E DIA", ItemID.ENCHANT_DIAMOND), + ENCHANT_DRAGONSTONE(Type.TABLET, "E.Dstn", "E DS", ItemID.ENCHANT_DRAGONSTONE), + ENCHANT_ONYX(Type.TABLET, "E.Onyx", "E ONX", ItemID.ENCHANT_ONYX), + TELEKINETIC_GRAB(Type.TABLET, "T.grab", "T.GRB", ItemID.TELEKINETIC_GRAB), + BONES_TO_PEACHES(Type.TABLET, "Peach", "BtP", ItemID.BONES_TO_PEACHES_8015), + BONES_TO_BANANAS(Type.TABLET, "Banana", "BtB", ItemID.BONES_TO_BANANAS), + RIMMINGTON_TELEPORT(Type.TABLET, "Rimmi", "RIM", ItemID.RIMMINGTON_TELEPORT), + TAVERLEY_TELEPORT(Type.TABLET, "Taver", "TAV", ItemID.TAVERLEY_TELEPORT), + POLLNIVNEACH_TELEPORT(Type.TABLET, "Pollnv", "POL", ItemID.POLLNIVNEACH_TELEPORT), + RELLEKKA_TELEPORT(Type.TABLET, "Rell", "REL", ItemID.RELLEKKA_TELEPORT), + BRIMHAVEN_TELEPORT(Type.TABLET, "Brimh", "BRIM", ItemID.BRIMHAVEN_TELEPORT), + YANILLE_TELEPORT(Type.TABLET, "Yanille", "YAN", ItemID.YANILLE_TELEPORT), + TROLLHEIM_TELEPORT(Type.TABLET, "Trollh", "T.HM", ItemID.TROLLHEIM_TELEPORT), + PRIFDDINAS_TELEPORT(Type.TABLET, "Prifd", "PRIF", ItemID.PRIFDDINAS_TELEPORT), + HOSIDIUS_TELEPORT(Type.TABLET, "Hosid", "HOS", ItemID.HOSIDIUS_TELEPORT), + ANNAKARL_TELEPORT(Type.TABLET, "Annak", "GDZ", ItemID.ANNAKARL_TELEPORT), + CARRALLANGAR_TELEPORT(Type.TABLET, "Carra", "CAR", ItemID.CARRALLANGAR_TELEPORT), + DAREEYAK_TELEPORT(Type.TABLET, "Dareey", "DAR", ItemID.DAREEYAK_TELEPORT), + KHARYRLL_TELEPORT(Type.TABLET, "Khary", "KHRL", ItemID.KHARYRLL_TELEPORT), + LASSAR_TELEPORT(Type.TABLET, "Lass", "LSR", ItemID.LASSAR_TELEPORT), + PADDEWWA_TELEPORT(Type.TABLET, "Paddew", "PDW", ItemID.PADDEWWA_TELEPORT), + SENNTISTEN_TELEPORT(Type.TABLET, "Sennt", "SNT", ItemID.SENNTISTEN_TELEPORT), + LUMBRIDGE_GRAVEYARD_TELEPORT(Type.TABLET, "L.Grave", "L.GRV", ItemID.LUMBRIDGE_GRAVEYARD_TELEPORT), + DRAYNOR_MANOR_TELEPORT(Type.TABLET, "D.Manor", "D.MNR", ItemID.DRAYNOR_MANOR_TELEPORT), + MIND_ALTAR_TELEPORT(Type.TABLET, "M.Altar", "M.ALT", ItemID.MIND_ALTAR_TELEPORT), + SALVE_GRAVEYARD_TELEPORT(Type.TABLET, "S.Grave", "S.GRV", ItemID.SALVE_GRAVEYARD_TELEPORT), + FENKENSTRAINS_CASTLE_TELEPORT(Type.TABLET, "Fenk", "FNK", ItemID.FENKENSTRAINS_CASTLE_TELEPORT), + WEST_ARDOUGNE_TELEPORT(Type.TABLET, "W.Ardy", "W.ARD", ItemID.WEST_ARDOUGNE_TELEPORT), + HARMONY_ISLAND_TELEPORT(Type.TABLET, "H.Isle", "HRM", ItemID.HARMONY_ISLAND_TELEPORT), + CEMETERY_TELEPORT(Type.TABLET, "Cemet", "CEM", ItemID.CEMETERY_TELEPORT), + BARROWS_TELEPORT(Type.TABLET, "Barrow", "BAR", ItemID.BARROWS_TELEPORT), + APE_ATOLL_TELEPORT(Type.TABLET, "Atoll", "APE", ItemID.APE_ATOLL_TELEPORT), + BATTLEFRONT_TELEPORT(Type.TABLET, "B.Front", "BF", ItemID.BATTLEFRONT_TELEPORT), + TARGET_TELEPORT(Type.TABLET, "Target", "TRG", ItemID.TARGET_TELEPORT), + VOLCANIC_MINE_TELEPORT(Type.TABLET, "V.Mine", "VM", ItemID.VOLCANIC_MINE_TELEPORT), + WILDERNESS_CRABS_TELEPORT(Type.TABLET, "W.Crab", "CRAB", ItemID.WILDERNESS_CRABS_TELEPORT); + + final Type type; + final String medName; + final String shortName; + final int[] itemIDs; + + ItemIdentification(Type type, String medName, String shortName, int... ids) + { + this.type = type; + this.medName = medName; + this.shortName = shortName; + this.itemIDs = ids; + } + + private static final Map itemIdentifications; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (ItemIdentification i : values()) + { + for (int id : i.itemIDs) + { + builder.put(id, i); + } + } + + itemIdentifications = builder.build(); + } + + static ItemIdentification get(int id) + { + return itemIdentifications.get(id); + } + + enum Type + { + SEED, + HERB, + SAPLING, + ORE, + GEM, + POTION, + IMPLING_JAR, + TABLET + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationConfig.java new file mode 100644 index 0000000000..115792329b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationConfig.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2019, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemidentification; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; + +@ConfigGroup("itemidentification") +public interface ItemIdentificationConfig extends Config +{ + @ConfigSection( + name = "Categories", + description = "The categories of items to identify", + position = 99 + ) + String identificationSection = "identification"; + + @ConfigItem( + keyName = "identificationType", + name = "Identification Type", + position = -4, + description = "How much to show of the item name" + ) + default ItemIdentificationMode identificationType() + { + return ItemIdentificationMode.SHORT; + } + + @ConfigItem( + keyName = "textColor", + name = "Color", + position = -3, + description = "The colour of the identification text" + ) + default Color textColor() + { + return Color.WHITE; + } + + @ConfigItem( + keyName = "showSeeds", + name = "Seeds", + description = "Show identification on Seeds", + section = identificationSection + ) + default boolean showSeeds() + { + return true; + } + + @ConfigItem( + keyName = "showHerbs", + name = "Herbs", + description = "Show identification on Herbs", + section = identificationSection + ) + default boolean showHerbs() + { + return false; + } + + @ConfigItem( + keyName = "showSaplings", + name = "Saplings", + description = "Show identification on Saplings and Seedlings", + section = identificationSection + ) + default boolean showSaplings() + { + return true; + } + + @ConfigItem( + keyName = "showOres", + name = "Ores", + description = "Show identification on Ores", + section = identificationSection + ) + default boolean showOres() + { + return false; + } + + @ConfigItem( + keyName = "showGems", + name = "Gems", + description = "Show identification on Gems", + section = identificationSection + ) + default boolean showGems() + { + return false; + } + + @ConfigItem( + keyName = "showPotions", + name = "Potions", + description = "Show identification on Potions", + section = identificationSection + ) + default boolean showPotions() + { + return false; + } + + @ConfigItem( + keyName = "showImplingJars", + name = "Impling jars", + description = "Show identification on Impling jars", + section = identificationSection + ) + default boolean showImplingJars() + { + return false; + } + + @ConfigItem( + keyName = "showTablets", + name = "Tablets", + description = "Show identification on Tablets", + section = identificationSection + ) + default boolean showTablets() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationMode.java new file mode 100644 index 0000000000..3a692822b9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationMode.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemidentification; + +public enum ItemIdentificationMode +{ + SHORT, + MEDIUM +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationOverlay.java new file mode 100644 index 0000000000..8d332ac837 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationOverlay.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemidentification; + +import com.google.inject.Inject; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import static net.runelite.api.widgets.WidgetID.GUIDE_PRICE_GROUP_ID; +import static net.runelite.api.widgets.WidgetID.KEPT_ON_DEATH_GROUP_ID; +import static net.runelite.api.widgets.WidgetID.LOOTING_BAG_GROUP_ID; +import static net.runelite.api.widgets.WidgetID.SEED_BOX_GROUP_ID; +import static net.runelite.api.widgets.WidgetID.KINGDOM_GROUP_ID; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.WidgetItemOverlay; +import net.runelite.client.ui.overlay.components.TextComponent; + +class ItemIdentificationOverlay extends WidgetItemOverlay +{ + private final ItemIdentificationConfig config; + private final ItemManager itemManager; + + @Inject + ItemIdentificationOverlay(ItemIdentificationConfig config, ItemManager itemManager) + { + this.config = config; + this.itemManager = itemManager; + showOnInventory(); + showOnBank(); + showOnInterfaces(KEPT_ON_DEATH_GROUP_ID, GUIDE_PRICE_GROUP_ID, LOOTING_BAG_GROUP_ID, SEED_BOX_GROUP_ID, KINGDOM_GROUP_ID); + } + + @Override + public void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget) + { + ItemIdentification iden = findItemIdentification(itemId); + if (iden == null) + { + return; + } + + switch (iden.type) + { + case SEED: + if (!config.showSeeds()) + { + return; + } + break; + case HERB: + if (!config.showHerbs()) + { + return; + } + break; + case SAPLING: + if (!config.showSaplings()) + { + return; + } + break; + case ORE: + if (!config.showOres()) + { + return; + } + break; + case GEM: + if (!config.showGems()) + { + return; + } + break; + case POTION: + if (!config.showPotions()) + { + return; + } + break; + case IMPLING_JAR: + if (!config.showImplingJars()) + { + return; + } + break; + case TABLET: + if (!config.showTablets()) + { + return; + } + break; + } + + graphics.setFont(FontManager.getRunescapeSmallFont()); + renderText(graphics, itemWidget.getCanvasBounds(), iden); + } + + private void renderText(Graphics2D graphics, Rectangle bounds, ItemIdentification iden) + { + final TextComponent textComponent = new TextComponent(); + textComponent.setPosition(new Point(bounds.x - 1, bounds.y + bounds.height - 1)); + textComponent.setColor(config.textColor()); + switch (config.identificationType()) + { + case SHORT: + textComponent.setText(iden.shortName); + break; + case MEDIUM: + textComponent.setText(iden.medName); + break; + } + textComponent.render(graphics); + } + + private ItemIdentification findItemIdentification(final int itemID) + { + final int realItemId = itemManager.canonicalize(itemID); + return ItemIdentification.get(realItemId); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationPlugin.java new file mode 100644 index 0000000000..1eb4158076 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentificationPlugin.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019, Hydrox6 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemidentification; + +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Item Identification", + description = "Show identifying text over items with difficult to distinguish sprites", + enabledByDefault = false +) +public class ItemIdentificationPlugin extends Plugin +{ + @Inject + private OverlayManager overlayManager; + + @Inject + private ItemIdentificationOverlay overlay; + + @Provides + ItemIdentificationConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(ItemIdentificationConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + } + + @Override + protected void shutDown() + { + overlayManager.remove(overlay); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java new file mode 100644 index 0000000000..5c667a2b1a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Charlie Waters + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemprices; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("itemprices") +public interface ItemPricesConfig extends Config +{ + @ConfigItem( + keyName = "showGEPrice", + name = "Show Grand Exchange Prices", + description = "Grand exchange prices should be shown on tooltips", + position = 1 + ) + default boolean showGEPrice() + { + return true; + } + + @ConfigItem( + keyName = "showHAValue", + name = "Show High Alchemy Values", + description = "High Alchemy values should be shown on tooltips", + position = 2 + ) + default boolean showHAValue() + { + return true; + } + + @ConfigItem( + keyName = "showEA", + name = "Show Price Each on Stacks", + description = "The price/value of each item should be shown on stacks", + position = 3 + ) + default boolean showEA() + { + return true; + } + + @ConfigItem( + keyName = "hideInventory", + name = "Hide Tooltips on Inventory Items", + description = "Tooltips should be hidden on items in the inventory", + position = 4 + ) + default boolean hideInventory() + { + return true; + } + + @ConfigItem( + keyName = "showAlchProfit", + name = "Show High Alchemy Profit", + description = "Show the profit from casting high alchemy on items", + position = 5 + ) + default boolean showAlchProfit() + { + return false; + } + + @ConfigItem( + keyName = "showWhileAlching", + name = "Show prices while alching", + description = "Show the price overlay while using High Alchemy. Takes priority over \"Hide tooltips on Inventory Items\"", + position = 6 + ) + default boolean showWhileAlching() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java new file mode 100644 index 0000000000..c614665401 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2018, Charlie Waters + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemprices; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.QuantityFormatter; + +class ItemPricesOverlay extends Overlay +{ + private static final int INVENTORY_ITEM_WIDGETID = WidgetInfo.INVENTORY.getPackedId(); + private static final int BANK_INVENTORY_ITEM_WIDGETID = WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getPackedId(); + private static final int BANK_ITEM_WIDGETID = WidgetInfo.BANK_ITEM_CONTAINER.getPackedId(); + private static final int EXPLORERS_RING_ITEM_WIDGETID = WidgetInfo.EXPLORERS_RING_ALCH_INVENTORY.getPackedId(); + private static final int SEED_VAULT_ITEM_WIDGETID = WidgetInfo.SEED_VAULT_ITEM_CONTAINER.getPackedId(); + private static final int SEED_VAULT_INVENTORY_ITEM_WIDGETID = WidgetInfo.SEED_VAULT_INVENTORY_ITEMS_CONTAINER.getPackedId(); + + private final Client client; + private final ItemPricesConfig config; + private final TooltipManager tooltipManager; + private final StringBuilder itemStringBuilder = new StringBuilder(); + + @Inject + ItemManager itemManager; + + @Inject + ItemPricesOverlay(Client client, ItemPricesConfig config, TooltipManager tooltipManager) + { + setPosition(OverlayPosition.DYNAMIC); + this.client = client; + this.config = config; + this.tooltipManager = tooltipManager; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (client.isMenuOpen()) + { + return null; + } + + final MenuEntry[] menuEntries = client.getMenuEntries(); + final int last = menuEntries.length - 1; + + if (last < 0) + { + return null; + } + + final MenuEntry menuEntry = menuEntries[last]; + final MenuAction action = MenuAction.of(menuEntry.getType()); + final int widgetId = menuEntry.getParam1(); + final int groupId = WidgetInfo.TO_GROUP(widgetId); + final boolean isAlching = menuEntry.getOption().equals("Cast") && menuEntry.getTarget().contains("High Level Alchemy"); + + // Tooltip action type handling + switch (action) + { + case ITEM_USE_ON_WIDGET: + if (!config.showWhileAlching() || !isAlching) + { + break; + } + case CC_OP: + case ITEM_USE: + case ITEM_FIRST_OPTION: + case ITEM_SECOND_OPTION: + case ITEM_THIRD_OPTION: + case ITEM_FOURTH_OPTION: + case ITEM_FIFTH_OPTION: + // Item tooltip values + switch (groupId) + { + case WidgetID.EXPLORERS_RING_ALCH_GROUP_ID: + if (!config.showWhileAlching()) + { + return null; + } + case WidgetID.INVENTORY_GROUP_ID: + if (config.hideInventory() && !(config.showWhileAlching() && isAlching)) + { + return null; + } + // intentional fallthrough + case WidgetID.BANK_GROUP_ID: + case WidgetID.BANK_INVENTORY_GROUP_ID: + case WidgetID.SEED_VAULT_GROUP_ID: + case WidgetID.SEED_VAULT_INVENTORY_GROUP_ID: + // Make tooltip + final String text = makeValueTooltip(menuEntry); + if (text != null) + { + tooltipManager.add(new Tooltip(ColorUtil.prependColorTag(text, new Color(238, 238, 238)))); + } + break; + } + break; + } + return null; + } + + private String makeValueTooltip(MenuEntry menuEntry) + { + // Disabling both disables all value tooltips + if (!config.showGEPrice() && !config.showHAValue()) + { + return null; + } + + final int widgetId = menuEntry.getParam1(); + ItemContainer container = null; + + // Inventory item + if (widgetId == INVENTORY_ITEM_WIDGETID || + widgetId == BANK_INVENTORY_ITEM_WIDGETID || + widgetId == EXPLORERS_RING_ITEM_WIDGETID || + widgetId == SEED_VAULT_INVENTORY_ITEM_WIDGETID) + { + container = client.getItemContainer(InventoryID.INVENTORY); + } + // Bank item + else if (widgetId == BANK_ITEM_WIDGETID) + { + container = client.getItemContainer(InventoryID.BANK); + } + // Seed vault item + else if (widgetId == SEED_VAULT_ITEM_WIDGETID) + { + container = client.getItemContainer(InventoryID.SEED_VAULT); + } + + if (container == null) + { + return null; + } + + // Find the item in the container to get stack size + final int index = menuEntry.getParam0(); + final Item item = container.getItem(index); + if (item != null) + { + return getItemStackValueText(item); + } + + return null; + } + + private String getItemStackValueText(Item item) + { + int id = itemManager.canonicalize(item.getId()); + int qty = item.getQuantity(); + + // Special case for coins and platinum tokens + if (id == ItemID.COINS_995) + { + return QuantityFormatter.formatNumber(qty) + " gp"; + } + else if (id == ItemID.PLATINUM_TOKEN) + { + return QuantityFormatter.formatNumber(qty * 1000) + " gp"; + } + + ItemComposition itemDef = itemManager.getItemComposition(id); + + // Only check prices for things with store prices + if (itemDef.getPrice() <= 0) + { + return null; + } + + int gePrice = 0; + int haPrice = 0; + int haProfit = 0; + final int itemHaPrice = itemDef.getHaPrice(); + + if (config.showGEPrice()) + { + gePrice = itemManager.getItemPrice(id); + } + if (config.showHAValue()) + { + haPrice = itemHaPrice; + } + if (gePrice > 0 && itemHaPrice > 0 && config.showAlchProfit()) + { + haProfit = calculateHAProfit(itemHaPrice, gePrice); + } + + if (gePrice > 0 || haPrice > 0) + { + return stackValueText(qty, gePrice, haPrice, haProfit); + } + + return null; + } + + private String stackValueText(int qty, int gePrice, int haValue, int haProfit) + { + if (gePrice > 0) + { + itemStringBuilder.append("GE: ") + .append(QuantityFormatter.quantityToStackSize((long) gePrice * qty)) + .append(" gp"); + if (config.showEA() && qty > 1) + { + itemStringBuilder.append(" (") + .append(QuantityFormatter.quantityToStackSize(gePrice)) + .append(" ea)"); + } + } + if (haValue > 0) + { + if (gePrice > 0) + { + itemStringBuilder.append("
"); + } + + itemStringBuilder.append("HA: ") + .append(QuantityFormatter.quantityToStackSize((long) haValue * qty)) + .append(" gp"); + if (config.showEA() && qty > 1) + { + itemStringBuilder.append(" (") + .append(QuantityFormatter.quantityToStackSize(haValue)) + .append(" ea)"); + } + } + + if (haProfit != 0) + { + Color haColor = haProfitColor(haProfit); + + itemStringBuilder.append("
"); + itemStringBuilder.append("HA Profit: ") + .append(ColorUtil.wrapWithColorTag(String.valueOf((long) haProfit * qty), haColor)) + .append(" gp"); + if (config.showEA() && qty > 1) + { + itemStringBuilder.append(" (") + .append(ColorUtil.wrapWithColorTag(String.valueOf(haProfit), haColor)) + .append(" ea)"); + } + } + + // Build string and reset builder + final String text = itemStringBuilder.toString(); + itemStringBuilder.setLength(0); + return text; + } + + private int calculateHAProfit(int haPrice, int gePrice) + { + int natureRunePrice = itemManager.getItemPrice(ItemID.NATURE_RUNE); + return haPrice - gePrice - natureRunePrice; + } + + private static Color haProfitColor(int haProfit) + { + return haProfit >= 0 ? Color.GREEN : Color.RED; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java new file mode 100644 index 0000000000..2434faade3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Charlie Waters + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemprices; + +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Item Prices", + description = "Show prices on hover for items in your inventory and bank", + tags = {"bank", "inventory", "overlay", "high", "alchemy", "grand", "exchange", "tooltips"}, + enabledByDefault = false +) +public class ItemPricesPlugin extends Plugin +{ + @Inject + private OverlayManager overlayManager; + + @Inject + private ItemPricesOverlay overlay; + + @Provides + ItemPricesConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(ItemPricesConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/BoostedStatBoost.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/BoostedStatBoost.java new file mode 100644 index 0000000000..e44c2490ac --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/BoostedStatBoost.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import net.runelite.client.plugins.itemstats.delta.DeltaCalculator; +import net.runelite.client.plugins.itemstats.stats.Stat; +import net.runelite.api.Client; + +/** + * A stat boost using the current boosted (or drained) stat. + */ +public class BoostedStatBoost extends StatBoost +{ + private final DeltaCalculator deltaCalculator; + + public BoostedStatBoost(Stat stat, boolean boost, DeltaCalculator deltaCalculator) + { + super(stat, boost); + this.deltaCalculator = deltaCalculator; + } + + @Override + public int heals(Client client) + { + int value = getStat().getValue(client); + return deltaCalculator.calculateDelta(value); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Builders.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Builders.java new file mode 100644 index 0000000000..e5cee44b35 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Builders.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016-2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import net.runelite.client.plugins.itemstats.delta.DeltaCalculator; +import net.runelite.client.plugins.itemstats.delta.DeltaPercentage; +import net.runelite.client.plugins.itemstats.stats.Stat; + +public class Builders +{ + public static Food food(int diff) + { + return food((max) -> diff); + } + + public static Food food(DeltaCalculator p) + { + return new Food(p); + } + + public static Effect combo(int primaries, SingleEffect... effect) + { + return new Combo(primaries, effect); + } + + public static Effect combo(SingleEffect... effect) + { + return new Combo(effect); + } + + public static SimpleStatBoost boost(Stat stat, int boost) + { + return boost(stat, (max) -> boost); + } + + public static SimpleStatBoost boost(Stat stat, DeltaCalculator p) + { + return new SimpleStatBoost(stat, true, p); + } + + public static SimpleStatBoost heal(Stat stat, int boost) + { + return heal(stat, (max) -> boost); + } + + public static SimpleStatBoost heal(Stat stat, DeltaCalculator p) + { + return new SimpleStatBoost(stat, false, p); + } + + public static SimpleStatBoost dec(Stat stat, int boost) + { + return heal(stat, -boost); + } + + public static DeltaPercentage perc(double perc, int delta) + { + return new DeltaPercentage(perc, delta); + } + + public static RangeStatBoost range(StatBoost a, StatBoost b) + { + return new RangeStatBoost(a, b); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Combo.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Combo.java new file mode 100644 index 0000000000..9749f1a17b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Combo.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016-2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import net.runelite.api.Client; + +public class Combo implements Effect +{ + private final SingleEffect[] calcs; + private final int numPrimaries; + + public Combo(SingleEffect[] calcs) + { + this(1, calcs); + } + + public Combo(int numPrimaries, SingleEffect[] calcs) + { + this.numPrimaries = numPrimaries; + this.calcs = calcs; + } + + @Override + public StatsChanges calculate(Client client) + { + StatsChanges out = new StatsChanges(calcs.length); + StatChange[] statChanges = out.getStatChanges(); + for (int i = 0; i < calcs.length; i++) + { + statChanges[i] = calcs[i].effect(client); + } + Positivity positivity = Positivity.NO_CHANGE; + for (int i = 0; i < numPrimaries; i++) + { + if (positivity.ordinal() < statChanges[i].getPositivity().ordinal()) + { + positivity = statChanges[i].getPositivity(); + } + } + out.setPositivity(positivity); + return out; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Effect.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Effect.java new file mode 100644 index 0000000000..d937405ed9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Effect.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016-2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import net.runelite.api.Client; + +public interface Effect +{ + StatsChanges calculate(Client client); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Food.java similarity index 55% rename from runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java rename to runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Food.java index bcf03fd585..ff3f3fb702 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/Food.java @@ -1,15 +1,15 @@ /* - * Copyright (c) 2018, Ron Young + * Copyright (c) 2016-2018, Adam * All rights reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -22,24 +22,24 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package net.runelite.client.plugins.itemstats; -package net.runelite.client.plugins.banktags.tabs; +import net.runelite.client.plugins.itemstats.delta.DeltaCalculator; +import net.runelite.api.Client; -class MenuIndexes +public class Food extends FoodBase { - static class NewTab + private final DeltaCalculator p; + + public Food(DeltaCalculator p) { - static final int NEW_TAB = 2; - static final int IMPORT_TAB = 3; - static final int OPEN_TAB_MENU = 4; + this.p = p; } - static class Tab + @Override + public int heals(Client client) { - static final int OPEN_TAG = 2; - static final int CHANGE_ICON = 3; - static final int DELETE_TAB = 4; - static final int EXPORT_TAB = 5; - static final int RENAME_TAB = 6; + return p.calculateDelta(getStat().getMaximum(client)); } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/FoodBase.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/FoodBase.java new file mode 100644 index 0000000000..83409af0ce --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/FoodBase.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016-2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import net.runelite.client.plugins.itemstats.stats.Stats; + +public abstract class FoodBase extends StatBoost +{ + public FoodBase() + { + super(Stats.HITPOINTS, false); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java new file mode 100644 index 0000000000..6391628e4d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2016-2018, Adam + * Copyright (c) 2018, Jordan Atwood + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import com.google.inject.Singleton; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import static net.runelite.api.ItemID.*; +import static net.runelite.client.plugins.itemstats.Builders.*; +import net.runelite.client.plugins.itemstats.food.Anglerfish; +import net.runelite.client.plugins.itemstats.potions.GauntletPotion; +import net.runelite.client.plugins.itemstats.potions.PrayerPotion; +import net.runelite.client.plugins.itemstats.potions.SaradominBrew; +import net.runelite.client.plugins.itemstats.potions.SuperRestore; +import net.runelite.client.plugins.itemstats.special.CastleWarsBandage; +import net.runelite.client.plugins.itemstats.special.SpicyStew; +import static net.runelite.client.plugins.itemstats.stats.Stats.*; + +@Singleton +@Slf4j +public class ItemStatChanges +{ + ItemStatChanges() + { + init(); + } + + private void init() + { + add(food(-5), POISON_KARAMBWAN); + add(food(1), POTATO, ONION, CABBAGE, POT_OF_CREAM, CHOPPED_ONION, ANCHOVIES); + add(food(2), TOMATO, CHOPPED_TOMATO, BANANA, SLICED_BANANA, ORANGE, ORANGE_SLICES, ORANGE_CHUNKS, + PINEAPPLE_RING, PINEAPPLE_CHUNKS, SPICY_SAUCE, CHEESE, SPINACH_ROLL, LEMON, LEMON_CHUNKS, LEMON_SLICES, + LIME, LIME_CHUNKS, LIME_SLICES, DWELLBERRIES); + add(food(3), SHRIMPS, COOKED_MEAT, COOKED_CHICKEN, ROE, CHOCOLATE_BAR); + add(food(4), SARDINE, CAKE, _23_CAKE, SLICE_OF_CAKE, CHOCOLATEY_MILK, BAKED_POTATO, EDIBLE_SEAWEED, MOONLIGHT_MEAD); + add(food(5), BREAD, HERRING, CHOCOLATE_CAKE, _23_CHOCOLATE_CAKE, CHOCOLATE_SLICE, COOKED_RABBIT, CHILLI_CON_CARNE, + FRIED_MUSHROOMS, FRIED_ONIONS, REDBERRY_PIE, HALF_A_REDBERRY_PIE, CAVIAR, PYSK_FISH_0); + add(food(6), CHOCICE, MACKEREL, MEAT_PIE, HALF_A_MEAT_PIE, GUANIC_BAT_0, ROAST_BIRD_MEAT, + SQUARE_SANDWICH, ROLL, BAGUETTE, TRIANGLE_SANDWICH, GIANT_CARP); + add(food(7), TROUT, COD, PLAIN_PIZZA, _12_PLAIN_PIZZA, APPLE_PIE, HALF_AN_APPLE_PIE, ROAST_RABBIT, + PREMADE_CH_CRUNCH, CHOCCHIP_CRUNCHIES, PREMADE_SY_CRUNCH, SPICY_CRUNCHIES); + add(food(8), PIKE, ROAST_BEAST_MEAT, MEAT_PIZZA, _12_MEAT_PIZZA, PREMADE_WM_CRUN, WORM_CRUNCHIES, PREMADE_TD_CRUNCH, + TOAD_CRUNCHIES, EGG_AND_TOMATO, PRAEL_BAT_1, PEACH, SUPHI_FISH_1); + add(food(9), PREMADE_P_PUNCH, PINEAPPLE_PUNCH, PREMADE_FR_BLAST, FRUIT_BLAST, SALMON, ANCHOVY_PIZZA, + _12_ANCHOVY_PIZZA); + add(food(10), TUNA, COOKED_CRAB_MEAT, CHOPPED_TUNA, COOKED_CHOMPY, FIELD_RATION); + add(food(11), RAINBOW_FISH, STEW, PINEAPPLE_PIZZA, _12_PINEAPPLE_PIZZA, COOKED_FISHCAKE, + PREMADE_VEG_BATTA, VEGETABLE_BATTA, PREMADE_WM_BATTA, WORM_BATTA, PREMADE_TD_BATTA, TOAD_BATTA, PREMADE_CT_BATTA, + CHEESETOM_BATTA, PREMADE_FRT_BATTA, FRUIT_BATTA, MUSHROOM__ONION, GIRAL_BAT_2, LAVA_EEL, LECKISH_FISH_2); + add(food(12), LOBSTER, PREMADE_WORM_HOLE, WORM_HOLE, PREMADE_VEG_BALL, VEG_BALL); + add(food(13), BASS, TUNA_AND_CORN); + add(food(14), POTATO_WITH_BUTTER, CHILLI_POTATO, SWORDFISH, PHLUXIA_BAT_3, PUMPKIN, EASTER_EGG, BRAWK_FISH_3); + add(food(15), PREMADE_TTL, TANGLED_TOADS_LEGS, PREMADE_CHOC_BOMB, CHOCOLATE_BOMB, COOKED_JUBBLY); + add(food(16), MONKFISH, POTATO_WITH_CHEESE, EGG_POTATO); + add(food(17), MYCIL_FISH_4, KRYKET_BAT_4); + add(food(18), COOKED_KARAMBWAN, BLIGHTED_KARAMBWAN); + add(food(19), CURRY, UGTHANKI_KEBAB, UGTHANKI_KEBAB_1885); + add(food(20), MUSHROOM_POTATO, SHARK, ROQED_FISH_5, MURNG_BAT_5, STUFFED_SNAKE); + add(food(21), SEA_TURTLE); + add(food(22), MANTA_RAY, BLIGHTED_MANTA_RAY, DARK_CRAB, TUNA_POTATO); + add(food(23), KYREN_FISH_6, PSYKK_BAT_6); + add(new Anglerfish(), ANGLERFISH, BLIGHTED_ANGLERFISH); + add(food(maxHP -> (int) Math.ceil(maxHP * .06)), STRAWBERRY); + add(food(maxHP -> (int) Math.ceil(maxHP * .05)), WATERMELON_SLICE); + add(food(perc(.1, 1)), COOKED_SWEETCORN, SWEETCORN_7088 /* Bowl of cooked sweetcorn */); + add(combo(food(1), boost(DEFENCE, perc(.02, 1))), CABBAGE_1967 /* Draynor Manor */); + add(combo(2, food(8), heal(RUN_ENERGY, 5)), PAPAYA_FRUIT); + add(range(food(5), food(7)), THIN_SNAIL_MEAT); + add(range(food(7), food(9)), FAT_SNAIL_MEAT); + add(range(food(7), food(10)), SPIDER_ON_STICK_6297, SPIDER_ON_SHAFT_6299); + + // Dorgeshuun Cuisine + add(food(2), BAT_SHISH, COATED_FROGS_LEGS, FILLETS, FINGERS, FROGBURGER, FROGSPAWN_GUMBO, GREEN_GLOOP_SOUP, + GRUBS__LA_MODE, MUSHROOMS, ROAST_FROG); + add(food(3), LOACH); + add(range(food(3), food(6)), FROG_SPAWN); + add(range(food(6), food(10)), COOKED_SLIMY_EEL); + add(range(food(8), food(12)), CAVE_EEL); + add(food(10), EEL_SUSHI); + + // Alcoholic Beverages + add(combo(food(11), dec(ATTACK, 2)), JUG_OF_WINE); + add(combo(food(14), dec(ATTACK, 3)), BOTTLE_OF_WINE); + add(combo(2, food(5), boost(STRENGTH, 6), heal(ATTACK, -4)), PREMADE_WIZ_BLZD, WIZARD_BLIZZARD); + add(combo(2, food(5), boost(STRENGTH, 4), heal(ATTACK, -3)), PREMADE_SGG, SHORT_GREEN_GUY); + add(combo(2, food(5), boost(STRENGTH, 7), heal(ATTACK, -4)), PREMADE_DR_DRAGON, DRUNK_DRAGON); + add(combo(2, food(5), boost(STRENGTH, 7), heal(ATTACK, -4)), PREMADE_CHOC_SDY, CHOC_SATURDAY); + + // Sq'irk Juice + add(heal(RUN_ENERGY, 5), WINTER_SQIRKJUICE); + add(combo(heal(RUN_ENERGY, 10), boost(THIEVING, 1)), SPRING_SQIRKJUICE); + add(combo(heal(RUN_ENERGY, 15), boost(THIEVING, 2)), AUTUMN_SQIRKJUICE); + add(combo(heal(RUN_ENERGY, 20), boost(THIEVING, 3)), SUMMER_SQIRKJUICE); + + // Combat potions + add(boost(ATTACK, perc(.10, 3)), ATTACK_POTION1, ATTACK_POTION2, ATTACK_POTION3, ATTACK_POTION4); + add(boost(STRENGTH, perc(.10, 3)), STRENGTH_POTION1, STRENGTH_POTION2, STRENGTH_POTION3, STRENGTH_POTION4); + add(boost(DEFENCE, perc(.10, 3)), DEFENCE_POTION1, DEFENCE_POTION2, DEFENCE_POTION3, DEFENCE_POTION4); + add(boost(MAGIC, 4), MAGIC_POTION1, MAGIC_POTION2, MAGIC_POTION3, MAGIC_POTION4); + add(boost(RANGED, perc(.10, 4)), RANGING_POTION1, RANGING_POTION2, RANGING_POTION3, RANGING_POTION4); + add(combo(2, boost(ATTACK, perc(.10, 3)), boost(STRENGTH, perc(.10, 3))), COMBAT_POTION1, COMBAT_POTION2, COMBAT_POTION3, COMBAT_POTION4); + add(boost(ATTACK, perc(.15, 5)), SUPER_ATTACK1, SUPER_ATTACK2, SUPER_ATTACK3, SUPER_ATTACK4); + add(boost(STRENGTH, perc(.15, 5)), SUPER_STRENGTH1, SUPER_STRENGTH2, SUPER_STRENGTH3, SUPER_STRENGTH4); + add(boost(DEFENCE, perc(.15, 5)), SUPER_DEFENCE1, SUPER_DEFENCE2, SUPER_DEFENCE3, SUPER_DEFENCE4); + add(boost(MAGIC, 3), MAGIC_ESSENCE1, MAGIC_ESSENCE2, MAGIC_ESSENCE3, MAGIC_ESSENCE4); + add(combo(3, boost(ATTACK, perc(.15, 5)), boost(STRENGTH, perc(.15, 5)), boost(DEFENCE, perc(.15, 5))), SUPER_COMBAT_POTION1, SUPER_COMBAT_POTION2, SUPER_COMBAT_POTION3, SUPER_COMBAT_POTION4); + add(combo(3, boost(ATTACK, perc(.20, 2)), boost(STRENGTH, perc(.12, 2)), heal(PRAYER, perc(.10, 0)), heal(DEFENCE, perc(.10, -2)), new BoostedStatBoost(HITPOINTS, false, perc(-.12, 0))), ZAMORAK_BREW1, ZAMORAK_BREW2, ZAMORAK_BREW3, ZAMORAK_BREW4); + add(new SaradominBrew(0.15, 0.2, 0.1, 2, 2), SARADOMIN_BREW1, SARADOMIN_BREW2, SARADOMIN_BREW3, SARADOMIN_BREW4); + add(boost(RANGED, perc(.15, 5)), SUPER_RANGING_1, SUPER_RANGING_2, SUPER_RANGING_3, SUPER_RANGING_4); + add(boost(MAGIC, perc(.15, 5)), SUPER_MAGIC_POTION_1, SUPER_MAGIC_POTION_2, SUPER_MAGIC_POTION_3, SUPER_MAGIC_POTION_4); + add(combo(2, boost(RANGED, perc(0.1, 4)), boost(DEFENCE, perc(0.15, 5))), BASTION_POTION1, BASTION_POTION2, BASTION_POTION3, BASTION_POTION4); + add(combo(2, boost(MAGIC, 4), boost(DEFENCE, perc(0.15, 5))), BATTLEMAGE_POTION1, BATTLEMAGE_POTION2, BATTLEMAGE_POTION3, BATTLEMAGE_POTION4); + add(combo(boost(MAGIC, 4), heal(HITPOINTS, -10)), DIVINE_MAGIC_POTION1, DIVINE_MAGIC_POTION2, DIVINE_MAGIC_POTION3, DIVINE_MAGIC_POTION4); + add(combo(boost(RANGED, perc(.10, 4)), heal(HITPOINTS, -10)), DIVINE_RANGING_POTION1, DIVINE_RANGING_POTION2, DIVINE_RANGING_POTION3, DIVINE_RANGING_POTION4); + add(combo(boost(ATTACK, perc(.15, 5)), heal(HITPOINTS, -10)), DIVINE_SUPER_ATTACK_POTION1, DIVINE_SUPER_ATTACK_POTION2, DIVINE_SUPER_ATTACK_POTION3, DIVINE_SUPER_ATTACK_POTION4); + add(combo(boost(STRENGTH, perc(.15, 5)), heal(HITPOINTS, -10)), DIVINE_SUPER_STRENGTH_POTION1, DIVINE_SUPER_STRENGTH_POTION2, DIVINE_SUPER_STRENGTH_POTION3, DIVINE_SUPER_STRENGTH_POTION4); + add(combo(boost(DEFENCE, perc(.15, 5)), heal(HITPOINTS, -10)), DIVINE_SUPER_DEFENCE_POTION1, DIVINE_SUPER_DEFENCE_POTION2, DIVINE_SUPER_DEFENCE_POTION3, DIVINE_SUPER_DEFENCE_POTION4); + add(combo(3, boost(ATTACK, perc(.15, 5)), boost(STRENGTH, perc(.15, 5)), boost(DEFENCE, perc(.15, 5)), heal(HITPOINTS, -10)), DIVINE_SUPER_COMBAT_POTION1, DIVINE_SUPER_COMBAT_POTION2, DIVINE_SUPER_COMBAT_POTION3, DIVINE_SUPER_COMBAT_POTION4); + add(combo(2, boost(RANGED, perc(0.1, 4)), boost(DEFENCE, perc(0.15, 5)), heal(HITPOINTS, -10)), DIVINE_BASTION_POTION1, DIVINE_BASTION_POTION2, DIVINE_BASTION_POTION3, DIVINE_BASTION_POTION4); + add(combo(2, boost(MAGIC, 4), boost(DEFENCE, perc(0.15, 5)), heal(HITPOINTS, -10)), DIVINE_BATTLEMAGE_POTION1, DIVINE_BATTLEMAGE_POTION2, DIVINE_BATTLEMAGE_POTION3, DIVINE_BATTLEMAGE_POTION4); + + // Regular overload (NMZ) + add(combo(5, boost(ATTACK, perc(.15, 5)), boost(STRENGTH, perc(.15, 5)), boost(DEFENCE, perc(.15, 5)), boost(RANGED, perc(.15, 5)), boost(MAGIC, perc(.15, 5)), heal(HITPOINTS, -50)), OVERLOAD_1, OVERLOAD_2, OVERLOAD_3, OVERLOAD_4); + + // Bandages (Castle Wars) + add(new CastleWarsBandage(), BANDAGES); + + // Recovery potions + add(combo(5, heal(ATTACK, perc(.30, 10)), heal(STRENGTH, perc(.30, 10)), heal(DEFENCE, perc(.30, 10)), heal(RANGED, perc(.30, 10)), heal(MAGIC, perc(.30, 10))), RESTORE_POTION1, RESTORE_POTION2, RESTORE_POTION3, RESTORE_POTION4); + add(heal(RUN_ENERGY, 10), ENERGY_POTION1, ENERGY_POTION2, ENERGY_POTION3, ENERGY_POTION4); + add(new PrayerPotion(7), PRAYER_POTION1, PRAYER_POTION2, PRAYER_POTION3, PRAYER_POTION4); + add(heal(RUN_ENERGY, 20), SUPER_ENERGY1, SUPER_ENERGY2, SUPER_ENERGY3, SUPER_ENERGY4); + add(new SuperRestore(.25, 8), SUPER_RESTORE1, SUPER_RESTORE2, SUPER_RESTORE3, SUPER_RESTORE4, + BLIGHTED_SUPER_RESTORE1, BLIGHTED_SUPER_RESTORE2, BLIGHTED_SUPER_RESTORE3, BLIGHTED_SUPER_RESTORE4); + add(new SuperRestore(.30, 4), SANFEW_SERUM1, SANFEW_SERUM2, SANFEW_SERUM3, SANFEW_SERUM4); + add(heal(RUN_ENERGY, 20), STAMINA_POTION1, STAMINA_POTION2, STAMINA_POTION3, STAMINA_POTION4); + + // Raids potions (+) + add(combo(5, boost(ATTACK, perc(.16, 6)), boost(STRENGTH, perc(.16, 6)), boost(DEFENCE, perc(.16, 6)), boost(RANGED, perc(.16, 6)), boost(MAGIC, perc(.16, 6)), heal(HITPOINTS, -50)), OVERLOAD_1_20993, OVERLOAD_2_20994, OVERLOAD_3_20995, OVERLOAD_4_20996); + add(combo(3, boost(ATTACK, perc(.16, 6)), boost(STRENGTH, perc(.16, 6)), boost(DEFENCE, perc(.16, 6))), ELDER_1_20921, ELDER_2_20922, ELDER_3_20923, ELDER_4_20924); + add(combo(2, boost(RANGED, perc(.16, 6)), boost(DEFENCE, perc(.16, 6))), TWISTED_1_20933, TWISTED_2_20934, TWISTED_3_20935, TWISTED_4_20936); + add(combo(2, boost(MAGIC, perc(.16, 6)), boost(DEFENCE, perc(.16, 6))), KODAI_1_20945, KODAI_2_20946, KODAI_3_20947, KODAI_4_20948); + add(new SuperRestore(.30, 11), REVITALISATION_1_20957, REVITALISATION_2_20958, REVITALISATION_3_20959, REVITALISATION_4_20960); + add(new SaradominBrew(0.15, 0.2, 0.1, 5, 4), XERICS_AID_1_20981, XERICS_AID_2_20982, XERICS_AID_3_20983, XERICS_AID_4_20984); + + // Raids potions + add(combo(5, boost(ATTACK, perc(.13, 5)), boost(STRENGTH, perc(.13, 5)), boost(DEFENCE, perc(.13, 5)), boost(RANGED, perc(.13, 5)), boost(MAGIC, perc(.13, 5)), heal(HITPOINTS, -50)), OVERLOAD_1_20989, OVERLOAD_2_20990, OVERLOAD_3_20991, OVERLOAD_4_20992); + add(combo(3, boost(ATTACK, perc(.13, 5)), boost(STRENGTH, perc(.13, 5)), boost(DEFENCE, perc(.13, 5))), ELDER_POTION_1, ELDER_POTION_2, ELDER_POTION_3, ELDER_POTION_4); + add(combo(2, boost(RANGED, perc(.13, 5)), boost(DEFENCE, perc(.13, 5))), TWISTED_POTION_1, TWISTED_POTION_2, TWISTED_POTION_3, TWISTED_POTION_4); + add(combo(2, boost(MAGIC, perc(.13, 5)), boost(DEFENCE, perc(.13, 5))), KODAI_POTION_1, KODAI_POTION_2, KODAI_POTION_3, KODAI_POTION_4); + + // Raids potions (-) + add(combo(5, boost(ATTACK, perc(.10, 4)), boost(STRENGTH, perc(.10, 4)), boost(DEFENCE, perc(.10, 4)), boost(RANGED, perc(.10, 4)), boost(MAGIC, perc(.10, 4)), heal(HITPOINTS, -50)), OVERLOAD_1_20985, OVERLOAD_2_20986, OVERLOAD_3_20987, OVERLOAD_4_20988); + add(combo(3, boost(ATTACK, perc(.10, 4)), boost(STRENGTH, perc(.10, 4)), boost(DEFENCE, perc(.10, 4))), ELDER_1, ELDER_2, ELDER_3, ELDER_4); + add(combo(3, boost(RANGED, perc(.10, 4)), boost(DEFENCE, perc(.10, 4))), TWISTED_1, TWISTED_2, TWISTED_3, TWISTED_4); + add(combo(3, boost(MAGIC, perc(.10, 4)), boost(DEFENCE, perc(.10, 4))), KODAI_1, KODAI_2, KODAI_3, KODAI_4); + + // Skill potions + add(boost(AGILITY, 3), AGILITY_POTION1, AGILITY_POTION2, AGILITY_POTION3, AGILITY_POTION4); + add(boost(FISHING, 3), FISHING_POTION1, FISHING_POTION2, FISHING_POTION3, FISHING_POTION4); + add(boost(HUNTER, 3), HUNTER_POTION1, HUNTER_POTION2, HUNTER_POTION3, HUNTER_POTION4); + add(combo(2, boost(HITPOINTS, 5), heal(RUN_ENERGY, 5)), GUTHIX_REST1, GUTHIX_REST2, GUTHIX_REST3, GUTHIX_REST4); + + // Misc/run energy + add(combo(food(3), range(heal(RUN_ENERGY, 5), heal(RUN_ENERGY, 10))), WHITE_TREE_FRUIT); + add(heal(RUN_ENERGY, 30), STRANGE_FRUIT); + add(heal(RUN_ENERGY, 50), MINT_CAKE); + add(combo(food(12), heal(RUN_ENERGY, 50)), GOUT_TUBER); + + // Pies + add(combo(2, heal(HITPOINTS, 6), boost(FARMING, 3)), GARDEN_PIE, HALF_A_GARDEN_PIE); + add(combo(2, heal(HITPOINTS, 6), boost(FISHING, 3)), FISH_PIE, HALF_A_FISH_PIE); + add(combo(2, heal(HITPOINTS, 7), boost(HERBLORE, 4)), BOTANICAL_PIE, HALF_A_BOTANICAL_PIE); + add(combo(2, heal(HITPOINTS, 8), boost(CRAFTING, 4)), MUSHROOM_PIE, HALF_A_MUSHROOM_PIE); + add(combo(2, heal(HITPOINTS, 8), boost(FISHING, 5)), ADMIRAL_PIE, HALF_AN_ADMIRAL_PIE); + add(combo(2, heal(HITPOINTS, 11), boost(SLAYER, 5), boost(RANGED, 4)), WILD_PIE, HALF_A_WILD_PIE); + add(combo(2, heal(HITPOINTS, 11), boost(AGILITY, 5), heal(RUN_ENERGY, 10)), SUMMER_PIE, HALF_A_SUMMER_PIE); + add(combo(2, heal(HITPOINTS, 10), boost(FLETCHING, 4)), DRAGONFRUIT_PIE, HALF_A_DRAGONFRUIT_PIE); + + // Other + add(combo(range(food(1), food(3)), heal(RUN_ENERGY, 10)), PURPLE_SWEETS_10476); + add(new SpicyStew(), SPICY_STEW); + add(boost(MAGIC, perc(.10, 1)), IMBUED_HEART); + add(combo(boost(ATTACK, 2), boost(STRENGTH, 1), heal(DEFENCE, -1)), JANGERBERRIES); + + // Gauntlet items + add(heal(HITPOINTS, 20), PADDLEFISH); + add(new GauntletPotion(), EGNIOL_POTION_1, EGNIOL_POTION_2, EGNIOL_POTION_3, EGNIOL_POTION_4); + + log.debug("{} items; {} behaviours loaded", effects.size(), new HashSet<>(effects.values()).size()); + } + + private final Map effects = new HashMap<>(); + + private void add(Effect effect, int... items) + { + assert items.length > 0; + for (int item : items) + { + Effect prev = effects.put(item, effect); + assert prev == null : "Item already added"; + } + } + + public Effect get(int id) + { + return effects.get(id); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesService.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesService.java new file mode 100644 index 0000000000..1189b83be1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesService.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Jos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +public interface ItemStatChangesService +{ + /** + * Get the item stat value + * + * @return ItemStatChanges + */ + Effect getItemStatChanges(int id); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesServiceImpl.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesServiceImpl.java new file mode 100644 index 0000000000..80ce669e6c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChangesServiceImpl.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, Jos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +class ItemStatChangesServiceImpl implements ItemStatChangesService +{ + private final ItemStatChanges itemstatchanges; + + @Inject + private ItemStatChangesServiceImpl(ItemStatChanges itemstatchanges) + { + this.itemstatchanges = itemstatchanges; + } + + @Override + public Effect getItemStatChanges(int id) + { + return itemstatchanges.get(id); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java new file mode 100644 index 0000000000..5d62df7abb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("itemstat") +public interface ItemStatConfig extends Config +{ + @ConfigItem( + keyName = "consumableStats", + name = "Enable consumable stats", + description = "Enables tooltips for consumable items (food, boosts)" + ) + default boolean consumableStats() + { + return true; + } + + @ConfigItem( + keyName = "equipmentStats", + name = "Enable equipment stats", + description = "Enables tooltips for equipment items (combat bonuses, weight, prayer bonuses)" + ) + default boolean equipmentStats() + { + return true; + } + + @ConfigItem( + keyName = "geStats", + name = "Enable GE item information", + description = "Shows an item information panel when buying items in the GE" + ) + default boolean geStats() + { + return true; + } + + @ConfigItem( + keyName = "relative", + name = "Show Relative", + description = "Show relative stat change in tooltip" + ) + default boolean relative() + { + return true; + } + + @ConfigItem( + keyName = "absolute", + name = "Show Absolute", + description = "Show absolute stat change in tooltip" + ) + default boolean absolute() + { + return true; + } + + @ConfigItem( + keyName = "theoretical", + name = "Show Theoretical", + description = "Show theoretical stat change in tooltip" + ) + default boolean theoretical() + { + return false; + } + + @ConfigItem( + keyName = "showWeight", + name = "Show Weight", + description = "Show weight in tooltip" + ) + default boolean showWeight() + { + return true; + } + + @ConfigItem( + keyName = "showStatsInBank", + name = "Show Stats In Bank", + description = "Show item stats on bank items tooltip" + ) + default boolean showStatsInBank() + { + return true; + } + + @ConfigItem( + keyName = "alwaysShowBaseStats", + name = "Always Show Base Stats", + description = "Always include the base items stats in the tooltip" + ) + default boolean alwaysShowBaseStats() + { + return false; + } + + @ConfigItem( + keyName = "colorBetterUncapped", + name = "Better (Uncapped)", + description = "Color to show when the stat change is fully consumed", + position = 10 + ) + default Color colorBetterUncapped() + { + return new Color(0x33EE33); + } + + @ConfigItem( + keyName = "colorBetterSomecapped", + name = "Better (Some capped)", + description = "Color to show when some stat changes are capped, but some are not", + position = 11 + ) + default Color colorBetterSomeCapped() + { + return new Color(0x9CEE33); + } + + + @ConfigItem( + keyName = "colorBetterCapped", + name = "Better (Capped)", + description = "Color to show when the stat change is positive, but not fully consumed", + position = 12 + ) + default Color colorBetterCapped() + { + return new Color(0xEEEE33); + } + @ConfigItem( + keyName = "colorNoChange", + name = "No change", + description = "Color to show when there is no change", + position = 13 + ) + default Color colorNoChange() + { + return new Color(0xEEEEEE); + } + + @ConfigItem( + keyName = "colorWorse", + name = "Worse", + description = "Color to show when the stat goes down", + position = 14 + ) + default Color colorWorse() + { + return new Color(0xEE3333); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java new file mode 100644 index 0000000000..ff3922fd92 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2018 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.itemstats; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.Inject; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.time.Duration; +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.MenuEntry; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.itemstats.potions.PotionDuration; +import net.runelite.client.ui.JagexColors; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.http.api.item.ItemEquipmentStats; +import net.runelite.http.api.item.ItemStats; +import org.apache.commons.lang3.time.DurationFormatUtils; + +public class ItemStatOverlay extends Overlay +{ + // Unarmed attack speed is 4 + @VisibleForTesting + static final ItemStats UNARMED = new ItemStats(false, true, 0, 0, + ItemEquipmentStats.builder() + .aspeed(4) + .build()); + + @Inject + private Client client; + + @Inject + private ItemManager itemManager; + + @Inject + private TooltipManager tooltipManager; + + @Inject + private ItemStatChanges statChanges; + + @Inject + private ItemStatConfig config; + + @Override + public Dimension render(Graphics2D graphics) + { + if (client.isMenuOpen() || (!config.relative() && !config.absolute() && !config.theoretical())) + { + return null; + } + + final MenuEntry[] menu = client.getMenuEntries(); + final int menuSize = menu.length; + + if (menuSize <= 0) + { + return null; + } + + final MenuEntry entry = menu[menuSize - 1]; + final int group = WidgetInfo.TO_GROUP(entry.getParam1()); + final int child = WidgetInfo.TO_CHILD(entry.getParam1()); + final Widget widget = client.getWidget(group, child); + + if (widget == null + || !(group == WidgetInfo.INVENTORY.getGroupId() + || group == WidgetInfo.EQUIPMENT.getGroupId() + || group == WidgetInfo.EQUIPMENT_INVENTORY_ITEMS_CONTAINER.getGroupId() + || (config.showStatsInBank() + && ((group == WidgetInfo.BANK_ITEM_CONTAINER.getGroupId() && child == WidgetInfo.BANK_ITEM_CONTAINER.getChildId()) + || group == WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getGroupId())))) + { + return null; + } + + int itemId = entry.getIdentifier(); + + if (group == WidgetInfo.EQUIPMENT.getGroupId() || + // For bank worn equipment, check widget parent to differentiate from normal bank items + (group == WidgetID.BANK_GROUP_ID && widget.getParentId() == WidgetInfo.BANK_EQUIPMENT_CONTAINER.getId())) + { + final Widget widgetItem = widget.getChild(1); + if (widgetItem != null) + { + itemId = widgetItem.getItemId(); + } + } + else if (group == WidgetInfo.EQUIPMENT_INVENTORY_ITEMS_CONTAINER.getGroupId() + || group == WidgetInfo.BANK_ITEM_CONTAINER.getGroupId() + || group == WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getGroupId()) + { + int index = entry.getParam0(); + if (index > -1) + { + final Widget widgetItem = widget.getChild(index); + if (widgetItem != null) + { + itemId = widgetItem.getItemId(); + } + } + } + + if (config.consumableStats()) + { + final Effect change = statChanges.get(itemId); + if (change != null) + { + final StringBuilder b = new StringBuilder(); + final StatsChanges statsChanges = change.calculate(client); + + for (final StatChange c : statsChanges.getStatChanges()) + { + b.append(buildStatChangeString(c)); + } + + final String tooltip = b.toString(); + + if (!tooltip.isEmpty()) + { + tooltipManager.add(new Tooltip(tooltip)); + } + } + + PotionDuration p = PotionDuration.get(itemId); + if (p != null) + { + PotionDuration.PotionDurationRange[] durationRanges = p.getDurationRanges(); + StringBuilder sb = new StringBuilder(); + if (durationRanges.length == 1) + { + // Only show "Duration: