def render_description_window(game): ent = entity_at_pos(game.walk_blocking_ents, *game.cursor.pos) if ent is not None: x, y = pos_on_screen(ent.x - 5, ent.y + 2, game.player) title = f'{ent.full_name}' body = ent.extended_descr(game, list_items_underneath = True) draw_window(title, body, game, window_x=x, window_y=y, show_cancel_option=False, title_color=ent.color)
def try_move(self, dx: int, dy: int, game: Game, ignore_entities: bool = False, ignore_walls: bool = False, absolute: bool = False): """ Attempts to move the entity in the given direction or to the given coordinates. Returns True on successful move. Returns False if wall blocks movement or entity is unable to move due to effects. Returns Entity if entity blocks movement. :param dx: :type dx: int :param dy: :type dy: int :param game: :type game: Game :param ignore_entities: Whether to ignore blocking entities when checking for movement. :type ignore_entities: bool :param ignore_walls: Whether to ignore walls when checking for movement. :type ignore_walls: bool :param absolute: Whether dx&dy denote directions or a x/y coordinates on the grid :type absolute: bool :return: :rtype: bool """ if not self.can_move: return False if not absolute: dest_x, dest_y = self.x + dx, self.y + dy else: dest_x, dest_y = dx, dy if game.map.is_wall(dest_x, dest_y) and not ignore_walls: return False blocked = entity_at_pos(game.walk_blocking_ents, dest_x, dest_y) if blocked and not ignore_entities: return blocked self.move(dx, dy, absolute=absolute) return True
def initialize_fov(game): game_map = game.map fov_map = tcod.map.Map(game_map.width, game_map.height) for x in range(game_map.width): for y in range(game_map.height): fov_map.transparent[y, x] = not game_map.tiles[(x, y)].block_sight fov_map.walkable[y, x] = not game_map.tiles[(x, y)].blocked ent = entity_at_pos(game.sight_blocking_ents, x, y) if ent: fov_map.transparent[y, x] = not ent.blocks.get( BlockLevel.SIGHT, False) #game_map.tiles[(x, y)].block_sight fov_map.walkable[y, x] = not ent.blocks.get( BlockLevel.WALK, False) return fov_map
def execute(self, tx: int, ty: int, game: Game, **kwargs): user = self.owner user.color_bg = None # Reset the entities bg-color, which the skill preparation had changed results = [] results.append({ 'message': Message(f'The {user.name} slams down!', category=MessageCategory.OBSERVATION, type=MessageType.COMBAT) }) hit = entity_at_pos(game.fighter_ents, tx, ty) if hit: if hit.fighter: results.extend( user.f.attack_setup(hit, game, dmg_mod_multipl=2, verb='slams', ignore_moveset=True)) return results
def ranged_attack(self, target_pos, game, draw_projectile=True): results = [] target = None if draw_projectile: projectile_result = animate_projectile( *self.owner.pos, *target_pos, game, color=colors.beige, ignore_entities=False ) # TODO color and projectile can later be modified by weapon/ammo if not isinstance(projectile_result, bool): target = projectile_result # todo if target.pos != target_pos add some randomness? else: target = entity_at_pos(game.blocking_ents, *target_pos) if target is not None and target.fighter is not None: results.extend(self.attack_setup(target, game)) else: exertion = self.get_attack_exertion(self.get_attack_power()) self.exert(exertion, 'attack') return results
def process_player_interaction(game, action): manual = action.get('manual') move = action.get('move') # dodge = action.get('dodge') interact = action.get('interact') direction = action.get('dir') wait = action.get('wait') pickup = action.get('pickup') prepare = action.get('prepare') equip = action.get('equip') quick_use_idx = action.get('quick_use') show_inventory = action.get('show_inventory') show_equipment = action.get('show_equipment') show_prepared = action.get('show_prepared') # drop_inventory = action.get('drop_inventory') # menu_selection = action.get('menu_selection') toggle_dash = action.get('toggle_dash') toggle_block = action.get('toggle_block') toggle_weapon = action.get('toggle_weapon') player = game.player game_map = game.map entities = game.entities fov_map = game.fov_map debug = action.get('debug') spawn = action.get('spawn') results = [] if move or interact: dx, dy = direction destination_x, destination_y = player.x + dx, player.y + dy blocking = player.f.is_blocking dashing = player.f.is_dashing if not game_map.is_wall(destination_x, destination_y): target = entity_at_pos(game.walk_blocking_ents, destination_x, destination_y) if target is None and interact: # Check for non-blocking interactable objects target = entity_at_pos(game.interactable_ents, destination_x, destination_y) if target: if player.can_attack is False: results.append({'message': Message('You are unable to attack!', type=MessageType.COMBAT_BAD)}) # If a NPC is blocking the way # elif target.fighter: if blocking: results.append({'message': Message('PLACEHOLDER: cant attack while blocking.', type=MessageType.SYSTEM)}) elif dashing: results.append({'message': Message('PLACEHOLDER: cant tackle adjacent target.', type=MessageType.SYSTEM)}) else: if player.active_weapon_is_ranged: # TODO should attacking in melee with a ranged weapon incur penalties or be disabled? attack_results = player.f.attack_setup(target, game, dmg_mod_multipl=0.2, ignore_moveset=True) results.append({'message': Message( 'PLACEHOLDER: attacking with ranged weapon in melee.', type=MessageType.SYSTEM)}) else: attack_results = player.f.attack_setup(target, game) results.extend(attack_results) # If a static object is blocking the way # elif target.architecture: if interact and target.architecture.on_interaction: # interacting with a architecture entity interaction_results = target.architecture.on_interaction(player, target, game) results.extend(interaction_results) if move and target.architecture.on_collision: # bumping into the object collision_results = target.architecture.on_collision(player, target, game) results.extend(collision_results) # elif move: # TODO Are these still required? # print('PLACEHOLDER: Your way is blocked.') # elif interact: # print('PLACEHOLDER: There is nothing to interact with') elif move: if player.can_move is False: results.append({'message': Message('You are unable to move!', type=MessageType.COMBAT_BAD)}) elif dashing: results.extend(player.f.dash(dx, dy, game)) else: player.move(dx, dy) results.append({'fov_recompute': True}) if not target or not target.fighter: # Movement other than fighting resets the current moveset if player.f.active_weapon is not None: player.f.active_weapon.moveset.cycle_moves(reset=True) game.state = GameState.NPCS_ACTIVE # Passing a turn or interacting # elif wait: if not player.in_combat(game): # outside combat, interact with item under player target = entity_at_pos(game.interactable_ents, *player.pos) if target is not None: interaction_results = target.architecture.on_interaction(player, target, game) results.extend(interaction_results) results.append({'waiting': True}) # Picking up an item # elif pickup: items = [item for item in game.item_ents if item.same_pos_as(player)] if items: # Option menu is displayed if > 1 item is on the ground choice = items[0] if len(items) == 1 else \ item_list_menu(player, items, game, title='Select Item', body='Pick up which item?') if choice is not None and choice is not False: pickup_results = player.inventory.add(choice) results.extend(pickup_results) else: results.append({'message': Message('There is nothing here.', category=MessageCategory.OBSERVATION)}) # Combat related # if toggle_block: results.extend(player.f.toggle_blocking()) if toggle_dash: results.extend(player.f.toggle_dashing()) # No dodging while blocking possible; disable one or the other, depending on input if player.f.is_blocking and player.f.is_dashing: if toggle_block: player.f.toggle_dashing() elif toggle_dash: player.f.toggle_blocking() else: logging.debug('ERROR: Both blocking and dashing active.') if toggle_weapon: new_weapon = player.f.toggle_weapon() results.append({'weapon_switched': new_weapon}) # Quick use handling # if quick_use_idx and quick_use_idx <= len(player.qu_inventory): quick_use_item = player.qu_inventory[quick_use_idx - 1] # -1 as the idx is passed as a number key qu_results = player.qu_inventory.use(quick_use_item, game) results.extend(qu_results) # Inventory display # if show_inventory or prepare or equip: if show_inventory and len(player.inventory) == 0: Message('Your inventory is empty.', category=MessageCategory.OBSERVATION).add_to_log(game) elif prepare and len(player.inventory.useable_items) <= 0: Message('You have no items to prepare.', category=MessageCategory.OBSERVATION).add_to_log(game) elif equip and len(player.inventory.equippable_items) <= 0: Message('You have no items to equip.', category=MessageCategory.OBSERVATION).add_to_log(game) else: game.previous_state = game.state game.state = GameState.SHOW_INVENTORY if show_prepared: if len(player.qu_inventory) > 0: game.previous_state = game.state game.state = GameState.SHOW_QU_INVENTORY else: Message('You have no items prepared.', category=MessageCategory.OBSERVATION).add_to_log(game) if show_equipment: if len(player.paperdoll.equipped_items) > 0: game.previous_state = game.state game.state = GameState.SHOW_EQUIPMENT else: Message('You have no items equipped.', category=MessageCategory.OBSERVATION).add_to_log(game) # Other # if manual: display_manual() if spawn: results.extend(spawn_menu(game)) if debug: debug_menu(game, clear=False) return results
def attack_setup(self, target, game, dmg_mod_multipl: float = 1, verb: str = 'hit', ignore_moveset: bool = False): results = [] extra_attacks = [] #atk_exertion_divider = cfg.ATK_EXERT_MULTIPL daze_chance = 50 # chance to daze attacker on successful block # TODO dynamically calculated if self.active_weapon is not None: # if a weapon is equipped, check the type melee_attack = True if self.active_weapon.type == ItemType.MELEE_WEAPON else False #ranged_attack = True if self.active_weapon.type == ItemType.RANGED_WEAPON else False else: # unarmed attack melee_attack = True ranged_attack = False ignore_moveset = True # Apply moveset modifers # attack_power = self.get_attack_power(dmg_mod_multipl, ignore_moveset) attack_exertion = self.get_attack_exertion(attack_power, ignore_moveset) if not ignore_moveset and self.active_weapon is not None: move_results = self.active_weapon.moveset.execute( self.owner, target) verb = move_results.get('attack_verb', verb) extra_attacks = move_results.get('extra_attacks', []) self.active_weapon.moveset.cycle_moves() # if ignore_moveset: # attack_power = choice(self.base_dmg_potential) * dmg_mod_multipl # attack_exertion = attack_power / atk_exertion_divider # else: # if self.active_weapon is not None: # move_results = self.active_weapon.moveset.execute(self.owner, target) # verb = move_results.get('attack_verb', verb) # extra_attacks = move_results.get('extra_attacks', []) # # attack_power = self.dmg_roll * dmg_mod_multipl # attack_exertion = attack_power / atk_exertion_divider * self.active_weapon.moveset.exert_multipl # self.active_weapon.moveset.cycle_moves() logging.debug( f'{self.owner.name} prepares to attack {target.name} with base damage {self.base_dmg_potential},' f' (modded {self.modded_dmg_potential}) for a total power of {attack_power} and {attack_exertion}exert' ) # Make sure attacker has enough stamina # if self.stamina < attack_exertion: message = M(f'You are too exhausted to attack!', category=MessageCategory.COMBAT, type=MessageType.ALERT) results.append({'message': message}) logging.debug( f'Canceling {self}\'s attack: stamina of {self.stamina} too low.' ) return results # Blocking # attack_blocked = False if target.f.is_blocking: attack_blocked = target.f.attempt_block(self, attack_power) if attack_blocked: # TODO should attacker also take sta damage? sta_dmg_multipl = self.active_weapon.moveset.get_modifier( Mod.BLOCK_STA_DMG_MULTIPL ) # Some weapons afflict a higher stamina damage sta_dmg = round((attack_power / 2) * sta_dmg_multipl) logging.debug( f'{target.name} block exert multiplied by {sta_dmg_multipl} due to {self.owner.name} attack mod' ) results.append(target.f.exert(sta_dmg, 'block')) if melee_attack and randint(0, 100) > daze_chance: if target.is_player: message = M( f'{target.address_colored.title()} block the attack, dazing {self.owner.address_colored}!', category=MessageCategory.COMBAT, type=MessageType.COMBAT_GOOD) else: message = M( f'{self.owner.address_colored.title()} blocks your attack, dazing {target.owner.address_colored}!', category=MessageCategory.COMBAT, type=MessageType.COMBAT_BAD) self.set_effect(State.DAZED, True, 1) results.append({'message': message}) else: attack_string = self.owner.verb_declination(verb) results.extend( self.attack_execute(target, attack_power, attack_string)) for attack_pos in extra_attacks: extra_target = entity_at_pos(game.npc_ents, *attack_pos) if extra_target: results.extend( self.attack_execute(extra_target, attack_power // 2, 'also hit') ) # TODO currently flat half damage and can't be blocked; can be replaced with moveset-tailored multiplier self.exert(attack_exertion, 'attack') return results