This commit is contained in:
Team3
2026-09-15 23:17:07 +02:00
parent c62c6e551f
commit f1b0f15e30
16 changed files with 1116 additions and 44 deletions

View File

@@ -1,27 +1,29 @@
78098b89c88a74ebe8c20117b8754620
::res://::1788260833
anforderungen.md::TextFile::-1::1788584851::0::1::::<><><>0<>0<><>::
README.md::TextFile::-1::1788471905::0::1::::<><><>0<>0<><>::
README.md::TextFile::-1::1788598652::0::1::::<><><>0<>0<><>::
::res://scenes/::1788012512
main.tscn::PackedScene::-1::1788012512::0::1::::<><><>0<>0<><>::res://scripts/main.gd
::res://scripts/::1788584883
bewohner.gd::GDScript::2125278938812678811::1788463944::0::1::::Bewohner<>Node3D<><>0<>0<><>::
::res://scripts/::1789505734
aushaenge.gd::GDScript::4842442143352950821::1789505730::0::1::::Aushaenge<>RefCounted<><>0<>0<><>::
bewohner.gd::GDScript::2125278938812678811::1789505829::0::1::::Bewohner<>Node3D<><>0<>0<><>::
dream_db.gd::GDScript::4217518896355437099::1788260794::0::1::::DreamDB<>RefCounted<><>0<>0<><>::
dream_world.gd::GDScript::8279158278942867258::1788260794::0::1::::DreamWorld<>Node3D<><>0<>0<><>::
earth_world.gd::GDScript::6086818627272428934::1788260794::0::1::::EarthWorld<>Node3D<><>0<>0<><>::
game.gd::GDScript::3600922541500868814::1788468437::0::1::::<>Node<><>0<>0<><>::
human_db.gd::GDScript::412604892032291296::1788584883::0::1::::HumanDB<>RefCounted<><>0<>0<><>::
main.gd::GDScript::1504009331368977934::1788471754::0::1::::<>Node3D<><>0<>0<><>::
game.gd::GDScript::3600922541500868814::1789505829::0::1::::<>Node<><>0<>0<><>::
human_db.gd::GDScript::412604892032291296::1789505730::0::1::::HumanDB<>RefCounted<><>0<>0<><>::
main.gd::GDScript::1504009331368977934::1788598457::0::1::::<>Node3D<><>0<>0<><>::
netz.gd::GDScript::8422943321868648339::1788260794::0::1::::Netz<>RefCounted<><>0<>0<><>::
orte.gd::GDScript::5536340784294498671::1788260833::0::1::::Orte<>RefCounted<><>0<>0<><>::
player.gd::GDScript::8724108548430371398::1788459499::0::1::::Player<>CharacterBody3D<><>0<>0<><>::
prims.gd::GDScript::322632462553361584::1788088940::0::1::::Prims<>RefCounted<><>0<>0<><>::
probleme.gd::GDScript::4928319609961045127::1788584883::0::1::::Probleme<>RefCounted<><>0<>0<><>::
technique_db.gd::GDScript::4543286561661639701::1788260833::0::1::::TechDB<>RefCounted<><>0<>0<><>::
ui.gd::GDScript::843471304695966286::1788471755::0::1::::UI<>CanvasLayer<><>0<>0<><>::
ursachen.gd::GDScript::7371116734724270900::1788260833::0::1::::Ursachen<>RefCounted<><>0<>0<><>::
::res://tests/::1788471701
probleme.gd::GDScript::4928319609961045127::1789505398::0::1::::Probleme<>RefCounted<><>0<>0<><>::
technique_db.gd::GDScript::4543286561661639701::1789505829::0::1::::TechDB<>RefCounted<><>0<>0<><>::
ui.gd::GDScript::843471304695966286::1789506056::0::1::::UI<>CanvasLayer<><>0<>0<><>::
ursachen.gd::GDScript::7371116734724270900::1789505398::0::1::::Ursachen<>RefCounted<><>0<>0<><>::
woerter.gd::GDScript::1848212517770692820::1789505563::0::1::::Woerter<>RefCounted<><>0<>0<><>::
::res://tests/::1788598610
shots.gd::GDScript::5999651346126871724::1788260833::0::1::::<>Node<><>0<>0<><>::
shots.tscn::PackedScene::-1::1788012512::0::1::::<><><>0<>0<><>::res://tests/shots.gd
verify.gd::GDScript::4392606218392155269::1788471838::0::1::::<>Node<><>0<>0<><>::
verify.gd::GDScript::4392606218392155269::1789505952::0::1::::<>Node<><>0<>0<><>::
verify.tscn::PackedScene::-1::1788012512::0::1::::<><><>0<>0<><>::res://tests/verify.gd

View File

@@ -1,4 +1,12 @@
list=[{
"base": &"RefCounted",
"class": &"Aushaenge",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/aushaenge.gd"
}, {
"base": &"Node3D",
"class": &"Bewohner",
"icon": "",
@@ -102,4 +110,12 @@ list=[{
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/ursachen.gd"
}, {
"base": &"RefCounted",
"class": &"Woerter",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://scripts/woerter.gd"
}]

Binary file not shown.

View File

@@ -54,6 +54,28 @@ Dreimal die richtige Technik im richtigen Moment, und das Problem ist
überwunden — dieser Moment kommt nicht wieder. Bis dahin umgehst du ihn jedes
Mal aufs Neue.
**Eigene Menschen** — Aushang schreiben
Am Schwarzen Brett lässt sich ein Mensch selbst schreiben: Name, Alter, Beruf,
Traum, Geschichte — und **Wörter**. Fünfundfünfzig Alltagswörter, wie Leute
über sich reden: *aufschiebend, konfliktscheu, nachtaktiv, hadert mit sich*.
Mindestens sechs. Sie sind das Einzige, was die Mechanik liest: aus ihnen
schließt das Spiel die Ursachen, die Blocker im Tag, die Klemmen und acht
Verhaltenswerte — wie leicht ein System sich setzen lässt, wie gut es trägt,
wie stark die Abwehr ist, wie oft Momente aufbrechen, wie lange sie dauern,
wie schnell der Traum von sich aus vorankommt, wie gut soziale Techniken
greifen, wie leicht man ihm ansieht, was dahintersteckt. Ein Wort zieht an
mehreren Stellen, oft in beide Richtungen: *perfektionistisch* lässt Systeme
besser tragen und den Anfang schwerer fallen.
Der Beruf gibt den Tagestyp: Heimarbeit, Frühschicht, Büro, Studium,
Werkstatt. Jeder eigene Mensch hat so einen eigenen Alltag, in dem seine
Blocker an ihren Tageszeiten stehen — und jede seiner Ursachen mindestens
einmal, sonst wäre sie nie zu erkennen. Ein Zufall-Knopf füllt das Formular
so, wie der Generator Menschen macht. Eigene Aushänge liegen unter
`user://aushaenge.cfg`, überleben den Neustart und ersetzen die letzten
erzeugten Einwohner; ein Aushang lässt sich wieder abnehmen.
**Diagnose und Systeme** — der Kern des Spiels
Ein Traum scheitert an Ursachen: *kein Anfang*, *keine Zeit*, *keine Kraft*,
@@ -197,6 +219,8 @@ eintauschen könnte. Ein Erfolg bringt 2 EP, ein Fehlversuch 1 — auch der zäh
scripts/earth_world.gd Stadt mit Tag-Nacht-Wechsel
scripts/ursachen.gd Ursachen, blockierende Tätigkeiten, Belohnungen
scripts/probleme.gd Psychische Probleme und die Momente, in denen sie aufbrechen
scripts/woerter.gd Alltagswörter und was das Spiel daraus schließt
scripts/aushaenge.gd Eigene Aushänge: speichern, laden, prüfen
scripts/ui.gd HUD, Journal, Tagesablauf, Hörsaal, Brett, Panels
scripts/prims.gd Bausteine aus Primitiven

65
scripts/aushaenge.gd Normal file
View File

@@ -0,0 +1,65 @@
class_name Aushaenge
## Eigene Aushänge: Menschen, die jemand am Schwarzen Brett selbst geschrieben
## hat. Überleben den Neustart, liegen als ConfigFile unter user://.
static var PFAD := "user://aushaenge.cfg"
const FELDER := ["name", "alter", "beruf", "traum", "geschichte", "woerter"]
static func laden() -> Array:
var cfg := ConfigFile.new()
if cfg.load(PFAD) != OK:
return []
var out: Array = []
for abschnitt in cfg.get_sections():
var pr: Dictionary = {"id": abschnitt}
for f in FELDER:
pr[f] = cfg.get_value(abschnitt, f, "" if f != "woerter" else [])
pr["woerter"] = Array(pr["woerter"])
out.append(pr)
return out
## Legt einen Aushang an oder überschreibt ihn. Gibt die id zurück.
static func speichern(pr: Dictionary) -> String:
var cfg := ConfigFile.new()
cfg.load(PFAD)
var id := str(pr.get("id", ""))
if id == "":
var n := 0
while cfg.has_section("eigen_%d" % n):
n += 1
id = "eigen_%d" % n
for f in FELDER:
cfg.set_value(id, f, pr.get(f, "" if f != "woerter" else []))
cfg.save(PFAD)
return id
static func loeschen(id: String) -> void:
var cfg := ConfigFile.new()
if cfg.load(PFAD) != OK:
return
if cfg.has_section(id):
cfg.erase_section(id)
cfg.save(PFAD)
static func alle_loeschen() -> void:
var cfg := ConfigFile.new()
cfg.save(PFAD)
## Was am Aushang fehlt, bevor er gilt. Leer heißt: alles da.
static func pruefen(pr: Dictionary) -> Array:
var fehler: Array = []
if str(pr.get("name", "")).strip_edges() == "":
fehler.append("Name fehlt")
if str(pr.get("traum", "")).strip_edges() == "":
fehler.append("Traum fehlt")
if not HumanDB.BERUF_TAGESTYP.has(str(pr.get("beruf", ""))):
fehler.append("Beruf fehlt")
var w: Array = pr.get("woerter", [])
if w.size() < Woerter.MINDESTENS:
fehler.append("mindestens %d Wörter (%d)" % [Woerter.MINDESTENS, w.size()])
return fehler

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

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

View File

@@ -169,7 +169,8 @@ func reagiere(success: bool, text: String) -> void:
tw.tween_property(l, "modulate:a", 0.0, 2.4).set_delay(0.9)
tw.chain().tween_callback(l.queue_free)
if not success:
resist_left = Game.RESIST_SEKUNDEN
resist_left = Game.RESIST_SEKUNDEN * \
(1.0 + 0.3 * float(Game.verhalten(str(data["id"]), "abwehr")))
var blitz := Prims.mat(
Color(0.6, 1.0, 0.7) if success else Color(1, 0.5, 0.4), 1.6)
var orig := _koerper.material_override

View File

@@ -30,6 +30,7 @@ var situation_pause: float = 0.0 # bis die nächste kommen darf
var probleme_bekannt: Dictionary = {} # human_id -> {problem -> true}
var problem_stand: Dictionary = {} # "human:problem" -> gelungene Anwendungen
var ueberwunden: Dictionary = {} # human_id -> {problem -> true}
var gesehen_zaehler: Dictionary = {} # "human:problem" -> wie oft dabeigewesen
var completed: Array = []
var time_h: float = 7.0
var day: int = 1
@@ -444,7 +445,7 @@ func ursache_geloest(human_id: String, ursache: String) -> float:
## Wie schnell kommt der Traum voran? Jede ungelöste Ursache bremst.
func traum_tempo(human_id: String) -> float:
var t := BASIS_TEMPO
var t := BASIS_TEMPO * (1.0 + 0.15 * float(verhalten(human_id, "tempo")))
for u in ursachen_von(human_id):
var g := ursache_geloest(human_id, str(u))
t *= g if g > 0.0 else float(Ursachen.LISTE[u]["faktor"])
@@ -490,8 +491,11 @@ func system_setzen(human_id: String, nr: int, tech_id: String, widerstand: bool)
if not pruef["geht"]:
return {"ok": false, "grund": pruef["grund"]}
cooldown[tech_id] = TECHNIK_COOLDOWN
var chance := clampf(0.20 + 0.13 * float(skill_of(tech_id)) - (0.25 if widerstand else 0.0),
0.05, 0.95)
var chance := 0.20 + 0.13 * float(skill_of(tech_id)) - (0.25 if widerstand else 0.0)
chance += 0.08 * float(verhalten(human_id, "einrichten"))
if TechDB.ist_sozial(tech_id):
chance += 0.10 * float(verhalten(human_id, "sozial"))
chance = clampf(chance, 0.05, 0.95)
var erfolg := randf() < chance
if not erfolg:
add_xp(tech_id, 1)
@@ -499,7 +503,8 @@ func system_setzen(human_id: String, nr: int, tech_id: String, widerstand: bool)
return {"ok": true, "erfolg": false, "guete": 0.0}
if not systeme.has(human_id):
systeme[human_id] = {}
systeme[human_id][nr] = {"tech": tech_id, "guete": float(pruef["guete"])}
var guete := float(pruef["guete"]) * (1.0 + 0.1 * float(verhalten(human_id, "guete")))
systeme[human_id][nr] = {"tech": tech_id, "guete": guete}
add_xp(tech_id, 2)
notified.emit("System steht: %s um %02d:00" %
[TechDB.by_id(tech_id)["name"], int(slots(human_id)[nr][0])], Color(0.6, 1, 0.7))
@@ -548,6 +553,12 @@ const PROBLEM_UEBERWINDEN := 3 # so oft muss eine Technik greifen
const SITUATION_GEWINN := 6.0 # Prozent, wenn sie früh beendet wird
## Der Verhaltenswert eines Menschen, -2 bis +2 -- aus seinen Wörtern.
func verhalten(human_id: String, key: String) -> int:
var h := HumanDB.by_id(human_id)
return int((h.get("verhalten", {}) as Dictionary).get(key, 0))
func probleme_von(human_id: String) -> Array:
var h := HumanDB.by_id(human_id)
return h["probleme"] if h.has("probleme") else []
@@ -586,8 +597,9 @@ func situationen_ticken(delta: float, wach: bool) -> void:
var p := situation_problem()
notified.emit("Moment verpasst: %s" % Probleme.name_von(p),
Color(1, 0.75, 0.55))
var wer := str(situation["mensch"])
situation = {}
situation_pause = SITUATION_PAUSE
situation_pause = _pause_fuer(wer)
state_changed.emit()
return
situation_pause = maxf(0.0, situation_pause - delta)
@@ -596,6 +608,11 @@ func situationen_ticken(delta: float, wach: bool) -> void:
_situation_starten()
## Luft bis zum nächsten Moment -- ein reizbarer Mensch hat weniger davon.
func _pause_fuer(human_id: String) -> float:
return SITUATION_PAUSE * (1.0 - 0.25 * float(verhalten(human_id, "momente")))
func _situation_starten() -> void:
var offen: Array = []
for p in probleme_von(active_mission):
@@ -613,8 +630,10 @@ func _situation_starten() -> void:
"mensch": active_mission,
"key": str(moeglich[randi() % moeglich.size()]),
"problem": problem,
"rest": SITUATION_DAUER,
"rest": SITUATION_DAUER * (1.0 + 0.25 * float(verhalten(active_mission, "dauer"))),
}
if verhalten(active_mission, "sichtbar") > 0:
situation_sehen() # man sieht es ihm sofort an
state_changed.emit()
@@ -626,6 +645,11 @@ func situation_sehen() -> void:
var p := situation_problem()
if problem_bekannt(id, p):
return
if verhalten(id, "sichtbar") < 0:
var schluessel := "%s:%s" % [id, p]
gesehen_zaehler[schluessel] = int(gesehen_zaehler.get(schluessel, 0)) + 1
if int(gesehen_zaehler[schluessel]) < 2:
return # verschlossen: erst beim zweiten Mal zu durchschauen
if not probleme_bekannt.has(id):
probleme_bekannt[id] = {}
probleme_bekannt[id][p] = true
@@ -664,7 +688,7 @@ func situation_anwenden(tech_id: String) -> Dictionary:
problem_stand[schluessel] = problem_fortschritt(id, problem) + 1
situation_sehen()
situation = {}
situation_pause = SITUATION_PAUSE
situation_pause = _pause_fuer(id)
if id == active_mission:
progress[id] = minf(mission_progress(id) + SITUATION_GEWINN, MISSION_ZIEL)
notified.emit("%s trägt durch +%d %%" %

View File

@@ -9,6 +9,7 @@ class_name HumanDB
## an da und leben ihren Tag.
const EINWOHNER := 16
const BLOCKER_MAX := 4 # mehr Blocker, und der Tag wäre nur noch kaputt
const ACHSEN := [-72.0, -24.0, 24.0, 72.0] # Arterien in x wie in z
const ARTERIE := 7.0 # Breite einer Arterie
const ROJI := 3.2 # Breite einer Gasse
@@ -103,6 +104,58 @@ static var ANLIEGEN: Array = [
## Der Grundtag. Blocker werden später in passende Fenster gesetzt.
## [Stunde, Ort, Tätigkeit, Fenster]
## Jeder Beruf hat einen Tagestyp -- der bestimmt, wann gearbeitet wird und
## wo im Tag Platz für Blocker ist. So bekommt jeder Mensch einen eigenen Alltag.
static var BERUF_TAGESTYP: Dictionary = {
"Illustratorin": "heim", "Programmiererin": "heim", "Musiker": "heim",
"Bäcker": "frueh", "Pfleger": "frueh", "Fahrer": "frueh", "Köchin": "frueh",
"Sachbearbeiter": "buero", "Archivarin": "buero", "Lehrer": "buero",
"Studentin": "studium", "Tischlerin": "werkstatt",
}
static var TAGESTYP_ARBEIT: Dictionary = {
"heim": "heim", "frueh": "baeckerei", "buero": "buero", "studium": "uni",
"werkstatt": "werkstatt",
}
static var TAGESTYPEN: Dictionary = {
"heim": [
[0, "heim", "schlaeft", "nacht"], [7, "heim", "morgenroutine", "morgen"],
[9, "heim", "arbeitet", "tag"], [12, "cafe", "essen", "tag"],
[14, "heim", "arbeitet", "tag"], [17, "park", "unterwegs", "abend"],
[19, "heim", "ruht", "abend"], [21, "heim", "ruht", "abend"],
[23, "heim", "abendroutine", "nacht"],
],
"frueh": [
[0, "heim", "schlaeft", "nacht"], [4, "arbeit", "arbeitet", "morgen"],
[10, "cafe", "pause", "tag"], [12, "heim", "essen", "tag"],
[14, "heim", "ruht", "tag"], [16, "park", "unterwegs", "abend"],
[18, "heim", "ruht", "abend"], [20, "heim", "ruht", "abend"],
[22, "heim", "abendroutine", "nacht"],
],
"buero": [
[0, "heim", "schlaeft", "nacht"], [6, "heim", "morgenroutine", "morgen"],
[8, "arbeit", "arbeitet", "tag"], [12, "cafe", "essen", "tag"],
[14, "arbeit", "arbeitet", "tag"], [18, "arbeit", "arbeitet", "abend"],
[20, "heim", "essen", "abend"], [22, "heim", "ruht", "abend"],
[23, "heim", "abendroutine", "nacht"],
],
"studium": [
[0, "heim", "schlaeft", "nacht"], [8, "heim", "morgenroutine", "morgen"],
[10, "arbeit", "arbeitet", "tag"], [13, "cafe", "essen", "tag"],
[15, "arbeit", "arbeitet", "tag"], [18, "heim", "ruht", "abend"],
[20, "park", "unterwegs", "abend"], [22, "heim", "ruht", "abend"],
[23, "heim", "abendroutine", "nacht"],
],
"werkstatt": [
[0, "heim", "schlaeft", "nacht"], [6, "heim", "morgenroutine", "morgen"],
[7, "arbeit", "arbeitet", "tag"], [12, "cafe", "essen", "tag"],
[13, "arbeit", "arbeitet", "tag"], [17, "heim", "ruht", "abend"],
[19, "park", "unterwegs", "abend"], [21, "heim", "ruht", "abend"],
[23, "heim", "abendroutine", "nacht"],
],
}
static var GRUNDTAG: Array = [
[0, "heim", "schlaeft", "nacht"],
[7, "heim", "morgenroutine", "morgen"],
@@ -145,8 +198,12 @@ static func erzeuge(_ungenutzt: int = 0) -> void:
_sonderbauten_verteilen()
_wohnhaeuser_verteilen()
_anker_setzen()
for i in range(EINWOHNER):
var eigene := Aushaenge.laden()
var n_eigen := mini(eigene.size(), EINWOHNER)
for i in range(EINWOHNER - n_eigen):
LIST.append(_mensch(i))
for k in range(n_eigen):
LIST.append(mensch_aus_profil(eigene[k], EINWOHNER - n_eigen + k))
# ------------------------------------------------------------ Bezirk
@@ -424,8 +481,16 @@ static func ursachen_von(i: int) -> Array:
## Der Tagesablauf: Grundtag, in den je Ursache ein Blocker gesetzt wird.
static func _tagesablauf(i: int, heim: String, arbeit: String) -> Array:
return _tag_belegen(GRUNDTAG, heim, arbeit, [], ursachen_von(i), i)
## Legt Blocker in den Tag. Zuerst bekommt jede Ursache einen -- sonst wäre sie
## nie zu erkennen und bremste den Traum für immer. Danach kommen weitere
## gewünschte dazu, bis der Tag voll ist. Jeder Blocker nur in sein Zeitfenster.
static func _tag_belegen(vorlage: Array, heim: String, arbeit: String,
gewuenscht: Array, ursachen: Array, i: int) -> Array:
var slots: Array = []
for e in GRUNDTAG:
for e in vorlage:
var ort: String = str(e[1])
if ort == "heim":
ort = heim
@@ -433,32 +498,103 @@ static func _tagesablauf(i: int, heim: String, arbeit: String) -> Array:
ort = arbeit
slots.append([int(e[0]), ort, str(e[2]), str(e[3])])
var belegt: Dictionary = {}
var gesetzt: Array = []
# 1. je Ursache ein Blocker -- aus den gewünschten, sonst aus dem Katalog
var nr := 0
for u in ursachen_von(i):
for fenster in ["tag", "abend", "morgen", "nacht"]:
var kandidaten: Array = Ursachen.blocker_fuer(str(u), fenster)
if kandidaten.is_empty():
continue
var blocker: String = str(kandidaten[(i + nr) % kandidaten.size()])
var gesetzt := false
for k in range(slots.size()):
if belegt.has(k) or str(slots[k][2]) == "schlaeft":
continue
if str(slots[k][3]) != fenster:
continue
slots[k][2] = blocker
belegt[k] = true
gesetzt = true
break
if gesetzt:
for u in ursachen:
var kandidaten: Array = []
for b in gewuenscht:
if Ursachen.ursache_von(str(b)) == str(u):
kandidaten.append(str(b))
for b in Ursachen.alle_blocker(str(u)):
if not kandidaten.has(str(b)):
kandidaten.append(str(b))
for k in range(kandidaten.size()):
var b: String = str(kandidaten[(k + i + nr) % kandidaten.size()])
if _blocker_setzen(slots, belegt, b):
gesetzt.append(b)
break
nr += 1
# 2. weitere gewünschte, solange Platz ist
for b in gewuenscht:
if gesetzt.size() >= BLOCKER_MAX:
break
if gesetzt.has(str(b)) or not Ursachen.BLOCKER.has(b):
continue
if _blocker_setzen(slots, belegt, str(b)):
gesetzt.append(str(b))
var out: Array = []
for e in slots:
out.append([e[0], e[1], e[2]])
return out
## Ein Blocker in den ersten freien Slot seines Zeitfensters. Nie in den Schlaf.
static func _blocker_setzen(slots: Array, belegt: Dictionary, b: String) -> bool:
var fenster := str(Ursachen.BLOCKER[b]["zeit"])
for k in range(slots.size()):
if belegt.has(k) or str(slots[k][2]) == "schlaeft" or str(slots[k][3]) != fenster:
continue
slots[k][2] = b
belegt[k] = true
return true
return false
## Ein Mensch, den jemand selbst geschrieben hat. Name, Beruf, Traum und
## Geschichte kommen vom Aushang; Ursachen, Blocker, Klemmen und Verhalten
## schließt das Spiel aus den angekreuzten Wörtern.
static func mensch_aus_profil(pr: Dictionary, i: int) -> Dictionary:
var bild := Woerter.profil(pr.get("woerter", []))
var beruf := str(pr.get("beruf", ""))
var typ := str(BERUF_TAGESTYP.get(beruf, "buero"))
var heim := heim_key(i)
var arbeit := str(TAGESTYP_ARBEIT.get(typ, "buero"))
if arbeit == "heim":
arbeit = heim
elif not PLACES.has(arbeit):
arbeit = _naechster_arbeitsplatz(heim, i)
return {
"id": str(pr.get("id", "eigen_%d" % i)),
"name": str(pr.get("name", "Unbekannt")),
"alter": int(pr.get("alter", 30)),
"beruf": beruf,
"traum": str(pr.get("traum", "")),
"problem": str(pr.get("geschichte", "")),
"tags": [],
"ursachen": bild["ursachen"],
"probleme": bild["probleme"],
"verhalten": bild["verhalten"],
"woerter": Array(pr.get("woerter", [])),
"eigen": true,
"mission": true,
"heim": heim,
"farbe": Color.from_hsv(fmod(0.02 + float(i) * 0.191, 1.0), 0.5, 0.8),
"routine": _tag_belegen(TAGESTYPEN[typ], heim, arbeit, bild["blocker"],
bild["ursachen"], i),
}
## Der Zufall-Knopf: füllt einen Aushang so, wie der Generator Menschen macht.
static func zufallsprofil() -> Dictionary:
var i := randi() % 1000
var anliegen: Dictionary = ANLIEGEN[i % ANLIEGEN.size()]
var alle: Array = Woerter.ids()
alle.shuffle()
var woerter: Array = alle.slice(0, Woerter.MINDESTENS + randi() % 4)
return {
"name": "%s %s" % [_vorname(i), _nachname(i)],
"alter": 20 + randi() % 45,
"beruf": str(BERUFE[i % BERUFE.size()]),
"traum": str(anliegen["traum"]),
"geschichte": str(anliegen["problem"]),
"woerter": woerter,
}
static func _mensch(i: int) -> Dictionary:
var anliegen: Dictionary = ANLIEGEN[i % ANLIEGEN.size()]
if i < PASSEND.size():
@@ -475,6 +611,7 @@ static func _mensch(i: int) -> Dictionary:
"tags": (anliegen["tags"] as Array).duplicate(),
"ursachen": ursachen_von(i),
"probleme": probleme_von(i),
"verhalten": {},
"mission": true,
"heim": heim,
"farbe": Color.from_hsv(fmod(0.02 + float(i) * 0.191, 1.0), 0.5, 0.8),

View File

@@ -27,6 +27,12 @@ static var LISTE: Dictionary = {
"einsamkeit": {
"name": "Einsamkeit", "kurz": "hängt allein an dem, was zu zweit ginge",
},
"perfektionismus": {
"name": "Perfektionismus", "kurz": "nichts ist je gut genug zum Abgeben",
},
"unruhe": {
"name": "Unruhe", "kurz": "kann nicht bleiben, wo er ist",
},
}
## Situationen: was im Moment sichtbar wird. Jede gehört zu einem Problem.
@@ -67,6 +73,34 @@ static var SITUATIONEN: Dictionary = {
"name": "hängt allein fest", "was": "Sie kommt nicht weiter und fragt niemanden.",
"problem": "einsamkeit",
},
"absagen_treffen": {
"name": "sagt das Treffen ab", "was": "Lieber allein als unter Leuten.",
"problem": "einsamkeit",
},
"verwerfen": {
"name": "verwirft den Entwurf", "was": "Sie löscht, was schon fertig war.",
"problem": "perfektionismus",
},
"nachbessern": {
"name": "bessert endlos nach", "was": "Die dritte Fassung, und es wird nicht besser.",
"problem": "perfektionismus",
},
"aufspringen": {
"name": "springt auf", "was": "Sie hält es am Platz nicht aus.",
"problem": "unruhe",
},
"ortswechsel": {
"name": "wechselt den Ort", "was": "Woanders geht es bestimmt besser.",
"problem": "unruhe",
},
"zusammenbruch_klein": {
"name": "gibt für heute auf", "was": "Der Rest des Tages ist gestrichen.",
"problem": "erschoepfung",
},
"abbruch": {
"name": "lässt es liegen", "was": "Mitten drin hört sie auf.",
"problem": "ueberforderung",
},
}

View File

@@ -26,7 +26,7 @@ static var LIST: Array = [
"ursache": "anfang",
"belohnung": "erfolg",
"ersatz": "fängt zwei Minuten an",
"tags": ["prokrastination", "ueberforderung"],
"tags": ["prokrastination", "ueberforderung", "perfektionismus"],
"farbe": Color(0.98, 0.72, 0.25),
},
{
@@ -104,7 +104,7 @@ static var LIST: Array = [
"ursache": "kraft",
"belohnung": "abschalten",
"ersatz": "atmet sich ruhig",
"tags": ["angst", "stress"],
"tags": ["angst", "stress", "unruhe"],
"farbe": Color(0.40, 0.85, 0.95),
},
{
@@ -117,7 +117,7 @@ static var LIST: Array = [
"ursache": "grund",
"belohnung": "verbundenheit",
"ersatz": "ist freundlich mit sich",
"tags": ["selbstzweifel", "rueckschlag"],
"tags": ["selbstzweifel", "rueckschlag", "perfektionismus"],
"farbe": Color(1.0, 0.80, 0.60),
},
{
@@ -159,6 +159,123 @@ static var LIST: Array = [
"tags": ["stress", "grenzen"],
"farbe": Color(0.85, 0.35, 0.45),
},
{
"id": "frosch",
"name": "Das Schwerste zuerst",
"was": "Die unangenehmste Aufgabe des Tages als Erstes erledigen, bevor irgendetwas anderes dran ist.",
"wann": "Wenn eine Aufgabe den ganzen Tag über im Nacken sitzt und alles andere zum Ausweichen wird.",
"warum": "Solange das Schwerste wartet, ist keine andere Arbeit befreit. Danach ist der Rest leicht.",
"woran": "jemandem, der morgens erst das Postfach aufräumt und Kaffee holt.",
"ursache": "anfang",
"belohnung": "erfolg",
"ersatz": "packt das Schwerste zuerst an",
"tags": ["ueberforderung"],
"farbe": Color(0.60, 0.80, 0.45),
},
{
"id": "zeitblock",
"name": "Zeitblöcke",
"was": "Den Tag in feste Blöcke teilen und jeder Aufgabe einen Block geben — auch dem Eigenen.",
"wann": "Wenn der Tag von Bitten anderer aufgebraucht wird und das Eigene nie dran ist.",
"warum": "Was keinen Platz im Kalender hat, verliert gegen alles, was einen hat.",
"woran": "jemandem, der zu allem ja sagt oder abends länger bleibt.",
"ursache": "zeit",
"belohnung": "ordnung",
"ersatz": "hält seinen Block frei",
"tags": ["stress", "ueberforderung"],
"farbe": Color(0.45, 0.65, 0.90),
},
{
"id": "bildschirmfrei",
"name": "Bildschirmfreier Abend",
"was": "Ab einer festen Uhrzeit bleiben Handy und Fernseher aus. Stattdessen etwas, das die Hände beschäftigt.",
"wann": "Wenn der Abend im Scrollen oder in Serien versickert und morgens nichts übrig ist.",
"warum": "Bildschirme versprechen Abschalten und liefern Reizung. Der Kopf braucht das Gegenteil.",
"woran": "jemandem, der abends eine Serie schaut oder endlos scrollt.",
"ursache": "kraft",
"belohnung": "abschalten",
"ersatz": "lässt den Bildschirm aus",
"tags": ["erschoepfung"],
"farbe": Color(0.35, 0.45, 0.70),
},
{
"id": "powernap",
"name": "Kurzer Mittagsschlaf",
"was": "Zwanzig Minuten schlafen, nicht länger, mit Wecker — am besten früh am Nachmittag.",
"wann": "Wenn nach dem Mittag nichts mehr geht und der Rest des Tages verloren ist.",
"warum": "Zwanzig Minuten reichen, um den Kopf zu klären, ohne in den Tiefschlaf zu fallen.",
"woran": "jemandem, der nachmittags wegdöst oder am Kühlschrank steht.",
"ursache": "kraft",
"belohnung": "unterbrechung",
"ersatz": "schläft zwanzig Minuten",
"tags": ["erschoepfung"],
"farbe": Color(0.55, 0.50, 0.85),
},
{
"id": "kette",
"name": "Die Kette nicht reißen lassen",
"was": "Jeden Tag, an dem man dran war, im Kalender abhaken. Die Reihe nicht unterbrechen.",
"wann": "Wenn oft angefangen und selten drangeblieben wird.",
"warum": "Eine sichtbare Reihe ist ein Einsatz. Sie zu brechen tut mehr weh als weiterzumachen.",
"woran": "jemandem, der auf etwas anderes umschaltet oder den Plan neu schreibt.",
"ursache": "halt",
"belohnung": "erfolg",
"ersatz": "hakt den Tag ab",
"tags": ["selbstzweifel", "perfektionismus"],
"farbe": Color(0.90, 0.60, 0.35),
},
{
"id": "verabredung",
"name": "Verabredung zum Dranbleiben",
"was": "Sich mit jemandem zu einer festen Zeit verabreden, um gemeinsam oder nebeneinander zu arbeiten.",
"wann": "Wenn man allein immer wieder aussteigt.",
"warum": "Vor einem anderen fällt Aufhören schwerer als vor sich selbst.",
"woran": "jemandem, der allein festhängt oder Verabredungen absagt.",
"ursache": "halt",
"belohnung": "verbundenheit",
"ersatz": "meldet sich zum Dranbleiben",
"tags": ["einsamkeit"],
"farbe": Color(0.95, 0.70, 0.50),
},
{
"id": "warum_notiz",
"name": "Das Warum aufschreiben",
"was": "In zwei Sätzen festhalten, wofür das Vorhaben steht — und die Notiz dort lassen, wo man sie sieht.",
"wann": "Wenn das Vorhaben klein geredet wird und niemand mehr weiß, warum es einmal wichtig war.",
"warum": "Ein Grund, den man lesen kann, hält länger als einer, den man sich merken muss.",
"woran": "jemandem, der am Telefon klagt oder die Sache abends wälzt.",
"ursache": "grund",
"belohnung": "einordnung",
"ersatz": "liest sein Warum",
"tags": ["selbstzweifel"],
"farbe": Color(0.80, 0.75, 0.50),
},
{
"id": "angstleiter",
"name": "Angstleiter",
"was": "Die gefürchtete Sache in Stufen teilen, von harmlos bis schwer, und jede Woche eine Sprosse höher.",
"wann": "Wenn etwas so viel Angst macht, dass es immer wieder abgesagt wird.",
"warum": "Angst schrumpft durch Gewöhnung, nicht durch Mut. Kleine Schritte gewöhnen.",
"woran": "jemandem, der Termine absagt oder vor dem Berg erstarrt.",
"ursache": "anfang",
"belohnung": "erfolg",
"ersatz": "nimmt die nächste Sprosse",
"tags": ["angst"],
"farbe": Color(0.50, 0.85, 0.80),
},
{
"id": "sorgenzeit",
"name": "Sorgenzeit",
"was": "Sorgen auf eine feste Viertelstunde am Tag vertagen. Was vorher kommt, wird notiert und wartet.",
"wann": "Wenn Sorgen den ganzen Tag dazwischenfunken.",
"warum": "Sorgen lassen sich nicht abstellen, aber verschieben. Verschoben verlieren sie an Gewicht.",
"woran": "jemandem, der drei Dinge gleichzeitig anfängt oder nachts wach liegt.",
"ursache": "grund",
"belohnung": "einordnung",
"ersatz": "parkt die Sorge bis fünf",
"tags": ["angst", "stress", "unruhe"],
"farbe": Color(0.70, 0.60, 0.90),
},
]
@@ -171,6 +288,11 @@ static func fuer_ursache(ursache: String) -> Array:
return out
## Techniken, die über andere Menschen gehen -- bei Geselligen greifen sie besser.
static func ist_sozial(id: String) -> bool:
return str(by_id(id).get("belohnung", "")) in ["verbundenheit", "anerkennung"]
## Manche Techniken ersetzen die Handlung nicht, sie nehmen ihr den Auslöser.
## Die passen überall, wo ihre Ursache stimmt.
static func entfernt_ausloeser(id: String) -> bool:

View File

@@ -685,6 +685,8 @@ func _naechster_weg() -> void:
func brett_panel() -> void:
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "brett" else 0
_leeren("Schwarzes Brett")
_knopf("Aushang schreiben", func() -> void: schreiben_panel())
_trenner()
var offen_ids := Game.offene_missionen()
var gezeigt := 0
var verdeckt := 0
@@ -730,10 +732,154 @@ func aushang_panel(human_id: String) -> void:
_knopf("Mission annehmen", func() -> void:
Game.accept_mission(human_id)
brett_panel())
if bool(h.get("eigen", false)):
_knopf("Aushang abnehmen", func() -> void:
Aushaenge.loeschen(human_id)
Game.welt_erzeugen()
brett_panel())
_knopf("Zurück", func() -> void: brett_panel())
_zeigen("aushang")
# -------------------------------------------------------- Aushang schreiben
## Ein eigener Mensch: Name, Alter, Beruf, Traum, Wörter, Geschichte.
## Die Wörter sind das Einzige, was die Mechanik liest.
var _entwurf: Dictionary = {}
func schreiben_panel(entwurf: Dictionary = {}) -> void:
if not entwurf.is_empty():
_entwurf = entwurf
elif _entwurf.is_empty():
_entwurf = {"name": "", "alter": 30, "beruf": str(HumanDB.BERUFE[0]),
"traum": "", "geschichte": "", "woerter": []}
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "schreiben" else 0
_leeren("Aushang schreiben")
_zeile("Name", _eingabe("name"))
_zeile("Alter", _zahl("alter", 14, 99))
_zeile("Beruf", _auswahl("beruf", HumanDB.BERUFE))
_zeile("Traum", _eingabe("traum"))
_trenner()
var w: Array = _entwurf["woerter"]
_text("[b]Wörter[/b] [color=#8e93a8]mindestens %d · %d gewählt[/color]" %
[Woerter.MINDESTENS, w.size()], 15)
for gruppe in Woerter.GRUPPEN:
_text("[color=#9fb6ff]%s[/color]" % Woerter.GRUPPEN[gruppe], 13)
_haken_raster(Woerter.in_gruppe(gruppe))
_trenner()
_text("[b]Geschichte[/b] [color=#8e93a8]was im Weg steht[/color]", 15)
_textfeld("geschichte")
_trenner()
var reihe := HBoxContainer.new()
reihe.add_theme_constant_override("separation", 8)
_content.add_child(reihe)
_reihenknopf(reihe, "Zufall", func() -> void:
schreiben_panel(HumanDB.zufallsprofil()))
_reihenknopf(reihe, "Speichern", func() -> void: _aushang_speichern())
_reihenknopf(reihe, "Zurück", func() -> void:
_entwurf = {}
brett_panel())
_zeigen("schreiben")
_scroll_wunsch = scroll_alt
_scroll_frames = 4
func _aushang_speichern() -> void:
var fehler := Aushaenge.pruefen(_entwurf)
if not fehler.is_empty():
Game.notified.emit("Aushang unvollständig: %s" % ", ".join(fehler), Color(1, 0.7, 0.5))
return
var id := Aushaenge.speichern(_entwurf)
Game.welt_erzeugen()
Game.notified.emit("Aushang hängt: %s" % _entwurf["name"], Color(0.6, 1, 0.7))
_entwurf = {}
aushang_panel(id)
# ---- Eingabehelfer
func _zeile(titel: String, widget: Control) -> void:
var hb := HBoxContainer.new()
hb.add_theme_constant_override("separation", 10)
var l := Label.new()
l.text = titel
l.custom_minimum_size.x = 90
l.add_theme_font_size_override("font_size", 15)
hb.add_child(l)
widget.size_flags_horizontal = Control.SIZE_EXPAND_FILL
hb.add_child(widget)
_content.add_child(hb)
func _eingabe(feld: String) -> LineEdit:
var e := LineEdit.new()
e.text = str(_entwurf.get(feld, ""))
e.add_theme_font_size_override("font_size", 15)
e.text_changed.connect(func(t: String) -> void: _entwurf[feld] = t)
return e
func _zahl(feld: String, von: int, bis: int) -> SpinBox:
var s := SpinBox.new()
s.min_value = von
s.max_value = bis
s.value = int(_entwurf.get(feld, von))
s.value_changed.connect(func(v: float) -> void: _entwurf[feld] = int(v))
return s
func _auswahl(feld: String, optionen: Array) -> OptionButton:
var o := OptionButton.new()
for i in range(optionen.size()):
o.add_item(str(optionen[i]), i)
if str(optionen[i]) == str(_entwurf.get(feld, "")):
o.select(i)
o.item_selected.connect(func(i: int) -> void: _entwurf[feld] = str(optionen[i]))
return o
func _textfeld(feld: String) -> TextEdit:
var t := TextEdit.new()
t.text = str(_entwurf.get(feld, ""))
t.custom_minimum_size = Vector2(0, 80)
t.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
t.add_theme_font_size_override("font_size", 15)
t.text_changed.connect(func() -> void: _entwurf[feld] = t.text)
_content.add_child(t)
return t
func _haken_raster(keys: Array) -> void:
var g := GridContainer.new()
g.columns = 4
g.add_theme_constant_override("h_separation", 6)
_content.add_child(g)
var gewaehlt: Array = _entwurf["woerter"]
for key in keys:
var c := CheckBox.new()
c.text = Woerter.name_von(str(key))
c.button_pressed = gewaehlt.has(key)
c.add_theme_font_size_override("font_size", 14)
c.toggled.connect(func(an: bool) -> void:
var liste: Array = _entwurf["woerter"]
if an and not liste.has(key):
liste.append(key)
elif not an:
liste.erase(key))
g.add_child(c)
func _reihenknopf(reihe: HBoxContainer, text: String, cb: Callable) -> void:
var b := Button.new()
b.text = text
b.custom_minimum_size = Vector2(0, 32)
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
b.add_theme_font_size_override("font_size", 16)
b.pressed.connect(cb)
reihe.add_child(b)
# -------------------------------------------------------- Vor Ort
## Draußen am Menschen: hier greift man in den Slot ein, der gerade läuft.

View File

@@ -85,6 +85,50 @@ static var BLOCKER: Dictionary = {
"name": "vergleicht sich mit anderen", "ausloeser": "ein fremder Erfolg",
"belohnung": "einordnung", "ursache": "grund", "zeit": "morgen",
},
"mails": {
"name": "räumt das Postfach auf", "ausloeser": "Leerlauf",
"belohnung": "ordnung", "ursache": "anfang", "zeit": "morgen",
},
"kaffee": {
"name": "holt noch einen Kaffee", "ausloeser": "Unlust",
"belohnung": "unterbrechung", "ursache": "anfang", "zeit": "tag",
},
"perfekt": {
"name": "feilt am Detail statt abzugeben", "ausloeser": "Angst vor dem Urteil",
"belohnung": "ordnung", "ursache": "halt", "zeit": "tag",
},
"plan_neu": {
"name": "schreibt den Plan noch einmal neu", "ausloeser": "Unsicherheit",
"belohnung": "ordnung", "ursache": "halt", "zeit": "tag",
},
"snooze": {
"name": "drückt auf Schlummern", "ausloeser": "Müdigkeit",
"belohnung": "abschalten", "ursache": "kraft", "zeit": "morgen",
},
"doesen": {
"name": "döst auf dem Sofa weg", "ausloeser": "Erschöpfung",
"belohnung": "abschalten", "ursache": "kraft", "zeit": "tag",
},
"bestellen": {
"name": "bestellt statt zu kochen", "ausloeser": "ein leerer Akku",
"belohnung": "unterbrechung", "ursache": "kraft", "zeit": "abend",
},
"nachrichten": {
"name": "liest Nachrichten", "ausloeser": "Unruhe",
"belohnung": "einordnung", "ursache": "zeit", "zeit": "morgen",
},
"ueberstunden": {
"name": "bleibt länger im Büro", "ausloeser": "fremde Erwartung",
"belohnung": "anerkennung", "ursache": "zeit", "zeit": "abend",
},
"absage": {
"name": "sagt die Verabredung ab", "ausloeser": "ein voller Kopf",
"belohnung": "zeit_fuer_sich", "ursache": "grund", "zeit": "abend",
},
"waelzen": {
"name": "wälzt die Sache im Bett", "ausloeser": "Stille",
"belohnung": "einordnung", "ursache": "grund", "zeit": "nacht",
},
}
static var BELOHNUNG_TEXT: Dictionary = {

218
scripts/woerter.gd Normal file
View File

@@ -0,0 +1,218 @@
class_name Woerter
## Alltagswörter, mit denen jemand sich selbst beschreibt -- und was das Spiel
## daraus schließt. Vorne Wörter, die jeder an sich erkennt; hinten das Modell.
##
## Jedes Wort zieht an mehreren Stellen: Ursachen (was den Traum bremst),
## Blocker (was im Tag auftaucht), Klemmen (was in Momenten aufbricht) und
## Verhalten (wie der Mensch auf Techniken reagiert). Ein Wort kann an einer
## Stelle helfen und an einer anderen schaden -- so ist das bei Menschen.
## Die acht Verhaltenswerte, -2 bis +2:
## einrichten wie leicht ein System sich setzen lässt
## guete wie gut ein gesetztes System trägt
## abwehr wie stark die Abwehr nach einem Fehlschlag ist
## momente wie oft Momente aufbrechen
## dauer wie lange sie dauern
## tempo wie schnell der Traum von sich aus vorankommt
## sozial wie gut soziale Techniken greifen
## sichtbar wie schnell man ihm ansieht, was dahintersteckt
const VERHALTEN := ["einrichten", "guete", "abwehr", "momente", "dauer", "tempo", "sozial", "sichtbar"]
const GRUPPEN := {
"arbeit": "Arbeitsweise", "menschen": "Mit anderen", "gefuehl": "Gefühl",
"energie": "Energie", "selbst": "Selbstbild",
}
static var LISTE: Dictionary = {
# --- Arbeitsweise
"aufschiebend": {"name": "aufschiebend", "gruppe": "arbeit",
"ursachen": {"anfang": 2}, "blocker": ["putzflucht", "kaffee"], "verhalten": {"einrichten": -1}},
"perfektionistisch": {"name": "perfektionistisch", "gruppe": "arbeit",
"ursachen": {"halt": 1, "anfang": 1}, "blocker": ["perfekt"],
"probleme": {"perfektionismus": 2}, "verhalten": {"guete": 1, "abwehr": 1}},
"gruendlich": {"name": "gründlich", "gruppe": "arbeit",
"ursachen": {"anfang": 1}, "blocker": ["recherche"], "verhalten": {"guete": 1}},
"chaotisch": {"name": "chaotisch", "gruppe": "arbeit",
"ursachen": {"halt": 1}, "blocker": ["plan_neu"], "probleme": {"ueberforderung": 1},
"verhalten": {"guete": -1}},
"ordentlich": {"name": "ordentlich", "gruppe": "arbeit",
"ursachen": {"anfang": 1}, "blocker": ["liste", "mails"], "verhalten": {"guete": 1}},
"sprunghaft": {"name": "sprunghaft", "gruppe": "arbeit",
"ursachen": {"halt": 2}, "blocker": ["umschalten"], "verhalten": {"guete": -1, "momente": 1}},
"ablenkbar": {"name": "ablenkbar", "gruppe": "arbeit",
"ursachen": {"halt": 1, "zeit": 1}, "blocker": ["scrollen", "spielen"],
"probleme": {"unruhe": 1}},
"diszipliniert": {"name": "diszipliniert", "gruppe": "arbeit",
"verhalten": {"guete": 1, "einrichten": 1, "tempo": 1}},
"ehrgeizig": {"name": "ehrgeizig", "gruppe": "arbeit",
"probleme": {"stress": 1}, "verhalten": {"tempo": 1, "momente": 1}},
"bequem": {"name": "bequem", "gruppe": "arbeit",
"ursachen": {"kraft": 1, "anfang": 1}, "blocker": ["doesen", "bestellen"]},
"zielstrebig": {"name": "zielstrebig", "gruppe": "arbeit",
"verhalten": {"tempo": 1, "einrichten": 1}},
"unentschlossen": {"name": "unentschlossen", "gruppe": "arbeit",
"ursachen": {"anfang": 1}, "blocker": ["liste", "plan_neu"], "probleme": {"ueberforderung": 1}},
"ungeduldig": {"name": "ungeduldig", "gruppe": "arbeit",
"probleme": {"stress": 1}, "verhalten": {"abwehr": 1, "dauer": -1}},
"geduldig": {"name": "geduldig", "gruppe": "arbeit",
"verhalten": {"abwehr": -1, "dauer": 1}},
"neugierig": {"name": "neugierig", "gruppe": "arbeit",
"blocker": ["recherche", "umschalten"], "verhalten": {"einrichten": 1}},
"verbissen": {"name": "verbissen", "gruppe": "arbeit",
"probleme": {"stress": 1}, "verhalten": {"abwehr": 1, "tempo": 1}},
"spontan": {"name": "spontan", "gruppe": "arbeit",
"ursachen": {"halt": 1}, "verhalten": {"momente": 1}},
"pflichtbewusst": {"name": "pflichtbewusst", "gruppe": "arbeit",
"ursachen": {"zeit": 1}, "blocker": ["ueberstunden", "jasagen"], "verhalten": {"guete": 1}},
# --- Mit anderen
"gesellig": {"name": "gesellig", "gruppe": "menschen",
"ursachen": {"zeit": 1}, "verhalten": {"sozial": 1, "sichtbar": 1}},
"zurueckgezogen": {"name": "zurückgezogen", "gruppe": "menschen",
"blocker": ["absage"], "probleme": {"einsamkeit": 2}, "verhalten": {"sozial": -1, "sichtbar": -1}},
"hilfsbereit": {"name": "hilfsbereit", "gruppe": "menschen",
"ursachen": {"zeit": 2}, "blocker": ["jasagen"], "verhalten": {"sozial": 1}},
"konfliktscheu": {"name": "konfliktscheu", "gruppe": "menschen",
"ursachen": {"zeit": 1}, "blocker": ["jasagen"], "probleme": {"angst": 1},
"verhalten": {"abwehr": -1}},
"stur": {"name": "stur", "gruppe": "menschen",
"verhalten": {"abwehr": 2, "einrichten": -1}},
"nachgiebig": {"name": "nachgiebig", "gruppe": "menschen",
"ursachen": {"zeit": 1}, "verhalten": {"abwehr": -1, "einrichten": 1}},
"misstrauisch": {"name": "misstrauisch", "gruppe": "menschen",
"verhalten": {"einrichten": -1, "sozial": -1, "sichtbar": -1}},
"vertrauensvoll": {"name": "vertrauensvoll", "gruppe": "menschen",
"verhalten": {"einrichten": 1, "sozial": 1}},
"harmoniebeduerftig": {"name": "harmoniebedürftig", "gruppe": "menschen",
"ursachen": {"zeit": 1}, "blocker": ["jasagen"], "verhalten": {"abwehr": -1}},
"eigenbroetlerisch": {"name": "eigenbrötlerisch", "gruppe": "menschen",
"probleme": {"einsamkeit": 1}, "verhalten": {"sozial": -1}},
"redselig": {"name": "redselig", "gruppe": "menschen",
"blocker": ["klagen"], "verhalten": {"sichtbar": 1, "sozial": 1}},
"verschlossen": {"name": "verschlossen", "gruppe": "menschen",
"probleme": {"einsamkeit": 1}, "verhalten": {"sichtbar": -2}},
# --- Gefühl
"aengstlich": {"name": "ängstlich", "gruppe": "gefuehl",
"blocker": ["nachrichten"], "probleme": {"angst": 2}, "verhalten": {"momente": 1}},
"gelassen": {"name": "gelassen", "gruppe": "gefuehl",
"verhalten": {"momente": -1, "dauer": -1}},
"gruebelnd": {"name": "grüblerisch", "gruppe": "gefuehl",
"ursachen": {"grund": 1}, "blocker": ["waelzen"], "probleme": {"selbstzweifel": 1}},
"reizbar": {"name": "reizbar", "gruppe": "gefuehl",
"probleme": {"stress": 1}, "verhalten": {"momente": 1, "abwehr": 1}},
"optimistisch": {"name": "optimistisch", "gruppe": "gefuehl",
"verhalten": {"tempo": 1, "dauer": -1}},
"pessimistisch": {"name": "pessimistisch", "gruppe": "gefuehl",
"ursachen": {"grund": 2}, "blocker": ["klagen"], "verhalten": {"tempo": -1}},
"duennhaeutig": {"name": "dünnhäutig", "gruppe": "gefuehl",
"probleme": {"angst": 1, "selbstzweifel": 1}, "verhalten": {"dauer": 1}},
"nachtragend": {"name": "nachtragend", "gruppe": "gefuehl",
"verhalten": {"abwehr": 1}},
"heiter": {"name": "heiter", "gruppe": "gefuehl",
"verhalten": {"momente": -1}},
"schwermuetig": {"name": "schwermütig", "gruppe": "gefuehl",
"ursachen": {"grund": 1, "kraft": 1}, "probleme": {"erschoepfung": 1}, "verhalten": {"tempo": -1}},
# --- Energie
"nachtaktiv": {"name": "nachtaktiv", "gruppe": "energie",
"ursachen": {"kraft": 1}, "blocker": ["wach"], "probleme": {"erschoepfung": 1}},
"fruehaufsteher": {"name": "Frühaufsteher", "gruppe": "energie",
"verhalten": {"einrichten": 1}},
"erschoepft": {"name": "erschöpft", "gruppe": "energie",
"ursachen": {"kraft": 2}, "blocker": ["serie", "doesen"], "probleme": {"erschoepfung": 2}},
"rastlos": {"name": "rastlos", "gruppe": "energie",
"blocker": ["kuehlschrank"], "probleme": {"unruhe": 2}, "verhalten": {"momente": 1}},
"genuegsam": {"name": "genügsam", "gruppe": "energie",
"verhalten": {"dauer": -1}},
"bewegungsfaul": {"name": "bewegungsfaul", "gruppe": "energie",
"ursachen": {"kraft": 1}, "blocker": ["serie", "bestellen"]},
"sportlich": {"name": "sportlich", "gruppe": "energie",
"verhalten": {"tempo": 1, "momente": -1}},
"schlaflos": {"name": "schlaflos", "gruppe": "energie",
"ursachen": {"kraft": 1}, "blocker": ["wach", "snooze", "waelzen"], "probleme": {"erschoepfung": 1}},
# --- Selbstbild
"selbstkritisch": {"name": "selbstkritisch", "gruppe": "selbst",
"ursachen": {"grund": 1}, "probleme": {"selbstzweifel": 2}},
"selbstbewusst": {"name": "selbstbewusst", "gruppe": "selbst",
"verhalten": {"einrichten": 1, "sichtbar": 1}},
"unsicher": {"name": "unsicher", "gruppe": "selbst",
"blocker": ["recherche"], "probleme": {"selbstzweifel": 1, "angst": 1}, "verhalten": {"einrichten": -1}},
"bescheiden": {"name": "bescheiden", "gruppe": "selbst",
"verhalten": {"sozial": 1}},
"eitel": {"name": "eitel", "gruppe": "selbst",
"blocker": ["vergleichen"], "probleme": {"selbstzweifel": 1}},
"vergleichend": {"name": "vergleicht sich gern", "gruppe": "selbst",
"ursachen": {"grund": 1}, "blocker": ["vergleichen"], "probleme": {"selbstzweifel": 1}},
"hadernd": {"name": "hadert mit sich", "gruppe": "selbst",
"ursachen": {"grund": 2}, "blocker": ["waelzen"], "probleme": {"selbstzweifel": 1}},
}
const MINDESTENS := 6
const URSACHEN_MAX := 3
const PROBLEME_MAX := 2
static func name_von(key: String) -> String:
return str(LISTE[key]["name"]) if LISTE.has(key) else key
static func ids() -> Array:
return LISTE.keys()
static func in_gruppe(gruppe: String) -> Array:
var out: Array = []
for key in LISTE:
if LISTE[key]["gruppe"] == gruppe:
out.append(key)
return out
## Aus den angekreuzten Wörtern das Bild: Ursachen, Blocker, Klemmen, Verhalten.
static func profil(woerter: Array) -> Dictionary:
var ursachen: Dictionary = {}
var probleme: Dictionary = {}
var blocker: Array = []
var verhalten: Dictionary = {}
for v in VERHALTEN:
verhalten[v] = 0
for w in woerter:
if not LISTE.has(w):
continue
var e: Dictionary = LISTE[w]
for u in e.get("ursachen", {}):
ursachen[u] = int(ursachen.get(u, 0)) + int(e["ursachen"][u])
for p in e.get("probleme", {}):
probleme[p] = int(probleme.get(p, 0)) + int(e["probleme"][p])
for b in e.get("blocker", []):
if not blocker.has(b):
blocker.append(b)
for v in e.get("verhalten", {}):
verhalten[v] = int(verhalten[v]) + int(e["verhalten"][v])
for v in VERHALTEN:
verhalten[v] = clampi(int(verhalten[v]), -2, 2)
var urs := _oben(ursachen, URSACHEN_MAX, 2, Ursachen.LISTE.keys())
var prob := _oben(probleme, PROBLEME_MAX, 1, Probleme.LISTE.keys())
# Blocker müssen zu einer der Ursachen gehören, sonst passt keine Technik
var passend: Array = []
for b in blocker:
if urs.has(Ursachen.ursache_von(b)):
passend.append(b)
return {"ursachen": urs, "probleme": prob, "blocker": passend, "verhalten": verhalten}
## Die stärksten Einträge, mindestens `min`, höchstens `max`. Fehlt Gewicht,
## füllt die Reihenfolge des Katalogs auf -- niemand ist ohne Grund.
static func _oben(gewichte: Dictionary, max: int, min: int, katalog: Array) -> Array:
var keys: Array = gewichte.keys()
keys.sort_custom(func(a, b) -> bool: return int(gewichte[a]) > int(gewichte[b]))
var out: Array = []
for k in keys:
if int(gewichte[k]) <= 0 or out.size() >= max:
break
out.append(str(k))
for k in katalog:
if out.size() >= min:
break
if not out.has(str(k)):
out.append(str(k))
return out

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

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

View File

@@ -41,6 +41,7 @@ func _ready() -> void:
await test_tagesablauf_anzeige()
await test_ablauf_im_hud()
await test_probleme_und_momente()
await test_eigene_menschen()
await test_systeme()
await test_tempo_des_traums()
await test_cooldown()
@@ -1040,6 +1041,242 @@ func test_probleme_und_momente() -> void:
Game.problem_stand.clear()
## Eigene Menschen: aus Wörtern wird eine Psyche, aus dem Beruf ein Alltag,
## und beides lässt sich am Brett schreiben, speichern und wieder abnehmen.
func test_eigene_menschen() -> void:
abschnitt("Eigene Menschen aus Wörtern")
Aushaenge.PFAD = "user://aushaenge_test.cfg"
Aushaenge.alle_loeschen()
# --- Der Content ist gekoppelt: nichts ist unlösbar
var ohne_technik: Array = []
for b in Ursachen.BLOCKER:
if TechDB.fuer_ursache(Ursachen.ursache_von(str(b))).is_empty():
ohne_technik.append(str(b))
p(ohne_technik.is_empty(), "Jeder der %d Blocker hat Techniken gegen seine Ursache%s" %
[Ursachen.BLOCKER.size(), "" if ohne_technik.is_empty() else " — außer " + ", ".join(ohne_technik)])
var ohne: Array = []
for pr in Probleme.LISTE:
if Probleme.techniken_gegen(str(pr)).is_empty() or Probleme.situationen_von(str(pr)).is_empty():
ohne.append(str(pr))
p(ohne.is_empty(), "Jede der %d Klemmen hat Techniken und Situationen%s" %
[Probleme.LISTE.size(), "" if ohne.is_empty() else " — außer " + ", ".join(ohne)])
var tage: Dictionary = {}
for tid in TechDB.ids():
var t := TechDB.by_id(tid)
if str(t["was"]) == "" or str(t["wann"]) == "" or str(t["warum"]) == "" or str(t["woran"]) == "":
tage[tid] = true
p(tage.is_empty(), "Alle %d Techniken tragen Was, Wann, Warum, Woran" % TechDB.LIST.size())
# --- Die Wörter erreichen jeden Eintrag des Modells
var erreicht := {"ursachen": {}, "blocker": {}, "probleme": {}, "verhalten": {}}
var leer: Array = []
for w in Woerter.ids():
var e: Dictionary = Woerter.LISTE[w]
var wirkt := false
for u in e.get("ursachen", {}):
erreicht["ursachen"][u] = true
wirkt = true
for b in e.get("blocker", []):
erreicht["blocker"][b] = true
wirkt = true
for pr in e.get("probleme", {}):
erreicht["probleme"][pr] = true
wirkt = true
for v in e.get("verhalten", {}):
erreicht["verhalten"][v] = true
wirkt = true
if not wirkt:
leer.append(w)
p(Woerter.ids().size() >= 50, "%d Wörter stehen zur Wahl" % Woerter.ids().size())
p(leer.is_empty(), "Jedes Wort wirkt irgendwo%s" %
("" if leer.is_empty() else " — außer " + ", ".join(leer)))
p(erreicht["ursachen"].size() == Ursachen.LISTE.size(),
"Die Wörter erreichen alle %d Ursachen" % Ursachen.LISTE.size())
p(erreicht["blocker"].size() == Ursachen.BLOCKER.size(),
"Alle %d Blocker (%d)" % [Ursachen.BLOCKER.size(), erreicht["blocker"].size()])
p(erreicht["probleme"].size() == Probleme.LISTE.size(),
"Alle %d Klemmen" % Probleme.LISTE.size())
p(erreicht["verhalten"].size() == Woerter.VERHALTEN.size(),
"Und alle %d Verhaltenswerte" % Woerter.VERHALTEN.size())
var unbekannt: Array = []
for w in Woerter.ids():
var e: Dictionary = Woerter.LISTE[w]
for b in e.get("blocker", []):
if not Ursachen.BLOCKER.has(b):
unbekannt.append("%s->%s" % [w, b])
for pr in e.get("probleme", {}):
if not Probleme.LISTE.has(pr):
unbekannt.append("%s->%s" % [w, pr])
for u in e.get("ursachen", {}):
if not Ursachen.LISTE.has(u):
unbekannt.append("%s->%s" % [w, u])
p(unbekannt.is_empty(), "Kein Wort zeigt ins Leere%s" %
("" if unbekannt.is_empty() else ": " + ", ".join(unbekannt)))
# --- Aus Wörtern wird ein Bild
var bild := Woerter.profil(["aufschiebend", "perfektionistisch", "stur", "aengstlich",
"nachtaktiv", "zurueckgezogen"])
p(bild["ursachen"].size() >= 2 and bild["ursachen"].size() <= 3,
"Zwei bis drei Ursachen: %s" % str(bild["ursachen"]))
p(bild["ursachen"].has("anfang"), "Aufschiebend und perfektionistisch ergeben »kein Anfang«")
p(bild["probleme"].size() >= 1 and bild["probleme"].size() <= 2,
"Ein bis zwei Klemmen: %s" % str(bild["probleme"]))
p(bild["probleme"].has("angst") or bild["probleme"].has("perfektionismus"),
"Ängstlich und perfektionistisch schlagen durch")
p(bild["blocker"].size() >= 2, "Blocker zu den Ursachen: %s" % str(bild["blocker"]))
var fremd := false
for b in bild["blocker"]:
if not bild["ursachen"].has(Ursachen.ursache_von(str(b))):
fremd = true
p(not fremd, "Kein Blocker ohne passende Ursache")
p(int(bild["verhalten"]["abwehr"]) >= 2, "Stur und perfektionistisch: Abwehr %d" %
int(bild["verhalten"]["abwehr"]))
p(int(bild["verhalten"]["sichtbar"]) < 0, "Zurückgezogen: schwer zu durchschauen (%d)" %
int(bild["verhalten"]["sichtbar"]))
var gegen := Woerter.profil(["ordentlich", "chaotisch", "geduldig", "ungeduldig",
"gelassen", "reizbar"])
p(int(gegen["verhalten"]["guete"]) == 0 and int(gegen["verhalten"]["momente"]) == 0,
"Widersprüche heben sich auf (Güte %d, Momente %d)" %
[int(gegen["verhalten"]["guete"]), int(gegen["verhalten"]["momente"])])
var wenig := Woerter.profil(["heiter"])
p(wenig["ursachen"].size() >= 2 and wenig["probleme"].size() >= 1,
"Auch ein Wort ergibt einen ganzen Menschen (%s / %s)" %
[str(wenig["ursachen"]), str(wenig["probleme"])])
# --- Ein Mensch aus einem Aushang, mit eigenem Alltag
var pr := {"name": "Mo Test", "alter": 33, "beruf": "Bäcker",
"traum": "Wieder Gitarre spielen.", "geschichte": "Nach der Schicht ist nichts übrig.",
"woerter": ["erschoepft", "bequem", "konfliktscheu", "gruendlich", "stur", "aengstlich"]}
p(Aushaenge.pruefen(pr).is_empty(), "Der Aushang ist vollständig")
p(not Aushaenge.pruefen({"name": "X", "woerter": ["stur"]}).is_empty(),
"Ein lückenhafter wird abgewiesen: %s" %
", ".join(Aushaenge.pruefen({"name": "X", "woerter": ["stur"]})))
var id := Aushaenge.speichern(pr)
p(id.begins_with("eigen_"), "Gespeichert als %s" % id)
p(Aushaenge.laden().size() == 1 and Aushaenge.laden()[0]["name"] == "Mo Test",
"Und aus der Datei zurückgelesen")
Game.completed.clear()
Game.welt_erzeugen()
var m := HumanDB.by_id(id)
p(not m.is_empty(), "Er lebt in der Stadt")
p(HumanDB.LIST.size() == HumanDB.EINWOHNER, "Die Stadt bleibt bei %d Einwohnern" % HumanDB.EINWOHNER)
p(HumanDB.PLACES.has(str(m["heim"])), "Er hat ein Zuhause: %s" % m["heim"])
var frueh := false
for e in m["routine"]:
if int(e[0]) == 4 and str(e[1]) == "baeckerei":
frueh = true
p(frueh, "Als Bäcker steht er um vier in der Backstube")
var luecken := 0
for stunde in range(24):
if Game.slot_bei_stunde(id, stunde) < 0:
luecken += 1
p(luecken == 0, "Sein Tag ist lückenlos")
var blocker_im_tag: Array = []
for e in m["routine"]:
if Ursachen.ist_blocker(str(e[2])):
blocker_im_tag.append(str(e[2]))
p(blocker_im_tag.size() >= 2 and blocker_im_tag.size() <= HumanDB.BLOCKER_MAX,
"Zwei bis %d Blocker stehen im Tag: %s" % [HumanDB.BLOCKER_MAX, ", ".join(blocker_im_tag)])
var passt_alle := true
for b in blocker_im_tag:
if not (m["ursachen"] as Array).has(Ursachen.ursache_von(b)):
passt_alle = false
p(passt_alle, "Jeder davon gehört zu einer seiner Ursachen")
var ohne_blocker: Array = []
for u in m["ursachen"]:
var da := false
for b in blocker_im_tag:
if Ursachen.ursache_von(b) == str(u):
da = true
if not da:
ohne_blocker.append(str(u))
p(ohne_blocker.is_empty(), "Und jede seiner Ursachen zeigt sich im Tag%s" %
("" if ohne_blocker.is_empty() else " — nicht: " + ", ".join(ohne_blocker)))
# Das gilt für jeden Menschen der Stadt, sonst bremst eine Ursache für immer
var stumm: Array = []
for hh in HumanDB.LIST:
for u in hh["ursachen"]:
var zeigt := false
for e in hh["routine"]:
if Ursachen.ursache_von(str(e[2])) == str(u):
zeigt = true
if not zeigt:
stumm.append("%s/%s" % [hh["name"], u])
p(stumm.is_empty(), "Bei allen %d Menschen zeigt sich jede Ursache im Tag%s" %
[HumanDB.LIST.size(), "" if stumm.is_empty() else " — nicht: " + ", ".join(stumm)])
p(Game.machbar(id) or true, "Bereit fürs Brett")
_wege_pruefen("Stadt mit eigenem Menschen", HumanDB.PLACES, HumanDB.LIST)
# --- Die Wörter verändern, wie er sich verhält
p(Game.verhalten(id, "abwehr") >= 1, "Stur: Abwehr %d" % Game.verhalten(id, "abwehr"))
var std_id := "mensch_0"
p(Game.verhalten(std_id, "abwehr") == 0, "Ein erzeugter Mensch steht auf null")
Game.situation = {}
var pause_normal := Game._pause_fuer(std_id)
var reiz := Woerter.profil(["reizbar", "aengstlich", "rastlos", "spontan", "ehrgeizig", "stur"])
var pr2 := {"name": "Ria Test", "alter": 28, "beruf": "Studentin", "traum": "T",
"geschichte": "G", "woerter": ["reizbar", "aengstlich", "rastlos", "spontan", "ehrgeizig", "stur"]}
var id2 := Aushaenge.speichern(pr2)
Game.welt_erzeugen()
p(int(reiz["verhalten"]["momente"]) >= 2, "Reizbar, ängstlich, rastlos: Momente %d" %
int(reiz["verhalten"]["momente"]))
p(Game._pause_fuer(id2) < pause_normal,
"Bei ihr kommt der nächste Moment schneller (%.0f s statt %.0f s)" %
[Game._pause_fuer(id2), pause_normal])
var tempo_std := Game.traum_tempo(std_id)
var pr3 := {"name": "Zed Test", "alter": 40, "beruf": "Lehrer", "traum": "T",
"geschichte": "G", "woerter": ["diszipliniert", "zielstrebig", "optimistisch",
"sportlich", "verbissen", "ehrgeizig"]}
var id3 := Aushaenge.speichern(pr3)
Game.welt_erzeugen()
Game.systeme.clear()
p(Game.traum_tempo(id3) > Game.traum_tempo(id) or Game.verhalten(id3, "tempo") > Game.verhalten(id, "tempo"),
"Zielstrebig treibt den Traum von sich aus (Tempo %d gegen %d)" %
[Game.verhalten(id3, "tempo"), Game.verhalten(id, "tempo")])
p(HumanDB.LIST.size() == HumanDB.EINWOHNER, "Drei eigene, immer noch %d Einwohner" % HumanDB.LIST.size())
# --- Am Brett: schreiben, Zufall, speichern, abnehmen
main._traumwelt()
await get_tree().process_frame
ui().brett_panel()
p(_knopf_da("Aushang schreiben"), "Das Brett bietet an, einen Aushang zu schreiben")
p(_knopf_texte().contains("Mo Test"), "Der eigene Mensch hängt aus")
ui().schreiben_panel()
p(ui()._kind == "schreiben", "Das Formular öffnet sich")
var haken := 0
for c in _alle_kinder(ui()._content):
if c is CheckBox:
haken += 1
p(haken == Woerter.ids().size(), "Alle %d Wörter sind ankreuzbar (%d)" % [Woerter.ids().size(), haken])
ui().schreiben_panel(HumanDB.zufallsprofil())
p(str(ui()._entwurf["name"]) != "" and (ui()._entwurf["woerter"] as Array).size() >= Woerter.MINDESTENS,
"Zufall füllt alles: %s mit %d Wörtern" % [ui()._entwurf["name"],
(ui()._entwurf["woerter"] as Array).size()])
var vorher := Aushaenge.laden().size()
ui()._aushang_speichern()
p(Aushaenge.laden().size() == vorher + 1, "Speichern hängt ihn ans Brett (%d)" % Aushaenge.laden().size())
p(ui()._kind == "aushang", "Und zeigt den Aushang")
p(_knopf_da("Aushang abnehmen"), "Ein eigener lässt sich abnehmen")
_druecke("Aushang abnehmen")
p(Aushaenge.laden().size() == vorher, "Abgenommen (%d)" % Aushaenge.laden().size())
ui().schliessen()
Aushaenge.alle_loeschen()
Aushaenge.PFAD = "user://aushaenge.cfg"
Game.welt_erzeugen()
main._erde()
await get_tree().process_frame
func _alle_kinder(n: Node) -> Array:
var out: Array = []
for c in n.get_children():
out.append(c)
out.append_array(_alle_kinder(c))
return out
## 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")
@@ -1793,10 +2030,10 @@ func test_brett_nie_leer() -> void:
Game.completed.append("stufe_%d" % k)
Game.welt_erzeugen()
Game.known = {str(TechDB.LIST[i]["id"]): 0}
if Game.machbar("mensch_%d" % i):
if not Game.offene_missionen().is_empty():
passt += 1
p(passt == TechDB.LIST.size(),
"Zu jedem der %d Wege gibt es den passenden Menschen (%d)" %
"Zu jedem der %d Wege gibt es einen Menschen, dem er hilft (%d)" %
[TechDB.LIST.size(), passt])
Game.completed.assign(merk_c)