# Game imports from utils.serializable import Serializable from utils.dice import Dice from events.event import Event from entities.player import Player from entities.npc import NPC from utils.item import Item from events.turn import TurnAction # Native imports 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): new_player = Player(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item) self.active_players.append(new_player) return new_player.id 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): new_npc = NPC(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item) self.active_npcs.append(new_npc) return new_npc.id 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) self.active_items.append(new_item) return new_item.id 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(int(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.get_id()) 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() return int(dmg_amount) 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?