@@ -2,7 +2,7 @@ import { useProfile } from '../../context/ProfileContext';
22import { useComparison } from '../../context/ComparisonContext' ;
33import { Card } from '../UI/Card' ;
44import { Button } from '../UI/Button' ;
5- import { X , Bookmark , GitCompare } from 'lucide-react' ;
5+ import { X , Bookmark , GitCompare , Shield , Zap } from 'lucide-react' ;
66import { ItemSlot , MountSlot , UserProfile } from '../../types/Profile' ;
77import { useState , useMemo } from 'react' ;
88import { ItemSelectorModal } from './ItemSelectorModal' ;
@@ -13,6 +13,7 @@ import { getItemImage } from '../../utils/itemAssets';
1313import { useGameData } from '../../hooks/useGameData' ;
1414import { AGES } from '../../utils/constants' ;
1515import { useTreeModifiers } from '../../hooks/useCalculatedStats' ;
16+ import { useSetBonuses } from '../../hooks/useSetBonuses' ;
1617import { formatSecondaryStat } from '../../utils/statNames' ;
1718import { SpriteSheetIcon } from '../UI/SpriteSheetIcon' ;
1819
@@ -83,6 +84,13 @@ const SLOT_TYPE_ID_MAP: Record<string, number> = {
8384 'Belt' : 7
8485} ;
8586
87+ // Map Set IDs to Icon filenames (same as Skins.tsx)
88+ const SET_ICONS : Record < string , string > = {
89+ 'SantaSet' : 'SteppingStoneCharIcon0.png' ,
90+ 'SnowmanSet' : 'SteppingStoneCharIcon1.png' ,
91+ 'SkiSet' : 'SteppingStoneCharIcon2.png'
92+ } ;
93+
8694interface EquipmentPanelProps {
8795 variant ?: 'default' | 'original' | 'test' ;
8896 title ?: string ;
@@ -153,6 +161,7 @@ export function EquipmentPanel({ variant = 'default', title, showCompareButton =
153161 const { data : itemBalancingConfig } = useGameData < any > ( 'ItemBalancingConfig.json' ) ;
154162 const { data : weaponLibrary } = useGameData < any > ( 'WeaponLibrary.json' ) ;
155163 const { data : secondaryStatLibrary } = useGameData < any > ( 'SecondaryStatLibrary.json' ) ;
164+ const { data : skinsLibrary } = useGameData < any > ( 'SkinsLibrary.json' ) ;
156165
157166 // Helper to calculate item perfection (avg of secondary stats vs max)
158167 const getPerfection = ( item : ItemSlot ) : number | null => {
@@ -240,12 +249,26 @@ export function EquipmentPanel({ variant = 'default', title, showCompareButton =
240249 if ( statType === 'Health' ) health += value ;
241250 }
242251
243- // For melee weapons: apply melee base multiplier (1.6x) to match in-game display
244252 if ( slotKey === 'Weapon' && isMelee && damage > 0 ) {
245253 damage = damage * meleeBaseMulti ;
246254 }
247255
248- return { damage, health, bonus, isMelee } ;
256+ // Apply Skin Multipliers
257+ let skinBonuses = { damage : 0 , health : 0 } ;
258+ if ( item . skin && item . skin . stats ) {
259+ const skinDamage = item . skin . stats [ 'Damage' ] || 0 ;
260+ const skinHealth = item . skin . stats [ 'Health' ] || 0 ;
261+
262+ if ( skinDamage ) {
263+ damage *= ( 1 + skinDamage ) ;
264+ }
265+ if ( skinHealth ) {
266+ health *= ( 1 + skinHealth ) ;
267+ }
268+ skinBonuses = { damage : skinDamage , health : skinHealth } ;
269+ }
270+
271+ return { damage, health, bonus, skinBonuses, isMelee } ;
249272 } ;
250273
251274 const getEquippedImage = ( slotKey : string , item : ItemSlot | null ) : string | null => {
@@ -333,9 +356,14 @@ export function EquipmentPanel({ variant = 'default', title, showCompareButton =
333356 { panelTitle }
334357 </ h2 >
335358 { showCompareButton && ! isComparing && variant === 'default' && (
336- < Button variant = "secondary" size = "sm" onClick = { enterCompareMode } >
359+ < Button
360+ variant = "primary"
361+ size = "sm"
362+ onClick = { enterCompareMode }
363+ className = "shadow-lg shadow-accent-primary/20 animate-pulse-subtle"
364+ >
337365 < GitCompare className = "w-4 h-4 mr-2" />
338- Compare
366+ Compare Build
339367 </ Button >
340368 ) }
341369 </ div >
@@ -409,6 +437,79 @@ export function EquipmentPanel({ variant = 'default', title, showCompareButton =
409437 )
410438 ) }
411439 </ div >
440+ { equipped . skin ? (
441+ < div className = "absolute -bottom-2 -right-2 z-20 w-8 h-8 rounded-md bg-bg-secondary border border-accent-primary shadow-sm overflow-hidden" title = { `Skin ID: ${ equipped . skin . idx } ` } >
442+ { ( ( ) => {
443+ // Calculate sprite position for skin icon
444+ // This logic mimics ItemSelectorModal and Skins.tsx
445+ const skinId = equipped . skin . idx ;
446+ // We need a helper or context to get global sorted index for precise sprite pos,
447+ // but for now, let's try to map it if possible or just use the generic icon.
448+ // Actually, without the full sorted list context, accurate sprite mapping is hard locally.
449+ // Let's use a simplified approach or generic skin icon if specific mapping is complex.
450+ // Ideally we should export getVisualOrder/sortedGlobalSkins logic.
451+
452+ // For now, let's display a styled indicator that looks better than "S"
453+ // Or better: Show the Skin Icon if we can calculate the background position.
454+ // Since we don't have the SkinsLibrary here easily without extra fetch,
455+ // let's stick to a robust visual indicator or fetch it.
456+ // Wait, we can use the same logic if we pass the mapping or use a helper.
457+
458+ return (
459+ < div className = "w-full h-full flex items-center justify-center bg-accent-primary/20" >
460+ < div
461+ className = "w-full h-full opacity-80"
462+ style = { {
463+ backgroundImage : 'url(./Texture2D/SkinsUiIcons.png)' ,
464+ backgroundSize : '400% 400%' ,
465+ backgroundPosition : ( ( ) => {
466+ if ( ! skinsLibrary ) return 'center' ;
467+
468+ const allSkins = Object . values ( skinsLibrary ) ;
469+ // Sort using same getVisualOrder logic as Skins.tsx
470+ const getVisualOrder = ( idx : number ) => {
471+ if ( idx === 0 ) return 0 ;
472+ if ( idx === 2 ) return 1 ;
473+ if ( idx === 1 ) return 2 ;
474+ return 10 + idx ;
475+ } ;
476+ const sortedSkins = [ ...allSkins ] . sort ( ( a : any , b : any ) => {
477+ const orderA = getVisualOrder ( a . SkinId . Idx ) ;
478+ const orderB = getVisualOrder ( b . SkinId . Idx ) ;
479+ if ( orderA !== orderB ) return orderA - orderB ;
480+
481+ const isHelmetA = a . SkinId . Type === 'Helmet' ;
482+ const isHelmetB = b . SkinId . Type === 'Helmet' ;
483+ if ( isHelmetA && ! isHelmetB ) return - 1 ;
484+ if ( ! isHelmetA && isHelmetB ) return 1 ;
485+ return 0 ;
486+ } ) ;
487+
488+ const globalIndex = sortedSkins . findIndex ( ( s : any ) =>
489+ s . SkinId . Idx === equipped . skin ! . idx &&
490+ s . SkinId . Type === ( equipped . skin ! . type || SLOT_TO_JSON_TYPE [ slot . key ] || slot . key )
491+ ) ;
492+
493+ if ( globalIndex === - 1 ) return 'center' ;
494+
495+ const SPRITE_COLS = 4 ;
496+ const SPRITE_ROWS = 4 ;
497+ const col = globalIndex % SPRITE_COLS ;
498+ const row = Math . floor ( globalIndex / SPRITE_COLS ) ;
499+
500+ const bgX = ( col * 100 ) / ( SPRITE_COLS - 1 ) ;
501+ const bgY = ( row * 100 ) / ( SPRITE_ROWS - 1 ) ;
502+
503+ return `${ Number . isNaN ( bgX ) ? 0 : bgX } % ${ Number . isNaN ( bgY ) ? 0 : bgY } %` ;
504+ } ) ( ) ,
505+ imageRendering : 'pixelated'
506+ } }
507+ />
508+ </ div >
509+ ) ;
510+ } ) ( ) }
511+ </ div >
512+ ) : null }
412513 </ div >
413514
414515 { /* Name - Below Icon */ }
@@ -433,13 +534,19 @@ export function EquipmentPanel({ variant = 'default', title, showCompareButton =
433534 { stats . damage > 0 && (
434535 < div className = "text-red-400 break-words flex flex-col items-center" >
435536 < span > ⚔️{ Math . round ( stats . damage ) . toLocaleString ( ) } </ span >
436- { stats . bonus > 0 && < span className = "text-green-400 text-[10px]" > (+{ Math . round ( stats . bonus * 100 ) } %)</ span > }
537+ < div className = "flex gap-1 flex-wrap justify-center font-bold" >
538+ { stats . bonus > 0 && < span className = "text-green-400 text-[10px]" > (+{ Math . round ( stats . bonus * 100 ) } %)</ span > }
539+ { stats . skinBonuses ?. damage > 0 && < span className = "text-accent-primary text-[10px]" > (+{ Math . round ( stats . skinBonuses . damage * 100 ) } % S)</ span > }
540+ </ div >
437541 </ div >
438542 ) }
439543 { stats . health > 0 && (
440544 < div className = "text-green-400 break-words flex flex-col items-center mt-0.5" >
441545 < span > ♥{ Math . round ( stats . health ) . toLocaleString ( ) } </ span >
442- { stats . bonus > 0 && < span className = "text-green-400 text-[10px]" > (+{ Math . round ( stats . bonus * 100 ) } %)</ span > }
546+ < div className = "flex gap-1 flex-wrap justify-center font-bold" >
547+ { stats . bonus > 0 && < span className = "text-green-400 text-[10px]" > (+{ Math . round ( stats . bonus * 100 ) } %)</ span > }
548+ { stats . skinBonuses ?. health > 0 && < span className = "text-accent-primary text-[10px]" > (+{ Math . round ( stats . skinBonuses . health * 100 ) } % S)</ span > }
549+ </ div >
443550 </ div >
444551 ) }
445552 </ div >
0 commit comments