Skip to content

Commit 20b59bb

Browse files
committed
✨Add Nest of Thorns
1 parent 9ba0f58 commit 20b59bb

File tree

2 files changed

+68
-91
lines changed

2 files changed

+68
-91
lines changed

src/modules/public/dota_rp_flow/component.py

Lines changed: 67 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from utils.dota import SteamUserUpdate
3838
from utils.dota.schemas import opendota
3939

40-
type ActiveMatch = PlayMatch | WatchMatch | UnsupportedMatch
40+
type ActiveMatch = PlayingMatch | SpectatingMatch | UnsupportedActivity
4141

4242
class ScoreQueryRow(TypedDict):
4343
friend_id: int
@@ -92,46 +92,36 @@ def __str__(self) -> str:
9292
return f"<{self.__class__.__name__}>"
9393

9494

95-
@dataclass(slots=True)
96-
class NotInDota(Activity): ...
97-
98-
99-
@dataclass(slots=True)
100-
class Transition(Activity): ...
101-
102-
10395
@dataclass(slots=True)
10496
class Dashboard(Activity): ...
10597

10698

10799
@dataclass(slots=True)
108-
class Playing(Activity):
100+
class PlayingPartial(Activity):
109101
watchable_game_id: str
110102

111103

112104
@dataclass(slots=True)
113-
class Watching(Activity):
105+
class SpectatingPartial(Activity):
114106
watching_server: str
115107

116108

117109
@dataclass(slots=True)
118-
class DemoMode(Activity): ...
119-
120-
121-
@dataclass(slots=True)
122-
class BotMatch(Activity): ...
110+
class UnsupportedPartial(Activity):
111+
msg: str
123112

124113

125114
@dataclass(slots=True)
126-
class Replay(Activity): ...
115+
class NotInDota(Activity): ...
127116

128117

129118
@dataclass(slots=True)
130-
class PrivateLobby(Activity): ...
119+
class Transition(Activity):
120+
msg: str
131121

132122

133123
@dataclass(slots=True)
134-
class CustomGames(Activity): ...
124+
class Unknown(Activity): ...
135125

136126

137127
class RichPresence:
@@ -170,8 +160,8 @@ def __init__(self, bot: IreBot, steam_user: Dota2SteamUser) -> None:
170160
self._bot: IreBot = bot
171161
self.steam_user: Dota2SteamUser = steam_user
172162
self.rich_presence: RichPresence = RichPresence(steam_user.rich_presence)
173-
self.active_match: PlayMatch | WatchMatch | UnsupportedMatch | None = None
174-
self.activity: Activity = Transition()
163+
self.active_match: PlayingMatch | SpectatingMatch | UnsupportedActivity | None = None
164+
self.activity: Activity = Transition("Haven't received any RP updates yet.")
175165

176166
@override
177167
def __repr__(self) -> str:
@@ -230,9 +220,9 @@ def color(self) -> str:
230220

231221
def format_match_response(func: Callable[..., Coroutine[Any, Any, str]]) -> Callable[..., Coroutine[Any, Any, str]]:
232222
@functools.wraps(func)
233-
async def wrapper(self: Match, *args: Any, **kwargs: Any) -> str:
234-
if isinstance(self, UnsupportedMatch):
235-
return self.activity_tag
223+
async def wrapper(self: LiveMatch, *args: Any, **kwargs: Any) -> str:
224+
if isinstance(self, UnsupportedActivity):
225+
return self.message
236226

237227
prefix = f"[{self.activity_tag}] " if self.activity_tag else ""
238228
response = await func(self, *args, **kwargs)
@@ -241,7 +231,7 @@ async def wrapper(self: Match, *args: Any, **kwargs: Any) -> str:
241231
return wrapper
242232

243233

244-
class Match:
234+
class LiveMatch:
245235
def __init__(
246236
self,
247237
bot: IreBot,
@@ -406,7 +396,7 @@ async def server_steam_id_command_response(self) -> str:
406396
return str(self.server_steam_id)
407397

408398

409-
class PlayMatch(Match):
399+
class PlayingMatch(LiveMatch):
410400
def __init__(self, bot: IreBot, watchable_game_id: str) -> None:
411401
super().__init__(bot)
412402
self.watchable_game_id: str = watchable_game_id
@@ -491,7 +481,7 @@ async def played_with(self, friend_id: int, last_game: MinimalMatch) -> str:
491481
return "No players from the last game present in the match"
492482

493483

494-
class WatchMatch(Match):
484+
class SpectatingMatch(LiveMatch):
495485
def __init__(self, bot: IreBot, watching_server: str) -> None:
496486
super().__init__(bot, tag="Spectating")
497487
self.watching_server: str = watching_server
@@ -538,16 +528,17 @@ async def update_data(self) -> None:
538528
self.update_data.stop()
539529

540530

541-
class UnsupportedMatch(Match):
531+
class UnsupportedActivity(LiveMatch):
542532
"""A class describing unsupported matches.
543533
544534
All chat commands for objects of this type should return unsupported message response.
545535
For example, if streamer is playing Demo Mode, then the bot should only respond with "Demo Mode is not supported",
546536
because, well, there is no data in Demo Mode to insect.
547537
"""
548538

549-
def __init__(self, bot: IreBot, tag: str = "") -> None:
550-
super().__init__(bot, tag)
539+
def __init__(self, bot: IreBot, message: str = "") -> None:
540+
super().__init__(bot, "")
541+
self.message = message
551542

552543

553544
class Dota2RichPresenceFlow(IrePublicComponent):
@@ -571,8 +562,8 @@ def __init__(self, bot: IreBot) -> None:
571562
super().__init__(bot)
572563
self.friends: dict[int, Friend] = {}
573564

574-
self.play_matches_index: dict[str, PlayMatch] = {}
575-
self.watch_matches_index: dict[str, WatchMatch] = {}
565+
self.play_matches_index: dict[str, PlayingMatch] = {}
566+
self.watch_matches_index: dict[str, SpectatingMatch] = {}
576567

577568
self.debug: bool = True
578569

@@ -636,39 +627,37 @@ async def get_activity(self, friend: Friend) -> Activity:
636627
# something is off
637628
lobby_param0 = rp.raw.get("param0") or "_missing"
638629
lobby_map: dict[str, Activity] = {
639-
LobbyParam0.DemoMode: DemoMode(),
640-
LobbyParam0.BotMatch: BotMatch(),
630+
LobbyParam0.DemoMode: UnsupportedPartial("Demo mode is not supported"),
631+
LobbyParam0.BotMatch: UnsupportedPartial("Bot matches are not supported"),
641632
}
642-
return lobby_map.get(lobby_param0, Transition())
633+
return lobby_map.get(lobby_param0, Transition("RP is `playing` but watchable_game_id=None"))
643634

644635
if watchable_game_id == "0":
645636
# something is off again
646637
# usually this happens when a player has just quit the match into the main menu
647638
# the status flickers for a few seconds to be `watchable_game_id=0`
648-
return Transition()
649-
return Playing(watchable_game_id)
639+
return Transition("RP is `playing` but watchable_game_id=0")
640+
return PlayingPartial(watchable_game_id)
650641

651642
# Watching
652643
if rp.status in {Status.Spectating, Status.WatchingTournament}:
653644
watching_server = rp.raw.get("watching_server")
654645
if watching_server is None:
655-
return Replay()
656-
return Watching(watching_server)
657-
658-
if rp.status == Status.BotPractice:
659-
return DemoMode()
660-
661-
# Private Lobby
662-
if rp.status == Status.PrivateLobby:
663-
return PrivateLobby()
664-
665-
# Custom games
666-
if rp.status == Status.CustomGameLobby:
667-
return CustomGames()
646+
return UnsupportedPartial("Data in watching replays is not supported")
647+
return SpectatingPartial(watching_server)
668648

669649
if rp.status == Status.NoStatus:
670650
# usually this happens in exact moment when the player closes Dota
671-
return Transition()
651+
return Transition("Closed Dota")
652+
653+
other_statuses = {
654+
Status.BotPractice: "Demo mode is not supported",
655+
Status.PrivateLobby: "Bot matches are not supported",
656+
Status.CustomGameLobby: "Private lobbies (this includes draft in public lobbies) are not supported",
657+
Status.CrownfallNestOfThorns: "Nest of Thorns is not supported."
658+
}
659+
if msg := other_statuses.get(rp.status):
660+
return UnsupportedPartial(msg)
672661

673662
# Unrecognized
674663
text = (
@@ -681,7 +670,7 @@ async def get_activity(self, friend: Friend) -> Activity:
681670
log.warning(text)
682671
await self.bot.error_webhook.send(content=self.bot.error_ping + "\n" + text)
683672

684-
return Transition()
673+
return Unknown()
685674

686675
async def analyze_rich_presence(self, friend: Friend) -> None:
687676
"""Analyze Rich Presence.
@@ -718,41 +707,25 @@ async def analyze_rich_presence(self, friend: Friend) -> None:
718707
# no activity changes = no need to do anything
719708
return
720709

721-
match new_activity:
722-
case Dashboard():
723-
await self.conclude_friend_match(friend)
724-
case Playing():
725-
# Friend is in a match as a player
726-
if (w_id := new_activity.watchable_game_id) not in self.play_matches_index:
727-
self.play_matches_index[w_id] = PlayMatch(self.bot, w_id)
728-
friend.active_match = self.play_matches_index[w_id]
729-
friend.active_match.friends.add(friend)
730-
case Watching():
731-
if (w_s := new_activity.watching_server) not in self.watch_matches_index:
732-
self.watch_matches_index[w_s] = WatchMatch(self.bot, w_s)
733-
friend.active_match = self.watch_matches_index[w_s]
734-
friend.active_match.friends.add(friend)
735-
case DemoMode():
736-
friend.active_match = UnsupportedMatch(self.bot, tag="Demo mode is not supported")
737-
case BotMatch():
738-
friend.active_match = UnsupportedMatch(self.bot, tag="Bot matches are not supported")
739-
case Replay():
740-
friend.active_match = UnsupportedMatch(self.bot, tag="Data in watching replays is not supported")
741-
case PrivateLobby():
742-
friend.active_match = UnsupportedMatch(
743-
self.bot, tag="Private lobbies (this includes draft in public lobbies) are not supported"
744-
)
745-
case CustomGames():
746-
friend.active_match = UnsupportedMatch(
747-
self.bot,
748-
tag="Custom Games Lobbies (in draft stage) are not supported - wait for the game to start.",
749-
)
750-
case Transition():
751-
# Wait for confirmed statuses
752-
return
753-
case _:
754-
# Wait for confirmed statuses
755-
return
710+
if isinstance(new_activity, Dashboard):
711+
await self.conclude_friend_match(friend)
712+
elif isinstance(new_activity, PlayingPartial):
713+
# Friend is in a match as a player
714+
if (w_id := new_activity.watchable_game_id) not in self.play_matches_index:
715+
self.play_matches_index[w_id] = PlayingMatch(self.bot, w_id)
716+
friend.active_match = self.play_matches_index[w_id]
717+
friend.active_match.friends.add(friend)
718+
elif isinstance(new_activity, SpectatingPartial):
719+
if (w_s := new_activity.watching_server) not in self.watch_matches_index:
720+
self.watch_matches_index[w_s] = SpectatingMatch(self.bot, w_s)
721+
friend.active_match = self.watch_matches_index[w_s]
722+
friend.active_match.friends.add(friend)
723+
elif isinstance(new_activity, UnsupportedPartial):
724+
friend.active_match = UnsupportedActivity(self.bot, message=new_activity.msg)
725+
else:
726+
# Transition, Unknown or (???)
727+
# wait for confirmed statuses
728+
return
756729

757730
# @commands.Component.listener("steam_user_update")
758731
async def steam_user_update(self, update: SteamUserUpdate) -> None:
@@ -782,7 +755,7 @@ async def conclude_friend_match(self, friend: Friend) -> None:
782755
This nulls `Friend.active_match` attribute as well as adds the match into the database
783756
if it's a play match.
784757
"""
785-
if (match := friend.active_match) and isinstance(match, PlayMatch) and match.match_id:
758+
if (match := friend.active_match) and isinstance(match, PlayingMatch) and match.match_id:
786759
player_slot = next(iter(s for (s, p) in enumerate(match.players) if p.friend_id == friend.steam_user.id), None)
787760
if player_slot is None:
788761
msg = "Somehow `player_slot` is `None` in `conclude_friend_match`"
@@ -960,7 +933,7 @@ async def played_with(self, ctx: IreContext) -> None:
960933
"""List recurring players from the last game present in the current match."""
961934
active_match = await self.find_active_match(ctx.broadcaster.id)
962935

963-
if isinstance(active_match, PlayMatch):
936+
if isinstance(active_match, PlayingMatch):
964937
friend_id, _, last_game = await self.get_last_game(ctx.broadcaster.id)
965938
response = await active_match.played_with(friend_id, last_game)
966939
else:
@@ -1208,6 +1181,9 @@ async def party(self, ctx: IreContext) -> None:
12081181
msg = "Streamer is not in a party."
12091182
await ctx.send(msg)
12101183
return
1184+
if party2 := friend.rich_presence.raw.get("party2"):
1185+
# Apparently if a party is too big, valve just slice the string into party2
1186+
party += party2
12111187

12121188
steam32_ids = [m.id for m in map(steam.ID, PARTY_MEMBERS_PATTERN.findall(party))]
12131189

@@ -1508,13 +1484,13 @@ async def debug_announce_gc_ready(self) -> None:
15081484
await self.debug_deliver("Connection to Dota 2 Game Coordinator is ready")
15091485

15101486
@commands.Component.listener("heroes_data_ready")
1511-
async def debug_announce_hero_data_ready(self, match: Match) -> None:
1487+
async def debug_announce_hero_data_ready(self, match: LiveMatch) -> None:
15121488
"""Announce in Irene's twitch chat that match heroes data is ready."""
15131489
if config["STEAM"]["IRENE_ID32"] in [f.steam_user.id for f in match.friends]:
15141490
await self.debug_deliver(f"Heroes Data for {match.match_id} is ready")
15151491

15161492
@commands.Component.listener("players_data_ready")
1517-
async def debug_announce_players_data_ready(self, match: Match) -> None:
1493+
async def debug_announce_players_data_ready(self, match: LiveMatch) -> None:
15181494
"""Announce in Irene's twitch chat that match players data is ready."""
15191495
if config["STEAM"]["IRENE_ID32"] in [f.steam_user.id for f in match.friends]:
15201496
await self.debug_deliver(f"Players Data for {match.match_id} is ready")

src/modules/public/dota_rp_flow/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Status(SteampyStrEnum):
5252
WatchingTournament = "#DOTA_RP_WATCHING_TOURNAMENT"
5353
CustomGameProgress = "#DOTA_RP_GAME_IN_PROGRESS_CUSTOM"
5454
CustomGameLobby = "#DOTA_RP_LOBBY_CUSTOM"
55+
CrownfallNestOfThorns = "#DOTA_Crownfall_EncounterStatus_NestOfThorns_WithDifficulty"
5556

5657
@classproperty
5758
def KNOWN_DISPLAY_NAMES(cls: type[Self]) -> Mapping[Status, str]: # type: ignore[GeneralTypeIssue] # noqa: N802, N805

0 commit comments

Comments
 (0)