From ade051890e1f31eb1361a6adc5cdca9db722c669 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Tue, 7 May 2024 23:09:52 +0200 Subject: [PATCH] discord login --- .vscode/launch.json | 3 +- .vscode/settings.json | 3 + requirements.txt | 7 ++ src/controller/auth.py | 36 +++++++ src/controller/client.py | 210 +++++++++++++++++++++++------------- src/controller/game.py | 26 +++-- src/model/card.py | 6 +- src/model/card_behaviour.py | 23 ++-- src/model/card_loader.py | 31 ++++-- src/model/game.py | 20 ++-- src/model/player.py | 26 +++-- src/model/serializable.py | 6 +- src/model/turn.py | 31 ++++-- src/netcode/models.py | 26 ++++- src/netcode/ws_server.py | 8 +- 15 files changed, 317 insertions(+), 145 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/controller/auth.py diff --git a/.vscode/launch.json b/.vscode/launch.json index c2c0c15..e221434 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,8 @@ "type": "debugpy", "request": "launch", "program": "src/main.py", - "console": "internalConsole" + "console": "internalConsole", + "envFile": "${workspaceFolder}/.env" } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ee51ecd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "mypy-type-checker.reportingScope": "workspace" +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7040c69..37de78b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,18 @@ annotated-types==0.6.0 +certifi==2024.2.2 +charset-normalizer==3.3.2 gevent==24.2.1 greenlet==3.0.3 +idna==3.7 mypy==1.9.0 mypy-extensions==1.0.0 pydantic==2.6.4 pydantic_core==2.16.3 +requests==2.31.0 +setuptools==69.5.1 +types-requests==2.31.0.20240406 typing_extensions==4.10.0 +urllib3==2.2.1 websockets==12.0 zope.event==5.0 zope.interface==6.2 diff --git a/src/controller/auth.py b/src/controller/auth.py new file mode 100644 index 0000000..cd40eba --- /dev/null +++ b/src/controller/auth.py @@ -0,0 +1,36 @@ +import requests +import os + + +def discord_process_code(code: str) -> dict | None: + response = requests.post( + url="https://discord.com/api/oauth2/token", + headers={ + "Content-Type": "application/x-www-form-urlencoded", + }, + data={ + "client_id": os.environ["DISCORD_CLIENTID"], + "client_secret": os.environ["DISCORD_CLIENTSECRET"], + "code": code, + "grant_type": "authorization_code", + "redirect_uri": os.environ["FRONTEND_ENDPOINT"] + "/login", + "scope": "identify", + }, + timeout=10, + ) + json_data = response.json() + if "error" in json_data: + print(json_data) + return None + + token_type = json_data["token_type"] + access_token = json_data["access_token"] + + response = requests.get( + url="https://discord.com/api/users/@me", + headers={ + "authorization": token_type + " " + access_token, + }, + timeout=10, + ) + return response.json() diff --git a/src/controller/client.py b/src/controller/client.py index 8c4780d..c5a3021 100644 --- a/src/controller/client.py +++ b/src/controller/client.py @@ -4,11 +4,14 @@ from typing import Callable, Any, TYPE_CHECKING from pydantic import ValidationError from websockets.server import WebSocketServerProtocol +from websockets.exceptions import ConnectionClosedOK -from netcode.models import Message, LoginMessage +from netcode.models import Message, LoginMessage, MessageType from model import Player +from .auth import discord_process_code + if TYPE_CHECKING: from controller import GameController @@ -55,7 +58,7 @@ class ClientController: """Hooks the client to the correct listen callbacks""" await self.send( Message( - type="context", + type=MessageType.CONTEXT, data_type="game_context_id", data="login", ) @@ -68,15 +71,18 @@ class ClientController: async def send(self, message: Message) -> None: """Send a Message object to the connected client""" - await self.websocket.send(message.model_dump_json()) + try: + await self.websocket.send(message.model_dump_json()) + except ConnectionClosedOK: + print("Connection Closed") async def send_error(self, error: str) -> None: """Send an error string to the connected client""" - await self.send(Message(type="error", data_type="error", data=error)) + await self.send(Message(type=MessageType.ERROR, data_type="error", data=error)) async def send_announce_dict( self, - notification_type: str, + notification_type: MessageType, data: dict, player_filter: Callable[[Player], Any] = lambda x: True, ): @@ -89,65 +95,15 @@ class ClientController: Message(type=notification_type, data_type="object", data=data) ) - @vibe_check(Message) - async def game_context(self, message: Message) -> None: - """Handles all messages related to when the current client is logged in - and connected to a running game""" - - if message.type == "temp_action": - if message.data_type == "set_health": - self.player.team.health = message.data - return await self.game_controller.game.notify_controller( - "update", self.player.team.serialize_guest() - ) - - if self.player.team != self.game_controller.game.current_turn: - return await self.send_error("it is not your turn to play!") - if message.type == "action": - # TODO : pydantic type check for message.data - match message.data_type: - case "play_card": - return await self.player.play_card(message.data) - - case "discard": - return await self.player.turn.discard_card(message.data) - - case "lock_card": - return await self.player.turn.lock_card(message.data) - - case "use_effect": - return await self.player.turn.use_effect_with_check( - message.data.get("effect_id"), message.data.get("target") - ) - - case "buy": - return await self.player.turn.buy_card(message.data) - - case "heal": - return await self.player.turn.heal(message.data) - - case "damage_team": - return await self.player.turn.damage_team(message.data) - - case "damage_champion": - return await self.player.turn.damage_champion(message.data) - - case "end_turn": - return await self.player.turn.end_turn() - - return await self.send_error( - "Couldn't process action with data_type : " + message.data_type - ) - @vibe_check(LoginMessage) async def login_context(self, message: LoginMessage) -> None: """Handles all messages related to when the current client isn't yet logged in""" username = message.data - if self.game_controller.game.started: - return await self.send_error( - "you cannot interact with a game that has already started" - ) - if message.type == "register": + if message.type == MessageType.REGISTER: + if self.game_controller.game.started: + return await self.send_error( + "you cannot interact with a game that has already started" + ) if len(username) < 3: return await self.send_error( "username must be longer than 3 characters" @@ -157,36 +113,81 @@ class ClientController: return await self.send_error("username already exists") player = await self.game_controller.game.create_player(username) - elif message.type == "login": + elif message.type == MessageType.LOGIN: player2 = self.game_controller.game.get_player(username) if player2 is None: return await self.send_error("player doesn't exists") player = player2 + elif message.type == MessageType.DISCORD_LOGIN: + user = discord_process_code(message.data) + if user is None: + await self.send_error("discord login not successful") + return + username = user["global_name"] + player = next( + ( + p + for p in self.game_controller.game.players + if p.discord_id == user["id"] + ), + None, + ) + if player is None: + if self.game_controller.game.started: + return await self.send_error( + "you cannot interact with a game that has already started" + ) + player = await self.game_controller.game.create_player(username) + player.discord_id = user["id"] + player.image = ( + "https://cdn.discordapp.com/avatars/" + + user["id"] + + "/" + + user["avatar"] + + ".webp" + ) else: return await self.send_error("invalid message type for context") self.player = player self.player.team.listeners.append(self.send_announce_dict) - self.on_message = self.pregame_context + if self.game_controller.game.started: + self.on_message = self.game_context + await self.send( + Message( + type=MessageType.CONTEXT, data_type="game_context_id", data="game" + ) + ) + for p in self.game_controller.game.players: + if p.team == player.team: + await self.send( + Message( + type=MessageType.UPDATE, + data_type="object", + data=player.serialize_team(), + ) + ) + else: + self.on_message = self.pregame_context + await self.send( + Message( + type=MessageType.CONTEXT, + data_type="game_context_id", + data="pregame", + ) + ) print("user logged in : " + username) await self.send( Message( - type="set", + type=MessageType.SET, data_type="self", data=self.player.uuid, ) ) - await self.send( - Message( - type="context", - data_type="game_context_id", - data="pregame", - ) - ) @vibe_check(Message) async def pregame_context(self, message: Message) -> None: @@ -197,9 +198,66 @@ class ClientController: if all(p.ready for p in self.game_controller.game.players): await self.game_controller.start_game() - elif message.type == "temp_action": - if message.data_type == "set_health": - self.player.team.health = message.data - await self.game_controller.game.notify_controller( - "update", self.player.team.serialize_guest() - ) + @vibe_check(Message) + async def game_context(self, message: Message) -> None: + """Handles all messages related to when the current client is logged in + and connected to a running game""" + + # if message.type == "temp_action": + # if message.data_type == "set_health": + # self.player.team.health = message.data + # return await self.game_controller.game.notify_controller( + # "update", self.player.team.serialize_guest() + # ) + + if self.player.team != self.game_controller.game.current_turn: + return await self.send_error("it is not your turn to play!") + if message.type == "action": + # TODO : pydantic type check for message.data + match message.data_type: + case "play_card": + if not isinstance(message.data, str): + return + return await self.player.play_card(message.data) + + case "discard": + if not isinstance(message.data, str): + return + return await self.player.turn.discard_card(message.data) + + case "lock_card": + if not isinstance(message.data, str): + return + return await self.player.turn.lock_card(message.data) + + case "use_effect": + return await self.player.turn.use_effect_with_check( + message.data.get("effect_id"), message.data.get("target") + ) + + case "buy": + if not isinstance(message.data, str): + return + return await self.player.turn.buy_card(message.data) + + case "heal": + if not isinstance(message.data, int): + return + return await self.player.turn.heal(message.data) + + case "damage_team": + if not isinstance(message.data, str): + return + return await self.player.turn.damage_team(message.data) + + case "damage_champion": + if not isinstance(message.data, str): + return + return await self.player.turn.damage_champion(message.data) + + case "end_turn": + return await self.player.turn.end_turn() + + return await self.send_error( + "Couldn't process action with data_type : " + message.data_type + ) diff --git a/src/controller/game.py b/src/controller/game.py index b4342c6..545ec37 100644 --- a/src/controller/game.py +++ b/src/controller/game.py @@ -1,8 +1,9 @@ import asyncio from typing import Callable, List, Any +from websockets.exceptions import ConnectionClosedOK from netcode import WSServer from netcode.ws_server import WebSocketServerProtocol -from netcode.models import Message +from netcode.models import Message, MessageType from model import Game, Player from controller import ( ClientController, @@ -31,14 +32,17 @@ class GameController: self.clients.remove(client) async def send(self, message: Message, websocket: WebSocketServerProtocol): - await websocket.send(message.model_dump_json()) + try: + await websocket.send(message.model_dump_json()) + except ConnectionClosedOK: + print("Connection Closed") async def broadcast(self, message: Message): await self.server.broadcast(message.model_dump_json()) async def broadcast_announce_dict( self, - notification_type: str, + notification_type: MessageType, data: dict, player_filter: Callable[[Player], Any], ): @@ -58,7 +62,7 @@ class GameController: async def announce_dict( self, - notification_type: str, + notification_type: MessageType, data: dict, websocket: WebSocketServerProtocol, ): @@ -74,39 +78,39 @@ class GameController: async def announce_game(self, websocket: WebSocketServerProtocol): for effect in self.game.effects: await self.announce_dict( - "create", + MessageType.CREATE, effect.serialize(), websocket, ) for card in self.game.cards: await self.announce_dict( - "create", + MessageType.CREATE, card.serialize(), websocket, ) for team in self.game.teams: await self.announce_dict( - "create", + MessageType.CREATE, team.serialize(), websocket, ) for player in self.game.players: await self.announce_dict( - "create", + MessageType.CREATE, player.turn.serialize(), websocket, ) await self.announce_dict( - "create", + MessageType.CREATE, player.serialize_guest(), websocket, ) await self.announce_dict( - "create", + MessageType.CREATE, self.game.serialize_guest(), websocket, ) @@ -119,5 +123,5 @@ class GameController: for c in self.clients: c.on_message = c.game_context await self.broadcast( - Message(type="context", data_type="game_context_id", data="game") + Message(type=MessageType.CONTEXT, data_type="game_context_id", data="game") ) diff --git a/src/model/card.py b/src/model/card.py index bfa1276..ecad2e9 100644 --- a/src/model/card.py +++ b/src/model/card.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Callable, TYPE_CHECKING, Union +from typing import List, Optional, Callable, TYPE_CHECKING, Union, Awaitable, Any from model import Serializable, CardRole, CardType, CardFaction from model.card_validator import CardEffects, EffectType, EffectTimes @@ -17,13 +17,13 @@ class Effect(Serializable): effect_type: Optional[EffectType] = None times: Optional[EffectTimes] = None card: "Card" - activate: Callable[["Turn"], Union["Player", "Card"]] + activate: Callable[["Turn", str | None], Awaitable[Any]] def __init__( self, effect: CardEffects, amount: int, - activate: Callable[["Turn"], Union["Player", "Card"]], + activate: Callable[["Turn", str | None], Awaitable[Any]], sub_effects: Optional[List["Effect"]] = None, effect_type: Optional[EffectType] = None, times: Optional[EffectTimes] = None, diff --git a/src/model/card_behaviour.py b/src/model/card_behaviour.py index 4cd7df5..0bd9eaa 100644 --- a/src/model/card_behaviour.py +++ b/src/model/card_behaviour.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Awaitable, Optional +from typing import TYPE_CHECKING, Optional, Callable, Coroutine, Any from model.card_validator import EffectParser from model.card import Effect, EffectType, CardEffects, EffectTimes, CardType @@ -6,6 +6,8 @@ from model.card import Effect, EffectType, CardEffects, EffectTimes, CardType if TYPE_CHECKING: from model import Turn +# pylint: disable=unused-argument,missing-function-docstring + # Effect Types async def effect_type_suicide(effect: Effect, turn: "Turn"): @@ -55,19 +57,20 @@ async def effect_heal(effect: Effect, turn: "Turn", _target: str): return True -async def effect_prepare(_effect: Effect, turn: "Turn", target: str): +async def effect_prepare(effect: Effect, turn: "Turn", target: str): + return turn.prepare(target) -async def effect_stun(_effect: Effect, turn: "Turn", target: str): +async def effect_stun(effect: Effect, turn: "Turn", target: str): return await turn.player.stun(target) -async def effect_sacrifice(_effect: Effect, turn: "Turn", target: str): +async def effect_sacrifice(effect: Effect, turn: "Turn", target: str): return await turn.sacrifice_card(target) -async def effect_draw(_effect: Effect, turn: "Turn", _target: str): +async def effect_draw(effect: Effect, turn: "Turn", _target: str): return await turn.player.draw_and_play(1) @@ -169,9 +172,11 @@ effect_times_all_behaviours = { } -def create_effect_behaviour(effect: EffectParser) -> Awaitable[None]: +def create_effect_behaviour( + effect: EffectParser, +) -> Callable[[Effect, "Turn", str | None], Coroutine[Any, Any, Any]]: - async def effect_type_behaviour(e: Effect, t: "Turn"): + async def effect_type_behaviour(effect: Effect, turn: "Turn"): return True if effect.effect_type in effect_types_all_behaviours: @@ -182,7 +187,7 @@ def create_effect_behaviour(effect: EffectParser) -> Awaitable[None]: card_effect_behaviour = card_effect_all_behaviours[effect.effect] - def effect_times_behaviour(_e: Effect, _t: "Turn"): + def effect_times_behaviour(effect: Effect, turn: "Turn"): return 1 if effect.times in effect_times_all_behaviours: @@ -194,7 +199,7 @@ def create_effect_behaviour(effect: EffectParser) -> Awaitable[None]: return False times = effect_times_behaviour(self, turn) for _ in range(times): - effect_result = await card_effect_behaviour(self, turn, target) + effect_result = await card_effect_behaviour(self, turn, (target or "")) if not effect_result: return False return True diff --git a/src/model/card_loader.py b/src/model/card_loader.py index 5be9b75..3a55bf9 100644 --- a/src/model/card_loader.py +++ b/src/model/card_loader.py @@ -1,5 +1,5 @@ from json import JSONDecodeError -from typing import List, Dict, Optional +from typing import List, Dict, Optional, TYPE_CHECKING, Union from pathlib import Path from pydantic import BaseModel, ValidationError @@ -8,6 +8,9 @@ from model.card_validator import CardParser, EffectParser from model.card import Effect, Card from .card_behaviour import create_effect_behaviour +if TYPE_CHECKING: + from model import Turn, Player + class CardContainerFile(BaseModel): cards: List[CardParser] @@ -29,7 +32,7 @@ def load_cards() -> List[CardParser]: return cards -def create_card(card_parser: CardParser) -> tuple[Card, list[Effect], list[Effect]]: +def create_card(card_parser: CardParser) -> tuple[Card, list[Effect]]: card_effects, all_effects = create_card_effects(card_parser) card = Card( card_id=card_parser.id, @@ -50,9 +53,14 @@ def create_card(card_parser: CardParser) -> tuple[Card, list[Effect], list[Effec def create_card_effects( card: CardParser, all_effects: Optional[List[Effect]] = None -) -> (List[Effect], List[Effect]): +) -> tuple[List[Effect], List[Effect]]: + if all_effects is None: - all_effects: List[Effect] = [] + all_effects = [] + + if all_effects is None: + raise RuntimeError("shut up MyPy") + combined: List[Effect] = [] mutexes: Dict[int, List[Effect]] = {} for mutex in set([c.mutex for c in card.effects if c.mutex is not None]): @@ -76,9 +84,13 @@ def create_card_effects( def create_effect_and_subeffects( effect: EffectParser, all_effects: Optional[List[Effect]] = None -) -> (Effect, List[Effect]): +) -> tuple[Effect, List[Effect]]: + if all_effects is None: - all_effects: List[Effect] = [] + all_effects = [] + if all_effects is None: + raise RuntimeError("shut up MyPy") + sub_effects: List[Effect] = [] mutexes: Dict[int, List[Effect]] = {} for mutex in set([c.mutex for c in effect.sub_effects if c.mutex is not None]): @@ -97,15 +109,16 @@ def create_effect_and_subeffects( if s.mutex in mutexes: sub_effects[index].mutex = mutexes[s.mutex] + def activate(turn: "Turn", target: str | None): + return create_effect_behaviour(effect=effect)(actual_effect, turn, target) + actual_effect = Effect( effect=effect.effect, amount=effect.amount, effect_type=effect.effect_type, sub_effects=sub_effects, times=effect.times, - activate=lambda *args, **kwargs: create_effect_behaviour(effect=effect)( - actual_effect, *args, **kwargs - ), + activate=activate, ) return actual_effect, all_effects diff --git a/src/model/game.py b/src/model/game.py index 31155ff..4642e06 100644 --- a/src/model/game.py +++ b/src/model/game.py @@ -7,6 +7,8 @@ from model import Card, CardRole, CardParser, Player, Team, Serializable, Effect from model.card_loader import load_cards, create_card_effects, create_card +from netcode.models import MessageType + class Game(Serializable): """Instance of the game, with players and a market""" @@ -17,7 +19,7 @@ class Game(Serializable): cards: List[Card] effects: List[Effect] market_stack: List[Card] - market: List[Card] + market: List[Card | None] gem_stack: List[Card] players: List[Player] teams: List[Team] @@ -26,7 +28,7 @@ class Game(Serializable): default_health = 50 - current_turn: Team + current_turn: Team | None def __init__(self) -> None: super().__init__() @@ -50,10 +52,10 @@ class Game(Serializable): card, all_effects = create_card(card_parser) for e in all_effects: self.effects.append(e) - await self.notify_controller("create", e.serialize()) + await self.notify_controller(MessageType.CREATE, e.serialize()) self.cards.append(card) - await self.notify_controller("create", card.serialize()) + await self.notify_controller(MessageType.CREATE, card.serialize()) return card async def init_cards(self): @@ -91,15 +93,15 @@ class Game(Serializable): self.players.append(player) player.stack_pile = await self.create_base_deck("base") - await self.notify_controller("create", player.turn.serialize()) - await self.notify_controller("create", player.serialize_guest()) - await self.notify_controller("update", self.serialize_guest()) + await self.notify_controller(MessageType.CREATE, player.turn.serialize()) + await self.notify_controller(MessageType.CREATE, player.serialize_guest()) + await self.notify_controller(MessageType.UPDATE, self.serialize_guest()) return player async def create_team(self) -> Team: team = Team(health=self.default_health) self.teams.append(team) - await self.notify_controller("create", team.serialize_guest()) + await self.notify_controller(MessageType.CREATE, team.serialize_guest()) return team @@ -169,7 +171,7 @@ class Game(Serializable): await p.start_turn() await self.notify_controller("update", self.serialize_guest()) - def search_card_in_pile(self, card_id: str, pile: List[Card]): + def search_card_in_pile(self, card_id: str, pile: list[Card | None] | list[Card]): return next( (m for m in [f for f in pile if f is not None] if str(m.uuid) == card_id), None, diff --git a/src/model/player.py b/src/model/player.py index 59a1b1d..9d1aa2d 100644 --- a/src/model/player.py +++ b/src/model/player.py @@ -6,6 +6,8 @@ from typing import List, Optional, TYPE_CHECKING from model import Card, Team, Serializable, Turn, CardType, CardRole +from netcode.models import MessageType + if TYPE_CHECKING: from model import Game @@ -15,6 +17,8 @@ class Player(Serializable): object_type = "player" username: str + image: Optional[str] = None + discord_id: Optional[int] = None stack_pile: List[Card] discard_pile: List[Card] @@ -110,7 +114,7 @@ class Player(Serializable): await asyncio.gather(*notifies) - def source_card(self, card_id: str, source: List[Card]) -> Card: + def source_card(self, card_id: str, source: List[Card]) -> Card | None: card = self.game.search_card_in_pile(card_id, source) if card is None: return None @@ -184,7 +188,9 @@ class Player(Serializable): return False self.discard_pile.append(card) - await self.game.notify_controller("update", self.game.serialize_guest()) + await self.game.notify_controller( + MessageType.UPDATE, self.game.serialize_guest() + ) await self.notify_update() return True @@ -199,7 +205,9 @@ class Player(Serializable): if card.card_role == CardRole.FIRE_GEM: self.game.gem_stack.append(card) await self.notify_update() - await self.game.notify_controller("update", self.game.serialize_guest()) + await self.game.notify_controller( + MessageType.UPDATE, self.game.serialize_guest() + ) return True async def play_from_discard(self, card_id: str): @@ -220,7 +228,7 @@ class Player(Serializable): self.stack_pile.append(card) else: print("Card not found in discard pile") - return + return False await self.notify_update() return True @@ -237,7 +245,9 @@ class Player(Serializable): return False self.board.append(card) - await self.game.notify_controller("update", self.game.serialize_guest()) + await self.game.notify_controller( + MessageType.UPDATE, self.game.serialize_guest() + ) await self.notify_update() return True @@ -253,7 +263,9 @@ class Player(Serializable): return False self.stack_pile.append(card) - await self.game.notify_controller("update", self.game.serialize_guest()) + await self.game.notify_controller( + MessageType.UPDATE, self.game.serialize_guest() + ) await self.notify_update() return True @@ -286,5 +298,5 @@ class Player(Serializable): print("player has already too many discard markers!") return False p.turn.fuck_u_markers += 1 - await self.game.notify_controller("update", p.turn.serialize()) + await self.game.notify_controller(MessageType.UPDATE, p.turn.serialize()) return True diff --git a/src/model/serializable.py b/src/model/serializable.py index b1c6b1a..cd34e6d 100644 --- a/src/model/serializable.py +++ b/src/model/serializable.py @@ -1,13 +1,15 @@ from typing import List, Callable, Awaitable, Any from uuid import uuid4, UUID +from netcode.models import MessageType + class Serializable: object_type = "serializable" uuid: UUID - listeners: List[Callable[[str, dict, Callable], Awaitable[Any]]] + listeners: List[Callable[[MessageType, dict, Callable], Awaitable[Any]]] def __init__(self) -> None: self.uuid = uuid4() @@ -24,7 +26,7 @@ class Serializable: async def notify_controller( self, - notification_type: str, + notification_type: MessageType, game_object: dict, # TODO : Player interface to typing player_filter: Callable[[Any], bool] = lambda _: True, diff --git a/src/model/turn.py b/src/model/turn.py index e309463..58d9e11 100644 --- a/src/model/turn.py +++ b/src/model/turn.py @@ -13,6 +13,7 @@ from model import ( Team, ) +from netcode.models import MessageType if TYPE_CHECKING: from model import Player @@ -64,9 +65,9 @@ class Turn(Serializable): for p in self.player.game.players: p.turn.champions_health = {} for c in p.board: - if c.card_type == CardType.CHAMPION: + if c.card_type == CardType.CHAMPION and c.defense is not None: p.turn.champions_health[str(c.uuid)] = c.defense - await p.game.notify_controller("update", p.turn.serialize()) + await p.game.notify_controller(MessageType.UPDATE, p.turn.serialize()) await self.player.end_turn() @@ -98,7 +99,7 @@ class Turn(Serializable): for _ in range(effect.amount): self.effects_availables.append(effect) - await self.player.game.notify_controller("update", self.serialize()) + await self.player.game.notify_controller(MessageType.UPDATE, self.serialize()) async def discard_card(self, card_id: str): card = self.player.game.search_card_in_pile(card_id, self.cards_locked) @@ -113,7 +114,7 @@ class Turn(Serializable): return print("you don't have any discard or fuck_u markers") await self.player.discard_card(card_id) - await self.player.game.notify_controller("update", self.serialize()) + await self.player.game.notify_controller(MessageType.UPDATE, self.serialize()) def sacrifice_card(self, card_id: str): card = self.player.game.search_card_in_pile(card_id, self.cards_locked) @@ -168,7 +169,7 @@ class Turn(Serializable): else: for _ in range(e.amount): self.effects_availables.append(e) - await self.player.game.notify_controller("update", self.serialize()) + await self.player.game.notify_controller(MessageType.UPDATE, self.serialize()) def prepare(self, card_id: str) -> bool: card = next( @@ -243,14 +244,18 @@ class Turn(Serializable): if await buy_function(card): self.gold_reserve -= card.cost - await self.player.game.notify_controller("update", self.serialize()) + await self.player.game.notify_controller( + MessageType.UPDATE, self.serialize() + ) return True return False async def heal(self, amount: int): self.player.team.health += amount - await self.player.game.notify_controller("update", self.player.team.serialize()) + await self.player.game.notify_controller( + MessageType.UPDATE, self.player.team.serialize() + ) def check_for_guard(self, team: "Team"): for p in self.player.game.players: @@ -277,8 +282,8 @@ class Turn(Serializable): self.damage_reserve -= 1 team.health -= 1 - await self.player.game.notify_controller("update", self.serialize()) - await self.player.game.notify_controller("update", team.serialize()) + await self.player.game.notify_controller(MessageType.UPDATE, self.serialize()) + await self.player.game.notify_controller(MessageType.UPDATE, team.serialize()) async def damage_champion(self, target: str): if self.damage_reserve <= 0: @@ -301,8 +306,12 @@ class Turn(Serializable): self.damage_reserve -= p.turn.champions_health[str(c.uuid)] await self.player.stun(str(c.uuid)) - await self.player.game.notify_controller("update", self.serialize()) - await self.player.game.notify_controller("update", p.turn.serialize()) + await self.player.game.notify_controller( + MessageType.UPDATE, self.serialize() + ) + await self.player.game.notify_controller( + MessageType.UPDATE, p.turn.serialize() + ) return print("card not found") diff --git a/src/netcode/models.py b/src/netcode/models.py index 518d090..b2c65a4 100644 --- a/src/netcode/models.py +++ b/src/netcode/models.py @@ -1,9 +1,31 @@ -from typing import Any +from enum import Enum +from typing import Any, Literal + from pydantic import BaseModel +class MessageType(str, Enum): + ACTION = "action" + REGISTER = "register" + LOGIN = "login" + READY = "ready" + DISCORD_LOGIN = "discord_login" + + CONTEXT = "context" + CREATE = "create" + ERROR = "error" + UPDATE = "update" + SET = "set" + + +class CreateMessageType(BaseModel): + type: Literal[MessageType.CREATE] + data_type: "object" + data: dict + + class Message(BaseModel): - type: str + type: MessageType data: Any | str | int | None = None data_type: str diff --git a/src/netcode/ws_server.py b/src/netcode/ws_server.py index 9c61f7a..7195749 100644 --- a/src/netcode/ws_server.py +++ b/src/netcode/ws_server.py @@ -9,20 +9,21 @@ class WSServer: connections: Set[WebSocketServerProtocol] on_new_connection_callback: Callable[ - [WebSocketServerProtocol, Callable[[Any], Awaitable[None]]], Awaitable[None] + [WebSocketServerProtocol, Callable[[], Any]], Awaitable[None] ] port: int def __init__(self) -> None: self.connections = set() - self.message_callbacks = [] + self.message_callbacks: list[Callable] = [] async def handler(self, websocket: WebSocketServerProtocol): """Is called everytime a client connects to the websocket""" self.connections.add(websocket) async def track_connection(): + """Keeps track of connected clients in the connections property""" try: await websocket.wait_closed() finally: @@ -30,9 +31,6 @@ class WSServer: await self.on_new_connection_callback(websocket, track_connection) - async def track_connection(self, websocket: WebSocketServerProtocol): - """Keeps track of connected clients in the connections property""" - async def serve(self, address="0.0.0.0", port=8765): """Call to start the websocket"""