From f15b832acaf3df54b67f45f9b385bc12a76323fd Mon Sep 17 00:00:00 2001 From: Askaholic Date: Sat, 1 Feb 2025 17:06:40 -0500 Subject: [PATCH 1/2] Use updated Victory enum values --- .github/workflows/test.yml | 2 +- server/db/typedefs.py | 9 ++++---- server/games/game.py | 2 +- tests/data/test-data.sql | 44 +++++++++++++++++++------------------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47d291633..26f1480bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ on: - cron: '0 0 * * *' env: - FAF_DB_VERSION: v133 + FAF_DB_VERSION: v136 FLYWAY_VERSION: 7.5.4 jobs: diff --git a/server/db/typedefs.py b/server/db/typedefs.py index 34cd99f94..482a4514e 100644 --- a/server/db/typedefs.py +++ b/server/db/typedefs.py @@ -6,10 +6,11 @@ @unique class Victory(Enum): - DEMORALIZATION = 0 - DOMINATION = 1 - ERADICATION = 2 - SANDBOX = 3 + DEMORALIZATION = "DEMORALIZATION" + DOMINATION = "DOMINATION" + ERADICATION = "ERADICATION" + SANDBOX = "SANDBOX" + DECAPITATION = "DECAPITATION" @unique diff --git a/server/games/game.py b/server/games/game.py index 42049eabf..c2bb9b58c 100644 --- a/server/games/game.py +++ b/server/games/game.py @@ -757,7 +757,7 @@ async def update_game_stats(self): # In some cases, games can be invalidated while running: we check for # those cases when the game ends and update this record as appropriate. - game_type = str(self.game_options.get("Victory").value) + game_type = self.game_options.get("Victory") async with self._db.acquire() as conn: await conn.execute( diff --git a/tests/data/test-data.sql b/tests/data/test-data.sql index 543a5bc49..0c99d8aab 100644 --- a/tests/data/test-data.sql +++ b/tests/data/test-data.sql @@ -197,28 +197,28 @@ insert into game_featuredMods (id, gamemod, name, description, publish, git_url, (25, 'coop', 'Coop', 'Multiplayer campaign games', 1, 'https://github.com/FAForever/fa-coop.git', 'master', 'cop', TRUE); insert into game_stats (id, startTime, gameName, gameType, gameMod, host, mapId, validity) values - (1, NOW(), 'Test game', '0', 6, 1, 1, 0), - (41935, NOW(), 'MapRepetition', '0', 6, 1, NULL, 0), - (41936, NOW() + interval 1 minute, 'MapRepetition', '0', 6, 1, 1, 0), - (41937, NOW() + interval 2 minute, 'MapRepetition', '0', 6, 1, 2, 0), - (41938, NOW() + interval 3 minute, 'MapRepetition', '0', 6, 1, 3, 0), - (41939, NOW() + interval 4 minute, 'MapRepetition', '0', 6, 1, 4, 0), - (41940, NOW() + interval 5 minute, 'MapRepetition', '0', 6, 1, 5, 0), - (41941, NOW() + interval 6 minute, 'MapRepetition', '0', 6, 1, 6, 0), - (41942, NOW(), 'OldRatingNull', '0', 6, 1, NULL, 0), - (41943, NOW(), 'OldRatingLose', '0', 6, 1, NULL, 0), - (41944, NOW(), 'OldRatingWin', '0', 6, 1, NULL, 0), - (41945, NOW() + interval 7 minute, 'MapRepetition', '0', 6, 2, 6, 0), - (41946, NOW() + interval 8 minute, 'MapRepetition', '0', 6, 2, 5, 0), - (41947, NOW() + interval 9 minute, 'MapRepetition', '0', 6, 2, 4, 0), - (41948, NOW() + interval 10 minute, 'MapRepetition', '0', 6, 2, 3, 0), - (41949, NOW() + interval 1 minute, 'MapRepetition', '0', 6, 1, 7, 0), - (41950, NOW() + interval 2 minute, 'MapRepetition', '0', 6, 1, 8, 0), - (41951, NOW() + interval 3 minute, 'MapRepetition', '0', 6, 1, 9, 0), - (41952, NOW() + interval 4 minute, 'MapRepetition', '0', 6, 1, 7, 0), - (41953, NOW() + interval 1 minute, 'MapRepetition', '0', 6, 2, 5, 0), - (41954, NOW() + interval 2 minute, 'MapRepetition', '0', 6, 2, 6, 0), - (41955, NOW() + interval 3 minute, 'MapRepetition', '0', 6, 2, 7, 0); + (1, NOW(), 'Test game', 'DEMORALIZATION', 6, 1, 1, 0), + (41935, NOW(), 'MapRepetition', 'DEMORALIZATION', 6, 1, NULL, 0), + (41936, NOW() + interval 1 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 1, 0), + (41937, NOW() + interval 2 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 2, 0), + (41938, NOW() + interval 3 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 3, 0), + (41939, NOW() + interval 4 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 4, 0), + (41940, NOW() + interval 5 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 5, 0), + (41941, NOW() + interval 6 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 6, 0), + (41942, NOW(), 'OldRatingNull', 'DEMORALIZATION', 6, 1, NULL, 0), + (41943, NOW(), 'OldRatingLose', 'DEMORALIZATION', 6, 1, NULL, 0), + (41944, NOW(), 'OldRatingWin', 'DEMORALIZATION', 6, 1, NULL, 0), + (41945, NOW() + interval 7 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 6, 0), + (41946, NOW() + interval 8 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 5, 0), + (41947, NOW() + interval 9 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 4, 0), + (41948, NOW() + interval 10 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 3, 0), + (41949, NOW() + interval 1 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 7, 0), + (41950, NOW() + interval 2 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 8, 0), + (41951, NOW() + interval 3 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 9, 0), + (41952, NOW() + interval 4 minute, 'MapRepetition', 'DEMORALIZATION', 6, 1, 7, 0), + (41953, NOW() + interval 1 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 5, 0), + (41954, NOW() + interval 2 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 6, 0), + (41955, NOW() + interval 3 minute, 'MapRepetition', 'DEMORALIZATION', 6, 2, 7, 0); insert into game_player_stats (gameId, playerId, AI, faction, color, team, place, mean, deviation, scoreTime) values (1, 1, 0, 0, 0, 2, 0, 1500, 500, NOW()), From b89c8ce8feb22a6a6f7452b141065d76b4586fe9 Mon Sep 17 00:00:00 2001 From: Askaholic Date: Sat, 1 Feb 2025 17:06:56 -0500 Subject: [PATCH 2/2] Allow decapitation as a rated victory condition --- server/games/game.py | 10 ++-- tests/integration_tests/test_game.py | 84 +++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/server/games/game.py b/server/games/game.py index c2bb9b58c..56eb00bea 100644 --- a/server/games/game.py +++ b/server/games/game.py @@ -692,10 +692,12 @@ async def validate_game_mode_settings(self): await self.mark_invalid(ValidityState.UNEVEN_TEAMS_NOT_RANKED) return - valid_options = { - "Victory": (Victory.DEMORALIZATION, ValidityState.WRONG_VICTORY_CONDITION) - } - await self._validate_game_options(valid_options) + if self.game_options.get("Victory") not in ( + Victory.DEMORALIZATION, + Victory.DECAPITATION, + ): + await self.mark_invalid(ValidityState.WRONG_VICTORY_CONDITION) + return async def _validate_game_options( self, diff --git a/tests/integration_tests/test_game.py b/tests/integration_tests/test_game.py index e26f60288..f777bfe39 100644 --- a/tests/integration_tests/test_game.py +++ b/tests/integration_tests/test_game.py @@ -28,6 +28,7 @@ async def host_game( *, mod: str = "faf", visibility: str = "public", + game_options: dict = {}, **kwargs ) -> int: await proto.send_message({ @@ -42,6 +43,13 @@ async def host_game( await open_fa(proto) await read_until_command(proto, "HostGame", target="game") + for option, value in game_options.items(): + await proto.send_message({ + "target": "game", + "command": "GameOption", + "args": [option, value], + }) + return game_id @@ -62,10 +70,16 @@ async def setup_game_1v1( guest_proto: Protocol, guest_id: int, mod: str = "faf", + game_options: dict = {}, **kwargs, ): # Set up the game - game_id = await host_game(host_proto, mod=mod, **kwargs) + game_id = await host_game( + host_proto, + mod=mod, + game_options=game_options, + **kwargs, + ) await join_game(guest_proto, game_id) # Set player options await send_player_options( @@ -552,7 +566,7 @@ async def test_game_ended_broadcasts_rating_update(lobby_server, channel): @fast_forward(60) -async def test_neroxis_map_generator_rates_game(lobby_server): +async def test_neroxis_map_generator_game_rated(lobby_server): host_id, _, host_proto = await connect_and_sign_in( ("test", "test_password"), lobby_server ) @@ -615,6 +629,72 @@ async def test_neroxis_map_generator_rates_game(lobby_server): await read_until_command(host_proto, "player_info", timeout=10) +@fast_forward(60) +async def test_decapitation_game_rated(lobby_server): + host_id, _, host_proto = await connect_and_sign_in( + ("test", "test_password"), lobby_server + ) + guest_id, _, guest_proto = await connect_and_sign_in( + ("Rhiza", "puff_the_magic_dragon"), lobby_server + ) + await read_until_command(guest_proto, "game_info") + ratings = await get_player_ratings(host_proto, "test", "Rhiza") + + await setup_game_1v1( + host_proto, + host_id, + guest_proto, + guest_id, + game_options={ + "Victory": "decapitation", + } + ) + await host_proto.send_message({ + "target": "game", + "command": "EnforceRating", + "args": [] + }) + + # End the game + # Reports results + for proto in (host_proto, guest_proto): + await proto.send_message({ + "target": "game", + "command": "GameResult", + "args": [1, "victory 10"] + }) + await proto.send_message({ + "target": "game", + "command": "GameResult", + "args": [2, "defeat -10"] + }) + # Report GameEnded + for proto in (host_proto, guest_proto): + await proto.send_message({ + "target": "game", + "command": "GameEnded", + "args": [] + }) + + # Check that the ratings were updated + new_ratings = await get_player_ratings(host_proto, "test", "Rhiza") + + assert ratings["test"][0] < new_ratings["test"][0] + assert ratings["Rhiza"][0] > new_ratings["Rhiza"][0] + + # Now disconnect both players + for proto in (host_proto, guest_proto): + await proto.send_message({ + "target": "game", + "command": "GameState", + "args": ["Ended"] + }) + + # The game should only be rated once + with pytest.raises(asyncio.TimeoutError): + await read_until_command(host_proto, "player_info", timeout=10) + + @fast_forward(30) async def test_double_host_message(lobby_server): _, _, proto = await connect_and_sign_in(