i don even know anymore

This commit is contained in:
2026-06-03 00:10:21 -04:00
parent eba2031b99
commit 4eca9df6cc
2 changed files with 149 additions and 98 deletions
+126 -75
View File
@@ -119,6 +119,7 @@ function partColor(hp, max) {
function freshBody() { return { head: 100, torso: 100, leftArm: 100, rightArm: 100, leftLeg: 100, rightLeg: 100 } }
function freshInjuries(){ return { head: [], torso: [], leftArm: [], rightArm: [], leftLeg: [], rightLeg: [] } }
function totalHp(body) { return Object.values(body).reduce((a, b) => a + b, 0) }
function isDead(integrity) { return !integrity || integrity.head <= 0 || integrity.torso <= 0 }
function randomPart() {
const parts = ['head', 'torso', 'leftArm', 'rightArm', 'leftLeg', 'rightLeg']
@@ -204,6 +205,7 @@ function computeShove(attackerPos, targetPos) {
* Returns { dmg, severity, injType, injLabel }
*/
function computeHit({ weaponId, skill, weaponConditions, bothMult = 1.0 }) {
if (skill.damageType === 'shove') return { dmg: 0, severity: 0, injType: 'shove', injLabel: 'shove' }
const base = weaponMap[weaponId]
if (!base) return { dmg: 1, severity: 1, injType: 'cut', injLabel: 'cut' }
@@ -280,7 +282,7 @@ function runEnemyAI(combat, playerParts, playerInjuries) {
for (let i = 0; i < combat.enemies.length; i++) {
const e = combat.enemies[i]
if (e.integrity.head <= 0) continue
if (isDead(e.integrity)) continue
const dist = Math.sqrt(
(combat.playerPos.x - e.pos.x) ** 2 + (combat.playerPos.y - e.pos.y) ** 2
@@ -360,6 +362,7 @@ export default function App() {
const [weaponConditions,setWeaponConditions]= useState({ rusty_sword: 65 })
const [gameTime, setGameTime] = useState({ day: 1, hour: 8 })
const [weather, setWeather] = useState('clear')
const [defeatedNpcs, setDefeatedNpcs] = useState([])
// ── ui state
const [hovered, setHovered] = useState(null)
@@ -407,7 +410,7 @@ export default function App() {
const [moveMode, setMoveMode] = useState(null)
// ── animation state
const [attackLerp, setAttackLerp] = useState(null)
const [attackLerp, setAttackLerp] = useState({ player: null, enemies: {} })
const [hitShake, setHitShake] = useState(null)
const [minimapView, setMinimapView] = useState({ x: 0, y: 0, scale: 1 })
@@ -454,7 +457,7 @@ export default function App() {
const playerHp = totalHp(bodyParts)
const currentWeight = playerInventory.reduce((sum, id) => sum + (itemMap[id]?.weight ?? 0), 0)
const shopAtLoc = shops.find(s => s.locationId === selected && s.locationId === playerLoc)
const liveCharacters= characters
const liveCharacters= characters.filter(c => !defeatedNpcs.includes(c.id))
const connectedTo = (locId) => {
const result = []
@@ -469,7 +472,7 @@ export default function App() {
// TIMER — only ticks while timerActive is true
// ─────────────────────────────────────────────────────────────────────────
const LUNGE_DIST = 15
const LUNGE_DIST = 18
function makeAttackAnim(action, cc) {
const isPlayer = action.actor === 'player'
@@ -512,20 +515,29 @@ export default function App() {
* Advance an attack animation by dt seconds. Returns true when done.
*/
function tickAttack(anim, dt) {
const liveCc = combatRef.current
if (anim.actor === 'player' && isDead(bodyPartsRef.current)) return true
if (anim.actor === 'enemy') {
const e = liveCc?.enemies[anim.enemyIndex]
if (!e || isDead(e.integrity)) { enemyActionsRef.current[anim.enemyIndex] = null; return true }
}
anim.elapsed += dt
const hitAt = 0.12
const hitAt = 0.15
const returnAt = 0.30
const t = anim.elapsed < hitAt
? anim.elapsed / hitAt
: anim.elapsed < 0.25
? 1 - (anim.elapsed - hitAt) / (0.25 - hitAt)
: anim.elapsed < returnAt
? 1 - (anim.elapsed - hitAt) / (returnAt - hitAt)
: 0
setAttackLerp({ actor: anim.actor, from: anim.startPos, to: anim.lungeTarget, t, enemyIndex: anim.enemyIndex })
setAttackLerp(prev => anim.actor === 'player'
? { ...prev, player: { from: anim.startPos, to: anim.lungeTarget, t } }
: { ...prev, enemies: { ...prev.enemies, [anim.enemyIndex]: { from: anim.startPos, to: anim.lungeTarget, t } } })
if (anim.elapsed >= hitAt && !anim.hitFired) {
anim.hitFired = true
const liveCc = combatRef.current
let inRange = false
if (liveCc) {
if (anim.actor === 'player') {
@@ -541,35 +553,38 @@ export default function App() {
}
}
setHitShake({ enemyIndex: anim.enemyIndex, start: performance.now() })
if (!inRange) {
const msg = anim.actor === 'player'
? 'Your attack misses!'
: `${liveCc?.enemies[anim.enemyIndex]?.name ?? 'Enemy'}'s attack misses!`
setCombat(prev => ({ ...prev, log: [...prev.log, msg] }))
} else if (anim.actor === 'player') {
// ── Player attacking enemy ──
} else {
setHitShake({ actor: anim.actor, enemyIndex: anim.enemyIndex, start: performance.now() })
if (anim.actor === 'player') {
// ── Player attacking enemy ──
const wid = anim.weaponId
if (wid !== 'fists' && wid !== 'left_arm' && wid !== 'right_arm') {
const base = weaponMap[wid]
setWeaponConditions(prev => ({ ...prev, [wid]: Math.max(0, (prev[wid] ?? base.maxCondition) - (1 + Math.floor(Math.random() * 2))) }))
}
const decayedPlayer = calcIntegrity(anim.attackerParts, anim.attackerInjuries, hitAt)
const decayedPlayer = calcIntegrity(bodyPartsRef.current, bodyInjuriesRef.current, hitAt)
if (liveCc) {
const enemy = liveCc.enemies[anim.enemyIndex]
if (enemy) {
const decayedEnemy = calcIntegrity(anim.targetParts, anim.targetInjuries, hitAt)
const hitEnemy = { ...decayedEnemy, [anim.targetPart]: Math.max(0, decayedEnemy[anim.targetPart] - anim.dmg) }
const newInjuries = applyInjury(anim.targetInjuries, anim.targetPart, anim.injType, anim.severity)
const decayedEnemy = calcIntegrity(enemy.integrity, enemy.injuries, hitAt)
const prevHp = decayedEnemy[anim.targetPart]
const hitEnemy = { ...decayedEnemy, [anim.targetPart]: Math.max(0, prevHp - anim.dmg) }
const newInjuries = prevHp > 0 && anim.severity > 0
? applyInjury(enemy.injuries, anim.targetPart, anim.injType, anim.severity)
: enemy.injuries
const log = [
`You ${anim.skillName?.toLowerCase() ?? 'attack'} ${enemy.name}'s ${bodyPartLabels[anim.targetPart]}${anim.injLabel} (severity ${anim.severity}, -${anim.dmg} integrity).`,
]
if (hitEnemy.head <= 0) {
log.push(`You crush ${enemy.name}'s head!`)
const log = anim.dmg > 0
? [`You ${anim.skillName?.toLowerCase() ?? 'attack'} ${enemy.name}'s ${bodyPartLabels[anim.targetPart]}${anim.injLabel} (severity ${anim.severity}, -${anim.dmg} integrity).`]
: []
if (isDead(hitEnemy)) {
if (anim.targetPart === 'head') log.push(`You crush ${enemy.name}'s head!`)
log.push(`${enemy.name} is defeated!`)
}
@@ -582,13 +597,19 @@ export default function App() {
}
setBodyParts(decayedPlayer)
setBodyInjuries(anim.attackerInjuries)
setBodyInjuries(bodyInjuriesRef.current)
} else {
// ── Enemy attacking player ──
const decayedEnemy = calcIntegrity(anim.attackerParts, anim.attackerInjuries, hitAt)
const hitPlayer = { ...anim.targetParts, [anim.targetPart]: Math.max(0, (anim.targetParts[anim.targetPart] ?? 100) - anim.dmg) }
const newInjuries = applyInjury(anim.targetInjuries, anim.targetPart, anim.injType, anim.severity)
const currentEnemy = liveCc?.enemies[anim.enemyIndex]
const decayedEnemy = currentEnemy
? calcIntegrity(currentEnemy.integrity, currentEnemy.injuries, hitAt)
: calcIntegrity(anim.attackerParts, anim.attackerInjuries, hitAt)
const decayedPlayer = calcIntegrity(bodyPartsRef.current, bodyInjuriesRef.current, hitAt)
const prevHp = decayedPlayer[anim.targetPart]
const hitPlayer = { ...decayedPlayer, [anim.targetPart]: Math.max(0, prevHp - anim.dmg) }
const newInjuries = prevHp > 0 && anim.severity > 0
? applyInjury(bodyInjuriesRef.current, anim.targetPart, anim.injType, anim.severity)
: bodyInjuriesRef.current
if (liveCc) {
const enemy = liveCc.enemies[anim.enemyIndex]
@@ -597,17 +618,22 @@ export default function App() {
setCombat(prev => ({
...prev,
enemies: prev.enemies.map((e, i) => i === anim.enemyIndex ? { ...e, integrity: decayedEnemy } : e),
log: [...prev.log, `${enemyName} lands a ${anim.injLabel} on your ${bodyPartLabels[anim.targetPart]} (severity ${anim.severity}, integrity -${anim.dmg}).`],
log: anim.dmg > 0
? [...prev.log, `${enemyName} lands a ${anim.injLabel} on your ${bodyPartLabels[anim.targetPart]} (severity ${anim.severity}, integrity -${anim.dmg}).`]
: prev.log,
}))
}
setBodyParts(hitPlayer)
setBodyInjuries(newInjuries)
}
}
}
if (anim.elapsed >= 0.35) {
setAttackLerp(null)
if (anim.elapsed >= 0.45) {
setAttackLerp(prev => anim.actor === 'player'
? { ...prev, player: null }
: { ...prev, enemies: { ...prev.enemies, [anim.enemyIndex]: undefined } })
setHitShake(null)
return true
}
@@ -715,7 +741,7 @@ export default function App() {
if (anim.type === 'enemyMove') {
const e = fresh?.enemies[i] ?? cc.enemies[i]
const finalPos = { x: anim.targets[i].toX, y: anim.targets[i].toY }
if (e && e.integrity.head > 0) {
if (e && !isDead(e.integrity)) {
const dist = Math.sqrt(
((fresh ?? cc).playerPos.x - finalPos.x) ** 2 + ((fresh ?? cc).playerPos.y - finalPos.y) ** 2
)
@@ -800,7 +826,7 @@ export default function App() {
useEffect(() => {
if (!combat || replayActive) return
if (playerHp <= 0 || !combat.enemies.some(e => e.integrity.head > 0)) endFight()
if (isDead(bodyParts) || !combat.enemies.some(e => !isDead(e.integrity))) endFight()
}, [combat, bodyParts, playerHp])
// ─────────────────────────────────────────────────────────────────────────
@@ -814,7 +840,7 @@ export default function App() {
combat: JSON.parse(JSON.stringify(combat)),
bodyParts: JSON.parse(JSON.stringify(bodyParts)),
bodyInjuries: JSON.parse(JSON.stringify(bodyInjuries)),
attackLerp: attackLerp ? { ...attackLerp } : null,
attackLerp: JSON.parse(JSON.stringify(attackLerp)),
hitShake: hitShake ? { ...hitShake } : null,
})
}
@@ -894,7 +920,7 @@ export default function App() {
const rec = fightRecord
const f0 = rec.frames[0]
setCombat(f0.combat); setBodyParts(f0.bodyParts); setBodyInjuries(f0.bodyInjuries)
setAttackLerp(f0.attackLerp ?? null); setHitShake(f0.hitShake ?? null)
setAttackLerp(f0.attackLerp && 'player' in f0.attackLerp ? f0.attackLerp : { player: null, enemies: {} }); setHitShake(f0.hitShake ?? null)
setCombatTime(0); setDisplayTime(0)
replayIdxRef.current = 0
@@ -906,7 +932,7 @@ export default function App() {
replayIdxRef.current = idx
const f = rec.frames[idx]
setCombat(f.combat); setBodyParts(f.bodyParts); setBodyInjuries(f.bodyInjuries)
setAttackLerp(f.attackLerp ?? null); setHitShake(f.hitShake ?? null)
setAttackLerp(f.attackLerp && 'player' in f.attackLerp ? f.attackLerp : { player: null, enemies: {} }); setHitShake(f.hitShake ?? null)
setCombatTime(f.time)
}
if (idx >= rec.frames.length - 1) return
@@ -950,7 +976,7 @@ export default function App() {
enemyActionsRef.current = initCombat.enemies.map(() => null)
positionsRef.current = {}
setSelectedEnemy(0); setReplayActive(false)
setExpandedWeapon('ROOT')
setExpandedWeapon('ROOT'); setAttackLerp({ player: null, enemies: {} }); setHitShake(null); setMoveMode(null)
recordingRef.current = {
initialCombat: JSON.parse(JSON.stringify(initCombat)),
frames: [{ time: 0, combat: JSON.parse(JSON.stringify(initCombat)), bodyParts: freshBody(), bodyInjuries: freshInjuries(), attackLerp: null, hitShake: null }],
@@ -961,29 +987,37 @@ export default function App() {
if (recordingRef.current) { pushSnapshot(); setFightRecord(recordingRef.current); recordingRef.current = null }
timerActiveRef.current = false; setTimerRunning(false)
setCombatTime(0); setDisplayTime(0)
playerActionRef.current = null
enemyActionsRef.current = []
if (combat) {
const deadIds = combat.enemies.filter(e => isDead(e.integrity)).map(e => e.id)
if (deadIds.length > 0) setDefeatedNpcs(prev => [...new Set([...prev, ...deadIds])])
}
}
/** Execute a player attack; timer starts immediately. */
const executeAttack = () => {
if (!limbSelect || !selectedTarget) return
if (!combat) return
const cc = combatRef.current
if (!cc) return
if (playerActionRef.current) return
if (isDead(bodyPartsRef.current)) return
const ei = attackEnemy ?? selectedEnemy
const enemy = combat.enemies[ei]
if (!enemy || enemy.integrity.head <= 0) return
const enemy = cc.enemies[ei]
if (!enemy || isDead(enemy.integrity)) return
const weaponId = limbSelect
if (!canReach(combat.playerPos, enemy.pos, weaponId, selectedSkill)) return
if (!canReach(cc.playerPos, enemy.pos, weaponId, selectedSkill)) return
const action = buildPlayerAttack({
weaponId, skillName: selectedSkill, targetPart: selectedTarget,
enemyIndex: ei, equippedWeapons, weaponConditions,
bodyParts, bodyInjuries, combat,
bodyParts: bodyPartsRef.current, bodyInjuries: bodyInjuriesRef.current, combat: cc,
})
if (action.type === 'shove_pending') {
const enemy2 = combat.enemies[action.enemyIndex]
if (!enemy2 || enemy2.integrity.head <= 0) return
if (!enemy2 || isDead(enemy2.integrity)) return
const { pos, actualPush } = computeShove(combat.playerPos, enemy2.pos)
setCombat(c => ({
...c,
@@ -1119,7 +1153,7 @@ export default function App() {
<div className="combat-layout">
{/* ── left: action tree */}
<div className="combat-left">
{playerHp > 0 && combat.enemies.some(e => e.integrity.head > 0) && (
{!isDead(bodyParts) && combat.enemies.some(e => !isDead(e.integrity)) && (
<div className={`combat-actions${busy ? ' combat-busy' : ''}`}>
<div className="tree-root">
<div className="tree-node folder" onClick={() => setExpandedWeapon(expandedWeapon === 'ROOT' ? null : 'ROOT')}>
@@ -1215,7 +1249,7 @@ export default function App() {
{/* ── center: minimap + log */}
<div className="combat-center">
<div className="minimap-container" style={{ position: 'relative' }}>
<div className="minimap-container" style={{ position: 'relative', borderColor: hitShake ? '#ef4444' : undefined }}>
<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>
@@ -1254,17 +1288,23 @@ export default function App() {
startMove(((e.clientX - r.left) / r.width) * 160 / minimapView.scale + minimapView.x,
((e.clientY - r.top) / r.height) * 160 / minimapView.scale + minimapView.y)
}}>
<g style={{ transform: `translate(${-minimapView.x * minimapView.scale}px, ${-minimapView.y * minimapView.scale}px) scale(${minimapView.scale})`, transformOrigin: '0 0' }}>
<g style={{
transform: `translate(${-minimapView.x * minimapView.scale + (hitShake ? (Math.random() - 0.5) * 2 : 0)}px, ${-minimapView.y * minimapView.scale + (hitShake ? (Math.random() - 0.5) * 2 : 0)}px) scale(${minimapView.scale})`,
transformOrigin: '0 0',
transition: hitShake ? 'none' : undefined,
}}>
<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) => {
const eLerp = attackLerp && attackLerp.actor === 'enemy' && attackLerp.enemyIndex === i
const dead = isDead(enemy.integrity)
const eLerp = attackLerp.enemies[i]
const isShaking = hitShake && hitShake.enemyIndex === i && hitShake.actor === 'enemy'
return (
<circle key={i}
cx={eLerp ? attackLerp.from.x + (attackLerp.to.x - attackLerp.from.x) * attackLerp.t : enemy.pos.x}
cy={eLerp ? attackLerp.from.y + (attackLerp.to.y - attackLerp.from.y) * attackLerp.t : 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 }) }}
cx={eLerp ? eLerp.from.x + (eLerp.to.x - eLerp.from.x) * eLerp.t : (isShaking ? enemy.pos.x + (Math.random() - 0.5) * 1.5 : enemy.pos.x)}
cy={eLerp ? eLerp.from.y + (eLerp.to.y - eLerp.from.y) * eLerp.t : (isShaking ? enemy.pos.y + (Math.random() - 0.5) * 1.5 : enemy.pos.y)}
r="5" fill={dead ? '#6b7280' : '#e74c3c'} stroke="#000" strokeWidth="0.5" opacity={dead ? 0.5 : 1}
onMouseEnter={e => { e.stopPropagation(); setHoverInfo({ type: 'enemy', name: enemy.name, attack: enemy.attack, speed: enemy.speed, defeated: isDead(enemy.integrity) }) }}
onMouseMove={e => setMousePos({ x: e.clientX, y: e.clientY })}
onMouseLeave={() => setHoverInfo(null)}
onClick={e => { e.stopPropagation(); setShowInventory(true); setPanelTarget(i) }} />
@@ -1275,9 +1315,9 @@ export default function App() {
)}
{combat && (
<circle
cx={attackLerp && attackLerp.actor === 'player' ? attackLerp.from.x + (attackLerp.to.x - attackLerp.from.x) * attackLerp.t : combat.playerPos.x}
cy={attackLerp && attackLerp.actor === 'player' ? attackLerp.from.y + (attackLerp.to.y - attackLerp.from.y) * attackLerp.t : combat.playerPos.y}
r="4" fill="#3498db" stroke="#000" strokeWidth="0.5" />
cx={attackLerp.player ? attackLerp.player.from.x + (attackLerp.player.to.x - attackLerp.player.from.x) * attackLerp.player.t : (hitShake && hitShake.actor === 'player' ? combat.playerPos.x + (Math.random() - 0.5) * 1.5 : combat.playerPos.x)}
cy={attackLerp.player ? attackLerp.player.from.y + (attackLerp.player.to.y - attackLerp.player.from.y) * attackLerp.player.t : (hitShake && hitShake.actor === 'player' ? combat.playerPos.y + (Math.random() - 0.5) * 1.5 : combat.playerPos.y)}
r="4" fill={isDead(bodyParts) ? '#6b7280' : '#3498db'} stroke="#000" strokeWidth="0.5" opacity={isDead(bodyParts) ? 0.5 : 1} />
)}
</g>
</svg>
@@ -1420,15 +1460,15 @@ export default function App() {
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button className="limb-confirm-btn" onClick={executeAttack}
disabled={!selectedSkill || attackEnemy === null || !selectedTarget || busy || playerHp <= 0}
style={{ opacity: (!selectedSkill || attackEnemy === null || !selectedTarget || busy || playerHp <= 0) ? 0.4 : 1 }}>Confirm</button>
disabled={!selectedSkill || attackEnemy === null || !selectedTarget || busy || isDead(bodyParts)}
style={{ opacity: (!selectedSkill || attackEnemy === null || !selectedTarget || busy || isDead(bodyParts)) ? 0.4 : 1 }}>Confirm</button>
<button className="limb-cancel-btn" onClick={cancelAttack} style={{ width: 'auto', padding: '10px 24px' }}>Cancel</button>
</div>
</div>
)}
{/* ── combat over */}
{combat && (playerHp <= 0 || !combat.enemies.some(e => e.integrity.head > 0)) && (
{combat && (isDead(bodyParts) || !combat.enemies.some(e => !isDead(e.integrity))) && (
<div className="combat-over-panel">
<div className="combat-over-title">Combat Over</div>
<div className="combat-over-btns">
@@ -1538,7 +1578,7 @@ export default function App() {
</div>
{hovered && (
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y - 10 }}>
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y }}>
<div className="tooltip-name">{locMap[hovered].name}</div>
<div className="tooltip-desc">{locMap[hovered].description}</div>
</div>
@@ -1603,25 +1643,36 @@ export default function App() {
<div className="location-log-content">
{locationLog.map((line, i) => <div key={i} className="location-log-line">{line}</div>)}
</div>
<div className="location-section">
<div className="location-section-header">Description</div>
<div className="location-section-body">
<div className="location-surrounding-desc">{locMap[showLocationModal]?.description}</div>
<div className="location-surrounding-subtitle">Connections</div>
{connections.filter(c => c.from === showLocationModal || c.to === showLocationModal).map(c => {
const id = c.from === showLocationModal ? c.to : c.from
return <div key={id} className="location-surrounding-connection">{locMap[id]?.name}</div>
})}
{shops.filter(s => s.locationId === showLocationModal).length > 0 && (
<>
<div className="location-surrounding-subtitle">Shops</div>
{shops.filter(s => s.locationId === showLocationModal).map(s => <div key={s.id} className="location-surrounding-shop">{s.name}</div>)}
</>
)}
</div>
</div>
</div>
<div className="location-main-col">
<div className="location-section">
<div className="location-section-header">Surroundings</div>
<div className="location-section-body">
<div className="location-surrounding-desc">{locMap[showLocationModal]?.description}</div>
<div className="location-surrounding-connections">
<div className="location-surrounding-subtitle">Connections</div>
{connections.filter(c => c.from === showLocationModal || c.to === showLocationModal).map(c => {
const id = c.from === showLocationModal ? c.to : c.from
return <div key={id} className="location-surrounding-connection">{locMap[id]?.name}</div>
})}
</div>
{shops.filter(s => s.locationId === showLocationModal).length > 0 && (
<div className="location-surrounding-shops">
<div className="location-surrounding-subtitle">Shops</div>
{shops.filter(s => s.locationId === showLocationModal).map(s => <div key={s.id} className="location-surrounding-shop">{s.name}</div>)}
</div>
)}
{(() => {
const corpsesHere = defeatedNpcs
.map(id => characters.find(c => c.id === id))
.filter(c => c && c.location === showLocationModal)
return corpsesHere.length === 0
? <div className="location-empty">No corpses here.</div>
: corpsesHere.map(c => <div key={c.id} className="location-surrounding-corpse">{c.name}'s corpse</div>)
})()}
</div>
</div>
<div className="location-section">
@@ -1874,19 +1925,19 @@ export default function App() {
{/* ═══════════════════════════════════════════════════════ TOOLTIPS */}
{hovered && (
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y - 10 }}>
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y }}>
<div className="tooltip-name">{locMap[hovered].name}</div>
<div className="tooltip-desc">{locMap[hovered].description}</div>
</div>
)}
{hoverInfo?.type === 'item' && itemMap[hoverInfo.id] && (
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y - 10 }}>
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y }}>
<div className="tooltip-name">{itemMap[hoverInfo.id].name}</div>
<div className="tooltip-desc">{itemMap[hoverInfo.id].description}</div>
</div>
)}
{hoverInfo?.type === 'weapon' && weaponMap[hoverInfo.id] && (
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y - 10 }}>
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y }}>
{hoverInfo.id === 'left_arm' || hoverInfo.id === 'right_arm'
? <><div className="tooltip-name">{weaponMap[hoverInfo.id].name}</div><div className="tooltip-desc">Unarmed strike (DMG: 10)</div></>
: <><div className="tooltip-name">{weaponMap[hoverInfo.id].name}</div><div className="tooltip-desc">DMG: {weaponMap[hoverInfo.id].damage} SPD: {weaponMap[hoverInfo.id].speed}</div>
@@ -1897,7 +1948,7 @@ export default function App() {
</div>
)}
{hoverInfo?.type === 'enemy' && (
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y - 10 }}>
<div className="tooltip" style={{ left: mousePos.x + 16, top: mousePos.y }}>
<div className="tooltip-name">{hoverInfo.name}</div>
<div className="tooltip-desc">{hoverInfo.defeated ? 'Defeated' : 'Hostile'}</div>
</div>