From fcd8cadf43d17d4b0be5968dc542598c8d02ac11 Mon Sep 17 00:00:00 2001 From: StrangeD0s <47105931+StrangeD0s@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:06:59 +0100 Subject: [PATCH] update and polish --- README.md | 2 + app/globals.css | 2 +- app/layout.tsx | 15 +- app/page.tsx | 80 +++++--- components/ActionPanel.tsx | 299 ++++++++++++++---------------- components/EventLog.tsx | 3 - components/GalaxyMap.tsx | 34 ++-- components/GameOverScreen.tsx | 35 +++- components/Hud.tsx | 4 +- components/PowerDistributor.tsx | 136 ++++++++++++++ components/StartScreen.tsx | 86 ++++++--- components/VictoryScreen.tsx | 113 +++++++++++ content/locales.ts | 68 +++++-- hooks/useGameEngine.ts | 219 ++++++++++++++++------ public/fonts/Aurebesh-English.ttf | Bin 0 -> 9172 bytes services/swapi.ts | 99 +++++++--- types/swapi.ts | 11 +- utils/getPlanetTheme.ts | 63 +++++++ 18 files changed, 915 insertions(+), 354 deletions(-) create mode 100644 components/PowerDistributor.tsx create mode 100644 components/VictoryScreen.tsx create mode 100644 public/fonts/Aurebesh-English.ttf create mode 100644 utils/getPlanetTheme.ts diff --git a/README.md b/README.md index 43d8496..7cb6773 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ 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. +This game is not meant to be played on a small mobile screen. + ## Getting Started First, run the development server: diff --git a/app/globals.css b/app/globals.css index 851f2d7..44f0171 100644 --- a/app/globals.css +++ b/app/globals.css @@ -6,8 +6,8 @@ } @theme { - /* Hier registrierst du die Font für Tailwind v4 */ --font-starjedi: var(--font-starjedi); + --font-aurebesh: var(--font-aurebesh) } @theme inline { diff --git a/app/layout.tsx b/app/layout.tsx index 590439b..52d75b5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +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 localFont from "next/font/local"; import "./globals.css"; const geistSans = Geist({ @@ -13,14 +13,18 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); -// Konfiguration der lokalen Star Jedi Font const starJedi = localFont({ src: "../public/fonts/Starjedi.ttf", variable: "--font-starjedi", }); +const aurebesh = localFont({ + src: "../public/fonts/Aurebesh-English.ttf", + variable: "--font-aurebesh", +}); + export const metadata: Metadata = { - title: "Outer Rim Smuggler", + title: "Smuggler's Run", description: "A Star Wars inspired roguelike adventure", }; @@ -30,7 +34,10 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/app/page.tsx b/app/page.tsx index fbbbed3..888b346 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -7,7 +7,9 @@ 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 +import VictoryScreen from "@/components/VictoryScreen"; +import PowerDistributor from "@/components/PowerDistributor"; +import { t } from "@/content/locales"; export default function Home() { const { @@ -18,19 +20,25 @@ export default function Home() { hp, loading, currentLocationId, + currentPlanet, selectedPlanet, setSelectedPlanet, handleAttack, executeJump, + handleRepair, isJumping, + power, + updatePower, activeEnemy, executeEscape, logs, isGameOver, restartGame, + isVictory, + isRerouting, + WIN_THRESHOLD, } = useGameEngine(); - // 1. Ladezustand if (loading) { return (
@@ -39,46 +47,60 @@ export default function Home() { ); } - // Aktuellen Planeten finden für das HUD - const currentPlanet = planets.find((p) => p.id === currentLocationId); + if (!gameStarted) return ; - // 2. Startbildschirm - if (!gameStarted) { - return ; - } + const activeDisplayPlanet = selectedPlanet || currentPlanet; + const isViewingCurrentLocation = + !!currentPlanet && activeDisplayPlanet?.id === currentPlanet.id; return ( -
+
-
- setSelectedPlanet(p)} - currentLocationId={currentLocationId} - /> - - {/* ActionPanel erscheint bei Zielwahl oder Feindkontakt */} - {(selectedPlanet || activeEnemy) && ( - +
+ - )} +
+
+ +
- +
+ +
+ +
+ +
+ + {isVictory && } - {/* Game Over Overlay */} {isGameOver && ( )} diff --git a/components/ActionPanel.tsx b/components/ActionPanel.tsx index f9978aa..d8e8943 100644 --- a/components/ActionPanel.tsx +++ b/components/ActionPanel.tsx @@ -2,223 +2,202 @@ import React from "react"; import { Planet, Starship } from "@/types/swapi"; -import { t } from "@/content/locales"; +import { t, LocaleType } from "@/content/locales"; +import { getPlanetTheme } from "@/utils/getPlanetTheme"; interface ActionPanelProps { planet?: Planet | null; enemy?: Starship | null; onAction: () => void; onEscape?: () => void; + onRepair?: () => void; isLoading?: boolean; + isCurrentLocation?: boolean; + currentHp: number; } -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, + onRepair, isLoading, + isCurrentLocation, + currentHp, }: 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}`; + const planetVisual = getPlanetTheme( + planet?.terrain, + planet?.climate, + t as LocaleType, + ); + + const uiTheme = isCombat + ? { + color: "text-red-500", + border: "border-red-600", + glow: "shadow-[0_0_30px_#dc2626]", + label: t.ACTION_PANEL.MODES.HOSTILE, + borderAlpha: "border-red-900/30", + labelAlpha: "text-red-500/50", + } + : { + color: "text-yellow-500", + border: "border-yellow-300", + glow: "shadow-[0_0_20px_rgba(234,179,8,0.2)]", + label: "NEUTRAL SYSTEM", + borderAlpha: "border-yellow-500/10", + labelAlpha: "text-yellow-500/50", + }; + + const stats = isCombat + ? [ + { + label: t.ACTION_PANEL.LABELS.MANUFACTURER, + value: enemy.manufacturer, + }, + { + label: t.ACTION_PANEL.LABELS.COST, + value: `${enemy.cost_in_credits} Cr`, + }, + ] + : [ + { + label: t.ACTION_PANEL.LABELS.POPULATION, + value: planet?.population.toLocaleString(), + }, + { label: t.ACTION_PANEL.LABELS.GRAVITY, value: planet?.gravity }, + ]; return (
+ {/* VISUAL UNIT */}
{isCombat ? ( -
+
!
) : ( - <> -
-
-
- +
)}
-
- {theme.label} + {isCurrentLocation && !isCombat + ? "CURRENT LOCATION" + : uiTheme.label}
-
+ {/* INFO SECTION */} +
-

- {displayName} -

+
+

+ {isCombat ? enemy.name : planet?.name} +

+ {isCombat && ( +
+ + DS {enemy.ds} + +
+ {[...Array(enemy.hp)].map((_, i) => ( +
+ ))} +
+
+ )} +

- {displaySubtitle} + {isCombat + ? `${t.ACTION_PANEL.LABELS.CLASS}: ${enemy.starship_class}` + : `${t.ACTION_PANEL.LABELS.SECTOR}: ${planet?.terrain}`}

-
- - {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} -

-
+ {stats.map((stat, idx) => ( +
+ {stat.label} +

{stat.value}

+
+ ))}
- -

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

+ {/* BUTTONS */}
- + {isCurrentLocation && !isCombat ? ( + + ) : ( + + )} {isCombat && ( diff --git a/components/EventLog.tsx b/components/EventLog.tsx index 3fb91b0..abe135c 100644 --- a/components/EventLog.tsx +++ b/components/EventLog.tsx @@ -17,7 +17,6 @@ interface EventLogProps { 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; @@ -39,14 +38,12 @@ export default function EventLog({ logs }: EventLogProps) { return (
- {/* Header */}
{t.EVENT_LOG.TITLE}
- {/* Scrollable Content */}
void; currentLocationId?: string; + selectedPlanetId?: string; } export default function GalaxyMap({ planets, onSelectLocation, currentLocationId, + selectedPlanetId, }: GalaxyMapProps) { return ( -
- {/* Hintergrund-Raster (Grid) */} +
+ {/* Background-Grid */}
- {/* Die Planeten (Nodes) aus der API */} {planets.map((planet) => { const isCurrent = planet.id === currentLocationId; + const isSelected = planet.id === selectedPlanetId; return ( +
+ +
+ {systems.map((sys) => ( +
+ + + {/* Visual Bar */} +
+ {[...Array(6)].map((_, i) => ( +
+ ))} +
+ + + {sys.label} + + + LVL {power[sys.key]} + +
+ ))} +
+ + {/* Overlay */} + {isRerouting && ( +
+
+ {t.ENGINE.SYSTEMS.REROUTING} +
+
+ )} +
+ ); +} diff --git a/components/StartScreen.tsx b/components/StartScreen.tsx index 6e45e6a..4007f8c 100644 --- a/components/StartScreen.tsx +++ b/components/StartScreen.tsx @@ -9,49 +9,76 @@ interface StartScreenProps { 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_NAV_TITLE}{" "} + + {t.START_SCREEN.FEATURE_NAV_DESC} +
    • - •{" "} - - {t.START_SCREEN.FEATURE_RISK_TITLE} - {" "} - {t.START_SCREEN.FEATURE_RISK_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.FEATURE_COMBAT_TITLE}{" "} + + {t.START_SCREEN.FEATURE_COMBAT_DESC} +
@@ -59,13 +86,12 @@ export default function StartScreen({ onStart }: StartScreenProps) {
-

+

{t.START_SCREEN.FOOTER_ENGINE}

diff --git a/components/VictoryScreen.tsx b/components/VictoryScreen.tsx new file mode 100644 index 0000000..2694adf --- /dev/null +++ b/components/VictoryScreen.tsx @@ -0,0 +1,113 @@ +"use client"; + +import React from "react"; +import { t } from "@/content/locales"; + +interface VictoryScreenProps { + credits: number; + onRestart: () => void; +} + +export default function VictoryScreen({ + credits, + onRestart, +}: VictoryScreenProps) { + const creditsBlue = "#4bd5ee"; + + return ( +
+
+
+
+
+ +
+ +
+
+

+ {t.VICTORY.TITLE} +

+

+ {t.VICTORY.SUBTITLE} +

+
+ +
+
+

+ {t.VICTORY.MESSAGE} +

+

+ {credits.toLocaleString()} CR +

+
+ +
+ The galaxy will remember your name. +
+
+ +
+ +
+
+
+ ); +} diff --git a/content/locales.ts b/content/locales.ts index ab56ff3..a6fc41a 100644 --- a/content/locales.ts +++ b/content/locales.ts @@ -1,24 +1,24 @@ // constants/locales.ts export const locales = { - de: { + en: { UI: { GAME_TITLE: "Smuggler's Run", SUBTITLE: "Fast Ships, faster credits!", START_BUTTON: "Initialize Systems", - RESTART_BUTTON: "Klon-Zylinder aktivieren", + RESTART_BUTTON: "Activate Clone Cylinder", JUMP_BUTTON: "Initiate Jump", - ATTACK_BUTTON: "Feuer frei", - ESCAPE_BUTTON: "Fluchtversuch", + ATTACK_BUTTON: "Open Fire", + ESCAPE_BUTTON: "Evasive Maneuvers", }, SYSTEM: { LOADING: "INITIALIZING HOLONET LINK...", DEEP_SPACE: "Deep Space", }, START_SCREEN: { - SUBTITLE: "Fast Ships, faster credits!", + SUBTITLE: "Episode I: 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:", + "Smuggler's Run is a minimalist space adventure game where you 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.", @@ -59,6 +59,8 @@ export const locales = { FIRE: "Open Fire", JUMP: "Initiate Jump", ESCAPE: "Evasive Maneuvers", + REPAIR: (cost: number) => `Repair ship for (${cost} Cr)`, + HULL_INTACT: "Hull integrity stable", }, }, GAME_OVER: { @@ -67,6 +69,13 @@ export const locales = { SCORE_LABEL: "Final Profit", RESTART_BUTTON: "Activate Clone Cylinder", }, + VICTORY: { + TITLE: "Victory", + SUBTITLE: "Wealth Threshold Exceeded", + MESSAGE: + "You have amassed enough credits to retire in the Outer Rim. The Empire's reach can no longer find you.", + BUTTON: "NEW ADVENTURE", + }, EVENT_LOG: { TITLE: "Event Log", EMPTY_STATE: "Waiting for signals...", @@ -74,7 +83,7 @@ export const locales = { HUD: { SECTOR_LABEL: "Current Sector", HULL_LABEL: "Hull Integrity", - GOAL_LABEL: "Credits to Freedom", // Oder "Credits to Falcon" + GOAL_LABEL: "Credits to Freedom", }, GALAXY_MAP: { SCAN_STATUS: "Sector Scan: Active", @@ -102,29 +111,54 @@ export const locales = { SUCCESS: "Jump successful! We've shaken off the pursuer.", }, COMBAT: { - INIT: (ship: string) => - `Weapon systems synchronized. Opening fire on ${ship}!`, + INIT: (ship: string) => `Weapon systems synchronized. Target: ${ship}!`, VICTORY: (ship: string, loot: number) => - `Direct hit! The ${ship} has been obliterated. +${loot} Credits salvaged.`, + `Target neutralized! The ${ship} has been destroyed. +${loot} Credits salvaged.`, COUNTER: (ship: string, dmg: number) => - `Shields holding! The ${ship} counters. -${dmg} HP`, + `The ${ship} returns fire! -${dmg} HP`, + HIT: (ship: string, hp: number) => + `Direct hit! ${ship} integrity at ${hp} HP.`, + EVASION: (roll: number, bonus: number) => + `[Evasion: ${roll}+${bonus}] High-speed maneuver successful! Enemy fire avoided.`, + }, + REPAIR: { + SUCCESS: (planet: string, cost: number) => + `Repairs complete at ${planet}. -${cost} Credits.`, + INSUFFICIENT_FUNDS: "Insufficient credits for repairs!", }, SYSTEMS: { CRITICAL_FAILURE: "CRITICAL STRUCTURAL DAMAGE! The ship is breaking apart...", + REROUTING: "Rerouting power systems...", + REROUTE_SUCCESS: "Power configuration updated.", + REROUTE_FAIL: (ship: string, dmg: number) => + `Power lag! ${ship} exploits the window. -${dmg} HP`, + SECTOR_UPDATE: "Nav-computer recalibrated. New sector discovered.", }, 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}]`, + RESULT_TOTAL: (total: number, bonus: number) => + `[Total: ${total} (Bonus: +${bonus})]`, }, }, + SYSTEMS: { + POWER_MANAGEMENT: "Power Management", + SUB_LABEL: "System Distribution Active", + RESET: "Reset", + BOOST: "Boost", + ENGINES: "Engines", + LASERS: "Lasers", + SHIELDS: "Shields", + TOTAL_POWER: "Total Power", + NO_ENERGY: "Energy Depleted", + REROUTING: "Rerouting Energy...", + }, }, - // Später einfach erweiterbar: - // en: { ... } }; -// Aktuelle Sprache festlegen (könnte später aus einem State kommen) -export const t = locales.de; +export type LocaleType = typeof locales.en; + +// Defaulting to English +export const t = locales.en; diff --git a/hooks/useGameEngine.ts b/hooks/useGameEngine.ts index 21a45f7..e763043 100644 --- a/hooks/useGameEngine.ts +++ b/hooks/useGameEngine.ts @@ -1,26 +1,53 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useMemo } from "react"; import { Planet, Starship } from "@/types/swapi"; -import { fetchPlanets, fetchStarships } from "@/services/swapi"; +import { fetchGameData } from "@/services/swapi"; import { LogEntry } from "@/components/EventLog"; import { t } from "@/content/locales"; export function useGameEngine() { + const [allPlanets, setAllPlanets] = useState([]); const [planets, setPlanets] = useState([]); const [starships, setStarships] = useState([]); const [loading, setLoading] = useState(true); - const [gameStarted, setGameStarted] = useState(false); const [isGameOver, setIsGameOver] = useState(false); + const [isVictory, setIsVictory] = 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 [isProcessing, setIsProcessing] = useState(false); + const [isRerouting, setIsRerouting] = useState(false); + const [activeEnemy, setActiveEnemy] = useState(null); const [logs, setLogs] = useState([]); + const [power, setPower] = useState({ + engines: 3, + lasers: 3, + shields: 3, + }); + + const MAX_POWER = 9; + const WIN_THRESHOLD = 100000; + + const selectRandomSector = useCallback( + (all: Planet[], count: number = 15) => { + const shuffled = [...all].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, count); + }, + [], + ); + + const currentPlanet = useMemo( + () => planets.find((p) => p.id === currentLocationId) || null, + [planets, currentLocationId], + ); + + // --- Log Helper --- const addLog = useCallback( (message: string, type: LogEntry["type"] = "info") => { const newLog: LogEntry = { @@ -37,25 +64,32 @@ export function useGameEngine() { [], ); - 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); - } + // --- Actions --- + + const updatePower = (newSettings: typeof power) => { + if (!activeEnemy) { + setPower(newSettings); + return; } - initGame(); - }, [addLog]); + + setIsRerouting(true); + addLog(t.ENGINE.SYSTEMS.REROUTING, "info"); + + setTimeout(() => { + setPower(newSettings); + if (Math.random() > 0.7) { + const damage = 1; + setHp((prev) => Math.max(0, prev - damage)); + addLog( + t.ENGINE.SYSTEMS.REROUTE_FAIL(activeEnemy.name, damage), + "danger", + ); + } else { + addLog(t.ENGINE.SYSTEMS.REROUTE_SUCCESS, "success"); + } + setIsRerouting(false); + }, 600); + }; const startGame = () => { setGameStarted(true); @@ -64,8 +98,7 @@ export function useGameEngine() { const executeJump = async () => { if (!selectedPlanet || selectedPlanet.id === currentLocationId) return; - - setIsJumping(true); + setIsProcessing(true); addLog( `${t.ENGINE.JUMP.INIT(selectedPlanet.name)} ${t.ENGINE.DICE.ROLLING_1W6}`, "info", @@ -74,7 +107,6 @@ export function useGameEngine() { 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)]; @@ -92,65 +124,113 @@ export function useGameEngine() { "success", ); } - setIsJumping(false); + setIsProcessing(false); setSelectedPlanet(null); }, 1200); }; const executeEscape = () => { if (!activeEnemy) return; - addLog(`${t.ENGINE.ESCAPE.INIT} ${t.ENGINE.DICE.ROLLING_1W6}`, "info"); - + setIsProcessing(true); + addLog(t.ENGINE.ESCAPE.INIT, "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)); + const engineBonus = Math.floor(power.engines / 2); + const total = roll + engineBonus; + if (total >= 5) { + setActiveEnemy(null); addLog( - `${rollInfo} ${t.ENGINE.ESCAPE.FAIL(activeEnemy.name, damage)}`, - "danger", + `${t.ENGINE.DICE.RESULT_TOTAL(total, engineBonus)} ${t.ENGINE.ESCAPE.SUCCESS}`, + "success", ); } else { - setActiveEnemy(null); - addLog(`${rollInfo} ${t.ENGINE.ESCAPE.SUCCESS}`, "success"); + const damage = 2; + setHp((prev) => Math.max(0, prev - damage)); + addLog( + `${t.ENGINE.DICE.RESULT_TOTAL(total, engineBonus)} ${t.ENGINE.ESCAPE.FAIL(activeEnemy.name, damage)}`, + "danger", + ); } + setIsProcessing(false); }, 800); }; const handleAttack = () => { if (!activeEnemy) return; - addLog( - `${t.ENGINE.COMBAT.INIT(activeEnemy.name)} ${t.ENGINE.DICE.ROLLING_2W6}`, - "info", - ); - + setIsProcessing(true); + addLog(t.ENGINE.COMBAT.INIT(activeEnemy.name), "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); - + const laserBonus = Math.floor(power.lasers / 2); + const total = d1 + d2 + laserBonus; if (total >= activeEnemy.ds) { - const loot = 300; - setCredits((prev) => prev + loot); - setActiveEnemy(null); - addLog( - `${rollInfo} ${t.ENGINE.COMBAT.VICTORY(activeEnemy.name, loot)}`, - "success", - ); + const newHp = (activeEnemy.hp || 1) - 1; + if (newHp <= 0) { + const loot = 300; + setCredits((prev) => prev + loot); + setActiveEnemy(null); + addLog(t.ENGINE.COMBAT.VICTORY(activeEnemy.name, loot), "success"); + } else { + setActiveEnemy({ ...activeEnemy, hp: newHp }); + addLog(t.ENGINE.COMBAT.HIT(activeEnemy.name, newHp), "success"); + } } else { - const diff = activeEnemy.ds - total; - setHp((prev) => Math.max(0, prev - diff)); - addLog( - `${rollInfo} ${t.ENGINE.COMBAT.COUNTER(activeEnemy.name, diff)}`, - "danger", - ); + const engineBonus = Math.floor(power.engines / 2); + const evasionRoll = Math.floor(Math.random() * 6) + 1; + if (evasionRoll + engineBonus >= 5) { + addLog(t.ENGINE.COMBAT.EVASION(evasionRoll, engineBonus), "success"); + } else { + const rawDiff = activeEnemy.ds - total; + const shieldBonus = Math.floor(power.shields / 2); + const dmg = Math.max(0, rawDiff - shieldBonus); + setHp((prev) => Math.max(0, prev - dmg)); + addLog(t.ENGINE.COMBAT.COUNTER(activeEnemy.name, dmg), "danger"); + } } + setIsProcessing(false); }, 1000); }; + const handleRepair = () => { + const missingHp = 10 - hp; + const cost = missingHp * 20; + if (missingHp <= 0) return; + if (credits >= cost) { + setCredits((prev) => prev - cost); + setHp(10); + addLog( + t.ENGINE.REPAIR.SUCCESS(currentPlanet?.name || "Unknown", cost), + "success", + ); + } else { + addLog(t.ENGINE.REPAIR.INSUFFICIENT_FUNDS, "danger"); + } + }; + + // --- Effects --- + + useEffect(() => { + async function initGame() { + try { + setLoading(true); + const { planets: p, starships: s } = await fetchGameData(); + setAllPlanets(p); + setStarships(s); + const initialSector = selectRandomSector(p, 15); + setPlanets(initialSector); + if (initialSector.length > 0) setCurrentLocationId(initialSector[0].id); + addLog(t.ENGINE.BOOT.SUCCESS, "success"); + } catch (error) { + console.error(error); + addLog(t.ENGINE.BOOT.ERROR, "danger"); + } finally { + setLoading(false); + } + } + initGame(); + }, [addLog, selectRandomSector]); + useEffect(() => { if (hp <= 0 && !isGameOver) { setIsGameOver(true); @@ -158,14 +238,27 @@ export function useGameEngine() { } }, [hp, isGameOver, addLog]); + useEffect(() => { + if (credits >= WIN_THRESHOLD && !isVictory) { + setIsVictory(true); + addLog(t.VICTORY.MESSAGE, "success"); + } + }, [credits, isVictory, addLog]); + const restartGame = () => { setHp(10); setCredits(100); + setPower({ engines: 3, lasers: 3, shields: 3 }); setIsGameOver(false); + setIsVictory(false); setActiveEnemy(null); setSelectedPlanet(null); setLogs([]); + const newSector = selectRandomSector(allPlanets, 15); + setPlanets(newSector); + if (newSector.length > 0) setCurrentLocationId(newSector[0].id); addLog(t.ENGINE.BOOT.RESTART, "success"); + addLog(t.ENGINE.SYSTEMS.SECTOR_UPDATE, "info"); }; return { @@ -177,15 +270,23 @@ export function useGameEngine() { credits, hp, currentLocationId, + currentPlanet, selectedPlanet, setSelectedPlanet, executeEscape, executeJump, handleAttack, - isJumping, + handleRepair, + isJumping: isProcessing, + isRerouting, activeEnemy, + power, + updatePower, + maxPower: MAX_POWER, logs, isGameOver, restartGame, + isVictory, + WIN_THRESHOLD, }; } diff --git a/public/fonts/Aurebesh-English.ttf b/public/fonts/Aurebesh-English.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f03b6b9fbd468c65263766fc95da55e0e75b334f GIT binary patch literal 9172 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 literal 0 HcmV?d00001 diff --git a/services/swapi.ts b/services/swapi.ts index 39e641f..4f9432a 100644 --- a/services/swapi.ts +++ b/services/swapi.ts @@ -1,35 +1,84 @@ import { SWAPIPlanet, SWAPIStarship, Planet, Starship } from "@/types/swapi"; const BASE_URL = "https://swapi.dev/api"; +const CACHE_KEY = "smugglers_run_cache"; +const CACHE_DURATION = 1000 * 60 * 60 * 24; // Cache TTL 24 hrs -export async function fetchPlanets(): Promise { - 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, - }), - ); +interface GameCache { + planets: Planet[]; + starships: Starship[]; + timestamp: number; } -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(); +async function fetchAllPages(endpoint: string): Promise { + let allResults: T[] = []; + let nextUrl = `${BASE_URL}/${endpoint}/`; - return data.results.map((s: SWAPIStarship): Starship => { - // Add difficulty score based on ship cost + while (nextUrl) { + const res = await fetch(nextUrl); + if (!res.ok) throw new Error(`${endpoint} data unavailable`); + const data = await res.json(); + allResults = [...allResults, ...data.results]; + nextUrl = data.next; + } + return allResults; +} + +export async function fetchGameData(): Promise<{ + planets: Planet[]; + starships: Starship[]; +}> { + // Try load from localstorage as simple cache + const cachedData = + typeof window !== "undefined" ? localStorage.getItem(CACHE_KEY) : null; + + if (cachedData) { + const parsedCache: GameCache = JSON.parse(cachedData); + const isExpired = Date.now() - parsedCache.timestamp > CACHE_DURATION; + + if (!isExpired) { + console.log("data loaded from cache"); + + return { planets: parsedCache.planets, starships: parsedCache.starships }; + } + } + + // If not in cache or outdated, fetch from Swapi + console.log("data not in cache. fetching from swapi..."); + + const [rawPlanets, rawStarships] = await Promise.all([ + fetchAllPages("planets"), + fetchAllPages("starships"), + ]); + + const planets: Planet[] = rawPlanets.map((p) => ({ + ...p, + id: p.url, + x: Math.floor(Math.random() * 80) + 10, + y: Math.floor(Math.random() * 70) + 15, + })); + + const starships: Starship[] = rawStarships.map((s) => { 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 }; + let ds = 8, + hp = 2; + if (isNaN(cost) || cost > 1000000) { + ds = 12; + hp = 6; + } else if (cost > 100000) { + ds = 10; + hp = 4; + } + return { ...s, id: s.url, ds, hp }; }); + + // Save to localstorage + const cacheToSave: GameCache = { + planets, + starships, + timestamp: Date.now(), + }; + localStorage.setItem(CACHE_KEY, JSON.stringify(cacheToSave)); + + return { planets, starships }; } diff --git a/types/swapi.ts b/types/swapi.ts index 4d2a2c9..b89954d 100644 --- a/types/swapi.ts +++ b/types/swapi.ts @@ -1,7 +1,4 @@ -// types/swapi.ts - -// Die korrigierten Rohdaten von der SWAPI -export interface SWAPIPlanet { // <-- Jetzt mit korrektem 'n' +export interface SWAPIPlanet { name: string; rotation_period: string; orbital_period: string; @@ -28,7 +25,6 @@ export interface SWAPIStarship { url: string; } -// Unsere erweiterten Typen für das Spiel export interface Planet extends SWAPIPlanet { id: string; x: number; @@ -37,5 +33,6 @@ export interface Planet extends SWAPIPlanet { export interface Starship extends SWAPIStarship { id: string; - ds: number; // Difficulty Score für Tunnel Goons -} \ No newline at end of file + ds: number; + hp: number; +} diff --git a/utils/getPlanetTheme.ts b/utils/getPlanetTheme.ts new file mode 100644 index 0000000..058f5aa --- /dev/null +++ b/utils/getPlanetTheme.ts @@ -0,0 +1,63 @@ +import { LocaleType } from "@/content/locales"; + +export const getPlanetTheme = ( + terrain: string = "", + climate: string = "", + t: LocaleType, +) => { + 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, + hex: "#60a5fa", + }; + 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, + hex: "#4ade80", + }; + 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, + hex: "#fb923c", + }; + 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, + hex: "#22d3ee", + }; + 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, + hex: "#c084fc", + }; + + return { + color: "text-yellow-500", + border: "border-yellow-500", + glow: "shadow-[0_0_20px_#eab308]", + label: t.ACTION_PANEL.MODES.STANDARD, + hex: "#eab308", + }; +};