From 592e06202e1f12dba6ba1e827133da71c42b0353 Mon Sep 17 00:00:00 2001 From: rintim Date: Sun, 21 Jun 2026 19:31:58 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=E4=B8=BA`GameEvent#forResult`?= =?UTF-8?q?=E7=9A=84=E8=BF=94=E5=9B=9E=E5=80=BC=E5=A2=9E=E5=8A=A0=E4=BF=9D?= =?UTF-8?q?=E5=BA=95=EF=BC=8C=E5=BD=93`result`=E5=B1=9E=E6=80=A7=E4=B8=8D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E6=97=B6=E8=BF=94=E5=9B=9E=E7=A9=BA=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/noname/library/element/gameEvent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/core/noname/library/element/gameEvent.ts b/apps/core/noname/library/element/gameEvent.ts index b803e955f8..7c81c91463 100644 --- a/apps/core/noname/library/element/gameEvent.ts +++ b/apps/core/noname/library/element/gameEvent.ts @@ -130,7 +130,7 @@ export class GameEvent implements PromiseLike { */ async forResult(): Promise> { await this; - return this.result; + return this.result ?? {}; } // #endregion From 597a7907d3c171105a593e28f1269b28758cd99c Mon Sep 17 00:00:00 2001 From: rintim Date: Mon, 22 Jun 2026 00:14:05 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E7=B2=BE=E7=BB=86=E5=8C=96?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E4=BE=9D=E8=B5=96=EF=BC=8C=E4=BD=BF=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E5=8F=AF=E5=88=86=E4=B8=BA=E4=BE=9D=E8=B5=96=E5=92=8C?= =?UTF-8?q?=E9=9D=9E=E4=BE=9D=E8=B5=96=E5=BD=A2=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/noname/init/compatible.js | 14 +++--- apps/core/noname/library/element/gameEvent.ts | 46 +++++++++++++++---- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/apps/core/noname/init/compatible.js b/apps/core/noname/init/compatible.js index e413f73f7b..2a6470f548 100644 --- a/apps/core/noname/init/compatible.js +++ b/apps/core/noname/init/compatible.js @@ -139,11 +139,12 @@ Object.assign(lib.element.GameEvent.prototype, { // forResult Object.assign(lib.element.GameEvent.prototype, { then(onfulfilled, onrejected) { - return (this.parent ? this.parent.waitNext() : this.start()).then( + const event = /** @type { any } */ (this); + return (event.parent ? event.parent.waitNext("safe") : event.start()).then( onfulfilled ? () => { return onfulfilled( - new Proxy(this, { + new Proxy(event, { get(target, p, receiver) { if (p === "then") { return void 0; @@ -158,14 +159,15 @@ Object.assign(lib.element.GameEvent.prototype, { ); }, async forResult(...params) { - await this; + const event = /** @type { any } */ (this); + await (event.parent ? event.parent.waitNext() : event.start()); if (params.length == 0) { - return this.result; + return event.result; } if (params.length == 1) { - return this.result[params[0]]; + return event.result[params[0]]; } - return Array.from(params).map(key => this.result[key]); + return Array.from(params).map(key => event.result[key]); }, forResultBool() { return this.forResult().then(r => r.bool); diff --git a/apps/core/noname/library/element/gameEvent.ts b/apps/core/noname/library/element/gameEvent.ts index 7c81c91463..7ea00bba64 100644 --- a/apps/core/noname/library/element/gameEvent.ts +++ b/apps/core/noname/library/element/gameEvent.ts @@ -16,6 +16,7 @@ type triggerPlayerTodo = { todoList: triggerSkillTodo[]; doneList: triggerSkillTodo[]; }; +type WaitNextMode = "strict" | "safe"; export class GameEvent implements PromiseLike { constructor(name: string = "", trigger: boolean = true, manager: GameEventManager = _status.eventManager) { //@ts-ignore @@ -82,6 +83,20 @@ export class GameEvent implements PromiseLike { */ result: Partial; cost_data: Result["cost_data"]; + error?: unknown; + #errorReported = false; + recordError(error: unknown, report = false) { + this.error = error; + if (report && !this.#errorReported) { + this.#errorReported = true; + game.print("游戏出错:" + this.name); + game.print(error?.toString?.() ?? String(error)); + console.error(error); + } + } + #doAsync(mode: WaitNextMode): Promise { + return this.parent ? this.parent.waitNext(mode).then(() => undefined) : this.start(); + } /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. @@ -89,7 +104,7 @@ export class GameEvent implements PromiseLike { * @returns A Promise for the completion of which ever callback is executed. */ then(onfulfilled?: (() => TResult1 | Promise) | null, onrejected?: ((reason: any) => TResult2 | Promise) | null): Promise { - return (this.parent ? this.parent.waitNext().then(() => undefined) : this.start()).then(onfulfilled, onrejected); + return this.#doAsync("safe").then(onfulfilled, onrejected); } /** * Attaches a callback for only the rejection of the Promise. @@ -120,6 +135,9 @@ export class GameEvent implements PromiseLike { } ); } + link() { + return this.#doAsync("strict"); + } /** * 获取 Result 对象中的信息。 * @example @@ -129,7 +147,7 @@ export class GameEvent implements PromiseLike { * ``` */ async forResult(): Promise> { - await this; + await this.link(); return this.result ?? {}; } // #endregion @@ -237,10 +255,12 @@ export class GameEvent implements PromiseLike { // } // this.manager.eventStack.push(this); this.manager.setStatusEvent(this, true); - await this.loop().then(() => { + try { + await this.loop(); + } finally { // this.manager.eventStack.pop(); this.manager.popStatusEvent(); - }); + } })(); return this.#start; } @@ -265,10 +285,9 @@ export class GameEvent implements PromiseLike { } else { this.#inContent = true; let next = this.content(this).catch(error => { + this.recordError(error, lib.config.ignore_error || (_status.connectMode && !lib.config.debug)); if (lib.config.ignore_error || (_status.connectMode && !lib.config.debug)) { - game.print("游戏出错:" + this.name); - game.print(error.toString()); - console.error(error); + return; } else { throw error; } @@ -303,7 +322,7 @@ export class GameEvent implements PromiseLike { return true; } #waitNext?: Promise | void>; - waitNext(): Promise | void> { + waitNext(mode: WaitNextMode = "strict"): Promise | void> { if (this.#waitNext) { return this.#waitNext; } @@ -323,7 +342,16 @@ export class GameEvent implements PromiseLike { return result; } const next = this.next[0]; - await next.start(); + try { + await next.start(); + } catch (error) { + if (mode === "strict") { + throw error; + } + next.recordError(error, true); + this.next.shift(); + continue; + } if (next.result) { result = next.result; } From a335a6d0454c93fff4425023c0c2d539fc1cb284 Mon Sep 17 00:00:00 2001 From: rintim Date: Mon, 22 Jun 2026 00:28:29 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96GameEvent=E7=9A=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E4=B8=8E=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E6=9C=BA=E5=88=B6=EF=BC=8C=E5=9C=A8=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E5=B1=82=E5=90=8C=E6=AD=A5=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/noname/init/compatible.js | 18 +++++++-- apps/core/noname/library/element/gameEvent.ts | 37 ++++++++++++++++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/apps/core/noname/init/compatible.js b/apps/core/noname/init/compatible.js index 2a6470f548..45f1064b8d 100644 --- a/apps/core/noname/init/compatible.js +++ b/apps/core/noname/init/compatible.js @@ -140,7 +140,12 @@ Object.assign(lib.element.GameEvent.prototype, { Object.assign(lib.element.GameEvent.prototype, { then(onfulfilled, onrejected) { const event = /** @type { any } */ (this); - return (event.parent ? event.parent.waitNext("safe") : event.start()).then( + const promise = event.parent + ? event.parent.waitNext("safe", event) + : event.start().catch(error => { + event.recordError(error, true); + }); + return promise.then( onfulfilled ? () => { return onfulfilled( @@ -158,11 +163,18 @@ Object.assign(lib.element.GameEvent.prototype, { onrejected ); }, - async forResult(...params) { + async link() { const event = /** @type { any } */ (this); await (event.parent ? event.parent.waitNext() : event.start()); + if (event.error) { + throw event.error; + } + }, + async forResult(...params) { + const event = /** @type { any } */ (this); + await event.link(); if (params.length == 0) { - return event.result; + return event.result ?? {}; } if (params.length == 1) { return event.result[params[0]]; diff --git a/apps/core/noname/library/element/gameEvent.ts b/apps/core/noname/library/element/gameEvent.ts index 7ea00bba64..4e553ff261 100644 --- a/apps/core/noname/library/element/gameEvent.ts +++ b/apps/core/noname/library/element/gameEvent.ts @@ -95,7 +95,15 @@ export class GameEvent implements PromiseLike { } } #doAsync(mode: WaitNextMode): Promise { - return this.parent ? this.parent.waitNext(mode).then(() => undefined) : this.start(); + if (this.parent) { + return this.parent.waitNext(mode, mode === "safe" ? this : undefined).then(() => undefined); + } + if (mode === "safe") { + return this.start().catch(error => { + this.recordError(error, true); + }); + } + return this.start(); } /** * Attaches callbacks for the resolution and/or rejection of the Promise. @@ -135,8 +143,11 @@ export class GameEvent implements PromiseLike { } ); } - link() { - return this.#doAsync("strict"); + async link(): Promise { + await this.#doAsync("strict"); + if (this.error) { + throw this.error; + } } /** * 获取 Result 对象中的信息。 @@ -322,10 +333,19 @@ export class GameEvent implements PromiseLike { return true; } #waitNext?: Promise | void>; - waitNext(mode: WaitNextMode = "strict"): Promise | void> { + #waitNextMode: WaitNextMode = "strict"; + #waitNextSafeEvents = new Set(); + waitNext(mode: WaitNextMode = "strict", safeEvent?: GameEvent): Promise | void> { + if (mode === "safe" && safeEvent) { + this.#waitNextSafeEvents.add(safeEvent); + } if (this.#waitNext) { + if (mode === "strict") { + this.#waitNextMode = "strict"; + } return this.#waitNext; } + this.#waitNextMode = mode; this.#waitNext = (async () => { let result; while (true) { @@ -345,7 +365,8 @@ export class GameEvent implements PromiseLike { try { await next.start(); } catch (error) { - if (mode === "strict") { + const safe = this.#waitNextMode === "safe" && (!this.#waitNextSafeEvents.size || this.#waitNextSafeEvents.has(next)); + if (!safe) { throw error; } next.recordError(error, true); @@ -357,7 +378,11 @@ export class GameEvent implements PromiseLike { } this.next.shift(); } - })().finally(() => (this.#waitNext = undefined)); + })().finally(() => { + this.#waitNext = undefined; + this.#waitNextMode = "strict"; + this.#waitNextSafeEvents.clear(); + }); return this.#waitNext; } // #endregion From 251377fabc4b3f59720c43380b5ed2ab9a394682 Mon Sep 17 00:00:00 2001 From: rintim Date: Mon, 22 Jun 2026 00:36:36 +0800 Subject: [PATCH 4/7] =?UTF-8?q?docs:=20=E4=B8=BA=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E5=A2=9E=E5=8A=A0=E5=87=BD=E6=95=B0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/noname/init/compatible.js | 3 ++ apps/core/noname/library/element/gameEvent.ts | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/apps/core/noname/init/compatible.js b/apps/core/noname/init/compatible.js index 45f1064b8d..f5e013eebd 100644 --- a/apps/core/noname/init/compatible.js +++ b/apps/core/noname/init/compatible.js @@ -163,6 +163,9 @@ Object.assign(lib.element.GameEvent.prototype, { onrejected ); }, + /** + * 显式声明当前事件依赖此事件完成,等待事件报错时会影响当前流程。 + */ async link() { const event = /** @type { any } */ (this); await (event.parent ? event.parent.waitNext() : event.start()); diff --git a/apps/core/noname/library/element/gameEvent.ts b/apps/core/noname/library/element/gameEvent.ts index 4e553ff261..f1ac35e1b7 100644 --- a/apps/core/noname/library/element/gameEvent.ts +++ b/apps/core/noname/library/element/gameEvent.ts @@ -16,6 +16,12 @@ type triggerPlayerTodo = { todoList: triggerSkillTodo[]; doneList: triggerSkillTodo[]; }; +/** + * 等待子事件时的错误传播模式。 + * + * - strict: 子事件报错会继续向当前等待方抛出。 + * - safe: 子事件报错只记录并继续等待流程,主要供裸 await 事件使用。 + */ type WaitNextMode = "strict" | "safe"; export class GameEvent implements PromiseLike { constructor(name: string = "", trigger: boolean = true, manager: GameEventManager = _status.eventManager) { @@ -85,6 +91,12 @@ export class GameEvent implements PromiseLike { cost_data: Result["cost_data"]; error?: unknown; #errorReported = false; + /** + * 记录事件执行过程中出现的错误。 + * + * @param error 捕获到的错误对象。 + * @param report 是否立即通过游戏日志和控制台报告;同一事件只报告一次。 + */ recordError(error: unknown, report = false) { this.error = error; if (report && !this.#errorReported) { @@ -94,6 +106,14 @@ export class GameEvent implements PromiseLike { console.error(error); } } + /** + * 以指定错误传播模式等待当前事件完成。 + * + * 当前事件有父事件时,会委托父事件推进 next 队列;safe 模式仅保护当前事件本身。 + * 当前事件没有父事件时,直接启动自身。 + * + * @param mode 等待时采用的错误传播模式。 + */ #doAsync(mode: WaitNextMode): Promise { if (this.parent) { return this.parent.waitNext(mode, mode === "safe" ? this : undefined).then(() => undefined); @@ -143,6 +163,12 @@ export class GameEvent implements PromiseLike { } ); } + /** + * 显式声明当前事件依赖此事件完成。 + * + * 与裸 await 事件不同,link 会使用 strict 模式等待;如果被等待事件已经记录过错误, + * 也会在这里重新抛出,让当前事件流程受到影响。 + */ async link(): Promise { await this.#doAsync("strict"); if (this.error) { @@ -335,6 +361,17 @@ export class GameEvent implements PromiseLike { #waitNext?: Promise | void>; #waitNextMode: WaitNextMode = "strict"; #waitNextSafeEvents = new Set(); + /** + * 推进并等待当前事件 next 队列中的子事件。 + * + * 默认 strict 模式会保持原有流程语义:子事件报错会中断当前等待。 + * safe 模式用于裸 await 事件,只记录指定 safeEvent 的错误并继续推进队列; + * 后续 strict 等待会将正在进行的 safe 等待升级为 strict。 + * + * @param mode 等待子事件时的错误传播模式,默认为 strict。 + * @param safeEvent safe 模式下允许被保底处理的目标子事件;不传则允许当前 safe 轮次处理所有子事件。 + * @returns 最后一个有 result 的子事件结果。 + */ waitNext(mode: WaitNextMode = "strict", safeEvent?: GameEvent): Promise | void> { if (mode === "safe" && safeEvent) { this.#waitNextSafeEvents.add(safeEvent); From 7c603f7ea961e45b0c4331a71b35771f1b26ff5b Mon Sep 17 00:00:00 2001 From: rintim Date: Mon, 22 Jun 2026 15:17:33 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E5=B0=86=E5=BA=95=E5=B1=82=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E5=A4=A7=E9=83=A8=E5=88=86awaited=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=94=B9=E4=B8=BA=E4=BD=BF=E7=94=A8link()=E4=BB=A5?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E4=BC=A0=E6=92=AD=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/noname/library/element/content.ts | 214 ++++++++++---------- 1 file changed, 108 insertions(+), 106 deletions(-) diff --git a/apps/core/noname/library/element/content.ts b/apps/core/noname/library/element/content.ts index 2d3258a5d6..81d47ee9f4 100644 --- a/apps/core/noname/library/element/content.ts +++ b/apps/core/noname/library/element/content.ts @@ -381,7 +381,7 @@ export const Content: Record = { } }; } - await next; + await next.link(); //变更角色的所属势力。如果新将是双势力,重选一下势力。 国战不因换将而重选势力 if (event.changeGroup !== false && get.mode() != "guozhan") { let newGroups = []; @@ -393,10 +393,10 @@ export const Content: Record = { if (newGroups.length > 1) { const { control: newGroup } = await player.chooseControl(newGroups).set("prompt", "请选择一个新的势力").forResult(); if (newGroup != player.group) { - await player.changeGroup(newGroup); + await player.changeGroup(newGroup).link(); } } else if (newGroups.length == 1 && newGroups[0] != player.group) { - await player.changeGroup(newGroups[0]); + await player.changeGroup(newGroups[0]).link(); } } }, @@ -604,7 +604,7 @@ export const Content: Record = { } next.player = player; - await next; + await next.link(); } } else { const next = game.createEvent(cardName); @@ -626,7 +626,7 @@ export const Content: Record = { autoViewAs.expired = card.expired; } next.player = player; - await next; + await next.link(); } } @@ -648,7 +648,7 @@ export const Content: Record = { const next = player.loseToDiscardpile(card); next.log = false; - await next; + await next.link(); await event.trigger("giftDenied"); @@ -664,11 +664,11 @@ export const Content: Record = { if (get.type(card) == "equip") { const next = target.equip(card); next.log = false; - await next; + await next.link(); } else { const next = target.gain(card, player); next.visible = true; - await next; + await next.link(); } await event.trigger("giftAccepted"); @@ -790,7 +790,7 @@ export const Content: Record = { player.$syncDisable(); if (cards.length > 0) { - await player.loseToDiscardpile(cards); + await player.loseToDiscardpile(cards).link(); } }, async enableEquip(event, trigger, player) { @@ -982,7 +982,7 @@ export const Content: Record = { // @ts-expect-error ignore next.visible = true; } - await next; + await next.link(); event.relatedLose = next; } for (const [key, value] of lib.commonArea) { @@ -993,7 +993,7 @@ export const Content: Record = { next.cards = list; next.player = player; next.type = event.name; - await next; + await next.link(); } } } @@ -1003,7 +1003,7 @@ export const Content: Record = { // @ts-expect-error ignore const cardInfo = get.info(card, false); if (player.isMin() || !player.canEquip(card)) { - await game.cardsDiscard(cards); + await game.cardsDiscard(cards).link(); delete player.equiping; return; } @@ -1036,14 +1036,14 @@ export const Content: Record = { next.setContent(onEquip); next.player = player; next.card = event.vcards[0]; - await next; + await next.link(); } } else { const next = game.createEvent(`equip_${card.name}`); next.setContent(cardInfo.onEquip); next.player = player; next.card = event.vcards[0]; - await next; + await next.link(); } if (cardInfo.equipDelay != false) { await game.delayx(); @@ -1069,7 +1069,7 @@ export const Content: Record = { } if (stop) { if (list.length) { - await game.cardsDiscard(list); + await game.cardsDiscard(list).link(); } return; } @@ -1094,7 +1094,7 @@ export const Content: Record = { next.setContent(cardInfo.prepareEquip); next.player = player; next.card = event.card; - await next; + await next.link(); } //同时播放所有装备牌的装备动画 if (event.cards.length) { @@ -1138,7 +1138,7 @@ export const Content: Record = { if (get.info(event.card, true)?.loseThrow) { player.$throw(result.cards, 1000); } - await loseEvent; + await loseEvent.link(); // @ts-expect-error ignore for (const card of result.cards) { if (card.willBeDestroyed("discardPile", player, event)) { @@ -1361,7 +1361,7 @@ export const Content: Record = { next.debateResult = get.copy(event.result); next.setContent(event.callback); - await next; + await next.link(); } return; @@ -2703,7 +2703,7 @@ export const Content: Record = { player.logSkill.apply(player, event.logSkill); } } - await next; + await next.link(); } else { event.result = { bool: false }; } @@ -2950,12 +2950,12 @@ export const Content: Record = { waitings.push(next); } } - await Promise.all(waitings); + await Promise.all(waitings.map(event => event?.link?.())); }, async orderingDiscard(event, trigger, player) { const cards = event.relatedEvent.orderingCards.filter(card => get.position(card, true) == "o"); if (cards.length) { - await game.cardsDiscard(cards); + await game.cardsDiscard(cards).link(); } }, async cardsGotoOrdering(event, trigger, player) { @@ -3005,7 +3005,7 @@ export const Content: Record = { evt.orderingCards.addArray(cards); } event.result = { cards }; - await Promise.all(waitings); + await Promise.all(waitings.map(event => event?.link?.())); }, async cardsGotoSpecial(event, trigger, player) { const { cards } = event; @@ -3041,7 +3041,7 @@ export const Content: Record = { } } - await Promise.all(waitings); + await Promise.all(waitings.map(event => event?.link?.())); }, async cardsGotoPile(event, trigger, player) { const { cards } = event; @@ -3070,7 +3070,7 @@ export const Content: Record = { } } - await Promise.all(waitings); + await Promise.all(waitings.map(event => event?.link?.())); }, async chooseToEnable(event, trigger, player) { const { source } = event; @@ -3253,14 +3253,14 @@ export const Content: Record = { if (vcard.cards?.length && vcard.cards.some(i => get.position(i, true) !== "o")) { continue; } - await player.equip(vcard); + await player.equip(vcard).link(); } for (const card of cards[0]) { const vcard = card[card.cardSymbol]; if (vcard.cards?.length && vcard.cards.some(i => get.position(i, true) !== "o")) { continue; } - await target.equip(vcard); + await target.equip(vcard).link(); } }, async disableJudge(event, trigger, player) { @@ -3268,7 +3268,7 @@ export const Content: Record = { const js = player.getCards("j"); player.storage._disableJudge = true; if (js.length) { - await player.discard(js); + await player.discard(js).link(); } game.broadcastAll((player, card) => { player.$disableJudge(); @@ -3595,14 +3595,14 @@ export const Content: Record = { async addJudgeCard(event) { const { target, card, cards } = event; if (!card?.cards.some(card => get.position(card, true) !== "o") && target.canAddJudge(card)) { - await target.addJudge(card, cards); + await target.addJudge(card, cards).link(); } }, async equipCard(event) { const { card, target } = event; if (!card?.cards.some(card => get.position(card, true) !== "o")) { - await target.equip(card); + await target.equip(card).link(); } //if (cards.length && get.position(cards[0], true) == "o") target.equip(cards[0]); }, @@ -3669,7 +3669,7 @@ export const Content: Record = { event.changeCard = "disabled"; } - await Promise.all(waitings); + await Promise.all(waitings.map(event => event?.link?.())); if (!targets.includes(game.me) || event.changeCard == "disabled" || _status.auto || !game.me.countCards("h")) { return; @@ -3788,7 +3788,7 @@ export const Content: Record = { } } event.next.push(phase); - await phase; + await phase.link(); } await event.trigger("phaseOver"); const findNext = current => { @@ -4225,7 +4225,7 @@ export const Content: Record = { } next.indexedData = event.indexedData; - await next; + await next.link(); //删除when生成的临时技能 if (event.skill.startsWith("player_when_")) { @@ -4867,7 +4867,7 @@ export const Content: Record = { event.redo(); } else { if (event.card) { - await player.lose(event.card, "visible", ui.ordering); + await player.lose(event.card, "visible", ui.ordering).link(); } player.$phaseJudge(event.card); event.cancelled = false; @@ -4901,7 +4901,7 @@ export const Content: Record = { next.card = VJudge; next.cards = VJudge?.cards ?? []; next.player = player; - await next; + await next.link(); } } else { const next = game.createEvent(name); @@ -4910,7 +4910,7 @@ export const Content: Record = { next.card = VJudge; next.cards = VJudge?.cards ?? []; next.player = player; - await next; + await next.link(); } ui.clear(); event.goto(1); @@ -5995,7 +5995,7 @@ export const Content: Record = { next.setContent(info.precontent); next.set("result", event.result); next.set("player", player); - await next; + await next.link(); } } } @@ -6029,7 +6029,7 @@ export const Content: Record = { } next.discarder = player; event.done = next; - await next; + await next.link(); } } else if (event._sendskill) { event.result._sendskill = event._sendskill; @@ -6047,7 +6047,7 @@ export const Content: Record = { } if (player && cards) { event._lose = true; - await player.lose(cards, ui.special).set("type", "gain").set("forceDie", true).set("getlx", false); + await player.lose(cards, ui.special).set("type", "gain").set("forceDie", true).set("getlx", false).link(); } let delay; @@ -6147,7 +6147,7 @@ export const Content: Record = { } } await delay; - await Promise.all(nexts); + await Promise.all(nexts.map(event => event?.link?.())); await game.delayx(); }, async discardMultiple(event, trigger, player) { @@ -6225,7 +6225,7 @@ export const Content: Record = { } } - await Promise.all(events); + await Promise.all(events.map(event => event?.link?.())); if (event.delay != false) { if (event.waitingForTransition) { @@ -6286,7 +6286,7 @@ export const Content: Record = { if (event.visible == true) { next.set("visible", true); } - await next; + await next.link(); } } else { event.finish(); @@ -6337,7 +6337,7 @@ export const Content: Record = { next.cards = cards; next.player = source; next.type = event.type; - await next; + await next.link(); } } } @@ -6610,7 +6610,7 @@ export const Content: Record = { next.getlx = false; waitings.push(next); } - await Promise.all(waitings); + await Promise.all(waitings.map(event => event?.link?.())); }, chooseToCompareMeanwhile: [ async (event, trigger, player) => { @@ -7127,8 +7127,8 @@ export const Content: Record = { .step(async (event, trigger, player) => { if (cards?.some(card => get.position(card) == "s")) { evt.isDestroyed = true; - await game.cardsGotoOrdering(cards); - await game.cardsDiscard(cards); + await game.cardsGotoOrdering(cards).link(); + await game.cardsDiscard(cards).link(); } }); event.untrigger(); @@ -7249,7 +7249,7 @@ export const Content: Record = { }, async (event, trigger, player) => { const target = event.target; - await game.cardsGotoOrdering([event.card1, event.card2]); + await game.cardsGotoOrdering([event.card1, event.card2]).link(); game.log(player, "揭示了和", target, "的延时拼点结果"); await game.delayx(); await event.trigger("compareCardShowBefore"); @@ -7454,10 +7454,10 @@ export const Content: Record = { } if (togain) { if (event.use) { - await player.chooseUseTarget(togain); + await player.chooseUseTarget(togain).link(); } else if (!event.nogain) { game.log(player, "获得了一张牌"); - await player.gain(togain, "draw"); + await player.gain(togain, "draw").link(); } } } @@ -8452,9 +8452,9 @@ export const Content: Record = { if (result?.control == "draw_card") { const next = target.draw(event.num1); next.gaintag.addArray(event.gaintag); - await next; + await next.link(); } else if (result?.control == "recover_hp") { - await target.recover(event.num2); + await target.recover(event.num2).link(); } } event.result = result; @@ -8853,7 +8853,7 @@ export const Content: Record = { if (event.delay === false) { next.set("delay", false); } - await next; + await next.link(); } }, ], @@ -9125,7 +9125,7 @@ export const Content: Record = { game.log(player, "展示了", cards); game.addVideo("showCards", player, [str, get.cardsInfo(cards)]); await game.delayx(2); - await next; + await next.link(); game.broadcast("closeDialog", event.dialogid); event.dialog.close(); }, @@ -9214,10 +9214,10 @@ export const Content: Record = { if (Array.from(ownerLose.values())?.flat()?.length > 0) { const next = game.loseAsync({ lose_list: Array.from(ownerLose.entries()) }).set("relatedEvent", event.relatedEvent || event.getParent()); next.setContent("chooseToCompareLose"); - await next; + await next.link(); } if (directLose.length > 0) { - await game.cardsGotoOrdering(directLose).set("relatedEvent", event.relatedEvent || event.getParent()); + await game.cardsGotoOrdering(directLose).set("relatedEvent", event.relatedEvent || event.getParent()).link(); } } @@ -9400,7 +9400,7 @@ export const Content: Record = { next.cards = event.result.cards; next.result = event.result; next.setContent(event.callback); - await next; + await next.link(); } }, async viewCards(event, trigger, player) { @@ -9951,20 +9951,20 @@ export const Content: Record = { const id = owner.playerid; const onLoseCards = cards.filter(card => get.owner(card) == owner); event.cards.removeArray(onLoseCards); - await owner.lose(onLoseCards, "visible", ui.ordering).set("relatedEvent", event.getParent()).set("getlx", false).set("type", "use"); + await owner.lose(onLoseCards, "visible", ui.ordering).set("relatedEvent", event.getParent()).set("getlx", false).set("type", "use").link(); } } if (cards_noowner.length) { - await game.cardsGotoOrdering(cards_noowner).set("relatedEvent", event.getParent()); + await game.cardsGotoOrdering(cards_noowner).set("relatedEvent", event.getParent()).link(); } }); } else { - await ownerx.lose(ownerCards, "visible", ui.ordering).set("type", "use"); + await ownerx.lose(ownerCards, "visible", ui.ordering).set("type", "use").link(); } } if (directDiscard.length) { event.lose_map.noowner.addArray(directDiscard); - await game.cardsGotoOrdering(directDiscard); + await game.cardsGotoOrdering(directDiscard).link(); } } await event.trigger("useCard0"); @@ -10126,7 +10126,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); event.redo(); } }, @@ -10159,7 +10159,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); event.redo(); } }, @@ -10206,7 +10206,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); event.redo(); } }, @@ -10242,7 +10242,7 @@ export const Content: Record = { if (targets.length == event.triggeredTargets4.length) { event.sortTarget(); } - await next; + await next.link(); event.redo(); } }, @@ -10266,7 +10266,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); } else if (info.reverseOrder && get.is.versus() && targets.length > 1) { const next = game.createEvent(`${event.card.name}ContentBefore`); next.setContent("reverseOrder"); @@ -10279,7 +10279,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); } else if (info.singleCard && info.filterAddedTarget && event.addedTargets && event.addedTargets.length < targets.length) { const next = game.createEvent(`${event.card.name}ContentBefore`); next.setContent("addExtraTarget"); @@ -10295,7 +10295,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); } }, async (event, trigger, player) => { @@ -10325,7 +10325,7 @@ export const Content: Record = { next.card = event.card; next.cards = cards; next.player = player; - await next; + await next.link(); return; } if (targets.length == 0 && !info.notarget) { @@ -10340,7 +10340,7 @@ export const Content: Record = { next.card = event.card; next.cards = cards; next.player = player; - await next; + await next.link(); return; } const next = game.createEvent(event.card.name); @@ -10422,7 +10422,7 @@ export const Content: Record = { if (event.forceDie) { next.forceDie = true; } - await next; + await next.link(); } }, async (event, trigger, player) => { @@ -10668,7 +10668,7 @@ export const Content: Record = { await event.trigger("useSkill"); } - return Promise.all(waitings); + return Promise.all(waitings.map(event => event?.link?.())); }, async (event, trigger, player) => { const { cards, targets } = event; @@ -10870,7 +10870,7 @@ export const Content: Record = { game.log(...logList); } next.gaintag.addArray(event.gaintag); - await next; + await next.link(); } event.result = { bool: true, @@ -10885,7 +10885,7 @@ export const Content: Record = { if (event.discarder) { event.done.discarder = event.discarder; } - await event.done; + await event.done.link(); await event.trigger("discard"); }, async loseToDiscardpile(event, trigger, player) { @@ -10905,7 +10905,7 @@ export const Content: Record = { } next.type = "loseToDiscardpile"; event.done = next; - await next; + await next.link(); await event.trigger("loseToDiscardpile"); }, respond: [ @@ -11113,20 +11113,20 @@ export const Content: Record = { const id = owner.playerid; const onLoseCards = cards.filter(card => get.owner(card) == owner); event.cards.removeArray(onLoseCards); - await owner.lose(onLoseCards, "visible", ui.ordering).set("relatedEvent", event.getParent()).set("getlx", false).set("type", "use"); + await owner.lose(onLoseCards, "visible", ui.ordering).set("relatedEvent", event.getParent()).set("getlx", false).set("type", "use").link(); } } if (cards_noowner.length) { - await game.cardsGotoOrdering(cards_noowner).set("relatedEvent", event.getParent()); + await game.cardsGotoOrdering(cards_noowner).set("relatedEvent", event.getParent()).link(); } }); } else { - await ownerx.lose(ownerCards, "visible", ui.ordering).set("type", "use"); + await ownerx.lose(ownerCards, "visible", ui.ordering).set("type", "use").link(); } } if (directDiscard.length) { event.lose_map.noowner.addArray(directDiscard); - await game.cardsGotoOrdering(directDiscard); + await game.cardsGotoOrdering(directDiscard).link(); } } await event.trigger("respond"); @@ -11175,7 +11175,7 @@ export const Content: Record = { next1.delay = false; } - await next1; + await next1.link(); cards = event.cards2; const next2 = target.lose(cards, ui.ordering); @@ -11187,7 +11187,7 @@ export const Content: Record = { next2.delay = false; } - await next2; + await next2.link(); if (!delayed) { await game.delay(); @@ -11306,7 +11306,7 @@ export const Content: Record = { next.cards = cards; next.player = player; next.type = event.name; - await next; + await next.link(); } } }, @@ -11572,7 +11572,7 @@ export const Content: Record = { next.cards = cards; next.player = player; next.type = event.name; - await next; + await next.link(); } } }, @@ -12068,7 +12068,7 @@ export const Content: Record = { next.cards = cards; next.relatedEvent = event; next.type = event.name; - await next; + await next.link(); } } }, @@ -12305,7 +12305,7 @@ export const Content: Record = { player.$damagepop(num, "wood"); game.log(player, `回复了${get.cnNumber(num)}点体力`); - await player.changeHp(num, false); + await player.changeHp(num, false).link(); } else { event._triggered = null; } @@ -12327,18 +12327,18 @@ export const Content: Record = { }); game.log(player, `失去了${get.cnNumber(num)}点体力`); - await player.changeHp(-num); + await player.changeHp(-num).link(); if (player.hp <= 0 && !event.nodying) { await game.delayx(); event._dyinged = true; - await player.dying(event); + await player.dying(event).link(); } }, async doubleDraw(event, trigger, player) { const result = await player.chooseBool("你的主副将体力上限之和是奇数,是否摸一张牌?").forResult(); if (result.bool) { - await player.draw(); + await player.draw().link(); } }, async loseMaxHp(event, trigger, player) { @@ -12361,7 +12361,7 @@ export const Content: Record = { event.changedHp = player.getHp() - Math.max(0, event.originalHp); if (player.maxHp <= 0) { - await player.die(event); + await player.die(event).link(); } }, async gainMaxHp(event, trigger, player) { @@ -12493,7 +12493,7 @@ export const Content: Record = { next.triggername = "_save"; next.forceDie = true; next.setContent("_save"); - await next; + await next.link(); } } }, @@ -12503,7 +12503,7 @@ export const Content: Record = { _status.dying = list; }, _status.dying); if (player.hp <= 0 && !event.nodying && !player.nodying) { - await player.die(event.reason); + await player.die(event.reason).link(); } }, ], @@ -12583,14 +12583,14 @@ export const Content: Record = { game.over(); } else */ if (player.hp != 0) { - await player.changeHp(0 - player.hp, false).set("forceDie", true); + await player.changeHp(0 - player.hp, false).set("forceDie", true).link(); } }, async (event, trigger, player) => { const { source } = event; //是否执行展示身份牌和胜负判断的操作 if (player.dieAfter && !event.reserveOut && !event.noDieAfter) { - await player.dieAfter(source); + await player.dieAfter(source).link(); } }, async (event, trigger, player) => { @@ -12659,7 +12659,7 @@ export const Content: Record = { //死亡弃置所有牌包括s和x区域的 event.cards = player.getCards("hejsx"); if (event.cards.length) { - await player.discard(event.cards).set("forceDie", true); + await player.discard(event.cards).set("forceDie", true).link(); //player.$throw(event.cards,1000); } } @@ -12668,7 +12668,7 @@ export const Content: Record = { const { source } = event; //是否执行击杀奖惩的操作 if (player.dieAfter2 && !event.reserveOut && !event.noDieAfter2) { - await player.dieAfter2(source); + await player.dieAfter2(source).link(); } }, async (event, trigger, player) => { @@ -12823,7 +12823,7 @@ export const Content: Record = { }, player); player.removeSkill("undist"); - await player.reviveEvent(hp, false); + await player.reviveEvent(hp, false).link(); await event.trigger("restEnd"); }, //复活事件 @@ -12971,7 +12971,7 @@ export const Content: Record = { // @ts-expect-error ignore next.visible = true; } - await next; + await next.link(); event.relatedLose = next; } let stop = false; @@ -12988,14 +12988,14 @@ export const Content: Record = { } if (stop) { if (list.length) { - await game.cardsDiscard(list); + await game.cardsDiscard(list).link(); } return; } } if (!cardInfo.effect && !cardInfo.noEffect) { if (event.cards.length) { - await game.cardsDiscard(event.cards); + await game.cardsDiscard(event.cards).link(); } return; } @@ -13154,9 +13154,9 @@ export const Content: Record = { } } } - await triggerFixing; + await triggerFixing.link(); if (event.next.includes(callback)) { - await callback; + await callback.link(); } }, ], @@ -13185,7 +13185,7 @@ export const Content: Record = { }, async chooseToGuanxing(event, trigger, player) { const cards = get.cards(event.num); - await game.cardsGotoOrdering(cards); + await game.cardsGotoOrdering(cards).link(); const next = player.chooseToMove("allowChooseAll"); next.set("forceDie", event.forceDie); next.set("includeOut", event.includeOut); @@ -13283,12 +13283,14 @@ export const Content: Record = { const top = result?.moved?.[0] || []; const bottom = result?.moved?.[1] || []; top.reverse(); - await game.cardsGotoPile(top.concat(bottom), ["top_cards", top], (event, card) => { - if (event.top_cards.includes(card)) { - return ui.cardPile.firstChild; - } - return null; - }); + await game + .cardsGotoPile(top.concat(bottom), ["top_cards", top], (event, card) => { + if (event.top_cards.includes(card)) { + return ui.cardPile.firstChild; + } + return null; + }) + .link(); game.addCardKnower(top, player); game.addCardKnower(bottom, player); event.result = { From 798eaed61a2359162ef443eb912d1e506d184f06 Mon Sep 17 00:00:00 2001 From: rintim Date: Mon, 22 Jun 2026 20:29:42 +0800 Subject: [PATCH 6/7] =?UTF-8?q?perf:=20=E5=B0=86=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E6=A0=87=E8=AF=86=E7=BD=AE=E5=85=A5=E5=AD=90?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 开发者模式打开后的`console.info`就先留着,后续稳定了再删 --- apps/core/noname/init/compatible.js | 4 +- apps/core/noname/library/element/gameEvent.ts | 52 +++++++++---------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/apps/core/noname/init/compatible.js b/apps/core/noname/init/compatible.js index f5e013eebd..ab743dcf08 100644 --- a/apps/core/noname/init/compatible.js +++ b/apps/core/noname/init/compatible.js @@ -140,8 +140,9 @@ Object.assign(lib.element.GameEvent.prototype, { Object.assign(lib.element.GameEvent.prototype, { then(onfulfilled, onrejected) { const event = /** @type { any } */ (this); + event._awaitingMode ??= "safe"; const promise = event.parent - ? event.parent.waitNext("safe", event) + ? event.parent.waitNext() : event.start().catch(error => { event.recordError(error, true); }); @@ -168,6 +169,7 @@ Object.assign(lib.element.GameEvent.prototype, { */ async link() { const event = /** @type { any } */ (this); + event._awaitingMode = "strict"; await (event.parent ? event.parent.waitNext() : event.start()); if (event.error) { throw event.error; diff --git a/apps/core/noname/library/element/gameEvent.ts b/apps/core/noname/library/element/gameEvent.ts index f1ac35e1b7..d1bb38b19a 100644 --- a/apps/core/noname/library/element/gameEvent.ts +++ b/apps/core/noname/library/element/gameEvent.ts @@ -114,16 +114,23 @@ export class GameEvent implements PromiseLike { * * @param mode 等待时采用的错误传播模式。 */ - #doAsync(mode: WaitNextMode): Promise { + async #doAsync(mode: WaitNextMode): Promise { + if (mode === "strict" || !this._awaitingMode) { + this._awaitingMode = mode; + } if (this.parent) { - return this.parent.waitNext(mode, mode === "safe" ? this : undefined).then(() => undefined); + await this.parent.waitNext(); + return; } - if (mode === "safe") { - return this.start().catch(error => { - this.recordError(error, true); - }); + // 我不确定根事件safe是否合理,但以防万一,确保safe的一致性 + try { + await this.start(); + } catch (error) { + if (mode === "strict") { + throw error; + } + this.recordError(error, true); } - return this.start(); } /** * Attaches callbacks for the resolution and/or rejection of the Promise. @@ -359,30 +366,19 @@ export class GameEvent implements PromiseLike { return true; } #waitNext?: Promise | void>; - #waitNextMode: WaitNextMode = "strict"; - #waitNextSafeEvents = new Set(); + /** + * 当前事件被等待时采用的错误传播模式。 + */ + protected _awaitingMode?: WaitNextMode; /** * 推进并等待当前事件 next 队列中的子事件。 * - * 默认 strict 模式会保持原有流程语义:子事件报错会中断当前等待。 - * safe 模式用于裸 await 事件,只记录指定 safeEvent 的错误并继续推进队列; - * 后续 strict 等待会将正在进行的 safe 等待升级为 strict。 - * - * @param mode 等待子事件时的错误传播模式,默认为 strict。 - * @param safeEvent safe 模式下允许被保底处理的目标子事件;不传则允许当前 safe 轮次处理所有子事件。 * @returns 最后一个有 result 的子事件结果。 */ - waitNext(mode: WaitNextMode = "strict", safeEvent?: GameEvent): Promise | void> { - if (mode === "safe" && safeEvent) { - this.#waitNextSafeEvents.add(safeEvent); - } + waitNext(): Promise | void> { if (this.#waitNext) { - if (mode === "strict") { - this.#waitNextMode = "strict"; - } return this.#waitNext; } - this.#waitNextMode = mode; this.#waitNext = (async () => { let result; while (true) { @@ -402,10 +398,16 @@ export class GameEvent implements PromiseLike { try { await next.start(); } catch (error) { - const safe = this.#waitNextMode === "safe" && (!this.#waitNextSafeEvents.size || this.#waitNextSafeEvents.has(next)); + const safe = next._awaitingMode === "safe"; if (!safe) { + if (lib.config.dev) { + console.info("%s 事件的报错已由 %s 重新抛出", next.name, this.name); + } throw error; } + if (lib.config.dev) { + console.info("%s 事件的报错已在 %s 屏蔽", next.name, this.name); + } next.recordError(error, true); this.next.shift(); continue; @@ -417,8 +419,6 @@ export class GameEvent implements PromiseLike { } })().finally(() => { this.#waitNext = undefined; - this.#waitNextMode = "strict"; - this.#waitNextSafeEvents.clear(); }); return this.#waitNext; } From 621c1279569f0d54cb62d9afe7821edd67ea4689 Mon Sep 17 00:00:00 2001 From: rintim Date: Mon, 22 Jun 2026 20:30:32 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=90=8E=E4=BC=9A=E5=AF=BC=E8=87=B4=E5=B1=8F?= =?UTF-8?q?=E8=94=BD=E6=8A=A5=E9=94=99=E9=80=89=E9=A1=B9=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/noname/init/compatible.js | 2 +- apps/core/noname/library/element/gameEvent.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/core/noname/init/compatible.js b/apps/core/noname/init/compatible.js index ab743dcf08..0e89898d46 100644 --- a/apps/core/noname/init/compatible.js +++ b/apps/core/noname/init/compatible.js @@ -171,7 +171,7 @@ Object.assign(lib.element.GameEvent.prototype, { const event = /** @type { any } */ (this); event._awaitingMode = "strict"; await (event.parent ? event.parent.waitNext() : event.start()); - if (event.error) { + if (event.error && !lib.config.ignore_error) { throw event.error; } }, diff --git a/apps/core/noname/library/element/gameEvent.ts b/apps/core/noname/library/element/gameEvent.ts index d1bb38b19a..255fe58661 100644 --- a/apps/core/noname/library/element/gameEvent.ts +++ b/apps/core/noname/library/element/gameEvent.ts @@ -178,7 +178,7 @@ export class GameEvent implements PromiseLike { */ async link(): Promise { await this.#doAsync("strict"); - if (this.error) { + if (this.error && !lib.config.ignore_error) { throw this.error; } }