Commit 4ecf82ee authored by nanahira's avatar nanahira

add onResponse

parent 74add160
......@@ -28,7 +28,7 @@
"ygopro-cdb-encode": "^1.0.2",
"ygopro-deck-encode": "^1.0.15",
"ygopro-lflist-encode": "^1.0.3",
"ygopro-msg-encode": "^1.1.10",
"ygopro-msg-encode": "^1.1.13",
"ygopro-yrp-encode": "^1.0.1",
"yuzuthread": "^1.0.8"
},
......@@ -79,7 +79,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
......@@ -1606,7 +1605,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz",
"integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
......@@ -1690,7 +1688,6 @@
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
......@@ -2171,7 +2168,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
......@@ -2568,7 +2564,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
......@@ -3229,7 +3224,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
......@@ -3286,7 +3280,6 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
......@@ -4163,7 +4156,6 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz",
"integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.5.0",
"cluster-key-slot": "^1.1.0",
......@@ -4408,7 +4400,6 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
......@@ -5851,7 +5842,6 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
......@@ -6700,7 +6690,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -6853,7 +6842,6 @@
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
......@@ -6962,7 +6950,6 @@
"resolved": "https://registry.npmjs.org/typed-struct/-/typed-struct-2.7.1.tgz",
"integrity": "sha512-GluzA9kYlHjATJmzBDA2X9G9237Md5zsJsc8uEkmpvUFeuUvt+e7Sq11/nQnVB2VZIfKNR1CrwTCgpJVz52pAA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -6989,7 +6976,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
......@@ -7439,9 +7425,9 @@
}
},
"node_modules/ygopro-msg-encode": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/ygopro-msg-encode/-/ygopro-msg-encode-1.1.10.tgz",
"integrity": "sha512-lRTbBwf3Gr6x1hIvTeojdbcWw91/UlbYAhjgPgH9RgUk+2Av18iq8hayKkZgpjqbYhdlHqyZj4aBMgDI+F5eJw==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ygopro-msg-encode/-/ygopro-msg-encode-1.1.13.tgz",
"integrity": "sha512-wWRn6zH4kgg8vS2Z9CCzTAMwPe6WWjZLFizdtUEO6uY+uTNn+dMEIWQpQKHL4eAMXexNDZG1lVGm70eaIKeUkw==",
"license": "MIT",
"dependencies": {
"typed-reflector": "^1.0.14",
......
......@@ -27,6 +27,7 @@ export const defaultConfig = {
DECK_EXTRA_MAX: '15',
DECK_SIDE_MAX: '15',
DECK_MAX_COPIES: '3',
OCGCORE_DEBUG_LOG: '',
...(Object.fromEntries(
Object.entries(DefaultHostinfo).map(([key, value]) => [
`HOSTINFO_${key.toUpperCase()}`,
......
......@@ -9,7 +9,6 @@ export class OcgcoreWorkerOptions {
hostinfo: HostInfo;
@TransportType(() => [YGOProDeck])
decks: YGOProDeck[];
isTag?: boolean;
playerNames?: string[];
registry?: Record<string, string>;
isTag: boolean;
registry: Record<string, string>;
}
......@@ -32,7 +32,7 @@ import { OcgcoreWorkerOptions } from './ocgcore-worker-options';
import { Subject } from 'rxjs';
import { calculateDuelOptions } from '../utility/calculate-duel-options';
import initSqlJs from 'sql.js';
import { YGOProMessages, OcgcoreCommonConstants } from 'ygopro-msg-encode';
import { YGOProMessages } from 'ygopro-msg-encode';
const { OcgcoreScriptConstants } = _OcgcoreConstants;
......@@ -305,43 +305,17 @@ export class OcgcoreWorker {
return this.duel.queryFieldInfo({ noParse: true });
}
@WorkerMethod()
@TransportEncoder<OcgcoreProcessResult[], SerializableProcessResult[]>(
// serialize in worker: only send raw
(results) =>
results.map((result) => ({
length: result.length,
raw: result.raw,
status: result.status,
})),
// deserialize in main thread: re-parse from raw
(serializedArray) =>
serializedArray.map((serialized) => ({
length: serialized.length,
raw: serialized.raw,
status: serialized.status,
message:
serialized.raw.length > 0
? (() => {
try {
return YGOProMessages.getInstanceFromPayload(serialized.raw);
} catch {
return undefined;
}
})()
: undefined,
})),
)
async advance(): Promise<OcgcoreProcessResult[]> {
const results: OcgcoreProcessResult[] = [];
async *advance() {
while (true) {
const res = this.duel.process({ noParse: true });
results.push(res);
const res = await this.process();
if (!res.raw.length) {
continue;
}
yield res;
if (res.status > 0) {
break;
}
}
return results;
}
@WorkerMethod()
......
import YGOProDeck from 'ygopro-deck-encode';
import { YGOProYrp, ReplayHeader } from 'ygopro-yrp-encode';
import { Room } from './room';
import { YGOProMsgBase } from 'ygopro-msg-encode';
// Constants from ygopro
const REPLAY_COMPRESSED = 0x1;
......@@ -17,6 +18,7 @@ export class DuelRecord {
date = new Date();
winPosition?: number;
responses: Buffer[] = [];
messages: YGOProMsgBase[] = [];
toYrp(room: Room) {
const isTag = room.isTag;
......
import { Awaitable } from 'nfkit';
import { Awaitable, ProtoMiddlewareDispatcher } from 'nfkit';
import { Context } from '../app';
import BetterLock from 'better-lock';
import {
......@@ -35,9 +35,25 @@ import {
YGOProCtosHandResult,
YGOProStocHandResult,
HandResult,
YGOProMsgStart,
YGOProMsgNewTurn,
YGOProMsgNewPhase,
YGOProMsgBase,
YGOProMsgResponseBase,
YGOProMsgRetry,
RequireQueryLocation,
RequireQueryCardLocation,
YGOProMsgUpdateData,
YGOProMsgUpdateCard,
CardQuery,
YGOProCtosResponse,
} from 'ygopro-msg-encode';
import { DefaultHostInfoProvider } from './default-hostinfo-provder';
import { CardReaderFinalized } from 'koishipro-core.js';
import {
CardReaderFinalized,
OcgcoreMessageType,
_OcgcoreConstants,
} from 'koishipro-core.js';
import { YGOProResourceLoader } from './ygopro-resource-loader';
import { blankLFList } from '../utility/blank-lflist';
import { calculateDuelOptions } from '../utility/calculate-duel-options';
......@@ -59,6 +75,14 @@ import { checkDeck, checkChangeSide } from '../utility/check-deck';
import { DuelRecord } from './duel-record';
import { generateSeed } from '../utility/generate-seed';
import { OnRoomDuelStart } from './room-event/on-room-duel-start';
import { OcgcoreWorker } from '../ocgcore-worker/ocgcore-worker';
import { initWorker } from 'yuzuthread';
import {
getZoneQueryFlag,
splitRefreshLocations,
} from '../utility/refresh-query';
const { OcgcoreScriptConstants } = _OcgcoreConstants;
export type RoomFinalizor = (self: Room) => Awaitable<any>;
......@@ -231,7 +255,7 @@ export class Room {
}
isPosSwapped = false;
getSwappedPos(clientOrPos: Client | number) {
getIngamePos(clientOrPos: Client | number) {
const pos = this.resolvePos(clientOrPos);
if (pos === NetPlayerType.OBSERVER || !this.isPosSwapped) {
return pos;
......@@ -239,16 +263,16 @@ export class Room {
return pos ^ (0x1 << this.teamOffsetBit);
}
getSwappedDuelPosByDuelPos(duelPos: number) {
getIngameDuelPosByDuelPos(duelPos: number) {
if ([0, 1].includes(duelPos) && this.isPosSwapped) {
return 1 - duelPos;
}
return duelPos;
}
getSwappedDuelPos(clientOrPos: Client | number) {
getIngameDuelPos(clientOrPos: Client | number) {
const duelPos = this.getDuelPos(clientOrPos);
return this.getSwappedDuelPosByDuelPos(duelPos);
return this.getIngameDuelPosByDuelPos(duelPos);
}
getDuelPosPlayers(duelPos: number) {
......@@ -258,6 +282,11 @@ export class Room {
return this.playingPlayers.filter((p) => this.getDuelPos(p) === duelPos);
}
getIngameDuelPosPlayers(duelPos: number) {
const swappedDuelPos = this.getIngameDuelPosByDuelPos(duelPos);
return this.getDuelPosPlayers(swappedDuelPos);
}
async join(client: Client) {
client.roomName = this.name;
client.isHost = !this.allPlayers.length;
......@@ -341,13 +370,17 @@ export class Room {
}
}
get lastDuelRecord() {
return this.duelRecords[this.duelRecords.length - 1];
}
async win(winMsg: Partial<YGOProMsgWin>, forceWinMatch = false) {
if (this.duelStage === DuelStage.Siding) {
this.playingPlayers
.filter((p) => !p.deck)
.forEach((p) => p.send(new YGOProStocDuelStart()));
}
const duelPos = this.getSwappedDuelPosByDuelPos(winMsg.player!);
const duelPos = this.getIngameDuelPosByDuelPos(winMsg.player!);
this.isPosSwapped = false;
await Promise.all(
this.allPlayers.map((p) =>
......@@ -362,7 +395,7 @@ export class Room {
...winMsg,
player: duelPos,
});
const lastDuelRecord = this.duelRecords[this.duelRecords.length - 1];
const lastDuelRecord = this.lastDuelRecord;
if (lastDuelRecord) {
lastDuelRecord.winPosition = duelPos;
}
......@@ -418,7 +451,7 @@ export class Room {
} else {
this.score[this.getDuelPos(client)] = -9;
await this.win(
{ player: this.getSwappedDuelPos(client), type: 0x4 },
{ player: this.getIngameDuelPos(client), type: 0x4 },
true,
);
}
......@@ -723,7 +756,7 @@ export class Room {
@RoomMethod()
private async onChat(client: Client, msg: YGOProCtosChat) {
return this.sendChat(msg.msg, this.getSwappedPos(client));
return this.sendChat(msg.msg, this.getIngamePos(client));
}
async sendChat(msg: string, type: number = ChatColor.BABYBLUE) {
......@@ -872,6 +905,12 @@ export class Room {
return true;
}
private ocgcore?: OcgcoreWorker;
private registry: Record<string, string> = {};
turnCount = 0;
turnPos = 0;
phase = undefined;
@RoomMethod({ allowInDuelStages: DuelStage.FirstGo })
private async onDuelStart(client: Client, msg: YGOProCtosTpResult) {
if (client !== this.firstgoPlayer) {
......@@ -885,7 +924,7 @@ export class Room {
);
if (this.isPosSwapped) {
this.playingPlayers.forEach((p) => {
duelRecord.players[this.getSwappedDuelPos(p)] = {
duelRecord.players[this.getIngameDuelPos(p)] = {
name: p.name,
deck: p.deck!,
};
......@@ -893,12 +932,339 @@ export class Room {
}
this.duelRecords.push(duelRecord);
const extraScriptPaths = [
'./script/patches/entry.lua',
'./script/special.lua',
'./script/init.lua',
...this.resourceLoader.extraScriptPaths,
];
const isMatchMode = this.winMatchCount > 1;
const duelMode = this.isTag ? 'tag' : isMatchMode ? 'match' : 'single';
const registry: Record<string, string> = {
...this.registry,
duel_mode: duelMode,
start_lp: String(this.hostinfo.start_lp),
start_hand: String(this.hostinfo.start_hand),
draw_count: String(this.hostinfo.draw_count),
player_type_0: this.isPosSwapped ? '1' : '0',
player_type_1: this.isPosSwapped ? '0' : '1',
};
if (isMatchMode) {
// Match mode uses completed duel count in gframe (before current duel result).
registry.duel_count = String(this.duelRecords.length - 1);
}
duelRecord.players.forEach((player, i) => {
registry[`player_name_${i}`] = player.name;
});
this.ocgcore = await initWorker(OcgcoreWorker, {
seed: duelRecord.seed,
hostinfo: this.hostinfo,
ygoproPaths: this.resourceLoader.ygoproPaths,
extraScriptPaths,
registry,
decks: duelRecord.players.map((p) => p.deck),
isTag: this.isTag,
});
const [
player0DeckCount,
player0ExtraCount,
player1DeckCount,
player1ExtraCount,
] = await Promise.all([
this.ocgcore.queryFieldCount({
player: 0,
location: OcgcoreScriptConstants.LOCATION_DECK,
}),
this.ocgcore.queryFieldCount({
player: 0,
location: OcgcoreScriptConstants.LOCATION_EXTRA,
}),
this.ocgcore.queryFieldCount({
player: 1,
location: OcgcoreScriptConstants.LOCATION_DECK,
}),
this.ocgcore.queryFieldCount({
player: 1,
location: OcgcoreScriptConstants.LOCATION_EXTRA,
}),
]);
const createStartMsg = (playerType: number) =>
new YGOProStocGameMsg().fromPartial({
msg: new YGOProMsgStart().fromPartial({
playerType,
duelRule: this.hostinfo.duel_rule,
startLp0: this.hostinfo.start_lp,
startLp1: this.hostinfo.start_lp,
player0: {
deckCount: player0DeckCount,
extraCount: player0ExtraCount,
},
player1: {
deckCount: player1DeckCount,
extraCount: player1ExtraCount,
},
}),
});
const duelPos0Clients = this.getIngameDuelPosPlayers(0);
const duelPos1Clients = this.getIngameDuelPosPlayers(1);
const watcherMsg = createStartMsg(this.isPosSwapped ? 0x11 : 0x10);
await Promise.all([
...duelPos0Clients.map((p) => p.send(createStartMsg(0))),
...duelPos1Clients.map((p) => p.send(createStartMsg(1))),
...[...this.watchers].map((p) => p.send(watcherMsg)),
]);
this.duelStage = DuelStage.Dueling;
this.ocgcore.message$.subscribe((msg) => {
if (
msg.type === OcgcoreMessageType.DebugMessage &&
!this.ctx.getConfig('OCGCORE_DEBUG_LOG', '')
) {
return;
}
this.allPlayers.forEach((p) => p.sendChat(`Debug: ${msg.message}`));
});
this.ocgcore.registry$.subscribe((registry) => {
Object.assign(this.registry, registry);
});
this.turnCount = 0;
this.turnPos = 0;
this.phase = undefined;
await this.handleGameMsg(watcherMsg.msg);
await this.ctx.dispatch(
new OnRoomDuelStart(this),
this.getDuelPosPlayers(this.getSwappedDuelPos(0))[0],
this.getOpreatingPlayer(this.turnPos),
);
this.allPlayers.forEach((p) => p.sendChat('TODO: duel start'));
return this.finalize();
return this.advance();
}
private async onNewTurn(tp: number) {
++this.turnCount;
this.turnPos = tp;
}
private async onNewPhase(phase: number) {
this.phase = phase;
}
getOpreatingPlayer(duelPos: number): Client | undefined {
const players = this.getIngameDuelPosPlayers(duelPos);
if (!this.isTag) {
return players[0];
}
if (players.length === 1) {
return players[0];
}
// tag_duel.cpp cur_player equivalent, computed from turnCount:
// duelPos 0: start from players[0], toggle every two turns from turn 3
// duelPos 1: start from players[1], toggle every two turns from turn 2
const tc = Math.max(0, this.turnCount);
if (duelPos === 0) {
const idx = Math.floor(Math.max(0, tc - 1) / 2) % 2;
return players[idx];
}
if (duelPos === 1) {
const idx = 1 - (Math.floor(tc / 2) % 2);
return players[idx];
}
return players[0];
}
private async refreshLocations(refresh: RequireQueryLocation) {
if (!this.ocgcore) {
return;
}
const locations = splitRefreshLocations(refresh.location);
for (const location of locations) {
const { cards } = await this.ocgcore.queryFieldCard({
player: refresh.player,
location,
queryFlag: getZoneQueryFlag(location),
useCache: 1,
});
await this.handleGameMsg(
new YGOProMsgUpdateData().fromPartial({
player: refresh.player,
location,
cards: cards ?? [],
}),
true,
);
}
}
private async refreshSingle(refresh: RequireQueryCardLocation) {
if (!this.ocgcore) {
return;
}
const locations = splitRefreshLocations(refresh.location);
for (const location of locations) {
const { card } = await this.ocgcore.queryCard({
player: refresh.player,
location,
sequence: refresh.sequence,
queryFlag:
0xf81fff |
OcgcoreCommonConstants.QUERY_CODE |
OcgcoreCommonConstants.QUERY_POSITION,
useCache: 0,
});
await this.handleGameMsg(
new YGOProMsgUpdateCard().fromPartial({
controller: refresh.player,
location,
sequence: refresh.sequence,
card:
card ??
(() => {
const empty = new CardQuery();
empty.flags = 0;
empty.empty = true;
return empty;
})(),
}),
true,
);
}
}
private async routeGameMsg(message: YGOProMsgBase) {
const sendTargets = message.getSendTargets();
const sendGameMsg = (c: Client, msg: YGOProMsgBase) =>
c.send(new YGOProStocGameMsg().fromPartial({ msg }));
await Promise.all(
sendTargets.map(async (pos) => {
if (pos === NetPlayerType.OBSERVER) {
const observerView = message.observerView();
await Promise.all(
[...this.watchers].map((w) => sendGameMsg(w, observerView)),
);
} else {
const playerView = message.playerView(pos);
const players = this.getIngameDuelPosPlayers(pos);
const operatingPlayer = this.getOpreatingPlayer(pos);
await Promise.all(
players.map((c) =>
sendGameMsg(
c,
c === operatingPlayer ? playerView : playerView.teammateView(),
),
),
);
}
}),
);
await Promise.all([
...message.getRequireRefreshCards().map((loc) => this.refreshSingle(loc)),
...message
.getRequireRefreshZones()
.map((loc) => this.refreshLocations(loc)),
]);
}
private async handleGameMsg(message: YGOProMsgBase, route = false) {
await this.localGameMsgDispatcher.dispatch(message);
await this.ctx.dispatch(message, this.getOpreatingPlayer(this.turnPos));
if (!route) {
await this.routeGameMsg(message);
}
}
localGameMsgDispatcher = new ProtoMiddlewareDispatcher()
.middleware(YGOProMsgNewTurn, async (message, next) => {
// check new turn
const player = message.player;
if (!(player & 0x2)) {
await this.onNewTurn(player & 0x1);
}
return next();
})
.middleware(YGOProMsgNewPhase, async (message, next) => {
// check new phase
await this.onNewPhase(message.phase);
return next();
})
.middleware(YGOProMsgBase, async (message, next) => {
// record messages for replay
if (!(message instanceof YGOProMsgResponseBase)) {
this.lastDuelRecord.messages.push(message);
}
return next();
})
.middleware(YGOProMsgBase, async (message, next) => {
//
if (this.pendingResponse && !(message instanceof YGOProMsgRetry)) {
// player made valid response
const resp = this.pendingResponse;
this.pendingResponse = undefined;
this.lastDuelRecord.responses.push(resp);
// TODO: clear timer
}
return next();
})
.middleware(YGOProMsgResponseBase, async (message, next) => {
this.responsePos = message.responsePlayer();
// TODO: set timer
return next();
});
private pendingResponse?: Buffer;
private responsePos?: number;
private async advance() {
if (!this.ocgcore) {
return;
}
try {
for await (const { status, message } of this.ocgcore.advance()) {
if (!message) {
this.logger.warn({ message }, 'Received empty message from ocgcore');
if (status) {
throw new Error(
'Cannot continue ocgcore because received empty message with non-advancing status ' +
status,
);
}
}
await this.handleGameMsg(message);
if (message instanceof YGOProMsgWin) {
return this.win(message);
}
await this.routeGameMsg(message);
}
} catch (e) {
this.logger.warn({ error: e }, 'Error while advancing ocgcore');
return this.finalize();
}
}
@RoomMethod({
allowInDuelStages: DuelStage.Dueling,
})
private async onResponse(client: Client, msg: YGOProCtosResponse) {
if (
this.responsePos == null ||
client !== this.getOpreatingPlayer(this.responsePos) ||
!this.ocgcore
) {
return;
}
this.pendingResponse = Buffer.from(msg.response);
this.responsePos = undefined;
await this.ocgcore.setResponse(msg.response);
return this.advance();
}
}
import { AppContext, ProtoMiddlewareDispatcher } from 'nfkit';
import { Client } from '../client/client';
export class Emitter extends ProtoMiddlewareDispatcher<[Client]> {
export class Emitter extends ProtoMiddlewareDispatcher<[client: Client]> {
constructor(private ctx: AppContext) {
super({
acceptResult: () => true,
......
import { _OcgcoreConstants } from 'koishipro-core.js';
const { OcgcoreScriptConstants } = _OcgcoreConstants;
export function splitRefreshLocations(location: number) {
const bits = [
OcgcoreScriptConstants.LOCATION_MZONE,
OcgcoreScriptConstants.LOCATION_SZONE,
OcgcoreScriptConstants.LOCATION_HAND,
OcgcoreScriptConstants.LOCATION_GRAVE,
OcgcoreScriptConstants.LOCATION_REMOVED,
OcgcoreScriptConstants.LOCATION_EXTRA,
OcgcoreScriptConstants.LOCATION_DECK,
OcgcoreScriptConstants.LOCATION_OVERLAY,
OcgcoreScriptConstants.LOCATION_FZONE,
OcgcoreScriptConstants.LOCATION_PZONE,
];
const locations = bits.filter((bit) => (location & bit) !== 0);
if (locations.length > 0) {
return locations;
}
return [location];
}
export function getZoneQueryFlag(location: number) {
if (location === OcgcoreScriptConstants.LOCATION_MZONE) {
return 0x881fff;
}
if (location === OcgcoreScriptConstants.LOCATION_SZONE) {
return 0xe81fff;
}
if (location === OcgcoreScriptConstants.LOCATION_HAND) {
return 0x681fff;
}
if (location === OcgcoreScriptConstants.LOCATION_GRAVE) {
return 0x081fff;
}
if (location === OcgcoreScriptConstants.LOCATION_EXTRA) {
return 0xe81fff;
}
return 0xf81fff;
}
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