diff --git a/Py4GWCoreLib/GlobalCache/GlobalCache.py b/Py4GWCoreLib/GlobalCache/GlobalCache.py index d605ff11a..22121e289 100644 --- a/Py4GWCoreLib/GlobalCache/GlobalCache.py +++ b/Py4GWCoreLib/GlobalCache/GlobalCache.py @@ -50,6 +50,7 @@ def _init_namespaces(self): self.SkillBar = SkillbarCache(self._ActionQueueManager) self.ShMem = Py4GWSharedMemoryManager() self.Coroutines: List[Generator] = [] + self._was_map_loading = False def _reset(self): @@ -61,7 +62,14 @@ def _reset(self): def _update_cache(self): self.Map._update_cache() - if self.Map.IsMapLoading() or self.Map.IsInCinematic(): + + # Track map loading state to detect transitions + is_map_loading = self.Map.IsMapLoading() + is_in_cinematic = self.Map.IsInCinematic() + map_just_finished_loading = self._was_map_loading and not is_map_loading + + # Update critical caches during map loading, cinematics, or immediately after map finishes loading + if is_map_loading or is_in_cinematic or map_just_finished_loading: self.Party._update_cache() self.Player._update_cache() self._RawItemCache.update() @@ -70,6 +78,9 @@ def _update_cache(self): self.Agent._update_cache() self.AgentArray._update_cache() self.SkillBar._update_cache() + + # Update the tracking variable for next iteration + self._was_map_loading = is_map_loading if self._TrottleTimers._75ms.IsExpired(): self._TrottleTimers._75ms.Reset() diff --git a/Py4GWCoreLib/GlobalCache/SkillbarCache.py b/Py4GWCoreLib/GlobalCache/SkillbarCache.py index 1370123f7..a827bf848 100644 --- a/Py4GWCoreLib/GlobalCache/SkillbarCache.py +++ b/Py4GWCoreLib/GlobalCache/SkillbarCache.py @@ -12,8 +12,54 @@ def _update_cache(self): def LoadSkillTemplate(self, skill_template): self._action_queue_manager.AddAction("ACTION", self._skillbar_instance.LoadSkillTemplate, skill_template) - def LoadHeroSkillTemplate (self, hero_index, skill_template): + def LoadHeroSkillTemplate(self, hero_index, skill_template): + """Load a hero skill template by party position (1-based index). + + Note: This uses an action queue and executes asynchronously. + + Args: + hero_index (int): The 1-based party position of the hero (1 = first hero, 2 = second hero, etc.) + skill_template (str): The skill template code to load. + """ self._action_queue_manager.AddAction("ACTION", self._skillbar_instance.LoadHeroSkillTemplate, hero_index, skill_template) + + def LoadHeroSkillTemplateByHeroID(self, hero_id, skill_template): + """Load a hero skill template by Hero ID. + + Note: This uses an action queue and executes asynchronously. + + Args: + hero_id (int): The Hero ID (e.g., HeroType.Koss = 6) + skill_template (str): The skill template code to load. + """ + from Py4GWCoreLib.Party import Party + + # Find which party position this hero is in + heroes = Party.GetHeroes() + for idx, hero in enumerate(heroes): + if hero.hero_id.GetID() == hero_id: + hero_index = idx + 1 # 1-indexed + self.LoadHeroSkillTemplate(hero_index, skill_template) + return + + print(f"Error: Hero with ID {hero_id} is not in your party.") + + def LoadHeroSkillTemplateByName(self, hero_name, skill_template): + """Load a hero skill template by hero name. + + Note: This uses an action queue and executes asynchronously. + + Args: + hero_name (str): The hero name (e.g., "Koss") + skill_template (str): The skill template code to load. + """ + from PyParty import Hero + + try: + hero_id = Hero(hero_name).GetID() + self.LoadHeroSkillTemplateByHeroID(hero_id, skill_template) + except Exception: + print(f"Error: Could not find hero with name '{hero_name}'") def GetSkillBySlot(self, slot): return self._skillbar_instance.GetSkill(slot) diff --git a/Py4GWCoreLib/Skillbar.py b/Py4GWCoreLib/Skillbar.py index b4c5caa34..7b1f84244 100644 --- a/Py4GWCoreLib/Skillbar.py +++ b/Py4GWCoreLib/Skillbar.py @@ -16,15 +16,84 @@ def LoadSkillTemplate(skill_template): skillbar_instance.LoadSkillTemplate(skill_template) @staticmethod - def LoadHeroSkillTemplate (hero_index, skill_template): + def LoadHeroSkillTemplate(hero_index, skill_template): """ - Purpose: Load a Hero skill template by Hero index and Template. + Purpose: Load a Hero skill template by party position. Args: - hero_index: int, template_name (str): The name of the skill template to load. - Returns: None + hero_index (int): The 1-based party position of the hero (1 = first hero, 2 = second hero, etc.). + This is NOT the hero ID (e.g., Koss=6). To use hero IDs, use LoadHeroSkillTemplateByHeroID() instead. + skill_template (str): The skill template code to load. + Returns: + bool: True if the template was loaded successfully, False otherwise. + + Example: + # Load a template on the first hero in your party (regardless of which hero it is) + SkillBar.LoadHeroSkillTemplate(1, "OQATEjpUjIACVAAAAAAAAAA") + + # Load on the second hero + SkillBar.LoadHeroSkillTemplate(2, "OQASEDqEC1vcNABWAAAA") """ skillbar_instance = PySkillbar.Skillbar() - skillbar_instance.LoadHeroSkillTemplate(hero_index, skill_template) + result = skillbar_instance.LoadHeroSkillTemplate(hero_index, skill_template) + if not result: + print(f"Warning: Failed to load skill template on hero at party position {hero_index}. " + f"Ensure a hero exists at that position and the template code is valid.") + return result + + @staticmethod + def LoadHeroSkillTemplateByHeroID(hero_id, skill_template): + """ + Purpose: Load a Hero skill template by Hero ID (e.g., Koss=6, Norgu=1). + Args: + hero_id (int): The Hero ID (see PyParty.HeroType for values). + skill_template (str): The skill template code to load. + Returns: + bool: True if the template was loaded successfully, False otherwise. + + Example: + from PyParty import HeroType + SkillBar.LoadHeroSkillTemplateByHeroID(HeroType.Koss, "OQATEjpUjIACVAAAAAAAAAA") + """ + from Py4GWCoreLib.Party import Party + + # Find which party position this hero is in + heroes = Party.GetHeroes() + for idx, hero in enumerate(heroes): + if hero.hero_id.GetID() == hero_id: + hero_index = idx + 1 # 1-indexed + return SkillBar.LoadHeroSkillTemplate(hero_index, skill_template) + + # Hero not found in party + try: + from PyParty import Hero + hero_name = Hero(hero_id).GetName() + print(f"Error: Hero '{hero_name}' (ID: {hero_id}) is not in your party.") + except Exception: + print(f"Error: Hero with ID {hero_id} is not in your party.") + return False + + @staticmethod + def LoadHeroSkillTemplateByName(hero_name, skill_template): + """ + Purpose: Load a Hero skill template by hero name. + Args: + hero_name (str): The hero name (e.g., "Koss", "Norgu", etc.) + skill_template (str): The skill template code to load. + Returns: + bool: True if the template was loaded successfully, False otherwise. + + Example: + SkillBar.LoadHeroSkillTemplateByName("Koss", "OQATEjpUjIACVAAAAAAAAAA") + """ + from Py4GWCoreLib.Party import Party + from PyParty import Hero + + try: + hero_id = Hero(hero_name).GetID() + return SkillBar.LoadHeroSkillTemplateByHeroID(hero_id, skill_template) + except Exception: + print(f"Error: Could not find hero with name '{hero_name}'") + return False @staticmethod def GetSkillbar(): diff --git a/Py4GWCoreLib/enums_src/Model_enums.py b/Py4GWCoreLib/enums_src/Model_enums.py index b01734fce..10bea81fc 100644 --- a/Py4GWCoreLib/enums_src/Model_enums.py +++ b/Py4GWCoreLib/enums_src/Model_enums.py @@ -1540,6 +1540,7 @@ class ModelID(IntEnum): Undead_Bone = 27974 Unnatural_Seed = 428 Unseen_Tonic = 31172 + Urn_of_Saint_Viktor = 801 Vabbian_Key = 15558 Vaettir_Essence = 27071 Varesh_Ossa_Mini = 21069 diff --git a/Py4GWCoreLib/py4gwcorelib_src/Lootconfig_src.py b/Py4GWCoreLib/py4gwcorelib_src/Lootconfig_src.py index 96b6bea38..0e31752be 100644 --- a/Py4GWCoreLib/py4gwcorelib_src/Lootconfig_src.py +++ b/Py4GWCoreLib/py4gwcorelib_src/Lootconfig_src.py @@ -531,6 +531,9 @@ ModelID.Hammer_of_Kathandrax, ModelID.Prismatic_Gelatinous_Material, ], + "General": [ + ModelID.Urn_of_Saint_Viktor, + ], }, } diff --git a/Py4GWCoreLib/routines_src/Yield.py b/Py4GWCoreLib/routines_src/Yield.py index 84c879c9b..9bb3d19cd 100644 --- a/Py4GWCoreLib/routines_src/Yield.py +++ b/Py4GWCoreLib/routines_src/Yield.py @@ -583,13 +583,70 @@ def GetAgentIDByModelID(model_id:int): @staticmethod def ChangeTarget(agent_id, log=False): """ - Purpose: Change the player's target to the specified agent ID. + Purpose: Load the specified hero skillbar by party position. Args: - agent_id (int): The ID of the agent to target. + hero_index (int): The 1-based party position of the hero (1 = first hero, 2 = second, etc.). + skill_template (str): The skill template code to load. + log (bool) Optional: Whether to log the action. Default is False. Returns: None """ - yield from Yield.Player.ChangeTarget(agent_id, log=log) - + + + GLOBAL_CACHE.SkillBar.LoadHeroSkillTemplate(hero_index, skill_template) + ConsoleLog("LoadHeroSkillbar", f"Loading hero at party position {hero_index} with template {skill_template}", log=log) + yield from Yield.wait(500) + + @staticmethod + def CastSkillID (skill_id:int,extra_condition=True, aftercast_delay=0, log=False): + from .Checks import Checks + if not Checks.Map.IsExplorable(): + return False + + player_agent_id = GLOBAL_CACHE.Player.GetAgentID() + enough_energy = Checks.Skills.HasEnoughEnergy(player_agent_id,skill_id) + skill_ready = Checks.Skills.IsSkillIDReady(skill_id) + + if not(enough_energy and skill_ready and extra_condition): + yield + return False + slot = GLOBAL_CACHE.SkillBar.GetSlotBySkillID(skill_id) + if slot <= 0 or slot > 8: + yield + return False + + GLOBAL_CACHE.SkillBar.UseSkill(GLOBAL_CACHE.SkillBar.GetSlotBySkillID(skill_id), aftercast_delay=aftercast_delay) + if log: + ConsoleLog("CastSkillID", f"Cast {GLOBAL_CACHE.Skill.GetName(skill_id)}, slot: {GLOBAL_CACHE.SkillBar.GetSlotBySkillID(skill_id)}", Console.MessageType.Info) + + if aftercast_delay > 0: + yield from Yield.wait(aftercast_delay) + return True + + @staticmethod + def IsSkillIDUsable(skill_id: int): + from .Checks import Checks + if not Checks.Map.IsExplorable(): + return False + + player_agent_id = GLOBAL_CACHE.Player.GetAgentID() + enough_energy = Checks.Skills.HasEnoughEnergy(player_agent_id,skill_id) + skill_ready = Checks.Skills.IsSkillIDReady(skill_id) + yield + return enough_energy and skill_ready + + @staticmethod + def IsSkillSlotUsable(skill_slot: int): + from .Checks import Checks + if not Checks.Map.IsExplorable(): + return False + + player_agent_id = GLOBAL_CACHE.Player.GetAgentID() + skill = GLOBAL_CACHE.SkillBar.GetSkillData(skill_slot) + enough_energy = Checks.Skills.HasEnoughEnergy(player_agent_id, skill.id) + skill_ready = Checks.Skills.IsSkillSlotReady(skill_slot) + yield + return enough_energy and skill_ready + @staticmethod def InteractAgent(agent_id:int, log:bool=False): """ diff --git a/Widgets/Data/modelid_drop_data.json b/Widgets/Data/modelid_drop_data.json index 3246e827e..81affdbf3 100644 --- a/Widgets/Data/modelid_drop_data.json +++ b/Widgets/Data/modelid_drop_data.json @@ -398,5 +398,6 @@ {"name": "Exquisite Surmia Carving (Cathedral of Flames)", "model_id": "ModelID.Exquisite_Surmia_Carving", "group": "Quest Items", "subgroup": "Dungeon quest items", "drop_info": "Found in Murakai's Chest"}, {"name": "Hammer of Kathandrax (Catacombs of Kathandrax)", "model_id": "ModelID.Hammer_of_Kathandrax", "group": "Quest Items", "subgroup": "Dungeon quest items", "drop_info": "Found in the Chest of Kathandrax"}, {"name": "Prismatic Gelatinous Material (Ooze Pit)", "model_id": "ModelID.Prismatic_Gelatinous_Material", "group": "Quest Items", "subgroup": "Dungeon quest items", "drop_info": "Found in the Prismatic Chest"}, + {"name": "Urn of Saint Viktor", "model_id": "ModelID.Urn_of_Saint_Viktor", "group": "Quest Items", "subgroup": "General", "drop_info": "Quest item used in various quests"}, {"name": "Prison Key", "model_id": "ModelID.Prison_Key", "group": "Quest Items", "subgroup": "Keys", "drop_info": "Found in EOTN dungeons"} ] diff --git a/docs/LoadHeroSkillTemplate_Usage.md b/docs/LoadHeroSkillTemplate_Usage.md new file mode 100644 index 000000000..05ea2b72f --- /dev/null +++ b/docs/LoadHeroSkillTemplate_Usage.md @@ -0,0 +1,146 @@ +# Loading Hero Skill Templates - Usage Guide + +## Overview +The `LoadHeroSkillTemplate` function allows you to load a skill build (template code) onto a hero in your party. + +## IMPORTANT: Party Position vs Hero ID + +The function expects a **1-based party position** (1 = first hero, 2 = second hero, etc.), **NOT** a Hero ID (like Koss=6). + +### What is Party Position? +- **Party Position 1** = The first hero in your party (could be any hero) +- **Party Position 2** = The second hero in your party +- **Party Position 3** = The third hero in your party +- etc. + +### What is Hero ID? +- **Hero ID** is a unique identifier for each specific hero: + - Norgu = 1 + - Koss = 6 + - Tahlkora = 3 + - etc. (see PyParty.HeroType) + +## Usage Methods + +### Method 1: Using Party Position (Default) +Use this when you want to load a template on "the first hero" or "the second hero" in your party, regardless of which specific hero it is. + +```python +from Py4GWCoreLib import SkillBar + +# Load a template on the first hero in your party +result = SkillBar.LoadHeroSkillTemplate(1, "OQATEjpUjIACVAAAAAAAAAA") + +# Load a template on the second hero in your party +result = SkillBar.LoadHeroSkillTemplate(2, "OQASEDqEC1vcNABWAAAA") +``` + +### Method 2: Using Hero ID (Recommended for specific heroes) +Use this when you want to load a template on a **specific hero** (e.g., always load on Koss). + +```python +from Py4GWCoreLib import SkillBar +from PyParty import HeroType + +# Load a template on Koss (wherever Koss is in your party) +result = SkillBar.LoadHeroSkillTemplateByHeroID(HeroType.Koss, "OQATEjpUjIACVAAAAAAAAAA") + +# Load a template on Tahlkora +result = SkillBar.LoadHeroSkillTemplateByHeroID(HeroType.Tahlkora, "OQASEDqEC1vcNABWAAAA") +``` + +### Method 3: Using Hero Name (Most User-Friendly) +Use this when you want to specify the hero by name. + +```python +from Py4GWCoreLib import SkillBar + +# Load a template on Koss by name +result = SkillBar.LoadHeroSkillTemplateByName("Koss", "OQATEjpUjIACVAAAAAAAAAA") + +# Load a template on Tahlkora by name +result = SkillBar.LoadHeroSkillTemplateByName("Tahlkora", "OQASEDqEC1vcNABWAAAA") +``` + +## Common Mistakes + +### ❌ WRONG: Using Hero ID with LoadHeroSkillTemplate +```python +from PyParty import HeroType + +# This will try to load the template on the 6th hero in your party, NOT on Koss! +# (Koss has hero_id=6, but that's not the same as party position 6) +SkillBar.LoadHeroSkillTemplate(HeroType.Koss, "template") # WRONG! +``` + +### ✅ CORRECT: Use the right method for your intent +```python +from PyParty import HeroType + +# If you want to load on Koss specifically: +SkillBar.LoadHeroSkillTemplateByHeroID(HeroType.Koss, "template") # CORRECT + +# OR +SkillBar.LoadHeroSkillTemplateByName("Koss", "template") # CORRECT + +# If you want to load on "the first hero" (whoever that is): +SkillBar.LoadHeroSkillTemplate(1, "template") # CORRECT +``` + +## Return Values +All methods return `True` if successful, `False` otherwise: + +```python +result = SkillBar.LoadHeroSkillTemplateByName("Koss", "OQATEjpUjIACVAAAAAAAAAA") +if not result: + print("Failed - Koss might not be in your party or template is invalid") +``` + +## Hero ID Reference +Common Hero IDs from PyParty.HeroType: +- Norgu = 1 +- Goren = 2 +- Tahlkora = 3 +- Master Of Whispers = 4 +- Acolyte Jin = 5 +- Koss = 6 +- Dunkoro = 7 +- Acolyte Sousuke = 8 +- Melonni = 9 +- Zhed Shadowhoof = 10 +- General Morgahn = 11 +- Magrid The Sly = 12 +- Zenmai = 13 +- Olias = 14 +- Razah = 15 +- MOX = 16 +- Keiran Thackeray = 17 +- Jora = 18 +- Pyre Fierceshot = 19 +- Anton = 20 +- Livia = 21 +- Hayda = 22 +- Kahmu = 23 +- Gwen = 24 +- Xandra = 25 +- Vekk = 26 +- Ogden = 27 +- Mercenary Heroes = 28-35 +- Miku = 36 +- Zei Ri = 37 + +## Troubleshooting + +**Error: "Failed to load skill template on hero at party position X"** +- The hero doesn't exist at that party position (you might only have 2 heroes but tried position 3) +- The template code is invalid + +**Error: "Hero 'Koss' (ID: 6) is not in your party"** +- The specified hero is not in your current party +- Add the hero to your party first + +**Why is my template not loading?** +1. Make sure you're using the correct method for your intent (party position vs hero ID) +2. Verify the hero is in your party +3. Check that the template code is valid +4. Ensure you have the skills unlocked