diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/Rs2SpiritTreeCache.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/Rs2SpiritTreeCache.java index ed2b294e416..01319ed81b4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/Rs2SpiritTreeCache.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/Rs2SpiritTreeCache.java @@ -97,19 +97,14 @@ public static SpiritTreeData getSpiritTreeData(SpiritTree spiritTree) { return getInstance().get(spiritTree, () -> { try { // Determine initial state based on spiritTree type - CropState cropState = null; - boolean availableForTravel = false; + CropState cropState = CropState.HARVESTABLE; + boolean availableForTravel = spiritTree.hasQuestRequirements(); - if (spiritTree.getType() == SpiritTree.SpiritTreeType.BUILT_IN) { - // Built-in trees are available if quest requirements are met - availableForTravel = spiritTree.hasQuestRequirements(); - } else if (spiritTree.getType() == SpiritTree.SpiritTreeType.FARMABLE) { - // Farmable trees - get current farming state + if (spiritTree.getType() == SpiritTree.SpiritTreeType.FARMABLE) { cropState = spiritTree.getPatchState(); - availableForTravel = spiritTree.isAvailableForTravel(); - }else if (spiritTree.getType() == SpiritTree.SpiritTreeType.POH) { - // Other types (e.g. POH spirit trees) - use default availability - availableForTravel = spiritTree.isAvailableForTravel(); + availableForTravel &= spiritTree.isAvailableForTravel(); + } else if (spiritTree.getType() == SpiritTree.SpiritTreeType.POH) { + availableForTravel &= spiritTree.hasLevelRequirement(); } log.debug("Initial spirit tree data for {}: \n\tcropState={}, available={}", @@ -590,6 +585,14 @@ public void onGameObjectSpawned(net.runelite.api.events.GameObjectSpawned event) getInstance().handleEvent(event); } + /** + * Handle GameObjectSpawned event and delegate to update strategy. + */ + @Subscribe + public void onGameObjectDespawned(net.runelite.api.events.GameObjectDespawned event) { + getInstance().handleEvent(event); + } + /** * Handle game state changes for cache lifecycle management (unchanged). */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/strategy/farming/SpiritTreeUpdateStrategy.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/strategy/farming/SpiritTreeUpdateStrategy.java index c01222d71db..0c47a9b9d82 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/strategy/farming/SpiritTreeUpdateStrategy.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/cache/strategy/farming/SpiritTreeUpdateStrategy.java @@ -6,16 +6,13 @@ import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.VarbitChanged; -import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.events.*; import net.runelite.api.gameval.ObjectID; import net.runelite.api.gameval.VarbitID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.questhelper.helpers.mischelpers.farmruns.CropState; import net.runelite.client.plugins.microbot.util.cache.Rs2Cache; -import net.runelite.client.plugins.microbot.util.cache.Rs2ObjectCache; import net.runelite.client.plugins.microbot.util.cache.model.SpiritTreeData; import net.runelite.client.plugins.microbot.util.cache.strategy.CacheOperations; import net.runelite.client.plugins.microbot.util.cache.strategy.CacheUpdateStrategy; @@ -59,27 +56,41 @@ public class SpiritTreeUpdateStrategy implements CacheUpdateStrategy cache) { try { - if (event instanceof WidgetLoaded) { handleWidgetLoaded((WidgetLoaded) event, cache); } else if (event instanceof VarbitChanged) { handleVarbitChanged((VarbitChanged) event, cache); } else if (event instanceof GameStateChanged) { handleGameStateChanged((GameStateChanged) event, cache); + } else if (event instanceof GameObjectSpawned) { + GameObject go = ((GameObjectSpawned) event).getGameObject(); + handleGameObjectChange(go, true, cache); + } else if (event instanceof GameObjectDespawned) { + GameObject go = ((GameObjectDespawned) event).getGameObject(); + handleGameObjectChange(go, false, cache); } } catch (Exception e) { log.error("Error handling event in SpiritTreeUpdateStrategy: {}", e.getMessage(), e); } } - + + /** + * Get the event types that are handled by this strategy + */ @Override public Class[] getHandledEventTypes() { - return new Class[]{WidgetLoaded.class, VarbitChanged.class, GameStateChanged.class}; - } + return new Class[]{WidgetLoaded.class, VarbitChanged.class, GameStateChanged.class, GameObjectSpawned.class, GameObjectDespawned.class}; + } + /** * Handle widget loaded events to detect spirit tree widget opening */ @@ -127,13 +138,38 @@ private void handleVarbitChanged(VarbitChanged event, CacheOperations cache) { + if(gameObject == null){ + return; } - + Arrays.stream(SpiritTree.values()).filter(tree -> tree.getType() == SpiritTree.SpiritTreeType.POH).forEach(tree -> { + if (tree.getObjectId().contains(gameObject.getId())) { + log.info("Found spirit tree object {} for POH spirit tree {}, {} to cache", gameObject.getId(), tree.name(), spawned ? "added" : "removed"); + SpiritTreeData newData = new SpiritTreeData( + tree, + spawned ? CropState.HARVESTABLE: CropState.DEAD, + spawned, + gameObject.getWorldLocation(), + false, // Not detected via widget + true // Detected via nearby tree if present + ); + cache.put(tree, newData); + } + }); + } - + /** * Handle GameStateChanged events to detect POH region changes and validate spirit tree presence */ @@ -148,53 +184,11 @@ private void handleGameStateChanged(GameStateChanged event, CacheOperations cache) { - try { - WorldPoint playerLocation = getPlayerLocation(); - if (playerLocation == null|| !Rs2Cache.isInPOH()) { - log.warn("Cannot determine player location for POH spirit tree detection"); - return; - } - // Stream over all SpiritTree values, filter for POH type, and update cache for each - Arrays.stream(SpiritTree.values()) - .filter(tree -> tree.getType() == SpiritTree.SpiritTreeType.POH) - .forEach(tree -> { - // Check for POH spirit tree object nearby using the objectId defined in the enum - boolean pohSpiritTreePresent = Rs2ObjectCache.getGameObjects() - .anyMatch(obj -> tree.getObjectId().contains(obj.getId()) && - obj.getWorldLocation().distanceTo(playerLocation) <= 40); - - SpiritTreeData newData = new SpiritTreeData( - tree, - pohSpiritTreePresent ? CropState.HARVESTABLE : CropState.DEAD, - pohSpiritTreePresent, - playerLocation, - false, // Not detected via widget - pohSpiritTreePresent // Detected via nearby tree if present - ); - - cache.put(tree, newData); - log.debug("Updated POH spirit tree cache for {} - {}", tree.name(), pohSpiritTreePresent ? "tree present and available" : "no tree present"); - }); - } catch (Exception e) { - log.error("Error updating POH spirit tree cache: {}", e.getMessage(), e); - } - } - /** * Update cache from spirit tree widget data */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/farming/SpiritTree.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/farming/SpiritTree.java index b78c9700be7..02cc212d506 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/farming/SpiritTree.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/farming/SpiritTree.java @@ -15,6 +15,7 @@ import net.runelite.client.plugins.microbot.questhelper.helpers.mischelpers.farmruns.CropState; import net.runelite.client.plugins.microbot.questhelper.helpers.mischelpers.farmruns.FarmingPatch; import net.runelite.client.plugins.microbot.util.cache.Rs2SpiritTreeCache; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.poh.PohTeleports; @@ -199,9 +200,9 @@ public enum SpiritTree { null, // Location is dynamic based on player's house SpiritTreeType.POH, List.of(Quest.TREE_GNOME_VILLAGE, Quest.THE_GRAND_TREE, Quest.FAIRYTALE_II__CURE_A_QUEEN), - 75, // Requires 75 Construction + 95, // Requires 95 Construction VarbitID.POH_SPIRIT_TREE_UPROOTED, // TODO Must be checkedPOH Spirit Tree varbit (combined tree/ring uses same varbit) - List.of(ObjectID.POH_SPIRIT_RING), // TODO Must be checked Spirit Ring object ID // we must update it. here are also variations for it.. leauge skins + Rs2GameObject.getObjectIdsByName("poh_spirit_ring"), new int[] {-1}, // Region must be player's own house "Your house" ); @@ -249,16 +250,6 @@ public boolean isAvailableForTravel() { return isPatchHealthyAndGrown(); } - // Check construction level for POH trees - if (type == SpiritTreeType.POH) { - if (Rs2Player.getRealSkillLevel(Skill.CONSTRUCTION) < requiredSkillLevel || !Rs2Farming.hasRequiredFarmingLevel(75)) { - return false; - } - - // For POH trees, check if they are built and player is in their house - return isPOHTreeAvailable(); - } - // Built-in trees are always available if quest requirements are met return true; }