from serializable import Serializable from dice import Dice from events.event import Event from entities.player import Player from entities.npc import NPC from items.item import Item from events.turn import TurnAction from enum import IntEnum class Stat(IntEnum): STRENGTH = 0, INTELLIGENCE = 1, DEXTERITY = 2, WISDOM = 3, CHARISMA = 4, HP = 5, ARMOR = 6, SPEED = 7 class Game(Serializable): def __init__(self, seed:int=42): self.active_players:list[Player] = [] self.active_npcs:list[NPC] = [] self.active_items:list[Item] = [] self.events:list[Event] = [] self.turn_order:list[str] = [] self.turn_idx = 0 def get_entity(self, entity_id:str): for entity in (self.active_players + self.active_npcs): if entity.id == entity_id: return entity raise ReferenceError(f"The player #{entity_id} doesn't exist!") def get_player(self, player_id:str): for player in self.active_players: if player.id == player_id: return player raise ReferenceError(f"The player #{player_id} doesn't exist!") def create_player(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item=None): new_player = Player(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item) self.active_players.append(new_player) def get_npc(self, npc_id:str): for npc in self.active_npcs: if npc.id == npc_id: return npc raise ReferenceError(f"The npc #{npc_id} doesn't exist!") def create_npc(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item=None): new_npc = NPC(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item) self.active_players.append(new_npc) def get_item(self, item_id:str): for item in self.active_items: if item.id == item_id: return item raise ReferenceError(f"The item #{item_id} doesn't exist!") def create_item(self,name:str, description:str, stat_modifier:dict[str, int]): new_item = Item(name, description, stat_modifier) def add_player(self, new_player:Player): if new_player.id in [player.id for player in self.active_players]: raise ReferenceError(f"Player id #{new_player.id} already present in game!") self.active_players.append(new_player) def add_ncp(self, new_ncp:NPC): if new_ncp.id in [npc.id for npc in self.active_npcs]: raise ReferenceError(f"NCP id #{new_ncp.id} already present in game!") self.active_npcs.append(new_ncp) def add_item(self, new_item:Item): if new_item.id in [item.id for item in self.active_items]: raise ReferenceError(f"Item id #{new_item.id} already present in game!") self.active_items.append(new_item) def add_item_to_entity(self, item_id:str, entity_id:str): item = self.get_item(item_id) entity = self.get_entity(entity_id) entity.set_equipped_item(item) self.active_items.remove(item) def get_current_event(self): idx = len(self.events) - 1 if idx < 0: raise IndexError("There is no event yet, you should create one!") return self.events[idx] def add_event(self, new_event:Event): self.events.append(new_event) self.update_turn_order() self.turn_idx = 0 def check_turn_ended(self): if self.turn_idx == len(self.turn_order)-1: # Turn end self.get_current_event().add_turn() self.update_turn_order() def update_turn_order(self): active_entities = self.get_current_event().entities entity_list = [self.get_entity(id) for id in active_entities] entity_list = self.sort_entities_by_speed(entity_list) self.turn_order = [entity.id for entity in entity_list] self.turn_idx = 0 # Selection sort based on entity's speed def sort_entities_by_speed(self, entity_list:list[NPC|Player]): n = len(entity_list) for i in range(n): max_idx = i # Current fastest entity for j in range(i+1, n): entity_max = entity_list[max_idx] entity_j = entity_list[j] if entity_max.speed < entity_j.speed: max_idx = j # New Maximum Speed for j entity # Swapping current index i with the new maximum in i+1, n-1 entity_list[i], entity_list[max_idx] = entity_list[max_idx], entity_list[i] return entity_list def kill_entity(self, entity_id:str): dead_entity = self.get_entity(entity_id) self.get_current_event().remove_entity(entity_id=entity_id) if isinstance(dead_entity, NPC): self.active_npcs.remove(dead_entity) if isinstance(dead_entity, Player): self.active_players.remove(dead_entity) def is_turn_coherent(self, entity_id:str): return self.turn_order[self.turn_idx] == entity_id def deal_damage(self, src:str, target:str, roll:int, stat:int, description:str): if not self.is_turn_coherent(src): raise ReferenceError(f"Entity #{src} tried performing an action while it was #{self.turn_order[self.turn_idx]}'s turn!") src_entity = self.get_entity(src) target_entity = self.get_entity(target) dmg_amount = 0 if stat == Stat.STRENGTH: # Strength damages physical and long closed range weapons dmg_amount += (roll * 5 / target_entity.get_armor()) * src_entity.get_strength() elif stat == Stat.INTELLIGENCE: # Using magic dmg_amount += (roll * 5 / target_entity.get_armor()) * src_entity.get_intelligence() elif stat == Stat.DEXTERITY: # Using daggers, bows or throws dmg_amount += (roll * 5 / target_entity.get_armor()) * src_entity.get_dexterity() target_entity.deal_damage(dmg_amount) additional_info = f"; {target_entity.name}({target_entity.get_id()}) took {dmg_amount} damage, {target_entity.get_hp()}hp remaining!" if target_entity.get_hp() <= 0: self.kill_entity(target_entity) additional_info = f"; {target_entity.name}({target_entity.get_id()}) took {dmg_amount} damage, {target_entity.name} died!" turn_finished = self.get_current_event().perform_action(TurnAction.DAMAGE, src, description=description+additional_info) self.turn_idx += 1 if turn_finished: self.update_turn_order() def modifying_stat(self, src:str, value:int, stat:int, description:str): if not self.is_turn_coherent(src): raise ReferenceError(f"Entity #{src} tried performing an action while it was #{self.turn_order[self.turn_idx]}'s turn!") src_entity = self.get_entity(src) match(stat): case Stat.STRENGTH: src_entity.set_strength(src_entity.strength + value) case Stat.INTELLIGENCE: src_entity.set_intelligence(src_entity.intelligence + value) case Stat.DEXTERITY: src_entity.set_dexterity(src_entity.dexterity + value) case Stat.WISDOM: src_entity.set_wisdom(src_entity.wisdom + value) case Stat.CHARISMA: src_entity.set_charisma(src_entity.charisma + value) case Stat.HP: src_entity.set_hp(src_entity.hp + value) case Stat.ARMOR: src_entity.set_armor(src_entity.armor + value) case Stat.SPEED: src_entity.set_speed(src_entity.speed + value) turn_finished = self.get_current_event().perform_action(TurnAction.STATS, src, description=description) self.turn_idx += 1 if turn_finished: self.update_turn_order() def simple_action(self, src:str, description:str): if not self.is_turn_coherent(src): raise ReferenceError(f"Entity #{src} tried performing an action while it was #{self.turn_order[self.turn_idx]}'s turn!") turn_finished = self.get_current_event().perform_action(TurnAction.BASIC, src, description=description) self.turn_idx += 1 if turn_finished: self.update_turn_order() #TODO: Add State Summary as Resource?