def load(self): filename = f'{self.cell_map:03}{self.cell_x:02}{self.cell_y:02}.map' maps_path = PathManager.get_map_file_path(filename) Logger.debug(f'[Maps] Loading map file: {filename}') if not path.exists(maps_path): Logger.warning(f'Unable to locate map file: {filename}') else: with open(maps_path, "rb") as map_tiles: version = PacketReader.read_string(map_tiles.read(10), 0) if version != MapTile.EXPECTED_VERSION: Logger.error(f'Unexpected map version. Expected "{MapTile.EXPECTED_VERSION}", received "{version}".') return # TODO: AreaFlags # for x in range(0, RESOLUTION_FLAGS + 1): # for y in range(0, RESOLUTION_FLAGS + 1): # self.explore_flag[x][y] = unpack('<H', map_tiles.read(2))[0] # # TODO: AreaTerrain # for x in range(0, RESOLUTION_TERRAIN + 1): # for y in range(0, RESOLUTION_TERRAIN + 1): # self.area_terrain[x][y] = map_tiles.read(1)[0] # # TODO: Liquids # for x in range(0, RESOLUTION_WATER + 1): # for y in range(0, RESOLUTION_WATER + 1): # self.water_level[x][y] = unpack('<f', map_tiles.read(4))[0] # Height Map for x in range(0, RESOLUTION_ZMAP + 1): for y in range(0, RESOLUTION_ZMAP + 1): self.z_coords[x][y] = unpack('<f', map_tiles.read(4))[0]
def set_on_cooldown(self, casting_spell, start_locked_cooldown=False): spell = casting_spell.spell_entry if spell.RecoveryTime == 0 and spell.CategoryRecoveryTime == 0: return timestamp = time.time() if start_locked_cooldown: data = pack('<IQ', spell.ID, self.unit_mgr.guid) self.unit_mgr.enqueue_packet( PacketWriter.get_packet(OpCode.SMSG_COOLDOWN_EVENT, data)) for cooldown in self.cooldowns: if cooldown.spell_id == spell.ID: cooldown.unlock(timestamp) return Logger.warning( f'[SpellManager]: Attempted to unlock cooldown for spell {spell.ID}, but the cooldown didn\'t exist.' ) cooldown_entry = CooldownEntry( spell, timestamp, casting_spell.trigger_cooldown_on_aura_remove()) self.cooldowns.append(cooldown_entry) if self.unit_mgr.get_type() != ObjectTypes.TYPE_PLAYER: return data = pack('<IQI', spell.ID, self.unit_mgr.guid, cooldown_entry.cooldown_length) self.unit_mgr.enqueue_packet( PacketWriter.get_packet(OpCode.SMSG_SPELL_COOLDOWN, data))
def handle_mod_base_resistance(aura, effect_target, remove): # This handler is a slight exception to the usual handling of aura stat modifiers. # Only this effect modifies base stats and is used by the Toughness talent which increases armor. # Since this is a slight exception, handle this case more specifically by changing *one* base stat of the unit. amount = aura.get_effect_points() if aura.spell_effect.misc_value == -1: # stat_type = UnitStats.ALL_RESISTANCES stat_type = UnitStats.RESISTANCE_PHYSICAL # This case shouldn't exist with an unmodified database. Logger.warning( "[AuraEffectHandler]: Unsupported behaviour in handle_mod_base_resistance." ) else: stat_type = UnitStats.RESISTANCE_START << aura.spell_effect.misc_value base_stat = effect_target.stat_manager.get_base_stat(stat_type) if remove: new_value = max(base_stat - amount, 0) # Avoid <0, though it should never occur. else: new_value = base_stat + amount effect_target.stat_manager.base_stats[stat_type] = new_value
def process_incoming(self): while self.keep_alive: reader = self.incoming_pending.get(block=True, timeout=None) if reader: # Can be None if we shutdown the thread. if reader.opcode: handler, found = Definitions.get_handler_from_packet( self, reader.opcode) if handler: res = handler(self, self.request, reader) if res == 0: Logger.debug( f'[{self.client_address[0]}] Handling {OpCode(reader.opcode).name}' ) elif res == 1: Logger.debug( f'[{self.client_address[0]}] Ignoring {OpCode(reader.opcode).name}' ) elif res < 0: self.disconnect() break elif not found: Logger.warning( f'[{self.client_address[0]}] Received unknown data: {reader.data}' ) else: self.disconnect()
def find_loot_by_loot_id(loot_id): loot = WorldDatabaseManager.CreatureLootTemplateHolder.creature_loot_template_get_by_creature( loot_id) if loot: return loot loot = WorldDatabaseManager.PickPocketingLootTemplateHolder.pickpocketing_loot_template_get_by_entry( loot_id) if loot: return loot loot = WorldDatabaseManager.ReferenceLootTemplateHolder.reference_loot_template_get_by_entry( loot_id) if loot: return loot loot = WorldDatabaseManager.FishingLootTemplateHolder.fishing_loot_template_get_by_entry( loot_id) if loot: return loot loot = WorldDatabaseManager.GameObjectLootTemplateHolder.gameobject_loot_template_get_by_entry( loot_id) if loot: return loot loot = WorldDatabaseManager.ItemLootTemplateHolder.item_loot_template_get_by_entry( loot_id) if loot: return loot if not loot: Logger.warning( f'Unable to locate referenced loot for id {loot_id}.') return None
def _check_tile_load(map_id, location_x, location_y, map_tile_x, map_tile_y): if not config.Server.Settings.use_map_tiles: return False # Check if the map is valid first. if map_id not in MAPS: Logger.warning(f'Wrong map, {map_id} not found.') return False tile = MAPS[map_id].tiles[map_tile_x][map_tile_y] # Tile exists and has been initialized, return if it's already valid (finished loading) or not. if tile is not None and tile.initialized: return tile.is_valid # Tile does not exist, try to load it. if tile is None: MapManager.load_map_tiles(map_id, location_x, location_y) # Grab the tile again. tile = MAPS[map_id].tiles[map_tile_x][map_tile_y] # Tile exist, its initialized and has loaded its internal data. return tile is not None and tile.initialized and tile.is_valid
def load(self): # Set as initialized to avoid another load() call from another thread. self.initialized = True filename = f'{self.cell_map:03}{self.cell_x:02}{self.cell_y:02}.map' maps_path = PathManager.get_map_file_path(filename) Logger.debug( f'[Maps] Loading map file: {filename}, Map:{self.cell_map} Tile:{self.cell_x},{self.cell_y}' ) if not path.exists(maps_path): Logger.warning( f'Unable to locate map file: {filename}, Map:{self.cell_map} Tile:{self.cell_x},{self.cell_y}' ) return else: with open(maps_path, "rb") as map_tiles: version = PacketReader.read_string(map_tiles.read(10), 0) if version != MapTile.EXPECTED_VERSION: Logger.error( f'Unexpected map version. Expected "{MapTile.EXPECTED_VERSION}", found "{version}".' ) return # Height Map for x in range(RESOLUTION_ZMAP): for y in range(RESOLUTION_ZMAP): self.z_height_map[x][y] = unpack( '<f', map_tiles.read(4))[0] # ZoneID, AreaNumber, AreaFlags, AreaLevel, AreaExploreFlag(Bit), AreaFactionMask for x in range(RESOLUTION_AREA_INFO): for y in range(RESOLUTION_AREA_INFO): zone_id = unpack('<i', map_tiles.read(4))[0] if zone_id == -1: # No area information. continue area_number = unpack('<I', map_tiles.read(4))[0] area_flags = unpack('<B', map_tiles.read(1))[0] area_level = unpack('<B', map_tiles.read(1))[0] area_explore_bit = unpack('<H', map_tiles.read(2))[0] area_faction_mask = unpack('<B', map_tiles.read(1))[0] # noinspection PyTypeChecker self.area_information[x][y] = AreaInformation( zone_id, area_number, area_flags, area_level, area_explore_bit, area_faction_mask) # Liquids for x in range(RESOLUTION_LIQUIDS): for y in range(RESOLUTION_LIQUIDS): liquid_type = unpack('<b', map_tiles.read(1))[0] if liquid_type == -1: # No liquid information / not rendered. continue height = unpack('<f', map_tiles.read(4))[0] # noinspection PyTypeChecker self.liquid_information[x][y] = LiquidInformation( liquid_type, height) # This is a valid tile, set as loaded. self.is_valid = True
def get_handler_from_packet(opcode): try: opcode_name = OpCode(opcode) if opcode_name in HANDLER_DEFINITIONS: return HANDLER_DEFINITIONS.get(OpCode(opcode)) else: Logger.warning('Received %s OpCode but is not handled.' % opcode_name) except ValueError: Logger.error('Received unknown OpCode (%u)' % opcode) return None
def resolve_effect_select(casting_spell, target_effect): if target_effect.effect_type == SpellEffects.SPELL_EFFECT_SCHOOL_DAMAGE: # Hellfire, aura of rot units = EffectTargets.resolve_all_around_caster( casting_spell, target_effect) return EffectTargets.get_enemies_from_unit_list( units, casting_spell.spell_caster) Logger.warning( f'Unimplemented implicit target called for spell {casting_spell.spell_entry.ID}' )
def get_handler_from_packet(world_session, opcode): try: opcode_name = OpCode(opcode) if opcode_name in HANDLER_DEFINITIONS: return HANDLER_DEFINITIONS.get(OpCode(opcode)), 1 else: Logger.warning('[%s] Received %s OpCode but is not handled.' % (world_session.client_address[0], opcode_name)) except ValueError: return None, -1 return None, 0
def load_items(self): character_inventory = RealmDatabaseManager.character_get_inventory( self.owner.guid) # First load bags for item_instance in character_inventory: item_template = WorldDatabaseManager.ItemTemplateHolder.item_template_get_by_entry( item_instance.item_template) if item_template and item_template.inventory_type == InventoryTypes.BAG: container_mgr = ContainerManager(owner=self.owner.guid, item_template=item_template, item_instance=item_instance) if self.is_bag_pos(container_mgr.current_slot): if item_instance.bag > 23: low_guid = container_mgr.guid & ~HighGuid.HIGHGUID_CONTAINER Logger.warning( f'Invalid bag slot {item_instance.bag} for guid {low_guid} owner {self.owner.guid}' ) continue self.containers[item_instance.bag].sorted_slots[ container_mgr.current_slot] = container_mgr self.containers[container_mgr.current_slot] = container_mgr # Then load items for item_instance in character_inventory: item_template = WorldDatabaseManager.ItemTemplateHolder.item_template_get_by_entry( item_instance.item_template) if item_template: if item_template.display_id > MAX_3368_ITEM_DISPLAY_ID and \ self.is_equipment_pos(item_instance.bag, item_instance.slot): Logger.error( f'Character {self.owner.player.name} has an equipped item ({item_template.entry} - {item_template.name}) ' f'with out of bounds display_id ({item_template.display_id}), ' f'deleting in order to prevent crashes.') RealmDatabaseManager.character_inventory_delete( item_instance) continue if item_template.inventory_type == InventoryTypes.BAG: if self.is_bag_pos(item_instance.slot): continue item_mgr = ContainerManager(owner=self.owner.guid, item_template=item_template, item_instance=item_instance) else: item_mgr = ItemManager(item_template=item_template, item_instance=item_instance) if item_instance.bag in self.containers and self.containers[ item_instance.bag]: self.containers[item_instance.bag].sorted_slots[ item_mgr.current_slot] = item_mgr
def add_threat(self, source: UnitManager, threat: float): if source != self: source_holder = self.holders.get(source.guid) if source_holder: new_threat = source_holder.total_threat + threat source_holder.total_threat = max(new_threat, 0.0) elif threat > 0.0: self.holders[source.guid] = ThreatHolder(source, threat) else: Logger.warning( f'Passed non positive threat {threat} from {source.guid & ~HighGuid.HIGHGUID_UNIT}' )
def _handle_use_goober(self, player): # Deadmines IronClad door (After triggering cannon) # TODO: This should be moved somewhere else to avoid hardcoding entries in the core GameObject manager. # Future instance/map scripts? if self.entry == 16398: # Cannon. # TODO, scripting, instancing, etc. iron_clad_doors = [go for go in MapManager.get_surrounding_gameobjects(self).values() if go.entry == 16397] if len(iron_clad_doors) > 0: self.send_custom_animation(0) iron_clad_doors[0].set_active() else: Logger.warning(f'Unimplemented gameobject use for type Goober entry {self.entry} name {self.gobject_template.name}') pass
def receive(self, sck): try: data = sck.recv(1024) reader = PacketReader(data) if reader.opcode: handler = Definitions.get_handler_from_packet(reader.opcode) if handler: Logger.debug('Handling %s' % OpCode(reader.opcode)) if handler(self, sck, reader) != 0: return -1 except OSError: Logger.warning('Tried to interact with a closed socket.') return -1
def get_handler_from_packet(world_session, opcode): try: opcode = OpCode(opcode) if opcode in HANDLER_DEFINITIONS: return HANDLER_DEFINITIONS.get(OpCode(opcode)), True Logger.warning( f'[{world_session.client_address[0]}] Received {opcode.name} OpCode but is not handled.' ) except ValueError: # No handler, OpCode not found return None, False # No handler, but OpCode found return None, True
def resolve_table_coordinates(casting_spell, target_effect): target_position = WorldDatabaseManager.spell_target_position_get_by_spell( casting_spell.spell_entry.ID) if not target_position: Logger.warning( f'Unimplemented target spell position for spell {casting_spell.spell_entry.ID}.' ) return [] return target_position.target_map, Vector( target_position.target_position_x, target_position.target_position_y, target_position.target_position_z, target_position.target_orientation)
def resolve_implicit_targets_reference(self, implicit_target): target = self.simple_targets[ implicit_target] if implicit_target in self.simple_targets else TARGET_RESOLVERS[ implicit_target](self.casting_spell) if target is None and implicit_target != 0: # Avoid crash on unfinished implementation while target resolving isn't finished TODO Logger.warning( f'Implicit target {implicit_target} resolved to None. Falling back to initial target or self.' ) target = self.initial_target if self.casting_spell.initial_target_is_object( ) else self.caster if type(target) is not list: return [target] return target
def receive_all(self, sck, expected_size): # Try to fill at once. received = sck.recv(expected_size) if not received: return b'' # We got what we expect, return buffer. if received == expected_size: return received # If we got incomplete data, request missing payload. buffer = bytearray(received) while len(buffer) < expected_size: Logger.warning('Got incomplete data from client, requesting missing payload.') received = sck.recv(expected_size - len(buffer)) if not received: return b'' buffer.extend(received) # Keep appending to our buffer until we're done. return buffer
def update_object(world_object, check_pending_changes=False): if world_object.current_cell: old_map = int(world_object.current_cell.split(':')[-1]) old_grid_manager = MapManager.get_grid_manager_by_map_id(old_map) else: old_grid_manager = None grid_manager = MapManager.get_grid_manager_by_map_id(world_object.map_) if grid_manager: grid_manager.update_object( world_object, old_grid_manager, check_pending_changes=check_pending_changes) else: Logger.warning( f'Warning, did not find grid_manager for map: {world_object.map_}' )
def receive(self, sck): try: data = sck.recv(2048) if len(data) > 0: reader = PacketReader(data) if reader.opcode: handler, res = Definitions.get_handler_from_packet(self, reader.opcode) if handler: Logger.debug('[%s] Handling %s' % (self.client_address[0], OpCode(reader.opcode))) if handler(self, sck, reader) != 0: return -1 elif res == -1: Logger.warning('[%s] Received unknown data: %s' % (self.client_address[0], data)) else: return -1 except OSError: self.disconnect() return -1
def resolve_implicit_targets_reference( self, implicit_target) -> Optional[list[Union[ObjectManager, Vector]]]: target = self.simple_targets[ implicit_target] if implicit_target in self.simple_targets else TARGET_RESOLVERS[ implicit_target](self.casting_spell, self.target_effect) # Avoid crash on unfinished implementation while target resolving isn't finished TODO # Implemented handlers should always return [] if no targets are found if target is None: Logger.warning( f'Implicit target {implicit_target} resolved to None. Falling back to initial target or self.' ) target = self.initial_target if self.casting_spell.initial_target_is_object( ) else self.casting_spell.spell_caster if type(target) is not list: return [target] return target
def calculate_z(map_id, x, y, current_z=0.0): try: map_tile_x, map_tile_y, tile_local_x, tile_local_y = MapManager.calculate_tile( x, y, RESOLUTION_ZMAP) x_normalized = RESOLUTION_ZMAP * (32.0 - (x / SIZE) - map_tile_x) - tile_local_x y_normalized = RESOLUTION_ZMAP * (32.0 - (y / SIZE) - map_tile_y) - tile_local_y if map_id not in MAPS or not MAPS[map_id].tiles[map_tile_x][ map_tile_y]: Logger.warning( f'Tile [{map_tile_x},{map_tile_y}] information not found.') return current_z if current_z else 0.0 else: try: val_1 = MapManager.get_height(map_id, map_tile_x, map_tile_y, tile_local_x, tile_local_y) val_2 = MapManager.get_height(map_id, map_tile_x, map_tile_y, tile_local_x + 1, tile_local_y) top_height = MapManager._lerp(val_1, val_2, x_normalized) val_3 = MapManager.get_height(map_id, map_tile_x, map_tile_y, tile_local_x, tile_local_y + 1) val_4 = MapManager.get_height(map_id, map_tile_x, map_tile_y, tile_local_x + 1, tile_local_y + 1) bottom_height = MapManager._lerp(val_3, val_4, x_normalized) return MapManager._lerp(top_height, bottom_height, y_normalized) # Z except: return MAPS[map_id].tiles[map_tile_x][map_tile_y].z_coords[ tile_local_x][tile_local_x] except: Logger.error(traceback.format_exc()) return current_z if current_z else 0.0
def handle_item_cast_attempt(self, item, caster): for spell_info in item.spell_stats: if spell_info.spell_id == 0: break spell = DbcDatabaseManager.SpellHolder.spell_get_by_id( spell_info.spell_id) if not spell: Logger.warning( f'Spell {spell_info.spell_id} tied to item {item.item_template.entry} ({item.item_template.name}) could not be found in the spell database.' ) continue casting_spell = self.try_initialize_spell( spell, caster, caster, SpellTargetMask.SELF, item) # TODO item spells targeting others? if not casting_spell: continue if casting_spell.is_refreshment_spell( ): # Food/drink items don't send sit packet - handle here caster.set_stand_state(StandState.UNIT_SITTING) self.start_spell_cast(spell, caster, caster, SpellTargetMask.SELF, item)
def load_area_triggers(self): if self.quest.entry in WorldDatabaseManager.QuestRelationHolder.AREA_TRIGGER_RELATION: self.area_triggers = WorldDatabaseManager.QuestRelationHolder.AREA_TRIGGER_RELATION[self.quest.entry] else: Logger.warning(f'Unable to locate area trigger/s for quest {self.quest.entry}')
def resolve_all_hostile_around_caster( casting_spell, target_effect): # TODO Charge effects only? Logger.warning( f'Unimplemented implicit target called for spell {casting_spell.spell_entry.ID}' )
def send_trainer_list(self, world_session): if not self.can_train(world_session.player_mgr): Logger.anticheat(f'send_trainer_list called from NPC {self.entry} by player with GUID {world_session.player_mgr.guid} but this unit does not train that player\'s class. Possible cheating') return train_spell_bytes: bytes = b'' train_spell_count: int = 0 trainer_ability_list: list[TrainerTemplate] = WorldDatabaseManager.TrainerSpellHolder.trainer_spells_get_by_trainer(self.entry) if not trainer_ability_list or trainer_ability_list.count == 0: Logger.warning(f'send_trainer_list called from NPC {self.entry} but no trainer spells found!') return for trainer_spell in trainer_ability_list: # trainer_spell: The spell the trainer uses to teach the player. player_spell_id = trainer_spell.playerspell ability_spell_chain: SpellChain = WorldDatabaseManager.SpellChainHolder.spell_chain_get_by_spell(player_spell_id) spell_level: int = trainer_spell.reqlevel # Use this and not spell data, as there are differences between data source (2003 Game Guide) and what is in spell table. spell_rank: int = ability_spell_chain.rank prev_spell: int = ability_spell_chain.prev_spell spell_is_too_high_level: bool = spell_level > world_session.player_mgr.level if player_spell_id in world_session.player_mgr.spell_manager.spells: status = TrainerServices.TRAINER_SERVICE_USED else: if prev_spell in world_session.player_mgr.spell_manager.spells and spell_rank > 1 and not spell_is_too_high_level: status = TrainerServices.TRAINER_SERVICE_AVAILABLE elif spell_rank == 1 and not spell_is_too_high_level: status = TrainerServices.TRAINER_SERVICE_AVAILABLE else: status = TrainerServices.TRAINER_SERVICE_UNAVAILABLE data: bytes = pack( '<IBI3B6I', player_spell_id, # Spell id status, # Status trainer_spell.spellcost, # Cost trainer_spell.talentpointcost, # Talent Point Cost trainer_spell.skillpointcost, # Skill Point Cost spell_level, # Required Level trainer_spell.reqskill, # Required Skill Line trainer_spell.reqskillvalue, # Required Skill Rank 0, # Required Skill Step prev_spell, # Required Ability (1) 0, # Required Ability (2) 0 # Required Ability (3) ) train_spell_bytes += data train_spell_count += 1 # TODO: Temp placeholder. greeting: str = f'Hello, {world_session.player_mgr.player.name}! Ready for some training?' greeting_bytes = PacketWriter.string_to_bytes(greeting) greeting_bytes = pack( f'<{len(greeting_bytes)}s', greeting_bytes ) data = pack('<Q2I', self.guid, TrainerTypes.TRAINER_TYPE_GENERAL, train_spell_count) + train_spell_bytes + greeting_bytes world_session.player_mgr.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_TRAINER_LIST, data))
def resolve_aoe_party(casting_spell, target_effect): Logger.warning( f'Unimplemented implicit target called for spell {casting_spell.spell_entry.ID}' )
def init_stats(self): base_stats = WorldDatabaseManager.player_get_class_level_stats(self.unit_mgr.class_, self.unit_mgr.level) if not base_stats: if self.unit_mgr.level > 60: # Default to max available base stats, level 60. base_stats = WorldDatabaseManager.player_get_class_level_stats(self.unit_mgr.class_, 60) Logger.warning(f'Unsupported base stats for level ({self.unit_mgr.level})' f' Unit class ({Classes(self.unit_mgr.class_).name})' f' Unit type ({ObjectTypeIds(self.unit_mgr.get_type_id()).name})' f' Using level 60 base stats.') else: Logger.error( f'Unsupported base stats for level ({self.unit_mgr.level})' f' Unit class ({Classes(self.unit_mgr.class_).name})' f' Unit type ({ObjectTypeIds(self.unit_mgr.get_type_id()).name}).') return # Player specific. if self.unit_mgr.get_type_id() == ObjectTypeIds.ID_PLAYER: base_attrs = WorldDatabaseManager.player_get_level_stats(self.unit_mgr.class_, self.unit_mgr.level, self.unit_mgr.race) if not base_attrs: if self.unit_mgr.level > 60: # Default to max available attributes, level 60. base_attrs = WorldDatabaseManager.player_get_level_stats(self.unit_mgr.class_, 60, self.unit_mgr.race) Logger.warning(f'Unsupported base attributes for level ({self.unit_mgr.level})' f' Unit type ({ObjectTypeIds(self.unit_mgr.get_type_id()).name})' f' Unit class ({Classes(self.unit_mgr.class_).name})' f' Unit race ({Races(self.unit_mgr.race).name})' f' Using level 60 attributes.') else: Logger.error(f'Unsupported base attributes for level ({self.unit_mgr.level})' f' Unit type ({ObjectTypeIds(self.unit_mgr.get_type_id()).name})' f' Unit class ({Classes(self.unit_mgr.class_).name})' f' Unit race ({Races(self.unit_mgr.race).name})') return self.base_stats[UnitStats.HEALTH] = base_stats.basehp self.base_stats[UnitStats.MANA] = base_stats.basemana self.base_stats[UnitStats.STRENGTH] = base_attrs.str self.base_stats[UnitStats.AGILITY] = base_attrs.agi self.base_stats[UnitStats.STAMINA] = base_attrs.sta self.base_stats[UnitStats.INTELLECT] = base_attrs.inte self.base_stats[UnitStats.SPIRIT] = base_attrs.spi self.unit_mgr.base_hp = base_stats.basehp self.unit_mgr.base_mana = base_stats.basemana # Creatures. else: self.base_stats[UnitStats.HEALTH] = self.unit_mgr.max_health self.base_stats[UnitStats.MANA] = self.unit_mgr.max_power_1 self.base_stats[UnitStats.SPIRIT] = 1 # Players don't have a flat dodge/block chance. self.base_stats[UnitStats.DODGE_CHANCE] = BASE_DODGE_CHANCE_CREATURE / 100 # Players have block scaling, assign flat 5% to creatures. self.base_stats[UnitStats.BLOCK_CHANCE] = BASE_BLOCK_PARRY_CHANCE / 100 self.base_stats[UnitStats.CRITICAL] = BASE_MELEE_CRITICAL_CHANCE / 100 self.unit_mgr.base_hp = self.unit_mgr.max_health self.unit_mgr.base_mana = self.unit_mgr.max_power_1 # Don't overwrite base speed if it has been modified. self.base_stats[UnitStats.SPEED_RUNNING] = self.base_stats.get(UnitStats.SPEED_RUNNING, config.Unit.Defaults.run_speed) # Players and creatures have an unchanging base 5% chance to block and parry (before defense skill differences). # As block chance also scales with strength, the value is calculated in update_base_block_chance. self.base_stats[UnitStats.PARRY_CHANCE] = BASE_BLOCK_PARRY_CHANCE / 100 self.send_attributes() self.update_base_health_regen() self.update_base_mana_regen() self.update_base_proc_chance() self.update_base_melee_critical_chance() self.update_defense_bonuses()
def resolve_gameobject_script_near_caster(casting_spell, target_effect): Logger.warning( f'Unimplemented implicit target called for spell {casting_spell.spell_entry.ID}' )
def resolve_random_enemy_chain_in_area(casting_spell, target_effect): Logger.warning( f'Unimplemented implicit target called for spell {casting_spell.spell_entry.ID}' )