From c6604657de14ee6a7393de6f04200b5d2ab56dda Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Sat, 25 Feb 2023 00:13:19 -0600 Subject: [PATCH 1/8] Update master_controller.py --- game/controllers/master_controller.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index b97a03b..9456148 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -1,12 +1,17 @@ from copy import deepcopy from game.common.action import Action +from game.common.avatar import Avatar from game.common.enums import * from game.common.player import Player +from game.common.stats import GameStats import game.config as config from game.utils.thread import CommunicationThread - +from game.controllers.movement_controller import MovementController from game.controllers.controller import Controller +from game.controllers.interact_controller import InteractController + + class MasterController(Controller): From e7fe342af16cdcaaff9e368c668ba9faea8122f1 Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Sat, 25 Feb 2023 02:35:25 -0600 Subject: [PATCH 2/8] Update master_controller.py --- game/controllers/master_controller.py | 41 ++++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 9456148..5d35199 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -1,4 +1,5 @@ from copy import deepcopy +import random from game.common.action import Action from game.common.avatar import Avatar @@ -12,19 +13,22 @@ from game.controllers.interact_controller import InteractController - - class MasterController(Controller): def __init__(self): super().__init__() self.game_over = False - + self.event_timer = GameStats.event_timer + self.event_times = (0, 0) self.turn = None self.current_world_data = None + self.movement_controller = MovementController() + self.interact_controller = InteractController() # Receives all clients for the purpose of giving them the objects they will control def give_clients_objects(self, clients): - pass + starting_positions = [[3, 3], [3, 9]] + for index, client in enumerate (clients): + client.avatar = Avatar(position=starting_positions[index]) # Generator function. Given a key:value pair where the key is the identifier for the current world and the value is # the state of the world, returns the key that will give the appropriate world information @@ -41,28 +45,43 @@ def game_loop_logic(self, start=1): # Receives world data from the generated game log and is responsible for interpreting it def interpret_current_turn_data(self, clients, world, turn): self.current_world_data = world + if turn == 1: + random.seed(world["seed"]) + self.event_times = random.randrange(162, 172), random.randrange(329, 339) # Receive a specific client and send them what they get per turn. Also obfuscates necessary objects. def client_turn_arguments(self, client, turn): - actions = Action() - client.action = actions + turn_action = Action() + client.action = turn_action # Create deep copies of all objects sent to the player + current_world = deepcopy(self.current_world_data["game_map"]) + copy_avatar = deepcopy(client.avatar) # Obfuscate data in objects that that player should not be able to see - - args = (self.turn, actions, self.current_world_data) + # Currently world data isn't obfuscated at all + args = (self.turn, turn_action, self.current_world_data) return args # Perform the main logic that happens per turn def turn_logic(self, clients, turn): - pass + for client in clients: + self.movement_controller.handle_actions(self.current_world_data["game_map"], client) + self.interact_controller.handle_actions(client, self.current_world_data["game_map"]) + # checks event logic at the end of round + self.handle_events(clients) + + def handle_events(self, clients): + # If it is time to run an event, master controller picks an event to run + if self.turn == self.event_times[0] or self.turn == self.event_times[1]: + self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) # Return serialized version of game def create_turn_log(self, clients, turn): data = dict() - + data['tick'] = turn + data['clients'] = [client.to_json() for client in clients] # Add things that should be thrown into the turn logs here - data['temp'] = None + data['game_map'] = self.current_world_data["game_map"].to_json() return data From b3dd9279bf45fdc063a033845edeb9c6ca925c3f Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:01:40 -0600 Subject: [PATCH 3/8] Update master_controller.py --- game/controllers/master_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 5d35199..596824c 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -27,7 +27,7 @@ def __init__(self): # Receives all clients for the purpose of giving them the objects they will control def give_clients_objects(self, clients): starting_positions = [[3, 3], [3, 9]] - for index, client in enumerate (clients): + for index, client in enumerate(clients): client.avatar = Avatar(position=starting_positions[index]) # Generator function. Given a key:value pair where the key is the identifier for the current world and the value is From ee52ae0360e97c163b4cc12d4b043936e53085cc Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:02:00 -0600 Subject: [PATCH 4/8] Update master_controller.py --- game/controllers/master_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 596824c..c4035dd 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -73,7 +73,7 @@ def turn_logic(self, clients, turn): def handle_events(self, clients): # If it is time to run an event, master controller picks an event to run if self.turn == self.event_times[0] or self.turn == self.event_times[1]: - self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) + self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) #eventtype.example is just a placeholder for now # Return serialized version of game def create_turn_log(self, clients, turn): From 41b21f39e08e3874c8ca1804010fb5dcc9d0874d Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Wed, 8 Mar 2023 13:56:23 -0600 Subject: [PATCH 5/8] added type hints, dont need game stats its specific for 1.7 --- game/controllers/master_controller.py | 26 ++++++++++++++------------ game/engine.py | 6 +++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index c4035dd..1f81569 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -5,7 +5,8 @@ from game.common.avatar import Avatar from game.common.enums import * from game.common.player import Player -from game.common.stats import GameStats +# from game.common.stats import GameStats - don't need it because it is specifically for 1.7 +# but keeping it for now in case it breaks something import game.config as config from game.utils.thread import CommunicationThread from game.controllers.movement_controller import MovementController @@ -16,19 +17,19 @@ class MasterController(Controller): def __init__(self): super().__init__() - self.game_over = False - self.event_timer = GameStats.event_timer - self.event_times = (0, 0) - self.turn = None - self.current_world_data = None - self.movement_controller = MovementController() - self.interact_controller = InteractController() + self.game_over: bool = False + # self.event_timer = GameStats.event_timer + self.event_times: tuple[int, int] | None = None + self.turn: int = 1 + self.current_world_data: GameBoard | None = None + self.movement_controller: MovementController = MovementController() + self.interact_controller: InteractController = InteractController() # Receives all clients for the purpose of giving them the objects they will control - def give_clients_objects(self, clients): - starting_positions = [[3, 3], [3, 9]] + def give_clients_objects(self, clients: list(Player), world: GameBoard): + # starting_positions = [[3, 3], [3, 9]] for index, client in enumerate(clients): - client.avatar = Avatar(position=starting_positions[index]) + client.avatar = Avatar(position=game_board[index]) # Generator function. Given a key:value pair where the key is the identifier for the current world and the value is # the state of the world, returns the key that will give the appropriate world information @@ -73,7 +74,8 @@ def turn_logic(self, clients, turn): def handle_events(self, clients): # If it is time to run an event, master controller picks an event to run if self.turn == self.event_times[0] or self.turn == self.event_times[1]: - self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) #eventtype.example is just a placeholder for now + self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) + # event type.example is just a placeholder for now # Return serialized version of game def create_turn_log(self, clients, turn): diff --git a/game/engine.py b/game/engine.py index 5a51325..f74e2ac 100644 --- a/game/engine.py +++ b/game/engine.py @@ -36,8 +36,8 @@ def loop(self): if self.quiet_mode: f = open(os.devnull, 'w') sys.stdout = f - self.boot() self.load() + self.boot() for self.current_world_key in tqdm( self.master_controller.game_loop_logic(), bar_format=TQDM_BAR_FORMAT, @@ -146,9 +146,9 @@ def boot(self): self.clients.sort(key=lambda clnt: clnt.team_name, reverse=True) # Finally, request master controller to establish clients with basic objects if SET_NUMBER_OF_CLIENTS_START == 1: - self.master_controller.give_clients_objects(self.clients[0]) + self.master_controller.give_clients_objects(self.clients[0], self.world) else: - self.master_controller.give_clients_objects(self.clients) + self.master_controller.give_clients_objects(self.clients, self.world) # Loads in the world def load(self): From 3b964313df0335f330f244b3a24c2e3cacefdf64 Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Sat, 1 Apr 2023 00:09:54 -0500 Subject: [PATCH 6/8] changes stuff :) --- game/common/map/game_board.py | 2 +- game/common/player.py | 31 +++++++------ game/controllers/controller.py | 6 ++- game/controllers/interact_controller.py | 4 +- game/controllers/master_controller.py | 61 +++++++++++++------------ game/controllers/movement_controller.py | 4 +- game/engine.py | 6 ++- 7 files changed, 63 insertions(+), 51 deletions(-) diff --git a/game/common/map/game_board.py b/game/common/map/game_board.py index 135b2f0..60f1701 100644 --- a/game/common/map/game_board.py +++ b/game/common/map/game_board.py @@ -98,7 +98,7 @@ def __init__(self, seed: int | None = None, map_size: Vector = Vector(), self.walled: bool = walled # game_map is initially going to be None. Since generation is slow, call generate_map() as needed - self.game_map: list[list[GameObject]] | None = None + self.game_map: list[list[Tile]] | None = None @property def seed(self) -> int: diff --git a/game/common/player.py b/game/common/player.py index 11b668e..e8d329b 100644 --- a/game/common/player.py +++ b/game/common/player.py @@ -5,7 +5,7 @@ class Player(GameObject): - def __init__(self, code: object | None = None, team_name: str | None = None, action: ActionType | None = None, + def __init__(self, code: object | None = None, team_name: str | None = None, actions: list[ActionType] | list = [], avatar: Avatar | None = None): super().__init__() self.object_type: ObjectType = ObjectType.PLAYER @@ -14,21 +14,23 @@ def __init__(self, code: object | None = None, team_name: str | None = None, act self.team_name: str | None = team_name self.code: object = code # self.action: Action = action - self.action: ActionType | None = action + self.actions: list[ActionType] | list = actions self.avatar: Avatar | None = avatar @property - def action(self) -> ActionType | None: # change to Action if you want to use the action object - return self.__action + def actions(self) -> list[ActionType] | list: # change to Action if you want to use the action object + return self.__actions - @action.setter - def action(self, action: ActionType | None) -> None: # showing it returns nothing(like void in java) + @actions.setter + def actions(self, actions: list[ActionType] | list) -> None: # showing it returns nothing(like void in java) # if it's (not none = and) if its (none = or) - if action is not None and not isinstance(action, ActionType): # since it can be either none or action type we need it to be and not or - # ^change to Action if you want to use the action object - raise ValueError(f'{self.__class__.__name__}.action must be ActionType or None') + # going across all action types and making it a boolean, if any are true this will be true\/ + if actions is None or not isinstance(actions, list) \ + or (len(actions) > 0 + and any(map(lambda action_type: not isinstance(action_type, ActionType), actions))): + raise ValueError(f'{self.__class__.__name__}.action must be an empty list or a list of action types') # ^if it's not either throw an error - self.__action = action + self.__actions = actions @property def functional(self) -> bool: @@ -76,22 +78,21 @@ def to_json(self): data['functional'] = self.functional # data['error'] = self.error # .to_json() if self.error is not None else None data['team_name'] = self.team_name - data['action'] = self.actiontype.to_json() if self.actiontype is not None else None + data['actions'] = self.actions data['avatar'] = self.avatar.to_json() if self.avatar is not None else None return data def from_json(self, data): super().from_json(data) - + self.functional = data['functional'] # self.error = data['error'] # .from_json(data['action']) if data['action'] is not None else None self.team_name = data['team_name'] - action: ActionType | None = data['action'] + self.actions: list[ActionType] | list = data['actions'] avatar: Avatar | None = data['avatar'] - if action is None and avatar is None: - self.action = None + if avatar is None: self.avatar = None return self # match case for action diff --git a/game/controllers/controller.py b/game/controllers/controller.py index 233adc8..7f7dd2b 100644 --- a/game/controllers/controller.py +++ b/game/controllers/controller.py @@ -1,5 +1,7 @@ -from game.common.enums import DebugLevel +from game.common.enums import DebugLevel, ActionType from game.config import Debug +from game.common.player import Player +from game.common.map.game_board import GameBoard class Controller: @@ -8,7 +10,7 @@ def __init__(self): self.debug_level = DebugLevel.controller self.debug = False - def handle_actions(self, client, world): + def handle_actions(self, action: ActionType, client: Player, world: GameBoard): return def print(self, *args): diff --git a/game/controllers/interact_controller.py b/game/controllers/interact_controller.py index 40ed713..fefa015 100644 --- a/game/controllers/interact_controller.py +++ b/game/controllers/interact_controller.py @@ -12,10 +12,10 @@ class InteractController(Controller): def __init__(self): super().__init__() - def handle_actions(self, client: Player, world: GameBoard): + def handle_actions(self, action: ActionType, client: Player, world: GameBoard): # match interaction type with x and y vector: Vector - match client.action: + match action: case ActionType.INTERACT_UP: vector = Vector(x=0, y=-1) case ActionType.INTERACT_DOWN: diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 1f81569..8f479ea 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -5,31 +5,30 @@ from game.common.avatar import Avatar from game.common.enums import * from game.common.player import Player -# from game.common.stats import GameStats - don't need it because it is specifically for 1.7 -# but keeping it for now in case it breaks something -import game.config as config +import game.config as config # this is for turns from game.utils.thread import CommunicationThread from game.controllers.movement_controller import MovementController from game.controllers.controller import Controller from game.controllers.interact_controller import InteractController +from game.common.map.game_board import GameBoard class MasterController(Controller): def __init__(self): super().__init__() self.game_over: bool = False - # self.event_timer = GameStats.event_timer - self.event_times: tuple[int, int] | None = None + # self.event_timer = GameStats.event_timer # anything related to events are commented it out until made + # self.event_times: tuple[int, int] | None = None self.turn: int = 1 - self.current_world_data: GameBoard | None = None + self.current_world_data: dict = None self.movement_controller: MovementController = MovementController() self.interact_controller: InteractController = InteractController() # Receives all clients for the purpose of giving them the objects they will control - def give_clients_objects(self, clients: list(Player), world: GameBoard): - # starting_positions = [[3, 3], [3, 9]] + def give_clients_objects(self, clients: list[Player], world: dict): + # starting_positions = [[3, 3], [3, 9]] # would be done in generate game for index, client in enumerate(clients): - client.avatar = Avatar(position=game_board[index]) + client.avatar: Avatar = Avatar(position=world[index]) # Generator function. Given a key:value pair where the key is the identifier for the current world and the value is # the state of the world, returns the key that will give the appropriate world information @@ -44,51 +43,57 @@ def game_loop_logic(self, start=1): self.turn += 1 # Receives world data from the generated game log and is responsible for interpreting it - def interpret_current_turn_data(self, clients, world, turn): + def interpret_current_turn_data(self, clients: list[Player], world: dict, turn): self.current_world_data = world if turn == 1: - random.seed(world["seed"]) - self.event_times = random.randrange(162, 172), random.randrange(329, 339) + random.seed(world["game_board"].seed) + # self.event_times = random.randrange(162, 172), random.randrange(329, 339) # Receive a specific client and send them what they get per turn. Also obfuscates necessary objects. - def client_turn_arguments(self, client, turn): - turn_action = Action() - client.action = turn_action + def client_turn_arguments(self, client: Player, turn): + # turn_action: Action = Action() + # client.action: Action = turn_action + # ^if you want to use action as an object instead of an enum + + turn_actions: list[ActionType] | list = [] + client.actions = turn_actions # Create deep copies of all objects sent to the player - current_world = deepcopy(self.current_world_data["game_map"]) + current_world = deepcopy(self.current_world_data["game_board"]) # what is current world and copy avatar copy_avatar = deepcopy(client.avatar) # Obfuscate data in objects that that player should not be able to see # Currently world data isn't obfuscated at all - args = (self.turn, turn_action, self.current_world_data) + args = (self.turn, turn_actions, current_world, copy_avatar) return args # Perform the main logic that happens per turn - def turn_logic(self, clients, turn): + def turn_logic(self, clients: list[Player], turn): for client in clients: - self.movement_controller.handle_actions(self.current_world_data["game_map"], client) - self.interact_controller.handle_actions(client, self.current_world_data["game_map"]) + for action in client.actions: + self.movement_controller.handle_actions(action, client, self.current_world_data["game_board"]) + self.interact_controller.handle_actions(action, client, self.current_world_data["game_board"]) # checks event logic at the end of round - self.handle_events(clients) + # self.handle_events(clients) - def handle_events(self, clients): + # comment out for now, nothing is in place for event types yet + # def handle_events(self, clients): # If it is time to run an event, master controller picks an event to run - if self.turn == self.event_times[0] or self.turn == self.event_times[1]: - self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) - # event type.example is just a placeholder for now + # if self.turn == self.event_times[0] or self.turn == self.event_times[1]: + # self.current_world_data["game_map"].generate_event(EventType.example, EventType.example) + # event type.example is just a placeholder for now # Return serialized version of game - def create_turn_log(self, clients, turn): + def create_turn_log(self, clients: list[Player], turn): data = dict() data['tick'] = turn data['clients'] = [client.to_json() for client in clients] # Add things that should be thrown into the turn logs here - data['game_map'] = self.current_world_data["game_map"].to_json() + data['game_board'] = self.current_world_data["game_board"].to_json() return data # Gather necessary data together in results file - def return_final_results(self, clients, turn): + def return_final_results(self, clients: list[Player], turn): data = dict() data['players'] = list() diff --git a/game/controllers/movement_controller.py b/game/controllers/movement_controller.py index 0838fd0..4f2af7a 100644 --- a/game/controllers/movement_controller.py +++ b/game/controllers/movement_controller.py @@ -11,11 +11,11 @@ class MovementController(Controller): def __init__(self): super().__init__() - def handle_actions(self, client: Player, world: GameBoard): + def handle_actions(self, action: ActionType, client: Player, world: GameBoard): avatar_pos: Vector = Vector(client.avatar.position.x, client.avatar.position.y) pos_mod: Vector - match client.action: + match action: case ActionType.MOVE_UP: pos_mod = Vector(x=0, y=-1) case ActionType.MOVE_DOWN: diff --git a/game/engine.py b/game/engine.py index f74e2ac..178cc14 100644 --- a/game/engine.py +++ b/game/engine.py @@ -5,6 +5,7 @@ import sys import traceback +from game.common.map.game_board import GameBoard from game.common.player import Player from game.config import * from game.controllers.master_controller import MasterController @@ -167,8 +168,10 @@ def load(self): world = None with open(GAME_MAP_FILE) as json_file: world = json.load(json_file) + world['game_board'] = GameBoard().from_json(world['game_board']) self.world = world + # Sits on top of all actions that need to happen before the player takes their turn def pre_tick(self): # Increment the tick @@ -260,7 +263,8 @@ def post_tick(self): else: data = self.master_controller.create_turn_log(self.clients, self.tick_number) - self.game_logs[self.tick_number] = data + with open(os.path.join(LOGS_DIR, f"turn_{self.tick_number:04d}.json"), 'w+') as f: + json.dump(data, f) # Perform a game over check if self.master_controller.game_over: From 3662e4c8dd5e7fbe84d36c520c4881549ff1f736 Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Sat, 1 Apr 2023 00:21:28 -0500 Subject: [PATCH 7/8] added max number of actions per turn --- game/config.py | 2 ++ game/controllers/master_controller.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/game/config.py b/game/config.py index 47e8d41..aa3a065 100644 --- a/game/config.py +++ b/game/config.py @@ -10,6 +10,8 @@ MAX_SECONDS_PER_TURN = 0.1 # max number of basic operations clients have for their turns +MAX_NUMBER_OF_ACTIONS_PER_TURN = 2 # max number of actions per turn is currently set to 2 + MIN_CLIENTS_START = None # minimum number of clients required to start running the game; should be None when SET_NUMBER_OF_CLIENTS is used MAX_CLIENTS_START = None # maximum number of clients required to start running the game; should be None when SET_NUMBER_OF_CLIENTS is used SET_NUMBER_OF_CLIENTS_START = 2 # required number of clients to start running the game; should be None when MIN_CLIENTS or MAX_CLIENTS are used diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 8f479ea..01f7dab 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -11,6 +11,7 @@ from game.controllers.controller import Controller from game.controllers.interact_controller import InteractController from game.common.map.game_board import GameBoard +from game.config import MAX_NUMBER_OF_ACTIONS_PER_TURN class MasterController(Controller): @@ -69,9 +70,9 @@ def client_turn_arguments(self, client: Player, turn): # Perform the main logic that happens per turn def turn_logic(self, clients: list[Player], turn): for client in clients: - for action in client.actions: - self.movement_controller.handle_actions(action, client, self.current_world_data["game_board"]) - self.interact_controller.handle_actions(action, client, self.current_world_data["game_board"]) + for i in range(MAX_NUMBER_OF_ACTIONS_PER_TURN): + self.movement_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"]) + self.interact_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"]) # checks event logic at the end of round # self.handle_events(clients) From 77efe0d5a31eafc34c0a37a7c9eeb69be6f573d5 Mon Sep 17 00:00:00 2001 From: MyFridgeIsFinePleaseStopAsking <101206316+MyFridgeIsFinePleaseStopAsking@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:14:12 -0500 Subject: [PATCH 8/8] Update player.py --- game/common/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/common/player.py b/game/common/player.py index e8d329b..1b4daf7 100644 --- a/game/common/player.py +++ b/game/common/player.py @@ -5,7 +5,7 @@ class Player(GameObject): - def __init__(self, code: object | None = None, team_name: str | None = None, actions: list[ActionType] | list = [], + def __init__(self, code: object | None = None, team_name: str | None = None, actions: list[ActionType] = [], avatar: Avatar | None = None): super().__init__() self.object_type: ObjectType = ObjectType.PLAYER