fix(v2): full v1-style radar canvas + inventory icon composites
Radar — now pixel-accurate reproduction of v1: - 300×300 canvas with dark circular background - Semi-transparent dereth.png map overlay (heading-rotated) - 4 range rings + crosshair lines - Compass labels (N=red, E/S/W=gray) rotating with heading - Facing direction indicator line - Entity dots color-coded by type (Monster=red, Player=blue, NPC=green, Portal=purple, Corpse=orange, Container=yellow) - Player dot: gold center with white border - Heading-up rotation for all entity positions - Click to select entity (white selection ring) - Scroll to zoom (0.02-5.0 AC units range) - Entity list with color dot, name, type, distance, compass direction - Selected entity highlighted with blue left border Inventory — v1-style icon composites + slot styling: - 3-layer icon composite: underlay → base → overlay images using portal.dat offset formula + icon_overlay_id/IntValues - Equipment slots: 3D beveled border + cyan glow when equipped (matching v1's outset border + #00ffff shadow) - Pack item cells: purple gradient background (v1's #3d007a) - Proper 36×36px icon rendering with pixelated scaling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5c982d6f5
commit
cf078b7765
6 changed files with 437 additions and 183 deletions
|
|
@ -23,11 +23,26 @@ interface Item {
|
|||
}
|
||||
|
||||
// Icon helper: convert raw icon ID to hex filename
|
||||
function iconHex(raw: number): string {
|
||||
if (!raw || raw <= 0) return '06000133';
|
||||
return (raw + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
|
||||
}
|
||||
function iconUrl(item: Item): string {
|
||||
const raw = item.icon ?? item.Icon ?? 0;
|
||||
if (raw === 0) return '/icons/06000133.png'; // fallback
|
||||
const hex = (raw + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
|
||||
return `/icons/${hex}.png`;
|
||||
return `/icons/${iconHex(item.icon ?? item.Icon ?? 0)}.png`;
|
||||
}
|
||||
function overlayUrl(item: Item): string | null {
|
||||
const id = (item as any).icon_overlay_id;
|
||||
if (id && id > 0) return `/icons/${iconHex(id)}.png`;
|
||||
const iv = item.IntValues;
|
||||
if (iv?.['218103849'] && Number(iv['218103849']) > 100) return `/icons/${iconHex(Number(iv['218103849']))}.png`;
|
||||
return null;
|
||||
}
|
||||
function underlayUrl(item: Item): string | null {
|
||||
const id = (item as any).icon_underlay_id;
|
||||
if (id && id > 0) return `/icons/${iconHex(id)}.png`;
|
||||
const iv = item.IntValues;
|
||||
if (iv?.['218103850'] && Number(iv['218103850']) > 100) return `/icons/${iconHex(Number(iv['218103850']))}.png`;
|
||||
return null;
|
||||
}
|
||||
|
||||
function itemName(item: Item): string { return item.name ?? item.Name ?? 'Unknown'; }
|
||||
|
|
@ -77,12 +92,16 @@ const SLOT_BG: Record<number, string> = {};
|
|||
[2,4,134217728,268435456,536870912,1073741824].forEach(m => SLOT_BG[m] = '#1e3e3e');
|
||||
[2097152,1048576,4194304,16777216,33554432,8388608].forEach(m => SLOT_BG[m] = '#142040');
|
||||
|
||||
function ItemIcon({ item, size = 38 }: { item: Item; size?: number }) {
|
||||
function ItemIcon({ item, size = 36 }: { item: Item; size?: number }) {
|
||||
const under = underlayUrl(item);
|
||||
const over = overlayUrl(item);
|
||||
const imgStyle: React.CSSProperties = { position: 'absolute', top: 0, left: 0, width: size, height: size, border: 'none', background: 'transparent', imageRendering: 'pixelated' };
|
||||
return (
|
||||
<div title={itemTooltip(item)} style={{ width: size, height: size, position: 'relative', cursor: 'help' }}>
|
||||
<img src={iconUrl(item)} alt={itemName(item)}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain', imageRendering: 'pixelated' }}
|
||||
{under && <img src={under} alt="" style={{ ...imgStyle, zIndex: 1 }} onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />}
|
||||
<img src={iconUrl(item)} alt={itemName(item)} style={{ ...imgStyle, zIndex: 2 }}
|
||||
onError={e => { (e.target as HTMLImageElement).src = '/icons/06000133.png'; }} />
|
||||
{over && <img src={over} alt="" style={{ ...imgStyle, zIndex: 3 }} onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -154,12 +173,13 @@ export const InventoryWindow: React.FC<Props> = ({ id, charName, zIndex }) => {
|
|||
<div key={slot.key} title={item ? itemTooltip(item) : slot.name}
|
||||
style={{
|
||||
position: 'absolute', left: (slot.col - 1) * 44 + 4, top: (slot.row - 1) * 44 + 4,
|
||||
width: 40, height: 40, background: item ? slot.bg : `${slot.bg}55`,
|
||||
border: `1px solid ${item ? '#555' : '#2a2a2a'}`, borderRadius: 3,
|
||||
width: 36, height: 36, background: item ? '#5a5a62' : '#3a3a42',
|
||||
border: item ? '2px solid #00ffff' : '2px outset #6a6a72',
|
||||
boxShadow: item ? '0 0 5px #00ffff, inset 0 0 5px rgba(0,255,255,0.2)' : 'none',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden',
|
||||
}}>
|
||||
{item ? <ItemIcon item={item} size={36} /> :
|
||||
<span style={{ fontSize: '0.45rem', color: '#444', textAlign: 'center', lineHeight: 1 }}>{slot.name}</span>}
|
||||
{item ? <ItemIcon item={item} size={32} /> :
|
||||
<span style={{ fontSize: '0.42rem', color: '#555', textAlign: 'center', lineHeight: 1 }}>{slot.name}</span>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
@ -171,9 +191,10 @@ export const InventoryWindow: React.FC<Props> = ({ id, charName, zIndex }) => {
|
|||
<div style={{ flex: 1, overflowY: 'auto', display: 'flex', flexWrap: 'wrap', gap: 2, padding: 4, alignContent: 'flex-start' }}>
|
||||
{activeItems.map((item, i) => (
|
||||
<div key={item.item_id ?? item.Id ?? i} title={itemTooltip(item)}
|
||||
style={{ width: 40, height: 40, background: '#1a1a1a', border: '1px solid #2a2a2a', borderRadius: 2,
|
||||
style={{ width: 36, height: 36, background: 'linear-gradient(135deg, #3d007a 0%, #1a0033 100%)',
|
||||
border: '1px solid #4a148c',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'help', overflow: 'hidden' }}>
|
||||
<ItemIcon item={item} size={36} />
|
||||
<ItemIcon item={item} size={32} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue