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