def add_aura(self, aura): # Note: This order of applying, removing colliding and then returning might be problematic if cases are added to can_apply_aura. # At the moment mount behaviour depends on this order can_apply = self.can_apply_aura(aura) self.remove_colliding_effects(aura) if not can_apply: return aura.initialize_period_timestamps( ) # Initialize periodic spell timestamps on application AuraEffectHandler.handle_aura_effect_change(aura) aura.index = self.get_next_aura_index(aura) self.active_auras[aura.index] = aura if not aura.passive: self.write_aura_to_unit(aura) self.write_aura_flag_to_unit(aura) self.send_aura_duration(aura) # Aura application threat TODO handle threat elsewhere if aura.is_harmful() and aura.source_spell.generates_threat(): self.unit_mgr.attack(aura.caster) self.unit_mgr.set_dirty()
def check_aura_procs(self, involved_cast=None, killed_unit=False, damage_info=None, is_melee_swing=False): is_receiver = (damage_info and damage_info.target is self.unit_mgr) or \ (involved_cast and involved_cast.spell_caster is not self.unit_mgr) # Always pass the second unit as the effect target. The handler will choose the target based on the spell. if damage_info: effect_target = damage_info.attacker if is_receiver else damage_info.target elif involved_cast: # All targets for the spell could be passed, but this would only matter for ProcFlags.SPELL_CAST # SPELL_CAST is only used by one deprecated spell which will have the correct target in initial_target. effect_target = involved_cast.spell_caster if is_receiver else involved_cast.initial_target else: effect_target = self.unit_mgr flag_cases = { ProcFlags.DEAL_COMBAT_DMG: not is_receiver and damage_info and damage_info.total_damage > 0, # -> cast on target. ProcFlags.TAKE_COMBAT_DMG: is_receiver and damage_info and damage_info.total_damage > 0, ProcFlags.KILL: killed_unit, ProcFlags.HEARTBEAT: False, # Heartbeat effects are handled in their respective places on update - ignore the flag here. ProcFlags.DODGE: is_receiver and damage_info and damage_info.proc_victim & ProcFlags.DODGE, ProcFlags.PARRY: is_receiver and damage_info and damage_info.proc_victim & ProcFlags.PARRY, ProcFlags.BLOCK: is_receiver and damage_info and damage_info.proc_victim & ProcFlags.BLOCK, ProcFlags.SWING: not is_receiver and is_melee_swing, ProcFlags.SPELL_CAST: not is_receiver and involved_cast, # Only used by zzOLDMind Bomb. ProcFlags.SPELL_HIT: is_receiver and involved_cast, } for aura in list(self.active_auras.values()): flags = aura.source_spell.spell_entry.ProcFlags if not flags: continue for proc_flag, condition in flag_cases.items(): if proc_flag & flags and condition and aura.proc_charges != 0: # Proc charges are set to -1 for auras with no charges so check for 0. # Remove charge before trigger to avoid infinite loops with procs. aura.proc_charges -= 1 AuraEffectHandler.handle_aura_effect_change(aura, effect_target, is_proc=True) if aura.proc_charges == 0: self.remove_aura(aura)
def update(self, elapsed): if self.has_duration(): self.duration -= int(elapsed * 1000) if not self.target: # Auras that are only tied to effects - ie. persistent area auras return if self.is_periodic(): AuraEffectHandler.handle_aura_effect_change(self)
def update(self, timestamp): if self.has_duration(): self.spell_effect.update_effect_aura(timestamp) if self.is_periodic(): AuraEffectHandler.handle_aura_effect_change(self, self.target) if self.source_spell.cast_state != SpellState.SPELL_STATE_ACTIVE: self.spell_effect.remove_old_periodic_effect_ticks()
def remove_aura(self, aura): # TODO check if aura can be removed (by player) AuraEffectHandler.handle_aura_effect_change(aura, True) self.active_auras.pop(aura.index) if aura.passive: return # Passive auras aren't written to unit self.write_aura_to_unit(aura, clear=True) self.write_aura_flag_to_unit(aura, clear=True) self.unit_mgr.set_dirty()
def remove_colliding_effects(self, aura): # Special case with SpellEffect mounting and mounting by aura if aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOUNTED and \ aura.target.unit_flags & UnitFlags.UNIT_MASK_MOUNTED and not \ self.get_auras_by_type(AuraTypes.SPELL_AURA_MOUNTED): AuraEffectHandler.handle_mounted( aura, aura.target, remove=True) # Remove mount effect # If a mount aura would be applied but we dismount the unit, don't apply the new mount aura. return False aura_spell_template = aura.source_spell.spell_entry new_aura_name = aura_spell_template.Name_enUS new_aura_rank = DbcDatabaseManager.SpellHolder.spell_get_rank_by_spell( aura_spell_template) aura_effect_index = aura.spell_effect.effect_index caster_guid = aura.caster.guid for applied_aura in list(self.active_auras.values()): applied_spell_entry = applied_aura.source_spell.spell_entry applied_aura_name = applied_spell_entry.Name_enUS applied_aura_rank = DbcDatabaseManager.SpellHolder.spell_get_rank_by_spell( applied_spell_entry) # TODO Same effects but different spells (exclusivity groups)? # Note: This method ignores the case of a weaker spell being applied, as that is handled in can_apply_aura. is_similar_and_weaker = applied_aura.spell_effect.effect_index == aura_effect_index and \ applied_aura_name == new_aura_name and applied_aura_rank < new_aura_rank are_exclusive_by_source = ExtendedSpellData.AuraSourceRestrictions.are_colliding_auras( aura.spell_id, applied_aura.spell_id) # Paladin seals, warlock curses # Source doesn't matter for unique auras. is_unique = applied_aura.source_spell.spell_entry.AttributesEx & SpellAttributesEx.SPELL_ATTR_EX_AURA_UNIQUE or not aura.harmful # Buffs are unique. is_stacking = applied_aura.can_stack is_same_but_different_aura_index = aura.spell_id == applied_aura.spell_id and aura.spell_effect.effect_index != applied_aura.spell_effect.effect_index casters_are_same = applied_aura.caster.guid == caster_guid if is_similar_and_weaker and (is_unique or casters_are_same and not is_stacking) or \ are_exclusive_by_source and casters_are_same and not is_same_but_different_aura_index: self.remove_aura(applied_aura) continue if applied_aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOD_SHAPESHIFT and \ aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOD_SHAPESHIFT: self.remove_aura( applied_aura) # Player can only be in one shapeshift form. continue return True
def update(self, timestamp): if self.has_duration(): self.spell_effect.update_effect_aura(timestamp) if self.is_periodic(): AuraEffectHandler.handle_aura_effect_change(self, self.target) # Don't remove periodic ticks for channeled spells. # Channeled spells have special handling for applied auras because of targeting reasons. # See SpellManager::handle_spell_effect_update for more information. if self.source_spell.cast_state != SpellState.SPELL_STATE_ACTIVE: self.spell_effect.remove_old_periodic_effect_ticks()
def add_aura(self, aura): can_apply = self.can_apply_aura( aura) and self.remove_colliding_effects(aura) if not can_apply: return # Application threat and negative aura application interrupts. if aura.harmful: if aura.caster.object_type_mask & ObjectTypeFlags.TYPE_UNIT and \ aura.source_spell.generates_threat(): # TODO Replace once threat is handled properly. self.unit_mgr.attack(aura.caster) self.check_aura_interrupts(negative_aura_applied=True) applied_similar_auras = self.get_similar_applied_auras( aura, accept_all_ranks=False, accept_all_sources=False) is_refresh = len(applied_similar_auras) > 0 if is_refresh > 0: # Only one similar aura from the same source can be applied. # Lower ranks are removed by remove_colliding_effects. similar_aura = applied_similar_auras[0] if aura.can_stack and similar_aura.applied_stacks < similar_aura.max_stacks: similar_aura.applied_stacks += 1 # Add a stack if the aura isn't at max already similar_aura.spell_effect.start_aura_duration( overwrite=True) # Refresh duration # Note that this aura will not be actually applied. # Index and stacks are copied for sending information and updating effect points. aura.applied_stacks = similar_aura.applied_stacks aura.index = similar_aura.index else: aura.index = self.get_next_aura_index(aura) self.active_auras[aura.index] = aura # Handle effects after possible stack increase/refresh to update stats properly. AuraEffectHandler.handle_aura_effect_change(aura, aura.target) # TODO Some aura applications appear twice in the combat log. # For example, the proc effect from frost armor appears twice. # Gouge seems to appear twice against players (tested in duels), but not against NPCs. if not aura.passive: if not is_refresh: self.write_aura_to_unit(aura) self.write_aura_flag_to_unit(aura) self.send_aura_duration(aura)
def remove_aura(self, aura, canceled=False): AuraEffectHandler.handle_aura_effect_change(aura, aura.target, remove=True) if not self.active_auras.pop(aura.index, None): return # Some area effect auras (paladin auras, tranq etc.) are tied to spell effects. Cancel cast on aura cancel, canceling the auras as well. self.unit_mgr.spell_manager.remove_cast(aura.source_spell, interrupted=canceled) # Some spells start cooldown on aura remove, handle that case here. if aura.source_spell.trigger_cooldown_on_aura_remove(): self.unit_mgr.spell_manager.set_on_cooldown( aura.source_spell, start_locked_cooldown=True) self.write_aura_to_unit(aura, clear=True)
def remove_colliding_effects(self, aura): # Special case with SpellEffect mounting and mounting by aura if aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOUNTED and \ aura.target.unit_flags & UnitFlags.UNIT_MASK_MOUNTED and not \ self.get_auras_by_type(AuraTypes.SPELL_AURA_MOUNTED): AuraEffectHandler.handle_mounted(aura, True) # Remove mount effect aura_spell_template = aura.source_spell.spell_entry aura_effect_index = aura.spell_effect.effect_index caster_guid = aura.caster.guid for applied_aura in list(self.active_auras.values()): if applied_aura.caster.guid != caster_guid or \ applied_aura.spell_effect.effect_index != aura_effect_index or \ applied_aura.source_spell.spell_entry != aura_spell_template: continue self.remove_aura( applied_aura) # Remove identical auras the caster has applied
def add_aura(self, aura): can_apply = self.can_apply_aura( aura) and self.remove_colliding_effects(aura) if not can_apply: return # Application threat and negative aura application interrupts. if aura.harmful: # Add threat for non-player targets against unit casters. if aura.caster.object_type_mask & ObjectTypeFlags.TYPE_UNIT and \ self.unit_mgr.get_type_id() == ObjectTypeIds.ID_UNIT and aura.source_spell.generates_threat(): # TODO: Threat calculation. self.unit_mgr.threat_manager.add_threat(aura.caster, 10) self.check_aura_interrupts(negative_aura_applied=True) applied_similar_auras = self.get_similar_applied_auras( aura, accept_all_ranks=False, accept_all_sources=False) is_refresh = len(applied_similar_auras) > 0 if is_refresh > 0: # Only one similar aura from the same source can be applied. # Lower ranks are removed by remove_colliding_effects. similar_aura = applied_similar_auras[0] if aura.can_stack and similar_aura.applied_stacks < similar_aura.max_stacks: similar_aura.applied_stacks += 1 # Add a stack if the aura isn't at max already similar_aura.spell_effect.start_aura_duration( overwrite=True) # Refresh duration # Note that this aura will not be actually applied. # Index and stacks are copied for sending information and updating effect points. aura.applied_stacks = similar_aura.applied_stacks aura.index = similar_aura.index else: aura.index = self.get_next_aura_index(aura) self.active_auras[aura.index] = aura # Handle effects after possible stack increase/refresh to update stats properly. AuraEffectHandler.handle_aura_effect_change(aura, aura.target) self.write_aura_to_unit(aura, is_refresh=is_refresh)
def remove_colliding_effects(self, aura): # Special case with SpellEffect mounting and mounting by aura if aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOUNTED and \ aura.target.unit_flags & UnitFlags.UNIT_MASK_MOUNTED and not \ self.get_auras_by_type(AuraTypes.SPELL_AURA_MOUNTED): AuraEffectHandler.handle_mounted( aura, aura.target, remove=True) # Remove mount effect aura_spell_template = aura.source_spell.spell_entry aura_effect_index = aura.spell_effect.effect_index caster_guid = aura.caster.guid for applied_aura in list(self.active_auras.values()): # TODO is_similar does not match different spell ranks is_similar = applied_aura.source_spell.spell_entry == aura_spell_template and \ applied_aura.spell_effect.effect_index == aura_effect_index # Spell and effect are the same are_exclusive_by_source = ExtendedSpellData.AuraSourceRestrictions.are_colliding_auras( aura.spell_id, applied_aura.spell_id) # Paladin seals, warlock curses # Source doesn't matter for unique auras is_unique = applied_aura.source_spell.spell_entry.AttributesEx & SpellAttributesEx.SPELL_ATTR_EX_AURA_UNIQUE or not aura.harmful # Buffs are unique. is_stacking = applied_aura.can_stack casters_are_same = applied_aura.caster.guid == caster_guid if is_similar and (is_unique or casters_are_same and not is_stacking) or \ are_exclusive_by_source and casters_are_same: self.remove_aura(applied_aura) continue if applied_aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOD_SHAPESHIFT and \ aura.spell_effect.aura_type == AuraTypes.SPELL_AURA_MOD_SHAPESHIFT: self.remove_aura( applied_aura) # Player can only be in one shapeshift form continue
aura.spell_id)) > 0: if existing_auras[0].applied_stacks < existing_auras[0].max_stacks: existing_auras[ 0].applied_stacks += 1 # Add a stack if the aura isn't at max already existing_auras[0].spell_effect.start_aura_duration( overwrite=True) # Refresh duration # Note that this aura will not be actually applied. Index and stacks are copied for sending information and updating effect points. aura.applied_stacks = existing_auras[0].applied_stacks aura.index = existing_auras[0].index else: aura.index = self.get_next_aura_index(aura) self.active_auras[aura.index] = aura # Handle effects after possible stack increase to update stats properly AuraEffectHandler.handle_aura_effect_change(aura, aura.target) if not aura.passive: self.write_aura_to_unit(aura) self.write_aura_flag_to_unit(aura) self.send_aura_duration(aura) # Aura application threat TODO handle threat elsewhere if aura.harmful: if aura.source_spell.generates_threat(): self.unit_mgr.attack(aura.caster) self.check_aura_interrupts(negative_aura_applied=True) self.unit_mgr.set_dirty() has_moved = False # Set from SpellManager - TODO pass movement info from unit update instead