From fb82795815f6722eb4b52332597983de4681dbb8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:10:15 +0000 Subject: [PATCH 1/4] Add Urn of Saint Viktor (ItemID 801) to LootManager Co-authored-by: Ewoog <72410352+Ewoog@users.noreply.github.com> --- Py4GWCoreLib/enums_src/Model_enums.py | 1 + Py4GWCoreLib/py4gwcorelib_src/Lootconfig_src.py | 3 +++ Widgets/Data/modelid_drop_data.json | 1 + 3 files changed, 5 insertions(+) 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/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"} ] From 0d873187981494d1f953d917bc7ddbb40818c2b7 Mon Sep 17 00:00:00 2001 From: Ewoog <72410352+Ewoog@users.noreply.github.com> Date: Sat, 22 Nov 2025 09:00:06 -0600 Subject: [PATCH 2/4] Merge pull request #39 from Ewoog/copilot/troubleshoot-hero-builds-loading Fix LoadHeroSkillTemplate - Clarify party position parameter and add hero_id/name helpers --- Py4GWCoreLib/GlobalCache/SkillbarCache.py | 48 ++++++- Py4GWCoreLib/Skillbar.py | 79 +++++++++++- Py4GWCoreLib/routines_src/Yield.py | 10 +- docs/LoadHeroSkillTemplate_Usage.md | 146 ++++++++++++++++++++++ 4 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 docs/LoadHeroSkillTemplate_Usage.md diff --git a/Py4GWCoreLib/GlobalCache/SkillbarCache.py b/Py4GWCoreLib/GlobalCache/SkillbarCache.py index b8f538391..75c320956 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 7c8b5b009..ad76466c8 100644 --- a/Py4GWCoreLib/Skillbar.py +++ b/Py4GWCoreLib/Skillbar.py @@ -13,15 +13,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/routines_src/Yield.py b/Py4GWCoreLib/routines_src/Yield.py index f5cb15365..6cff2974d 100644 --- a/Py4GWCoreLib/routines_src/Yield.py +++ b/Py4GWCoreLib/routines_src/Yield.py @@ -524,17 +524,17 @@ def LoadSkillbar(skill_template:str, log=False): @staticmethod def LoadHeroSkillbar(hero_index:int, skill_template:str, log=False): """ - Purpose: Load the specified hero skillbar. + Purpose: Load the specified hero skillbar by party position. Args: - hero_index (int): The index of the hero (1-4). - skill_template (str): The name of the skill template to load. - log (bool) Optional: Whether to log the action. Default is True. + 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 """ GLOBAL_CACHE.SkillBar.LoadHeroSkillTemplate(hero_index, skill_template) - ConsoleLog("LoadHeroSkillbar", f"Loading hero {hero_index} skill Template {skill_template}", log=log) + ConsoleLog("LoadHeroSkillbar", f"Loading hero at party position {hero_index} with template {skill_template}", log=log) yield from Yield.wait(500) @staticmethod 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 From 97f1ea860280657ea8b0a38bca57c0768af49f10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 14:54:09 +0000 Subject: [PATCH 3/4] Initial plan From fb4dc47f48f0d31674339f4d4685db434e150b60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 14:59:06 +0000 Subject: [PATCH 4/4] Fix Party cache update during map transitions The map verify timeout was caused by a race condition where the Party cache was not being updated immediately after a map finished loading. This change tracks the map loading state and ensures Party._update_cache() is called not only during map loading, but also immediately after the map transitions from loading to ready state. This eliminates the window where IsPartyLoaded() could return stale data during map verification. Co-authored-by: Ewoog <72410352+Ewoog@users.noreply.github.com> --- Py4GWCoreLib/GlobalCache/GlobalCache.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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()