diff --git a/weltenauge/PROJEKT_UEBERSICHT.md b/weltenauge/PROJEKT_UEBERSICHT.md index 9d88359..3446a06 100644 --- a/weltenauge/PROJEKT_UEBERSICHT.md +++ b/weltenauge/PROJEKT_UEBERSICHT.md @@ -2,14 +2,6 @@ "weltenauge" ist ein von Neverwinter Nights (NWN) inspiriertes Multiplayer-Rollenspiel. Das Kernstück ist eine robuste Client/Server-Architektur, die in einer einzigen Godot-Codebase umgesetzt wird. -Dies ist ein "Ewigkeits-Projekt", das nichtkommerziell ist, und -vom Entwickler immer wieder für längere Zeit weggelegt wird. Fokus liegt auf 3D Welten und Weltensimulation. -Dadurch ist es notwendig, dass das gesamte System so modular und -selbsterklärend aufgebaut wird, dass eine Rückkehr zum Projekt nach -langer Pause immer noch machbar ist. Features sind daher soweit einzuschränken, -dass ein absolut Minimaler Prototyp in einem vernünftigen Zeitraum erstellbar wird. -Ausserdem ist Modularität und einfache Testbarkeit Pflicht. - ## 2. Kernkonzepte der Architektur ### Client/Server-Modell @@ -63,11 +55,11 @@ Alle Skripte und die dazugehörige Logik sollen thematisch in Unterordnern unter ## 5. Implementierungsdetails & Konventionen - **Click-to-Move:** Die Spielerbewegung soll aus Performancegründen über eine AStar-Implementierung (`systems/navigation/`) gesteuert werden. -- **GDScript-Konvention: `async`-Schlüsselwort vermeiden:** In GDScript 4 wird jede Funktion, die `await` enthält, automatisch von der Engine als asynchron behandelt. Das `async`-Schlüsselwort in der Funktionssignatur (`async func...`) ist optional und hat in diesem Projekt wiederholt zu schwer diagnostizierbaren Syntaxfehlern geführt. - - **Regel:** Das `async`-Schlüsselwort **darf nicht** verwendet werden. - - **Korrekt:** `func my_function(): await get_tree().process_frame` - - **Falsch:** `async func my_function(): await get_tree().process_frame` - +- **Version:** Basis ist **Godot 4.6**. Nutze aktuelle APIs (besonders für Navigation und Multiplayer). +- **GDScript-Regel (No Async Keyword):** Das Schlüsselwort `async` darf **nicht** verwendet werden. Funktionen mit `await` sind automatisch asynchron. +- - *Korrekt:* `func move(): await get_tree().process_frame` +- **Statische Typisierung:** Nutze konsequent Typ-Hints (z.B. `var health: int = 100`), um Fehlerquellen zu minimieren. +- **Node-Referenzen:** Nutze `@onready var` und relative Pfade. Vermeide `/root/`-Pfade wegen der SubViewport-Struktur. ## 6. Globale Skripte (Autoloads) @@ -114,9 +106,17 @@ Dieser Abschnitt beschreibt die Verantwortlichkeiten der zentralen Skripte im Pr ## 8. Wichtige Erkenntnisse & Best Practices -Dieser Abschnitt dokumentiert wichtige technische Lektionen, die während der Entwicklung gelernt wurden. +* **MultiplayerSynchronizer:** Konfiguration **nur per Code** in `_ready()`. +* **Property-Syntax:** Pfade in der `SceneReplicationConfig` müssen mit Doppelpunkt starten, wenn sie sich auf den Root-Pfad beziehen (z.B. `config.add_property(":sync_position")`). +* **MultiplayerSpawner:** Nutze einen generischen Spawner in der Basis-Level-Szene für automatische Synchronisation bei Late-Joinern. -### Technische Detail +## 7. Anweisungen für die KI (Refactoring & Generierung) + +* **Thinking-Phase:** Analysiere vor der Code-Ausgabe, ob Änderungen die SubViewport-Isolation oder die Server-Autorität beeinflussen könnten. +* **Minimaler Eingriff:** Verändere nur angeforderte Logik. Behalte Kommentare und Signal-Strukturen bei. +* **Stil:** Schreibe sauberen, performanten GDScript 2.0 Code. Bevorzuge Komposition gegenüber Vererbung. + +### Technische Details Es gibt kein async Keyword, aber await funktioniert trotzdem. => await get_tree().process_frame diff --git a/weltenauge/game_root.tscn b/weltenauge/game_root.tscn index 4d5bc4a..ec26684 100644 --- a/weltenauge/game_root.tscn +++ b/weltenauge/game_root.tscn @@ -2,8 +2,12 @@ [ext_resource type="Script" uid="uid://162cjpb4lac4" path="res://game_root.gd" id="1_xj5yv"] [ext_resource type="PackedScene" uid="uid://bhywhrlwjox6t" path="res://ui/menus/main_menu.tscn" id="2_pjpa4"] +[ext_resource type="PackedScene" uid="uid://ca2bhph2k3xpy" path="res://ui/character_select/character_select.tscn" id="3_bxsma"] [node name="Game_root" type="Node2D" unique_id=1336425327] script = ExtResource("1_xj5yv") [node name="MainMenu" parent="." unique_id=1731390780 instance=ExtResource("2_pjpa4")] +visible = false + +[node name="CharacterSelect" parent="." unique_id=2079351175 instance=ExtResource("3_bxsma")] diff --git a/weltenauge/systems/data/character_data.gd b/weltenauge/systems/data/character_data.gd new file mode 100644 index 0000000..fea9a96 --- /dev/null +++ b/weltenauge/systems/data/character_data.gd @@ -0,0 +1,9 @@ +class_name CharacterData + +var id: int = -1 # Eindeutige ID (wird später wichtig) +var name: String = "New Hero" +var level: int = 1 +var area: String = "keuwin_grasland" # Start-Area +var position: Vector3 = Vector3.ZERO +var testvariable: String = "test" +# Hier kommen später noch Inventar, Ausrüstung, etc. hinzu... diff --git a/weltenauge/systems/data/character_data.gd.uid b/weltenauge/systems/data/character_data.gd.uid new file mode 100644 index 0000000..e409918 --- /dev/null +++ b/weltenauge/systems/data/character_data.gd.uid @@ -0,0 +1 @@ +uid://dsylf8nx1e2wc diff --git a/weltenauge/systems/data/save_load.gd b/weltenauge/systems/data/save_load.gd new file mode 100644 index 0000000..272ea65 --- /dev/null +++ b/weltenauge/systems/data/save_load.gd @@ -0,0 +1,47 @@ +class_name SaveLoad + +const SAVE_PATH = "user://player_data.json" + +static func save_character(data: CharacterData): + var save_dict = { + "id": data.id, + "name": data.name, + "level": data.level, + "area": data.area, + "position": { # Vector3 als Dictionary speichern + "x": data.position.x, + "y": data.position.y, + "z": data.position.z + } + } + + var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE) + if file: + file.store_string(JSON.stringify(save_dict, "\t")) + file.close() + print("Saved player data to ", SAVE_PATH) + else: + printerr("Error saving to ", SAVE_PATH) + +static func load_character() -> CharacterData: + var file = FileAccess.open(SAVE_PATH, FileAccess.READ) + if file: + var json_string = file.get_as_text() + file.close() + + var data = JSON.parse_string(json_string) + if data == null: + printerr("JSON Parse Error") + return null + + var char_data = CharacterData.new() + char_data.id = data["id"] + char_data.name = data["name"] + char_data.level = data["level"] + char_data.area = data["area"] + char_data.position = Vector3(data["position"]["x"],data["position"]["y"],data["position"]["z"]) + print("Loaded player data from ", SAVE_PATH) + return char_data + else: + print("No savefile found, returning default CharacterData.") + return null diff --git a/weltenauge/systems/data/save_load.gd.uid b/weltenauge/systems/data/save_load.gd.uid new file mode 100644 index 0000000..a4c4271 --- /dev/null +++ b/weltenauge/systems/data/save_load.gd.uid @@ -0,0 +1 @@ +uid://dhettikhnfdt4 diff --git a/weltenauge/ui/character_select/character_select.gd b/weltenauge/ui/character_select/character_select.gd new file mode 100644 index 0000000..fbf5f4c --- /dev/null +++ b/weltenauge/ui/character_select/character_select.gd @@ -0,0 +1,47 @@ +extends Control + +@onready var character_list = $ItemList # Oder Tree, je nachdem wie die Liste aussieht + +var characters: Array[CharacterData] = [] + +func _ready(): + # Vorhandene Charaktere laden + load_characters() + update_ui() + +func load_characters(): + # (Hier kommt die Logik zum Durchsuchen des "user://" Ordners nach JSON-Dateien hin) + # (Für den Anfang können wir einfach einen Standard-Charakter erstellen) + var data = SaveLoad.load_character() + if data == null: + data = CharacterData.new() + data.id = 1 # Eindeutige ID generieren (später) + characters = [data] + SaveLoad.save_character(data) + else: + characters = [data] + + +func update_ui(): + character_list.clear() + for char in characters: + character_list.add_item(char.name) # Hier später: Icons, Stats etc. + +func _on_character_selected(index): + var selected_character = characters[index] + # (Hier Code zum Anzeigen der Details und Bearbeiten des Charakters) + # (Am Ende: StartGame.emit(selected_character)) + pass + +signal StartGame(character: CharacterData) # Ab hier wirds interessant. +func _on_start_game_pressed(): + if characters.size() > 0: + StartGame.emit(characters[0]) # Einfachheitshalber: Ersten Charakter nehmen + # Aufräumen und die Spielwelt laden (später) + +func _on_create_character_pressed(): + var new_character = CharacterData.new() + new_character.id = characters.size() + 1 # Provisorische ID + characters.append(new_character) + SaveLoad.save_character(new_character) + update_ui() diff --git a/weltenauge/ui/character_select/character_select.gd.uid b/weltenauge/ui/character_select/character_select.gd.uid new file mode 100644 index 0000000..aaec836 --- /dev/null +++ b/weltenauge/ui/character_select/character_select.gd.uid @@ -0,0 +1 @@ +uid://b5htxbkq23mo6 diff --git a/weltenauge/ui/character_select/character_select.tscn b/weltenauge/ui/character_select/character_select.tscn new file mode 100644 index 0000000..150ee7d --- /dev/null +++ b/weltenauge/ui/character_select/character_select.tscn @@ -0,0 +1,30 @@ +[gd_scene format=3 uid="uid://ca2bhph2k3xpy"] + +[ext_resource type="Script" uid="uid://b5htxbkq23mo6" path="res://ui/character_select/character_select.gd" id="1_o7qwi"] + +[node name="CharacterSelect" type="Control" unique_id=2056198803] +layout_mode = 3 +anchors_preset = 0 +script = ExtResource("1_o7qwi") + +[node name="ItemList" type="ItemList" parent="." unique_id=1442954519] +layout_mode = 0 +offset_top = 32.0 +offset_right = 368.0 +offset_bottom = 571.0 + +[node name="Label" type="Label" parent="." unique_id=1413425912] +layout_mode = 0 +offset_left = 111.0 +offset_top = 2.0 +offset_right = 217.0 +offset_bottom = 31.0 +text = "Charaktere:" + +[node name="Button" type="Button" parent="." unique_id=50672893] +layout_mode = 0 +offset_left = 113.0 +offset_top = 585.0 +offset_right = 224.0 +offset_bottom = 621.0 +text = "Start"