1
+ /* eslint-disable no-mixed-spaces-and-tabs */
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
1
3
import { Injectable } from '@angular/core' ;
4
+ import { GameFormat , GameType } from '@firestone-hs/reference-data' ;
5
+ import { GameStateUpdatesService , Metadata } from '@firestone/game-state' ;
6
+ import { MatchInfo , PlayerInfo , Rank } from '@firestone/memory' ;
2
7
import { GameStatusService , PreferencesService } from '@firestone/shared/common/service' ;
3
- import { BehaviorSubject , combineLatest , map , of , switchMap , tap } from 'rxjs' ;
8
+ import { CardsFacadeService , ILocalizationService } from '@firestone/shared/framework/core' ;
9
+ import { BehaviorSubject , combineLatest , distinctUntilChanged , map , of , shareReplay , switchMap , tap } from 'rxjs' ;
10
+ import { IN_GAME_TEXT_PLACEHOLDER } from './discord-presence-manager.service' ;
4
11
5
12
@Injectable ( )
6
13
export class PresenceManagerService {
7
14
public presence$$ = new BehaviorSubject < Presence | null > ( null ) ;
8
15
9
- constructor ( private readonly prefs : PreferencesService , private readonly gameStatus : GameStatusService ) {
16
+ constructor (
17
+ private readonly prefs : PreferencesService ,
18
+ private readonly gameStatus : GameStatusService ,
19
+ private readonly gameStateUpdates : GameStateUpdatesService ,
20
+ private readonly i18n : ILocalizationService ,
21
+ private readonly allCards : CardsFacadeService ,
22
+ ) {
10
23
this . init ( ) ;
11
24
}
12
25
13
26
private async init ( ) {
14
27
await this . prefs . isReady ( ) ;
15
28
await this . gameStatus . isReady ( ) ;
29
+ await this . gameStateUpdates . isReady ( ) ;
30
+ await this . allCards . waitForReady ( ) ;
16
31
17
32
this . prefs
18
33
. preferences$ ( ( prefs ) => prefs . discordRichPresence )
@@ -29,17 +44,160 @@ export class PresenceManagerService {
29
44
} ) ;
30
45
}
31
46
47
+ // TODO: support Arena (W-L), Duels (MMR + W-L), Battlegrounds (MMR), Mercs
32
48
private buildPresence ( ) {
33
- return combineLatest ( [ this . gameStatus . inGame$$ ] ) . pipe (
34
- map ( ( [ inGame ] ) => ( {
35
- enabled : true ,
36
- inGame : inGame ?? false ,
37
- } ) ) ,
49
+ const metaData$ = this . gameStateUpdates . gameState$$ . pipe (
50
+ map ( ( gameState ) => gameState ?. metadata ) ,
51
+ distinctUntilChanged ( ) ,
52
+ tap ( ( metadata ) => console . debug ( '[presence] new metadata' , metadata ) ) ,
53
+ shareReplay ( 1 ) ,
38
54
) ;
55
+ const matchInfo$ = this . gameStateUpdates . gameState$$ . pipe (
56
+ map ( ( gameState ) => gameState ?. matchInfo ) ,
57
+ distinctUntilChanged ( ) ,
58
+ tap ( ( matchInfo ) => console . debug ( '[presence] new matchInfo' , matchInfo ) ) ,
59
+ shareReplay ( 1 ) ,
60
+ ) ;
61
+ const playerHero$ = this . gameStateUpdates . gameState$$ . pipe (
62
+ map ( ( gameState ) => gameState ?. playerDeck ?. hero ?. cardId ) ,
63
+ distinctUntilChanged ( ) ,
64
+ tap ( ( hero ) => console . debug ( '[presence] new hero' , hero ) ) ,
65
+ shareReplay ( 1 ) ,
66
+ ) ;
67
+ return combineLatest ( [
68
+ this . gameStatus . inGame$$ ,
69
+ //TODO: premium status. Wait until we migrate to tebex, so that we can use the refactored services?
70
+ this . prefs . preferences$ (
71
+ ( prefs ) => prefs . discordRpcEnableCustomInGameText ,
72
+ ( prefs ) => prefs . discordRpcEnableCustomInMatchText ,
73
+ ( prefs ) => prefs . discordRpcCustomInGameText ,
74
+ ( prefs ) => prefs . discordRpcCustomInMatchText ,
75
+ ) ,
76
+ metaData$ ,
77
+ matchInfo$ ,
78
+ playerHero$ ,
79
+ ] ) . pipe (
80
+ map (
81
+ ( [
82
+ inGame ,
83
+ [ enableCustomInGameText , enableCustomInMatchText , gameText , matchText ] ,
84
+ metaData ,
85
+ matchInfo ,
86
+ playerHero ,
87
+ ] ) => ( {
88
+ enabled : true ,
89
+ inGame : inGame ?? false ,
90
+ text :
91
+ inGame && ( ( enableCustomInGameText && gameText ) || ( enableCustomInMatchText && matchText ) )
92
+ ? this . buildCustomText (
93
+ enableCustomInGameText ? gameText : null ,
94
+ enableCustomInMatchText ? matchText : null ,
95
+ metaData ,
96
+ matchInfo ,
97
+ playerHero ,
98
+ ) ?? IN_GAME_TEXT_PLACEHOLDER
99
+ : IN_GAME_TEXT_PLACEHOLDER ,
100
+ } ) ,
101
+ ) ,
102
+ ) ;
103
+ }
104
+
105
+ private buildCustomText (
106
+ gameText : string | null ,
107
+ matchText : string | null ,
108
+ metaData : Metadata | undefined ,
109
+ matchInfo : MatchInfo | undefined ,
110
+ playerHero : string | undefined ,
111
+ // additionalResult: string | undefined,
112
+ ) : string | null | undefined {
113
+ if ( ! metaData || ! matchInfo ?. localPlayer ) {
114
+ return gameText ?? this . i18n . translateString ( 'settings.general.discord.in-game-text-default-value' ) ;
115
+ }
116
+ const mode = this . i18n . translateString ( `global.game-mode.${ metaData . gameType } ` ) ! ;
117
+ const rank = this . buildRankText ( matchInfo , metaData ) ;
118
+ // const [wins, losses] = additionalResult?.includes('-') ? additionalResult.split('-') : [null, null];
119
+ const hero = this . allCards . getCard ( playerHero ?? '' ) ?. name ?? 'Unknown hero' ;
120
+ return (
121
+ matchText
122
+ ?. replace ( '{rank}' , rank ?? '' )
123
+ . replace ( '{mode}' , mode )
124
+ . replace ( '{hero}' , hero ) ??
125
+ this . i18n . translateString ( 'settings.general.discord.in-game-text-in-match-default-value' )
126
+ ) ;
127
+ // .replace('{wins}', wins ?? '')
128
+ // .replace('{losses}', losses ?? '');
129
+ }
130
+
131
+ private buildRankText ( matchInfo : MatchInfo , metaData : Metadata ) : string | null {
132
+ switch ( metaData . gameType ) {
133
+ case GameType . GT_PVPDR :
134
+ case GameType . GT_PVPDR_PAID :
135
+ console . warn ( 'missing duels support for now' ) ;
136
+ return this . i18n . translateString ( 'settings.general.discord.in-game-text.mmr' , {
137
+ value : '???' ,
138
+ } ) ;
139
+ case GameType . GT_RANKED :
140
+ return this . buildRankedRank ( matchInfo . localPlayer , metaData . formatType ) ;
141
+ case GameType . GT_BATTLEGROUNDS :
142
+ case GameType . GT_BATTLEGROUNDS_FRIENDLY :
143
+ case GameType . GT_BATTLEGROUNDS_AI_VS_AI :
144
+ case GameType . GT_BATTLEGROUNDS_PLAYER_VS_AI :
145
+ console . warn ( 'missing duels support for now' ) ;
146
+ return this . i18n . translateString ( 'settings.general.discord.in-game-text.mmr' , {
147
+ value : '???' ,
148
+ } ) ;
149
+ default :
150
+ return null ;
151
+ }
152
+ }
153
+
154
+ private buildRankedRank ( player : PlayerInfo , format : GameFormat ) : string | null {
155
+ switch ( format ) {
156
+ case GameFormat . FT_STANDARD :
157
+ return this . toRankedString ( player . standard ) ;
158
+ case GameFormat . FT_WILD :
159
+ return this . toRankedString ( player . wild ) ;
160
+ case GameFormat . FT_CLASSIC :
161
+ return this . toRankedString ( player . classic ) ;
162
+ case GameFormat . FT_TWIST :
163
+ return this . toRankedString ( player . twist ) ;
164
+ default :
165
+ return null ;
166
+ }
167
+ }
168
+
169
+ private toRankedString ( rank : Rank | undefined ) : string | null {
170
+ if ( ! rank ) {
171
+ return 'Unknown rank' ;
172
+ }
173
+ if ( rank . leagueId === 0 ) {
174
+ return this . i18n . translateString ( 'settings.general.discord.in-game-text.legend' , { rank : rank . legendRank } ) ;
175
+ } else {
176
+ return this . i18n . translateString ( 'settings.general.discord.in-game-text.rank' , {
177
+ league : this . i18n . translateString ( `global.ranks.constructed.${ this . rankToLeague ( rank . leagueId ) } ` ) ,
178
+ rank : rank . rankValue ,
179
+ } ) ;
180
+ }
181
+ }
182
+
183
+ private rankToLeague ( rank : number ) : string | null {
184
+ if ( rank < 10 ) {
185
+ return this . i18n . translateString ( 'global.ranks.constructed.bronze' ) ;
186
+ } else if ( rank < 20 ) {
187
+ return this . i18n . translateString ( 'global.ranks.constructed.silver' ) ;
188
+ } else if ( rank < 30 ) {
189
+ return this . i18n . translateString ( 'global.ranks.constructed.gold' ) ;
190
+ } else if ( rank < 40 ) {
191
+ return this . i18n . translateString ( 'global.ranks.constructed.platinum' ) ;
192
+ } else if ( rank < 50 ) {
193
+ return this . i18n . translateString ( 'global.ranks.constructed.diamond' ) ;
194
+ }
195
+ return this . i18n . translateString ( 'global.ranks.constructed.legend' ) ;
39
196
}
40
197
}
41
198
42
199
export interface Presence {
43
200
readonly enabled : boolean ;
44
201
readonly inGame ?: boolean ;
202
+ readonly text ?: string ;
45
203
}
0 commit comments