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