Commit 49b4f00e authored by Chunchi Che's avatar Chunchi Che

Merge branch 'feat/mycard_match' into 'main'

支持萌卡匹配(目前仅开放娱乐匹配功能)

See merge request !272
parents 6d78b4fb 332f394c
Pipeline #23209 passed with stages
in 11 minutes and 53 seconds
Subproject commit 12f48aa7901f81f4b356b968bc7a8281a1a2d848
Subproject commit a02a8caa38767bb06e1a9d5be97773af5a0fc4b2
This diff is collapsed.
import { ygopro } from "../idl/ocgcore";
import { YgoProPacket } from "./packet";
import {
STOC_CHANGE_SIDE,
STOC_CHAT,
STOC_DECK_COUNT,
STOC_DUEL_START,
......@@ -15,7 +16,9 @@ import {
STOC_SELECT_TP,
STOC_TIME_LIMIT,
STOC_TYPE_CHANGE,
STOC_WAITING_SIDE,
} from "./protoDecl";
import StocChangeSide from "./stoc/stocChangeSide";
import StocChat from "./stoc/stocChat";
import StocDeckCount from "./stoc/stocDeckCount";
import StocDuelStart from "./stoc/stocDuelStart";
......@@ -30,6 +33,7 @@ import StocSelectHand from "./stoc/stocSelectHand";
import StocSelectTp from "./stoc/stocSelectTp";
import StocTimeLimit from "./stoc/stocTimeLimit";
import StocTypeChange from "./stoc/stocTypeChange";
import StocWaitingSide from "./stoc/stocWaitingSide";
/*
* 将[`ygoProPacket`]对象转换成[`ygopro.YgoStocMsg`]对象
......@@ -97,6 +101,14 @@ export function adaptStoc(packet: YgoProPacket): ygopro.YgoStocMsg {
pb = new StocErrorMsg(packet).upcast();
break;
}
case STOC_CHANGE_SIDE: {
pb = new StocChangeSide(packet).upcast();
break;
}
case STOC_WAITING_SIDE: {
pb = new StocWaitingSide(packet).upcast();
break;
}
default: {
break;
}
......
......@@ -31,6 +31,8 @@ export const STOC_DUEL_START = 21;
export const STOC_GAME_MSG = 1;
export const STOC_TIME_LIMIT = 24;
export const STOC_ERROR_MSG = 2;
export const STOC_CHANGE_SIDE = 7;
export const STOC_WAITING_SIDE = 8;
export const MSG_START = 4;
export const MSG_DRAW = 90;
......
import { ygopro } from "../../idl/ocgcore";
import { StocAdapter, YgoProPacket } from "../packet";
export default class ChangeSide implements StocAdapter {
packet: YgoProPacket;
constructor(packet: YgoProPacket) {
this.packet = packet;
}
upcast(): ygopro.YgoStocMsg {
return new ygopro.YgoStocMsg({
stoc_change_side: new ygopro.StocChangeSide({}),
});
}
}
import { ygopro } from "../../idl/ocgcore";
import { StocAdapter, YgoProPacket } from "../packet";
export default class WaitingSide implements StocAdapter {
packet: YgoProPacket;
constructor(packet: YgoProPacket) {
this.packet = packet;
}
upcast(): ygopro.YgoStocMsg {
return new ygopro.YgoStocMsg({
stoc_waiting_side: new ygopro.StocWaitingSide({}),
});
}
}
......@@ -88,13 +88,9 @@ const ReplayIgnoreMsg = [
"announce",
];
let animation: Promise<unknown> = new Promise<void>((rs) => rs());
export default async function handleGameMsg(pb: ygopro.YgoStocMsg) {
animation = animation.then(() => _handleGameMsg(pb));
}
async function _handleGameMsg(pb: ygopro.YgoStocMsg) {
export default async function handleGameMsg(
pb: ygopro.YgoStocMsg,
): Promise<void> {
const msg = pb.stoc_game_msg;
if (ActiveList.includes(msg.gameMsg)) {
......
......@@ -6,7 +6,15 @@ import PlayerType = ygopro.StocGameMessage.MsgStart.PlayerType;
import { fetchCard, ygopro } from "@/api";
import { useConfig } from "@/config";
import { sleep } from "@/infra";
import { cardStore, CardType, matStore, RoomStage, roomStore } from "@/stores";
import {
cardStore,
CardType,
matStore,
RoomStage,
roomStore,
SideStage,
sideStore,
} from "@/stores";
import { replayStart } from "@/ui/Match/ReplayModal";
const TOKEN_SIZE = 13; // 每人场上最多就只可能有13个token
......@@ -19,9 +27,14 @@ export default async (start: ygopro.StocGameMessage.MsgStart) => {
? 1
: 0;
// 通知房间页面决斗开始
// 这行在该函数中的位置不能随便放,否则可能会block住
roomStore.stage = RoomStage.DUEL_START;
if (sideStore.stage !== SideStage.NONE) {
// 更新Side状态
sideStore.stage = SideStage.DUEL_START;
} else {
// 通知房间页面决斗开始
// 这行在该函数中的位置不能随便放,否则可能会block住
roomStore.stage = RoomStage.DUEL_START;
}
matStore.initInfo.set(0, {
life: start.life1,
......
import { ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { RoomStage, roomStore } from "@/stores";
import { RoomStage, roomStore, SideStage, sideStore } from "@/stores";
export default function handleSelectTp(_: ygopro.YgoStocMsg) {
roomStore.stage = RoomStage.TP_SELECTING;
eventbus.emit(Task.Tp);
if (sideStore.stage !== SideStage.NONE) {
sideStore.stage = SideStage.TP_SELECTING;
} else {
roomStore.stage = RoomStage.TP_SELECTING;
eventbus.emit(Task.Tp);
}
}
......@@ -20,13 +20,23 @@ import handleHsPlayerEnter from "./room/hsPlayerEnter";
import handleHsWatchChange from "./room/hsWatchChange";
import handleJoinGame from "./room/joinGame";
import handleTypeChange from "./room/typeChange";
import { handleChangeSide } from "./side/changeSide";
import { handleWaitingSide } from "./side/waitingSide";
/*
* 先将从长连接中读取到的二进制数据通过Adapter转成protobuf结构体,
* 然后再分发到各个处理函数中去处理。
*
* */
let animation: Promise<void> = Promise.resolve();
export default async function handleSocketMessage(e: MessageEvent) {
// 确保按序执行
animation = animation.then(() => _handle(e));
}
async function _handle(e: MessageEvent) {
const packet = YgoProPacket.deserialize(e.data);
const pb = adaptStoc(packet);
......@@ -93,6 +103,14 @@ export default async function handleSocketMessage(e: MessageEvent) {
await handleErrorMsg(pb.stoc_error_msg);
break;
}
case "stoc_change_side": {
handleChangeSide(pb.stoc_change_side);
break;
}
case "stoc_waiting_side": {
handleWaitingSide(pb.stoc_waiting_side);
break;
}
default: {
console.log(packet);
......
......@@ -23,6 +23,6 @@ export default function handleSocketOpen(
ws.binaryType = "arraybuffer";
sendPlayerInfo(ws, player);
sendJoinGame(ws, NeosConfig.version, passWd); // todo: version use config
sendJoinGame(ws, NeosConfig.version, passWd);
}
}
import { ygopro } from "@/api";
import { RoomStage, roomStore } from "@/stores";
import { RoomStage, roomStore, SideStage, sideStore } from "@/stores";
export default function handleDuelStart(_pb: ygopro.YgoStocMsg) {
roomStore.stage = RoomStage.MORA;
if (sideStore.stage !== SideStage.NONE) {
sideStore.stage = SideStage.SIDE_CHANGED;
} else {
roomStore.stage = RoomStage.MORA;
}
}
import { ygopro } from "@/api";
import { SideStage, sideStore } from "@/stores";
export function handleChangeSide(_: ygopro.StocChangeSide) {
sideStore.stage = SideStage.SIDE_CHANGING;
}
import { ygopro } from "@/api";
import { SideStage, sideStore } from "@/stores";
export function handleWaitingSide(_: ygopro.StocWaitingSide) {
sideStore.stage = SideStage.WAITING;
}
......@@ -7,6 +7,7 @@ export * from "./matStore";
export * from "./placeStore";
export * from "./replayStore";
export * from "./roomStore";
export * from "./sideStore";
import { devtools } from "valtio/utils";
......@@ -21,6 +22,7 @@ import { matStore } from "./matStore";
import { placeStore } from "./placeStore";
import { replayStore } from "./replayStore";
import { roomStore } from "./roomStore";
import { sideStore } from "./sideStore";
const { DEV } = useEnv();
......@@ -33,6 +35,7 @@ devtools(accountStore, { name: "account", enabled: DEV });
devtools(roomStore, { name: "room", enabled: DEV });
devtools(deckStore, { name: "deck", enabled: DEV });
devtools(initStore, { name: "init", enabled: DEV });
devtools(sideStore, { name: "side", enabled: DEV });
// 重置`Store`
export const resetUniverse = () => {
......@@ -43,4 +46,12 @@ export const resetUniverse = () => {
placeStore.reset();
replayStore.reset();
roomStore.reset();
sideStore.reset();
};
// 重置决斗相关的`Store`
export const resetDuel = () => {
cardStore.reset();
matStore.reset();
placeStore.reset();
};
import { proxy } from "valtio";
import { IDeck } from "./deckStore";
import { type NeosStore } from "./shared";
export enum SideStage {
NONE = 0, // 没有进入SIDE阶段
SIDE_CHANGING = 1, // 正在更换副卡组
SIDE_CHANGED = 2, // 副卡组更换完毕
TP_SELECTING = 5, // 正在选边
TP_SELECTED = 6, // 选边完成
DUEL_START = 7, // 决斗开始
WAITING = 8, // 观战者等待双方玩家
}
const emptyDeck: IDeck = { deckName: "", main: [], extra: [], side: [] };
class SideStore implements NeosStore {
stage: SideStage = SideStage.NONE;
deck: IDeck = emptyDeck;
reset(): void {
this.stage = SideStage.NONE;
this.deck = emptyDeck;
}
}
export const sideStore = proxy(new SideStore());
......@@ -147,7 +147,7 @@ export const Component: React.FC = () => {
Component.displayName = "Build";
/** 正在编辑的卡组 */
const DeckEditor: React.FC<{
export const DeckEditor: React.FC<{
deck: IDeck;
onClear: () => void;
onReset: () => void;
......
import React from "react";
import React, { useEffect } from "react";
import { resetUniverse } from "@/stores";
import { ChangeSideModal, TpModal } from "../Side";
import {
Alert,
AnnounceModal,
......@@ -18,6 +21,13 @@ import {
import { LifeBar, Mat, Menu, Underlying } from "./PlayMat";
export const Component: React.FC = () => {
useEffect(() => {
return () => {
// Duel组件卸载的时候初始化一些store
resetUniverse();
};
}, []);
return (
<>
<Underlying />
......@@ -37,6 +47,8 @@ export const Component: React.FC = () => {
<AnnounceModal />
<SimpleSelectCardsModal />
<EndModal />
<ChangeSideModal />
<TpModal />
</>
);
};
......
import React, { CSSProperties } from "react";
import { useNavigate } from "react-router-dom";
import { proxy, useSnapshot } from "valtio";
import { fetchStrings, Region } from "@/api";
import { matStore, replayStore, resetUniverse } from "@/stores";
import { matStore, replayStore, resetDuel } from "@/stores";
import { NeosModal } from "../NeosModal";
import styles from "./index.module.scss";
......@@ -25,11 +24,10 @@ export const EndModal: React.FC = () => {
const { isOpen, isWin, reason } = useSnapshot(localStore);
const { isReplay } = useSnapshot(matStore);
const navigate = useNavigate();
const onReturn = () => {
resetUniverse();
resetDuel();
rs();
navigate("/match");
// TODO: 这里暂时不自动跳转,决斗结束后让玩家自己手动选择回到主页
};
return (
......
import { useSnapshot } from "valtio";
import { cardStore } from "@/stores";
import { Bg } from "../Bg";
......@@ -21,7 +23,8 @@ export const Mat: React.FC = () => {
};
const Cards: React.FC = () => {
const length = cardStore.inner.length;
const { inner } = useSnapshot(cardStore);
const length = inner.length;
return (
<>
{Array.from({ length }).map((_, i) => (
......
import { Button, Input, Modal } from "antd";
import { App, Button, Input, Modal } from "antd";
import React, { ChangeEvent, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { proxy, useSnapshot } from "valtio";
......@@ -7,7 +7,7 @@ import { useConfig } from "@/config";
import { accountStore, roomStore } from "@/stores";
import styles from "./MatchModal.module.scss";
import { init } from "./util";
import { connectSrvpro } from "./util";
const NeosConfig = useConfig();
const serverConfig = NeosConfig.servers;
......@@ -27,6 +27,7 @@ const defaultProps: Props = {
export const matchStore = proxy<Props>(defaultProps);
export const MatchModal: React.FC = ({}) => {
const { message } = App.useApp();
const { open } = useSnapshot(matchStore);
const { user } = useSnapshot(accountStore);
const { joined, errorMsg } = useSnapshot(roomStore);
......@@ -50,7 +51,7 @@ export const MatchModal: React.FC = ({}) => {
const handleSubmit = async () => {
setConfirmLoading(true);
await init({ player, ip: server, passWd: passwd });
await connectSrvpro({ player, ip: server, passWd: passwd });
};
useEffect(() => {
......@@ -70,7 +71,7 @@ export const MatchModal: React.FC = ({}) => {
useEffect(() => {
// 出现错误
if (errorMsg !== undefined && errorMsg !== "") {
alert(errorMsg);
message.error(errorMsg);
setConfirmLoading(false);
roomStore.errorMsg = undefined;
}
......
......@@ -6,7 +6,7 @@ import { proxy, useSnapshot } from "valtio";
import { matStore } from "@/stores";
import { Uploader } from "../Shared";
import { init } from "./util";
import { connectSrvpro } from "./util";
const localStore = proxy({
open: false,
......@@ -45,7 +45,7 @@ export const ReplayModal: React.FC = () => {
// FIXME: 这样写应该不对,有空来修
window.myExtraDeckCodes = [];
await init({
await connectSrvpro({
ip: "",
player: "",
passWd: "",
......
import {
EditOutlined,
LoadingOutlined,
PlayCircleOutlined,
SettingFilled,
} from "@ant-design/icons";
import { Button, Space } from "antd";
import { App, Button, Space } from "antd";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio";
import { match } from "@/api";
import { useConfig } from "@/config";
import { accountStore, deckStore, IDeck, roomStore } from "@/stores";
import { Background, IconFont, Select } from "@/ui/Shared";
......@@ -15,24 +17,69 @@ import { Background, IconFont, Select } from "@/ui/Shared";
import styles from "./index.module.scss";
import { MatchModal, matchStore } from "./MatchModal";
import { ReplayModal, replayOpen } from "./ReplayModal";
import { init } from "./util";
import { connectSrvpro } from "./util";
const NeosConfig = useConfig();
export const Component: React.FC = () => {
const { message } = App.useApp();
const serverList = NeosConfig.servers;
const [server, setServer] = useState(
`${serverList[0].ip}:${serverList[0].port}`,
);
const { decks } = useSnapshot(deckStore);
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0])));
const { user } = useSnapshot(accountStore);
const user = accountStore.user;
const { joined } = useSnapshot(roomStore);
const [singleLoading, setSingleLoading] = useState(false); // 单人模式的loading状态
const [matchLoading, setMatchLoading] = useState(false);
const navigate = useNavigate();
// 竞技匹配
const onCompetitiveMatch = () => message.error("暂未开放,敬请期待");
// 娱乐匹配
const onEntertainMatch = async () => {
if (!user) {
message.error("请先登录萌卡账号");
} else {
setMatchLoading(true);
const matchInfo = await match(user.username, user.external_id);
if (matchInfo) {
await connectSrvpro({
ip: matchInfo.address + ":" + (matchInfo.port + 1),
player: user.username,
passWd: matchInfo.password,
});
} else {
message.error("匹配失败T_T");
}
}
};
// 单人模式
const onAIMatch = async () => {
setSingleLoading(true);
// 初始化,然后等待后端通知成功加入房间后跳转页面
await connectSrvpro({
ip: server,
player: user?.name ?? "Guest",
passWd: "AI",
});
};
// 自定义房间
const onCustomRoom = () => (matchStore.open = true);
// 观战列表
const onWatchList = () => message.error("开发中,敬请期待");
useEffect(() => {
// 人机对战跳转
if (joined) {
setSingleLoading(false);
setMatchLoading(false);
navigate(`/waitroom`);
}
}, [joined]);
......@@ -68,7 +115,7 @@ export const Component: React.FC = () => {
if (item) {
setDeck(item);
} else {
alert(`Deck ${value} not found`);
message.error(`Deck ${value} not found`);
}
}}
options={decks.map((deck) => ({
......@@ -90,34 +137,37 @@ export const Component: React.FC = () => {
title="竞技匹配"
desc="与天梯其他数万名玩家激战,追求胜利登顶最强。每月最后一天晚上10点结算成绩,获取奖励与公布排名。"
icon={<IconFont type="icon-battle" size={32} />}
onClick={() => alert("开发中,敬请期待")}
onClick={onCompetitiveMatch}
/>
<Mode
title="娱乐匹配"
desc="过去一周竞技匹配使用数最靠前的20个卡组被禁止使用。将胜负暂且搁置,尽情享受决斗的乐趣。"
icon={<IconFont type="icon-coffee" size={28} />}
onClick={() => alert("开发中,敬请期待")}
icon={
matchLoading ? (
<LoadingOutlined />
) : (
<IconFont type="icon-coffee" size={28} />
)
}
onClick={onEntertainMatch}
/>
<Mode
title="单人模式"
desc="开启与AI的决斗,验证自己的卡组,或者只是打发时间。"
icon={<IconFont type="icon-chip" size={26} />}
onClick={async () => {
// TODO: 有时间可以做一个Loading
// 初始化,然后等待后端通知成功加入房间后跳转页面
await init({
ip: server,
player: user?.name ?? "Guest",
passWd: "AI",
});
}}
icon={
singleLoading ? (
<LoadingOutlined />
) : (
<IconFont type="icon-chip" size={26} />
)
}
onClick={onAIMatch}
/>
<Mode
title="自定义房间"
desc="创建双打TAG或自定义规则的房间,或与好友约战,甚至举办竞技比赛。"
icon={<SettingFilled />}
onClick={() => (matchStore.open = true)}
onClick={onCustomRoom}
/>
<Mode
title="录像回放"
......@@ -129,7 +179,7 @@ export const Component: React.FC = () => {
title="观战列表"
desc="观看MyCard上正在进行的决斗"
icon={<PlayCircleOutlined />}
onClick={() => alert("开发中,敬请期待")}
onClick={onWatchList}
/>
</div>
</div>
......
......@@ -7,8 +7,8 @@ import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite";
const NeosConfig = useConfig();
// 进行进入房间/回放前的一些初始化操作
export const init = async (params: {
// 连接SRVPRO服务
export const connectSrvpro = async (params: {
ip: string;
player: string;
passWd: string;
......
import { App, Button, Modal } from "antd";
import React, { useEffect } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useSnapshot } from "valtio";
import { CardMeta, sendUpdateDeck } from "@/api";
import { roomStore, SideStage, sideStore } from "@/stores";
import { DeckEditor } from "../../BuildDeck";
import { editDeckStore } from "../../BuildDeck/store";
import { iDeckToEditingDeck } from "../../BuildDeck/utils";
import { Background } from "../../Shared";
export const ChangeSideModal: React.FC = () => {
const { message } = App.useApp();
const { deckName, main, extra, side } = useSnapshot(editDeckStore);
const { stage } = useSnapshot(sideStore);
const { errorMsg } = useSnapshot(roomStore);
const cardMeta2Id = (meta: CardMeta) => meta.id;
const handleSummit = () => {
const newDeck = {
deckName: deckName,
main: main.map(cardMeta2Id),
extra: extra.map(cardMeta2Id),
side: side.map(cardMeta2Id),
};
sendUpdateDeck(newDeck);
editDeckStore.edited = false;
};
useEffect(() => {
if (stage === SideStage.SIDE_CHANGED) {
message.info("副卡组更换成功,请耐心等待其他玩家更换卡组");
}
}, [stage]);
useEffect(() => {
if (errorMsg !== undefined && errorMsg !== "") {
message.error(errorMsg);
roomStore.errorMsg = undefined;
}
}, [errorMsg]);
return (
<Modal
title="请选择更换副卡组"
open={
stage === SideStage.SIDE_CHANGING || stage === SideStage.SIDE_CHANGED
}
width={700}
closable={false}
footer={
<Button
disabled={stage > SideStage.SIDE_CHANGING}
onClick={handleSummit}
>
副卡组更换完毕
</Button>
}
>
<DndProvider backend={HTML5Backend}>
<Background />
<DeckEditor
deck={sideStore.deck}
onClear={() => message.error("对局中清空卡组不怕找不回来吗?!")}
onSave={() => message.error("点击右下角按钮确认副卡组更换完毕")}
onReset={async () => {
editDeckStore.set(await iDeckToEditingDeck(sideStore.deck));
}}
/>
</DndProvider>
</Modal>
);
};
.container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
gap: 80px;
button {
width: 100px;
height: 50px;
font-size: 16px;
}
}
import { Button, Modal } from "antd";
import React from "react";
import { useSnapshot } from "valtio";
import { sendTpResult } from "@/api";
import { SideStage, sideStore } from "@/stores";
import styles from "./index.module.scss";
export const TpModal: React.FC = () => {
const { stage } = useSnapshot(sideStore);
return (
<Modal
centered
open={stage === SideStage.TP_SELECTING}
footer={<></>}
closable={false}
>
<div className={styles.container}>
<Button
onClick={() => {
sendTpResult(true);
sideStore.stage = SideStage.TP_SELECTED;
}}
>
先手
</Button>
<Button
onClick={() => {
sendTpResult(false);
sideStore.stage = SideStage.TP_SELECTED;
}}
>
后手
</Button>
</div>
</Modal>
);
};
export * from "./ChangeSideModal";
export * from "./TpModal";
......@@ -68,7 +68,9 @@ export const MoraPopover: React.FC<
};
export const TpPopover: React.FC<
React.PropsWithChildren<{ onSelect?: (result: Tp) => void }>
React.PropsWithChildren<{
onSelect?: (result: Tp) => void;
}>
> = ({ children, onSelect }) => {
const [open, setOpen] = useState(false);
......
......@@ -14,7 +14,7 @@ import {
import socketMiddleWare, { socketCmd } from "@/middleware/socket";
import PlayerState = ygopro.StocHsPlayerChange.State;
import SelfType = ygopro.StocTypeChange.SelfType;
import { Avatar, Button, Skeleton, Space } from "antd";
import { App, Avatar, Button, Skeleton, Space } from "antd";
import classNames from "classnames";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
......@@ -29,6 +29,7 @@ import {
resetUniverse,
RoomStage,
roomStore,
sideStore,
} from "@/stores";
import { Background, IconFont, Select, SpecialButton } from "@/ui/Shared";
......@@ -39,11 +40,13 @@ import { Mora, MoraPopover, Tp, TpPopover } from "./Popover";
const NeosConfig = useConfig();
export const Component: React.FC = () => {
const { message } = App.useApp();
const { user } = useSnapshot(accountStore);
const [collapsed, setCollapsed] = useState(false);
const { decks } = useSnapshot(deckStore);
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0])));
const room = useSnapshot(roomStore);
const { errorMsg } = room;
const me = room.getMePlayer();
const op = room.getOpPlayer();
const navigate = useNavigate();
......@@ -55,6 +58,13 @@ export const Component: React.FC = () => {
// TODO: 重置房间状态(也可能是在这个页面的loader之中重置,就看是进房间重置还是离开时重置,可能需要考虑意外离开的情况)
}
}, [room.stage]);
useEffect(() => {
// 出现错误
if (errorMsg !== undefined && errorMsg !== "") {
message.error(errorMsg);
roomStore.errorMsg = undefined;
}
}, [errorMsg]);
return (
<div
......@@ -75,7 +85,6 @@ export const Component: React.FC = () => {
<Controller
onDeckChange={(deckName: string) => {
const deck = deckStore.get(deckName);
// 同步后端
if (deck) {
setDeck(deck);
} else {
......@@ -96,6 +105,9 @@ export const Component: React.FC = () => {
onClick={() => {
if (me?.state === PlayerState.NO_READY) {
sendUpdateDeck(deck);
// 设置side里面的卡组
sideStore.deck = deck;
// 设置额外卡组数据
window.myExtraDeckCodes = [...deck.extra];
sendHsReady();
} else {
......
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