This commit is contained in:
team3
2026-08-30 11:50:09 +02:00
parent 4d771c9a6e
commit a46eba4145
23 changed files with 816 additions and 601 deletions

View File

@@ -1,21 +1,24 @@
78098b89c88a74ebe8c20117b8754620 78098b89c88a74ebe8c20117b8754620
::res://::1787989983 ::res://::1787996624
anforderungen.md::TextFile::-1::1787989000::0::1::::<><><>0<>0<><>:: anforderungen.md::TextFile::-1::1788038502::0::1::::<><><>0<>0<><>::
README.md::TextFile::-1::1788038596::0::1::::<><><>0<>0<><>::
::res://scenes/::1787989294 ::res://scenes/::1787989294
main.tscn::PackedScene::-1::1787989294::0::1::::<><><>0<>0<><>::res://scripts/main.gd main.tscn::PackedScene::-1::1787989294::0::1::::<><><>0<>0<><>::res://scripts/main.gd
::res://scripts/::1787989847 ::res://scripts/::1788039082
dream_world.gd::GDScript::8279158278942867258::1787991037::0::1::::DreamWorld<>Node3D<><>0<>0<><>:: bewohner.gd::GDScript::2125278938812678811::1788039082::0::1::::Bewohner<>Node3D<><>0<>0<><>::
earth_world.gd::GDScript::6086818627272428934::1787990934::0::1::::EarthWorld<>Node3D<><>0<>0<><>:: dream_db.gd::GDScript::4217518896355437099::1788039046::0::1::::DreamDB<>RefCounted<><>0<>0<><>::
game.gd::GDScript::3600922541500868814::1787990324::0::1::::<>Node<><>0<>0<><>:: dream_world.gd::GDScript::8279158278942867258::1788039140::0::1::::DreamWorld<>Node3D<><>0<>0<><>::
human.gd::GDScript::781542295747092807::1787991037::0::1::::Human<>Node3D<><>0<>0<><>:: earth_world.gd::GDScript::6086818627272428934::1788039160::0::1::::EarthWorld<>Node3D<><>0<>0<><>::
human_db.gd::GDScript::412604892032291296::1787990324::0::1::::HumanDB<>RefCounted<><>0<>0<><>:: game.gd::GDScript::3600922541500868814::1788038470::0::1::::<>Node<><>0<>0<><>::
main.gd::GDScript::1504009331368977934::1787990676::0::1::::<>Node3D<><>0<>0<><>:: human_db.gd::GDScript::412604892032291296::1788039057::0::1::::HumanDB<>RefCounted<><>0<>0<><>::
main.gd::GDScript::1504009331368977934::1788039160::0::1::::<>Node3D<><>0<>0<><>::
orte.gd::GDScript::5536340784294498671::1788039046::0::1::::Orte<>RefCounted<><>0<>0<><>::
player.gd::GDScript::8724108548430371398::1787990898::0::1::::Player<>CharacterBody3D<><>0<>0<><>:: player.gd::GDScript::8724108548430371398::1787990898::0::1::::Player<>CharacterBody3D<><>0<>0<><>::
prims.gd::GDScript::322632462553361584::1787990855::0::1::::Prims<>RefCounted<><>0<>0<><>:: prims.gd::GDScript::322632462553361584::1787990855::0::1::::Prims<>RefCounted<><>0<>0<><>::
technique_db.gd::GDScript::4543286561661639701::1787991124::0::1::::TechDB<>RefCounted<><>0<>0<><>:: technique_db.gd::GDScript::4543286561661639701::1788039160::0::1::::TechDB<>RefCounted<><>0<>0<><>::
ui.gd::GDScript::843471304695966286::1787991124::0::1::::UI<>CanvasLayer<><>0<>0<><>:: ui.gd::GDScript::843471304695966286::1788039160::0::1::::UI<>CanvasLayer<><>0<>0<><>::
::res://tests/::1787990698 ::res://tests/::1787990698
shots.gd::GDScript::5999651346126871724::1787990988::0::1::::<>Node<><>0<>0<><>:: shots.gd::GDScript::5999651346126871724::1788038553::0::1::::<>Node<><>0<>0<><>::
shots.tscn::PackedScene::-1::1787990528::0::1::::<><><>0<>0<><>::res://tests/shots.gd shots.tscn::PackedScene::-1::1787990528::0::1::::<><><>0<>0<><>::res://tests/shots.gd
verify.gd::GDScript::4392606218392155269::1787991067::0::1::::<>Node<><>0<>0<><>:: verify.gd::GDScript::4392606218392155269::1788038533::0::1::::<>Node<><>0<>0<><>::
verify.tscn::PackedScene::-1::1787989983::0::1::::<><><>0<>0<><>::res://tests/verify.gd verify.tscn::PackedScene::-1::1787989983::0::1::::<><><>0<>0<><>::res://tests/verify.gd

View File

@@ -1,5 +1,21 @@
list=[{ list=[{
"base": &"Node3D", "base": &"Node3D",
"class": &"Bewohner",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/bewohner.gd"
}, {
"base": &"RefCounted",
"class": &"DreamDB",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/dream_db.gd"
}, {
"base": &"Node3D",
"class": &"DreamWorld", "class": &"DreamWorld",
"icon": "", "icon": "",
"is_abstract": false, "is_abstract": false,
@@ -15,14 +31,6 @@ list=[{
"language": &"GDScript", "language": &"GDScript",
"path": "res://scripts/earth_world.gd" "path": "res://scripts/earth_world.gd"
}, { }, {
"base": &"Node3D",
"class": &"Human",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/human.gd"
}, {
"base": &"RefCounted", "base": &"RefCounted",
"class": &"HumanDB", "class": &"HumanDB",
"icon": "", "icon": "",
@@ -31,6 +39,14 @@ list=[{
"language": &"GDScript", "language": &"GDScript",
"path": "res://scripts/human_db.gd" "path": "res://scripts/human_db.gd"
}, { }, {
"base": &"RefCounted",
"class": &"Orte",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/orte.gd"
}, {
"base": &"CharacterBody3D", "base": &"CharacterBody3D",
"class": &"Player", "class": &"Player",
"icon": "", "icon": "",

Binary file not shown.

View File

@@ -4,32 +4,38 @@ Ein 3D-Spiel in Godot 4.7 (GDScript). Alle Objekte bestehen aus Godot-Primitiven
(Box, Zylinder, Kugel, Kapsel) — kein externes Asset. (Box, Zylinder, Kugel, Kapsel) — kein externes Asset.
Du bist ein Wesen aus der Traumwelt. Menschen tragen Träume mit sich herum, die Du bist ein Wesen aus der Traumwelt. Menschen tragen Träume mit sich herum, die
sie allein nicht bewegt bekommen. Du wirst auf die Erde geschickt, um zu helfen. sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.
## Starten ## Starten
godot --path . godot --path .
Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren · Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren ·
**Tab** Kodex · **Esc** Maus freigeben. **J** Journal · **Esc** Maus freigeben.
Auf der Erde zusätzlich **Leertaste** steigen und **Umschalt** sinken. Auf der Erde zusätzlich **Leertaste** steigen und **Umschalt** sinken.
## Spielschleife ## Spielschleife
**Traumwelt** (Insel mit drei betretbaren Gebäuden) **Journal** (Taste **J**, in beiden Welten dabei)
* **Bibliothek der Techniken** — zwölf echte Lösungswege. Jede Technik nennt * **Techniken** — zwölf echte Lösungswege. Jede nennt *Was* sie tut, *Wann* sie
*Was* sie tut, *Wann* sie passt und *Warum* sie hilft. Lernen kostet 1 Einsicht. passt und *Warum* sie hilft. Lernen kostet 1 Einsicht. Anwenden verbessert eine
* **Missionshalle** — vier Menschen mit ihrem Traum und dem, was im Weg steht. Technik von allein; Vertiefen für 1 Einsicht geht zusätzlich.
Eine Mission annehmen. * **Missionen** — vier Menschen mit ihrem Traum und dem, was im Weg steht.
* **Meditationskammer** — eine gelernte Technik für 1 Einsicht vertiefen (+3 EP).
**Traumwelt** — eine schwebende Insel mit einem Dorf
* Vier Wesen wohnen hier: Ailen die Traumweberin, Morr der Erinnerungssammler,
Ivo der Bote, Sela die Wächterin des Tors.
* Jedes hat ein eigenes Haus und einen eigenen Tagesablauf. Die Häuser lassen
sich betreten, auf der Lichtung treffen sich die Wesen.
* **Tor zur Erde** — öffnet sich nur mit angenommener Mission. * **Tor zur Erde** — öffnet sich nur mit angenommener Mission.
**Erde** (Kleinstadt, ein Tag dauert 6 Minuten) **Erde** — eine Kleinstadt, ein Tag dauert 6 Minuten
* Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände. * Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände.
* Vier Menschen leben ihren Tagesablauf: schlafen, arbeiten, grübeln, essen, * Vier Menschen leben ihren Tagesablauf: schlafen, arbeiten, grübeln, essen,
spazieren. Jeder Ort und jede Tätigkeit steht fest. spazieren.
* Nah herangehen, **E** drücken, Technik wählen. Das Panel zeigt für jede * Nah herangehen, **E** drücken, Technik wählen. Das Panel zeigt für jede
Technik die Erfolgschance in genau dieser Situation. Technik die Erfolgschance in genau dieser Situation.
* Die Säule im Park bringt dich zurück in die Traumwelt. * Die Säule im Park bringt dich zurück in die Traumwelt.
@@ -49,24 +55,28 @@ bringen 3 Einsicht zurück.
## Aufbau ## Aufbau
project.godot Projekt, Autoload `Game` project.godot Projekt, Autoload `Game`
scenes/main.tscn Einstieg scenes/main.tscn Einstieg
scripts/game.gd Zustand, Regeln, Uhr (Autoload) scripts/game.gd Zustand, Regeln, Uhr (Autoload)
scripts/technique_db.gd die zwölf Techniken mit ihren Hinweisen scripts/technique_db.gd die zwölf Techniken mit ihren Hinweisen
scripts/human_db.gd Orte, Menschen, Tagesabläufe, Wegfindung scripts/human_db.gd Kleinstadt und Menschen
scripts/main.gd Weltenwechsel, Fokus, Interaktion scripts/dream_db.gd Traumdorf und Traumwesen
scripts/player.gd Läufer in der Traumwelt, Geist auf der Erde scripts/orte.gd Orte, Türen, Wege, Tätigkeiten (beide Welten)
scripts/human.gd Mensch mit Tagesablauf und Reaktionen scripts/bewohner.gd Bewohner mit Tagesablauf und Reaktionen
scripts/dream_world.gd Traumwelt inklusive Innenräume scripts/main.gd Weltenwechsel, Fokus, Interaktion
scripts/earth_world.gd Stadt mit Tag-Nacht-Wechsel scripts/player.gd Läufer in der Traumwelt, Geist auf der Erde
scripts/ui.gd HUD und alle Panels scripts/dream_world.gd Insel mit Dorf
scripts/prims.gd Bausteine aus Primitiven scripts/earth_world.gd Stadt mit Tag-Nacht-Wechsel
scripts/ui.gd HUD, Journal und alle Panels
scripts/prims.gd Bausteine aus Primitiven
## Prüfen ## Prüfen
godot --headless --path . tests/verify.tscn # 81 Prüfungen der Spiellogik godot --headless --path . tests/verify.tscn # Prüfungen der Spiellogik
godot --path . tests/shots.tscn # 16 Screenshots nach user://shots/ godot --path . tests/shots.tscn # 15 Screenshots nach user://shots/
`verify` fährt das echte Spiel: Gebäude betreten, Technik lernen, Mission `verify` fährt das echte Spiel: Traumdorf mit vier Wesen und ihren Tagesabläufen,
annehmen, zur Erde reisen, Tagesabläufe über einen ganzen Tag, Erfolgschancen, Häuser betreten, Journal in beiden Welten, Technik lernen und vertiefen,
Mission abschließen, Tastensteuerung. Beendet sich mit Code 1, wenn etwas fehlt. Verbessern allein durchs Anwenden, Mission annehmen, zur Erde reisen,
Tagesabläufe über einen ganzen Tag, Erfolgschancen, Mission abschließen,
Tastensteuerung. Beendet sich mit Code 1, wenn etwas fehlt.

View File

@@ -1,14 +1,24 @@
Funktionen Traumwelt
- Bewegen
- Gebäude betretten
- Techniken lernen, echte Lösungen für Probleme
- Techniken haben Hinweise, was sie machen, wann sie angewand werden und warum sie helfen
- Missionen annehmen, also einem Menschen helfen
Funktionen Erde
- Menschen mit Techniken manipulieren, Erfolgschance hängt vom Skill ab
- Bewegung als Geist unsichtbar
- Menschen haben ihre Tagesabläufe als Slice of Life
Kern Kern
- Wesen aus einer Traumwelt werden auf die Erde geschickt um Menschen helfen ihre Träume zu erfüllen - Wesen aus einer Traumwelt werden auf die Erde geschickt,
um Menschen zu helfen, ihre Träume zu erfüllen.
Journal (immer dabei)
- Techniken lernen
- Techniken verbessern
- Missionen annehmen
Traumwelt
- Bewegen
- Orte betreten
- Traumwesen leben ihren Tag
Erde
- Bewegen als unsichtbarer Geist
- Menschen leben ihren Tag
- Menschen lesen
- Menschen beeinflussen
Übergreifend
- Zwischen den Welten reisen
- Zeit vergeht
- Missionen gelingen oder scheitern

View File

@@ -1,15 +1,17 @@
extends Node3D extends Node3D
class_name Human class_name Bewohner
## Ein Mensch auf der Erde. Läuft seinen Tagesablauf ab und kann von einer ## Bewohner einer Welt: läuft seinen Tagesablauf ab. Auf der Erde kann er von
## Technik erreicht werden -- oder eben nicht. ## einer Technik erreicht werden -- oder eben nicht.
const GEHTEMPO := 6.0 const GEHTEMPO := 6.0
var data: Dictionary var data: Dictionary
var idx: int = 0 var idx: int = 0
var place: String = "" var place: String = ""
var activity: String = "schlaeft" var activity: String = ""
var resist_left: float = 0.0 var resist_left: float = 0.0
var places: Dictionary = {}
var mit_mission := false
var _visual: Node3D var _visual: Node3D
var _label: Label3D var _label: Label3D
@@ -22,21 +24,30 @@ var _bob: float = 0.0
var _offset: Vector3 var _offset: Vector3
func setup(d: Dictionary, index: int) -> void: func setup(d: Dictionary, index: int, orte: Dictionary) -> void:
data = d data = d
idx = index idx = index
name = "Human_" + str(d["id"]) places = orte
mit_mission = bool(d.get("mission", false))
name = "Bewohner_" + str(d["id"])
var winkel := float(index) * TAU / 4.0 var winkel := float(index) * TAU / 4.0
_offset = Vector3(cos(winkel), 0, sin(winkel)) * 2.2 _offset = Vector3(cos(winkel), 0, sin(winkel)) * 2.2
_visual = Node3D.new() _visual = Node3D.new()
add_child(_visual) add_child(_visual)
var c: Color = d["farbe"] var c: Color = d["farbe"]
var leuchtet := bool(d.get("leuchtet", false))
var hoehe := 1.05 + float(index % 3) * 0.08 var hoehe := 1.05 + float(index % 3) * 0.08
_koerper = Prims.capsule(_visual, Vector3(0, hoehe * 0.62, 0), 0.32, hoehe, c) _koerper = Prims.capsule(_visual, Vector3(0, hoehe * 0.62, 0), 0.32, hoehe, c)
_kopf = Prims.sphere(_visual, Vector3(0, hoehe * 1.18 + 0.1, 0), 0.24, c.lightened(0.45)) _kopf = Prims.sphere(_visual, Vector3(0, hoehe * 1.18 + 0.1, 0), 0.24, c.lightened(0.45))
Prims.box(_visual, Vector3(0, hoehe * 0.95, 0), Vector3(0.9, 0.14, 0.3), c.darkened(0.25), false) Prims.box(_visual, Vector3(0, hoehe * 0.95, 0), Vector3(0.9, 0.14, 0.3), c.darkened(0.25), false)
Prims.box(_visual, Vector3(0, 0.06, 0), Vector3(0.5, 0.12, 0.7), c.darkened(0.45), false) Prims.box(_visual, Vector3(0, 0.06, 0), Vector3(0.5, 0.12, 0.7), c.darkened(0.45), false)
if leuchtet:
_koerper.material_override = Prims.mat(c, 0.9)
_kopf.material_override = Prims.mat(c.lightened(0.45), 0.8)
var reif := Prims.cyl(_visual, Vector3(0, hoehe * 0.5, 0), 0.62, 0.05,
c.lightened(0.3), false, 1.6)
reif.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
_aura = Prims.cyl(self, Vector3(0, 0.05, 0), 0.95, 0.05, Color(1, 0.85, 0.35), false, 2.0) _aura = Prims.cyl(self, Vector3(0, 0.05, 0), 0.95, 0.05, Color(1, 0.85, 0.35), false, 2.0)
_aura.visible = false _aura.visible = false
@@ -48,7 +59,7 @@ func setup(d: Dictionary, index: int) -> void:
var start := _routine_at(Game.time_h) var start := _routine_at(Game.time_h)
place = start[0] place = start[0]
activity = start[1] activity = start[1]
global_position = HumanDB.place_center(place) + _offset global_position = Orte.center(places, place) + _offset
func _routine_at(h: float) -> Array: func _routine_at(h: float) -> Array:
@@ -66,7 +77,7 @@ func _process(delta: float) -> void:
var soll := _routine_at(Game.time_h) var soll := _routine_at(Game.time_h)
if soll[0] != place: if soll[0] != place:
var war_unterwegs := not _path.is_empty() var war_unterwegs := not _path.is_empty()
_path = HumanDB.route(place, soll[0], _offset) _path = Orte.route(places, place, soll[0], _offset)
if war_unterwegs and _path.size() > 1: if war_unterwegs and _path.size() > 1:
_path.remove_at(0) # steht schon draußen, alte Tür überspringen _path.remove_at(0) # steht schon draußen, alte Tür überspringen
place = soll[0] place = soll[0]
@@ -97,8 +108,8 @@ func _process(delta: float) -> void:
_visual.rotation.z = lerp_angle(_visual.rotation.z, PI * 0.5 if activity == "schlaeft" else 0.0, _visual.rotation.z = lerp_angle(_visual.rotation.z, PI * 0.5 if activity == "schlaeft" else 0.0,
1.0 - pow(0.01, delta)) 1.0 - pow(0.01, delta))
var erledigt: bool = Game.completed.has(data["id"]) var erledigt: bool = mit_mission and Game.completed.has(data["id"])
var aktiv: bool = Game.active_mission == data["id"] var aktiv: bool = mit_mission and Game.active_mission == data["id"]
_aura.visible = aktiv or erledigt _aura.visible = aktiv or erledigt
_strahl.visible = aktiv _strahl.visible = aktiv
if _aura.visible: if _aura.visible:
@@ -113,13 +124,17 @@ func _process(delta: float) -> void:
func beschriftung() -> String: func beschriftung() -> String:
var s: String = data["name"] + "\n" + HumanDB.activity_text(activity) var s: String = data["name"]
if data.has("rolle"):
s += ", " + str(data["rolle"])
s += "\n" + Orte.text(activity)
if resist_left > 0.0: if resist_left > 0.0:
s += " (macht dicht)" s += " (macht dicht)"
if Game.completed.has(data["id"]): if mit_mission:
s += "\n★ Traum erfüllt" if Game.completed.has(data["id"]):
elif Game.active_mission == data["id"]: s += "\n★ Traum erfüllt"
s += "\nTraum: %d %%" % int(Game.mission_progress(data["id"])) elif Game.active_mission == data["id"]:
s += "\nTraum: %d %%" % int(Game.mission_progress(data["id"]))
return s return s

1
scripts/bewohner.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://5jrbe4hct8jn

100
scripts/dream_db.gd Normal file
View File

@@ -0,0 +1,100 @@
class_name DreamDB
## Die Traumwelt: ein Dorf. Jedes Wesen hat ein Haus und einen Tagesablauf.
static var PLACES: Dictionary = {
"haus_ailen": {
"name": "Ailens Webstube", "pos": [-28.0, -22.0], "size": [12.0, 10.0],
"dir": "back", "color": Color(0.72, 0.62, 0.92),
},
"haus_morr": {
"name": "Morrs Kammer", "pos": [28.0, -22.0], "size": [12.0, 10.0],
"dir": "back", "color": Color(0.58, 0.72, 0.90),
},
"haus_ivo": {
"name": "Ivos Unterstand", "pos": [-28.0, 22.0], "size": [12.0, 10.0],
"dir": "forward", "color": Color(0.88, 0.66, 0.80),
},
"haus_sela": {
"name": "Selas Turmstube", "pos": [28.0, 22.0], "size": [12.0, 10.0],
"dir": "forward", "color": Color(0.62, 0.82, 0.88),
},
"lichtung": {
"name": "Die Lichtung", "pos": [0.0, 2.0], "size": [0.0, 0.0],
"dir": "none", "color": Color(0.60, 0.50, 0.85),
},
"tor": {
"name": "Tor zur Erde", "pos": [0.0, -34.0], "size": [0.0, 0.0],
"dir": "none", "color": Color(0.50, 0.80, 1.0),
},
}
static var LIST: Array = [
{
"id": "ailen",
"name": "Ailen",
"rolle": "Traumweberin",
"leuchtet": true,
"farbe": Color(0.78, 0.55, 0.95),
"routine": [
[0, "haus_ailen", "webt"],
[6, "lichtung", "ruht"],
[8, "haus_ailen", "webt"],
[14, "lichtung", "singt"],
[16, "haus_ailen", "webt"],
[20, "haus_ailen", "traeumt"],
],
},
{
"id": "morr",
"name": "Morr",
"rolle": "Erinnerungssammler",
"leuchtet": true,
"farbe": Color(0.45, 0.70, 0.95),
"routine": [
[0, "haus_morr", "traeumt"],
[5, "lichtung", "sammelt"],
[9, "haus_morr", "sortiert"],
[15, "lichtung", "sammelt"],
[19, "haus_morr", "sortiert"],
[22, "haus_morr", "traeumt"],
],
},
{
"id": "ivo",
"name": "Ivo",
"rolle": "Bote",
"leuchtet": true,
"farbe": Color(0.95, 0.60, 0.75),
"routine": [
[0, "haus_ivo", "traeumt"],
[4, "tor", "beobachtet"],
[8, "lichtung", "wandert"],
[10, "haus_ivo", "ruht"],
[13, "tor", "beobachtet"],
[18, "lichtung", "singt"],
[21, "haus_ivo", "traeumt"],
],
},
{
"id": "sela",
"name": "Sela",
"rolle": "Wächterin des Tors",
"leuchtet": true,
"farbe": Color(0.55, 0.95, 0.90),
"routine": [
[0, "tor", "wacht"],
[7, "lichtung", "ruht"],
[9, "tor", "wacht"],
[17, "haus_sela", "ruht"],
[19, "lichtung", "singt"],
[21, "haus_sela", "traeumt"],
],
},
]
static func by_id(id: String) -> Dictionary:
for w in LIST:
if w["id"] == id:
return w
return {}

1
scripts/dream_db.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://b1c86jtc5qd3h

View File

@@ -1,24 +1,21 @@
extends Node3D extends Node3D
class_name DreamWorld class_name DreamWorld
## Die Traumwelt: Insel mit drei betretbaren Gebäuden und dem Tor zur Erde. ## Die Traumwelt: eine schwebende Insel mit einem Dorf. Vier Wesen wohnen hier,
## jedes in seinem Haus, jedes mit eigenem Tagesablauf.
const INNEN_Y := -300.0 var spawn := Vector3(0, 2.0, 20.0)
var bewohner: Array = []
var spawn := Vector3(0, 2.0, 14.0)
var _deko: Array = [] var _deko: Array = []
var _regale: Dictionary = {} # tech_id -> MeshInstance3D
var _stelen: Dictionary = {} # human_id -> Dictionary
func _ready() -> void: func _ready() -> void:
_environment() _environment()
_insel() _insel()
_gebaeude_bibliothek() _haeuser()
_gebaeude_missionshalle() _lichtung()
_gebaeude_meditation()
_tor_zur_erde() _tor_zur_erde()
Game.state_changed.connect(_refresh) _wesen()
_refresh()
func _environment() -> void: func _environment() -> void:
@@ -35,19 +32,19 @@ func _environment() -> void:
sky.sky_material = mat sky.sky_material = mat
env.sky = sky env.sky = sky
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_energy = 1.8 env.ambient_light_energy = 2.1
env.glow_enabled = true env.glow_enabled = true
env.glow_intensity = 0.5 env.glow_intensity = 0.32
env.glow_bloom = 0.15 env.glow_bloom = 0.05
env.fog_enabled = true env.fog_enabled = true
env.fog_light_color = Color(0.30, 0.18, 0.45) env.fog_light_color = Color(0.30, 0.18, 0.45)
env.fog_density = 0.006 env.fog_density = 0.0015
we.environment = env we.environment = env
add_child(we) add_child(we)
var sun := DirectionalLight3D.new() var sun := DirectionalLight3D.new()
sun.rotation_degrees = Vector3(-46, 25, 0) sun.rotation_degrees = Vector3(-46, 25, 0)
sun.light_energy = 1.15 sun.light_energy = 1.5
sun.light_color = Color(0.85, 0.80, 1.0) sun.light_color = Color(0.85, 0.80, 1.0)
sun.shadow_enabled = true sun.shadow_enabled = true
sun.shadow_blur = 2.0 sun.shadow_blur = 2.0
@@ -55,24 +52,23 @@ func _environment() -> void:
func _insel() -> void: func _insel() -> void:
Prims.cyl(self, Vector3(0, -1.0, 0), 50.0, 2.0, Color(0.28, 0.20, 0.42)) Prims.cyl(self, Vector3(0, -1.03, 0), 52.0, 2.0, Color(0.48, 0.40, 0.64))
Prims.cyl(self, Vector3(0, -4.0, 0), 38.0, 4.0, Color(0.20, 0.14, 0.32), false) Prims.cyl(self, Vector3(0, -4.0, 0), 40.0, 4.0, Color(0.34, 0.26, 0.50), false)
Prims.cyl(self, Vector3(0, -9.0, 0), 20.0, 6.0, Color(0.15, 0.10, 0.26), false) Prims.cyl(self, Vector3(0, -9.0, 0), 22.0, 6.0, Color(0.24, 0.17, 0.38), false)
# Wege
var wege: Array[Vector3] = [Vector3(-22, 0, -6), Vector3(0, 0, -12), Vector3(22, 0, -6), # Ringweg, auf dem die Wesen gehen
Vector3(0, 0, 8)] for i in range(36):
for w in wege: var a := TAU * float(i) / 36.0
var d: Vector3 = w.normalized() * (w.length() * 0.5) Prims.box(self, Vector3(cos(a) * 15.0, 0.03, sin(a) * 15.0),
var b := Prims.box(self, Vector3(d.x, 0.02, d.z), Vector3(2.6, 0.06, 2.6), Color(0.66, 0.58, 0.84), false)
Vector3(3.0, 0.08, w.length()), Color(0.45, 0.35, 0.62), false)
b.rotation.y = atan2(w.x, w.z)
# schwebende Deko # schwebende Deko
var rng := RandomNumberGenerator.new() var rng := RandomNumberGenerator.new()
rng.seed = 7 rng.seed = 7
for i in range(34): for i in range(34):
var a := rng.randf() * TAU var a := rng.randf() * TAU
var r := rng.randf_range(16.0, 46.0) var r := rng.randf_range(18.0, 48.0)
var p := Vector3(cos(a) * r, rng.randf_range(4.0, 22.0), sin(a) * r) var p := Vector3(cos(a) * r, rng.randf_range(5.0, 24.0), sin(a) * r)
var s := rng.randf_range(0.6, 2.0) var s := rng.randf_range(0.6, 2.0)
var c := Color(0.6 + rng.randf() * 0.3, 0.5 + rng.randf() * 0.4, 1.0) var c := Color(0.6 + rng.randf() * 0.3, 0.5 + rng.randf() * 0.4, 1.0)
var wuerfel := Prims.box(self, p, Vector3(s, s, s), c, false, 1.1) var wuerfel := Prims.box(self, p, Vector3(s, s, s), c, false, 1.1)
@@ -80,175 +76,108 @@ func _insel() -> void:
_deko.append(wuerfel) _deko.append(wuerfel)
func _haeuser() -> void:
for key in DreamDB.PLACES:
var pl: Dictionary = DreamDB.PLACES[key]
var s: Array = pl["size"]
if s[0] <= 0.0:
continue
var c := Orte.center(DreamDB.PLACES, key)
var groesse := Vector3(s[0], 3.2, s[1])
var farbe: Color = pl["color"]
Prims.room(self, c, groesse, farbe, farbe.darkened(0.35),
Orte.DIR_VEC[pl["dir"]], 3.4, true)
Prims.label(self, c + Vector3(0, 5.0, 0), pl["name"], 0.6, Color(1, 0.95, 0.85))
_einrichtung(c, groesse, farbe, key)
var licht := OmniLight3D.new()
licht.position = c + Vector3(0, 2.4, 0)
licht.omni_range = 18.0
licht.light_energy = 2.2
licht.light_color = farbe.lightened(0.5)
add_child(licht)
# Weg von der Tür zum Ring
var tuer := Orte.door(DreamDB.PLACES, key)
var weg := tuer.normalized() * 15.0 - tuer
var b := Prims.box(self, (tuer + tuer.normalized() * 15.0) * 0.5 + Vector3(0, 0.03, 0),
Vector3(2.4, 0.06, weg.length()), Color(0.66, 0.58, 0.84), false)
b.rotation.y = atan2(weg.x, weg.z)
func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
var w := groesse.x * 0.5 - 1.8
var h := groesse.z * 0.5 - 1.8
# Schlafnest hat jedes Haus
Prims.cyl(self, c + Vector3(-w, 0.3, -h), 1.4, 0.5, farbe.darkened(0.2), false)
match key:
"haus_ailen": # Webstuhl
Prims.box(self, c + Vector3(w, 1.1, 0), Vector3(0.3, 2.2, 3.0), farbe.darkened(0.45))
for i in range(5):
Prims.box(self, c + Vector3(w, 1.1, -1.2 + i * 0.6), Vector3(0.1, 1.8, 0.06),
Color(0.9, 0.85, 1.0), false, 1.4)
"haus_morr": # Regale voller Fundstücke
for i in range(3):
Prims.box(self, c + Vector3(w, 0.6 + i * 0.7, 0), Vector3(1.2, 0.12, 4.0),
farbe.darkened(0.45))
for j in range(4):
Prims.box(self, c + Vector3(w, 0.85 + i * 0.7, -1.5 + j * 1.0),
Vector3(0.4, 0.4, 0.4), Color(0.8, 0.9, 1.0), false, 0.8)
"haus_ivo": # Karten und ein Fernrohr
Prims.box(self, c + Vector3(w, 0.75, 0), Vector3(1.6, 0.12, 2.6), farbe.darkened(0.45))
var rohr := Prims.cyl(self, c + Vector3(w, 1.6, 0), 0.18, 2.0,
Color(0.85, 0.9, 1.0), false, 0.6)
rohr.rotation.x = 0.9
"haus_sela": # Schale mit Licht
Prims.cyl(self, c + Vector3(w, 0.5, 0), 1.0, 1.0, farbe.darkened(0.4), false)
Prims.sphere(self, c + Vector3(w, 1.4, 0), 0.55, Color(0.8, 1.0, 1.0), 2.0)
func _lichtung() -> void:
var c := Orte.center(DreamDB.PLACES, "lichtung")
Prims.cyl(self, c + Vector3(0, 0.04, 0), 11.0, 0.12, Color(0.60, 0.52, 0.78), false)
Prims.cyl(self, c + Vector3(0, 0.14, 0), 3.2, 0.2, Color(0.55, 0.80, 1.0, 0.5), false, 1.2)
for i in range(8):
var a := TAU * float(i) / 8.0
var p := c + Vector3(cos(a) * 8.5, 0, sin(a) * 8.5)
Prims.cyl(self, p + Vector3(0, 1.6, 0), 0.28, 3.2, Color(0.72, 0.64, 0.88))
Prims.sphere(self, p + Vector3(0, 3.6, 0), 0.42, Color(0.75, 0.85, 1.0), 2.0)
Prims.label(self, c + Vector3(0, 5.2, 0), DreamDB.PLACES["lichtung"]["name"], 0.6,
Color(1, 0.95, 0.85))
func _tor_zur_erde() -> void:
var pos := Orte.center(DreamDB.PLACES, "tor")
Prims.cyl(self, pos + Vector3(0, 0.3, 0), 5.5, 0.6, Color(0.35, 0.30, 0.50))
for i in range(10):
var a := PI * float(i) / 9.0
Prims.box(self, pos + Vector3(cos(a) * 4.2, sin(a) * 4.2 + 0.6, 0),
Vector3(0.7, 0.7, 0.7), Color(0.5, 0.85, 1.0), false, 1.8)
var scheibe := Prims.cyl(self, pos + Vector3(0, 2.6, 0), 3.0, 0.2,
Color(0.45, 0.75, 1.0, 0.28), false, 1.0)
scheibe.rotation.x = PI * 0.5
Prims.label(self, pos + Vector3(0, 6.4, 0), "Tor zur Erde", 0.8, Color(0.7, 0.95, 1))
var n := Node3D.new()
n.position = pos + Vector3(0, 1.6, 0)
add_child(n)
n.add_to_group("interactable")
n.set_meta("kind", "erde")
n.set_meta("arg", "")
n.set_meta("titel", "Tor zur Erde")
func _wesen() -> void:
for i in range(DreamDB.LIST.size()):
var w := Bewohner.new()
add_child(w)
w.setup(DreamDB.LIST[i], i, DreamDB.PLACES)
bewohner.append(w)
func _process(delta: float) -> void: func _process(delta: float) -> void:
for i in range(_deko.size()): for i in range(_deko.size()):
var n: Node3D = _deko[i] var n: Node3D = _deko[i]
n.rotation.y += delta * (0.2 + 0.05 * (i % 5)) n.rotation.y += delta * (0.2 + 0.05 * (i % 5))
n.rotation.x += delta * 0.13 n.rotation.x += delta * 0.13
n.position.y += sin(Time.get_ticks_msec() * 0.0007 + i) * delta * 0.6 n.position.y += sin(Time.get_ticks_msec() * 0.0007 + i) * delta * 0.6
# ---------------------------------------------------------------- Bausteine
## Erzeugt Außenbau + Innenraum und verbindet beide mit Tür-Zonen.
func _gebaeude(titel: String, a_center: Vector3, a_size: Vector3,
i_center: Vector3, i_size: Vector3, farbe: Color) -> Node3D:
Prims.room(self, a_center, a_size, farbe, farbe.darkened(0.3), Vector3.BACK, 3.6, false)
Prims.box(self, a_center + Vector3(0, a_size.y + 0.25, 0),
Vector3(a_size.x + 1.0, 0.5, a_size.z + 1.0), farbe.lightened(0.15))
Prims.box(self, a_center + Vector3(0, a_size.y + 1.1, 0),
Vector3(a_size.x * 0.5, 1.2, a_size.z * 0.5), farbe.lightened(0.3))
Prims.label(self, a_center + Vector3(0, a_size.y + 3.0, 0), titel, 1.4,
Color(1, 0.95, 0.8))
var aussen_tuer := a_center + Vector3(0, 0, a_size.z * 0.5)
var draussen := a_center + Vector3(0, 0.2, a_size.z * 0.5 + 3.2)
var innen := Node3D.new()
innen.name = titel.replace(" ", "_")
add_child(innen)
Prims.room(innen, i_center, i_size, farbe.lightened(0.12), farbe.darkened(0.25),
Vector3.BACK, 3.6, true)
Prims.box(innen, i_center + Vector3(0, i_size.y + 0.2, 0),
Vector3(i_size.x, 0.4, i_size.z), farbe.darkened(0.4))
for sx: float in [-1.0, 1.0]:
for sz: float in [-1.0, 1.0]:
var l := OmniLight3D.new()
l.position = i_center + Vector3(sx * i_size.x * 0.28, i_size.y - 0.8, sz * i_size.z * 0.28)
l.omni_range = 34.0
l.light_energy = 4.5
l.light_color = Color(0.90, 0.90, 1.0)
innen.add_child(l)
var innen_spawn := i_center + Vector3(0, 0.3, i_size.z * 0.5 - 3.0)
_tuer(self, aussen_tuer + Vector3(0, 1.2, 0), innen_spawn)
_tuer(innen, i_center + Vector3(0, 1.2, i_size.z * 0.5), draussen)
Prims.label(innen, i_center + Vector3(0, i_size.y - 0.9, i_size.z * 0.5 - 0.35), "Ausgang", 0.45,
Color(1, 0.85, 0.6))
return innen
func _tuer(parent: Node, pos: Vector3, ziel: Vector3) -> void:
var a := Area3D.new()
a.position = pos
var cs := CollisionShape3D.new()
var sh := BoxShape3D.new()
sh.size = Vector3(3.4, 2.4, 1.6)
cs.shape = sh
a.add_child(cs)
parent.add_child(a)
a.body_entered.connect(func(body: Node3D) -> void:
if body is Player and Game.teleport_lock <= 0.0:
Game.teleport_lock = 0.9
body.global_position = ziel
body.velocity = Vector3.ZERO)
func _interaktion(parent: Node, pos: Vector3, kind: String, arg: String, titel: String) -> Node3D:
var n := Node3D.new()
n.position = pos
parent.add_child(n)
n.add_to_group("interactable")
n.set_meta("kind", kind)
n.set_meta("arg", arg)
n.set_meta("titel", titel)
return n
# ---------------------------------------------------------------- Gebäude
func _gebaeude_bibliothek() -> void:
var innen := _gebaeude("Bibliothek der Techniken",
Vector3(-24, 0, -16), Vector3(16, 5, 14),
Vector3(0, INNEN_Y, 0), Vector3(34, 6, 24),
Color(0.58, 0.50, 0.80))
Prims.label(innen, Vector3(0, INNEN_Y + 5.0, -10.5),
"Berühre eine Technik, um ihre Hinweise zu lesen.", 0.4, Color(0.9, 0.9, 1))
var ids := TechDB.ids()
for i in range(ids.size()):
var reihe := i / 6
var spalte := i % 6
var p := Vector3(-12.5 + spalte * 5.0, INNEN_Y, -6.5 + reihe * 7.0)
var t := TechDB.by_id(ids[i])
Prims.cyl(innen, p + Vector3(0, 0.5, 0), 0.8, 1.0, Color(0.30, 0.26, 0.45))
var kristall := Prims.box(innen, p + Vector3(0, 1.8, 0), Vector3(0.9, 0.9, 0.9),
t["farbe"], false, 1.4)
kristall.rotation = Vector3(0.6, 0.8, 0.0)
_regale[ids[i]] = kristall
Prims.label(innen, p + Vector3(0, 3.2 + float(reihe) * 0.6, 0), t["name"], 0.36, Color(1, 1, 1))
_interaktion(innen, p + Vector3(0, 1.2, 0), "technik", ids[i], t["name"])
func _gebaeude_missionshalle() -> void:
var innen := _gebaeude("Missionshalle",
Vector3(0, 0, -28), Vector3(18, 5, 14),
Vector3(90, INNEN_Y, 0), Vector3(26, 6, 20),
Color(0.74, 0.55, 0.48))
Prims.label(innen, Vector3(90, INNEN_Y + 5.0, -8.5),
"Menschen, deren Traum feststeckt.", 0.42, Color(1, 0.92, 0.8))
for i in range(HumanDB.LIST.size()):
var h: Dictionary = HumanDB.LIST[i]
var p := Vector3(90.0 + (float(i) - 1.5) * 7.0, INNEN_Y, -3.0)
Prims.cyl(innen, p + Vector3(0, 0.4, 0), 1.2, 0.8, Color(0.35, 0.28, 0.26))
var saeule := Prims.box(innen, p + Vector3(0, 2.0, 0), Vector3(1.4, 3.2, 1.4),
h["farbe"], false, 0.8)
var sphere := Prims.sphere(innen, p + Vector3(0, 4.1, 0), 0.55,
(h["farbe"] as Color).lightened(0.4), 1.6)
var lab := Prims.label(innen, p + Vector3(0, 5.2, 0), h["name"], 0.32, Color(1, 1, 1))
_stelen[h["id"]] = {"saeule": saeule, "kugel": sphere, "label": lab}
_interaktion(innen, p + Vector3(0, 1.5, 0), "mission", h["id"], h["name"])
func _gebaeude_meditation() -> void:
var innen := _gebaeude("Meditationskammer",
Vector3(24, 0, -16), Vector3(14, 5, 14),
Vector3(-90, INNEN_Y, 0), Vector3(20, 6, 18),
Color(0.46, 0.66, 0.74))
Prims.label(innen, Vector3(-90, INNEN_Y + 5.0, -7.5),
"Übung vertieft, was du schon kannst.", 0.42, Color(0.85, 1, 1))
Prims.cyl(innen, Vector3(-90, INNEN_Y + 0.5, -1.0), 3.0, 1.0, Color(0.24, 0.34, 0.40))
var kugel := Prims.sphere(innen, Vector3(-90, INNEN_Y + 2.6, -1.0), 1.2,
Color(0.55, 0.95, 1.0), 1.8)
kugel.name = "Meditationskugel"
_interaktion(innen, Vector3(-90, INNEN_Y + 1.5, -1.0), "vertiefen", "", "Meditationskugel")
func _tor_zur_erde() -> void:
var pos := Vector3(0, 0, 18)
Prims.cyl(self, pos + Vector3(0, 0.3, 0), 5.0, 0.6, Color(0.35, 0.30, 0.50))
for i in range(10):
var a := PI * float(i) / 9.0
Prims.box(self, pos + Vector3(cos(a) * 4.2, sin(a) * 4.2 + 0.6, 0),
Vector3(0.7, 0.7, 0.7), Color(0.5, 0.85, 1.0), false, 1.8)
var scheibe := Prims.cyl(self, pos + Vector3(0, 2.6, 0), 3.0, 0.2,
Color(0.45, 0.75, 1.0, 0.28), false, 1.0)
scheibe.rotation.x = PI * 0.5
Prims.label(self, pos + Vector3(0, 6.4, 0), "Tor zur Erde", 0.7, Color(0.7, 0.95, 1))
_interaktion(self, pos + Vector3(0, 1.6, 0), "erde", "", "Tor zur Erde")
# ---------------------------------------------------------------- Aktualisieren
func _refresh() -> void:
for id in _regale:
var m: MeshInstance3D = _regale[id]
var t := TechDB.by_id(id)
if Game.knows(id):
m.material_override = Prims.mat(t["farbe"], 2.2)
m.scale = Vector3.ONE * (1.0 + 0.12 * Game.skill_of(id))
else:
m.material_override = Prims.mat((t["farbe"] as Color).darkened(0.35), 0.5)
m.scale = Vector3.ONE * 0.75
for id in _stelen:
var s: Dictionary = _stelen[id]
var h := HumanDB.by_id(id)
var lab: Label3D = s["label"]
if Game.completed.has(id):
s["kugel"].material_override = Prims.mat(Color(0.5, 1.0, 0.6), 2.4)
lab.text = h["name"] + "\n★ erfüllt"
elif Game.active_mission == id:
s["kugel"].material_override = Prims.mat(Color(1, 0.85, 0.35), 2.4)
lab.text = h["name"] + "\naktive Mission · %d %%" % int(Game.mission_progress(id))
else:
s["kugel"].material_override = Prims.mat((h["farbe"] as Color).lightened(0.4), 1.2)
lab.text = h["name"] + "\nwartet"

View File

@@ -44,7 +44,7 @@ func _environment() -> void:
func _boden() -> void: func _boden() -> void:
Prims.box(self, Vector3(0, -0.5, 0), Vector3(240, 1, 240), Color(0.36, 0.52, 0.30)) Prims.box(self, Vector3(0, -0.53, 0), Vector3(240, 1, 240), Color(0.36, 0.52, 0.30))
# Ringweg, auf dem die Menschen gehen # Ringweg, auf dem die Menschen gehen
for i in range(36): for i in range(36):
var a := TAU * float(i) / 36.0 var a := TAU * float(i) / 36.0
@@ -57,16 +57,16 @@ func _orte() -> void:
if key == "park": if key == "park":
continue continue
var pl: Dictionary = HumanDB.PLACES[key] var pl: Dictionary = HumanDB.PLACES[key]
var c := HumanDB.place_center(key) var c := Orte.center(HumanDB.PLACES, key)
var s: Array = pl["size"] var s: Array = pl["size"]
var groesse := Vector3(s[0], 2.3, s[1]) var groesse := Vector3(s[0], 2.3, s[1])
var farbe: Color = pl["color"] var farbe: Color = pl["color"]
var richtung: Vector3 = HumanDB.DIR_VEC[pl["dir"]] var richtung: Vector3 = Orte.DIR_VEC[pl["dir"]]
Prims.room(self, c, groesse, farbe, farbe.darkened(0.35), richtung, 3.4, true) Prims.room(self, c, groesse, farbe, farbe.darkened(0.35), richtung, 3.4, true)
Prims.label(self, c + Vector3(0, 5.0, 0), pl["name"], 0.6, Color(1, 1, 0.9)) Prims.label(self, c + Vector3(0, 5.0, 0), pl["name"], 0.6, Color(1, 1, 0.9))
_einrichtung(c, groesse, farbe, key) _einrichtung(c, groesse, farbe, key)
var tuer := HumanDB.place_door(key) var tuer := Orte.door(HumanDB.PLACES, key)
var weg := tuer.normalized() * 15.0 - tuer var weg := tuer.normalized() * 15.0 - tuer
var b := Prims.box(self, (tuer + tuer.normalized() * 15.0) * 0.5 + Vector3(0, 0.03, 0), var b := Prims.box(self, (tuer + tuer.normalized() * 15.0) * 0.5 + Vector3(0, 0.03, 0),
Vector3(2.6, 0.06, weg.length()), Color(0.62, 0.58, 0.50), false) Vector3(2.6, 0.06, weg.length()), Color(0.62, 0.58, 0.50), false)
@@ -152,9 +152,9 @@ func _saeule() -> void:
func _menschen() -> void: func _menschen() -> void:
for i in range(HumanDB.LIST.size()): for i in range(HumanDB.LIST.size()):
var m := Human.new() var m := Bewohner.new()
add_child(m) add_child(m)
m.setup(HumanDB.LIST[i], i) m.setup(HumanDB.LIST[i], i, HumanDB.PLACES)
humans.append(m) humans.append(m)

View File

@@ -34,7 +34,7 @@ func _setup_input() -> void:
"steigen": [KEY_SPACE], "steigen": [KEY_SPACE],
"sinken": [KEY_SHIFT], "sinken": [KEY_SHIFT],
"interagieren": [KEY_E], "interagieren": [KEY_E],
"kodex": [KEY_TAB], "journal": [KEY_J, KEY_TAB],
} }
for action in binds: for action in binds:
if not InputMap.has_action(action): if not InputMap.has_action(action):

View File

@@ -1 +0,0 @@
uid://let5f4ntq0j8

View File

@@ -1,5 +1,5 @@
class_name HumanDB class_name HumanDB
## Orte der Kleinstadt und die Menschen mit ihren Tagesabläufen. ## Die Kleinstadt auf der Erde und die Menschen mit ihren Tagesabläufen.
## Gebäude liegen im Ring um den Park. Alle Türen zeigen zur Mitte. ## Gebäude liegen im Ring um den Park. Alle Türen zeigen zur Mitte.
## pos = Standplatz drinnen, size = Grundfläche, dir = Richtung der Türlücke. ## pos = Standplatz drinnen, size = Grundfläche, dir = Richtung der Türlücke.
@@ -42,31 +42,6 @@ static var PLACES: Dictionary = {
}, },
} }
static var DIR_VEC: Dictionary = {
"back": Vector3(0, 0, 1),
"forward": Vector3(0, 0, -1),
"left": Vector3(-1, 0, 0),
"right": Vector3(1, 0, 0),
"none": Vector3(0, 0, 0),
}
static var ACTIVITY_TEXT: Dictionary = {
"schlaeft": "schläft",
"morgenroutine": "Morgenroutine",
"arbeitet": "arbeitet",
"pause": "macht Pause",
"essen": "isst",
"gruebelt": "grübelt",
"ruht": "ruht sich aus",
"angespannt": "ist angespannt",
"abendroutine": "Abendroutine",
"aufraeumen": "räumt auf",
"wartet": "wartet",
"muede": "ist müde",
"unterwegs": "geht spazieren",
"zoegert": "zögert",
}
static var LIST: Array = [ static var LIST: Array = [
{ {
"id": "lena", "id": "lena",
@@ -76,6 +51,7 @@ static var LIST: Array = [
"traum": "Ihr Bilderbuch endlich zu Ende zeichnen.", "traum": "Ihr Bilderbuch endlich zu Ende zeichnen.",
"problem": "Sie schiebt die letzten zwölf Seiten seit acht Monaten vor sich her. Jeder Entwurf kommt ihr zu schlecht vor, also fängt sie lieber gar nicht erst an.", "problem": "Sie schiebt die letzten zwölf Seiten seit acht Monaten vor sich her. Jeder Entwurf kommt ihr zu schlecht vor, also fängt sie lieber gar nicht erst an.",
"tags": ["prokrastination", "selbstzweifel", "ablenkung"], "tags": ["prokrastination", "selbstzweifel", "ablenkung"],
"mission": true,
"farbe": Color(0.85, 0.45, 0.55), "farbe": Color(0.85, 0.45, 0.55),
"routine": [ "routine": [
[0, "haus_lena", "schlaeft"], [0, "haus_lena", "schlaeft"],
@@ -100,6 +76,7 @@ static var LIST: Array = [
"traum": "Wieder Klavier spielen wie mit zwanzig.", "traum": "Wieder Klavier spielen wie mit zwanzig.",
"problem": "Das Klavier steht zugestellt im Flur. Nach der Frühschicht ist er zu müde, um auch nur den Deckel zu öffnen.", "problem": "Das Klavier steht zugestellt im Flur. Nach der Frühschicht ist er zu müde, um auch nur den Deckel zu öffnen.",
"tags": ["gewohnheit", "erschoepfung", "ablenkung"], "tags": ["gewohnheit", "erschoepfung", "ablenkung"],
"mission": true,
"farbe": Color(0.55, 0.45, 0.30), "farbe": Color(0.55, 0.45, 0.30),
"routine": [ "routine": [
[0, "haus_bruno", "schlaeft"], [0, "haus_bruno", "schlaeft"],
@@ -122,6 +99,7 @@ static var LIST: Array = [
"traum": "Ihre Abschlussarbeit vortragen, ohne zu erstarren.", "traum": "Ihre Abschlussarbeit vortragen, ohne zu erstarren.",
"problem": "Die Arbeit ist längst fertig. Aber vor jedem Vortrag wird ihr schwarz vor Augen, und sie meldet sich wieder ab.", "problem": "Die Arbeit ist längst fertig. Aber vor jedem Vortrag wird ihr schwarz vor Augen, und sie meldet sich wieder ab.",
"tags": ["angst", "ueberforderung", "selbstzweifel"], "tags": ["angst", "ueberforderung", "selbstzweifel"],
"mission": true,
"farbe": Color(0.35, 0.60, 0.85), "farbe": Color(0.35, 0.60, 0.85),
"routine": [ "routine": [
[0, "haus_mira", "schlaeft"], [0, "haus_mira", "schlaeft"],
@@ -145,6 +123,7 @@ static var LIST: Array = [
"traum": "Abends wieder Zeit für seine Tochter haben.", "traum": "Abends wieder Zeit für seine Tochter haben.",
"problem": "Er sagt zu jeder Bitte ja und sitzt dafür jeden Abend bis acht im Büro. Zu Hause ist er nur noch müde.", "problem": "Er sagt zu jeder Bitte ja und sitzt dafür jeden Abend bis acht im Büro. Zu Hause ist er nur noch müde.",
"tags": ["stress", "grenzen", "erschoepfung"], "tags": ["stress", "grenzen", "erschoepfung"],
"mission": true,
"farbe": Color(0.40, 0.55, 0.45), "farbe": Color(0.40, 0.55, 0.45),
"routine": [ "routine": [
[0, "haus_tobias", "schlaeft"], [0, "haus_tobias", "schlaeft"],
@@ -166,41 +145,3 @@ static func by_id(id: String) -> Dictionary:
if h["id"] == id: if h["id"] == id:
return h return h
return {} return {}
static func place_center(key: String) -> Vector3:
var p: Array = PLACES[key]["pos"]
return Vector3(p[0], 0.0, p[1])
## Punkt direkt vor der Türlücke, außerhalb des Gebäudes.
static func place_door(key: String) -> Vector3:
var pl: Dictionary = PLACES[key]
var c := place_center(key)
var d: Vector3 = DIR_VEC[pl["dir"]]
if d == Vector3.ZERO:
return c
var size: Array = pl["size"]
var half: float = size[1] * 0.5 if absf(d.z) > 0.0 else size[0] * 0.5
return c + d * (half + 2.5)
## Route von Ort a nach Ort b: aus der Tür, über den Platz, zur neuen Tür, hinein.
static func route(a: String, b: String, offset: Vector3) -> Array:
var pts: Array = []
if a == b:
return pts
var da := place_door(a)
var db := place_door(b)
pts.append(da)
if da.length() > 17.0:
pts.append(da.normalized() * 15.0)
if db.length() > 17.0:
pts.append(db.normalized() * 15.0)
pts.append(db)
pts.append(place_center(b) + offset)
return pts
static func activity_text(a: String) -> String:
return ACTIVITY_TEXT.get(a, a)

View File

@@ -100,18 +100,11 @@ func _fokus_suchen() -> void:
func _hinweis_setzen() -> void: func _hinweis_setzen() -> void:
if _fokus_mensch != null: if _fokus_mensch != null:
ui.setze_hinweis("[E] %s ansprechen — %s" % ui.setze_hinweis("[E] %s ansprechen — %s" %
[_fokus_mensch.data["name"], HumanDB.activity_text(_fokus_mensch.activity)]) [_fokus_mensch.data["name"], Orte.text(_fokus_mensch.activity)])
elif _fokus != null: elif _fokus != null:
var kind: String = _fokus.get_meta("kind") var kind: String = _fokus.get_meta("kind")
var titel: String = _fokus.get_meta("titel") var titel: String = _fokus.get_meta("titel")
match kind: match kind:
"technik":
ui.setze_hinweis("[E] %s%s" % [titel,
"" if not Game.knows(_fokus.get_meta("arg")) else " (gelernt)"])
"mission":
ui.setze_hinweis("[E] %s — Mission ansehen" % titel)
"vertiefen":
ui.setze_hinweis("[E] Meditieren")
"erde": "erde":
ui.setze_hinweis("[E] Zur Erde reisen") ui.setze_hinweis("[E] Zur Erde reisen")
"traum": "traum":
@@ -135,8 +128,8 @@ func _unhandled_input(event: InputEvent) -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
player.look_enabled = true player.look_enabled = true
return return
if event.is_action_pressed("kodex"): if event.is_action_pressed("journal"):
ui.kodex_panel() ui.journal_panel()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
return return
if event.is_action_pressed("interagieren"): if event.is_action_pressed("interagieren"):
@@ -151,14 +144,7 @@ func _interagieren() -> void:
if _fokus == null: if _fokus == null:
return return
var kind: String = _fokus.get_meta("kind") var kind: String = _fokus.get_meta("kind")
var arg: String = _fokus.get_meta("arg")
match kind: match kind:
"technik":
ui.technik_panel(arg)
"mission":
ui.mission_panel(arg)
"vertiefen":
ui.vertiefen_panel()
"erde": "erde":
if Game.active_mission == "" and Game.completed.size() < HumanDB.LIST.size(): if Game.active_mission == "" and Game.completed.size() < HumanDB.LIST.size():
Game.notified.emit( Game.notified.emit(

75
scripts/orte.gd Normal file
View File

@@ -0,0 +1,75 @@
class_name Orte
## Gemeinsame Geometrie für beide Welten: Orte, Türen, Wege, Tätigkeiten.
static var DIR_VEC: Dictionary = {
"back": Vector3(0, 0, 1),
"forward": Vector3(0, 0, -1),
"left": Vector3(-1, 0, 0),
"right": Vector3(1, 0, 0),
"none": Vector3(0, 0, 0),
}
static var ACTIVITY_TEXT: Dictionary = {
# Erde
"schlaeft": "schläft",
"morgenroutine": "Morgenroutine",
"arbeitet": "arbeitet",
"pause": "macht Pause",
"essen": "isst",
"gruebelt": "grübelt",
"ruht": "ruht sich aus",
"angespannt": "ist angespannt",
"abendroutine": "Abendroutine",
"aufraeumen": "räumt auf",
"wartet": "wartet",
"muede": "ist müde",
"unterwegs": "geht spazieren",
"zoegert": "zögert",
# Traumwelt
"webt": "webt Traumfäden",
"sammelt": "sammelt Erinnerungen",
"sortiert": "sortiert, was verloren ging",
"beobachtet": "schaut zur Erde hinunter",
"wacht": "hält Wache am Tor",
"singt": "singt",
"wandert": "wandert über die Insel",
"traeumt": "träumt",
}
static func text(a: String) -> String:
return ACTIVITY_TEXT.get(a, a)
static func center(places: Dictionary, key: String) -> Vector3:
var p: Array = places[key]["pos"]
return Vector3(p[0], 0.0, p[1])
## Punkt direkt vor der Türlücke, außerhalb des Gebäudes.
static func door(places: Dictionary, key: String) -> Vector3:
var pl: Dictionary = places[key]
var c := center(places, key)
var d: Vector3 = DIR_VEC[pl["dir"]]
if d == Vector3.ZERO:
return c
var size: Array = pl["size"]
var half: float = size[1] * 0.5 if absf(d.z) > 0.0 else size[0] * 0.5
return c + d * (half + 2.5)
## Route von Ort a nach Ort b: aus der Tür, über den freien Platz, wieder hinein.
static func route(places: Dictionary, a: String, b: String, offset: Vector3) -> Array:
var pts: Array = []
if a == b:
return pts
var da := door(places, a)
var db := door(places, b)
pts.append(da)
if da.length() > 17.0:
pts.append(da.normalized() * 15.0)
if db.length() > 17.0:
pts.append(db.normalized() * 15.0)
pts.append(db)
pts.append(center(places, b) + offset)
return pts

1
scripts/orte.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://ck43yu1d5s856

View File

@@ -51,11 +51,15 @@ func _ready() -> void:
pivot.position.y = 1.5 pivot.position.y = 1.5
add_child(pivot) add_child(pivot)
arm = SpringArm3D.new() arm = SpringArm3D.new()
arm.position.x = 0.75
arm.spring_length = 6.5 arm.spring_length = 6.5
arm.margin = 0.3 arm.margin = 0.2
var arm_form := SphereShape3D.new()
arm_form.radius = 0.45 # Kugel statt Strahl: die Kamera klemmt nicht in Wänden
arm.shape = arm_form
pivot.add_child(arm) pivot.add_child(arm)
arm.add_excluded_object(get_rid())
cam = Camera3D.new() cam = Camera3D.new()
cam.position.x = 0.7 # Blick über die Schulter, am Ende des Arms
cam.fov = 74.0 cam.fov = 74.0
arm.add_child(cam) arm.add_child(cam)
set_mode(Mode.TRAUM) set_mode(Mode.TRAUM)

View File

@@ -97,7 +97,7 @@ static func label(parent: Node, pos: Vector3, text: String, size: float = 0.35,
l.billboard = BaseMaterial3D.BILLBOARD_ENABLED l.billboard = BaseMaterial3D.BILLBOARD_ENABLED
l.no_depth_test = false l.no_depth_test = false
l.modulate = c l.modulate = c
l.outline_size = 24 l.outline_size = 10
l.outline_modulate = Color(0, 0, 0, 0.85) l.outline_modulate = Color(0, 0, 0, 0.85)
l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
parent.add_child(l) parent.add_child(l)
@@ -112,7 +112,7 @@ static func room(parent: Node, center: Vector3, size: Vector3, wall_c: Color,
var t := 0.4 var t := 0.4
var h := size.y var h := size.y
if with_floor: if with_floor:
box(parent, center + Vector3(0, 0.04, 0), Vector3(size.x, 0.2, size.z), floor_c) box(parent, center + Vector3(0, -0.1, 0), Vector3(size.x, 0.2, size.z), floor_c)
var seiten: Array[Vector3] = [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT] var seiten: Array[Vector3] = [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT]
for d in seiten: for d in seiten:
var quer := absf(d.z) > 0.0 # Wand liegt entlang der X-Achse var quer := absf(d.z) > 0.0 # Wand liegt entlang der X-Achse

View File

@@ -154,7 +154,7 @@ static func tags_text(tags: Array) -> String:
static func momente_text(momente: Array) -> String: static func momente_text(momente: Array) -> String:
var out: Array = [] var out: Array = []
for m in momente: for m in momente:
out.append(HumanDB.activity_text(m)) out.append(Orte.text(m))
return ", ".join(out) return ", ".join(out)

View File

@@ -18,6 +18,9 @@ var _titel: Label
var _content: VBoxContainer var _content: VBoxContainer
var _scroll: ScrollContainer var _scroll: ScrollContainer
var _apply_human: Node3D var _apply_human: Node3D
var _journal_seite := "techniken"
var _scroll_wunsch := 0
var _scroll_frames := 0
var _on_travel: Callable var _on_travel: Callable
@@ -45,7 +48,7 @@ func _baue_hud() -> void:
add_child(_hud) add_child(_hud)
var steuerung := Label.new() var steuerung := Label.new()
steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · Tab Kodex · Esc Maus frei" steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · J Journal · Esc Maus frei"
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT) steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
steuerung.position = Vector2(-640, 16) steuerung.position = Vector2(-640, 16)
steuerung.size = Vector2(620, 24) steuerung.size = Vector2(620, 24)
@@ -130,6 +133,9 @@ func _process(_delta: float) -> void:
var wunsch := _content.get_combined_minimum_size().y var wunsch := _content.get_combined_minimum_size().y
var platz := get_viewport().get_visible_rect().size.y - 220.0 var platz := get_viewport().get_visible_rect().size.y - 220.0
_scroll.custom_minimum_size.y = clampf(wunsch, 120.0, platz) _scroll.custom_minimum_size.y = clampf(wunsch, 120.0, platz)
if _scroll_frames > 0:
_scroll.scroll_vertical = _scroll_wunsch
_scroll_frames -= 1
_panel.position = (get_viewport().get_visible_rect().size - _panel.size) * 0.5 _panel.position = (get_viewport().get_visible_rect().size - _panel.size) * 0.5
@@ -148,7 +154,7 @@ func _hud_aktualisieren() -> void:
elif Game.completed.size() >= HumanDB.LIST.size(): elif Game.completed.size() >= HumanDB.LIST.size():
zeilen.append("[color=#88ff99]Alle Träume erfüllt.[/color]") zeilen.append("[color=#88ff99]Alle Träume erfüllt.[/color]")
else: else:
zeilen.append("[color=#aaaaaa]Keine Mission. Missionshalle in der Traumwelt.[/color]") zeilen.append("[color=#aaaaaa]Keine Mission. Journal öffnen mit J.[/color]")
_hud.text = "\n".join(zeilen) _hud.text = "\n".join(zeilen)
@@ -242,88 +248,96 @@ func _hinweis_block(t: Dictionary) -> String:
[t["was"], t["wann"], t["warum"]]) [t["was"], t["wann"], t["warum"]])
# -------------------------------------------------------- Bibliothek # -------------------------------------------------------- Journal
func technik_panel(id: String) -> void: ## Das Journal ist immer dabei. Hier werden Techniken gelernt und vertieft
var t := TechDB.by_id(id) ## und Missionen angenommen.
_leeren(t["name"]) func journal_panel(seite: String = "") -> void:
_text(_hinweis_block(t)) if seite != "":
_trenner() _journal_seite = seite
_text("[color=#9fb6ff]Hilft bei:[/color] %s\n[color=#9fb6ff]Gute Momente:[/color] %s" % var scroll_alt := _scroll.scroll_vertical if offen and _kind == "journal" else 0
[TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 15) _leeren("Journal")
if Game.knows(id): _reiter()
_text("[color=#88ff99]Gelernt — Stufe %d (%s)[/color]" % if _journal_seite == "missionen":
[Game.skill_of(id), Game.xp_to_next(id)]) _journal_missionen()
else: else:
_text("[color=#ffcc66]Noch nicht gelernt. Kosten: 1 Einsicht (du hast %d).[/color]" % _journal_techniken()
Game.einsicht) _zeigen("journal")
_knopf("Technik lernen (1 Einsicht)", func() -> void: _scroll_wunsch = scroll_alt
if Game.learn(id): _scroll_frames = 4
technik_panel(id), Game.einsicht >= 1)
_zeigen("technik")
# -------------------------------------------------------- Missionshalle func _reiter() -> void:
var hb := HBoxContainer.new()
func mission_panel(id: String) -> void: hb.add_theme_constant_override("separation", 8)
var h := HumanDB.by_id(id) _content.add_child(hb)
_leeren("%s, %d — %s" % [h["name"], h["alter"], h["beruf"]]) for eintrag in [["techniken", "Techniken"], ["missionen", "Missionen"]]:
_text("[b]Traum:[/b] %s" % h["traum"]) var b := Button.new()
_text("[b]Was im Weg steht:[/b] %s" % h["problem"]) b.text = eintrag[1]
_text("[color=#9fb6ff]Themen:[/color] %s" % TechDB.tags_text(h["tags"]), 15) b.custom_minimum_size = Vector2(0, 36)
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
b.add_theme_font_size_override("font_size", 17)
b.disabled = _journal_seite == eintrag[0]
if b.disabled:
b.add_theme_color_override("font_disabled_color", Color(1, 0.88, 0.55))
hb.add_child(b)
if not b.disabled:
b.pressed.connect(func() -> void: journal_panel(eintrag[0]))
var mission := "keine"
if Game.active_mission != "":
mission = "%s (%d %%)" % [HumanDB.by_id(Game.active_mission)["name"],
int(Game.mission_progress(Game.active_mission))]
_text("[color=#aaaaaa]Einsicht: [b]%d[/b] · Mission: %s[/color]" %
[Game.einsicht, mission], 15)
_trenner() _trenner()
if Game.completed.has(id):
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]")
elif Game.active_mission == id:
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" % int(Game.mission_progress(id)))
else:
_text("Fortschritt: %d %%" % int(Game.mission_progress(id)))
_knopf("Mission annehmen", func() -> void:
Game.accept_mission(id)
mission_panel(id))
_zeigen("mission")
# -------------------------------------------------------- Meditation func _journal_techniken() -> void:
_text("Zwölf Wege, die Menschen wirklich weiterbringen. Jede Anwendung " +
func vertiefen_panel() -> void: "draußen macht eine Technik von allein besser. Vertiefen geht auch hier.", 15)
_leeren("Meditationskammer")
_text("Übung kostet 1 Einsicht und bringt 3 Erfahrungspunkte. Du hast [b]%d[/b] Einsicht." %
Game.einsicht)
_trenner() _trenner()
if Game.known.is_empty():
_text("[color=#ffcc66]Du kennst noch keine Technik. Geh in die Bibliothek.[/color]")
for id in TechDB.ids(): for id in TechDB.ids():
if not Game.knows(id):
continue
var t := TechDB.by_id(id) var t := TechDB.by_id(id)
_text("[b]%s[/b] — Stufe %d (%s)" % [t["name"], Game.skill_of(id), Game.xp_to_next(id)], 16) if Game.knows(id):
_knopf("Vertiefen (1 Einsicht)", func() -> void: _text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
if Game.deepen(id): [t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
vertiefen_panel(), Game.einsicht >= 1) else:
_zeigen("vertiefen") _text("[b][color=#9aa0b8]%s[/color][/b] · noch nicht gelernt" % t["name"], 18)
# -------------------------------------------------------- Kodex
func kodex_panel() -> void:
_leeren("Kodex der Techniken")
if Game.known.is_empty():
_text("Du kennst noch keine Technik. In der Bibliothek der Traumwelt liegen zwölf.")
for id in TechDB.ids():
if not Game.knows(id):
continue
var t := TechDB.by_id(id)
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
[t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
_text(_hinweis_block(t), 15) _text(_hinweis_block(t), 15)
_text("[color=#9fb6ff]Hilft bei:[/color] %s [color=#9fb6ff]Momente:[/color] %s" % _text("[color=#9fb6ff]Hilft bei:[/color] %s [color=#9fb6ff]Momente:[/color] %s" %
[TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 14) [TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 14)
if Game.knows(id):
_knopf("Vertiefen (1 Einsicht)", func() -> void:
if Game.deepen(id):
journal_panel(), Game.einsicht >= 1)
else:
_knopf("Lernen (1 Einsicht)", func() -> void:
if Game.learn(id):
journal_panel(), Game.einsicht >= 1)
_trenner()
func _journal_missionen() -> void:
_text("Menschen, deren Traum feststeckt.", 15)
_trenner()
for h in HumanDB.LIST:
var id: String = h["id"]
_text("[b][color=#ffe3a0]%s[/color][/b], %d — %s" %
[h["name"], h["alter"], h["beruf"]], 18)
_text("[b]Traum:[/b] %s" % h["traum"], 16)
_text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15)
_text("[color=#9fb6ff]Themen:[/color] %s" % TechDB.tags_text(h["tags"]), 14)
if Game.completed.has(id):
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]", 15)
elif Game.active_mission == id:
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" %
int(Game.mission_progress(id)), 15)
else:
_text("Fortschritt: %d %%" % int(Game.mission_progress(id)), 15)
_knopf("Mission annehmen", func() -> void:
Game.accept_mission(id)
journal_panel())
_trenner() _trenner()
var fehlend := TechDB.LIST.size() - Game.known.size()
if fehlend > 0:
_text("[color=#aaaaaa]%d weitere Techniken warten in der Bibliothek.[/color]" % fehlend, 15)
_zeigen("kodex")
# -------------------------------------------------------- Anwenden auf der Erde # -------------------------------------------------------- Anwenden auf der Erde
@@ -332,7 +346,7 @@ func anwenden_panel(mensch: Node3D) -> void:
_apply_human = mensch _apply_human = mensch
var h: Dictionary = mensch.data var h: Dictionary = mensch.data
var aktiv: String = mensch.activity var aktiv: String = mensch.activity
_leeren("%s — %s" % [h["name"], HumanDB.activity_text(aktiv)]) _leeren("%s — %s" % [h["name"], Orte.text(aktiv)])
_text("[b]Traum:[/b] %s" % h["traum"], 16) _text("[b]Traum:[/b] %s" % h["traum"], 16)
if Game.active_mission == h["id"]: if Game.active_mission == h["id"]:
_text("Fortschritt: [b]%d %%[/b]" % int(Game.mission_progress(h["id"])), 16) _text("Fortschritt: [b]%d %%[/b]" % int(Game.mission_progress(h["id"])), 16)
@@ -404,11 +418,14 @@ func intro_panel(on_close: Callable) -> void:
_text("Du bist ein Wesen aus der Traumwelt. Menschen tragen Träume mit sich herum, " + _text("Du bist ein Wesen aus der Traumwelt. Menschen tragen Träume mit sich herum, " +
"die sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.") "die sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.")
_trenner() _trenner()
_text("[b]Dein Journal — Taste J[/b]\n" +
"· Es ist immer dabei, in beiden Welten.\n" +
"· [b]Techniken[/b] — lerne echte Lösungswege. Jede sagt dir, was sie tut, " +
"wann sie passt und warum sie hilft. Anwenden macht sie von allein besser.\n" +
"· [b]Missionen[/b] — nimm einen Menschen an, dem du helfen willst.")
_text("[b]In der Traumwelt[/b]\n" + _text("[b]In der Traumwelt[/b]\n" +
"· [b]Bibliothek der Techniken[/b] — lerne echte Lösungswege. Jede Technik sagt dir, " + "· Ein Dorf auf einer schwebenden Insel. Vier Wesen wohnen hier, jedes in " +
"was sie tut, wann sie passt und warum sie hilft.\n" + "seinem Haus, jedes mit eigenem Tagesablauf. Die Häuser stehen offen.\n" +
"· [b]Missionshalle[/b] — nimm einen Menschen an, dem du helfen willst.\n" +
"· [b]Meditationskammer[/b] — vertiefe, was du schon kannst.\n" +
"· [b]Tor zur Erde[/b] — reise hinab, sobald du eine Mission hast.") "· [b]Tor zur Erde[/b] — reise hinab, sobald du eine Mission hast.")
_text("[b]Auf der Erde[/b]\n" + _text("[b]Auf der Erde[/b]\n" +
"· Du bist ein unsichtbarer Geist und schwebst durch Wände. Leertaste hoch, Umschalt runter.\n" + "· Du bist ein unsichtbarer Geist und schwebst durch Wände. Leertaste hoch, Umschalt runter.\n" +

View File

@@ -21,61 +21,78 @@ func _ready() -> void:
await knipse("01_intro") await knipse("01_intro")
main.ui.schliessen() main.ui.schliessen()
main.player.global_position = Vector3(0, 3.0, 26.0) main.player.global_position = Vector3(0, 4.0, 34.0)
main.player.yaw = 0.0 main.player.yaw = 0.0
main.player.pitch = -0.10 main.player.pitch = -0.14
await warte(20) Game.time_h = 10.0
await warte(30)
await knipse("02_traumwelt") await knipse("02_traumwelt")
main.player.global_position = Vector3(-24, 2.0, 4.0) main.player.global_position = Vector3(0, 12.0, 30.0)
main.player.yaw = 0.0 main.player.pitch = -0.5
main.player.pitch = -0.02
await warte(20) await warte(20)
await knipse("03_bibliothek_aussen") await knipse("03_traumdorf_ueberblick")
Game.teleport_lock = 0.0 var haus := Orte.center(DreamDB.PLACES, "haus_ailen")
main.player.global_position = Vector3(0, -299.0, 2.0) main.player.global_position = haus + Vector3(0, 2.0, 12.0)
main.player.yaw = 0.0 main.player.yaw = 0.0
main.player.pitch = -0.12 main.player.pitch = -0.18
await warte(30) await warte(20)
await knipse("04_bibliothek_innen") await knipse("04_haus_aussen")
main.ui.technik_panel("pomodoro") # ein Wesen in seinem Haus
var ailen: Bewohner = null
for w in main.welt.bewohner:
if w.data["id"] == "ailen":
ailen = w
for i in range(900):
main._process(0.05)
for k in main.welt.get_children():
if k is Bewohner:
k._process(0.05)
Game.time_h = 10.0
if ailen.place == "haus_ailen" and ailen._path.is_empty():
break
main.player.global_position = ailen.global_position + Vector3(3.0, 0.6, 1.2)
main.player.yaw = -PI * 0.32
main.player.pitch = -0.06
await warte(20)
await knipse("05_haus_innen")
main.ui.journal_panel("techniken")
await warte(6) await warte(6)
await knipse("05_technik_hinweise") await knipse("06_journal_techniken")
main.ui.schliessen() main.ui.schliessen()
Game.learn("pomodoro") Game.learn("pomodoro")
Game.learn("zerlegen") Game.learn("zerlegen")
Game.learn("box_atmung") Game.learn("box_atmung")
main.player.global_position = Vector3(90, -299.0, 2.0) main.ui.journal_panel("missionen")
main.player.yaw = 0.0
main.player.pitch = -0.08
await warte(20)
await knipse("06_missionshalle")
main.ui.mission_panel("lena")
await warte(6) await warte(6)
await knipse("07_mission_panel") await knipse("07_journal_missionen")
main.ui.schliessen() main.ui.schliessen()
Game.accept_mission("lena") Game.accept_mission("lena")
main.ui.kodex_panel() # Lichtung, wenn sich die Wesen dort treffen
await warte(6) for i in range(1200):
await knipse("08_kodex") main._process(0.05)
main.ui.schliessen() for k in main.welt.get_children():
if k is Bewohner:
Game.teleport_lock = 0.0 k._process(0.05)
main.player.global_position = Vector3(-90, -299.0, 5.0) Game.time_h = 19.5
main.player.global_position = Vector3(0.0, 1.0, 22.0)
main.player.yaw = 0.0 main.player.yaw = 0.0
main.player.pitch = -0.1 main.player.pitch = -0.20
await warte(20) await warte(20)
await knipse("08b_meditation") await knipse("08_lichtung")
main.ui.vertiefen_panel()
await warte(6) var tor := Orte.center(DreamDB.PLACES, "tor")
await knipse("08c_vertiefen") main.player.global_position = tor + Vector3(0, 2.5, 14.0)
main.ui.schliessen() main.player.yaw = 0.0
main.player.pitch = -0.12
await warte(20)
await knipse("09_tor_zur_erde")
main._erde() main._erde()
Game.time_h = 10.5 Game.time_h = 10.5
@@ -84,16 +101,16 @@ func _ready() -> void:
main.player.pitch = -0.62 main.player.pitch = -0.62
main.player.yaw = 0.0 main.player.yaw = 0.0
await warte(20) await warte(20)
await knipse("09_erde_uebersicht") await knipse("10_erde_uebersicht")
var lena: Human = null var lena: Bewohner = null
for m in main.welt.humans: for m in main.welt.humans:
if m.data["id"] == "lena": if m.data["id"] == "lena":
lena = m lena = m
for i in range(900): for i in range(900):
main._process(0.05) main._process(0.05)
for k in main.welt.get_children(): for k in main.welt.get_children():
if k is Human: if k is Bewohner:
k._process(0.05) k._process(0.05)
Game.time_h = 10.5 Game.time_h = 10.5
if lena.place == "haus_lena" and lena._path.is_empty(): if lena.place == "haus_lena" and lena._path.is_empty():
@@ -102,27 +119,27 @@ func _ready() -> void:
main.player.yaw = PI main.player.yaw = PI
main.player.pitch = -0.15 main.player.pitch = -0.15
await warte(20) await warte(20)
await knipse("10_lena_zoegert") await knipse("11_lena_zoegert")
main.ui.anwenden_panel(lena) main.ui.anwenden_panel(lena)
await warte(6) await warte(6)
await knipse("11_anwenden") await knipse("12_anwenden")
main.ui._anwenden("pomodoro") main.ui._anwenden("pomodoro")
await warte(25) await warte(25)
await knipse("12_reaktion") await knipse("13_reaktion")
Game.time_h = 13.5 Game.time_h = 13.5
for i in range(1200): for i in range(1200):
main._process(0.05) main._process(0.05)
for k in main.welt.get_children(): for k in main.welt.get_children():
if k is Human: if k is Bewohner:
k._process(0.05) k._process(0.05)
Game.time_h = 13.5 Game.time_h = 13.5
main.player.global_position = Vector3(0, 11.0, -14.0) main.player.global_position = Vector3(0, 11.0, -14.0)
main.player.yaw = PI main.player.yaw = PI
main.player.pitch = -0.5 main.player.pitch = -0.5
await warte(20) await warte(20)
await knipse("13_cafe_mittag") await knipse("14_cafe_mittag")
Game.time_h = 22.5 Game.time_h = 22.5
await warte(40) await warte(40)
@@ -130,7 +147,7 @@ func _ready() -> void:
main.player.yaw = 0.0 main.player.yaw = 0.0
main.player.pitch = -0.5 main.player.pitch = -0.5
await warte(20) await warte(20)
await knipse("14_nacht") await knipse("15_nacht")
print("Screenshots: %d" % n) print("Screenshots: %d" % n)
print("Ordner: %s" % ProjectSettings.globalize_path(OUT)) print("Ordner: %s" % ProjectSettings.globalize_path(OUT))

View File

@@ -23,8 +23,9 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
await test_start() await test_start()
await test_bibliothek() await test_journal()
await test_gebaeude_betreten() await test_traumdorf()
await test_haus_betreten()
await test_mission_annehmen() await test_mission_annehmen()
await test_reise_zur_erde() await test_reise_zur_erde()
await test_tagesablauf() await test_tagesablauf()
@@ -32,7 +33,8 @@ func _ready() -> void:
await test_geist() await test_geist()
await test_technik_anwenden() await test_technik_anwenden()
await test_mission_abschliessen() await test_mission_abschliessen()
await test_ui_panels() await test_techniken_verbessern_sich()
await test_journal_ueberall()
await test_steuerung() await test_steuerung()
print("\n=========================================") print("\n=========================================")
@@ -57,10 +59,23 @@ func schritte(n: int, dt: float = 0.05, zeit: float = -1.0) -> void:
if welt().has_method("_process"): if welt().has_method("_process"):
welt()._process(dt) welt()._process(dt)
for kind in welt().get_children(): for kind in welt().get_children():
if kind is Human: if kind is Bewohner:
kind._process(dt) kind._process(dt)
func laufe(von: Vector3, yaw: float, frames: int) -> Vector3:
main.player.set_mode(Player.Mode.TRAUM)
main.player.look_enabled = true
main.player.global_position = von
main.player.velocity = Vector3.ZERO
main.player.yaw = yaw
Input.action_press("vor")
for i in range(frames):
await get_tree().physics_frame
Input.action_release("vor")
return main.player.global_position
# -------------------------------------------------------------------- # --------------------------------------------------------------------
func test_start() -> void: func test_start() -> void:
@@ -71,31 +86,31 @@ func test_start() -> void:
p(welt() is DreamWorld, "Startwelt ist die Traumwelt") p(welt() is DreamWorld, "Startwelt ist die Traumwelt")
p(main.player != null and main.player.mode == Player.Mode.TRAUM, "Spieler läuft (kein Geist)") p(main.player != null and main.player.mode == Player.Mode.TRAUM, "Spieler läuft (kein Geist)")
var inter := get_tree().get_nodes_in_group("interactable") var inter := get_tree().get_nodes_in_group("interactable")
p(inter.size() == 12 + 4 + 1 + 1, p(inter.size() == 1 and inter[0].get_meta("kind") == "erde",
"18 Interaktionspunkte (12 Techniken, 4 Missionen, Meditation, Tor) — gefunden: %d" % inter.size()) "Einzige Interaktion in der Traumwelt ist das Tor zur Erde (%d)" % inter.size())
p(Game.einsicht == 3, "Startkapital 3 Einsicht") p(Game.einsicht == 3, "Startkapital 3 Einsicht")
p(Game.known.is_empty(), "Noch keine Technik gelernt") p(Game.known.is_empty(), "Noch keine Technik gelernt")
func test_bibliothek() -> void: func test_journal() -> void:
abschnitt("Techniken lernen (Bibliothek)") abschnitt("Journal")
for t in TechDB.LIST: for t in TechDB.LIST:
var vollstaendig: bool = t["was"] != "" and t["wann"] != "" and t["warum"] != "" var vollstaendig: bool = t["was"] != "" and t["wann"] != "" and t["warum"] != ""
if not vollstaendig: if not vollstaendig:
p(false, "Technik %s hat alle drei Hinweise" % t["id"]) p(false, "Technik %s hat alle drei Hinweise" % t["id"])
p(true, "Alle %d Techniken tragen Was/Wann/Warum" % TechDB.LIST.size()) p(true, "Alle %d Techniken tragen Was/Wann/Warum" % TechDB.LIST.size())
# Über den echten Interaktionsweg: Spieler an ein Pult stellen ui().journal_panel("techniken")
var ziel: Node3D = null p(ui().offen and ui()._kind == "journal", "Journal öffnet sich")
for n in get_tree().get_nodes_in_group("interactable"): var knoepfe := 0
if n.get_meta("kind") == "technik" and n.get_meta("arg") == "pomodoro": for c in ui()._content.get_children():
ziel = n if c is Button:
p(ziel != null, "Pult für die Pomodoro-Technik existiert") knoepfe += 1
main.player.global_position = ziel.global_position + Vector3(0, 0.5, 1.0) p(knoepfe >= TechDB.LIST.size(), "Jede der %d Techniken hat einen Knopf (%d)" %
main._process(0.05) [TechDB.LIST.size(), knoepfe])
p(main._fokus == ziel, "Spieler erfasst das Pult als Fokus") ui().journal_panel("missionen")
main._interagieren() p(ui()._journal_seite == "missionen" and ui()._content.get_child_count() > 0,
p(ui().offen and ui()._kind == "technik", "Technik-Panel öffnet sich") "Missionsseite baut auf")
ui().schliessen() ui().schliessen()
p(Game.learn("pomodoro"), "Pomodoro gelernt") p(Game.learn("pomodoro"), "Pomodoro gelernt")
@@ -111,51 +126,96 @@ func test_bibliothek() -> void:
Game.known["pomodoro"] = 0 Game.known["pomodoro"] = 0
func test_gebaeude_betreten() -> void: func test_traumdorf() -> void:
abschnitt("Gebäude betreten") abschnitt("Traumwesen leben ihren Tag")
var vorher: Vector3 = main.player.global_position var wesen: Array = welt().bewohner
main.player.global_position = Vector3(-24, 1.0, -4.0) p(wesen.size() == 4, "Vier Wesen wohnen in der Traumwelt (%d)" % wesen.size())
Game.teleport_lock = 0.0
for i in range(12):
await get_tree().physics_frame
main.player.global_position = Vector3(-24, 1.0, -9.0)
for i in range(20):
await get_tree().physics_frame
var drin: bool = main.player.global_position.y < -250.0
p(drin, "Tür der Bibliothek versetzt in den Innenraum (y=%.1f)" % main.player.global_position.y)
# drinnen stehen die Pulte var haeuser := 0
var innen_pulte := 0 for key in DreamDB.PLACES:
for n in get_tree().get_nodes_in_group("interactable"): var s: Array = DreamDB.PLACES[key]["size"]
if n.get_meta("kind") == "technik" and n.global_position.y < -250.0: if s[0] > 0.0:
innen_pulte += 1 haeuser += 1
p(innen_pulte == 12, "12 Technik-Pulte stehen im Innenraum (%d)" % innen_pulte) p(haeuser == 4, "Es gibt vier Häuser (%d)" % haeuser)
Game.teleport_lock = 0.0 var eigene: Dictionary = {}
main.player.global_position = Vector3(0, -299.0, 6.0) for w in wesen:
for i in range(10): var haus := "haus_" + str(w.data["id"])
await get_tree().physics_frame p(DreamDB.PLACES.has(haus), "%s hat ein eigenes Haus (%s)" % [w.data["name"], haus])
main.player.global_position = Vector3(0, -299.0, 12.0) eigene[haus] = true
for i in range(20): var kommt_vor := false
await get_tree().physics_frame for e in w.data["routine"]:
p(main.player.global_position.y > -100.0, if e[1] == haus:
"Ausgang bringt zurück nach draußen (y=%.1f)" % main.player.global_position.y) kommt_vor = true
main.player.global_position = vorher p(kommt_vor, "Die Routine von %s führt ins eigene Haus" % w.data["name"])
p(eigene.size() == 4, "Jedes Wesen hat ein anderes Haus")
var luecken := 0
for w in wesen:
for h in range(24):
var r: Array = w._routine_at(float(h) + 0.5)
if not DreamDB.PLACES.has(r[0]) or not Orte.ACTIVITY_TEXT.has(r[1]):
luecken += 1
p(luecken == 0, "Alle vier haben rund um die Uhr Ort und Tätigkeit")
# Ein voller Tag am Stück: niemand bleibt hängen
Game.time_h = 0.0
Game.day = 1
var besucht: Dictionary = {}
for w in wesen:
besucht[w.data["id"]] = {}
for i in range(9000):
schritte(1)
for w in wesen:
if w._path.is_empty():
besucht[w.data["id"]][w.place] = true
if Game.time_h >= 23.5:
break
var alle_ok := true
for w in wesen:
var soll: Dictionary = {}
for e in w.data["routine"]:
soll[e[1]] = true
var ist: int = besucht[w.data["id"]].size()
if ist < soll.size():
alle_ok = false
p(false, "%s erreichte nur %d von %d Stationen" % [w.data["name"], ist, soll.size()])
p(alle_ok, "Über einen ganzen Tag erreicht jedes Wesen jede Station seiner Routine")
var zuhause := 0
schritte(20, 0.05, 1.0)
for w in wesen:
var r: Array = w._routine_at(1.0)
if w.place == r[0]:
zuhause += 1
p(zuhause == 4, "Um 01:00 sind alle vier an ihrem Nachtplatz (%d)" % zuhause)
func test_haus_betreten() -> void:
abschnitt("Orte betreten")
var haus := Orte.center(DreamDB.PLACES, "haus_ailen")
var tuer := Orte.door(DreamDB.PLACES, "haus_ailen")
var ziel: Vector3 = await laufe(tuer + Vector3(0, 1.0, 1.5), 0.0, 80)
var drin: bool = absf(ziel.x - haus.x) < 6.0 and absf(ziel.z - haus.z) < 5.0
p(drin, "Durch die Tür läuft man ins Haus (Ziel %.1f / %.1f, Haus %.1f / %.1f)" %
[ziel.x, ziel.z, haus.x, haus.z])
# Die Wand hält
var vor_wand := haus + Vector3(-11.0, 1.0, 0)
var gestoppt: Vector3 = await laufe(vor_wand, -PI * 0.5, 80)
p(gestoppt.x < haus.x - 5.5,
"Die Seitenwand hält den Spieler auf (x = %.1f, Wand bei %.1f)" %
[gestoppt.x, haus.x - 6.0])
main.player.set_mode(Player.Mode.TRAUM)
main.player.global_position = (welt() as DreamWorld).spawn
func test_mission_annehmen() -> void: func test_mission_annehmen() -> void:
abschnitt("Mission annehmen") abschnitt("Mission annehmen")
var ziel: Node3D = null ui().journal_panel("missionen")
for n in get_tree().get_nodes_in_group("interactable"): p(ui().offen and ui()._journal_seite == "missionen", "Missionsseite des Journals offen")
if n.get_meta("kind") == "mission" and n.get_meta("arg") == "lena":
ziel = n
p(ziel != null, "Stele für Lena Vogt existiert")
Game.teleport_lock = 1.0
main.player.global_position = ziel.global_position + Vector3(0, 0, 1.2)
main._process(0.05)
p(main._fokus == ziel, "Stele wird als Fokus erkannt")
main._interagieren()
p(ui().offen and ui()._kind == "mission", "Missions-Panel öffnet sich")
ui().schliessen() ui().schliessen()
p(Game.accept_mission("lena"), "Mission Lena angenommen") p(Game.accept_mission("lena"), "Mission Lena angenommen")
p(Game.active_mission == "lena", "Mission ist aktiv") p(Game.active_mission == "lena", "Mission ist aktiv")
@@ -180,7 +240,7 @@ func test_reise_zur_erde() -> void:
func test_tagesablauf() -> void: func test_tagesablauf() -> void:
abschnitt("Tagesablauf der Menschen") abschnitt("Tagesablauf der Menschen")
var lena: Human = null var lena: Bewohner = null
for m in welt().humans: for m in welt().humans:
if m.data["id"] == "lena": if m.data["id"] == "lena":
lena = m lena = m
@@ -188,7 +248,7 @@ func test_tagesablauf() -> void:
schritte(30, 0.05, 2.0) schritte(30, 0.05, 2.0)
p(lena.activity == "schlaeft", "Um 02:00 schläft Lena (%s)" % lena.activity) p(lena.activity == "schlaeft", "Um 02:00 schläft Lena (%s)" % lena.activity)
p(lena.global_position.distance_to(HumanDB.place_center("haus_lena")) < 4.0, p(lena.global_position.distance_to(Orte.center(HumanDB.PLACES, "haus_lena")) < 4.0,
"Sie schläft in ihrem Atelier") "Sie schläft in ihrem Atelier")
var gewandert := false var gewandert := false
@@ -199,9 +259,6 @@ func test_tagesablauf() -> void:
break break
p(gewandert, "Um 08:00 geht Lena ins Café und kommt dort an") p(gewandert, "Um 08:00 geht Lena ins Café und kommt dort an")
p(lena.activity == "essen", "Im Café isst sie (%s)" % lena.activity) p(lena.activity == "essen", "Im Café isst sie (%s)" % lena.activity)
p(lena.global_position.distance_to(HumanDB.place_center("cafe")) < 4.0,
"Sie steht im Café (Abstand %.1f)" %
lena.global_position.distance_to(HumanDB.place_center("cafe")))
var daheim := false var daheim := false
for i in range(600): for i in range(600):
@@ -212,53 +269,57 @@ func test_tagesablauf() -> void:
p(daheim, "Um 10:00 ist sie zurück im Atelier") p(daheim, "Um 10:00 ist sie zurück im Atelier")
p(lena.activity == "zoegert", "Dort zögert sie vor der Arbeit (%s)" % lena.activity) p(lena.activity == "zoegert", "Dort zögert sie vor der Arbeit (%s)" % lena.activity)
# alle vier haben zu jeder Stunde einen definierten Ort
var luecken := 0 var luecken := 0
for m in welt().humans: for m in welt().humans:
for h in range(24): for h in range(24):
var r: Array = m._routine_at(float(h) + 0.5) var r: Array = m._routine_at(float(h) + 0.5)
if not HumanDB.PLACES.has(r[0]) or not HumanDB.ACTIVITY_TEXT.has(r[1]): if not HumanDB.PLACES.has(r[0]) or not Orte.ACTIVITY_TEXT.has(r[1]):
luecken += 1 luecken += 1
p(luecken == 0, "Alle 4 Menschen haben rund um die Uhr Ort und Tätigkeit") p(luecken == 0, "Alle 4 Menschen haben rund um die Uhr Ort und Tätigkeit")
# ein voller Tag am Stück, mit echter Uhr: niemand bleibt hängen
Game.time_h = 0.0 Game.time_h = 0.0
var besuchte: Dictionary = {} Game.day = 1
var besucht: Dictionary = {}
for m in welt().humans: for m in welt().humans:
besuchte[m.data["id"]] = {} besucht[m.data["id"]] = {}
for i in range(7200): for i in range(9000):
schritte(1) schritte(1)
for m in welt().humans: for m in welt().humans:
if m._path.is_empty(): if m._path.is_empty():
besuchte[m.data["id"]][m.place] = true besucht[m.data["id"]][m.place] = true
if Game.time_h >= 23.5 and Game.day >= 1: if Game.time_h >= 23.5:
break break
var alle_ok := true var alle_ok := true
for m in welt().humans: for m in welt().humans:
var orte: int = besuchte[m.data["id"]].size()
var soll: Dictionary = {} var soll: Dictionary = {}
for e in m.data["routine"]: for e in m.data["routine"]:
soll[e[1]] = true soll[e[1]] = true
if orte < soll.size(): var ist: int = besucht[m.data["id"]].size()
if ist < soll.size():
alle_ok = false alle_ok = false
p(false, "%s erreichte nur %d von %d Orten" % [m.data["name"], orte, soll.size()]) p(false, "%s erreichte nur %d von %d Stationen" % [m.data["name"], ist, soll.size()])
p(alle_ok, "Über einen ganzen Tag erreicht jeder Mensch jede Station seiner Routine") p(alle_ok, "Über einen ganzen Tag erreicht jeder Mensch jede Station seiner Routine")
func test_wege_machbar() -> void: func test_wege_machbar() -> void:
abschnitt("Wege passen in die Zeitfenster") abschnitt("Wege passen in die Zeitfenster")
_wege_pruefen("Erde", HumanDB.PLACES, HumanDB.LIST)
_wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST)
func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array) -> void:
var sek_pro_stunde := Game.DAY_SECONDS / 24.0 var sek_pro_stunde := Game.DAY_SECONDS / 24.0
var alles_ok := true var alles_ok := true
var engste := 999.0 var engste := 999.0
var engste_text := "" var engste_text := ""
for m in HumanDB.LIST: for m in liste:
var r: Array = m["routine"] var r: Array = m["routine"]
for i in range(r.size()): for i in range(r.size()):
var von: String = String(r[i - 1][1]) if i > 0 else String(r[r.size() - 1][1]) var von: String = String(r[i - 1][1]) if i > 0 else String(r[r.size() - 1][1])
var nach: String = String(r[i][1]) var nach: String = String(r[i][1])
if von == nach: if von == nach:
continue continue
var dauer := _weg_laenge(von, nach) / Human.GEHTEMPO var dauer := _weg_laenge(places, von, nach) / Bewohner.GEHTEMPO
var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() \ var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() \
else (24.0 - float(r[i][0])) else (24.0 - float(r[i][0]))
var fenster := slot_h * sek_pro_stunde var fenster := slot_h * sek_pro_stunde
@@ -270,13 +331,13 @@ func test_wege_machbar() -> void:
engste = fenster - dauer engste = fenster - dauer
engste_text = ("%s: %s -> %s (%.0f s Weg, %.0f s Fenster)" engste_text = ("%s: %s -> %s (%.0f s Weg, %.0f s Fenster)"
% [m["name"], von, nach, dauer, fenster]) % [m["name"], von, nach, dauer, fenster])
p(alles_ok, "Jeder Weg passt in sein Zeitfenster — engster Fall: " + engste_text) p(alles_ok, "%s — jeder Weg passt, engster Fall: %s" % [welt_name, engste_text])
func _weg_laenge(von: String, nach: String) -> float: func _weg_laenge(places: Dictionary, von: String, nach: String) -> float:
var pts: Array = HumanDB.route(von, nach, Vector3.ZERO) var pts: Array = Orte.route(places, von, nach, Vector3.ZERO)
var l := 0.0 var l := 0.0
var pos := HumanDB.place_center(von) var pos := Orte.center(places, von)
for pt in pts: for pt in pts:
l += pos.distance_to(pt) l += pos.distance_to(pt)
pos = pt pos = pt
@@ -293,11 +354,10 @@ func test_geist() -> void:
main.player.velocity = Vector3(6, 0, 0) main.player.velocity = Vector3(6, 0, 0)
for i in range(30): for i in range(30):
await get_tree().physics_frame await get_tree().physics_frame
p(main.player.global_position.x > vor.x, "Der Geist bewegt sich frei durch den Raum") p(main.player.global_position.x > 0.5, "Der Geist bewegt sich frei durch den Raum")
p(absf(main.player.global_position.y - 8.0) < 0.5, "Ohne Schwerkraft fällt er nicht") p(absf(main.player.global_position.y - 8.0) < 0.5, "Ohne Schwerkraft fällt er nicht")
# keiner der Menschen reagiert auf ihn var lena: Bewohner = welt().humans[0]
var lena: Human = welt().humans[0]
var davor: Vector3 = lena.global_position var davor: Vector3 = lena.global_position
main.player.global_position = lena.global_position + Vector3(0, 1, 0.5) main.player.global_position = lena.global_position + Vector3(0, 1, 0.5)
schritte(10) schritte(10)
@@ -306,18 +366,18 @@ func test_geist() -> void:
func test_technik_anwenden() -> void: func test_technik_anwenden() -> void:
abschnitt("Techniken anwenden — Erfolgschance") abschnitt("Techniken anwenden — Erfolgschance")
var lena: Human = null var lena: Bewohner = null
for m in welt().humans: for m in welt().humans:
if m.data["id"] == "lena": if m.data["id"] == "lena":
lena = m lena = m
Game.time_h = 9.5
for i in range(900): for i in range(900):
schritte(1) schritte(1, 0.05, 10.2)
if lena.place == "haus_lena" and lena._path.is_empty(): if lena.place == "haus_lena" and lena._path.is_empty():
break break
p(lena.activity == "zoegert", "Lena zögert gerade") p(lena.activity == "zoegert", "Lena zögert gerade")
main.player.set_mode(Player.Mode.GEIST)
main.player.global_position = lena.global_position + Vector3(0, 1.0, 1.5) main.player.global_position = lena.global_position + Vector3(0, 1.0, 1.5)
main._process(0.05) main._process(0.05)
p(main._fokus_mensch == lena, "Der Geist erfasst Lena in Reichweite") p(main._fokus_mensch == lena, "Der Geist erfasst Lena in Reichweite")
@@ -329,27 +389,23 @@ func test_technik_anwenden() -> void:
var s1: float = Game.evaluate("pomodoro", "lena", "zoegert", false)["chance"] var s1: float = Game.evaluate("pomodoro", "lena", "zoegert", false)["chance"]
Game.known["pomodoro"] = 28 Game.known["pomodoro"] = 28
var s5: float = Game.evaluate("pomodoro", "lena", "zoegert", false)["chance"] var s5: float = Game.evaluate("pomodoro", "lena", "zoegert", false)["chance"]
p(Game.skill_of("pomodoro") == 5, "Pomodoro auf Stufe 5 gebracht")
p(s5 > s1, "Höherer Skill = höhere Erfolgschance (%.0f%% -> %.0f%%)" % [s1 * 100, s5 * 100]) p(s5 > s1, "Höherer Skill = höhere Erfolgschance (%.0f%% -> %.0f%%)" % [s1 * 100, s5 * 100])
Game.known["pomodoro"] = 0 Game.known["pomodoro"] = 0
var passend: Dictionary = Game.evaluate("pomodoro", "lena", "zoegert", false) var passend: Dictionary = Game.evaluate("pomodoro", "lena", "zoegert", false)
var unpassend: Dictionary = Game.evaluate("pomodoro", "lena", "essen", false) var unpassend: Dictionary = Game.evaluate("pomodoro", "lena", "essen", false)
p(passend["moment"] and not unpassend["moment"], "Moment-Erkennung: zögert ja, isst nein") p(passend["moment"] and not unpassend["moment"], "Moment-Erkennung: zögert ja, isst nein")
p(passend["chance"] > unpassend["chance"], p(passend["chance"] > unpassend["chance"], "Passender Moment erhöht die Chance")
"Passender Moment erhöht die Chance (%.0f%% > %.0f%%)" % p(passend["tag"], "Pomodoro trifft Lenas Thema Aufschieben")
[passend["chance"] * 100, unpassend["chance"] * 100])
p(passend["tag"], "Pomodoro trifft Lenas Thema Prokrastination")
var falsches_thema: Dictionary = Game.evaluate("pomodoro", "mira", "zoegert", false) var falsches_thema: Dictionary = Game.evaluate("pomodoro", "mira", "zoegert", false)
p(not falsches_thema["tag"], "Bei Mira trifft Pomodoro das Thema nicht") p(not falsches_thema["tag"], "Bei Mira trifft Pomodoro das Thema nicht")
p(passend["chance"] > falsches_thema["chance"], "Passendes Thema erhöht die Chance") p(passend["chance"] > falsches_thema["chance"], "Passendes Thema erhöht die Chance")
p(Game.evaluate("pomodoro", "lena", "schlaeft", false)["blocked"],
var blockiert: Dictionary = Game.evaluate("pomodoro", "lena", "schlaeft", false) "Schlafende Menschen sind nicht erreichbar")
p(blockiert["blocked"], "Schlafende Menschen sind nicht erreichbar") p(Game.evaluate("pomodoro", "lena", "zoegert", true)["chance"] < passend["chance"],
var wehrt: Dictionary = Game.evaluate("pomodoro", "lena", "zoegert", true) "Wer abblockt, ist schwerer zu erreichen")
p(wehrt["chance"] < passend["chance"], "Wer abblockt, ist schwerer zu erreichen") p(Game.evaluate("nein_sagen", "lena", "zoegert", false)["blocked"],
var ungelernt: Dictionary = Game.evaluate("nein_sagen", "lena", "zoegert", false) "Ungelernte Techniken lassen sich nicht anwenden")
p(ungelernt["blocked"], "Ungelernte Techniken lassen sich nicht anwenden")
var vorher_p: float = Game.mission_progress("lena") var vorher_p: float = Game.mission_progress("lena")
var res := Game.apply_technique("pomodoro", "lena", "zoegert", false) var res := Game.apply_technique("pomodoro", "lena", "zoegert", false)
@@ -360,7 +416,6 @@ func test_technik_anwenden() -> void:
p(Game.mission_progress("lena") == vorher_p, "Misserfolg bringt keinen Fortschritt") p(Game.mission_progress("lena") == vorher_p, "Misserfolg bringt keinen Fortschritt")
p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung") p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung")
# Wiederholung derselben Technik wirkt schwächer
Game.progress["lena"] = 0.0 Game.progress["lena"] = 0.0
Game.used.clear() Game.used.clear()
Game.known["pomodoro"] = 28 Game.known["pomodoro"] = 28
@@ -390,34 +445,69 @@ func test_mission_abschliessen() -> void:
p(Game.active_mission == "", "Aktive Mission wurde freigegeben") p(Game.active_mission == "", "Aktive Mission wurde freigegeben")
p(Game.einsicht == einsicht_vorher + 3, "Abschluss bringt 3 Einsicht") p(Game.einsicht == einsicht_vorher + 3, "Abschluss bringt 3 Einsicht")
schritte(3) schritte(3)
var lena: Human = welt().humans[0] var lena: Bewohner = welt().humans[0]
p(lena.beschriftung().contains("erfüllt"), "Lena zeigt sichtbar den erfüllten Traum") p(lena.beschriftung().contains("erfüllt"), "Lena zeigt sichtbar den erfüllten Traum")
func test_ui_panels() -> void: func test_techniken_verbessern_sich() -> void:
abschnitt("Oberfläche") abschnitt("Techniken verbessern sich durch Anwendung")
ui().kodex_panel() Game.einsicht = 2
p(ui().offen and ui()._content.get_child_count() > 0, "Kodex zeigt gelernte Techniken") p(Game.learn("reframing"), "Technik im Journal gelernt")
p(Game.skill_of("reframing") == 1, "Start auf Stufe 1")
var einsicht_vorher := Game.einsicht
var anwendungen := 0
while Game.skill_of("reframing") < 3 and anwendungen < 60:
Game.apply_technique("reframing", "mira", "gruebelt", false)
anwendungen += 1
p(Game.skill_of("reframing") >= 3,
"Nach %d Anwendungen von allein auf Stufe %d" % [anwendungen, Game.skill_of("reframing")])
p(Game.einsicht == einsicht_vorher, "Das kostet keine Einsicht")
var vor_xp: int = Game.known["reframing"]
var res := Game.apply_technique("reframing", "mira", "essen", false)
p(Game.known["reframing"] > vor_xp,
"Auch ein %s bringt Erfahrung" % ("Erfolg" if res["success"] else "Fehlversuch"))
var xp_vor: int = Game.known["reframing"]
p(Game.deepen("reframing"), "Vertiefen im Journal funktioniert")
p(Game.known["reframing"] == xp_vor + 3, "Vertiefen bringt 3 EP")
p(Game.einsicht == einsicht_vorher - 1, "Vertiefen kostet 1 Einsicht")
func test_journal_ueberall() -> void:
abschnitt("Journal ist immer dabei")
p(InputMap.has_action("journal"), "Eigene Aktion für das Journal")
var tasten: Array = []
for ev in InputMap.action_get_events("journal"):
tasten.append(ev.physical_keycode)
p(tasten.has(KEY_J) and tasten.has(KEY_TAB), "Auf J und Tab gelegt")
p(main.auf_erde, "Test läuft auf der Erde")
ui().journal_panel()
p(ui().offen, "Journal öffnet auf der Erde")
ui().schliessen() ui().schliessen()
ui().vertiefen_panel()
p(ui()._content.get_child_count() > 0, "Meditationskammer listet Techniken") main._traumwelt()
await get_tree().process_frame
ui().journal_panel()
p(ui().offen, "Journal öffnet auch in der Traumwelt")
ui().schliessen() ui().schliessen()
var e_vor := Game.einsicht
p(Game.deepen("pomodoro"), "Vertiefen funktioniert") # Traumwesen sind keine Missionsziele
p(Game.einsicht == e_vor - 1, "Vertiefen kostet 1 Einsicht") var wesen: Bewohner = welt().bewohner[0]
ui().mission_panel("bruno") main.player.set_mode(Player.Mode.TRAUM)
p(ui()._content.get_child_count() > 0, "Missions-Panel für Bruno baut auf") main.player.global_position = wesen.global_position + Vector3(0, 1.0, 1.2)
ui().schliessen() main._process(0.05)
ui().anwenden_panel(welt().humans[1]) p(main._fokus_mensch == null, "Traumwesen lassen sich nicht mit Techniken bearbeiten")
p(ui()._content.get_child_count() > 0, "Anwenden-Panel für Bruno baut auf") p(not wesen.mit_mission, "Sie tragen keine Missionsmarkierung")
ui().schliessen() p(wesen.beschriftung().contains(str(wesen.data["rolle"])),
p(main._fokus == null or true, "HUD bleibt fehlerfrei") "Ihre Beschriftung nennt Name und Rolle: %s" % wesen.beschriftung().replace("\n", " / "))
func test_steuerung() -> void: func test_steuerung() -> void:
abschnitt("Steuerung") abschnitt("Steuerung")
var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "sinken", var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "sinken",
"interagieren", "kodex"] "interagieren", "journal"]
var alle := true var alle := true
for a in aktionen: for a in aktionen:
if not InputMap.has_action(a) or InputMap.action_get_events(a).is_empty(): if not InputMap.has_action(a) or InputMap.action_get_events(a).is_empty():
@@ -425,7 +515,7 @@ func test_steuerung() -> void:
p(false, "Aktion '%s' ist belegt" % a) p(false, "Aktion '%s' ist belegt" % a)
p(alle, "Alle acht Steuerungsaktionen sind mit Tasten belegt") p(alle, "Alle acht Steuerungsaktionen sind mit Tasten belegt")
main.ui.schliessen() ui().schliessen()
main.player.look_enabled = true main.player.look_enabled = true
main.player.set_mode(Player.Mode.GEIST) main.player.set_mode(Player.Mode.GEIST)
main.player.global_position = Vector3(0, 8.0, 0) main.player.global_position = Vector3(0, 8.0, 0)