From 81a53e441e8c0fb4ef86f51f99847d0dcf468ba2 Mon Sep 17 00:00:00 2001 From: StrangeD0s <47105931+StrangeD0s@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:11:26 +0100 Subject: [PATCH] mvp --- app/components/apitest.tsx | 20 --- app/globals.css | 5 + app/layout.tsx | 13 +- app/page.tsx | 140 +++++++++++---------- components/ActionPanel.tsx | 230 ++++++++++++++++++++++++++++++++++ components/EventLog.tsx | 76 +++++++++++ components/GalaxyMap.tsx | 82 ++++++++++++ components/GameOverScreen.tsx | 41 ++++++ components/Hud.tsx | 76 +++++++++++ components/StartScreen.tsx | 75 +++++++++++ content/locales.ts | 130 +++++++++++++++++++ hooks/useGameEngine.ts | 191 ++++++++++++++++++++++++++++ public/fonts/Starjedi.ttf | Bin 0 -> 46512 bytes services/swapi.ts | 35 ++++++ types/swapi.ts | 41 ++++++ 15 files changed, 1069 insertions(+), 86 deletions(-) delete mode 100644 app/components/apitest.tsx create mode 100644 components/ActionPanel.tsx create mode 100644 components/EventLog.tsx create mode 100644 components/GalaxyMap.tsx create mode 100644 components/GameOverScreen.tsx create mode 100644 components/Hud.tsx create mode 100644 components/StartScreen.tsx create mode 100644 content/locales.ts create mode 100644 hooks/useGameEngine.ts create mode 100644 public/fonts/Starjedi.ttf create mode 100644 services/swapi.ts create mode 100644 types/swapi.ts diff --git a/app/components/apitest.tsx b/app/components/apitest.tsx deleted file mode 100644 index 18a54ef..0000000 --- a/app/components/apitest.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// app/Apitest.tsx - -export default async function Apitest() { - // 1. Fetch the data directly in the component - const res = await fetch('https://swapi.py4e.com/api/people/1/'); - const data = await res.json(); - - // 2. This console.log will appear in your TERMINAL, not the browser console - // because this is a Server Component! - console.log("Fetched Pilot:", data.name); - - return ( -
-

Smuggler Intel

-

Name: {data.name}

-

Height: {data.height}cm

-

Mass: {data.mass}kg

-
- ); -} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index a2dc41e..851f2d7 100644 --- a/app/globals.css +++ b/app/globals.css @@ -5,6 +5,11 @@ --foreground: #171717; } +@theme { + /* Hier registrierst du die Font für Tailwind v4 */ + --font-starjedi: var(--font-starjedi); +} + @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..590439b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import localFont from "next/font/local"; // Wichtig: localFont importieren import "./globals.css"; const geistSans = Geist({ @@ -12,9 +13,15 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); +// Konfiguration der lokalen Star Jedi Font +const starJedi = localFont({ + src: "../public/fonts/Starjedi.ttf", + variable: "--font-starjedi", +}); + export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Outer Rim Smuggler", + description: "A Star Wars inspired roguelike adventure", }; export default function RootLayout({ @@ -23,7 +30,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/app/page.tsx b/app/page.tsx index 5d884e6..fbbbed3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,73 +1,87 @@ -import Image from "next/image"; -import Apitest from "./components/apitest"; +"use client"; + +import { useGameEngine } from "@/hooks/useGameEngine"; +import HUD from "../components/Hud"; +import GalaxyMap from "../components/GalaxyMap"; +import EventLog from "../components/EventLog"; +import ActionPanel from "../components/ActionPanel"; +import StartScreen from "@/components/StartScreen"; +import GameOverScreen from "@/components/GameOverScreen"; +import { t } from "@/content/locales"; // Import der Locales export default function Home() { + const { + gameStarted, + startGame, + planets, + credits, + hp, + loading, + currentLocationId, + selectedPlanet, + setSelectedPlanet, + handleAttack, + executeJump, + isJumping, + activeEnemy, + executeEscape, + logs, + isGameOver, + restartGame, + } = useGameEngine(); + // 1. Ladezustand + if (loading) { + return ( +
+
{t.SYSTEM.LOADING}
+
+ ); + } + // Aktuellen Planeten finden für das HUD + const currentPlanet = planets.find((p) => p.id === currentLocationId); + + // 2. Startbildschirm + if (!gameStarted) { + return ; + } return ( -
+
+ -{/* Add your component here */} - - -
- Next.js logo + setSelectedPlanet(p)} + currentLocationId={currentLocationId} /> -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
+ + {/* ActionPanel erscheint bei Zielwahl oder Feindkontakt */} + {(selectedPlanet || activeEnemy) && ( + + )} + + + + + {/* Game Over Overlay */} + {isGameOver && ( + + )} + ); } diff --git a/components/ActionPanel.tsx b/components/ActionPanel.tsx new file mode 100644 index 0000000..f9978aa --- /dev/null +++ b/components/ActionPanel.tsx @@ -0,0 +1,230 @@ +"use client"; + +import React from "react"; +import { Planet, Starship } from "@/types/swapi"; +import { t } from "@/content/locales"; + +interface ActionPanelProps { + planet?: Planet | null; + enemy?: Starship | null; + onAction: () => void; + onEscape?: () => void; + isLoading?: boolean; +} + +const getPlanetTheme = (terrain: string = "", climate: string = "") => { + const terr = terrain.toLowerCase(); + const clim = climate.toLowerCase(); + + if (terr.includes("ocean") || terr.includes("water")) + return { + color: "text-blue-400", + border: "border-blue-500", + glow: "shadow-[0_0_20px_#3b82f6]", + label: t.ACTION_PANEL.MODES.AQUATIC, + }; + if (terr.includes("forest") || terr.includes("jungle")) + return { + color: "text-green-400", + border: "border-green-500", + glow: "shadow-[0_0_20px_#22c55e]", + label: t.ACTION_PANEL.MODES.VERDANT, + }; + if (terr.includes("desert") || clim.includes("arid")) + return { + color: "text-orange-400", + border: "border-orange-500", + glow: "shadow-[0_0_20px_#f97316]", + label: t.ACTION_PANEL.MODES.ARID, + }; + if ( + terr.includes("ice") || + terr.includes("glacier") || + clim.includes("frozen") + ) + return { + color: "text-cyan-200", + border: "border-cyan-300", + glow: "shadow-[0_0_20px_#a5f3fc]", + label: t.ACTION_PANEL.MODES.CRYO, + }; + if (terr.includes("city") || terr.includes("urban")) + return { + color: "text-purple-400", + border: "border-purple-500", + glow: "shadow-[0_0_20px_#a855f7]", + label: t.ACTION_PANEL.MODES.ECUMENOPOLIS, + }; + + return { + color: "text-yellow-500", + border: "border-yellow-500", + glow: "shadow-[0_0_20px_#eab308]", + label: t.ACTION_PANEL.MODES.STANDARD, + }; +}; + +export default function ActionPanel({ + planet, + enemy, + onAction, + onEscape, + isLoading, +}: ActionPanelProps) { + const isCombat = !!enemy; + const theme = + !isCombat && planet + ? getPlanetTheme(planet.terrain, planet.climate) + : { + color: "text-red-500", + border: "border-red-600", + glow: "shadow-[0_0_30px_#dc2626]", + label: t.ACTION_PANEL.MODES.HOSTILE, + }; + + const displayName = isCombat ? enemy.name : planet?.name; + const displaySubtitle = isCombat + ? `${t.ACTION_PANEL.LABELS.CLASS}: ${enemy.starship_class} // ${t.ACTION_PANEL.LABELS.CREW}: ${enemy.crew}` + : `${t.ACTION_PANEL.LABELS.SECTOR}: ${planet?.climate} // ${planet?.terrain}`; + + return ( +
+
+ +
+
+
+ {isCombat ? ( +
+ ! +
+ ) : ( + <> +
+
+
+ + )} +
+ +
+ {theme.label} +
+
+ +
+
+

+ {displayName} +

+

+ {displaySubtitle} +

+
+ +
+
+ + {isCombat + ? t.ACTION_PANEL.LABELS.MANUFACTURER + : t.ACTION_PANEL.LABELS.POPULATION} + +

+ {isCombat + ? enemy.manufacturer + : planet?.population.toLocaleString()} +

+
+
+ + {isCombat + ? t.ACTION_PANEL.LABELS.COST + : t.ACTION_PANEL.LABELS.GRAVITY} + +

+ {isCombat ? `${enemy.cost_in_credits} Cr` : planet?.gravity} +

+
+
+ +

+ {isCombat + ? t.ACTION_PANEL.STATUS.COMBAT_WARNING + : t.ACTION_PANEL.STATUS.PLANET_SCAN} +

+
+ +
+ + + {isCombat && ( + + )} +
+
+
+ ); +} diff --git a/components/EventLog.tsx b/components/EventLog.tsx new file mode 100644 index 0000000..3fb91b0 --- /dev/null +++ b/components/EventLog.tsx @@ -0,0 +1,76 @@ +"use client"; + +import React, { useEffect, useRef } from "react"; +import { t } from "@/content/locales"; // Import der Locales + +export interface LogEntry { + id: string; + message: string; + type: "info" | "success" | "danger" | "warning"; + timestamp: string; +} + +interface EventLogProps { + logs: LogEntry[]; +} + +export default function EventLog({ logs }: EventLogProps) { + const scrollRef = useRef(null); + + // Auto-Scroll nach unten bei neuen Einträgen + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [logs]); + + const getTypeStyles = (type: LogEntry["type"]) => { + switch (type) { + case "success": + return "text-green-400"; + case "danger": + return "text-red-500 font-bold animate-pulse"; + case "warning": + return "text-orange-400"; + default: + return "text-yellow-500/80"; + } + }; + + return ( +
+ {/* Header */} +
+ + {t.EVENT_LOG.TITLE} + +
+ + {/* Scrollable Content */} +
+ {logs.length === 0 && ( +

+ {t.EVENT_LOG.EMPTY_STATE} +

+ )} + {logs.map((log) => ( +
+ + {log.timestamp} + + + {log.type === "danger" && "⚠ "} + {log.message} + +
+ ))} +
+
+ ); +} diff --git a/components/GalaxyMap.tsx b/components/GalaxyMap.tsx new file mode 100644 index 0000000..c7e3228 --- /dev/null +++ b/components/GalaxyMap.tsx @@ -0,0 +1,82 @@ +"use client"; + +import React from "react"; +import { Planet } from "@/types/swapi"; +import { t } from "@/content/locales"; // Import der Locales + +interface GalaxyMapProps { + planets: Planet[]; + onSelectLocation: (planet: Planet) => void; + currentLocationId?: string; +} + +export default function GalaxyMap({ + planets, + onSelectLocation, + currentLocationId, +}: GalaxyMapProps) { + return ( +
+ {/* Hintergrund-Raster (Grid) */} +
+ + {/* Die Planeten (Nodes) aus der API */} + {planets.map((planet) => { + const isCurrent = planet.id === currentLocationId; + + return ( + + ); + })} + + {/* Scan-Linie Effekt */} +
+ + {/* Info Overlay */} +
+ {t.GALAXY_MAP.SCAN_STATUS}
+ {t.GALAXY_MAP.OBJECTS_DETECTED(planets.length)}
+ {t.GALAXY_MAP.HOLONET_STATUS} +
+
+ ); +} diff --git a/components/GameOverScreen.tsx b/components/GameOverScreen.tsx new file mode 100644 index 0000000..edfc5eb --- /dev/null +++ b/components/GameOverScreen.tsx @@ -0,0 +1,41 @@ +"use client"; + +import React from "react"; +import { t } from "@/content/locales"; + +interface GameOverScreenProps { + credits: number; + onRestart: () => void; +} + +export default function GameOverScreen({ + credits, + onRestart, +}: GameOverScreenProps) { + return ( +
+
+

+ {t.GAME_OVER.TITLE} +

+

{t.GAME_OVER.SUBTITLE}

+ +
+

+ {t.GAME_OVER.SCORE_LABEL} +

+

+ {credits.toLocaleString()} Credits +

+
+ + +
+
+ ); +} diff --git a/components/Hud.tsx b/components/Hud.tsx new file mode 100644 index 0000000..c282a2b --- /dev/null +++ b/components/Hud.tsx @@ -0,0 +1,76 @@ +"use client"; + +import React from "react"; +import { t } from "@/content/locales"; + +interface HUDProps { + credits: number; + hp: number; + maxHp: number; + location: string; + targetCredits: number; +} + +export default function HUD({ + credits, + hp, + maxHp, + location, + targetCredits, +}: HUDProps) { + const progress = Math.min((credits / targetCredits) * 100, 100); + + return ( +
+
+ {/* LORE & LOCATION */} +
+ + {t.HUD.SECTOR_LABEL} + + + {location.toUpperCase()} + +
+ + {/* SHIP STATUS (HEALTH) */} +
+
+ {t.HUD.HULL_LABEL} + + {hp} / {maxHp} + +
+
+
+
+
+ + {/* ECONOMY (CREDITS) */} +
+ + {t.HUD.GOAL_LABEL} + +
+ + {credits.toLocaleString()} + + + / {targetCredits.toLocaleString()} + +
+ {/* Progress Bar for the Goal */} +
+
+
+
+
+
+ ); +} diff --git a/components/StartScreen.tsx b/components/StartScreen.tsx new file mode 100644 index 0000000..6e45e6a --- /dev/null +++ b/components/StartScreen.tsx @@ -0,0 +1,75 @@ +"use client"; + +import React from "react"; +import { t } from "@/content/locales"; + +interface StartScreenProps { + onStart: () => void; +} + +export default function StartScreen({ onStart }: StartScreenProps) { + return ( +
+ {/* Hintergrund-Deko */} +
+ +
+
+

+ {t.UI.GAME_TITLE} +

+

+ {t.START_SCREEN.SUBTITLE} +

+
+ +
+

+ {t.START_SCREEN.INTRO_TEXT} + + {" "} + {t.START_SCREEN.MISSION_HIGHLIGHT} + +

+
    +
  • + •{" "} + + {t.START_SCREEN.FEATURE_NAV_TITLE} + {" "} + {t.START_SCREEN.FEATURE_NAV_DESC} +
  • +
  • + •{" "} + + {t.START_SCREEN.FEATURE_RISK_TITLE} + {" "} + {t.START_SCREEN.FEATURE_RISK_DESC} +
  • +
  • + •{" "} + + {t.START_SCREEN.FEATURE_COMBAT_TITLE} + {" "} + {t.START_SCREEN.FEATURE_COMBAT_DESC} +
  • +
+
+ +
+ + +

+ {t.START_SCREEN.FOOTER_ENGINE} +

+
+
+
+ ); +} diff --git a/content/locales.ts b/content/locales.ts new file mode 100644 index 0000000..ab56ff3 --- /dev/null +++ b/content/locales.ts @@ -0,0 +1,130 @@ +// constants/locales.ts + +export const locales = { + de: { + UI: { + GAME_TITLE: "Smuggler's Run", + SUBTITLE: "Fast Ships, faster credits!", + START_BUTTON: "Initialize Systems", + RESTART_BUTTON: "Klon-Zylinder aktivieren", + JUMP_BUTTON: "Initiate Jump", + ATTACK_BUTTON: "Feuer frei", + ESCAPE_BUTTON: "Fluchtversuch", + }, + SYSTEM: { + LOADING: "INITIALIZING HOLONET LINK...", + DEEP_SPACE: "Deep Space", + }, + START_SCREEN: { + SUBTITLE: "Fast Ships, faster credits!", + INTRO_TEXT: + "Smuggler's Run is a minimalist space adventure game, where you, the player, are an unlicensed space trader, flying cargo from planet to planet while trying not to be blown to pieces by bounty hunters, imperial forces or rebel do-gooders. Your mission is simple:", + MISSION_HIGHLIGHT: "Survive and earn credits", + FEATURE_NAV_TITLE: "Navigation:", + FEATURE_NAV_DESC: "Jump between sectors to deliver cargo.", + FEATURE_RISK_TITLE: "Risk:", + FEATURE_RISK_DESC: + "Hyperspace is treacherous. Imperial patrols are everywhere.", + FEATURE_COMBAT_TITLE: "Combat:", + FEATURE_COMBAT_DESC: "Use your 2D6 cannons to survive interceptions.", + FOOTER_ENGINE: "Star Wars API v1.0 // Engine: Tunnel Goons", + }, + ACTION_PANEL: { + MODES: { + HOSTILE: "Hostile", + AQUATIC: "Aquatic", + VERDANT: "Verdant", + ARID: "Arid", + CRYO: "Cryo", + ECUMENOPOLIS: "Ecumenopolis", + STANDARD: "Standard", + }, + LABELS: { + CLASS: "Class", + CREW: "Crew", + SECTOR: "Sector", + MANUFACTURER: "Manufacturer", + POPULATION: "Population", + COST: "Cost", + GRAVITY: "Gravity", + }, + STATUS: { + COMBAT_WARNING: + "WARNING: Hostile contact confirmed. Weapons emitters charging.", + PLANET_SCAN: + "Receiving encrypted signals from sector. Landing clearance pending...", + ENGAGING: "Engaging...", + }, + BUTTONS: { + FIRE: "Open Fire", + JUMP: "Initiate Jump", + ESCAPE: "Evasive Maneuvers", + }, + }, + GAME_OVER: { + TITLE: "Wasted in Space", + SUBTITLE: "Your ship has been reduced to stardust.", + SCORE_LABEL: "Final Profit", + RESTART_BUTTON: "Activate Clone Cylinder", + }, + EVENT_LOG: { + TITLE: "Event Log", + EMPTY_STATE: "Waiting for signals...", + }, + HUD: { + SECTOR_LABEL: "Current Sector", + HULL_LABEL: "Hull Integrity", + GOAL_LABEL: "Credits to Freedom", // Oder "Credits to Falcon" + }, + GALAXY_MAP: { + SCAN_STATUS: "Sector Scan: Active", + OBJECTS_DETECTED: (count: number) => `Objects Detected: ${count}`, + HOLONET_STATUS: "HoloNet Status: Connected", + }, + ENGINE: { + BOOT: { + SUCCESS: "HoloNet connection stable. Nav-computer ready.", + ERROR: "Critical error loading galaxy data!", + WELCOME: "Engines on standby. Nav-computer online. Welcome, Commander.", + RESTART: "Systems rebooting... A new journey begins.", + }, + JUMP: { + INIT: (target: string) => `Hyperspace jump to ${target} initiated...`, + INTERCEPTION: (ship: string) => + `INTERCEPTION! A ${ship} has pulled us out of hyperspace!`, + SUCCESS: (target: string, reward: number) => + `Safe exit in the ${target} system. +${reward} Credits earned.`, + }, + ESCAPE: { + INIT: "Emergency maneuvers initiated!", + FAIL: (ship: string, dmg: number) => + `Escape failed! The ${ship} hits us hard. -${dmg} HP`, + SUCCESS: "Jump successful! We've shaken off the pursuer.", + }, + COMBAT: { + INIT: (ship: string) => + `Weapon systems synchronized. Opening fire on ${ship}!`, + VICTORY: (ship: string, loot: number) => + `Direct hit! The ${ship} has been obliterated. +${loot} Credits salvaged.`, + COUNTER: (ship: string, dmg: number) => + `Shields holding! The ${ship} counters. -${dmg} HP`, + }, + SYSTEMS: { + CRITICAL_FAILURE: + "CRITICAL STRUCTURAL DAMAGE! The ship is breaking apart...", + }, + DICE: { + ROLLING_1W6: "[Rolling 1D6...]", + ROLLING_2W6: "[Rolling 2D6...]", + RESULT_1W6: (res: number) => `[Roll: ${res}]`, + RESULT_2W6: (d1: number, d2: number, total: number) => + `[Roll: ${d1} + ${d2} = ${total}]`, + }, + }, + }, + // Später einfach erweiterbar: + // en: { ... } +}; + +// Aktuelle Sprache festlegen (könnte später aus einem State kommen) +export const t = locales.de; diff --git a/hooks/useGameEngine.ts b/hooks/useGameEngine.ts new file mode 100644 index 0000000..21a45f7 --- /dev/null +++ b/hooks/useGameEngine.ts @@ -0,0 +1,191 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { Planet, Starship } from "@/types/swapi"; +import { fetchPlanets, fetchStarships } from "@/services/swapi"; +import { LogEntry } from "@/components/EventLog"; +import { t } from "@/content/locales"; + +export function useGameEngine() { + const [planets, setPlanets] = useState([]); + const [starships, setStarships] = useState([]); + const [loading, setLoading] = useState(true); + + const [gameStarted, setGameStarted] = useState(false); + const [isGameOver, setIsGameOver] = useState(false); + const [credits, setCredits] = useState(100); + const [hp, setHp] = useState(10); + const [currentLocationId, setCurrentLocationId] = useState(""); + const [selectedPlanet, setSelectedPlanet] = useState(null); + const [isJumping, setIsJumping] = useState(false); + const [activeEnemy, setActiveEnemy] = useState(null); + const [logs, setLogs] = useState([]); + + const addLog = useCallback( + (message: string, type: LogEntry["type"] = "info") => { + const newLog: LogEntry = { + id: Math.random().toString(36).substring(2, 9), + message, + type, + timestamp: new Date().toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }), + }; + setLogs((prev) => [...prev, newLog]); + }, + [], + ); + + useEffect(() => { + async function initGame() { + try { + const [planets, ships] = await Promise.all([ + fetchPlanets(), + fetchStarships(), + ]); + setPlanets(planets); + setStarships(ships); + if (planets.length > 0) setCurrentLocationId(planets[0].id); + addLog(t.ENGINE.BOOT.SUCCESS, "success"); + } catch (error) { + addLog(t.ENGINE.BOOT.ERROR, "danger"); + } finally { + setLoading(false); + } + } + initGame(); + }, [addLog]); + + const startGame = () => { + setGameStarted(true); + addLog(t.ENGINE.BOOT.WELCOME, "info"); + }; + + const executeJump = async () => { + if (!selectedPlanet || selectedPlanet.id === currentLocationId) return; + + setIsJumping(true); + addLog( + `${t.ENGINE.JUMP.INIT(selectedPlanet.name)} ${t.ENGINE.DICE.ROLLING_1W6}`, + "info", + ); + + setTimeout(() => { + const roll = Math.floor(Math.random() * 6) + 1; + const rollInfo = t.ENGINE.DICE.RESULT_1W6(roll); + + if (roll >= 5) { + const randomShip = + starships[Math.floor(Math.random() * starships.length)]; + setActiveEnemy(randomShip); + addLog( + `${rollInfo} ${t.ENGINE.JUMP.INTERCEPTION(randomShip.name)}`, + "danger", + ); + } else { + const reward = 150; + setCredits((prev) => prev + reward); + setCurrentLocationId(selectedPlanet.id); + addLog( + `${rollInfo} ${t.ENGINE.JUMP.SUCCESS(selectedPlanet.name, reward)}`, + "success", + ); + } + setIsJumping(false); + setSelectedPlanet(null); + }, 1200); + }; + + const executeEscape = () => { + if (!activeEnemy) return; + addLog(`${t.ENGINE.ESCAPE.INIT} ${t.ENGINE.DICE.ROLLING_1W6}`, "info"); + + setTimeout(() => { + const roll = Math.floor(Math.random() * 6) + 1; + const rollInfo = t.ENGINE.DICE.RESULT_1W6(roll); + + if (roll <= 2) { + const damage = 1; + setHp((prev) => Math.max(0, prev - damage)); + addLog( + `${rollInfo} ${t.ENGINE.ESCAPE.FAIL(activeEnemy.name, damage)}`, + "danger", + ); + } else { + setActiveEnemy(null); + addLog(`${rollInfo} ${t.ENGINE.ESCAPE.SUCCESS}`, "success"); + } + }, 800); + }; + + const handleAttack = () => { + if (!activeEnemy) return; + addLog( + `${t.ENGINE.COMBAT.INIT(activeEnemy.name)} ${t.ENGINE.DICE.ROLLING_2W6}`, + "info", + ); + + setTimeout(() => { + const d1 = Math.floor(Math.random() * 6) + 1; + const d2 = Math.floor(Math.random() * 6) + 1; + const total = d1 + d2; + const rollInfo = t.ENGINE.DICE.RESULT_2W6(d1, d2, total); + + if (total >= activeEnemy.ds) { + const loot = 300; + setCredits((prev) => prev + loot); + setActiveEnemy(null); + addLog( + `${rollInfo} ${t.ENGINE.COMBAT.VICTORY(activeEnemy.name, loot)}`, + "success", + ); + } else { + const diff = activeEnemy.ds - total; + setHp((prev) => Math.max(0, prev - diff)); + addLog( + `${rollInfo} ${t.ENGINE.COMBAT.COUNTER(activeEnemy.name, diff)}`, + "danger", + ); + } + }, 1000); + }; + + useEffect(() => { + if (hp <= 0 && !isGameOver) { + setIsGameOver(true); + addLog(t.ENGINE.SYSTEMS.CRITICAL_FAILURE, "danger"); + } + }, [hp, isGameOver, addLog]); + + const restartGame = () => { + setHp(10); + setCredits(100); + setIsGameOver(false); + setActiveEnemy(null); + setSelectedPlanet(null); + setLogs([]); + addLog(t.ENGINE.BOOT.RESTART, "success"); + }; + + return { + planets, + starships, + loading, + gameStarted, + startGame, + credits, + hp, + currentLocationId, + selectedPlanet, + setSelectedPlanet, + executeEscape, + executeJump, + handleAttack, + isJumping, + activeEnemy, + logs, + isGameOver, + restartGame, + }; +} diff --git a/public/fonts/Starjedi.ttf b/public/fonts/Starjedi.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2ac5bb1bf042d673d07a9233fad599e60c782b87 GIT binary patch literal 46512 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 { + const res = await fetch(`${BASE_URL}/planets/`); + if (!res.ok) throw new Error("Galaxy data unavailable"); + const data = await res.json(); + + return data.results.map( + (p: SWAPIPlanet): Planet => ({ + ...p, + id: p.url, + // Add random x/y coordinates for galaxy map + x: Math.floor(Math.random() * 80) + 10, + y: Math.floor(Math.random() * 70) + 15, + }), + ); +} + +export async function fetchStarships(): Promise { + const res = await fetch(`${BASE_URL}/starships/`); + if (!res.ok) throw new Error("Imperial records unavailable"); + const data = await res.json(); + + return data.results.map((s: SWAPIStarship): Starship => { + // Add difficulty score based on ship cost + const cost = parseInt(s.cost_in_credits); + let ds = 8; + if (isNaN(cost) || cost > 1000000) ds = 12; + else if (cost > 100000) ds = 10; + + return { ...s, id: s.url, ds }; + }); +} diff --git a/types/swapi.ts b/types/swapi.ts new file mode 100644 index 0000000..4d2a2c9 --- /dev/null +++ b/types/swapi.ts @@ -0,0 +1,41 @@ +// types/swapi.ts + +// Die korrigierten Rohdaten von der SWAPI +export interface SWAPIPlanet { // <-- Jetzt mit korrektem 'n' + name: string; + rotation_period: string; + orbital_period: string; + diameter: string; + climate: string; + gravity: string; + terrain: string; + surface_water: string; + population: string; + url: string; +} + +export interface SWAPIStarship { + name: string; + model: string; + manufacturer: string; + cost_in_credits: string; + length: string; + max_atmosphering_speed: string; + crew: string; + passengers: string; + cargo_capacity: string; + starship_class: string; + url: string; +} + +// Unsere erweiterten Typen für das Spiel +export interface Planet extends SWAPIPlanet { + id: string; + x: number; + y: number; +} + +export interface Starship extends SWAPIStarship { + id: string; + ds: number; // Difficulty Score für Tunnel Goons +} \ No newline at end of file