Commit 49c04e7c authored by Chunchi Che's avatar Chunchi Che

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

Feat/ui/select counter

See merge request mycard/Neos!159
parents 2984f1c7 e8dcd004
Pipeline #21026 passed with stages
in 17 minutes and 35 seconds
...@@ -102,6 +102,14 @@ impl BufferWriter { ...@@ -102,6 +102,14 @@ impl BufferWriter {
self.array.extend(value.to_le_bytes()); self.array.extend(value.to_le_bytes());
} }
pub fn writeUint16(&mut self, value: u16) {
self.array.extend(value.to_le_bytes());
}
pub fn writeInt16(&mut self, value: i16) {
self.array.extend(value.to_le_bytes());
}
pub fn writeUint32(&mut self, value: u32) { pub fn writeUint32(&mut self, value: u32) {
self.array.extend(value.to_le_bytes()); self.array.extend(value.to_le_bytes());
} }
......
...@@ -10,6 +10,7 @@ import adaptSelectPositionResponse from "./selectPosition"; ...@@ -10,6 +10,7 @@ import adaptSelectPositionResponse from "./selectPosition";
import adaptSelectOptionResponse from "./selectOption"; import adaptSelectOptionResponse from "./selectOption";
import adaptSelectBattleCmdResponse from "./selectBattleCmd"; import adaptSelectBattleCmdResponse from "./selectBattleCmd";
import adaptSelectUnselectCardResponse from "./selectUnselectCard"; import adaptSelectUnselectCardResponse from "./selectUnselectCard";
import adaptSelectCounterResponse from "./selectCounter";
/* /*
* CTOS CTOS_RESPONSE * CTOS CTOS_RESPONSE
...@@ -72,6 +73,13 @@ export default class CtosResponsePacket extends YgoProPacket { ...@@ -72,6 +73,13 @@ export default class CtosResponsePacket extends YgoProPacket {
break; break;
} }
case "select_counter_response": {
extraData = adaptSelectCounterResponse(
response.select_counter_response
);
break;
}
default: { default: {
break; break;
} }
......
import { ygopro } from "../../../idl/ocgcore";
// @ts-ignore
import { BufferWriter } from "rust-src";
export default (response: ygopro.CtosGameMsgResponse.SelectCounterResponse) => {
const writer = new BufferWriter();
for (const count of response.selected_count) {
writer.writeInt16(count);
}
return writer.toArray();
};
...@@ -268,3 +268,17 @@ export function sendSelectUnselectCardResponse(value: { ...@@ -268,3 +268,17 @@ export function sendSelectUnselectCardResponse(value: {
socketMiddleWare({ cmd: socketCmd.SEND, payload }); socketMiddleWare({ cmd: socketCmd.SEND, payload });
} }
export function sendSelectCounterResponse(counts: number[]) {
const response = new ygopro.YgoCtosMsg({
ctos_response: new ygopro.CtosGameMsgResponse({
select_counter_response:
new ygopro.CtosGameMsgResponse.SelectCounterResponse({
selected_count: counts,
}),
}),
});
const payload = new GameMsgResponse(response).serialize();
socketMiddleWare({ cmd: socketCmd.SEND, payload });
}
...@@ -59,9 +59,10 @@ import { ...@@ -59,9 +59,10 @@ import {
setCheckCardModalV3OverFlowImpl, setCheckCardModalV3OverFlowImpl,
setCheckCardModalV3ResponseAbleImpl, setCheckCardModalV3ResponseAbleImpl,
resetCheckCardModalV3Impl, resetCheckCardModalV3Impl,
setCheckCardModalV3SelectedImpl,
checkCardModalV3Case, checkCardModalV3Case,
setCardModalCountersImpl, setCardModalCountersImpl,
setCheckCounterImpl,
clearCheckCounterImpl,
} from "./modal/mod"; } from "./modal/mod";
import { import {
MonsterState, MonsterState,
...@@ -184,7 +185,10 @@ const initialState: DuelState = { ...@@ -184,7 +185,10 @@ const initialState: DuelState = {
allLevel: 0, allLevel: 0,
mustSelectList: [], mustSelectList: [],
selectAbleList: [], selectAbleList: [],
selectedList: [], },
checkCounterModal: {
isOpen: false,
options: [],
}, },
}, },
}; };
...@@ -279,8 +283,9 @@ const duelSlice = createSlice({ ...@@ -279,8 +283,9 @@ const duelSlice = createSlice({
setCheckCardModalV3OverFlow: setCheckCardModalV3OverFlowImpl, setCheckCardModalV3OverFlow: setCheckCardModalV3OverFlowImpl,
setCheckCardModalV3ResponseAble: setCheckCardModalV3ResponseAbleImpl, setCheckCardModalV3ResponseAble: setCheckCardModalV3ResponseAbleImpl,
resetCheckCardModalV3: resetCheckCardModalV3Impl, resetCheckCardModalV3: resetCheckCardModalV3Impl,
setCheckCardModalV3Selected: setCheckCardModalV3SelectedImpl,
setCardModalCounters: setCardModalCountersImpl, setCardModalCounters: setCardModalCountersImpl,
setCheckCounter: setCheckCounterImpl,
clearCheckCounter: clearCheckCounterImpl,
// 通用的`Reducer` // 通用的`Reducer`
clearAllIdleInteractivities: clearAllIdleInteractivitiesImpl, clearAllIdleInteractivities: clearAllIdleInteractivitiesImpl,
...@@ -393,8 +398,9 @@ export const { ...@@ -393,8 +398,9 @@ export const {
setCheckCardModalV3OverFlow, setCheckCardModalV3OverFlow,
setCheckCardModalV3ResponseAble, setCheckCardModalV3ResponseAble,
resetCheckCardModalV3, resetCheckCardModalV3,
setCheckCardModalV3Selected,
setCardModalCounters, setCardModalCounters,
setCheckCounter,
clearCheckCounter,
} = duelSlice.actions; } = duelSlice.actions;
export const selectDuelHsStart = (state: RootState) => { export const selectDuelHsStart = (state: RootState) => {
return state.duel.meInitInfo != null; return state.duel.meInitInfo != null;
......
...@@ -5,7 +5,7 @@ import { ...@@ -5,7 +5,7 @@ import {
CaseReducer, CaseReducer,
createAsyncThunk, createAsyncThunk,
} from "@reduxjs/toolkit"; } from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "../../../api/cards"; import { fetchCard } from "../../../api/cards";
import { RootState } from "../../../store"; import { RootState } from "../../../store";
import { ygopro } from "../../../api/ocgcore/idl/ocgcore"; import { ygopro } from "../../../api/ocgcore/idl/ocgcore";
import { findCardByLocation } from "../util"; import { findCardByLocation } from "../util";
...@@ -48,17 +48,6 @@ export const setCheckCardModalV3ResponseAbleImpl: DuelReducer<boolean> = ( ...@@ -48,17 +48,6 @@ export const setCheckCardModalV3ResponseAbleImpl: DuelReducer<boolean> = (
state.modalState.checkCardModalV3.responseable = action.payload; state.modalState.checkCardModalV3.responseable = action.payload;
}; };
export const setCheckCardModalV3SelectedImpl: DuelReducer<
{
meta: CardMeta;
level1: number;
level2: number;
response: number;
}[]
> = (state, action) => {
state.modalState.checkCardModalV3.selectedList = action.payload;
};
// 增加卡牌选项 // 增加卡牌选项
export const fetchCheckCardMetasV3 = createAsyncThunk( export const fetchCheckCardMetasV3 = createAsyncThunk(
"duel/fetchCheckCardMetaV3", "duel/fetchCheckCardMetaV3",
...@@ -137,7 +126,6 @@ export const resetCheckCardModalV3Impl: CaseReducer<DuelState> = (state) => { ...@@ -137,7 +126,6 @@ export const resetCheckCardModalV3Impl: CaseReducer<DuelState> = (state) => {
modalState.responseable = undefined; modalState.responseable = undefined;
modalState.mustSelectList = []; modalState.mustSelectList = [];
modalState.selectAbleList = []; modalState.selectAbleList = [];
modalState.selectedList = [];
}; };
export const selectCheckCardModalV3 = (state: RootState) => export const selectCheckCardModalV3 = (state: RootState) =>
......
// 后续对于`MSG_SELECT_XXX`的处理UI都尽量用`Babylon.js`实现而不会通过`Antd`的`Modal`实现,因此这里不追求工程质量,暂时简单实现下。
import { PayloadAction, CaseReducer } from "@reduxjs/toolkit";
import { RootState } from "../../../store";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
import { ygopro } from "../../../api/ocgcore/idl/ocgcore";
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;
...@@ -92,12 +92,15 @@ export interface ModalState { ...@@ -92,12 +92,15 @@ export interface ModalState {
level2: number; level2: number;
response: number; response: number;
}[]; }[];
// TODO: remove this prop };
selectedList: { // 指示器选择弹窗
meta: CardMeta; checkCounterModal: {
level1: number; isOpen: boolean;
level2: number; counterType?: number;
response: number; min?: number;
options: {
code: number;
max: number;
}[]; }[];
}; };
} }
...@@ -110,3 +113,4 @@ export * from "./positionModalSlice"; ...@@ -110,3 +113,4 @@ export * from "./positionModalSlice";
export * from "./optionModalSlice"; export * from "./optionModalSlice";
export * from "./checkCardModalV2Slice"; export * from "./checkCardModalV2Slice";
export * from "./checkCardModalV3Slice"; export * from "./checkCardModalV3Slice";
export * from "./checkCounterModalSlice";
...@@ -29,9 +29,11 @@ export function judgeSelf(player: number, state: Draft<DuelState>): boolean { ...@@ -29,9 +29,11 @@ export function judgeSelf(player: number, state: Draft<DuelState>): boolean {
* 通过`controler`,`zone`和`sequence`获取卡牌状态*/ * 通过`controler`,`zone`和`sequence`获取卡牌状态*/
export function findCardByLocation( export function findCardByLocation(
state: Draft<DuelState>, state: Draft<DuelState>,
location: ygopro.CardLocation location:
| ygopro.CardLocation
| ReturnType<typeof ygopro.CardLocation.prototype.toObject>
): CardState | undefined { ): CardState | undefined {
const controler = location.controler; const controler = location.controler!;
const zone = location.location; const zone = location.location;
const sequence = location.sequence; const sequence = location.sequence;
......
import { ygopro } from "../../api/ocgcore/idl/ocgcore"; import { ygopro } from "../../api/ocgcore/idl/ocgcore";
import { setCheckCounter } from "../../reducers/duel/mod";
import { AppDispatch } from "../../store"; import { AppDispatch } from "../../store";
import MsgSelectCounter = ygopro.StocGameMessage.MsgSelectCounter; import MsgSelectCounter = ygopro.StocGameMessage.MsgSelectCounter;
export default (selectCounter: MsgSelectCounter, dispatch: AppDispatch) => { export default (selectCounter: MsgSelectCounter, dispatch: AppDispatch) => {
console.log(selectCounter); dispatch(setCheckCounter(selectCounter.toObject()));
}; };
...@@ -21,7 +21,7 @@ const CheckCardModalV3 = () => { ...@@ -21,7 +21,7 @@ const CheckCardModalV3 = () => {
const max = state.selectMax || 0; const max = state.selectMax || 0;
const mustSelectOptions = state.mustSelectList; const mustSelectOptions = state.mustSelectList;
const selectAbleOptions = state.selectAbleList; const selectAbleOptions = state.selectAbleList;
const [selectedOptions, setSelectedOptions] = useState(state.selectedList); const [selectedOptions, setSelectedOptions] = useState([]);
const overflow = state.overflow; const overflow = state.overflow;
const LevelSum = state.allLevel; const LevelSum = state.allLevel;
const Level1Sum = mustSelectOptions const Level1Sum = mustSelectOptions
......
import { Button, Row, Col, Card, InputNumber } from "antd";
import React, { useRef, useState } from "react";
import { sendSelectCounterResponse } from "../../api/ocgcore/ocgHelper";
import { fetchStrings } from "../../api/strings";
import { useAppSelector } from "../../hook";
import { clearCheckCounter } from "../../reducers/duel/mod";
import { selectCheckCounterModal } from "../../reducers/duel/modal/checkCounterModalSlice";
import { store } from "../../store";
import DragModal from "./dragModal";
import NeosConfig from "../../../neos.config.json";
const CheckCounterModal = () => {
const dispatch = store.dispatch;
const state = useAppSelector(selectCheckCounterModal);
const isOpen = state.isOpen;
const counterName = fetchStrings("!counter", `0x${state.counterType!}`);
const min = state.min || 0;
const options = state.options;
const [selected, setSelected] = useState(new Array(options.length));
const sum = selected.reduce((sum, current) => sum + current, 0);
const finishable = sum == min;
const draggleRef = useRef<HTMLDivElement>(null);
const onFinish = () => {
sendSelectCounterResponse(selected);
dispatch(clearCheckCounter());
};
return (
<DragModal
modalProps={{
title: `请移除${min}个${counterName}`,
open: isOpen,
closable: false,
footer: (
<Button disabled={!finishable} onClick={onFinish}>
finish
</Button>
),
}}
dragRef={draggleRef}
draggable={true}
>
<Row>
{options.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`}
/>
}
>
<InputNumber
min={0}
max={option.max}
defaultValue={0}
onChange={(value) => {
let newSelected = [...selected];
newSelected[idx] = value || 0;
setSelected(newSelected);
}}
/>
</Card>
</Col>
);
})}
</Row>
</DragModal>
);
};
export default CheckCounterModal;
...@@ -27,6 +27,7 @@ import SendBox from "./sendBox"; ...@@ -27,6 +27,7 @@ import SendBox from "./sendBox";
import PlayerStatus from "./status"; import PlayerStatus from "./status";
import Alert from "./alert"; import Alert from "./alert";
import CheckCardModalV3 from "./checkCardModalV3"; import CheckCardModalV3 from "./checkCardModalV3";
import CheckCounterModal from "./checkCounterModal";
// Ref: https://github.com/brianzinn/react-babylonjs/issues/126 // Ref: https://github.com/brianzinn/react-babylonjs/issues/126
const NeosDuel = () => { const NeosDuel = () => {
...@@ -48,6 +49,7 @@ const NeosDuel = () => { ...@@ -48,6 +49,7 @@ const NeosDuel = () => {
<OptionModal /> <OptionModal />
<CheckCardModalV2 /> <CheckCardModalV2 />
<CheckCardModalV3 /> <CheckCardModalV3 />
<CheckCounterModal />
</> </>
); );
}; };
......
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