Wire quest flow and combat across all levels.
Connect bear-save, massage, farm paywall, cheese catapults, Dressler hijack, final boss phases, fat-stim UI, and scene fixes so the satire path plays through.
This commit is contained in:
parent
938cc4bc82
commit
1d9e0a6532
51 changed files with 1899 additions and 251 deletions
25
DESIGN.md
Normal file
25
DESIGN.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Explosion der Spiele – Design-Spec (kanonisch)
|
||||
|
||||
Quelle: mündlicher/diktierter Gesamtplan. Scaffold folgt diesem Flow.
|
||||
|
||||
## Kamera / Grafik
|
||||
- 3D-Welt, alles als Billboard-Planes (Mario-Bäume), Third-Person-Follow-Kamera
|
||||
|
||||
## Flow
|
||||
1. **Title** – verpixelt Mario/Sonic/Burger-Regen, verstörter Mensch + Blase, „Explosion der Spiele / Pay2Win (um reiche Leute anzulocken, hihihihi)“, extrem laute Musik (nicht leiser), Burger-Cham alle 2 Min, First-Run Sprachwahl DE/EN/BY
|
||||
2. **Intro** – Tisch + Konsole, epischer Sprecher, Explosion, „sonst kommt der Hongkong“
|
||||
3. **Village** – kaputtes AC-Dorf, fliehende Dorfbewohner („Hongkong!“), Raum mit Tisch, Knopf unter Tisch → Schuhmacher Reif
|
||||
4. **Shoe Boss (Reif)** – Mensch + Stock + Schuh; Sieg → Tonne Tiramisu → 10 Min „zu fett“ + Shorts-Stimulation (Subway Surf / satisfying)
|
||||
5. **Meadow** – Eisbär vs Schlangenmähdrescher; Schlangen ins Inventar → Massage-Studio; Zahn-Belohnung; Glitzos (4 Hits / 4 Hits tot; Zahn → 2 Hits; nur auf Wiese)
|
||||
6. **Massage Studio** – Kuh-Masseur; Secret: trinken → melkt sich → Milch → Skip zum Endboss
|
||||
7. **Catscene** – „keine Open World“; Skip-Versuch → Reset von vorne (Wortwitz cat/Katze)
|
||||
8. **Farm Path** – enger Weg, Gabelung; links Hof; rechts dunkelgrauer Magier (zurückwerfen / Wut → Planet weg → Mond)
|
||||
9. **Farm** – Bayer immer BY; Schweine/Kühe/Hühner+Urhahn; Lava-Chicken; Paywall 1,99 / Dresslerpunkte (Spaß-weiter); Schein der Uralten
|
||||
10. **Dressler** – Riesenapfel mit Augen, Mähdrescher hijacken; drop zertresselten Apfel → essen → Teleport Endboss
|
||||
11. **Moon** – Käseverkäufer/Katapulte, Mondschwert, Sonnenbrille gratis, Fake-Bowser Hammer-Minispiel, Hochzeitsei/Whiff
|
||||
12. **Final** – alle: Hongkong war Fake → Glitschkönig; Banane + Wartchen; Pixelhaufen + Quirxels; Split; „ich werde wiederkommen“; Eisbär-Zahnarzt-Guilt
|
||||
13. **End** – AC-Dorf, Hausbau, wütende Dorfbewohner; Quest Zahnionär (Zahnarzt überfallen)
|
||||
14. **Death** – bunt blinkend, Totenkopf alle 10s, Todesursachen, Wiederbeleben Ja/Nein (Nein = trotzdem revive, Ja = Beenden – absurde Satire)
|
||||
|
||||
## Pay-to-Win
|
||||
- Dresslerpunkt = 1€ Mock; Shop in Optionen; Paywall-Texte mit „Spaß, du darfst weiter“
|
||||
19
README.md
19
README.md
|
|
@ -14,23 +14,26 @@ Pay2Win – um reiche Leute anzulocken. Absichtlich chaotisches Meta-Comedy-Acti
|
|||
|
||||
| Aktion | Taste |
|
||||
|--------|--------|
|
||||
| Bewegen | WASD / Pfeile |
|
||||
| Bewegen | WASD / Pfeile (relativ zur Kamera) |
|
||||
| Umschauen | Maus |
|
||||
| Interagieren | E / Leertaste |
|
||||
| Angriff | Linksklick / J |
|
||||
| Pause (Shop/Inventar/Settings) | Esc |
|
||||
| Cutscene skippen | Enter |
|
||||
|
||||
Third-Person-Kamera folgt dem Spieler (`SpringArm3D`). Billboards nutzen Y-Billboard (Mario-Baum-Stil).
|
||||
|
||||
Im Inventar (Pause): Item doppelklicken/aktivieren — **Milch** skippt zum Endboss.
|
||||
|
||||
## Scaffold-Inhalt
|
||||
|
||||
- 12 Szenen verdrahtet (Title → Intro → Village → … → Final → End + Death)
|
||||
- Autoloads: `GameState`, `Inventory`, `Achievements`, `Settings`, `Locale` (DE/EN/BY), `Shop` (Mock), `AudioDirector`, `UIRoot`
|
||||
- Billboard-Prefab (`PlaneMesh` + `billboard_mode`)
|
||||
- Pay-to-Win-Shop ohne echte Zahlungen (`1€ = 1 Dresslerpunkt` Mock-Button)
|
||||
- Musik-Lautstärke absichtlich gesperrt (bleibt laut)
|
||||
- Burger-Cham-Event alle 120s auf dem Title Screen
|
||||
- Save unter `user://save.json`
|
||||
Kanonischer Plan: **[DESIGN.md](DESIGN.md)**
|
||||
|
||||
- Story-Flow inkl. Catscene + Dressler-Boss; Magier-Branch → Mond
|
||||
- Third-Person-Kamera, Billboard-Y-Planes
|
||||
- First-Run-Sprache DE/EN/BY, Musik gesperrt laut, Burger-Cham
|
||||
- Milch-Skip, Tiramisu-Fett, Glitzos, Bowser-Hammer, Death Ja/Nein-Satire
|
||||
- Save: `user://save.json`
|
||||
|
||||
## Regenerieren von Platzhaltern
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
[
|
||||
{"id": "tooth", "name_key": "item_tooth", "stackable": true},
|
||||
{"id": "snake", "name_key": "item_snake", "stackable": false},
|
||||
{"id": "snake", "name_key": "item_snake", "stackable": true},
|
||||
{"id": "milk", "name_key": "item_milk", "stackable": true, "usable": true},
|
||||
{"id": "banana", "name_key": "item_banana", "stackable": true},
|
||||
{"id": "wartchen", "name_key": "item_wartchen", "stackable": true},
|
||||
{"id": "banana", "name_key": "item_banana", "stackable": true, "usable": true},
|
||||
{"id": "wartchen", "name_key": "item_wartchen", "stackable": false},
|
||||
{"id": "sunglasses", "name_key": "item_sunglasses", "stackable": false},
|
||||
{"id": "death_ticket", "name_key": "item_death_ticket", "stackable": true, "usable": true},
|
||||
{"id": "moon_sword", "name_key": "item_moon_sword", "stackable": false},
|
||||
{"id": "chicken", "name_key": "item_chicken", "stackable": true},
|
||||
{"id": "cheese", "name_key": "item_cheese", "stackable": true},
|
||||
{"id": "tiramisu", "name_key": "item_tiramisu", "stackable": true, "usable": true}
|
||||
{"id": "tiramisu", "name_key": "item_tiramisu", "stackable": true, "usable": true},
|
||||
{"id": "dressler_apple", "name_key": "item_dressler_apple", "stackable": false, "usable": true},
|
||||
{"id": "wedding_egg", "name_key": "item_wedding_egg", "stackable": false, "usable": true},
|
||||
{"id": "lava_chicken", "name_key": "item_lava_chicken", "stackable": true}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,32 +20,32 @@
|
|||
"lang_voice_de": "Am deutschen Wesen soll die Welt genesen",
|
||||
"lang_voice_en": "I drink my garden in the cinema",
|
||||
"lang_voice_by": "Schulz",
|
||||
"dialog_reif": "Wennst no ned reif bist, muaß i da an Schuah schenken!",
|
||||
"dialog_reif": "Wenn du noch nicht reif bist, muss ich dir einen Schuh schenken!",
|
||||
"dialog_snake": "I bin ganz verspannt, oi tut des weh",
|
||||
"dialog_bear": "Hongkong hot den Rasenmäher zu Schlangen gmochd",
|
||||
"dialog_hongkong_npc": "Hongkong, Hongkong is er!",
|
||||
"dialog_cow": "Massage: Schlang / Spieler / Quadrat-Emoji",
|
||||
"dialog_cow": "Masseur-Kuh: Massiere die Schlange jetzt – oder soll ich erst viel trinken?",
|
||||
"dialog_farmer": "Servus! De Schweine brauchn Quests und der Urhahn pickt.",
|
||||
"dialog_mage": "Rechts gehts zum Hof. Links is Fake-Open-World.",
|
||||
"dialog_cheese": "Der Mond is aus Kas. Willst a Sonnenbrille?",
|
||||
"dialog_bowser": "I halt a Hendl gfangn! (Fake Bowser)",
|
||||
"dialog_hongkong_boss": "I kimm wieder!",
|
||||
"dialog_bear_end": "Guad dassd unsere Welt grettet host. Aber Zahnarzt kostet immer no.",
|
||||
"dialog_cheese": "Verschimmelter Käse! Nicht den Mund treffen lassen – stech den KÄSE, nicht den Verkäufer!",
|
||||
"dialog_bowser": "Fake Bowser hält das Huhn! Rate den richtigen Hammer – sonst frisst er dich.",
|
||||
"dialog_hongkong_boss": "Ich werde wiederkommen!",
|
||||
"dialog_bear_end": "Gut, dass du unsere Welt gerettet hast. Aber ich bin wütend – die Zahnarztkosten sind nicht günstiger geworden. Schlechtes Gewissen inklusive.",
|
||||
"dialog_dressler": "Dresslauf die Eins!",
|
||||
"dialog_intro_1": "Du sitzt an deim Tisch…",
|
||||
"dialog_intro_2": "Du muasst es schaffn, sonst kommt der Hongkong",
|
||||
"death_shoe": "Schuah gschenkt kriagt",
|
||||
"death_mower": "Mähdrescher-Biss",
|
||||
"death_rooster": "Vom Urhahn totpickt",
|
||||
"death_quirxel": "Vom Quirxel gquadratet",
|
||||
"death_default": "Z'Tode gstorbn",
|
||||
"dialog_intro_1": "Du sitzt an deinem Tisch und spielst dein Lieblingsspiel…",
|
||||
"dialog_intro_2": "Du musst es schaffen. Du musst es schaffen – sonst kommt der Hongkong.",
|
||||
"death_shoe": "Da hat dir wohl wer 'nen Schuh geschenkt.",
|
||||
"death_mower": "Du wurdest von einem Mähdrescher gebissen. Dass sowas mal passiert, hätte ich jetzt nicht gedacht.",
|
||||
"death_rooster": "Vom Urhahn totgepickt. 10 Sekunden Schock.",
|
||||
"death_quirxel": "Von einem Quirxel gequadratet.",
|
||||
"death_default": "Zu Tode gestorben.",
|
||||
"item_tooth": "Zahn",
|
||||
"item_snake": "Schlang",
|
||||
"item_milk": "Selbergmelkte Milch",
|
||||
"item_banana": "Banane",
|
||||
"item_wartchen": "Wartchen",
|
||||
"item_sunglasses": "Coole Sonnenbrille",
|
||||
"item_death_ticket": "Ticket: Z'Tode gessen",
|
||||
"item_death_ticket": "Schein der Uralten",
|
||||
"item_moon_sword": "Mondschwert",
|
||||
"item_chicken": "Hendl",
|
||||
"item_cheese": "Verschimmelta Kas",
|
||||
|
|
@ -84,5 +84,60 @@
|
|||
"ach_tooth_desc": "Zähne beim Zahnarzt klauen",
|
||||
"ach_all_title": "Burger, Burger, Burger. Heit ess i alle Quests",
|
||||
"ach_all_desc": "Alle Achievements freischaltn",
|
||||
"burger_cham": "Burger! Beste Qualität von der Explosion der Spiele"
|
||||
"burger_cham": "Burger! Beste Qualität von der Explosion der Spiele",
|
||||
"title_pay2win": "Pay2Win (damit de Reichen kemma, hihihihi)",
|
||||
"title_music_hint": "Hey, das hier ist so eine laute Musik extra. Damit du direkt auf Starten drückst…",
|
||||
"lang_pick_title": "Sproch aussucha",
|
||||
"lang_de": "Deutsch",
|
||||
"lang_en": "English",
|
||||
"lang_by": "Bayerisch",
|
||||
"dialog_villager_run": "Hongkong! Hongkong ist er! Er hat alles zerstört!",
|
||||
"dialog_table_hint": "Unter dem Tisch ist ein Knopf…",
|
||||
"dialog_tiramisu": "Eine Tonne Tiramisu! Du bist jetzt 10 Minuten zu fett. Stimulation: Subway Surf & Satisfying Shorts.",
|
||||
"dialog_bear_help": "Hilfe! Der Schlangenmähdrescher würgt mich!",
|
||||
"dialog_bear_saved": "Hongkong hat den Rasenmäher zu Schlangen verwandelt. Nimm meinen Zahn – und bring die Schlangen zur Massage!",
|
||||
"dialog_snake_pain": "Ich bin sehr verspannt, oh, mir tut alles so weh.",
|
||||
"dialog_villager_snake": "Der Schlange geht es nicht so gut, ich glaube, sie braucht eine Massage.",
|
||||
"dialog_cow_drink": "Gluck gluck… ich melk mich selbst. Hier, selbst gemelkte Milch.",
|
||||
"dialog_cow_massage": "Die Schlange schläft ein… Quest erledigt.",
|
||||
"dialog_catscene": "Du hast das Dorf verlassen. Nun könntest du in eine Open World… aber das wäre zu viel Arbeit. Keine Open World. Du bist traurig. Ist mir egal.",
|
||||
"dialog_catscene_skip": "Du willst diese spannende Katzin überspringen? Spinnst du? Strafe: alles von vorne!",
|
||||
"dialog_mage_push": "Mit der Macht… zurück nach links!",
|
||||
"dialog_mage_angry": "Ich bin der dunkelgraue Magier – NICHT der schwarze! *explodiert den Planeten*",
|
||||
"dialog_farmer_direct": "Grrrrirstsel, du bist ja brav und bist gleich hierher gegangen!",
|
||||
"dialog_farmer_detour": "Grrrrirstsel, du bist also nicht gleich hierher gegangen – ich muss erst den Magier holen. Umtalentiert!",
|
||||
"dialog_farmer_quest": "Weißt du was? Wennst die Tierregie machst, kriegst von mir 1,99. Schweine füttern, Kühe melken, Hühner sneaken – Vorsicht Urhahn!",
|
||||
"dialog_urhahn": "KIKERIKIIII – der Urhahn pickt dich tot. 10 Sekunden Schock.",
|
||||
"dialog_paywall": "Du willst weiterspielen? Zahl jetzt 1,99… oder Dresslerpunkte im Shop.",
|
||||
"dialog_dressler_drop": "Auf dem Apfel steht: ESSEN NICHT – außer du willst das Spiel durchspielen.",
|
||||
"dialog_boss_unclever": "Ich weiß, wie du zu mir gelangt bist. Das ist sehr unkreativ, aber wir hatten keine Ideen mehr.",
|
||||
"dialog_sunglasses_free": "Sonnenbrille? Nein Spaß – gratis!",
|
||||
"dialog_reveal": "Übrigens: Hongkong war nur ein Prank – er heißt echt Glitschkönig! Hahaha! Du bist voll drauf reingefallen. Belohnung: Banane + Wartchen.",
|
||||
"dialog_end_angry": "Die Dorfbewohner sind wütend wegen der Zahnarztkosten. Bau trotzdem dein Haus.",
|
||||
"dialog_toothionaire": "Brich beim Zahnarzt ein und klaue die Zähne – werde Zahnionär!",
|
||||
"death_glitzo": "⬜",
|
||||
"death_bowser": "Fake Bowser hat dich aufgegessen.",
|
||||
"death_cheese": "Verschimmelter Käse im Mund. Pfui.",
|
||||
"death_ticket_eaten": "Schein der Uralten gegessen. Zu verrückt? Verstanden.",
|
||||
"death_revive": "Wiederbeleben",
|
||||
"death_yes": "Ja",
|
||||
"death_no": "Nein",
|
||||
"death_yes_hint": "Ja = Spiel beenden (zur Title).",
|
||||
"death_no_hint": "Nein = trotzdem wiederbeleben. Satire.",
|
||||
"shop_joke_continue": "Kostet 10 Dresslerpunkte… Spaß, du darfst weiter! Pay2Win war nur für reiche Leute.",
|
||||
"shop_joke_free": "Nein Spaß, das bekommst du gratis.",
|
||||
"shop_continue_joke": "Weiter spielen (10 Punkte? Spaß)",
|
||||
"shop_next_level": "Nächstes Level freikaufen (1 Punkt)",
|
||||
"shop_burger": "Burger (1 Punkt)",
|
||||
"item_dressler_apple": "Zertresselter Apfel",
|
||||
"item_wedding_egg": "Hochzeitsei / Whiff",
|
||||
"item_lava_chicken": "Lava-Chicken",
|
||||
"status_fat": "ZU FETT (%s)",
|
||||
"status_shock": "URHAHN-SCHOCK",
|
||||
"status_glitch": "VERGLITSCHT",
|
||||
"choice_massage_now": "Schlange jetzt massieren",
|
||||
"choice_drink_first": "Erst viel trinken (Secret)",
|
||||
"choice_hammer_1": "Hammer 1",
|
||||
"choice_hammer_2": "Hammer 2",
|
||||
"choice_hammer_3": "Hammer 3"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,28 +24,28 @@
|
|||
"dialog_snake": "Ich bin sehr verspannt, oh, mir tut alles so weh",
|
||||
"dialog_bear": "Hongkong hat den Rasenmäher zu Schlangen verwandelt",
|
||||
"dialog_hongkong_npc": "Hongkong, Hongkong ist er!",
|
||||
"dialog_cow": "Massageoptionen: Schlange / Spieler / Quadrat-Emoji",
|
||||
"dialog_cow": "Masseur-Kuh: Massiere die Schlange jetzt – oder soll ich erst viel trinken?",
|
||||
"dialog_farmer": "Servus! Die Schweine brauchen Quests und der Urhahn pickt.",
|
||||
"dialog_mage": "Der Weg nach rechts führt zum Bauernhof. Links ist Fake-Open-World.",
|
||||
"dialog_cheese": "Der Mond ist aus Käse. Willst du eine Sonnenbrille?",
|
||||
"dialog_bowser": "Ich halte ein Huhn gefangen! (Fake Bowser)",
|
||||
"dialog_cheese": "Verschimmelter Käse! Nicht den Mund treffen lassen – stech den KÄSE, nicht den Verkäufer!",
|
||||
"dialog_bowser": "Fake Bowser hält das Huhn! Rate den richtigen Hammer – sonst frisst er dich.",
|
||||
"dialog_hongkong_boss": "Ich werde wiederkommen!",
|
||||
"dialog_bear_end": "Gut, dass du unsere Welt gerettet hast. Aber die Zahnarztkosten sind immer noch teuer.",
|
||||
"dialog_bear_end": "Gut, dass du unsere Welt gerettet hast. Aber ich bin wütend – die Zahnarztkosten sind nicht günstiger geworden. Schlechtes Gewissen inklusive.",
|
||||
"dialog_dressler": "Dresslauf die Eins!",
|
||||
"dialog_intro_1": "Du sitzt an deinem Tisch…",
|
||||
"dialog_intro_2": "Du musst es schaffen, sonst kommt der Hongkong",
|
||||
"death_shoe": "Schuh geschenkt",
|
||||
"death_mower": "Mähdrescher-Biss",
|
||||
"death_rooster": "Vom Urhahn totgepickt",
|
||||
"death_quirxel": "Von einem Quirxel gequadratet",
|
||||
"death_default": "Zu Tode gestorben",
|
||||
"dialog_intro_1": "Du sitzt an deinem Tisch und spielst dein Lieblingsspiel…",
|
||||
"dialog_intro_2": "Du musst es schaffen. Du musst es schaffen – sonst kommt der Hongkong.",
|
||||
"death_shoe": "Da hat dir wohl wer 'nen Schuh geschenkt.",
|
||||
"death_mower": "Du wurdest von einem Mähdrescher gebissen. Dass sowas mal passiert, hätte ich jetzt nicht gedacht.",
|
||||
"death_rooster": "Vom Urhahn totgepickt. 10 Sekunden Schock.",
|
||||
"death_quirxel": "Von einem Quirxel gequadratet.",
|
||||
"death_default": "Zu Tode gestorben.",
|
||||
"item_tooth": "Zahn",
|
||||
"item_snake": "Schlange",
|
||||
"item_milk": "Selbst gemelkte Milch",
|
||||
"item_banana": "Banane",
|
||||
"item_wartchen": "Wartchen",
|
||||
"item_sunglasses": "Coole Sonnenbrille",
|
||||
"item_death_ticket": "Ticket: Zu Tode gegessen",
|
||||
"item_death_ticket": "Schein der Uralten",
|
||||
"item_moon_sword": "Mondschwert",
|
||||
"item_chicken": "Huhn",
|
||||
"item_cheese": "Verschimmelter Käse",
|
||||
|
|
@ -84,5 +84,60 @@
|
|||
"ach_tooth_desc": "Zähne beim Zahnarzt klauen",
|
||||
"ach_all_title": "Burger, Burger, Burger. Heute ess ich alle Quests",
|
||||
"ach_all_desc": "Alle Achievements freischalten",
|
||||
"burger_cham": "Burger! Beste Qualität von der Explosion der Spiele"
|
||||
"burger_cham": "Burger! Beste Qualität von der Explosion der Spiele",
|
||||
"title_pay2win": "Pay2Win (um reiche Leute anzulocken, hihihihi)",
|
||||
"title_music_hint": "Hey, das hier ist so eine laute Musik extra. Damit du direkt auf Starten drückst…",
|
||||
"lang_pick_title": "Sprache wählen",
|
||||
"lang_de": "Deutsch",
|
||||
"lang_en": "English",
|
||||
"lang_by": "Bayerisch",
|
||||
"dialog_villager_run": "Hongkong! Hongkong ist er! Er hat alles zerstört!",
|
||||
"dialog_table_hint": "Unter dem Tisch ist ein Knopf…",
|
||||
"dialog_tiramisu": "Eine Tonne Tiramisu! Du bist jetzt 10 Minuten zu fett. Stimulation: Subway Surf & Satisfying Shorts.",
|
||||
"dialog_bear_help": "Hilfe! Der Schlangenmähdrescher würgt mich!",
|
||||
"dialog_bear_saved": "Hongkong hat den Rasenmäher zu Schlangen verwandelt. Nimm meinen Zahn – und bring die Schlangen zur Massage!",
|
||||
"dialog_snake_pain": "Ich bin sehr verspannt, oh, mir tut alles so weh.",
|
||||
"dialog_villager_snake": "Der Schlange geht es nicht so gut, ich glaube, sie braucht eine Massage.",
|
||||
"dialog_cow_drink": "Gluck gluck… ich melk mich selbst. Hier, selbst gemelkte Milch.",
|
||||
"dialog_cow_massage": "Die Schlange schläft ein… Quest erledigt.",
|
||||
"dialog_catscene": "Du hast das Dorf verlassen. Nun könntest du in eine Open World… aber das wäre zu viel Arbeit. Keine Open World. Du bist traurig. Ist mir egal.",
|
||||
"dialog_catscene_skip": "Du willst diese spannende Katzin überspringen? Spinnst du? Strafe: alles von vorne!",
|
||||
"dialog_mage_push": "Mit der Macht… zurück nach links!",
|
||||
"dialog_mage_angry": "Ich bin der dunkelgraue Magier – NICHT der schwarze! *explodiert den Planeten*",
|
||||
"dialog_farmer_direct": "Grrrrirstsel, du bist ja brav und bist gleich hierher gegangen!",
|
||||
"dialog_farmer_detour": "Grrrrirstsel, du bist also nicht gleich hierher gegangen – ich muss erst den Magier holen. Umtalentiert!",
|
||||
"dialog_farmer_quest": "Weißt du was? Wennst die Tierregie machst, kriegst von mir 1,99. Schweine füttern, Kühe melken, Hühner sneaken – Vorsicht Urhahn!",
|
||||
"dialog_urhahn": "KIKERIKIIII – der Urhahn pickt dich tot. 10 Sekunden Schock.",
|
||||
"dialog_paywall": "Du willst weiterspielen? Zahl jetzt 1,99… oder Dresslerpunkte im Shop.",
|
||||
"dialog_dressler_drop": "Auf dem Apfel steht: ESSEN NICHT – außer du willst das Spiel durchspielen.",
|
||||
"dialog_boss_unclever": "Ich weiß, wie du zu mir gelangt bist. Das ist sehr unkreativ, aber wir hatten keine Ideen mehr.",
|
||||
"dialog_sunglasses_free": "Sonnenbrille? Nein Spaß – gratis!",
|
||||
"dialog_reveal": "Übrigens: Hongkong war nur ein Prank – er heißt echt Glitschkönig! Hahaha! Du bist voll drauf reingefallen. Belohnung: Banane + Wartchen.",
|
||||
"dialog_end_angry": "Die Dorfbewohner sind wütend wegen der Zahnarztkosten. Bau trotzdem dein Haus.",
|
||||
"dialog_toothionaire": "Brich beim Zahnarzt ein und klaue die Zähne – werde Zahnionär!",
|
||||
"death_glitzo": "⬜",
|
||||
"death_bowser": "Fake Bowser hat dich aufgegessen.",
|
||||
"death_cheese": "Verschimmelter Käse im Mund. Pfui.",
|
||||
"death_ticket_eaten": "Schein der Uralten gegessen. Zu verrückt? Verstanden.",
|
||||
"death_revive": "Wiederbeleben",
|
||||
"death_yes": "Ja",
|
||||
"death_no": "Nein",
|
||||
"death_yes_hint": "Ja = Spiel beenden (zur Title).",
|
||||
"death_no_hint": "Nein = trotzdem wiederbeleben. Satire.",
|
||||
"shop_joke_continue": "Kostet 10 Dresslerpunkte… Spaß, du darfst weiter! Pay2Win war nur für reiche Leute.",
|
||||
"shop_joke_free": "Nein Spaß, das bekommst du gratis.",
|
||||
"shop_continue_joke": "Weiter spielen (10 Punkte? Spaß)",
|
||||
"shop_next_level": "Nächstes Level freikaufen (1 Punkt)",
|
||||
"shop_burger": "Burger (1 Punkt)",
|
||||
"item_dressler_apple": "Zertresselter Apfel",
|
||||
"item_wedding_egg": "Hochzeitsei / Whiff",
|
||||
"item_lava_chicken": "Lava-Chicken",
|
||||
"status_fat": "ZU FETT (%s)",
|
||||
"status_shock": "URHAHN-SCHOCK",
|
||||
"status_glitch": "VERGLITSCHT",
|
||||
"choice_massage_now": "Schlange jetzt massieren",
|
||||
"choice_drink_first": "Erst viel trinken (Secret)",
|
||||
"choice_hammer_1": "Hammer 1",
|
||||
"choice_hammer_2": "Hammer 2",
|
||||
"choice_hammer_3": "Hammer 3"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,32 +20,32 @@
|
|||
"lang_voice_de": "Am deutschen Wesen soll die Welt genesen",
|
||||
"lang_voice_en": "I drink my garden in the cinema",
|
||||
"lang_voice_by": "Schulz",
|
||||
"dialog_reif": "If you are not ripe yet, I must gift you a shoe!",
|
||||
"dialog_reif": "Wenn du noch nicht reif bist, muss ich dir einen Schuh schenken!",
|
||||
"dialog_snake": "I am very tense, oh everything hurts",
|
||||
"dialog_bear": "Hongkong turned the lawnmower into snakes",
|
||||
"dialog_hongkong_npc": "Hongkong, Hongkong is he!",
|
||||
"dialog_cow": "Massage options: Snake / Player / Square Emoji",
|
||||
"dialog_cow": "Masseur-Kuh: Massiere die Schlange jetzt – oder soll ich erst viel trinken?",
|
||||
"dialog_farmer": "Howdy! Pigs need quests and the Ur-rooster pecks.",
|
||||
"dialog_mage": "Right path leads to the farm. Left is fake open world.",
|
||||
"dialog_cheese": "The moon is made of cheese. Want sunglasses?",
|
||||
"dialog_bowser": "I am holding a chicken hostage! (Fake Bowser)",
|
||||
"dialog_hongkong_boss": "I will return!",
|
||||
"dialog_bear_end": "Good that you saved our world. But dental costs are still expensive.",
|
||||
"dialog_dressler": "Dress run the one!",
|
||||
"dialog_intro_1": "You sit at your desk…",
|
||||
"dialog_intro_2": "You must succeed, or Hongkong comes",
|
||||
"death_shoe": "Gifted a shoe",
|
||||
"death_mower": "Combine-harvester bite",
|
||||
"death_rooster": "Pecked to death by Ur-rooster",
|
||||
"death_quirxel": "Squared by a Quirxel",
|
||||
"death_default": "Died to death",
|
||||
"dialog_cheese": "Verschimmelter Käse! Nicht den Mund treffen lassen – stech den KÄSE, nicht den Verkäufer!",
|
||||
"dialog_bowser": "Fake Bowser hält das Huhn! Rate den richtigen Hammer – sonst frisst er dich.",
|
||||
"dialog_hongkong_boss": "Ich werde wiederkommen!",
|
||||
"dialog_bear_end": "Gut, dass du unsere Welt gerettet hast. Aber ich bin wütend – die Zahnarztkosten sind nicht günstiger geworden. Schlechtes Gewissen inklusive.",
|
||||
"dialog_dressler": "Dresslauf die Eins!",
|
||||
"dialog_intro_1": "Du sitzt an deinem Tisch und spielst dein Lieblingsspiel…",
|
||||
"dialog_intro_2": "Du musst es schaffen. Du musst es schaffen – sonst kommt der Hongkong.",
|
||||
"death_shoe": "Da hat dir wohl wer 'nen Schuh geschenkt.",
|
||||
"death_mower": "Du wurdest von einem Mähdrescher gebissen. Dass sowas mal passiert, hätte ich jetzt nicht gedacht.",
|
||||
"death_rooster": "Vom Urhahn totgepickt. 10 Sekunden Schock.",
|
||||
"death_quirxel": "Von einem Quirxel gequadratet.",
|
||||
"death_default": "Zu Tode gestorben.",
|
||||
"item_tooth": "Tooth",
|
||||
"item_snake": "Snake",
|
||||
"item_milk": "Self-reported milk",
|
||||
"item_banana": "Banana",
|
||||
"item_wartchen": "Wartchen",
|
||||
"item_sunglasses": "Cool sunglasses",
|
||||
"item_death_ticket": "Ticket: Eaten to death",
|
||||
"item_death_ticket": "Schein der Uralten",
|
||||
"item_moon_sword": "Moon sword",
|
||||
"item_chicken": "Chicken",
|
||||
"item_cheese": "Moldy cheese",
|
||||
|
|
@ -84,5 +84,60 @@
|
|||
"ach_tooth_desc": "Steal teeth at the dentist",
|
||||
"ach_all_title": "Burger, Burger, Burger. Today I eat all quests",
|
||||
"ach_all_desc": "Unlock all achievements",
|
||||
"burger_cham": "Burger! Best quality from Explosion der Spiele"
|
||||
"burger_cham": "Burger! Beste Qualität von der Explosion der Spiele",
|
||||
"title_pay2win": "Pay2Win (to attract rich people, hihihihi)",
|
||||
"title_music_hint": "Hey, das hier ist so eine laute Musik extra. Damit du direkt auf Starten drückst…",
|
||||
"lang_pick_title": "Choose language",
|
||||
"lang_de": "Deutsch",
|
||||
"lang_en": "English",
|
||||
"lang_by": "Bayerisch",
|
||||
"dialog_villager_run": "Hongkong! Hongkong ist er! Er hat alles zerstört!",
|
||||
"dialog_table_hint": "Unter dem Tisch ist ein Knopf…",
|
||||
"dialog_tiramisu": "Eine Tonne Tiramisu! Du bist jetzt 10 Minuten zu fett. Stimulation: Subway Surf & Satisfying Shorts.",
|
||||
"dialog_bear_help": "Hilfe! Der Schlangenmähdrescher würgt mich!",
|
||||
"dialog_bear_saved": "Hongkong hat den Rasenmäher zu Schlangen verwandelt. Nimm meinen Zahn – und bring die Schlangen zur Massage!",
|
||||
"dialog_snake_pain": "Ich bin sehr verspannt, oh, mir tut alles so weh.",
|
||||
"dialog_villager_snake": "Der Schlange geht es nicht so gut, ich glaube, sie braucht eine Massage.",
|
||||
"dialog_cow_drink": "Gluck gluck… ich melk mich selbst. Hier, selbst gemelkte Milch.",
|
||||
"dialog_cow_massage": "Die Schlange schläft ein… Quest erledigt.",
|
||||
"dialog_catscene": "Du hast das Dorf verlassen. Nun könntest du in eine Open World… aber das wäre zu viel Arbeit. Keine Open World. Du bist traurig. Ist mir egal.",
|
||||
"dialog_catscene_skip": "Skip this exciting catscene? Are you nuts? Penalty: start over!",
|
||||
"dialog_mage_push": "Mit der Macht… zurück nach links!",
|
||||
"dialog_mage_angry": "Ich bin der dunkelgraue Magier – NICHT der schwarze! *explodiert den Planeten*",
|
||||
"dialog_farmer_direct": "Grrrrirstsel, du bist ja brav und bist gleich hierher gegangen!",
|
||||
"dialog_farmer_detour": "Grrrrirstsel, du bist also nicht gleich hierher gegangen – ich muss erst den Magier holen. Umtalentiert!",
|
||||
"dialog_farmer_quest": "Weißt du was? Wennst die Tierregie machst, kriegst von mir 1,99. Schweine füttern, Kühe melken, Hühner sneaken – Vorsicht Urhahn!",
|
||||
"dialog_urhahn": "KIKERIKIIII – der Urhahn pickt dich tot. 10 Sekunden Schock.",
|
||||
"dialog_paywall": "Du willst weiterspielen? Zahl jetzt 1,99… oder Dresslerpunkte im Shop.",
|
||||
"dialog_dressler_drop": "Auf dem Apfel steht: ESSEN NICHT – außer du willst das Spiel durchspielen.",
|
||||
"dialog_boss_unclever": "Ich weiß, wie du zu mir gelangt bist. Das ist sehr unkreativ, aber wir hatten keine Ideen mehr.",
|
||||
"dialog_sunglasses_free": "Sonnenbrille? Nein Spaß – gratis!",
|
||||
"dialog_reveal": "Übrigens: Hongkong war nur ein Prank – er heißt echt Glitschkönig! Hahaha! Du bist voll drauf reingefallen. Belohnung: Banane + Wartchen.",
|
||||
"dialog_end_angry": "Die Dorfbewohner sind wütend wegen der Zahnarztkosten. Bau trotzdem dein Haus.",
|
||||
"dialog_toothionaire": "Brich beim Zahnarzt ein und klaue die Zähne – werde Zahnionär!",
|
||||
"death_glitzo": "⬜",
|
||||
"death_bowser": "Fake Bowser hat dich aufgegessen.",
|
||||
"death_cheese": "Verschimmelter Käse im Mund. Pfui.",
|
||||
"death_ticket_eaten": "Schein der Uralten gegessen. Zu verrückt? Verstanden.",
|
||||
"death_revive": "Revive",
|
||||
"death_yes": "Yes",
|
||||
"death_no": "No",
|
||||
"death_yes_hint": "Ja = Spiel beenden (zur Title).",
|
||||
"death_no_hint": "Nein = trotzdem wiederbeleben. Satire.",
|
||||
"shop_joke_continue": "Kostet 10 Dresslerpunkte… Spaß, du darfst weiter! Pay2Win war nur für reiche Leute.",
|
||||
"shop_joke_free": "Nein Spaß, das bekommst du gratis.",
|
||||
"shop_continue_joke": "Weiter spielen (10 Punkte? Spaß)",
|
||||
"shop_next_level": "Nächstes Level freikaufen (1 Punkt)",
|
||||
"shop_burger": "Burger (1 Punkt)",
|
||||
"item_dressler_apple": "Zertresselter Apfel",
|
||||
"item_wedding_egg": "Hochzeitsei / Whiff",
|
||||
"item_lava_chicken": "Lava-Chicken",
|
||||
"status_fat": "ZU FETT (%s)",
|
||||
"status_shock": "URHAHN-SCHOCK",
|
||||
"status_glitch": "VERGLITSCHT",
|
||||
"choice_massage_now": "Schlange jetzt massieren",
|
||||
"choice_drink_first": "Erst viel trinken (Secret)",
|
||||
"choice_hammer_1": "Hammer 1",
|
||||
"choice_hammer_2": "Hammer 2",
|
||||
"choice_hammer_3": "Hammer 3"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
; Engine configuration file.
|
||||
; Explosion der Spiele — Pay2Win Chaos Scaffold (Godot 4.3+)
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
|
|
@ -91,4 +96,3 @@ skip_cutscene={
|
|||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
anti_aliasing/quality/msaa_3d=0
|
||||
|
|
|
|||
72
scenes/catscene.tscn
Normal file
72
scenes/catscene.tscn
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/levels/catscene.gd" id="1"]
|
||||
|
||||
[node name="Catscene" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Bg" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.15, 0.12, 0.2, 1)
|
||||
|
||||
[node name="Cat" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -80.0
|
||||
offset_top = -120.0
|
||||
offset_right = 80.0
|
||||
offset_bottom = -20.0
|
||||
color = Color(1, 0.6, 0.2, 1)
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -420.0
|
||||
offset_top = 40.0
|
||||
offset_right = 420.0
|
||||
offset_bottom = 200.0
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
text = "..."
|
||||
|
||||
[node name="ContinueButton" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -140.0
|
||||
offset_top = -140.0
|
||||
offset_right = 140.0
|
||||
offset_bottom = -90.0
|
||||
text = "Weiter"
|
||||
|
||||
[node name="SkipButton" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -140.0
|
||||
offset_top = -80.0
|
||||
offset_right = 140.0
|
||||
offset_bottom = -30.0
|
||||
text = "Skip"
|
||||
|
||||
[connection signal="pressed" from="ContinueButton" to="." method="_on_continue_pressed"]
|
||||
[connection signal="pressed" from="SkipButton" to="." method="_on_skip_pressed"]
|
||||
|
|
@ -16,7 +16,7 @@ layout_mode = 1
|
|||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.7, 0.1, 0.5, 1)
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="Title" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
|
|
@ -24,9 +24,9 @@ anchors_preset = 5
|
|||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -300.0
|
||||
offset_top = 120.0
|
||||
offset_top = 80.0
|
||||
offset_right = 300.0
|
||||
offset_bottom = 180.0
|
||||
offset_bottom = 140.0
|
||||
theme_override_font_sizes/font_size = 36
|
||||
horizontal_alignment = 1
|
||||
text = "DU BIST GESTORBEN"
|
||||
|
|
@ -36,13 +36,26 @@ layout_mode = 1
|
|||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -300.0
|
||||
offset_top = 200.0
|
||||
offset_right = 300.0
|
||||
offset_bottom = 250.0
|
||||
offset_left = -360.0
|
||||
offset_top = 160.0
|
||||
offset_right = 360.0
|
||||
offset_bottom = 260.0
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
text = "..."
|
||||
|
||||
[node name="Hint" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -360.0
|
||||
offset_top = 280.0
|
||||
offset_right = 360.0
|
||||
offset_bottom = 360.0
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="Skull" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
|
|
@ -51,13 +64,13 @@ anchor_left = 0.5
|
|||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -40.0
|
||||
offset_top = -40.0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
offset_left = -60.0
|
||||
offset_top = -60.0
|
||||
offset_right = 60.0
|
||||
offset_bottom = 60.0
|
||||
color = Color(1, 1, 1, 1)
|
||||
|
||||
[node name="ContinueButton" type="Button" parent="."]
|
||||
[node name="ReviveButton" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
|
|
@ -65,9 +78,37 @@ anchor_top = 1.0
|
|||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -160.0
|
||||
offset_top = -120.0
|
||||
offset_top = -200.0
|
||||
offset_right = 160.0
|
||||
offset_bottom = -70.0
|
||||
text = "Continue"
|
||||
offset_bottom = -150.0
|
||||
text = "Wiederbeleben"
|
||||
|
||||
[connection signal="pressed" from="ContinueButton" to="." method="_on_continue_pressed"]
|
||||
[node name="NoButton" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -200.0
|
||||
offset_top = -130.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = -80.0
|
||||
text = "Nein"
|
||||
|
||||
[node name="YesButton" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 20.0
|
||||
offset_top = -130.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = -80.0
|
||||
text = "Ja"
|
||||
|
||||
[connection signal="pressed" from="ReviveButton" to="." method="_on_revive_pressed"]
|
||||
[connection signal="pressed" from="NoButton" to="." method="_on_no_pressed"]
|
||||
[connection signal="pressed" from="YesButton" to="." method="_on_yes_pressed"]
|
||||
|
|
|
|||
55
scenes/dressler_boss.tscn
Normal file
55
scenes/dressler_boss.tscn
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/levels/dressler_boss.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/prefabs/player.tscn" id="2"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/prefabs/enemy.tscn" id="3"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/prefabs/exit_trigger.tscn" id="4"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/prefabs/hazard.tscn" id="5"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_ground"]
|
||||
size = Vector3(40, 0.2, 40)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Mat_ground"]
|
||||
albedo_color = Color(0.35, 0.55, 0.2, 1)
|
||||
shading_mode = 0
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape_ground"]
|
||||
size = Vector3(40, 0.2, 40)
|
||||
|
||||
[node name="dressler_boss" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
|
||||
mesh = SubResource("BoxMesh_ground")
|
||||
surface_material_override/0 = SubResource("Mat_ground")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"]
|
||||
shape = SubResource("BoxShape_ground")
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 6)
|
||||
|
||||
[node name="Dressler" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -3)
|
||||
max_hp = 10
|
||||
texture_path = "res://assets/sprites/placeholders/apple.png"
|
||||
defeat_flag = "dressler_defeated"
|
||||
achievement_on_defeat = "dressler_apples"
|
||||
|
||||
[node name="Mower1" parent="." instance=ExtResource("5")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0.1, -1)
|
||||
death_cause_key = "death_mower"
|
||||
|
||||
[node name="Mower2" parent="." instance=ExtResource("5")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.1, -1)
|
||||
death_cause_key = "death_mower"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -12)
|
||||
require_flag = "dressler_defeated"
|
||||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -45,6 +41,12 @@ shape = SubResource("BoxShape_ground")
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 4)
|
||||
|
||||
|
||||
|
||||
[node name="DentistHeist" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -5)
|
||||
dialog_key = "dialog_toothionaire"
|
||||
texture_path = "res://assets/sprites/placeholders/npc.png"
|
||||
|
||||
[node name="BearEnd" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.1, 0)
|
||||
dialog_key = "dialog_bear_end"
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -49,7 +45,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 4)
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0.1, 0)
|
||||
dialog_key = "dialog_farmer"
|
||||
texture_path = "res://assets/sprites/placeholders/farmer.png"
|
||||
give_item_id = "chicken"
|
||||
|
||||
[node name="Urhahn" parent="." instance=ExtResource("5")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.1, -2)
|
||||
|
|
@ -62,14 +57,34 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.1, -4)
|
|||
death_cause_key = "death_rooster"
|
||||
|
||||
|
||||
[node name="Dressler" parent="." instance=ExtResource("5")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0.1, -4)
|
||||
max_hp = 5
|
||||
texture_path = "res://assets/sprites/placeholders/apple.png"
|
||||
defeat_flag = "dressler_defeated"
|
||||
achievement_on_defeat = "dressler_apples"
|
||||
|
||||
|
||||
[node name="PigQuest" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.1, 2)
|
||||
dialog_key = "dialog_farmer_quest"
|
||||
texture_path = "res://assets/sprites/placeholders/npc.png"
|
||||
quest_flag = "pigs_fed"
|
||||
|
||||
[node name="CowQuest" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0.1, 2)
|
||||
dialog_key = "dialog_farmer_quest"
|
||||
texture_path = "res://assets/sprites/placeholders/cow.png"
|
||||
quest_flag = "cows_milked"
|
||||
|
||||
[node name="ChickenQuest" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.1, -6)
|
||||
dialog_key = "dialog_urhahn"
|
||||
texture_path = "res://assets/sprites/placeholders/rooster.png"
|
||||
give_item_id = "chicken"
|
||||
quest_flag = "chickens_taken"
|
||||
|
||||
|
||||
[node name="TicketVendor" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.1, -8)
|
||||
dialog_key = "item_death_ticket"
|
||||
texture_path = "res://assets/sprites/placeholders/npc.png"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -12)
|
||||
require_flag = "urhahn_defeated"
|
||||
require_flag = "paywall_cleared"
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -51,7 +47,13 @@ dialog_key = "dialog_mage"
|
|||
texture_path = "res://assets/sprites/placeholders/mage.png"
|
||||
quest_flag = "mage_talked"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.1, -10)
|
||||
require_flag = "mage_talked"
|
||||
[node name="LeftExit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0.1, -10)
|
||||
use_goto_next = false
|
||||
next_scene_id = "farm"
|
||||
|
||||
[node name="RightZone" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.1, -6)
|
||||
use_goto_next = false
|
||||
next_scene_id = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -49,11 +45,47 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 4)
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -2)
|
||||
dialog_key = "dialog_cow"
|
||||
texture_path = "res://assets/sprites/placeholders/cow.png"
|
||||
require_item_id = "snake"
|
||||
consume_required = true
|
||||
quest_flag = "snake_massaged"
|
||||
achievement_id = "snake_massage"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -10)
|
||||
|
||||
|
||||
[node name="ChoiceUI" type="CanvasLayer" parent="."]
|
||||
layer = 50
|
||||
|
||||
[node name="Panel" type="PanelContainer" parent="ChoiceUI"]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -220.0
|
||||
offset_top = -100.0
|
||||
offset_right = 220.0
|
||||
offset_bottom = 100.0
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="ChoiceUI/Panel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="ChoiceUI/Panel/Margin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ChoiceUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
autowrap_mode = 3
|
||||
text = "Kuh"
|
||||
|
||||
[node name="MassageBtn" type="Button" parent="ChoiceUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Massage"
|
||||
|
||||
[node name="DrinkBtn" type="Button" parent="ChoiceUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Trinken"
|
||||
|
||||
[connection signal="pressed" from="ChoiceUI/Panel/Margin/VBox/MassageBtn" to="." method="_on_massage_pressed"]
|
||||
[connection signal="pressed" from="ChoiceUI/Panel/Margin/VBox/DrinkBtn" to="." method="_on_drink_pressed"]
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -60,6 +56,12 @@ texture_path = "res://assets/sprites/placeholders/bear.png"
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -5)
|
||||
death_cause_key = "death_mower"
|
||||
|
||||
|
||||
[node name="SnakePickup" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0.1, -4)
|
||||
dialog_key = "dialog_snake_pain"
|
||||
texture_path = "res://assets/sprites/placeholders/snake.png"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -12)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -57,14 +53,63 @@ dialog_key = "dialog_cheese"
|
|||
texture_path = "res://assets/sprites/placeholders/apple.png"
|
||||
give_item_id = "moon_sword"
|
||||
|
||||
[node name="FakeBowser" parent="." instance=ExtResource("5")]
|
||||
[node name="FakeBowser" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -4)
|
||||
max_hp = 8
|
||||
dialog_key = "dialog_bowser"
|
||||
texture_path = "res://assets/sprites/placeholders/bowser.png"
|
||||
defeat_flag = "bowser_defeated"
|
||||
achievement_on_defeat = "beat_bowser"
|
||||
|
||||
|
||||
[node name="MoonResearcher" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.1, -6)
|
||||
dialog_key = "dialog_sunglasses_free"
|
||||
texture_path = "res://assets/sprites/placeholders/npc.png"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -12)
|
||||
require_flag = "bowser_defeated"
|
||||
|
||||
|
||||
[node name="HammerUI" type="CanvasLayer" parent="."]
|
||||
layer = 50
|
||||
|
||||
[node name="Panel" type="PanelContainer" parent="HammerUI"]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -110.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 110.0
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="HammerUI/Panel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="HammerUI/Panel/Margin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="HammerUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
autowrap_mode = 3
|
||||
text = "Hammer"
|
||||
|
||||
[node name="H1" type="Button" parent="HammerUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Hammer 1"
|
||||
|
||||
[node name="H2" type="Button" parent="HammerUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Hammer 2"
|
||||
|
||||
[node name="H3" type="Button" parent="HammerUI/Panel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Hammer 3"
|
||||
|
||||
[connection signal="pressed" from="HammerUI/Panel/Margin/VBox/H1" to="." method="_on_h1"]
|
||||
[connection signal="pressed" from="HammerUI/Panel/Margin/VBox/H2" to="." method="_on_h2"]
|
||||
[connection signal="pressed" from="HammerUI/Panel/Margin/VBox/H3" to="." method="_on_h3"]
|
||||
|
|
|
|||
18
scenes/prefabs/cheese_projectile.tscn
Normal file
18
scenes/prefabs/cheese_projectile.tscn
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/combat/cheese_projectile.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/prefabs/billboard_sprite.tscn" id="2"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 0.35
|
||||
|
||||
[node name="CheeseProjectile" type="Area3D"]
|
||||
collision_layer = 4
|
||||
collision_mask = 2
|
||||
monitoring = true
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
9
scenes/prefabs/cheese_seller.tscn
Normal file
9
scenes/prefabs/cheese_seller.tscn
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/combat/cheese_seller.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/prefabs/billboard_sprite.tscn" id="2"]
|
||||
|
||||
[node name="CheeseSeller" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
|
|
@ -8,4 +8,4 @@ script = ExtResource("1")
|
|||
max_hp = 4
|
||||
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@
|
|||
script = ExtResource("1")
|
||||
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
radius = 0.35
|
||||
height = 1.0
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_spring"]
|
||||
radius = 0.2
|
||||
|
||||
[node name="Player" type="CharacterBody3D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
|
|
@ -17,4 +20,17 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
|||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
|
||||
[node name="CameraYaw" type="Node3D" parent="."]
|
||||
|
||||
[node name="SpringArm3D" type="SpringArm3D" parent="CameraYaw"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4, 0)
|
||||
spring_length = 5.5
|
||||
collision_mask = 1
|
||||
margin = 0.2
|
||||
shape = SubResource("SphereShape3D_spring")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraYaw/SpringArm3D"]
|
||||
current = true
|
||||
fov = 70.0
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dbw7nd6f3bxoy"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/levels/title_screen.gd" id="1"]
|
||||
|
||||
|
|
@ -28,35 +28,50 @@ anchors_preset = 5
|
|||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -360.0
|
||||
offset_top = 80.0
|
||||
offset_top = 64.0
|
||||
offset_right = 360.0
|
||||
offset_bottom = 140.0
|
||||
offset_bottom = 124.0
|
||||
grow_horizontal = 2
|
||||
theme_override_font_sizes/font_size = 40
|
||||
horizontal_alignment = 1
|
||||
text = "Explosion der Spiele"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Subtitle" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -360.0
|
||||
offset_top = 140.0
|
||||
offset_right = 360.0
|
||||
offset_bottom = 180.0
|
||||
offset_left = -420.0
|
||||
offset_top = 124.0
|
||||
offset_right = 420.0
|
||||
offset_bottom = 170.0
|
||||
grow_horizontal = 2
|
||||
horizontal_alignment = 1
|
||||
text = "Pay2Win"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="MusicHint" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -400.0
|
||||
offset_top = 180.0
|
||||
offset_right = 400.0
|
||||
offset_bottom = 240.0
|
||||
grow_horizontal = 2
|
||||
text = "LAUTE MUSIK"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="Bubble" type="Label" parent="."]
|
||||
layout_mode = 0
|
||||
offset_left = 40.0
|
||||
offset_top = 500.0
|
||||
offset_top = 480.0
|
||||
offset_right = 420.0
|
||||
offset_bottom = 620.0
|
||||
autowrap_mode = 3
|
||||
offset_bottom = 600.0
|
||||
text = "..."
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="Disturbed" type="ColorRect" parent="."]
|
||||
layout_mode = 0
|
||||
|
|
@ -81,4 +96,47 @@ grow_horizontal = 2
|
|||
grow_vertical = 0
|
||||
text = "Starten"
|
||||
|
||||
[node name="LangPanel" type="PanelContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -180.0
|
||||
offset_top = -120.0
|
||||
offset_right = 180.0
|
||||
offset_bottom = 120.0
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="LangPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="LangPanel/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="Title" type="Label" parent="LangPanel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Sprache"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BtnDE" type="Button" parent="LangPanel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Deutsch"
|
||||
|
||||
[node name="BtnEN" type="Button" parent="LangPanel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "English"
|
||||
|
||||
[node name="BtnBY" type="Button" parent="LangPanel/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Bayerisch"
|
||||
|
||||
[connection signal="pressed" from="StartButton" to="." method="_on_start_pressed"]
|
||||
[connection signal="pressed" from="LangPanel/Margin/VBox/BtnDE" to="." method="_on_lang_de"]
|
||||
[connection signal="pressed" from="LangPanel/Margin/VBox/BtnEN" to="." method="_on_lang_en"]
|
||||
[connection signal="pressed" from="LangPanel/Margin/VBox/BtnBY" to="." method="_on_lang_by"]
|
||||
|
|
|
|||
|
|
@ -195,3 +195,25 @@ layout_mode = 2
|
|||
[connection signal="value_changed" from="PausePanel/Margin/VBox/MusicSlider" to="." method="_on_music_changed"]
|
||||
[connection signal="item_selected" from="PausePanel/Margin/VBox/LangOption" to="." method="_on_lang_selected"]
|
||||
[connection signal="item_activated" from="PausePanel/Margin/VBox/InventoryList" to="." method="_on_inventory_activated"]
|
||||
|
||||
[node name="FatStim" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
mouse_filter = 2
|
||||
color = Color(0.05, 0.05, 0.08, 0.55)
|
||||
|
||||
[node name="FatLabel" type="Label" parent="FatStim"]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -280.0
|
||||
offset_top = -40.0
|
||||
offset_right = 280.0
|
||||
offset_bottom = 40.0
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
text = "SUBWAY SURF / SATISFYING SHORTS"
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
|
@ -49,7 +45,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 4)
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0.1, 0)
|
||||
dialog_key = "dialog_hongkong_npc"
|
||||
texture_path = "res://assets/sprites/placeholders/npc.png"
|
||||
give_item_id = "snake"
|
||||
|
||||
[node name="House" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0.1, -2)
|
||||
|
|
@ -61,9 +56,13 @@ texture_path = "res://assets/sprites/placeholders/house.png"
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.1, 2)
|
||||
dialog_key = "dialog_hongkong_npc"
|
||||
texture_path = "res://assets/sprites/placeholders/npc.png"
|
||||
give_item_id = "tooth"
|
||||
quest_flag = "teeth_stolen"
|
||||
achievement_id = "tooth_millionaire"
|
||||
|
||||
|
||||
[node name="TableButton" parent="." instance=ExtResource("4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 1)
|
||||
dialog_key = "dialog_table_hint"
|
||||
texture_path = "res://assets/sprites/placeholders/house.png"
|
||||
on_success_goto = "shoe_boss"
|
||||
|
||||
[node name="Exit" parent="." instance=ExtResource("3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, -10)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func _ready() -> void:
|
|||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_music = AudioStreamPlayer.new()
|
||||
_music.bus = "Music"
|
||||
_music.volume_db = 6.0 # intentionally loud
|
||||
_music.volume_db = 12.0 # intentionally painfully loud
|
||||
add_child(_music)
|
||||
_sfx = AudioStreamPlayer.new()
|
||||
_sfx.bus = "SFX"
|
||||
|
|
@ -48,7 +48,7 @@ func play_title_music() -> void:
|
|||
if stream == null:
|
||||
return
|
||||
_music.stream = stream
|
||||
_music.volume_db = 6.0
|
||||
_music.volume_db = 12.0
|
||||
if not _music.playing:
|
||||
_music.play()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,12 @@
|
|||
extends Node
|
||||
## Global game flow, HP, death, playtime, save/load.
|
||||
## TODO: Expand save schema when real progression exists.
|
||||
## Global game flow matching DESIGN.md.
|
||||
|
||||
signal hp_changed(hp: int, max_hp: int)
|
||||
signal died(cause_key: String)
|
||||
signal scene_changed(scene_id: String)
|
||||
signal status_changed
|
||||
|
||||
const SAVE_PATH := "user://save.json"
|
||||
const SCENE_ORDER: Array[String] = [
|
||||
"title_screen",
|
||||
"intro_cutscene",
|
||||
"village",
|
||||
"shoe_boss",
|
||||
"meadow",
|
||||
"massage_studio",
|
||||
"farm_path",
|
||||
"farm",
|
||||
"moon",
|
||||
"final_boss",
|
||||
"end_screen",
|
||||
]
|
||||
|
||||
const SCENE_PATHS := {
|
||||
"title_screen": "res://scenes/title_screen.tscn",
|
||||
|
|
@ -28,23 +15,47 @@ const SCENE_PATHS := {
|
|||
"shoe_boss": "res://scenes/shoe_boss.tscn",
|
||||
"meadow": "res://scenes/meadow.tscn",
|
||||
"massage_studio": "res://scenes/massage_studio.tscn",
|
||||
"catscene": "res://scenes/catscene.tscn",
|
||||
"farm_path": "res://scenes/farm_path.tscn",
|
||||
"farm": "res://scenes/farm.tscn",
|
||||
"dressler_boss": "res://scenes/dressler_boss.tscn",
|
||||
"moon": "res://scenes/moon.tscn",
|
||||
"final_boss": "res://scenes/final_boss.tscn",
|
||||
"end_screen": "res://scenes/end_screen.tscn",
|
||||
"death_screen": "res://scenes/death_screen.tscn",
|
||||
}
|
||||
|
||||
## Default linear beats (branches override via goto_scene / flags).
|
||||
const SCENE_ORDER: Array[String] = [
|
||||
"title_screen",
|
||||
"intro_cutscene",
|
||||
"village",
|
||||
"shoe_boss",
|
||||
"meadow",
|
||||
"massage_studio",
|
||||
"catscene",
|
||||
"farm_path",
|
||||
"farm",
|
||||
"dressler_boss",
|
||||
"moon",
|
||||
"final_boss",
|
||||
"end_screen",
|
||||
]
|
||||
|
||||
var current_scene_id: String = "title_screen"
|
||||
var max_hp: int = 5
|
||||
var hp: int = 5
|
||||
var max_hp: int = 4
|
||||
var hp: int = 4
|
||||
var death_count: int = 0
|
||||
var last_death_cause_key: String = "death_default"
|
||||
var playtime_seconds: float = 0.0
|
||||
var house_built: bool = false
|
||||
var flags: Dictionary = {}
|
||||
|
||||
## Status: fat (tiramisu), glitched (3 glitzo hits), urhahn shock
|
||||
var fat_time_left: float = 0.0
|
||||
var glitch_hits: int = 0
|
||||
var shock_time_left: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
load_game()
|
||||
|
|
@ -57,6 +68,42 @@ func _process(delta: float) -> void:
|
|||
playtime_seconds += delta
|
||||
if playtime_seconds >= 3600.0:
|
||||
Achievements.unlock("played_hour")
|
||||
var dirty := false
|
||||
if fat_time_left > 0.0:
|
||||
fat_time_left = max(0.0, fat_time_left - delta)
|
||||
dirty = true
|
||||
if shock_time_left > 0.0:
|
||||
shock_time_left = max(0.0, shock_time_left - delta)
|
||||
dirty = true
|
||||
if dirty:
|
||||
status_changed.emit()
|
||||
|
||||
func is_fat() -> bool:
|
||||
return fat_time_left > 0.0
|
||||
|
||||
func is_shocked() -> bool:
|
||||
return shock_time_left > 0.0
|
||||
|
||||
func is_glitched_slow() -> bool:
|
||||
return glitch_hits >= 3
|
||||
|
||||
func apply_fat(seconds: float = 600.0) -> void:
|
||||
fat_time_left = max(fat_time_left, seconds)
|
||||
Achievements.unlock("tiramisu_tonne")
|
||||
status_changed.emit()
|
||||
|
||||
func apply_shock(seconds: float = 10.0) -> void:
|
||||
shock_time_left = max(shock_time_left, seconds)
|
||||
status_changed.emit()
|
||||
|
||||
func register_glitzo_hit() -> void:
|
||||
glitch_hits += 1
|
||||
status_changed.emit()
|
||||
take_damage(1, "death_glitzo")
|
||||
|
||||
func clear_glitch() -> void:
|
||||
glitch_hits = 0
|
||||
status_changed.emit()
|
||||
|
||||
func goto_scene(scene_id: String) -> void:
|
||||
if not SCENE_PATHS.has(scene_id):
|
||||
|
|
@ -70,13 +117,35 @@ func goto_scene(scene_id: String) -> void:
|
|||
get_tree().change_scene_to_file(SCENE_PATHS[scene_id])
|
||||
|
||||
func goto_next() -> void:
|
||||
var idx := SCENE_ORDER.find(current_scene_id)
|
||||
if idx < 0:
|
||||
goto_scene("village")
|
||||
return
|
||||
if idx >= SCENE_ORDER.size() - 1:
|
||||
match current_scene_id:
|
||||
"village":
|
||||
goto_scene("shoe_boss")
|
||||
"shoe_boss":
|
||||
goto_scene("meadow")
|
||||
"meadow":
|
||||
goto_scene("massage_studio")
|
||||
"massage_studio":
|
||||
goto_scene("catscene")
|
||||
"catscene":
|
||||
goto_scene("farm_path")
|
||||
"farm_path":
|
||||
if get_flag("planet_destroyed", false):
|
||||
goto_scene("moon")
|
||||
else:
|
||||
goto_scene("farm")
|
||||
"farm":
|
||||
goto_scene("dressler_boss")
|
||||
"dressler_boss":
|
||||
goto_scene("moon")
|
||||
"moon":
|
||||
goto_scene("final_boss")
|
||||
"final_boss":
|
||||
goto_scene("end_screen")
|
||||
return
|
||||
_:
|
||||
var idx := SCENE_ORDER.find(current_scene_id)
|
||||
if idx < 0 or idx >= SCENE_ORDER.size() - 1:
|
||||
goto_scene("end_screen")
|
||||
else:
|
||||
goto_scene(SCENE_ORDER[idx + 1])
|
||||
|
||||
func skip_to_final_boss() -> void:
|
||||
|
|
@ -84,14 +153,28 @@ func skip_to_final_boss() -> void:
|
|||
flags["milk_skip_used"] = true
|
||||
goto_scene("final_boss")
|
||||
|
||||
func reset_to_intro() -> void:
|
||||
## Catscene skip punishment.
|
||||
hp = max_hp
|
||||
glitch_hits = 0
|
||||
fat_time_left = 0.0
|
||||
shock_time_left = 0.0
|
||||
flags.clear()
|
||||
Inventory.deserialize({"items": {}})
|
||||
goto_scene("intro_cutscene")
|
||||
|
||||
func start_new_game() -> void:
|
||||
hp = max_hp
|
||||
glitch_hits = 0
|
||||
fat_time_left = 0.0
|
||||
shock_time_left = 0.0
|
||||
flags.clear()
|
||||
house_built = false
|
||||
goto_scene("intro_cutscene")
|
||||
|
||||
func heal_full() -> void:
|
||||
hp = max_hp
|
||||
glitch_hits = 0
|
||||
hp_changed.emit(hp, max_hp)
|
||||
|
||||
func take_damage(amount: int = 1, cause_key: String = "death_default") -> void:
|
||||
|
|
@ -109,6 +192,18 @@ func die(cause_key: String = "death_default") -> void:
|
|||
save_game()
|
||||
goto_scene("death_screen")
|
||||
|
||||
func revive_continue() -> void:
|
||||
heal_full()
|
||||
## Return to last non-death gameplay scene, default village.
|
||||
var back := str(flags.get("last_gameplay_scene", "village"))
|
||||
if back == "death_screen" or back == "title_screen":
|
||||
back = "village"
|
||||
goto_scene(back)
|
||||
|
||||
func quit_to_title() -> void:
|
||||
heal_full()
|
||||
goto_scene("title_screen")
|
||||
|
||||
func set_flag(key: String, value: Variant = true) -> void:
|
||||
flags[key] = value
|
||||
save_game()
|
||||
|
|
@ -116,7 +211,12 @@ func set_flag(key: String, value: Variant = true) -> void:
|
|||
func get_flag(key: String, default_value: Variant = false) -> Variant:
|
||||
return flags.get(key, default_value)
|
||||
|
||||
func remember_gameplay_scene() -> void:
|
||||
if current_scene_id not in ["title_screen", "death_screen", "intro_cutscene", "catscene"]:
|
||||
flags["last_gameplay_scene"] = current_scene_id
|
||||
|
||||
func save_game() -> void:
|
||||
remember_gameplay_scene()
|
||||
var data := {
|
||||
"hp": hp,
|
||||
"max_hp": max_hp,
|
||||
|
|
@ -126,6 +226,8 @@ func save_game() -> void:
|
|||
"current_scene_id": current_scene_id,
|
||||
"house_built": house_built,
|
||||
"flags": flags,
|
||||
"fat_time_left": fat_time_left,
|
||||
"glitch_hits": glitch_hits,
|
||||
"inventory": Inventory.serialize(),
|
||||
"achievements": Achievements.serialize(),
|
||||
"shop": Shop.serialize(),
|
||||
|
|
@ -133,7 +235,6 @@ func save_game() -> void:
|
|||
}
|
||||
var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||||
if file == null:
|
||||
push_warning("Could not write save file")
|
||||
return
|
||||
file.store_string(JSON.stringify(data, "\t"))
|
||||
|
||||
|
|
@ -155,6 +256,8 @@ func load_game() -> void:
|
|||
current_scene_id = str(data.get("current_scene_id", "title_screen"))
|
||||
house_built = bool(data.get("house_built", false))
|
||||
flags = data.get("flags", {})
|
||||
fat_time_left = float(data.get("fat_time_left", 0.0))
|
||||
glitch_hits = int(data.get("glitch_hits", 0))
|
||||
Inventory.deserialize(data.get("inventory", {}))
|
||||
Achievements.deserialize(data.get("achievements", {}))
|
||||
Shop.deserialize(data.get("shop", {}))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ extends Node
|
|||
signal inventory_changed
|
||||
signal item_used(item_id: String)
|
||||
|
||||
var _items: Dictionary = {} # id -> count
|
||||
var _items: Dictionary = {}
|
||||
var _defs: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -13,7 +13,6 @@ func _ready() -> void:
|
|||
func _load_defs() -> void:
|
||||
var file := FileAccess.open("res://data/items.json", FileAccess.READ)
|
||||
if file == null:
|
||||
push_error("Missing items.json")
|
||||
return
|
||||
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
||||
if typeof(parsed) != TYPE_ARRAY:
|
||||
|
|
@ -64,13 +63,30 @@ func use_item(item_id: String) -> bool:
|
|||
remove_item("death_ticket", 1)
|
||||
Achievements.unlock("eaten_to_death")
|
||||
item_used.emit(item_id)
|
||||
GameState.die("death_ticket_eaten")
|
||||
return true
|
||||
"tiramisu":
|
||||
remove_item("tiramisu", 1)
|
||||
var eaten := int(GameState.get_flag("tiramisu_kg", 0)) + 1
|
||||
GameState.set_flag("tiramisu_kg", eaten)
|
||||
if eaten >= 1000:
|
||||
Achievements.unlock("tiramisu_tonne")
|
||||
GameState.apply_fat(600.0)
|
||||
item_used.emit(item_id)
|
||||
return true
|
||||
"dressler_apple":
|
||||
remove_item("dressler_apple", 1)
|
||||
item_used.emit(item_id)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_boss_unclever"), 4.0)
|
||||
GameState.goto_scene("final_boss")
|
||||
return true
|
||||
"wedding_egg":
|
||||
remove_item("wedding_egg", 1)
|
||||
item_used.emit(item_id)
|
||||
GameState.set_flag("used_whiff", true)
|
||||
GameState.goto_scene("final_boss")
|
||||
return true
|
||||
"banana":
|
||||
remove_item("banana", 1)
|
||||
GameState.heal_full()
|
||||
item_used.emit(item_id)
|
||||
return true
|
||||
_:
|
||||
|
|
|
|||
|
|
@ -11,11 +11,14 @@ var language: String = "de"
|
|||
var sfx_volume: float = 1.0
|
||||
## Intentionally locked loud — slider is UI-disabled.
|
||||
var music_volume: float = 1.0
|
||||
var language_chosen: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
_ensure_buses()
|
||||
apply_audio()
|
||||
# Locale reloads after both Settings + Locale exist (Locale._ready / deserialize).
|
||||
|
||||
func needs_language_picker() -> bool:
|
||||
return not language_chosen
|
||||
|
||||
func _ensure_buses() -> void:
|
||||
_ensure_bus(MUSIC_BUS)
|
||||
|
|
@ -34,6 +37,7 @@ func set_language(lang: String) -> void:
|
|||
if lang not in ["de", "en", "by"]:
|
||||
return
|
||||
language = lang
|
||||
language_chosen = true
|
||||
Locale.reload()
|
||||
AudioDirector.play_language_voice(lang)
|
||||
settings_changed.emit()
|
||||
|
|
@ -46,7 +50,6 @@ func set_sfx_volume(value: float) -> void:
|
|||
GameState.save_game()
|
||||
|
||||
func set_music_volume(_value: float) -> void:
|
||||
## Satire lock: music stays maxed.
|
||||
music_volume = 1.0
|
||||
apply_audio()
|
||||
settings_changed.emit()
|
||||
|
|
@ -68,12 +71,14 @@ func serialize() -> Dictionary:
|
|||
"language": language,
|
||||
"sfx_volume": sfx_volume,
|
||||
"music_volume": 1.0,
|
||||
"language_chosen": language_chosen,
|
||||
}
|
||||
|
||||
func deserialize(data: Dictionary) -> void:
|
||||
language = str(data.get("language", "de"))
|
||||
sfx_volume = float(data.get("sfx_volume", 1.0))
|
||||
music_volume = 1.0
|
||||
language_chosen = bool(data.get("language_chosen", false))
|
||||
Locale.reload()
|
||||
apply_audio()
|
||||
settings_changed.emit()
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ var dressler_points: int = 0
|
|||
|
||||
const CATALOG := [
|
||||
{"id": "buy_point", "cost": 0, "mock_euro": true},
|
||||
{"id": "sunglasses", "cost": 5, "gives": "sunglasses"},
|
||||
{"id": "death_ticket", "cost": 3, "gives": "death_ticket"},
|
||||
{"id": "milk", "cost": 10, "gives": "milk"},
|
||||
{"id": "tiramisu", "cost": 1, "gives": "tiramisu"},
|
||||
{"id": "continue_joke", "cost": 10, "joke_continue": true},
|
||||
{"id": "death_ticket", "cost": 1, "gives": "death_ticket"},
|
||||
{"id": "sunglasses", "cost": 1, "gives": "sunglasses", "actually_free": true},
|
||||
{"id": "burger", "cost": 1, "gives": "tiramisu"},
|
||||
{"id": "next_level", "cost": 1, "unlock_next": true},
|
||||
]
|
||||
|
||||
func buy_mock_euro_point() -> void:
|
||||
## Satire: pretend the user paid 1€.
|
||||
dressler_points += 1
|
||||
points_changed.emit(dressler_points)
|
||||
GameState.save_game()
|
||||
|
|
@ -27,6 +27,25 @@ func purchase(entry_id: String) -> bool:
|
|||
for entry in CATALOG:
|
||||
if str(entry["id"]) != entry_id:
|
||||
continue
|
||||
if bool(entry.get("actually_free", false)):
|
||||
Inventory.add_item(str(entry.get("gives", "")), 1)
|
||||
if str(entry.get("gives", "")) == "sunglasses":
|
||||
Achievements.unlock("cool_sunglasses")
|
||||
purchase_made.emit(entry_id)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("shop_joke_free"), 3.0)
|
||||
GameState.save_game()
|
||||
return true
|
||||
if bool(entry.get("joke_continue", false)):
|
||||
## „kostet 10 Punkte“ → Spaß, du darfst weiter
|
||||
var ui2 := get_node_or_null("/root/UIRoot")
|
||||
if ui2 and ui2.has_method("show_dialog"):
|
||||
ui2.show_dialog(Locale.tr_key("shop_joke_continue"), 4.0)
|
||||
GameState.set_flag("paywall_cleared", true)
|
||||
purchase_made.emit(entry_id)
|
||||
GameState.save_game()
|
||||
return true
|
||||
var cost := int(entry.get("cost", 0))
|
||||
if dressler_points < cost:
|
||||
return false
|
||||
|
|
@ -34,8 +53,8 @@ func purchase(entry_id: String) -> bool:
|
|||
var gives := str(entry.get("gives", ""))
|
||||
if gives != "":
|
||||
Inventory.add_item(gives, 1)
|
||||
if gives == "sunglasses":
|
||||
Achievements.unlock("cool_sunglasses")
|
||||
if bool(entry.get("unlock_next", false)):
|
||||
GameState.set_flag("paywall_cleared", true)
|
||||
points_changed.emit(dressler_points)
|
||||
purchase_made.emit(entry_id)
|
||||
GameState.save_game()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
extends Node3D
|
||||
class_name BillboardSprite
|
||||
## 2D sprite on a 3D plane that always faces the camera (Mario-tree style).
|
||||
## TODO: Swap placeholder textures for real pixel art.
|
||||
## Mario-tree billboard: upright Quad (perpendicular to ground), yaw-only toward camera.
|
||||
## PlaneMesh defaults to flat on XZ — that made sprites look like floor stickers.
|
||||
|
||||
@export var texture: Texture2D
|
||||
@export var pixel_size: float = 0.05
|
||||
|
|
@ -9,9 +9,14 @@ class_name BillboardSprite
|
|||
|
||||
@onready var _mesh_instance: MeshInstance3D = $MeshInstance3D
|
||||
|
||||
var _quad_height: float = 1.0
|
||||
|
||||
func _ready() -> void:
|
||||
_apply()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_face_camera_yaw_only()
|
||||
|
||||
func set_texture(tex: Texture2D) -> void:
|
||||
texture = tex
|
||||
_apply()
|
||||
|
|
@ -19,19 +24,41 @@ func set_texture(tex: Texture2D) -> void:
|
|||
func _apply() -> void:
|
||||
if _mesh_instance == null:
|
||||
return
|
||||
var plane := PlaneMesh.new()
|
||||
plane.size = Vector2(1.0, 1.0)
|
||||
_mesh_instance.mesh = plane
|
||||
# QuadMesh is vertical (XY), facing +Z — correct default for standing sprites.
|
||||
var quad := QuadMesh.new()
|
||||
var w := 1.0
|
||||
var h := 1.0
|
||||
if texture != null:
|
||||
w = float(texture.get_width()) * pixel_size
|
||||
h = float(texture.get_height()) * pixel_size
|
||||
quad.size = Vector2(w, h)
|
||||
_quad_height = h
|
||||
_mesh_instance.mesh = quad
|
||||
# Pivot at feet: lift mesh so bottom sits on this node's origin.
|
||||
_mesh_instance.position = Vector3(0.0, h * 0.5, 0.0)
|
||||
_mesh_instance.rotation = Vector3.ZERO
|
||||
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
# No engine billboard — we yaw the Node3D ourselves so orientation stays upright.
|
||||
mat.billboard_mode = BaseMaterial3D.BILLBOARD_DISABLED
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
|
||||
mat.alpha_scissor_threshold = 0.1
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED # visible from both sides
|
||||
mat.albedo_color = modulate_color
|
||||
if texture != null:
|
||||
mat.albedo_texture = texture
|
||||
var w := float(texture.get_width()) * pixel_size
|
||||
var h := float(texture.get_height()) * pixel_size
|
||||
plane.size = Vector2(w, h)
|
||||
_mesh_instance.material_override = mat
|
||||
|
||||
func _face_camera_yaw_only() -> void:
|
||||
var cam := get_viewport().get_camera_3d()
|
||||
if cam == null:
|
||||
return
|
||||
var to_cam := cam.global_position - global_position
|
||||
to_cam.y = 0.0
|
||||
if to_cam.length_squared() < 0.0001:
|
||||
return
|
||||
# QuadMesh faces +Z; look_at aims -Z at the target — aim -Z away from camera
|
||||
# so +Z faces the camera.
|
||||
look_at(global_position - to_cam, Vector3.UP)
|
||||
|
|
|
|||
40
scripts/combat/cheese_projectile.gd
Normal file
40
scripts/combat/cheese_projectile.gd
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
extends Area3D
|
||||
class_name CheeseProjectile
|
||||
## Moldy cheese shot at the player. Attack the cheese (not the seller) to destroy it.
|
||||
|
||||
var velocity: Vector3 = Vector3.ZERO
|
||||
var lifetime: float = 5.0
|
||||
var hp: int = 1
|
||||
|
||||
func _ready() -> void:
|
||||
collision_layer = 4
|
||||
collision_mask = 2
|
||||
monitoring = true
|
||||
body_entered.connect(_on_body)
|
||||
add_to_group("enemy")
|
||||
add_to_group("cheese_projectile")
|
||||
var bill := get_node_or_null("BillboardSprite")
|
||||
if bill and bill.has_method("set_texture"):
|
||||
var tex: Texture2D = load("res://assets/sprites/placeholders/cheese.png")
|
||||
if tex:
|
||||
bill.set_texture(tex)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
global_position += velocity * delta
|
||||
lifetime -= delta
|
||||
if lifetime <= 0.0:
|
||||
call_deferred("queue_free")
|
||||
|
||||
func take_hit(_amount: int = 1) -> void:
|
||||
hp -= 1
|
||||
AudioDirector.play_hit()
|
||||
if hp <= 0:
|
||||
var seller = get_meta("seller", null)
|
||||
if seller != null and is_instance_valid(seller) and seller.has_method("notify_cheese_destroyed"):
|
||||
seller.notify_cheese_destroyed()
|
||||
call_deferred("queue_free")
|
||||
|
||||
func _on_body(body: Node3D) -> void:
|
||||
if body.is_in_group("player"):
|
||||
GameState.die("death_cheese")
|
||||
call_deferred("queue_free")
|
||||
67
scripts/combat/cheese_seller.gd
Normal file
67
scripts/combat/cheese_seller.gd
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
extends Node3D
|
||||
class_name CheeseSeller
|
||||
## Places a cheese table and periodically launches moldy cheese at the player.
|
||||
## DESIGN: stab the cheese (projectile), not the seller — seller dies of grief when cheese destroyed.
|
||||
|
||||
@export var texture_path: String = "res://assets/sprites/placeholders/cheese.png"
|
||||
@export var fire_interval: float = 2.8
|
||||
@export var cheese_speed: float = 7.0
|
||||
|
||||
var _alive: bool = true
|
||||
var _timer: float = 0.0
|
||||
var _grief: int = 0
|
||||
|
||||
@onready var _billboard: BillboardSprite = $BillboardSprite
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("interactable")
|
||||
add_to_group("cheese_seller")
|
||||
_timer = randf() * fire_interval
|
||||
if _billboard:
|
||||
var tex: Texture2D = load(texture_path)
|
||||
if tex:
|
||||
_billboard.set_texture(tex)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not _alive:
|
||||
return
|
||||
_timer -= delta
|
||||
if _timer <= 0.0:
|
||||
_timer = fire_interval
|
||||
_fire()
|
||||
|
||||
func _fire() -> void:
|
||||
var player := get_tree().get_first_node_in_group("player")
|
||||
if player == null:
|
||||
return
|
||||
var proj_scene: PackedScene = load("res://scenes/prefabs/cheese_projectile.tscn")
|
||||
if proj_scene == null:
|
||||
return
|
||||
var proj: Node3D = proj_scene.instantiate()
|
||||
get_tree().current_scene.add_child(proj)
|
||||
proj.global_position = global_position + Vector3(0, 0.8, 0)
|
||||
var dir: Vector3 = (player.global_position + Vector3(0, 0.8, 0) - proj.global_position).normalized()
|
||||
proj.set("velocity", dir * cheese_speed)
|
||||
proj.add_to_group("cheese_projectile")
|
||||
proj.add_to_group("enemy")
|
||||
proj.set_meta("seller", self)
|
||||
|
||||
func notify_cheese_destroyed() -> void:
|
||||
_grief += 1
|
||||
if _grief >= 2:
|
||||
_alive = false
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_cheese"))
|
||||
queue_free()
|
||||
|
||||
func on_interact(_player: Node) -> void:
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_cheese"))
|
||||
|
||||
func take_hit(_amount: int = 1) -> void:
|
||||
## Hitting the seller does nothing useful (DESIGN).
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_cheese"))
|
||||
21
scripts/levels/catscene.gd
Normal file
21
scripts/levels/catscene.gd
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
extends Control
|
||||
## Catscene: open-world joke. Skipping resets the run.
|
||||
|
||||
@onready var label: Label = $Label
|
||||
@onready var skip_btn: Button = $SkipButton
|
||||
@onready var cont_btn: Button = $ContinueButton
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "catscene"
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
label.text = Locale.tr_key("dialog_catscene")
|
||||
skip_btn.text = "Skip Katzin / Catscene"
|
||||
cont_btn.text = Locale.tr_key("menu_continue")
|
||||
|
||||
func _on_continue_pressed() -> void:
|
||||
GameState.goto_scene("farm_path")
|
||||
|
||||
func _on_skip_pressed() -> void:
|
||||
label.text = Locale.tr_key("dialog_catscene_skip")
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
GameState.reset_to_intro()
|
||||
|
|
@ -1,17 +1,28 @@
|
|||
extends Control
|
||||
## Blinky death screen with cause text + skull every 10s.
|
||||
## TODO: Final flashy death art.
|
||||
## Blinky death screen + absurd revive (Nein=revive, Ja=quit).
|
||||
|
||||
@onready var title: Label = $Title
|
||||
@onready var cause: Label = $Cause
|
||||
@onready var cont: Button = $ContinueButton
|
||||
@onready var btn_revive: Button = $ReviveButton
|
||||
@onready var btn_no: Button = $NoButton
|
||||
@onready var btn_yes: Button = $YesButton
|
||||
@onready var hint: Label = $Hint
|
||||
@onready var skull: ColorRect = $Skull
|
||||
@onready var bg: ColorRect = $Bg
|
||||
|
||||
var _blink: float = 0.0
|
||||
var _skull_timer: float = 0.0
|
||||
var _colors: Array[Color] = [
|
||||
Color(0.05, 0.05, 0.05),
|
||||
Color(0.9, 0.1, 0.4),
|
||||
Color(0.1, 0.8, 0.3),
|
||||
Color(0.2, 0.3, 1.0),
|
||||
Color(1.0, 0.8, 0.1),
|
||||
]
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "death_screen"
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_refresh()
|
||||
Locale.locale_changed.connect(_refresh)
|
||||
skull.visible = false
|
||||
|
|
@ -19,11 +30,15 @@ func _ready() -> void:
|
|||
func _refresh() -> void:
|
||||
title.text = Locale.tr_key("death_title")
|
||||
cause.text = Locale.tr_key(GameState.last_death_cause_key)
|
||||
cont.text = Locale.tr_key("death_continue")
|
||||
btn_revive.text = Locale.tr_key("death_revive")
|
||||
btn_no.text = Locale.tr_key("death_no")
|
||||
btn_yes.text = Locale.tr_key("death_yes")
|
||||
hint.text = Locale.tr_key("death_no_hint") + "\n" + Locale.tr_key("death_yes_hint")
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_blink += delta
|
||||
modulate = Color(1, 0.4 + 0.6 * abs(sin(_blink * 6.0)), 0.4 + 0.6 * abs(sin(_blink * 4.0)))
|
||||
var idx := int(_blink) % _colors.size()
|
||||
bg.color = _colors[idx]
|
||||
_skull_timer += delta
|
||||
if _skull_timer >= 10.0:
|
||||
_skull_timer = 0.0
|
||||
|
|
@ -31,6 +46,13 @@ func _process(delta: float) -> void:
|
|||
await get_tree().create_timer(1.0).timeout
|
||||
skull.visible = false
|
||||
|
||||
func _on_continue_pressed() -> void:
|
||||
GameState.heal_full()
|
||||
GameState.goto_scene("title_screen")
|
||||
func _on_revive_pressed() -> void:
|
||||
GameState.revive_continue()
|
||||
|
||||
func _on_no_pressed() -> void:
|
||||
## Satire: Nein = trotzdem wiederbeleben
|
||||
GameState.revive_continue()
|
||||
|
||||
func _on_yes_pressed() -> void:
|
||||
## Satire: Ja = Spiel beenden → Title
|
||||
GameState.quit_to_title()
|
||||
|
|
|
|||
63
scripts/levels/dressler_boss.gd
Normal file
63
scripts/levels/dressler_boss.gd
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
extends Node3D
|
||||
## Dresslermeister: Augen öffnen, Mähdrescher hijacken, Apfel dreschen.
|
||||
|
||||
var _hijacked: int = 0
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "dressler_boss"
|
||||
GameState.heal_full()
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_dressler"))
|
||||
var dressler := get_node_or_null("Dressler")
|
||||
if dressler and dressler.has_signal("defeated"):
|
||||
dressler.defeated.connect(_on_dressler_defeated)
|
||||
# Convert mower hazards into hijackables
|
||||
for name in ["Mower1", "Mower2"]:
|
||||
var m := get_node_or_null(name)
|
||||
if m:
|
||||
_make_hijackable(m)
|
||||
|
||||
func _make_hijackable(mower: Node) -> void:
|
||||
mower.set_meta("hijacked", false)
|
||||
if mower.has_signal("body_entered"):
|
||||
# Keep death if not hijacked — wrap
|
||||
pass
|
||||
# Add interactable proxy as child Area isn't enough; use nearby attack
|
||||
mower.add_to_group("hijackable_mower")
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("interact") or Input.is_action_just_pressed("attack"):
|
||||
_try_hijack()
|
||||
|
||||
func _try_hijack() -> void:
|
||||
var player := get_tree().get_first_node_in_group("player")
|
||||
if player == null:
|
||||
return
|
||||
for mower in get_tree().get_nodes_in_group("hijackable_mower"):
|
||||
if mower is Node3D and player.global_position.distance_to(mower.global_position) <= 2.5:
|
||||
if bool(mower.get_meta("hijacked", false)):
|
||||
continue
|
||||
mower.set_meta("hijacked", true)
|
||||
_hijacked += 1
|
||||
# Disable kill: remove from monitoring / free hazard script effect
|
||||
if mower.has_method("set"):
|
||||
mower.set("monitoring", false)
|
||||
if mower is Area3D:
|
||||
(mower as Area3D).monitoring = false
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog("Fahrer rausgeworfen! Dresche den Apfel! (%d/2)" % _hijacked)
|
||||
# Bonus damage to dressler for each hijack
|
||||
var dressler := get_node_or_null("Dressler")
|
||||
if dressler and dressler.has_method("take_hit"):
|
||||
dressler.take_hit(3)
|
||||
return
|
||||
|
||||
func _on_dressler_defeated() -> void:
|
||||
Achievements.unlock("dressler_apples")
|
||||
Inventory.add_item("dressler_apple", 1)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_dressler_drop"), 4.0)
|
||||
GameState.set_flag("dressler_defeated", true)
|
||||
|
|
@ -1,8 +1,52 @@
|
|||
extends Node3D
|
||||
## End village with house-build stub.
|
||||
## Enddorf: Hausbau + Zahnionär. Kein Happy End.
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "end_screen"
|
||||
GameState.heal_full()
|
||||
if GameState.get_flag("house_built", false) or GameState.house_built:
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_end_angry"), 4.0)
|
||||
var house := get_node_or_null("HouseSpot")
|
||||
if house and house.has_signal("interacted"):
|
||||
house.interacted.connect(_on_house)
|
||||
var dentist := get_node_or_null("DentistHeist")
|
||||
if dentist and dentist.has_signal("interacted"):
|
||||
dentist.interacted.connect(_on_heist)
|
||||
if GameState.house_built or GameState.get_flag("house_built", false):
|
||||
_show_house()
|
||||
|
||||
func _on_house(_n: Node = null) -> void:
|
||||
GameState.house_built = true
|
||||
GameState.set_flag("house_built", true)
|
||||
_show_house()
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("build_house"))
|
||||
|
||||
func _show_house() -> void:
|
||||
var house := get_node_or_null("BuiltHouse")
|
||||
if house:
|
||||
house.visible = true
|
||||
return
|
||||
var scene: PackedScene = load("res://scenes/prefabs/npc.tscn")
|
||||
if scene == null:
|
||||
return
|
||||
var h: Node3D = scene.instantiate()
|
||||
h.name = "BuiltHouse"
|
||||
add_child(h)
|
||||
h.global_position = Vector3(3, 0.1, -2)
|
||||
h.set("texture_path", "res://assets/sprites/placeholders/house.png")
|
||||
h.set("dialog_key", "build_house")
|
||||
var bill = h.get_node_or_null("BillboardSprite")
|
||||
if bill and bill.has_method("set_texture"):
|
||||
var tex: Texture2D = load("res://assets/sprites/placeholders/house.png")
|
||||
if tex:
|
||||
bill.set_texture(tex)
|
||||
|
||||
func _on_heist(_n: Node = null) -> void:
|
||||
Inventory.add_item("tooth", 100)
|
||||
Achievements.unlock("tooth_millionaire")
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_toothionaire"), 4.0)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,81 @@
|
|||
extends Node3D
|
||||
## Level stub: farm
|
||||
## TODO: Replace placeholders with final pixel art / audio.
|
||||
## Bayerischer Hof: 3 Quests → Paywall-Joke → Exit zu Dressler. Urhahn-Schock.
|
||||
|
||||
var _fed_pigs: bool = false
|
||||
var _milked: bool = false
|
||||
var _chickens: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "farm"
|
||||
GameState.heal_full()
|
||||
_fed_pigs = bool(GameState.get_flag("pigs_fed", false))
|
||||
_milked = bool(GameState.get_flag("cows_milked", false))
|
||||
_chickens = bool(GameState.get_flag("chickens_taken", false))
|
||||
var mood := str(GameState.get_flag("farmer_mood", "direct"))
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
if mood == "detour":
|
||||
ui.show_dialog(Locale.tr_key("dialog_farmer_detour"), 4.0)
|
||||
else:
|
||||
ui.show_dialog(Locale.tr_key("dialog_farmer_direct"), 3.5)
|
||||
await get_tree().create_timer(1.2).timeout
|
||||
ui.show_dialog(Locale.tr_key("dialog_farmer_quest"), 4.0)
|
||||
var urhahn := get_node_or_null("Urhahn")
|
||||
if urhahn and urhahn.has_signal("defeated"):
|
||||
urhahn.defeated.connect(_on_urhahn_down)
|
||||
var hazard := get_node_or_null("RoosterHazard")
|
||||
if hazard and hazard.has_signal("body_entered"):
|
||||
# Extra: shock before death for flavor when contacting hazard
|
||||
if not hazard.body_entered.is_connected(_on_rooster_touch):
|
||||
hazard.body_entered.connect(_on_rooster_touch)
|
||||
var exit := get_node_or_null("Exit")
|
||||
if exit:
|
||||
exit.set("require_flag", "paywall_cleared")
|
||||
var ticket := get_node_or_null("TicketVendor")
|
||||
if ticket and ticket.has_signal("interacted"):
|
||||
ticket.interacted.connect(_on_ticket)
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if GameState.get_flag("pigs_fed", false) and not _fed_pigs:
|
||||
complete_pig()
|
||||
if GameState.get_flag("cows_milked", false) and not _milked:
|
||||
complete_cow()
|
||||
if GameState.get_flag("chickens_taken", false) and not _chickens:
|
||||
complete_chicken()
|
||||
|
||||
func complete_pig() -> void:
|
||||
_fed_pigs = true
|
||||
_check_quests()
|
||||
|
||||
func complete_cow() -> void:
|
||||
_milked = true
|
||||
_check_quests()
|
||||
|
||||
func complete_chicken() -> void:
|
||||
_chickens = true
|
||||
if not Inventory.has_item("lava_chicken"):
|
||||
Inventory.add_item("lava_chicken", 1)
|
||||
_check_quests()
|
||||
|
||||
func _check_quests() -> void:
|
||||
if not (_fed_pigs and _milked and _chickens):
|
||||
return
|
||||
GameState.set_flag("farm_quests_done", true)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_paywall"), 4.0)
|
||||
Shop.purchase("continue_joke")
|
||||
GameState.set_flag("paywall_cleared", true)
|
||||
|
||||
func _on_urhahn_down() -> void:
|
||||
GameState.set_flag("urhahn_defeated", true)
|
||||
|
||||
func _on_rooster_touch(body: Node3D) -> void:
|
||||
if body.is_in_group("player") and not GameState.get_flag("urhahn_defeated", false):
|
||||
GameState.apply_shock(10.0)
|
||||
|
||||
func _on_ticket(_n: Node = null) -> void:
|
||||
Inventory.add_item("death_ticket", 1)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("item_death_ticket"))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,52 @@
|
|||
extends Node3D
|
||||
## Level stub: farm_path
|
||||
## TODO: Replace placeholders with final pixel art / audio.
|
||||
## Enger Weg + Gabelung. Rechts = Magier (Knockback / Planet destroy).
|
||||
|
||||
var _went_right: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "farm_path"
|
||||
GameState.heal_full()
|
||||
_went_right = bool(GameState.get_flag("went_right_first", false))
|
||||
var left := get_node_or_null("LeftExit")
|
||||
if left:
|
||||
left.set("use_goto_next", false)
|
||||
left.set("next_scene_id", "")
|
||||
if left.has_signal("body_entered") and not left.body_entered.is_connected(_on_left_body):
|
||||
left.body_entered.connect(_on_left_body)
|
||||
var right := get_node_or_null("RightZone")
|
||||
if right:
|
||||
right.set("use_goto_next", false)
|
||||
right.set("next_scene_id", "")
|
||||
if right.has_signal("body_entered") and not right.body_entered.is_connected(_on_right_body):
|
||||
right.body_entered.connect(_on_right_body)
|
||||
|
||||
func _on_left_body(body: Node3D) -> void:
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
if _went_right or GameState.get_flag("went_right_first", false):
|
||||
GameState.set_flag("farmer_mood", "detour")
|
||||
else:
|
||||
GameState.set_flag("farmer_mood", "direct")
|
||||
GameState.set_flag("planet_destroyed", false)
|
||||
GameState.goto_scene("farm")
|
||||
|
||||
func _on_right_body(body: Node3D) -> void:
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
_went_right = true
|
||||
GameState.set_flag("went_right_first", true)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
# Knockback toward left path
|
||||
if body is CharacterBody3D:
|
||||
body.global_position += Vector3(-4, 0, 0)
|
||||
if GameState.get_flag("called_black_mage", false):
|
||||
GameState.set_flag("mage_angered", true)
|
||||
GameState.set_flag("planet_destroyed", true)
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_mage_angry"), 4.0)
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
GameState.goto_scene("moon")
|
||||
else:
|
||||
GameState.set_flag("called_black_mage", true)
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_mage_push"))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,91 @@
|
|||
extends Node3D
|
||||
## Hongkong / Glitschkönig final boss.
|
||||
## Glitschkönig: Reveal, Quirxels, Split-Phasen, Flucht des letzten Pixels.
|
||||
|
||||
enum Phase { FULL, SPLIT, FLEE, DONE }
|
||||
|
||||
var _phase: Phase = Phase.FULL
|
||||
var _revealed: bool = false
|
||||
var _splits_left: int = 2
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "final_boss"
|
||||
GameState.heal_full()
|
||||
_revealed = bool(GameState.get_flag("glitsch_revealed", false))
|
||||
if not _revealed:
|
||||
_do_reveal()
|
||||
var boss := get_node_or_null("Hongkong")
|
||||
if boss:
|
||||
boss.set("max_hp", 12)
|
||||
if boss.has_signal("hit"):
|
||||
boss.hit.connect(_on_boss_hit)
|
||||
if boss.has_signal("defeated"):
|
||||
boss.defeated.connect(_on_boss_defeated)
|
||||
var t := Timer.new()
|
||||
t.wait_time = 2.8
|
||||
t.autostart = true
|
||||
t.timeout.connect(_spawn_quirxel)
|
||||
add_child(t)
|
||||
|
||||
func _do_reveal() -> void:
|
||||
GameState.set_flag("glitsch_revealed", true)
|
||||
Inventory.add_item("banana", 1)
|
||||
Inventory.add_item("wartchen", 1)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_hongkong_boss"))
|
||||
ui.show_dialog(Locale.tr_key("dialog_reveal"), 5.5)
|
||||
|
||||
func _on_boss_hit(remaining: int) -> void:
|
||||
if _phase == Phase.FULL and remaining <= 6 and _splits_left > 0:
|
||||
_phase = Phase.SPLIT
|
||||
_splits_left -= 1
|
||||
_spawn_split_clone()
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog("Er teilt sich wie Minecraft-Schleim!", 2.5)
|
||||
elif remaining <= 2 and _phase != Phase.FLEE and _phase != Phase.DONE:
|
||||
_phase = Phase.FLEE
|
||||
var boss := get_node_or_null("Hongkong")
|
||||
if boss is Node3D:
|
||||
boss.global_position += Vector3(randf_range(-4, 4), 0, -3)
|
||||
|
||||
func _spawn_split_clone() -> void:
|
||||
var scene: PackedScene = load("res://scenes/prefabs/enemy.tscn")
|
||||
if scene == null:
|
||||
return
|
||||
var c: Node3D = scene.instantiate()
|
||||
c.name = "GlitschSplit"
|
||||
add_child(c)
|
||||
c.global_position = Vector3(randf_range(-3, 3), 0.1, -2)
|
||||
c.set("max_hp", 4)
|
||||
c.set("texture_path", "res://assets/sprites/placeholders/hongkong.png")
|
||||
var bill = c.get_node_or_null("BillboardSprite")
|
||||
if bill and bill.has_method("set_texture"):
|
||||
var tex: Texture2D = load("res://assets/sprites/placeholders/hongkong.png")
|
||||
if tex:
|
||||
bill.set_texture(tex)
|
||||
|
||||
func _spawn_quirxel() -> void:
|
||||
if _phase == Phase.DONE or GameState.get_flag("hongkong_defeated", false):
|
||||
return
|
||||
var scene: PackedScene = load("res://scenes/prefabs/hazard.tscn")
|
||||
if scene == null:
|
||||
return
|
||||
var h: Node3D = scene.instantiate()
|
||||
add_child(h)
|
||||
h.global_position = Vector3(randf_range(-6, 6), 0.1, randf_range(-6, 0))
|
||||
h.set("death_cause_key", "death_quirxel")
|
||||
await get_tree().create_timer(5.0).timeout
|
||||
if is_instance_valid(h):
|
||||
h.queue_free()
|
||||
|
||||
func _on_boss_defeated() -> void:
|
||||
_phase = Phase.DONE
|
||||
Achievements.unlock("beat_hongkong")
|
||||
GameState.set_flag("hongkong_defeated", true)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_hongkong_boss"), 3.0)
|
||||
await get_tree().create_timer(2.5).timeout
|
||||
ui.show_dialog(Locale.tr_key("dialog_bear_end"), 5.0)
|
||||
await get_tree().create_timer(3.5).timeout
|
||||
GameState.goto_scene("end_screen")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
extends Control
|
||||
## Intro: desk + console + explosion.
|
||||
## TODO: Real cutscene art and epic narrator VO.
|
||||
## Intro: Tisch, Konsole, Sprecher, Explosion.
|
||||
|
||||
@onready var label: Label = $Label
|
||||
|
||||
|
|
@ -8,6 +7,7 @@ var _step: int = 0
|
|||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "intro_cutscene"
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
AudioDirector.stop_music()
|
||||
_show_step()
|
||||
|
||||
|
|
@ -20,10 +20,10 @@ func _show_step() -> void:
|
|||
0:
|
||||
label.text = Locale.tr_key("dialog_intro_1")
|
||||
1:
|
||||
label.text = Locale.tr_key("dialog_intro_2")
|
||||
2:
|
||||
AudioDirector.play_explosion()
|
||||
label.text = "BOOM"
|
||||
2:
|
||||
label.text = Locale.tr_key("dialog_intro_2")
|
||||
_:
|
||||
GameState.goto_scene("village")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,53 @@
|
|||
extends Node3D
|
||||
## Level stub: massage_studio
|
||||
## TODO: Replace placeholders with final pixel art / audio.
|
||||
## Massage: Schlange massieren → Bear-Save; oder Secret-Milch.
|
||||
|
||||
@onready var choice_ui: CanvasLayer = $ChoiceUI
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "massage_studio"
|
||||
GameState.heal_full()
|
||||
choice_ui.visible = false
|
||||
var cow := get_node_or_null("CowMasseur")
|
||||
if cow and cow.has_signal("interacted"):
|
||||
cow.interacted.connect(_on_cow)
|
||||
if cow:
|
||||
cow.require_item_id = ""
|
||||
cow.achievement_id = ""
|
||||
cow.quest_flag = ""
|
||||
cow.consume_required = false
|
||||
cow.give_item_id = ""
|
||||
|
||||
func _on_cow(_npc: Node = null) -> void:
|
||||
choice_ui.visible = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
$ChoiceUI/Panel/Margin/VBox/Label.text = Locale.tr_key("dialog_cow")
|
||||
$ChoiceUI/Panel/Margin/VBox/MassageBtn.text = Locale.tr_key("choice_massage_now")
|
||||
$ChoiceUI/Panel/Margin/VBox/DrinkBtn.text = Locale.tr_key("choice_drink_first")
|
||||
|
||||
func _close_choices() -> void:
|
||||
choice_ui.visible = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
func _on_massage_pressed() -> void:
|
||||
_close_choices()
|
||||
if not Inventory.has_item("snake"):
|
||||
var ui0 := get_node_or_null("/root/UIRoot")
|
||||
if ui0 and ui0.has_method("show_dialog"):
|
||||
ui0.show_dialog(Locale.tr_key("dialog_villager_snake"))
|
||||
return
|
||||
Inventory.remove_item("snake", 1)
|
||||
GameState.set_flag("snake_massaged", true)
|
||||
Achievements.unlock("snake_massage")
|
||||
if not GameState.get_flag("bear_saved", false):
|
||||
Inventory.add_item("tooth", 1)
|
||||
GameState.set_flag("bear_saved", true)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_cow_massage") + "\n" + Locale.tr_key("dialog_bear_saved"), 5.0)
|
||||
|
||||
func _on_drink_pressed() -> void:
|
||||
_close_choices()
|
||||
Inventory.add_item("milk", 1)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_cow_drink"), 4.0)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,129 @@
|
|||
extends Node3D
|
||||
## Level stub: meadow
|
||||
## TODO: Replace placeholders with final pixel art / audio.
|
||||
## Wiese: Eisbär retten (Schlangen holen → Massage ODER hier abgeben), Glitzos.
|
||||
|
||||
var _glitzo_timer: float = 0.0
|
||||
var _bear_saved: bool = false
|
||||
var _attack_cd: float = 0.0
|
||||
var _first_glitzo_done: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "meadow"
|
||||
GameState.heal_full()
|
||||
_bear_saved = bool(GameState.get_flag("bear_saved", false))
|
||||
_first_glitzo_done = Achievements.is_unlocked("first_glitzo")
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
if _bear_saved:
|
||||
ui.show_dialog(Locale.tr_key("dialog_bear_saved"))
|
||||
else:
|
||||
ui.show_dialog(Locale.tr_key("dialog_bear_help"))
|
||||
var bear := get_node_or_null("Bear")
|
||||
if bear and bear.has_signal("interacted"):
|
||||
bear.interacted.connect(_on_bear)
|
||||
var snake := get_node_or_null("SnakePickup")
|
||||
if snake and snake.has_signal("interacted"):
|
||||
snake.interacted.connect(_on_snake_pickup)
|
||||
# Disable mower kill if snakes already bagged? Keep hazard until massage done.
|
||||
if GameState.get_flag("snake_massaged", false):
|
||||
_disable_mower()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_glitzo_timer += delta
|
||||
if _glitzo_timer >= 3.5:
|
||||
_glitzo_timer = 0.0
|
||||
_spawn_glitzo()
|
||||
_tick_glitzo_attacks(delta)
|
||||
_cull_glitzos_off_meadow()
|
||||
|
||||
func _tick_glitzo_attacks(delta: float) -> void:
|
||||
_attack_cd = max(0.0, _attack_cd - delta)
|
||||
if _attack_cd > 0.0:
|
||||
return
|
||||
var player := get_tree().get_first_node_in_group("player")
|
||||
if player == null:
|
||||
return
|
||||
for child in get_children():
|
||||
if not child.is_in_group("enemy"):
|
||||
continue
|
||||
if not str(child.get("texture_path")).ends_with("glitzo.png") and not child.name.begins_with("Glitzo"):
|
||||
continue
|
||||
if child is Node3D and player.global_position.distance_to(child.global_position) < 1.6:
|
||||
_attack_cd = 1.15
|
||||
GameState.register_glitzo_hit()
|
||||
return
|
||||
|
||||
func _cull_glitzos_off_meadow() -> void:
|
||||
## DESIGN: leave the meadow → they shed pixels and die.
|
||||
for child in get_children():
|
||||
if child is Node3D and (child.name.begins_with("Glitzo") or str(child.get("texture_path")).ends_with("glitzo.png")):
|
||||
var p: Vector3 = child.global_position
|
||||
if abs(p.x) > 18.0 or abs(p.z) > 18.0:
|
||||
child.queue_free()
|
||||
|
||||
func _spawn_glitzo() -> void:
|
||||
var scene: PackedScene = load("res://scenes/prefabs/enemy.tscn")
|
||||
if scene == null:
|
||||
return
|
||||
var g: Node3D = scene.instantiate()
|
||||
g.name = "GlitzoSpawn_%d" % Time.get_ticks_msec()
|
||||
g.set("max_hp", 4)
|
||||
g.set("texture_path", "res://assets/sprites/placeholders/glitzo.png")
|
||||
if not _first_glitzo_done:
|
||||
g.set("achievement_on_defeat", "first_glitzo")
|
||||
add_child(g)
|
||||
g.global_position = Vector3(randf_range(-8, 8), 0.1, randf_range(-6, 2))
|
||||
var bill: Node = g.get_node_or_null("BillboardSprite")
|
||||
if bill and bill.has_method("set_texture"):
|
||||
var tex: Texture2D = load("res://assets/sprites/placeholders/glitzo.png")
|
||||
if tex:
|
||||
bill.set_texture(tex)
|
||||
if g.has_signal("defeated"):
|
||||
g.defeated.connect(_on_glitzo_killed)
|
||||
|
||||
func _on_glitzo_killed() -> void:
|
||||
_first_glitzo_done = true
|
||||
|
||||
func _on_bear(_npc: Node = null) -> void:
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if _bear_saved:
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_bear_saved"))
|
||||
return
|
||||
# Need snakes rescued via inventory + preferably massaged; tooth if massaged or handing snakes in after pickup
|
||||
if Inventory.has_item("snake") and GameState.get_flag("snake_massaged", false):
|
||||
_save_bear()
|
||||
return
|
||||
if Inventory.has_item("snake"):
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_villager_snake"))
|
||||
return
|
||||
# First talk: point at snakes / give hint, auto-pickup one for convenience
|
||||
Inventory.add_item("snake", 1)
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_snake_pain") + "\n" + Locale.tr_key("dialog_bear_help"))
|
||||
|
||||
func _on_snake_pickup(_npc: Node = null) -> void:
|
||||
if not Inventory.has_item("snake"):
|
||||
Inventory.add_item("snake", 1)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_snake_pain"))
|
||||
|
||||
func _save_bear() -> void:
|
||||
_bear_saved = true
|
||||
GameState.set_flag("bear_saved", true)
|
||||
if not Inventory.has_item("tooth"):
|
||||
Inventory.add_item("tooth", 1)
|
||||
_disable_mower()
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_bear_saved"), 4.5)
|
||||
|
||||
func _disable_mower() -> void:
|
||||
var mower := get_node_or_null("MowerHazard")
|
||||
if mower:
|
||||
mower.queue_free()
|
||||
|
||||
func notify_snake_massaged() -> void:
|
||||
if not _bear_saved:
|
||||
_save_bear()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,91 @@
|
|||
extends Node3D
|
||||
## Moon cheese level.
|
||||
## Mond: Käsekatapulte, Schwert, Sonnenbrille, Fake-Bowser Hammer-Fight.
|
||||
|
||||
@onready var hammer_ui: CanvasLayer = $HammerUI
|
||||
var _correct_hammer: int = 1
|
||||
var _hits_needed: int = 3
|
||||
var _hits: int = 0
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "moon"
|
||||
GameState.heal_full()
|
||||
Achievements.unlock("moon_cheese")
|
||||
hammer_ui.visible = false
|
||||
_spawn_cheese_sellers()
|
||||
var bowser := get_node_or_null("FakeBowser")
|
||||
if bowser and bowser.has_signal("interacted"):
|
||||
bowser.interacted.connect(_start_bowser_fight)
|
||||
var sword := get_node_or_null("SwordStone")
|
||||
if sword:
|
||||
sword.give_item_id = "moon_sword"
|
||||
sword.dialog_key = "dialog_cheese"
|
||||
var shades := get_node_or_null("MoonResearcher")
|
||||
if shades and shades.has_signal("interacted"):
|
||||
shades.interacted.connect(_on_shades)
|
||||
# Convert old CheeseSeller NPC if present into shooter
|
||||
var old := get_node_or_null("CheeseSeller")
|
||||
if old and not old.has_method("notify_cheese_destroyed"):
|
||||
old.queue_free()
|
||||
|
||||
func _spawn_cheese_sellers() -> void:
|
||||
var scene: PackedScene = load("res://scenes/prefabs/cheese_seller.tscn")
|
||||
if scene == null:
|
||||
return
|
||||
for i in range(4):
|
||||
var s: Node3D = scene.instantiate()
|
||||
add_child(s)
|
||||
s.global_position = Vector3(-6.0 + i * 4.0, 0.1, -2.0 - (i % 2) * 2.0)
|
||||
|
||||
func _on_shades(_n: Node = null) -> void:
|
||||
Shop.purchase("sunglasses")
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_sunglasses_free"))
|
||||
|
||||
func _start_bowser_fight(_n: Node = null) -> void:
|
||||
if GameState.get_flag("bowser_defeated", false):
|
||||
Inventory.add_item("wedding_egg", 1)
|
||||
return
|
||||
hammer_ui.visible = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_hits = 0
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_bowser"))
|
||||
_refresh_hammers()
|
||||
|
||||
func _refresh_hammers() -> void:
|
||||
_correct_hammer = randi_range(1, 3)
|
||||
$HammerUI/Panel/Margin/VBox/Label.text = Locale.tr_key("dialog_bowser") + " [%d/%d]" % [_hits, _hits_needed]
|
||||
$HammerUI/Panel/Margin/VBox/H1.text = Locale.tr_key("choice_hammer_1")
|
||||
$HammerUI/Panel/Margin/VBox/H2.text = Locale.tr_key("choice_hammer_2")
|
||||
$HammerUI/Panel/Margin/VBox/H3.text = Locale.tr_key("choice_hammer_3")
|
||||
|
||||
func _pick(hammer: int) -> void:
|
||||
if hammer == _correct_hammer:
|
||||
_hits += 1
|
||||
AudioDirector.play_hit()
|
||||
if _hits >= _hits_needed:
|
||||
hammer_ui.visible = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
GameState.set_flag("bowser_defeated", true)
|
||||
Achievements.unlock("beat_bowser")
|
||||
Inventory.add_item("chicken", 1)
|
||||
Inventory.add_item("wedding_egg", 1)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog("Hochzeitsei! Im Inventar benutzen = Whiff zum Endboss.", 4.0)
|
||||
else:
|
||||
_refresh_hammers()
|
||||
else:
|
||||
hammer_ui.visible = false
|
||||
GameState.die("death_bowser")
|
||||
|
||||
func _on_h1() -> void:
|
||||
_pick(1)
|
||||
|
||||
func _on_h2() -> void:
|
||||
_pick(2)
|
||||
|
||||
func _on_h3() -> void:
|
||||
_pick(3)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
extends Node3D
|
||||
## Schuhmacher Reif boss under the table.
|
||||
## Schuhmacher Reif. Sieg → Tonne Tiramisu + Fett-Debuff.
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "shoe_boss"
|
||||
|
|
@ -7,3 +7,14 @@ func _ready() -> void:
|
|||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_reif"))
|
||||
var reif := get_node_or_null("Reif")
|
||||
if reif and reif.has_signal("defeated"):
|
||||
reif.defeated.connect(_on_reif_defeated)
|
||||
|
||||
func _on_reif_defeated() -> void:
|
||||
Inventory.add_item("tiramisu", 1000)
|
||||
GameState.apply_fat(600.0)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_tiramisu"), 5.0)
|
||||
GameState.set_flag("reif_defeated", true)
|
||||
|
|
|
|||
|
|
@ -1,26 +1,35 @@
|
|||
extends Control
|
||||
## Title screen with placeholder rain + loud music.
|
||||
## TODO: Real title art (Mario/Sonic/Burger rain, disturbed human).
|
||||
## Title: pixel rain, Pay2Win, loud music, Burger-Cham, first-run language.
|
||||
|
||||
@onready var subtitle: Label = $Subtitle
|
||||
@onready var bubble: Label = $Bubble
|
||||
@onready var start_btn: Button = $StartButton
|
||||
@onready var rain: Node2D = $Rain
|
||||
@onready var lang_panel: PanelContainer = $LangPanel
|
||||
@onready var music_hint: Label = $MusicHint
|
||||
|
||||
var _rain_sprites: Array[Sprite2D] = []
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "title_screen"
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
AudioDirector.burger_cham_enabled = true
|
||||
AudioDirector.play_title_music()
|
||||
_refresh()
|
||||
Locale.locale_changed.connect(_refresh)
|
||||
_spawn_rain()
|
||||
lang_panel.visible = Settings.needs_language_picker()
|
||||
start_btn.visible = not lang_panel.visible
|
||||
|
||||
func _refresh() -> void:
|
||||
subtitle.text = Locale.tr_key("title_subtitle")
|
||||
subtitle.text = Locale.tr_key("title_pay2win")
|
||||
bubble.text = Locale.tr_key("title_bubble")
|
||||
start_btn.text = Locale.tr_key("menu_start")
|
||||
music_hint.text = Locale.tr_key("title_music_hint")
|
||||
$LangPanel/Margin/VBox/Title.text = Locale.tr_key("lang_pick_title")
|
||||
$LangPanel/Margin/VBox/BtnDE.text = Locale.tr_key("lang_de")
|
||||
$LangPanel/Margin/VBox/BtnEN.text = Locale.tr_key("lang_en")
|
||||
$LangPanel/Margin/VBox/BtnBY.text = Locale.tr_key("lang_by")
|
||||
|
||||
func _spawn_rain() -> void:
|
||||
var paths = [
|
||||
|
|
@ -28,7 +37,7 @@ func _spawn_rain() -> void:
|
|||
"res://assets/sprites/placeholders/sonic.png",
|
||||
"res://assets/sprites/placeholders/burger.png",
|
||||
]
|
||||
for i in range(24):
|
||||
for i in range(28):
|
||||
var s := Sprite2D.new()
|
||||
s.texture = load(paths[i % paths.size()])
|
||||
s.position = Vector2(randf() * 1280.0, randf() * -720.0)
|
||||
|
|
@ -39,11 +48,29 @@ func _spawn_rain() -> void:
|
|||
|
||||
func _process(delta: float) -> void:
|
||||
for s in _rain_sprites:
|
||||
s.position.y += (80.0 + s.get_index() * 3.0) * delta
|
||||
s.position.y += (90.0 + s.get_index() * 2.5) * delta
|
||||
if s.position.y > 760.0:
|
||||
s.position.y = -40.0
|
||||
s.position.x = randf() * 1280.0
|
||||
|
||||
func _pick_lang(lang: String) -> void:
|
||||
Settings.set_language(lang)
|
||||
lang_panel.visible = false
|
||||
start_btn.visible = true
|
||||
_refresh()
|
||||
|
||||
func _on_lang_de() -> void:
|
||||
_pick_lang("de")
|
||||
|
||||
func _on_lang_en() -> void:
|
||||
_pick_lang("en")
|
||||
|
||||
func _on_lang_by() -> void:
|
||||
_pick_lang("by")
|
||||
|
||||
func _on_start_pressed() -> void:
|
||||
if Settings.needs_language_picker():
|
||||
lang_panel.visible = true
|
||||
return
|
||||
AudioDirector.burger_cham_enabled = false
|
||||
GameState.start_new_game()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,34 @@
|
|||
extends Node3D
|
||||
## Level stub: village
|
||||
## TODO: Replace placeholders with final pixel art / audio.
|
||||
## Kaputtes AC-Dorf: fliehende NPCs, Tisch-Knopf → Reif.
|
||||
|
||||
var _flee_dirs: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.current_scene_id = "village"
|
||||
GameState.heal_full()
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("dialog_villager_run"))
|
||||
# Strip wrong early toothionaire / snake gift
|
||||
var dentist := get_node_or_null("Dentist")
|
||||
if dentist:
|
||||
dentist.give_item_id = ""
|
||||
dentist.achievement_id = ""
|
||||
dentist.quest_flag = ""
|
||||
dentist.dialog_key = "dialog_villager_snake"
|
||||
var fan := get_node_or_null("NPC_HongkongFan")
|
||||
if fan:
|
||||
fan.give_item_id = ""
|
||||
fan.dialog_key = "dialog_villager_run"
|
||||
for n in get_children():
|
||||
if n.is_in_group("interactable") and n.name != "TableButton" and n is Node3D:
|
||||
_flee_dirs[n] = Vector3(randf_range(-1, 1), 0, randf_range(-1, 1)).normalized()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for n in _flee_dirs.keys():
|
||||
if not is_instance_valid(n):
|
||||
continue
|
||||
var node: Node3D = n
|
||||
node.global_position += _flee_dirs[n] * 1.8 * delta
|
||||
if node.global_position.length() > 22.0:
|
||||
_flee_dirs[n] = -_flee_dirs[n]
|
||||
|
|
|
|||
|
|
@ -1,38 +1,92 @@
|
|||
extends CharacterBody3D
|
||||
class_name PlayerController
|
||||
## Simple top-down-ish 3D movement for billboard levels.
|
||||
## Third-person controller: camera follows player, movement is camera-relative.
|
||||
## Billboard sprites face the camera (Mario-tree style).
|
||||
|
||||
@export var move_speed: float = 6.0
|
||||
@export var attack_range: float = 1.8
|
||||
@export var attack_damage: int = 1
|
||||
@export var mouse_sensitivity: float = 0.0025
|
||||
@export var min_pitch_deg: float = -35.0
|
||||
@export var max_pitch_deg: float = 55.0
|
||||
@export var spring_length: float = 5.5
|
||||
@export var camera_height: float = 1.4
|
||||
|
||||
signal interacted
|
||||
signal attacked
|
||||
|
||||
@onready var _billboard: BillboardSprite = $BillboardSprite
|
||||
@onready var _yaw_pivot: Node3D = $CameraYaw
|
||||
@onready var _spring: SpringArm3D = $CameraYaw/SpringArm3D
|
||||
@onready var _camera: Camera3D = $CameraYaw/SpringArm3D/Camera3D
|
||||
|
||||
var _locked: bool = false
|
||||
var _pitch: float = deg_to_rad(-18.0)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
if _spring:
|
||||
_spring.spring_length = spring_length
|
||||
_spring.collision_mask = 1
|
||||
_spring.position = Vector3(0.0, camera_height, 0.0)
|
||||
if _camera:
|
||||
_camera.current = true
|
||||
_camera.fov = 70.0
|
||||
_apply_pitch()
|
||||
_set_mouse_captured(true)
|
||||
if _billboard and _billboard.texture == null:
|
||||
var tex: Texture2D = load("res://assets/sprites/placeholders/player.png")
|
||||
if tex:
|
||||
_billboard.set_texture(tex)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_set_mouse_captured(false)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if _locked or get_tree().paused:
|
||||
return
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
var motion := event as InputEventMouseMotion
|
||||
_yaw_pivot.rotate_y(-motion.relative.x * mouse_sensitivity)
|
||||
_pitch = clampf(
|
||||
_pitch - motion.relative.y * mouse_sensitivity,
|
||||
deg_to_rad(min_pitch_deg),
|
||||
deg_to_rad(max_pitch_deg)
|
||||
)
|
||||
_apply_pitch()
|
||||
elif event is InputEventMouseButton and event.pressed:
|
||||
_set_mouse_captured(true)
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if _locked or get_tree().paused:
|
||||
velocity = Vector3.ZERO
|
||||
return
|
||||
|
||||
var input := Vector2(
|
||||
Input.get_axis("move_left", "move_right"),
|
||||
Input.get_axis("move_up", "move_down")
|
||||
)
|
||||
var dir := Vector3(input.x, 0.0, input.y)
|
||||
var basis_yaw := _yaw_pivot.global_transform.basis
|
||||
var forward := -basis_yaw.z
|
||||
forward.y = 0.0
|
||||
forward = forward.normalized()
|
||||
var right := basis_yaw.x
|
||||
right.y = 0.0
|
||||
right = right.normalized()
|
||||
var dir := (right * input.x) + (forward * -input.y)
|
||||
if dir.length() > 1.0:
|
||||
dir = dir.normalized()
|
||||
velocity = dir * move_speed
|
||||
var speed := move_speed
|
||||
if GameState.is_fat():
|
||||
speed *= 0.15
|
||||
elif GameState.is_glitched_slow():
|
||||
speed *= 0.55
|
||||
if GameState.is_shocked():
|
||||
velocity = Vector3.ZERO
|
||||
return
|
||||
velocity = dir * speed
|
||||
move_and_slide()
|
||||
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
_try_interact()
|
||||
if Input.is_action_just_pressed("attack"):
|
||||
|
|
@ -40,6 +94,28 @@ func _physics_process(_delta: float) -> void:
|
|||
|
||||
func lock_input(locked: bool) -> void:
|
||||
_locked = locked
|
||||
if locked:
|
||||
_set_mouse_captured(false)
|
||||
elif not get_tree().paused:
|
||||
_set_mouse_captured(true)
|
||||
|
||||
func set_gameplay_focused(focused: bool) -> void:
|
||||
## Called by pause UI: release mouse in menus, recapture in gameplay.
|
||||
if focused and not _locked:
|
||||
_set_mouse_captured(true)
|
||||
else:
|
||||
_set_mouse_captured(false)
|
||||
|
||||
func _apply_pitch() -> void:
|
||||
if _spring == null:
|
||||
return
|
||||
_spring.rotation.x = _pitch
|
||||
|
||||
func _set_mouse_captured(captured: bool) -> void:
|
||||
if captured:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
else:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
func _try_interact() -> void:
|
||||
interacted.emit()
|
||||
|
|
@ -52,7 +128,14 @@ func _try_interact() -> void:
|
|||
func _try_attack() -> void:
|
||||
attacked.emit()
|
||||
AudioDirector.play_hit()
|
||||
var dmg := attack_damage
|
||||
if Inventory.has_item("tooth"):
|
||||
dmg = max(dmg, 2)
|
||||
if Inventory.has_item("moon_sword"):
|
||||
dmg = max(dmg, 99)
|
||||
if Inventory.has_item("wartchen"):
|
||||
dmg = max(dmg, 3)
|
||||
for body in get_tree().get_nodes_in_group("enemy"):
|
||||
if body is Node3D and global_position.distance_to(body.global_position) <= attack_range:
|
||||
if body.has_method("take_hit"):
|
||||
body.take_hit(attack_damage)
|
||||
body.take_hit(dmg)
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ def player_scene() -> str:
|
|||
radius = 0.35
|
||||
height = 1.0
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_spring"]
|
||||
radius = 0.2
|
||||
|
||||
[node name="Player" type="CharacterBody3D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
|
|
@ -106,7 +109,20 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
|||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
|
||||
[node name="CameraYaw" type="Node3D" parent="."]
|
||||
|
||||
[node name="SpringArm3D" type="SpringArm3D" parent="CameraYaw"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4, 0)
|
||||
spring_length = 5.5
|
||||
collision_mask = 1
|
||||
margin = 0.2
|
||||
shape = SubResource("SphereShape3D_spring")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraYaw/SpringArm3D"]
|
||||
current = true
|
||||
fov = 70.0
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -120,7 +136,7 @@ def entity_scene(name: str, script: str, extras: str = "") -> str:
|
|||
script = ExtResource("1")
|
||||
{extras}
|
||||
[node name="BillboardSprite" parent="." instance=ExtResource("2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -373,11 +389,6 @@ script = ExtResource("1")
|
|||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 8, 0)
|
||||
shadow_enabled = false
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 12, 12)
|
||||
current = true
|
||||
fov = 50.0
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
collision_layer = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ extends CanvasLayer
|
|||
@onready var music_slider: HSlider = $PausePanel/Margin/VBox/MusicSlider
|
||||
@onready var lang_option: OptionButton = $PausePanel/Margin/VBox/LangOption
|
||||
@onready var hp_label: Label = $HUD/HpLabel
|
||||
@onready var fat_stim: ColorRect = $FatStim
|
||||
@onready var fat_label: Label = $FatStim/FatLabel
|
||||
|
||||
var _dialog_timer: float = 0.0
|
||||
var _fat_blink: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
|
@ -35,6 +38,7 @@ func _ready() -> void:
|
|||
Inventory.inventory_changed.connect(_refresh_inventory)
|
||||
Shop.points_changed.connect(_refresh_points)
|
||||
GameState.hp_changed.connect(_on_hp)
|
||||
GameState.status_changed.connect(func(): _on_hp(GameState.hp, GameState.max_hp))
|
||||
_on_hp(GameState.hp, GameState.max_hp)
|
||||
_refresh_inventory()
|
||||
_refresh_points(Shop.dressler_points)
|
||||
|
|
@ -44,6 +48,17 @@ func _process(delta: float) -> void:
|
|||
_dialog_timer -= delta
|
||||
if _dialog_timer <= 0.0:
|
||||
dialog_panel.visible = false
|
||||
_update_fat_stim(delta)
|
||||
|
||||
func _update_fat_stim(delta: float) -> void:
|
||||
if fat_stim == null:
|
||||
return
|
||||
var fat := GameState.is_fat()
|
||||
fat_stim.visible = fat and GameState.current_scene_id not in ["title_screen", "death_screen", "intro_cutscene", "catscene"]
|
||||
if fat:
|
||||
_fat_blink += delta
|
||||
fat_label.text = Locale.tr_key("dialog_tiramisu")
|
||||
fat_stim.color = Color(0.05, 0.05, 0.08, 0.35 + 0.25 * abs(sin(_fat_blink * 3.0)))
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("pause_menu"):
|
||||
|
|
@ -51,14 +66,20 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
|
||||
func toggle_pause() -> void:
|
||||
if GameState.current_scene_id in ["title_screen", "death_screen", "intro_cutscene"]:
|
||||
if GameState.current_scene_id in ["title_screen", "death_screen", "intro_cutscene", "catscene"]:
|
||||
return
|
||||
pause_panel.visible = not pause_panel.visible
|
||||
get_tree().paused = pause_panel.visible
|
||||
_sync_player_focus(not pause_panel.visible)
|
||||
if pause_panel.visible:
|
||||
_refresh_texts()
|
||||
_refresh_inventory()
|
||||
|
||||
func _sync_player_focus(focused: bool) -> void:
|
||||
for node in get_tree().get_nodes_in_group("player"):
|
||||
if node.has_method("set_gameplay_focused"):
|
||||
node.set_gameplay_focused(focused)
|
||||
|
||||
func show_dialog(text: String, duration: float = 3.5) -> void:
|
||||
dialog_label.text = text
|
||||
dialog_panel.visible = true
|
||||
|
|
@ -84,8 +105,8 @@ func _refresh_texts() -> void:
|
|||
$PausePanel/Margin/VBox/BuyPointButton.text = Locale.tr_key("menu_buy_point")
|
||||
$PausePanel/Margin/VBox/BuySunglassesButton.text = Locale.tr_key("shop_sunglasses")
|
||||
$PausePanel/Margin/VBox/BuyTicketButton.text = Locale.tr_key("shop_ticket")
|
||||
$PausePanel/Margin/VBox/BuyMilkButton.text = Locale.tr_key("shop_milk")
|
||||
$PausePanel/Margin/VBox/BuyTiramisuButton.text = Locale.tr_key("shop_tiramisu")
|
||||
$PausePanel/Margin/VBox/BuyMilkButton.text = Locale.tr_key("shop_continue_joke")
|
||||
$PausePanel/Margin/VBox/BuyTiramisuButton.text = Locale.tr_key("shop_next_level")
|
||||
$PausePanel/Margin/VBox/SfxLabel.text = Locale.tr_key("menu_sfx")
|
||||
$PausePanel/Margin/VBox/MusicLabel.text = Locale.tr_key("menu_music")
|
||||
$PausePanel/Margin/VBox/LangLabel.text = Locale.tr_key("menu_language")
|
||||
|
|
@ -105,7 +126,14 @@ func _refresh_inventory() -> void:
|
|||
inventory_list.set_item_metadata(inventory_list.item_count - 1, id)
|
||||
|
||||
func _on_hp(hp: int, max_hp: int) -> void:
|
||||
hp_label.text = "HP %d/%d" % [hp, max_hp]
|
||||
var extra := ""
|
||||
if GameState.is_fat():
|
||||
extra += " | " + Locale.tr_key("status_fat", [str(int(GameState.fat_time_left))])
|
||||
if GameState.is_shocked():
|
||||
extra += " | " + Locale.tr_key("status_shock")
|
||||
if GameState.is_glitched_slow():
|
||||
extra += " | " + Locale.tr_key("status_glitch")
|
||||
hp_label.text = "HP %d/%d%s" % [hp, max_hp, extra]
|
||||
|
||||
func _on_achievement(_id: String, title: String) -> void:
|
||||
toast_label.text = Locale.tr_key("toast_achievement", [title])
|
||||
|
|
@ -122,6 +150,7 @@ func _on_burger_cham() -> void:
|
|||
func _on_resume_pressed() -> void:
|
||||
pause_panel.visible = false
|
||||
get_tree().paused = false
|
||||
_sync_player_focus(true)
|
||||
|
||||
func _on_buy_point() -> void:
|
||||
Shop.purchase("buy_point")
|
||||
|
|
@ -133,10 +162,10 @@ func _on_buy_ticket() -> void:
|
|||
Shop.purchase("death_ticket")
|
||||
|
||||
func _on_buy_milk() -> void:
|
||||
Shop.purchase("milk")
|
||||
Shop.purchase("continue_joke")
|
||||
|
||||
func _on_buy_tiramisu() -> void:
|
||||
Shop.purchase("tiramisu")
|
||||
Shop.purchase("next_level")
|
||||
|
||||
func _on_sfx_changed(value: float) -> void:
|
||||
Settings.set_sfx_volume(value)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue