Spaces:
Running
Running
// --- Game State --- | |
// (Keep the gameState definition from the previous step, including the nested 'character' object) | |
let gameState = { | |
currentPageId: 1, | |
character: { | |
name: "Hero", race: "Human", alignment: "Neutral Good", class: "Fighter", | |
level: 1, xp: 0, xpToNextLevel: 100, statPointsPerLevel: 1, availableStatPoints: 0, | |
stats: { strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30 }, | |
inventory: [] | |
} | |
}; | |
// --- DOM Element Getters --- | |
// (Keep all the getters from the previous step: charNameInput, charRaceSpan, etc.) | |
const charNameInput = document.getElementById('char-name'); | |
const charRaceSpan = document.getElementById('char-race'); | |
const charAlignmentSpan = document.getElementById('char-alignment'); | |
const charClassSpan = document.getElementById('char-class'); | |
const charLevelSpan = document.getElementById('char-level'); | |
const charXPSpan = document.getElementById('char-xp'); | |
const charXPNextSpan = document.getElementById('char-xp-next'); | |
const charHPSpan = document.getElementById('char-hp'); | |
const charMaxHPSpan = document.getElementById('char-max-hp'); | |
const charInventoryList = document.getElementById('char-inventory-list'); | |
const statSpans = { | |
strength: document.getElementById('stat-strength'), intelligence: document.getElementById('stat-intelligence'), | |
wisdom: document.getElementById('stat-wisdom'), dexterity: document.getElementById('stat-dexterity'), | |
constitution: document.getElementById('stat-constitution'), charisma: document.getElementById('stat-charisma'), | |
}; | |
const statIncreaseButtons = document.querySelectorAll('.stat-increase'); | |
const levelUpButton = document.getElementById('levelup-btn'); | |
const saveCharButton = document.getElementById('save-char-btn'); | |
const exportCharButton = document.getElementById('export-char-btn'); | |
const statIncreaseCostSpan = document.getElementById('stat-increase-cost'); | |
const statPointsAvailableSpan = document.getElementById('stat-points-available'); // Added span for clarity | |
// --- Character Sheet Functions --- | |
function renderCharacterSheet() { | |
// (Keep the logic from the previous renderCharacterSheet function) | |
// ... | |
const char = gameState.character; | |
charNameInput.value = char.name; | |
charRaceSpan.textContent = char.race; | |
charAlignmentSpan.textContent = char.alignment; | |
charClassSpan.textContent = char.class; | |
charLevelSpan.textContent = char.level; | |
charXPSpan.textContent = char.xp; | |
charXPNextSpan.textContent = char.xpToNextLevel; | |
char.stats.hp = Math.min(char.stats.hp, char.stats.maxHp); | |
charHPSpan.textContent = char.stats.hp; | |
charMaxHPSpan.textContent = char.stats.maxHp; | |
for (const stat in statSpans) { | |
if (statSpans.hasOwnProperty(stat) && char.stats.hasOwnProperty(stat)) { | |
statSpans[stat].textContent = char.stats[stat]; | |
} | |
} | |
charInventoryList.innerHTML = ''; | |
const maxSlots = 15; | |
for (let i = 0; i < maxSlots; i++) { | |
const li = document.createElement('li'); | |
if (i < char.inventory.length) { | |
const item = char.inventory[i]; | |
const itemInfo = itemsData[item] || { type: 'unknown', description: '???' }; | |
const itemSpan = document.createElement('span'); | |
itemSpan.classList.add(`item-${itemInfo.type || 'unknown'}`); | |
itemSpan.title = itemInfo.description; | |
itemSpan.textContent = item; | |
li.appendChild(itemSpan); | |
} else { | |
const emptySlotSpan = document.createElement('span'); | |
emptySlotSpan.classList.add('item-slot'); | |
emptySlotSpan.textContent = '[Empty]'; // Set text directly | |
li.appendChild(emptySlotSpan); | |
} | |
charInventoryList.appendChild(li); | |
} | |
updateLevelUpAvailability(); // Handles button disabling logic | |
} | |
function calculateStatIncreaseCost() { | |
// (Keep the same logic) | |
return (gameState.character.level * 10) + 5; | |
} | |
function updateLevelUpAvailability() { | |
const char = gameState.character; | |
const canLevelUp = char.xp >= char.xpToNextLevel; | |
levelUpButton.disabled = !canLevelUp; | |
const cost = calculateStatIncreaseCost(); | |
const canIncreaseWithXP = char.xp >= cost; | |
const canIncreaseWithPoints = char.availableStatPoints > 0; | |
statIncreaseButtons.forEach(button => { | |
button.disabled = !(canIncreaseWithPoints || canIncreaseWithXP); | |
// Optionally disable if level up is pending | |
// button.disabled = button.disabled || canLevelUp; | |
}); | |
// Update cost/points display text | |
statIncreaseCostSpan.textContent = cost; | |
statPointsAvailableSpan.textContent = char.availableStatPoints; | |
} | |
function handleLevelUp() { | |
// (Keep the same logic) | |
const char = gameState.character; | |
if (char.xp >= char.xpToNextLevel) { | |
char.level++; | |
char.xp -= char.xpToNextLevel; | |
char.xpToNextLevel = Math.floor(char.xpToNextLevel * 1.6); | |
char.availableStatPoints += char.statPointsPerLevel; | |
const conModifier = Math.floor((char.stats.constitution - 10) / 2); | |
const hpGain = Math.max(1, Math.floor(Math.random() * 6) + 1 + conModifier); | |
char.stats.maxHp += hpGain; | |
char.stats.hp = char.stats.maxHp; | |
console.log(`Leveled Up to ${char.level}! Gained ${char.statPointsPerLevel} stat point(s) and ${hpGain} HP.`); | |
renderCharacterSheet(); | |
} else { | |
console.warn("Not enough XP to level up yet."); | |
} | |
} | |
function handleStatIncrease(statName) { | |
// (Keep the same logic, including point spending priority) | |
const char = gameState.character; | |
const cost = calculateStatIncreaseCost(); | |
if (char.availableStatPoints > 0) { | |
char.stats[statName]++; | |
char.availableStatPoints--; | |
console.log(`Increased ${statName} using a point. ${char.availableStatPoints} points remaining.`); | |
if (statName === 'constitution') { /* ... update maxHP ... */ } // Add HP update logic here if needed | |
renderCharacterSheet(); | |
return; | |
} | |
if (char.xp >= cost) { | |
char.stats[statName]++; | |
char.xp -= cost; | |
console.log(`Increased ${statName} for ${cost} XP.`); | |
if (statName === 'constitution') { /* ... update maxHP ... */ } // Add HP update logic here if needed | |
renderCharacterSheet(); | |
} else { | |
console.warn(`Not enough XP or points to increase ${statName}.`); | |
} | |
} | |
function saveCharacter() { | |
try { | |
localStorage.setItem('textAdventureCharacter', JSON.stringify(gameState.character)); | |
console.log('Character saved locally.'); | |
// Update button text for confirmation - NO EMOJI | |
saveCharButton.textContent = 'Saved!'; | |
saveCharButton.disabled = true; // Briefly disable | |
setTimeout(() => { | |
saveCharButton.textContent = 'Save'; // Restore original text | |
saveCharButton.disabled = false; | |
}, 1500); | |
} catch (e) { | |
console.error('Error saving character:', e); | |
alert('Failed to save character.'); | |
} | |
} | |
function loadCharacter() { | |
// (Keep the same logic) | |
try { | |
const savedData = localStorage.getItem('textAdventureCharacter'); | |
if (savedData) { | |
const loadedChar = JSON.parse(savedData); | |
gameState.character = { ...gameState.character, ...loadedChar, stats: { ...gameState.character.stats, ...(loadedChar.stats || {}) }, inventory: loadedChar.inventory || [] }; | |
console.log('Character loaded from local storage.'); | |
return true; | |
} | |
} catch (e) { console.error('Error loading character:', e); } | |
return false; | |
} | |
function exportCharacter() { | |
// (Keep the same logic) | |
try { | |
const charJson = JSON.stringify(gameState.character, null, 2); | |
const blob = new Blob([charJson], { type: 'application/json' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); a.href = url; | |
const filename = `${gameState.character.name.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'character'}_save.json`; | |
a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); | |
console.log(`Character exported as ${filename}`); | |
} catch (e) { console.error('Error exporting character:', e); alert('Failed to export character data.'); } | |
} | |
// --- Event Listeners --- | |
// (Keep the same event listeners, they reference elements by ID) | |
charNameInput.addEventListener('change', () => { | |
gameState.character.name = charNameInput.value.trim() || "Hero"; | |
console.log(`Name changed to: ${gameState.character.name}`); | |
}); | |
levelUpButton.addEventListener('click', handleLevelUp); | |
statIncreaseButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
const statToIncrease = button.dataset.stat; | |
if (statToIncrease) { handleStatIncrease(statToIncrease); } | |
}); | |
}); | |
saveCharButton.addEventListener('click', saveCharacter); | |
exportCharButton.addEventListener('click', exportCharacter); | |
// --- Modify Existing Functions --- | |
function startGame() { | |
// (Keep the same logic, including loadCharacter and default merging) | |
if (!loadCharacter()) { console.log("No saved character found, starting new."); } | |
gameState.character = { | |
...{ name: "Hero", race: "Human", alignment: "Neutral Good", class: "Fighter", level: 1, xp: 0, xpToNextLevel: 100, statPointsPerLevel: 1, availableStatPoints: 0, stats: { strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30 }, inventory: [] }, | |
...gameState.character | |
}; | |
gameState.character.stats = { | |
strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30, | |
...(gameState.character.stats || {}) | |
} | |
gameState.currentPageId = 1; | |
renderCharacterSheet(); | |
renderPage(gameState.currentPageId); | |
} | |
function handleChoiceClick(choiceData) { | |
// (Keep the same logic, including reward processing) | |
const nextPageId = parseInt(choiceData.nextPage); const itemToAdd = choiceData.addItem; if (isNaN(nextPageId)) { console.error("Invalid nextPageId:", choiceData.nextPage); return; } | |
if (itemToAdd && !gameState.character.inventory.includes(itemToAdd)) { gameState.character.inventory.push(itemToAdd); console.log("Added item:", itemToAdd); } | |
gameState.currentPageId = nextPageId; const nextPageData = gameData[nextPageId]; | |
if (nextPageData) { if (nextPageData.hpLoss) { gameState.character.stats.hp -= nextPageData.hpLoss; console.log(`Lost ${nextPageData.hpLoss} HP.`); if (gameState.character.stats.hp <= 0) { gameState.character.stats.hp = 0; console.log("Player died from HP loss!"); renderCharacterSheet(); renderPage(99); return; } } | |
if (nextPageData.reward) { if (nextPageData.reward.xp) { gameState.character.xp += nextPageData.reward.xp; console.log(`Gained ${nextPageData.reward.xp} XP!`); } if (nextPageData.reward.statIncrease) { const stat = nextPageData.reward.statIncrease.stat; const amount = nextPageData.reward.statIncrease.amount; if (gameState.character.stats.hasOwnProperty(stat)) { gameState.character.stats[stat] += amount; console.log(`Stat ${stat} increased by ${amount}!`); if (stat === 'constitution') { /* ... update maxHP ... */ } } } if(nextPageData.reward.addItem && !gameState.character.inventory.includes(nextPageData.reward.addItem)){ gameState.character.inventory.push(nextPageData.reward.addItem); console.log(`Found item: ${nextPageData.reward.addItem}`); } } | |
if (nextPageData.gameOver) { console.log("Reached Game Over page."); renderCharacterSheet(); renderPage(nextPageId); return; } | |
} else { console.error(`Data for page ${nextPageId} not found!`); renderCharacterSheet(); renderPage(99); return; } | |
renderCharacterSheet(); renderPage(nextPageId); | |
} | |
// --- REMEMBER --- | |
// Make sure to remove the OLD #stats-display and #inventory-display elements from your HTML | |
// and the old updateStatsDisplay() and updateInventoryDisplay() functions from your JS. | |
// --- Initialization --- | |
// initThreeJS(); // Keep your Three.js initialization | |
startGame(); |