Commit 9f0682ec authored by Chunchi Che's avatar Chunchi Che

remove PlayMat and remain NewPlayMat

parent 0679fcf3
......@@ -13,9 +13,8 @@ import {
SortCardModal,
YesNoModal,
} from "./Message";
import { Mat as NewMat } from "./NewPlayMat";
import { Mat } from "./NewPlayMat";
import { Menu } from "./NewPlayMat/Menu";
import Mat from "./PlayMat";
const NeosDuel = () => {
return (
......@@ -24,7 +23,7 @@ const NeosDuel = () => {
{/* <Test /> */}
{/* <Mat /> */}
<Menu />
<NewMat />
<Mat />
<CardModal />
<CardListModal />
<HintNotification />
......
......@@ -121,8 +121,8 @@ const onCardClick = (card: CardType) => {
// 中央弹窗展示选中卡牌信息
messageStore.cardModal.meta = {
id: card.code,
text: card.text,
data: card.data,
text: card.meta.text,
data: card.meta.data,
};
messageStore.cardModal.interactivies = card.idleInteractivities.map(
(interactivity) => ({
......@@ -139,8 +139,8 @@ const onCardClick = (card: CardType) => {
card.overlayMaterials.map((overlay) => ({
meta: {
id: overlay.code,
text: overlay.text,
data: overlay.data,
text: overlay.meta.text,
data: overlay.meta.data,
},
interactivies: [],
})) || [];
......@@ -153,8 +153,8 @@ const onFieldClick = (card: CardType) => () => {
messageStore.cardListModal.list = displayStates.map((item) => ({
meta: {
id: item.code,
text: item.text,
data: item.data,
text: item.meta.text,
data: item.meta.data,
},
interactivies: item.idleInteractivities.map((interactivy) => ({
desc: interactTypeToString(interactivy.interactType),
......
......@@ -11,10 +11,7 @@ import {
sendSurrender,
ygopro,
} from "@/api";
import {
clearAllIdleInteractivities as clearAllIdleInteractivities,
matStore,
} from "@/stores";
import { cardStore, matStore } from "@/stores";
import PhaseType = ygopro.StocGameMessage.MsgNewPhase.PhaseType;
const { phase } = matStore;
......@@ -37,22 +34,25 @@ export const Menu = () => {
? 3
: 7;
const clearAllIdleInteractivities = () => {
for (const card of cardStore.inner) {
card.idleInteractivities = [];
}
};
const onBp = () => {
sendSelectIdleCmdResponse(6);
clearAllIdleInteractivities(0);
clearAllIdleInteractivities(1);
clearAllIdleInteractivities();
phase.enableBp = false;
};
const onM2 = () => {
sendSelectBattleCmdResponse(2);
clearAllIdleInteractivities(0);
clearAllIdleInteractivities(1);
clearAllIdleInteractivities();
phase.enableM2 = false;
};
const onEp = () => {
sendSelectIdleCmdResponse(response);
clearAllIdleInteractivities(0);
clearAllIdleInteractivities(1);
clearAllIdleInteractivities();
phase.enableEp = false;
};
const onSurrender = () => {
......
import "@/styles/mat.css";
import classnames from "classnames";
import React, { MouseEventHandler } from "react";
import { sendSelectPlaceResponse } from "@/api";
import {
CardState,
clearAllPlaceInteradtivities,
DuelFieldState,
} from "@/stores";
export const Block: React.FC<{
isExtra?: boolean;
highlight?: boolean;
onClick?: MouseEventHandler;
outerLeft?: boolean;
outerRight?: boolean;
}> = ({
isExtra = false,
highlight = false,
onClick,
outerLeft = false,
outerRight = false,
}) => (
<div
className={classnames("block", {
"block-extra": isExtra,
"block-left": outerLeft,
"block-right": outerRight,
})}
style={
{
"--highlight-on": highlight ? 1 : 0,
} as any
}
onClick={onClick}
/>
);
export function BlockRow<T extends DuelFieldState>(props: {
states: T;
leftState?: CardState;
rightState?: CardState;
}) {
return (
<div className="block-row">
{props.leftState ? (
<Block
highlight={props.leftState.placeInteractivity !== undefined}
onClick={() => {
onBlockClick(props.leftState!);
}}
outerLeft
/>
) : (
<></>
)}
{props.states.map((block, idx) => (
<Block
key={idx}
highlight={block.placeInteractivity !== undefined}
onClick={() => {
onBlockClick(block);
}}
/>
))}
{props.rightState ? (
<Block
highlight={props.rightState.placeInteractivity !== undefined}
onClick={() => {
onBlockClick(props.rightState!);
}}
outerRight
/>
) : (
<></>
)}
</div>
);
}
export const ExtraBlockRow: React.FC<{
meLeft: CardState;
meRight: CardState;
opLeft: CardState;
opRight: CardState;
}> = ({ meLeft, meRight, opLeft, opRight }) => (
<div className="block-row">
<Block
highlight={
meLeft.placeInteractivity !== undefined ||
opLeft.placeInteractivity !== undefined
}
isExtra={true}
onClick={() => {
onBlockClick(meLeft);
onBlockClick(opLeft);
}}
/>
<Block
highlight={
meRight.placeInteractivity !== undefined ||
opRight.placeInteractivity !== undefined
}
isExtra={true}
onClick={() => {
onBlockClick(meRight);
onBlockClick(opRight);
}}
/>
</div>
);
const onBlockClick = (state: CardState) => {
if (state.placeInteractivity) {
sendSelectPlaceResponse(state.placeInteractivity.response);
clearAllPlaceInteradtivities(0);
clearAllPlaceInteradtivities(1);
}
};
import "@/styles/mat.css";
import classnames from "classnames";
import React, { type CSSProperties, MouseEventHandler } from "react";
import { useConfig } from "@/config";
import { Chain } from "./Chain";
const NeosConfig = useConfig();
const ASSETS_BASE =
import.meta.env.BASE_URL == "/"
? NeosConfig.assetsPath
: import.meta.env.BASE_URL + NeosConfig.assetsPath;
const FOCUS_SCALE = 2.5;
const FOCUS_HIGHT = 100;
export const Card: React.FC<{
code: number;
row: number;
col: number;
hight: number;
opponent?: boolean;
defense?: boolean;
facedown?: boolean;
vertical?: boolean;
highlight?: boolean;
focus?: boolean;
fly?: boolean;
chainIdx?: number;
transTime?: number;
onClick?: MouseEventHandler<{}>;
style?: CSSProperties;
}> = ({
code,
row,
col,
hight,
defense = false,
facedown = false,
opponent = false,
vertical = false,
highlight = false,
focus = false,
fly = false,
chainIdx,
transTime = 0.3,
onClick,
style = {},
}) => (
<div
className={classnames("card", {
"card-defense": defense,
fly: fly && !focus,
})}
style={
{
"--h": focus ? FOCUS_HIGHT : hight,
"--r": row,
"--c": col,
"--shadow": hight > 0 ? 1 : 0,
"--opponent-deg": opponent ? "180deg" : "0deg",
"--vertical": vertical ? 1 : 0,
"--trans-time": `${
fly ? NeosConfig.ui.chainingDelay / 1000 : transTime
}s`,
"--highlight-on": highlight ? 1 : 0,
"--scale-focus": focus ? FOCUS_SCALE : 1,
"--card-img": facedown
? `url(${ASSETS_BASE + "/card_back.jpg"})`
: `url(${NeosConfig.cardImgUrl + "/" + code + ".jpg"})`,
...style,
} as any
}
onClick={onClick}
>
{chainIdx ? <Chain chainIdx={chainIdx} /> : <></>}
</div>
);
import "@/styles/chain.css";
import React from "react";
const CIRCLES_COUNT = 10;
const EASE = 0.2;
const R = 60;
export const Chain: React.FC<{ chainIdx: number }> = (props: {
chainIdx: number;
}) => (
<div
className="circles"
style={
{
"--R": R + "px",
} as any
}
>
{calcXYs(30, CIRCLES_COUNT).map((item, idx) => (
<div
className="circle"
key={idx}
style={
{
"--x": item.X + "px",
"--y": item.Y + "px",
"--ease": (idx * EASE).toString() + "s",
} as any
}
></div>
))}
<div className="font">{props.chainIdx}</div>
</div>
);
// Ref: https://zhuanlan.zhihu.com/p/104226591
/**
* R:大圆半径,2*R = 外部正方形的边长
* counts: 圆的数量
* 返回值:
* [
* [x1,y1],
* [x2,y2],
* ...
* ]
*/
function calcXYs(R: number, counts: number) {
// 当前度数
let deg = 0;
// 单位度数
let pDeg = 360 / counts;
return Array(counts)
.fill(0)
.map((_, i) => {
// 度数以单位度数递增
deg = pDeg * i;
// Math.sin接收的参数以 π 为单位,需要根据360度 = 2π进行转化
const proportion = Math.PI / 180;
// 以外部DIV左下角为原点,计算小圆圆心的横纵坐标
const Y = R + R * Math.sin(proportion * deg);
const X = R + R * Math.cos(proportion * deg);
return { X, Y, deg };
});
}
import "@/styles/mat.css";
import React from "react";
import { useSnapshot } from "valtio";
import { ygopro } from "@/api";
import { CardState, DuelFieldState, matStore, messageStore } from "@/stores";
import { interactTypeToString } from "../utils";
import { BlockRow, ExtraBlockRow } from "./Block";
import { Card } from "./Card";
import { Menu } from "./Menu";
import YgoZone = ygopro.CardZone;
import YgoPosition = ygopro.CardPosition;
type RenderCard = CardState & {
sequence: number;
opponent?: boolean;
};
const HIGH_SCALE = 0.1;
const DEFAULT_HIGH = 1;
export const Mat = () => {
const snap = useSnapshot(matStore);
const monsters = snap.monsters;
const magics = snap.magics;
const hands = snap.hands;
const grave = snap.graveyards;
const banished = snap.banishedZones;
const deck = snap.decks;
const extraDeck = snap.extraDecks;
const mapper =
(opponent?: boolean) => (state: CardState, sequence: number) => {
return {
sequence,
opponent,
...state,
};
};
const filter = (state: CardState) => state.occupant !== undefined;
const renderCards: RenderCard[] = monsters.me
.map(mapper())
.filter(filter)
.concat(monsters.op.map(mapper(true)).filter(filter))
.concat(magics.me.map(mapper()).filter(filter))
.concat(magics.op.map(mapper(true)).filter(filter))
.concat(hands.me.map(mapper()).filter(filter))
.concat(hands.op.map(mapper(true)).filter(filter))
.concat(grave.me.map(mapper()).filter(filter))
.concat(grave.op.map(mapper(true)).filter(filter))
.concat(banished.me.map(mapper()).filter(filter))
.concat(banished.op.map(mapper(true)).filter(filter))
.concat(deck.me.map(mapper()).filter(filter))
.concat(deck.op.map(mapper(true)).filter(filter))
.concat(extraDeck.me.map(mapper()).filter(filter))
.concat(extraDeck.op.map(mapper(true)).filter(filter));
renderCards.sort((card_a, card_b) => (card_a.uuid > card_b.uuid ? 1 : 0));
return (
<>
<Menu />
<div id="life-bar-container">
<div id="life-bar">{`${snap.initInfo.me.name}: ${snap.initInfo.me.life}`}</div>
<div id="life-bar">{`${snap.initInfo.op.name}: ${snap.initInfo.op.life}`}</div>
</div>
<div id="camera">
<div id="board">
<div id="board-bg">
<BlockRow states={magics.op.slice(0, 5) as DuelFieldState} />
<BlockRow
states={monsters.op.slice(0, 5) as DuelFieldState}
rightState={magics.op[5] as CardState}
/>
<ExtraBlockRow
meLeft={monsters.me[5] as CardState}
meRight={monsters.me[6] as CardState}
opLeft={monsters.op[5] as CardState}
opRight={monsters.op[6] as CardState}
/>
<BlockRow
states={monsters.me.slice(0, 5) as DuelFieldState}
leftState={magics.me[5] as CardState}
/>
<BlockRow states={magics.me.slice(0, 5) as DuelFieldState} />
</div>
{renderCards.map((card) => (
<Card
key={card.uuid}
code={card.occupant!.id}
row={cardStateToRow(card)}
col={cardStateToCol(card)}
hight={CardStateToHigh(card)}
defense={
!card.focus &&
(card.location.position === YgoPosition.DEFENSE ||
card.location.position === YgoPosition.FACEDOWN_DEFENSE ||
card.location.position === YgoPosition.FACEUP_DEFENSE)
}
facedown={CardStateToFaceDown(card)}
vertical={card.location.zone == YgoZone.HAND || card.focus}
highlight={card.idleInteractivities.length > 0}
focus={
card.focus ||
(card.chaining && card.location.zone == YgoZone.HAND)
}
fly={
(card.chaining && card.location.zone != YgoZone.HAND) ||
card.attackTarget !== undefined ||
card.directAttack
}
opponent={card.opponent}
chainIdx={card.chainIndex}
onClick={
card.location.zone == YgoZone.SZONE ||
card.location.zone == YgoZone.MZONE ||
card.location.zone == YgoZone.HAND
? onCardClick(card)
: card.location.zone == YgoZone.DECK
? () => {}
: onFieldClick(renderCards, card)
}
/>
))}
</div>
</div>
</>
);
};
function cardStateToRow(state: RenderCard): number {
if (state.focus) return 2;
if (state.directAttack) {
// 正在直接攻击玩家
if (state.opponent) {
return 4.5;
} else {
return -0.5;
}
}
if (state.attackTarget) {
// 正在攻击怪兽
return (
cardStateToRow(state.attackTarget) -
0.2 * (state.attackTarget.opponent ? -1 : 1)
);
}
if (state.opponent) {
switch (state.location.zone) {
case YgoZone.EXTRA:
case YgoZone.DECK:
return 0;
case YgoZone.HAND:
return state.chaining ? 2 : -1;
case YgoZone.SZONE:
return state.sequence >= 5 ? 1 : 0;
case YgoZone.GRAVE:
return 1;
case YgoZone.MZONE:
return state.sequence >= 5 ? 2 : 1;
case YgoZone.REMOVED:
return 2;
default:
return 0;
}
} else {
switch (state.location.zone) {
case YgoZone.EXTRA:
case YgoZone.DECK:
return 4;
case YgoZone.HAND:
return state.chaining ? 2 : 5;
case YgoZone.SZONE:
return state.sequence >= 5 ? 3 : 4;
case YgoZone.GRAVE:
return 3;
case YgoZone.MZONE:
return state.sequence >= 5 ? 2 : 3;
case YgoZone.REMOVED:
return 2;
default:
return 0;
}
}
}
function cardStateToCol(state: RenderCard): number {
if (state.focus) return 2;
if (state.attackTarget) {
// 正在攻击对方怪兽
return cardStateToCol(state.attackTarget);
}
if (state.opponent) {
switch (state.location.zone) {
case YgoZone.EXTRA:
return 5;
case YgoZone.HAND:
return state.chaining ? 2 : 4 - state.sequence;
case YgoZone.SZONE:
return state.sequence >= 5 ? 5 : 4 - state.sequence;
case YgoZone.DECK:
case YgoZone.REMOVED:
case YgoZone.GRAVE:
return -1;
case YgoZone.MZONE:
return state.sequence >= 5
? state.sequence == 5
? 3
: 1
: 4 - state.sequence;
default:
return 0;
}
} else {
switch (state.location.zone) {
case YgoZone.EXTRA:
return -1;
case YgoZone.HAND:
return state.chaining ? 2 : state.sequence;
case YgoZone.SZONE:
return state.sequence >= 5 ? -1 : state.sequence;
case YgoZone.DECK:
case YgoZone.REMOVED:
case YgoZone.GRAVE:
return 5;
case YgoZone.MZONE:
return state.sequence >= 5
? state.sequence == 5
? 1
: 3
: state.sequence;
default:
return 0;
}
}
}
function CardStateToHigh(state: RenderCard): number {
switch (state.location.zone) {
case YgoZone.EXTRA:
case YgoZone.DECK:
case YgoZone.REMOVED:
case YgoZone.GRAVE:
return state.sequence * HIGH_SCALE;
case YgoZone.SZONE:
case YgoZone.MZONE:
case YgoZone.HAND:
return DEFAULT_HIGH;
default:
return 0;
}
}
function CardStateToFaceDown(state: RenderCard): boolean {
if (state.focus && state.occupant?.id !== 0) return false;
const position = state.location.position;
return (
((position === YgoPosition.FACEDOWN ||
position === YgoPosition.FACEDOWN_ATTACK ||
position === YgoPosition.FACEDOWN_DEFENSE) &&
state.location.zone != YgoZone.HAND) ||
state.occupant!.id == 0
);
}
const onCardClick = (state: CardState) => () => {
const occupant = state.occupant;
if (occupant) {
// 中央弹窗展示选中卡牌信息
messageStore.cardModal.meta = occupant;
messageStore.cardModal.interactivies = state.idleInteractivities.map(
(interactivity) => ({
desc: interactTypeToString(interactivity.interactType),
effectCode: interactivity.activateIndex,
response: interactivity.response,
})
);
messageStore.cardModal.counters = state.counters;
messageStore.cardModal.isOpen = true;
// 侧边栏展示超量素材信息
if (state.overlay_materials && state.overlay_materials.length > 0) {
messageStore.cardListModal.list =
state.overlay_materials?.map((overlay) => ({
meta: overlay,
interactivies: [],
})) || [];
messageStore.cardListModal.isOpen = true;
}
}
};
const onFieldClick = (states: Array<CardState>, clicked: CardState) => () => {
const displayStates = states.filter(
(state) =>
state.location.zone == clicked.location.zone &&
state.location.controler === clicked.location.controler
);
messageStore.cardListModal.list = displayStates.map((item) => ({
meta: item.occupant,
interactivies: item.idleInteractivities.map((interactivy) => ({
desc: interactTypeToString(interactivy.interactType),
response: interactivy.response,
})),
}));
messageStore.cardListModal.isOpen = true;
};
import "@/styles/mat.css";
import { Button, Modal } from "antd";
import React, { useState } from "react";
import { useSnapshot } from "valtio";
import {
fetchStrings,
sendSelectBattleCmdResponse,
sendSelectIdleCmdResponse,
sendSurrender,
ygopro,
} from "@/api";
import { cardStore, matStore } from "@/stores";
import PhaseType = ygopro.StocGameMessage.MsgNewPhase.PhaseType;
const { phase } = matStore;
export const Menu = () => {
const snapPhase = useSnapshot(phase);
const enableBp = snapPhase.enableBp;
const enableM2 = snapPhase.enableM2;
const enableEp = snapPhase.enableEp;
const currentPhase = snapPhase.currentPhase;
const [modalOpen, setModalOpen] = useState(false);
const response =
currentPhase === PhaseType.BATTLE_START ||
currentPhase === PhaseType.BATTLE_STEP ||
currentPhase === PhaseType.DAMAGE ||
currentPhase === PhaseType.DAMAGE_GAL ||
currentPhase === PhaseType.BATTLE
? 3
: 7;
const clearAllIdleInteractivities = () => {
for (const card of cardStore.inner) {
card.idleInteractivities = [];
}
};
const onBp = () => {
sendSelectIdleCmdResponse(6);
clearAllIdleInteractivities();
phase.enableBp = false;
};
const onM2 = () => {
sendSelectBattleCmdResponse(2);
clearAllIdleInteractivities();
phase.enableM2 = false;
};
const onEp = () => {
sendSelectIdleCmdResponse(response);
clearAllIdleInteractivities();
phase.enableEp = false;
};
const onSurrender = () => {
setModalOpen(true);
};
return (
<div id="controller">
<button disabled={!enableBp} onClick={onBp}>
{fetchStrings("!system", 80)}
</button>
<button disabled={!enableM2} onClick={onM2}>
进入主要阶段2
</button>
<button disabled={!enableEp} onClick={onEp}>
{fetchStrings("!system", 81)}
</button>
<button onClick={onSurrender}>{fetchStrings("!system", 1351)}</button>
<Modal
title="是否确认要投降?"
open={modalOpen}
closable={false}
footer={
<>
<Button
onClick={() => {
sendSurrender();
setModalOpen(false);
}}
>
Yes
</Button>
<Button
onClick={() => {
setModalOpen(false);
}}
>
No
</Button>
</>
}
/>
</div>
);
};
import { Mat } from "./Mat";
export default Mat;
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