2025-09-05 10:50:28 +02:00

713 lines
21 KiB
JavaScript

import storyNodes from "./story.js";
import sounds from "./sounds.js";
import items from "./items.js";
import recipes from "./recipes.js";
// ==================================================================
// Variablen und Elemente
// ==================================================================
const storyTextElement = document.getElementById("story-text");
const optionsButtonsElement = document.getElementById("options-buttons");
const prolog1 = document.getElementById("prolog-1");
const prolog2 = document.getElementById("prolog-2");
const gameContainer = document.getElementById("game-container");
const introContainer = document.getElementById("intro-container");
const landingPage = document.getElementById("landingpage");
const initialStartBtn = document.getElementById("initial-start-btn");
const startBtn = document.getElementById("weiter-btn");
const openEyesBtn = document.getElementById("open-eyes-btn");
const continueBtn = document.getElementById("continue-btn");
const healthFillBar = document.getElementById("health-bar-fill");
const hungerFillBar = document.getElementById("hunger-bar-fill");
const thirstFillBar = document.getElementById("thirst-bar-fill");
const prologMusic = new Audio(sounds.prologMusic);
const crashSound = new Audio(sounds.crashSound);
const openCraftingBtn = document.getElementById("open-crafting-btn");
const closeCraftingBtn = document.getElementById("close-crafting-btn");
const craftingModal = document.getElementById("crafting-modal");
const openInventoryBtn = document.getElementById("open-inventory-btn");
const inventoryModal = document.getElementById("inventory-modal");
const closeInventoryBtn = document.getElementById("close-inventory-btn");
const openBasebuildingBtn = document.getElementById("open-basebuilding-btn");
const basebuildingModal = document.getElementById("basebuilding-modal");
const closeBasebuildingBtn = document.getElementById("close-basebuilding-btn");
// ==================================================================
// Savegame Handling
// ==================================================================
if (localStorage.getItem("saveGameData")) {
continueBtn.classList.remove("hidden");
}
// ==================================================================
// Prolog Music
// ==================================================================
prologMusic.loop = true;
prologMusic.volume = 0.5;
// ==================================================================
// Event Listener Setup
// ==================================================================
// -------Prolog Start-------
initialStartBtn.addEventListener("click", () => {
localStorage.removeItem("saveGameData");
landingPage.classList.add("hidden");
introContainer.classList.remove("hidden");
prologMusic.play();
});
// -------Spielstand Laden-------
continueBtn.addEventListener("click", () => {
introContainer.classList.add("hidden");
landingPage.classList.add("hidden");
gameContainer.classList.remove("hidden");
loadGame();
showStoryNode(playerState.currentSceneId);
});
// -------Spiel Start-------
startBtn.addEventListener("click", () => {
prologMusic.pause();
crashSound.play();
prolog1.classList.add("hidden");
prolog2.classList.remove("hidden");
});
// -------Augen öffnen-------
openEyesBtn.addEventListener("click", () => {
prolog2.classList.add("hidden");
introContainer.classList.add("hidden");
gameContainer.classList.remove("hidden");
updateInventoryDisplay();
startGame();
});
// ------Open-Crafting------
openCraftingBtn.addEventListener("click", () => {
playSound(sounds.uiClick);
openCraftingMenu();
});
// ------Close-Crafting------
closeCraftingBtn.addEventListener("click", (event) => {
if (event.target === closeCraftingBtn) {
playSound(sounds.uiClick);
craftingModal.classList.add("hidden");
}
});
// ------Open-Inventar------
if (openInventoryBtn) {
openInventoryBtn.addEventListener('click', () => {
toggleInventory();
});
}
window.addEventListener('keydown', (event) => {
if (event.key.toLowerCase() === 'i') {
toggleInventory();
}
});
// ------Close-Inventar-------
if (closeInventoryBtn) {
closeInventoryBtn.addEventListener('click', () => {
if (inventoryModal) inventoryModal.classList.add('hidden');
});
}
// ------Basebuilding Events------
if (openBasebuildingBtn) {
openBasebuildingBtn.addEventListener('click', () => {
openBasebuildingModal();
});
}
if (closeBasebuildingBtn) {
closeBasebuildingBtn.addEventListener('click', () => {
if (basebuildingModal) basebuildingModal.classList.add('hidden');
});
}
// ==================================================================
// Main Game Logic
// ==================================================================
landingPage.classList.remove("hidden");
let playerState = {
health: 100,
hunger: 100,
thirst: 100,
energy: 100,
inventory: [],
currentSceneId: "start_game",
sickness: null,
storyFlags: {},
baseBuilding: {
cabinSize: 'm',
placedItems: {}
}
};
// Basebuilding variables
let currentCabinSize = 'm';
let selectedBuildingItem = null;
function startGame() {
showStoryNode("start_game");
}
// -------Health, Hunger, Thirst Update-------
function updateStatsDisplay() {
healthFillBar.style.width = playerState.health + "%";
hungerFillBar.style.width = playerState.hunger + "%";
thirstFillBar.style.width = playerState.thirst + "%";
}
// ==================================================================
// Inventory Display
// ==================================================================
function updateInventoryDisplay() {
const inventoryItemsElement = document.getElementById("inventory-items");
if (inventoryItemsElement) inventoryItemsElement.innerHTML = "";
playerState.inventory.forEach((itemName) => {
const itemData = items[itemName];
if (!itemData) {
console.log(`Item "${itemName}" nicht gefunden!`);
return;
}
const itemElement = document.createElement("div");
itemElement.classList.add("inventory-item");
itemElement.addEventListener("click", () => useItem(itemName));
const itemImage = document.createElement("img");
itemImage.src = itemData.image;
const tooltipElement = document.createElement("div");
tooltipElement.classList.add("item-tooltip");
tooltipElement.innerHTML = `
<h4>${itemName}</h4>
<p>${itemData.description}</p>
`;
itemElement.addEventListener('mouseenter', (e) => {
positionTooltip(tooltipElement, itemElement);
});
itemElement.appendChild(itemImage);
itemElement.appendChild(tooltipElement);
if (inventoryItemsElement) inventoryItemsElement.appendChild(itemElement);
});
}
function toggleInventory() {
if (inventoryModal) {
inventoryModal.classList.toggle('hidden');
updateInventoryDisplay();
}
}
// ==================================================================
// Professional Tooltip Positioning System
// ==================================================================
function positionTooltip(tooltip, item) {
tooltip.style.top = '';
tooltip.style.bottom = '';
tooltip.style.left = '';
tooltip.style.right = '';
tooltip.style.transform = '';
tooltip.className = tooltip.className.replace(/tooltip-(top|bottom|left|right)/g, '');
tooltip.style.display = 'flex';
tooltip.style.visibility = 'hidden';
const tooltipRect = tooltip.getBoundingClientRect();
const itemRect = item.getBoundingClientRect();
const viewport = {
top: 0,
left: 0,
right: window.innerWidth,
bottom: window.innerHeight
};
const modalContent = item.closest('.modal-content');
const containerRect = modalContent ? modalContent.getBoundingClientRect() : viewport;
const margin = 16;
const arrowSize = 8;
const spaces = {
top: itemRect.top - containerRect.top,
bottom: containerRect.bottom - itemRect.bottom,
left: itemRect.left - containerRect.left,
right: containerRect.right - itemRect.right
};
let position = 'bottom';
let maxSpace = spaces.top;
if (spaces.bottom > maxSpace && spaces.bottom >= tooltipRect.height + margin + arrowSize) {
position = 'top';
maxSpace = spaces.bottom;
}
if (spaces.left > maxSpace && spaces.left >= tooltipRect.width + margin + arrowSize) {
position = 'right';
maxSpace = spaces.left;
}
if (spaces.right > maxSpace && spaces.right >= tooltipRect.width + margin + arrowSize) {
position = 'left';
maxSpace = spaces.right;
}
tooltip.classList.add(`tooltip-${position}`);
switch (position) {
case 'top':
tooltip.style.top = `${itemRect.bottom + arrowSize}px`;
tooltip.style.left = `${Math.max(containerRect.left + margin,
Math.min(itemRect.left + itemRect.width / 2 - tooltipRect.width / 2,
containerRect.right - tooltipRect.width - margin))}px`;
break;
case 'bottom':
tooltip.style.top = `${itemRect.top - tooltipRect.height - arrowSize}px`;
tooltip.style.left = `${Math.max(containerRect.left + margin,
Math.min(itemRect.left + itemRect.width / 2 - tooltipRect.width / 2,
containerRect.right - tooltipRect.width - margin))}px`;
break;
case 'left':
tooltip.style.top = `${Math.max(containerRect.top + margin,
Math.min(itemRect.top + itemRect.height / 2 - tooltipRect.height / 2,
containerRect.bottom - tooltipRect.height - margin))}px`;
tooltip.style.left = `${itemRect.right + arrowSize}px`;
break;
case 'right':
tooltip.style.top = `${Math.max(containerRect.top + margin,
Math.min(itemRect.top + itemRect.height / 2 - tooltipRect.height / 2,
containerRect.bottom - tooltipRect.height - margin))}px`;
tooltip.style.left = `${itemRect.left - tooltipRect.width - arrowSize}px`;
break;
}
const finalRect = tooltip.getBoundingClientRect();
if (finalRect.left < viewport.left + margin) {
tooltip.style.left = `${viewport.left + margin}px`;
}
if (finalRect.right > viewport.right - margin) {
tooltip.style.left = `${viewport.right - tooltipRect.width - margin}px`;
}
if (finalRect.top < viewport.top + margin) {
tooltip.style.top = `${viewport.top + margin}px`;
}
if (finalRect.bottom > viewport.bottom - margin) {
tooltip.style.top = `${viewport.bottom - tooltipRect.height - margin}px`;
}
tooltip.style.visibility = 'visible';
}
// ==================================================================
// Story Flags System
// ==================================================================
function setStoryFlag(flagName, value = true) {
if (!playerState.storyFlags) {
playerState.storyFlags = {};
}
playerState.storyFlags[flagName] = value;
saveGame();
}
function hasStoryFlag(flagName) {
return playerState.storyFlags && playerState.storyFlags[flagName] === true;
}
// ==================================================================
// Base Building System
// ==================================================================
function openBasebuildingModal() {
if (basebuildingModal) {
basebuildingModal.classList.remove('hidden');
initializeBasebuilding();
}
}
function initializeBasebuilding() {
const sizeButtons = document.querySelectorAll('.size-btn');
sizeButtons.forEach(btn => {
btn.addEventListener('click', () => changeCabinSize(btn.dataset.size));
});
const buildingItems = document.querySelectorAll('.building-item');
buildingItems.forEach(btn => {
btn.addEventListener('click', () => selectBuildingItem(btn.dataset.item));
});
currentCabinSize = playerState.baseBuilding.cabinSize || 'm';
updateCabinDisplay();
}
function changeCabinSize(size) {
currentCabinSize = size;
playerState.baseBuilding.cabinSize = size;
document.querySelectorAll('.size-btn').forEach(btn => btn.classList.remove('active'));
document.querySelector(`[data-size="${size}"]`).classList.add('active');
updateCabinDisplay();
saveGame();
}
function selectBuildingItem(item) {
selectedBuildingItem = item;
document.querySelectorAll('.building-item').forEach(btn => btn.classList.remove('selected'));
document.querySelector(`[data-item="${item}"]`).classList.add('selected');
}
function updateCabinDisplay() {
const cabinGrid = document.getElementById('cabin-grid');
if (!cabinGrid) return;
cabinGrid.className = `cabin-grid cabin-${currentCabinSize}`;
cabinGrid.innerHTML = '';
const dimensions = {
'm': { cols: 6, rows: 4 },
'l': { cols: 8, rows: 6 },
'xl': { cols: 10, rows: 8 }
};
const { cols, rows } = dimensions[currentCabinSize];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const cell = document.createElement('div');
cell.className = 'cabin-cell';
cell.dataset.row = row;
cell.dataset.col = col;
const cellKey = `${row}-${col}`;
const placedItem = playerState.baseBuilding.placedItems[cellKey];
if (placedItem) {
cell.classList.add('occupied');
cell.textContent = getItemEmoji(placedItem);
}
cell.addEventListener('click', () => placeBuildingItem(row, col));
cabinGrid.appendChild(cell);
}
}
}
function placeBuildingItem(row, col) {
if (!selectedBuildingItem) {
alert('Wähle zuerst ein Objekt zum Platzieren aus!');
return;
}
const cellKey = `${row}-${col}`;
if (playerState.baseBuilding.placedItems[cellKey]) {
delete playerState.baseBuilding.placedItems[cellKey];
} else {
playerState.baseBuilding.placedItems[cellKey] = selectedBuildingItem;
}
updateCabinDisplay();
saveGame();
}
function getItemEmoji(item) {
const emojis = {
'bed': '🛏️',
'table': '🪑',
'chest': '📦',
'fireplace': '🔥',
'workbench': '🔨'
};
return emojis[item] || '❓';
}
// ==================================================================
// Crafting
// ==================================================================
function craftItem(recipeName) {
const recipe = recipes[recipeName];
if (!recipe) {
console.log(`Rezept "${recipeName}" nicht gefunden`);
return;
}
recipe.ingredients.forEach((ingredient) => {
const itemIndex = playerState.inventory.indexOf(ingredient);
if (itemIndex > -1) {
playerState.inventory.splice(itemIndex, 1);
}
});
const productName = recipe.result || recipe.product;
if (!productName) {
console.log(`Rezept "${recipeName}" hat kein result/product Feld`);
return;
}
playerState.inventory.push(productName);
playSound(sounds.craftSuccess);
updateInventoryDisplay();
saveGame();
}
function openCraftingMenu() {
const craftingOptionsContainer = document.getElementById('crafting-options');
craftingOptionsContainer.innerHTML = '';
let canCraftSomething = false;
Object.keys(recipes).forEach(recipeName => {
const recipe = recipes[recipeName];
const canCraft = recipe.ingredients.every(ingredient => playerState.inventory.includes(ingredient));
if (canCraft) {
canCraftSomething = true;
const button = document.createElement("button");
const productName = recipe.result || recipe.product || "Unbekanntes Produkt";
button.innerText = `Herstellen: ${productName}`;
button.classList.add("btn", "craft-btn");
button.addEventListener("click", () => {
craftItem(recipeName);
openCraftingMenu();
});
craftingOptionsContainer.appendChild(button);
}
});
if (!canCraftSomething) {
craftingOptionsContainer.innerHTML = '<p>Du hast nicht die richtigen Materialien, um etwas herzustellen.</p>';
}
craftingModal.classList.remove('hidden');
updateInventoryDisplay();
}
// ==================================================================
// STORY NODE HANDLING
// ==================================================================
function showStoryNode(storyNodeId) {
if (storyNodeId === "restart") {
localStorage.removeItem("saveGameData");
return window.location.reload();
}
const storyNode = storyNodes.find((node) => node.id === storyNodeId);
if (!storyNode) {
console.error(`Story-Knoten mit ID "${storyNodeId}" nicht gefunden!`);
return;
}
if (storyNode.soundEffect) {
const soundKey = storyNode.soundEffect;
const soundPath = sounds[soundKey];
if (soundPath) {
playSound(soundPath);
}
}
if (storyNode.setState) {
if (typeof storyNode.setState === "function") {
playerState = storyNode.setState(playerState);
}
}
const bodyStyle = document.body.style;
bodyStyle.backgroundImage = `url('${storyNode.image}')`;
bodyStyle.backgroundSize = "cover";
bodyStyle.backgroundPosition = "center";
bodyStyle.backgroundAttachment = "fixed";
bodyStyle.backgroundRepeat = "no-repeat";
const storyText = typeof storyNode.text === 'function' ? storyNode.text(playerState) : storyNode.text;
storyTextElement.innerText = storyText;
updateStatsDisplay();
updateInventoryDisplay();
while (optionsButtonsElement.firstChild) {
optionsButtonsElement.removeChild(optionsButtonsElement.firstChild);
}
const options = typeof storyNode.options === 'function' ? storyNode.options(playerState) : storyNode.options;
options.forEach((option) => {
if (option.requiredState && !option.requiredState(playerState)) {
return;
}
const button = document.createElement("button");
button.innerText = option.text;
button.classList.add("btn");
button.addEventListener("click", () => selectOption(option));
optionsButtonsElement.appendChild(button);
});
}
// ==================================================================
// Use Item Logic
// ==================================================================
function useItem(itemName) {
const item = items[itemName];
if (!item) return;
if (item.sound && sounds[item.sound]) {
playSound(sounds[item.sound]);
}
if (item.effect) {
playerState.health += item.effect.health || 0;
playerState.hunger += item.effect.hunger || 0;
playerState.thirst += item.effect.thirst || 0;
playerState.energy += item.effect.energy || 0;
playerState.health = Math.max(0, Math.min(100, playerState.health));
playerState.hunger = Math.max(0, Math.min(100, playerState.hunger));
playerState.thirst = Math.max(0, Math.min(100, playerState.thirst));
playerState.energy = Math.max(0, Math.min(100, playerState.energy));
}
if (item.consumable) {
const itemIndex = playerState.inventory.indexOf(itemName);
if (itemIndex > -1) {
playerState.inventory.splice(itemIndex, 1);
}
}
}
updateStatsDisplay();
updateInventoryDisplay();
// ==================================================================
// Savegame Handling & Loadgame Logic
// ==================================================================
function saveGame() {
const gameStateJson = JSON.stringify(playerState);
localStorage.setItem("saveGameData", gameStateJson);
console.log("Spiel gespeichert:", playerState);
}
function loadGame() {
const savedGameJson = localStorage.getItem("saveGameData");
if (savedGameJson) {
const savedGameState = JSON.parse(savedGameJson);
playerState = savedGameState;
console.log("Spiel geladen:", playerState);
} else {
console.log("Kein gespeichertes Spiel gefunden.");
}
updateInventoryDisplay();
}
// ==================================================================
// Option Selection Logic
// ==================================================================
function selectOption(option) {
const allButtons = optionsButtonsElement.querySelectorAll('.btn');
allButtons.forEach(button => {
button.disabled = true;
});
storyTextElement.classList.add('fading-out');
if (option.soundEffect) {
const soundKey = option.soundEffect;
const soundPath = sounds[soundKey];
if (soundPath) {
playSound(soundPath);
}
} else {
// playSound(sounds.uiClick);
}
setTimeout(() => {
storyTextElement.classList.remove('fading-out');
if (option.setState) {
if (typeof option.setState === "function") {
playerState = option.setState(playerState);
} else {
playerState.health += option.setState.health || 0;
playerState.hunger += option.setState.hunger || 0;
playerState.thirst += option.setState.thirst || 0;
playerState.health = Math.max(0, Math.min(100, playerState.health));
playerState.hunger = Math.max(0, Math.min(100, playerState.hunger));
playerState.thirst = Math.max(0, Math.min(100, playerState.thirst));
}
}
updateStatsDisplay();
updateInventoryDisplay();
const nextStoryNodeId = option.nextText;
if (nextStoryNodeId) {
playerState.currentSceneId = nextStoryNodeId;
}
saveGame();
if (nextStoryNodeId) {
showStoryNode(nextStoryNodeId);
}
}, 800);
}
function playSound(soundFile) {
new Audio(soundFile).play();
}