game_variables.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import { presence } from "./util";
  2. const variableCache: Record<string, unknown> = {},
  3. variableCacheTimes: Record<string, number> = {};
  4. export enum GameScreenType {
  5. Loading,
  6. PlayerTurn,
  7. OtherTurn,
  8. DrawEnd,
  9. HandEnd,
  10. GameEnd,
  11. }
  12. /* eslint-disable camelcase */
  13. // This is just to make sure that the above line is not removed by eslint
  14. // while at the same time passing Deepscan issues.
  15. const unused_variable = (a: number, b: number) => a + b;
  16. unused_variable(1, 2);
  17. interface GameEndResult {
  18. players: {
  19. part_point_1: number; // final raw score
  20. total_point: number; // +/- value (divide by 1000 to get the displayed value)
  21. seat: number; // player index
  22. }[]; // index indicates the position of the player
  23. }
  24. /* eslint-enable camelcase */
  25. let wasDrawScreen = false;
  26. export async function getGameType(): Promise<GameScreenType> {
  27. const {
  28. "uiscript.UI_Win.Inst.enable": enableWinScreen,
  29. "uiscript.UI_ScoreChange.Inst.enable": enableScoreChangeScreen,
  30. "uiscript.UI_Huleshow.Inst.enable": enableDrawScreen,
  31. "uiscript.UI_Loading.Inst.enable": enableLoadingScreen,
  32. "view.DesktopMgr.Inst.index_player": activePlayerIndex,
  33. "view.DesktopMgr.Inst.gameEndResult": gameEndResult,
  34. "view.DesktopMgr.Inst.seat": playerIndex,
  35. } = await getVariable({
  36. "view.DesktopMgr.Inst.seat": 0,
  37. "view.DesktopMgr.Inst.gameEndResult": null as GameEndResult | null,
  38. "view.DesktopMgr.Inst.index_player": 0,
  39. "uiscript.UI_Win.Inst.enable": false,
  40. "uiscript.UI_ScoreChange.Inst.enable": false,
  41. "uiscript.UI_Huleshow.Inst.enable": false,
  42. "uiscript.UI_Loading.Inst.enable": false,
  43. });
  44. if (enableLoadingScreen) return GameScreenType.Loading;
  45. if (enableDrawScreen) wasDrawScreen = true;
  46. if (enableWinScreen) {
  47. wasDrawScreen = false;
  48. return GameScreenType.HandEnd;
  49. }
  50. if (enableScoreChangeScreen)
  51. return wasDrawScreen ? GameScreenType.DrawEnd : GameScreenType.HandEnd;
  52. if (gameEndResult) return GameScreenType.GameEnd;
  53. if (playerIndex === activePlayerIndex) return GameScreenType.PlayerTurn;
  54. return GameScreenType.OtherTurn;
  55. }
  56. export async function getBasicGameInfo(): Promise<{
  57. playerIndex: number;
  58. activePlayerIndex: number;
  59. honbaCount: number;
  60. roundCount: number;
  61. windIndex: number;
  62. tilesLeft: number;
  63. gameMode: number;
  64. gameEndResult: GameEndResult | null;
  65. }> {
  66. const {
  67. "view.DesktopMgr.Inst.seat": playerIndex,
  68. "view.DesktopMgr.Inst.index_player": activePlayerIndex,
  69. "view.DesktopMgr.Inst.index_ben": honbaCount,
  70. "view.DesktopMgr.Inst.index_ju": roundCount,
  71. "view.DesktopMgr.Inst.index_change": windIndex,
  72. "view.DesktopMgr.Inst.left_tile_count": tilesLeft,
  73. "view.DesktopMgr.Inst.game_config.mode": gameMode,
  74. "view.DesktopMgr.Inst.gameEndResult": gameEndResult,
  75. } = await getVariable({
  76. "view.DesktopMgr.Inst.seat": 0,
  77. "view.DesktopMgr.Inst.index_player": 0,
  78. "view.DesktopMgr.Inst.index_ben": 0,
  79. "view.DesktopMgr.Inst.index_ju": 0,
  80. "view.DesktopMgr.Inst.index_change": 0,
  81. "view.DesktopMgr.Inst.left_tile_count": 0,
  82. "view.DesktopMgr.Inst.game_config.mode": 0,
  83. "view.DesktopMgr.Inst.gameEndResult": null as GameEndResult | null,
  84. });
  85. return {
  86. playerIndex,
  87. activePlayerIndex,
  88. honbaCount,
  89. roundCount,
  90. windIndex,
  91. tilesLeft,
  92. gameMode,
  93. gameEndResult,
  94. };
  95. }
  96. export async function getPlayerInfo(playerIndex: number): Promise<{
  97. playerSeat: number;
  98. playerScore: number;
  99. playerName: string;
  100. }> {
  101. const {
  102. [`view.DesktopMgr.Inst.player_datas[${playerIndex}].nickname`]:
  103. playerName,
  104. } = await getVariable({
  105. [`view.DesktopMgr.Inst.player_datas[${playerIndex}].nickname`]:
  106. "Unknown Player",
  107. }),
  108. seatIndex = await getPlayerListIndexFromSeat(playerIndex),
  109. {
  110. [`view.DesktopMgr.Inst.players[${seatIndex}].seat`]: playerSeat,
  111. [`view.DesktopMgr.Inst.players[${seatIndex}].score`]: playerScore,
  112. } = await getVariable({
  113. [`view.DesktopMgr.Inst.players[${seatIndex}].seat`]: 0,
  114. [`view.DesktopMgr.Inst.players[${seatIndex}].score`]: 0,
  115. });
  116. return {
  117. playerSeat: playerSeat as number,
  118. playerScore: playerScore as number,
  119. playerName: playerName as string,
  120. };
  121. }
  122. async function getPlayerListIndexFromSeat(playerSeat: number): Promise<number> {
  123. const {
  124. "view.DesktopMgr.Inst.players[0].seat": seat0,
  125. "view.DesktopMgr.Inst.players[1].seat": seat1,
  126. "view.DesktopMgr.Inst.players[2].seat": seat2,
  127. "view.DesktopMgr.Inst.players[3].seat": seat3,
  128. } = await getVariable({
  129. "view.DesktopMgr.Inst.players[0].seat": 0,
  130. "view.DesktopMgr.Inst.players[1].seat": 1,
  131. "view.DesktopMgr.Inst.players[2].seat": 2,
  132. "view.DesktopMgr.Inst.players[3].seat": 3,
  133. });
  134. let playerIndex = 0;
  135. switch (playerSeat) {
  136. case seat0: {
  137. playerIndex = 0;
  138. break;
  139. }
  140. case seat1: {
  141. playerIndex = 1;
  142. break;
  143. }
  144. case seat2: {
  145. playerIndex = 2;
  146. break;
  147. }
  148. case seat3:
  149. {
  150. playerIndex = 3;
  151. // No default
  152. }
  153. break;
  154. }
  155. return playerIndex;
  156. }
  157. export async function getHandEndInfo(): Promise<{
  158. handScore: number;
  159. seat: number;
  160. }> {
  161. const {
  162. // while there can be more than one winner, this presence currently only displays the first
  163. "uiscript.UI_Win.Inst.data.hules[0].dadian": handScore,
  164. "uiscript.UI_Win.Inst.data.hules[0].seat": seat,
  165. } = await getVariable({
  166. "uiscript.UI_Win.Inst.data.hules[0].dadian": 0,
  167. "uiscript.UI_Win.Inst.data.hules[0].seat": 0,
  168. });
  169. return { handScore, seat };
  170. }
  171. export async function isInGame(): Promise<boolean> {
  172. const { "GameMgr.Inst.ingame": ingame } = await getVariable({
  173. "GameMgr.Inst.ingame": false,
  174. });
  175. return ingame;
  176. }
  177. export async function getAccountInfo(): Promise<{
  178. nickname: string;
  179. coppers: number;
  180. signature: string;
  181. }> {
  182. const {
  183. "GameMgr.Inst.account_data.nickname": nickname,
  184. "GameMgr.Inst.account_data.gold": coppers,
  185. "GameMgr.Inst.account_data.signature": signature,
  186. } = await getVariable({
  187. "GameMgr.Inst.account_data.nickname": "Unknown Player",
  188. "GameMgr.Inst.account_data.gold": 0,
  189. "GameMgr.Inst.account_data.signature": "",
  190. });
  191. return { nickname, coppers, signature };
  192. }
  193. export enum HomeScreenType {
  194. LoggingIn,
  195. Shop,
  196. Dorm,
  197. CreateRoom,
  198. RoomLobby,
  199. Gacha,
  200. Rules,
  201. PlayerInfo,
  202. Achievement,
  203. RankedQueue,
  204. FriendlyQueue,
  205. TournamentQueue,
  206. Loading,
  207. Home,
  208. }
  209. export async function getHomeScreenType(): Promise<HomeScreenType> {
  210. const {
  211. "uiscript.UI_Entrance.Inst.enable": enabledEntrance,
  212. "uiscript.UI_Lobby.Inst.enable": enabledLobby,
  213. "uiscript.UI_Shop.Inst.enable": enabledShop,
  214. "uiscript.UI_Sushe.Inst.enable": enabledDorm,
  215. "uiscript.UI_Create_Room.Inst.enable": enabledCreateRoom,
  216. "uiscript.UI_Treasure.Inst.enable": enabledGacha,
  217. "uiscript.UI_Rules.Inst.enable": enabledRules,
  218. "uiscript.UI_PlayerInfo.Inst.enable": enabledPlayerInfo,
  219. "uiscript.UI_Achievement.Inst.enable": enabledAchievement,
  220. "uiscript.UI_Loading.Inst.enable": enabledLoading,
  221. "uiscript.UI_WaitingRoom.Inst.enable": enabledRoomLobby,
  222. } = await getVariable({
  223. "uiscript.UI_Entrance.Inst.enable": false,
  224. "uiscript.UI_Lobby.Inst.enable": false,
  225. "uiscript.UI_Shop.Inst.enable": false,
  226. "uiscript.UI_Sushe.Inst.enable": false,
  227. "uiscript.UI_WaitingRoom.Inst.enable": false,
  228. "uiscript.UI_Treasure.Inst.enable": false,
  229. "uiscript.UI_Rules.Inst.enable": false,
  230. "uiscript.UI_PlayerInfo.Inst.enable": false,
  231. "uiscript.UI_Achievement.Inst.enable": false,
  232. "uiscript.UI_Loading.Inst.enable": false,
  233. "uiscript.UI_Create_Room.Inst.enable": false,
  234. });
  235. if (enabledEntrance) return HomeScreenType.LoggingIn;
  236. if (enabledShop) return HomeScreenType.Shop;
  237. if (enabledDorm) return HomeScreenType.Dorm;
  238. if (enabledCreateRoom) return HomeScreenType.CreateRoom;
  239. if (enabledGacha) return HomeScreenType.Gacha;
  240. if (enabledRules) return HomeScreenType.Rules;
  241. if (enabledPlayerInfo) return HomeScreenType.PlayerInfo;
  242. if (enabledAchievement) return HomeScreenType.Achievement;
  243. if (enabledLoading) return HomeScreenType.Loading;
  244. if (enabledRoomLobby) return HomeScreenType.RoomLobby;
  245. if (enabledLobby) {
  246. const { "uiscript.UI_Lobby.Inst.nowpage": lobbyPage } = await getVariable({
  247. "uiscript.UI_Lobby.Inst.nowpage": 0,
  248. });
  249. switch (lobbyPage) {
  250. case 1:
  251. return HomeScreenType.RankedQueue;
  252. case 2:
  253. return HomeScreenType.FriendlyQueue;
  254. case 3:
  255. return HomeScreenType.TournamentQueue;
  256. }
  257. return HomeScreenType.Home;
  258. }
  259. return HomeScreenType.Loading;
  260. }
  261. export async function getGachaInfo(): Promise<string> {
  262. const { "uiscript.UI_Treasure.Inst.tab_index": tabIndex } = await getVariable(
  263. {
  264. "uiscript.UI_Treasure.Inst.tab_index": 0,
  265. }
  266. ),
  267. { [`uiscript.UI_Treasure.Inst.tabs[${tabIndex}].name.text`]: tabName } =
  268. await getVariable({
  269. [`uiscript.UI_Treasure.Inst.tabs[${tabIndex}].name.text`]:
  270. "Unknown Tab",
  271. });
  272. return tabName;
  273. }
  274. export async function getAchievementInfo(): Promise<{
  275. totalAchievementCount: number;
  276. totalCountString: string;
  277. bronzeAchievementCount: string;
  278. silverAchievementCount: string;
  279. goldAchievementCount: string;
  280. }> {
  281. const {
  282. "uiscript.UI_Achievement.Inst.total_achievement_count":
  283. totalAchievementCount,
  284. "uiscript.UI_Achievement.Inst.total_count.text": totalCountString,
  285. "uiscript.UI_Achievement.Inst.total_achievement.lst[0].text":
  286. bronzeAchievementCount,
  287. "uiscript.UI_Achievement.Inst.total_achievement.lst[1].text":
  288. silverAchievementCount,
  289. "uiscript.UI_Achievement.Inst.total_achievement.lst[2].text":
  290. goldAchievementCount,
  291. } = await getVariable({
  292. "uiscript.UI_Achievement.Inst.total_achievement_count": 0,
  293. "uiscript.UI_Achievement.Inst.total_count.text": "0",
  294. "uiscript.UI_Achievement.Inst.total_achievement.lst[0].text": "0",
  295. "uiscript.UI_Achievement.Inst.total_achievement.lst[1].text": "0",
  296. "uiscript.UI_Achievement.Inst.total_achievement.lst[2].text": "0",
  297. });
  298. return {
  299. totalAchievementCount,
  300. totalCountString,
  301. bronzeAchievementCount,
  302. silverAchievementCount,
  303. goldAchievementCount,
  304. };
  305. }
  306. export async function getWaitingRoomInfo(): Promise<{
  307. roomId: number;
  308. mode: string;
  309. maxPlayers: number;
  310. currentPlayers: number;
  311. }> {
  312. const {
  313. "uiscript.UI_WaitingRoom.Inst.room_id": roomId,
  314. "uiscript.UI_WaitingRoom.Inst.container_rules.modes[0]": mode,
  315. "uiscript.UI_WaitingRoom.Inst.max_player_count": maxPlayers,
  316. "uiscript.UI_WaitingRoom.Inst.players.length": currentPlayers,
  317. } = await getVariable({
  318. "uiscript.UI_WaitingRoom.Inst.room_id": 0,
  319. "uiscript.UI_WaitingRoom.Inst.container_rules.modes[0]": "Unknown Mode",
  320. "uiscript.UI_WaitingRoom.Inst.max_player_count": 0,
  321. "uiscript.UI_WaitingRoom.Inst.players.length": 0,
  322. });
  323. return { roomId, mode, maxPlayers, currentPlayers };
  324. }
  325. export async function getVariable<T extends Record<string, unknown>>(
  326. fallback: T
  327. ): Promise<T> {
  328. const variables = Object.keys(fallback),
  329. results = await Promise.all(
  330. variables.map(variable => getVariableWrapper(variable).catch(() => ({})))
  331. ),
  332. output = Object.assign({}, fallback);
  333. for (const result of results) Object.assign(output, result);
  334. return output as T;
  335. }
  336. function getVariableWrapper(
  337. variable: string
  338. ): Promise<Record<string, unknown>> {
  339. const now = performance.now();
  340. if (variableCacheTimes[variable] && now - variableCacheTimes[variable] < 1500)
  341. return Promise.resolve(variableCache[variable] as Record<string, unknown>);
  342. return new Promise((resolve, reject) => {
  343. const timeout = setTimeout(() => {
  344. reject(new Error("Variable retrieval timed out"));
  345. }, 500);
  346. presence
  347. .getPageVariable(variable)
  348. .then(result => {
  349. variableCache[variable] = result;
  350. variableCacheTimes[variable] = now;
  351. resolve(result);
  352. })
  353. .catch(reject)
  354. .finally(() => {
  355. clearTimeout(timeout);
  356. });
  357. });
  358. }