def attack(self, target): """ Attack random member of enemy party not on cooldown. If all members of enemy party on cooldown, kills one at random. Puts attacker on cooldown :param target: Entity party being attacked :return: dict results of attack """ results = [] self.cooldown = self.offensive_cd # pick random member off cooldown member = target.party.random_member_no_cooldown() if member: results.append({ 'message': Message("{} {} stuns {} {} for {} turns".format( self.profession, self.name, member.profession, member.name, member.defensive_cd)) }) member.take_damage() else: # if all on cooldown, kill random member member = target.party.random_member() results.append({ 'message': Message( "{} {} kills {} {}".format(self.profession, self.name, member.profession, member.name), libtcod.orange) }) # removes member, checks for death results.extend(target.party.remove_member(member)) return results
def cast_lightning(*args, **kwargs): caster = args[0] entities = kwargs.get('entities') fov_map = kwargs.get('fov_map') damage = kwargs.get('damage') maximum_range = kwargs.get('maximum_range') results = [] target = None closest_distance = maximum_range + 1 for entity in entities: if entity.fighter and entity != caster and tcod.map_is_in_fov(fov_map, entity.x, entity.y): distance = caster.distance_to(entity) if distance < closest_distance: target = entity closest_distance = distance if target: results.append({'consumed': True, 'target': target, 'message': Message( 'A lightning bolt strikes the {0} with a thunderous boom! {1} takes {2} damage.'.format( target.name, target.name, damage), tcod.dark_yellow)}) results.extend(target.fighter.take_damage(damage)) else: results.append({'consumed': False, 'target': None, 'message': Message( 'No enemy is close enough to strike.', tcod.red)}) 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 tcod.map_is_in_fov(fov_map, target_x, target_y): results.append({'consumed': False, 'message': Message('You cannot target something outside your field of view.', tcod.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} glaze over, as it starts to ' 'stumble around'.format(entity.name), tcod.light_green)}) break else: results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', tcod.yellow)}) return results
def cast_fireball(*args, **kwargs): 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 tcod.map_is_in_fov(fov_map, target_x, target_y): results.append({'consumed': False, 'message': Message('You cannot target something outside your field of view', tcod.yellow)}) return results results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles.'. format(radius), tcod.orange)}) for entity in entities: if entity.distance(target_x, target_y) <= radius and entity.fighter: results.append({'message': Message('The {0} gets burned for {1} hit points.'.format(entity.name, damage), tcod.orange)}) results.extend(entity.fighter.take_damage(damage)) return results
def heal(*args, **kwargs): entity = args[0] amount = kwargs.get('amount') results = [] if entity.fighter.hp == entity.fighter.max_hp: results.append({'consumed': False, 'message': Message('You are already at full health.', tcod.yellow)}) else: entity.fighter.heal(amount) results.append({'consumed': True, 'message': Message('Your wounds are starting to heal!', tcod.green)}) return results
def attack(self, target): results = [] damage = self.power - target.fighter.defense if damage > 0: target.fighter.take_damage(damage) results.append({'message': Message('{0} attacks {1} for {2} hit points.'.format( self.owner.name.capitalize(), target.name, str(damage)), tcod.white)}) results.extend(target.fighter.take_damage(damage)) else: results.append({'message': Message('{0} attacks {1} but does no damage.'.format( self.owner.name.capitalize(), target.name), tcod.white)}) return results
def use(self, item_entity, **kwargs): results = [] item_component = item_entity.item if item_component.use_function is None: equippable_component = item_entity.equippable # If equipment can be equipped, do so. Otherwise, print message if equippable_component: results.append({'equip': item_entity}) else: results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), tcod.yellow)}) else: # Determines if targeting is true or not and if the coords were passed if item_component.targeting and not (kwargs.get('target_x') or kwargs.get('target_y')): results.append({'targeting': item_entity}) 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'): self.remove_item(item_entity) results.extend(item_use_results) return results
def add_coins(self, amount): """ Add coins to the party's treasure horde :param amount: :return: None """ self.coins += amount return Message(text='Hero Party found {} coins'.format(amount))
def add_member(self, party_member): """ Append a PartyMember to the list :param party_member: PartyMember object to add to the party list :return: results and message """ if len(self.members) < 6: self.members.append(party_member) message = Message('{} {} added to party'.format( party_member.profession, party_member.name), color=libtcod.lighter_blue) results = True else: message = Message('Not enough space for new members!'.format( party_member.profession, party_member.name), color=libtcod.lighter_blue) results = False return message, results
def add_item(self, item): results = [] if len(self.items) >= self.capacity: results.append({ 'item_added': None, 'message': Message('You cannot carry any more, your inventory is full', tcod.yellow) }) else: results.append({ 'item_added': item, 'message': Message('You pick up the {0} and place it in your inventory'.format(item.name), tcod.blue) }) self.items.append(item) return results
def drop_item(self, item): results = [] item.x = self.owner.x item.y = self.owner.y self.remove_item(item) results.append({'item_dropped': item, 'message': Message('You dropped the {0}.'.format(item.name), tcod.yellow)}) return results
def pay_members(self): """ Remove coins from the party's treasure horde for each party member. If there are not enough coins to pay all party members, a random party member will be removed until there are :return: result list of messages """ results = [] while self.total_party_cost() > self.coins: person = self.random_member() results.append( Message(text="{} {} has left {} due to lack of funding".format( person.profession, person.name, self.owner.name), color=libtcod.orange)) results.extend(self.remove_member(party_member=person)) for member in self.members: results.append( Message(text="{} the {} gets paid {}".format( member.name, member.profession, member.cost))) self.coins -= member.get_cost() return results
def kill_monster(entity): death_message = Message(text='{} is dead!'.format( entity.name.capitalize()), color=libtcod.orange) entity.char = '%' entity.color = libtcod.dark_red entity.blocks = False entity.render_order = RenderOrder.CORPSE entity.ai = None entity.name = 'Remains of {}'.format(entity.name) return death_message
def kill_monster(monster): death_message = Message('{0} is dead!'.format(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 drop_item(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 the {0}.'.format(item.name), tcod.yellow)}) 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), tcod.red)}) return results
def remove_member(self, party_member): """ Remove a PartyMember from the list :param party_member: PartyMember object to remove from the party list :return: results of check for death """ results = [] self.members.remove(party_member) results.append({ 'message': Message('{} {} removed from {}'.format(party_member.profession, party_member.name, self.owner.name), color=libtcod.lighter_blue) }) if not self.members: results.append({'dead': self.owner}) return results
def next_floor(self, player, message_log, constants): self.dungeon_level += 1 entities = [player] # Creates a new map self.tiles = self.initialize_tiles() self.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'], constants['map_width'], constants['map_height'], player, entities) # TODO note when we add stairs back up, we need to make sure that player doesn't gain health again going down # Gives player half of their max hp back player.fighter.heal(player.fighter.max_hp // 2) message_log.add_message( Message( 'You take a moment to rest heading down the stairs, restoring some health', tcod.violet)) return entities
def main(): screen_width = 80 screen_height = 50 bar_width = 20 panel_height = 7 panel_y = screen_height - panel_height message_x = bar_width + 2 message_width = screen_width - bar_width - 2 message_height = panel_height - 1 map_width = 80 map_height = 43 # Conway's Game Of Life variables # 3 6 5 6 3 - good choice # 3 6 5 7 3 - another good choice # 3 5 5 6 4 - larger, more open # 2 4 6 8 6 - very full, but lots of obstacles (Forest?) # 3 7 5 6 4 - small, windy, lots of caverns survive_min = 3 survive_max = 7 resurrect_min = 6 resurrect_max = 6 iterations = 4 # zone variables zone_seed_min_distance = 10 min_cavern_size = 15 max_monsters_per_room = 3 fov_radius = 8 colors = { 'dark_wall': libtcod.darker_gray, 'dark_ground': libtcod.dark_gray, 'light_wall': libtcod.sepia, 'light_ground': libtcod.light_sepia } party_component = Party() member_1 = PartyMember(name="Bill", profession="Soldier", offensive_cd=4, defensive_cd=4, attack_type={'line': 1}, cost=5) member_2 = PartyMember(name="John", profession="Archer", offensive_cd=5, defensive_cd=5, attack_type={'direct': 4}, cost=4) member_3 = PartyMember(name="Sam", profession="Defender", offensive_cd=5, defensive_cd=3, attack_type={'line': 1}, cost=5) member_4 = PartyMember(name="Ivan", profession="Ice Mage", offensive_cd=6, defensive_cd=6, attack_type={'cone': 3}, cost=8) party_component.add_member(member_1) party_component.add_member(member_2) party_component.add_member(member_3) party_component.add_member(member_4) player = Entity(x=0, y=0, char='@', color=libtcod.white, name='Hero Party', blocks=True, render_order=RenderOrder.ACTOR, party=party_component) entities = [player] libtcod.console_set_custom_font(fontFile='images/arial10x10.png', flags=libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) libtcod.console_init_root(w=screen_width, h=screen_height, title='Hero Party', fullscreen=False) con = libtcod.console_new(w=screen_width, h=screen_height) panel = libtcod.console_new(w=screen_width, h=panel_height) game_map = GameMap(width=map_width, height=map_height) game_map.make_map(survive_min=survive_min, survive_max=survive_max, resurrect_min=resurrect_min, resurrect_max=resurrect_max, iterations=iterations, zone_seed_min_distance=zone_seed_min_distance, min_cavern_size=min_cavern_size, player=player, entities=entities, max_monsters_per_room=max_monsters_per_room) fov_recompute = True fov_map = initialize_fov(game_map=game_map) message_log = MessageLog(x=message_x, width=message_width, height=message_height) key = libtcod.Key() mouse = libtcod.Mouse() game_state = GameStates.PLAYER_TURN previous_member = None target_tiles = None while not libtcod.console_is_window_closed(): libtcod.sys_check_for_event(mask=libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, k=key, m=mouse) if fov_recompute: recompute_fov(fov_map=fov_map, x=player.x, y=player.y, radius=fov_radius) render_all(con=con, panel=panel, entities=entities, player=player, game_map=game_map, fov_map=fov_map, fov_recompute=fov_recompute, message_log=message_log, screen_width=screen_width, screen_height=screen_height, acting_member=previous_member, target_tiles=target_tiles, bar_width=bar_width, panel_height=panel_height, panel_y=panel_y, mouse=mouse, colors=colors) libtcod.console_flush() clear_all(con=con, entities=entities) # --------- PLAYER TURN: GET INPUTS ------------- action = handle_keys(key=key, game_state=game_state) mouse_action = handle_mouse(mouse) move = action.get('move') exit_game = action.get('exit_game') fullscreen = action.get('fullscreen') auto = action.get('auto') selected_member = action.get('member') act_dir = action.get('act_dir') left_click = mouse_action.get('left_click') right_click = mouse_action.get('right_click') # --------- PLAYER TURN: PROCESS INPUTS ------------- player_turn_results = [] # AUTOMATIC ----------------------------------------- if auto and GameStates.PLAYER_TURN: for entity in entities: if not entity.ai and not entity.blocks and entity.party.members \ and entity.x == player.x and entity.y == player.y: player_turn_results.append({'add_member': entity}) elif not entity.ai and not entity.blocks and entity.party.coins \ and entity.x == player.x and entity.y == player.y: player_turn_results.append({'loot_coins': entity}) else: # Wait game_state = GameStates.ENEMY_TURN # MOVEMENT ------------------------------------------ if move and game_state == GameStates.PLAYER_TURN: dx, dy = move destination_x = player.x + dx destination_y = player.y + dy if not game_map.is_blocked(x=destination_x, y=destination_y) \ and not get_blocking_entities_at_location(entities=entities, x=destination_x, y=destination_y): player.move(dx=dx, dy=dy) fov_recompute = True game_state = GameStates.ENEMY_TURN # SELECTED MEMBER ----------------------------------- if selected_member and selected_member <= len(player.party.members) and \ player.party.members[selected_member - 1].cooldown < 1: if not previous_member: previous_member = selected_member target_tiles = get_target_tiles(entity=player, member=previous_member - 1, game_map=game_map, fov_map=fov_map) player_turn_results.append({'message': Message("Direction?")}) game_state = GameStates.TARGETING elif previous_member == selected_member: previous_member = None target_tiles = None game_state = GameStates.PLAYER_TURN if act_dir and previous_member and game_state == GameStates.TARGETING: attack_tiles = get_target_tiles(entity=player, member=previous_member - 1, game_map=game_map, fov_map=fov_map, attack_dir=[act_dir]) targets = [ entity for entity in entities if (entity.x, entity.y) in attack_tiles and entity.ai ] # TODO this code should really be somewhere else... if targets: if player.party.members[previous_member - 1].attack_type.get('line'): for target in targets: attack_results = player.party.members[ previous_member - 1].attack(target=target) player_turn_results.extend(attack_results) elif player.party.members[previous_member - 1].attack_type.get('direct'): closest = targets[0] closest_dist = distance_to(player.x, closest.x, player.y, closest.y) targets.remove(closest) for target in targets: distance = distance_to(player.x, player.y, target.x, target.y) if distance < closest_dist: closest = target closest_dist = distance attack_results = player.party.members[previous_member - 1].attack( target=closest) player_turn_results.extend(attack_results) elif player.party.members[previous_member - 1].attack_type.get('cone'): for target in targets: attack_results = player.party.members[ previous_member - 1].attack(target=target) player_turn_results.extend(attack_results) else: player_turn_results.append( {'message': Message("Attack hits nothing")}) previous_member = None target_tiles = None game_state = GameStates.ENEMY_TURN if exit_game: return True if fullscreen: libtcod.console_set_fullscreen( fullscreen=not libtcod.console_is_fullscreen()) # --------- PLAYER TURN: PROCESS RESULTS ------------- for result in player_turn_results: rescued_members = result.get('add_member') message = result.get('message') dead_entity = result.get('dead') loot_coins = result.get('loot_coins') if rescued_members: for member in rescued_members.party.members: msg, res = player.party.add_member(member) message_log.add_message(msg) if res: entities.remove(rescued_members) if loot_coins: msg = player.party.add_coins(loot_coins.party.coins) message_log.add_message(msg) entities.remove(loot_coins) if message: message_log.add_message(message=message) if dead_entity: if dead_entity == player: msg, game_state = kill_player(dead_entity) else: msg = kill_monster(dead_entity) message_log.add_message(message=msg) # --------- ENEMY TURN: GET INPUT ------------- if game_state == GameStates.ENEMY_TURN: entities_in_distance_order = sorted( entities, key=lambda z: z.distance_to(player)) for entity in entities_in_distance_order: if entity.ai: enemy_turn_results = entity.ai.take_turn(target=player, fov_map=fov_map, game_map=game_map, entities=entities) # --------- ENEMY TURN: PROCESS RESULTS ------------- for result in enemy_turn_results: message = result.get('message') dead_entity = result.get('dead') if message: message_log.add_message(message=message) if dead_entity: if dead_entity == player: message, game_state = kill_player( player=dead_entity) else: message = kill_monster(entity=dead_entity) message_log.add_message(message=message) if game_state == GameStates.PLAYER_DEAD: break if game_state == GameStates.PLAYER_DEAD: break else: game_state = GameStates.PLAYER_TURN for entity in entities: if entity.party: entity.party.tick_all()
def kill_player(player): player.char = '%' player.color = tcod.dark_red return Message('You died!', tcod.red), GameStates.PLAYER_DEAD
def play_game(player, entities, game_map, message_log, game_state, con, panel, constants): # Field of view variables fov_recompute = True # We only need to recompute when character moves fov_map = initialize_fov(game_map) # Variables for keyboard and mouse inputs key = tcod.Key() mouse = tcod.Mouse() # Player goes first game_state = GameStates.PLAYERS_TURN # Save previous game state (for inventory support) previous_game_state = game_state # Saves targeting item targeting_item = None # Main game loop while not tcod.console_is_window_closed(): tcod.sys_check_for_event(tcod.EVENT_KEY_PRESS | tcod.EVENT_MOUSE, key, mouse) # Updates field of view if needed if fov_recompute: recompute_fov(fov_map, player.x, player.y, constants['fov_radius'], constants['fov_light_walls'], constants['fov_algorithm']) # Draws player and sets recompute to false until next player move render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, constants['screen_width'], constants['screen_height'], constants['bar_width'], constants['panel_height'], constants['panel_y'], mouse, constants['colors'], game_state) fov_recompute = False tcod.console_flush() # Updates spot last at with a blank (avoids multiple @'s) clear_all(con, entities) # Keyboard and mouse inputs action = handle_keys(key, game_state) mouse_action = handle_mouse(mouse) # TODO add in help menu that lists commands, available both through menu and by hitting '?' # Action handlers move = action.get('move') wait = action.get('wait') pickup = action.get('pickup') show_inventory = action.get('show_inventory') drop_inventory = action.get('drop_inventory') inventory_index = action.get('inventory_index') level_up = action.get('level_up') show_character_screen = action.get('show_character_screen') # TODO add in take_stairs_up take_stairs_down = action.get('take_stairs_down') exit = action.get('exit') fullscreen = action.get('fullscreen') # Mouse action handlers left_click = mouse_action.get('left_click') right_click = mouse_action.get('right_click') # List to hold for result of battles player_turn_results = [] # Player turn and handling of item pickups if move and game_state == GameStates.PLAYERS_TURN: dx, dy = move destination_x = player.x + dx destination_y = player.y + dy if not game_map.is_blocked(destination_x, destination_y): target = get_blocking_entities_at_location( entities, destination_x, destination_y) if target: attack_results = player.fighter.attack(target) player_turn_results.extend(attack_results) else: player.move(dx, dy) fov_recompute = True game_state = GameStates.ENEMY_TURN # Player waits for a turn (and does nothing) elif wait and game_state == GameStates.PLAYERS_TURN: message_log.add_message( Message('You twiddle your thumbs for a turn.', tcod.turquoise)) game_state = GameStates.ENEMY_TURN # Pickup items elif pickup and game_state == GameStates.PLAYERS_TURN: for entity in entities: if entity.item and entity.x == player.x and entity.y == player.y: pickup_results = player.inventory.add_item(entity) player_turn_results.extend(pickup_results) break else: message_log.add_message( Message('There is nothing here to pick up.', tcod.yellow)) # Show inventory if show_inventory: previous_game_state = game_state game_state = GameStates.SHOW_INVENTORY # Drops item from inventory if drop_inventory: previous_game_state = game_state game_state = GameStates.DROP_INVENTORY # Use or drop item (only when in inventory game state and not dead) if inventory_index is not None and previous_game_state != GameStates.PLAYER_DEAD and inventory_index < len( player.inventory.items): item = player.inventory.items[inventory_index] if game_state == GameStates.SHOW_INVENTORY: player_turn_results.extend( player.inventory.use(item, entities=entities, fov_map=fov_map)) elif game_state == GameStates.DROP_INVENTORY: player_turn_results.extend(player.inventory.drop_item(item)) # Leveling up if level_up: if level_up == 'hp': player.fighter.max_hp += 20 player.fighter.hp += 20 elif level_up == 'str': player.fighter.power += 1 elif level_up == 'def': player.fighter.defense += 1 game_state = previous_game_state # Character screen, switches to appropriate game state if show_character_screen: previous_game_state = game_state game_state = GameStates.CHARACTER_SCREEN # TODO will likely need to add a stairs_down and stairs_up to entities when making going up floors # Goes down a flight of stairs, going to a new map if take_stairs_down and game_state == GameStates.PLAYERS_TURN: for entity in entities: if entity.stairs and entity.x == player.x and entity.y == player.y: entities = game_map.next_floor(player, message_log, constants) fov_map = initialize_fov(game_map) fov_recompute = True tcod.console_clear(con) break else: message_log.add_message( Message('There are no stairs here.', tcod.yellow)) # Targeting mode is active - left mouse click sets target, right mouse click cancels if game_state == GameStates.TARGETING: if left_click: target_x, target_y = left_click item_use_results = player.inventory.use(targeting_item, entities=entities, fov_map=fov_map, target_x=target_x, target_y=target_y) player_turn_results.extend(item_use_results) elif right_click: player_turn_results.append({'targeting_cancelled': True}) # Reverts back to previous game state while viewing inventory; otherwise, closes and saves game if exit: if game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY, GameStates.CHARACTER_SCREEN): game_state = previous_game_state elif game_state == GameStates.TARGETING: player_turn_results.append({'targeting_cancelled': True}) else: save_game(player, entities, game_map, message_log, game_state) return True # Toggles fullscreen if fullscreen: tcod.console_set_fullscreen(not tcod.console_is_fullscreen()) # Iterates results after turn for player_turn_result in player_turn_results: message = player_turn_result.get('message') dead_entity = player_turn_result.get('dead') item_added = player_turn_result.get('item_added') item_consumed = player_turn_result.get('consumed') item_dropped = player_turn_result.get('item_dropped') targeting = player_turn_result.get('targeting') targeting_cancelled = player_turn_result.get('targeting_cancelled') xp = player_turn_result.get('xp') # Displays supplied message if message: message_log.add_message(message) # Player or monster has died if dead_entity: if dead_entity == player: message, game_state = kill_player(dead_entity) else: message = kill_monster(dead_entity) message_log.add_message(message) # Item was added to inventory if item_added: entities.remove(item_added) game_state = GameStates.ENEMY_TURN # Item was used if item_consumed: game_state = GameStates.ENEMY_TURN # Item was dropped if item_dropped: entities.append(item_dropped) game_state = GameStates.ENEMY_TURN # Targeting is activated, switch to targeting mode if targeting: previous_game_state = GameStates.PLAYERS_TURN game_state = GameStates.TARGETING targeting_item = targeting message_log.add_message(targeting_item.item.targeting_message) # Targeting was cancelled, revert to previous game state if targeting_cancelled: game_state = previous_game_state message_log.add_message(Message('Targeting cancelled.')) # Experience results if xp: leveled_up = player.level.add_xp(xp) message_log.add_message( Message('You gain {0} experience points.'.format(xp))) if leveled_up: message_log.add_message( Message( 'You have leveled up and reached level {0}!'. format(player.level.current_level), tcod.green)) previous_game_state = game_state game_state = GameStates.LEVEL_UP # Enemies turn if game_state == GameStates.ENEMY_TURN: for entity in entities: if entity.ai: enemy_turn_results = entity.ai.take_turn( player, fov_map, game_map, entities) for enemy_turn_result in enemy_turn_results: message = enemy_turn_result.get('message') dead_entity = enemy_turn_result.get('dead') if message: message_log.add_message(message) if dead_entity: if dead_entity == player: message, game_state = kill_player(dead_entity) else: message = kill_monster(dead_entity) message_log.add_message(message) if game_state == GameStates.PLAYER_DEAD: break if game_state == GameStates.PLAYER_DEAD: break else: game_state = GameStates.PLAYERS_TURN
def kill_player(player): player.char = '%' player.color = libtcod.dark_red return Message(text='Your party has been slain!', color=libtcod.red), GameStates.PLAYER_DEAD
def place_entities(self, room, entities): # Bases number of monsters and item to which level of dungeon the player is on max_monsters_per_room = from_dungeon_level([[2, 1], [3, 4], [5, 6]], self.dungeon_level) max_items_per_room = from_dungeon_level([[1, 1], [2, 4]], self.dungeon_level) number_of_monsters = randint(0, max_monsters_per_room) number_of_items = randint(0, max_items_per_room) # Monster chances to spawn - trolls will have more weight the further down the floors a player goes monster_chances = { 'orc': 80, 'troll': from_dungeon_level([[15, 3], [30, 5], [60, 7]], self.dungeon_level) } # Item chances to drop - item weight changes based on the dungeon level with less healing potions spawning item_chances = { 'healing_potion': 35, 'sword': from_dungeon_level([[5, 4]], self.dungeon_level), 'shield': from_dungeon_level([[15, 8]], self.dungeon_level), 'lightning_scroll': from_dungeon_level([[25, 4]], self.dungeon_level), 'fireball_scroll': from_dungeon_level([[25, 6]], self.dungeon_level), 'confusion_scroll': from_dungeon_level([[10, 2]], self.dungeon_level) } # Spawns monsters for i in range(number_of_monsters): x = randint(room.x1 + 1, room.x2 - 1) y = randint(room.y1 + 1, room.y2 - 1) # TODO - add more monsters as you go further down in levels # Checks if location to place monster is empty if not any([ entity for entity in entities if entity.x == x and entity.y == y ]): # Used to determine which monster to spawn monster_choice = random_choice_from_dict(monster_chances) # Orc if monster_choice == 'orc': fighter_component = Fighter(hp=20, defense=0, power=4, xp=35) ai_component = BasicMonster() monster = Entity(x, y, 'o', tcod.desaturated_green, 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) # Troll else: fighter_component = Fighter(hp=30, defense=2, power=8, xp=100) ai_component = BasicMonster() monster = Entity(x, y, 'T', tcod.darker_green, 'Troll', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) entities.append(monster) # Spawns items for i in range(number_of_items): x = randint(room.x1 + 1, room.x2 - 1) y = randint(room.y1 + 1, room.y2 - 1) if not any([ entity for entity in entities if entity.x == x and entity.y == y ]): # Used to determine what item to spawn item_choice = random_choice_from_dict(item_chances) # Healing potion - heals 4 damage if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=40) item = Entity(x, y, '!', tcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) # Sword elif item_choice == 'sword': equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=3) item = Entity(x, y, '/', tcod.sky, 'Sword', equippable=equippable_component) # Shield elif item_choice == 'shield': equippable_component = Equippable(EquipmentSlots.OFF_HAND, defense_bonus=1) item = Entity(x, y, '[', tcod.darker_orange, 'Shield', equippable=equippable_component) # Fireball scroll - deals 12 damage to all enemies in a radius of 3 tiles elif item_choice == 'fireball_scroll': item_component = Item( use_function=cast_fireball, targeting=True, targeting_message=Message( 'Left-click a target tile for the fireball, or right-click to cancel.', tcod.light_cyan), damage=25, radius=3) item = Entity(x, y, '#', tcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) # Confuse scroll - confuses enemy for 10 turns elif item_choice == 'confusion_scroll': item_component = Item( use_function=cast_confuse, targeting=True, targeting_message=Message( 'Left-click an enemy to confuse it, or right-click to cancel', tcod.light_cyan)) item = Entity(x, y, '#', tcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component) # Lightning scroll - deals 20 damage to nearest enemy else: item_component = Item(use_function=cast_lightning, damage=40, maximum_range=5) item = Entity(x, y, '#', tcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) entities.append(item)