Skip to content
Merged

Minor. #1619

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
2 changes: 1 addition & 1 deletion game/world/managers/objects/item/ItemManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ def get_item_query_packets(item_templates: List[ItemTemplate]) -> List[bytes]:
query_data.extend(item_bytes)
written_items += 1

packet = pack(f'<I{len(query_data)}s', written_items, bytes(query_data))
packet = pack(f'<B{len(query_data)}s', written_items, bytes(query_data))
packets.append(PacketWriter.get_packet(OpCode.SMSG_ITEM_QUERY_MULTIPLE_RESPONSE, packet))
query_data.clear()
written_items = 0
Expand Down
7 changes: 7 additions & 0 deletions game/world/managers/objects/spell/CastingSpell.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ def get_damage_school_mask(self):
school_mask = 1 << damage_school
return school_mask

def can_reflect(self):
return (self.spell_entry.School # Not physical.
and not self.spell_entry.Attributes & SpellAttributes.SPELL_ATTR_IS_ABILITY
and not self.spell_entry.Attributes & SpellAttributes.SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY
and not self.spell_entry.Attributes & SpellAttributes.SPELL_ATTR_PASSIVE
and not self.spell_entry.AttributesEx & SpellAttributesEx.SPELL_ATTR_EX_NEGATIVE)

def get_ammo_for_cast(self) -> Optional[ItemManager]:
if not self.is_ranged_weapon_attack():
return None
Expand Down
17 changes: 15 additions & 2 deletions game/world/managers/objects/spell/SpellManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,15 +497,20 @@ def apply_spell_effects(self, casting_spell: CastingSpell, remove=False, update=

spell_target = target
spell_caster = casting_spell.spell_caster
# Swap target/caster on reflect. TODO actual reflect handling - this flag is never set.
# Swap target/caster on reflect.
# TODO: Proper handling of reflect if possible, we don't have MISS_REASON_REFLECTED from vanilla.
# Reflect is not handled as a miss in alpha, instead, its probably handled through SMSG_SPELL_GO or
# SMSG_ATTACKERSTATEUPDATEDEBUGINFOSPELL.
# So for now, the damage will be reflected but combat log will display as if the reflector
# casted the spell and the victim states will be wrong.
if info.flags & SpellHitFlags.REFLECTED:
spell_target = casting_spell.spell_caster
spell_caster = target

if info.result == SpellMissReason.MISS_REASON_NONE:
SpellEffectHandler.apply_effect(casting_spell, effect, spell_caster, spell_target)
elif target.is_unit() and casting_spell.generates_threat_on_miss() and \
casting_spell.spell_caster.can_attack_target(target): # Add threat for failed hostile casts.
casting_spell.spell_caster.can_attack_target(target): # Add threat for failed hostile casts.
target.threat_manager.add_threat(casting_spell.spell_caster)

if len(object_targets) > 0:
Expand Down Expand Up @@ -1815,6 +1820,14 @@ def send_cast_result(self, casting_spell, error, misc_data=-1):
is_player = self.caster.is_player()
spell_id = casting_spell.spell_entry.ID

# Item casts which set the spell as modal (Spell_C_SetModal) will always display the cast result as failed
# if the spell id is provided.
# if (msg == * (CDataStore **)s_modalSpellID) (msg is spell id, s_modalSpellID is set upon spell cast)
# Spell_C_CancelSpell(0, 0, a1, SPELL_FAILED_ERROR);
# This fixes item casts like 5810 (Fresh Carcass) and 5867 (Etched Phial).
if casting_spell.source_item and not casting_spell.is_instant_cast():
spell_id = 0

if casting_spell.hide_result:
error = SpellCheckCastResult.SPELL_FAILED_DONT_REPORT

Expand Down
35 changes: 29 additions & 6 deletions game/world/managers/objects/spell/aura/AuraEffectHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,32 @@ def handle_mod_threat(aura, effect_target, remove):
amount = aura.get_effect_points()
effect_target.stat_manager.apply_aura_stat_bonus(aura.index, UnitStats.THREAT_GENERATION, amount, percentual=True)

@staticmethod
def handle_reflect_spells(aura, effect_target, remove):
if remove:
effect_target.stat_manager.remove_aura_stat_bonus(aura.index, percentual=False)
return
amount = aura.get_effect_points() / 100
effect_target.stat_manager.apply_aura_stat_bonus(aura.index, UnitStats.SPELL_REFLECT, amount, percentual=False)

@staticmethod
def handle_reflect_spells_school(aura, effect_target, remove):
if remove:
effect_target.stat_manager.remove_aura_stat_bonus(aura.index, percentual=False)
return
amount = aura.get_effect_points() / 100
misc_value = aura.spell_effect.misc_value # Spell school

if misc_value == -2:
spell_school = SpellSchoolMask.SPELL_SCHOOL_MASK_ALL
elif misc_value == -1:
spell_school = SpellSchoolMask.SPELL_SCHOOL_MASK_SPELL
else:
spell_school = 1 << misc_value

effect_target.stat_manager.apply_aura_stat_bonus(aura.index, UnitStats.SPELL_REFLECT_SCHOOL, amount,
percentual=False, misc_value=spell_school)


AURA_EFFECTS = {
AuraTypes.SPELL_AURA_BIND_SIGHT: AuraEffectHandler.handle_bind_sight,
Expand Down Expand Up @@ -843,7 +869,6 @@ def handle_mod_threat(aura, effect_target, remove):
AuraTypes.SPELL_AURA_MOD_FEAR: AuraEffectHandler.handle_mod_fear,
AuraTypes.SPELL_AURA_MOD_CONFUSE: AuraEffectHandler.handle_mod_confuse,


# Immunity modifiers.
AuraTypes.SPELL_AURA_EFFECT_IMMUNITY: AuraEffectHandler.handle_effect_immunity,
AuraTypes.SPELL_AURA_STATE_IMMUNITY: AuraEffectHandler.handle_state_immunity,
Expand Down Expand Up @@ -873,18 +898,16 @@ def handle_mod_threat(aura, effect_target, remove):
AuraTypes.SPELL_AURA_MOD_INCREASE_SWIM_SPEED: AuraEffectHandler.handle_increase_swim_speed,
AuraTypes.SPELL_AURA_MOD_ATTACKSPEED: AuraEffectHandler.handle_mod_attack_speed,
AuraTypes.SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: AuraEffectHandler.handle_mod_casting_speed,

AuraTypes.SPELL_AURA_MOD_HIT_CHANCE: AuraEffectHandler.handle_mod_hit_chance,
AuraTypes.SPELL_AURA_MOD_PARRY_PERCENT: AuraEffectHandler.handle_mod_parry_chance,
AuraTypes.SPELL_AURA_MOD_DODGE_PERCENT: AuraEffectHandler.handle_mod_dodge_chance,
AuraTypes.SPELL_AURA_MOD_BLOCK_PERCENT: AuraEffectHandler.handle_mod_block_chance,

AuraTypes.SPELL_AURA_MOD_DAMAGE_TAKEN: AuraEffectHandler.handle_mod_damage_taken,

AuraTypes.SPELL_AURA_MOD_DAMAGE_DONE: AuraEffectHandler.handle_mod_damage_done,
AuraTypes.SPELL_AURA_MOD_DAMAGE_DONE_CREATURE: AuraEffectHandler.handle_mod_damage_done_creature,

AuraTypes.SPELL_AURA_MOD_THREAT: AuraEffectHandler.handle_mod_threat
AuraTypes.SPELL_AURA_MOD_THREAT: AuraEffectHandler.handle_mod_threat,
AuraTypes.SPELL_AURA_REFLECT_SPELLS: AuraEffectHandler.handle_reflect_spells,
AuraTypes.SPELL_AURA_REFLECT_SPELLS_SCHOOL: AuraEffectHandler.handle_reflect_spells_school
}

PROC_AURA_EFFECTS = [
Expand Down
4 changes: 1 addition & 3 deletions game/world/managers/objects/units/DamageInfoHolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ def __init__(self,
# the right packet structure.
def get_damage_done_packet(self):
flags = WorldTextFlags.NORMAL_DAMAGE

if self.hit_info & SpellHitFlags.CRIT:
flags |= WorldTextFlags.CRIT
if self.hit_info & SpellHitFlags.REFLECTED:
flags &= ~(WorldTextFlags.NORMAL_DAMAGE | WorldTextFlags.CRIT)
flags |= WorldTextFlags.MISS_ABSORBED
if self.absorb:
flags |= WorldTextFlags.MISS_ABSORBED

Expand Down
4 changes: 4 additions & 0 deletions game/world/managers/objects/units/UnitManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,10 @@ def calculate_spell_damage(self, base_damage, miss_reason, hit_flags, spell_effe
damage_info.absorb = target.get_school_absorb_for_damage(damage_info)
damage_info.total_damage = max(0, damage_info.base_damage - damage_info.absorb)

# TODO: We never reach this even when set over get_spell_miss_result_against_self.
if damage_info.hit_info & SpellHitFlags.REFLECTED:
damage_info.proc_ex = ProcFlagsExLegacy.REFLECT

# Target will die because of this attack.
if target.health - damage_info.total_damage <= 0:
damage_info.hit_info |= HitInfo.UNIT_DEAD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def send_inventory_list(creature_mgr, player_mgr):
item_template = WorldDatabaseManager.ItemTemplateHolder.item_template_get_by_entry(item_entry.item)
if item_template:
items_data.extend(pack(
'<7I',
'<3i4I',
count + 1, # m_muid, acts as slot counter.
item_template.entry,
item_template.display_id,
Expand All @@ -58,14 +58,14 @@ def send_inventory_list(creature_mgr, player_mgr):
item_template.buy_count # Stack count.
))
item_templates.append(item_template)
else:
Logger.warning(f'Vendor {creature_mgr.get_name()} has invalid item with entry {item_entry.item}')

item_count = len(item_templates)
if item_count == 0:
items_data.extend(pack('<B', 0))
else:
items_data.extend(items_data)

data = pack(f'<QB{len(items_data)}s', creature_mgr.guid, item_count, bytes(items_data))
data = pack(f'<QB{len(items_data)}s', creature_mgr.guid, item_count, items_data)

# Send all vendor item query details.
if item_count > 0:
Expand Down
13 changes: 13 additions & 0 deletions game/world/managers/objects/units/player/StatManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class UnitStats(IntFlag):
SPELL_SCHOOL_CRITICAL = auto()
SPELL_SCHOOL_POWER_COST = auto()
SPELL_CASTING_SPEED = auto()
SPELL_REFLECT = auto()
SPELL_REFLECT_SCHOOL = auto()

DAMAGE_DONE = auto()
DAMAGE_DONE_SCHOOL = auto()
Expand Down Expand Up @@ -1003,6 +1005,17 @@ def get_spell_miss_result_against_self(self, casting_spell) -> Tuple[SpellMissRe
self.unit_mgr.has_damage_immunity(spell_school, casting_spell=casting_spell):
return SpellMissReason.MISS_REASON_IMMUNE, hit_flags

# Reflect.
if casting_spell.can_reflect() and caster.can_attack_target(self.unit_mgr):
# Base reflect.
reflect_chance = self.unit_mgr.stat_manager.get_total_stat(UnitStats.SPELL_REFLECT, accept_float=True)
# School reflect.
reflect_chance += self.unit_mgr.stat_manager.get_total_stat(
UnitStats.SPELL_REFLECT_SCHOOL, 1 << spell_school, misc_value_is_mask=True, accept_float=True)
if reflect_chance and random.random() < reflect_chance:
hit_flags |= SpellHitFlags.REFLECTED
return SpellMissReason.MISS_REASON_NONE, hit_flags

is_base_attack_spell = casting_spell.is_weapon_attack()

attack_type = casting_spell.get_attack_type() if is_base_attack_spell else -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,15 +705,6 @@ def send_quest_query_response(self, quest):
name_bytes
))

# Objective texts.
req_objective_text_list = QuestHelpers.generate_objective_text_list(quest)
for index, objective_text in enumerate(req_objective_text_list):
req_objective_text_bytes = PacketWriter.string_to_bytes(req_objective_text_list[index])
data.extend(pack(
f'{len(req_objective_text_bytes)}s',
req_objective_text_bytes
))

self.player_mgr.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_QUEST_QUERY_RESPONSE, data))

def send_quest_giver_request_items(self, quest, quest_giver_id, close_on_cancel):
Expand Down