Commit 4691e0ef authored by Chunchi Che's avatar Chunchi Che

remove card states in matStore

parent 7ff59210
import { ygopro } from "@/api";
import { fetchCard, getCardStr } from "@/api/cards";
import { matStore, messageStore } from "@/stores";
export const fetchCheckCardMeta = async (
{
code,
location,
level1,
level2,
response,
effectDescCode,
}: {
code: number;
location: ygopro.CardLocation;
level1?: number;
level2?: number;
response: number;
effectDescCode?: number;
},
selected?: boolean,
mustSelect?: boolean
) => {
const controller = location.controler;
const newID =
code != 0
? code
: matStore.in(location.location).of(controller)[location.sequence]
?.occupant?.id || 0;
const meta = await fetchCard(newID);
const effectDesc = effectDescCode
? getCardStr(meta, effectDescCode & 0xf)
: undefined;
const newOption = {
meta,
location: location.toObject(),
level1,
level2,
effectDesc,
response,
};
if (selected) {
messageStore.selectCardActions.selecteds.push(newOption);
} else if (mustSelect) {
messageStore.selectCardActions.mustSelects.push(newOption);
} else {
messageStore.selectCardActions.selectables.push(newOption);
}
};
import type { ygopro } from "@/api"; import type { ygopro } from "@/api";
import { DESCRIPTION_LIMIT, fetchStrings, getStrings } from "@/api"; import { DESCRIPTION_LIMIT, fetchStrings, getStrings } from "@/api";
import { fetchCard } from "@/api/cards"; import { fetchCard } from "@/api/cards";
import { cardStore } from "@/stores/cardStore";
import { matStore } from "../store"; import { matStore } from "../store";
...@@ -63,12 +64,13 @@ export const fetchEsHintMeta = async ({ ...@@ -63,12 +64,13 @@ export const fetchEsHintMeta = async ({
} }
if (location) { if (location) {
const fieldMeta = matStore const fieldMeta = cardStore.at(
.in(location.location) location.location,
.of(location.controler) location.controler,
.at(location.sequence); location.sequence
if (fieldMeta?.occupant?.text.name) { );
esHint = esHint.replace("[?]", fieldMeta.occupant.text.name); if (fieldMeta.text.name) {
esHint = esHint.replace("[?]", fieldMeta.text.name);
} }
} }
......
import { fetchCard } from "@/api";
import { matStore } from "@/stores";
export const fetchOverlayMeta = async (
controller: number,
sequence: number,
overlayCodes: number[],
append?: boolean
) => {
const metas = await Promise.all(
overlayCodes.map(async (id) => await fetchCard(id))
);
const target = matStore.monsters.of(controller)[sequence];
if (target && target.occupant) {
if (append) {
target.overlay_materials = (target.overlay_materials || []).concat(metas);
} else {
target.overlay_materials = metas;
}
}
};
import type { ygopro } from "@/api";
import { matStore } from "@/stores";
export const getCardByLocation = (location: ygopro.CardLocation) => {
return matStore.in(location.location).of(location.controler)[
location.sequence
];
};
export * from "./fetchCheckCardMeta";
export * from "./fetchHint"; export * from "./fetchHint";
export * from "./fetchOverlayMeta";
export * from "./getCardByLocation";
/* eslint valtio/avoid-this-in-proxy: 0 */ /* eslint valtio/avoid-this-in-proxy: 0 */
import { cloneDeep } from "lodash-es";
import { v4 as v4uuid } from "uuid";
import { proxy } from "valtio"; import { proxy } from "valtio";
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { fetchCard } from "@/api/cards";
import type { import type { InitInfo, MatState } from "./types";
CardState,
DuelFieldState as ArrayCardState,
InitInfo,
MatState,
} from "./types";
import { InteractType } from "./types";
/** /**
* 根据controller判断是自己还是对方。 * 根据controller判断是自己还是对方。
...@@ -21,122 +12,6 @@ import { InteractType } from "./types"; ...@@ -21,122 +12,6 @@ import { InteractType } from "./types";
const getWhom = (controller: number): "me" | "op" => const getWhom = (controller: number): "me" | "op" =>
isMe(controller) ? "me" : "op"; isMe(controller) ? "me" : "op";
/** 卡的列表,提供了一些方便的方法 */
class CardArray extends Array<CardState> implements ArrayCardState {
public __proto__ = CardArray.prototype;
public zone: ygopro.CardZone = ygopro.CardZone.MZONE;
public getController: () => number = () => 1;
private genCard = async (
uuid: string,
controller: number,
id: number,
position?: ygopro.CardPosition,
focus?: boolean,
chainIndex?: number
) => ({
uuid,
occupant: await fetchCard(id),
location: {
controler: controller,
zone: this.zone,
position:
position == undefined ? ygopro.CardPosition.FACEUP_ATTACK : position,
},
focus: focus ?? false,
chaining: false,
chainIndex,
directAttack: false,
counters: {},
idleInteractivities: [],
});
// methods
remove(sequence: number) {
return this.splice(sequence, 1)[0];
}
async insert(
uuid: string,
id: number,
sequence: number,
position?: ygopro.CardPosition,
focus?: boolean,
chainIndex?: number
) {
const card = await this.genCard(
uuid,
this.getController(),
id,
position,
focus,
chainIndex
);
this.splice(sequence, 0, card);
}
async add(
data: { uuid: string; id: number }[],
position?: ygopro.CardPosition,
focus?: boolean
) {
const cards = await Promise.all(
data.map(async ({ uuid, id }) =>
this.genCard(uuid, this.getController(), id, position, focus)
)
);
this.splice(this.length, 0, ...cards);
}
async setOccupant(
sequence: number,
id: number,
position?: ygopro.CardPosition,
focus?: boolean
) {
const meta = await fetchCard(id);
const target = this[sequence];
target.focus = focus ?? false;
target.occupant = meta;
if (position) {
target.location.position = position;
}
}
addIdleInteractivity(
sequence: number,
interactivity: CardState["idleInteractivities"][number]
) {
this[sequence].idleInteractivities.push(interactivity);
}
clearIdleInteractivities() {
this.forEach((card) => (card.idleInteractivities = []));
}
setPlaceInteractivityType(sequence: number, interactType: InteractType) {
this[sequence].placeInteractivity = {
interactType: interactType,
response: {
controler: this.getController(),
zone: this.zone,
sequence,
},
};
}
clearPlaceInteractivity() {
this.forEach((card) => (card.placeInteractivity = undefined));
}
}
const genDuelCardArray = (cardStates: CardState[], zone: ygopro.CardZone) => {
// 为什么不放在构造函数里面,是因为不想改造继承自Array的构造函数
const me = cloneDeep(new CardArray(...cardStates));
me.zone = zone;
me.getController = () => (matStore.selfType === 1 ? 0 : 1);
const op = cloneDeep(new CardArray(...cardStates));
op.zone = zone;
op.getController = () => (matStore.selfType === 1 ? 1 : 0);
const res = proxy({
me,
op,
of: (controller: number) => res[getWhom(controller)],
});
return res;
};
/** /**
* 根据自己的先后手判断是否是自己 * 根据自己的先后手判断是否是自己
* 原本名字叫judgeSelf * 原本名字叫judgeSelf
...@@ -156,24 +31,6 @@ export const isMe = (controller: number): boolean => { ...@@ -156,24 +31,6 @@ export const isMe = (controller: number): boolean => {
} }
}; };
/**
* 生成一个指定长度的卡片数组
*/
const genBlock = (zone: ygopro.CardZone, n: number) =>
Array(n)
.fill(null)
.map((_) => ({
uuid: v4uuid(), // WARN: 这里其实应该不分配UUID
location: {
zone,
},
focus: false,
chaining: false,
directAttack: false,
idleInteractivities: [],
counters: {},
}));
const initInfo: MatState["initInfo"] = (() => { const initInfo: MatState["initInfo"] = (() => {
const defaultInitInfo = { const defaultInitInfo = {
masterRule: "UNKNOWN", masterRule: "UNKNOWN",
...@@ -199,46 +56,11 @@ const hint: MatState["hint"] = proxy({ ...@@ -199,46 +56,11 @@ const hint: MatState["hint"] = proxy({
code: -1, code: -1,
}); });
/**
* zone -> matStore
*/
const getZone = (zone: ygopro.CardZone) => {
switch (zone) {
case ygopro.CardZone.MZONE:
return matStore.monsters;
case ygopro.CardZone.SZONE:
return matStore.magics;
case ygopro.CardZone.HAND:
return matStore.hands;
case ygopro.CardZone.DECK:
return matStore.decks;
case ygopro.CardZone.GRAVE:
return matStore.graveyards;
case ygopro.CardZone.REMOVED:
return matStore.banishedZones;
case ygopro.CardZone.EXTRA:
return matStore.extraDecks;
default:
console.error("in error", zone);
return matStore.extraDecks;
}
};
const { SZONE, MZONE, GRAVE, REMOVED, HAND, DECK, EXTRA } = ygopro.CardZone;
/** /**
* 💡 决斗盘状态仓库,本文件核心, * 💡 决斗盘状态仓库,本文件核心,
* 具体介绍可以点进`MatState`去看 * 具体介绍可以点进`MatState`去看
*/ */
export const matStore: MatState = proxy<MatState>({ export const matStore: MatState = proxy<MatState>({
magics: genDuelCardArray(genBlock(SZONE, 6), SZONE),
monsters: genDuelCardArray(genBlock(MZONE, 7), MZONE),
graveyards: genDuelCardArray([], GRAVE),
banishedZones: genDuelCardArray([], REMOVED),
hands: genDuelCardArray([], HAND),
decks: genDuelCardArray([], DECK),
extraDecks: genDuelCardArray([], EXTRA),
chains: [], chains: [],
timeLimits: { timeLimits: {
...@@ -266,65 +88,8 @@ export const matStore: MatState = proxy<MatState>({ ...@@ -266,65 +88,8 @@ export const matStore: MatState = proxy<MatState>({
waiting: false, waiting: false,
unimplemented: 0, unimplemented: 0,
// methods // methods
in: getZone,
isMe, isMe,
async setChaining(location, code, isChaining) {
const target = this.in(location.location)
.of(location.controler)
.at(location.sequence);
if (target) {
target.chaining = isChaining;
if (target.occupant && isChaining) {
// 目前需要判断`isChaining`为ture才设置meta,因为有些手坑发效果后会move到墓地,
// 运行到这里的时候已经和原来的位置对不上了,这时候不设置meta
const meta = await fetchCard(code);
target.occupant = meta;
}
if (target.location.zone == ygopro.CardZone.HAND) {
target.location.position = isChaining
? ygopro.CardPosition.FACEUP_ATTACK
: ygopro.CardPosition.FACEDOWN_ATTACK;
}
}
},
setChained(location, chainIndex) {
const target = this.in(location.location)
.of(location.controler)
.at(location.sequence);
if (target) {
target.chainIndex = chainIndex;
} else {
console.warn(`target is null in setChained, location=${location}`);
}
},
setFocus(location, focus) {
const target = this.in(location.location)
.of(location.controler)
.at(location.sequence);
if (target) {
target.focus = focus;
} else {
console.warn(`target is null in setFocus, location=${location}`);
}
},
}); });
// @ts-ignore 挂到全局,便于调试 // @ts-ignore 挂到全局,便于调试
window.matStore = matStore; window.matStore = matStore;
// 修改原型链,因为valtio的proxy会把原型链改掉。这应该是valtio的一个bug...有空提issue去改
(["me", "op"] as const).forEach((who) => {
(
[
"hands",
"decks",
"extraDecks",
"graveyards",
"banishedZones",
"monsters",
"magics",
] as const
).forEach((zone) => {
matStore[zone][who].__proto__ = CardArray.prototype;
});
});
import type { ygopro } from "@/api"; import type { ygopro } from "@/api";
import type { CardMeta } from "@/api/cards";
// >>> play mat state >>> // >>> play mat state >>>
...@@ -9,54 +8,6 @@ export interface BothSide<T> { ...@@ -9,54 +8,6 @@ export interface BothSide<T> {
/** 根据controller返回对应的数组,op或者me */ /** 根据controller返回对应的数组,op或者me */
of: (controller: number) => T; of: (controller: number) => T;
} }
/**
* CardState的顺序index,被称为sequence
*/
export interface DuelFieldState extends Array<CardState> {
/** 移除特定位置的卡片 */
remove: (sequence: number) => CardState;
/** 在指定位置插入卡片 */
insert: (
uuid: string,
id: number,
sequence: number,
position?: ygopro.CardPosition,
focus?: boolean,
chainIndex?: number
) => Promise<void>;
/** 在末尾添加卡片 */
add: (
data: { uuid: string; id: number }[],
position?: ygopro.CardPosition,
focus?: boolean
) => Promise<void>;
/** 设置占据这个位置的卡片信息 */
setOccupant: (
sequence: number,
id: number,
position?: ygopro.CardPosition,
focus?: boolean
) => Promise<void>;
/** 添加 idle 的交互性 */
addIdleInteractivity: (
sequence: number,
interactivity: CardState["idleInteractivities"][number]
) => void;
/** 移除 idle 的交互性 */
clearIdleInteractivities: () => void;
/** 设置 place 的交互种类 */
setPlaceInteractivityType: (
sequence: number,
interactType: InteractType
) => void;
/** 移除 place 的交互性 */
clearPlaceInteractivity: () => void;
// 让原型链不报错
__proto__?: DuelFieldState;
}
type test = DuelFieldState extends (infer S)[] ? S : never;
export interface MatState { export interface MatState {
selfType: number; selfType: number;
...@@ -65,20 +16,6 @@ export interface MatState { ...@@ -65,20 +16,6 @@ export interface MatState {
set: (controller: number, obj: Partial<InitInfo>) => void; set: (controller: number, obj: Partial<InitInfo>) => void;
}; // 双方的初始化信息 }; // 双方的初始化信息
hands: BothSide<HandState>; // 双方的手牌
monsters: BothSide<MonsterState>; // 双方的怪兽区状态
magics: BothSide<MagicState>; // 双方的魔法区状态
graveyards: BothSide<GraveyardState>; // 双方的墓地状态
banishedZones: BothSide<BanishedZoneState>; // 双方的除外区状态
decks: BothSide<DeckState>; // 双方的卡组状态
extraDecks: BothSide<ExtraDeckState>; // 双方的额外卡组状态
chains: ygopro.CardLocation[]; // 连锁的卡片位置 chains: ygopro.CardLocation[]; // 连锁的卡片位置
timeLimits: BothSide<number> & { timeLimits: BothSide<number> & {
...@@ -97,23 +34,8 @@ export interface MatState { ...@@ -97,23 +34,8 @@ export interface MatState {
unimplemented: number; // 未处理的`Message` unimplemented: number; // 未处理的`Message`
// >>> methods >>>
/** 根据zone获取hands/masters/magics... */
in: (zone: ygopro.CardZone) => BothSide<DuelFieldState>;
/** 根据自己的先后手判断是否是自己 */ /** 根据自己的先后手判断是否是自己 */
isMe: (player: number) => boolean; isMe: (player: number) => boolean;
// 添加连锁中状态
// - 当是手牌以外的卡时,修改code并设置chaining字段;
// - 当是手牌中的卡时,修改code,设置chaining字段,并修改position,参数`isChaining`为true时修改成`FaceUpAttack`,为false时修改成`FaceDownAttack`
setChaining: (
location: ygopro.CardLocation,
code: number,
isChaining: boolean
) => Promise<void>;
// 添加被连锁状态
setChained: (location: ygopro.CardLocation, chainIndex?: number) => void;
// 设置聚焦状态
setFocus: (location: ygopro.CardLocation, focus: boolean) => void;
} }
export interface InitInfo { export interface InitInfo {
...@@ -124,49 +46,6 @@ export interface InitInfo { ...@@ -124,49 +46,6 @@ export interface InitInfo {
extraSize: number; extraSize: number;
} }
/**
* 场上某位置的状态,
* 以后会更名为 BlockState
*/
export interface CardState {
uuid: string; // 一张卡的唯一标识
occupant?: CardMeta; // 占据此位置的卡牌元信息
location: {
controler?: number; // 控制这个位置的玩家,0或1
zone: ygopro.CardZone; // 怪兽区/魔法陷阱区/手牌/卡组/墓地/除外区
position?: ygopro.CardPosition; // 卡片的姿势:攻击还是守备
}; // 位置信息,叫location的原因是为了和ygo对齐
focus: boolean; // 用于实现动画效果,当这个字段为true时,该张卡片会被放大并在屏幕中央展示
chaining: boolean; // 是否在连锁中
chainIndex?: number /*连锁的序号,如果为空表示不在连锁
TODO: 目前是妥协的设计,因为其实一张卡是可以在同一个连锁链中被连锁多次的,这里为了避免太过复杂只保存最后的连锁序号*/;
directAttack: boolean; // 是否正在直接攻击为玩家
attackTarget?: CardState & { sequence: number; opponent: boolean }; // 攻击目标。(嵌套结构可行么?)
idleInteractivities: Interactivity<number>[]; // IDLE状态下的互动信息
placeInteractivity?: Interactivity<{
controler: number;
zone: ygopro.CardZone;
sequence: number;
}>; // 选择位置状态下的互动信息
overlay_materials?: CardMeta[]; // 超量素材, FIXME: 这里需要加上UUID
counters: { [type: number]: number }; // 指示器
reload?: boolean; // 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false
}
export interface BlockState {
// 位置信息
location: {
controller: number;
zone: ygopro.CardZone;
};
// 选择位置状态下的互动信息
placeInteractivity?: Interactivity<{
controler: number;
zone: ygopro.CardZone;
sequence: number;
}>;
}
export interface Interactivity<T> { export interface Interactivity<T> {
interactType: InteractType; interactType: InteractType;
// 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号 // 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号
...@@ -196,13 +75,6 @@ export enum InteractType { ...@@ -196,13 +75,6 @@ export enum InteractType {
ATTACK = 8, ATTACK = 8,
} }
export interface HandState extends DuelFieldState {}
export interface MonsterState extends DuelFieldState {}
export interface MagicState extends DuelFieldState {}
export interface GraveyardState extends DuelFieldState {}
export interface BanishedZoneState extends DuelFieldState {}
export interface DeckState extends DuelFieldState {}
export interface ExtraDeckState extends DuelFieldState {}
export interface TimeLimit { export interface TimeLimit {
leftTime: number; leftTime: number;
} }
......
import { matStore } from "@/stores";
export const clearAllIdleInteractivities = (controller: number) => { export const clearAllIdleInteractivities = (controller: number) => {
matStore.banishedZones.of(controller).clearIdleInteractivities(); // TODO
matStore.decks.of(controller).clearIdleInteractivities();
matStore.extraDecks.of(controller).clearIdleInteractivities();
matStore.graveyards.of(controller).clearIdleInteractivities();
matStore.hands.of(controller).clearIdleInteractivities();
matStore.magics.of(controller).clearIdleInteractivities();
matStore.monsters.of(controller).clearIdleInteractivities();
}; };
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { matStore } from "@/stores";
/** 清空所有place互动性,也可以删除某一个zone的互动性。zone为空则为清除所有。 */ /** 清空所有place互动性,也可以删除某一个zone的互动性。zone为空则为清除所有。 */
export const clearAllPlaceInteradtivities = ( export const clearAllPlaceInteradtivities = (
controller: number, controller: number,
zone?: ygopro.CardZone zone?: ygopro.CardZone
) => { ) => {
if (zone) { // TODO
matStore.in(zone).of(controller).clearPlaceInteractivity();
} else {
matStore.banishedZones.of(controller).clearPlaceInteractivity();
matStore.decks.of(controller).clearPlaceInteractivity();
matStore.extraDecks.of(controller).clearPlaceInteractivity();
matStore.graveyards.of(controller).clearPlaceInteractivity();
matStore.hands.of(controller).clearPlaceInteractivity();
matStore.magics.of(controller).clearPlaceInteractivity();
matStore.monsters.of(controller).clearPlaceInteractivity();
}
}; };
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