forgot
This commit is contained in:
+112
-7
@@ -947,15 +947,86 @@ body {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.limb-cancel-btn {
|
||||
background: #1e1e30;
|
||||
color: #6b7280;
|
||||
border: 1px solid #2e303a;
|
||||
border-radius: 3px;
|
||||
padding: 14px 40px;
|
||||
cursor: pointer;
|
||||
.limb-select-panel {
|
||||
width: 600px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.limb-select-title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.limb-select-skills {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.limb-select-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.limb-target-btn {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border: 1px solid #5F71C5;
|
||||
border-radius: 1px;
|
||||
padding: 8px 14px;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
background: none;
|
||||
color: #5F71C5;
|
||||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.limb-target-btn:hover {
|
||||
background: #1e2642;
|
||||
color: #5F71C5;
|
||||
}
|
||||
|
||||
.limb-target-selected {
|
||||
background: #1e2642;
|
||||
border-color: #fbbf24;
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.limb-target-selected:hover {
|
||||
background: #1e2642;
|
||||
border-color: #fcd34d;
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
.limb-confirm-btn {
|
||||
border: 1px solid #22c55e;
|
||||
border-radius: 1px;
|
||||
padding: 10px 24px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
background: #22c55e;
|
||||
color: #000;
|
||||
transition: background 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.limb-confirm-btn:hover {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.limb-cancel-btn {
|
||||
text-align: center;
|
||||
border: 1px solid #2e303a;
|
||||
border-radius: 1px;
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
background: #1e1e30;
|
||||
color: #6b7280;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.limb-cancel-btn:hover {
|
||||
@@ -1318,6 +1389,40 @@ body {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.world-info-panel {
|
||||
background: rgba(15, 15, 26, 0.75);
|
||||
border: 2px solid #5F71C5;
|
||||
border-radius: 3px;
|
||||
padding: 24px 32px 32px;
|
||||
min-width: 280px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.world-info-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #2e303a;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.inventory-panel {
|
||||
background: rgba(15, 15, 26, 0.75);
|
||||
border: 2px solid #5F71C5;
|
||||
|
||||
+376
-356
@@ -196,6 +196,7 @@ function App() {
|
||||
const [weaponConditions, setWeaponConditions] = useState({ rusty_sword: 65 })
|
||||
const [buyArmChoice, setBuyArmChoice] = useState(null)
|
||||
const [showInventory, setShowInventory] = useState(false)
|
||||
const [showWorldInfo, setShowWorldInfo] = useState(false)
|
||||
const [invTab, setInvTab] = useState('health')
|
||||
const [gameTime, setGameTime] = useState({ day: 1, hour: 8 })
|
||||
const [contextMenu, setContextMenu] = useState(null)
|
||||
@@ -246,9 +247,12 @@ function App() {
|
||||
const [selectedSkill, setSelectedSkill] = useState(null)
|
||||
const [selectedEnemy, setSelectedEnemy] = useState(0)
|
||||
const [targetEnemy, setTargetEnemy] = useState(null)
|
||||
const [showEnemyInv, setShowEnemyInv] = useState(false)
|
||||
const [enemyInvTab, setEnemyInvTab] = useState('health')
|
||||
const [charView, setCharView] = useState(null)
|
||||
const [attackEnemy, setAttackEnemy] = useState(null)
|
||||
const [panelTarget, setPanelTarget] = useState('player')
|
||||
const [minimapView, setMinimapView] = useState({ x: 0, y: 0, scale: 1 })
|
||||
const minimapPanRef = useRef(false)
|
||||
const minimapPanStart = useRef({ x: 0, y: 0 })
|
||||
const minimapMovedRef = useRef(false)
|
||||
|
||||
const liveCharacters = characters
|
||||
|
||||
@@ -364,6 +368,8 @@ function App() {
|
||||
return {
|
||||
id: npc.id,
|
||||
name: npc.name,
|
||||
age: Math.floor(Math.random() * 30) + 18,
|
||||
location: npc.location ?? 'Unknown',
|
||||
pos: { x: 120 + index * spread, y: 60 + index * spread },
|
||||
integrity: freshBody(),
|
||||
injuries: freshInjuries(),
|
||||
@@ -708,15 +714,30 @@ function App() {
|
||||
}, [combatTime, combat, bodyInjuries])
|
||||
|
||||
const handleItemClick = (itemId, i) => {
|
||||
setCharView('player')
|
||||
setShowInventory(true); setPanelTarget('player')
|
||||
}
|
||||
|
||||
const confirmLimbAttack = (part) => {
|
||||
if (!limbSelect) return
|
||||
const partMap = { larm: 'leftArm', rarm: 'rightArm', lleg: 'leftLeg', rleg: 'rightLeg' }
|
||||
const partMap = { larm: 'leftArm', rarm: 'rightArm', lleg: 'leftLeg', rleg: 'rightLeg' }
|
||||
|
||||
const selectLimbTarget = (part) => {
|
||||
const fullPart = partMap[part] || part
|
||||
playerAttack(fullPart, limbSelect, null, selectedEnemy)
|
||||
setSelectedTarget(fullPart)
|
||||
}
|
||||
|
||||
const executeAttack = () => {
|
||||
if (!limbSelect || !selectedTarget) return
|
||||
playerAttack(selectedTarget, limbSelect, selectedSkill, attackEnemy)
|
||||
setLimbSelect(null)
|
||||
setSelectedSkill(null)
|
||||
setSelectedTarget(null)
|
||||
setAttackEnemy(null)
|
||||
}
|
||||
|
||||
const cancelAttack = () => {
|
||||
setLimbSelect(null)
|
||||
setSelectedSkill(null)
|
||||
setSelectedTarget(null)
|
||||
setAttackEnemy(null)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -737,7 +758,7 @@ function App() {
|
||||
<div className="tree-root">
|
||||
<div className="tree-node folder" onClick={() => setExpandedWeapon(expandedWeapon === 'ROOT' ? null : 'ROOT')}>
|
||||
<span className="tree-toggle">{expandedWeapon === 'ROOT' ? '▼' : '▶'}</span>
|
||||
<span className="tree-label" style={{ color: '#22c55e', cursor: 'pointer' }} onClick={e => { e.stopPropagation(); setCharView('player') }}>Player</span>
|
||||
<span className="tree-label" style={{ color: '#22c55e', cursor: 'pointer' }} onClick={e => { e.stopPropagation(); setShowInventory(true); setPanelTarget('player') }}>Player</span>
|
||||
</div>
|
||||
{expandedWeapon === 'ROOT' && (
|
||||
<div className="tree-children">
|
||||
@@ -856,26 +877,76 @@ function App() {
|
||||
)}
|
||||
</div>
|
||||
<div className="combat-center">
|
||||
<div className="minimap-container">
|
||||
<svg viewBox="0 0 160 160" className="minimap" onClick={(e) => {
|
||||
if (!moveMode) return
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 160
|
||||
const y = ((e.clientY - rect.top) / rect.height) * 160
|
||||
startMove(x, y)
|
||||
}}>
|
||||
<rect x="0" y="0" width="160" height="160" fill="#171721" />
|
||||
{MAP_BLOCKS.map((block, i) => (
|
||||
<rect key={i} x={block.x} y={block.y} width={block.w} height={block.h} fill="#2e303a" stroke="#1f2028" strokeWidth="1" />
|
||||
))}
|
||||
{(combat?.enemies || []).map((enemy, i) => (
|
||||
<circle key={i} cx={enemy.pos.x} cy={enemy.pos.y} r="5" fill="#e74c3c" stroke="#000" strokeWidth="1.5"
|
||||
onMouseEnter={(e) => { e.stopPropagation(); setHoverInfo({ type: 'enemy', name: enemy.name, attack: enemy.attack, speed: enemy.speed }) }}
|
||||
onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}
|
||||
onMouseLeave={() => setHoverInfo(null)}
|
||||
onClick={() => { setTargetEnemy(i); setCharView(i) }} />
|
||||
))}
|
||||
{combat && <circle cx={combat.playerPos.x} cy={combat.playerPos.y} r="4" fill="#3498db" stroke="#000" strokeWidth="1.5" />}
|
||||
<div className="minimap-container" style={{ position: 'relative' }}>
|
||||
<div className="minimap-toolbar">
|
||||
<button className="zoom-btn" onClick={() => setMinimapView(v => { const ns = Math.min(3, v.scale * 1.5); return { x: v.x + 0.5 * (160 / v.scale - 160 / ns), y: v.y + 0.5 * (160 / v.scale - 160 / ns), scale: ns } })}>+</button>
|
||||
<button className="zoom-btn" onClick={() => setMinimapView(v => { const ns = Math.max(0.5, v.scale / 1.5); return { x: v.x + 0.5 * (160 / v.scale - 160 / ns), y: v.y + 0.5 * (160 / v.scale - 160 / ns), scale: ns } })}>−</button>
|
||||
<button className="zoom-btn" onClick={() => setMinimapView({ x: 0, y: 0, scale: 1 })}>⟲</button>
|
||||
</div>
|
||||
<svg viewBox="0 0 160 160" className="minimap"
|
||||
style={{ cursor: limbSelect ? 'default' : (minimapPanRef.current ? 'grabbing' : 'grab') }}
|
||||
onMouseDown={(e) => {
|
||||
if (limbSelect) return
|
||||
minimapPanRef.current = true
|
||||
minimapMovedRef.current = false
|
||||
minimapPanStart.current = { x: e.clientX, y: e.clientY, vx: minimapView.x, vy: minimapView.y }
|
||||
const svg = e.currentTarget
|
||||
const onMove = (ev) => {
|
||||
if (!minimapPanRef.current) return
|
||||
const dx = ev.clientX - minimapPanStart.current.x
|
||||
const dy = ev.clientY - minimapPanStart.current.y
|
||||
if (Math.abs(dx) > 2 || Math.abs(dy) > 2) minimapMovedRef.current = true
|
||||
const r = svg.getBoundingClientRect()
|
||||
const worldDx = (dx / r.width) * 160 / minimapView.scale
|
||||
const worldDy = (dy / r.height) * 160 / minimapView.scale
|
||||
setMinimapView(v => {
|
||||
return { ...v, x: minimapPanStart.current.vx - worldDx, y: minimapPanStart.current.vy - worldDy }
|
||||
})
|
||||
}
|
||||
const onUp = () => { minimapPanRef.current = false; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp) }
|
||||
window.addEventListener('mousemove', onMove)
|
||||
window.addEventListener('mouseup', onUp)
|
||||
}}
|
||||
onWheel={(e) => {
|
||||
if (limbSelect) return
|
||||
e.preventDefault()
|
||||
const delta = e.deltaY > 0 ? 0.9 : 1.1
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
const cx = e.clientX, cy = e.clientY
|
||||
setMinimapView(v => {
|
||||
const newScale = Math.max(0.5, Math.min(3, v.scale * delta))
|
||||
const mx = (cx - rect.left) / rect.width
|
||||
const my = (cy - rect.top) / rect.height
|
||||
const newW = 160 / newScale
|
||||
const newX = v.x + mx * (160 / v.scale - 160 / newScale)
|
||||
const newY = v.y + my * (160 / v.scale - 160 / newScale)
|
||||
return { x: newX, y: newY, scale: newScale }
|
||||
})
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (minimapMovedRef.current) { minimapMovedRef.current = false; return }
|
||||
if (limbSelect) return
|
||||
if (!moveMode) return
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 160 / minimapView.scale + minimapView.x
|
||||
const y = ((e.clientY - rect.top) / rect.height) * 160 / minimapView.scale + minimapView.y
|
||||
startMove(x, y)
|
||||
}}>
|
||||
<g style={{ transform: `translate(${-minimapView.x * minimapView.scale}px, ${-minimapView.y * minimapView.scale}px) scale(${minimapView.scale})`, transformOrigin: '0 0' }}>
|
||||
<rect x="0" y="0" width="160" height="160" fill="#171721" />
|
||||
{MAP_BLOCKS.map((block, i) => (
|
||||
<rect key={i} x={block.x} y={block.y} width={block.w} height={block.h} fill="#2e303a" stroke="#1f2028" strokeWidth="1" />
|
||||
))}
|
||||
{(combat?.enemies || []).map((enemy, i) => (
|
||||
<circle key={i} cx={enemy.pos.x} cy={enemy.pos.y} r="5" fill="#e74c3c" stroke="#000" strokeWidth="0.5"
|
||||
onMouseEnter={(e) => { e.stopPropagation(); setHoverInfo({ type: 'enemy', name: enemy.name, attack: enemy.attack, speed: enemy.speed }) }}
|
||||
onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}
|
||||
onMouseLeave={() => setHoverInfo(null)}
|
||||
onClick={(e) => { e.stopPropagation(); setTargetEnemy(i); setShowInventory(true); setPanelTarget(i) }} />
|
||||
))}
|
||||
{combat && selectedSkill && limbSelect && <circle cx={combat.playerPos.x} cy={combat.playerPos.y} r={getSkillRange(limbSelect, selectedSkill)} fill="none" stroke="rgba(52,152,219,0.5)" strokeWidth="1" strokeDasharray="4 4" />}
|
||||
{combat && <circle cx={combat.playerPos.x} cy={combat.playerPos.y} r="4" fill="#3498db" stroke="#000" strokeWidth="0.5" />}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="combat-log">
|
||||
@@ -890,254 +961,14 @@ function App() {
|
||||
<div className="combat-right">
|
||||
<div className="enemy-list">
|
||||
{(combat?.enemies || []).map((enemy, i) => (
|
||||
<div key={i} className="enemy-entry" onClick={() => { setSelectedEnemy(i); setTargetEnemy(i); setCharView(i) }}>
|
||||
<div key={i} className="enemy-entry" onClick={() => { setSelectedEnemy(i); setTargetEnemy(i); setShowInventory(true); setPanelTarget(i) }}>
|
||||
<span className="enemy-name">{enemy.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{charView !== null && (
|
||||
<div className="inventory-overlay" onClick={() => { setCharView(null); setHoverInfo(null); setContextMenu(null) }}
|
||||
onContextMenu={e => { e.preventDefault(); setContextMenu(null) }}
|
||||
onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}>
|
||||
<div className="inventory-panel" onClick={e => e.stopPropagation()}>
|
||||
<button className="inv-close" onClick={() => { setCharView(null); setHoverInfo(null) }}>X</button>
|
||||
<div className="inv-layout">
|
||||
<div className="inv-body-col">
|
||||
{charView === 'player' ? (
|
||||
<BodyImage body={bodyParts} />
|
||||
) : combat?.enemies[charView] ? (
|
||||
<BodyImage body={combat.enemies[charView].integrity} />
|
||||
) : null}
|
||||
</div>
|
||||
<div className="inv-right-col">
|
||||
<div className="inv-tabs">
|
||||
{charView === 'player' ? (
|
||||
<>
|
||||
<button className={`inv-tab ${invTab === 'health' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('health')}>Health</button>
|
||||
<button className={`inv-tab ${invTab === 'inventory' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('inventory')}>Inventory</button>
|
||||
<button className={`inv-tab ${invTab === 'stats' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('stats')}>Statistics</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button className={`inv-tab ${enemyInvTab === 'health' ? 'active' : ''}`}
|
||||
onClick={() => setEnemyInvTab('health')}>Health</button>
|
||||
<button className={`inv-tab ${enemyInvTab === 'inventory' ? 'active' : ''}`}
|
||||
onClick={() => setEnemyInvTab('inventory')}>Inventory</button>
|
||||
<button className={`inv-tab ${enemyInvTab === 'stats' ? 'active' : ''}`}
|
||||
onClick={() => setEnemyInvTab('stats')}>Statistics</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{charView === 'player' ? (
|
||||
<>
|
||||
{invTab === 'health' && (
|
||||
<div className="inv-parts-col">
|
||||
<div className="inv-parts-title">Body Parts</div>
|
||||
{Object.keys(MAX_BODY).map(part => {
|
||||
const hp = bodyParts[part]
|
||||
const max = MAX_BODY[part]
|
||||
const injuries = bodyInjuries[part] ?? []
|
||||
return (
|
||||
<div key={part}>
|
||||
<div className="part-row">
|
||||
<div className="part-color" style={{ background: partColor(hp, max) }} />
|
||||
<div className="part-label">{bodyPartLabels[part]}</div>
|
||||
<div className="part-bar-track">
|
||||
<div className="part-bar-fill" style={{ width: `${(hp / max) * 100}%`, background: partColor(hp, max) }} />
|
||||
</div>
|
||||
<div className="part-hp">{hp}/{max}</div>
|
||||
</div>
|
||||
{injuries.length > 0 && (
|
||||
<div className="part-injuries">
|
||||
{injuries.map((inj, i) => (
|
||||
<span key={i} className={`part-injury part-injury--${inj.type}`}>{inj.type} ({inj.severity})</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="inv-total-row">
|
||||
<span>Integrity</span>
|
||||
<span>{playerHp}/{MAX_HP}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{invTab === 'inventory' && (
|
||||
<div className="inv-inv-content">
|
||||
<div className="inv-equip-section">
|
||||
<div className="inv-parts-title">Weapons</div>
|
||||
{(() => {
|
||||
const lw = equippedWeapons.left, rw = equippedWeapons.right
|
||||
if (lw === rw) {
|
||||
return (
|
||||
<div className="equip-row"
|
||||
onMouseEnter={() => setHoverInfo({ type: 'weapon', id: lw })}
|
||||
onMouseLeave={() => setHoverInfo(null)}
|
||||
onContextMenu={e => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, weaponId: lw, side: 'both' }) }}>
|
||||
<span className="equip-name">{lw === 'fists' ? 'Fists' : `Both: ${weaponMap[lw].name}`}</span>
|
||||
{lw !== 'fists' && (
|
||||
<><span className="equip-stat">DMG: {weaponMap[lw].damage}</span>
|
||||
<span className="equip-stat">COND: {weaponConditions[lw] ?? weaponMap[lw].maxCondition}/{weaponMap[lw].maxCondition}</span>
|
||||
{weaponMap[lw].passives.length > 0 && (
|
||||
<span className="equip-passives">{weaponMap[lw].passives.join(', ')}</span>
|
||||
)}</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return ['left', 'right'].map(side => {
|
||||
const wid = equippedWeapons[side]
|
||||
if (wid === 'fists') return null
|
||||
return (
|
||||
<div key={side} className="equip-row"
|
||||
onMouseEnter={() => setHoverInfo({ type: 'weapon', id: wid })}
|
||||
onMouseLeave={() => setHoverInfo(null)}
|
||||
onContextMenu={e => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, weaponId: wid, side }) }}>
|
||||
<span className="equip-name">{side === 'left' ? 'Left' : 'Right'}: {weaponMap[wid].name}</span>
|
||||
<span className="equip-stat">DMG: {weaponMap[wid].damage}</span>
|
||||
<span className="equip-stat">COND: {weaponConditions[wid] ?? weaponMap[wid].maxCondition}/{weaponMap[wid].maxCondition}</span>
|
||||
{weaponMap[wid].passives.length > 0 && (
|
||||
<span className="equip-passives">{weaponMap[wid].passives.join(', ')}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</div>
|
||||
<div className="inv-divider" />
|
||||
<div className="inv-parts-title">Inventory ({playerInventory.length})</div>
|
||||
{playerInventory.length === 0 ? (
|
||||
<div className="inv-empty">Empty</div>
|
||||
) : (
|
||||
<>
|
||||
{playerInventory.map((itemId, i) => (
|
||||
<div key={i} className="inv-item"
|
||||
onMouseEnter={() => setHoverInfo({ type: 'item', id: itemId })}
|
||||
onMouseLeave={() => setHoverInfo(null)}>
|
||||
{itemMap[itemId]?.name ?? itemId}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div className="inv-weight">Weight: {currentWeight}/{MAX_WEIGHT}</div>
|
||||
<div className="inv-money">{playerMoney}g</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{invTab === 'stats' && (
|
||||
<div className="inv-stats-content">
|
||||
<div className="inv-parts-title">Character</div>
|
||||
<div className="stat-row"><span className="stat-label">Name</span><span className="stat-value">You</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Age</span><span className="stat-value">24</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Location</span><span className="stat-value">{locMap[playerLoc]?.name ?? playerLoc}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Day</span><span className="stat-value">{gameTime.day}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Time</span><span className="stat-value">{String(gameTime.hour).padStart(2, '0')}:00</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Weather</span><span className="stat-value">{weatherLabels[weather]}</span></div>
|
||||
<div className="inv-divider" />
|
||||
<div className="stat-row"><span className="stat-label">Equipped</span><span className="stat-value">{equippedWeapons.left === equippedWeapons.right ? (equippedWeapons.left === 'fists' ? 'Fists' : `Both: ${weaponMap[equippedWeapons.left].name}`) : [equippedWeapons.left !== 'fists' ? `L:${weaponMap[equippedWeapons.left].name}` : '', equippedWeapons.right !== 'fists' ? `R:${weaponMap[equippedWeapons.right].name}` : ''].filter(Boolean).join(' + ')}</span></div>
|
||||
{(() => {
|
||||
const shown = new Set()
|
||||
const rows = []
|
||||
const addWeapon = (wid) => {
|
||||
if (shown.has(wid) || wid === 'fists') return
|
||||
shown.add(wid)
|
||||
rows.push(
|
||||
<Fragment key={wid}>
|
||||
<div className="stat-row"><span className="stat-label">Skills</span><span className="stat-value">{weaponMap[wid].skills.map(s => s.name).join(', ')}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Weapon SPD</span><span className="stat-value">{weaponMap[wid].speed}</span></div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
addWeapon(equippedWeapons.left)
|
||||
addWeapon(equippedWeapons.right)
|
||||
return rows
|
||||
})()}
|
||||
<div className="inv-divider" />
|
||||
<div className="stat-row"><span className="stat-label">Integrity</span><span className="stat-value">{playerHp}/{MAX_HP}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Items</span><span className="stat-value">{playerInventory.length}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Weight</span><span className="stat-value">{currentWeight}/{MAX_WEIGHT}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Money</span><span className="stat-value">{playerMoney}g</span></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : combat?.enemies[charView] ? (
|
||||
<>
|
||||
{enemyInvTab === 'health' && (
|
||||
<div className="inv-parts-col">
|
||||
<div className="inv-parts-title">Body Parts</div>
|
||||
{Object.keys(MAX_BODY).map(part => {
|
||||
const hp = combat.enemies[charView].integrity[part]
|
||||
const max = MAX_BODY[part]
|
||||
const injuries = combat.enemies[charView].injuries[part] ?? []
|
||||
return (
|
||||
<div key={part}>
|
||||
<div className="part-row">
|
||||
<div className="part-color" style={{ background: partColor(hp, max) }} />
|
||||
<div className="part-label">{bodyPartLabels[part]}</div>
|
||||
<div className="part-bar-track">
|
||||
<div className="part-bar-fill" style={{ width: `${(hp / max) * 100}%`, background: partColor(hp, max) }} />
|
||||
</div>
|
||||
<div className="part-hp">{hp}/{max}</div>
|
||||
</div>
|
||||
{injuries.length > 0 && (
|
||||
<div className="part-injuries">
|
||||
{injuries.map((inj, i) => (
|
||||
<span key={i} className={`part-injury part-injury--${inj.type}`}>{inj.type} ({inj.severity})</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="inv-total-row">
|
||||
<span>Integrity</span>
|
||||
<span>{Object.values(combat.enemies[charView].integrity).reduce((a, b) => a + b, 0)}/{MAX_HP}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{enemyInvTab === 'inventory' && (
|
||||
<div className="inv-inv-content">
|
||||
<div className="inv-parts-title">Inventory ({(combat.enemies[charView].inventory || []).length})</div>
|
||||
{(combat.enemies[charView].inventory || []).length === 0 ? (
|
||||
<div className="inv-empty">Empty</div>
|
||||
) : (
|
||||
<>
|
||||
{combat.enemies[charView].inventory.map((itemId, i) => (
|
||||
<div key={i} className="inv-item"
|
||||
onMouseEnter={() => setHoverInfo({ type: 'item', id: itemId })}
|
||||
onMouseLeave={() => setHoverInfo(null)}>
|
||||
{itemMap[itemId]?.name ?? itemId}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div className="inv-money">{combat.enemies[charView].money || 0}g</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{enemyInvTab === 'stats' && (
|
||||
<div className="inv-stats-content">
|
||||
<div className="inv-parts-title">{combat.enemies[charView].name}</div>
|
||||
<div className="stat-row"><span className="stat-label">Attack</span><span className="stat-value">{combat.enemies[charView].attack || 0}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Speed</span><span className="stat-value">{combat.enemies[charView].speed || 0}</span></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSettings && (
|
||||
<div className="settings-sidebar">
|
||||
<div className="settings-header">
|
||||
@@ -1186,21 +1017,93 @@ function App() {
|
||||
</div>
|
||||
)}
|
||||
{limbSelect && (
|
||||
<div className="limb-select-overlay" onClick={() => setLimbSelect(null)}>
|
||||
<div className="limb-select-panel" onClick={e => e.stopPropagation()}>
|
||||
<div className="limb-select-title">Select body part to attack</div>
|
||||
<div className="limb-select-actions">
|
||||
<button className="limb-action-btn" onClick={() => confirmLimbAttack("torso")}>Torso</button>
|
||||
<button className="limb-action-btn" onClick={() => confirmLimbAttack("head")}>Head</button>
|
||||
<button className="limb-action-btn" onClick={() => confirmLimbAttack("larm")}>Left Arm</button>
|
||||
<button className="limb-action-btn" onClick={() => confirmLimbAttack("rarm")}>Right Arm</button>
|
||||
<button className="limb-action-btn" onClick={() => confirmLimbAttack("lleg")}>Left Leg</button>
|
||||
<button className="limb-action-btn" onClick={() => confirmLimbAttack("rleg")}>Right Leg</button>
|
||||
</div>
|
||||
<div className="limb-cancel-row">
|
||||
<button className="limb-cancel-btn" onClick={() => setLimbSelect(null)}>Cancel</button>
|
||||
</div>
|
||||
<div className="inventory-overlay" onClick={() => cancelAttack()} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0 }}>
|
||||
<div className="inventory-panel limb-select-panel" onClick={e => e.stopPropagation()}>
|
||||
<button className="inv-close" onClick={() => cancelAttack()}>X</button>
|
||||
{(() => {
|
||||
const weapId = limbSelect
|
||||
const isArm = weapId === 'left_arm' || weapId === 'right_arm'
|
||||
const weapon = !isArm ? weaponMap[weapId] : null
|
||||
const weaponName = isArm ? (weapId === 'left_arm' ? 'Left Arm' : 'Right Arm') : (weapon?.name ?? weapId)
|
||||
const skills = weapon?.skills ?? []
|
||||
if (isArm && !selectedSkill) selectSkill('Punch')
|
||||
return (
|
||||
<>
|
||||
<div className="inv-parts-title limb-select-title">{weaponName}</div>
|
||||
{!selectedSkill && skills.length > 0 ? (
|
||||
<div className="limb-select-skills">
|
||||
<div className="inv-parts-title" style={{ marginBottom: 8 }}>Select Skill</div>
|
||||
<div className="limb-select-grid">
|
||||
{skills.map((skill, i) => (
|
||||
<button key={i} className="limb-target-btn" onClick={() => { setSelectedSkill(skill.name); setAttackEnemy(null) }}>
|
||||
<div style={{ fontWeight: 'bold' }}>{skill.name}</div>
|
||||
<div style={{ fontSize: 11, color: '#6b7280' }}>{skill.damageType} x{skill.mult} (rng {skill.range})</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : selectedSkill && attackEnemy === null ? (
|
||||
<div className="limb-select-enemies">
|
||||
<div className="inv-parts-title" style={{ marginBottom: 8 }}>Select Enemy</div>
|
||||
<div className="limb-select-grid">
|
||||
{(combat?.enemies || []).map((enemy, i) => (
|
||||
<button key={i} className="limb-target-btn" onClick={() => setAttackEnemy(i)}
|
||||
style={{ textAlign: 'center' }}>
|
||||
<div style={{ fontWeight: 'bold' }}>{enemy.name}</div>
|
||||
<div style={{ fontSize: 11, color: '#6b7280' }}>HP: {Object.values(enemy.integrity).reduce((a, b) => a + b, 0)}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="limb-select-body" style={{ display: 'flex', gap: 16 }}>
|
||||
<div className="limb-select-info" style={{ flex: 1 }}>
|
||||
{skills.length > 0 && selectedSkill && (
|
||||
<div className="inv-parts-title" style={{ marginBottom: 8, color: '#fbbf24' }}>Skill: {selectedSkill}</div>
|
||||
)}
|
||||
{attackEnemy !== null && combat?.enemies[attackEnemy] && (
|
||||
<div className="inv-parts-title" style={{ marginBottom: 8, color: '#22c55e' }}>Target: {combat.enemies[attackEnemy].name}</div>
|
||||
)}
|
||||
{isArm && (
|
||||
<div className="inv-parts-title" style={{ marginBottom: 8, color: '#6b7280', fontSize: 12 }}>A weak unarmed punch (DMG: 10)</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inv-parts-title" style={{ marginBottom: 8 }}>Limb</div>
|
||||
<div className="limb-target-grid" style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{[
|
||||
{ key: 'torso', label: 'Torso' },
|
||||
{ key: 'head', label: 'Head' },
|
||||
{ key: 'larm', label: 'Left Arm' },
|
||||
{ key: 'rarm', label: 'Right Arm' },
|
||||
{ key: 'lleg', label: 'Left Leg' },
|
||||
{ key: 'rleg', label: 'Right Leg' },
|
||||
].map(({ key, label }) => {
|
||||
const fullPart = partMap[key] || key
|
||||
const isSelected = selectedTarget === fullPart
|
||||
return (
|
||||
<button key={key}
|
||||
className={'limb-target-btn' + (isSelected ? ' limb-target-selected' : '')}
|
||||
onClick={() => selectLimbTarget(key)}
|
||||
style={{ whiteSpace: 'nowrap', padding: '6px 16px' }}>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
{selectedTarget && (
|
||||
<div className="attack-action-bar" style={{ display: 'flex', flexDirection: 'column', gap: 8, marginLeft: 12 }}>
|
||||
<button className="limb-confirm-btn" onClick={() => executeAttack()}>Confirm</button>
|
||||
<button className="limb-cancel-btn" onClick={() => cancelAttack()} style={{ width: 'auto', padding: '10px 24px' }}>Cancel</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1268,7 +1171,14 @@ function App() {
|
||||
|
||||
<div className="top-bar">
|
||||
<div className="top-bar-left">
|
||||
<button className="icon-btn" onClick={() => setShowInventory(true)} title="Character">
|
||||
<button className="icon-btn" onClick={() => { setShowWorldInfo(s => !s) }} title="World">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<ellipse cx="12" cy="12" rx="4" ry="10" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
</button>
|
||||
<button className="icon-btn" onClick={() => { setShowInventory(true); setPanelTarget('player') }} title="Character">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="8" r="4" />
|
||||
<path d="M4 22a8 8 0 0 1 16 0" />
|
||||
@@ -1483,27 +1393,35 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{showInventory && (
|
||||
<div className="inventory-overlay" onClick={() => { setShowInventory(false); setHoverInfo(null); setContextMenu(null) }}
|
||||
onContextMenu={e => { e.preventDefault(); setContextMenu(null) }}
|
||||
onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}>
|
||||
<div className="inventory-panel" onClick={e => e.stopPropagation()}>
|
||||
<button className="inv-close" onClick={() => { setShowInventory(false); setHoverInfo(null) }}>X</button>
|
||||
<div className="inv-layout">
|
||||
<div className="inv-body-col">
|
||||
<BodyImage body={bodyParts} />
|
||||
</div>
|
||||
<div className="inv-right-col">
|
||||
<div className="inv-tabs">
|
||||
<button className={`inv-tab ${invTab === 'health' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('health')}>Health</button>
|
||||
<button className={`inv-tab ${invTab === 'inventory' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('inventory')}>Inventory</button>
|
||||
<button className={`inv-tab ${invTab === 'stats' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('stats')}>Statistics</button>
|
||||
</div>
|
||||
{showInventory && (
|
||||
<div className="inventory-overlay" onClick={() => { setShowInventory(false); setPanelTarget('player'); setHoverInfo(null); setContextMenu(null) }}
|
||||
onContextMenu={e => { e.preventDefault(); setContextMenu(null) }}
|
||||
onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}>
|
||||
<div className="inventory-panel" onClick={e => e.stopPropagation()}>
|
||||
<button className="inv-close" onClick={() => { setShowInventory(false); setPanelTarget('player'); setHoverInfo(null) }}>X</button>
|
||||
<div className="inv-layout">
|
||||
<div className="inv-body-col">
|
||||
{panelTarget === 'player' ? (
|
||||
<BodyImage body={bodyParts} />
|
||||
) : combat?.enemies[panelTarget] ? (
|
||||
<BodyImage body={combat.enemies[panelTarget].integrity} />
|
||||
) : null}
|
||||
</div>
|
||||
<div className="inv-right-col">
|
||||
<div className="inv-tabs">
|
||||
<button className={`inv-tab ${invTab === 'health' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('health')}>Health</button>
|
||||
<button className={`inv-tab ${invTab === 'inventory' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('inventory')}>Inventory</button>
|
||||
<button className={`inv-tab ${invTab === 'stats' ? 'active' : ''}`}
|
||||
onClick={() => setInvTab('stats')}>Statistics</button>
|
||||
</div>
|
||||
|
||||
{panelTarget === 'player' ? (
|
||||
<>
|
||||
{invTab === 'health' && (
|
||||
<div className="inv-parts-col">
|
||||
<div className="inv-parts-title">Body Parts</div>
|
||||
@@ -1606,9 +1524,6 @@ function App() {
|
||||
<div className="stat-row"><span className="stat-label">Name</span><span className="stat-value">You</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Age</span><span className="stat-value">24</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Location</span><span className="stat-value">{locMap[playerLoc]?.name ?? playerLoc}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Day</span><span className="stat-value">{gameTime.day}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Time</span><span className="stat-value">{String(gameTime.hour).padStart(2, '0')}:00</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Weather</span><span className="stat-value">{weatherLabels[weather]}</span></div>
|
||||
<div className="inv-divider" />
|
||||
<div className="stat-row"><span className="stat-label">Equipped</span><span className="stat-value">{equippedWeapons.left === equippedWeapons.right ? (equippedWeapons.left === 'fists' ? 'Fists' : `Both: ${weaponMap[equippedWeapons.left].name}`) : [equippedWeapons.left !== 'fists' ? `L:${weaponMap[equippedWeapons.left].name}` : '', equippedWeapons.right !== 'fists' ? `R:${weaponMap[equippedWeapons.right].name}` : ''].filter(Boolean).join(' + ')}</span></div>
|
||||
{(() => {
|
||||
@@ -1635,60 +1550,165 @@ function App() {
|
||||
<div className="stat-row"><span className="stat-label">Money</span><span className="stat-value">{playerMoney}g</span></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : combat?.enemies[panelTarget] ? (
|
||||
<>
|
||||
{invTab === 'health' && (
|
||||
<div className="inv-parts-col">
|
||||
<div className="inv-parts-title">Body Parts</div>
|
||||
{Object.keys(MAX_BODY).map(part => {
|
||||
const hp = combat.enemies[panelTarget].integrity[part]
|
||||
const max = MAX_BODY[part]
|
||||
const injuries = combat.enemies[panelTarget].injuries[part] ?? []
|
||||
return (
|
||||
<div key={part}>
|
||||
<div className="part-row">
|
||||
<div className="part-color" style={{ background: partColor(hp, max) }} />
|
||||
<div className="part-label">{bodyPartLabels[part]}</div>
|
||||
<div className="part-bar-track">
|
||||
<div className="part-bar-fill" style={{ width: `${(hp / max) * 100}%`, background: partColor(hp, max) }} />
|
||||
</div>
|
||||
<div className="part-hp">{hp}/{max}</div>
|
||||
</div>
|
||||
{injuries.length > 0 && (
|
||||
<div className="part-injuries">
|
||||
{injuries.map((inj, i) => (
|
||||
<span key={i} className={`part-injury part-injury--${inj.type}`}>{inj.type} ({inj.severity})</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="inv-total-row">
|
||||
<span>Integrity</span>
|
||||
<span>{Object.values(combat.enemies[panelTarget].integrity).reduce((a, b) => a + b, 0)}/{MAX_HP}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSettings && (
|
||||
<div className="settings-sidebar">
|
||||
<div className="settings-header">
|
||||
Settings
|
||||
<button className="settings-close" onClick={() => setShowSettings(false)}>X</button>
|
||||
{invTab === 'inventory' && (
|
||||
<div className="inv-inv-content">
|
||||
<div className="inv-parts-title">Inventory ({(combat.enemies[panelTarget].inventory || []).length})</div>
|
||||
{(combat.enemies[panelTarget].inventory || []).length === 0 ? (
|
||||
<div className="inv-empty">Empty</div>
|
||||
) : (
|
||||
<>
|
||||
{combat.enemies[panelTarget].inventory.map((itemId, i) => (
|
||||
<div key={i} className="inv-item"
|
||||
onMouseEnter={() => setHoverInfo({ type: 'item', id: itemId })}
|
||||
onMouseLeave={() => setHoverInfo(null)}>
|
||||
{itemMap[itemId]?.name ?? itemId}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div className="inv-money">{combat.enemies[panelTarget].money || 0}g</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{invTab === 'stats' && (
|
||||
<div className="inv-stats-content">
|
||||
{(() => {
|
||||
const e = combat.enemies[panelTarget]
|
||||
const wid = e.weapon ?? 'fists'
|
||||
const wep = weaponMap[wid]
|
||||
return (
|
||||
<>
|
||||
<div className="inv-parts-title">{e.name}</div>
|
||||
<div className="stat-row"><span className="stat-label">Name</span><span className="stat-value">{e.name}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Age</span><span className="stat-value">{e.age ?? '??'}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Location</span><span className="stat-value">{e.location ?? 'Unknown'}</span></div>
|
||||
<div className="inv-divider" />
|
||||
<div className="stat-row"><span className="stat-label">Weapon</span><span className="stat-value">{wep?.name ?? wid}</span></div>
|
||||
{wep && wid !== 'fists' && (
|
||||
<><div className="stat-row"><span className="stat-label">DMG</span><span className="stat-value">{e.attack}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">SPD</span><span className="stat-value">{e.speed}</span></div></>
|
||||
)}
|
||||
{wep && wep.skills.length > 0 && (
|
||||
<div className="stat-row"><span className="stat-label">Skills</span><span className="stat-value">{wep.skills.map(s => s.name).join(', ')}</span></div>
|
||||
)}
|
||||
{wid === 'fists' && (
|
||||
<div className="stat-row"><span className="stat-label">Attack</span><span className="stat-value">{e.attack}</span></div>
|
||||
)}
|
||||
<div className="inv-divider" />
|
||||
<div className="stat-row"><span className="stat-label">Integrity</span><span className="stat-value">{Object.values(e.integrity).reduce((a, b) => a + b, 0)}/{MAX_HP}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Items</span><span className="stat-value">{(e.inventory || []).length}</span></div>
|
||||
<div className="stat-row"><span className="stat-label">Money</span><span className="stat-value">{e.money || 0}g</span></div>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="settings-body">
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-title">Display</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Zoom</span>
|
||||
<div className="settings-zoom-group">
|
||||
<button className="settings-zoom-btn" onClick={zoomOut}>−</button>
|
||||
<span className="settings-zoom-val">{Math.round(view.scale * 100)}%</span>
|
||||
<button className="settings-zoom-btn" onClick={zoomIn}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-title">Game</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Day</span>
|
||||
<span className="settings-value">{gameTime.day}</span>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Time</span>
|
||||
<span className="settings-value">{String(gameTime.hour).padStart(2, '0')}:00</span>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Weather</span>
|
||||
<span className="settings-value">{weatherLabels[weather]}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-title">Player</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Location</span>
|
||||
<span className="settings-value">{locMap[playerLoc]?.name ?? playerLoc}</span>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Money</span>
|
||||
<span className="settings-value">{playerMoney}g</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSettings && (
|
||||
<div className="settings-sidebar">
|
||||
<div className="settings-header">
|
||||
Settings
|
||||
<button className="settings-close" onClick={() => setShowSettings(false)}>X</button>
|
||||
</div>
|
||||
<div className="settings-body">
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-title">Display</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Zoom</span>
|
||||
<div className="settings-zoom-group">
|
||||
<button className="settings-zoom-btn" onClick={zoomOut}>−</button>
|
||||
<span className="settings-zoom-val">{Math.round(view.scale * 100)}%</span>
|
||||
<button className="settings-zoom-btn" onClick={zoomIn}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-title">Game</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Day</span>
|
||||
<span className="settings-value">{gameTime.day}</span>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Time</span>
|
||||
<span className="settings-value">{String(gameTime.hour).padStart(2, '0')}:00</span>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Weather</span>
|
||||
<span className="settings-value">{weatherLabels[weather]}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-title">Player</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Location</span>
|
||||
<span className="settings-value">{locMap[playerLoc]?.name ?? playerLoc}</span>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-label">Money</span>
|
||||
<span className="settings-value">{playerMoney}g</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showWorldInfo && (
|
||||
<div className="inventory-overlay" onClick={() => setShowWorldInfo(false)}>
|
||||
<div className="world-info-panel" onClick={e => e.stopPropagation()}>
|
||||
<button className="inv-close" onClick={() => setShowWorldInfo(false)}>X</button>
|
||||
<div className="inv-parts-title" style={{ marginBottom: 16 }}>World</div>
|
||||
<div className="world-info-grid">
|
||||
<div className="info-row"><span className="info-label">Day</span><span className="info-value">{gameTime.day}</span></div>
|
||||
<div className="info-row"><span className="info-label">Time</span><span className="info-value">{String(gameTime.hour).padStart(2, '0')}:00</span></div>
|
||||
<div className="info-row"><span className="info-label">Weather</span><span className="info-value">{weatherLabels[weather]}</span></div>
|
||||
<div className="info-row"><span className="info-label">Location</span><span className="info-value">{locMap[playerLoc]?.name ?? playerLoc}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hoverInfo?.type === 'enemy' && (
|
||||
|
||||
Reference in New Issue
Block a user