Commit e597a650 authored by timel's avatar timel

refactor: register and call

parent a8c0b184
......@@ -12,10 +12,13 @@ export enum Task {
const getEnd = (task: Task) => `${task}-end`;
/** 在组件之中注册方法 */
const register = (task: Task, fn: (...args: any[]) => Promise<any>) => {
const register = <T extends unknown[]>(
task: Task,
fn: (...args: T) => Promise<void>
) => {
eventEmitter.on(
task,
async ({ taskId, args }: { taskId: string; args: any[] }) => {
async ({ taskId, args }: { taskId: string; args: T }) => {
await fn(...args);
eventEmitter.emit(getEnd(task), taskId);
}
......
import { ygopro } from "@/api";
import { eventbus, sleep, Task } from "@/infra";
import { sleep } from "@/infra";
import { cardStore, fetchEsHintMeta } from "@/stores";
import { callCardAttack } from "@/ui/Duel/PlayMat/Card";
export default async (attack: ygopro.StocGameMessage.MsgAttack) => {
fetchEsHintMeta({
......@@ -16,14 +17,14 @@ export default async (attack: ygopro.StocGameMessage.MsgAttack) => {
if (attacker) {
if (attack.direct_attack) {
await eventbus.call(Task.Attack, attacker.uuid, true);
await callCardAttack(attacker.uuid, {
directAttack: true,
});
} else {
await eventbus.call(
Task.Attack,
attacker.uuid,
false,
attack.target_location
);
await callCardAttack(attacker.uuid, {
directAttack: false,
target: attack.target_location,
});
}
} else {
console.warn(`<Attack>attacker from ${attack.attacker_location} is null`);
......
import { fetchCard, ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { cardStore, fetchEsHintMeta } from "@/stores";
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
export default async (draw: ygopro.StocGameMessage.MsgDraw) => {
fetchEsHintMeta({ originMsg: "玩家抽卡时" });
......@@ -27,6 +27,6 @@ export default async (draw: ygopro.StocGameMessage.MsgDraw) => {
await Promise.all(
cardStore
.at(ygopro.CardZone.HAND, draw.player)
.map((card) => eventbus.call(Task.Move, card.uuid))
.map((card) => callCardMove(card.uuid))
);
};
import { fetchCard, ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { cardStore, CardType } from "@/stores";
import { REASON_MATERIAL, TYPE_TOKEN } from "../../common";
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
type MsgMove = ygopro.StocGameMessage.MsgMove;
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, TZONE } = ygopro.CardZone;
......@@ -114,7 +114,7 @@ export default async (move: MsgMove) => {
overlayMaterial.location.zone = to.zone;
overlayMaterial.location.sequence = to.sequence;
await eventbus.call(Task.Move, overlayMaterial.uuid);
await callCardMove(overlayMaterial.uuid);
} else {
console.warn(
`<Move>overlayMaterial from zone=${location.zone}, controller=${location.controller}, sequence=${location.sequence}, overlay_sequence=${location.overlay_sequence} is null`
......@@ -157,13 +157,13 @@ export default async (move: MsgMove) => {
target.location = to;
// 维护完了之后,开始动画
const p = eventbus.call(Task.Move, target.uuid, from.zone);
const p = callCardMove(target.uuid, { fromZone: from.zone });
// 如果from或者to是手卡,那么需要刷新除了这张卡之外,这个玩家的所有手卡
if ([from.zone, to.zone].includes(HAND)) {
const pHands = cardStore
.at(HAND, target.location.controller)
.filter((c) => c.uuid !== target.uuid)
.map(async (c) => await eventbus.call(Task.Move, c.uuid));
.map(async (c) => await callCardMove(c.uuid));
await Promise.all([p, ...pHands]);
} else {
await p;
......@@ -181,7 +181,7 @@ export default async (move: MsgMove) => {
overlay.location.sequence = to.sequence;
overlay.location.position = to.position;
await eventbus.call(Task.Move, overlay.uuid);
await callCardMove(overlay.uuid);
}
}
};
import { ygopro } from "@/api";
import MsgPosChange = ygopro.StocGameMessage.MsgPosChange;
import { eventbus, Task } from "@/infra";
import { cardStore, fetchEsHintMeta } from "@/stores";
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
export default async (posChange: MsgPosChange) => {
const { location, controller, sequence } = posChange.card_info;
......@@ -10,7 +12,7 @@ export default async (posChange: MsgPosChange) => {
target.location.position = posChange.cur_position;
// TODO: 暂时用`Move`动画,后续可以单独实现一个改变表示形式的动画
await eventbus.call(Task.Move, target.uuid);
await callCardMove(target.uuid);
} else {
console.warn(`<PosChange>target from ${posChange.card_info} is null`);
}
......
import { ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { cardStore } from "@/stores";
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
type MsgShuffleHandExtra = ygopro.StocGameMessage.MsgShuffleHandExtra;
......@@ -23,7 +23,7 @@ export default async (shuffleHandExtra: MsgShuffleHandExtra) => {
hash.set(card.code, sequences);
// 触发动画
await eventbus.call(Task.Move, card.uuid);
await callCardMove(card.uuid);
} else {
console.warn(
`<ShuffleHandExtra>sequence poped is none, controller=${controller}, code=${card.code}, sequence=${sequence}`
......
import { ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { cardStore } from "@/stores";
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
import MsgShuffleSetCard = ygopro.StocGameMessage.MsgShuffleSetCard;
// 后端传过来的`from_locations`的列表是切洗前场上卡的location,它们在列表里面按照切洗后的顺序排列
......@@ -42,7 +42,7 @@ export default async (shuffleSetCard: MsgShuffleSetCard) => {
// 更新sequence
overlay.location.sequence = overlay_location.sequence;
// 渲染动画
await eventbus.call(Task.Move, overlay.uuid);
await callCardMove(overlay.uuid);
// 这里其实有个疑惑,如果超量素材也跟着洗切的话,洗切的意义好像就没有了,感觉算是个k社没想好的设计?
}
}
......
import { ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { cardStore } from "@/stores";
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
import MsgSwapGraveDeck = ygopro.StocGameMessage.MsgSwapGraveDeck;
const { DECK, GRAVE } = ygopro.CardZone;
......@@ -12,11 +13,11 @@ export default async (swapGraveDeck: MsgSwapGraveDeck) => {
for (const card of deck) {
card.location.zone = GRAVE;
await eventbus.call(Task.Move, card.uuid);
await callCardMove(card.uuid);
}
for (const card of grave) {
card.location.zone = DECK;
await eventbus.call(Task.Move, card.uuid);
await callCardMove(card.uuid);
}
};
import { fetchCard, ygopro } from "@/api";
import MsgUpdateData = ygopro.StocGameMessage.MsgUpdateData;
import { callCardMove } from "@/ui/Duel/PlayMat/Card";
import { eventbus, Task } from "@/infra";
import { cardStore } from "@/stores";
import MsgUpdateData = ygopro.StocGameMessage.MsgUpdateData;
export default async (updateData: MsgUpdateData) => {
const { player: controller, zone, actions } = updateData;
if (controller !== undefined && zone !== undefined && actions !== undefined) {
......@@ -28,7 +28,7 @@ export default async (updateData: MsgUpdateData) => {
// Currently only update position
target.location.position = action.location.position;
// animation
await eventbus.call(Task.Move, target.uuid);
await callCardMove(target.uuid);
}
}
if (action?.type_ >= 0) {
......
......@@ -27,11 +27,9 @@ import { interactTypeToString } from "../../utils";
import {
attack,
focus,
moveToDeck,
moveToGround,
moveToHand,
moveToOutside,
moveToToken,
move,
type MoveOptions,
type AttackOptions,
} from "./springs";
import type { SpringApiProps } from "./springs/types";
import { preloadCardImage } from "./springs/utils";
......@@ -62,32 +60,9 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
} satisfies SpringApiProps)
);
const move = async (toZone: ygopro.CardZone, fromZone?: ygopro.CardZone) => {
switch (toZone) {
case MZONE:
case SZONE:
await moveToGround({ card, api, fromZone });
break;
case HAND:
await moveToHand({ card, api, fromZone });
break;
case DECK:
case EXTRA:
await moveToDeck({ card, api, fromZone });
break;
case GRAVE:
case REMOVED:
await moveToOutside({ card, api, fromZone });
break;
case TZONE:
await moveToToken({ card, api, fromZone });
break;
}
};
// 每张卡都需要移动到初始位置
useEffect(() => {
move(card.location.zone);
addToAnimation(() => move({ card, api }));
}, []);
const [glowing, setGrowing] = useState(false);
......@@ -102,44 +77,32 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
animationQueue.current = animationQueue.current.then(p).then(rs);
});
useEffect(() => {
eventbus.register(
Task.Move,
async (uuid: string, fromZone?: ygopro.CardZone) => {
if (uuid === card.uuid) {
await addToAnimation(async () => {
await preloadCardImage(card.code);
await move(card.location.zone, fromZone);
});
}
}
);
eventbus.register(Task.Focus, async (uuid: string) => {
const register = <T extends any[]>(
task: Task,
fn: (...args: T) => Promise<unknown>
) => {
eventbus.register(task, async (uuid, ...rest: T) => {
if (uuid === card.uuid) {
await addToAnimation(async () => {
await preloadCardImage(card.code);
setClassFocus(true);
setTimeout(() => setClassFocus(false), 1000);
await focus({ card, api });
});
await fn(...rest);
}
});
};
eventbus.register(
Task.Attack,
async (
uuid: string,
directAttack: boolean,
target?: ygopro.CardLocation
) => {
if (uuid === card.uuid) {
await addToAnimation(() =>
attack({ card, api, target, directAttack })
);
}
}
);
useEffect(() => {
register(Task.Move, async (options?: MoveOptions) => {
await addToAnimation(() => move({ card, api, options }));
});
register(Task.Focus, async () => {
await preloadCardImage(card.code);
setClassFocus(true);
setTimeout(() => setClassFocus(false), 1000);
await focus({ card, api });
});
register(Task.Attack, async (options: AttackOptions) => {
await addToAnimation(() => attack({ card, api, options }));
});
}, []);
// <<< 动画 <<<
......@@ -382,3 +345,13 @@ const handleEffectActivation = (
};
// <<< 下拉菜单 <<<
const call =
<Options,>(task: Task) =>
async (uuid: string, options?: Options extends {} ? Options : never) => {
eventbus.call(task, uuid, options);
};
export const callCardMove = call<MoveOptions>(Task.Move);
export const callCardFocus = call(Task.Focus);
export const callCardAttack = call<AttackOptions>(Task.Attack);
......@@ -5,34 +5,29 @@ import { ygopro } from "@/api";
import { CardType, isMe } from "@/stores";
import { matConfig } from "@/ui/Shared";
import type { SpringApi } from "./types";
import type { SpringApi, AttackFunc } from "./types";
import { asyncStart } from "./utils";
const { BLOCK_WIDTH, BLOCK_HEIGHT_M, BLOCK_HEIGHT_S, COL_GAP, ROW_GAP } =
matConfig;
export const attack = async (props: {
card: CardType;
api: SpringApi;
directAttack: boolean;
target?: ygopro.CardLocation;
}) => {
const { card, api, directAttack, target } = props;
export const attack: AttackFunc = async (props) => {
const { card, api, options } = props;
const current = api.current[0].get();
let x = current.x;
let y = current.y;
let rz = current.rz;
if (directAttack) {
if (options?.directAttack) {
// 直接攻击
y = BLOCK_HEIGHT_M + BLOCK_HEIGHT_S;
if (isMe(card.location.controller)) {
y = -y;
}
} else if (target) {
} else if (options?.target) {
// 攻击`target`
const { controller, sequence } = target;
const { controller, sequence } = options.target;
if (sequence > 4) {
// 额外怪兽区
x = (sequence > 5 ? 1 : -1) * (BLOCK_WIDTH + COL_GAP);
......
export * from "./attack";
export * from "./focus";
export * from "./moveToDeck";
export * from "./moveToGround";
export * from "./moveToHand";
export * from "./moveToOutside";
export * from "./moveToToken";
export * from "./move";
export * from "./utils";
export * from "./types";
import { ygopro } from "@/api";
import type { MoveFunc } from "./types";
import { moveToGround } from "./moveToGround";
import { moveToHand } from "./moveToHand";
import { moveToDeck } from "./moveToDeck";
import { moveToOutside } from "./moveToOutside";
import { moveToToken } from "./moveToToken";
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE } =
ygopro.CardZone;
export const move: MoveFunc = async (props) => {
const { card } = props;
switch (card.location.zone) {
case MZONE:
case SZONE:
await moveToGround(props);
break;
case HAND:
await moveToHand(props);
break;
case DECK:
case EXTRA:
await moveToDeck(props);
break;
case GRAVE:
case REMOVED:
await moveToOutside(props);
break;
case TZONE:
await moveToToken(props);
break;
}
};
......@@ -2,7 +2,8 @@ import { ygopro } from "@/api";
import { isMe } from "@/stores";
import { matConfig } from "@/ui/Shared";
import { asyncStart, type MoveFunc } from "./utils";
import { asyncStart } from "./utils";
import type { MoveFunc } from "./types";
const {
BLOCK_WIDTH,
......
......@@ -4,7 +4,8 @@ import { ygopro } from "@/api";
import { isMe } from "@/stores";
import { matConfig } from "@/ui/Shared";
import { asyncStart, type MoveFunc } from "./utils";
import { asyncStart } from "./utils";
import type { MoveFunc } from "./types";
const {
BLOCK_WIDTH,
......@@ -20,7 +21,7 @@ const {
const { MZONE, SZONE, TZONE } = ygopro.CardZone;
export const moveToGround: MoveFunc = async (props) => {
const { card, api, fromZone } = props;
const { card, api, options } = props;
const { location } = card;
......@@ -87,7 +88,8 @@ export const moveToGround: MoveFunc = async (props) => {
: 0;
// 动画
if (fromZone === TZONE) {
const isToken = options?.fromZone === TZONE;
if (isToken) {
// 如果是Token,直接先移动到那个位置,然后再放大
api.set({
x,
......@@ -115,10 +117,12 @@ export const moveToGround: MoveFunc = async (props) => {
await asyncStart(api)({
height,
z: 0,
subZ: isToken ? 100 : 0,
zIndex: is_overlay ? 1 : 3,
config: {
easing: easings.easeInQuad,
clamp: true,
},
});
if (isToken) api.set({ subZ: 0 });
};
......@@ -2,7 +2,8 @@ import { ygopro } from "@/api";
import { cardStore, isMe } from "@/stores";
import { matConfig } from "@/ui/Shared";
import { asyncStart, type MoveFunc } from "./utils";
import { asyncStart } from "./utils";
import type { MoveFunc } from "./types";
const {
BLOCK_HEIGHT_M,
......
......@@ -2,7 +2,8 @@ import { ygopro } from "@/api";
import { isMe } from "@/stores";
import { matConfig } from "@/ui/Shared";
import { asyncStart, type MoveFunc } from "./utils";
import { asyncStart } from "./utils";
import type { MoveFunc } from "./types";
const {
BLOCK_WIDTH,
......
import { asyncStart, type MoveFunc } from "./utils";
import { asyncStart } from "./utils";
import type { MoveFunc } from "./types";
export const moveToToken: MoveFunc = async (props) => {
const { api } = props;
......
import { type SpringRef } from "@react-spring/web";
import type { ygopro } from "@/api";
import type { CardType } from "@/stores";
import type { SpringRef } from "@react-spring/web";
export interface SpringApiProps {
x: number;
......@@ -20,3 +22,19 @@ export interface SpringApiProps {
}
export type SpringApi = SpringRef<SpringApiProps>;
type OptionsToFunc<Options> = (props: {
card: CardType;
api: SpringApi;
options?: Options;
}) => Promise<void>;
export type MoveOptions = { fromZone?: ygopro.CardZone };
export type MoveFunc = OptionsToFunc<MoveOptions>;
export type AttackOptions =
| {
directAttack: true;
}
| { directAttack: false; target: ygopro.CardLocation };
export type AttackFunc = OptionsToFunc<AttackOptions>;
import { type SpringConfig, type SpringRef } from "@react-spring/web";
import type { ygopro } from "@/api";
import { type CardType } from "@/stores";
import { getCardImgUrl } from "@/ui/Shared";
import type { SpringApi } from "./types";
export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
return (p: Partial<T> & { config?: SpringConfig }) =>
new Promise((resolve) => {
......@@ -16,12 +12,6 @@ export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
});
};
export type MoveFunc = (props: {
card: CardType;
api: SpringApi;
fromZone?: ygopro.CardZone;
}) => Promise<void>;
// >>> preload image >>>
const preloadImageSet = new Set<string>();
export const preloadImage = (src: string) =>
......
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