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 {
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) {
self.array.extend(value.to_le_bytes());
}
......
......@@ -10,6 +10,7 @@ import adaptSelectPositionResponse from "./selectPosition";
import adaptSelectOptionResponse from "./selectOption";
import adaptSelectBattleCmdResponse from "./selectBattleCmd";
import adaptSelectUnselectCardResponse from "./selectUnselectCard";
import adaptSelectCounterResponse from "./selectCounter";
/*
* CTOS CTOS_RESPONSE
......@@ -72,6 +73,13 @@ export default class CtosResponsePacket extends YgoProPacket {
break;
}
case "select_counter_response": {
extraData = adaptSelectCounterResponse(
response.select_counter_response
);
break;
}
default: {
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: {
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 {
setCheckCardModalV3OverFlowImpl,
setCheckCardModalV3ResponseAbleImpl,
resetCheckCardModalV3Impl,
setCheckCardModalV3SelectedImpl,
checkCardModalV3Case,
setCardModalCountersImpl,
setCheckCounterImpl,
clearCheckCounterImpl,
} from "./modal/mod";
import {
MonsterState,
......@@ -184,7 +185,10 @@ const initialState: DuelState = {
allLevel: 0,
mustSelectList: [],
selectAbleList: [],
selectedList: [],
},
checkCounterModal: {
isOpen: false,
options: [],
},
},
};
......@@ -279,8 +283,9 @@ const duelSlice = createSlice({
setCheckCardModalV3OverFlow: setCheckCardModalV3OverFlowImpl,
setCheckCardModalV3ResponseAble: setCheckCardModalV3ResponseAbleImpl,
resetCheckCardModalV3: resetCheckCardModalV3Impl,
setCheckCardModalV3Selected: setCheckCardModalV3SelectedImpl,
setCardModalCounters: setCardModalCountersImpl,
setCheckCounter: setCheckCounterImpl,
clearCheckCounter: clearCheckCounterImpl,
// 通用的`Reducer`
clearAllIdleInteractivities: clearAllIdleInteractivitiesImpl,
......@@ -393,8 +398,9 @@ export const {
setCheckCardModalV3OverFlow,
setCheckCardModalV3ResponseAble,
resetCheckCardModalV3,
setCheckCardModalV3Selected,
setCardModalCounters,
setCheckCounter,
clearCheckCounter,
} = duelSlice.actions;
export const selectDuelHsStart = (state: RootState) => {
return state.duel.meInitInfo != null;
......
......@@ -5,7 +5,7 @@ import {
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "../../../api/cards";
import { fetchCard } from "../../../api/cards";
import { RootState } from "../../../store";
import { ygopro } from "../../../api/ocgcore/idl/ocgcore";
import { findCardByLocation } from "../util";
......@@ -48,17 +48,6 @@ export const setCheckCardModalV3ResponseAbleImpl: DuelReducer<boolean> = (
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(
"duel/fetchCheckCardMetaV3",
......@@ -137,7 +126,6 @@ export const resetCheckCardModalV3Impl: CaseReducer<DuelState> = (state) => {
modalState.responseable = undefined;
modalState.mustSelectList = [];
modalState.selectAbleList = [];
modalState.selectedList = [];
};
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 {
level2: number;
response: number;
}[];
// TODO: remove this prop
selectedList: {
meta: CardMeta;
level1: number;
level2: number;
response: number;
};
// 指示器选择弹窗
checkCounterModal: {
isOpen: boolean;
counterType?: number;
min?: number;
options: {
code: number;
max: number;
}[];
};
}
......@@ -110,3 +113,4 @@ export * from "./positionModalSlice";
export * from "./optionModalSlice";
export * from "./checkCardModalV2Slice";
export * from "./checkCardModalV3Slice";
export * from "./checkCounterModalSlice";
......@@ -29,9 +29,11 @@ export function judgeSelf(player: number, state: Draft<DuelState>): boolean {
* 通过`controler`,`zone`和`sequence`获取卡牌状态*/
export function findCardByLocation(
state: Draft<DuelState>,
location: ygopro.CardLocation
location:
| ygopro.CardLocation
| ReturnType<typeof ygopro.CardLocation.prototype.toObject>
): CardState | undefined {
const controler = location.controler;
const controler = location.controler!;
const zone = location.location;
const sequence = location.sequence;
......
import { ygopro } from "../../api/ocgcore/idl/ocgcore";
import { setCheckCounter } from "../../reducers/duel/mod";
import { AppDispatch } from "../../store";
import MsgSelectCounter = ygopro.StocGameMessage.MsgSelectCounter;
export default (selectCounter: MsgSelectCounter, dispatch: AppDispatch) => {
console.log(selectCounter);
dispatch(setCheckCounter(selectCounter.toObject()));
};
......@@ -21,7 +21,7 @@ const CheckCardModalV3 = () => {
const max = state.selectMax || 0;
const mustSelectOptions = state.mustSelectList;
const selectAbleOptions = state.selectAbleList;
const [selectedOptions, setSelectedOptions] = useState(state.selectedList);
const [selectedOptions, setSelectedOptions] = useState([]);
const overflow = state.overflow;
const LevelSum = state.allLevel;
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";
import PlayerStatus from "./status";
import Alert from "./alert";
import CheckCardModalV3 from "./checkCardModalV3";
import CheckCounterModal from "./checkCounterModal";
// Ref: https://github.com/brianzinn/react-babylonjs/issues/126
const NeosDuel = () => {
......@@ -48,6 +49,7 @@ const NeosDuel = () => {
<OptionModal />
<CheckCardModalV2 />
<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