|
|
|
|
|
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: [] |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
function renderCharacterSheet() { |
|
|
|
|
|
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]'; |
|
li.appendChild(emptySlotSpan); |
|
} |
|
charInventoryList.appendChild(li); |
|
} |
|
|
|
updateLevelUpAvailability(); |
|
} |
|
|
|
function calculateStatIncreaseCost() { |
|
|
|
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); |
|
|
|
|
|
}); |
|
|
|
|
|
statIncreaseCostSpan.textContent = cost; |
|
statPointsAvailableSpan.textContent = char.availableStatPoints; |
|
} |
|
|
|
|
|
function handleLevelUp() { |
|
|
|
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) { |
|
|
|
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') { } |
|
renderCharacterSheet(); |
|
return; |
|
} |
|
|
|
if (char.xp >= cost) { |
|
char.stats[statName]++; |
|
char.xp -= cost; |
|
console.log(`Increased ${statName} for ${cost} XP.`); |
|
if (statName === 'constitution') { } |
|
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.'); |
|
|
|
saveCharButton.textContent = 'Saved!'; |
|
saveCharButton.disabled = true; |
|
setTimeout(() => { |
|
saveCharButton.textContent = 'Save'; |
|
saveCharButton.disabled = false; |
|
}, 1500); |
|
} catch (e) { |
|
console.error('Error saving character:', e); |
|
alert('Failed to save character.'); |
|
} |
|
} |
|
|
|
function loadCharacter() { |
|
|
|
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() { |
|
|
|
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.'); } |
|
} |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
function startGame() { |
|
|
|
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) { |
|
|
|
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') { } } } 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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
startGame(); |