Commit 2aa3893b authored by timel's avatar timel

Merge branch 'dev/valtio' into 'main'

Valtio

See merge request mycard/Neos!169
parents 39c16d26 0d7db7ab
Pipeline #21397 passed with stages
in 19 minutes and 16 seconds
This diff is collapsed.
......@@ -51,15 +51,14 @@ export interface CardText {
* */
export async function fetchCard(
id: number,
local?: boolean
local: boolean = true
): Promise<CardMeta> {
if (local) {
return await sqliteMiddleWare({
const res = await sqliteMiddleWare({
cmd: sqliteCmd.SELECT,
payload: { id },
}).then((res) =>
res.selectResult ? res.selectResult : { id, data: {}, text: {} }
);
});
return res.selectResult ? res.selectResult : { id, data: {}, text: {} };
}
const res = await axios.get<CardMeta>("http://localhost:3030/cards/" + id);
......
export * from "./cards";
export * from "./deck";
export * from "./ocgcore/idl/ocgcore";
export * from "./ocgcore/ocgHelper";
export * from "./strings";
......@@ -10,3 +10,10 @@ interface ImportMetaEnv {
interface ImportMeta {
readonly env: ImportMetaEnv;
}
// // 重新声明useSnapshot,暂时先这么写。原版的会把所有的改成readonly,引发一些棘手的类型报错。
// import "valtio/react";
// declare module "valtio/react" {
// export declare function useSnapshot<T extends object>(proxyObject: T): T;
// export {};
// }
export * from "./useApp";
export * from "./useEnv";
export * from "./useMeshClick";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from "@/store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
......@@ -23,12 +23,8 @@ import { ConfigProvider, theme } from "antd";
import zhCN from "antd/locale/zh_CN";
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import { ValtioProvider } from "@/valtioStores";
import { store } from "./store";
import Neos from "./ui/Neos";
const root = ReactDOM.createRoot(
......@@ -37,16 +33,9 @@ const root = ReactDOM.createRoot(
root.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<ValtioProvider>
<ConfigProvider
theme={{ algorithm: theme.darkAlgorithm }}
locale={zhCN}
>
<Neos />
</ConfigProvider>
</ValtioProvider>
</Provider>
<ConfigProvider theme={{ algorithm: theme.darkAlgorithm }} locale={zhCN}>
<Neos />
</ConfigProvider>
</BrowserRouter>
</React.StrictMode>
);
......@@ -75,7 +75,10 @@ export default async function (action: sqliteAction): Promise<sqliteResult> {
selectResult: constructCardMeta(code, dataResult, textResult),
};
} else {
console.warn("ygo db not init or id not provied!");
if (action.payload?.id !== 0) {
// 0是无效的卡片ID,不需要报错,返回空即可
console.warn("ygo db not init or id not provied!");
}
}
return {};
......
/*
* Chat状态更新逻辑
*
* */
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface chatState {
message: string;
}
const initialState: chatState = {
message: "",
};
const chatSlice = createSlice({
name: "chat",
initialState,
reducers: {
postChat: (state, action: PayloadAction<string>) => {
state.message = action.payload;
},
},
});
export const { postChat } = chatSlice.actions;
export const selectChat = (state: RootState) => state.chat.message;
export default chatSlice.reducer;
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
DuelFieldState,
DuelReducer,
extendIdleInteractivities,
extendMeta,
extendState,
Interactivity,
removeCard,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface BanishedZoneState extends DuelFieldState {}
// 初始化除外区状态
export const initBanishedZoneImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
if (judgeSelf(player, state)) {
state.meBanishedZone = { inner: [] };
} else {
state.opBanishedZone = { inner: [] };
}
};
// 增加除外区
export const fetchBanishedZoneMeta = createAsyncMetaThunk(
"duel/fetchBanishedZoneMeta"
);
export const banishedZoneCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchBanishedZoneMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const newExclusion = {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.REMOVED,
},
idleInteractivities: [],
counters: {},
};
if (judgeSelf(controler, state)) {
extendState(state.meBanishedZone, newExclusion, sequence);
} else {
extendState(state.opBanishedZone, newExclusion, sequence);
}
});
builder.addCase(fetchBanishedZoneMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendMeta(state.meBanishedZone, meta, sequence);
} else {
extendMeta(state.opBanishedZone, meta, sequence);
}
});
};
// 删除除外区
export const removeBanishedZoneImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const banishedZone = judgeSelf(action.payload.controler, state)
? state.meBanishedZone
: state.opBanishedZone;
removeCard(banishedZone, action.payload.sequence);
};
export const addBanishedZoneIdleInteractivitiesImpl: DuelReducer<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}> = (state, action) => {
const banishedZone = judgeSelf(action.payload.player, state)
? state.meBanishedZone
: state.opBanishedZone;
extendIdleInteractivities(
banishedZone,
action.payload.sequence,
action.payload.interactivity
);
};
export const selectMeBanishedZone = (state: RootState) =>
state.duel.meBanishedZone || { inner: [] };
export const selectOpBanishedZone = (state: RootState) =>
state.duel.opBanishedZone || { inner: [] };
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import {
clearIdleInteractivities,
clearPlaceInteractivities,
DuelReducer,
reloadFieldMeta,
updateCardData,
} from "./generic";
import { judgeSelf } from "./util";
import MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
type MsgUpdateData = ReturnType<
typeof ygopro.StocGameMessage.MsgUpdateData.prototype.toObject
>;
export const clearAllIdleInteractivitiesImpl: DuelReducer<number> = (
state,
action
) => {
const player = action.payload;
const states = judgeSelf(player, state)
? [
state.meHands,
state.meMonsters,
state.meMagics,
state.meGraveyard,
state.meBanishedZone,
state.meExtraDeck,
]
: [
state.opHands,
state.opMonsters,
state.opMagics,
state.opGraveyard,
state.opBanishedZone,
state.opExtraDeck,
];
states.forEach((item) => clearIdleInteractivities(item));
};
export const clearAllPlaceInteractivitiesImpl: DuelReducer<number> = (
state,
action
) => {
const player = action.payload;
const states = judgeSelf(player, state)
? [
state.meHands,
state.meMonsters,
state.meMagics,
state.meGraveyard,
state.meBanishedZone,
]
: [
state.opHands,
state.opMonsters,
state.opMagics,
state.opGraveyard,
state.opBanishedZone,
];
states.forEach((item) => clearPlaceInteractivities(item));
};
export const updateFieldDataImpl: DuelReducer<MsgUpdateData> = (
state,
action
) => {
const player = action.payload.player;
const zone = action.payload.zone;
const actions = action.payload.actions;
if (player !== undefined && zone !== undefined && actions !== undefined) {
switch (zone) {
case ygopro.CardZone.HAND: {
const hand = judgeSelf(player, state) ? state.meHands : state.opHands;
updateCardData(hand, actions);
break;
}
case ygopro.CardZone.EXTRA: {
const extra = judgeSelf(player, state)
? state.meExtraDeck
: state.opExtraDeck;
updateCardData(extra, actions);
break;
}
case ygopro.CardZone.MZONE: {
const monster = judgeSelf(player, state)
? state.meMonsters
: state.opMonsters;
updateCardData(monster, actions);
break;
}
case ygopro.CardZone.SZONE: {
const magics = judgeSelf(player, state)
? state.meMagics
: state.opMagics;
updateCardData(magics, actions);
break;
}
case ygopro.CardZone.GRAVE: {
const graveyard = judgeSelf(player, state)
? state.meGraveyard
: state.opGraveyard;
updateCardData(graveyard, actions);
break;
}
case ygopro.CardZone.REMOVED: {
const BanishedZone = judgeSelf(player, state)
? state.meBanishedZone
: state.opBanishedZone;
updateCardData(BanishedZone, actions);
break;
}
default: {
break;
}
}
}
};
export const reloadFieldImpl: DuelReducer<MsgReloadField> = (state, action) => {
const _duel_rule = action.payload.duel_rule;
// 初始化`DuelState`
state.meDeck = { inner: [] };
state.opDeck = { inner: [] };
state.meExtraDeck = { inner: [] };
state.opExtraDeck = { inner: [] };
state.meMonsters = { inner: [] };
state.opMonsters = { inner: [] };
state.meMagics = { inner: [] };
state.opMagics = { inner: [] };
state.meGraveyard = { inner: [] };
state.opGraveyard = { inner: [] };
state.meBanishedZone = { inner: [] };
state.opBanishedZone = { inner: [] };
state.meHands = { inner: [] };
state.opHands = { inner: [] };
for (const reload of action.payload.actions) {
const player = reload.player;
// DECK
const deck = judgeSelf(player, state) ? state.meDeck : state.opDeck;
reloadFieldMeta(
deck,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.DECK),
player
);
// EXTRA_DECK
const extraDeck = judgeSelf(player, state)
? state.meExtraDeck
: state.opExtraDeck;
reloadFieldMeta(
extraDeck,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.EXTRA),
player
);
// MZONE
const monster = judgeSelf(player, state)
? state.meMonsters
: state.opMonsters;
reloadFieldMeta(
monster,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.MZONE),
player
);
// SZONE
const magics = judgeSelf(player, state) ? state.meMagics : state.opMagics;
reloadFieldMeta(
magics,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.SZONE),
player
);
// GRAVE
const graveyard = judgeSelf(player, state)
? state.meGraveyard
: state.opGraveyard;
reloadFieldMeta(
graveyard,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.GRAVE),
player
);
// REMOVED
const banishedZone = judgeSelf(player, state)
? state.meBanishedZone
: state.opBanishedZone;
reloadFieldMeta(
banishedZone,
reload.zone_actions.filter(
(item) => item.zone == ygopro.CardZone.REMOVED
),
player
);
// HANDS
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
reloadFieldMeta(
hands,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.HAND),
player
);
}
};
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { CardState, DuelFieldState } from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface DeckState extends DuelFieldState {}
// 初始化卡组状态
export const initDeckImpl: CaseReducer<
DuelState,
PayloadAction<{ player: number; deskSize: number }>
> = (state, action) => {
const player = action.payload.player;
const deckSize = action.payload.deskSize;
let deck: CardState[] = new Array(deckSize);
for (let i = 0; i < deckSize; i++) {
deck.push({
occupant: { id: 0, data: {}, text: {} },
location: {
controler: player,
location: ygopro.CardZone.DECK,
},
idleInteractivities: [],
counters: {},
});
}
if (judgeSelf(player, state)) {
state.meDeck = { inner: deck };
} else {
state.opDeck = { inner: deck };
}
};
export const selectMeDeck = (state: RootState) =>
state.duel.meDeck || { inner: [] };
export const selectOpDeck = (state: RootState) =>
state.duel.opDeck || { inner: [] };
import { ActionReducerMapBuilder } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
createAsyncRepeatedMetaThunk,
DuelFieldState,
DuelReducer,
extendIdleInteractivities,
extendMeta,
extendState,
Interactivity,
removeCard,
updateCardMeta,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface ExtraDeckState extends DuelFieldState {}
// 初始化额外卡组
export const initMeExtraDeckMeta = createAsyncRepeatedMetaThunk(
"duel/initExtraDeckMeta"
);
// 增加额外卡组
export const fetchExtraDeckMeta = createAsyncMetaThunk(
"duel/fetchExtraDeckMeta"
);
export const extraDeckCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(initMeExtraDeckMeta.pending, (state, action) => {
const _ = action.meta.arg.controler;
const ids = action.meta.arg.codes;
const cards = ids.map((id) => {
return {
occupant: { id, data: {}, text: {} },
location: {
location: ygopro.CardZone.EXTRA,
},
idleInteractivities: [],
counters: {},
};
});
state.meExtraDeck = { inner: cards };
});
builder.addCase(initMeExtraDeckMeta.fulfilled, (state, action) => {
const _ = action.payload.controler;
const metas = action.payload.metas;
updateCardMeta(state.meExtraDeck, metas);
});
builder.addCase(fetchExtraDeckMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const newExtraDeck = {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.EXTRA,
},
idleInteractivities: [],
counters: {},
};
const extraDeck = judgeSelf(controler, state)
? state.meExtraDeck
: state.opExtraDeck;
extendState(extraDeck, newExtraDeck, sequence);
});
builder.addCase(fetchExtraDeckMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
const extraDeck = judgeSelf(controler, state)
? state.meExtraDeck
: state.opExtraDeck;
extendMeta(extraDeck, meta, sequence);
});
};
// 删除额外卡组
export const removeExtraDeckImpl: DuelReducer<{
controler: number;
sequence: number;
}> = (state, action) => {
const extraDeck = judgeSelf(action.payload.controler, state)
? state.meExtraDeck
: state.opExtraDeck;
removeCard(extraDeck, action.payload.sequence);
};
export const addExtraDeckIdleInteractivitiesImpl: DuelReducer<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}> = (state, action) => {
const extraDeck = judgeSelf(action.payload.player, state)
? state.meExtraDeck
: state.opExtraDeck;
extendIdleInteractivities(
extraDeck,
action.payload.sequence,
action.payload.interactivity
);
};
export const selectMeExtraDeck = (state: RootState) =>
state.duel.meExtraDeck || { inner: [] };
export const selectOpExtraDeck = (state: RootState) =>
state.duel.opExtraDeck || { inner: [] };
import {
AsyncThunk,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { DuelState } from "./mod";
import ReloadFieldAction = ygopro.StocGameMessage.MsgReloadField.ZoneAction;
type UpdateDataAction = ReturnType<
typeof ygopro.StocGameMessage.MsgUpdateData.Action.prototype.toObject
>;
export type DuelReducer<T> = CaseReducer<DuelState, PayloadAction<T>>;
export interface DuelFieldState {
inner: CardState[];
}
export interface CardState {
occupant?: CardMeta; // 占据此位置的卡牌元信息
location: {
controler?: number;
location?: number;
position?: ygopro.CardPosition;
overlay_sequence?: number;
}; // 位置信息
idleInteractivities: Interactivity<number>[]; // IDLE状态下的互动信息
placeInteractivities?: Interactivity<{
controler: number;
zone: ygopro.CardZone;
sequence: number;
}>; // 选择位置状态下的互动信息
overlay_materials?: CardMeta[]; // 超量素材
counters: { [type: number]: number }; // 指示器
reload?: boolean; // 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false
}
export enum InteractType {
// 可普通召唤
SUMMON = 1,
// 可特殊召唤
SP_SUMMON = 2,
// 可改变表示形式
POS_CHANGE = 3,
// 可前场放置
MSET = 4,
// 可后场放置
SSET = 5,
// 可发动效果
ACTIVATE = 6,
// 可作为位置选择
PLACE_SELECTABLE = 7,
// 可攻击
ATTACK = 8,
}
export interface Interactivity<T> {
interactType: InteractType;
// 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号
activateIndex?: number;
// 如果`interactType`是`ATTACK`,这个字段表示是否可以直接攻击
directAttackAble?: boolean;
// 用户点击后,需要回传给服务端的`response`
response: T;
}
export function createAsyncMetaThunk(name: string): AsyncThunk<
{ controler: number; sequence: number; meta: CardMeta },
{
controler: number;
sequence: number;
position?: ygopro.CardPosition;
code: number;
},
{}
> {
return createAsyncThunk(
name,
async (param: {
controler: number;
sequence: number;
position?: ygopro.CardPosition;
code: number;
}) => {
const code = param.code;
const meta = await fetchCard(code, true);
const response = {
controler: param.controler,
sequence: param.sequence,
meta,
};
return response;
}
);
}
export function createAsyncRepeatedMetaThunk(
name: string
): AsyncThunk<
{ controler: number; metas: CardMeta[] },
{ controler: number; codes: number[] },
{}
> {
return createAsyncThunk(
name,
async (param: { controler: number; codes: number[] }) => {
const controler = param.controler;
const Ids = param.codes;
const metas = await Promise.all(
Ids.map(async (id) => {
if (id == 0) {
return { id, data: {}, text: {} };
} else {
return await fetchCard(id, true);
}
})
);
const response = { controler, metas };
return response;
}
);
}
/*
* 扩充决斗区域卡片内容
*
* @param state - 需要扩充的区域,比如`MonsterState`
* @param newState - 新增加的`CardState`
* @sequence - 新增加的卡片的序列号,可选,如果为空则补充到列表末尾
*
* */
export function extendState<T extends DuelFieldState>(
state: T | undefined,
newState: CardState,
sequence?: number
) {
if (state) {
let index = sequence !== undefined ? sequence : state.inner.length;
state.inner.splice(index, 0, newState);
}
}
export function extendOccupant<T extends DuelFieldState>(
state: T | undefined,
newMeta: CardMeta,
sequence: number,
position?: ygopro.CardPosition
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.occupant = newMeta;
if (typeof position !== "undefined") {
target.location.position = position;
}
}
}
}
export function extendMeta<T extends DuelFieldState>(
state: T | undefined,
newMeta: CardMeta,
sequence: number
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.occupant = newMeta;
}
}
}
export function extendPlaceInteractivity<T extends DuelFieldState>(
state: T | undefined,
controler: number,
sequence: number,
zone: ygopro.CardZone
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.placeInteractivities = {
interactType: InteractType.PLACE_SELECTABLE,
response: {
controler,
zone,
sequence,
},
};
}
}
}
export function clearPlaceInteractivities<T extends DuelFieldState>(
state: T | undefined
) {
if (state) {
for (let item of state.inner) {
item.placeInteractivities = undefined;
}
}
}
export function removeCard<T extends DuelFieldState>(
state: T | undefined,
sequence: number
) {
if (state) {
state.inner = state.inner.filter((_, idx) => idx != sequence);
}
}
export function removeOccupant<T extends DuelFieldState>(
state: T | undefined,
sequence: number
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.occupant = undefined;
}
}
}
export function removeOverlay<T extends DuelFieldState>(
state: T | undefined,
sequence: number
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.overlay_materials = [];
}
}
}
export function insertCard<T extends DuelFieldState>(
state: T | undefined,
sequence: number,
card: CardState
) {
if (state) {
state.inner.splice(sequence, 0, card);
}
}
export function updateCardMeta<T extends DuelFieldState>(
state: T | undefined,
metas: CardMeta[]
) {
if (state) {
state.inner.forEach((item) => {
metas.forEach((meta) => {
if (item.occupant?.id === meta.id) {
item.occupant = meta;
}
});
});
}
}
export function extendIdleInteractivities<T extends DuelFieldState>(
state: T | undefined,
sequence: number,
interactivity: Interactivity<number>
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.idleInteractivities.push(interactivity);
}
}
}
export function clearIdleInteractivities<T extends DuelFieldState>(
state: T | undefined
) {
if (state) {
state.inner.forEach((item) => {
item.idleInteractivities = [];
});
}
}
export function setPosition<T extends DuelFieldState>(
state: T | undefined,
sequence: number,
position: ygopro.CardPosition
) {
const target = state?.inner.find((_, idx) => idx == sequence);
if (target && target.occupant) {
target.location.position = position;
}
}
export function updateCardData<T extends DuelFieldState>(
state: T | undefined,
actions: UpdateDataAction[]
) {
for (const payload of actions) {
const sequence = payload.location?.sequence;
if (typeof sequence !== "undefined") {
const target = state?.inner.find((_, idx) => idx == sequence);
if (target && (target.occupant || target.reload)) {
if (target.occupant === undefined) {
target.occupant = { id: payload.code!, data: {}, text: {} };
}
const occupant = target.occupant;
// 目前只更新以下字段
if (payload.code !== undefined && payload.code >= 0) {
occupant.id = payload.code;
occupant.text.id = payload.code;
}
if (payload.location !== undefined) {
target.location.position = payload.location.position;
}
if (payload.type_ !== undefined && payload.type_ >= 0) {
occupant.data.type = payload.type_;
}
if (payload.level !== undefined && payload.level >= 0) {
occupant.data.level = payload.level;
}
if (payload.attribute !== undefined && payload.attribute >= 0) {
occupant.data.attribute = payload.attribute;
}
if (payload.race !== undefined && payload.race >= 0) {
occupant.data.race = payload.race;
}
if (payload.attack !== undefined && payload.attack >= 0) {
occupant.data.atk = payload.attack;
}
if (payload.defense !== undefined && payload.defense >= 0) {
occupant.data.def = payload.defense;
}
// TODO: counters
}
if (target?.reload) {
target.reload = false;
}
}
}
}
export function reloadFieldMeta<T extends DuelFieldState>(
state: T,
actions: ReloadFieldAction[],
controler: number
) {
actions.sort((a, b) => a.sequence - b.sequence);
const cards = actions.map((action) => {
// FIXME: OVERLAY
return {
location: {
controler,
location: action.zone,
position: action.position,
},
idleInteractivities: [],
counters: {},
reload: true,
};
});
state.inner = cards;
}
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
DuelFieldState,
DuelReducer,
extendIdleInteractivities,
extendMeta,
extendState,
Interactivity,
removeCard,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface GraveyardState extends DuelFieldState {}
// 初始化墓地状态
export const initGraveyardImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
if (judgeSelf(player, state)) {
state.meGraveyard = { inner: [] };
} else {
state.opGraveyard = { inner: [] };
}
};
// 增加墓地
export const fetchGraveyardMeta = createAsyncMetaThunk(
"duel/fetchGraveyardMeta"
);
export const graveyardCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchGraveyardMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const newGraveyard = {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.GRAVE,
},
idleInteractivities: [],
counters: {},
};
if (judgeSelf(controler, state)) {
extendState(state.meGraveyard, newGraveyard, sequence);
} else {
extendState(state.opGraveyard, newGraveyard, sequence);
}
});
builder.addCase(fetchGraveyardMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendMeta(state.meGraveyard, meta, sequence);
} else {
extendMeta(state.opGraveyard, meta, sequence);
}
});
};
// 删除墓地
export const removeGraveyardImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const graveyard = judgeSelf(action.payload.controler, state)
? state.meGraveyard
: state.opGraveyard;
removeCard(graveyard, action.payload.sequence);
};
export const addGraveyardIdleInteractivitiesImpl: DuelReducer<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}> = (state, action) => {
const graveyard = judgeSelf(action.payload.player, state)
? state.meGraveyard
: state.opGraveyard;
extendIdleInteractivities(
graveyard,
action.payload.sequence,
action.payload.interactivity
);
};
export const selectMeGraveyard = (state: RootState) =>
state.duel.meGraveyard || { inner: [] };
export const selectOpGraveyard = (state: RootState) =>
state.duel.opGraveyard || { inner: [] };
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
createAsyncRepeatedMetaThunk,
DuelFieldState,
extendMeta,
insertCard,
Interactivity,
removeCard,
updateCardMeta,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface HandState extends DuelFieldState {}
// 增加手牌
export const fetchHandsMeta = createAsyncRepeatedMetaThunk(
"duel/fetchHandsMeta"
);
// 清空手牌互动性
export const clearHandsIdleInteractivityImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
if (hands) {
for (let hand of hands.inner) {
hand.idleInteractivities = [];
}
}
};
// 添加手牌互动性
export const addHandsIdleInteractivityImpl: CaseReducer<
DuelState,
PayloadAction<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}>
> = (state, action) => {
const player = action.payload.player;
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
if (hands) {
const sequence = action.payload.sequence;
const interactivity = action.payload.interactivity;
hands.inner[sequence].idleInteractivities.push(interactivity);
}
};
// 删除手牌
export const removeHandImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const controler = action.payload[0];
const sequence = action.payload[1];
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
removeCard(hands, sequence);
};
// 在特定位置增加手牌
export const insertHandMeta = createAsyncMetaThunk("duel/insertHandMeta");
export const updateHandsMeta = createAsyncRepeatedMetaThunk(
"duel/updateHandsMeta"
);
export const handsCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchHandsMeta.pending, (state, action) => {
// Meta结果没返回之前先更新手牌`ID`
const player = action.meta.arg.controler;
const ids = action.meta.arg.codes;
const cards = ids.map((id) => {
return {
occupant: { id, data: {}, text: {} },
location: {
controler: player,
location: ygopro.CardZone.HAND,
},
counters: {},
idleInteractivities: [],
};
});
if (judgeSelf(player, state)) {
if (state.meHands) {
state.meHands.inner = state.meHands.inner.concat(cards);
} else {
state.meHands = { inner: cards };
}
} else {
if (state.opHands) {
state.opHands.inner = state.opHands.inner.concat(cards);
} else {
state.opHands = { inner: cards };
}
}
});
builder.addCase(fetchHandsMeta.fulfilled, (state, action) => {
// `Meta`结果回来后更新手牌的`Meta`结果
const player = action.payload.controler;
const metas = action.payload.metas;
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
updateCardMeta(hands, metas);
});
builder.addCase(insertHandMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
insertCard(hands, sequence, {
occupant: { id: code, data: {}, text: {} },
location: { controler },
idleInteractivities: [],
counters: {},
});
});
builder.addCase(insertHandMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
extendMeta(hands, meta, sequence);
});
builder.addCase(updateHandsMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const codes = action.meta.arg.codes;
const metas = codes.map((code) => {
return {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.HAND,
},
idleInteractivities: [],
counters: {},
};
});
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
if (hands) {
hands.inner = metas;
}
});
builder.addCase(updateHandsMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const metas = action.payload.metas;
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
updateCardMeta(hands, metas);
});
};
export const selectMeHands = (state: RootState) =>
state.duel.meHands || { inner: [] };
export const selectOpHands = (state: RootState) =>
state.duel.opHands || { inner: [] };
import { ActionReducerMapBuilder, createAsyncThunk } from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { DESCRIPTION_LIMIT, fetchStrings, getStrings } from "@/api/strings";
import { RootState } from "@/store";
import { DuelReducer } from "./generic";
import { DuelState } from "./mod";
import { findCardByLocation } from "./util";
export interface HintState {
code: number;
msg?: string;
esHint?: string;
esSelectHint?: string;
}
export const initHintImpl: DuelReducer<void> = (state) => {
state.hint = { code: 0 };
};
export const fetchCommonHintMeta = createAsyncThunk(
"duel/fetchCommonHintMeta",
async (hintData: number) => {
return fetchStrings("!system", hintData);
}
);
export const fetchSelectHintMeta = createAsyncThunk(
"duel/fetchSelectHintMeta",
async (param: { selectHintData: number; esHint?: string }) => {
const selectHintData = param.selectHintData;
let selectHintMeta = "";
if (selectHintData > DESCRIPTION_LIMIT) {
// 针对`MSG_SELECT_PLACE`的特化逻辑
const cardMeta = await fetchCard(selectHintData, true);
selectHintMeta = fetchStrings("!system", 569).replace(
"[%ls]",
cardMeta.text.name || "[?]"
);
} else {
selectHintMeta = await getStrings(selectHintData);
}
return {
selectHintMeta,
esHint: param.esHint,
};
}
);
export const fetchEsHintMeta = createAsyncThunk(
"duel/fetchEsHintMeta",
async (param: {
originMsg: string | number;
location?: ygopro.CardLocation;
cardID?: number;
}) => {
const originMsg =
typeof param.originMsg === "string"
? param.originMsg
: fetchStrings("!system", param.originMsg);
const location = param.location;
if (param.cardID) {
const cardMeta = await fetchCard(param.cardID, true);
return { originMsg, cardMeta, location };
} else {
return { originMsg, location };
}
}
);
export const hintCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchCommonHintMeta.pending, (state, action) => {
const code = action.meta.arg;
if (state.hint) {
state.hint.code = code;
}
});
builder.addCase(fetchCommonHintMeta.fulfilled, (state, action) => {
const hintMeta = action.payload;
if (state.hint) {
state.hint.msg = hintMeta;
}
});
builder.addCase(fetchSelectHintMeta.pending, (state, action) => {
const code = action.meta.arg.selectHintData;
if (state.hint) {
state.hint.code = code;
}
});
builder.addCase(fetchSelectHintMeta.fulfilled, (state, action) => {
const selectHintMsg = action.payload.selectHintMeta;
const esHint = action.payload.esHint;
const hint = state.hint;
if (hint) {
if (hint.code > DESCRIPTION_LIMIT) {
// 针对`MSG_SELECT_PLACE`的特化逻辑
hint.msg = selectHintMsg;
} else {
hint.esSelectHint = selectHintMsg;
if (esHint) hint.esHint = esHint;
}
}
});
builder.addCase(fetchEsHintMeta.fulfilled, (state, action) => {
const originMsg = action.payload.originMsg;
const cardMeta = action.payload.cardMeta;
const location = action.payload.location;
const hint = state.hint;
if (hint) {
let esHint = originMsg;
if (cardMeta?.text.name) {
esHint = originMsg.replace("[?]", cardMeta.text.name);
}
if (location) {
const fieldMeta = findCardByLocation(state, location);
if (fieldMeta?.occupant?.text.name) {
esHint = originMsg.replace("[?]", fieldMeta.occupant.text.name);
}
}
hint.esHint = esHint;
}
});
};
export const selectHint = (state: RootState) => state.duel.hint;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
import MsgUpdateHp = ygopro.StocGameMessage.MsgUpdateHp;
export interface InitInfo {
masterRule?: string;
life: number;
deckSize: number;
extraSize: number;
}
// 更新自己的初始生命值,卡组信息
export const infoInitImpl: CaseReducer<
DuelState,
PayloadAction<[number, InitInfo]>
> = (state, action) => {
const player = action.payload[0];
const initInfo = action.payload[1];
if (judgeSelf(player, state)) {
state.meInitInfo = initInfo;
} else {
state.opInitInfo = initInfo;
}
};
export const updateHpImpl: CaseReducer<
DuelState,
PayloadAction<ygopro.StocGameMessage.MsgUpdateHp>
> = (state, action) => {
const player = action.payload.player;
const actionType = action.payload.type_;
const value = action.payload.value;
const info = judgeSelf(player, state) ? state.meInitInfo : state.opInitInfo;
if (info) {
switch (actionType) {
case MsgUpdateHp.ActionType.DAMAGE: {
info.life = info.life - value;
break;
}
case MsgUpdateHp.ActionType.RECOVER: {
info.life = info.life + value;
break;
}
default: {
break;
}
}
}
};
export const selectMeInitInfo = (state: RootState) => state.duel.meInitInfo;
export const selectOpInitInfo = (state: RootState) => state.duel.opInitInfo;
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
clearIdleInteractivities,
clearPlaceInteractivities,
createAsyncMetaThunk,
DuelFieldState,
extendIdleInteractivities,
extendOccupant,
extendPlaceInteractivity,
Interactivity,
removeOccupant,
setPosition,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface MagicState extends DuelFieldState {}
// 初始化自己的魔法陷阱区状态
export const initMagicsImpl: CaseReducer<DuelState, PayloadAction<number>> = (
state,
action
) => {
const player = action.payload;
const magics = {
inner: [
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
// 场地区
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
],
};
if (judgeSelf(player, state)) {
state.meMagics = magics;
} else {
state.opMagics = magics;
}
};
export const addMagicPlaceInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const controler = action.payload[0];
const sequence = action.payload[1];
const magics = judgeSelf(controler, state) ? state.meMagics : state.opMagics;
extendPlaceInteractivity(magics, controler, sequence, ygopro.CardZone.SZONE);
};
export const clearMagicPlaceInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
const magics = judgeSelf(player, state) ? state.meMagics : state.opMagics;
clearPlaceInteractivities(magics);
};
export const addMagicIdleInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}>
> = (state, action) => {
const magics = judgeSelf(action.payload.player, state)
? state.meMagics
: state.opMagics;
extendIdleInteractivities(
magics,
action.payload.sequence,
action.payload.interactivity
);
};
export const clearMagicIdleInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const magics = judgeSelf(action.payload, state)
? state.meMagics
: state.opMagics;
clearIdleInteractivities(magics);
};
// 增加魔法陷阱
export const fetchMagicMeta = createAsyncMetaThunk("duel/fetchMagicMeta");
export const magicCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchMagicMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const position = action.meta.arg.position;
const code = action.meta.arg.code;
const meta = { id: code, data: {}, text: {} };
if (judgeSelf(controler, state)) {
extendOccupant(state.meMagics, meta, sequence, position);
} else {
extendOccupant(state.opMagics, meta, sequence, position);
}
});
builder.addCase(fetchMagicMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendOccupant(state.meMagics, meta, sequence);
} else {
extendOccupant(state.opMagics, meta, sequence);
}
});
};
// 删除魔法陷阱
export const removeMagicImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const controler = action.payload.controler;
const magics = judgeSelf(controler, state) ? state.meMagics : state.opMagics;
removeOccupant(magics, action.payload.sequence);
};
// 改变魔法表示形式
export const setMagicPositionImpl: CaseReducer<
DuelState,
PayloadAction<{
controler: number;
sequence: number;
position: ygopro.CardPosition;
}>
> = (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const position = action.payload.position;
const magics = judgeSelf(controler, state) ? state.meMagics : state.opMagics;
setPosition(magics, sequence, position);
};
export const selectMeMagics = (state: RootState) =>
state.duel.meMagics || { inner: [] };
export const selectOpMagics = (state: RootState) =>
state.duel.opMagics || { inner: [] };
This diff is collapsed.
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { CardMeta } from "@/api/cards";
import { RootState } from "@/store";
import { DuelState } from "../mod";
// 更新卡牌列表弹窗打开状态
export const setCardListModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.cardListModal.isOpen = action.payload;
};
// 更新卡牌列表数据
export const setCardListModalInfoImpl: CaseReducer<
DuelState,
PayloadAction<
{
meta?: CardMeta;
interactivies: { desc: string; response: number }[];
}[]
>
> = (state, action) => {
const list = action.payload;
state.modalState.cardListModal.list = list;
};
export const selectCardListModalIsOpen = (state: RootState) =>
state.duel.modalState.cardListModal.isOpen;
export const selectCardListModalInfo = (state: RootState) =>
state.duel.modalState.cardListModal.list;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { CardMeta } from "@/api/cards";
import { RootState } from "@/store";
import { DuelState } from "../mod";
// 更新卡牌弹窗打开状态
export const setCardModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.cardModal.isOpen = action.payload;
};
// 更新卡牌弹窗文本
export const setCardModalMetaImpl: CaseReducer<
DuelState,
PayloadAction<CardMeta>
> = (state, action) => {
state.modalState.cardModal.meta = action.payload;
};
// 更新卡牌弹窗互动选项
export const setCardModalInteractiviesImpl: CaseReducer<
DuelState,
PayloadAction<{ desc: string; response: number }[]>
> = (state, action) => {
state.modalState.cardModal.interactivies = action.payload;
};
// 更新卡牌弹窗指示器
export const setCardModalCountersImpl: CaseReducer<
DuelState,
PayloadAction<{ [type: number]: number }>
> = (state, action) => {
state.modalState.cardModal.counters = action.payload;
};
export const selectCardModalIsOpen = (state: RootState) =>
state.duel.modalState.cardModal.isOpen;
export const selectCardModalMeta = (state: RootState) =>
state.duel.modalState.cardModal.meta;
export const selectCardModalInteractivies = (state: RootState) =>
state.duel.modalState.cardModal.interactivies;
export const selectCardModalCounters = (state: RootState) =>
state.duel.modalState.cardModal.counters;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { fetchCard, getCardStr } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "../mod";
import { cmpCardLocation, findCardByLocation, judgeSelf } from "../util";
// 更新卡牌选择弹窗打开状态
export const setCheckCardModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.checkCardModal.isOpen = action.payload;
};
// 更新卡牌选择弹窗选择数目状态
export const setCheckCardModalMinMaxImpl: CaseReducer<
DuelState,
PayloadAction<{ min: number; max: number }>
> = (state, action) => {
state.modalState.checkCardModal.selectMin = action.payload.min;
state.modalState.checkCardModal.selectMax = action.payload.max;
};
// 更新卡牌选择弹窗的提交回调
export const setCheckCardModalOnSubmitImpl: CaseReducer<
DuelState,
PayloadAction<string>
> = (state, action) => {
state.modalState.checkCardModal.onSubmit = action.payload;
};
// 更新卡牌选择弹窗是否可以取消
export const setCheckCardMOdalCancelAbleImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.checkCardModal.cancelAble = action.payload;
};
// 更新卡牌选择弹窗取消时返回给服务端的`Response`
export const setCheckCardModalCancelResponseImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
state.modalState.checkCardModal.cancelResponse = action.payload;
};
// 增加卡牌选择选项
export const fetchCheckCardMeta = createAsyncThunk(
"duel/fetchCheckCardMeta",
async (param: {
tagName: string;
option: {
code: number;
location: ygopro.CardLocation;
response: number;
effectDescCode?: number;
};
}) => {
// FIXME: 这里如果传的`controler`如果是对手,对应的`code`会为零,这时候就无法更新对应的`Meta`信息了,后续需要修复。`fetchCheckCardMetaV2`和`fetchCheckCardMetaV3`同理
const meta = await fetchCard(param.option.code, true);
const response = {
tagName: param.tagName,
option: {
meta,
location: param.option.location.toObject(),
},
};
return response;
}
);
export const checkCardModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMeta.pending, (state, action) => {
const tagName = action.meta.arg.tagName;
const code = action.meta.arg.option.code;
const location = action.meta.arg.option.location;
const controler = location.controler;
const effectDescCode = action.meta.arg.option.effectDescCode;
const response = action.meta.arg.option.response;
const combinedTagName = judgeSelf(controler, state)
? `我方的${tagName}`
: `对方的${tagName}`;
const newID =
code != 0 ? code : findCardByLocation(state, location)?.occupant?.id || 0;
const newOption = {
meta: { id: newID, data: {}, text: {} },
location: location.toObject(),
effectDescCode,
response,
};
for (const tag of state.modalState.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
tag.options.push(newOption);
return;
}
}
state.modalState.checkCardModal.tags.push({
tagName: combinedTagName,
options: [newOption],
});
});
builder.addCase(fetchCheckCardMeta.fulfilled, (state, action) => {
const tagName = action.payload.tagName;
const option = action.payload.option;
const controler = option.location.controler!;
const combinedTagName = judgeSelf(controler, state)
? `我方的${tagName}`
: `对方的${tagName}`;
for (const tag of state.modalState.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
for (const old of tag.options) {
if (
option.meta.id == old.meta.id &&
cmpCardLocation(option.location, old.location)
) {
const cardID = old.meta.id;
old.meta = option.meta;
old.meta.id = cardID;
const effectDescCode = old.effectDescCode;
const effectDesc = effectDescCode
? getCardStr(old.meta, effectDescCode & 0xf)
: undefined;
old.effectDesc = effectDesc;
}
}
}
}
});
};
export const resetCheckCardModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.checkCardModal.isOpen = false;
state.modalState.checkCardModal.selectMin = undefined;
state.modalState.checkCardModal.selectMax = undefined;
state.modalState.checkCardModal.cancelAble = false;
state.modalState.checkCardModal.cancelResponse = undefined;
state.modalState.checkCardModal.tags = [];
};
export const selectCheckCardModalIsOpen = (state: RootState) =>
state.duel.modalState.checkCardModal.isOpen;
export const selectCheckCardModalMinMax = (state: RootState) => {
return {
min: state.duel.modalState.checkCardModal.selectMin || 0,
max: state.duel.modalState.checkCardModal.selectMax || 0,
};
};
export const selectCheckCardModalTags = (state: RootState) =>
state.duel.modalState.checkCardModal.tags;
export const selectCheckCardModalOnSubmit = (state: RootState) =>
state.duel.modalState.checkCardModal.onSubmit;
export const selectCheckCardModalCancelAble = (state: RootState) =>
state.duel.modalState.checkCardModal.cancelAble;
export const selectCheckCardModalCacnelResponse = (state: RootState) =>
state.duel.modalState.checkCardModal.cancelResponse;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelReducer } from "../generic";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
// 更新打开状态
export const setCheckCardModalV2IsOpenImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.isOpen = action.payload;
};
// 更新选择数目
export const setCheckCardModalV2MinMaxImpl: DuelReducer<{
min: number;
max: number;
}> = (state, action) => {
state.modalState.checkCardModalV2.selectMin = action.payload.min;
state.modalState.checkCardModalV2.selectMax = action.payload.max;
};
// 更新是否可以取消
export const setCheckCardModalV2CancelAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.cancelAble = action.payload;
};
// 更新是否可以结束
export const setCheckCardModalV2FinishAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.finishAble = action.payload;
};
// 更新是否可以回应
export const setCheckCardModalV2ResponseAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.responseable = action.payload;
};
// 增加卡牌选项
export const fetchCheckCardMetasV2 = createAsyncThunk(
"duel/fetchCheckCardMetaV2",
async (param: {
selected: boolean;
options: {
code: number;
location: ygopro.CardLocation;
response: number;
}[];
}) => {
const metas = await Promise.all(
param.options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
const response = {
selected: param.selected,
metas,
};
return response;
}
);
export const checkCardModalV2Case = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMetasV2.pending, (state, action) => {
const selected = action.meta.arg.selected;
const options = action.meta.arg.options;
for (const option of options) {
if (option.code == 0) {
const newCode =
findCardByLocation(state, option.location)?.occupant?.id || 0;
option.code = newCode;
}
}
if (selected) {
state.modalState.checkCardModalV2.selectedOptions = options;
} else {
state.modalState.checkCardModalV2.selectableOptions = options;
}
});
builder.addCase(fetchCheckCardMetasV2.fulfilled, (state, action) => {
const selected = action.payload.selected;
const metas = action.payload.metas;
const options = selected
? state.modalState.checkCardModalV2.selectedOptions
: state.modalState.checkCardModalV2.selectableOptions;
options.forEach((option) => {
metas.forEach((meta) => {
if (option.code == meta.id) {
option.name = meta.text.name;
option.desc = meta.text.desc;
}
});
});
});
};
export const resetCheckCardModalV2Impl: CaseReducer<DuelState> = (state) => {
const modalState = state.modalState.checkCardModalV2;
modalState.isOpen = false;
modalState.finishAble = false;
modalState.cancelAble = false;
modalState.responseable = false;
modalState.selectableOptions = [];
modalState.selectedOptions = [];
};
export const selectCheckCardModalV2IsOpen = (state: RootState) =>
state.duel.modalState.checkCardModalV2.isOpen;
export const selectCheckCardModalV2MinMax = (state: RootState) => {
return {
min: state.duel.modalState.checkCardModalV2.selectMin || 0,
max: state.duel.modalState.checkCardModalV2.selectMax || 0,
};
};
export const selectCheckCardModalV2CancelAble = (state: RootState) =>
state.duel.modalState.checkCardModalV2.cancelAble;
export const selectCheckCardModalV2FinishAble = (state: RootState) =>
state.duel.modalState.checkCardModalV2.finishAble;
export const selectCheckCardModalV2ResponseAble = (state: RootState) =>
state.duel.modalState.checkCardModalV2.responseable;
export const selectCheckCardModalV2SelectAbleOptions = (state: RootState) =>
state.duel.modalState.checkCardModalV2.selectableOptions;
export const selectCheckCardModalV2SelectedOptions = (state: RootState) =>
state.duel.modalState.checkCardModalV2.selectedOptions;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelReducer } from "../generic";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
// 更新打开状态
export const setCheckCardModalV3IsOpenImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV3.isOpen = action.payload;
};
// 更新选择数目
export const setCheckCardModalV3MinMaxImpl: DuelReducer<{
min: number;
max: number;
}> = (state, action) => {
state.modalState.checkCardModalV3.selectMin = action.payload.min;
state.modalState.checkCardModalV3.selectMax = action.payload.max;
};
export const setCheckCardModalV3AllLevelImpl: DuelReducer<number> = (
state,
action
) => {
state.modalState.checkCardModalV3.allLevel = action.payload;
};
export const setCheckCardModalV3OverFlowImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV3.overflow = action.payload;
};
export const setCheckCardModalV3ResponseAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV3.responseable = action.payload;
};
// 增加卡牌选项
export const fetchCheckCardMetasV3 = createAsyncThunk(
"duel/fetchCheckCardMetaV3",
async (param: {
mustSelect: boolean;
options: {
code: number;
location: ygopro.CardLocation;
level1: number;
level2: number;
response: number;
}[];
}) => {
const metas = await Promise.all(
param.options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
const response = {
mustSelect: param.mustSelect,
metas,
};
return response;
}
);
export const checkCardModalV3Case = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMetasV3.pending, (state, action) => {
const mustSelect = action.meta.arg.mustSelect;
const options = action.meta.arg.options.map((option) => {
if (option.code == 0) {
const newCode =
findCardByLocation(state, option.location)?.occupant?.id || 0;
option.code = newCode;
}
return {
meta: { id: option.code, data: {}, text: {} },
level1: option.level1,
level2: option.level2,
response: option.response,
};
});
if (mustSelect) {
state.modalState.checkCardModalV3.mustSelectList = options;
} else {
state.modalState.checkCardModalV3.selectAbleList = options;
}
});
builder.addCase(fetchCheckCardMetasV3.fulfilled, (state, action) => {
const mustSelect = action.payload.mustSelect;
const metas = action.payload.metas;
const options = mustSelect
? state.modalState.checkCardModalV3.mustSelectList
: state.modalState.checkCardModalV3.selectAbleList;
options.forEach((option) => {
metas.forEach((meta) => {
if (option.meta.id == meta.id) {
option.meta = meta;
}
});
});
});
};
export const resetCheckCardModalV3Impl: CaseReducer<DuelState> = (state) => {
const modalState = state.modalState.checkCardModalV3;
modalState.isOpen = false;
modalState.overflow = false;
modalState.allLevel = 0;
modalState.responseable = undefined;
modalState.mustSelectList = [];
modalState.selectAbleList = [];
};
export const selectCheckCardModalV3 = (state: RootState) =>
state.duel.modalState.checkCardModalV3;
// 后续对于`MSG_SELECT_XXX`的处理UI都尽量用`Babylon.js`实现而不会通过`Antd`的`Modal`实现,因此这里不追求工程质量,暂时简单实现下。
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
type SelectCounter = ReturnType<
typeof ygopro.StocGameMessage.MsgSelectCounter.prototype.toObject
>;
export const setCheckCounterImpl: CaseReducer<
DuelState,
PayloadAction<SelectCounter>
> = (state, action) => {
const modal = state.modalState.checkCounterModal;
const payload = action.payload;
modal.counterType = payload.counter_type;
modal.min = payload.min;
modal.options = payload.options!.map((option) => {
const code = option.code
? option.code
: findCardByLocation(state, option.location!)?.occupant?.id || 0;
return {
code,
max: option.counter_count!,
};
});
modal.isOpen = true;
};
export const clearCheckCounterImpl: CaseReducer<DuelState> = (state) => {
state.modalState.checkCounterModal.isOpen = false;
state.modalState.checkCounterModal.min = undefined;
state.modalState.checkCounterModal.counterType = undefined;
state.modalState.checkCounterModal.options = [];
};
export const selectCheckCounterModal = (state: RootState) =>
state.duel.modalState.checkCounterModal;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { fetchCard, getCardStr } from "@/api/cards";
import { RootState } from "@/store";
import { DuelState } from "../mod";
export const setOptionModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.optionModal.isOpen = action.payload;
};
export const resetOptionModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.optionModal.options = [];
};
// 增加选项
export const fetchOptionMeta = createAsyncThunk(
"duel/fetchOptionMeta",
async (param: { code: number; response: number }) => {
const meta = await fetchCard(param.code >> 4, true);
const msg = getCardStr(meta, param.code & 0xf) || "[?]";
const response = { msg, response: param.response };
return response;
}
);
export const optionModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchOptionMeta.fulfilled, (state, action) => {
state.modalState.optionModal.options.push(action.payload);
});
};
export const selectOptionModalIsOpen = (state: RootState) =>
state.duel.modalState.optionModal.isOpen;
export const selectOptionModalOptions = (state: RootState) =>
state.duel.modalState.optionModal.options;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "../mod";
export const setPositionModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.positionModal.isOpen = action.payload;
};
export const setPositionModalPositionsImpl: CaseReducer<
DuelState,
PayloadAction<ygopro.CardPosition[]>
> = (state, action) => {
state.modalState.positionModal.positions = action.payload;
};
export const resetPositionModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.positionModal.isOpen = false;
state.modalState.positionModal.positions = [];
};
export const selectPositionModalIsOpen = (state: RootState) =>
state.duel.modalState.positionModal.isOpen;
export const selectPositionModalPositions = (state: RootState) =>
state.duel.modalState.positionModal.positions;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelReducer } from "../generic";
import { DuelState } from "../mod";
type SortCard = ReturnType<
typeof ygopro.StocGameMessage.MsgSortCard.Info.prototype.toObject
>;
export const setSortCardModalIsOpenImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.sortCardModal.isOpen = action.payload;
};
export const resetSortCardModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.sortCardModal.isOpen = false;
state.modalState.sortCardModal.options = [];
};
export const fetchSortCardMeta = createAsyncThunk(
"duel/fetchSortCardMeta",
async (param: SortCard) => {
const meta = await fetchCard(param.code!, true);
return {
meta,
response: param.response!,
};
}
);
export const sortCardModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
// 这里更合理的做法是`pending`的时候先更新`options`,等`meta`数据返回后再异步更新`meta`
builder.addCase(fetchSortCardMeta.fulfilled, (state, action) => {
state.modalState.sortCardModal.options.push(action.payload);
});
};
export const selectSortCardModal = (state: RootState) =>
state.duel.modalState.sortCardModal;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchStrings, getStrings } from "@/api/strings";
import { RootState } from "@/store";
import { DuelState } from "../mod";
// 更新YesNo弹窗是否打开状态
export const setYesNoModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.yesNoModal.isOpen = action.payload;
};
// 设置YesNo弹窗展示内容
export const fetchYesNoMeta = createAsyncThunk(
"duel/fetchYesNoMeta",
async (param: {
code: number;
location: ygopro.CardLocation;
descCode: number;
textGenerator: (
desc: string,
cardMeta: CardMeta,
cardLocation: ygopro.CardLocation
) => string;
}) => {
const desc = fetchStrings("!system", param.descCode);
const meta = await fetchCard(param.code, true);
// TODO: 国际化文案
return param.textGenerator(desc, meta, param.location);
}
);
export const fetchYesNoMetaWithEffecDesc = createAsyncThunk(
"duel/fetchYesNoMetaWithEffecDesc",
async (effectDesc: number) => {
return getStrings(effectDesc);
}
);
export const YesNoModalCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchYesNoMeta.fulfilled, (state, action) => {
state.modalState.yesNoModal.msg = action.payload;
});
builder.addCase(fetchYesNoMetaWithEffecDesc.fulfilled, (state, action) => {
state.modalState.yesNoModal.msg = action.payload;
});
};
export const selectYesNoModalIsOpen = (state: RootState) =>
state.duel.modalState.yesNoModal.isOpen;
export const selectYesNOModalMsg = (state: RootState) =>
state.duel.modalState.yesNoModal.msg;
This diff is collapsed.
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
import { DuelState } from "./mod";
export interface PhaseState {
currentPhase: string; // 当前的阶段
enableBp: boolean; // 允许进入战斗阶段
enableM2: boolean; // 允许进入M2阶段
enableEp: boolean; // 允许回合结束
}
export const newPhaseImpl: CaseReducer<DuelState, PayloadAction<string>> = (
state,
action
) => {
if (state.phase) {
state.phase.currentPhase = action.payload;
} else {
state.phase = {
currentPhase: action.payload,
enableBp: false,
enableM2: false,
enableEp: false,
};
}
};
export const setEnableBpImpl: CaseReducer<DuelState, PayloadAction<boolean>> = (
state,
action
) => {
if (state.phase) {
state.phase.enableBp = action.payload;
}
};
export const setEnableM2Impl: CaseReducer<DuelState, PayloadAction<boolean>> = (
state,
action
) => {
if (state.phase) {
state.phase.enableM2 = action.payload;
}
};
export const setEnableEpImpl: CaseReducer<DuelState, PayloadAction<boolean>> = (
state,
action
) => {
if (state.phase) {
state.phase.enableEp = action.payload;
}
};
export const selectCurrentPhase = (state: RootState) =>
state.duel.phase?.currentPhase;
export const selectEnableBp = (state: RootState) =>
state.duel.phase?.enableBp || false;
export const selectEnableM2 = (state: RootState) =>
state.duel.phase?.enableBp || false;
export const selectEnableEp = (state: RootState) =>
state.duel.phase?.enableEp || false;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface TimeLimit {
leftTime: number;
}
// 更新计时
export const updateTimeLimitImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const player = action.payload[0];
const leftTime = action.payload[1];
if (judgeSelf(player, state)) {
state.meTimeLimit = { leftTime };
} else {
state.opTimeLimit = { leftTime };
}
};
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export const newTurnImpl: CaseReducer<DuelState, PayloadAction<number>> = (
state,
action
) => {
state.currentPlayer = action.payload;
};
export const selectCurrentPlayerIsMe = (state: RootState) =>
judgeSelf(state.duel.currentPlayer!, state.duel);
/*
* 对局内状态更新逻辑的一些共用函数和数据结构
*
* */
import { Draft } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { CardState } from "./generic";
import { DuelState } from "./mod";
type Location =
| ygopro.CardLocation
| ReturnType<typeof ygopro.CardLocation.prototype.toObject>;
/*
* 通过`player`和`selfType`判断是应该处理自己还是对手
* */
export function judgeSelf(player: number, state: Draft<DuelState>): boolean {
const selfType = state.selfType;
if (selfType === 1) {
// 自己是先攻
return player == 0;
} else if (selfType === 2) {
// 自己是后攻
return player == 1;
} else {
// currently never reach
return false;
}
}
/*
* 通过`controler`,`zone`和`sequence`获取卡牌状态*/
export function findCardByLocation(
state: Draft<DuelState>,
location: Location
): CardState | undefined {
const controler = location.controler!;
const zone = location.location;
const sequence = location.sequence;
const finder = (_: any, idx: number) => idx == sequence;
switch (zone) {
case ygopro.CardZone.HAND: {
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
return hands?.inner.find(finder);
}
case ygopro.CardZone.MZONE: {
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
return monsters?.inner.find(finder);
}
case ygopro.CardZone.SZONE: {
const magics = judgeSelf(controler, state)
? state.meMagics
: state.opMagics;
return magics?.inner.find(finder);
}
case ygopro.CardZone.REMOVED: {
const banishedZones = judgeSelf(controler, state)
? state.meBanishedZone
: state.opBanishedZone;
return banishedZones?.inner.find(finder);
}
case ygopro.CardZone.GRAVE: {
const cemerety = judgeSelf(controler, state)
? state.meGraveyard
: state.opGraveyard;
return cemerety?.inner.find(finder);
}
default: {
return undefined;
}
}
}
export function cmpCardLocation(
left: Location,
right?: Location,
strict?: boolean
): boolean {
if (strict) {
return JSON.stringify(left) === JSON.stringify(right);
} else {
return (
left.controler === right?.controler &&
left.location === right?.location &&
left.sequence === right?.sequence
);
}
}
/*
* 加入房间状态更新逻辑
*
* */
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface JoinState {
value: boolean;
}
const initialState: JoinState = {
value: false,
};
const joinedSlice = createSlice({
name: "join",
initialState,
reducers: {
setJoined: (state) => {
state.value = true;
},
setUnJoined: (state) => {
state.value = false;
},
},
});
export const { setJoined, setUnJoined } = joinedSlice.actions;
export const selectJoined = (state: RootState) => state.join.value;
export default joinedSlice.reducer;
/*
* 猜拳页面的状态更新逻辑
*
* */
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface moraState {
duelStart: boolean;
selectHandAble: boolean;
selectTpAble: boolean;
}
const initialState: moraState = {
duelStart: false,
selectHandAble: false,
selectTpAble: false,
};
const moraSlice = createSlice({
name: "mora",
initialState,
reducers: {
duelStart: (state) => {
state.duelStart = true;
},
selectHandAble: (state) => {
state.selectHandAble = true;
},
unSelectHandAble: (state) => {
state.selectHandAble = false;
},
selectTpAble: (state) => {
state.selectTpAble = true;
},
unSelectTpAble: (state) => {
state.selectTpAble = false;
},
},
});
export const {
duelStart,
selectHandAble,
unSelectHandAble,
selectTpAble,
unSelectTpAble,
} = moraSlice.actions;
export const selectDuelStart = (state: RootState) => state.mora.duelStart;
export const selectHandSelectAble = (state: RootState) =>
state.mora.selectHandAble;
export const selectTpSelectAble = (state: RootState) => state.mora.selectTpAble;
export default moraSlice.reducer;
/*
* 进入房间的玩家状态更新逻辑
*
* */
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface Player {
name?: string;
state?: string;
isHost?: boolean;
deckInfo?: deckInfo;
}
export interface deckInfo {
mainCnt: number;
extraCnt: number;
sideCnt: number;
}
export interface playerState {
player0: Player;
player1: Player;
observerCount: number;
isHost: boolean;
}
const initialState: playerState = {
player0: {},
player1: {},
observerCount: 0,
isHost: false,
};
const playerSlice = createSlice({
name: "player",
initialState,
reducers: {
player0Enter: (state, action: PayloadAction<string>) => {
state.player0.name = action.payload;
},
player1Enter: (state, action: PayloadAction<string>) => {
state.player1.name = action.payload;
},
player0Update: (state, action: PayloadAction<string>) => {
state.player0.state = action.payload;
},
player1Update: (state, action: PayloadAction<string>) => {
state.player1.state = action.payload;
},
player0Leave: (state) => {
state.player0 = {};
},
player1Leave: (state) => {
state.player1 = {};
},
player0DeckInfo: (state, action: PayloadAction<deckInfo>) => {
state.player0.deckInfo = action.payload;
},
player1DeckInfo: (state, action: PayloadAction<deckInfo>) => {
state.player1.deckInfo = action.payload;
},
hostChange: (state, action: PayloadAction<number>) => {
const i = action.payload;
if (i === 0) {
state.player0.isHost = true;
state.player1.isHost = false;
} else {
state.player1.isHost = true;
state.player0.isHost = false;
}
},
observerIncrement: (state) => {
state.observerCount += 1;
},
observerChange: (state, action: PayloadAction<number>) => {
state.observerCount = action.payload;
},
updateIsHost: (state, action: PayloadAction<boolean>) => {
state.isHost = action.payload;
},
},
});
export const {
player0Enter,
player1Enter,
player0Update,
player1Update,
player0Leave,
player1Leave,
player0DeckInfo,
player1DeckInfo,
hostChange,
observerIncrement,
observerChange,
updateIsHost,
} = playerSlice.actions;
export const selectPlayer0 = (state: RootState) => state.player.player0;
export const selectPlayer1 = (state: RootState) => state.player.player1;
export const selectIsHost = (state: RootState) => state.player.isHost;
export const selectObserverCount = (state: RootState) =>
state.player.observerCount;
export default playerSlice.reducer;
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/stores";
export default (
attack: ygopro.StocGameMessage.MsgAttack,
dispatch: AppDispatch
) => {
dispatch(
fetchEsHintMeta({ originMsg: "「[?]」攻击时", location: attack.location })
);
export default (attack: ygopro.StocGameMessage.MsgAttack) => {
fetchEsHintMeta({
originMsg: "「[?]」攻击时",
location: attack.location,
});
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/stores";
export default (
_: ygopro.StocGameMessage.MsgAttackDisabled,
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: "攻击被无效时" }));
export default (_: ygopro.StocGameMessage.MsgAttackDisabled) => {
fetchEsHintMeta({ originMsg: "攻击被无效时" });
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
export default (
chaining: ygopro.StocGameMessage.MsgChaining,
dispatch: AppDispatch
) => {
dispatch(
fetchEsHintMeta({ originMsg: "「[?]」被发动时", cardID: chaining.code })
);
import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/stores";
export default (chaining: ygopro.StocGameMessage.MsgChaining) => {
fetchEsHintMeta({
originMsg: "「[?]」被发动时",
cardID: chaining.code,
});
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchHandsMeta } from "@/reducers/duel/handsSlice";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { fetchEsHintMeta, matStore } from "@/stores";
export default (
draw: ygopro.StocGameMessage.MsgDraw,
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: "玩家抽卡时" }));
dispatch(fetchHandsMeta({ controler: draw.player, codes: draw.cards }));
export default (draw: ygopro.StocGameMessage.MsgDraw) => {
fetchEsHintMeta({ originMsg: "玩家抽卡时" });
matStore.hands.of(draw.player).add(draw.cards);
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/stores";
export default (
_: ygopro.StocGameMessage.MsgFlipSummoned,
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: 1608 }));
export default (_: ygopro.StocGameMessage.MsgFlipSummoned) => {
fetchEsHintMeta({ originMsg: 1608 });
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/stores";
export default (
flipSummoning: ygopro.StocGameMessage.MsgFlipSummoning,
dispatch: AppDispatch
) => {
dispatch(
fetchEsHintMeta({
originMsg: "「[?]」反转召唤宣言时",
cardID: flipSummoning.code,
})
);
export default (flipSummoning: ygopro.StocGameMessage.MsgFlipSummoning) => {
fetchEsHintMeta({
originMsg: "「[?]」反转召唤宣言时",
cardID: flipSummoning.code,
});
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { setWaiting } from "@/reducers/duel/mod";
import { store } from "@/store";
import { ygopro } from "@/api";
import { matStore } from "@/stores";
import onMsgAttack from "./attack";
import onMsgAttackDisable from "./attackDisable";
......@@ -57,211 +56,210 @@ const ActiveList = [
];
export default function handleGameMsg(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const msg = pb.stoc_game_msg;
if (ActiveList.includes(msg.gameMsg)) {
dispatch(setWaiting(false));
matStore.waiting = false;
}
switch (msg.gameMsg) {
case "start": {
onMsgStart(msg.start, dispatch);
onMsgStart(msg.start);
break;
}
case "draw": {
onMsgDraw(msg.draw, dispatch);
onMsgDraw(msg.draw);
break;
}
case "new_turn": {
onMsgNewTurn(msg.new_turn, dispatch);
onMsgNewTurn(msg.new_turn);
break;
}
case "new_phase": {
onMsgNewPhase(msg.new_phase, dispatch);
onMsgNewPhase(msg.new_phase);
break;
}
case "hint": {
onMsgHint(msg.hint, dispatch);
onMsgHint(msg.hint);
break;
}
case "select_idle_cmd": {
onMsgSelectIdleCmd(msg.select_idle_cmd, dispatch);
onMsgSelectIdleCmd(msg.select_idle_cmd);
break;
}
case "select_place": {
onMsgSelectPlace(msg.select_place, dispatch);
onMsgSelectPlace(msg.select_place);
break;
}
case "move": {
onMsgMove(msg.move, dispatch);
onMsgMove(msg.move);
break;
}
case "select_card": {
onMsgSelectCard(msg.select_card, dispatch);
onMsgSelectCard(msg.select_card);
break;
}
case "select_chain": {
onMsgSelectChain(msg.select_chain, dispatch);
onMsgSelectChain(msg.select_chain);
break;
}
case "select_effect_yn": {
onMsgSelectEffectYn(msg.select_effect_yn, dispatch);
onMsgSelectEffectYn(msg.select_effect_yn);
break;
}
case "select_position": {
onMsgSelectPosition(msg.select_position, dispatch);
onMsgSelectPosition(msg.select_position);
break;
}
case "select_option": {
onMsgSelectOption(msg.select_option, dispatch);
onMsgSelectOption(msg.select_option);
break;
}
case "shuffle_hand": {
onMsgShuffleHand(msg.shuffle_hand, dispatch);
onMsgShuffleHand(msg.shuffle_hand);
break;
}
case "select_battle_cmd": {
onMsgSelectBattleCmd(msg.select_battle_cmd, dispatch);
onMsgSelectBattleCmd(msg.select_battle_cmd);
break;
}
case "pos_change": {
onMsgPosChange(msg.pos_change, dispatch);
onMsgPosChange(msg.pos_change);
break;
}
case "select_unselect_card": {
onMsgSelectUnselectCard(msg.select_unselect_card, dispatch);
onMsgSelectUnselectCard(msg.select_unselect_card);
break;
}
case "select_yes_no": {
onMsgSelectYesNo(msg.select_yes_no, dispatch);
onMsgSelectYesNo(msg.select_yes_no);
break;
}
case "update_hp": {
onMsgUpdateHp(msg.update_hp, dispatch);
onMsgUpdateHp(msg.update_hp);
break;
}
case "win": {
onMsgWin(msg.win, dispatch);
onMsgWin(msg.win);
break;
}
case "wait": {
onMsgWait(msg.wait, dispatch);
onMsgWait(msg.wait);
break;
}
case "update_data": {
onMsgUpdateData(msg.update_data, dispatch);
onMsgUpdateData(msg.update_data);
break;
}
case "reload_field": {
onMsgReloadField(msg.reload_field, dispatch);
onMsgReloadField(msg.reload_field);
break;
}
case "select_sum": {
onMsgSelectSum(msg.select_sum, dispatch);
onMsgSelectSum(msg.select_sum);
break;
}
case "select_tribute": {
onMsgSelectTribute(msg.select_tribute, dispatch);
onMsgSelectTribute(msg.select_tribute);
break;
}
case "update_counter": {
onMsgUpdateCounter(msg.update_counter, dispatch);
onMsgUpdateCounter(msg.update_counter);
break;
}
case "select_counter": {
onMsgSelectCounter(msg.select_counter, dispatch);
onMsgSelectCounter(msg.select_counter);
break;
}
case "sort_card": {
onMsgSortCard(msg.sort_card, dispatch);
onMsgSortCard(msg.sort_card);
break;
}
case "set": {
onMsgSet(msg.set, dispatch);
onMsgSet(msg.set);
break;
}
case "swap": {
onMsgSwap(msg.swap, dispatch);
onMsgSwap(msg.swap);
break;
}
case "attack": {
onMsgAttack(msg.attack, dispatch);
onMsgAttack(msg.attack);
break;
}
case "attack_disable": {
onMsgAttackDisable(msg.attack_disable, dispatch);
onMsgAttackDisable(msg.attack_disable);
break;
}
case "chaining": {
onMsgChaining(msg.chaining, dispatch);
onMsgChaining(msg.chaining);
break;
}
case "summoning": {
onMsgSummoning(msg.summoning, dispatch);
onMsgSummoning(msg.summoning);
break;
}
case "summoned": {
onMsgSummoned(msg.summoned, dispatch);
onMsgSummoned(msg.summoned);
break;
}
case "flip_summoning": {
onMsgFlipSummoning(msg.flip_summoning, dispatch);
onMsgFlipSummoning(msg.flip_summoning);
break;
}
case "flip_summoned": {
onMsgFilpSummoned(msg.flip_summoned, dispatch);
onMsgFilpSummoned(msg.flip_summoned);
break;
}
case "sp_summoning": {
onMsgSpSummoning(msg.sp_summoning, dispatch);
onMsgSpSummoning(msg.sp_summoning);
break;
}
case "sp_summoned": {
onMsgSpSummoned(msg.sp_summoned, dispatch);
onMsgSpSummoned(msg.sp_summoned);
break;
}
case "unimplemented": {
onUnimplemented(msg.unimplemented, dispatch);
onUnimplemented(msg.unimplemented);
break;
}
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { ygopro } from "@/api";
import {
fetchCommonHintMeta,
fetchEsHintMeta,
fetchSelectHintMeta,
} from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
} from "@/stores";
import MsgHint = ygopro.StocGameMessage.MsgHint;
export default (hint: MsgHint, dispatch: AppDispatch) => {
export default (hint: MsgHint) => {
switch (hint.hint_type) {
case MsgHint.HintType.HINT_EVENT: {
dispatch(fetchEsHintMeta({ originMsg: hint.hint_data }));
fetchEsHintMeta({ originMsg: hint.hint_data });
break;
}
case MsgHint.HintType.HINT_MESSAGE: {
dispatch(fetchCommonHintMeta(hint.hint_data));
fetchCommonHintMeta(hint.hint_data);
break;
}
case MsgHint.HintType.HINT_SELECTMSG: {
dispatch(
fetchSelectHintMeta({ selectHintData: hint.hint_data, esHint: "" })
);
fetchSelectHintMeta({
selectHintData: hint.hint_data,
esHint: "",
});
break;
}
default: {
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import MsgMove = ygopro.StocGameMessage.MsgMove;
import { fetchBanishedZoneMeta } from "@/reducers/duel/banishedZoneSlice";
import { fetchExtraDeckMeta } from "@/reducers/duel/extraDeckSlice";
import { fetchGraveyardMeta } from "@/reducers/duel/graveyardSlice";
import { insertHandMeta } from "@/reducers/duel/handsSlice";
import { fetchMagicMeta } from "@/reducers/duel/magicSlice";
import {
removeBanishedZone,
removeExtraDeck,
removeGraveyard,
removeHand,
removeMagic,
removeMonster,
removeOverlay,
} from "@/reducers/duel/mod";
import {
fetchMonsterMeta,
fetchOverlayMeta,
} from "@/reducers/duel/monstersSlice";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { fetchOverlayMeta, store } from "@/stores";
type MsgMove = ygopro.StocGameMessage.MsgMove;
import { REASON_MATERIAL } from "../../common";
const { matStore } = store;
const OVERLAY_STACK: { code: number; sequence: number }[] = [];
export default (move: MsgMove, dispatch: AppDispatch) => {
export default (move: MsgMove) => {
const code = move.code;
const from = move.from;
const to = move.to;
const reason = move.reason;
switch (from.location) {
case ygopro.CardZone.HAND: {
dispatch(removeHand([from.controler, from.sequence]));
break;
}
case ygopro.CardZone.MZONE: {
dispatch(
removeMonster({ controler: from.controler, sequence: from.sequence })
);
// TODO: 如果后面做动画的话,要考虑DECK的情况。
// 现在不会对DECK做判断
break;
}
switch (from.location) {
case ygopro.CardZone.MZONE:
case ygopro.CardZone.SZONE: {
dispatch(
removeMagic({ controler: from.controler, sequence: from.sequence })
);
break;
}
case ygopro.CardZone.GRAVE: {
dispatch(
removeGraveyard({ controler: from.controler, sequence: from.sequence })
);
break;
}
case ygopro.CardZone.REMOVED: {
dispatch(
removeBanishedZone({
controler: from.controler,
sequence: from.sequence,
})
);
// 魔陷和怪兽需要清掉占用、清掉超量素材
const target = matStore.in(from.location).of(from.controler)[
from.sequence
];
target.occupant = undefined;
target.overlay_materials = [];
break;
}
case ygopro.CardZone.REMOVED:
case ygopro.CardZone.GRAVE:
case ygopro.CardZone.HAND:
case ygopro.CardZone.EXTRA: {
dispatch(
removeExtraDeck({ controler: from.controler, sequence: from.sequence })
);
// 其余区域就是在list删掉这张卡
matStore.in(from.location).of(from.controler).remove(from.sequence);
break;
}
// 仅仅去除超量素材
case ygopro.CardZone.OVERLAY: {
dispatch(
removeOverlay({
controler: from.controler,
sequence: from.sequence,
overlaySequence: from.overlay_sequence,
})
);
const target = matStore.monsters.of(from.controler)[from.sequence];
if (target && target.overlay_materials) {
target.overlay_materials.splice(from.overlay_sequence, 1);
}
break;
}
default: {
......@@ -92,81 +51,28 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
}
switch (to.location) {
// @ts-ignore
case ygopro.CardZone.MZONE: {
dispatch(
fetchMonsterMeta({
controler: to.controler,
sequence: to.sequence,
position: to.position,
code,
})
);
// 处理超量素材
// 设置超量素材
const overlayMetarials = OVERLAY_STACK.splice(0, OVERLAY_STACK.length);
let sorted = overlayMetarials
const sorted = overlayMetarials
.sort((a, b) => a.sequence - b.sequence)
.map((overlay) => overlay.code);
dispatch(
fetchOverlayMeta({
controler: to.controler,
sequence: to.sequence,
overlayCodes: sorted,
})
);
break;
fetchOverlayMeta(to.controler, to.sequence, sorted);
// 设置Occupant,和魔陷区/其他区共用一个逻辑,特地不写break
}
case ygopro.CardZone.SZONE: {
dispatch(
fetchMagicMeta({
controler: to.controler,
sequence: to.sequence,
position: to.position,
code,
})
);
break;
}
case ygopro.CardZone.GRAVE: {
dispatch(
fetchGraveyardMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
matStore
.in(to.location)
.of(to.controler)
.setOccupant(to.sequence, code, to.position);
break;
}
case ygopro.CardZone.REMOVED:
case ygopro.CardZone.GRAVE:
case ygopro.CardZone.EXTRA:
case ygopro.CardZone.HAND: {
dispatch(
insertHandMeta({ controler: to.controler, sequence: to.sequence, code })
);
break;
}
case ygopro.CardZone.REMOVED: {
dispatch(
fetchBanishedZoneMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
break;
}
case ygopro.CardZone.EXTRA: {
dispatch(
fetchExtraDeckMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
matStore.in(to.location).of(to.controler).insert(to.sequence, code);
break;
}
case ygopro.CardZone.OVERLAY: {
......@@ -176,21 +82,12 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
OVERLAY_STACK.push({ code, sequence: to.overlay_sequence });
} else {
// 其他情况下,比如“宵星的机神 丁吉尔苏”的“补充超量素材”效果,直接更新状态中
dispatch(
fetchOverlayMeta({
controler: to.controler,
sequence: to.sequence,
overlayCodes: [code],
append: true,
})
);
fetchOverlayMeta(to.controler, to.sequence, [code], true);
}
break;
}
default: {
console.log(`Unhandled zone type ${to.location}`);
break;
}
}
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { updatePhase } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { matStore, type PhaseName } from "@/stores";
export default (
newPhase: ygopro.StocGameMessage.MsgNewPhase,
dispatch: AppDispatch
) => {
dispatch(
updatePhase(
ygopro.StocGameMessage.MsgNewPhase.PhaseType[newPhase.phase_type]
)
);
export default (newPhase: ygopro.StocGameMessage.MsgNewPhase) => {
// ts本身还没有这么智能,所以需要手动指定类型
const currentPhase = ygopro.StocGameMessage.MsgNewPhase.PhaseType[
newPhase.phase_type
] as PhaseName;
matStore.phase.currentPhase = currentPhase;
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { updateTurn } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import { matStore } from "@/stores";
export default (
newTurn: ygopro.StocGameMessage.MsgNewTurn,
dispatch: AppDispatch
) => {
export default (newTurn: ygopro.StocGameMessage.MsgNewTurn) => {
const player = newTurn.player;
dispatch(updateTurn(player));
matStore.currentPlayer = player;
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { setMagicPosition, setMonsterPosition } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import { ygopro } from "@/api";
import MsgPosChange = ygopro.StocGameMessage.MsgPosChange;
import { fetchEsHintMeta, matStore } from "@/stores";
export default (posChange: MsgPosChange) => {
const { location, controler, sequence } = posChange.card_info;
export default (posChange: MsgPosChange, dispatch: AppDispatch) => {
const cardInfo = posChange.card_info;
switch (cardInfo.location) {
switch (location) {
case ygopro.CardZone.MZONE: {
dispatch(
setMonsterPosition({
controler: cardInfo.controler,
sequence: cardInfo.sequence,
position: posChange.cur_position,
})
);
matStore.monsters.of(controler)[sequence].location.position =
posChange.cur_position;
break;
}
case ygopro.CardZone.SZONE: {
dispatch(
setMagicPosition({
controler: cardInfo.controler,
sequence: cardInfo.sequence,
position: posChange.cur_position,
})
);
matStore.magics.of(controler)[sequence].location.position =
posChange.cur_position;
break;
}
default: {
console.log(`Unhandled zone ${cardInfo.location}`);
console.log(`Unhandled zone ${location}`);
}
}
dispatch(fetchEsHintMeta({ originMsg: 1600 }));
fetchEsHintMeta({
originMsg: 1600,
});
};
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { reloadField } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
import { ygopro } from "@/api";
import { matStore } from "@/stores";
export default (field: MsgReloadField, dispatch: AppDispatch) => {
dispatch(reloadField(field));
type MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
type ZoneActions = ygopro.StocGameMessage.MsgReloadField.ZoneAction[];
export default (field: MsgReloadField) => {
const _duel_rule = field.duel_rule; // TODO: duel_rule
const gamers = ["me", "op"] as const;
gamers.forEach((gamer) => {
matStore.banishedZones[gamer].length = 0;
matStore.extraDecks[gamer].length = 0;
matStore.graveyards[gamer].length = 0;
matStore.hands[gamer].length = 0;
matStore.monsters[gamer].length = 0;
matStore.magics[gamer].length = 0;
});
const { MZONE, SZONE, HAND, DECK, GRAVE, REMOVED, EXTRA } = ygopro.CardZone;
const zones = [MZONE, SZONE, HAND, DECK, GRAVE, REMOVED, EXTRA] as const;
field.actions.forEach(({ player, zone_actions }) => {
zones.forEach((zone) => {
reloadDuelField(
zone,
zone_actions.filter((item) => item.zone === zone),
player
);
});
});
};
/** 可以理解成reload DuelFieldState */
function reloadDuelField(
cardZone: ygopro.CardZone,
zoneActions: ZoneActions,
controller: number
) {
zoneActions.sort((a, b) => a.sequence - b.sequence);
const cards = zoneActions.map((action) => {
// FIXME: OVERLAY
return {
location: {
controler: controller,
location: action.zone,
position: action.position,
},
idleInteractivities: [],
counters: {},
reload: true,
};
});
matStore.in(cardZone).of(controller).length = 0;
matStore
.in(cardZone)
.of(controller)
.push(...cards);
}
import { ActionCreatorWithPayload } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { Interactivity, InteractType } from "@/reducers/duel/generic";
import { ygopro } from "@/api";
import {
addHandsIdleInteractivity,
addMagicIdleInteractivities,
addMonsterIdleInteractivities,
clearAllIdleInteractivities,
setEnableEp,
setEnableM2,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
clearAllIdleInteractivities as clearAllIdleInteractivities,
type Interactivity,
InteractType,
matStore,
} from "@/stores";
import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd;
export default (selectBattleCmd: MsgSelectBattleCmd, dispatch: AppDispatch) => {
export default (selectBattleCmd: MsgSelectBattleCmd) => {
const player = selectBattleCmd.player;
const cmds = selectBattleCmd.battle_cmds;
// 先清掉之前的互动性
dispatch(clearAllIdleInteractivities(player));
const dispatcher = (
battleData: MsgSelectBattleCmd.BattleCmd.BattleData,
interactType: InteractType | undefined,
actionCreator: ActionCreatorWithPayload<
{
player: number;
sequence: number;
interactivity: Interactivity<number>;
},
string
>
) => {
const cardInfo = battleData.card_info;
if (interactType === InteractType.ACTIVATE) {
dispatch(
actionCreator({
player,
sequence: cardInfo.sequence,
interactivity: {
interactType,
activateIndex: battleData.effect_description,
response: battleData.response,
},
})
);
} else if (interactType === InteractType.ATTACK) {
dispatch(
actionCreator({
player,
sequence: cardInfo.sequence,
interactivity: {
interactType,
directAttackAble: battleData.direct_attackable,
response: battleData.response,
},
})
);
} else {
console.log(`Unhandled InteractType`);
}
};
clearAllIdleInteractivities(player);
cmds.forEach((cmd) => {
const interactType = battleTypeToInteracType(cmd.battle_type);
cmd.battle_datas.forEach((data) => {
const cardInfo = data.card_info;
switch (cardInfo.location) {
case ygopro.CardZone.HAND: {
dispatcher(data, interactType, addHandsIdleInteractivity);
break;
}
case ygopro.CardZone.MZONE: {
dispatcher(data, interactType, addMonsterIdleInteractivities);
break;
}
case ygopro.CardZone.SZONE: {
dispatcher(data, interactType, addMagicIdleInteractivities);
const { location, sequence } = data.card_info;
break;
}
default: {
}
// valtio
if (interactType) {
const map: Partial<
Record<InteractType, undefined | Partial<Interactivity<number>>>
> = {
[InteractType.ACTIVATE]: { activateIndex: data.effect_description },
[InteractType.ATTACK]: { directAttackAble: data.direct_attackable },
};
const tmp = map[interactType]; // 添加额外信息
matStore
.in(location)
.of(player)
.addIdleInteractivity(sequence, {
...tmp,
interactType,
response: data.response,
});
} else {
console.warn(`Undefined InteractType`);
}
});
});
dispatch(setEnableM2(selectBattleCmd.enable_m2));
dispatch(setEnableEp(selectBattleCmd.enable_ep));
matStore.phase.enableM2 = selectBattleCmd.enable_m2;
matStore.phase.enableEp = selectBattleCmd.enable_ep;
};
function battleTypeToInteracType(
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
export * from "./methods";
export * from "./store";
export * from "./types";
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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