File size: 12,280 Bytes
1f6a578
 
e34e138
 
 
1f6a578
 
 
 
e34e138
 
44717a8
1f6a578
 
 
e34e138
 
 
 
 
 
 
 
 
 
1f6a578
 
 
 
e34e138
 
 
 
 
 
1f6a578
e34e138
1f6a578
e34e138
 
1f6a578
 
e34e138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44717a8
1f6a578
e34e138
 
 
 
 
 
 
 
 
 
 
 
 
 
1f6a578
e34e138
 
 
44717a8
 
1f6a578
44717a8
 
e34e138
1f6a578
 
44717a8
 
e34e138
 
 
 
 
1f6a578
 
 
 
e34e138
1f6a578
 
e34e138
44717a8
 
1f6a578
 
 
44717a8
 
1f6a578
e34e138
1f6a578
 
e34e138
 
1f6a578
 
 
e34e138
 
1f6a578
e34e138
1f6a578
e34e138
1f6a578
 
e34e138
 
44717a8
 
 
e34e138
1f6a578
e34e138
 
 
 
 
 
1f6a578
 
e34e138
1f6a578
e34e138
44717a8
e34e138
 
 
1f6a578
 
e34e138
 
1f6a578
44717a8
 
 
 
e34e138
 
 
1f6a578
 
 
 
 
 
 
 
e34e138
1f6a578
 
44717a8
908fc8e
 
e34e138
1f6a578
 
e34e138
 
 
1f6a578
 
 
e34e138
1f6a578
 
06d7ab8
 
e34e138
1f6a578
 
 
e34e138
 
1f6a578
e34e138
1f6a578
 
 
06d7ab8
 
1f6a578
 
 
e34e138
 
1f6a578
e34e138
 
 
 
 
1f6a578
e34e138
 
 
 
44717a8
 
e34e138
4b81814
e34e138
1f6a578
 
e34e138
1f6a578
 
e34e138
1f6a578
 
 
 
 
 
 
06d7ab8
 
44717a8
1f6a578
 
 
 
 
 
 
 
 
 
06d7ab8
 
1f6a578
 
 
06d7ab8
1f6a578
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// --- 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();