Skip to content

Commit 550d1af

Browse files
committed
feat: add /vm add commands
1 parent 4ea881f commit 550d1af

2 files changed

Lines changed: 98 additions & 13 deletions

File tree

rcon/user_config/vote_map.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,10 @@ class DefaultMethods(str, enum.Enum):
3939

4040
class VoteFlag(BaseModel):
4141
flag: str = Field(
42-
...,
4342
description="A single-character flag",
4443
min_length=1,
4544
)
4645
vote_count: int = Field(
47-
...,
4846
ge=0,
4947
le=100,
5048
description="Number of votes (must be 0 or greater)"

rcon/vote_map.py

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313

1414
from rcon import maps
1515
from rcon.cache_utils import get_redis_client
16-
from rcon.maps import categorize_maps, numbered_maps, sort_maps_by_gamemode
16+
from rcon.maps import (
17+
Environment,
18+
GameMode,
19+
categorize_maps,
20+
sort_maps_by_gamemode,
21+
)
1722
from rcon.models import PlayerID, PlayerOptins, enter_session
1823
from rcon.player_history import get_player
1924
from rcon.rcon import CommandFailedError, Rcon, get_rcon
@@ -482,7 +487,9 @@ def handle_vote_command(self, struct_log: StructuredLogLineWithMetaData) -> bool
482487
cmd_handler = VoteMapCommandHandler(self, self._rcon, self._config_loader())
483488
return cmd_handler.execute(struct_log, player_id)
484489

485-
def register_vote(self, player_id: str, timestamp: int, entry: int, count: int | None = None):
490+
def register_vote(
491+
self, player_id: str, timestamp: int, entry: int, count: int | None = None
492+
):
486493
"""
487494
Checks whether the player exists, the vote is not too old and
488495
if there is any game going on.\n
@@ -541,7 +548,9 @@ def register_vote(self, player_id: str, timestamp: int, entry: int, count: int |
541548
# Check for player's flags
542549
# Pick the one with the lowest vote_count
543550
else:
544-
player_flags = list(map(lambda flag_record: flag_record["flag"], player["flags"]))
551+
player_flags = list(
552+
map(lambda flag_record: flag_record["flag"], player["flags"])
553+
)
545554
config_flags = self._config_loader().vote_flags
546555
flag_vote_counts = []
547556
for flag in config_flags:
@@ -578,7 +587,7 @@ def register_vote(self, player_id: str, timestamp: int, entry: int, count: int |
578587
return selected_map_id
579588

580589
@validate_map_ids
581-
def register_player_choice(self, map_id: str, player_id: str, player_name: str):
590+
def register_player_choice(self, map_id: str, player_id: str):
582591
if self.get_player_choice():
583592
raise Exception("Player's map choice was already selected.")
584593

@@ -606,6 +615,8 @@ def register_player_choice(self, map_id: str, player_id: str, player_name: str):
606615
f"Map choice not registered!\nSomething went wrong while searching for your {player_id=} profile."
607616
)
608617

618+
player_name = player["names"][0]["name"]
619+
609620
self._state.set_player_choice(player_id, player_name)
610621
# Access state's method directly to prevent sorting by game mode
611622
self._state.set_selection([map_id] + selection)
@@ -911,7 +922,10 @@ class VoteMapCommandHandler:
911922
SHOW_CMD_PATTERN = re.compile(r"(!votemap|!vm)$", re.IGNORECASE)
912923
NEVER_CMD_PATTERN = re.compile(r"(!votemap|!vm)\s*never$", re.IGNORECASE)
913924
ALLOW_CMD_PATTERN = re.compile(r"(!votemap|!vm)\s*allow$", re.IGNORECASE)
914-
CHOICE_CMD_PATTERN = re.compile(r"(!votemap|!vm)\s*add$", re.IGNORECASE)
925+
CHOICE_HELP_CMD_PATTERN = re.compile(r"(!votemap|!vm)\s*add\s*help$", re.IGNORECASE)
926+
CHOICE_CMD_PATTERN = re.compile(
927+
r"(!votemap|!vm)\s*add\s+(\w{3}\s+\w+\s+\w+|\w{3}\s+\w+|\w{3})", re.IGNORECASE
928+
)
915929

916930
def __init__(self, votemap: VoteMap, rcon: Rcon, config: VoteMapUserConfig):
917931
self.votemap = votemap
@@ -934,8 +948,11 @@ def execute(self, log: StructuredLogLineWithMetaData, player_id: str):
934948
self.handle_opt_out(player_id)
935949
elif self.ALLOW_CMD_PATTERN.match(message):
936950
self.handle_opt_in(player_id)
937-
elif self.CHOICE_CMD_PATTERN.match(message):
938-
self.handle_player_choice(player_id, log)
951+
elif self.CHOICE_HELP_CMD_PATTERN.match(message):
952+
self.handle_player_choice_help(player_id)
953+
elif match := self.CHOICE_CMD_PATTERN.match(message):
954+
args = match.group(2).lower().split()
955+
self.handle_player_choice(player_id, *args)
939956
else:
940957
# fallback
941958
message = "INVALID COMMAND!\n" + (
@@ -948,9 +965,78 @@ def execute(self, log: StructuredLogLineWithMetaData, player_id: str):
948965

949966
return True
950967

951-
# TODO
952-
def handle_player_choice(self, player_id: str, log: StructuredLogLineWithMetaData):
953-
pass
968+
def handle_player_choice_help(self, player_id: str):
969+
all_maps = self.rcon.get_maps()
970+
tag_to_id = {m.map.tag: m.map.shortname for m in all_maps}
971+
environments = [weather.value for weather in Environment]
972+
game_modes = [mode.value for mode in GameMode]
973+
974+
975+
help_text = "How to add your map?"
976+
help_text += "\n\nType in the chat:\n'!vm add <map_tag> [game_mode] [environment]'"
977+
help_text += "\nDefaults: game_mode=warfare, environment=day"
978+
help_text += "\neg. '!vm add car' -> carentan warfare day"
979+
help_text += "\n\nMAP_TAG options:"
980+
help_text += "\n" + "\n".join(f"{key} -> {value}" for key, value in tag_to_id.items())
981+
help_text += "\n\nGAME_MODE options:"
982+
help_text += "\n" + "\n".join(game_modes)
983+
help_text += "\n\nENVIRONMENT options:"
984+
help_text += "\n" + "\n".join(environments)
985+
986+
self.rcon.message_player(player_id=player_id, message=help_text)
987+
988+
def handle_player_choice(
989+
self,
990+
player_id: str,
991+
map_tag: str,
992+
game_mode: str = "warfare",
993+
environment: str = "day",
994+
):
995+
all_maps = self.rcon.get_maps()
996+
tag_to_id = {m.map.tag: m.map.shortname for m in all_maps}
997+
environments = [weather.value for weather in Environment]
998+
game_modes = [mode.value for mode in GameMode]
999+
1000+
if map_tag.upper() not in tag_to_id:
1001+
raise Exception(
1002+
f"""INVALID MAP\n
1003+
OPTIONS:\n\
1004+
{"\n".join(f"{key} -> {value}" for key, value in tag_to_id.items())}"""
1005+
)
1006+
1007+
if game_mode not in game_modes:
1008+
raise Exception(
1009+
f"""INVALID GAME MODE\n
1010+
OPTIONS:\n\
1011+
{"\n".join(game_modes)}"""
1012+
)
1013+
1014+
if environment not in environments:
1015+
raise Exception(
1016+
f"""INVALID ENVIRONMENT\n
1017+
OPTIONS:\n
1018+
{"\n".join(environments)}"""
1019+
)
1020+
1021+
map_found = list(filter(
1022+
lambda m: m.map.tag == map_tag.upper()
1023+
and m.game_mode == game_mode
1024+
and m.environment == environment,
1025+
all_maps,
1026+
))
1027+
1028+
if len(map_found) == 0:
1029+
raise Exception(
1030+
f"""INVALID OPTIONS\n
1031+
The map with the given options does not exist.\n
1032+
OPTIONS:\n
1033+
{"\n".join([f"{map_tag=}", f"{game_mode=}", f"{environment=}"])}"""
1034+
)
1035+
1036+
map_choice = map_found[0]
1037+
self.votemap.register_player_choice(map_choice.id, player_id)
1038+
success_msg = f"SUCCESS\n\nMap selected:\n{map_choice.pretty_name}\nType !vm too see the new selection"
1039+
self.rcon.message_player(player_id=player_id, message=success_msg)
9541040

9551041
def handle_vote(
9561042
self, player_id: str, log: StructuredLogLineWithMetaData, entry: int
@@ -1240,6 +1326,7 @@ class VoteMapNoInitialised(Exception):
12401326
class PlayerNotFound(Exception):
12411327
pass
12421328

1329+
12431330
class PlayerVoteMapBan(Exception):
12441331
def __init__(self, message="Player is banned from voting"):
1245-
super().__init__(message)
1332+
super().__init__(message)

0 commit comments

Comments
 (0)