Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Py4GWCoreLib/GlobalCache/GlobalCache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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()
Expand All @@ -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()
Expand Down
48 changes: 47 additions & 1 deletion Py4GWCoreLib/GlobalCache/SkillbarCache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
79 changes: 74 additions & 5 deletions Py4GWCoreLib/Skillbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
1 change: 1 addition & 0 deletions Py4GWCoreLib/enums_src/Model_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Py4GWCoreLib/py4gwcorelib_src/Lootconfig_src.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,9 @@
ModelID.Hammer_of_Kathandrax,
ModelID.Prismatic_Gelatinous_Material,
],
"General": [
ModelID.Urn_of_Saint_Viktor,
],
},
}

Expand Down
65 changes: 61 additions & 4 deletions Py4GWCoreLib/routines_src/Yield.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
1 change: 1 addition & 0 deletions Widgets/Data/modelid_drop_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
]
Loading