-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
59 lines (52 loc) · 16.2 KB
/
Copy pathscript.js
File metadata and controls
59 lines (52 loc) · 16.2 KB
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
document.addEventListener('DOMContentLoaded', () => {
// ----------------------------------------------------------------
// --- DATA DEFINITIONS (EASY TO EXPAND!) ---
// ----------------------------------------------------------------
const TIME_PHASES = ['Morning', 'Afternoon', 'Evening', 'Night'];
const DIFFICULTIES = {
easy: { startingItems: { wood: 10, fiber: 5, cooked_trout: 2 }, hungerModifier: 0.7, enemyDamageModifier: 0.7 },
normal: { startingItems: { wood: 5, fiber: 2 }, hungerModifier: 1.0, enemyDamageModifier: 1.0 },
hard: { startingItems: { wood: 2 }, hungerModifier: 1.3, enemyDamageModifier: 1.3 }
};
const ITEMS = { wood: { name: 'Wood', weight: 2 }, stone: { name: 'Stone', weight: 3 }, fiber: { name: 'Plant Fiber', weight: 0.5 }, herbs: { name: 'Medicinal Herbs', weight: 0.2 }, pelt: { name: 'Animal Pelt', weight: 1.5 }, boss_pelt: { name: 'Ancient Pelt', weight: 5 }, raw_trout: { name: 'Raw Trout', weight: 1 }, cooked_trout: { name: 'Cooked Trout', weight: 1, restores: 35 }, raw_pike: { name: 'Raw Pike', weight: 2 }, cooked_pike: { name: 'Cooked Pike', weight: 2, restores: 50 }, spear: { name: 'Stone Spear', weight: 4, damage: 15, range: 'short' }, bow: { name: 'Simple Bow', weight: 3, damage: 20, range: 'long' }, fishing_rod: { name: 'Fishing Rod', weight: 2 }, bandage: { name: 'Bandage', weight: 0.1, heals: 30 } };
const CRAFTING_RECIPES = { spear: { name: 'Stone Spear', ingredients: { wood: 5, stone: 2, fiber: 3 }, result: { item: 'spear', quantity: 1 }, requires: 'workbench' }, bow: { name: 'Simple Bow', ingredients: { wood: 8, fiber: 5 }, result: { item: 'bow', quantity: 1 }, requires: 'workbench' }, fishing_rod: { name: 'Fishing Rod', ingredients: { wood: 5, fiber: 3 }, result: { item: 'fishing_rod', quantity: 1 } }, bandage: { name: 'Bandage', ingredients: { fiber: 2, herbs: 1 }, result: { item: 'bandage', quantity: 1 } }, cooked_trout: { name: 'Cooked Trout', ingredients: { raw_trout: 1 }, result: { item: 'cooked_trout', quantity: 1 }, requires: 'firepit' }, cooked_pike: { name: 'Cooked Pike', ingredients: { raw_pike: 1 }, result: { item: 'cooked_pike', quantity: 1 }, requires: 'firepit' } };
const BUILDINGS = { shelter: { name: 'Shelter', ingredients: { wood: 20, fiber: 10 }, provides: 'Protection from elements, enables bed' }, firepit: { name: 'Fire Pit', ingredients: { wood: 5, stone: 8 }, provides: 'Cooking and protection at night' }, workbench: { name: 'Workbench', ingredients: { wood: 15, stone: 5 }, provides: 'Advanced crafting' }, storage_chest: { name: 'Storage Chest', ingredients: { wood: 25 }, provides: '+50 storage' }, bed: { name: 'Bed', ingredients: { wood: 10, fiber: 10, pelt: 5 }, requires: 'shelter', provides: 'Sleep through the night safely' } };
const CREATURES = { rabbit: { name: 'Rabbit', health: 10, reward: { fiber: 2 } }, mountain_goat: { name: 'Mountain Goat', health: 30, reward: { pelt: 2 } }, wolf: { name: 'Wolf', health: 40, damage: 15, range: 'short', reward: { pelt: 1 } }, shadow_stalker: { name: 'Shadow Stalker', health: 50, damage: 20, range: 'short', reward: { fiber: 5 }, nightOnly: true }, ancient_bear: { name: 'Ancient Bear', health: 250, damage: 40, range: 'short', reward: { boss_pelt: 1 } } };
const MAP_DATA = { "0,0": { biome: 'forest', discovered: true }, "0,1": { biome: 'forest' }, "0,-1": { biome: 'mountains' }, "1,0": { biome: 'lake' }, "1,1": { biome: 'forest' } };
const BIOMES = { forest: { name: 'Forest', forageLoot: { wood: 0.8, fiber: 0.7, herbs: 0.3 }, huntable: ['rabbit', 'wolf', 'shadow_stalker'] }, lake: { name: 'Lake', forageLoot: { fiber: 0.5, stone: 0.4 }, huntable: [], fishable: { raw_trout: 0.6, raw_pike: 0.3 } }, mountains: { name: 'Mountains', forageLoot: { stone: 0.8, herbs: 0.5 }, huntable: ['mountain_goat'] } };
const WEATHERS = { clear: { name: 'Clear ☀️', effects: { message: 'The sky is clear.' } }, rainy: { name: 'Rainy 🌧️', effects: { message: 'A steady rain falls.', hungerDrain: 2, fishingBonus: 0.2 } }, cold: { name: 'Cold ❄️', effects: { message: 'A biting cold sets in.', hungerDrain: 5, hopeDrain: 2 } } };
let gameState;
function getInitialGameState(difficulty) {
const settings = DIFFICULTIES[difficulty];
return {
day: 1, timeIndex: 0, playerX: 0, playerY: 0,
health: 100, maxHealth: 100, hunger: 100, maxHunger: 100, hope: 100,
inventory: { ...settings.startingItems },
storage: {}, inventoryCapacity: 20, storageCapacity: 0,
currentWeather: 'clear', built: {}, equippedWeapon: null, inCombat: false, currentEnemy: null, gameEnded: false,
difficultyModifiers: {
hunger: settings.hungerModifier,
enemyDamage: settings.enemyDamageModifier
}
};
}
const homeScreenEl = document.getElementById('home-screen'), gameContainerEl = document.getElementById('game-container'), difficultyButtons = document.querySelectorAll('.difficulty-btn'), startGameBtn = document.getElementById('start-game-btn'), dayStatEl = document.getElementById('day-stat'), clockStatEl = document.getElementById('clock-stat'), locationStatEl = document.getElementById('location-stat'), weatherStatEl = document.getElementById('weather-stat'), logTextEl = document.getElementById('log-text'), healthBarFillEl = document.getElementById('health-bar-fill'), healthStatTextEl = document.getElementById('health-stat-text'), hungerBarFillEl = document.getElementById('hunger-bar-fill'), hungerStatTextEl = document.getElementById('hunger-stat-text'), inventoryDisplayEl = document.getElementById('inventory-display'), inventoryWeightEl = document.getElementById('inventory-weight'), inventoryCapacityEl = document.getElementById('inventory-capacity'), actionsTabEl = document.getElementById('actions-tab'), craftingTabEl = document.getElementById('crafting-tab'), buildingTabEl = document.getElementById('building-tab'), tabs = document.querySelectorAll('.tab-link'), tabContents = document.querySelectorAll('.tab-content'), endingScreenEl = document.getElementById('ending-screen'), endingTitleEl = document.getElementById('ending-title'), endingTextEl = document.getElementById('ending-text');
function advanceTime(blocks = 1, actionLog) { if (gameState.gameEnded) return; logTextEl.textContent = actionLog; for (let i = 0; i < blocks; i++) { gameState.timeIndex++; if (gameState.timeIndex >= TIME_PHASES.length) { gameState.timeIndex = 0; gameState.day++; applyOvernightPenalties(); const weatherKeys = Object.keys(WEATHERS); gameState.currentWeather = weatherKeys[Math.floor(Math.random() * weatherKeys.length)]; } } if (checkGameOver()) return; updateAllUI(); }
function applyOvernightPenalties() { let hungerLoss = (10 + (WEATHERS[gameState.currentWeather].effects.hungerDrain || 0)) * gameState.difficultyModifiers.hunger; let hopeLoss = (WEATHERS[gameState.currentWeather].effects.hopeDrain || 0); if (!gameState.built.shelter) { hungerLoss += 5; hopeLoss += 5; } if (!gameState.built.firepit) { hopeLoss += 5; } gameState.hunger = Math.max(0, gameState.hunger - hungerLoss); gameState.hope = Math.max(0, gameState.hope - hopeLoss); if (gameState.hunger <= 0) gameState.health -= 15; }
function checkGameOver() { if (gameState.health <= 0) { endGame('You succumbed to your wounds.'); return true; } if (gameState.hope <= 0) { endGame('You lost the will to live.'); return true; } return false; }
function endGame(baseMessage) { if (gameState.gameEnded) return; gameState.gameEnded = true; endingTitleEl.textContent = "Your story ends..."; endingTextEl.textContent = `${baseMessage} You survived ${gameState.day} days.`; endingScreenEl.classList.remove('hidden'); }
function updateAllUI() { updateStatsUI(); updateInventoryUI(); updateActionsUI(); updateCraftingUI(); updateBuildingUI(); }
function updateStatsUI() { dayStatEl.textContent = gameState.day; clockStatEl.textContent = TIME_PHASES[gameState.timeIndex]; const locationKey = `${gameState.playerX},${gameState.playerY}`; const biomeName = BIOMES[MAP_DATA[locationKey].biome].name; locationStatEl.textContent = `Location: (${gameState.playerX}, ${gameState.playerY}) - ${biomeName}`; weatherStatEl.textContent = `Weather: ${WEATHERS[gameState.currentWeather].name}`; healthBarFillEl.style.width = `${(gameState.health / gameState.maxHealth) * 100}%`; healthStatTextEl.textContent = `${gameState.health}/${gameState.maxHealth}`; hungerBarFillEl.style.width = `${(gameState.hunger / gameState.maxHunger) * 100}%`; hungerStatTextEl.textContent = `${gameState.hunger}/${gameState.maxHunger}`; }
function updateInventoryUI() { inventoryDisplayEl.innerHTML = ''; let totalWeight = 0; for (const itemKey in gameState.inventory) { const quantity = gameState.inventory[itemKey]; if (quantity > 0) { const item = ITEMS[itemKey]; inventoryDisplayEl.innerHTML += `<div class="inventory-item">${item.name} x${quantity}</div>`; totalWeight += item.weight * quantity; } } inventoryWeightEl.textContent = totalWeight.toFixed(1); inventoryCapacityEl.textContent = gameState.inventoryCapacity; }
function updateActionsUI() { actionsTabEl.innerHTML = ''; const isNight = TIME_PHASES[gameState.timeIndex] === 'Night'; actionsTabEl.innerHTML += `<button data-action="eat" ${!hasItem('cooked_trout') && !hasItem('cooked_pike') ? 'disabled' : ''}>Eat Cooked Food</button>`; actionsTabEl.innerHTML += `<button data-action="heal" ${!hasItem('bandage') ? 'disabled' : ''}>Use Bandage</button>`; if (gameState.built.bed) actionsTabEl.innerHTML += `<button data-action="sleep">Sleep in Bed</button>`; const directions = { moveNorth: [0, 1], moveSouth: [0, -1], moveEast: [1, 0], moveWest: [-1, 0] }; for (const dir in directions) { const [dx, dy] = directions[dir]; const nextCoord = `${gameState.playerX + dx},${gameState.playerY + dy}`; if (MAP_DATA[nextCoord]) actionsTabEl.innerHTML += `<button data-action="${dir}" ${isNight ? 'disabled' : ''}>Move ${dir.replace('move', '')}</button>`; } const currentLocationKey = `${gameState.playerX},${gameState.playerY}`; const currentBiomeData = BIOMES[MAP_DATA[currentLocationKey].biome]; if (currentBiomeData.forageLoot) actionsTabEl.innerHTML += `<button data-action="forage">Forage</button>`; if (currentBiomeData.huntable?.length > 0) actionsTabEl.innerHTML += `<button data-action="hunt" class="danger-action">Hunt</button>`; if (currentBiomeData.fishable) actionsTabEl.innerHTML += `<button data-action="fish" ${!hasItem('fishing_rod') ? 'disabled' : ''}>Fish</button>`; }
function updateCraftingUI() { craftingTabEl.innerHTML = ''; for (const recipeKey in CRAFTING_RECIPES) { const recipe = CRAFTING_RECIPES[recipeKey]; if (recipe.requires && !gameState.built[recipe.requires]) continue; let canCraft = true; let ingredientText = []; for (const ing in recipe.ingredients) { const needed = recipe.ingredients[ing]; const owned = gameState.inventory[ing] || 0; ingredientText.push(`${ITEMS[ing].name} ${owned}/${needed}`); if (owned < needed) canCraft = false; } craftingTabEl.innerHTML += `<button data-craft="${recipeKey}" ${!canCraft ? 'disabled' : ''}>Craft ${recipe.name} (${ingredientText.join(', ')})</button>`; } }
function updateBuildingUI() { buildingTabEl.innerHTML = ''; for (const buildingKey in BUILDINGS) { if (gameState.built[buildingKey] && buildingKey !== 'storage_chest') continue; if (BUILDINGS[buildingKey].requires && !gameState.built[BUILDINGS[buildingKey].requires]) continue; const building = BUILDINGS[buildingKey]; let canBuild = true; let ingredientText = []; for (const ing in building.ingredients) { const needed = building.ingredients[ing]; const owned = gameState.inventory[ing] || 0; ingredientText.push(`${ITEMS[ing].name} ${owned}/${needed}`); if (owned < needed) canBuild = false; } buildingTabEl.innerHTML += `<button data-build="${buildingKey}" ${!canBuild ? 'disabled' : ''}>Build ${building.name} (${ingredientText.join(', ')})</button>`; } }
function handleGlobalClick(e) { const target = e.target.closest('button'); if (!target) return; const action = target.dataset.action, craftItem = target.dataset.craft, buildItem = target.dataset.build; if (action) { const directions = { moveNorth: [0, 1], moveSouth: [0, -1], moveEast: [1, 0], moveWest: [-1, 0] }; if (directions[action]) { const [dx, dy] = directions[action]; gameState.playerX += dx; gameState.playerY += dy; MAP_DATA[`${gameState.playerX},${gameState.playerY}`].discovered = true; advanceTime(1, `You travel ${action.replace('move', '').toLowerCase()}...`); return; } switch (action) { case 'forage': const biome = BIOMES[MAP_DATA[`${gameState.playerX},${gameState.playerY}`].biome]; let logMessage = "You forage but find nothing useful."; for (const item in biome.forageLoot) { if (Math.random() < biome.forageLoot[item]) { addItemToInventory(item, 1); logMessage = `You foraged and found some ${ITEMS[item].name}.`; break; } } advanceTime(1, logMessage); break; case 'fish': const fishBiome = BIOMES[MAP_DATA[`${gameState.playerX},${gameState.playerY}`].biome]; let fishLog = "You cast your line... "; let caught = false; for (const fish in fishBiome.fishable) { if (Math.random() < fishBiome.fishable[fish]) { addItemToInventory(fish, 1); fishLog += `and catch a ${ITEMS[fish].name}!`; caught = true; break; } } if (!caught) fishLog += "but nothing bites."; advanceTime(1, fishLog); break; case 'sleep': const timeTillMorning = TIME_PHASES.length - gameState.timeIndex; gameState.hope = Math.min(100, gameState.hope + 20); advanceTime(timeTillMorning, "You rest peacefully in your bed, feeling safe and hopeful."); break; case 'eat': if (hasItem('cooked_pike')) { removeItem('cooked_pike', 1); gameState.hunger = Math.min(gameState.maxHunger, gameState.hunger + ITEMS.cooked_pike.restores); } else if (hasItem('cooked_trout')) { removeItem('cooked_trout', 1); gameState.hunger = Math.min(gameState.maxHunger, gameState.hunger + ITEMS.cooked_trout.restores); } updateAllUI(); break; case 'heal': if (hasItem('bandage')) { removeItem('bandage', 1); gameState.health = Math.min(gameState.maxHealth, gameState.health + ITEMS.bandage.heals); } updateAllUI(); break; } } if (craftItem) { const recipe = CRAFTING_RECIPES[craftItem]; for (const ing in recipe.ingredients) removeItem(ing, recipe.ingredients[ing]); addItemToInventory(recipe.result.item, recipe.result.quantity); logTextEl.textContent = `You successfully crafted a ${recipe.name}.`; updateAllUI(); } if (buildItem) { const building = BUILDINGS[buildItem]; for (const ing in building.ingredients) removeItem(ing, building.ingredients[ing]); gameState.built[buildItem] = (gameState.built[buildItem] || 0) + 1; if (buildItem === 'storage_chest') gameState.storageCapacity += 50; advanceTime(2, `You spend time building a ${building.name}.`); } }
function hasItem(itemKey, quantity = 1) { return (gameState.inventory[itemKey] || 0) >= quantity; }
function addItemToInventory(itemKey, quantity) { let currentWeight = 0; for(const key in gameState.inventory) currentWeight += (ITEMS[key].weight * gameState.inventory[key]); if(currentWeight + (ITEMS[itemKey].weight * quantity) > gameState.inventoryCapacity) { logTextEl.textContent = "You don't have enough inventory space!"; return; } gameState.inventory[itemKey] = (gameState.inventory[itemKey] || 0) + quantity; }
function removeItem(itemKey, quantity) { if (hasItem(itemKey, quantity)) gameState.inventory[itemKey] -= quantity; }
let currentDifficulty = 'normal';
difficultyButtons.forEach(btn => { btn.addEventListener('click', () => { difficultyButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); currentDifficulty = btn.dataset.difficulty; }); });
startGameBtn.addEventListener('click', () => { homeScreenEl.classList.add('hidden'); gameContainerEl.classList.remove('hidden'); initializeGame(currentDifficulty); });
function initializeGame(difficulty) { gameState = getInitialGameState(difficulty); document.body.addEventListener('click', handleGlobalClick); tabs.forEach(tab => tab.addEventListener('click', (e) => { e.stopPropagation(); tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); tabContents.forEach(c => c.classList.remove('active')); document.getElementById(tab.dataset.tab).classList.add('active'); })); document.getElementById('restart-btn').addEventListener('click', (e) => { e.stopPropagation(); endingScreenEl.classList.add('hidden'); homeScreenEl.classList.remove('hidden'); gameContainerEl.classList.add('hidden'); }); updateAllUI(); }
});