// --- 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();