Commit ae0cafb9 authored by Chunchi Che's avatar Chunchi Che

Merge branch 'feat/ui/grave' into 'main'

Feat/ui/grave

See merge request !59
parents 04463686 471d1f17
import { judgeSelf, Cemetery } from "./util";
import {
PayloadAction,
CaseReducer,
createAsyncThunk,
ActionReducerMapBuilder,
} from "@reduxjs/toolkit";
import { DuelState } from "./mod";
import { RootState } from "../../store";
import { fetchCard } from "../../api/cards";
export interface CemeteryState {
cemetery: Cemetery[];
}
// 初始化墓地状态
export const initCemeteryImpl: CaseReducer<DuelState, PayloadAction<number>> = (
state,
action
) => {
const player = action.payload;
if (judgeSelf(player, state)) {
state.meCemetery = { cemetery: [] };
} else {
state.opCemetery = { cemetery: [] };
}
};
// 增加墓地
export const fetchCemeteryMeta = createAsyncThunk(
"duel/fetchCemeteryMeta",
async (param: { controler: number; sequence: number; code: number }) => {
const code = param.code;
const meta = await fetchCard(code);
const response = {
controler: param.controler,
sequence: param.sequence,
meta,
};
return response;
}
);
export const cemeteryCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchCemeteryMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const meta = { id: code, data: {}, text: {} };
if (judgeSelf(controler, state)) {
if (state.meCemetery) {
state.meCemetery.cemetery.push({ sequence, meta });
} else {
state.meCemetery = { cemetery: [{ sequence, meta }] };
}
} else {
if (state.opCemetery) {
state.opCemetery.cemetery.push({ sequence, meta });
} else {
state.opCemetery = { cemetery: [{ sequence, meta }] };
}
}
});
builder.addCase(fetchCemeteryMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
if (state.meCemetery) {
for (const cemetery of state.meCemetery.cemetery) {
if (cemetery.sequence == sequence) {
cemetery.meta = meta;
}
}
}
} else {
if (state.opCemetery) {
for (const cemetery of state.opCemetery.cemetery) {
if (cemetery.sequence == sequence) {
cemetery.meta = meta;
}
}
}
}
});
};
export const selectMeCemetery = (state: RootState) =>
state.duel.meCemetery || { cemetery: [] };
export const selectOpCemetery = (state: RootState) =>
state.duel.opCemetery || { cemetery: [] };
......@@ -23,6 +23,8 @@ import {
setCardModalTextImpl,
setCardModalImgUrlImpl,
setCardModalInteractiviesImpl,
setCardListModalIsOpenImpl,
setCardListModalInfoImpl,
} from "./modalSlice";
import {
MonsterState,
......@@ -38,6 +40,7 @@ import {
clearMagicSelectInfoImpl,
magicCase,
} from "./magicSlice";
import { CemeteryState, initCemeteryImpl, cemeteryCase } from "./cemeretySlice";
export interface DuelState {
selfType?: number;
......@@ -53,6 +56,9 @@ export interface DuelState {
meMagics?: MagicState; // 自己的魔法陷阱区状态
opMagics?: MagicState; // 对手的魔法陷阱区状态
meCemetery?: CemeteryState; // 自己的墓地状态
opCemetery?: CemeteryState; // 对手的墓地状态
meTimeLimit?: TimeLimit; // 自己的计时
opTimeLimit?: TimeLimit; // 对手的计时
......@@ -69,6 +75,7 @@ export interface DuelState {
const initialState: DuelState = {
modalState: {
cardModal: { isOpen: false, interactivies: [] },
cardListModal: { isOpen: false, list: [] },
},
};
......@@ -99,17 +106,23 @@ const duelSlice = createSlice({
addMagicPlaceSelectAble: addMagicPlaceSelectAbleImpl,
clearMagicSelectInfo: clearMagicSelectInfoImpl,
// 墓地相关`Reducer`
initCemetery: initCemeteryImpl,
// UI相关`Reducer`
setCardModalIsOpen: setCardModalIsOpenImpl,
setCardModalText: setCardModalTextImpl,
setCardModalImgUrl: setCardModalImgUrlImpl,
setCardModalInteractivies: setCardModalInteractiviesImpl,
setCardListModalIsOpen: setCardListModalIsOpenImpl,
setCardListModalInfo: setCardListModalInfoImpl,
},
extraReducers(builder) {
handsCase(builder);
hintCase(builder);
monsterCase(builder);
magicCase(builder);
cemeteryCase(builder);
},
});
......@@ -132,6 +145,9 @@ export const {
addMagicPlaceSelectAble,
clearMagicSelectInfo,
removeHand,
initCemetery,
setCardListModalIsOpen,
setCardListModalInfo,
} = duelSlice.actions;
export const selectDuelHsStart = (state: RootState) => {
return state.duel.meInitInfo != null;
......
......@@ -11,6 +11,15 @@ export interface ModalState {
imgUrl?: string;
interactivies: { desc: string; response: number }[];
};
// 卡牌列表弹窗
cardListModal: {
isOpen: boolean;
list: {
name?: string;
desc?: string;
imgUrl?: string;
}[];
};
}
// 更新卡牌弹窗打开状态
......@@ -49,6 +58,24 @@ export const setCardModalInteractiviesImpl: CaseReducer<
state.modalState.cardModal.interactivies = action.payload;
};
// 更新卡牌列表弹窗打开状态
export const setCardListModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.cardListModal.isOpen = action.payload;
};
// 更新卡牌列表文本
export const setCardListModalInfoImpl: CaseReducer<
DuelState,
PayloadAction<{ name?: string; desc?: string; imgUrl?: string }[]>
> = (state, action) => {
const list = action.payload;
state.modalState.cardListModal.list = list;
};
export const selectCardModalIsOpen = (state: RootState) =>
state.duel.modalState.cardModal.isOpen;
export const selectCardModalName = (state: RootState) =>
......@@ -59,3 +86,7 @@ export const selectCardModalImgUrl = (state: RootState) =>
state.duel.modalState.cardModal.imgUrl;
export const selectCardModalInteractivies = (state: RootState) =>
state.duel.modalState.cardModal.interactivies;
export const selectCardListModalIsOpen = (state: RootState) =>
state.duel.modalState.cardListModal.isOpen;
export const selectCardListModalInfo = (state: RootState) =>
state.duel.modalState.cardListModal.list;
......@@ -69,3 +69,8 @@ export interface SlotState {
export type Monster = SlotState;
export type Magic = SlotState;
export interface Cemetery {
sequence: number;
meta: CardMeta;
}
......@@ -4,6 +4,7 @@ import { AppDispatch } from "../../store";
import { fetchMonsterMeta } from "../../reducers/duel/monstersSlice";
import { removeHand } from "../../reducers/duel/mod";
import { fetchMagicMeta } from "../../reducers/duel/magicSlice";
import { fetchCemeteryMeta } from "../../reducers/duel/cemeretySlice";
export default (move: MsgMove, dispatch: AppDispatch) => {
const code = move.code;
......@@ -48,6 +49,17 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
break;
}
case ygopro.CardZone.GRAVE: {
dispatch(
fetchCemeteryMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
break;
}
default: {
console.log(`Unhandled zone type ${to.location}`);
......
......@@ -5,6 +5,7 @@ import {
setSelfType,
initMonsters,
initMagics,
initCemetery,
} from "../../reducers/duel/mod";
export default (
......@@ -36,4 +37,6 @@ export default (
dispatch(initMonsters(1));
dispatch(initMagics(0));
dispatch(initMagics(1));
dispatch(initCemetery(0));
dispatch(initCemetery(1));
};
import React from "react";
import { useAppSelector } from "../../hook";
import { store } from "../../store";
import {
selectCardListModalIsOpen,
selectCardListModalInfo,
} from "../../reducers/duel/modalSlice";
import { setCardListModalIsOpen } from "../../reducers/duel/mod";
import { Modal, List, Popover, Card } from "antd";
const { Meta } = Card;
const CARD_WIDTH = 100;
const CardListModal = () => {
const dispatch = store.dispatch;
const isOpen = useAppSelector(selectCardListModalIsOpen);
const list = useAppSelector(selectCardListModalInfo);
const handleOkOrCancel = () => {
dispatch(setCardListModalIsOpen(false));
};
return (
<Modal open={isOpen} onOk={handleOkOrCancel} onCancel={handleOkOrCancel}>
<List
itemLayout="horizontal"
dataSource={list}
renderItem={(item) => (
<Popover
content={
<Card
hoverable
style={{ width: CARD_WIDTH }}
cover={<img alt={item.name} src={item.imgUrl} />}
>
<Meta title={item.name} />
<p>{item.desc}</p>
</Card>
}
>
<List.Item>
<List.Item.Meta title={item.name} description={item.desc} />
</List.Item>
</Popover>
)}
></List>
</Modal>
);
};
export default CardListModal;
......@@ -31,7 +31,6 @@ const CardModal = () => {
};
return (
<>
<Modal open={isOpen} onOk={handleOkOrCancel} onCancel={handleOkOrCancel}>
<Card
hoverable
......@@ -56,7 +55,6 @@ const CardModal = () => {
);
})}
</Modal>
</>
);
};
......
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
import { Cemetery } from "../../reducers/duel/util";
import {
selectMeCemetery,
selectOpCemetery,
} from "../../reducers/duel/cemeretySlice";
import { store } from "../../store";
import { useAppSelector } from "../../hook";
import { useClick } from "./hook";
import { useRef } from "react";
import {
setCardListModalInfo,
setCardListModalIsOpen,
} from "../../reducers/duel/mod";
const Cemetery = () => {
const shape = CONFIG.CemeterySlotShape();
const position = new BABYLON.Vector3(
3.2,
shape.depth / 2 + CONFIG.Floating,
-2.0
const shape = CONFIG.CemeterySlotShape();
const depth = 0.02;
const Cemeteries = () => {
const meCemetery = useAppSelector(selectMeCemetery).cemetery;
const opCemetery = useAppSelector(selectOpCemetery).cemetery;
return (
<>
<CCemetery
state={meCemetery}
position={cemeteryPosition(0, meCemetery.length)}
rotation={CONFIG.CardSlotRotation(false)}
/>
<CCemetery
state={opCemetery}
position={cemeteryPosition(1, opCemetery.length)}
rotation={CONFIG.CardSlotRotation(true)}
/>
</>
);
};
const CCemetery = (props: {
state: Cemetery[];
position: BABYLON.Vector3;
rotation: BABYLON.Vector3;
}) => {
const boxRef = useRef(null);
const dispatch = store.dispatch;
useClick(
(_event) => {
if (props.state.length != 0) {
dispatch(
setCardListModalInfo(
props.state.map((cemetery) => {
return {
name: cemetery.meta.text.name,
desc: cemetery.meta.text.desc,
imgUrl: `https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${cemetery.meta.id}.jpg`,
};
})
)
);
dispatch(setCardListModalIsOpen(true));
}
},
boxRef,
[props.state]
);
const rotation = CONFIG.CemeterySlotRotation();
return (
<box
name="cemetery"
ref={boxRef}
width={shape.width}
height={shape.height}
depth={shape.depth}
position={position}
rotation={rotation}
depth={depth * props.state.length}
position={props.position}
rotation={props.rotation}
>
<standardMaterial
name="cemetery-mat"
diffuseColor={CONFIG.CemeteryColor()}
diffuseTexture={
new BABYLON.Texture(`http://localhost:3030/images/card_back.jpg`)
}
alpha={props.state.length == 0 ? 0 : 1}
/>
</box>
);
};
export default Cemetery;
const cemeteryPosition = (player: number, cemeteryLength: number) => {
const x = player == 0 ? 3.2 : -3.2;
const y = (depth * cemeteryLength) / 2 + CONFIG.Floating;
const z = player == 0 ? -2.0 : 2.0;
return new BABYLON.Vector3(x, y, z);
};
export default Cemeteries;
......@@ -11,6 +11,8 @@ import Magics from "./magics";
import Field from "./field";
import Deck from "./deck";
import Exclusion from "./exclusion";
import Cemeteries from "./cemetery";
import CardListModal from "./cardListModal";
// Ref: https://github.com/brianzinn/react-babylonjs/issues/126
const NeosDuel = () => (
......@@ -27,6 +29,7 @@ const NeosDuel = () => (
<Magics />
<Field />
<Deck />
<Cemeteries />
<Exclusion />
<Ground />
</Provider>
......@@ -35,6 +38,7 @@ const NeosDuel = () => (
)}
</ReactReduxContext.Consumer>
<CardModal />
<CardListModal />
<HintNotification />
</>
);
......
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