def cast_paralysis(*args, **kwargs): caster = args[0] entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') target_x = kwargs.get('target_x') target_y = kwargs.get('target_y') results = [] if not libtcod.map_is_in_fov(fov_map, target_x, target_y): results.append({ 'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow) }) return results for entity in entities: if entity.x == target_x and entity.y == target_y and entity.ai: factor = caster.fighter.base_magic extra_turns = math.ceil(math.log(factor, 10) + math.ceil(factor/100)) turns = 2 + extra_turns confused_ai = ParalysedMonster(entity.ai, turns) confused_ai.owner = entity entity.ai = confused_ai results.append({ 'consumed': True, 'message': Message('%s is paralysed for %i turns' % (entity.name, turns), libtcod.light_cyan), 'stat_paralyzed': True }) break else: results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', libtcod.yellow)}) return results
def cast_confuse(*args, **kwargs): entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') target_x = kwargs.get('target_x') target_y = kwargs.get('target_y') results = [] if not libtcod.map_is_in_fov(fov_map, target_x, target_y): results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) return results for entity in entities: if entity.x == target_x and entity.y == target_y and entity.ai: confused_ai = ConfusedMonster(entity.ai, 10) confused_ai.owner = entity entity.ai = confused_ai results.append({'consumed': True, 'message': Message('The eyes of the {0} look vacant, as he starts to stumble around!'.format(entity.name), libtcod.light_green)}) break else: results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', libtcod.yellow)}) return results
def cast_fireball(*args, **kwargs): caster = args[0] entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') damage = kwargs.get('damage') radius = kwargs.get('radius') target_x = kwargs.get('target_x') target_y = kwargs.get('target_y') results = [] if not libtcod.map_is_in_fov(fov_map, target_x, target_y): results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) return results results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles!'.format(radius), libtcod.orange)}) base_dmg = math.ceil(caster.fighter.base_magic * (damage*0.1)) + damage for entity in entities: if entity.distance(target_x, target_y) <= radius and entity.fighter: rolled_dmg = random.randint(int(base_dmg/2), base_dmg*2) results.append({ 'message': Message('The {0} gets burned for {1} hit points.'.format(entity.name, rolled_dmg), libtcod.orange), 'stat_magic_damage': rolled_dmg }) results.extend(entity.fighter.take_damage(rolled_dmg)) return results
def apply(self, fighter): fighter.base_max_hp += self.factor fighter.hp += self.factor return { 'message': Message('Player got +%i Max HP' % self.factor, libtcod.light_blue) }
def use_hotkey(self, key, **kwargs): results = [] print("Using hotkey %i" % key) slot = self.tome_slots[key - 1] item_component = slot.item.item radius = item_component.function_kwargs.get('radius') if slot.quantity <= 0: results.append({ 'message': Message('Not enough tomes to cast %s' % slot.item.name, libtcod.yellow) }) elif item_component.targeting and not (kwargs.get('target_x') or kwargs.get('target_y')): target = {'targeting': slot.item, 'targeting_index': key} if item_component.targeting_area: target['targeting_area'] = True target['radius'] = radius print("return result %s" % str(target)) results.append(target) else: kwargs = {**item_component.function_kwargs, **kwargs} item_use_results = item_component.use_function( self.owner, **kwargs) for item_use_result in item_use_results: if item_use_result.get('consumed'): slot.quantity -= 1 results.extend(item_use_results) return results
def drop_item(self, game_map, entities): active_hand = self.get_active_hand() if active_hand: dropped_item = deepcopy(active_hand) self.discard_item() dropped_item.current_room = self.owner.current_room dropped_item.set_map_position(game_map) dropped_item.room_x = self.owner.room_x dropped_item.room_y = self.owner.room_y entities.append(dropped_item) return Message("You drop the {}.".format(dropped_item.name), (255, 255, 255)) else: return Message("You have nothing in that hand to drop.", (255, 255, 255))
def add_coins(self, amount): results = [] new_amount = self.coins + amount if new_amount > self.max_coins: results.append({ 'message': Message('Maximum money (%i) reached' % self.max_coins, libtcod.yellow) }) self.coins = self.max_coins else: self.coins = new_amount results.append({ 'message': Message('Added %i gold coins to purse' % amount, libtcod.green) }) return results
def attack(self, target): results = [] damage = self.power - target.fighter.defense #hit = random.randint(self.power, self.power*10) > random.randint(target.fighter.defense, target.fighter.defense*10) damage = random.randint(int(self.power/2), int(self.power*2)) if damage > 0: text = '%s attacks %s for %i hit points' % (self.owner.name.capitalize(), target.name, damage) results.append({ 'message': Message(text, libtcod.white), 'stat_damage': {'amount': damage, 'target': target.name} }) results.extend(target.fighter.take_damage(damage)) else: text = '%s attacks %s but does no damage' % (self.owner.name.capitalize(), target.name) results.append({ 'message': Message(text, libtcod.white) }) return results
def make(self): target_msg = Message('Left-click to cast, right-click to cancel', tcod.light_cyan) item_component = Item(use_function=cast_paralysis, targeting=True, targeting_message=target_msg) item = Entity(0, 0, '#', tcod.pink, 'Paralysis Tome', True, render_order=RenderOrder.ITEM, item=item_component) return item
def heal(*args, **kwargs): entity = args[0] amount = kwargs.get('amount') results = [] base_heal = math.ceil(entity.fighter.base_magic/4)*5 + amount + math.ceil(entity.fighter.base_max_hp*0.1) if entity.fighter.hp == entity.fighter.base_max_hp: results.append({'consumed': False, 'message': Message('You are already at full HP', libtcod.yellow)}) else: rolled_amount = random.randint(math.ceil(base_heal/1.5), base_heal*2) entity.fighter.heal(rolled_amount) results.append({ 'consumed': True, 'message': Message('Tome of Healing recovered %i HP' % rolled_amount, libtcod.green), 'stat_heal': rolled_amount }) return results
def take_turn(self, target, fov_map, game_map, entities): results = [] if self.number_of_turns > 0: self.number_of_turns -= 1 else: self.owner.ai = self.previous_ai results.append({ 'message': Message('%s is no longer confused' % (self.owner.name), libtcod.light_yellow) }) return results
def remove_coins(self, amount): if amount > self.coins: return { 'success': False, 'message': Message("Not enough gold to buy item", libtcod.light_yellow) } self.coins -= amount return {'success': True}
def make(self): target_msg = Message('Left-click to cast, right-click to cancel', tcod.light_cyan) item_component = Item(use_function=cast_magic_missile, targeting=True, targeting_message=target_msg, damage=12) item = Entity(0, 0, '#', tcod.blue, 'Magic Missile Tome', True, render_order=RenderOrder.ITEM, item=item_component) return item
def attack(self, target): results = [] damage = 0 active_hand = self.get_active_hand() if not active_hand: pass elif active_hand.weapon: damage += active_hand.weapon.power active_hand.weapon.uses -= 1 if damage > 0: results.append({ "message": Message( '{} attacks {} ({})'.format(self.owner.name.capitalize(), target.name.capitalize(), str(damage)), (255, 255, 255)) }) results.extend(target.fighter.take_damage(damage)) else: results.append({ "message": Message( '{} attacks without a weapon (0)'.format( self.owner.name.capitalize(), target.name), (255, 255, 255)) }) if not active_hand: pass elif active_hand.weapon.uses <= 0: item_name = active_hand.name results.append({ "message": Message("{}'s {} breaks!".format(self.owner.name, item_name), (255, 255, 255)) }) self.discard_item() return results
def take_damage(self, amount): results = [] self.hp -= amount if self.hp < 0: self.hp = 0 if self.hp == 0: if self.owner.purse: results.append({'loot': self.owner.purse.coins}) results.append({'message': Message('Looted %i gold coins from %s' % (self.owner.purse.coins, self.owner.name), libtcod.light_green)}) if self.owner.boss: print("Boss killed!!") results.append({ 'message': Message('Boss %s slain! Proceed to the stairs' % self.owner.name, libtcod.light_yellow) }) results.append({ 'boss_dead': True }) results.append({'dead': self.owner}) return results
def pickup_item(self, entities): active_hand = self.get_active_hand() if active_hand: return Message("You're already holding something in this hand.", (255, 255, 255)) else: for entity in entities: if isinstance(entity, Item): if entity.map_x == self.owner.map_x and entity.map_y == self.owner.map_y: if entity.room_x == self.owner.room_x and entity.room_y == self.owner.room_y: self.equip_item(entity) item_name = entity.name entities.remove(entity) return Message( "You pickup the {}".format(item_name), (255, 255, 255)) else: return Message("There's nothing here to pick up.", (255, 255, 255))
def make(self): target_msg = Message('Left-click to cast, right-click to cancel', tcod.light_cyan) item_component = Item(use_function=cast_fireball, radius=2, damage=10, targeting=True, targeting_area=True, targeting_message=target_msg) item = Entity(0, 0, '#', tcod.red, 'Fireball Tome', True, render_order=RenderOrder.ITEM, item=item_component) return item
def take_turn(self, target, fov_map, game_map, entities): level = game_map.dungeon_level - 1 monster = self.owner if libtcod.map_is_in_fov(fov_map, monster.x, monster.y): if randint(0, 100) < self.chance: return [{ 'summon': {'monster': self.summon, 'level': level}, 'message': Message('%s has summoned a %s' % (monster.name, self.summon), libtcod.purple) }] else: return super().take_turn(target, fov_map, game_map, entities) return []
def kill_monster(monster): death_message = Message('%s is dead!' % monster.name.capitalize(), tcod.orange) monster.char = '%' monster.color = tcod.dark_red monster.blocks = False monster.fighter = None monster.ai = None monster.name = 'remains of ' + monster.name monster.render_order = RenderOrder.CORPSE return death_message
def cast_magic_missile(*args, **kwargs): caster = args[0] entities = kwargs.get('entities') damage = kwargs.get('damage') fov_map = kwargs.get('fov_map') target_x = kwargs.get('target_x') target_y = kwargs.get('target_y') results = [] if not libtcod.map_is_in_fov(fov_map, target_x, target_y): results.append({ 'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow) }) return results for entity in entities: if entity.x == target_x and entity.y == target_y and entity.ai: factor = caster.fighter.base_magic print("Factor: %i" % factor) #missiles = math.ceil(factor/10) missiles = math.ceil(math.log(factor, 10) + math.ceil(factor/100)) base_dmg = math.ceil((damage + factor)/2) print("missiles %i" % missiles) dmg = [random.randint(int(base_dmg/4), int(base_dmg*1.5)) for _ in range(missiles)] print("%s" % str(dmg)) rolled_dmg = reduce(lambda x,acc: acc+x, dmg) print("final damage: %s" % str(rolled_dmg)) results.extend(entity.fighter.take_damage(rolled_dmg)) results.append({ 'consumed': True, 'message': Message('%i magic missile(s) hit %s. Damage taken: %i' % (missiles, entity.name, rolled_dmg), libtcod.cyan), 'stat_magic_damage': rolled_dmg }) break else: results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', libtcod.yellow)}) return results
def kill_monster(monster): death_message = Message("The {0} dies!".format(monster.name.capitalize()), (255, 255, 255)) monster.char = "%" monster.colour = (255, 255, 255) monster.blocks = False monster.fighter = None monster.ai = None monster.name = "Remains" monster.render_order = RenderOrder.CORPSE return death_message
def next_floor(self, player, message_log, config, component): self.dungeon_level += 1 entities = [player] self.tiles = self.initialize_tiles() self.make_map(config.get('MAX_ROOMS'), config.get('ROOM_MIN_SIZE'), config.get('ROOM_MAX_SIZE'), player, entities, config.get('MAX_MONSTERS'), config.get('MAX_ITEMS'), component) player.fighter.heal(player.fighter.max_hp // 2) message_log.add_message( Message('You take a moment to rest', libtcod.light_violet)) return entities
def attack(self, target): results = [] damage = self.power - target._class.defense # Result for hit if damage > 0: target._class.take_damage(damage) results.append({ 'message': Message('{0} attacks {1} for {2} hit points.'.format( self.owner.name.capitalize(), target.name, str(damage))) }) results.extend(target._class.take_damage(damage)) # Result for no damage else: results.append({ 'message': Message('{0} attacks {1} but does no damage.'.format( self.owner.name.capitalize(), target.name)) }) return results
def take_turn(self, target, fov_map, game_map, entities): results = [] if self.number_of_turns > 0: random_x = self.owner.x + randint(0, 2) - 1 random_y = self.owner.y + randint(0, 2) - 1 if random_x != self.owner.x and random_y != self.owner.y: self.owner.move_towards(random_x, random_y, game_map, entities) self.number_of_turns -= 1 else: self.owner.ai = self.previous_ai results.append({'message': Message('The {0} is no longer confused!'.format(self.owner.name), libtcod.red)}) return results
def drop(self, item): results = [] if self.owner.equipment.main_hand == item or self.owner.equipment.off_hand == item: self.owner.equipment.toggle_equip(item) item.x = self.owner.x item.y = self.owner.y self.remove_item(item) results.append({ 'item_dropped': item, 'message': Message('You dropped %s' % item.name, libtcod.yellow) }) return results
def add_item(self, item): results = [] results.append({ 'item_added': item, 'message': Message('You pick up the %s!' % item.name, libtcod.blue) }) tome_slot = list( filter(lambda slot: slot.item.name == item.name, self.tome_slots)) if len(tome_slot) == 1 and tome_slot[0] is not None: tome_slot[0].quantity += 1 else: raise SystemError("Failed to add item to tome slot with name %s" % item.name) return results
def shuffle_rooms(self, player, entities): viable_coordinates = [] # Append an x,y for each location in the game map. for y in range(self.map_height): for x in range(self.map_width): viable_coordinates.append((x, y)) # Remove the player's current location or things could get messy. Shuffle the list. viable_coordinates.remove((player.map_x, player.map_y)) shuffle(viable_coordinates) # This avoids leaving one room copied and not replaced, ending up with a duplicate. final_copy_x, final_copy_y = viable_coordinates[0] final_copied_room = deepcopy(self.rooms[final_copy_x][final_copy_y]) # Pop the first room coords, and set that to be replaced by the next room in the list. Continue in this way. while len(viable_coordinates) > 1: replace_x, replace_y = viable_coordinates.pop(0) copy_x, copy_y = viable_coordinates[0] copied_room = deepcopy(self.rooms[copy_x][copy_y]) self.rooms[replace_x][replace_y] = copied_room # There will always be one room left because of this method, so that's why we copied before the while loop final_replace_x, final_replace_y = viable_coordinates.pop(0) self.rooms[final_replace_x][final_replace_y] = final_copied_room self.update_rooms_index( ) # Update the room index with the new positions. message = Message("You feel an odd sensation of movement...") for entity in entities: if entity is player: continue else: entity.set_map_position(self) return message
def perform(self, state, action, mouse_action, mouse_move): player_turn_result = [] exit = action.get('exit') exit_instructions = action.get('exit_instructions') move = action.get('move') pickup = action.get('pickup') show_inventory = action.get('show_inventory') inventory_index = action.get('inventory_index') shop_index = action.get('shop_index') take_stairs = action.get('take_stairs') drop_inventory = action.get('drop_inventory') targeting_cancelled = action.get('targeting_cancelled') hotkey = action.get('hotkey') # revive = action.get('revive') replay = action.get('replay') show_help = action.get('show_help') debug_take_stairs = action.get('debug_take_stairs') show_character_screen = action.get('show_character_screen') left_click = mouse_action.get('left_click') right_click = mouse_action.get('right_click') (mouse_x, mouse_y) = mouse_move if replay: player_turn_result.append({'next_stage': 'main_menu'}) if show_help: state.game_state = GameStates.INSTRUCTIONS if exit_instructions: state.game_state = GameStates.PLAYERS_TURN redraw = True self.scene.fov_recompute = True if show_inventory: if state.game_state == GameStates.INVENTORY: state.game_state = state.previous_game_state #redraw = True else: state.previous_game_state = state.game_state state.game_state = GameStates.INVENTORY if drop_inventory: if state.game_state == GameStates.DROP_INVENTORY: state.game_state = state.previous_game_state #redraw = True else: state.previous_state_game = state.game_state state.game_state = GameStates.DROP_INVENTORY if show_character_screen: if state.game_state == GameStates.CHARACTER_SCREEN: state.game_state = state.previous_game_state #redraw = True else: state.previous_game_state = state.game_state state.game_state = GameStates.CHARACTER_SCREEN if targeting_cancelled: state.game_state = state.previous_game_state state.message_log.add_message(Message('Targeting cancelled')) if shop_index is not None and shop_index < len( state.game_map.shopkeeper.shop.options): tome = state.game_map.shopkeeper.shop.options[shop_index] player_turn_result.extend(tome.use(state.player)) if inventory_index is not None and state.previous_game_state != GameStates.PLAYER_DEAD and inventory_index < len( state.player.inventory.items): item = state.player.inventory.items[inventory_index] if state.game_state == GameStates.INVENTORY: # TODO: Move fov_map to game_map player_turn_result.extend( state.player.inventory.use(item, entities=state.entities, fov_map=self.scene.fov_map)) elif state.game_state == GameStates.DROP_INVENTORY: player_turn_result.extend(state.player.inventory.drop(item)) if take_stairs and state.game_state == GameStates.PLAYERS_TURN: for entity in state.entities: if entity.stairs and entity.x == state.player.x and entity.y == state.player.y: state.entities = state.game_map.next_floor( state.player, state.message_log, CONFIG, component) self.scene.fov_map = GameScene.init_fov_map(state.game_map) self.scene.fov_recompute = True tcod.console_clear(self.scene.owner.con) break else: state.message_log.add_message( Message('No stairs found', tcod.yellow)) if debug_take_stairs: for entity in state.entities: if entity.stairs: state.entities = state.game_map.next_floor( state.player, state.message_log, CONFIG, component) self.scene.fov_map = GameScene.init_fov_map(state.game_map) self.scene.fov_recompute = True tcod.console_clear(self.scene.owner.con) if exit: if state.game_state in (GameStates.CHARACTER_SCREEN, GameStates.INVENTORY): state.game_state = state.previous_game_state redraw = True elif state.game_state == GameStates.SHOP: state.game_state = GameStates.ENEMY_TURN elif state.game_state == GameStates.TARGETING: player_turn_result.append({'targeting_cancelled': True}) else: # TODO: Port save #save_game(state.player, state.entities, state.game_map, state.message_log, state.game_state) player_turn_result.append({'exit_game': True}) if hotkey and state.game_state == GameStates.PLAYERS_TURN: results = state.player.inventory.use_hotkey( int(hotkey), entities=state.entities, fov_map=self.scene.fov_map) player_turn_result.extend(results) if move and state.game_state == GameStates.PLAYERS_TURN: dx, dy = move dest_x = state.player.x + dx dest_y = state.player.y + dy if not state.game_map.is_blocked(dest_x, dest_y): target = Entity.get_blocking(state.entities, dest_x, dest_y) if target: if target.shop: player_turn_result.append({'open_shop': True}) elif target.container: open_results = target.container.open(state.player) player_turn_result.extend(open_results) elif target.item: pickup_results = state.player.inventory.add_item( target) player_turn_result.extend(pickup_results) else: attack_results = state.player.fighter.attack(target) player_turn_result.extend(attack_results) else: state.player.move(dx, dy) self.scene.fov_recompute = True state.game_state = GameStates.ENEMY_TURN elif pickup and state.game_state == GameStates.PLAYERS_TURN: for entity in state.entities: if entity.item and entity.x == state.player.x and entity.y == state.player.y: pickup_results = state.player.inventory.add_item(entity) player_turn_result.extend(pickup_results) break else: state.message_log.add_message( Message('There is nothing here to pickup', tcod.yellow)) if state.game_state == GameStates.TARGETING: if left_click: target_x, target_y = left_click target_y = target_y - CONFIG.get('MAP_Y') # Offset item_use_results = state.player.inventory.use_hotkey( state.targeting_index, entities=state.entities, fov_map=self.scene.fov_map, target_x=target_x, target_y=target_y) player_turn_result.extend(item_use_results) elif right_click: player_turn_result.append({'targeting_cancelled': True}) self.scene.fov_recompute = True return player_turn_result
def update(self, state, results, scene): for result in results: print("Processing result %s" % result) message = result.get('message') stat_heal = result.get('stat_heal') stat_damage = result.get('stat_damage') stat_magic_damage = result.get('stat_magic_damage') stat_paralyzed = result.get('stat_paralyzed') dead_entity = result.get('dead') item_added = result.get('item_added') item_consumed = result.get('consumed') item_dropped = result.get('item_dropped') equip = result.get('equip') boss_dead = result.get('boss_dead') targeting = result.get('targeting') targeting_index = result.get('targeting_index') targeting_cancelled = result.get('targeting_cancelled') targeting_area = result.get('targeting_area') targeting_radius = result.get('radius') exit_game = result.get('exit_game') open_shop = result.get('open_shop') container_consumed = result.get('container_consumed') loot = result.get('loot') next_stage = result.get('next_stage') if stat_heal: state.history.healed += stat_heal if stat_paralyzed: state.history.paralyzed += 1 if stat_magic_damage: state.history.compute_magic_damage(stat_magic_damage) if stat_damage: target = stat_damage.get('target') amount = stat_damage.get('amount') state.history.compute_damage(target, amount) if exit_game: return {'exit_game': True} if message: state.message_log.add_message(message) if dead_entity: if dead_entity == state.player: message, state.game_state = GameAiAct.kill_player( dead_entity) else: state.history.killed_monster(dead_entity.name) message = GameAiAct.kill_monster(dead_entity) state.message_log.add_message(message) if item_added: state.entities.remove(item_added) state.history.compute_loot(item_added) state.game_state = GameStates.ENEMY_TURN if container_consumed: state.entities.remove(container_consumed) state.history.chests_open += 1 state.game_state = GameStates.ENEMY_TURN if loot: message = state.player.purse.add_coins(loot) #state.message_log.add_message(message) if boss_dead: print("Boss dead event!") state.history.bosses_killed += 1 state.entities = state.game_map.next_floor( state.player, state.message_log, CONFIG, component) scene.fov_map = GameScene.init_fov_map(state.game_map) scene.fov_recompute = True tcod.console_clear(scene.owner.con) if item_consumed: state.game_state = GameStates.ENEMY_TURN if open_shop: state.game_state = GameStates.SHOP if equip: equip_results = state.player.equipment.toggle_equip(equip) for equip_results in equip_results: equipped = equip_results.get('equipped') dequipped = equip_results.get('dequipped') if equipped: state.message_log.add_message( Message('Equipped %s' % equipped)) if dequipped: state.message_log.add_message( Message('Dequipped %s' % dequipped)) state.game_state = GameStates.ENEMY_TURN if item_dropped: state.entities.append(item_dropped) state.game_state = GameStates.ENEMY_TURN if targeting: state.previous_game_state = GameStates.PLAYERS_TURN state.game_state = GameStates.TARGETING state.targeting_item = targeting state.message_log.add_message( state.targeting_item.item.targeting_message) if targeting_area and targeting_radius: state.targeting_area = True state.targeting_radius = targeting_radius else: state.targeting_area = False if targeting_index: state.targeting_index = targeting_index if targeting_cancelled: state.game_state = state.previous_game_state state.message_log.add_message(Message('Targeting Cancelled')) if next_stage: return {'next_stage': next_stage} return {}
def kill_player(player): player.char = '%' player.color = tcod.dark_red return Message('You Died!', tcod.red), GameStates.PLAYER_DEAD