Commit 6f35edc4 authored by Chunchi Che's avatar Chunchi Che

Merge branch 'optimize/store/modal' into 'main'

Optimize/store/modal

See merge request mycard/Neos!179
parents 3803129a 1f953299
Pipeline #21529 passed with stages
in 13 minutes and 35 seconds
neos-protobuf @ be66d514
Subproject commit 1648b05d2e0bf0fc84c9e98104b8d9ca6a014c1a
Subproject commit be66d514be6d1224163f55bca85dc361d0c9fe6a
This diff is collapsed.
......@@ -2,15 +2,14 @@ import { ygopro } from "../../../idl/ocgcore";
import { YgoProPacket } from "../../packet";
import { CTOS_RESPONSE } from "../../protoDecl";
import adaptSelectBattleCmdResponse from "./selectBattleCmd";
import adaptSelectCardResponse from "./selectCard";
import adaptSelectChainResponse from "./selectChain";
import adaptSelectCounterResponse from "./selectCounter";
import adaptSelectEffectYnResponse from "./selectEffectYn";
import adaptSelectIdleCmdResponse from "./selectIdleCmd";
import adaptSelectMultiResponse from "./selectMulti";
import adaptSelectOptionResponse from "./selectOption";
import adaptSelectPlaceResponse from "./selectPlace";
import adaptSelectPositionResponse from "./selectPosition";
import adaptSelectUnselectCardResponse from "./selectUnselectCard";
import adaptSelectSingleResponse from "./selectSingle";
import adaptSortCardResponse from "./sortCard";
/*
......@@ -37,13 +36,13 @@ export default class CtosResponsePacket extends YgoProPacket {
break;
}
case "select_card": {
extraData = adaptSelectCardResponse(response.select_card);
case "select_multi": {
extraData = adaptSelectMultiResponse(response.select_multi);
break;
}
case "select_chain": {
extraData = adaptSelectChainResponse(response.select_chain);
case "select_single": {
extraData = adaptSelectSingleResponse(response.select_single);
break;
}
......@@ -67,13 +66,6 @@ export default class CtosResponsePacket extends YgoProPacket {
break;
}
case "select_unselect_card": {
extraData = adaptSelectUnselectCardResponse(
response.select_unselect_card
);
break;
}
case "select_counter_response": {
extraData = adaptSelectCounterResponse(
response.select_counter_response
......
......@@ -2,7 +2,7 @@ import { BufferWriter } from "rust-src";
import { ygopro } from "../../../idl/ocgcore";
export default (response: ygopro.CtosGameMsgResponse.SelectCardResponse) => {
export default (response: ygopro.CtosGameMsgResponse.SelectMultiResponse) => {
const writer = new BufferWriter();
writer.writeUint8(response.selected_ptrs.length);
......
......@@ -2,7 +2,7 @@ import { BufferWriter } from "rust-src";
import { ygopro } from "../../../idl/ocgcore";
export default (response: ygopro.CtosGameMsgResponse.SelectChainResponse) => {
export default (response: ygopro.CtosGameMsgResponse.SelectSingleResponse) => {
const writer = new BufferWriter();
writer.writeInt32(response.selected_ptr);
......
import { BufferWriter } from "rust-src";
import { ygopro } from "../../../idl/ocgcore";
export default (
response: ygopro.CtosGameMsgResponse.SelectUnselectCardResponse
) => {
const writer = new BufferWriter();
if (response.cancel_or_finish) {
writer.writeInt32(-1);
} else {
writer.writeUint8(1);
writer.writeUint8(response.selected_ptr);
}
return writer.toArray();
};
......@@ -172,10 +172,10 @@ export function sendSelectPlaceResponse(value: {
socketMiddleWare({ cmd: socketCmd.SEND, payload });
}
export function sendSelectCardResponse(value: number[]) {
export function sendSelectMultiResponse(value: number[]) {
const response = new ygopro.YgoCtosMsg({
ctos_response: new ygopro.CtosGameMsgResponse({
select_card: new ygopro.CtosGameMsgResponse.SelectCardResponse({
select_multi: new ygopro.CtosGameMsgResponse.SelectMultiResponse({
selected_ptrs: value,
}),
}),
......@@ -185,10 +185,10 @@ export function sendSelectCardResponse(value: number[]) {
socketMiddleWare({ cmd: socketCmd.SEND, payload });
}
export function sendSelectChainResponse(value: number) {
export function sendSelectSingleResponse(value: number) {
const response = new ygopro.YgoCtosMsg({
ctos_response: new ygopro.CtosGameMsgResponse({
select_chain: new ygopro.CtosGameMsgResponse.SelectChainResponse({
select_single: new ygopro.CtosGameMsgResponse.SelectSingleResponse({
selected_ptr: value,
}),
}),
......@@ -252,24 +252,6 @@ export function sendSelectBattleCmdResponse(value: number) {
socketMiddleWare({ cmd: socketCmd.SEND, payload });
}
export function sendSelectUnselectCardResponse(value: {
cancel_or_finish?: boolean;
selected_ptr?: number;
}) {
const response = new ygopro.YgoCtosMsg({
ctos_response: new ygopro.CtosGameMsgResponse({
select_unselect_card:
new ygopro.CtosGameMsgResponse.SelectUnselectCardResponse({
selected_ptr: value.selected_ptr,
cancel_or_finish: value.cancel_or_finish,
}),
}),
});
const payload = new GameMsgResponse(response).serialize();
socketMiddleWare({ cmd: socketCmd.SEND, payload });
}
export function sendSelectCounterResponse(counts: number[]) {
const response = new ygopro.YgoCtosMsg({
ctos_response: new ygopro.CtosGameMsgResponse({
......
import { ygopro } from "@/api";
import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard;
import { CardZoneToChinese, fetchCheckCardMeta, messageStore } from "@/stores";
import { fetchCheckCardMeta, messageStore } from "@/stores";
export default (selectCard: MsgSelectCard) => {
const _player = selectCard.player;
const _cancelable = selectCard.cancelable; // TODO: 处理可取消逻辑
const cancelable = selectCard.cancelable;
const min = selectCard.min;
const max = selectCard.max;
const cards = selectCard.cards;
// TODO: handle release_param
messageStore.checkCardModal.selectMin = min;
messageStore.checkCardModal.selectMax = max;
messageStore.checkCardModal.onSubmit = "sendSelectCardResponse";
messageStore.selectCardActions.min = min;
messageStore.selectCardActions.max = max;
messageStore.selectCardActions.cancelAble = cancelable;
for (const card of cards) {
const tagName = CardZoneToChinese(card.location.location);
fetchCheckCardMeta(card.location.location, {
fetchCheckCardMeta({
code: card.code,
location: card.location,
response: card.response,
});
}
messageStore.checkCardModal.isOpen = true;
messageStore.selectCardActions.isOpen = true;
};
import { sendSelectChainResponse, ygopro } from "@/api";
import { sendSelectSingleResponse, ygopro } from "@/api";
import {
fetchCheckCardMeta,
fetchSelectHintMeta,
......@@ -7,7 +7,6 @@ import {
type MsgSelectChain = ygopro.StocGameMessage.MsgSelectChain;
export default (selectChain: MsgSelectChain) => {
const player = selectChain.player;
const spCount = selectChain.special_count;
const forced = selectChain.forced;
const hint0 = selectChain.hint0;
......@@ -50,7 +49,7 @@ export default (selectChain: MsgSelectChain) => {
switch (handle_flag) {
case 0: {
// 直接回答
sendSelectChainResponse(-1);
sendSelectSingleResponse(-1);
break;
}
......@@ -58,14 +57,13 @@ export default (selectChain: MsgSelectChain) => {
case 3: {
// 处理强制发动的卡
messageStore.checkCardModal.selectMin = 1;
messageStore.checkCardModal.selectMax = 1;
messageStore.checkCardModal.onSubmit = "sendSelectChainResponse";
messageStore.checkCardModal.cancelAble = !forced;
messageStore.checkCardModal.cancelResponse = -1;
messageStore.selectCardActions.isChain = true;
messageStore.selectCardActions.min = 1;
messageStore.selectCardActions.max = 1;
messageStore.selectCardActions.cancelAble = !forced;
for (const chain of chains) {
fetchCheckCardMeta(chain.location.location, {
fetchCheckCardMeta({
code: chain.code,
location: chain.location,
response: chain.response,
......@@ -75,13 +73,13 @@ export default (selectChain: MsgSelectChain) => {
fetchSelectHintMeta({
selectHintData: 203,
});
messageStore.checkCardModal.isOpen = true;
messageStore.selectCardActions.isOpen = true;
break;
}
case 4: {
// 有一张强制发动的卡,直接回应
sendSelectChainResponse(chains[0].response);
sendSelectSingleResponse(chains[0].response);
break;
}
......
import { fetchStrings, ygopro } from "@/api";
import { CardMeta, fetchCard } from "@/api/cards";
import { CardZoneToChinese, messageStore } from "@/stores";
import { messageStore } from "@/stores";
type MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn;
// 这里改成了 async 不知道有没有影响
export default async (selectEffectYn: MsgSelectEffectYn) => {
const player = selectEffectYn.player;
const code = selectEffectYn.code;
const location = selectEffectYn.location;
const effect_description = selectEffectYn.effect_description;
......@@ -20,7 +19,7 @@ export default async (selectEffectYn: MsgSelectEffectYn) => {
) => {
const desc1 = desc.replace(
`[%ls]`,
CardZoneToChinese(cardLocation.location)
fetchStrings("!system", cardLocation.location + 1000)
);
const desc2 = desc1.replace(`[%ls]`, cardMeta.text.name || "[?]");
return desc2;
......@@ -29,14 +28,7 @@ export default async (selectEffectYn: MsgSelectEffectYn) => {
const desc1 = desc.replace(`[%ls]`, cardMeta.text.name || "[?]");
return desc1;
};
// dispatch(
// fetchYesNoMeta({
// code,
// location,
// descCode: effect_description,
// textGenerator,
// })
// );
// TODO: 国际化文案
const desc = fetchStrings("!system", effect_description);
......
import { ygopro } from "@/api";
import { fetchCheckCardMetasV3, messageStore } from "@/stores";
import { fetchCheckCardMeta, messageStore } from "@/stores";
type MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum;
export default (selectSum: MsgSelectSum) => {
messageStore.checkCardModalV3.overflow = selectSum.overflow != 0;
messageStore.checkCardModalV3.allLevel = selectSum.level_sum;
messageStore.checkCardModalV3.selectMin = selectSum.min;
messageStore.checkCardModalV3.selectMax = selectSum.max;
messageStore.selectCardActions.overflow = selectSum.overflow != 0;
messageStore.selectCardActions.totalLevels = selectSum.level_sum;
messageStore.selectCardActions.min = selectSum.min;
messageStore.selectCardActions.max = selectSum.max;
fetchCheckCardMetasV3({
mustSelect: true,
options: selectSum.must_select_cards,
});
for (const option of selectSum.must_select_cards) {
fetchCheckCardMeta(option, false, true);
}
fetchCheckCardMetasV3({
mustSelect: false,
options: selectSum.selectable_cards,
});
for (const option of selectSum.selectable_cards) {
fetchCheckCardMeta(option);
}
messageStore.checkCardModalV3.isOpen = true;
messageStore.selectCardActions.isOpen = true;
};
import { ygopro } from "@/api";
import { fetchCheckCardMetasV3, messageStore } from "@/stores";
import { fetchCheckCardMeta, messageStore } from "@/stores";
type MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute;
export default (selectTribute: MsgSelectTribute) => {
// TODO: 当玩家选择卡数大于`max`时,是否也合法?
messageStore.checkCardModalV3.overflow = true;
messageStore.checkCardModalV3.allLevel = 0;
messageStore.checkCardModalV3.selectMin = selectTribute.min;
messageStore.checkCardModalV3.selectMax = selectTribute.max;
messageStore.selectCardActions.overflow = true;
messageStore.selectCardActions.totalLevels = 0;
messageStore.selectCardActions.min = selectTribute.min;
messageStore.selectCardActions.max = selectTribute.max;
fetchCheckCardMetasV3({
mustSelect: false,
options: selectTribute.selectable_cards.map((card) => {
return {
code: card.code,
location: card.location,
level1: card.level,
level2: card.level,
response: card.response,
};
}),
});
for (const option of selectTribute.selectable_cards) {
fetchCheckCardMeta(option);
}
messageStore.checkCardModalV3.isOpen = true;
messageStore.selectCardActions.isOpen = true;
};
import { ygopro } from "@/api";
import { fetchCheckCardMetasV2, messageStore } from "@/stores";
import { fetchCheckCardMeta, messageStore } from "@/stores";
type MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard;
......@@ -11,33 +11,18 @@ export default ({
selectable_cards: selectableCards,
selected_cards: selectedCards,
}: MsgSelectUnselectCard) => {
messageStore.checkCardModalV2.isOpen = true;
messageStore.checkCardModalV2.finishAble = finishable;
messageStore.checkCardModalV2.cancelAble = cancelable;
messageStore.checkCardModalV2.selectMin = min;
messageStore.checkCardModalV2.selectMax = max;
messageStore.selectCardActions.isOpen = true;
messageStore.selectCardActions.finishAble = finishable;
messageStore.selectCardActions.cancelAble = cancelable;
messageStore.selectCardActions.min = min;
messageStore.selectCardActions.max = max;
messageStore.selectCardActions.single = true;
fetchCheckCardMetasV2({
selected: false,
options: selectableCards.map((card) => {
return {
code: card.code,
location: card.location,
response: card.response,
};
}),
});
for (const option of selectableCards) {
fetchCheckCardMeta(option);
}
fetchCheckCardMetasV2({
selected: true,
options: selectedCards.map((card) => {
return {
code: card.code,
location: card.location,
response: card.response,
};
}),
});
messageStore.checkCardModalV2.responseable = true;
for (const option of selectedCards) {
fetchCheckCardMeta(option, true);
}
};
import { ygopro } from "@/api";
export function CardZoneToChinese(zone: ygopro.CardZone): string {
switch (zone) {
case ygopro.CardZone.DECK: {
return "卡组";
}
case ygopro.CardZone.HAND: {
return "手牌";
}
case ygopro.CardZone.EXTRA: {
return "额外卡组";
}
case ygopro.CardZone.GRAVE: {
return "墓地";
}
case ygopro.CardZone.FZONE: {
return "FZONE";
}
case ygopro.CardZone.MZONE: {
return "怪兽区";
}
case ygopro.CardZone.SZONE: {
return "魔法陷阱区";
}
case ygopro.CardZone.REMOVED: {
return "除外区";
}
case ygopro.CardZone.OVERLAY: {
return "超量区";
}
case ygopro.CardZone.PZONE: {
return "灵摆区";
}
case ygopro.CardZone.ONFIELD: {
return "场地区";
}
default: {
return "未知区域";
}
}
}
......@@ -2,102 +2,50 @@ import { ygopro } from "@/api";
import { fetchCard, getCardStr } from "@/api/cards";
import { matStore, messageStore } from "@/stores";
import { CardZoneToChinese } from "./cardZoneToChinese";
type Location =
| ygopro.CardLocation
| ReturnType<typeof ygopro.CardLocation.prototype.toObject>;
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
);
}
}
/**
* 这段代码定义了一个异步函数 fetchCheckCardMeta,它的作用是获取一张卡片的元数据并将其添加到某个名为 messageStore.checkCardModal 的对象上。
该函数的第一个参数是一个枚举值 ygopro.CardZone,表示卡片所在的区域。其余参数是一个包含卡片编号、位置、响应码和效果描述代码等信息的对象。
首先,这个函数会根据区域类型调用 CardZoneToChinese() 函数生成一个中文名称。然后,它会调用 fetchCard() 异步函数来获取指定卡片的元数据 meta。
接下来,函数会根据传递进来的 location 对象获取卡片所属的控制者,并根据控制者判断这张卡片是我方的还是对方的。然后,它会根据卡片的位置信息获取卡片的实际 ID,并构造一个新的选项 newOption。
接着,函数会遍历已有的 messageStore.checkCardModal.tags,查找是否存在名为 combinedTagName 的标签。如果找到了,则将新选项 newOption 加入该标签的选项列表中并立即返回。如果找不到,则创建一个新标签,并将新选项 newOption 添加到其中。
最后,函数会再次遍历所有标签,查找是否存在名为 combinedTagName 的标签。如果找到了,则遍历该标签中的所有选项,并查找是否存在与 location 对象中指定的卡片位置信息完全相同的选项。如果找到了,则更新该选项的元数据和效果描述等信息。
*/
export const fetchCheckCardMeta = async (
zone: ygopro.CardZone,
{
code,
location,
level1,
level2,
response,
effectDescCode,
}: {
code: number;
location: ygopro.CardLocation;
level1?: number;
level2?: number;
response: number;
effectDescCode?: number;
}
},
selected?: boolean,
mustSelect?: boolean
) => {
const tagName = CardZoneToChinese(zone);
const meta = await fetchCard(code);
const controller = location.controler;
const combinedTagName = matStore.isMe(controller)
? `我方的${tagName}`
: `对方的${tagName}`;
const newID =
code != 0
? code
: matStore.in(location.location).of(controller)[location.sequence]
?.occupant?.id || 0;
const meta = await fetchCard(code);
const effectDesc = effectDescCode
? getCardStr(meta, effectDescCode & 0xf)
: undefined;
const newOption = {
meta: { id: newID, data: {}, text: {} },
code: newID,
location: location.toObject(),
effectDescCode,
level1,
level2,
effectDesc,
response,
};
for (const tag of messageStore.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
tag.options.push(newOption);
return;
}
}
messageStore.checkCardModal.tags.push({
tagName: combinedTagName,
options: [newOption],
});
for (const tag of messageStore.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
for (const old of tag.options) {
if (meta.id == old.meta.id && cmpCardLocation(location, old.location)) {
const cardID = old.meta.id;
old.meta = meta;
old.meta.id = cardID;
const effectDescCode = old.effectDescCode;
const effectDesc = effectDescCode
? getCardStr(old.meta, effectDescCode & 0xf)
: undefined;
old.effectDesc = effectDesc;
}
}
}
if (selected) {
messageStore.selectCardActions.selecteds.push(newOption);
} else if (mustSelect) {
messageStore.selectCardActions.mustSelects.push(newOption);
} else {
messageStore.selectCardActions.selectables.push(newOption);
}
};
export * from "./cardZoneToChinese";
export * from "./fetchCheckCardMeta";
export * from "./fetchHint";
export * from "./fetchOverlayMeta";
......
import { messageStore } from "../store";
const { selectCardActions } = messageStore;
export const clearSelectActions = () => {
selectCardActions.isOpen = false;
selectCardActions.isChain = undefined;
selectCardActions.min = undefined;
selectCardActions.max = undefined;
selectCardActions.cancelAble = false;
selectCardActions.totalLevels = undefined;
selectCardActions.selecteds = [];
selectCardActions.selectables = [];
selectCardActions.mustSelects = [];
selectCardActions.finishAble = false;
selectCardActions.overflow = false;
selectCardActions.single = undefined;
};
import { fetchCard, type ygopro } from "@/api";
import { getCardByLocation, messageStore } from "@/stores";
export const fetchCheckCardMetasV2 = async ({
selected,
options,
}: {
selected: boolean;
options: {
code: number;
location: ygopro.CardLocation;
response: number;
name?: string;
desc?: string;
}[];
}) => {
const metas = await Promise.all(
options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
for (const option of options) {
if (option.code == 0) {
const newCode = getCardByLocation(option.location)?.occupant?.id || 0;
option.code = newCode;
}
}
options.forEach((option) => {
metas.forEach((meta) => {
if (option.code == meta.id) {
option.name = meta.text.name;
option.desc = meta.text.desc;
}
});
});
if (selected) {
messageStore.checkCardModalV2.selectedOptions = options;
} else {
messageStore.checkCardModalV2.selectableOptions = options;
}
};
import { fetchCard, type ygopro } from "@/api";
import { getCardByLocation, messageStore } from "@/stores";
export const fetchCheckCardMetasV3 = async ({
mustSelect,
options,
}: {
mustSelect: boolean;
options: {
code: number;
location: ygopro.CardLocation;
level1: number;
level2: number;
response: number;
}[];
}) => {
const metas = await Promise.all(
options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
const newOptions = options.map((option) => {
if (option.code == 0) {
const newCode = getCardByLocation(option.location)?.occupant?.id || 0;
option.code = newCode;
}
return {
meta: { id: option.code, data: {}, text: {} },
level1: option.level1,
level2: option.level2,
response: option.response,
};
});
newOptions.forEach((option) => {
metas.forEach((meta) => {
if (option.meta.id == meta.id) {
option.meta = meta;
}
});
});
if (mustSelect) {
messageStore.checkCardModalV3.mustSelectList = newOptions;
} else {
messageStore.checkCardModalV3.selectAbleList = newOptions;
}
};
export * from "./clearAllIdleInteractivities";
export * from "./clearAllPlaceInteradtivities";
export * from "./fetchCheckCardMetasV2";
export * from "./fetchCheckCardMetasV3";
export * from "./clearSelectActions";
......@@ -5,25 +5,17 @@ import type { ModalState } from "./types";
export const messageStore = proxy<ModalState>({
cardModal: { isOpen: false, interactivies: [], counters: {} },
cardListModal: { isOpen: false, list: [] },
checkCardModal: { isOpen: false, cancelAble: false, tags: [] },
yesNoModal: { isOpen: false },
positionModal: { isOpen: false, positions: [] },
optionModal: { isOpen: false, options: [] },
checkCardModalV2: {
selectCardActions: {
isOpen: false,
cancelAble: false,
finishAble: false,
responseable: false,
selectableOptions: [],
selectedOptions: [],
},
checkCardModalV3: {
isOpen: false,
overflow: false,
allLevel: 0,
mustSelectList: [],
selectAbleList: [],
selecteds: [],
selectables: [],
mustSelects: [],
},
yesNoModal: { isOpen: false },
positionModal: { isOpen: false, positions: [] },
optionModal: { isOpen: false, options: [] },
checkCounterModal: {
isOpen: false,
options: [],
......
import type { CardMeta, ygopro } from "@/api";
type CardLocation = ReturnType<typeof ygopro.CardLocation.prototype.toObject>;
interface Option {
// card id
code: number;
location?: CardLocation;
// 效果
effectDesc?: string;
// 作为素材的cost,比如同调召唤的星级
level1?: number;
level2?: number;
response: number;
}
export interface ModalState {
// 卡牌弹窗
cardModal: {
......@@ -17,24 +29,27 @@ export interface ModalState {
interactivies: { desc: string; response: number }[];
}[];
};
// 卡牌选择弹窗
checkCardModal: {
// 卡牌选择状态
selectCardActions: {
isOpen: boolean;
onSubmit?: string;
selectMin?: number;
selectMax?: number;
// 如果是连锁,发response给后端的方式稍微有点不同,这里标记下
isChain?: boolean;
min?: number;
max?: number;
// 是否只能选择单个
single?: boolean;
cancelAble: boolean;
cancelResponse?: number;
tags: {
tagName: string;
options: {
meta: CardMeta;
location?: CardLocation;
effectDescCode?: number;
effectDesc?: string;
response: number;
}[];
}[];
finishAble: boolean;
// 上级/同调/超量/链接召唤的总cost
totalLevels?: number;
// cost是否可以溢出,比如同调召唤是false,某些链接召唤是true
overflow?: boolean;
// 已经选择的列表
selecteds: Option[];
// 可以选择的列表
selectables: Option[];
// 必须选择的列表
mustSelects: Option[];
};
// Yes or No弹窗
yesNoModal: {
......@@ -51,48 +66,6 @@ export interface ModalState {
isOpen: boolean;
options: { msg: string; response: number }[];
};
// 卡牌选择弹窗V2
checkCardModalV2: {
isOpen: boolean;
cancelAble: boolean;
finishAble: boolean;
selectMin?: number;
selectMax?: number;
responseable?: boolean;
selectableOptions: {
code: number;
name?: string;
desc?: string;
response: number;
}[];
selectedOptions: {
code: number;
name?: string;
desc?: string;
response: number;
}[];
};
// 卡牌选择弹窗V3
checkCardModalV3: {
isOpen: boolean;
overflow: boolean;
allLevel: number;
selectMin?: number;
selectMax?: number;
responseable?: boolean;
mustSelectList: {
meta: CardMeta;
level1: number;
level2: number;
response: number;
}[];
selectAbleList: {
meta: CardMeta;
level1: number;
level2: number;
response: number;
}[];
};
// 指示器选择弹窗
checkCounterModal: {
isOpen: boolean;
......
......@@ -4,13 +4,11 @@ import {
Alert,
CardListModal,
CardModal,
CheckCardModal,
CheckCardModalV2,
CheckCardModalV3,
CheckCounterModal,
HintNotification,
OptionModal,
PositionModal,
SelectActionsModal,
SortCardModal,
YesNoModal,
} from "./Message";
......@@ -24,12 +22,10 @@ const NeosDuel = () => {
<CardModal />
<CardListModal />
<HintNotification />
<CheckCardModal />
<SelectActionsModal />
<YesNoModal />
<PositionModal />
<OptionModal />
<CheckCardModalV2 />
<CheckCardModalV3 />
<CheckCounterModal />
<SortCardModal />
</>
......
import { ThunderboltOutlined } from "@ant-design/icons";
import { CheckCard, CheckCardProps } from "@ant-design/pro-components";
import { Button, Col, Popover, Row } from "antd";
import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { sendSelectCardResponse, sendSelectChainResponse } from "@/api";
import { useConfig } from "@/config";
import { matStore, messageStore } from "@/stores";
import { DragModal } from "./DragModal";
const NeosConfig = useConfig();
const { checkCardModal } = messageStore;
export const CheckCardModal = () => {
const snapCheckCardModal = useSnapshot(checkCardModal);
const isOpen = snapCheckCardModal.isOpen;
const min = snapCheckCardModal.selectMin ?? 0;
const max = snapCheckCardModal.selectMax ?? 10;
const tabs = snapCheckCardModal.tags;
const onSubmit = snapCheckCardModal.onSubmit;
const cancelAble = snapCheckCardModal.cancelAble;
const cancelResponse = snapCheckCardModal.cancelResponse;
const [response, setResponse] = useState<number[]>([]);
const defaultValue: number[] = [];
const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片";
// TODO: 这里可以考虑更好地封装
const sendResponseHandler = (
handlerName: string | undefined,
response: number[]
) => {
switch (handlerName) {
case "sendSelectChainResponse": {
sendSelectChainResponse(response[0]);
break;
}
case "sendSelectCardResponse": {
sendSelectCardResponse(response);
break;
}
default: {
}
}
};
const resetCheckCardModal = () => {
checkCardModal.isOpen = false;
checkCardModal.selectMin = undefined;
checkCardModal.selectMax = undefined;
checkCardModal.cancelAble = false;
checkCardModal.cancelResponse = undefined;
checkCardModal.tags = [];
};
return (
<DragModal
title={`${preHintMsg} ${selectHintMsg} ${min}-${max}`}
open={isOpen}
closable={false}
footer={
<>
<Button
disabled={response.length < min || response.length > max}
onClick={() => {
sendResponseHandler(onSubmit, response);
checkCardModal.isOpen = false;
resetCheckCardModal();
}}
onFocus={() => {}}
onBlur={() => {}}
>
submit
</Button>
{cancelAble ? (
<Button
onClick={() => {
if (cancelResponse) {
sendResponseHandler(onSubmit, [cancelResponse]);
}
checkCardModal.isOpen = false;
resetCheckCardModal();
}}
onFocus={() => {}}
onBlur={() => {}}
>
cancel
</Button>
) : (
<></>
)}
</>
}
width={800}
>
<CheckCard.Group
multiple
bordered
size="small"
defaultValue={defaultValue}
onChange={(value) => {
// @ts-ignore
setResponse(value);
}}
>
{tabs.map((tab, idx) => {
return (
<Row key={idx}>
{tab.options.map((option, idx) => {
return (
<Col span={4} key={idx}>
<HoverCheckCard
hoverContent={option.effectDesc}
title={option.meta.text.name}
description={option.meta.text.desc}
style={{ width: 120 }}
cover={
<img
alt={option.meta.id.toString()}
src={
option.meta.id
? `${NeosConfig.cardImgUrl}/${option.meta.id}.jpg`
: `${NeosConfig.assetsPath}/card_back.jpg`
}
style={{ width: 100 }}
/>
}
value={option.response}
/>
</Col>
);
})}
</Row>
);
})}
</CheckCard.Group>
</DragModal>
);
};
const HoverCheckCard = (props: CheckCardProps & { hoverContent?: string }) => {
const [hover, setHover] = useState(false);
const onMouseEnter = () => setHover(true);
const onMouseLeave = () => setHover(false);
return (
<>
<CheckCard {...props} />
{props.hoverContent ? (
<Popover content={<p>{props.hoverContent}</p>} open={hover}>
<Button
icon={<ThunderboltOutlined />}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
></Button>
</Popover>
) : (
<></>
)}
</>
);
};
import { CheckCard } from "@ant-design/pro-components";
import { Button, Card, Col, Row } from "antd";
import React from "react";
import { useSnapshot } from "valtio";
import { sendSelectUnselectCardResponse } from "@/api";
import { useConfig } from "@/config";
import { matStore, messageStore } from "@/stores";
import { DragModal } from "./DragModal";
const { checkCardModalV2 } = messageStore;
const NeosConfig = useConfig();
export const CheckCardModalV2 = () => {
const snapCheckCardModalV2 = useSnapshot(checkCardModalV2);
const isOpen = snapCheckCardModalV2.isOpen;
const min = snapCheckCardModalV2.selectMin ?? 0;
const max = snapCheckCardModalV2.selectMax ?? 10;
const cancelable = snapCheckCardModalV2.cancelAble;
const finishable = snapCheckCardModalV2.finishAble;
const selectableOptions = snapCheckCardModalV2.selectableOptions;
const selectedOptions = snapCheckCardModalV2.selectedOptions;
const responseable = snapCheckCardModalV2.responseable;
const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片";
const resetCheckCardModalV2 = () => {
checkCardModalV2.isOpen = false;
checkCardModalV2.finishAble = false;
checkCardModalV2.cancelAble = false;
checkCardModalV2.responseable = false;
checkCardModalV2.selectableOptions = [];
checkCardModalV2.selectedOptions = [];
};
const onFinishOrCancel = () => {
sendSelectUnselectCardResponse({ cancel_or_finish: true });
checkCardModalV2.isOpen = false;
checkCardModalV2.responseable = false;
resetCheckCardModalV2();
};
return (
<DragModal
title={`${preHintMsg} ${selectHintMsg} ${min}-${max}`}
open={isOpen}
closable={false}
footer={
<>
<Button
disabled={!finishable || !responseable}
onClick={onFinishOrCancel}
>
finish
</Button>
<Button
disabled={!cancelable || !responseable}
onClick={onFinishOrCancel}
>
cancel
</Button>
</>
}
width={800}
>
<CheckCard.Group
bordered
size="small"
onChange={(value) => {
if (responseable) {
// @ts-ignore
sendSelectUnselectCardResponse({ selected_ptr: value });
checkCardModalV2.isOpen = false;
checkCardModalV2.responseable = false;
}
}}
>
<Row>
{selectableOptions.map((option, idx) => {
return (
<Col span={4} key={idx}>
<CheckCard
title={option.name}
description={option.desc}
style={{ width: 120 }}
cover={
<img
alt={option.code.toString()}
src={`${NeosConfig.cardImgUrl}/${option.code}.jpg`}
style={{ width: 100 }}
/>
}
value={option.response}
/>
</Col>
);
})}
</Row>
</CheckCard.Group>
<p>已经选择的卡片</p>
<Row>
{selectedOptions.map((option, idx) => {
return (
<Col span={4} key={idx}>
<Card
hoverable
style={{ width: 120 }}
cover={
<img
alt={option.code.toString()}
src={`${NeosConfig.cardImgUrl}/${option.code}.jpg`}
/>
}
/>
</Col>
);
})}
</Row>
</DragModal>
);
};
import { CheckCard } from "@ant-design/pro-components";
import { Button, Card, Col, Row } from "antd";
import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { sendSelectCardResponse } from "@/api";
import { useConfig } from "@/config";
import { matStore, messageStore } from "@/stores";
import { DragModal } from "./DragModal";
const NeosConfig = useConfig();
const { checkCardModalV3 } = messageStore;
export const CheckCardModalV3 = () => {
const snapCheckCardModalV3 = useSnapshot(checkCardModalV3);
const isOpen = snapCheckCardModalV3.isOpen;
const min = snapCheckCardModalV3.selectMin || 0;
const max = snapCheckCardModalV3.selectMax || 0;
const mustSelectOptions = snapCheckCardModalV3.mustSelectList;
const selectAbleOptions = snapCheckCardModalV3.selectAbleList;
const overflow = snapCheckCardModalV3.overflow;
const LevelSum = snapCheckCardModalV3.allLevel;
const [selectedOptions, setSelectedOptions] = useState([]);
const Level1Sum = mustSelectOptions
.concat(selectedOptions)
.map((option) => option.level1)
.reduce((sum, current) => sum + current, 0);
const Level2Sum = mustSelectOptions
.concat(selectedOptions)
.map((option) => option.level2)
.reduce((sum, current) => sum + current, 0);
const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片";
const responseable =
(overflow
? Level1Sum >= LevelSum || Level2Sum >= LevelSum
: Level1Sum == LevelSum || Level2Sum == LevelSum) &&
selectedOptions.length <= max &&
selectedOptions.length >= min;
const onFinish = () => {
sendSelectCardResponse(
mustSelectOptions.concat(selectedOptions).map((option) => option.response)
);
checkCardModalV3.isOpen = false;
checkCardModalV3.responseable = false;
checkCardModalV3.overflow = false;
checkCardModalV3.allLevel = 0;
checkCardModalV3.mustSelectList = [];
checkCardModalV3.selectAbleList = [];
};
return (
<DragModal
title={`${preHintMsg} ${selectHintMsg} ${min}-${max}`}
open={isOpen}
closable={false}
footer={
<>
<Button disabled={!responseable} onClick={onFinish}>
finish
</Button>
</>
}
width={800}
>
<CheckCard.Group
bordered
size="small"
multiple={true}
onChange={(values: any) => {
console.log(values);
setSelectedOptions(values);
}}
>
<Row>
{selectAbleOptions.map((option, idx) => {
return (
<Col span={4} key={idx}>
<CheckCard
title={option.meta.text.name}
description={option.meta.text.desc}
style={{ width: 120 }}
cover={
<img
alt={option.meta.id.toString()}
src={`${NeosConfig.cardImgUrl}/${option.meta.id}.jpg`}
style={{ width: 100 }}
/>
}
value={option}
/>
</Col>
);
})}
</Row>
</CheckCard.Group>
<p>必须选择的卡片</p>
<Row>
{mustSelectOptions.map((option, idx) => {
return (
<Col span={4} key={idx}>
<Card
hoverable
style={{ width: 120 }}
cover={
<img
alt={option.meta.id.toString()}
src={`${NeosConfig.cardImgUrl}/${option.meta.id}.jpg`}
/>
}
/>
</Col>
);
})}
</Row>
</DragModal>
);
};
import { ThunderboltOutlined } from "@ant-design/icons";
import { CheckCard, CheckCardProps } from "@ant-design/pro-components";
import { Button, Card, Col, Popover, Row } from "antd";
import React, { useState } from "react";
import { useSnapshot } from "valtio";
import {
fetchStrings,
sendSelectMultiResponse,
sendSelectSingleResponse,
} from "@/api";
import { useConfig } from "@/config";
import { clearSelectActions, matStore, messageStore } from "@/stores";
import { DragModal } from "./DragModal";
const NeosConfig = useConfig();
const CANCEL_RESPONSE = -1;
const FINISH_RESPONSE = -1;
const { selectCardActions } = messageStore;
export const SelectActionsModal = () => {
const snap = useSnapshot(selectCardActions);
const isOpen = snap.isOpen;
const isChain = snap.isChain;
const min = snap.min ?? 0;
const max = snap.max ?? 0;
const single = snap.single ?? false;
const selecteds = snap.selecteds;
const selectables = snap.selectables;
const mustSelects = snap.mustSelects;
const [response, setResponse] = useState([]);
const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片";
const cancelable = snap.cancelAble;
const finishable = snap.finishAble;
const totalLevels = snap.totalLevels ?? 0;
const overflow = snap.overflow || false;
const LevelSum1 = mustSelects
.concat(response)
.map((option) => option.level1 || 0)
.reduce((sum, current) => sum + current, 0);
const LevelSum2 = mustSelects
.concat(response)
.map((option) => option.level2 || 0)
.reduce((sum, current) => sum + current, 0);
const levelMatched = overflow
? LevelSum1 >= totalLevels || LevelSum2 >= totalLevels
: LevelSum1 == totalLevels || LevelSum2 == totalLevels;
const submitable = single
? response.length == 1
: response.length >= min && response.length <= max && levelMatched;
return (
<DragModal
title={`${preHintMsg} ${selectHintMsg} ${min}-${max} ${
single ? "每次选择一张" : ""
}`}
open={isOpen}
closable={false}
footer={
<>
<Button
disabled={!submitable}
onClick={() => {
const values = mustSelects
.concat(response)
.map((option) => option.response);
if (isChain) {
sendSelectSingleResponse(values[0]);
} else {
sendSelectMultiResponse(values);
}
clearSelectActions();
}}
onFocus={() => {}}
onBlur={() => {}}
>
{fetchStrings("!system", 1211)}
</Button>
<Button
disabled={!finishable}
onClick={() => {
sendSelectSingleResponse(FINISH_RESPONSE);
clearSelectActions();
}}
onFocus={() => {}}
onBlur={() => {}}
>
{fetchStrings("!system", 1296)}
</Button>
<Button
disabled={!cancelable}
onClick={() => {
sendSelectSingleResponse(CANCEL_RESPONSE);
clearSelectActions();
}}
onFocus={() => {}}
onBlur={() => {}}
>
{fetchStrings("!system", 1295)}
</Button>
</>
}
width={800}
>
<CheckCard.Group
multiple
bordered
size="small"
onChange={(value) => {
// @ts-ignore
setResponse(value);
}}
>
<Row>
{selectables.map((option, idx) => {
return (
<Col span={4} key={idx}>
<HoverCheckCard
hoverContent={option.effectDesc}
style={{ width: 120 }}
cover={
<img
alt={option.code.toString()}
src={
option.code
? `${NeosConfig.cardImgUrl}/${option.code}.jpg`
: `${NeosConfig.assetsPath}/card_back.jpg`
}
style={{ width: 100 }}
/>
}
value={option}
/>
</Col>
);
})}
</Row>
<p>{fetchStrings("!system", 212)}</p>
<Row>
{selecteds.concat(mustSelects).map((option, idx) => {
return (
<Col span={4} key={idx}>
<Card
style={{ width: 120 }}
cover={
<img
alt={option.code.toString()}
src={
option.code
? `${NeosConfig.cardImgUrl}/${option.code}.jpg`
: `${NeosConfig.assetsPath}/card_back.jpg`
}
/>
}
/>
</Col>
);
})}
</Row>
</CheckCard.Group>
</DragModal>
);
};
const HoverCheckCard = (props: CheckCardProps & { hoverContent?: string }) => {
const [hover, setHover] = useState(false);
const onMouseEnter = () => setHover(true);
const onMouseLeave = () => setHover(false);
return (
<>
<CheckCard {...props} />
{props.hoverContent ? (
<Popover content={<p>{props.hoverContent}</p>} open={hover}>
<Button
icon={<ThunderboltOutlined />}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
></Button>
</Popover>
) : (
<></>
)}
</>
);
};
export * from "./Alert";
export * from "./CardListModal";
export * from "./CardModal";
export * from "./CheckCardModal";
export * from "./CheckCardModalV2";
export * from "./CheckCardModalV3";
export * from "./CheckCounterModal";
export * from "./DragModal";
export * from "./HintNotification";
export * from "./OptionModal";
export * from "./PositionModal";
export * from "./SelectActionsModal";
export * from "./SendBox";
export * from "./SortCardModal";
export * from "./Status";
......
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