fix: preserve rpg custom world detail profiles
This commit is contained in:
@@ -7,7 +7,10 @@ import {
|
||||
type Encounter,
|
||||
type SceneHostileNpc,
|
||||
} from '../../types';
|
||||
import { GameCanvasEntityLayer } from './GameCanvasEntityLayer';
|
||||
import {
|
||||
GameCanvasEntityLayer,
|
||||
getCombatFloatingNumberPresentation,
|
||||
} from './GameCanvasEntityLayer';
|
||||
import {
|
||||
CHARACTER_COMBAT_HP_TOP_PX,
|
||||
ENTITY_CONTAINER_REM,
|
||||
@@ -125,6 +128,21 @@ function renderEntityLayer(effectNpcId: string | null) {
|
||||
}
|
||||
|
||||
describe('GameCanvasEntityLayer', () => {
|
||||
it('keeps combat floating numbers readable on dark noisy battle backgrounds', () => {
|
||||
const damage = getCombatFloatingNumberPresentation(false);
|
||||
const healing = getCombatFloatingNumberPresentation(true);
|
||||
|
||||
expect(damage.toneClass).toContain('bg-rose-950/72');
|
||||
expect(damage.toneClass).toContain('text-rose-50');
|
||||
expect(damage.textStyle.WebkitTextStroke).toContain('rgba(127, 29, 29');
|
||||
expect(damage.textStyle.textShadow).toContain('rgba(0, 0, 0');
|
||||
|
||||
expect(healing.toneClass).toContain('bg-emerald-950/70');
|
||||
expect(healing.toneClass).toContain('text-emerald-50');
|
||||
expect(healing.textStyle.WebkitTextStroke).toContain('rgba(6, 78, 59');
|
||||
expect(healing.textStyle.textShadow).toContain('rgba(0, 0, 0');
|
||||
});
|
||||
|
||||
it('uses mirrored stage anchors for player and opponent containers', () => {
|
||||
expect(getMirroredStageEntityLeft('15%', 'player')).toBe('15%');
|
||||
expect(getMirroredStageEntityLeft('15%', 'opponent')).toBe(`calc(100% - 15% - ${ENTITY_CONTAINER_REM}rem)`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {motion} from 'motion/react';
|
||||
import {type ReactNode, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState} from 'react';
|
||||
|
||||
import {getCharacterById} from '../../data/characterPresets';
|
||||
import {getFacingTowardPlayer, MONSTERS_BY_WORLD} from '../../data/hostileNpcs';
|
||||
@@ -130,6 +130,45 @@ function getSceneTransitionMotionConfig(
|
||||
};
|
||||
}
|
||||
|
||||
export function getCombatFloatingNumberPresentation(isHealing: boolean): {
|
||||
toneClass: string;
|
||||
textStyle: CSSProperties;
|
||||
} {
|
||||
const textShadow = [
|
||||
'0 1px 0 rgba(0, 0, 0, 0.98)',
|
||||
'0 0 8px rgba(0, 0, 0, 0.92)',
|
||||
'0 0 16px rgba(0, 0, 0, 0.72)',
|
||||
].join(', ');
|
||||
|
||||
if (isHealing) {
|
||||
return {
|
||||
toneClass: [
|
||||
'border-emerald-100/70',
|
||||
'bg-emerald-950/70',
|
||||
'text-emerald-50',
|
||||
'shadow-[0_0_18px_rgba(52,211,153,0.55)]',
|
||||
].join(' '),
|
||||
textStyle: {
|
||||
WebkitTextStroke: '1.45px rgba(6, 78, 59, 0.95)',
|
||||
textShadow,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
toneClass: [
|
||||
'border-rose-100/75',
|
||||
'bg-rose-950/72',
|
||||
'text-rose-50',
|
||||
'shadow-[0_0_20px_rgba(248,113,113,0.68)]',
|
||||
].join(' '),
|
||||
textStyle: {
|
||||
WebkitTextStroke: '1.55px rgba(127, 29, 29, 0.98)',
|
||||
textShadow,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function CombatFloatingNumber({
|
||||
event,
|
||||
onDone,
|
||||
@@ -139,23 +178,20 @@ function CombatFloatingNumber({
|
||||
}) {
|
||||
const isHealing = event.delta > 0;
|
||||
const deltaText = `${isHealing ? '+' : ''}${event.delta}`;
|
||||
const colorClass = isHealing ? 'text-emerald-200' : 'text-rose-200';
|
||||
const glowClass = isHealing
|
||||
? 'drop-shadow-[0_0_8px_rgba(52,211,153,0.9)]'
|
||||
: 'drop-shadow-[0_0_8px_rgba(248,113,113,0.9)]';
|
||||
const presentation = getCombatFloatingNumberPresentation(isHealing);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={event.id}
|
||||
initial={{opacity: 0, y: 10, scale: 0.76}}
|
||||
animate={{opacity: [0, 1, 1, 0], y: [10, -12, -31, -50], scale: [0.76, 1.22, 1, 0.9]}}
|
||||
initial={{opacity: 0, y: 8, scale: 0.72}}
|
||||
animate={{opacity: [0, 1, 1, 0], y: [8, -14, -36, -58], scale: [0.72, 1.18, 1.04, 0.92]}}
|
||||
transition={{duration: 0.92, ease: 'easeOut'}}
|
||||
onAnimationComplete={() => onDone(event.id)}
|
||||
className={`pointer-events-none absolute -top-16 left-1/2 z-[14] -translate-x-1/2 text-lg font-black leading-none ${colorClass} ${glowClass}`}
|
||||
className={`pointer-events-none absolute -top-[4.65rem] left-1/2 z-[38] flex min-w-[2.4rem] -translate-x-1/2 select-none items-center justify-center rounded-full border px-1.5 py-0.5 text-[1.45rem] font-black leading-none tracking-[-0.04em] sm:text-[1.6rem] ${presentation.toneClass}`}
|
||||
data-testid={`combat-feedback-${event.targetKey}`}
|
||||
aria-label={`战斗数值 ${deltaText}`}
|
||||
>
|
||||
<span className="[-webkit-text-stroke:1px_rgba(24,24,27,0.76)]">
|
||||
<span style={presentation.textStyle}>
|
||||
{deltaText}
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user