All files / components/screens/training/components DamageNumber3D.tsx

4.34% Statements 1/23
0% Branches 0/19
0% Functions 0/3
4.54% Lines 1/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                                                1x                                                                                                                                                                      
/**
 * DamageNumber3D - Floating damage number effect in 3D space
 *
 * Shows damage numbers that float up and fade out
 */
 
import { Html } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import React, { useRef, useMemo } from "react";
import { FONT_FAMILY } from "../../../../types/constants";
import { applyHtmlOverlayStyles } from "../../../../utils/htmlOverlayHelpers";
 
/**
 * Props for DamageNumber3D component
 */
export interface DamageNumber3DProps {
  /** Initial 3D position */
  readonly position: [number, number, number];
  /** Damage value to display */
  readonly damage: number;
  /** Type of hit (affects color) */
  readonly type: "normal" | "perfect" | "critical";
  /** Callback when animation completes */
  readonly onComplete: () => void;
  /** Animation duration in seconds */
  readonly duration?: number;
}
 
/**
 * DamageNumber3D Component
 * Displays floating damage numbers in 3D space
 */
export const DamageNumber3D: React.FC<DamageNumber3DProps> = ({
  position,
  damage,
  type,
  onComplete,
  duration = 1.5,
}) => {
  const startTimeRef = useRef<number | null>(null);
  const divRef = useRef<HTMLDivElement>(null);
  const completedRef = useRef(false);
 
  // Get color based on type
  const color =
    type === "critical"
      ? "#ff0000"
      : type === "perfect"
      ? "#ffd700"
      : "#ffffff";
 
  // Apply Html overlay styles for damage numbers (effects layer)
  const overlayStyle = useMemo(() => {
    return applyHtmlOverlayStyles("effects", false, 10, true, false);
  }, []);
 
  // Animate floating and fading using refs to avoid unnecessary re-renders
  useFrame(() => {
    // Lazy initialize start time on first frame
    startTimeRef.current ??= performance.now();
 
    const elapsed = (performance.now() - startTimeRef.current) / 1000;
    const progress = Math.min(elapsed / duration, 1);
 
    if (progress >= 1 && !completedRef.current) {
      completedRef.current = true;
      onComplete();
      return;
    }
 
    if (completedRef.current) return; // Stop processing after completion
 
    // Float upward with easing - use CSS transform for position animation
    const floatDistance = 1.5;
    const yOffset = floatDistance * progress;
 
    // Update DOM directly to avoid React re-renders
    if (divRef.current) {
      divRef.current.style.transform = `translateY(-${yOffset * 30}px)`; // Scale to pixels
      divRef.current.style.opacity = String(1 - progress);
    }
  });
 
  return (
    <Html
      position={position}
      center={overlayStyle.center}
      distanceFactor={overlayStyle.distanceFactor}
      style={{
        pointerEvents: overlayStyle.pointerEvents,
        transition: "none",
      }}
    >
      <div
        ref={divRef}
        data-testid="damage-number-3d"
        style={{
          fontSize:
            type === "critical" ? "32px" : type === "perfect" ? "28px" : "24px",
          fontWeight: "bold",
          color,
          fontFamily: FONT_FAMILY.KOREAN,
          textShadow: `0 0 10px ${color}, 0 0 20px ${color}`,
          opacity: 1,
          transform: type === "critical" ? "scale(1.2)" : "scale(1)",
          zIndex: overlayStyle.zIndex,
        }}
      >
        -{damage}
      </div>
    </Html>
  );
};
 
export default DamageNumber3D;