Commit 5cb666a6 authored by nanahira's avatar nanahira

add direct join

parent 64d1a5bb
Pipeline #43289 failed with stages
in 41 seconds
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
"ipaddr.js": "^2.3.0", "ipaddr.js": "^2.3.0",
"koishi": "^4.18.10", "koishi": "^4.18.10",
"koishipro-core.js": "^1.3.4", "koishipro-core.js": "^1.3.4",
"nfkit": "^1.0.33", "nfkit": "^1.0.35",
"p-queue": "6.6.2", "p-queue": "6.6.2",
"pg": "^8.18.0", "pg": "^8.18.0",
"pino": "^10.3.1", "pino": "^10.3.1",
...@@ -6353,9 +6353,9 @@ ...@@ -6353,9 +6353,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nfkit": { "node_modules/nfkit": {
"version": "1.0.33", "version": "1.0.35",
"resolved": "https://registry.npmjs.org/nfkit/-/nfkit-1.0.33.tgz", "resolved": "https://registry.npmjs.org/nfkit/-/nfkit-1.0.35.tgz",
"integrity": "sha512-mhF4ZAoGUD3cI0sB/+qH2AothZG2j5y18FkyTKF6etR6nod8jBJWQ5hAr3Q6HnaWlG3HpUKN5i1wfZqQP6hyZw==", "integrity": "sha512-hixpSFi84StHoZPbhpJTfMBKjtnYj8K+aV7dlV6VmIFZ/MdTba7XDsq77R9tH/BaHnMg0q+szLQwpn4GqqF6gQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-int64": { "node_modules/node-int64": {
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
"ipaddr.js": "^2.3.0", "ipaddr.js": "^2.3.0",
"koishi": "^4.18.10", "koishi": "^4.18.10",
"koishipro-core.js": "^1.3.4", "koishipro-core.js": "^1.3.4",
"nfkit": "^1.0.33", "nfkit": "^1.0.35",
"p-queue": "6.6.2", "p-queue": "6.6.2",
"pg": "^8.18.0", "pg": "^8.18.0",
"pino": "^10.3.1", "pino": "^10.3.1",
......
...@@ -124,6 +124,8 @@ export const TRANSLATIONS = { ...@@ -124,6 +124,8 @@ export const TRANSLATIONS = {
cloud_replay_detail_players: 'Duel: ', cloud_replay_detail_players: 'Duel: ',
cloud_replay_detail_score: 'Score: ', cloud_replay_detail_score: 'Score: ',
cloud_replay_detail_winner: 'Winner: ', cloud_replay_detail_winner: 'Winner: ',
cloud_replay_detail_access_hint_part1: 'Host Password: enter ',
cloud_replay_detail_access_hint_part2: ' to access this replay.',
cloud_replay_menu_play: 'Play Cloud Replay', cloud_replay_menu_play: 'Play Cloud Replay',
cloud_replay_menu_download_yrp: 'Download YRP Replay', cloud_replay_menu_download_yrp: 'Download YRP Replay',
cloud_replay_menu_back: 'Back', cloud_replay_menu_back: 'Back',
...@@ -247,6 +249,8 @@ export const TRANSLATIONS = { ...@@ -247,6 +249,8 @@ export const TRANSLATIONS = {
cloud_replay_detail_players: '对局:', cloud_replay_detail_players: '对局:',
cloud_replay_detail_score: '比分:', cloud_replay_detail_score: '比分:',
cloud_replay_detail_winner: '胜者:', cloud_replay_detail_winner: '胜者:',
cloud_replay_detail_access_hint_part1: '主机密码输入 ',
cloud_replay_detail_access_hint_part2: ' 即可访问本录像。',
cloud_replay_menu_play: '播放云录像', cloud_replay_menu_play: '播放云录像',
cloud_replay_menu_download_yrp: '下载 YRP 录像', cloud_replay_menu_download_yrp: '下载 YRP 录像',
cloud_replay_menu_back: '返回', cloud_replay_menu_back: '返回',
......
...@@ -45,6 +45,13 @@ type ReplayPage = { ...@@ -45,6 +45,13 @@ type ReplayPage = {
nextCursor?: number; nextCursor?: number;
}; };
type ReplayDetailMenuOptions = {
includeBack: boolean;
showReplayPassHint: boolean;
};
type DirectReplayPassAction = 'detail' | 'watch' | 'downloadYrp';
declare module '../../room' { declare module '../../room' {
interface Room { interface Room {
identifier?: string; identifier?: string;
...@@ -78,7 +85,12 @@ export class CloudReplayService { ...@@ -78,7 +85,12 @@ export class CloudReplayService {
async tryHandleJoinPass(pass: string, client: Client) { async tryHandleJoinPass(pass: string, client: Client) {
const normalized = (pass || '').trim().toUpperCase(); const normalized = (pass || '').trim().toUpperCase();
if (!normalized || !['R', 'W'].includes(normalized)) { if (!normalized) {
return false;
}
const directPass = this.parseDirectReplayPass(normalized);
if (!['R', 'W'].includes(normalized) && !directPass) {
return false; return false;
} }
...@@ -92,6 +104,27 @@ export class CloudReplayService { ...@@ -92,6 +104,27 @@ export class CloudReplayService {
return true; return true;
} }
if (directPass) {
const replayId = this.parseReplayId(directPass.replayIdText);
if (replayId == null) {
await client.die('#{cloud_replay_no}', ChatColor.RED);
return true;
}
if (directPass.action === 'detail') {
await this.openReplayDetailMenuById(client, replayId);
return true;
}
if (directPass.action === 'watch') {
await this.playReplayById(client, replayId);
return true;
}
await this.downloadReplayYrpById(client, replayId);
return true;
}
await this.openReplayListMenu(client); await this.openReplayListMenu(client);
return true; return true;
} }
...@@ -207,11 +240,6 @@ export class CloudReplayService { ...@@ -207,11 +240,6 @@ export class CloudReplayService {
private async renderReplayListMenu(client: Client) { private async renderReplayListMenu(client: Client) {
await this.menuManager.launchMenu(client, async () => { await this.menuManager.launchMenu(client, async () => {
const page = await this.getReplayPage(client); const page = await this.getReplayPage(client);
if (!page.entries.length) {
await client.die('#{cloud_replay_no}', ChatColor.RED);
return;
}
const menu: MenuEntry[] = []; const menu: MenuEntry[] = [];
if (!this.isFirstReplayPage(client)) { if (!this.isFirstReplayPage(client)) {
menu.push({ menu.push({
...@@ -227,8 +255,10 @@ export class CloudReplayService { ...@@ -227,8 +255,10 @@ export class CloudReplayService {
menu.push({ menu.push({
title: this.formatDate(replay.endTime), title: this.formatDate(replay.endTime),
callback: async (currentClient) => { callback: async (currentClient) => {
currentClient.cloudReplaySelectedReplayId = replay.id; await this.renderReplayDetailMenu(currentClient, replay.id, {
await this.renderReplayDetailMenu(currentClient, replay.id); includeBack: true,
showReplayPassHint: true,
});
}, },
}); });
} }
...@@ -246,24 +276,42 @@ export class CloudReplayService { ...@@ -246,24 +276,42 @@ export class CloudReplayService {
}); });
} }
private async renderReplayDetailMenu(client: Client, replayId: number) { private async renderReplayDetailMenu(
const replay = await this.findOwnedReplayById(client, replayId); client: Client,
replayId: number,
options: ReplayDetailMenuOptions,
) {
const replay = await this.findReplayById(replayId);
if (!replay) { if (!replay) {
await client.sendChat('#{cloud_replay_no}', ChatColor.RED); if (options.includeBack) {
await this.renderReplayListMenu(client); await client.sendChat('#{cloud_replay_no}', ChatColor.RED);
await this.renderReplayListMenu(client);
} else {
await client.die('#{cloud_replay_no}', ChatColor.RED);
}
return; return;
} }
client.cloudReplaySelectedReplayId = replay.id;
if (options.showReplayPassHint) {
await client.sendChat(
`#{cloud_replay_detail_access_hint_part1}R#${replay.id}#{cloud_replay_detail_access_hint_part2}`,
ChatColor.BABYBLUE,
);
}
await this.sendReplayDetail(client, replay); await this.sendReplayDetail(client, replay);
const menu: MenuEntry[] = [ const menu: MenuEntry[] = [
{
title: this.formatDate(replay.endTime),
callback: async (currentClient) => {
await this.renderReplayDetailMenu(currentClient, replay.id, options);
},
},
{ {
title: '#{cloud_replay_menu_play}', title: '#{cloud_replay_menu_play}',
callback: async (currentClient) => { callback: async (currentClient) => {
const selectedReplay = await this.findOwnedReplayById( const selectedReplay = await this.findReplayById(replay.id);
currentClient,
replayId,
);
if (!selectedReplay) { if (!selectedReplay) {
await currentClient.die('#{cloud_replay_no}', ChatColor.RED); await currentClient.die('#{cloud_replay_no}', ChatColor.RED);
return; return;
...@@ -274,10 +322,7 @@ export class CloudReplayService { ...@@ -274,10 +322,7 @@ export class CloudReplayService {
{ {
title: '#{cloud_replay_menu_download_yrp}', title: '#{cloud_replay_menu_download_yrp}',
callback: async (currentClient) => { callback: async (currentClient) => {
const selectedReplay = await this.findOwnedReplayById( const selectedReplay = await this.findReplayById(replay.id);
currentClient,
replayId,
);
if (!selectedReplay) { if (!selectedReplay) {
await currentClient.die('#{cloud_replay_no}', ChatColor.RED); await currentClient.die('#{cloud_replay_no}', ChatColor.RED);
return; return;
...@@ -285,17 +330,45 @@ export class CloudReplayService { ...@@ -285,17 +330,45 @@ export class CloudReplayService {
await this.downloadReplayYrp(currentClient, selectedReplay); await this.downloadReplayYrp(currentClient, selectedReplay);
}, },
}, },
{ ];
if (options.includeBack) {
menu.push({
title: '#{cloud_replay_menu_back}', title: '#{cloud_replay_menu_back}',
callback: async (currentClient) => { callback: async (currentClient) => {
await this.renderReplayListMenu(currentClient); await this.renderReplayListMenu(currentClient);
}, },
}, });
]; }
await this.menuManager.launchMenu(client, menu); await this.menuManager.launchMenu(client, menu);
} }
private async openReplayDetailMenuById(client: Client, replayId: number) {
await this.renderReplayDetailMenu(client, replayId, {
includeBack: false,
showReplayPassHint: false,
});
}
private async playReplayById(client: Client, replayId: number) {
const replay = await this.findReplayById(replayId);
if (!replay) {
await client.die('#{cloud_replay_no}', ChatColor.RED);
return;
}
await this.playReplayStream(client, replay, false);
}
private async downloadReplayYrpById(client: Client, replayId: number) {
const replay = await this.findReplayById(replayId);
if (!replay) {
await client.die('#{cloud_replay_no}', ChatColor.RED);
return;
}
await this.downloadReplayYrp(client, replay, true);
}
private async sendReplayDetail(client: Client, replay: DuelRecordEntity) { private async sendReplayDetail(client: Client, replay: DuelRecordEntity) {
const dateText = this.formatDate(replay.endTime); const dateText = this.formatDate(replay.endTime);
const versus = this.formatReplayVersus(replay); const versus = this.formatReplayVersus(replay);
...@@ -414,8 +487,15 @@ export class CloudReplayService { ...@@ -414,8 +487,15 @@ export class CloudReplayService {
return !pos0Player?.isFirst; return !pos0Player?.isFirst;
} }
private async downloadReplayYrp(client: Client, replay: DuelRecordEntity) { private async downloadReplayYrp(
client: Client,
replay: DuelRecordEntity,
withJoinGame = false,
) {
try { try {
if (withJoinGame) {
await client.send(this.createJoinGamePacket(replay));
}
await client.send(new YGOProStocDuelStart()); await client.send(new YGOProStocDuelStart());
await client.send(this.createReplayPacket(replay)); await client.send(this.createReplayPacket(replay));
await client.send(new YGOProStocDuelEnd()); await client.send(new YGOProStocDuelEnd());
...@@ -563,18 +643,6 @@ export class CloudReplayService { ...@@ -563,18 +643,6 @@ export class CloudReplayService {
return qb.orderBy('replay.id', 'DESC').take(take).getMany(); return qb.orderBy('replay.id', 'DESC').take(take).getMany();
} }
private async findOwnedReplayById(client: Client, replayId: number) {
const replay = await this.findReplayById(replayId);
if (!replay) {
return undefined;
}
const clientKey = this.clientKeyProvider.getClientKey(client);
const hasOwnedPlayer = replay.players.some(
(player) => player.clientKey === clientKey,
);
return hasOwnedPlayer ? replay : undefined;
}
private async findReplayById(replayId: number) { private async findReplayById(replayId: number) {
const database = this.ctx.database; const database = this.ctx.database;
if (!database) { if (!database) {
...@@ -717,4 +785,37 @@ export class CloudReplayService { ...@@ -717,4 +785,37 @@ export class CloudReplayService {
private resolveSeatCount(hostInfo: HostInfo) { private resolveSeatCount(hostInfo: HostInfo) {
return this.isTagMode(hostInfo) ? 4 : 2; return this.isTagMode(hostInfo) ? 4 : 2;
} }
private parseDirectReplayPass(pass: string) {
if (pass.startsWith('YRP#')) {
return {
action: 'downloadYrp' as DirectReplayPassAction,
replayIdText: pass.slice('YRP#'.length),
};
}
if (pass.startsWith('W#')) {
return {
action: 'watch' as DirectReplayPassAction,
replayIdText: pass.slice('W#'.length),
};
}
if (pass.startsWith('R#')) {
return {
action: 'detail' as DirectReplayPassAction,
replayIdText: pass.slice('R#'.length),
};
}
return undefined;
}
private parseReplayId(replayIdText: string) {
if (!/^\d+$/.test(replayIdText)) {
return undefined;
}
const replayId = Number(replayIdText);
if (!Number.isSafeInteger(replayId)) {
return undefined;
}
return replayId;
}
} }
...@@ -1843,7 +1843,7 @@ export class Room { ...@@ -1843,7 +1843,7 @@ export class Room {
return this.win({ player: 1 - this.getIngameDuelPos(client), type: 0x0 }); return this.win({ player: 1 - this.getIngameDuelPos(client), type: 0x0 });
} }
async getLP(player: number): Promise<number | undefined> { async getFieldInfo() {
if (!this.ocgcore) { if (!this.ocgcore) {
return undefined; return undefined;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment