def event_force_move(self, entity, dx, dy): game_map = self.container.map position = entity[PositionComponent] x1 = position.x y1 = position.y x2 = x1 + dx y2 = y1 + dy for i, (x, y) in enumerate(tcod.line_iter(x1, y1, x2, y2)): if not game_map.walkable[x, y]: self.container.event('wall_slam', entity=entity) break other_entity = next( (e for e in self.container.entities if PositionComponent in e and BlockingComponent in e and e[PositionComponent].x == x and e[PositionComponent].y == y and e is not entity), None, ) if other_entity is not None: self.container.event('entity_slam', entity=entity, other_entity=other_entity) break else: position.x = x position.y = y
def event_cursor_fire(self): player = self.container.player cursor = next( (e for e in self.container.entities if CursorComponent in e and PositionComponent in e), None, ) if cursor is None: return item = get_weapon(player) if item is None or WeaponComponent not in item: self.container.event('no_weapon_equipped') return wc: WeaponComponent = item[WeaponComponent] max_range = wc.max_range player_position = player[PositionComponent] cursor_position = cursor[PositionComponent] x1 = player_position.x y1 = player_position.y x2 = cursor_position.x y2 = cursor_position.y if wc.smite: blocking_entity = next( (e for e in self.container.entities if PositionComponent in e and CombatComponent in e and e[PositionComponent].x == x2 and e[PositionComponent].y == y2 and e is not player and e is not cursor), None, ) if blocking_entity is not None: self.container.event('attack', attacker=player, defender=blocking_entity) return else: for i, (x, y) in enumerate(tcod.line_iter(x1, y1, x2, y2)): if i > max_range: break blocking_entity = next( (e for e in self.container.entities if PositionComponent in e and BlockingComponent in e and e[PositionComponent].x == x and e[PositionComponent].y == y and e is not player and e is not cursor), None, ) if blocking_entity is not None: self.container.event('attack', attacker=player, defender=blocking_entity) return self.container.event('no_target')
def build_path(self): """ Build a path using only cardinal directions from self to chosen_tile. """ path = [] legal_tiles = [] closest_tile = None legal_tiles.extend(self.legal_tiles.get('green')) legal_tiles.extend(self.legal_tiles.get('yellow')) legal_tiles.extend(self.legal_tiles.get('red')) if len(legal_tiles) > 0 and self.chosen_tile not in legal_tiles: closest_tile = legal_tiles.pop() for tile in legal_tiles: if distance_to( tile, self.chosen_tile, manhattan=False) < distance_to( closest_tile, self.chosen_tile, manhattan=False): closest_tile = tile xo, yo = self.owner.location.x, self.owner.location.y xd, yd = self.chosen_tile if closest_tile: xd, yd = closest_tile path = list(libtcod.line_iter(xo, yo, xd, yd)) if path: self.path = fill_in_line(path) return self.path return []
def click_move(self, x, y, game_map, entities): line = libtcod.line_iter(self.x, self.y, x, y) skip_first = True for x, y in line: if skip_first: skip_first = False else: self.move_towards(x, y, game_map, entities)
def path_unblocked(game_map, xo, yo, xd, yd): path = list(libtcod.line_iter(xo, yo, xd, yd)) for (x, y) in path: if 0 < x < game_map.width and 0 < y < game_map.height and not game_map.tiles[ 'blocks_path'][x][y]: return True else: return False
def calculate_movement_range(self, game_map): """ Find three lists of tiles the player may move to. First list is in case of speed increase. Second list is in case of speed equilibrium. Third list is in case of speed decrease. """ green_list = [] yellow_list = [] red_list = [] x = self.owner.location.x y = self.owner.location.y min = -self.max_impulse max = self.max_impulse + 1 for xi in range(min, max): for yi in range(min, max): if (abs(xi) + abs(yi)) <= self.max_impulse: xo = self.owner.location.x xd = x + self.speed_x + xi yo = self.owner.location.y yd = y + self.speed_y + yi new_speed = abs(xd - xo) + abs(yd - yo) line = list(libtcod.line_iter(xo, yo, xd, yd)) good_tile = True if new_speed > self.max_speed: # Can't exceed max speed. continue if game_map.is_blocked(xd, yd): # Can't path to blocked tiles. continue for tile in line: xt, yt = tile if game_map.is_blocked(xt, yt): # Can't path to tiles behind blocked tiles. good_tile = False if new_speed > self.speed and new_speed <= self.cruising_speed and good_tile: green_list.append( (x + self.speed_x + xi, y + self.speed_y + yi)) if new_speed == self.speed and new_speed <= self.cruising_speed and good_tile: yellow_list.append( (x + self.speed_x + xi, y + self.speed_y + yi)) if new_speed < self.speed and new_speed <= self.cruising_speed and good_tile: red_list.append( (x + self.speed_x + xi, y + self.speed_y + yi)) self.legal_tiles['red'] = red_list self.legal_tiles['yellow'] = yellow_list self.legal_tiles['green'] = green_list
def handle_activation(thing, actor, target_pos, rng): if thing in activates_as: actor_pos = props.position[actor] for pos in tcod.line_iter(actor_pos[0], actor_pos[1], target_pos[0], target_pos[1]): if pos in props.terrain_at: t = props.terrain_at[pos] if t == things.mountains: props.terrain_at[pos] = things.desert events.terrain_change.trigger(pos)
def find_line(coords1: Tuple[int, int], coords2: Tuple[int, int], include_origin: bool = False) -> Iterator[Tuple[int, int]]: """Converts who x,y coords into a list of tiles. coords1 = (x1, y1) coords2 = (x2, y2)""" x1, y1 = coords1 x2, y2 = coords2 rtn = tcod.line_iter(x1, y1, x2, y2) if not include_origin: next(rtn) return rtn
def __init__(self, xo, yo, xd, yd): self.step = 0 path = list(tcod.line_iter(xo, yo, xd, yd)) if randint(0, 1) == 0: # "Close" star self.glyph = ord('.') self.color = ( 255, 255, 255 ) # choice([(128, 128, 255), (255, 255, 0), (255, 128, 128), (255, 255, 255)]) slow_path = [ point for point in path[0:len(path) // 4] for _ in (0, 1) ] fast_path = path[len(path) // 4:] full_path = slow_path + fast_path self.path = full_path[randint(0, 4):] else: # "Far" star self.glyph = ord('.') self.color = ( 191, 191, 191 ) # choice([(64, 64, 128), (128, 128, 0), (128, 64, 64), (128, 128, 128)]) path = path[len(path) // randint(3, 4):] self.path = [point for point in path for _ in (0, 1)]
def test_line_iter(): """ libtcodpy.line_iter """ assert list(libtcodpy.line_iter(*LINE_ARGS)) == INCLUSIVE_RESULTS
def render_all(entities, player, game_map, message_log, bar_width, panel_y, coordinates, camera, game_state, targeting_item): entities_in_render_order = sorted(entities, key=lambda x: x.render_order.value) # Draw the map camera_moved = camera.move_camera(player.x, player.y, game_map) terminal.layer(RenderLayer.MAP.value) if camera_moved: clear_map_layer() game_map.render_from_camera(camera) # Draw all entities in the list terminal.layer(RenderLayer.ENTITIES.value) for entity in entities_in_render_order: entity.draw(camera, game_map) terminal.layer(RenderLayer.OVERLAY.value) clear_layer() for efx in game_map.effects: efx.update() if efx.expired: game_map.effects.remove(efx) elif efx.render: (term_x, term_y) = camera.map_to_term_coord(efx.x, efx.y) terminal.put(term_x, term_y, efx.gfx_effect_tile) if game_state == GameStates.TARGETING: from entity import get_blocking_entities_at_location terminal.composition(terminal.TK_ON) mouse_map_x = int(coordinates[0] / 4) mouse_map_y = int(coordinates[1] / 2) mouse_map_x += camera.camera_x mouse_map_y += camera.camera_y line = tcod.line_iter(player.x, player.y, mouse_map_x, mouse_map_y) for coord in line: if coord[0] == player.x and coord[1] == player.y: continue cell_term_x, cell_term_y = camera.map_to_term_coord( coord[0], coord[1]) if coord[0] == mouse_map_x and coord[ 1] == mouse_map_y and game_map.fov[ coord[0], coord[1]] and not game_map.is_blocked( coord[0], coord[1]): terminal.color(terminal.color_from_argb(125, 0, 255, 0)) elif get_blocking_entities_at_location(entities, coord[0], coord[1]): terminal.color(terminal.color_from_argb(125, 255, 0, 0)) elif not game_map.fov[coord[0], coord[1]] or game_map.is_blocked( coord[0], coord[1]): terminal.color(terminal.color_from_argb(125, 255, 0, 0)) else: terminal.color(terminal.color_from_argb(125, 255, 255, 0)) terminal.put(x=cell_term_x, y=cell_term_y, c=0x3014) terminal.composition(terminal.TK_OFF) if targeting_item: function_kwargs = targeting_item.item.function_kwargs if function_kwargs: radius = function_kwargs.get("radius") if radius: for cell_x, cell_y in disk(mouse_map_x, mouse_map_y, radius, game_map.width, game_map.height): (cell_term_x, cell_term_y) = camera.map_to_term_coord( cell_x, cell_y) if cell_term_x and cell_term_y: # Omit cells outside of the terminal window if get_blocking_entities_at_location( entities, cell_x, cell_y) and game_map.fov[cell_x, cell_y]: terminal.color( terminal.color_from_argb(125, 0, 255, 0)) else: terminal.color( terminal.color_from_argb(125, 139, 0, 0)) terminal.put(x=cell_term_x, y=cell_term_y, c=0x3014) terminal.layer(RenderLayer.HUD.value) clear_layer() # HP bar render_bar(1, panel_y + 6, bar_width, 'HP', player.fighter.current_hp, player.fighter.max_hp, "red", "darker red") terminal.printf(1, panel_y + 7, f"Dungeon Level: {game_map.dungeon_level}") entity_names = get_names_under_mouse(coordinates, camera, entities, game_map) terminal.printf(1, panel_y, f"[color=white]{entity_names.title()}") terminal.layer(RenderLayer.HUD.value) line_y = panel_y + 1 for message in message_log.messages: terminal.color(terminal.color_from_name(message.color)) print_shadowed_text(message_log.x, line_y, message.text) line_y += 1 if game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): if game_state == GameStates.SHOW_INVENTORY: title = "INVENTORY – press key next to item to use it" elif game_state == GameStates.DROP_INVENTORY: title = "INVENTORY – press key next to item to drop it" inventory_menu(player, title).draw()
def main(): constants = get_constants() libtcod.console_set_custom_font( 'rexpaint_cp437_10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_ASCII_INROW) libtcod.console_init_root(constants['screen_width'], constants['screen_height'], title='MVP v0.0') con = libtcod.console_new(constants['screen_width'], constants['screen_height']) panel = libtcod.console_new(constants['screen_width'], constants['screen_height']) status = libtcod.console_new(constants['screen_width'], constants['screen_height']) player = None cursor = None entities = [] game_map = None message_log = None game_state = None turn_state = None event_queue = None player, cursor, entities, game_map, message_log, game_state, turn_state, event_queue = get_game_variables( constants) previous_game_state = game_state key = libtcod.Key() mouse = libtcod.Mouse() fov_recompute = True fov_map = initialize_fov(game_map) while not libtcod.console_is_window_closed(): libtcod.sys_check_for_event( libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse) if fov_recompute: recompute_fov(fov_map, player.location.x, player.location.y, constants['fov_radius'], constants['fov_light_walls'], constants['fov_algorithm']) entities_extended = entities.copy() entities_extended.append(cursor) render_all(con, panel, entities_extended, 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'], status, constants['status_height'], constants['status_width'], constants['status_x'], game_state, turn_state, player, cursor) libtcod.console_flush() clear_all(con, entities_extended) fov_recompute = False """ Handle the Player Actions. """ action = handle_keys(key, game_state) mouse_action = handle_mouse(mouse) if action or mouse_action or mouse.dx or mouse.dy: fov_recompute = True move = action.get('move') # Attempt to move. delta_speed = action.get( 'delta_speed' ) # Changes the max speed reached by the unit durnig autopathing movement. next_turn_phase = action.get( 'next_turn_phase') # Move to the next phase. reset_targets = action.get('reset_targets') # Reset targets. select = action.get( 'select') # A target has been selected via keyboard. show_weapons_menu = action.get( 'show_weapons_menu' ) # Show the weapons menu in order to choose a weapon to fire. weapons_menu_index = action.get( 'weapons_menu_index') # Choose an item from the weapons menu. look = action.get('look') # Enter the LOOK game state. exit = action.get('exit') # Exit whatever screen is open. fullscreen = action.get('fullscreen') # Set game to full screen. left_click = mouse_action.get('left_click') right_click = mouse_action.get('right_click') # TODO: Figure out where this should go. if look and game_state == GameStates.PLAYER_TURN: previous_game_state = game_state game_state = GameStates.LOOK cursor.cursor.turn_on(player) continue """ Handle the turn. """ if game_state == GameStates.ENEMY_TURN or game_state == GameStates.PLAYER_TURN: turn_results = [] active_entity = None # Choose the active entity. if not event_queue.empty(): entity_uuid = event_queue.fetch() for entity in entities: if entity.uuid == entity_uuid: entity.age += 1 active_entity = entity # This phase is not for the active_entity. if turn_state == TurnStates.UPKEEP_PHASE: for entity in entities: if entity.required_game_state == game_state: entity.reset() else: entity.reset(only_action_points=True) event_queue.register_list(entities) turn_state = TurnStates.PRE_MOVEMENT_PHASE # This phase is for the player only. if turn_state == TurnStates.PRE_MOVEMENT_PHASE: if game_state == GameStates.ENEMY_TURN: turn_state = TurnStates.MOVEMENT_PHASE else: # Change player max speed. if delta_speed: turn_results.append( player.propulsion.change_cruising_speed( delta_speed)) # Highlight the legal tiles. if not player.propulsion.legal_tiles: game_map.reset_highlighted() player.propulsion.calculate_movement_range(game_map) game_map.set_highlighted( player.propulsion.legal_tiles['red'], color=libtcod.dark_red) game_map.set_highlighted( player.propulsion.legal_tiles['green'], color=libtcod.light_green) game_map.set_highlighted( player.propulsion.legal_tiles['yellow'], color=libtcod.yellow) fov_recompute = True if player.propulsion.chosen_tile: player.propulsion.choose_tile( player.propulsion.chosen_tile, game_map) game_map.set_pathable(player.propulsion.path, color=libtcod.blue) fov_recompute = True # Left clicking choose path final destination. if left_click: game_map.reset_pathable() player.propulsion.path.clear() turn_results.append( player.propulsion.choose_tile((mouse.cx, mouse.cy), game_map)) game_map.set_pathable(player.propulsion.path, color=libtcod.blue) fov_recompute = True # Right clicking deconstructs path. if right_click: game_map.reset_pathable() player.propulsion.chosen_tile = None player.propulsion.reset() # Attempt to end the turn. if next_turn_phase: if not player.propulsion.chosen_tile: # Phase doesn't end, but goes through one last time with a chosen tile. player.propulsion.chosen_tile = (player.location.x, player.location.y) else: next_turn_phase = False player.propulsion.update_speed() turn_state = TurnStates.MOVEMENT_PHASE # This phase is the big one. All entities act, except for projectiles of this game_state. # This phase ends when all entities have spent their action_points. if turn_state == TurnStates.MOVEMENT_PHASE: if active_entity: if active_entity is player: if active_entity.required_game_state == game_state: turn_results.extend( movement(player, entities, game_map)) if next_turn_phase: next_turn_phase = False player.action_points = 0 elif not active_entity.required_game_state == game_state: turn_results.extend( player.arsenal.fire_active_weapon()) else: turn_results.extend( active_entity.ai.take_turn(game_state, turn_state, entities, game_map)) if active_entity is None and event_queue.empty(): turn_state = TurnStates.POST_MOVEMENT_PHASE # This phase is not for the active_entity. if turn_state == TurnStates.POST_MOVEMENT_PHASE: game_map.reset_flags() fov_recompute = True turn_state = TurnStates.PRE_ATTACK_PHASE # This phase is not for the active_entity. # Entities have their action points refilled in order to use their weapons. if turn_state == TurnStates.PRE_ATTACK_PHASE: if game_state == GameStates.PLAYER_TURN: message_log.add_message( Message( 'Press f to target. Press ESC to stop targeting. Enter to change phase. Press r to choose new targets.', libtcod.orange)) for entity in entities: entity.reset(only_action_points=True) event_queue.register_list(entities) turn_state = TurnStates.ATTACK_PHASE # This phase is for active_entity of the required game_state. # They choose their weapons and targets. if turn_state == TurnStates.ATTACK_PHASE: if active_entity: if active_entity is player: if active_entity.required_game_state == game_state: if show_weapons_menu: previous_game_state = game_state game_state = GameStates.SHOW_WEAPONS_MENU if reset_targets: active_entity.arsenal.reset() message_log.add_message( Message('Targeting reset.', libtcod.light_blue)) game_map.reset_flags() fov_recompute = True if next_turn_phase: next_turn_phase = False active_entity.action_points = 0 else: active_entity.action_points = 0 elif active_entity.ai: turn_results.extend( active_entity.ai.take_turn(game_state, turn_state, entities, game_map)) if active_entity is None and event_queue.empty(): turn_state = TurnStates.POST_ATTACK_PHASE # This phase is not for active_entity. if turn_state == TurnStates.POST_ATTACK_PHASE: fov_recompute = True game_map.reset_flags() w = player.arsenal.get_active_weapon() if w is not None: for x, y in w.targets: erase_cell(con, x, y) turn_state = TurnStates.CLEANUP_PHASE # This phase is useless? if turn_state == TurnStates.CLEANUP_PHASE: if game_state == GameStates.PLAYER_TURN: game_state = GameStates.ENEMY_TURN elif game_state == GameStates.ENEMY_TURN: game_state = GameStates.PLAYER_TURN turn_state = TurnStates.UPKEEP_PHASE # Communicate turn_results. for result in turn_results: message = result.get( 'message') # Push a message to the message log. dead_entity = result.get('dead') # Kill the entity. remove_entity = result.get( 'remove') # Remove the entity (leaves no corpse behind). new_projectile = result.get( 'new_projectile') # Creates a new projectile. end_turn = result.get('end_turn') # End the turn. if result: fov_recompute = True if dead_entity: if dead_entity == player: message, game_state = kill_player(dead_entity) else: message = kill_enemy(dead_entity) event_queue.release(dead_entity) if new_projectile: entity_type, location, target, APs, required_game_state = new_projectile projectile = factory.entity_factory( entity_type, location, entities) projectile.action_points = APs xo, yo = location xd, yd = target ### Path extending code. # Determine maximums, based on direction. if xd - xo > 0: max_x = game_map.width else: max_x = 0 if yd - yo > 0: max_y = game_map.height else: max_y = 0 # Determine the endpoints. if xo - xd == 0: # The projectile moves vertically. y_end = max_y else: y_end = yo - (xo - max_x) * (yo - yd) // (xo - xd) if yo - yd == 0: # The projectile moves horizontally. x_end = max_x else: x_end = xo - (yo - max_y) * (xo - xd) // (yo - yd) # Ensure the enpoints are in the map. if not 0 < y_end < game_map.width: y_end = max_y if not 0 < x_end < game_map.height: x_end = max_x projectile.projectile.path = list( libtcod.line_iter(int(x_end), int(y_end), xo, yo)) projectile.projectile.path.pop( ) # Don't start where the shooter is standing. projectile.projectile.path.pop( 0) # Don't end out of bounds. projectile.required_game_state = required_game_state event_queue.register(projectile) if remove_entity: entities.remove(remove_entity) if end_turn: active_entity.action_points = 0 if message: message_log.add_message(Message(message, libtcod.yellow)) # Refill the queue with the active_entity, if appropriate. if active_entity and active_entity.action_points > 0: event_queue.register(active_entity) """ Handle the targeting cursor. """ if game_state == GameStates.TARGETING: targeting_results = [] if move: cursor.cursor.move(move, player.arsenal.get_active_weapon(), player) elif select: cursor.cursor.target(game_map, player.arsenal.get_active_weapon()) fov_recompute = True for result in targeting_results: message = result.get('message') target = result.get('target') if message: message_log.add_message(Message(message, libtcod.red)) if target: pass """ Handle the Show Weapons Menu state. """ if game_state == GameStates.SHOW_WEAPONS_MENU: menu_results = [] if weapons_menu_index is not None and weapons_menu_index < len( player.arsenal.weapons ) and previous_game_state != GameStates.PLAYER_DEAD: menu_results.append( player.arsenal.weapons[weapons_menu_index].activate()) cursor.cursor.turn_on( player, player.arsenal.weapons[weapons_menu_index].targets) game_state = GameStates.TARGETING for result in menu_results: message = result.get('message') if message: message_log.add_message( Message(message, libtcod.dark_green)) """ Handle the Look game state. """ if game_state == GameStates.LOOK: if move: dx, dy = move cursor_movement(cursor, dx, dy) """ Handle the death of the player. """ if game_state == GameStates.PLAYER_DEAD: print('You have died.') return True """ Handle commands that activate regardless of game state. """ if exit: if game_state == GameStates.SHOW_WEAPONS_MENU or game_state == GameStates.LOOK: game_state = previous_game_state cursor.cursor.turn_off() elif game_state == GameStates.TARGETING: cursor.cursor.turn_off() fov_recompute = True game_state = previous_game_state else: return True if fullscreen: libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
def choose_tile(self, tile, game_map): """ Attempt to choose a tile. """ ### Choose tile. self.chosen_tile = tile xo, yo = self.owner.location.x, self.owner.location.y xd, yd = tile ### Check to see if it's in the legal tile range. legal_tiles = [] legal_tiles.extend(self.legal_tiles.get('green')) legal_tiles.extend(self.legal_tiles.get('yellow')) legal_tiles.extend(self.legal_tiles.get('red')) if legal_tiles and tile in legal_tiles: path = list(libtcod.line_iter(xo, yo, xd, yd)) if path: self.path = fill_in_line(path) return {} ### Adjust speed. # Calculate true_angle xc, yc = xo + self.speed_x, yo + self.speed_y # This is the center of the legal_tiles grid. dx = xd - xc dy = yd - yc true_angle = math.atan2(dy, dx) true_angle = int(true_angle * 180 / math.pi) + 180 # Now loop over all possible impulses in order to find the closest angle. available_impulse = self.max_speed - self.speed if available_impulse > self.max_impulse: available_impulse = self.max_impulse closest_angle = 999 closest_impulse = 0 for impulse_x in range(-available_impulse, available_impulse + 1): for impulse_y in range(-available_impulse, available_impulse + 1): # Try every combo of impulse available. if (abs(impulse_x) + abs(impulse_y) <= available_impulse and abs(self.speed_x + impulse_x) + abs(self.speed_y + impulse_y) <= self.cruising_speed): # If it's not more than what's available, see what the angle would be. new_angle = math.atan2(impulse_y, impulse_x) new_angle = int(new_angle * 180 / math.pi) + 180 if (abs(new_angle - true_angle) < abs(closest_angle - true_angle) or (abs(new_angle - true_angle) == abs(closest_angle - true_angle) and abs(impulse_x) + abs(impulse_y) > closest_impulse)): # If the angle is close, then we have a new coordinate. closest_angle = new_angle closest_impulse = abs(impulse_x) + abs(impulse_y) dx = impulse_x dy = impulse_y new_xd, new_yd = xo + dx + self.speed_x, yo + dy + self.speed_y path = list(libtcod.line_iter(xo, yo, new_xd, new_yd)) if path: self.path = fill_in_line(path) return {}
def can_see(self, actor_pos, target_pos): for pos in tcod.line_iter(actor_pos[0], actor_pos[1], target_pos[0], target_pos[1]): if any(t in props.blocks_vision for t in props.things_at[pos]): return False return True