Skip to content

Commit

Permalink
Refactor game options handling / Issue/#859 game title constraints (#992
Browse files Browse the repository at this point in the history
)

* Refactor how game maps are handled

* Refactor how game options are handled

* Misc cleanup

* Update note about maps/name.zip format

* Add comment to deprecate `map_file_path` in `game_info` messages

* Strip whitespace from game title and enforce minimum length
  • Loading branch information
Askaholic authored Jan 1, 2024
1 parent 224661c commit 6d9ffe8
Show file tree
Hide file tree
Showing 26 changed files with 799 additions and 248 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ aio_pika = "~=8.2"
aiocron = "*"
aiohttp = "*"
aiomysql = {git = "https://github.com/aio-libs/aiomysql"}
cachetools = "*"
docopt = "*"
humanize = ">=2.6.0"
maxminddb = "*"
Expand Down
11 changes: 10 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 62 additions & 15 deletions server/game_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
from typing import Optional, Union, ValuesView

import aiocron
from sqlalchemy import select
from cachetools import LRUCache
from sqlalchemy import func, select

from server.config import config

from . import metrics
from .core import Service
from .db import FAFDatabase
from .db.models import game_featuredMods
from .db.models import game_featuredMods, map_version
from .decorators import with_logger
from .exceptions import DisabledError
from .games import (
Expand All @@ -30,6 +31,7 @@
from .message_queue_service import MessageQueueService
from .players import Player
from .rating_service import RatingService
from .types import MAP_DEFAULT, Map, NeroxisGeneratedMap


@with_logger
Expand Down Expand Up @@ -57,12 +59,15 @@ def __init__(
self._allow_new_games = False
self._drain_event = None

# Populated below in really_update_static_ish_data.
# Populated below in update_data.
self.featured_mods = dict()

# A set of mod ids that are allowed in ranked games
self.ranked_mods: set[str] = set()

# A cache of map_version info needed by Game
self.map_info_cache = LRUCache(maxsize=256)

# The set of active games
self._games: dict[int, Game] = dict()

Expand Down Expand Up @@ -96,14 +101,16 @@ async def update_data(self):
time we need, but which can in principle change over time.
"""
async with self._db.acquire() as conn:
rows = await conn.execute(select(
game_featuredMods.c.id,
game_featuredMods.c.gamemod,
game_featuredMods.c.name,
game_featuredMods.c.description,
game_featuredMods.c.publish,
game_featuredMods.c.order
).select_from(game_featuredMods))
rows = await conn.execute(
select(
game_featuredMods.c.id,
game_featuredMods.c.gamemod,
game_featuredMods.c.name,
game_featuredMods.c.description,
game_featuredMods.c.publish,
game_featuredMods.c.order
)
)

for row in rows:
self.featured_mods[row.gamemod] = FeaturedMod(
Expand All @@ -115,11 +122,51 @@ async def update_data(self):
row.order
)

result = await conn.execute("SELECT uid FROM table_mod WHERE ranked = 1")
result = await conn.execute(
"SELECT uid FROM table_mod WHERE ranked = 1"
)

# Turn resultset into a list of uids
self.ranked_mods = {row.uid for row in result}

async def get_map(self, folder_name: str) -> Map:
folder_name = folder_name.lower()
filename = f"maps/{folder_name}.zip"

map = self.map_info_cache.get(filename)
if map is not None:
return map

async with self._db.acquire() as conn:
result = await conn.execute(
select(
map_version.c.id,
map_version.c.filename,
map_version.c.ranked,
)
.where(
func.lower(map_version.c.filename) == filename
)
)
row = result.fetchone()
if not row:
# The map requested is not in the database. This is fine as
# players may be using privately shared or generated maps that
# are not in the vault.
return Map(
id=None,
folder_name=folder_name,
ranked=NeroxisGeneratedMap.is_neroxis_map(folder_name),
)

map = Map(
id=row.id,
folder_name=folder_name,
ranked=row.ranked
)
self.map_info_cache[filename] = map
return map

def mark_dirty(self, obj: Union[Game, MatchmakerQueue]):
if isinstance(obj, Game):
self._dirty_games.add(obj)
Expand Down Expand Up @@ -150,7 +197,7 @@ def create_game(
visibility=VisibilityState.PUBLIC,
host: Optional[Player] = None,
name: Optional[str] = None,
mapname: Optional[str] = None,
map: Map = MAP_DEFAULT,
password: Optional[str] = None,
matchmaker_queue_id: Optional[int] = None,
**kwargs
Expand All @@ -164,10 +211,10 @@ def create_game(
game_id = self.create_uid()
game_args = {
"database": self._db,
"id_": game_id,
"id": game_id,
"host": host,
"name": name,
"map_": mapname,
"map": map,
"game_mode": game_mode,
"game_service": self,
"game_stats_service": self.game_stats_service,
Expand Down
29 changes: 5 additions & 24 deletions server/gameconnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
GameConnectionState,
GameError,
GameState,
ValidityState,
Victory
ValidityState
)
from .games.typedefs import FA
from .player_service import PlayerService
Expand Down Expand Up @@ -134,7 +133,7 @@ async def _handle_lobby_state(self):
"""
player_state = self.player.state
if player_state == PlayerState.HOSTING:
await self.send_HostGame(self.game.map_folder_name)
await self.send_HostGame(self.game.map.folder_name)
self.game.set_hosted()
# If the player is joining, we connect him to host
# followed by the rest of the players.
Expand Down Expand Up @@ -228,25 +227,7 @@ async def handle_game_option(self, key: str, value: Any):
if not self.is_host():
return

if key == "Victory":
self.game.gameOptions["Victory"] = Victory.__members__.get(
value.upper(), None
)
else:
self.game.gameOptions[key] = value

if key == "Slots":
self.game.max_players = int(value)
elif key == "ScenarioFile":
raw = repr(value)
self.game.map_scenario_path = \
raw.replace("\\", "/").replace("//", "/").replace("'", "")
self.game.map_file_path = "maps/{}.zip".format(
self.game.map_scenario_path.split("/")[2].lower()
)
elif key == "Title":
with contextlib.suppress(ValueError):
self.game.name = value
await self.game.game_options.set_option(key, value)

self._mark_dirty()

Expand Down Expand Up @@ -339,13 +320,13 @@ async def handle_operation_complete(
async with self._db.acquire() as conn:
result = await conn.execute(
select(coop_map.c.id).where(
coop_map.c.filename == self.game.map_file_path
coop_map.c.filename == self.game.map.file_path
)
)
row = result.fetchone()
if not row:
self._logger.debug(
"can't find coop map: %s", self.game.map_file_path
"can't find coop map: %s", self.game.map.file_path
)
return
mission = row.id
Expand Down
3 changes: 2 additions & 1 deletion server/games/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from .coop import CoopGame
from .custom_game import CustomGame
from .game import Game, GameError
from .game import Game, GameError, GameOptions
from .ladder_game import LadderGame
from .typedefs import (
FeaturedModType,
Expand Down Expand Up @@ -37,6 +37,7 @@ class FeaturedMod(NamedTuple):
"Game",
"GameConnectionState",
"GameError",
"GameOptions",
"GameState",
"GameType",
"InitMode",
Expand Down
2 changes: 1 addition & 1 deletion server/games/coop.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.validity = ValidityState.COOP_NOT_RANKED
self.gameOptions.update({
self.game_options.update({
"Victory": Victory.SANDBOX,
"TeamSpawn": "fixed",
"RevealedCivilians": "No",
Expand Down
4 changes: 2 additions & 2 deletions server/games/custom_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ class CustomGame(Game):
init_mode = InitMode.NORMAL_LOBBY
game_type = GameType.CUSTOM

def __init__(self, id_, *args, **kwargs):
def __init__(self, id, *args, **kwargs):
new_kwargs = {
"rating_type": RatingType.GLOBAL,
"setup_timeout": 30
}
new_kwargs.update(kwargs)
super().__init__(id_, *args, **new_kwargs)
super().__init__(id, *args, **new_kwargs)

async def _run_pre_rate_validity_checks(self):
limit = len(self.players) * 60
Expand Down
Loading

0 comments on commit 6d9ffe8

Please sign in to comment.