Commit 52da069f authored by Chunchi Che's avatar Chunchi Che

Container Architecture

parent bf54d780
This diff is collapsed.
import { WebSocketStream } from "@/infra";
import {
cardStore,
chatStore,
matStore,
placeStore,
roomStore,
} from "@/stores";
import { CONTAINERS } from ".";
import { Context } from "./context";
import { Container } from "./impl";
const UI_KEY = "NEOS_UI";
export function initUIContainer(conn: WebSocketStream) {
const context = new Context({
matStore,
cardStore,
placeStore,
roomStore,
chatStore,
});
const container = new Container(context, conn);
CONTAINERS.set(UI_KEY, container);
}
export function getUIContainer(): Container {
const container = CONTAINERS.get(UI_KEY);
if (container) {
return container;
} else {
throw Error("UI Container not initialized !!");
}
}
// Context of a Duel, containing datas and states
// that we need to interact with server and player
import {
CardStore,
ChatStore,
MatStore,
PlaceStore,
RoomStore,
} from "@/stores";
interface ContextInitInfo {
matStore?: MatStore;
cardStore?: CardStore;
placeStore?: PlaceStore;
roomStore?: RoomStore;
chatStore?: ChatStore;
}
export class Context {
public matStore: MatStore;
public cardStore: CardStore;
public placeStore: PlaceStore;
public roomStore: RoomStore;
public chatStore: ChatStore;
constructor();
constructor(initInfo: ContextInitInfo);
constructor(initInfo?: ContextInitInfo) {
const { matStore, cardStore, placeStore, roomStore, chatStore } =
initInfo ?? {};
this.matStore = matStore ?? new MatStore();
this.cardStore = cardStore ?? new CardStore();
this.placeStore = placeStore ?? new PlaceStore();
this.roomStore = roomStore ?? new RoomStore();
this.chatStore = chatStore ?? new ChatStore();
}
}
import { WebSocketStream } from "@/infra";
import { Context } from "./context";
export class Container {
public context: Context;
public conn: WebSocketStream;
constructor(context: Context, conn: WebSocketStream) {
this.context = context;
this.conn = conn;
}
}
import { Container } from "./impl";
export { Context } from "./context";
export { Container } from "./impl";
// Global collection of `Container`s
export const CONTAINERS: Map<string, Container> = new Map();
...@@ -6,79 +6,36 @@ ...@@ -6,79 +6,36 @@
* */ * */
import { WebSocketStream } from "@/infra"; import { WebSocketStream } from "@/infra";
import handleSocketMessage from "../service/onSocketMessage";
import handleSocketOpen from "../service/onSocketOpen"; import handleSocketOpen from "../service/onSocketOpen";
export enum socketCmd { // FIXME: 应该有个返回值,告诉业务方本次请求的结果。比如建立长连接失败。
// 建立长连接 export function initSocket(initInfo: {
CONNECT, ip: string;
// 断开长连接 player: string;
DISCONNECT, passWd: string;
// 通过长连接发送数据 }): WebSocketStream {
SEND, const { ip, player, passWd } = initInfo;
return new WebSocketStream(ip, (conn, _event) =>
handleSocketOpen(conn, ip, player, passWd),
);
} }
export interface socketAction { export function initReplaySocket(replayInfo: {
cmd: socketCmd; url: string; // 提供回放服务的地址
// 创建长连接需要业务方传入的数据 data: ArrayBuffer; // 回放数据
initInfo?: { }): WebSocketStream {
ip: string; const { url, data } = replayInfo;
player: string; return new WebSocketStream(url, (conn, _event) => {
passWd: string; console.info("replay websocket open.");
}; conn.binaryType = "arraybuffer";
isReplay?: boolean; // 是否是回放模式 conn.send(data);
replayInfo?: { });
Url: string; // 提供回放服务的地址
data: ArrayBuffer; // 回放数据
};
// 通过长连接发送的数据
payload?: Uint8Array;
} }
let ws: WebSocketStream | null = null; export function sendSocketData(conn: WebSocketStream, payload: Uint8Array) {
conn.ws.send(payload);
// FIXME: 应该有个返回值,告诉业务方本次请求的结果。比如建立长连接失败。 }
export default async function (action: socketAction) {
switch (action.cmd) {
case socketCmd.CONNECT: {
const { initInfo: info, isReplay, replayInfo } = action;
if (info) {
ws = new WebSocketStream(info.ip, (conn, _event) =>
handleSocketOpen(conn, info.ip, info.player, info.passWd),
);
await ws.execute(handleSocketMessage);
} else if (isReplay && replayInfo) {
ws = new WebSocketStream(replayInfo.Url, (conn, _event) => {
console.info("replay websocket open.");
conn.binaryType = "arraybuffer";
conn.send(replayInfo.data);
});
await ws.execute(handleSocketMessage);
}
break;
}
case socketCmd.DISCONNECT: {
if (ws) {
ws.close();
}
break;
}
case socketCmd.SEND: {
const payload = action.payload;
if (ws && payload) {
ws.ws.send(payload);
}
break;
}
default: {
console.log("Unhandled socket command: " + action.cmd);
break; export function closeSocket(conn: WebSocketStream) {
} conn.close();
}
} }
// TODO: this middleware should be managed under `Container`, too.
import { isNil } from "lodash-es"; import { isNil } from "lodash-es";
import { Database } from "sql.js"; import { Database } from "sql.js";
......
...@@ -25,6 +25,7 @@ import { ...@@ -25,6 +25,7 @@ import {
convertPositionResponse, convertPositionResponse,
parsePlayerFromMsg, parsePlayerFromMsg,
} from "@/api/ygoAgent/transaction"; } from "@/api/ygoAgent/transaction";
import { Container } from "@/container";
import { cardStore, matStore } from "@/stores"; import { cardStore, matStore } from "@/stores";
import { argmax, computeSetDifference } from "./util"; import { argmax, computeSetDifference } from "./util";
...@@ -103,7 +104,11 @@ async function sendRequest(req: PredictReq) { ...@@ -103,7 +104,11 @@ async function sendRequest(req: PredictReq) {
// 1. 逻辑需要拆分下 // 1. 逻辑需要拆分下
// 2. 这个函数在外面被各个 service 模块分散调用, // 2. 这个函数在外面被各个 service 模块分散调用,
// 需要改成在`gameMsg.ts`调用,并通过`try..catch`正确处理错误。 // 需要改成在`gameMsg.ts`调用,并通过`try..catch`正确处理错误。
export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) { export async function sendAIPredictAsResponse(
container: Container,
msg: ygopro.StocGameMessage,
) {
const conn = container.conn;
const input = genAgentInput(msg); const input = genAgentInput(msg);
const msgName = input.action_msg.data.msg_type; const msgName = input.action_msg.data.msg_type;
const multiSelectMsgs = ["select_card", "select_tribute", "select_sum"]; const multiSelectMsgs = ["select_card", "select_tribute", "select_sum"];
...@@ -128,7 +133,7 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) { ...@@ -128,7 +133,7 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) {
responses.push(response); responses.push(response);
} }
if (response === -1 || selected.length === msg_.max) { if (response === -1 || selected.length === msg_.max) {
sendSelectMultiResponse(responses); sendSelectMultiResponse(conn, responses);
break; break;
} }
} }
...@@ -153,7 +158,7 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) { ...@@ -153,7 +158,7 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) {
selected.push(idx); selected.push(idx);
responses.push(pred.response); responses.push(pred.response);
if (pred.can_finish) { if (pred.can_finish) {
sendSelectMultiResponse(responses); sendSelectMultiResponse(conn, responses);
break; break;
} }
} }
...@@ -169,31 +174,31 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) { ...@@ -169,31 +174,31 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) {
switch (msgName) { switch (msgName) {
case "announce_attrib": case "announce_attrib":
case "announce_number": case "announce_number":
sendSelectOptionResponse(response); sendSelectOptionResponse(conn, response);
break; break;
case "select_battlecmd": case "select_battlecmd":
sendSelectBattleCmdResponse(response); sendSelectBattleCmdResponse(conn, response);
break; break;
case "select_chain": case "select_chain":
sendSelectSingleResponse(response); sendSelectSingleResponse(conn, response);
break; break;
case "select_yesno": case "select_yesno":
case "select_effectyn": case "select_effectyn":
sendSelectEffectYnResponse(response === 1); sendSelectEffectYnResponse(conn, response === 1);
break; break;
case "select_idlecmd": case "select_idlecmd":
sendSelectIdleCmdResponse(response); sendSelectIdleCmdResponse(conn, response);
break; break;
case "select_option": case "select_option":
sendSelectOptionResponse(response); sendSelectOptionResponse(conn, response);
break; break;
case "select_position": case "select_position":
sendSelectPositionResponse(convertPositionResponse(response)); sendSelectPositionResponse(conn, convertPositionResponse(response));
break; break;
case "select_place": { case "select_place": {
const place = (msg as unknown as ygopro.StocGameMessage.MsgSelectPlace) const place = (msg as unknown as ygopro.StocGameMessage.MsgSelectPlace)
.places[response]; .places[response];
sendSelectPlaceResponse({ sendSelectPlaceResponse(conn, {
controller: place.controller, controller: place.controller,
zone: place.zone, zone: place.zone,
sequence: place.sequence, sequence: place.sequence,
...@@ -202,9 +207,9 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) { ...@@ -202,9 +207,9 @@ export async function sendAIPredictAsResponse(msg: ygopro.StocGameMessage) {
} }
case "select_unselect_card": { case "select_unselect_card": {
if (response === -1) { if (response === -1) {
sendSelectSingleResponse(-1); sendSelectSingleResponse(conn, -1);
} else { } else {
sendSelectMultiResponse([response]); sendSelectMultiResponse(conn, [response]);
} }
break; break;
} }
......
import { fetchStrings, Region, ygopro } from "@/api"; import { fetchStrings, Region, ygopro } from "@/api";
import { displayOptionModal } from "@/ui/Duel/Message"; import { displayOptionModal } from "@/ui/Duel/Message";
import MsgAnnounce = ygopro.StocGameMessage.MsgAnnounce; import MsgAnnounce = ygopro.StocGameMessage.MsgAnnounce;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { displayAnnounceModal } from "@/ui/Duel/Message/AnnounceModal"; import { displayAnnounceModal } from "@/ui/Duel/Message/AnnounceModal";
export default async (announce: MsgAnnounce) => { export default async (container: Container, announce: MsgAnnounce) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
// TODO: 如果是开启 AI 模式,不应该调用这个函数 // TODO: 如果是开启 AI 模式,不应该调用这个函数
console.log("intercept announce"); console.log("intercept announce");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
announce as unknown as ygopro.StocGameMessage, announce as unknown as ygopro.StocGameMessage,
); );
return; return;
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { Container } from "@/container";
import { replayStore } from "@/stores"; import { replayStore } from "@/stores";
import { showWaiting } from "@/ui/Duel/Message"; import { showWaiting } from "@/ui/Duel/Message";
...@@ -90,6 +91,7 @@ const ReplayIgnoreMsg = [ ...@@ -90,6 +91,7 @@ const ReplayIgnoreMsg = [
]; ];
export default async function handleGameMsg( export default async function handleGameMsg(
container: Container,
pb: ygopro.YgoStocMsg, pb: ygopro.YgoStocMsg,
): Promise<void> { ): Promise<void> {
const msg = pb.stoc_game_msg; const msg = pb.stoc_game_msg;
...@@ -129,12 +131,12 @@ export default async function handleGameMsg( ...@@ -129,12 +131,12 @@ export default async function handleGameMsg(
break; break;
} }
case "select_idle_cmd": { case "select_idle_cmd": {
onMsgSelectIdleCmd(msg.select_idle_cmd); onMsgSelectIdleCmd(container, msg.select_idle_cmd);
break; break;
} }
case "select_place": { case "select_place": {
onMsgSelectPlace(msg.select_place); onMsgSelectPlace(container, msg.select_place);
break; break;
} }
...@@ -143,27 +145,27 @@ export default async function handleGameMsg( ...@@ -143,27 +145,27 @@ export default async function handleGameMsg(
break; break;
} }
case "select_card": { case "select_card": {
onMsgSelectCard(msg.select_card); onMsgSelectCard(container, msg.select_card);
break; break;
} }
case "select_chain": { case "select_chain": {
onMsgSelectChain(msg.select_chain); onMsgSelectChain(container, msg.select_chain);
break; break;
} }
case "select_effect_yn": { case "select_effect_yn": {
await onMsgSelectEffectYn(msg.select_effect_yn); await onMsgSelectEffectYn(container, msg.select_effect_yn);
break; break;
} }
case "select_position": { case "select_position": {
await onMsgSelectPosition(msg.select_position); await onMsgSelectPosition(container, msg.select_position);
break; break;
} }
case "select_option": { case "select_option": {
await onMsgSelectOption(msg.select_option); await onMsgSelectOption(container, msg.select_option);
break; break;
} }
...@@ -173,7 +175,7 @@ export default async function handleGameMsg( ...@@ -173,7 +175,7 @@ export default async function handleGameMsg(
break; break;
} }
case "select_battle_cmd": { case "select_battle_cmd": {
onMsgSelectBattleCmd(msg.select_battle_cmd); onMsgSelectBattleCmd(container, msg.select_battle_cmd);
break; break;
} }
...@@ -183,12 +185,12 @@ export default async function handleGameMsg( ...@@ -183,12 +185,12 @@ export default async function handleGameMsg(
break; break;
} }
case "select_unselect_card": { case "select_unselect_card": {
await onMsgSelectUnselectCard(msg.select_unselect_card); await onMsgSelectUnselectCard(container, msg.select_unselect_card);
break; break;
} }
case "select_yes_no": { case "select_yes_no": {
await onMsgSelectYesNo(msg.select_yes_no); await onMsgSelectYesNo(container, msg.select_yes_no);
break; break;
} }
...@@ -218,12 +220,12 @@ export default async function handleGameMsg( ...@@ -218,12 +220,12 @@ export default async function handleGameMsg(
break; break;
} }
case "select_sum": { case "select_sum": {
onMsgSelectSum(msg.select_sum); onMsgSelectSum(container, msg.select_sum);
break; break;
} }
case "select_tribute": { case "select_tribute": {
onMsgSelectTribute(msg.select_tribute); onMsgSelectTribute(container, msg.select_tribute);
break; break;
} }
...@@ -307,7 +309,7 @@ export default async function handleGameMsg( ...@@ -307,7 +309,7 @@ export default async function handleGameMsg(
break; break;
} }
case "announce": { case "announce": {
await onAnnounce(msg.announce); await onAnnounce(container, msg.announce);
break; break;
} }
......
...@@ -7,9 +7,13 @@ import { ...@@ -7,9 +7,13 @@ import {
} from "@/stores"; } from "@/stores";
import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd; import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
export default async (selectBattleCmd: MsgSelectBattleCmd) => { export default async (
container: Container,
selectBattleCmd: MsgSelectBattleCmd,
) => {
const player = selectBattleCmd.player; const player = selectBattleCmd.player;
const cmds = selectBattleCmd.battle_cmds; const cmds = selectBattleCmd.battle_cmds;
...@@ -21,6 +25,7 @@ export default async (selectBattleCmd: MsgSelectBattleCmd) => { ...@@ -21,6 +25,7 @@ export default async (selectBattleCmd: MsgSelectBattleCmd) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectBattleCmd"); console.log("intercept selectBattleCmd");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectBattleCmd as unknown as ygopro.StocGameMessage, selectBattleCmd as unknown as ygopro.StocGameMessage,
); );
return; return;
......
import { sendSelectMultiResponse, ygopro } from "@/api"; import { sendSelectMultiResponse, ygopro } from "@/api";
import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard; import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"; import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal";
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
export default async (selectCard: MsgSelectCard) => { export default async (container: Container, selectCard: MsgSelectCard) => {
const { cancelable, min, max, cards } = selectCard; const { cancelable, min, max, cards } = selectCard;
const conn = container.conn;
// TODO: handle release_param // TODO: handle release_param
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectCard"); console.log("intercept selectCard");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectCard as unknown as ygopro.StocGameMessage, selectCard as unknown as ygopro.StocGameMessage,
); );
return; return;
...@@ -22,7 +25,7 @@ export default async (selectCard: MsgSelectCard) => { ...@@ -22,7 +25,7 @@ export default async (selectCard: MsgSelectCard) => {
if (!cancelable && cards.length === 1) { if (!cancelable && cards.length === 1) {
// auto send // auto send
sendSelectMultiResponse([cards[0].response]); sendSelectMultiResponse(conn, [cards[0].response]);
return; return;
} }
......
import { sendSelectSingleResponse, ygopro } from "@/api"; import { sendSelectSingleResponse, ygopro } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { ChainSetting, fetchSelectHintMeta, matStore } from "@/stores"; import { ChainSetting, fetchSelectHintMeta, matStore } from "@/stores";
import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"; import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal";
...@@ -6,7 +7,8 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal" ...@@ -6,7 +7,8 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
type MsgSelectChain = ygopro.StocGameMessage.MsgSelectChain; type MsgSelectChain = ygopro.StocGameMessage.MsgSelectChain;
export default async (selectChain: MsgSelectChain) => { export default async (container: Container, selectChain: MsgSelectChain) => {
const conn = container.conn;
const spCount = selectChain.special_count; const spCount = selectChain.special_count;
const forced = selectChain.forced; const forced = selectChain.forced;
const _hint0 = selectChain.hint0; const _hint0 = selectChain.hint0;
...@@ -16,7 +18,7 @@ export default async (selectChain: MsgSelectChain) => { ...@@ -16,7 +18,7 @@ export default async (selectChain: MsgSelectChain) => {
if (chainSetting === ChainSetting.CHAIN_IGNORE) { if (chainSetting === ChainSetting.CHAIN_IGNORE) {
// 如果玩家配置了忽略连锁,直接回应后端并返回 // 如果玩家配置了忽略连锁,直接回应后端并返回
sendSelectSingleResponse(-1); sendSelectSingleResponse(conn, -1);
return; return;
} }
...@@ -61,7 +63,7 @@ export default async (selectChain: MsgSelectChain) => { ...@@ -61,7 +63,7 @@ export default async (selectChain: MsgSelectChain) => {
switch (handle_flag) { switch (handle_flag) {
case 0: { case 0: {
// 直接回答 // 直接回答
sendSelectSingleResponse(-1); sendSelectSingleResponse(conn, -1);
break; break;
} }
...@@ -71,6 +73,7 @@ export default async (selectChain: MsgSelectChain) => { ...@@ -71,6 +73,7 @@ export default async (selectChain: MsgSelectChain) => {
// TODO: 确认AI模型是否可以处理其他case的情况 // TODO: 确认AI模型是否可以处理其他case的情况
console.log("intercept selectChain"); console.log("intercept selectChain");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectChain as unknown as ygopro.StocGameMessage, selectChain as unknown as ygopro.StocGameMessage,
); );
return; return;
...@@ -96,7 +99,7 @@ export default async (selectChain: MsgSelectChain) => { ...@@ -96,7 +99,7 @@ export default async (selectChain: MsgSelectChain) => {
} }
case 4: { case 4: {
// 有一张强制发动的卡,直接回应 // 有一张强制发动的卡,直接回应
sendSelectSingleResponse(chains[0].response); sendSelectSingleResponse(conn, chains[0].response);
break; break;
} }
......
import { fetchStrings, Region, type ygopro } from "@/api"; import { fetchStrings, Region, type ygopro } from "@/api";
import { CardMeta, fetchCard } from "@/api/cards"; import { CardMeta, fetchCard } from "@/api/cards";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { displayYesNoModal } from "@/ui/Duel/Message"; import { displayYesNoModal } from "@/ui/Duel/Message";
...@@ -7,10 +8,14 @@ import { displayYesNoModal } from "@/ui/Duel/Message"; ...@@ -7,10 +8,14 @@ import { displayYesNoModal } from "@/ui/Duel/Message";
type MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn; type MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn;
// 这里改成了 async 不知道有没有影响 // 这里改成了 async 不知道有没有影响
export default async (selectEffectYn: MsgSelectEffectYn) => { export default async (
container: Container,
selectEffectYn: MsgSelectEffectYn,
) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectEffectYn"); console.log("intercept selectEffectYn");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectEffectYn as unknown as ygopro.StocGameMessage, selectEffectYn as unknown as ygopro.StocGameMessage,
); );
return; return;
......
...@@ -8,8 +8,12 @@ import { ...@@ -8,8 +8,12 @@ import {
} from "@/stores"; } from "@/stores";
import MsgSelectIdleCmd = ygopro.StocGameMessage.MsgSelectIdleCmd; import MsgSelectIdleCmd = ygopro.StocGameMessage.MsgSelectIdleCmd;
import { Container } from "@/container";
export default async (selectIdleCmd: MsgSelectIdleCmd) => { export default async (
container: Container,
selectIdleCmd: MsgSelectIdleCmd,
) => {
const player = selectIdleCmd.player; const player = selectIdleCmd.player;
const cmds = selectIdleCmd.idle_cmds; const cmds = selectIdleCmd.idle_cmds;
...@@ -21,6 +25,7 @@ export default async (selectIdleCmd: MsgSelectIdleCmd) => { ...@@ -21,6 +25,7 @@ export default async (selectIdleCmd: MsgSelectIdleCmd) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectIdleCmd"); console.log("intercept selectIdleCmd");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectIdleCmd as unknown as ygopro.StocGameMessage, selectIdleCmd as unknown as ygopro.StocGameMessage,
); );
return; return;
......
...@@ -5,27 +5,33 @@ import { ...@@ -5,27 +5,33 @@ import {
sendSelectOptionResponse, sendSelectOptionResponse,
type ygopro, type ygopro,
} from "@/api"; } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { displayOptionModal } from "@/ui/Duel/Message"; import { displayOptionModal } from "@/ui/Duel/Message";
export default async (selectOption: ygopro.StocGameMessage.MsgSelectOption) => { export default async (
container: Container,
selectOption: ygopro.StocGameMessage.MsgSelectOption,
) => {
const conn = container.conn;
const options = selectOption.options; const options = selectOption.options;
if (options.length === 0) { if (options.length === 0) {
sendSelectOptionResponse(0); sendSelectOptionResponse(conn, 0);
return; return;
} }
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectOption"); console.log("intercept selectOption");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectOption as unknown as ygopro.StocGameMessage, selectOption as unknown as ygopro.StocGameMessage,
); );
return; return;
} }
if (options.length === 1) { if (options.length === 1) {
sendSelectOptionResponse(options[0].response); sendSelectOptionResponse(conn, options[0].response);
return; return;
} }
......
import { sendSelectPlaceResponse, ygopro } from "@/api"; import { sendSelectPlaceResponse, ygopro } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { InteractType, matStore, placeStore } from "@/stores"; import { InteractType, matStore, placeStore } from "@/stores";
type MsgSelectPlace = ygopro.StocGameMessage.MsgSelectPlace; type MsgSelectPlace = ygopro.StocGameMessage.MsgSelectPlace;
export default async (selectPlace: MsgSelectPlace) => { export default async (container: Container, selectPlace: MsgSelectPlace) => {
const conn = container.conn;
if (selectPlace.count !== 1) { if (selectPlace.count !== 1) {
console.warn(`Unhandled case: ${selectPlace}`); console.warn(`Unhandled case: ${selectPlace}`);
return; return;
...@@ -13,6 +15,7 @@ export default async (selectPlace: MsgSelectPlace) => { ...@@ -13,6 +15,7 @@ export default async (selectPlace: MsgSelectPlace) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectPlace"); console.log("intercept selectPlace");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectPlace as unknown as ygopro.StocGameMessage, selectPlace as unknown as ygopro.StocGameMessage,
); );
return; return;
...@@ -20,7 +23,7 @@ export default async (selectPlace: MsgSelectPlace) => { ...@@ -20,7 +23,7 @@ export default async (selectPlace: MsgSelectPlace) => {
if (selectPlace.places.length === 1) { if (selectPlace.places.length === 1) {
const place = selectPlace.places[0]; const place = selectPlace.places[0];
sendSelectPlaceResponse({ sendSelectPlaceResponse(conn, {
controller: place.controller, controller: place.controller,
zone: place.zone, zone: place.zone,
sequence: place.sequence, sequence: place.sequence,
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { displayPositionModal } from "@/ui/Duel/Message"; import { displayPositionModal } from "@/ui/Duel/Message";
type MsgSelectPosition = ygopro.StocGameMessage.MsgSelectPosition; type MsgSelectPosition = ygopro.StocGameMessage.MsgSelectPosition;
export default async (selectPosition: MsgSelectPosition) => { export default async (
container: Container,
selectPosition: MsgSelectPosition,
) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectPosition"); console.log("intercept selectPosition");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectPosition as unknown as ygopro.StocGameMessage, selectPosition as unknown as ygopro.StocGameMessage,
); );
return; return;
......
...@@ -4,13 +4,15 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal" ...@@ -4,13 +4,15 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
type MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum; type MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
export default async (selectSum: MsgSelectSum) => { export default async (container: Container, selectSum: MsgSelectSum) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectSum"); console.log("intercept selectSum");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectSum as unknown as ygopro.StocGameMessage, selectSum as unknown as ygopro.StocGameMessage,
); );
return; return;
......
...@@ -4,13 +4,18 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal" ...@@ -4,13 +4,18 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
type MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute; type MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
export default async (selectTribute: MsgSelectTribute) => { export default async (
container: Container,
selectTribute: MsgSelectTribute,
) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectTribute"); console.log("intercept selectTribute");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectTribute as unknown as ygopro.StocGameMessage, selectTribute as unknown as ygopro.StocGameMessage,
); );
return; return;
......
...@@ -6,12 +6,17 @@ import { fetchCheckCardMeta } from "../utils"; ...@@ -6,12 +6,17 @@ import { fetchCheckCardMeta } from "../utils";
import { isAllOnField } from "./util"; import { isAllOnField } from "./util";
type MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard; type MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
export default async (selectUnselectCards: MsgSelectUnselectCard) => { export default async (
container: Container,
selectUnselectCards: MsgSelectUnselectCard,
) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectUnselectCards"); console.log("intercept selectUnselectCards");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectUnselectCards as unknown as ygopro.StocGameMessage, selectUnselectCards as unknown as ygopro.StocGameMessage,
); );
return; return;
......
import { getStrings, ygopro } from "@/api"; import { getStrings, ygopro } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { displayYesNoModal } from "@/ui/Duel/Message"; import { displayYesNoModal } from "@/ui/Duel/Message";
type MsgSelectYesNo = ygopro.StocGameMessage.MsgSelectYesNo; type MsgSelectYesNo = ygopro.StocGameMessage.MsgSelectYesNo;
export default async (selectYesNo: MsgSelectYesNo) => { export default async (container: Container, selectYesNo: MsgSelectYesNo) => {
if (matStore.autoSelect) { if (matStore.autoSelect) {
console.log("intercept selectYesNo"); console.log("intercept selectYesNo");
await sendAIPredictAsResponse( await sendAIPredictAsResponse(
container,
selectYesNo as unknown as ygopro.StocGameMessage, selectYesNo as unknown as ygopro.StocGameMessage,
); );
return; return;
......
import { sendTimeConfirm, ygopro } from "@/api"; import { sendTimeConfirm, ygopro } from "@/api";
import { Container } from "@/container";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
export default function handleTimeLimit(timeLimit: ygopro.StocTimeLimit) { export default function handleTimeLimit(
container: Container,
timeLimit: ygopro.StocTimeLimit,
) {
matStore.timeLimits.set(timeLimit.player, timeLimit.left_time); matStore.timeLimits.set(timeLimit.player, timeLimit.left_time);
if (matStore.isMe(timeLimit.player)) { if (matStore.isMe(timeLimit.player)) {
sendTimeConfirm(); sendTimeConfirm(container.conn);
} }
} }
import { Container } from "@/container";
import { WebSocketStream } from "@/infra";
import handleSocketMessage from "./onSocketMessage";
export async function pollSocketLooper(
container: Container,
conn: WebSocketStream,
) {
await conn.execute((event) => handleSocketMessage(container, event));
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
* */ * */
import { adaptStoc } from "@/api/ocgcore/ocgAdapter/adapter"; import { adaptStoc } from "@/api/ocgcore/ocgAdapter/adapter";
import { YgoProPacket } from "@/api/ocgcore/ocgAdapter/packet"; import { YgoProPacket } from "@/api/ocgcore/ocgAdapter/packet";
import { Container } from "@/container";
import { replayStore } from "@/stores"; import { replayStore } from "@/stores";
import handleGameMsg from "./duel/gameMsg"; import handleGameMsg from "./duel/gameMsg";
...@@ -32,12 +33,15 @@ import { handleWaitingSide } from "./side/waitingSide"; ...@@ -32,12 +33,15 @@ import { handleWaitingSide } from "./side/waitingSide";
let animation: Promise<void> = Promise.resolve(); let animation: Promise<void> = Promise.resolve();
export default async function handleSocketMessage(e: MessageEvent) { export default async function handleSocketMessage(
container: Container,
e: MessageEvent,
) {
// 确保按序执行 // 确保按序执行
animation = animation.then(() => _handle(e)); animation = animation.then(() => _handle(container, e));
} }
async function _handle(e: MessageEvent) { async function _handle(container: Container, e: MessageEvent) {
const packets = YgoProPacket.deserialize(e.data); const packets = YgoProPacket.deserialize(e.data);
for (const packet of packets) { for (const packet of packets) {
...@@ -97,12 +101,12 @@ async function _handle(e: MessageEvent) { ...@@ -97,12 +101,12 @@ async function _handle(e: MessageEvent) {
// 如果不是回放模式,则记录回放数据 // 如果不是回放模式,则记录回放数据
replayStore.record(packet); replayStore.record(packet);
} }
await handleGameMsg(pb); await handleGameMsg(container, pb);
break; break;
} }
case "stoc_time_limit": { case "stoc_time_limit": {
handleTimeLimit(pb.stoc_time_limit); handleTimeLimit(container, pb.stoc_time_limit);
break; break;
} }
case "stoc_error_msg": { case "stoc_error_msg": {
......
...@@ -24,7 +24,7 @@ export interface CardType { ...@@ -24,7 +24,7 @@ export interface CardType {
}; };
} }
class CardStore implements NeosStore { export class CardStore implements NeosStore {
inner: CardType[] = []; inner: CardType[] = [];
at(zone: ygopro.CardZone, controller: number): CardType[]; at(zone: ygopro.CardZone, controller: number): CardType[];
at( at(
......
...@@ -2,15 +2,13 @@ import { proxy } from "valtio"; ...@@ -2,15 +2,13 @@ import { proxy } from "valtio";
import { type NeosStore } from "./shared"; import { type NeosStore } from "./shared";
export interface ChatState extends NeosStore { export class ChatStore implements NeosStore {
sender: number; sender: number = -1;
message: string; message: string = "";
reset(): void {
this.message = "";
}
} }
export const chatStore = proxy<ChatState>({ export const chatStore = proxy<ChatStore>(new ChatStore());
sender: -1,
message: "",
reset() {
chatStore.message = "";
},
});
...@@ -18,6 +18,7 @@ const getWhom = (controller: number): "me" | "op" => ...@@ -18,6 +18,7 @@ const getWhom = (controller: number): "me" | "op" =>
* 原本名字叫judgeSelf * 原本名字叫judgeSelf
*/ */
export const isMe = (controller: number): boolean => { export const isMe = (controller: number): boolean => {
// FIXME: all of the `matStore` need to access with container
switch (matStore.selfType) { switch (matStore.selfType) {
case 1: case 1:
// 自己是先攻 // 自己是先攻
...@@ -102,7 +103,7 @@ const initialState: Omit<MatState, "reset"> = { ...@@ -102,7 +103,7 @@ const initialState: Omit<MatState, "reset"> = {
autoSelect: false, autoSelect: false,
}; };
class MatStore implements MatState, NeosStore { export class MatStore implements MatState, NeosStore {
chains = initialState.chains; chains = initialState.chains;
chainSetting = initialState.chainSetting; chainSetting = initialState.chainSetting;
timeLimits = initialState.timeLimits; timeLimits = initialState.timeLimits;
......
...@@ -58,7 +58,7 @@ const initialState = { ...@@ -58,7 +58,7 @@ const initialState = {
}, },
}; };
class PlaceStore implements NeosStore { export class PlaceStore implements NeosStore {
inner: { inner: {
[zone: number]: { [zone: number]: {
me: BlockState[]; me: BlockState[];
...@@ -70,14 +70,15 @@ class PlaceStore implements NeosStore { ...@@ -70,14 +70,15 @@ class PlaceStore implements NeosStore {
controller: number; controller: number;
sequence: number; sequence: number;
}): BlockState | undefined { }): BlockState | undefined {
return placeStore.inner[location.zone][ return this.inner[location.zone][
// FIXME: inject `matStore`
matStore.isMe(location.controller) ? "me" : "op" matStore.isMe(location.controller) ? "me" : "op"
][location.sequence]; ][location.sequence];
} }
clearAllInteractivity() { clearAllInteractivity() {
(["me", "op"] as const).forEach((who) => { (["me", "op"] as const).forEach((who) => {
([MZONE, SZONE] as const).forEach((where) => { ([MZONE, SZONE] as const).forEach((where) => {
placeStore.inner[where][who].forEach( this.inner[where][who].forEach(
(block) => (block.interactivity = undefined), (block) => (block.interactivity = undefined),
); );
}); });
...@@ -87,7 +88,7 @@ class PlaceStore implements NeosStore { ...@@ -87,7 +88,7 @@ class PlaceStore implements NeosStore {
const resetObj = cloneDeep(initialState); const resetObj = cloneDeep(initialState);
Object.keys(resetObj).forEach((key) => { Object.keys(resetObj).forEach((key) => {
// @ts-ignore // @ts-ignore
placeStore.inner[key] = resetObj[key]; this.inner[key] = resetObj[key];
}); });
} }
} }
......
...@@ -33,7 +33,7 @@ export enum RoomStage { ...@@ -33,7 +33,7 @@ export enum RoomStage {
DUEL_START = 6, // 决斗开始 DUEL_START = 6, // 决斗开始
} }
class RoomStore implements NeosStore { export class RoomStore implements NeosStore {
joined: boolean = false; // 是否已经加入房间 joined: boolean = false; // 是否已经加入房间
players: (Player | undefined)[] = Array.from({ length: 4 }).map( players: (Player | undefined)[] = Array.from({ length: 4 }).map(
(_) => undefined, (_) => undefined,
......
...@@ -4,11 +4,13 @@ import { useNavigate } from "react-router-dom"; ...@@ -4,11 +4,13 @@ import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { sendSurrender } from "@/api"; import { sendSurrender } from "@/api";
import { getUIContainer } from "@/container/compat";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
export const Alert = () => { export const Alert = () => {
const matSnap = useSnapshot(matStore); const matSnap = useSnapshot(matStore);
const unimplemented = matSnap.unimplemented; const unimplemented = matSnap.unimplemented;
const container = getUIContainer();
const navigate = useNavigate(); const navigate = useNavigate();
...@@ -24,7 +26,7 @@ export const Alert = () => { ...@@ -24,7 +26,7 @@ export const Alert = () => {
banner banner
afterClose={() => { afterClose={() => {
// 发送投降信号 // 发送投降信号
sendSurrender(); sendSurrender(container.conn);
navigate("/match"); navigate("/match");
}} }}
/> />
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
sendSelectOptionResponse, sendSelectOptionResponse,
} from "@/api"; } from "@/api";
import { isDeclarable, isToken } from "@/common"; import { isDeclarable, isToken } from "@/common";
import { getUIContainer } from "@/container/compat";
import { emptySearchConditions } from "@/middleware/sqlite/fts"; import { emptySearchConditions } from "@/middleware/sqlite/fts";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
...@@ -35,6 +36,7 @@ export const AnnounceModal: React.FC = () => { ...@@ -35,6 +36,7 @@ export const AnnounceModal: React.FC = () => {
const [searchWord, setSearchWord] = useState(""); const [searchWord, setSearchWord] = useState("");
const [cardList, setCardList] = useState<CardMeta[]>([]); const [cardList, setCardList] = useState<CardMeta[]>([]);
const [selected, setSelected] = useState<number | undefined>(undefined); const [selected, setSelected] = useState<number | undefined>(undefined);
const container = getUIContainer();
const handleSearch = () => { const handleSearch = () => {
const result = searchCards({ const result = searchCards({
...@@ -51,7 +53,7 @@ export const AnnounceModal: React.FC = () => { ...@@ -51,7 +53,7 @@ export const AnnounceModal: React.FC = () => {
}; };
const onSummit = () => { const onSummit = () => {
if (selected !== undefined) { if (selected !== undefined) {
sendSelectOptionResponse(selected); sendSelectOptionResponse(container.conn, selected);
rs(); rs();
setSearchWord(""); setSearchWord("");
setCardList([]); setCardList([]);
......
...@@ -5,6 +5,7 @@ import React, { useEffect, useState } from "react"; ...@@ -5,6 +5,7 @@ import React, { useEffect, useState } from "react";
import { proxy, useSnapshot } from "valtio"; import { proxy, useSnapshot } from "valtio";
import { fetchStrings, Region, sendSelectCounterResponse } from "@/api"; import { fetchStrings, Region, sendSelectCounterResponse } from "@/api";
import { getUIContainer } from "@/container/compat";
import { YgoCard } from "@/ui/Shared"; import { YgoCard } from "@/ui/Shared";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
...@@ -27,6 +28,7 @@ const defaultProps = { ...@@ -27,6 +28,7 @@ const defaultProps = {
const localStore = proxy<CheckCounterModalProps>(defaultProps); const localStore = proxy<CheckCounterModalProps>(defaultProps);
export const CheckCounterModal = () => { export const CheckCounterModal = () => {
const container = getUIContainer();
const snapCheckCounterModal = useSnapshot(localStore); const snapCheckCounterModal = useSnapshot(localStore);
const isOpen = snapCheckCounterModal.isOpen; const isOpen = snapCheckCounterModal.isOpen;
...@@ -46,7 +48,7 @@ export const CheckCounterModal = () => { ...@@ -46,7 +48,7 @@ export const CheckCounterModal = () => {
}, [options]); }, [options]);
const onFinish = () => { const onFinish = () => {
sendSelectCounterResponse(selected); sendSelectCounterResponse(container.conn, selected);
rs(); rs();
}; };
......
...@@ -12,6 +12,8 @@ import { ...@@ -12,6 +12,8 @@ import {
sendSelectIdleCmdResponse, sendSelectIdleCmdResponse,
sendSelectOptionResponse, sendSelectOptionResponse,
} from "@/api"; } from "@/api";
import { Container } from "@/container";
import { getUIContainer } from "@/container/compat";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
...@@ -29,6 +31,7 @@ const store = proxy(defaultStore); ...@@ -29,6 +31,7 @@ const store = proxy(defaultStore);
const MAX_NUM_PER_PAGE = 4; const MAX_NUM_PER_PAGE = 4;
export const OptionModal = () => { export const OptionModal = () => {
const container = getUIContainer();
const snap = useSnapshot(store); const snap = useSnapshot(store);
const { title, isOpen, min, options } = snap; const { title, isOpen, min, options } = snap;
// options可能太多,因此分页展示 // options可能太多,因此分页展示
...@@ -41,7 +44,7 @@ export const OptionModal = () => { ...@@ -41,7 +44,7 @@ export const OptionModal = () => {
const responses = selecteds.flat(); const responses = selecteds.flat();
if (responses.length > 0) { if (responses.length > 0) {
const response = responses.reduce((res, current) => res | current, 0); // 多个选择求或 const response = responses.reduce((res, current) => res | current, 0); // 多个选择求或
sendSelectOptionResponse(response); sendSelectOptionResponse(container.conn, response);
rs(); rs();
} }
}; };
...@@ -132,6 +135,7 @@ export const displayOptionModal = async ( ...@@ -132,6 +135,7 @@ export const displayOptionModal = async (
}; };
export const handleEffectActivation = async ( export const handleEffectActivation = async (
container: Container,
meta: CardMeta, meta: CardMeta,
effectInteractivies: { effectInteractivies: {
desc: string; desc: string;
...@@ -144,7 +148,7 @@ export const handleEffectActivation = async ( ...@@ -144,7 +148,7 @@ export const handleEffectActivation = async (
} }
if (effectInteractivies.length === 1) { if (effectInteractivies.length === 1) {
// 如果只有一个效果,点击直接触发 // 如果只有一个效果,点击直接触发
sendSelectIdleCmdResponse(effectInteractivies[0].response); sendSelectIdleCmdResponse(container.conn, effectInteractivies[0].response);
} else { } else {
// optionsModal // optionsModal
const options = effectInteractivies.map((effect) => { const options = effectInteractivies.map((effect) => {
......
...@@ -5,6 +5,7 @@ import React, { useState } from "react"; ...@@ -5,6 +5,7 @@ import React, { useState } from "react";
import { proxy, useSnapshot } from "valtio"; import { proxy, useSnapshot } from "valtio";
import { sendSelectPositionResponse, ygopro } from "@/api"; import { sendSelectPositionResponse, ygopro } from "@/api";
import { getUIContainer } from "@/container/compat";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
...@@ -91,6 +92,7 @@ const translations: Translations = { ...@@ -91,6 +92,7 @@ const translations: Translations = {
}; };
export const PositionModal = () => { export const PositionModal = () => {
const container = getUIContainer();
const { isOpen, positions } = useSnapshot(localStore); const { isOpen, positions } = useSnapshot(localStore);
const [selected, setSelected] = useState<ygopro.CardPosition | undefined>( const [selected, setSelected] = useState<ygopro.CardPosition | undefined>(
undefined, undefined,
...@@ -105,7 +107,7 @@ export const PositionModal = () => { ...@@ -105,7 +107,7 @@ export const PositionModal = () => {
disabled={selected === undefined} disabled={selected === undefined}
onClick={() => { onClick={() => {
if (selected !== undefined) { if (selected !== undefined) {
sendSelectPositionResponse(selected); sendSelectPositionResponse(container.conn, selected);
rs(); rs();
} }
}} }}
......
import { INTERNAL_Snapshot as Snapshot, proxy, useSnapshot } from "valtio"; import { INTERNAL_Snapshot as Snapshot, proxy, useSnapshot } from "valtio";
import { sendSelectMultiResponse, sendSelectSingleResponse } from "@/api"; import { sendSelectMultiResponse, sendSelectSingleResponse } from "@/api";
import { getUIContainer } from "@/container/compat";
import { import {
type Option, type Option,
...@@ -32,25 +33,26 @@ const defaultProps: Omit< ...@@ -32,25 +33,26 @@ const defaultProps: Omit<
const localStore = proxy(defaultProps); const localStore = proxy(defaultProps);
export const SelectActionsModal: React.FC = () => { export const SelectActionsModal: React.FC = () => {
const container = getUIContainer();
const snap = useSnapshot(localStore); const snap = useSnapshot(localStore);
const onSubmit = (options: Snapshot<Option[]>) => { const onSubmit = (options: Snapshot<Option[]>) => {
const values = options.map((option) => option.response!); const values = options.map((option) => option.response!);
if (localStore.isChain) { if (localStore.isChain) {
sendSelectSingleResponse(values[0]); sendSelectSingleResponse(container.conn, values[0]);
} else { } else {
sendSelectMultiResponse(values); sendSelectMultiResponse(container.conn, values);
} }
rs(); rs();
}; };
const onFinish = () => { const onFinish = () => {
sendSelectSingleResponse(FINISH_RESPONSE); sendSelectSingleResponse(container.conn, FINISH_RESPONSE);
rs(); rs();
}; };
const onCancel = () => { const onCancel = () => {
sendSelectSingleResponse(CANCEL_RESPONSE); sendSelectSingleResponse(container.conn, CANCEL_RESPONSE);
rs(); rs();
}; };
......
...@@ -22,6 +22,7 @@ import { proxy, useSnapshot } from "valtio"; ...@@ -22,6 +22,7 @@ import { proxy, useSnapshot } from "valtio";
import { sendSortCardResponse } from "@/api"; import { sendSortCardResponse } from "@/api";
import { CardMeta, getCardImgUrl } from "@/api/cards"; import { CardMeta, getCardImgUrl } from "@/api/cards";
import { getUIContainer } from "@/container/compat";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
...@@ -41,6 +42,7 @@ const defaultProps = { ...@@ -41,6 +42,7 @@ const defaultProps = {
const localStore = proxy<SortCardModalProps>(defaultProps); const localStore = proxy<SortCardModalProps>(defaultProps);
export const SortCardModal = () => { export const SortCardModal = () => {
const container = getUIContainer();
const { isOpen, options } = useSnapshot(localStore); const { isOpen, options } = useSnapshot(localStore);
const [items, setItems] = useState(options); const [items, setItems] = useState(options);
const sensors = useSensors( const sensors = useSensors(
...@@ -51,7 +53,10 @@ export const SortCardModal = () => { ...@@ -51,7 +53,10 @@ export const SortCardModal = () => {
); );
const onFinish = () => { const onFinish = () => {
sendSortCardResponse(items.map((item) => item.response)); sendSortCardResponse(
container.conn,
items.map((item) => item.response),
);
rs(); rs();
}; };
const onDragEnd = (event: DragEndEvent) => { const onDragEnd = (event: DragEndEvent) => {
......
...@@ -3,6 +3,7 @@ import React from "react"; ...@@ -3,6 +3,7 @@ import React from "react";
import { proxy, useSnapshot } from "valtio"; import { proxy, useSnapshot } from "valtio";
import { sendSelectEffectYnResponse } from "@/api"; import { sendSelectEffectYnResponse } from "@/api";
import { getUIContainer } from "@/container/compat";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
...@@ -16,6 +17,7 @@ const defaultProps = { isOpen: false }; ...@@ -16,6 +17,7 @@ const defaultProps = { isOpen: false };
const localStore = proxy<YesNoModalProps>(defaultProps); const localStore = proxy<YesNoModalProps>(defaultProps);
export const YesNoModal: React.FC = () => { export const YesNoModal: React.FC = () => {
const container = getUIContainer();
const { isOpen, msg } = useSnapshot(localStore); const { isOpen, msg } = useSnapshot(localStore);
const hint = useSnapshot(matStore.hint); const hint = useSnapshot(matStore.hint);
...@@ -31,7 +33,7 @@ export const YesNoModal: React.FC = () => { ...@@ -31,7 +33,7 @@ export const YesNoModal: React.FC = () => {
<> <>
<Button <Button
onClick={() => { onClick={() => {
sendSelectEffectYnResponse(false); sendSelectEffectYnResponse(container.conn, false);
rs(); rs();
}} }}
> >
...@@ -40,7 +42,7 @@ export const YesNoModal: React.FC = () => { ...@@ -40,7 +42,7 @@ export const YesNoModal: React.FC = () => {
<Button <Button
type="primary" type="primary"
onClick={() => { onClick={() => {
sendSelectEffectYnResponse(true); sendSelectEffectYnResponse(container.conn, true);
rs(); rs();
}} }}
> >
......
...@@ -2,6 +2,8 @@ import classnames from "classnames"; ...@@ -2,6 +2,8 @@ import classnames from "classnames";
import { type INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio"; import { type INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio";
import { sendSelectPlaceResponse, ygopro } from "@/api"; import { sendSelectPlaceResponse, ygopro } from "@/api";
import { Container } from "@/container";
import { getUIContainer } from "@/container/compat";
import { import {
type BlockState, type BlockState,
cardStore, cardStore,
...@@ -46,6 +48,7 @@ const BgExtraRow: React.FC<{ ...@@ -46,6 +48,7 @@ const BgExtraRow: React.FC<{
meSnap: Snapshot<BlockState[]>; meSnap: Snapshot<BlockState[]>;
opSnap: Snapshot<BlockState[]>; opSnap: Snapshot<BlockState[]>;
}> = ({ meSnap, opSnap }) => { }> = ({ meSnap, opSnap }) => {
const container = getUIContainer();
return ( return (
<div className={classnames(styles.row)}> <div className={classnames(styles.row)}>
{Array.from({ length: 2 }).map((_, i) => ( {Array.from({ length: 2 }).map((_, i) => (
...@@ -53,8 +56,8 @@ const BgExtraRow: React.FC<{ ...@@ -53,8 +56,8 @@ const BgExtraRow: React.FC<{
key={i} key={i}
className={styles.extra} className={styles.extra}
onClick={() => { onClick={() => {
onBlockClick(meSnap[i].interactivity); onBlockClick(container, meSnap[i].interactivity);
onBlockClick(opSnap[1 - i].interactivity); onBlockClick(container, opSnap[1 - i].interactivity);
}} }}
disabled={meSnap[i].disabled || opSnap[1 - i].disabled} disabled={meSnap[i].disabled || opSnap[1 - i].disabled}
highlight={!!meSnap[i].interactivity || !!opSnap[1 - i].interactivity} highlight={!!meSnap[i].interactivity || !!opSnap[1 - i].interactivity}
...@@ -71,23 +74,27 @@ const BgRow: React.FC<{ ...@@ -71,23 +74,27 @@ const BgRow: React.FC<{
szone?: boolean; szone?: boolean;
opponent?: boolean; opponent?: boolean;
snap: Snapshot<BlockState[]>; snap: Snapshot<BlockState[]>;
}> = ({ szone = false, opponent = false, snap }) => ( }> = ({ szone = false, opponent = false, snap }) => {
<div className={classnames(styles.row, { [styles.opponent]: opponent })}> const container = getUIContainer();
{Array.from({ length: 5 }).map((_, i) => ( return (
<BgBlock <div className={classnames(styles.row, { [styles.opponent]: opponent })}>
key={i} {Array.from({ length: 5 }).map((_, i) => (
className={classnames({ [styles.szone]: szone })} <BgBlock
onClick={() => onBlockClick(snap[i].interactivity)} key={i}
disabled={snap[i].disabled} className={classnames({ [styles.szone]: szone })}
highlight={!!snap[i].interactivity} onClick={() => onBlockClick(container, snap[i].interactivity)}
chains={{ chains: snap[i].chainIndex }} disabled={snap[i].disabled}
/> highlight={!!snap[i].interactivity}
))} chains={{ chains: snap[i].chainIndex }}
</div> />
); ))}
</div>
);
};
const BgOtherBlocks: React.FC<{ op?: boolean }> = ({ op }) => { const BgOtherBlocks: React.FC<{ op?: boolean }> = ({ op }) => {
useSnapshot(cardStore); useSnapshot(cardStore);
const container = getUIContainer();
const meController = isMe(0) ? 0 : 1; const meController = isMe(0) ? 0 : 1;
const judgeGlowing = (zone: ygopro.CardZone) => const judgeGlowing = (zone: ygopro.CardZone) =>
!!cardStore !!cardStore
...@@ -134,7 +141,7 @@ const BgOtherBlocks: React.FC<{ op?: boolean }> = ({ op }) => { ...@@ -134,7 +141,7 @@ const BgOtherBlocks: React.FC<{ op?: boolean }> = ({ op }) => {
/> />
<BgBlock <BgBlock
className={styles.field} className={styles.field}
onClick={() => onBlockClick(field.interactivity)} onClick={() => onBlockClick(container, field.interactivity)}
disabled={field.disabled} disabled={field.disabled}
highlight={!!field.interactivity} highlight={!!field.interactivity}
chains={{ chains: field.chainIndex, op }} chains={{ chains: field.chainIndex, op }}
...@@ -171,9 +178,12 @@ export const Bg: React.FC = () => { ...@@ -171,9 +178,12 @@ export const Bg: React.FC = () => {
); );
}; };
const onBlockClick = (placeInteractivity: PlaceInteractivity) => { const onBlockClick = (
container: Container,
placeInteractivity: PlaceInteractivity,
) => {
if (placeInteractivity) { if (placeInteractivity) {
sendSelectPlaceResponse(placeInteractivity.response); sendSelectPlaceResponse(container.conn, placeInteractivity.response);
cardStore.inner.forEach((card) => (card.idleInteractivities = [])); cardStore.inner.forEach((card) => (card.idleInteractivities = []));
placeStore.clearAllInteractivity(); placeStore.clearAllInteractivity();
} }
......
...@@ -11,6 +11,8 @@ import { ...@@ -11,6 +11,8 @@ import {
sendSelectIdleCmdResponse, sendSelectIdleCmdResponse,
ygopro, ygopro,
} from "@/api"; } from "@/api";
import { Container } from "@/container";
import { getUIContainer } from "@/container/compat";
import { eventbus, Task } from "@/infra"; import { eventbus, Task } from "@/infra";
import { cardStore, CardType, Interactivity, InteractType } from "@/stores"; import { cardStore, CardType, Interactivity, InteractType } from "@/stores";
import { showCardModal as displayCardModal } from "@/ui/Duel/Message/CardModal"; import { showCardModal as displayCardModal } from "@/ui/Duel/Message/CardModal";
...@@ -40,6 +42,7 @@ import type { SpringApiProps } from "./springs/types"; ...@@ -40,6 +42,7 @@ import type { SpringApiProps } from "./springs/types";
const { HAND, GRAVE, REMOVED, EXTRA, MZONE, SZONE, TZONE } = ygopro.CardZone; const { HAND, GRAVE, REMOVED, EXTRA, MZONE, SZONE, TZONE } = ygopro.CardZone;
export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
const container = getUIContainer();
const card = cardStore.inner[idx]; const card = cardStore.inner[idx];
const snap = useSnapshot(card); const snap = useSnapshot(card);
...@@ -161,7 +164,10 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { ...@@ -161,7 +164,10 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
if (!isField) { if (!isField) {
// 单卡: 直接召唤/特殊召唤/... // 单卡: 直接召唤/特殊召唤/...
const card = cards[0]; const card = cards[0];
sendSelectIdleCmdResponse(getNonEffectResponse(action, card)); sendSelectIdleCmdResponse(
container.conn,
getNonEffectResponse(action, card),
);
clearAllIdleInteractivities(); clearAllIdleInteractivities();
} else { } else {
// 场地: 选择卡片 // 场地: 选择卡片
...@@ -174,7 +180,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { ...@@ -174,7 +180,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
})), })),
}); });
if (option.length > 0) { if (option.length > 0) {
sendSelectIdleCmdResponse(option[0].response!); sendSelectIdleCmdResponse(container.conn, option[0].response!);
clearAllIdleInteractivities(); clearAllIdleInteractivities();
} }
} }
...@@ -223,6 +229,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { ...@@ -223,6 +229,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
} }
// 选择发动哪个效果 // 选择发动哪个效果
handleEffectActivation( handleEffectActivation(
container,
tmpCard.idleInteractivities tmpCard.idleInteractivities
.filter( .filter(
({ interactType }) => interactType === InteractType.ACTIVATE, ({ interactType }) => interactType === InteractType.ACTIVATE,
...@@ -246,7 +253,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { ...@@ -246,7 +253,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
const selectInfo = card.selectInfo; const selectInfo = card.selectInfo;
if (selectInfo.selectable || selectInfo.selected) { if (selectInfo.selectable || selectInfo.selected) {
if (selectInfo.response !== undefined) { if (selectInfo.response !== undefined) {
sendSelectMultiResponse([selectInfo.response]); sendSelectMultiResponse(container.conn, [selectInfo.response]);
clearSelectInfo(); clearSelectInfo();
} else { } else {
console.error("card is selectable but the response is undefined!"); console.error("card is selectable but the response is undefined!");
...@@ -358,13 +365,14 @@ type DropdownItem = NonNullable<MenuProps["items"]>[number] & { ...@@ -358,13 +365,14 @@ type DropdownItem = NonNullable<MenuProps["items"]>[number] & {
}; };
const handleEffectActivation = ( const handleEffectActivation = (
container: Container,
effectInteractivies: Interactivy[], effectInteractivies: Interactivy[],
meta?: CardMeta, meta?: CardMeta,
) => { ) => {
if (!effectInteractivies.length) return; if (!effectInteractivies.length) return;
else if (effectInteractivies.length === 1) { else if (effectInteractivies.length === 1) {
// 如果只有一个效果,点击直接触发 // 如果只有一个效果,点击直接触发
sendSelectIdleCmdResponse(effectInteractivies[0].response); sendSelectIdleCmdResponse(container.conn, effectInteractivies[0].response);
} else { } else {
// optionsModal // optionsModal
const options = effectInteractivies.map((effect) => { const options = effectInteractivies.map((effect) => {
......
...@@ -35,6 +35,8 @@ import styles from "./index.module.scss"; ...@@ -35,6 +35,8 @@ import styles from "./index.module.scss";
import PhaseType = ygopro.StocGameMessage.MsgNewPhase.PhaseType; import PhaseType = ygopro.StocGameMessage.MsgNewPhase.PhaseType;
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getUIContainer } from "@/container/compat";
import { clearAllIdleInteractivities, clearSelectInfo } from "../../utils"; import { clearAllIdleInteractivities, clearSelectInfo } from "../../utils";
import { openChatBox } from "../ChatBox"; import { openChatBox } from "../ChatBox";
...@@ -217,6 +219,7 @@ const initialPhaseBind: [ ...@@ -217,6 +219,7 @@ const initialPhaseBind: [
]; ];
export const Menu = () => { export const Menu = () => {
const container = getUIContainer();
const { t: i18n } = useTranslation("Menu"); const { t: i18n } = useTranslation("Menu");
const { const {
currentPlayer, currentPlayer,
...@@ -269,8 +272,9 @@ export const Menu = () => { ...@@ -269,8 +272,9 @@ export const Menu = () => {
label, label,
disabled: disabled, disabled: disabled,
onClick: () => { onClick: () => {
if (response === 2) sendSelectIdleCmdResponse(response); if (response === 2)
else sendSelectBattleCmdResponse(response); sendSelectIdleCmdResponse(container.conn, response);
else sendSelectBattleCmdResponse(container.conn, response);
clearAllIdleInteractivities(); clearAllIdleInteractivities();
}, },
icon: disabled ? <CheckOutlined /> : <ArrowRightOutlined />, icon: disabled ? <CheckOutlined /> : <ArrowRightOutlined />,
...@@ -302,7 +306,9 @@ export const Menu = () => { ...@@ -302,7 +306,9 @@ export const Menu = () => {
{ {
label: i18n("Confirm"), label: i18n("Confirm"),
danger: true, danger: true,
onClick: sendSurrender, onClick: () => {
sendSurrender(container.conn);
},
}, },
].map((item, i) => ({ key: i, ...item })); ].map((item, i) => ({ key: i, ...item }));
...@@ -417,10 +423,11 @@ const ChainIcon: React.FC<{ chainSetting: ChainSetting }> = ({ ...@@ -417,10 +423,11 @@ const ChainIcon: React.FC<{ chainSetting: ChainSetting }> = ({
}; };
const SelectManager: React.FC = () => { const SelectManager: React.FC = () => {
const container = getUIContainer();
const { t: i18n } = useTranslation("Menu"); const { t: i18n } = useTranslation("Menu");
const { finishable, cancelable } = useSnapshot(matStore.selectUnselectInfo); const { finishable, cancelable } = useSnapshot(matStore.selectUnselectInfo);
const onFinishOrCancel = () => { const onFinishOrCancel = () => {
sendSelectSingleResponse(FINISH_CANCEL_RESPONSE); sendSelectSingleResponse(container.conn, FINISH_CANCEL_RESPONSE);
clearSelectInfo(); clearSelectInfo();
}; };
return ( return (
......
...@@ -101,7 +101,7 @@ export const WatchContent: React.FC = () => { ...@@ -101,7 +101,7 @@ export const WatchContent: React.FC = () => {
<Input <Input
className={styles.input} className={styles.input}
placeholder={i18n("SearchRoomByPlayerUsername")} placeholder={i18n("SearchRoomByPlayerUsername")}
bordered={false} variant="borderless"
suffix={<Button type="text" icon={<SearchOutlined />} />} suffix={<Button type="text" icon={<SearchOutlined />} />}
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
......
...@@ -2,8 +2,10 @@ import rustInit from "rust-src"; ...@@ -2,8 +2,10 @@ import rustInit from "rust-src";
import { initStrings, initSuperPrerelease } from "@/api"; import { initStrings, initSuperPrerelease } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import socketMiddleWare, { socketCmd } from "@/middleware/socket"; import { getUIContainer, initUIContainer } from "@/container/compat";
import { initReplaySocket, initSocket } from "@/middleware/socket";
import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite"; import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite";
import { pollSocketLooper } from "@/service/executor";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
...@@ -38,20 +40,25 @@ export const connectSrvpro = async (params: { ...@@ -38,20 +40,25 @@ export const connectSrvpro = async (params: {
await initSuperPrerelease(); await initSuperPrerelease();
if (params.replay && params.replayData) { if (params.replay && params.replayData) {
// 连接回放websocket服务 // connect to replay Server
socketMiddleWare({ const conn = initReplaySocket({
cmd: socketCmd.CONNECT, url: NeosConfig.replayUrl,
isReplay: true, data: params.replayData,
replayInfo: {
Url: NeosConfig.replayUrl,
data: params.replayData,
},
}); });
// initialize the UI Container
initUIContainer(conn);
// execute the event looper
pollSocketLooper(getUIContainer(), conn);
} else { } else {
// 通过socket中间件向ygopro服务端请求建立长连接 // connect to the ygopro Server
socketMiddleWare({ const conn = initSocket(params);
cmd: socketCmd.CONNECT,
initInfo: params, // initialize the UI Contaner
}); initUIContainer(conn);
// execute the event looper
pollSocketLooper(getUIContainer(), conn);
} }
}; };
...@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react"; ...@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { sendChat } from "@/api"; import { sendChat } from "@/api";
import { getUIContainer } from "@/container/compat";
import { chatStore, isMe, roomStore } from "@/stores"; import { chatStore, isMe, roomStore } from "@/stores";
interface ChatItem { interface ChatItem {
...@@ -14,6 +15,7 @@ interface ChatItem { ...@@ -14,6 +15,7 @@ interface ChatItem {
} }
export const useChat = (isDuel: boolean = false) => { export const useChat = (isDuel: boolean = false) => {
const container = getUIContainer();
const [chatList, setChatList] = useState<ChatItem[]>([]); const [chatList, setChatList] = useState<ChatItem[]>([]);
const [input, setInput] = useState<string | undefined>(undefined); const [input, setInput] = useState<string | undefined>(undefined);
const chat = useSnapshot(chatStore); const chat = useSnapshot(chatStore);
...@@ -31,7 +33,7 @@ export const useChat = (isDuel: boolean = false) => { ...@@ -31,7 +33,7 @@ export const useChat = (isDuel: boolean = false) => {
/** 发信息 */ /** 发信息 */
const onSend = () => { const onSend = () => {
if (input !== undefined) { if (input !== undefined) {
sendChat(input); sendChat(container.conn, input);
setInput(""); setInput("");
} }
}; };
......
...@@ -3,11 +3,13 @@ import React from "react"; ...@@ -3,11 +3,13 @@ import React from "react";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { sendTpResult } from "@/api"; import { sendTpResult } from "@/api";
import { getUIContainer } from "@/container/compat";
import { SideStage, sideStore } from "@/stores"; import { SideStage, sideStore } from "@/stores";
import styles from "./TpModal.module.scss"; import styles from "./TpModal.module.scss";
export const TpModal: React.FC = () => { export const TpModal: React.FC = () => {
const container = getUIContainer();
const { stage } = useSnapshot(sideStore); const { stage } = useSnapshot(sideStore);
return ( return (
...@@ -20,7 +22,7 @@ export const TpModal: React.FC = () => { ...@@ -20,7 +22,7 @@ export const TpModal: React.FC = () => {
<div className={styles.container}> <div className={styles.container}>
<Button <Button
onClick={() => { onClick={() => {
sendTpResult(true); sendTpResult(container.conn, true);
sideStore.stage = SideStage.TP_SELECTED; sideStore.stage = SideStage.TP_SELECTED;
}} }}
> >
...@@ -28,7 +30,7 @@ export const TpModal: React.FC = () => { ...@@ -28,7 +30,7 @@ export const TpModal: React.FC = () => {
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
sendTpResult(false); sendTpResult(container.conn, false);
sideStore.stage = SideStage.TP_SELECTED; sideStore.stage = SideStage.TP_SELECTED;
}} }}
> >
......
...@@ -8,6 +8,7 @@ import { useSnapshot } from "valtio"; ...@@ -8,6 +8,7 @@ import { useSnapshot } from "valtio";
import { CardMeta, fetchCard, sendUpdateDeck } from "@/api"; import { CardMeta, fetchCard, sendUpdateDeck } from "@/api";
import { isExtraDeckCard } from "@/common"; import { isExtraDeckCard } from "@/common";
import { getUIContainer } from "@/container/compat";
import { AudioActionType, changeScene } from "@/infra/audio"; import { AudioActionType, changeScene } from "@/infra/audio";
import { IDeck, roomStore, SideStage, sideStore } from "@/stores"; import { IDeck, roomStore, SideStage, sideStore } from "@/stores";
...@@ -24,6 +25,7 @@ export const loader: LoaderFunction = async () => { ...@@ -24,6 +25,7 @@ export const loader: LoaderFunction = async () => {
}; };
export const Component: React.FC = () => { export const Component: React.FC = () => {
const container = getUIContainer();
const { message } = App.useApp(); const { message } = App.useApp();
const initialDeck = sideStore.getSideDeck(); const initialDeck = sideStore.getSideDeck();
const { stage } = useSnapshot(sideStore); const { stage } = useSnapshot(sideStore);
...@@ -65,7 +67,7 @@ export const Component: React.FC = () => { ...@@ -65,7 +67,7 @@ export const Component: React.FC = () => {
message.info("重置成功"); message.info("重置成功");
}; };
const onSummit = () => { const onSummit = () => {
sendUpdateDeck(deck); sendUpdateDeck(container.conn, deck);
sideStore.setSideDeck(deck); sideStore.setSideDeck(deck);
}; };
......
...@@ -11,7 +11,6 @@ import { ...@@ -11,7 +11,6 @@ import {
sendUpdateDeck, sendUpdateDeck,
ygopro, ygopro,
} from "@/api"; } from "@/api";
import socketMiddleWare, { socketCmd } from "@/middleware/socket";
import PlayerState = ygopro.StocHsPlayerChange.State; import PlayerState = ygopro.StocHsPlayerChange.State;
import SelfType = ygopro.StocTypeChange.SelfType; import SelfType = ygopro.StocTypeChange.SelfType;
import { App, Avatar, Button, Skeleton, Space } from "antd"; import { App, Avatar, Button, Skeleton, Space } from "antd";
...@@ -22,7 +21,9 @@ import { LoaderFunction, useNavigate } from "react-router-dom"; ...@@ -22,7 +21,9 @@ import { LoaderFunction, useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { getUIContainer } from "@/container/compat";
import { AudioActionType, changeScene } from "@/infra/audio"; import { AudioActionType, changeScene } from "@/infra/audio";
import { closeSocket } from "@/middleware/socket";
import { import {
accountStore, accountStore,
deckStore, deckStore,
...@@ -49,6 +50,7 @@ export const loader: LoaderFunction = async () => { ...@@ -49,6 +50,7 @@ export const loader: LoaderFunction = async () => {
}; };
export const Component: React.FC = () => { export const Component: React.FC = () => {
const container = getUIContainer();
const { t: i18n } = useTranslation("WaitRoom"); const { t: i18n } = useTranslation("WaitRoom");
const { message } = App.useApp(); const { message } = App.useApp();
const { user } = useSnapshot(accountStore); const { user } = useSnapshot(accountStore);
...@@ -64,7 +66,7 @@ export const Component: React.FC = () => { ...@@ -64,7 +66,7 @@ export const Component: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const updateDeck = (deck: IDeck) => { const updateDeck = (deck: IDeck) => {
sendUpdateDeck(deck); sendUpdateDeck(container.conn, deck);
matStore.mainDeck = deck.main; matStore.mainDeck = deck.main;
// 设置side里面的卡组 // 设置side里面的卡组
sideStore.setSideDeck(deck); sideStore.setSideDeck(deck);
...@@ -73,7 +75,7 @@ export const Component: React.FC = () => { ...@@ -73,7 +75,7 @@ export const Component: React.FC = () => {
const onDeckSelected = (deckName: string) => { const onDeckSelected = (deckName: string) => {
const newDeck = deckStore.get(deckName); const newDeck = deckStore.get(deckName);
if (newDeck) { if (newDeck) {
sendHsNotReady(); sendHsNotReady(container.conn);
updateDeck(newDeck); updateDeck(newDeck);
setDeck(newDeck); setDeck(newDeck);
} else { } else {
...@@ -85,12 +87,12 @@ export const Component: React.FC = () => { ...@@ -85,12 +87,12 @@ export const Component: React.FC = () => {
if (me?.state === PlayerState.NO_READY) { if (me?.state === PlayerState.NO_READY) {
if (deck) { if (deck) {
updateDeck(deck); updateDeck(deck);
sendHsReady(); sendHsReady(container.conn);
} else { } else {
message.error("请先选择卡组"); message.error("请先选择卡组");
} }
} else { } else {
sendHsNotReady(); sendHsNotReady(container.conn);
} }
}; };
...@@ -98,7 +100,7 @@ export const Component: React.FC = () => { ...@@ -98,7 +100,7 @@ export const Component: React.FC = () => {
// 组件初始化时发一次更新卡组的包 // 组件初始化时发一次更新卡组的包
// //
// 否则娱乐匹配准备会有问题(原因不明) // 否则娱乐匹配准备会有问题(原因不明)
if (deck) sendUpdateDeck(deck); if (deck) sendUpdateDeck(container.conn, deck);
}, []); }, []);
useEffect(() => { useEffect(() => {
if (room.stage === RoomStage.DUEL_START) { if (room.stage === RoomStage.DUEL_START) {
...@@ -186,11 +188,11 @@ export const Component: React.FC = () => { ...@@ -186,11 +188,11 @@ export const Component: React.FC = () => {
</div> </div>
<ActionButton <ActionButton
onMoraSelect={(mora) => { onMoraSelect={(mora) => {
sendHandResult(mora); sendHandResult(container.conn, mora);
roomStore.stage = RoomStage.HAND_SELECTED; roomStore.stage = RoomStage.HAND_SELECTED;
}} }}
onTpSelect={(tp) => { onTpSelect={(tp) => {
sendTpResult(tp === Tp.First); sendTpResult(container.conn, tp === Tp.First);
roomStore.stage = RoomStage.TP_SELECTED; roomStore.stage = RoomStage.TP_SELECTED;
}} }}
/> />
...@@ -265,6 +267,7 @@ const MoraAvatar: React.FC<{ mora?: Mora }> = ({ mora }) => ( ...@@ -265,6 +267,7 @@ const MoraAvatar: React.FC<{ mora?: Mora }> = ({ mora }) => (
const Controller: React.FC<{ onDeckChange: (deckName: string) => void }> = ({ const Controller: React.FC<{ onDeckChange: (deckName: string) => void }> = ({
onDeckChange, onDeckChange,
}) => { }) => {
const container = getUIContainer();
const { t: i18n } = useTranslation("WaitRoom"); const { t: i18n } = useTranslation("WaitRoom");
const snapDeck = useSnapshot(deckStore); const snapDeck = useSnapshot(deckStore);
const snapRoom = useSnapshot(roomStore); const snapRoom = useSnapshot(roomStore);
...@@ -289,9 +292,9 @@ const Controller: React.FC<{ onDeckChange: (deckName: string) => void }> = ({ ...@@ -289,9 +292,9 @@ const Controller: React.FC<{ onDeckChange: (deckName: string) => void }> = ({
icon={<IconFont type="icon-record" size={18} />} icon={<IconFont type="icon-record" size={18} />}
onClick={() => { onClick={() => {
if (snapRoom.selfType !== SelfType.OBSERVER) { if (snapRoom.selfType !== SelfType.OBSERVER) {
sendHsToObserver(); sendHsToObserver(container.conn);
} else { } else {
sendHsToDuelList(); sendHsToDuelList(container.conn);
} }
}} }}
> >
...@@ -328,8 +331,8 @@ const SideButtons: React.FC<{ ...@@ -328,8 +331,8 @@ const SideButtons: React.FC<{
</span> </span>
} }
onClick={() => { onClick={() => {
// 断开websocket🔗 // 断开websocket🔗
socketMiddleWare({ cmd: socketCmd.DISCONNECT }); closeSocket(getUIContainer().conn);
// 重置stores // 重置stores
resetUniverse(); resetUniverse();
// 返回上一个路由 // 返回上一个路由
...@@ -357,6 +360,7 @@ const ActionButton: React.FC<{ ...@@ -357,6 +360,7 @@ const ActionButton: React.FC<{
onMoraSelect: (mora: Mora) => void; onMoraSelect: (mora: Mora) => void;
onTpSelect: (tp: Tp) => void; onTpSelect: (tp: Tp) => void;
}> = ({ onMoraSelect, onTpSelect }) => { }> = ({ onMoraSelect, onTpSelect }) => {
const container = getUIContainer();
const room = useSnapshot(roomStore); const room = useSnapshot(roomStore);
const { stage, isHost } = room; const { stage, isHost } = room;
const { t: i18n } = useTranslation("WaitRoom"); const { t: i18n } = useTranslation("WaitRoom");
...@@ -373,7 +377,7 @@ const ActionButton: React.FC<{ ...@@ -373,7 +377,7 @@ const ActionButton: React.FC<{
room.getOpPlayer()?.state !== PlayerState.READY)) room.getOpPlayer()?.state !== PlayerState.READY))
} }
onClick={() => { onClick={() => {
sendHsStart(); sendHsStart(container.conn);
}} }}
> >
{stage === RoomStage.WAITING ? ( {stage === RoomStage.WAITING ? (
......
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