def main(): try: sequencer.begin_sequence(scene_1) except Exception: msg = exception_message(header='RUNTIME ERROR', log_handler=logger) msg += '\n\nSee the log for more details.' # Print message in red print_color('{RA}{FLred}', end='') print_color(msg, do_not_format=True)
def do_list(self, arg): """List your current moves. Usage: list [future commands]""" # Should be the same as in the Move shell print_color(self.fighter.string_moves( ignore_skills=True, ignore_items=True) ) print() self.updateCmdqueue(arg)
def do_stats(self, arg): """View your current stats. Usage: stats [future commands]""" print_color( self.fighter.battle_env.fightChartOne( self.fighter, color=cfg_engine.GAME_DISPLAY_STATS_COLOR_MODE )[0] ) print() self.updateCmdqueue(arg)
def do_display(self, arg): """Display the current battle. Usage: display [future commands]""" print_color(self.fighter.battle_env.fightChartTwo( self.fighter, self.opponent, topMessage='<--', tabs=cfg_engine.GAME_DISPLAY_USE_TABS, color_mode=cfg_engine.GAME_DISPLAY_STATS_COLOR_MODE) ) print() self.updateCmdqueue(arg)
def a_turn(): nonlocal move_result nonlocal statLogA nonlocal statLogB effects_messages_durations = a.update_status_effect_durations() effects_messages_values = a.update_status_effect_values() if effects_messages_values: autoplay_pause() a.print_status_effect_messages( effects_messages_values, get_status_effects_print_delay()) if autoplay: print() elif turn > 1: # Only triggers after first turn so battle starts immediately autoplay_pause() if is_dead(a) or is_dead(b): return False # Print a chart of both fighters' stats print(fight_chart(topMessage='<--')) autoplay_pause() if effects_messages_durations: a.print_status_effect_messages( effects_messages_durations, get_status_effects_print_delay()) print() if cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: # Show damage difference statLogA = self.battle_stats_log(a) statLogB = self.battle_stats_log(b) # Print user moves if enabled if cfg_engine.GAME_DISPLAY_PRINT_MOVES: print_color(a.string_moves()) # Fighter attacks move_result = a.move(b, do_not_send=stop_after_move) if not cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: # Show regeneration statLogA = self.battle_stats_log(a) if not is_dead(a) and not is_dead(b): a.update_stats() return True return False
def input_gamemode(gamemodes): print_color(f"Gamemodes: {', '.join(gamemodes[1:])}") gamemodes = [gm.casefold() for gm in gamemodes] gamemode = input_color( 'Type a gamemode (case-insensitive) to play it,\n' f'or nothing to skip. {cfg_engine.GAME_SETUP_INPUT_COLOR}').casefold() while gamemode not in gamemodes: gamemode = input_color( 'Unknown gamemode; check spelling: ' f'{cfg_engine.GAME_SETUP_INPUT_COLOR}').casefold() logger.debug(f'Got gamemode from user: {gamemode!r}') return gamemode
def print_end_message(end_message): speed = cfg_engine.GAME_DISPLAY_SPEED time.sleep(0.75 / speed) print_color(end_message[0]) time.sleep(0.5 / speed) print_color(end_message[1]) time.sleep(0.25 / speed) print_color(end_message[2]) time.sleep(0.8 / speed) print_color(end_message[3])
def b_turn(): nonlocal move_result nonlocal statLogA nonlocal statLogB effects_messages_durations = b.update_status_effect_durations() effects_messages_values = b.update_status_effect_values() autoplay_pause() if effects_messages_values: b.print_status_effect_messages( effects_messages_values, get_status_effects_print_delay()) if autoplay: print() if is_dead(a) or is_dead(b): return False print(fight_chart(topMessage='-->')) autoplay_pause() if effects_messages_durations: b.print_status_effect_messages( effects_messages_durations, get_status_effects_print_delay()) print() if cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: statLogA = self.battle_stats_log(a) statLogB = self.battle_stats_log(b) if cfg_engine.GAME_DISPLAY_PRINT_MOVES: print_color(b.string_moves()) move_result = b.move(a, do_not_send=stop_after_move) if not cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: statLogB = self.battle_stats_log(b) if not is_dead(a) and not is_dead(b): b.update_stats() return True return False
def default(self, line): """Searches for the move.""" inputMove = line.lower().title() moveFind = self.fighter.find_move( {'name': inputMove}, ignore_movetypes=True, ignore_skills=True, ignore_items=True, exactSearch=cfg_interface.MOVES_REQUIRE_EXACT_SEARCH, detailedFail=True) # If search worked, print info about the move if not isinstance(moveFind, (type(None), BoolDetailed)): namespace = fighter_stats.ALL_STAT_INFOS.copy() namespace['move'] = moveFind print_color(moveFind['description'], namespace=namespace) print() return # Else request for a move again elif moveFind is None: # Search failed (detailedFail=False) message = 'Did not find move' if 'returnTo' in self.namespace: print_color(message) return else: self.prompt = message + ', type again: ' elif isinstance(moveFind, BoolDetailed): # Search failed (detailedFail=True) if moveFind.name == 'TooManyResults': moveCount = int(moveFind.description.split()[1]) message = f'Found {moveCount:,} different moves' if 'returnTo' in self.namespace: print_color(message) return else: self.prompt = message + ', type again: ' elif moveFind.name == 'NoResults': message = 'Did not find move' if 'returnTo' in self.namespace: print_color(message) return else: self.prompt = message + ', type again: ' else: raise RuntimeError('MoveInfoShell from moveFind in player_move ' f'returned unknown object {moveFind!r}')
def default(self, line): """Called when a command prefix is not recognized.""" print_color('{Fyello}Unknown command')
def help_move(self): print_color(""" Type the move you want to use. Use "list" to display your available moves. """)
def default(self, line): """Searches for the move.""" inputMove = line.lower().title() moveFind, unsatisfactories = self.fighter.find_move( {'name': inputMove}, ignore_skills=True, ignore_items=True, exactSearch=cfg_interface.MOVES_REQUIRE_EXACT_SEARCH, detailedFail=True, showUnsatisfactories=True) # If search worked, check for unsatisfactories if not isinstance(moveFind, (type(None), BoolDetailed)): if unsatisfactories: # Missing dependencies; explain then request another move reasonMessage = f'Could not use {moveFind}; ' \ + moveFind.parse_unsatisfactories( unsatisfactories) if 'returnTo' in self.namespace: print_color(reasonMessage) self.namespace['shell_result'] = False return self.exit() else: self.prompt = reasonMessage + '\nPick another move: ' return else: # All dependencies satisfied; use the move self.namespace['shell_result'] = moveFind if 'returnTo' in self.namespace: self.namespace['newMoveCMD'] = '' else: self.namespace['newMoveCMD'] = 'move' return self.exit() # Else request for a move again elif moveFind is None: # Search failed (detailedFail=False) if 'returnTo' in self.namespace: print_color('Did not find move') self.namespace['shell_result'] = False return self.exit() else: self.prompt = 'Did not find move, type again: ' elif isinstance(moveFind, BoolDetailed): # Search failed (detailedFail=True) if moveFind.name == 'TooManyResults': moveCount = int(moveFind.description.split()[1]) message = f'Found {moveCount:,} different moves' if 'returnTo' in self.namespace: print_color(message) self.namespace['shell_result'] = False return self.exit() else: self.prompt = message + ', type again: ' elif moveFind.name == 'NoResults': message = 'Did not find move' if 'returnTo' in self.namespace: print_color(message) self.namespace['shell_result'] = False return self.exit() else: self.prompt = message + ', type again: ' else: raise RuntimeError('moveFind in player_move returned ' f'unknown object {moveFind!r}')
def begin_battle(self, a, b, *, statLogA=None, statLogB=None, starting_turn=2, autoplay=2, return_end_message=True, stop_after_move=False): """ Args: a (Fighter) b (Fighter): The two fighters battling each other. statLogA (Optional[List[dict]]) statLogB (Optional[List[dict]]): A stat log generated by self.battle_stats_log() for their respective fighters. This info is used to calculate what stats have changed. starting_turn (int): The starting turn. If uneven, fighter B will move first. By default the starting turn is 2 so if one wins and `return_end_message` is True, it will show that the battle took one turn (`turn // 2`). autoplay (Autoplay): Autoplay.INSTANT: Disables pauses. Autoplay.SLEEP: Pauses using time.sleep. Autoplay.INPUT: Pauses using input(). return_end_message (bool): If True, a list of color-formatted end messages is returned and can be printed out with `self.print_end_message`. Otherwise, returns None. If you know you will not print the default end message, set this to False so the messages do not get generated. stop_after_move (bool): If True, the next Fighter to move will not trigger `target.move_receive` but instead this method returns the results of `sender.move(do_not_send=True)`. `statLogA`, `statLogB`, and `turn` values are included. Note that `turn` is assigned to the "starting_turn" key to simplify using the return value as a starred expression for this method. Creating a custom code loop: def step_battle(results=None): results = {} if results is None else results return begin_battle(a, b, stop_after_move=True, **results) results = step_battle() while isinstance(results, dict): move_result = results.pop('move_result') if move_result is None: # Move failed due to unsatisfied requirements results = step_battle(results) continue # <Your code here> results = step_battle(results) Returns: dict: Returned when `stop_after_move` and a Fighter moves. See Args for more info. list: When `return_end_message`, this list contains 4 lines: "Number of Turns: {turn}" Fighter A's health: {hp} Fighter B's health: {hp} "The winner is {winner}!" None: Returned when return_end_message is False. """ def fight_chart(*, topMessage, color_mode=None): if color_mode is None: color_mode = cfg_engine.GAME_DISPLAY_STATS_COLOR_MODE return self.fightChartTwo(a, b, statLogA, statLogB, statsToShow=self.stats_to_show, topMessage=topMessage, tabs=cfg_engine.GAME_DISPLAY_USE_TABS, color_mode=color_mode) def autoplay_pause(): if autoplay == Autoplay.INSTANT: print() elif autoplay == Autoplay.SLEEP: if a.is_player or b.is_player: # If there is one/two players, pause AUTOPLAY seconds util.pause(cfg_engine.GAME_AUTOPLAY_NORMAL_DELAY / cfg_engine.GAME_DISPLAY_SPEED) else: # If two AIs are fighting, pause FIGHT_AI seconds util.pause(cfg_engine.GAME_AUTOPLAY_AI_DELAY / cfg_engine.GAME_DISPLAY_SPEED) print() elif autoplay == Autoplay.INPUT: input() else: raise TypeError( 'expected autoplay argument to be an Autoplay enum ' f'but received {autoplay!r}') def cannotMove(fighter): for effect in fighter.status_effects: if 'noMove' in effect: return True return False def get_status_effects_print_delay(): if autoplay == Autoplay.INSTANT: return 0 elif autoplay == Autoplay.SLEEP: if a.is_player or b.is_player: return (cfg_engine.GAME_AUTOPLAY_NORMAL_DELAY / cfg_engine.GAME_DISPLAY_SPEED) else: return (cfg_engine.GAME_AUTOPLAY_AI_DELAY / cfg_engine.GAME_DISPLAY_SPEED) elif autoplay == Autoplay.INPUT: return None else: raise TypeError( 'expected autoplay argument to be an Autoplay enum ' f'but received {autoplay!r}') def is_dead(fighter): return hasattr(fighter, 'hp') and fighter.hp <= 0 logger.info('Starting fight against ' f'{a.name_decolored} and {b.name_decolored}') turn = starting_turn move_result = None statLogA = self.battle_stats_log(a) if statLogA is None else statLogA statLogB = self.battle_stats_log(b) if statLogB is None else statLogB def a_turn(): nonlocal move_result nonlocal statLogA nonlocal statLogB effects_messages_durations = a.update_status_effect_durations() effects_messages_values = a.update_status_effect_values() if effects_messages_values: autoplay_pause() a.print_status_effect_messages( effects_messages_values, get_status_effects_print_delay()) if autoplay: print() elif turn > 1: # Only triggers after first turn so battle starts immediately autoplay_pause() if is_dead(a) or is_dead(b): return False # Print a chart of both fighters' stats print(fight_chart(topMessage='<--')) autoplay_pause() if effects_messages_durations: a.print_status_effect_messages( effects_messages_durations, get_status_effects_print_delay()) print() if cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: # Show damage difference statLogA = self.battle_stats_log(a) statLogB = self.battle_stats_log(b) # Print user moves if enabled if cfg_engine.GAME_DISPLAY_PRINT_MOVES: print_color(a.string_moves()) # Fighter attacks move_result = a.move(b, do_not_send=stop_after_move) if not cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: # Show regeneration statLogA = self.battle_stats_log(a) if not is_dead(a) and not is_dead(b): a.update_stats() return True return False def b_turn(): nonlocal move_result nonlocal statLogA nonlocal statLogB effects_messages_durations = b.update_status_effect_durations() effects_messages_values = b.update_status_effect_values() autoplay_pause() if effects_messages_values: b.print_status_effect_messages( effects_messages_values, get_status_effects_print_delay()) if autoplay: print() if is_dead(a) or is_dead(b): return False print(fight_chart(topMessage='-->')) autoplay_pause() if effects_messages_durations: b.print_status_effect_messages( effects_messages_durations, get_status_effects_print_delay()) print() if cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: statLogA = self.battle_stats_log(a) statLogB = self.battle_stats_log(b) if cfg_engine.GAME_DISPLAY_PRINT_MOVES: print_color(b.string_moves()) move_result = b.move(a, do_not_send=stop_after_move) if not cfg_engine.GAME_DISPLAY_SHOW_STAT_DIFFERENCE: statLogB = self.battle_stats_log(b) if not is_dead(a) and not is_dead(b): b.update_stats() return True return False def game_loop(): nonlocal turn someone_has_moved = False while True: if turn % 2 == 0: continue_turn = a_turn() someone_has_moved = True turn += 1 else: # Skip to fighter B's turn continue_turn = True if not continue_turn or stop_after_move and someone_has_moved: # Either a_turn() says a fighter has no health # or stop_after_move is True and fighter A has moved break # Repeat for B if turn % 2 != 0: continue_turn = b_turn() someone_has_moved = True turn += 1 else: # Skip to fighter A's turn continue_turn = True if not continue_turn or stop_after_move and someone_has_moved: # Either b_turn() says a fighter has no health # or stop_after_move is True and fighter B has moved break print() if not is_dead(a) and not is_dead(b): game_loop() if not is_dead(a) and not is_dead(b) and stop_after_move: # Return results of move since `stop_after_move` is True # along with the info required to continue `begin_battle` return { 'move_result': move_result, 'starting_turn': turn, 'statLogA': statLogA, 'statLogB': statLogB } # Semantically, one turn is where both fighters move turn //= 2 # Print results winner = a if not is_dead(a) and is_dead(b) \ else b if is_dead(a) and not is_dead(b) \ else None # Show the stat change only for the loser if winner is a: statLogA = None elif winner is b: statLogB = None else: # No winner; don't show any stat change statLogA = statLogB = None fightChart = fight_chart(topMessage='END') fightChart_nocolor = fight_chart(topMessage='END', color_mode=0) logFightChart = '\n'.join(fightChart_nocolor.split('\n')[1:]) logger.info(f'Fight ended in {turn} turn{plural(turn)}:\n' f'{logFightChart}\n') # add green color to the top message and print it autoplay_pause() print_color(fightChart.replace('END', '{FLgree}END{RA}')) print() if return_end_message: end_message = [] end_message.append(f"Number of Turns: {turn}") a_win_color = (ColoramaCodes.FLgree if a.hp > 0 else ColoramaCodes.FLred) b_win_color = (ColoramaCodes.FLgree if b.hp > 0 else ColoramaCodes.FLred) end_message.append( format_color( "{a}'s {a.stats['hp'].ext_full.title()}: " '{a_win_color}{a.hp}', namespace=locals())) end_message.append( format_color( "{b}'s {b.stats['hp'].ext_full.title()}: " '{b_win_color}{b.hp}', namespace=locals())) winner_str = str(winner) if winner is not None else 'nobody' end_message.append(format_color(f'The winner is {winner_str}!')) return end_message
def missile_evasion(evader, missile, side='left'): """Start a missile evasion. Args: evader (Fighter): The fighter evading the missile. missile (str): The move that the missile is being used from. side (Union["left", "right"]): The fighter's side to display on. The missile moves first regardless. """ missile_type = missile['missile_type'] speed = missile['missile_speed'] field_of_regard = missile['field_of_regard'] track_rate = missile['track_rate'] base_damage = missile['base_damage'] blast_radius = missile['blast_radius'] missile_fighter = engine.Fighter(name='{FLmage}Missile{RA}', stats=stats.get_defaults( stats.missile_stats), moves=moves.missile_moves, counters={}, AI=ai.MissileAI()) with engine.BattleEnvironment(fighters=(evader, missile_fighter)) as battle: def step_battle(results=None): results = {} if results is None else results return battle.begin_battle(a, b, stop_after_move=True, autoplay=Autoplay.SLEEP, **results) results = {'starting_turn': 2 + int(side == 'left')} if side == 'left': a, b = evader, missile_fighter elif side == 'right': a, b = missile_fighter, evader else: raise ValueError(f'Unknown side {side!r}') results = step_battle(results) while isinstance(results, dict): move_result = results.pop('move_result') if move_result is None: results = step_battle(results) continue sender, move = move_result['sender'], move_result['move'] if sender == evader: effect = move['effect'] if isinstance(effect, dict): actual_effect = effect.get(missile_type, 0) else: actual_effect = effect missile_fighter.er += actual_effect elif sender == missile_fighter: missile_fighter.di -= speed if missile_fighter.er < field_of_regard: # Missile can still see evader weight = max( 0, min( 1 - missile_fighter.er / (1.5 * field_of_regard), 1 - (missile_fighter.er / field_of_regard)**10, )) error_correction = int(track_rate * weight) missile_fighter.er -= error_correction if missile_fighter.di == 0: # Detonation sleep = (cfg_engine.GAME_AUTOPLAY_AI_DELAY / cfg_engine.GAME_DISPLAY_SPEED) print_color('\nThe missile makes its last adjustments!', end='\n\n\n') time.sleep(sleep) print(battle.fightChartTwo( a, b, statLogA=results['statLogA'], statLogB=results['statLogB'], tabs=cfg_engine.GAME_DISPLAY_USE_TABS, color_mode=cfg_engine.GAME_DISPLAY_STATS_COLOR_MODE), end='\n\n\n') value = -int(base_damage * (1 - max(0, missile_fighter.er / blast_radius))) if value < 0: print_typewriter('... Hit!', sleep_char=SLEEP_CHAR_DELAY_NORMAL, sleep_char_specifics={'.': 0.5}) else: print_typewriter('... Miss!', sleep_char=SLEEP_CHAR_DELAY_NORMAL, sleep_char_specifics={'.': 0.5}) return value else: target = b if sender == a else a target.print_move(sender, move, None, None, 'fastMessage') results = step_battle(results)