diff --git a/CursorUserSetup-x64-3.6.21.exe b/CursorUserSetup-x64-3.6.21.exe deleted file mode 100644 index fd33e58..0000000 Binary files a/CursorUserSetup-x64-3.6.21.exe and /dev/null differ diff --git a/malebody.png b/malebody.png deleted file mode 100644 index 0188a75..0000000 Binary files a/malebody.png and /dev/null differ diff --git a/src/App.css b/src/App.css index c6c5c0b..eeafedb 100644 --- a/src/App.css +++ b/src/App.css @@ -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; diff --git a/src/App.jsx b/src/App.jsx index 2a962ff..deb13b3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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() {
setExpandedWeapon(expandedWeapon === 'ROOT' ? null : 'ROOT')}> {expandedWeapon === 'ROOT' ? '▼' : '▶'} - { e.stopPropagation(); setCharView('player') }}>Player + { e.stopPropagation(); setShowInventory(true); setPanelTarget('player') }}>Player
{expandedWeapon === 'ROOT' && (
@@ -856,26 +877,76 @@ function App() { )}
-
- { - 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) - }}> - - {MAP_BLOCKS.map((block, i) => ( - - ))} - {(combat?.enemies || []).map((enemy, i) => ( - { 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 && } +
+
+ + + +
+ { + 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) + }}> + + + {MAP_BLOCKS.map((block, i) => ( + + ))} + {(combat?.enemies || []).map((enemy, i) => ( + { 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 && } + {combat && } +
@@ -890,254 +961,14 @@ function App() {
{(combat?.enemies || []).map((enemy, i) => ( -
{ setSelectedEnemy(i); setTargetEnemy(i); setCharView(i) }}> +
{ setSelectedEnemy(i); setTargetEnemy(i); setShowInventory(true); setPanelTarget(i) }}> {enemy.name}
))}
- {charView !== null && ( -
{ setCharView(null); setHoverInfo(null); setContextMenu(null) }} - onContextMenu={e => { e.preventDefault(); setContextMenu(null) }} - onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}> -
e.stopPropagation()}> - -
-
- {charView === 'player' ? ( - - ) : combat?.enemies[charView] ? ( - - ) : null} -
-
-
- {charView === 'player' ? ( - <> - - - - - ) : ( - <> - - - - - )} -
- {charView === 'player' ? ( - <> - {invTab === 'health' && ( -
-
Body Parts
- {Object.keys(MAX_BODY).map(part => { - const hp = bodyParts[part] - const max = MAX_BODY[part] - const injuries = bodyInjuries[part] ?? [] - return ( -
-
-
-
{bodyPartLabels[part]}
-
-
-
-
{hp}/{max}
-
- {injuries.length > 0 && ( -
- {injuries.map((inj, i) => ( - {inj.type} ({inj.severity}) - ))} -
- )} -
- ) - })} -
- Integrity - {playerHp}/{MAX_HP} -
-
- )} - - {invTab === 'inventory' && ( -
-
-
Weapons
- {(() => { - const lw = equippedWeapons.left, rw = equippedWeapons.right - if (lw === rw) { - return ( -
setHoverInfo({ type: 'weapon', id: lw })} - onMouseLeave={() => setHoverInfo(null)} - onContextMenu={e => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, weaponId: lw, side: 'both' }) }}> - {lw === 'fists' ? 'Fists' : `Both: ${weaponMap[lw].name}`} - {lw !== 'fists' && ( - <>DMG: {weaponMap[lw].damage} - COND: {weaponConditions[lw] ?? weaponMap[lw].maxCondition}/{weaponMap[lw].maxCondition} - {weaponMap[lw].passives.length > 0 && ( - {weaponMap[lw].passives.join(', ')} - )} - )} -
- ) - } - return ['left', 'right'].map(side => { - const wid = equippedWeapons[side] - if (wid === 'fists') return null - return ( -
setHoverInfo({ type: 'weapon', id: wid })} - onMouseLeave={() => setHoverInfo(null)} - onContextMenu={e => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, weaponId: wid, side }) }}> - {side === 'left' ? 'Left' : 'Right'}: {weaponMap[wid].name} - DMG: {weaponMap[wid].damage} - COND: {weaponConditions[wid] ?? weaponMap[wid].maxCondition}/{weaponMap[wid].maxCondition} - {weaponMap[wid].passives.length > 0 && ( - {weaponMap[wid].passives.join(', ')} - )} -
- ) - }) - })()} -
-
-
Inventory ({playerInventory.length})
- {playerInventory.length === 0 ? ( -
Empty
- ) : ( - <> - {playerInventory.map((itemId, i) => ( -
setHoverInfo({ type: 'item', id: itemId })} - onMouseLeave={() => setHoverInfo(null)}> - {itemMap[itemId]?.name ?? itemId} -
- ))} - - )} -
Weight: {currentWeight}/{MAX_WEIGHT}
-
{playerMoney}g
-
- )} - - {invTab === 'stats' && ( -
-
Character
-
NameYou
-
Age24
-
Location{locMap[playerLoc]?.name ?? playerLoc}
-
Day{gameTime.day}
-
Time{String(gameTime.hour).padStart(2, '0')}:00
-
Weather{weatherLabels[weather]}
-
-
Equipped{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(' + ')}
- {(() => { - const shown = new Set() - const rows = [] - const addWeapon = (wid) => { - if (shown.has(wid) || wid === 'fists') return - shown.add(wid) - rows.push( - -
Skills{weaponMap[wid].skills.map(s => s.name).join(', ')}
-
Weapon SPD{weaponMap[wid].speed}
-
- ) - } - addWeapon(equippedWeapons.left) - addWeapon(equippedWeapons.right) - return rows - })()} -
-
Integrity{playerHp}/{MAX_HP}
-
Items{playerInventory.length}
-
Weight{currentWeight}/{MAX_WEIGHT}
-
Money{playerMoney}g
-
- )} - - ) : combat?.enemies[charView] ? ( - <> - {enemyInvTab === 'health' && ( -
-
Body Parts
- {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 ( -
-
-
-
{bodyPartLabels[part]}
-
-
-
-
{hp}/{max}
-
- {injuries.length > 0 && ( -
- {injuries.map((inj, i) => ( - {inj.type} ({inj.severity}) - ))} -
- )} -
- ) - })} -
- Integrity - {Object.values(combat.enemies[charView].integrity).reduce((a, b) => a + b, 0)}/{MAX_HP} -
-
- )} - - {enemyInvTab === 'inventory' && ( -
-
Inventory ({(combat.enemies[charView].inventory || []).length})
- {(combat.enemies[charView].inventory || []).length === 0 ? ( -
Empty
- ) : ( - <> - {combat.enemies[charView].inventory.map((itemId, i) => ( -
setHoverInfo({ type: 'item', id: itemId })} - onMouseLeave={() => setHoverInfo(null)}> - {itemMap[itemId]?.name ?? itemId} -
- ))} - - )} -
{combat.enemies[charView].money || 0}g
-
- )} - - {enemyInvTab === 'stats' && ( -
-
{combat.enemies[charView].name}
-
Attack{combat.enemies[charView].attack || 0}
-
Speed{combat.enemies[charView].speed || 0}
-
- )} - - ) : null} -
-
-
-
- )} {showSettings && (
@@ -1186,21 +1017,93 @@ function App() {
)} {limbSelect && ( -
setLimbSelect(null)}> -
e.stopPropagation()}> -
Select body part to attack
-
- - - - - - -
-
- -
+
cancelAttack()} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0 }}> +
e.stopPropagation()}> + + {(() => { + 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 ( + <> +
{weaponName}
+ {!selectedSkill && skills.length > 0 ? ( +
+
Select Skill
+
+ {skills.map((skill, i) => ( + + ))} +
+
+ ) : selectedSkill && attackEnemy === null ? ( +
+
Select Enemy
+
+ {(combat?.enemies || []).map((enemy, i) => ( + + ))} +
+
+ ) : ( +
+
+ {skills.length > 0 && selectedSkill && ( +
Skill: {selectedSkill}
+ )} + {attackEnemy !== null && combat?.enemies[attackEnemy] && ( +
Target: {combat.enemies[attackEnemy].name}
+ )} + {isArm && ( +
A weak unarmed punch (DMG: 10)
+ )} +
+
+
Limb
+
+ {[ + { 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 ( + + ) + })} +
+
+
+ )} + + ) + })()}
+ {selectedTarget && ( +
+ + +
+ )}
)}
@@ -1268,7 +1171,14 @@ function App() {
- +
)} + + )} - {showInventory && ( -
{ setShowInventory(false); setHoverInfo(null); setContextMenu(null) }} - onContextMenu={e => { e.preventDefault(); setContextMenu(null) }} - onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}> -
e.stopPropagation()}> - -
-
- -
-
-
- - - -
+ {showInventory && ( +
{ setShowInventory(false); setPanelTarget('player'); setHoverInfo(null); setContextMenu(null) }} + onContextMenu={e => { e.preventDefault(); setContextMenu(null) }} + onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}> +
e.stopPropagation()}> + +
+
+ {panelTarget === 'player' ? ( + + ) : combat?.enemies[panelTarget] ? ( + + ) : null} +
+
+
+ + + +
+ {panelTarget === 'player' ? ( + <> {invTab === 'health' && (
Body Parts
@@ -1606,9 +1524,6 @@ function App() {
NameYou
Age24
Location{locMap[playerLoc]?.name ?? playerLoc}
-
Day{gameTime.day}
-
Time{String(gameTime.hour).padStart(2, '0')}:00
-
Weather{weatherLabels[weather]}
Equipped{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(' + ')}
{(() => { @@ -1635,60 +1550,165 @@ function App() {
Money{playerMoney}g
)} -
-
-
-
- )} + + ) : combat?.enemies[panelTarget] ? ( + <> + {invTab === 'health' && ( +
+
Body Parts
+ {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 ( +
+
+
+
{bodyPartLabels[part]}
+
+
+
+
{hp}/{max}
+
+ {injuries.length > 0 && ( +
+ {injuries.map((inj, i) => ( + {inj.type} ({inj.severity}) + ))} +
+ )} +
+ ) + })} +
+ Integrity + {Object.values(combat.enemies[panelTarget].integrity).reduce((a, b) => a + b, 0)}/{MAX_HP} +
+
+ )} - {showSettings && ( -
-
- Settings - + {invTab === 'inventory' && ( +
+
Inventory ({(combat.enemies[panelTarget].inventory || []).length})
+ {(combat.enemies[panelTarget].inventory || []).length === 0 ? ( +
Empty
+ ) : ( + <> + {combat.enemies[panelTarget].inventory.map((itemId, i) => ( +
setHoverInfo({ type: 'item', id: itemId })} + onMouseLeave={() => setHoverInfo(null)}> + {itemMap[itemId]?.name ?? itemId} +
+ ))} + + )} +
{combat.enemies[panelTarget].money || 0}g
+
+ )} + + {invTab === 'stats' && ( +
+ {(() => { + const e = combat.enemies[panelTarget] + const wid = e.weapon ?? 'fists' + const wep = weaponMap[wid] + return ( + <> +
{e.name}
+
Name{e.name}
+
Age{e.age ?? '??'}
+
Location{e.location ?? 'Unknown'}
+
+
Weapon{wep?.name ?? wid}
+ {wep && wid !== 'fists' && ( + <>
DMG{e.attack}
+
SPD{e.speed}
+ )} + {wep && wep.skills.length > 0 && ( +
Skills{wep.skills.map(s => s.name).join(', ')}
+ )} + {wid === 'fists' && ( +
Attack{e.attack}
+ )} +
+
Integrity{Object.values(e.integrity).reduce((a, b) => a + b, 0)}/{MAX_HP}
+
Items{(e.inventory || []).length}
+
Money{e.money || 0}g
+ + ) + })()} +
+ )} + + ) : null}
-
-
-
Display
-
- Zoom -
- - {Math.round(view.scale * 100)}% - -
-
-
-
-
Game
-
- Day - {gameTime.day} -
-
- Time - {String(gameTime.hour).padStart(2, '0')}:00 -
-
- Weather - {weatherLabels[weather]} -
-
-
-
Player
-
- Location - {locMap[playerLoc]?.name ?? playerLoc} -
-
- Money - {playerMoney}g -
+
+
+
+ )} + + {showSettings && ( +
+
+ Settings + +
+
+
+
Display
+
+ Zoom +
+ + {Math.round(view.scale * 100)}% +
- )} - +
+
Game
+
+ Day + {gameTime.day} +
+
+ Time + {String(gameTime.hour).padStart(2, '0')}:00 +
+
+ Weather + {weatherLabels[weather]} +
+
+
+
Player
+
+ Location + {locMap[playerLoc]?.name ?? playerLoc} +
+
+ Money + {playerMoney}g +
+
+
+
+ )} + + {showWorldInfo && ( +
setShowWorldInfo(false)}> +
e.stopPropagation()}> + +
World
+
+
Day{gameTime.day}
+
Time{String(gameTime.hour).padStart(2, '0')}:00
+
Weather{weatherLabels[weather]}
+
Location{locMap[playerLoc]?.name ?? playerLoc}
+
+
+
)} {hoverInfo?.type === 'enemy' && (