update
This commit is contained in:
24
README.md
24
README.md
@@ -11,7 +11,13 @@ sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.
|
|||||||
godot --path .
|
godot --path .
|
||||||
|
|
||||||
Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren ·
|
Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren ·
|
||||||
**J** Journal · **1/2/3** Tempo · **Esc** Maus freigeben.
|
**F** folgen · **R** notieren · **J** Journal · **1/2/3** Tempo ·
|
||||||
|
**Esc** Maus freigeben.
|
||||||
|
|
||||||
|
Der Tagesablauf steht im **HUD**: 24 Felder, eines je Stunde, die laufende
|
||||||
|
hervorgehoben. Grau heißt unbekannt, gelb „hier stimmt etwas nicht, aber du
|
||||||
|
hast es noch nicht benannt", rot ein benannter Blocker, grün ein gebautes
|
||||||
|
System. Darunter steht, was gerade läuft.
|
||||||
In der Traumwelt springt **Leertaste**; auf der Erde steigt sie und **Umschalt**
|
In der Traumwelt springt **Leertaste**; auf der Erde steigt sie und **Umschalt**
|
||||||
lässt sinken.
|
lässt sinken.
|
||||||
|
|
||||||
@@ -24,7 +30,9 @@ Das Journal führt Buch — es lehrt und vergibt nichts.
|
|||||||
* **Techniken** — was du gelernt hast, mit Stufe und Erfahrung. Besser werden
|
* **Techniken** — was du gelernt hast, mit Stufe und Erfahrung. Besser werden
|
||||||
sie nur draußen.
|
sie nur draußen.
|
||||||
* **Missionen** — die Menschen, für die du dich entschieden hast, jeweils mit
|
* **Missionen** — die Menschen, für die du dich entschieden hast, jeweils mit
|
||||||
ihrem **Tagesablauf**.
|
ihrem **Tagesablauf**: der Tag von 0 bis 23 Uhr, in Abschnitte geteilt. Es
|
||||||
|
gibt keine Wochentage; jeder Tag ist derselbe. Zu Beginn steht in jedem
|
||||||
|
Abschnitt nur ein Strich — der Tag ist leer, bis du dabei warst.
|
||||||
|
|
||||||
**Diagnose und Systeme** — der Kern des Spiels
|
**Diagnose und Systeme** — der Kern des Spiels
|
||||||
|
|
||||||
@@ -32,6 +40,10 @@ Ein Traum scheitert an Ursachen: *kein Anfang*, *keine Zeit*, *keine Kraft*,
|
|||||||
*kein Halt*, *kein Grund*. Jeder Mensch hat zwei bis drei davon, nie alle. Die
|
*kein Halt*, *kein Grund*. Jeder Mensch hat zwei bis drei davon, nie alle. Die
|
||||||
stehen nirgends — du findest sie nur, indem du dabeistehst.
|
stehen nirgends — du findest sie nur, indem du dabeistehst.
|
||||||
|
|
||||||
|
Dabeisein füllt den Abschnitt im Ablauf: du siehst, dass sie um zehn aufräumt.
|
||||||
|
Ob das ihren Traum aufhält, entscheidest du — mit **R**. Bis dahin steht dort
|
||||||
|
ein Fragezeichen.
|
||||||
|
|
||||||
Sichtbar wird eine Ursache an den Tätigkeiten, die sie hervorbringt. Wer nie in
|
Sichtbar wird eine Ursache an den Tätigkeiten, die sie hervorbringt. Wer nie in
|
||||||
Gang kommt, räumt auf statt anzufangen, recherchiert oder schreibt Später-Listen.
|
Gang kommt, räumt auf statt anzufangen, recherchiert oder schreibt Später-Listen.
|
||||||
Wer keine Kraft hat, schaut abends eine Serie und bleibt danach wach. Jede
|
Wer keine Kraft hat, schaut abends eine Serie und bleibt danach wach. Jede
|
||||||
@@ -116,6 +128,14 @@ Abwehr. Am Ausgang ändert sich dadurch nichts, nur an der Wartezeit.
|
|||||||
ihren Tag: schlafen, arbeiten, grübeln, essen, spazieren. Wer wo wohnt,
|
ihren Tag: schlafen, arbeiten, grübeln, essen, spazieren. Wer wo wohnt,
|
||||||
arbeitet in der Nähe; das Café liegt in der Mitte.
|
arbeitet in der Nähe; das Café liegt in der Mitte.
|
||||||
* Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände.
|
* Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände.
|
||||||
|
* **F** hängt dich an einen Menschen: du treibst hinter ihm her, durch Türen
|
||||||
|
und über den ganzen Tag, ohne ihm nachlaufen zu müssen. **F** noch einmal —
|
||||||
|
oder ein Schritt mit **WASD** — lässt ihn wieder los.
|
||||||
|
* **R notieren.** Was den Traum aufhält, steht nirgends geschrieben. Du siehst
|
||||||
|
nur, was der Mensch tut, und musst selbst entscheiden: läuft hier etwas
|
||||||
|
schief? Triffst du den Moment, steht die Ursache im Journal und der Slot
|
||||||
|
wird bebaubar. Greifst du daneben, schaust du sechs Sekunden umsonst hin.
|
||||||
|
Zusehen allein deckt nichts auf — das Bemerken ist der Zug.
|
||||||
* Nah herangehen, **E** drücken, Technik wählen. Das Panel nennt für jede
|
* Nah herangehen, **E** drücken, Technik wählen. Das Panel nennt für jede
|
||||||
Technik nur, ob Thema und Moment passen — keine Zahl.
|
Technik nur, ob Thema und Moment passen — keine Zahl.
|
||||||
* Die Säule im Park bringt dich zurück in die Traumwelt.
|
* Die Säule im Park bringt dich zurück in die Traumwelt.
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ Kern
|
|||||||
um Menschen zu helfen, ihre Träume zu erfüllen.
|
um Menschen zu helfen, ihre Träume zu erfüllen.
|
||||||
|
|
||||||
Loops
|
Loops
|
||||||
- Techniken lernen -> Systeme anpassen -> Probleme lösen
|
|
||||||
- Träume erfüllen -> Welt vergrößeren -> Techniken entdecken
|
- Träume erfüllen -> Welt vergrößeren -> Techniken entdecken
|
||||||
|
- Techniken entdecken -> Techniken lernen -> Missionen freischalten
|
||||||
|
- Menschen beobachten -> Probleme finden -> Techniken anwenden
|
||||||
|
|
||||||
Funktionen
|
Funktionen
|
||||||
- Bewegung: Bewegen, Springen, Geist
|
- Bewegung: Bewegen, Springen, Geist, Anhängen
|
||||||
- Journal: Gelernte Techniken, Akzetierte Missionen
|
- Journal: Gelernte Techniken, Akzetierte Missionen
|
||||||
- Techniken: Lernen, Verbessern
|
- Techniken: Lernen, Verbessern
|
||||||
- Missionen: Pipeline
|
- Missionen: Pipeline, Beobachten
|
||||||
- Techniken: Cooldown, Fehlschlag
|
- Techniken: Cooldown, Fehlschlag
|
||||||
- Menschen: Tagesablauf, Tempo
|
- Menschen: Tagesablauf, Tempo, Verfolgung
|
||||||
- Prozedurale Generierung: Dorf, Stadt
|
- Prozedurale Generierung: Dorf, Stadt
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ func _process(roh: float) -> void:
|
|||||||
if _path.is_empty():
|
if _path.is_empty():
|
||||||
activity = soll[1]
|
activity = soll[1]
|
||||||
else:
|
else:
|
||||||
var schritt := d.normalized() * GEHTEMPO * delta
|
# nie über den Wegpunkt hinaus -- sonst pendelt er bei hohem Tempo
|
||||||
|
# um ihn herum, statt anzukommen
|
||||||
|
var schritt := d.normalized() * minf(GEHTEMPO * delta, d.length())
|
||||||
global_position += schritt
|
global_position += schritt
|
||||||
_visual.rotation.y = lerp_angle(_visual.rotation.y, atan2(-d.x, -d.z), 1.0 - pow(0.002, delta))
|
_visual.rotation.y = lerp_angle(_visual.rotation.y, atan2(-d.x, -d.z), 1.0 - pow(0.002, delta))
|
||||||
_bob += delta * 9.0
|
_bob += delta * 9.0
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ var progress: Dictionary = {} # human_id -> 0..100
|
|||||||
var cooldown: Dictionary = {} # tech_id -> Restsekunden
|
var cooldown: Dictionary = {} # tech_id -> Restsekunden
|
||||||
var systeme: Dictionary = {} # human_id -> {slot -> {tech, guete}}
|
var systeme: Dictionary = {} # human_id -> {slot -> {tech, guete}}
|
||||||
var erkannt_slots: Dictionary = {} # human_id -> {slot -> true}
|
var erkannt_slots: Dictionary = {} # human_id -> {slot -> true}
|
||||||
var beobachtung: Dictionary = {} # "human:slot" -> Sekunden
|
var gesehen: Dictionary = {} # human_id -> {slot -> true}, wo man dabei war
|
||||||
|
var notiz_sperre: float = 0.0 # nach einem Fehlgriff kurz gesperrt
|
||||||
var completed: Array = []
|
var completed: Array = []
|
||||||
var time_h: float = 7.0
|
var time_h: float = 7.0
|
||||||
var day: int = 1
|
var day: int = 1
|
||||||
@@ -55,6 +56,8 @@ func _setup_input() -> void:
|
|||||||
"steigen": [KEY_SPACE],
|
"steigen": [KEY_SPACE],
|
||||||
"sinken": [KEY_SHIFT],
|
"sinken": [KEY_SHIFT],
|
||||||
"interagieren": [KEY_E],
|
"interagieren": [KEY_E],
|
||||||
|
"folgen": [KEY_F],
|
||||||
|
"notieren": [KEY_R],
|
||||||
"journal": [KEY_J, KEY_TAB],
|
"journal": [KEY_J, KEY_TAB],
|
||||||
"tempo_1": [KEY_1, KEY_KP_1],
|
"tempo_1": [KEY_1, KEY_KP_1],
|
||||||
"tempo_2": [KEY_2, KEY_KP_2],
|
"tempo_2": [KEY_2, KEY_KP_2],
|
||||||
@@ -305,7 +308,7 @@ func _insel_waechst() -> void:
|
|||||||
const BASIS_TEMPO := 45.0 # Prozent je Tag, wenn nichts mehr bremst
|
const BASIS_TEMPO := 45.0 # Prozent je Tag, wenn nichts mehr bremst
|
||||||
const GUETE_ERSATZ := 1.35 # Blocker mit gleicher Belohnung ersetzt
|
const GUETE_ERSATZ := 1.35 # Blocker mit gleicher Belohnung ersetzt
|
||||||
const GUETE_NEU := 1.0 # System in einen freien Slot gesetzt
|
const GUETE_NEU := 1.0 # System in einen freien Slot gesetzt
|
||||||
const ERKENNEN_SEK := 3.0 # so lange muss man dabeistehen
|
const NOTIZ_SPERRE := 6.0 # nach einem Fehlgriff schaut man erst wieder hin
|
||||||
|
|
||||||
|
|
||||||
func slots(human_id: String) -> Array:
|
func slots(human_id: String) -> Array:
|
||||||
@@ -313,6 +316,34 @@ func slots(human_id: String) -> Array:
|
|||||||
return h["routine"] if h.has("routine") else []
|
return h["routine"] if h.has("routine") else []
|
||||||
|
|
||||||
|
|
||||||
|
## Welcher Slot deckt diese Stunde ab? Jeder Tag ist gleich, es gibt keine
|
||||||
|
## Wochentage -- der Ablauf 0-23 Uhr wiederholt sich.
|
||||||
|
func slot_bei_stunde(human_id: String, stunde: int) -> int:
|
||||||
|
var r := slots(human_id)
|
||||||
|
if r.is_empty():
|
||||||
|
return -1
|
||||||
|
var nr := r.size() - 1
|
||||||
|
for i in range(r.size()):
|
||||||
|
if float(stunde) >= float(r[i][0]):
|
||||||
|
nr = i
|
||||||
|
return nr
|
||||||
|
|
||||||
|
|
||||||
|
## Dabeigewesen: die Stunde ist im Ablauf nicht mehr leer. Was dort schiefläuft,
|
||||||
|
## steht damit noch nicht fest -- das muss der Spieler benennen.
|
||||||
|
func gesehen_slot(human_id: String, nr: int) -> bool:
|
||||||
|
return gesehen.get(human_id, {}).has(nr)
|
||||||
|
|
||||||
|
|
||||||
|
func sehen(human_id: String, nr: int) -> void:
|
||||||
|
if gesehen_slot(human_id, nr):
|
||||||
|
return
|
||||||
|
if not gesehen.has(human_id):
|
||||||
|
gesehen[human_id] = {}
|
||||||
|
gesehen[human_id][nr] = true
|
||||||
|
state_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
func slot_grund(human_id: String, nr: int) -> String:
|
func slot_grund(human_id: String, nr: int) -> String:
|
||||||
var r := slots(human_id)
|
var r := slots(human_id)
|
||||||
return str(r[nr][2]) if nr >= 0 and nr < r.size() else ""
|
return str(r[nr][2]) if nr >= 0 and nr < r.size() else ""
|
||||||
@@ -339,23 +370,30 @@ func erkannt(human_id: String, nr: int) -> bool:
|
|||||||
return erkannt_slots.get(human_id, {}).has(nr)
|
return erkannt_slots.get(human_id, {}).has(nr)
|
||||||
|
|
||||||
|
|
||||||
## Dabeistehen deckt auf, was dort wirklich läuft -- und damit die Ursache.
|
## Zusehen allein deckt nichts auf. Der Spieler muss den Moment benennen:
|
||||||
func beobachten(human_id: String, nr: int, delta: float) -> bool:
|
## "hier läuft etwas schief". Trifft er, steht die Ursache im Journal.
|
||||||
|
## Greift er daneben, schaut er eine Weile umsonst hin.
|
||||||
|
## Grundlage bleibt awareness training -- nur ist das Bemerken jetzt ein Akt.
|
||||||
|
func notieren(human_id: String, nr: int) -> String:
|
||||||
|
if notiz_sperre > 0.0:
|
||||||
|
return "gesperrt"
|
||||||
|
var grund := slot_grund(human_id, nr)
|
||||||
|
if not Ursachen.ist_blocker(grund):
|
||||||
|
notiz_sperre = NOTIZ_SPERRE
|
||||||
|
notified.emit("Hier läuft nichts schief — %s ist einfach ihr Tag." %
|
||||||
|
Orte.text(grund), Color(1, 0.75, 0.55))
|
||||||
|
return "daneben"
|
||||||
if erkannt(human_id, nr):
|
if erkannt(human_id, nr):
|
||||||
return false
|
notified.emit("Das hast du schon notiert: %s." % Orte.text(grund),
|
||||||
var schluessel := "%s:%d" % [human_id, nr]
|
Color(0.8, 0.85, 1))
|
||||||
var stand: float = float(beobachtung.get(schluessel, 0.0)) + delta
|
return "bekannt"
|
||||||
beobachtung[schluessel] = stand
|
|
||||||
if stand < ERKENNEN_SEK:
|
|
||||||
return false
|
|
||||||
if not erkannt_slots.has(human_id):
|
if not erkannt_slots.has(human_id):
|
||||||
erkannt_slots[human_id] = {}
|
erkannt_slots[human_id] = {}
|
||||||
erkannt_slots[human_id][nr] = true
|
erkannt_slots[human_id][nr] = true
|
||||||
var b := slot_grund(human_id, nr)
|
notified.emit("Erkannt: %s — %s" % [Ursachen.BLOCKER[grund]["name"],
|
||||||
notified.emit("Erkannt: %s — %s" % [Ursachen.BLOCKER[b]["name"],
|
Ursachen.LISTE[Ursachen.ursache_von(grund)]["name"]], Color(0.8, 0.95, 1))
|
||||||
Ursachen.LISTE[Ursachen.ursache_von(b)]["name"]], Color(0.8, 0.95, 1))
|
|
||||||
state_changed.emit()
|
state_changed.emit()
|
||||||
return true
|
return "treffer"
|
||||||
|
|
||||||
|
|
||||||
## Eine Ursache gilt als erkannt, sobald eine ihrer Tätigkeiten aufgedeckt ist.
|
## Eine Ursache gilt als erkannt, sobald eine ihrer Tätigkeiten aufgedeckt ist.
|
||||||
|
|||||||
@@ -72,21 +72,12 @@ func _process(delta: float) -> void:
|
|||||||
if ui.offen:
|
if ui.offen:
|
||||||
ui.setze_hinweis("")
|
ui.setze_hinweis("")
|
||||||
return
|
return
|
||||||
|
Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta)
|
||||||
_fokus_suchen()
|
_fokus_suchen()
|
||||||
_beobachten(dt)
|
_dabeisein()
|
||||||
_hinweis_setzen()
|
_hinweis_setzen()
|
||||||
|
|
||||||
|
|
||||||
## Dabeistehen deckt auf, was der Mensch gerade wirklich tut.
|
|
||||||
func _beobachten(dt: float) -> void:
|
|
||||||
if _fokus_mensch == null or not auf_erde:
|
|
||||||
return
|
|
||||||
var id: String = str(_fokus_mensch.data["id"])
|
|
||||||
if id != Game.active_mission:
|
|
||||||
return
|
|
||||||
Game.beobachten(id, int(_fokus_mensch.slot), dt)
|
|
||||||
|
|
||||||
|
|
||||||
func _fokus_suchen() -> void:
|
func _fokus_suchen() -> void:
|
||||||
_fokus = null
|
_fokus = null
|
||||||
_fokus_mensch = null
|
_fokus_mensch = null
|
||||||
@@ -111,14 +102,12 @@ func _fokus_suchen() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _hinweis_setzen() -> void:
|
func _hinweis_setzen() -> void:
|
||||||
if _fokus_mensch != null:
|
if player.folgt != null and is_instance_valid(player.folgt):
|
||||||
var mid: String = str(_fokus_mensch.data["name"])
|
ui.setze_hinweis("[F] loslassen · %s — %s" %
|
||||||
var mnr: int = int(_fokus_mensch.slot)
|
[player.folgt.data["name"], _notiz_hinweis(player.folgt)])
|
||||||
var mkey: String = str(_fokus_mensch.data["id"])
|
elif _fokus_mensch != null:
|
||||||
if mkey == Game.active_mission and not Game.erkannt(mkey, mnr):
|
ui.setze_hinweis("[F] folgen · %s — %s" %
|
||||||
ui.setze_hinweis("%s — schau zu, was sie hier tut …" % mid)
|
[_fokus_mensch.data["name"], _notiz_hinweis(_fokus_mensch)])
|
||||||
else:
|
|
||||||
ui.setze_hinweis("[E] %s — %s" % [mid, 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")
|
||||||
@@ -137,6 +126,18 @@ func _hinweis_setzen() -> void:
|
|||||||
ui.setze_hinweis("")
|
ui.setze_hinweis("")
|
||||||
|
|
||||||
|
|
||||||
|
## Was am Menschen gerade dransteht: notieren, ansprechen oder nur zusehen.
|
||||||
|
func _notiz_hinweis(mensch: Bewohner) -> String:
|
||||||
|
var tat := Orte.text(mensch.activity)
|
||||||
|
if str(mensch.data["id"]) != Game.active_mission:
|
||||||
|
return tat
|
||||||
|
if Game.notiz_sperre > 0.0:
|
||||||
|
return "%s (%.0f s)" % [tat, Game.notiz_sperre]
|
||||||
|
if Game.erkannt(str(mensch.data["id"]), int(mensch.slot)):
|
||||||
|
return "%s · [E] ansprechen" % tat
|
||||||
|
return "%s · [R] notieren, wenn hier etwas schiefläuft" % tat
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if event.is_action_pressed("ui_cancel") and not ui.offen:
|
if event.is_action_pressed("ui_cancel") and not ui.offen:
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if \
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if \
|
||||||
@@ -162,6 +163,66 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
if event.is_action_pressed("interagieren"):
|
if event.is_action_pressed("interagieren"):
|
||||||
_interagieren()
|
_interagieren()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
if event.is_action_pressed("folgen"):
|
||||||
|
_verfolgen()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
if event.is_action_pressed("notieren"):
|
||||||
|
_notieren()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
## Wer dabeisteht, füllt die Stunde im Ablauf -- mehr nicht. Was dort
|
||||||
|
## schiefläuft, muss er selbst benennen.
|
||||||
|
func _dabeisein() -> void:
|
||||||
|
if _fokus_mensch == null or not auf_erde:
|
||||||
|
return
|
||||||
|
var id: String = str(_fokus_mensch.data["id"])
|
||||||
|
if id == Game.active_mission:
|
||||||
|
Game.sehen(id, int(_fokus_mensch.slot))
|
||||||
|
|
||||||
|
|
||||||
|
## Den Moment benennen: hier läuft etwas schief. Das ist der Kern des
|
||||||
|
## Beobachtens -- der Spieler entscheidet, wann er hinschaut.
|
||||||
|
func _notieren() -> void:
|
||||||
|
if not auf_erde:
|
||||||
|
return
|
||||||
|
var ziel: Bewohner = _fokus_mensch
|
||||||
|
if ziel == null and player.folgt != null and is_instance_valid(player.folgt):
|
||||||
|
ziel = player.folgt
|
||||||
|
if ziel == null:
|
||||||
|
Game.notified.emit("Geh näher an den Menschen heran, dem du hilfst.",
|
||||||
|
Color(1, 0.85, 0.5))
|
||||||
|
return
|
||||||
|
var id: String = str(ziel.data["id"])
|
||||||
|
if id != Game.active_mission:
|
||||||
|
Game.notified.emit("Für %s hast du keine Mission angenommen." % ziel.data["name"],
|
||||||
|
Color(1, 0.85, 0.5))
|
||||||
|
return
|
||||||
|
if Game.notiz_sperre > 0.0:
|
||||||
|
Game.notified.emit("Du bist noch beim letzten Fehlgriff (%.0f s)." %
|
||||||
|
Game.notiz_sperre, Color(1, 0.75, 0.55))
|
||||||
|
return
|
||||||
|
Game.notieren(id, int(ziel.slot))
|
||||||
|
|
||||||
|
|
||||||
|
## Als Geist an einen Menschen hängen und ihn durch seinen Tag begleiten.
|
||||||
|
func _verfolgen() -> void:
|
||||||
|
if not auf_erde:
|
||||||
|
return
|
||||||
|
if player.folgt != null:
|
||||||
|
var name: String = player.folgt.data["name"]
|
||||||
|
player.folgt = null
|
||||||
|
Game.notified.emit("Du lässt %s wieder los." % name, Color(0.8, 0.85, 1))
|
||||||
|
return
|
||||||
|
if _fokus_mensch == null:
|
||||||
|
Game.notified.emit("Geh näher an einen Menschen heran, um ihm zu folgen.",
|
||||||
|
Color(1, 0.85, 0.5))
|
||||||
|
return
|
||||||
|
player.folgt = _fokus_mensch
|
||||||
|
Game.notified.emit("Du hängst dich an %s." % _fokus_mensch.data["name"],
|
||||||
|
Color(0.7, 0.9, 1))
|
||||||
|
|
||||||
|
|
||||||
func _interagieren() -> void:
|
func _interagieren() -> void:
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ const SPEED_GEIST := 11.0
|
|||||||
const GRAVITY := 24.0
|
const GRAVITY := 24.0
|
||||||
const JUMP := 9.0
|
const JUMP := 9.0
|
||||||
const MAUS := 0.0026
|
const MAUS := 0.0026
|
||||||
|
const FOLGE_ABSTAND := 3.0
|
||||||
|
const FOLGE_SPIEL := 1.5
|
||||||
|
|
||||||
var mode: int = Mode.TRAUM
|
var mode: int = Mode.TRAUM
|
||||||
var yaw := 0.0
|
var yaw := 0.0
|
||||||
var pitch := -0.25
|
var pitch := -0.25
|
||||||
var look_enabled := true
|
var look_enabled := true
|
||||||
|
var folgt: Node3D = null # Mensch, an den sich der Geist gehängt hat
|
||||||
|
|
||||||
var pivot: Node3D
|
var pivot: Node3D
|
||||||
var arm: SpringArm3D
|
var arm: SpringArm3D
|
||||||
@@ -67,6 +70,7 @@ func _ready() -> void:
|
|||||||
|
|
||||||
func set_mode(m: int) -> void:
|
func set_mode(m: int) -> void:
|
||||||
mode = m
|
mode = m
|
||||||
|
folgt = null
|
||||||
if m == Mode.GEIST:
|
if m == Mode.GEIST:
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
@@ -118,6 +122,23 @@ func _physics_process(delta: float) -> void:
|
|||||||
var basis_yaw := Basis(Vector3.UP, yaw)
|
var basis_yaw := Basis(Vector3.UP, yaw)
|
||||||
dir = (basis_yaw * Vector3(input.x, 0, input.y)).normalized()
|
dir = (basis_yaw * Vector3(input.x, 0, input.y)).normalized()
|
||||||
|
|
||||||
|
if mode == Mode.GEIST and folgt != null:
|
||||||
|
if not is_instance_valid(folgt):
|
||||||
|
folgt = null
|
||||||
|
elif dir.length() > 0.01:
|
||||||
|
folgt = null # eigene Bewegung löst die Verfolgung
|
||||||
|
else:
|
||||||
|
var schulter := folgt.global_position + Vector3(0, 1.6, 0) \
|
||||||
|
- blickrichtung() * FOLGE_ABSTAND
|
||||||
|
global_position = global_position.lerp(schulter, 1.0 - pow(0.004, delta))
|
||||||
|
# weich nachziehen, aber an der Leine: sonst reißt der Faden beim Gehen
|
||||||
|
var ab := global_position - schulter
|
||||||
|
if ab.length() > FOLGE_SPIEL:
|
||||||
|
global_position = schulter + ab.normalized() * FOLGE_SPIEL
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
body.rotation.y = lerp_angle(body.rotation.y, yaw, 1.0 - pow(0.001, delta))
|
||||||
|
return
|
||||||
|
|
||||||
if mode == Mode.GEIST:
|
if mode == Mode.GEIST:
|
||||||
var v := dir * SPEED_GEIST
|
var v := dir * SPEED_GEIST
|
||||||
if look_enabled:
|
if look_enabled:
|
||||||
|
|||||||
189
scripts/ui.gd
189
scripts/ui.gd
@@ -12,6 +12,12 @@ var _kind := ""
|
|||||||
var _hud: RichTextLabel
|
var _hud: RichTextLabel
|
||||||
var _hinweis: Label
|
var _hinweis: Label
|
||||||
var _log: VBoxContainer
|
var _log: VBoxContainer
|
||||||
|
var _ablauf: HBoxContainer
|
||||||
|
var _ablauf_titel: Label
|
||||||
|
var _ablauf_jetzt: Label
|
||||||
|
var _ablauf_grund: ColorRect
|
||||||
|
var _zellen: Array = []
|
||||||
|
var _ticks: Array = []
|
||||||
var _dim: ColorRect
|
var _dim: ColorRect
|
||||||
var _panel: PanelContainer
|
var _panel: PanelContainer
|
||||||
var _titel: Label
|
var _titel: Label
|
||||||
@@ -47,11 +53,14 @@ func _baue_hud() -> void:
|
|||||||
_hud.add_theme_font_size_override("bold_font_size", 17)
|
_hud.add_theme_font_size_override("bold_font_size", 17)
|
||||||
add_child(_hud)
|
add_child(_hud)
|
||||||
|
|
||||||
|
_baue_ablauf()
|
||||||
|
|
||||||
var steuerung := Label.new()
|
var steuerung := Label.new()
|
||||||
steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · J Journal · 1/2/3 Tempo · Esc Maus frei"
|
steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · F Folgen · " + \
|
||||||
|
"R Notieren · J Journal · 1/2/3 Tempo · 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(-720, 16)
|
||||||
steuerung.size = Vector2(620, 24)
|
steuerung.size = Vector2(700, 24)
|
||||||
steuerung.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
steuerung.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
steuerung.add_theme_font_size_override("font_size", 14)
|
steuerung.add_theme_font_size_override("font_size", 14)
|
||||||
steuerung.add_theme_color_override("font_color", Color(1, 1, 1, 0.65))
|
steuerung.add_theme_color_override("font_color", Color(1, 1, 1, 0.65))
|
||||||
@@ -79,6 +88,111 @@ func _baue_hud() -> void:
|
|||||||
add_child(_log)
|
add_child(_log)
|
||||||
|
|
||||||
|
|
||||||
|
## Der Tagesablauf im HUD: 24 Felder, eines je Stunde, immer sichtbar.
|
||||||
|
const ABLAUF_X := 18.0
|
||||||
|
const ABLAUF_Y := 126.0
|
||||||
|
const ZELLE_BREIT := 14.0
|
||||||
|
const ZELLE_LUECKE := 2.0
|
||||||
|
|
||||||
|
func _baue_ablauf() -> void:
|
||||||
|
var breite := 24.0 * (ZELLE_BREIT + ZELLE_LUECKE) - ZELLE_LUECKE
|
||||||
|
|
||||||
|
_ablauf_grund = ColorRect.new()
|
||||||
|
_ablauf_grund.color = Color(0, 0, 0, 0.42)
|
||||||
|
_ablauf_grund.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_ablauf_grund.position = Vector2(ABLAUF_X - 8.0, ABLAUF_Y - 6.0)
|
||||||
|
_ablauf_grund.custom_minimum_size = Vector2(breite + 16.0, 88.0)
|
||||||
|
_ablauf_grund.size = _ablauf_grund.custom_minimum_size
|
||||||
|
_ablauf_grund.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(_ablauf_grund)
|
||||||
|
|
||||||
|
_ablauf_titel = Label.new()
|
||||||
|
_ablauf_titel.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_ablauf_titel.position = Vector2(ABLAUF_X, ABLAUF_Y)
|
||||||
|
_ablauf_titel.add_theme_font_size_override("font_size", 14)
|
||||||
|
_ablauf_titel.add_theme_color_override("font_color", Color(1, 1, 1, 0.7))
|
||||||
|
_ablauf_titel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(_ablauf_titel)
|
||||||
|
|
||||||
|
_ablauf = HBoxContainer.new()
|
||||||
|
_ablauf.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_ablauf.position = Vector2(ABLAUF_X, ABLAUF_Y + 22.0)
|
||||||
|
_ablauf.add_theme_constant_override("separation", int(ZELLE_LUECKE))
|
||||||
|
_ablauf.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(_ablauf)
|
||||||
|
for stunde in range(24):
|
||||||
|
var zelle := ColorRect.new()
|
||||||
|
zelle.custom_minimum_size = Vector2(ZELLE_BREIT, 16)
|
||||||
|
zelle.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_ablauf.add_child(zelle)
|
||||||
|
_zellen.append(zelle)
|
||||||
|
|
||||||
|
# Stundenmarken unter dem Streifen
|
||||||
|
for stunde in [0, 6, 12, 18]:
|
||||||
|
var tick := Label.new()
|
||||||
|
tick.text = "%d" % stunde
|
||||||
|
tick.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
tick.position = Vector2(ABLAUF_X + float(stunde) * (ZELLE_BREIT + ZELLE_LUECKE),
|
||||||
|
ABLAUF_Y + 42.0)
|
||||||
|
tick.add_theme_font_size_override("font_size", 11)
|
||||||
|
tick.add_theme_color_override("font_color", Color(1, 1, 1, 0.45))
|
||||||
|
tick.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(tick)
|
||||||
|
_ticks.append(tick)
|
||||||
|
|
||||||
|
_ablauf_jetzt = Label.new()
|
||||||
|
_ablauf_jetzt.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_ablauf_jetzt.position = Vector2(ABLAUF_X, ABLAUF_Y + 60.0)
|
||||||
|
_ablauf_jetzt.add_theme_font_size_override("font_size", 15)
|
||||||
|
_ablauf_jetzt.add_theme_color_override("font_color", Color(1, 0.95, 0.8))
|
||||||
|
_ablauf_jetzt.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
add_child(_ablauf_jetzt)
|
||||||
|
|
||||||
|
|
||||||
|
## Farbe je Stunde: unbekannt, gesehen, ungeklärt, benannt, gebaut.
|
||||||
|
func _ablauf_farbe(human_id: String, nr: int) -> Color:
|
||||||
|
if not Game.gesehen_slot(human_id, nr):
|
||||||
|
return Color(0.22, 0.23, 0.30)
|
||||||
|
if not Game.system_in(human_id, nr).is_empty():
|
||||||
|
return Color(0.44, 0.78, 0.50)
|
||||||
|
var grund := Game.slot_grund(human_id, nr)
|
||||||
|
if Ursachen.ist_blocker(grund):
|
||||||
|
return Color(0.85, 0.66, 0.32) if not Game.erkannt(human_id, nr) \
|
||||||
|
else Color(0.84, 0.42, 0.35)
|
||||||
|
if grund == "schlaeft":
|
||||||
|
return Color(0.30, 0.33, 0.44)
|
||||||
|
return Color(0.46, 0.50, 0.62)
|
||||||
|
|
||||||
|
|
||||||
|
func _ablauf_aktualisieren() -> void:
|
||||||
|
var id := Game.active_mission
|
||||||
|
var an := id != ""
|
||||||
|
_ablauf.visible = an
|
||||||
|
_ablauf_titel.visible = an
|
||||||
|
_ablauf_jetzt.visible = an
|
||||||
|
_ablauf_grund.visible = an
|
||||||
|
for t in _ticks:
|
||||||
|
(t as Label).visible = an
|
||||||
|
if not an:
|
||||||
|
return
|
||||||
|
var jetzt := int(Game.time_h)
|
||||||
|
var offen := 0
|
||||||
|
for stunde in range(24):
|
||||||
|
var nr := Game.slot_bei_stunde(id, stunde)
|
||||||
|
var f := _ablauf_farbe(id, nr)
|
||||||
|
if not Game.gesehen_slot(id, nr):
|
||||||
|
offen += 1
|
||||||
|
if stunde == jetzt:
|
||||||
|
f = f.lightened(0.55)
|
||||||
|
var zelle: ColorRect = _zellen[stunde]
|
||||||
|
zelle.color = f
|
||||||
|
zelle.custom_minimum_size.y = 20 if stunde == jetzt else 16
|
||||||
|
_ablauf_titel.text = "Ihr Tag 0 – 23 Uhr%s" % \
|
||||||
|
("" if offen == 0 else " · %d Stunden unbekannt" % offen)
|
||||||
|
var nr_jetzt := Game.slot_bei_stunde(id, jetzt)
|
||||||
|
_ablauf_jetzt.text = "%02d Uhr %s" % [jetzt, _stunden_text(id, nr_jetzt)]
|
||||||
|
|
||||||
|
|
||||||
func _baue_panel() -> void:
|
func _baue_panel() -> void:
|
||||||
_dim = ColorRect.new()
|
_dim = ColorRect.new()
|
||||||
_dim.color = Color(0, 0, 0, 0.62)
|
_dim.color = Color(0, 0, 0, 0.62)
|
||||||
@@ -163,6 +277,7 @@ func _hud_aktualisieren() -> void:
|
|||||||
else:
|
else:
|
||||||
zeilen.append("[color=#aaaaaa]Keine Mission. Schwarzes Brett am Dorfplatz.[/color]")
|
zeilen.append("[color=#aaaaaa]Keine Mission. Schwarzes Brett am Dorfplatz.[/color]")
|
||||||
_hud.text = "\n".join(zeilen)
|
_hud.text = "\n".join(zeilen)
|
||||||
|
_ablauf_aktualisieren()
|
||||||
|
|
||||||
|
|
||||||
func setze_hinweis(text: String) -> void:
|
func setze_hinweis(text: String) -> void:
|
||||||
@@ -381,13 +496,11 @@ func systeme_panel(human_id: String) -> void:
|
|||||||
_text("[b]%s[/b] — %s %s" % [Ursachen.LISTE[name]["name"],
|
_text("[b]%s[/b] — %s %s" % [Ursachen.LISTE[name]["name"],
|
||||||
Ursachen.LISTE[name]["kurz"], stand], 15)
|
Ursachen.LISTE[name]["kurz"], stand], 15)
|
||||||
if offene > 0:
|
if offene > 0:
|
||||||
_text("[color=#aaaaaa]Steh dabei, wenn sie etwas tut, das du noch nicht " +
|
_text("[color=#aaaaaa]Steh dabei, dann füllt sich ihr Tag. Und drücke [b]R[/b], " +
|
||||||
"kennst. Dann zeigt sich, warum.[/color]", 14)
|
"wenn du meinst, dass eine Stunde den Traum aufhält.[/color]", 14)
|
||||||
_trenner()
|
_trenner()
|
||||||
|
|
||||||
_text("[b]Ihr Tag[/b]", 17)
|
_tagesablauf(human_id)
|
||||||
for nr in range(Game.slots(human_id).size()):
|
|
||||||
_slot_zeile(human_id, nr)
|
|
||||||
_trenner()
|
_trenner()
|
||||||
_knopf("Zurück zum Journal", func() -> void: journal_panel("missionen"))
|
_knopf("Zurück zum Journal", func() -> void: journal_panel("missionen"))
|
||||||
_zeigen("systeme")
|
_zeigen("systeme")
|
||||||
@@ -395,27 +508,50 @@ func systeme_panel(human_id: String) -> void:
|
|||||||
_scroll_frames = 4
|
_scroll_frames = 4
|
||||||
|
|
||||||
|
|
||||||
func _slot_zeile(human_id: String, nr: int) -> void:
|
## Der Ablauf von 0 bis 23 Uhr. Jeder Tag ist gleich -- es gibt keine
|
||||||
var slot: Array = Game.slots(human_id)[nr]
|
## Wochentage. Zu Beginn ist jede Stunde leer; wer dabeisteht, füllt sie.
|
||||||
var stunde := "%02d:00" % int(slot[0])
|
func _tagesablauf(human_id: String) -> void:
|
||||||
var grund := Game.slot_grund(human_id, nr)
|
var offen := 0
|
||||||
if grund == "schlaeft":
|
for stunde in range(24):
|
||||||
_knopf("%s schläft" % stunde, func() -> void: pass, false)
|
if not Game.gesehen_slot(human_id, Game.slot_bei_stunde(human_id, stunde)):
|
||||||
return
|
offen += 1
|
||||||
|
_text("[b]Ihr Tag[/b] [color=#8e93a8]0 – 23 Uhr, jeden Tag derselbe%s[/color]" %
|
||||||
|
("" if offen == 0 else " · %d Stunden noch leer" % offen), 17)
|
||||||
|
|
||||||
|
# eine Zeile je Abschnitt des Tages -- auch wenn noch keiner bekannt ist,
|
||||||
|
# steht der Tag vollständig da
|
||||||
|
var von := 0
|
||||||
|
while von < 24:
|
||||||
|
var nr := Game.slot_bei_stunde(human_id, von)
|
||||||
|
var bis := von
|
||||||
|
while bis + 1 < 24 and Game.slot_bei_stunde(human_id, bis + 1) == nr:
|
||||||
|
bis += 1
|
||||||
|
_stunden_zeile(human_id, nr, von, bis, _stunden_text(human_id, nr))
|
||||||
|
von = bis + 1
|
||||||
|
|
||||||
|
|
||||||
|
func _stunden_text(human_id: String, nr: int) -> String:
|
||||||
|
if not Game.gesehen_slot(human_id, nr):
|
||||||
|
return "—"
|
||||||
var sys := Game.system_in(human_id, nr)
|
var sys := Game.system_in(human_id, nr)
|
||||||
if not sys.is_empty():
|
if not sys.is_empty():
|
||||||
_knopf("%s %s ← %s" % [stunde, Game.slot_aktivitaet(human_id, nr),
|
return "%s ← %s" % [Game.slot_aktivitaet(human_id, nr),
|
||||||
TechDB.by_id(str(sys["tech"]))["name"]],
|
TechDB.by_id(str(sys["tech"]))["name"]]
|
||||||
func() -> void: slot_panel(human_id, nr))
|
var grund := Game.slot_grund(human_id, nr)
|
||||||
return
|
|
||||||
if not Game.erkannt(human_id, nr):
|
if not Game.erkannt(human_id, nr):
|
||||||
_knopf("%s ?" % stunde, func() -> void: pass, false)
|
return "?"
|
||||||
return
|
|
||||||
var text := Orte.text(grund)
|
|
||||||
if Ursachen.ist_blocker(grund):
|
if Ursachen.ist_blocker(grund):
|
||||||
text = "%s (%s)" % [Ursachen.BLOCKER[grund]["name"],
|
return "%s (%s)" % [Ursachen.BLOCKER[grund]["name"],
|
||||||
Ursachen.LISTE[Ursachen.BLOCKER[grund]["ursache"]]["name"]]
|
Ursachen.LISTE[Ursachen.BLOCKER[grund]["ursache"]]["name"]]
|
||||||
_knopf("%s %s" % [stunde, text], func() -> void: slot_panel(human_id, nr))
|
return Orte.text(grund)
|
||||||
|
|
||||||
|
|
||||||
|
func _stunden_zeile(human_id: String, nr: int, von: int, bis: int, text: String) -> void:
|
||||||
|
var spanne := "%02d – %02d" % [von, bis + 1 if bis < 23 else 24]
|
||||||
|
var offen: bool = Game.gesehen_slot(human_id, nr) and Game.erkannt(human_id, nr) \
|
||||||
|
and Game.slot_grund(human_id, nr) != "schlaeft"
|
||||||
|
_knopf("%s %s" % [spanne, text],
|
||||||
|
func() -> void: slot_panel(human_id, nr), offen)
|
||||||
|
|
||||||
|
|
||||||
## Was soll in diesem Slot stehen?
|
## Was soll in diesem Slot stehen?
|
||||||
@@ -580,8 +716,9 @@ func anwenden_panel(mensch: Node3D) -> void:
|
|||||||
return
|
return
|
||||||
var grund := Game.slot_grund(id, nr)
|
var grund := Game.slot_grund(id, nr)
|
||||||
if not Game.erkannt(id, nr):
|
if not Game.erkannt(id, nr):
|
||||||
_text("[color=#ffcc66]Was hier läuft, hast du noch nicht gesehen. " +
|
_text("[color=#ffcc66]Was hier läuft, hast du noch nicht notiert. " +
|
||||||
"Bleib stehen und schau zu.[/color]", 16)
|
"Sieh ihr zu und drücke [b]R[/b], wenn du meinst, hier läuft etwas " +
|
||||||
|
"schief.[/color]", 16)
|
||||||
_zeigen("anwenden")
|
_zeigen("anwenden")
|
||||||
return
|
return
|
||||||
if Ursachen.ist_blocker(grund):
|
if Ursachen.ist_blocker(grund):
|
||||||
|
|||||||
351
tests/verify.gd
351
tests/verify.gd
@@ -34,7 +34,12 @@ func _ready() -> void:
|
|||||||
await test_tagesablauf()
|
await test_tagesablauf()
|
||||||
await test_wege_machbar()
|
await test_wege_machbar()
|
||||||
await test_geist()
|
await test_geist()
|
||||||
|
await test_verfolgung()
|
||||||
|
await test_gehen_bei_tempo()
|
||||||
await test_diagnose()
|
await test_diagnose()
|
||||||
|
await test_notieren_im_spiel()
|
||||||
|
await test_tagesablauf_anzeige()
|
||||||
|
await test_ablauf_im_hud()
|
||||||
await test_systeme()
|
await test_systeme()
|
||||||
await test_tempo_des_traums()
|
await test_tempo_des_traums()
|
||||||
await test_cooldown()
|
await test_cooldown()
|
||||||
@@ -691,6 +696,110 @@ func test_geist() -> void:
|
|||||||
p(lena.global_position.distance_to(davor) < 2.0, "Menschen nehmen den Geist nicht wahr")
|
p(lena.global_position.distance_to(davor) < 2.0, "Menschen nehmen den Geist nicht wahr")
|
||||||
|
|
||||||
|
|
||||||
|
## Bei hohem Tempo wird der Schritt je Bild größer als die Ankunftsschwelle.
|
||||||
|
## Wer dann über den Wegpunkt hinausläuft, pendelt um ihn herum statt anzukommen.
|
||||||
|
func test_gehen_bei_tempo() -> void:
|
||||||
|
abschnitt("Gehen bleibt auch im Schnelllauf ruhig")
|
||||||
|
ui().schliessen()
|
||||||
|
var mensch: Bewohner = welt().humans[0]
|
||||||
|
var stufe_kehrt: Dictionary = {}
|
||||||
|
for stufe in [1, 3]:
|
||||||
|
Game.tempo_setzen(stufe)
|
||||||
|
Game.time_h = 12.9
|
||||||
|
for i in range(400):
|
||||||
|
schritte(1, 1.0 / 60.0)
|
||||||
|
if not mensch._path.is_empty():
|
||||||
|
break
|
||||||
|
var kehrt := 0
|
||||||
|
var bewegt := 0
|
||||||
|
var vorher: Vector3 = mensch.global_position
|
||||||
|
var richtung := Vector3.ZERO
|
||||||
|
for i in range(600):
|
||||||
|
schritte(1, 1.0 / 60.0)
|
||||||
|
if mensch._path.is_empty():
|
||||||
|
continue
|
||||||
|
var d: Vector3 = mensch.global_position - vorher
|
||||||
|
d.y = 0.0
|
||||||
|
if d.length() > 0.001:
|
||||||
|
bewegt += 1
|
||||||
|
if richtung != Vector3.ZERO and d.normalized().dot(richtung) < -0.5:
|
||||||
|
kehrt += 1
|
||||||
|
richtung = d.normalized()
|
||||||
|
vorher = mensch.global_position
|
||||||
|
stufe_kehrt[stufe] = kehrt
|
||||||
|
var weite := Bewohner.GEHTEMPO * (1.0 / 60.0) * Game.faktor_von(stufe)
|
||||||
|
p(kehrt <= 4, "Tempo %d (%d×, %.2f je Bild): %d Kehrtwenden in %d Bildern" %
|
||||||
|
[stufe, int(Game.faktor_von(stufe)), weite, kehrt, bewegt])
|
||||||
|
p(int(stufe_kehrt[3]) <= int(stufe_kehrt[1]) + 4,
|
||||||
|
"Schnelllauf zappelt nicht mehr als Normaltempo (%d gegen %d)" %
|
||||||
|
[stufe_kehrt[3], stufe_kehrt[1]])
|
||||||
|
Game.tempo_setzen(1)
|
||||||
|
|
||||||
|
|
||||||
|
## Sich an einen Menschen hängen: der Geist begleitet ihn durch seinen Tag,
|
||||||
|
## statt ihm hinterherzulaufen. Damit bleibt er die ganze Zeit beobachtbar.
|
||||||
|
func test_verfolgung() -> void:
|
||||||
|
abschnitt("Als Geist einen Menschen begleiten")
|
||||||
|
p(InputMap.has_action("folgen") and not InputMap.action_get_events("folgen").is_empty(),
|
||||||
|
"Eigene Taste fürs Folgen")
|
||||||
|
ui().schliessen()
|
||||||
|
main.player.set_mode(Player.Mode.GEIST)
|
||||||
|
main.player.look_enabled = true
|
||||||
|
main.player.yaw = 0.0
|
||||||
|
var mensch: Bewohner = welt().humans[0]
|
||||||
|
Game.active_mission = str(mensch.data["id"])
|
||||||
|
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
|
||||||
|
main.player.velocity = Vector3.ZERO
|
||||||
|
main._process(0.05)
|
||||||
|
p(main._fokus_mensch == mensch, "Der Mensch ist im Fokus")
|
||||||
|
|
||||||
|
main._verfolgen()
|
||||||
|
p(main.player.folgt == mensch, "F hängt den Geist an ihn")
|
||||||
|
|
||||||
|
# Er lebt seinen Tag weiter, der Geist bleibt dran
|
||||||
|
var weiteste := 0.0
|
||||||
|
var gelaufen := false
|
||||||
|
var im_fokus := 0
|
||||||
|
for i in range(900):
|
||||||
|
schritte(1)
|
||||||
|
await get_tree().physics_frame
|
||||||
|
if not mensch._path.is_empty():
|
||||||
|
gelaufen = true
|
||||||
|
if main._fokus_mensch == mensch:
|
||||||
|
im_fokus += 1
|
||||||
|
weiteste = maxf(weiteste,
|
||||||
|
main.player.global_position.distance_to(mensch.global_position))
|
||||||
|
p(gelaufen, "Er wechselt in dieser Zeit den Ort")
|
||||||
|
p(weiteste < 6.0, "Der Geist bleibt die ganze Zeit dicht dabei (höchstens %.1f)" % weiteste)
|
||||||
|
p(im_fokus > 850, "Und behält ihn im Fokus (%d von 900 Schritten)" % im_fokus)
|
||||||
|
p(main._fokus_mensch == mensch or main.player.folgt == mensch,
|
||||||
|
"Unterwegs bleibt er notierbar")
|
||||||
|
|
||||||
|
# Eigene Bewegung löst die Verfolgung
|
||||||
|
Input.action_press("vor")
|
||||||
|
for i in range(10):
|
||||||
|
await get_tree().physics_frame
|
||||||
|
Input.action_release("vor")
|
||||||
|
p(main.player.folgt == null, "Wer selbst losgeht, lässt ihn los")
|
||||||
|
|
||||||
|
# Die Taste löst ebenfalls
|
||||||
|
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
|
||||||
|
main.player.velocity = Vector3.ZERO
|
||||||
|
main._process(0.05)
|
||||||
|
main._verfolgen()
|
||||||
|
p(main.player.folgt == mensch, "Wieder angehängt")
|
||||||
|
main._verfolgen()
|
||||||
|
p(main.player.folgt == null, "F lässt wieder los")
|
||||||
|
|
||||||
|
# In der Traumwelt gibt es niemanden zu verfolgen
|
||||||
|
main._traumwelt()
|
||||||
|
await get_tree().process_frame
|
||||||
|
main._verfolgen()
|
||||||
|
p(main.player.folgt == null, "In der Traumwelt hängt sich der Läufer an niemanden")
|
||||||
|
main._erde()
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
|
||||||
## Woran der Traum scheitert, steht nirgends. Man findet es, indem man
|
## Woran der Traum scheitert, steht nirgends. Man findet es, indem man
|
||||||
## dabeisteht -- awareness training, bevor irgendetwas ersetzt wird.
|
## dabeisteht -- awareness training, bevor irgendetwas ersetzt wird.
|
||||||
func test_diagnose() -> void:
|
func test_diagnose() -> void:
|
||||||
@@ -699,7 +808,7 @@ func test_diagnose() -> void:
|
|||||||
Game.active_mission = id
|
Game.active_mission = id
|
||||||
Game.systeme.clear()
|
Game.systeme.clear()
|
||||||
Game.erkannt_slots.clear()
|
Game.erkannt_slots.clear()
|
||||||
Game.beobachtung.clear()
|
Game.notiz_sperre = 0.0
|
||||||
|
|
||||||
var urs: Array = Game.ursachen_von(id)
|
var urs: Array = Game.ursachen_von(id)
|
||||||
p(urs.size() >= 2 and urs.size() <= 3,
|
p(urs.size() >= 2 and urs.size() <= 3,
|
||||||
@@ -739,11 +848,19 @@ func test_diagnose() -> void:
|
|||||||
p(not Game.ursache_bekannt(id, ursache), "Und die Ursache ist noch unbekannt")
|
p(not Game.ursache_bekannt(id, ursache), "Und die Ursache ist noch unbekannt")
|
||||||
|
|
||||||
# Solange man nicht lange genug dabeisteht, passiert nichts
|
# Solange man nicht lange genug dabeisteht, passiert nichts
|
||||||
Game.beobachten(id, blocker_nr, Game.ERKENNEN_SEK * 0.4)
|
# Zusehen allein deckt nichts auf -- der Spieler muss den Moment benennen
|
||||||
p(not Game.erkannt(id, blocker_nr), "Kurz hinschauen reicht nicht")
|
for i in range(60):
|
||||||
Game.beobachten(id, blocker_nr, Game.ERKENNEN_SEK)
|
schritte(1)
|
||||||
p(Game.erkannt(id, blocker_nr), "Nach %.0f Sekunden dabeistehen zeigt es sich" %
|
p(not Game.erkannt(id, blocker_nr), "Bloßes Dabeistehen deckt nichts auf")
|
||||||
Game.ERKENNEN_SEK)
|
p(Game.notieren(id, neutral_nr) == "daneben",
|
||||||
|
"Im freien Slot geht die Notiz daneben")
|
||||||
|
p(Game.notiz_sperre > 0.0, "Ein Fehlgriff sperrt kurz (%.0f s)" % Game.notiz_sperre)
|
||||||
|
p(Game.notieren(id, blocker_nr) == "gesperrt", "Solange nimmt er keine Notiz an")
|
||||||
|
Game.notiz_sperre = 0.0
|
||||||
|
p(Game.notieren(id, blocker_nr) == "treffer", "Im Blocker-Slot sitzt sie")
|
||||||
|
p(Game.erkannt(id, blocker_nr), "Und deckt auf, was dort läuft")
|
||||||
|
p(is_equal_approx(Game.notiz_sperre, 0.0), "Ein Treffer sperrt nicht")
|
||||||
|
p(Game.notieren(id, blocker_nr) == "bekannt", "Zweimal notieren bringt nichts Neues")
|
||||||
p(Game.ursache_bekannt(id, ursache),
|
p(Game.ursache_bekannt(id, ursache),
|
||||||
"Und damit die Ursache: %s" % Ursachen.LISTE[ursache]["name"])
|
"Und damit die Ursache: %s" % Ursachen.LISTE[ursache]["name"])
|
||||||
|
|
||||||
@@ -761,6 +878,226 @@ func test_diagnose() -> void:
|
|||||||
|
|
||||||
|
|
||||||
## Gegen eine Ursache baut man ein System. Passt die Belohnung, hält es besser.
|
## Gegen eine Ursache baut man ein System. Passt die Belohnung, hält es besser.
|
||||||
|
## Der Ablauf steht auch im HUD, ohne dass man ein Panel öffnen muss.
|
||||||
|
func test_ablauf_im_hud() -> void:
|
||||||
|
abschnitt("Der Ablauf steht im HUD")
|
||||||
|
var id := "mensch_0"
|
||||||
|
Game.gesehen.clear()
|
||||||
|
Game.erkannt_slots.clear()
|
||||||
|
Game.systeme.clear()
|
||||||
|
Game.active_mission = ""
|
||||||
|
ui().schliessen()
|
||||||
|
ui()._hud_aktualisieren()
|
||||||
|
p(not ui()._ablauf.visible, "Ohne Mission zeigt das HUD keinen Ablauf")
|
||||||
|
|
||||||
|
Game.active_mission = id
|
||||||
|
Game.time_h = 9.5
|
||||||
|
ui()._hud_aktualisieren()
|
||||||
|
p(ui()._ablauf.visible, "Mit Mission schon")
|
||||||
|
p(ui()._zellen.size() == 24, "Ein Feld je Stunde (%d)" % ui()._zellen.size())
|
||||||
|
p(ui()._ablauf_titel.text.contains("0 – 23"), "Beschriftet von 0 bis 23 Uhr")
|
||||||
|
p(ui()._ablauf_titel.text.contains("24 Stunden unbekannt"),
|
||||||
|
"Am Anfang ist alles unbekannt: %s" % ui()._ablauf_titel.text)
|
||||||
|
|
||||||
|
var unbekannt: Color = ui()._ablauf_farbe(id, Game.slot_bei_stunde(id, 9))
|
||||||
|
var nr := Game.slot_bei_stunde(id, 9)
|
||||||
|
Game.sehen(id, nr)
|
||||||
|
ui()._hud_aktualisieren()
|
||||||
|
p(ui()._ablauf_farbe(id, nr) != unbekannt, "Eine gesehene Stunde färbt sich um")
|
||||||
|
p(not ui()._ablauf_titel.text.contains("24 Stunden"),
|
||||||
|
"Und der Zähler geht runter: %s" % ui()._ablauf_titel.text)
|
||||||
|
p(ui()._ablauf_jetzt.text.begins_with("09 Uhr"),
|
||||||
|
"Darunter steht die laufende Stunde: %s" % ui()._ablauf_jetzt.text)
|
||||||
|
|
||||||
|
# Die laufende Stunde ist hervorgehoben
|
||||||
|
var jetzt: ColorRect = ui()._zellen[9]
|
||||||
|
var andere: ColorRect = ui()._zellen[8]
|
||||||
|
p(jetzt.custom_minimum_size.y > andere.custom_minimum_size.y,
|
||||||
|
"Das Feld der laufenden Stunde steht heraus")
|
||||||
|
|
||||||
|
# Ein benannter Blocker sieht anders aus als eine harmlose Stunde
|
||||||
|
var blocker_nr := -1
|
||||||
|
var frei_nr := -1
|
||||||
|
for n in range(Game.slots(id).size()):
|
||||||
|
var g := Game.slot_grund(id, n)
|
||||||
|
if Ursachen.ist_blocker(g) and blocker_nr < 0:
|
||||||
|
blocker_nr = n
|
||||||
|
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and frei_nr < 0:
|
||||||
|
frei_nr = n
|
||||||
|
Game.sehen(id, blocker_nr)
|
||||||
|
Game.sehen(id, frei_nr)
|
||||||
|
var vor_notiz: Color = ui()._ablauf_farbe(id, blocker_nr)
|
||||||
|
Game.notieren(id, blocker_nr)
|
||||||
|
p(ui()._ablauf_farbe(id, blocker_nr) != vor_notiz,
|
||||||
|
"Notiert wechselt der Blocker noch einmal die Farbe")
|
||||||
|
p(ui()._ablauf_farbe(id, blocker_nr) != ui()._ablauf_farbe(id, frei_nr),
|
||||||
|
"Und hebt sich von einer harmlosen Stunde ab")
|
||||||
|
Game.notiz_sperre = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
## Der Ablauf 0-23 Uhr: am Anfang leer, füllt sich durchs Dabeisein.
|
||||||
|
func test_tagesablauf_anzeige() -> void:
|
||||||
|
abschnitt("Der Tag steht als Ablauf von 0 bis 23 Uhr")
|
||||||
|
var id := "mensch_0"
|
||||||
|
Game.active_mission = id
|
||||||
|
Game.gesehen.clear()
|
||||||
|
Game.erkannt_slots.clear()
|
||||||
|
Game.systeme.clear()
|
||||||
|
Game.notiz_sperre = 0.0
|
||||||
|
|
||||||
|
# Jede Stunde des Tages hat einen Slot, und jeder Tag ist derselbe
|
||||||
|
var luecken := 0
|
||||||
|
for stunde in range(24):
|
||||||
|
if Game.slot_bei_stunde(id, stunde) < 0:
|
||||||
|
luecken += 1
|
||||||
|
p(luecken == 0, "Alle 24 Stunden sind belegt")
|
||||||
|
Game.day = 1
|
||||||
|
var tag_eins: Array = []
|
||||||
|
for stunde in range(24):
|
||||||
|
tag_eins.append(Game.slot_bei_stunde(id, stunde))
|
||||||
|
Game.day = 9
|
||||||
|
var tag_neun: Array = []
|
||||||
|
for stunde in range(24):
|
||||||
|
tag_neun.append(Game.slot_bei_stunde(id, stunde))
|
||||||
|
p(tag_eins == tag_neun, "Tag 9 sieht aus wie Tag 1 — es gibt keine Wochentage")
|
||||||
|
Game.day = 1
|
||||||
|
|
||||||
|
ui().systeme_panel(id)
|
||||||
|
var zeilen := _knopf_texte()
|
||||||
|
p(zeilen.contains("00 – "), "Der Ablauf beginnt um 00 Uhr")
|
||||||
|
p(zeilen.contains(" – 24"), "Und reicht bis 24 Uhr")
|
||||||
|
var leer := 0
|
||||||
|
for stunde in range(24):
|
||||||
|
if not Game.gesehen_slot(id, Game.slot_bei_stunde(id, stunde)):
|
||||||
|
leer += 1
|
||||||
|
p(leer == 24, "Am Anfang ist jede der 24 Stunden unbekannt (%d)" % leer)
|
||||||
|
p(zeilen.contains("—") and not zeilen.contains("schläft"),
|
||||||
|
"Und im Ablauf steht nur ein Strich")
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
# Dabeistehen füllt genau die Stunde, in der man steht
|
||||||
|
var mensch: Bewohner = null
|
||||||
|
for m in welt().humans:
|
||||||
|
if m.data["id"] == id:
|
||||||
|
mensch = m
|
||||||
|
var frei_nr := -1
|
||||||
|
for nr in range(Game.slots(id).size()):
|
||||||
|
var g := Game.slot_grund(id, nr)
|
||||||
|
if g != "schlaeft" and not Ursachen.ist_blocker(g):
|
||||||
|
frei_nr = nr
|
||||||
|
break
|
||||||
|
await _hin_zu(mensch, float(Game.slots(id)[frei_nr][0]) + 0.5)
|
||||||
|
main._process(0.05)
|
||||||
|
p(Game.gesehen_slot(id, frei_nr), "Wer dabeisteht, füllt die Stunde")
|
||||||
|
ui().systeme_panel(id)
|
||||||
|
p(_knopf_texte().contains(Orte.text(Game.slot_grund(id, frei_nr))),
|
||||||
|
"Sie steht jetzt im Ablauf: %s" % Orte.text(Game.slot_grund(id, frei_nr)))
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
# Ein Blocker bleibt ein Fragezeichen, bis er benannt ist
|
||||||
|
var blocker_nr := -1
|
||||||
|
for nr in range(Game.slots(id).size()):
|
||||||
|
if Ursachen.ist_blocker(Game.slot_grund(id, nr)):
|
||||||
|
blocker_nr = nr
|
||||||
|
break
|
||||||
|
Game.sehen(id, blocker_nr)
|
||||||
|
ui().systeme_panel(id)
|
||||||
|
p(_knopf_texte().contains("?"), "Was schiefläuft, steht erst als Fragezeichen da")
|
||||||
|
ui().schliessen()
|
||||||
|
Game.notieren(id, blocker_nr)
|
||||||
|
ui().systeme_panel(id)
|
||||||
|
var b: Dictionary = Ursachen.BLOCKER[Game.slot_grund(id, blocker_nr)]
|
||||||
|
p(_knopf_texte().contains(str(b["name"])),
|
||||||
|
"Nach dem Notieren steht dort, was sie tut")
|
||||||
|
p(_knopf_texte().contains(str(Ursachen.LISTE[b["ursache"]]["name"])),
|
||||||
|
"Und warum: %s" % Ursachen.LISTE[b["ursache"]]["name"])
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
|
||||||
|
## Der ganze Weg: dabeistehen, den Moment einschätzen, R drücken.
|
||||||
|
func test_notieren_im_spiel() -> void:
|
||||||
|
abschnitt("Notieren ist eine Entscheidung, kein Timer")
|
||||||
|
var id := "mensch_0"
|
||||||
|
Game.active_mission = id
|
||||||
|
Game.erkannt_slots.clear()
|
||||||
|
Game.notiz_sperre = 0.0
|
||||||
|
ui().schliessen()
|
||||||
|
main.player.set_mode(Player.Mode.GEIST)
|
||||||
|
main.player.look_enabled = true
|
||||||
|
|
||||||
|
var blocker_nr := -1
|
||||||
|
var frei_nr := -1
|
||||||
|
for nr in range(Game.slots(id).size()):
|
||||||
|
var g := Game.slot_grund(id, nr)
|
||||||
|
if Ursachen.ist_blocker(g) and blocker_nr < 0:
|
||||||
|
blocker_nr = nr
|
||||||
|
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and frei_nr < 0:
|
||||||
|
frei_nr = nr
|
||||||
|
p(blocker_nr >= 0 and frei_nr >= 0, "Ihr Tag hat einen Blocker und einen freien Slot")
|
||||||
|
|
||||||
|
var mensch: Bewohner = null
|
||||||
|
for m in welt().humans:
|
||||||
|
if m.data["id"] == id:
|
||||||
|
mensch = m
|
||||||
|
|
||||||
|
# Im freien Slot danebengegriffen
|
||||||
|
await _hin_zu(mensch, float(Game.slots(id)[frei_nr][0]) + 0.5)
|
||||||
|
p(mensch.slot == frei_nr, "Sie steht im freien Slot (%d)" % mensch.slot)
|
||||||
|
main._notieren()
|
||||||
|
p(not Game.erkannt(id, blocker_nr), "Hier gibt es nichts zu notieren")
|
||||||
|
p(Game.notiz_sperre > 0.0, "Der Fehlgriff kostet eine Sperre")
|
||||||
|
|
||||||
|
# Sperre läuft in Echtzeit ab
|
||||||
|
for i in range(200):
|
||||||
|
main._process(0.05)
|
||||||
|
p(is_equal_approx(Game.notiz_sperre, 0.0), "Sie läuft von allein ab")
|
||||||
|
|
||||||
|
# Im Blocker-Slot getroffen
|
||||||
|
await _hin_zu(mensch, float(Game.slots(id)[blocker_nr][0]) + 0.5)
|
||||||
|
p(mensch.slot == blocker_nr, "Jetzt steht sie im Blocker-Slot (%d)" % mensch.slot)
|
||||||
|
p(main._fokus_mensch == mensch, "Und ist in Reichweite")
|
||||||
|
main._notieren()
|
||||||
|
p(Game.erkannt(id, blocker_nr), "R notiert, was hier schiefläuft: %s" %
|
||||||
|
Orte.text(Game.slot_grund(id, blocker_nr)))
|
||||||
|
|
||||||
|
# Ein fremder Mensch geht niemanden etwas an
|
||||||
|
Game.erkannt_slots.clear()
|
||||||
|
Game.notiz_sperre = 0.0
|
||||||
|
var fremd: Bewohner = null
|
||||||
|
for m in welt().humans:
|
||||||
|
if m.data["id"] != id:
|
||||||
|
fremd = m
|
||||||
|
main.player.folgt = null
|
||||||
|
main.player.global_position = fremd.global_position + Vector3(0, 1.5, 1.5)
|
||||||
|
main._process(0.05)
|
||||||
|
main._notieren()
|
||||||
|
p(Game.erkannt_slots.is_empty(),
|
||||||
|
"Ohne angenommene Mission notiert er bei %s nichts" % fremd.data["name"])
|
||||||
|
p(is_equal_approx(Game.notiz_sperre, 0.0), "Und das kostet auch keine Sperre")
|
||||||
|
|
||||||
|
# Aus der Verfolgung heraus geht es ebenfalls
|
||||||
|
Game.erkannt_slots.clear()
|
||||||
|
await _hin_zu(mensch, float(Game.slots(id)[blocker_nr][0]) + 0.5)
|
||||||
|
main._verfolgen()
|
||||||
|
p(main.player.folgt == mensch, "An sie gehängt")
|
||||||
|
main._notieren()
|
||||||
|
p(Game.erkannt(id, blocker_nr), "Auch im Vorbeischweben lässt sich notieren")
|
||||||
|
main.player.folgt = null
|
||||||
|
|
||||||
|
|
||||||
|
## Stellt den Geist zur genannten Stunde neben den Menschen.
|
||||||
|
func _hin_zu(mensch: Bewohner, stunde: float) -> void:
|
||||||
|
for i in range(600):
|
||||||
|
schritte(1, 0.05, stunde)
|
||||||
|
if mensch._path.is_empty():
|
||||||
|
break
|
||||||
|
main.player.folgt = null
|
||||||
|
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 1.5)
|
||||||
|
main.player.velocity = Vector3.ZERO
|
||||||
|
await get_tree().process_frame
|
||||||
|
main._process(0.05)
|
||||||
|
|
||||||
|
|
||||||
func test_systeme() -> void:
|
func test_systeme() -> void:
|
||||||
abschnitt("Systeme — gegen die Ursache, nicht gegen die Tätigkeit")
|
abschnitt("Systeme — gegen die Ursache, nicht gegen die Tätigkeit")
|
||||||
var id := "mensch_0"
|
var id := "mensch_0"
|
||||||
@@ -1612,7 +1949,7 @@ func _erster_traum_tage(saat: int) -> int:
|
|||||||
Game.known.clear()
|
Game.known.clear()
|
||||||
Game.systeme.clear()
|
Game.systeme.clear()
|
||||||
Game.erkannt_slots.clear()
|
Game.erkannt_slots.clear()
|
||||||
Game.beobachtung.clear()
|
Game.notiz_sperre = 0.0
|
||||||
Game.progress.clear()
|
Game.progress.clear()
|
||||||
Game.completed.clear()
|
Game.completed.clear()
|
||||||
Game.active_mission = ""
|
Game.active_mission = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user