Commit f15b790f authored by Chunchi Che's avatar Chunchi Che

Merge branch 'dev/dropmenu' into 'main'

optimize: card dropdown menu

See merge request mycard/Neos!242
parents 2e712f62 9d4a6da6
...@@ -19,7 +19,7 @@ const defaultConfig: DefaultsConfig = { ...@@ -19,7 +19,7 @@ const defaultConfig: DefaultsConfig = {
const aiModeConfig: DefaultsConfig = { const aiModeConfig: DefaultsConfig = {
...defaultConfig, ...defaultConfig,
defaultDeck: VITE_AI_MODE_DEFAULT_DECK || "Hero", defaultDeck: VITE_AI_MODE_DEFAULT_DECK || "Hero",
defaultPlayer: `AiKiller${Math.random().toString(36).slice(2)}}`, defaultPlayer: `AiKiller-${Math.random().toString(36).slice(2, 6)}}`,
defaultPassword: "AI", defaultPassword: "AI",
}; };
......
...@@ -22,6 +22,6 @@ declare global { ...@@ -22,6 +22,6 @@ declare global {
color: ( color: (
color: string, color: string,
backgroundColor?: string backgroundColor?: string
) => (...args: any[]) => void; ) => (...args: Parameters<console.log>) => void;
} }
} }
...@@ -157,14 +157,15 @@ export default async (move: MsgMove) => { ...@@ -157,14 +157,15 @@ export default async (move: MsgMove) => {
target.location = to; target.location = to;
// 维护完了之后,开始动画 // 维护完了之后,开始动画
await eventbus.call(Task.Move, target.uuid); await eventbus.call(Task.Move, target.uuid, from.zone);
// 如果from或者to是手卡,那么需要刷新除了这张卡之外,这个玩家的所有手卡 // 如果from或者to是手卡,那么需要刷新除了这张卡之外,这个玩家的所有手卡
if ([from.zone, to.zone].includes(HAND)) { if ([from.zone, to.zone].includes(HAND)) {
for (const card of cardStore.at(HAND, target.location.controller)) { await Promise.all(
if (card.uuid !== target.uuid) { cardStore
await eventbus.call(Task.Move, card.uuid); .at(HAND, target.location.controller)
} .filter((c) => c.uuid !== target.uuid)
} .map(async (c) => await eventbus.call(Task.Move, c.uuid))
);
} }
// 超量素材位置跟随超量怪兽移动 // 超量素材位置跟随超量怪兽移动
...@@ -177,6 +178,7 @@ export default async (move: MsgMove) => { ...@@ -177,6 +178,7 @@ export default async (move: MsgMove) => {
overlay.location.zone = to.zone; overlay.location.zone = to.zone;
overlay.location.controller = to.controller; overlay.location.controller = to.controller;
overlay.location.sequence = to.sequence; overlay.location.sequence = to.sequence;
overlay.location.position = to.position;
await eventbus.call(Task.Move, overlay.uuid); await eventbus.call(Task.Move, overlay.uuid);
} }
......
...@@ -93,7 +93,7 @@ export interface HintState { ...@@ -93,7 +93,7 @@ export interface HintState {
} }
export interface PhaseState { export interface PhaseState {
currentPhase: ygopro.StocGameMessage.MsgNewPhase.PhaseType; // TODO 当前的阶段 应该改成enum currentPhase: ygopro.StocGameMessage.MsgNewPhase.PhaseType;
enableBp: boolean; // 允许进入战斗阶段 enableBp: boolean; // 允许进入战斗阶段
enableM2: boolean; // 允许进入M2阶段 enableM2: boolean; // 允许进入M2阶段
enableEp: boolean; // 允许回合结束 enableEp: boolean; // 允许回合结束
......
...@@ -20,39 +20,21 @@ export interface BlockState { ...@@ -20,39 +20,21 @@ export interface BlockState {
disabled: boolean; // 是否被禁用 disabled: boolean; // 是否被禁用
} }
const genPLaces = (n: number): BlockState[] =>
Array.from({ length: n }).map(() => ({
interactivity: undefined,
disabled: false,
}));
export const placeStore = proxy({ export const placeStore = proxy({
inner: { inner: {
[MZONE]: { [MZONE]: {
me: Array.from({ length: 7 }).map( me: genPLaces(7),
() => op: genPLaces(7),
({
interactivity: undefined,
disabled: false,
} as BlockState)
),
op: Array.from({ length: 7 }).map(
() =>
({
interactivity: undefined,
disabled: false,
} as BlockState)
),
}, },
[SZONE]: { [SZONE]: {
me: Array.from({ length: 6 }).map( me: genPLaces(6),
() => op: genPLaces(6),
({
interactivity: undefined,
disabled: false,
} as BlockState)
),
op: Array.from({ length: 6 }).map(
() =>
({
interactivity: undefined,
disabled: false,
} as BlockState)
),
}, },
}, },
set( set(
......
...@@ -41,7 +41,11 @@ body { ...@@ -41,7 +41,11 @@ body {
margin: 0; margin: 0;
place-items: center; place-items: center;
min-width: 320px; min-width: 320px;
min-height: 100vh; position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
} }
a { a {
......
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
SortCardModal, SortCardModal,
YesNoModal, YesNoModal,
} from "./Message"; } from "./Message";
import { LifeBar, Mat, Menu, Timer } from "./PlayMat"; import { LifeBar, Mat, Menu } from "./PlayMat";
const NeosDuel = () => { const NeosDuel = () => {
return ( return (
...@@ -23,7 +23,6 @@ const NeosDuel = () => { ...@@ -23,7 +23,6 @@ const NeosDuel = () => {
<Alert /> <Alert />
<Menu /> <Menu />
<LifeBar /> <LifeBar />
<Timer />
<Mat /> <Mat />
<CardModal /> <CardModal />
<CardListModal /> <CardListModal />
......
...@@ -10,9 +10,9 @@ import { useConfig } from "@/config"; ...@@ -10,9 +10,9 @@ import { useConfig } from "@/config";
import { HandResult, matStore } from "@/stores"; import { HandResult, matStore } from "@/stores";
const style = { const style = {
borderStyle: "groove", // borderStyle: "groove",
borderRadius: "8px", // borderRadius: "8px",
backgroundColor: "#303030", backgroundColor: "#444",
}; };
const NeosConfig = useConfig(); const NeosConfig = useConfig();
......
...@@ -30,7 +30,7 @@ const CheckCardStyle = { ...@@ -30,7 +30,7 @@ const CheckCardStyle = {
}; };
const CheckGroupStyle = { const CheckGroupStyle = {
display: "grid", display: "grid",
gridTemplateColumns: "repeat(6, 1fr)", gridTemplateColumns: "repeat(5, 1fr)",
gap: 10, gap: 10,
}; };
......
...@@ -13,7 +13,7 @@ section#mat { ...@@ -13,7 +13,7 @@ section#mat {
top: 2%; top: 2%;
height: 96%; height: 96%;
width: 96%; width: 96%;
transform: translateZ(calc(var(--z) * 1px + 0.1px)) transform: translateZ(calc((var(--z) + var(--sub-z)) * 1px + 0.1px))
rotateY(calc(var(--ry) * 1deg)); rotateY(calc(var(--ry) * 1deg));
transition: 0.2s scale; transition: 0.2s scale;
cursor: pointer; cursor: pointer;
...@@ -107,9 +107,12 @@ section#mat { ...@@ -107,9 +107,12 @@ section#mat {
} }
.mat-card.highlight .card-shadow { .mat-card.highlight .card-shadow {
--card-shadow-color: #0099ff;
display: block !important; display: block !important;
background: linear-gradient(to right, #0079c6, #009cff) !important; background: var(--card-shadow-color) !important;
filter: blur(8px); border-radius: 5px;
box-shadow: 0 0 4px 0 var(--card-shadow-color), 0 0 25px 2px #0099ff87;
transform: translateZ(calc((var(--z)) * 1px + 0.1px));
} }
@keyframes focus { @keyframes focus {
......
This diff is collapsed.
...@@ -5,7 +5,7 @@ import { ygopro } from "@/api"; ...@@ -5,7 +5,7 @@ import { ygopro } from "@/api";
import { CardType, isMe } from "@/stores"; import { CardType, isMe } from "@/stores";
import { matConfig } from "../../utils"; import { matConfig } from "../../utils";
import { SpringApi } from "./types"; import type { SpringApi } from "./types";
import { asyncStart } from "./utils"; import { asyncStart } from "./utils";
const { BLOCK_WIDTH, BLOCK_HEIGHT_M, BLOCK_HEIGHT_S, COL_GAP, ROW_GAP } = const { BLOCK_WIDTH, BLOCK_HEIGHT_M, BLOCK_HEIGHT_S, COL_GAP, ROW_GAP } =
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { type CardType, matStore } from "@/stores"; import { type CardType, matStore } from "@/stores";
import { SpringApi } from "./types"; import type { SpringApi } from "./types";
import { asyncStart } from "./utils"; import { asyncStart } from "./utils";
/** 发动效果的动画 */ /** 发动效果的动画 */
...@@ -13,11 +13,16 @@ export const focus = async (props: { card: CardType; api: SpringApi }) => { ...@@ -13,11 +13,16 @@ export const focus = async (props: { card: CardType; api: SpringApi }) => {
) { ) {
const current = api.current[0].get(); const current = api.current[0].get();
await asyncStart(api)({ await asyncStart(api)({
y: current.y + (matStore.isMe(card.location.controller) ? -1 : 1) * 200, // TODO: 放到config之中 y: current.y + (matStore.isMe(card.location.controller) ? -1 : 1) * 120, // TODO: 放到config之中
ry: 0, ry: 0,
rz: 0, rz: 0,
}); });
await asyncStart(api)({ y: current.y, ry: current.ry, rz: current.rz }); await asyncStart(api)({
y: current.y,
ry: current.ry,
rz: current.rz,
z: current.z,
});
} else { } else {
await asyncStart(api)({ await asyncStart(api)({
focusScale: 1.5, focusScale: 1.5,
......
...@@ -4,3 +4,4 @@ export * from "./moveToDeck"; ...@@ -4,3 +4,4 @@ export * from "./moveToDeck";
export * from "./moveToGround"; export * from "./moveToGround";
export * from "./moveToHand"; export * from "./moveToHand";
export * from "./moveToOutside"; export * from "./moveToOutside";
export * from "./moveToToken";
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { type CardType, isMe } from "@/stores"; import { isMe } from "@/stores";
import { matConfig } from "../../utils"; import { matConfig } from "../../utils";
import { SpringApi } from "./types"; import { asyncStart, type MoveFunc } from "./utils";
const { const {
BLOCK_WIDTH, BLOCK_WIDTH,
...@@ -18,7 +18,7 @@ const { ...@@ -18,7 +18,7 @@ const {
const { DECK, EXTRA } = ygopro.CardZone; const { DECK, EXTRA } = ygopro.CardZone;
export const moveToDeck = async (props: { card: CardType; api: SpringApi }) => { export const moveToDeck: MoveFunc = async (props) => {
const { card, api } = props; const { card, api } = props;
// report // report
const { location } = card; const { location } = card;
...@@ -41,7 +41,8 @@ export const moveToDeck = async (props: { card: CardType; api: SpringApi }) => { ...@@ -41,7 +41,8 @@ export const moveToDeck = async (props: { card: CardType; api: SpringApi }) => {
let rz = zone === EXTRA ? DECK_ROTATE_Z.value : -DECK_ROTATE_Z.value; let rz = zone === EXTRA ? DECK_ROTATE_Z.value : -DECK_ROTATE_Z.value;
rz += isMe(controller) ? 0 : 180; rz += isMe(controller) ? 0 : 180;
const z = sequence; const z = sequence;
api.start({
await asyncStart(api)({
x, x,
y, y,
z, z,
......
import { easings } from "@react-spring/web"; import { easings } from "@react-spring/web";
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { type CardType, isMe } from "@/stores"; import { isMe } from "@/stores";
import { matConfig } from "../../utils"; import { matConfig } from "../../utils";
import { SpringApi } from "./types"; import { asyncStart, type MoveFunc } from "./utils";
import { asyncStart } from "./utils";
const { const {
BLOCK_WIDTH, BLOCK_WIDTH,
...@@ -16,13 +15,10 @@ const { ...@@ -16,13 +15,10 @@ const {
ROW_GAP, ROW_GAP,
} = matConfig; } = matConfig;
const { MZONE, SZONE } = ygopro.CardZone; const { MZONE, SZONE, TZONE } = ygopro.CardZone;
export const moveToGround = async (props: { export const moveToGround: MoveFunc = async (props) => {
card: CardType; const { card, api, fromZone } = props;
api: SpringApi;
}) => {
const { card, api } = props;
const { location } = card; const { location } = card;
...@@ -84,26 +80,41 @@ export const moveToGround = async (props: { ...@@ -84,26 +80,41 @@ export const moveToGround = async (props: {
let rz = isMe(controller) ? 0 : 180; let rz = isMe(controller) ? 0 : 180;
rz += defence ? 90 : 0; rz += defence ? 90 : 0;
const ry = [
ygopro.CardPosition.FACEDOWN,
ygopro.CardPosition.FACEDOWN_ATTACK,
ygopro.CardPosition.FACEDOWN_DEFENSE,
].includes(position ?? 5)
? 180
: 0;
// 动画 // 动画
if (fromZone === TZONE) {
// 如果是Token,直接先移动到那个位置,然后再放大
api.set({
x,
y,
ry,
rz,
height: 0,
});
} else {
await asyncStart(api)({
x,
y,
height,
z: is_overlay ? 120 : 200,
ry,
rz,
config: {
// mass: 0.5,
easing: easings.easeInOutSine,
},
});
}
await asyncStart(api)({ await asyncStart(api)({
x,
y,
height, height,
z: is_overlay ? 120 : 200,
ry: [
ygopro.CardPosition.FACEDOWN,
ygopro.CardPosition.FACEDOWN_ATTACK,
ygopro.CardPosition.FACEDOWN_DEFENSE,
].includes(position ?? 5)
? 180
: 0,
rz,
config: {
// mass: 0.5,
easing: easings.easeInOutSine,
},
});
await asyncStart(api)({
z: 0, z: 0,
zIndex: is_overlay ? 1 : 3, zIndex: is_overlay ? 1 : 3,
config: { config: {
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { cardStore, type CardType, isMe } from "@/stores"; import { cardStore, isMe } from "@/stores";
import { matConfig } from "../../utils"; import { matConfig } from "../../utils";
import { SpringApi } from "./types"; import { asyncStart, type MoveFunc } from "./utils";
const { const {
BLOCK_HEIGHT_M, BLOCK_HEIGHT_M,
...@@ -16,7 +16,7 @@ const { ...@@ -16,7 +16,7 @@ const {
const { HAND } = ygopro.CardZone; const { HAND } = ygopro.CardZone;
export const moveToHand = async (props: { card: CardType; api: SpringApi }) => { export const moveToHand: MoveFunc = async (props) => {
const { card, api } = props; const { card, api } = props;
const { sequence, controller } = card.location; const { sequence, controller } = card.location;
// 手卡会有很复杂的计算... // 手卡会有很复杂的计算...
...@@ -44,14 +44,14 @@ export const moveToHand = async (props: { card: CardType; api: SpringApi }) => { ...@@ -44,14 +44,14 @@ export const moveToHand = async (props: { card: CardType; api: SpringApi }) => {
const negativeX = Math.sin(angle) * r; const negativeX = Math.sin(angle) * r;
const negativeY = Math.cos(angle) * r + HAND_CARD_HEIGHT.value / 2; const negativeY = Math.cos(angle) * r + HAND_CARD_HEIGHT.value / 2;
const x = hand_circle_center_x + negativeX * (isMe(controller) ? 1 : -1); const x = hand_circle_center_x + negativeX * (isMe(controller) ? 1 : -1);
const y = hand_circle_center_y - negativeY + 140; // 常量 是手动调的 这里肯定有问题 有空来修 const y = hand_circle_center_y - negativeY + 140; // FIXME: 常量 是手动调的 这里肯定有问题 有空来修
const _rz = (angle * 180) / Math.PI; const _rz = (angle * 180) / Math.PI;
api.start({ await asyncStart(api)({
x: isMe(controller) ? x : -x, x: isMe(controller) ? x : -x,
y: isMe(controller) ? y : -y, y: isMe(controller) ? y : -y,
z: 0, z: sequence + 5,
rz: isMe(controller) ? _rz : 180 - _rz, rz: isMe(controller) ? _rz : 180 - _rz,
ry: isMe(controller) ? 0 : 180, ry: isMe(controller) ? 0 : 180,
height: HAND_CARD_HEIGHT.value, height: HAND_CARD_HEIGHT.value,
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { type CardType, isMe } from "@/stores"; import { isMe } from "@/stores";
import { matConfig } from "../../utils"; import { matConfig } from "../../utils";
import { SpringApi } from "./types"; import { asyncStart, type MoveFunc } from "./utils";
const { BLOCK_WIDTH, BLOCK_HEIGHT_M, BLOCK_HEIGHT_S, COL_GAP, ROW_GAP } = const { BLOCK_WIDTH, BLOCK_HEIGHT_M, BLOCK_HEIGHT_S, COL_GAP, ROW_GAP } =
matConfig; matConfig;
const { GRAVE } = ygopro.CardZone; const { GRAVE } = ygopro.CardZone;
export const moveToOutside = async (props: { export const moveToOutside: MoveFunc = async (props) => {
card: CardType;
api: SpringApi;
}) => {
const { card, api } = props; const { card, api } = props;
// report // report
const { zone, controller, position } = card.location; const { zone, controller, position, sequence } = card.location;
let x = (BLOCK_WIDTH.value + COL_GAP.value) * 3, let x = (BLOCK_WIDTH.value + COL_GAP.value) * 3,
y = zone === GRAVE ? BLOCK_HEIGHT_M.value + ROW_GAP.value : 0; y = zone === GRAVE ? BLOCK_HEIGHT_M.value + ROW_GAP.value : 0;
...@@ -23,12 +20,15 @@ export const moveToOutside = async (props: { ...@@ -23,12 +20,15 @@ export const moveToOutside = async (props: {
x = -x; x = -x;
y = -y; y = -y;
} }
api.start({ await asyncStart(api)({
x, x,
y, y,
z: 0, z: 0,
height: BLOCK_HEIGHT_S.value, height: BLOCK_HEIGHT_S.value,
rz: isMe(controller) ? 0 : 180, rz: isMe(controller) ? 0 : 180,
ry: [ygopro.CardPosition.FACEDOWN].includes(position) ? 180 : 0, ry: [ygopro.CardPosition.FACEDOWN].includes(position) ? 180 : 0,
subZ: 100,
zIndex: sequence,
}); });
api.set({ subZ: 0 });
}; };
import { asyncStart, type MoveFunc } from "./utils";
export const moveToToken: MoveFunc = async (props) => {
const { api } = props;
await asyncStart(api)({
height: 0,
});
};
...@@ -14,6 +14,8 @@ export interface SpringApiProps { ...@@ -14,6 +14,8 @@ export interface SpringApiProps {
focusDisplay: string; focusDisplay: string;
focusOpacity: number; focusOpacity: number;
// <<< focus // <<< focus
subZ: number; // 0 -> 100,这是为了让卡片移动过程中,稍微上浮一些,避免一些奇怪的遮挡问题
} }
export type SpringApi = SpringRef<SpringApiProps>; export type SpringApi = SpringRef<SpringApiProps>;
import { type SpringConfig, type SpringRef } from "@react-spring/web"; import { type SpringConfig, type SpringRef } from "@react-spring/web";
import type { ygopro } from "@/api";
import { type CardType } from "@/stores";
import type { SpringApi } from "./types";
export const asyncStart = <T extends {}>(api: SpringRef<T>) => { export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
return (p: Partial<T> & { config?: SpringConfig }) => return (p: Partial<T> & { config?: SpringConfig }) =>
new Promise((resolve) => { new Promise((resolve) => {
api.start({ api.start({
...p, ...p,
onRest: resolve, onResolve: resolve,
}); });
}); });
}; };
export type MoveFunc = (props: {
card: CardType;
api: SpringApi;
fromZone?: ygopro.CardZone;
}) => Promise<void>;
...@@ -3,20 +3,24 @@ ...@@ -3,20 +3,24 @@
display: flex; display: flex;
top: 0; top: 0;
left: 0; left: 0;
height: 100vh; // FIXME: 100% on safari bottom: 0;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
padding: 20px 35px; padding: 20px;
margin-left: 10px;
pointer-events: none; pointer-events: none;
z-index: 100; z-index: 100;
--bg-color: #323232;
width: 200px;
} }
.life-bar { .life-bar {
width: 160px; position: relative;
overflow: hidden;
width: 100%;
color: white; color: white;
background-color: #323232; background-color: var(--bg-color);
font-family: var(--theme-font); font-family: var(--theme-font);
border: 1px solid #222;
padding: 1rem; padding: 1rem;
padding-bottom: 0.6rem; padding-bottom: 0.6rem;
border-radius: 8px; border-radius: 8px;
...@@ -32,3 +36,43 @@ ...@@ -32,3 +36,43 @@
font-size: 1.8rem; font-size: 1.8rem;
} }
} }
.timer-container {
background-color: var(--bg-color);
border-radius: 4px;
padding: 0.4rem 1rem;
font-size: 0.8rem;
font-weight: bold;
font-family: var(--theme-font);
width: fit-content;
color: white;
display: flex;
gap: 8px;
align-items: center;
overflow: hidden;
position: relative;
.timer-text {
min-width: 3.25em;
}
}
.floodlight {
position: absolute;
height: 100%;
width: 40px;
background-color: #aaa;
top: 0;
right: 0;
filter: blur(30px);
transform: skewX(-20deg);
}
.floodlight-run {
animation: floodlight 4s linear infinite;
}
@keyframes floodlight {
0% {
right: -80px;
}
100% {
right: calc(100% + 80px);
}
}
import "./index.scss"; import "./index.scss";
import { Progress } from "antd";
import classNames from "classnames"; import classNames from "classnames";
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import AnimatedNumbers from "react-animated-numbers"; import AnimatedNumbers from "react-animated-numbers";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { useEnv } from "@/hook";
import { matStore, playerStore } from "@/stores"; import { matStore, playerStore } from "@/stores";
// 三个候选方案 // 三个候选方案
// https://snack.expo.dev/?platform=web // https://snack.expo.dev/?platform=web
// https://github.com/heyman333/react-animated-numbers // https://github.com/heyman333/react-animated-numbers
// https://www.npmjs.com/package/react-countup?activeTab=dependents // https://www.npmjs.com/package/react-countup?activeTab=dependents
export const LifeBar: React.FC = () => { export const LifeBar: React.FC = () => {
const snap = useSnapshot(matStore.initInfo); const snapInitInfo = useSnapshot(matStore.initInfo);
const snapPlayer = useSnapshot(playerStore); const snapPlayer = useSnapshot(playerStore);
const { currentPlayer } = useSnapshot(matStore); const { currentPlayer } = useSnapshot(matStore);
...@@ -21,35 +22,102 @@ export const LifeBar: React.FC = () => { ...@@ -21,35 +22,102 @@ export const LifeBar: React.FC = () => {
const [opLife, setOpLife] = React.useState(0); const [opLife, setOpLife] = React.useState(0);
useEffect(() => { useEffect(() => {
setMeLife(snap.me.life); setMeLife(snapInitInfo.me.life);
}, [snap.me.life]); }, [snapInitInfo.me.life]);
useEffect(() => {
setOpLife(snapInitInfo.op.life);
}, [snapInitInfo.op.life]);
const snapTimeLimit = useSnapshot(matStore.timeLimits);
const [myTimeLimit, setMyTimeLimit] = useState(snapTimeLimit.me);
const [opTimeLimit, setOpTimeLimit] = useState(snapTimeLimit.op);
useEffect(() => {
setMyTimeLimit(snapTimeLimit.me);
}, [snapTimeLimit.me]);
useEffect(() => {
setOpTimeLimit(snapTimeLimit.op);
}, [snapTimeLimit.op]);
useEffect(() => {
setInterval(() => {
setMyTimeLimit((time) => time - 1);
setOpTimeLimit((time) => time - 1);
}, 1000);
}, []);
useEffect(() => { useEffect(() => {
setOpLife(snap.op.life); if (useEnv().VITE_IS_AI_MODE) {
}, [snap.op.life]); // 如果是AI模式
// FIXME: 探索一个优雅的、判断当前是不是AI模式的方法,用户手动输入AI也是AI模式
setMyTimeLimit(240);
setOpTimeLimit(240);
}
}, [currentPlayer]);
return ( return (
<div id="life-bar-container"> <div id="life-bar-container">
<LifeBarItem
active={!matStore.isMe(currentPlayer)}
name={snapPlayer.getOpPlayer().name ?? "?"}
life={opLife}
timeLimit={opTimeLimit}
isMe={false}
/>
<LifeBarItem
active={matStore.isMe(currentPlayer)}
name={snapPlayer.getMePlayer().name ?? "?"}
life={meLife}
timeLimit={myTimeLimit}
isMe={true}
/>
</div>
);
};
const LifeBarItem: React.FC<{
active: boolean;
name: string;
life: number;
timeLimit: number;
isMe: boolean;
}> = ({ active, name, life, timeLimit, isMe }) => {
const mm = Math.floor(timeLimit / 60);
const ss = timeLimit % 60;
const timeText =
timeLimit < 0
? "00:00"
: `${mm < 10 ? "0" + mm : mm}:${ss < 10 ? "0" + ss : ss}`;
return (
<div
style={{
flexDirection: isMe ? "column-reverse" : "column",
overflow: "hidden",
display: "flex",
gap: "0.5rem",
position: "relative",
}}
>
<div <div
className={classNames("life-bar", { className={classNames("life-bar", {
"life-bar-activated": matStore.isMe(currentPlayer), "life-bar-activated": active,
})} })}
> >
<div className="name">{snapPlayer.getOpPlayer().name}</div> <div className="name">{name}</div>
<div className="life"> <div className="life">{<AnimatedNumbers animateToNumber={life} />}</div>
{<AnimatedNumbers animateToNumber={opLife} />}
</div>
</div> </div>
<div {active && (
className={classNames("life-bar", { <div className="timer-container">
"life-bar-activated": matStore.isMe(currentPlayer), <Progress
})} type="circle"
> percent={Math.floor((timeLimit / 240) * 100)}
<div className="name">{snapPlayer.getMePlayer().name}</div> strokeWidth={20}
<div className="life"> size={14}
<AnimatedNumbers animateToNumber={meLife} /> />
<div className="timer-text">{timeText}</div>
<div className="floodlight floodlight-run" />
</div> </div>
</div> )}
</div> </div>
); );
}; };
...@@ -10,26 +10,4 @@ ...@@ -10,26 +10,4 @@
padding: 8px; padding: 8px;
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
.floodlight {
position: absolute;
height: 100%;
width: 40px;
background-color: white;
top: 0;
right: 0;
filter: blur(30px);
transform: skewX(-20deg);
}
.floodlight-run {
animation: floodlight 1s linear infinite;
}
}
@keyframes floodlight {
0% {
right: -80px;
}
100% {
right: calc(100% + 80px);
}
} }
...@@ -124,7 +124,6 @@ export const Menu = () => { ...@@ -124,7 +124,6 @@ export const Menu = () => {
> >
<Button icon={<CloseCircleFilled />} type="text"></Button> <Button icon={<CloseCircleFilled />} type="text"></Button>
</DropdownWithTitle> </DropdownWithTitle>
{/* <div className="floodlight floodlight-run" /> */}
</div> </div>
</> </>
); );
......
#timer-container {
position: fixed;
display: flex;
top: 0;
right: 0;
height: 100vh;
padding: 20px 35px;
flex-direction: column;
pointer-events: none;
}
.timer {
width: 100px;
color: white;
background-color: #323232;
font-family: var(--theme-font);
border: 1px solid #222;
padding: 1rem;
padding-bottom: 0.6rem;
border-radius: 8px;
text-align: center;
display: flex;
flex-direction: column;
font-size: 1.2rem;
}
import "./index.scss";
import React, { useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import { matStore } from "@/stores";
export const Timer: React.FC = () => {
const [time, setTime] = useState(0);
const snap = useSnapshot(matStore);
useEffect(() => {
const interval = setInterval(() => {
if (time > 0) {
setTime((time) => time - 1);
}
}, 1000);
return () => clearInterval(interval);
}, [time]);
useEffect(() => {
setTime(snap.timeLimits.me);
}, [snap.timeLimits.me]);
useEffect(() => {
setTime(snap.timeLimits.op);
}, [snap.timeLimits.op]);
return (
<div id="timer-container">
<div className="timer">{time}</div>
</div>
);
};
export * from "./LifeBar"; export * from "./LifeBar";
export * from "./Mat"; export * from "./Mat";
export * from "./Menu"; export * from "./Menu";
export * from "./Timer";
...@@ -65,7 +65,7 @@ export const matConfig = { ...@@ -65,7 +65,7 @@ export const matConfig = {
unit: UNIT.PX, unit: UNIT.PX,
}, },
HAND_CARD_HEIGHT: { HAND_CARD_HEIGHT: {
value: 140, value: 130,
unit: UNIT.PX, unit: UNIT.PX,
}, },
DECK_OFFSET_X: { DECK_OFFSET_X: {
......
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