def run_program_loop(journal: Journal): """ Runs the program loop: asking the user what do to, until the user chooses to exit the app. :param journal: The Journal object to work with :return: """ cmd = "." while cmd and cmd != "x": cmd = ui.read_menu_input() if cmd == "l": ui.print_entries(journal.entries) elif cmd == "a": text = ui.read_add_entry_input() journal.add_entry(text) elif cmd == "x": journal.save(journal_file_name) exit(0)
class Character(Mob): # Stand-in numbers. LEVEL_CHART = { 1: 20, 2: 50, 3: 100, 4: 175, 5: 300, 6: 600, } def __init__(self, name, room): self.action_index = ACTION_INDEX self.base_defense = 10 self.attack = 0.01 self.xp = 0 self.level = 1 self.xp_to_level = self.set_xp_to_level() self.constitution = 10 self.dexterity = 10 self.intelligence = 10 self.strength = 10 self.find_traps = 50 self.turn_counter = 1 self.max_hp = self.set_max_hp() # TODO: Figure out how to calculate Stamina and Mana; # TODO: Implement stamina and mana drain from certain abilities. # TODO: Implement stamina and mana regen during advance_turn(). self.stamina = 0 self.mana = 0 self.inventory = Inventory() self.journal = Journal() self.explored_rooms = dict() self.cooldowns = dict() self.messages = None super().__init__(name, room, max_hp=self.max_hp) self.assign_room(room) def ability_mod(self, abil): ability = getattr(self, abil) return int((ability / 2) - 5) def add_messages(self, message): if self.messages is None: self.messages = [] self.messages.append(message) def advance_turn(self, mob=None): if mob is not None: if mob.current_hp >= 1: mob.attack_player(self) for corpse in Corpse._all: corpse.decay() for monster in Monster._all: monster.reduce_stun() self.check_level_up() self.reduce_cooldowns() self.turn_counter += 1 def assign_room(self, room): self.room = room if room.zone in self.explored_rooms: if room not in self.explored_rooms[room.zone]: self.explored_rooms[room.zone].append(room) else: self.explored_rooms[room.zone] = [room] def attack_target_mob(self, text_input, dam_mod=None, success_verb=None, fail_verb=None, outcome=f""): """ Check a player's attack on a mob and modify the journal appropriately. :param text_input: A string representing the intended target. :param dam_mod: A modifier when using a special attack. :param success_verb: A verb overriding the player's equipped weapon verb; used in special attacks. :param fail_verb: A verb overriding the player's equipped weapon verb; used in special attacks. :param outcome: An appendix to add to attacks if they have a special outcome (e.g. a target is stunned or on fire) :return: A namedtuple with 2 positions - (Bool: was hit successful, target object) """ # TODO: Time to kill bats and goblins is good (2-3 rounds, max end is 5-6 with bad luck). # TODO: They hit too frequently/do too much damage without being able to heal or mitigate damage. # TODO: Add in heal ability/potions (how to make it useful, but not too easy? - extended CD?) # TODO: Increase monster EXP to reduce levelling rate (too slow ATM). # TODO: Damage mitigation via armor (both reduce to-hit and reduce damage amount?) entry = f"" target, search_term = search(self, text_input, "room.mobs") Report = namedtuple("Report", ("success", "target")) if target: # Just going to have the player attack the first mob of that type in the room for now. I'll figure out how # I want to do selection later. mob = target[0] attack = attack_action(self, mob, dam_mod) if attack.hit: if success_verb: attack_verb = f"{success_verb} the {mob}" else: attack_verb = ( f"{self.equipped_weapon.main_hand.success_verb} the {mob} with your " f"{self.equipped_weapon.main_hand.name}") if attack.hit_type == "hit": entry += f"You {attack_verb}" elif attack.hit_type == "crit": entry += f"You savagely {attack_verb}" entry += f" for {attack.damage} damage" if mob.current_hp <= 0: self.xp += mob.xp entry += (f", killing it!\n" f"You gain {mob.xp} xp.") mob.kill_monster() else: entry += f"{outcome}.\n" else: if fail_verb: attack_verb = f"{fail_verb} the {mob}" else: attack_verb = ( f"{self.equipped_weapon.main_hand.fail_verb} the {mob} with your " f"{self.equipped_weapon.main_hand.name}") entry += f"You {attack_verb} but" if attack.hit_type == "miss": entry += (f" missed.") if attack.hit_type == "dodge": entry += (f" the {mob} dodged out of the way.") self.add_messages(entry) self.journal.add_entry(f"(Round {self.turn_counter}): " + entry) return Report(attack.hit, mob) else: self.add_messages(f"You do not see one of those.") return Report(False, None) def basic_attack(self, text_input): attack = self.attack_target_mob(text_input) if attack.target: if attack.target.current_hp >= 1: self.advance_turn(attack.target) else: self.advance_turn() else: self.advance_turn() def check_level_up(self): if self.xp >= self.xp_to_level: # Just gonna have hp double at the moment. self.level += 1 self.max_hp = self.set_max_hp() self.current_hp = self.max_hp self.xp_to_level = self.set_xp_to_level() ding = f"DING! You've leveled up to {self.level}!\n" self.journal.add_entry(ding) ding += (f"Your stats are now:\n" f"{self.view_stats_no_header()}") self.add_messages(ding) else: pass def check_cooldowns(self): cds = f"" first = True for abil, cd in self.cooldowns.items(): if first: cds += f"{abil.title()}: {cd} rounds\n" first = False else: cds += (f"{abil.title()}: {cd} rounds\n" f"----------------------") self.add_messages(cds) def check_turn(self): self.add_messages(f"Turn: {self.turn_counter}") def clear_messages(self): self.messages = None def examine_object(self, item): return examine_object(self, item) def examine_room(self): self.add_messages(self.room.examine()) def haste_amount(self): return self.equipped_weapon.main_hand.cd_redux def move_action(self, noun, text_input): # TODO: Add sneak action for Rogues. attack_of_opportunity(self, text_input) move_action(self, noun) self.advance_turn() def print_messages(self): messages = f"" for message in self.messages: messages += message + f"\n" messages += f"----------------------------\n" return print(messages) def read_journal(self): self.add_messages(self.journal.read_journal()) def reduce_cooldowns(self): for abil, cd in self.cooldowns.items(): if cd > 0: self.cooldowns[abil] = cd - 1 def roll_damage(self): base_damage = random.randint(self.equipped_weapon.main_hand.damage_min, self.equipped_weapon.main_hand.damage_max) bonus_stat = self.equipped_weapon.main_hand.main_stat bonus_amount = getattr(self, bonus_stat) total_damage = int(base_damage * (1 + (1 / 1 - math.exp(-bonus_amount / 255)))) return total_damage def set_max_hp(self): return (self.level * 20) + self.ability_mod("constitution") def set_xp_to_level(self): return int(80 * ((1 + .25)**self.level)) def show_world_map(self): self.room.zone.world_map.show_world_map(self) def show_zone_map(self): self.room.zone.show_zone_map(self) def view_stats_no_header(self): stats = (f"Class: {self.name}\n" f"Level: {self.level} ({self.xp}/{self.xp_to_level} xp.)\n" f"HP: {self.current_hp}/{self.max_hp}\n" f"Defense: {self.current_phys_defense()}") return stats def view_stats_header(self): stats = (f"Your Character Stats:\n" f"Class: {self.name}\n" f"Level: {self.level} ({self.xp}/{self.xp_to_level} xp.)\n" f"HP: {self.current_hp}/{self.max_hp}\n" f"Defense: {self.current_phys_defense()}") self.add_messages(stats) def view_equipped(self): equipped_armor = self.equipped_armor.__str__() equipped_weapons = self.equipped_weapon.__str__() self.add_messages(equipped_armor) self.add_messages(equipped_weapons) def view_inventory(self): self.add_messages(self.inventory.__str__()) def equip_item(self, text_input): attack_of_opportunity(self, text_input) equip_action(self, text_input) self.advance_turn() def loot_corpse(self): pass