extends PluginButton const FirestormSkillId = 5531 var api var opener_complete: bool var loop_complete: bool var opener_step: int var loop_step: int var wait_skillbar_ready: bool var swap_weapons: bool var rotation_stall_warning_issued: bool var fiery_greatswotd_active: bool var last_skill_slot_cast: int var opening_rotation: Array = [ "Fire Attunement", "Firestorm", "Deploy Jade Sphere", "Surging Flames", "Flame Wheel", "Relentless Fire", "Molten End", "Triple Sear" ] var loop_rotation: Array = [ "Air Attunement", "Crescent Wind", "Shock Blast", "Deploy Jade Sphere", "Hurricane of Pain", "Water Attunement", "Deploy Jade Sphere", # Whilst letting Hurricane of Pain finish. "Icy Coil", "Shattering Ice", "Cleansing Typhoon", "Crashing Font", "Rain of Blows", "Earth Attunement", "Rocky Loop", # Whilst letting Rain of Blows finish. "Ground Pound", "Conjure Fiery Greatsword", "Firestorm", # Drop Fiery Greatsword "Deploy Jade Sphere", "Whirling Stones", "Fire Attunement", "Deploy Jade Sphere", "Relentless Fire", "Grand Finale", "Surging Flames", "Molten End", "Flame Wheel", "Triple Sear" ] func _ready(): ProjectSettings.load_resource_pack("plugins/gw2-assist.pck", false) api = load("plugins/gw2-assist/api.gd").new() if (api == null): printerr("Unable to find plugin GW2A.") return # Set our logging context so we can see what this script is doing vs GW2A # One can observe both by opening two powershell consoles and running the below to filter out GW2A logs: Get-Content .\gw2cc.log -wait | where { $_ -match “Custom Catalyst Rotation” } # And the below command to see everything else in context: Get-Content .\gw2cc.log -wait api.get_logger().setContext("Custom Catalyst Rotation") if (not api.is_ready()): api.get_logger().fatal("Enable GW2A before running this script.") return if (not api.is_connected("world_state_changed", self, "handle_world_state_changed")): api.connect("world_state_changed", self, "handle_world_state_changed") if (not api.is_connected("in_combat_changed", self, "handle_in_combat_changed")): api.connect("in_combat_changed", self, "handle_in_combat_changed") if (not api.is_connected("skillbar_ready", self, "handle_skillbar_ready")): api.connect("skillbar_ready", self, "handle_skillbar_ready") if (not api.is_connected("selecting_skill", self, "handle_selecting_skill")): api.connect("selecting_skill", self, "handle_selecting_skill") api.set_targeting_mode(api.TargetingModeEnum.values.Nearest) api.dump_skillbar() func handle_world_state_changed(ready: bool) -> void: reset_state() func handle_in_combat_changed(in_combat: bool) -> void: if (in_combat): return api.get_logger().info("Resetting rotation because the player is out of combat.") reset_state() func handle_skillbar_ready(skillbar, standby): if (wait_skillbar_ready and skillbar_is_fiery_greatsword()): api.get_logger().debug("Firestorm is available in the skillbar, we can now continue the rotation.") wait_skillbar_ready = false func handle_selecting_skill(override_skill_select_info: Dictionary) -> void: if (wait_skillbar_ready): override_skill_select_info["command"] = "exit" return if (swap_weapons): if (not api.get_character_cooldowns().has(FirestormSkillId)): # We'll wait for Firestorm to go on cooldown before dropping the greatsword here. # Additionally, the skill might misfire so we'll enfroce another cast here... override_skill_select_info["slot_id"] = api.Gw2aSkillbarSlot.Weapon5 return swap_weapons = false api.get_logger().debug("Swapping weapons.") override_skill_select_info["command"] = "swap" return if (last_skill_slot_cast != 0 and last_skill_slot_cast != api.Gw2aSkillbarSlot.Weapon3): # We do one last check here to ensure the previously cast skill has indeed been cast. # if (api.get_skill_status(last_skill_slot_cast) != api.Gw2aSkillStatus.Cooldown and api.get_skill_status(last_skill_slot_cast) != api.Gw2aSkillStatus.DisabledByMechanic): # api.get_logger().debug("Trying to cast the previous skill again because it did not go on cooldown.") # override_skill_select_info["slot_id"] = last_skill_slot_cast # last_skill_slot_cast = 0 # return # Or we could just brute force a second cast for good measure... override_skill_select_info["slot_id"] = last_skill_slot_cast last_skill_slot_cast = 0 return var skillName: String = loop_rotation[loop_step] if (opener_complete) else opening_rotation[opener_step] # Because we have a duplicate skill name in the skillbar (Greatsword Firestorm and utility Firestorm Glyph), # we need to override the skill slot to the one we actually want if it is present in the skillbar. var skillSlot: int if (skillName == "Firestorm" and skillbar_is_fiery_greatsword()): api.get_logger().debug("Going to override skill slot for Firestorm to weapon 5 because Fiery Greatsword is active.") skillSlot = api.Gw2aSkillbarSlot.Weapon5 else: skillSlot = api.find_skill_by_name(skillName) if (skillSlot == api.Gw2aSkillbarSlot.NotSpecified): # NOTE: We might have an error in our rotation so lets log it and advance the rotation so we can view the results to fix it later. api.get_logger().warn("Could not find skill \"%s\". Skipping this step..." % [ skillName ]) advance_rotation_step() override_skill_select_info["command"] = "exit" return var skillStatus: int = api.get_skill_status(skillSlot) if (skillStatus == api.Gw2aSkillStatus.DisabledByMechanic): # We allow the rotation to continue here to demonstrate how this can be accomplished. api.get_logger().warn("Skill \"%s\" is disabled by a game mechanic, probably because it is an attunement and is already selected. Skipping this step." % [ skillName ]) advance_rotation_step() override_skill_select_info["command"] = "exit" return if (skillStatus != api.Gw2aSkillStatus.Ready): # Uncomment the below code block to stall the rotation. # if (not rotation_stall_warning_issued): # rotation_stall_warning_issued = true # api.get_logger().warn("Rotation stalled because skill \"%s\" status is %s." % [ skillName, str(api.Gw2aSkillStatus.keys()[api.Gw2aSkillStatus.values().find(skillStatus)]) ]) # override_skill_select_info["command"] = "exit" # return # Instead of stalling the rotation using the above code, we will allow it to continue... api.get_logger().warn("Skill \"%s\" is on cooldown. Skipping this step." % [ skillName ]) advance_rotation_step() override_skill_select_info["command"] = "exit" return rotation_stall_warning_issued = false match (skillName): "Conjure Fiery Greatsword": api.get_logger().debug("Waiting for a skillbar update.") wait_skillbar_ready = true "Firestorm": if (skillbar_is_fiery_greatsword()): api.get_logger().debug("Going to swap weapons on the next iteration.") swap_weapons = true api.get_logger().debug("%s step %d of %d is casting \"%s\" in slot %d." % [ "Loop" if (opener_complete) else "Opener", (loop_step if (opener_complete) else opener_step) + 1, loop_rotation.size() if (opener_complete) else opening_rotation.size(), skillName, skillSlot ]) override_skill_select_info["slot_id"] = skillSlot last_skill_slot_cast = skillSlot advance_rotation_step() # For debugging, we can stop the rotation when we're at the end of it. # if (loop_complete): # api.get_logger().debug("Loop rotation ended, stopping combat assist..") # api.set_combat_state(false) # reset_state() func advance_rotation_step(): api.get_logger().debug("Advancing rotation step...") if (opener_complete): loop_step += 1 if (loop_step >= loop_rotation.size()): loop_complete = true loop_step = 0 else: opener_step += 1 if (opener_step >= opening_rotation.size()): opener_complete = true opener_step = 0 func skillbar_is_fiery_greatsword() -> bool: var skillbar = api.get_skillbar() return skillbar[api.Gw2aSkillbarSlot.Weapon5]["id"] == FirestormSkillId if (skillbar.has(api.Gw2aSkillbarSlot.Weapon5)) else false func reset_state(): opener_complete = false loop_complete = false wait_skillbar_ready = false swap_weapons = false opener_step = 0 loop_step = 0 last_skill_slot_cast = 0 rotation_stall_warning_issued = false