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