RPG Plus: prologue, village and forest, trainer and seer, sample chest page and slime route, test suite

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cthangoverandClaude Opus 5.5 committed 2026-10-24 10:01:08 +02:00
commit 4c5454d061
36 files changed
+1153

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
* text=auto eol=lf
# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf
# Binaries: never converted, never diffed as text.
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
*.svg text
*.ogg binary
*.wav binary
*.mp3 binary
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary
*.dll binary
*.so binary
*.dylib binary
*.pdb binary
*.exe binary
*.zip binary
*.pck binary
*.moc3 binary
*.res binary
*.scn binary
+37
View File
@@ -0,0 +1,37 @@
# Godot 4.x
.godot/
.idea/
sdk/
bin/
obj/
#ai
.omo/
.opencode/
gen/
obj/
bin/
*.tres~
*.tscn~
# GDScript LSP
.godot/
# Export
*.exe
*.dmg
*.apk
*.aab
*.pck
*.zip
*.log
# OS
.DS_Store
Thumbs.db
# Editor
*.import
*.godot.uid
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = The story of the RPG Plus sample, built on top of the RPG demo rather than beside it: a novel-style prologue at a crossroads with a class choice and a first charisma check, then the whole rpg_demo village and forest without a single edit — a trainer and a fortune teller are added by this mod's own map events file, a training yard scene, gear at the merchant's (a shop patch), a wolf with stats (a character patch). Only data and scenarios.
+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = La historia del ejemplo RPG Plus, construida encima de la demo RPG y no a su lado: un prólogo estilo novela en un cruce con elección de clase y una primera tirada de carisma, luego toda la aldea y el bosque de rpg_demo sin tocar nada — un maestro y una adivina añadidos con el propio archivo de eventos de mapa del mod, una escena del patio de entrenamiento, equipo en la tienda (un parche de la tienda), un lobo con estadísticas (un parche de personaje). Solo datos y escenarios.
+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = RPG Plus サンプルの物語。RPG デモの横ではなく上に作られている:十字路でのノベル風プロローグで職業を選び、最初のカリスマ判定。その後は rpg_demo の村と森をそのまま——師匠と占い師はこの MOD 自身のマップイベントファイルで追加、稽古場のシーン、商人の装備(店のパッチ)、ステータス付きの狼(キャラクターのパッチ)。データとシナリオのみ。
+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = RPG Plus 샘플의 이야기. RPG 데모 옆이 아니라 그 위에 만들었습니다: 갈림길의 노벨식 프롤로그에서 직업을 고르고 첫 매력 판정을 하고, 이어서 rpg_demo의 마을과 숲 전체를 한 줄도 고치지 않고 — 스승과 점쟁이는 이 모드의 자체 맵 이벤트 파일로 추가, 수련장 장면, 상인의 장비(상점 패치), 능력치를 가진 늑대(캐릭터 패치). 데이터와 시나리오만.
+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = Сюжет образца RPG Plus, построенный поверх RPG-демо, а не рядом с ним: пролог-новелла на перекрёстке с выбором класса и первой проверкой харизмы, затем вся деревня и лес rpg_demo без единой правки — наставник и гадалка добавлены собственным файлом событий карты, сцена тренировочной площадки, снаряжение у торговца (патч лавки), волк со статами (патч персонажа). Только данные и сценарии.
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = The crossroads
rpg_plus/prologue/line1 = The signpost honestly points three ways at once. None of them looks worth the walk.
rpg_plus/prologue/line2 = Below lies the village by the Grey Forest. They say it needs someone who is not afraid of wolves. Or is, but quietly. Twenty-five coins clink in your purse — all that is left of your old life.
rpg_plus/prologue/ask = Who were you before you came here?
rpg_plus/prologue/opt_warrior = A soldier. A sword is simple.
rpg_plus/prologue/opt_mage = A mage's apprentice. Fire listens to me.
rpg_plus/prologue/opt_rogue = A wanderer. You can strike a deal with anyone.
rpg_plus/prologue/warrior = The wooden sword on your back is all the barracks let you keep. That, and the habit of striking first.
rpg_plus/prologue/mage = The staff is warm, the crystal hums. Your teacher said fire obeys the clever. He never got round to the rest.
rpg_plus/prologue/rogue = A dagger up the sleeve, a smile at the ready. Doors open for the smile. The ones that don't, open for the dagger.
rpg_plus/prologue/seer = A wanderer in a lilac cloak sits by the signpost. She looks at you as if she already knew how it ends, and finds it boring.
rpg_plus/prologue/seer_ask = What will you say to her?
rpg_plus/prologue/opt_ask_way = Politely ask the way [Charisma 10]
rpg_plus/prologue/opt_pass = Walk past in silence
rpg_plus/prologue/gift = “Polite. Fancy that.” She tosses you a clover charm. “Keep it. Luck loves those who ask. Not for long, but it does.”
rpg_plus/prologue/snub = “You'll find it yourself,” she turns away. “Everyone does. Some even alive.”
rpg_plus/prologue/path = The path runs down to the village. The mist parts reluctantly, as if it knew where you were going.
rpg_plus/prologue/mage_hint = The staff gives a quiet crackle of sparks. Wolves, they say, dislike fire. The staff seems to dislike wolves.
rpg_plus/prologue/sheet_hint = Your sheet is the book button on the top bar: stats, level, equipment. Gear is put on from the bag.
rpg_plus/yard/intro = “Well,” the master folds his arms. “Show me. Just don't kill yourself, I'm not answering for you.”
rpg_plus/yard/ask = What shall we do?
rpg_plus/yard/opt_drill = Drill your strikes (+20 exp, once)
rpg_plus/yard/opt_log = Lift the log above your head [Strength 12]
rpg_plus/yard/opt_back = Go back to the village
rpg_plus/yard/drill_done = “Not bad. A hundred more of those and you'll pass for a fighter. From a distance.”
rpg_plus/yard/drilled = “That's enough for today. Muscles grow while you sleep. Go to sleep.”
rpg_plus/yard/log_ok = “Ha! You lifted it.” The master tosses you a vial. “Strength tonic. Don't thank me. And don't drink it on an empty stomach.”
rpg_plus/yard/log_again = “Again? Show-off. No more tonic. There's respect. A little.”
rpg_plus/yard/log_fail = “The log won. It happens. Grow a bit — or buy a strength tonic from the merchant. They say it helps. The merchant says.”
rpg_plus/trainer/hello = “New here? Show me your hands. Aha... I can work with that. If I really try.”
rpg_plus/trainer/veteran = “You've toughened up. Level three. Soon you'll start teaching me. Don't.”
rpg_plus/trainer/opt_yard = Go to the training yard
rpg_plus/trainer/opt_later = Not now
rpg_plus/trainer/bye = “Come back when your hands start itching. If the wolf eats you, don't bother.”
rpg_plus/seer/hello = “Want to know what awaits you? Give me your hand. Not that one — the one you don't steal with.”
rpg_plus/seer/fortune = “The wolf at the cave fears fire more than steel. And strike where it has no shield. That part is free. The rest isn't.”
rpg_plus/seer/charm = “And find yourself a charm. Luck does not cling to empty hands. Nor to full ones for long, mind.”
rpg_plus/seer/fog = “Mist... I see nothing. Either you have no future or the stars are cross today. Come tomorrow. If there is one.”
rpg_plus/chest/closed = The chest is not locked. Its owner is either trusting or keeps only hopes in it.
rpg_plus/chest/coins = Ten coins. The owner was trusting. Was.
rpg_plus/chest/empty = Empty. The trust ran out with the coins.
rpg_plus/slime/pet = A tame slime. Someone feeds it by hand. The hands are still whole.
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = El cruce
rpg_plus/prologue/line1 = El poste señala con honradez tres caminos a la vez. Ninguno apetece.
rpg_plus/prologue/line2 = Abajo está la aldea junto al Bosque Gris. Dicen que necesita a alguien que no tema a los lobos. O que los tema, pero en silencio. En la bolsa tintinean veinticinco monedas: todo lo que queda de tu vida anterior.
rpg_plus/prologue/ask = ¿Quién eras antes de llegar aquí?
rpg_plus/prologue/opt_warrior = Un soldado. Una espada es sencilla.
rpg_plus/prologue/opt_mage = Aprendiz de mago. El fuego me escucha.
rpg_plus/prologue/opt_rogue = Un vagabundo. Con cualquiera se llega a un trato.
rpg_plus/prologue/warrior = La espada de madera a la espalda es todo lo que te dejó el cuartel. Eso y la costumbre de golpear primero.
rpg_plus/prologue/mage = El bastón está tibio y el cristal zumba. Tu maestro decía que el fuego obedece a los listos. Del resto no llegó a hablar.
rpg_plus/prologue/rogue = Una daga en la manga y una sonrisa a punto. Las puertas se abren con la sonrisa. Las que no, con la daga.
rpg_plus/prologue/seer = Junto al poste se sienta una caminante de capa lila. Te mira como si ya supiera cómo acaba todo, y le aburriera.
rpg_plus/prologue/seer_ask = ¿Qué le dirás?
rpg_plus/prologue/opt_ask_way = Preguntar el camino con cortesía [Carisma 10]
rpg_plus/prologue/opt_pass = Pasar de largo en silencio
rpg_plus/prologue/gift = —Educado. Mira tú. —Te lanza un amuleto de trébol—. Quédatelo. La suerte quiere a quien pregunta. Poco tiempo, pero quiere.
rpg_plus/prologue/snub = —Ya lo encontrarás —se da la vuelta—. Todos lo encuentran. Algunos, incluso vivos.
rpg_plus/prologue/path = El sendero baja hacia la aldea. La niebla se abre a regañadientes, como si supiera adónde vas.
rpg_plus/prologue/mage_hint = El bastón chasquea una chispa. Dicen que a los lobos no les gusta el fuego. Al bastón, al parecer, no le gustan los lobos.
rpg_plus/prologue/sheet_hint = Tu hoja es el botón del libro en la barra superior: estadísticas, nivel, equipo. El equipo se pone desde la bolsa.
rpg_plus/yard/intro = —Bueno —el maestro se cruza de brazos—. Enséñame. Solo no te mates, que no respondo por ti.
rpg_plus/yard/ask = ¿Qué hacemos?
rpg_plus/yard/opt_drill = Practicar golpes (+20 de experiencia, una vez)
rpg_plus/yard/opt_log = Levantar el tronco sobre la cabeza [Fuerza 12]
rpg_plus/yard/opt_back = Volver a la aldea
rpg_plus/yard/drill_done = —No está mal. Cien más como esos y pasarás por luchador. De lejos.
rpg_plus/yard/drilled = —Basta por hoy. Los músculos crecen mientras duermes. Vete a dormir.
rpg_plus/yard/log_ok = —¡Ja! Lo levantaste. —El maestro te lanza un frasco—. Tónico de fuerza. No me des las gracias. Y no lo bebas en ayunas.
rpg_plus/yard/log_again = —¿Otra vez? Fanfarrón. No hay más tónico. Hay respeto. Un poco.
rpg_plus/yard/log_fail = —Ganó el tronco. Pasa. Crece un poco… o cómprale un tónico de fuerza al mercader. Dicen que ayuda. Lo dice el mercader.
rpg_plus/trainer/hello = —¿Nuevo? Enséñame las manos. Ajá... con esto se puede trabajar. Si uno se empeña.
rpg_plus/trainer/veteran = —Te has curtido. Nivel tres. Pronto querrás enseñarme a mí. Ni se te ocurra.
rpg_plus/trainer/opt_yard = Ir al patio de entrenamiento
rpg_plus/trainer/opt_later = Ahora no
rpg_plus/trainer/bye = —Vuelve cuando te piquen las manos. Si te come el lobo, no hace falta.
rpg_plus/seer/hello = —¿Quieres saber qué te espera? Dame la mano. Esa no: la que no usas para robar.
rpg_plus/seer/fortune = —El lobo de la cueva teme más al fuego que al acero. Y golpea donde no tenga escudo. Esto es gratis. Lo demás, no.
rpg_plus/seer/charm = —Y búscate un amuleto. La suerte no se agarra a manos vacías. A las llenas tampoco por mucho tiempo.
rpg_plus/seer/fog = —Niebla... No veo nada. O no tienes futuro, o las estrellas están de malas. Vuelve mañana. Si hay mañana.
rpg_plus/chest/closed = El cofre no está cerrado. Su dueño o es confiado o solo guarda esperanzas en él.
rpg_plus/chest/coins = Diez monedas. El dueño era confiado. Era.
rpg_plus/chest/empty = Vacío. La confianza se acabó con las monedas.
rpg_plus/slime/pet = Un slime domesticado. Alguien le da de comer en la mano. Las manos siguen enteras.
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = 十字路
rpg_plus/prologue/line1 = 道標は正直に三方向を同時に指している。どれにも行きたくない。
rpg_plus/prologue/line2 = 下には灰色の森のそばの村。狼を恐れない者が要るという。恐れていても、黙っていればいい。財布で鳴る硬貨二十五枚——前の人生の残りのすべて。
rpg_plus/prologue/ask = ここに来る前、お前は何者だった?
rpg_plus/prologue/opt_warrior = 兵士だ。剣は単純だ。
rpg_plus/prologue/opt_mage = 魔法使いの弟子だ。炎は言うことを聞く。
rpg_plus/prologue/opt_rogue = 流れ者だ。誰とでも話はつけられる。
rpg_plus/prologue/warrior = 背中の木の剣が兵舎に残してもらえたすべて。それと先に殴る癖。
rpg_plus/prologue/mage = 杖は温かく、水晶が唸る。師は、炎は賢い者に従うと言った。残りの者については言いそびれた。
rpg_plus/prologue/rogue = 袖に短剣、笑顔はいつでも。扉は笑顔で開く。開かない扉は短剣で。
rpg_plus/prologue/seer = 道標のそばに薄紫の外套の旅人が座っている。結末をもう知っていて、先に退屈しているような目つきだ。
rpg_plus/prologue/seer_ask = 彼女に何と言う?
rpg_plus/prologue/opt_ask_way = 丁寧に道を尋ねる[カリスマ 10]
rpg_plus/prologue/opt_pass = 黙って通り過ぎる
rpg_plus/prologue/gift = 「礼儀正しいね。珍しい」彼女はクローバーのお守りを放ってよこす。「持ってな。運は尋ねる者を好む。長続きはしないけどね」
rpg_plus/prologue/snub = 「自分で見つけな」彼女はそっぽを向く。「みんな見つける。生きたまま見つける者もいる」
rpg_plus/prologue/path = 小道は村へ下っていく。霧はしぶしぶ晴れる。どこへ行くのか知っているかのように。
rpg_plus/prologue/mage_hint = 杖が小さく火花を弾く。狼は火が嫌いだという。杖のほうも狼が嫌いらしい。
rpg_plus/prologue/sheet_hint = 自分のシートは上のバーの本のボタン:ステータス、レベル、装備。装備はかばんから身に着ける。
rpg_plus/yard/intro = 「さて」師匠は腕を組む。「見せてみろ。死ぬなよ、責任は取らん」
rpg_plus/yard/ask = 何をする?
rpg_plus/yard/opt_drill = 打ち込み稽古(経験値 +20、一度だけ)
rpg_plus/yard/opt_log = 丸太を頭上に持ち上げる[筋力 12]
rpg_plus/yard/opt_back = 村へ戻る
rpg_plus/yard/drill_done = 「悪くない。あと百回やれば戦士に見えるさ。遠目にはな」
rpg_plus/yard/drilled = 「今日はここまで。筋肉は寝ている間に育つ。寝ろ」
rpg_plus/yard/log_ok = 「はっ!持ち上げたな」師匠が小瓶を放る。「力の強壮剤だ。礼はいらん。空きっ腹で飲むなよ」
rpg_plus/yard/log_again = 「また?見栄っ張りめ。強壮剤はもうない。敬意ならある。少しな」
rpg_plus/yard/log_fail = 「丸太の勝ちだ。よくある。もう少し育つか、商人から力の強壮剤を買え。効くらしい。商人が言うには」
rpg_plus/trainer/hello = 「新入りか?手を見せろ。ほう……鍛えられなくはない。本気でやればな」
rpg_plus/trainer/veteran = 「たくましくなったな。レベル三か。そのうち俺に教え始めるんだろう。やめろ」
rpg_plus/trainer/opt_yard = 稽古場へ行く
rpg_plus/trainer/opt_later = 今はいい
rpg_plus/trainer/bye = 「手がうずいたら来い。狼に食われたら来なくていい」
rpg_plus/seer/hello = 「この先を知りたいかい?手をお出し。そっちじゃない——盗みに使わないほうの手だよ」
rpg_plus/seer/fortune = 「洞窟の狼は鋼より火を恐れる。それと盾のないところを打ちな。ここまでは無料。残りは有料だよ」
rpg_plus/seer/charm = 「それとお守りを見つけな。運は空の手にはしがみつかない。満ちた手にも長くはね」
rpg_plus/seer/fog = 「霧……何も見えない。お前に未来がないか、今日の星の機嫌が悪いか。明日おいで。明日があればね」
rpg_plus/chest/closed = 宝箱に鍵はない。持ち主はお人好しか、中身が希望だけか。
rpg_plus/chest/coins = 硬貨が十枚。持ち主はお人好しだった。過去形で。
rpg_plus/chest/empty = 空っぽだ。信頼は硬貨と一緒に尽きた。
rpg_plus/slime/pet = 飼いならされたスライム。誰かが手から餌をやっている。手はまだ無事だ。
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = 갈림길
rpg_plus/prologue/line1 = 이정표는 정직하게 세 방향을 한꺼번에 가리킨다. 어디로도 가고 싶지 않다.
rpg_plus/prologue/line2 = 아래에는 회색 숲 곁의 마을이 있다. 늑대를 두려워하지 않는 누군가가 필요하단다. 아니면 두려워하되 조용히. 지갑에서 동전 스물다섯 닢이 짤랑인다 — 지난 삶에서 남은 전부.
rpg_plus/prologue/ask = 여기 오기 전, 너는 누구였지?
rpg_plus/prologue/opt_warrior = 병사. 검은 단순하지.
rpg_plus/prologue/opt_mage = 마법사의 제자. 불이 내 말을 듣지.
rpg_plus/prologue/opt_rogue = 떠돌이. 누구와도 말은 통하지.
rpg_plus/prologue/warrior = 등에 멘 나무 검이 병영이 남겨 준 전부다. 먼저 치는 버릇과 함께.
rpg_plus/prologue/mage = 지팡이는 따뜻하고 수정이 웅웅댄다. 스승은 불이 영리한 자의 말을 듣는다고 했다. 나머지에 대해서는 말할 틈이 없었다.
rpg_plus/prologue/rogue = 소매엔 단검, 미소는 언제든. 문은 미소에 열린다. 안 열리는 문은 단검에.
rpg_plus/prologue/seer = 이정표 옆에 연보랏빛 망토의 나그네가 앉아 있다. 결말을 이미 알고, 미리 지루해하는 눈빛이다.
rpg_plus/prologue/seer_ask = 그녀에게 뭐라고 할까?
rpg_plus/prologue/opt_ask_way = 정중하게 길을 묻는다 [매력 10]
rpg_plus/prologue/opt_pass = 말없이 지나친다
rpg_plus/prologue/gift = “예의 바르네. 별일이야.” 그녀가 클로버 부적을 던져 준다. “가져. 행운은 묻는 자를 좋아해. 오래는 아니지만.”
rpg_plus/prologue/snub = “알아서 찾게 될 거야.” 그녀가 고개를 돌린다. “다들 찾아. 어떤 이들은 살아서.”
rpg_plus/prologue/path = 오솔길이 마을로 내려간다. 안개가 마지못해 걷힌다. 네가 어디로 가는지 아는 것처럼.
rpg_plus/prologue/mage_hint = 지팡이가 조용히 불꽃을 튀긴다. 늑대는 불을 싫어한다지. 지팡이도 늑대를 싫어하는 모양이다.
rpg_plus/prologue/sheet_hint = 네 시트는 위쪽 막대의 책 버튼이야: 능력치, 레벨, 장비. 장비는 가방에서 착용해.
rpg_plus/yard/intro = “자,” 스승이 팔짱을 낀다. “보여 봐. 죽지만 마라. 난 책임 안 진다.”
rpg_plus/yard/ask = 뭘 할까?
rpg_plus/yard/opt_drill = 타격 훈련 (+20 경험치, 한 번)
rpg_plus/yard/opt_log = 통나무를 머리 위로 든다 [힘 12]
rpg_plus/yard/opt_back = 마을로 돌아간다
rpg_plus/yard/drill_done = “나쁘지 않군. 이런 걸 백 번만 더 하면 싸움꾼으로 보일 거다. 멀리서 보면.”
rpg_plus/yard/drilled = “오늘은 그만. 근육은 잘 때 자란다. 가서 자라.”
rpg_plus/yard/log_ok = “하! 들었군.” 스승이 작은 병을 던져 준다. “힘의 강장제다. 고맙단 말은 됐고. 빈속에 마시지 마라.”
rpg_plus/yard/log_again = “또? 허세는. 강장제는 더 없다. 존중은 있다. 조금.”
rpg_plus/yard/log_fail = “통나무가 이겼군. 그럴 수 있지. 좀 더 크든가, 상인한테 힘의 강장제를 사라. 효과 있다더라. 상인이 그러더라.”
rpg_plus/trainer/hello = “신참이냐? 손 좀 보자. 아하... 해볼 만은 하군. 아주 마음먹으면.”
rpg_plus/trainer/veteran = “단단해졌군. 3레벨이라. 곧 나를 가르치려 들겠지. 하지 마라.”
rpg_plus/trainer/opt_yard = 수련장으로 간다
rpg_plus/trainer/opt_later = 지금은 됐어
rpg_plus/trainer/bye = “손이 근질거리면 와라. 늑대한테 먹히면 안 와도 된다.”
rpg_plus/seer/hello = “무엇이 기다리는지 알고 싶어? 손을 줘. 그거 말고 — 도둑질 안 하는 손.”
rpg_plus/seer/fortune = “동굴의 늑대는 강철보다 불을 더 무서워해. 그리고 보호막이 없는 데를 쳐. 여기까진 공짜야. 나머진 아니고.”
rpg_plus/seer/charm = “그리고 부적 하나 구해. 행운은 빈손에 붙어 있지 않아. 가득 찬 손에도 오래는 아니고.”
rpg_plus/seer/fog = “안개... 아무것도 안 보여. 너한테 미래가 없거나, 오늘 별들이 심술이 났거나. 내일 와. 내일이 있다면.”
rpg_plus/chest/closed = 상자는 잠겨 있지 않다. 주인이 사람을 잘 믿거나, 안에 희망만 넣어 두었거나.
rpg_plus/chest/coins = 동전 열 닢. 주인은 사람을 잘 믿었다. 과거형이다.
rpg_plus/chest/empty = 비었다. 믿음은 동전과 함께 바닥났다.
rpg_plus/slime/pet = 길들인 슬라임. 누군가 손으로 먹이를 준다. 손은 아직 멀쩡하다.
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = Перекрёсток
rpg_plus/prologue/line1 = Указатель честно показывает в три стороны сразу. Идти не хочется ни в одну.
rpg_plus/prologue/line2 = Внизу — деревня у Серого леса. Говорят, там нужен кто-нибудь, кто не боится волков. Или боится, но молча. В кошельке звенят двадцать пять монет — всё, что осталось от прошлой жизни.
rpg_plus/prologue/ask = Кем ты был, пока не пришёл сюда?
rpg_plus/prologue/opt_warrior = Солдатом. Меч — это просто.
rpg_plus/prologue/opt_mage = Учеником мага. Огонь меня слушается.
rpg_plus/prologue/opt_rogue = Бродягой. Договориться можно с кем угодно.
rpg_plus/prologue/warrior = Деревянный меч за спиной — всё, что тебе оставили в казарме. Кроме привычки бить первым.
rpg_plus/prologue/mage = Посох тёплый, кристалл гудит. Учитель говорил, что огонь слушается умных. Про остальных он не успел рассказать.
rpg_plus/prologue/rogue = Кинжал в рукаве, улыбка наготове. Двери открываются от улыбки. Те, что не открываются, — от кинжала.
rpg_plus/prologue/seer = У указателя сидит странница в лиловом плаще. Смотрит так, будто уже знает, чем всё кончится, и ей заранее скучно.
rpg_plus/prologue/seer_ask = Что скажешь ей?
rpg_plus/prologue/opt_ask_way = Вежливо спросить дорогу [Харизма 10]
rpg_plus/prologue/opt_pass = Молча пройти мимо
rpg_plus/prologue/gift = — Вежливый. Надо же. — Она бросает тебе оберег из клевера. — Держи. Удача любит тех, кто спрашивает. Недолго, но любит.
rpg_plus/prologue/snub = — Найдёшь сам, — она отворачивается. — Все находят. Некоторые даже живыми.
rpg_plus/prologue/path = Тропа сбегает к деревне. Туман расступается неохотно — будто знает, куда ты идёшь.
rpg_plus/prologue/mage_hint = Посох тихо щёлкает искрой. Волки, говорят, огня не любят. Посох, похоже, не любит волков.
rpg_plus/prologue/sheet_hint = Твой лист — кнопка с книгой на панели сверху: статы, уровень, снаряжение. Надеть вещь можно из сумки.
rpg_plus/yard/intro = — Ну, — наставник скрещивает руки. — Показывай. Только не убейся: я за тебя не отвечаю.
rpg_plus/yard/ask = Чем займёмся?
rpg_plus/yard/opt_drill = Отработать удары (+20 опыта, один раз)
rpg_plus/yard/opt_log = Поднять бревно над головой [Сила 12]
rpg_plus/yard/opt_back = Вернуться в деревню
rpg_plus/yard/drill_done = — Неплохо. Ещё сотня таких — и сойдёшь за бойца. Издалека.
rpg_plus/yard/drilled = — На сегодня хватит. Мышцы растут, пока спишь. Иди спи.
rpg_plus/yard/log_ok = — Ха! Поднял. — Наставник бросает тебе склянку. — Тоник силы. Не благодари. И не пей натощак.
rpg_plus/yard/log_again = — Опять? Хвастун. Тоника больше нет. Есть уважение. Немного.
rpg_plus/yard/log_fail = — Бревно победило. Бывает. Подрасти — или купи у торговца тоник силы. Говорят, помогает. Торговец говорит.
rpg_plus/trainer/hello = — Новенький? Руки покажи. Ага... С этим можно работать. Если очень захотеть.
rpg_plus/trainer/veteran = — А ты окреп. Третий уровень. Скоро меня учить начнёшь. Не начинай.
rpg_plus/trainer/opt_yard = Пойти на тренировочную площадку
rpg_plus/trainer/opt_later = Не сейчас
rpg_plus/trainer/bye = — Приходи, когда руки зачешутся. Если волк загрызёт — можешь не приходить.
rpg_plus/seer/hello = — Хочешь знать, что тебя ждёт? Дай руку. Не эту — ту, которой не воруешь.
rpg_plus/seer/fortune = — Волк у пещеры боится огня больше, чем стали. И бей туда, где у него нет щита. Это бесплатно. Остальное — нет.
rpg_plus/seer/charm = — И найди себе оберег. Удача не держится за пустые руки. За полные, впрочем, тоже недолго.
rpg_plus/seer/fog = — Туман... Ничего не вижу. Либо у тебя нет будущего, либо звёзды сегодня злые. Приходи завтра. Если будет завтра.
rpg_plus/chest/closed = Сундук не заперт. Хозяин либо доверчив, либо хранит в нём только надежды.
rpg_plus/chest/coins = Десять монет. Хозяин был доверчив. Был.
rpg_plus/chest/empty = Пусто. Доверие кончилось вместе с монетами.
rpg_plus/slime/pet = Ручной слизень. Его кормят с руки. Руки пока целы.
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = Ngã ba
rpg_plus/prologue/line1 = Tấm biển thật thà chỉ cùng lúc ba hướng. Hướng nào cũng chẳng muốn đi.
rpg_plus/prologue/line2 = Phía dưới là ngôi làng bên Rừng Xám. Nghe nói ở đó cần một kẻ không sợ sói. Hoặc có sợ, nhưng im lặng. Trong túi leng keng hai mươi lăm đồng xu — tất cả những gì còn lại của kiếp trước.
rpg_plus/prologue/ask = Trước khi tới đây, ngươi là ai?
rpg_plus/prologue/opt_warrior = Một người lính. Kiếm thì đơn giản.
rpg_plus/prologue/opt_mage = Học trò pháp sư. Lửa nghe lời ta.
rpg_plus/prologue/opt_rogue = Kẻ lang thang. Thương lượng được với bất kỳ ai.
rpg_plus/prologue/warrior = Thanh kiếm gỗ sau lưng là tất cả những gì doanh trại để lại cho ngươi. Cùng với thói quen ra đòn trước.
rpg_plus/prologue/mage = Cây gậy ấm, viên pha lê ngân nga. Thầy bảo lửa nghe lời kẻ thông minh. Về những người còn lại, ông chưa kịp nói.
rpg_plus/prologue/rogue = Dao găm trong tay áo, nụ cười luôn sẵn. Cửa mở vì nụ cười. Cửa nào không mở thì có dao găm.
rpg_plus/prologue/seer = Bên tấm biển có một lữ khách khoác áo choàng tím nhạt. Cô nhìn ngươi như thể đã biết kết cục, và thấy chán từ trước.
rpg_plus/prologue/seer_ask = Ngươi sẽ nói gì với cô ấy?
rpg_plus/prologue/opt_ask_way = Lễ phép hỏi đường [Sức hút 10]
rpg_plus/prologue/opt_pass = Lặng lẽ đi qua
rpg_plus/prologue/gift = “Lễ phép. Lạ thật.” Cô ném cho ngươi một lá bùa cỏ bốn lá. “Giữ lấy. Vận may thích kẻ biết hỏi. Không lâu, nhưng thích.”
rpg_plus/prologue/snub = “Tự ngươi sẽ tìm ra,” cô quay đi. “Ai cũng tìm ra. Vài người còn sống sót nữa.”
rpg_plus/prologue/path = Con đường chạy xuống làng. Sương mù tản ra miễn cưỡng, như thể biết ngươi đang đi đâu.
rpg_plus/prologue/mage_hint = Cây gậy khẽ lách tách tia lửa. Nghe nói sói không ưa lửa. Có vẻ cây gậy cũng không ưa sói.
rpg_plus/prologue/sheet_hint = Bảng của ngươi là nút hình quyển sách trên thanh trên cùng: chỉ số, cấp, trang bị. Đồ được mặc từ trong túi.
rpg_plus/yard/intro = “Nào,” sư phụ khoanh tay. “Cho ta xem. Chỉ đừng tự giết mình, ta không chịu trách nhiệm đâu.”
rpg_plus/yard/ask = Làm gì đây?
rpg_plus/yard/opt_drill = Luyện đòn (+20 kinh nghiệm, một lần)
rpg_plus/yard/opt_log = Nâng khúc gỗ qua đầu [Sức mạnh 12]
rpg_plus/yard/opt_back = Quay về làng
rpg_plus/yard/drill_done = “Không tệ. Thêm trăm đòn như thế là trông giống chiến binh rồi. Từ xa.”
rpg_plus/yard/drilled = “Hôm nay thế là đủ. Cơ bắp lớn khi ngủ. Đi ngủ đi.”
rpg_plus/yard/log_ok = “Ha! Nâng được rồi.” Sư phụ ném cho ngươi một lọ. “Thuốc sức mạnh. Khỏi cảm ơn. Và đừng uống lúc bụng đói.”
rpg_plus/yard/log_again = “Lại nữa? Khoe khoang. Hết thuốc rồi. Còn chút nể phục. Chút thôi.”
rpg_plus/yard/log_fail = “Khúc gỗ thắng. Chuyện thường. Lớn thêm chút nữa — hoặc mua thuốc sức mạnh của thương nhân. Nghe nói có tác dụng. Thương nhân nói thế.”
rpg_plus/trainer/hello = “Người mới à? Đưa tay ta xem. À... cũng luyện được. Nếu thật cố gắng.”
rpg_plus/trainer/veteran = “Ngươi rắn rỏi hơn rồi. Cấp ba. Sắp tới định dạy lại ta à. Đừng.”
rpg_plus/trainer/opt_yard = Đến bãi tập
rpg_plus/trainer/opt_later = Để sau
rpg_plus/trainer/bye = “Khi nào tay ngứa ngáy thì quay lại. Bị sói ăn rồi thì khỏi.”
rpg_plus/seer/hello = “Muốn biết điều gì đang chờ ngươi? Đưa tay đây. Không phải tay đó — tay không dùng để trộm ấy.”
rpg_plus/seer/fortune = “Con sói ở hang sợ lửa hơn thép. Và hãy đánh vào chỗ nó không có khiên. Phần này miễn phí. Phần còn lại thì không.”
rpg_plus/seer/charm = “Và kiếm lấy một lá bùa. Vận may không bám vào bàn tay trống. Tay đầy cũng chẳng bám lâu.”
rpg_plus/seer/fog = “Sương mù... Ta chẳng thấy gì. Hoặc ngươi không có tương lai, hoặc hôm nay sao xấu tính. Mai quay lại. Nếu có ngày mai.”
rpg_plus/chest/closed = Rương không khóa. Chủ nhân hoặc cả tin, hoặc chỉ cất hy vọng trong đó.
rpg_plus/chest/coins = Mười đồng xu. Chủ nhân từng cả tin. Từng.
rpg_plus/chest/empty = Trống rỗng. Lòng tin cạn cùng với đồng xu.
rpg_plus/slime/pet = Một con slime thuần. Có người cho nó ăn bằng tay. Tay vẫn còn nguyên.
+42
View File
@@ -0,0 +1,42 @@
rpg_plus/prologue/title = 十字路口
rpg_plus/prologue/line1 = 路标老老实实地同时指向三个方向。哪一边都不想去。
rpg_plus/prologue/line2 = 下面是灰森林边的村子。听说那里需要一个不怕狼的人。或者怕,但别出声。钱袋里叮当响着二十五枚硬币——前半生剩下的全部。
rpg_plus/prologue/ask = 来这里之前,你是什么人?
rpg_plus/prologue/opt_warrior = 当兵的。剑很简单。
rpg_plus/prologue/opt_mage = 法师的学徒。火会听我的。
rpg_plus/prologue/opt_rogue = 流浪汉。跟谁都能谈成。
rpg_plus/prologue/warrior = 背上的木剑是军营留给你的全部。还有先动手的习惯。
rpg_plus/prologue/mage = 法杖温热,水晶嗡鸣。老师说过,火只听聪明人的话。至于其他人——他没来得及讲。
rpg_plus/prologue/rogue = 袖里藏刀,笑容随时奉上。门会为笑容打开。打不开的——就交给刀。
rpg_plus/prologue/seer = 路标旁坐着一位披淡紫斗篷的行者。她看你的样子,像是早知道结局,而且提前觉得无聊。
rpg_plus/prologue/seer_ask = 你要对她说什么?
rpg_plus/prologue/opt_ask_way = 客气地问路[魅力 10]
rpg_plus/prologue/opt_pass = 默默走过
rpg_plus/prologue/gift = “有礼貌。真稀奇。”她把一枚四叶草护符扔给你。“拿着。运气喜欢肯开口的人。喜欢得不久,但喜欢。”
rpg_plus/prologue/snub = “你自己会找到的,”她转过身去,“谁都找得到。有些人甚至是活着找到的。”
rpg_plus/prologue/path = 小路奔向村子。雾气不情不愿地散开,好像知道你要去哪儿。
rpg_plus/prologue/mage_hint = 法杖轻轻噼啪一声迸出火星。据说狼不喜欢火。看来法杖也不喜欢狼。
rpg_plus/prologue/sheet_hint = 你的角色面板是顶部栏上的书本按钮:属性、等级、装备。装备从背包里穿上。
rpg_plus/yard/intro = “好了,”师父抱起双臂,“露一手吧。别把自己弄死就行,我可不负责。”
rpg_plus/yard/ask = 做点什么?
rpg_plus/yard/opt_drill = 练习招式(+20 经验,仅一次)
rpg_plus/yard/opt_log = 把木头举过头顶[力量 12]
rpg_plus/yard/opt_back = 回村子
rpg_plus/yard/drill_done = “不赖。再练一百下,就能冒充战士了。远远地看的话。”
rpg_plus/yard/drilled = “今天够了。肌肉是睡觉时长的。去睡吧。”
rpg_plus/yard/log_ok = “哈!举起来了。”师父丢给你一个小瓶。“力量药剂。别道谢。也别空腹喝。”
rpg_plus/yard/log_again = “又来?爱显摆。药剂没了。有点尊重。一点点。”
rpg_plus/yard/log_fail = “木头赢了。常有的事。再长长——或者找商人买瓶力量药剂。听说有用。商人说的。”
rpg_plus/trainer/hello = “新来的?手伸出来。嗯……还能练。要是下狠心的话。”
rpg_plus/trainer/veteran = “你结实了。三级了。快要轮到你来教我了。别。”
rpg_plus/trainer/opt_yard = 去练武场
rpg_plus/trainer/opt_later = 现在不行
rpg_plus/trainer/bye = “手痒了再来。要是被狼吃了,就不用来了。”
rpg_plus/seer/hello = “想知道等着你的是什么?把手给我。不是这只——是你不用来偷东西的那只。”
rpg_plus/seer/fortune = “洞口的狼怕火胜过怕钢。还有,往它没护盾的地方打。这些免费。其余的不免费。”
rpg_plus/seer/charm = “再给自己找个护符。运气不会黏着空手。满手的也黏不久。”
rpg_plus/seer/fog = “雾……什么也看不见。要么你没有未来,要么今天星星心情不好。明天再来。如果有明天的话。”
rpg_plus/chest/closed = 箱子没上锁。主人要么很信任别人,要么里面只放着希望。
rpg_plus/chest/coins = 十枚硬币。主人曾经很信任别人。曾经。
rpg_plus/chest/empty = 空的。信任和硬币一起用完了。
rpg_plus/slime/pet = 一只驯服的史莱姆。有人用手喂它。手暂时还在。
+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = Câu chuyện của mẫu RPG Plus, xây trên bản demo RPG chứ không đặt cạnh nó: phần mở đầu kiểu tiểu thuyết ở ngã ba với lựa chọn lớp nhân vật và lần kiểm tra sức hút đầu tiên, rồi toàn bộ làng và rừng của rpg_demo không sửa một dòng — sư phụ và bà thầy bói được thêm bằng chính tệp sự kiện bản đồ của mod, cảnh bãi tập, trang bị ở cửa hàng (bản vá cửa hàng), con sói có chỉ số (bản vá nhân vật). Chỉ dữ liệu và kịch bản.
+2
View File
@@ -0,0 +1,2 @@
mod_rpg_plus_game_name = RPG Plus
mod_rpg_plus_game_desc = RPG Plus 示例的故事,建立在 RPG 演示之上而非之旁:十字路口的小说式序章,选择职业并进行第一次魅力检定;之后是 rpg_demo 的整个村庄和森林,一处未改——导师和占卜师由本模组自己的地图事件文件加入,练武场场景,商人处的装备(商店补丁),带属性的狼(角色补丁)。仅有数据和剧本。
+28
View File
@@ -0,0 +1,28 @@
{
"id": "rpg_plus_game",
"name": "RPG Plus",
"version": "1.0.0",
"author": "ct",
"description": "Content of the RPG Plus sample (collection rpg_plus), built on top of the RPG demo instead of beside it: a visual-novel prologue at a crossroads where the player picks the hero's class (warrior, mage, rogue: stats_base archetypes) and starting gear, and makes a first charisma check; then the whole rpg_demo village and forest, untouched, with a trainer and a fortune teller added through this mod's own map_events file, a training yard novel scene with exp and a strength check, the merchant selling the classic pack's gear (a shop patch), the wolf given stats (a character patch). Only data and scenarios, no C#.",
"category": "story",
"depends": [
"rpg_demo_game>=1",
"classic_rpg_pack>=1",
"stat_checks>=1",
"equipment>=1",
"battle_formulas>=1",
"rpg>=1",
"trading>=1",
"dialog_base>=1",
"characters_base>=1",
"items_base>=1"
],
"test_suites": [
{
"id": "play",
"activate": [
"--itest=rpg_plus"
]
}
]
}
+70
View File
@@ -0,0 +1,70 @@
{
"Events": [
{
"Id": "rpg_plus_trainer",
"X": 27,
"Y": 10,
"Sprite": "rpg_plus/trainer",
"Tile": -1,
"Dir": "Left",
"Trigger": "Action",
"Scenario": "rpg_plus/events/trainer",
"IsSolid": true
},
{
"Id": "rpg_plus_seer",
"X": 4,
"Y": 11,
"Sprite": "rpg_plus/seer",
"Tile": -1,
"Dir": "Down",
"Trigger": "Action",
"Scenario": "rpg_plus/events/seer",
"IsSolid": true
},
{
"Id": "rpg_plus_chest",
"X": 29,
"Y": 3,
"Tile": 14,
"Trigger": "Action",
"Commands": [
"ptext \"Сундук не заперт. Хозяин либо доверчив, либо хранит в нём только надежды.\" key=rpg_plus/chest/closed",
"action item.add id=money/coin count=10",
"ptext \"Десять монет. Хозяин был доверчив. Был.\" key=rpg_plus/chest/coins",
"flag rpg_plus_chest_open 1"
],
"Pages": [
{
"Condition": "rpg_plus_chest_open == 1",
"Tile": 15,
"Commands": [
"ptext \"Пусто. Доверие кончилось вместе с монетами.\" key=rpg_plus/chest/empty"
]
}
]
},
{
"Id": "rpg_plus_pet_slime",
"X": 6,
"Y": 15,
"Sprite": "village/slime",
"Dir": "Left",
"Trigger": "Action",
"Commands": [
"ptext \"Ручной слизень. Его кормят с руки. Руки пока целы.\" key=rpg_plus/slime/pet"
],
"Route": {
"Steps": [
"left",
"wait",
"right",
"right",
"wait",
"left"
],
"Every": 0.9
}
}
]
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+8
View File
@@ -0,0 +1,8 @@
{
"Items": [
{
"Id": "rpg_wolf",
"Stats": { "str": 5, "vit": 5, "agi": 7 }
}
]
}
+16
View File
@@ -0,0 +1,16 @@
{
"Items": [
{
"Id": "rpg_demo/village_merchant",
"Sells$add": [
{ "Item": "classic/iron_sword", "Price": 30 },
{ "Item": "classic/dagger", "Price": 18 },
{ "Item": "classic/oak_staff", "Price": 24 },
{ "Item": "classic/leather_armor", "Price": 20 },
{ "Item": "classic/robe", "Price": 16 },
{ "Item": "classic/silver_ring", "Price": 22 },
{ "Item": "classic/strength_tonic", "Price": 40 }
]
}
]
}
+15
View File
@@ -0,0 +1,15 @@
text "— Хочешь знать, что тебя ждёт? Дай руку. Не эту — ту, которой не воруешь." key=rpg_plus/seer/hello
check luk 14 who=rpg_hero else=fail -> :ok
:ok
text "— Волк у пещеры боится огня больше, чем стали. И бей туда, где у него нет щита. Это бесплатно. Остальное — нет." key=rpg_plus/seer/fortune
when "!equipped(rpg_hero, 'classic/clover_charm')" -> :charm
end
:charm
text "— И найди себе оберег. Удача не держится за пустые руки. За полные, впрочем, тоже недолго." key=rpg_plus/seer/charm
end
:fail
text "— Туман... Ничего не вижу. Либо у тебя нет будущего, либо звёзды сегодня злые. Приходи завтра. Если будет завтра." key=rpg_plus/seer/fog
end
@@ -0,0 +1,19 @@
text "— Новенький? Руки покажи. Ага... С этим можно работать. Если очень захотеть." key=rpg_plus/trainer/hello
when "level(rpg_hero) >= 3" -> :veteran
select ""
option "Пойти на тренировочную площадку" key=rpg_plus/trainer/opt_yard -> :yard
option "Не сейчас" key=rpg_plus/trainer/opt_later -> :bye
:veteran
text "— А ты окреп. Третий уровень. Скоро меня учить начнёшь. Не начинай." key=rpg_plus/trainer/veteran
select ""
option "Пойти на тренировочную площадку" key=rpg_plus/trainer/opt_yard -> :yard
option "Не сейчас" key=rpg_plus/trainer/opt_later -> :bye
:yard
switch_scene rpg_plus_training_yard
end
:bye
text "— Приходи, когда руки зачешутся. Если волк загрызёт — можешь не приходить." key=rpg_plus/trainer/bye
end
+64
View File
@@ -0,0 +1,64 @@
scene: rpg_plus_crossroads
priority: 10
is_one_run: true
save_allowed: true
---
# The prologue is a visual novel scene: a background, lines and choices. The choice gives the hero an
# archetype (stats_base: stat.archetype), starting gear (equipment: equip.put) and a first stat check
# (stat_checks: check, when) — the same mechanics then work on the tile map, in battles and in the sheet.
title "Перекрёсток" key=rpg_plus/prologue/title
ptext "Указатель честно показывает в три стороны сразу. Идти не хочется ни в одну." key=rpg_plus/prologue/line1
ptext "Внизу — деревня у Серого леса. Говорят, там нужен кто-нибудь, кто не боится волков. Или боится, но молча. В кошельке звенят двадцать пять монет — всё, что осталось от прошлой жизни." key=rpg_plus/prologue/line2
action item.add id=money/coin count=25
select "Кем ты был, пока не пришёл сюда?" key=rpg_plus/prologue/ask
option "Солдатом. Меч — это просто." key=rpg_plus/prologue/opt_warrior -> :warrior
option "Учеником мага. Огонь меня слушается." key=rpg_plus/prologue/opt_mage -> :mage
option "Бродягой. Договориться можно с кем угодно." key=rpg_plus/prologue/opt_rogue -> :rogue
:warrior
action stat.archetype who=rpg_hero id=classic/warrior
action equip.put who=rpg_hero item=classic/wood_sword give=1
ptext "Деревянный меч за спиной — всё, что тебе оставили в казарме. Кроме привычки бить первым." key=rpg_plus/prologue/warrior
goto -> :road
:mage
action stat.archetype who=rpg_hero id=classic/mage
action equip.put who=rpg_hero item=classic/oak_staff give=1
ptext "Посох тёплый, кристалл гудит. Учитель говорил, что огонь слушается умных. Про остальных он не успел рассказать." key=rpg_plus/prologue/mage
goto -> :road
:rogue
action stat.archetype who=rpg_hero id=classic/rogue
action equip.put who=rpg_hero item=classic/dagger give=1
ptext "Кинжал в рукаве, улыбка наготове. Двери открываются от улыбки. Те, что не открываются, — от кинжала." key=rpg_plus/prologue/rogue
:road
ptext "У указателя сидит странница в лиловом плаще. Смотрит так, будто уже знает, чем всё кончится, и ей заранее скучно." key=rpg_plus/prologue/seer
select "Что скажешь ей?" key=rpg_plus/prologue/seer_ask
option "Вежливо спросить дорогу [Харизма 10]" key=rpg_plus/prologue/opt_ask_way -> :ask_way
option "Молча пройти мимо" key=rpg_plus/prologue/opt_pass -> :leave
:ask_way
# A charisma check: d20 + charisma against 10. The roll is shown as one line, then the story branches.
check cha 10 who=rpg_hero else=snub -> :gift
:gift
text "— Вежливый. Надо же. — Она бросает тебе оберег из клевера. — Держи. Удача любит тех, кто спрашивает. Недолго, но любит." key=rpg_plus/prologue/gift
action item.add id=classic/clover_charm
goto -> :leave
:snub
text "— Найдёшь сам, — она отворачивается. — Все находят. Некоторые даже живыми." key=rpg_plus/prologue/snub
:leave
when "archetype(rpg_hero, 'classic/mage')" -> :mage_hint
ptext "Тропа сбегает к деревне. Туман расступается неохотно — будто знает, куда ты идёшь." key=rpg_plus/prologue/path
goto -> :go
:mage_hint
ptext "Посох тихо щёлкает искрой. Волки, говорят, огня не любят. Посох, похоже, не любит волков." key=rpg_plus/prologue/mage_hint
:go
ptext "Твой лист — кнопка с книгой на панели сверху: статы, уровень, снаряжение. Надеть вещь можно из сумки." key=rpg_plus/prologue/sheet_hint
switch_scene rpg_village
end
+47
View File
@@ -0,0 +1,47 @@
scene: rpg_plus_training_yard
priority: 10
save_allowed: true
---
# A visual novel scene reached from the tile map (the trainer's dialog switches to it) and left by
# switch_scene back to the map: the hero comes back to the cell he left. Exp, a strength check and a
# reward that is given only once (story flags).
text "— Ну, — наставник скрещивает руки. — Показывай. Только не убейся: я за тебя не отвечаю." key=rpg_plus/yard/intro
:menu
select "Чем займёмся?" key=rpg_plus/yard/ask
option "Отработать удары (+20 опыта, один раз)" key=rpg_plus/yard/opt_drill -> :drill
option "Поднять бревно над головой [Сила 12]" key=rpg_plus/yard/opt_log -> :log
option "Вернуться в деревню" key=rpg_plus/yard/opt_back -> :back
:drill
when "rpg_plus_drilled == 1" -> :drilled
action stat.exp who=rpg_hero value=20
flag rpg_plus_drilled 1
text "— Неплохо. Ещё сотня таких — и сойдёшь за бойца. Издалека." key=rpg_plus/yard/drill_done
goto -> :menu
:drilled
text "— На сегодня хватит. Мышцы растут, пока спишь. Иди спи." key=rpg_plus/yard/drilled
goto -> :menu
:log
check str 12 who=rpg_hero else=log_fail -> :log_ok
:log_ok
when "rpg_plus_tonic == 1" -> :log_again
action item.add id=classic/strength_tonic
flag rpg_plus_tonic 1
text "— Ха! Поднял. — Наставник бросает тебе склянку. — Тоник силы. Не благодари. И не пей натощак." key=rpg_plus/yard/log_ok
goto -> :menu
:log_again
text "— Опять? Хвастун. Тоника больше нет. Есть уважение. Немного." key=rpg_plus/yard/log_again
goto -> :menu
:log_fail
text "— Бревно победило. Бывает. Подрасти — или купи у торговца тоник силы. Говорят, помогает. Торговец говорит." key=rpg_plus/yard/log_fail
goto -> :menu
:back
switch_scene rpg_village
end
+5
View File
@@ -0,0 +1,5 @@
{
"name": "rpg_plus_crossroads",
"defaultBackground": "rpg_plus/crossroads",
"defaultAmbient": ""
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "rpg_plus_training_yard",
"defaultBackground": "rpg_plus/training_yard",
"defaultAmbient": ""
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "RPG Plus Play Test",
"description": "Integration test of the RPG Plus sample played like a player: the novel-style prologue (rogue class, the charisma check with its rolled line), the village of rpg_demo with this mod's trainer, the hero's sheet (archetype stats, dagger traits, bound battle attributes, the class skill in an action slot), gear put on through the inventory's item action and a strength tonic, conditions over stats and equipment, the merchant's patched stock, a battle with the stat-patched wolf and a formula skill, the training yard (exp, level-up, a skill learned at level 2), taking gear off in the character sheet panel, and a save/load round trip of stats and equipment. Activated by --itest=rpg_plus with --game=rpg_plus --new.",
"sources": [
"src/*.cs"
],
"depends": [
"rpg_plus_game>=1"
]
}
+459
View File
@@ -0,0 +1,459 @@
namespace Cthangover.Mods.RpgPlus.Tests
{
using System.Collections.Generic;
using System.Linq;
using Cthangover.Core.Battle;
using Cthangover.Core.Battle.Actions;
using Cthangover.Core.Bootstrap;
using Cthangover.Core.Characters;
using Cthangover.Core.Factories.Impls;
using Cthangover.Core.Games;
using Cthangover.Core.Items;
using Cthangover.Core.Scenes;
using Cthangover.Core.Settings;
using Cthangover.Core.UI;
using Cthangover.Core.Utils;
using Cthangover.Mods.Dialog;
using Cthangover.Mods.Equipment;
using Cthangover.Mods.Interface.View;
using Cthangover.Mods.Items;
using Cthangover.Mods.Rpg;
using Cthangover.Mods.Stats;
using Cthangover.Mods.Trading;
using Godot;
/// <summary>
/// Plays the RPG Plus sample end to end, the way a player would, and checks that the
/// mods agree with each other at every step: the prologue's class choice reaches the
/// sheet, the sheet reaches the battle attributes, gear and tonics change both, a
/// formula skill hits the stat-patched wolf, the training yard levels the hero up and
/// teaches the class skill, the sheet panel takes gear off, and a save brings it all back.
/// </summary>
public partial class RpgPlusTestDriver : Node
{
private const float TickSeconds = 0.1f;
private const float TimeoutSeconds = 150f;
private const string Hero = "rpg_hero";
private const string Crossroads = "rpg_plus_crossroads";
private const string Yard = "rpg_plus_training_yard";
private const string Village = "rpg_village";
private const string Rogue = "classic/rogue";
private const string Dagger = "classic/dagger";
private const string IronSword = "classic/iron_sword";
private const string Staff = "classic/oak_staff";
private const string Tonic = "classic/strength_tonic";
private const string Charm = "classic/clover_charm";
private const string WinFlag = "rpg_plus_test_win";
private const string SaveSlot = "slot_9902";
private const string SheetEntry = "sheet";
private readonly string shotsDir;
private readonly List<string> failures = new();
private readonly Queue<int> choices = new();
private int passed;
private int step;
private int waitTicks;
private int pagesPhase;
private double pagesWait;
private bool slimeMoved;
private int coinsBeforeChest;
private double elapsed;
private string checkValue;
private string checkResult;
private bool sawYard;
public RpgPlusTestDriver(string shotsDir)
{
this.shotsDir = shotsDir;
Name = "RpgPlusTestDriver";
var timer = new Timer { WaitTime = TickSeconds, Autostart = true };
AddChild(timer);
timer.Timeout += Tick;
}
private static RpgWorldView View => RpgWorldHost.Current?.View;
private static IInventory Inventory => GameData.Instance.Runtime.Inventory;
private static CharacterInfoData Entry => StatsService.RosterEntry(Hero);
private static DialogBox Box => RpgInput.FindDialogBox();
private string SceneName => GetTree().Root.GetNodeOrNull<SceneManager>("SceneManager")?.CurrentSceneName;
private static string Flag(string name) => GameData.Instance.Runtime.Flags.TryGetValue(name, out var value) ? value : null;
private void Tick()
{
if (step < 0)
return;
elapsed += TickSeconds;
if (elapsed > TimeoutSeconds)
{
Fail($"timeout at step {step} (scene {SceneName}, map {View?.Map?.ID}, dialog running {Box?.IsRunning})");
Finish();
return;
}
try
{
RunStep();
}
catch (System.Exception ex)
{
Fail($"step {step}: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}");
Finish();
}
}
private void RunStep()
{
switch (step)
{
case 0:
if (!Boot.IsReady || SceneName != Crossroads || Box?.IsRunning != true)
return;
Shot("prologue");
// The class question: the rogue; the seer: ask the way (the charisma check).
choices.Enqueue(2);
choices.Enqueue(0);
Next();
return;
case 1:
RememberCheck();
if (AdvanceDialog())
return;
if (View?.Map?.ID != Village || GodotSceneService.IsTransitioning || Entry == null || ++waitTicks < 3)
return;
CheckPrologue();
CheckSheet();
Next();
return;
case 2:
CheckGear();
CheckConditionsAndShop();
Check(RpgBattleService.Start("rpg_wolf", WinFlag), "a battle with the stat-patched wolf starts from the map");
Godot.Engine.TimeScale = 4;
Next();
return;
case 3:
if (GetTree().CurrentScene is not BattleSceneContext battle)
return;
if (battle.ActiveBattleCore is not IBattleAutoPlayable auto || !auto.IsWaitingForPlayerInput || GodotSceneService.IsTransitioning)
return;
Shot("battle");
CheckBattle();
battle.ShowWinground();
Next();
return;
case 4:
if (GetTree().Root.FindChild("ContinueButton", true, false) is not Button button || !button.IsVisibleInTree())
return;
button.EmitSignal(BaseButton.SignalName.Pressed);
Next();
return;
case 5:
if (View?.Map?.ID != Village || GodotSceneService.IsTransitioning || AdvanceDialog())
return;
Godot.Engine.TimeScale = 1;
// The victory is taken by the platform's shortcut (the wolf is not killed), so it
// awards no exp; the exp path is the training yard below.
if (!CheckPagesAndRoute())
return;
Check(Flag(WinFlag) == "1", "the win flag is set after the victory");
// To the trainer, added to the demo's village by this mod's own map events file.
Check(View.FindEvent("rpg_plus_trainer")?.Visible == true, "the trainer stands in the demo's village");
View.PlacePlayer(26, 10, RpgDirection.Right);
choices.Enqueue(0); // the trainer: to the yard
choices.Enqueue(0); // the yard: drill
choices.Enqueue(2); // the yard: back to the village
Tap(Key.E);
Next();
return;
case 6:
sawYard |= SceneName == Yard;
if (AdvanceDialog())
return;
if (!sawYard || View?.Map?.ID != Village || GodotSceneService.IsTransitioning || ++waitTicks < 3)
return;
CheckTraining();
Next();
return;
case 7:
// The toolbar button's own path.
UiRegistry.Toggle(SheetEntry);
var panel = SheetPanel();
Check(panel != null && panel.Visible, "the toolbar entry 'sheet' opens the character sheet");
if (panel == null)
{
Finish();
return;
}
panel.Select(Hero);
Next();
return;
case 8:
if (++waitTicks < 2)
return;
Shot("sheet_full");
CheckPanelAndTakeOff(SheetPanel());
Next();
return;
case 9:
if (++waitTicks < 3)
return;
var sheetPanel = SheetPanel();
Check(EquipmentService.ItemIn(Hero, "weapon") == null, "Take off in the sheet empties the weapon slot");
Check(Inventory.CheckCount(IronSword) == 1, "the sword is back in the bag");
Check(sheetPanel.FindChild("Slot_weapon", true, false)?.FindChild("TakeOff", true, false) == null, "the redrawn sheet shows the slot empty");
Shot("sheet");
sheetPanel.Hide();
Check(EquipmentService.Equip(Hero, Dagger), "equip.put's service puts the dagger on from the bag");
SaveService.Save(SaveSlot);
GameSession.OpenMainMenu(GetTree());
Next();
return;
case 10:
if (RpgWorldHost.Current != null || ++waitTicks < 5)
return;
Check(GameSession.LoadAndEnter(this, SaveSlot), "the save loads");
Next();
return;
case 11:
if (View?.Map?.ID != Village || GodotSceneService.IsTransitioning || ++waitTicks < 5)
return;
CheckLoaded();
SaveService.DeleteSave(SaveSlot);
Finish();
return;
}
}
private void RememberCheck()
{
var value = Box?.Runtime?.GetVariable("check_value");
if (!string.IsNullOrEmpty(value))
{
checkValue = value;
checkResult = Box.Runtime.GetVariable("check_result");
}
}
private void CheckPrologue()
{
Check(StatsService.ArchetypeOf(Hero) == Rogue, $"the prologue's choice made the hero a rogue, got {StatsService.ArchetypeOf(Hero)}");
Check(EquipmentService.ItemIn(Hero, "weapon") == Dagger, "the rogue starts with the dagger in the weapon slot");
Check(Inventory.CheckCount("money/coin") >= 25, "the purse of the prologue is in the bag");
var cha = StatsService.Sheet(Hero).Rounded("cha");
Check(checkValue == cha.ToString(), $"the check line rolled the hero's charisma ({checkValue} vs {cha})");
var success = checkResult == TranslationServer.Translate("checks/success").ToString();
Check(success == Inventory.HasItem(Charm), $"the seer's charm follows the check's outcome ({checkResult})");
}
private void CheckSheet()
{
var sheet = StatsService.Sheet(Hero);
Check(sheet.GetBase("agi") == 10 && sheet.Get("agi") == 12, $"rogue agility 8 + 2*level, +2 from the dagger: {sheet.GetBase("agi")} -> {sheet.Get("agi")}");
Check(sheet.Rounded("atk") == 13, $"attack = str + level + agi/4 (rogue trait) + 3 (dagger) = 13, got {sheet.Get("atk")}");
Check(Entry.Attributes.Attack.Value == 13 && Entry.Attributes.Attack.BaseValue == 13, $"the bound attack reached the battle attribute: {Entry.Attributes.Attack.Value}");
Check(Entry.Attributes.Health.BaseValue == sheet.Rounded("hp") && sheet.Rounded("hp") == 29, $"max health 12 + vit*3 + level*2 = 29 reached the battle attribute: {Entry.Attributes.Health.BaseValue}");
Check(Entry.ActionSlots?.Contains("classic/power_strike") == true, "the rogue's level 1 skill went into an action slot");
}
private void CheckGear()
{
Inventory.Add(IronSword, 1);
Check(UseOnHero(IronSword), "the bag's equip action puts the iron sword on");
Check(EquipmentService.ItemIn(Hero, "weapon") == IronSword, "the sword is in the weapon slot");
Check(Inventory.CheckCount(Dagger) == 1 && Inventory.CheckCount(IronSword) == 0, "the dagger went back to the bag, the sword left it");
var sheet = StatsService.Sheet(Hero);
Check(sheet.Get("agi") == 10 && sheet.Rounded("atk") == 14, $"without the dagger: agi 10, atk 7 + 2 + 5 = 14, got {sheet.Get("agi")} / {sheet.Get("atk")}");
Check(Entry.Attributes.Attack.Value == 14, "the battle attribute follows the gear change");
Inventory.Add(Staff, 1);
Check(!UseOnHero(Staff), "a mages-only staff refuses a rogue");
Check(Inventory.CheckCount(Staff) == 1 && EquipmentService.ItemIn(Hero, "weapon") == IronSword, "the refused staff stays in the bag");
Inventory.Add(Tonic, 1);
Check(UseOnHero(Tonic), "the strength tonic is applied");
Check(StatsService.Sheet(Hero).Rounded("str") == 7 && StatsService.Sheet(Hero).Rounded("atk") == 15, "the tonic raised strength for good and attack with it");
}
private void CheckConditionsAndShop()
{
Check(ScenarioCondition.Evaluate("equipped(rpg_hero, 'classic/iron_sword') && archetype(rpg_hero, 'classic/rogue') && stat(rpg_hero, str) >= 7 && level(rpg_hero) == 1"),
"conditions read equipment, archetype, stats and level");
Check(!ScenarioCondition.Evaluate("equipped(rpg_hero, 'classic/dagger')"), "a dagger in the bag is not worn");
var shop = ShopFactory.Instance.Get("rpg_demo/village_merchant");
Check(shop?.FindSell(IronSword)?.Price == 30, "the shop patch added the gear to the demo's merchant");
Check(shop?.FindSell("rpg_demo/elixir") != null, "the merchant still sells his own elixir");
}
private void CheckBattle()
{
var wolf = BattleRequest.Current?.EnemiesCards?.FirstOrDefault(e => e?.ID == "rpg_wolf");
Check(wolf != null, "the wolf is in the battle");
if (wolf == null)
return;
var wolfSheet = StatsService.Sheet("rpg_wolf");
Check(wolf.Attributes.Health.BaseValue == wolfSheet.Rounded("hp") && wolfSheet.Rounded("hp") == 31, $"the patched wolf fights with its sheet's health 31, got {wolf.Attributes.Health.BaseValue}");
Check(wolf.Attributes.Defence.Value == 2, $"and its sheet's shield floor(vit/2) = 2, got {wolf.Attributes.Defence.Value}");
var before = wolf.Attributes.Health.Value;
var hero = new Character { ID = Hero, Level = StatsService.LevelOf(Hero), Attributes = Entry.Attributes };
hero.Attributes.Point.Value = System.Math.Max(1, hero.Attributes.Point.Value);
var result = ActionExecutorHub.Instance.Execute(ActionCharacterFactory.Instance.Get("classic/power_strike"), hero, wolf);
Check(result.Result && result.Target.Damage > 0, $"the formula skill power strike hits, damage {result.Target.Damage}");
Check(wolf.Attributes.Health.Value == before - result.Target.Damage, "the damage left the wolf's health");
Check(result.Target.Defence == -2, $"the shield took its 2 first, change {result.Target.Defence}");
// Mana as a bar of the battle core: battle_formulas gives the pool, ff_battle draws it.
var mana = BattleUnitGauges.Read(hero).FirstOrDefault(g => g.Id == "mp");
Check(mana != null && mana.Max == StatsService.Sheet(Hero).Rounded("mp") && mana.Current <= mana.Max, $"the hero has a mana gauge of the sheet's mp, got {mana?.Current}/{mana?.Max}");
var bars = GetTree().Root.FindChildren("Gauges", "VBoxContainer", true, false).OfType<Control>().Where(c => c.IsVisibleInTree()).ToList();
Check(bars.Any(b => b.FindChildren("Value", "Label", true, false).OfType<Label>().Any(l => l.Text == $"{mana?.Current}/{mana?.Max}")), "the FF party panel shows the mana bar with its numbers");
}
private void CheckTraining()
{
Check(Flag("rpg_plus_drilled") == "1", "the training yard's drill ran");
Check(StatsService.LevelOf(Hero) == 2, $"20 exp from the drill took the hero to level 2, level {StatsService.LevelOf(Hero)}, exp {StatsService.ExpOf(Hero)}");
var learned = Entry.ActionSlots?.Contains("classic/guard_break") == true || GameData.Instance.Runtime.ActionPool.ActionIds.Contains("classic/guard_break");
Check(learned, "level 2 taught the rogue guard break (a slot or the action pool)");
var sheet = StatsService.Sheet(Hero);
Check(Entry.Attributes.Health.BaseValue == sheet.Rounded("hp") && sheet.Rounded("hp") == 34, $"max health grew with the level to 34, got {Entry.Attributes.Health.BaseValue}");
}
private void CheckPanelAndTakeOff(StatSheetPanel panel)
{
Check(panel.Visible && panel.SelectedId == Hero, "the sheet shows the hero");
Check(panel.FindChild("Stat_agi", true, false) != null && panel.FindChild("Stat_hp", true, false) != null, "the sheet lists the pack's stats");
Check(panel.FindChild("Section_equipment", true, false) != null, "the equipment mod's block is in the sheet");
var takeOff = panel.FindChild("Slot_weapon", true, false)?.FindChild("TakeOff", true, false) as Button;
Check(takeOff != null, "the worn sword has a Take off button");
takeOff?.EmitSignal(BaseButton.SignalName.Pressed);
}
private void CheckLoaded()
{
Check(StatsService.ArchetypeOf(Hero) == Rogue, "the archetype survives the save");
Check(EquipmentService.ItemIn(Hero, "weapon") == Dagger, "the worn dagger survives the save");
Check(StatsService.LevelOf(Hero) == 2, "the level survives the save");
var sheet = StatsService.Sheet(Hero);
Check(StatState.Instance.Find(Hero)?.Bonus.GetValueOrDefault("str") == 1, "the tonic's permanent bonus survives the save");
Check(sheet.Rounded("str") == 8, $"strength at level 2: 5 + 2 + 1 (tonic) = 8, got {sheet.Get("str")}");
Check(Entry.Attributes.Attack.Value == sheet.Rounded("atk"), "the loaded battle attributes agree with the sheet");
}
private static bool UseOnHero(string itemId)
{
// What the inventory panel does after the player picks the hero as the target.
var item = ItemFactory.Instance.Get(itemId);
if (item?.ItemAction is not ITargetedItemAction targeted || !targeted.UseOn(item, Entry))
return false;
Inventory.Remove(item, 1);
return true;
}
private static StatSheetPanel SheetPanel()
=> UiHost.Instance != null && UiHost.Instance.TryGetInstance(SheetEntry, out var control) ? control as StatSheetPanel : null;
/// <summary>
/// Event pages and a move route (rpg): the tame slime walks its route, the chest gives
/// coins once and then shows its open page. True when done; runs over several ticks.
/// </summary>
private bool CheckPagesAndRoute()
{
var chest = View.FindEvent("rpg_plus_chest");
var slime = View.FindEvent("rpg_plus_pet_slime");
switch (pagesPhase)
{
case 0:
Check(chest?.Visible == true && chest.PageIndex == 0, "the closed chest shows its first page");
Check(slime?.Current?.Route != null, "the tame slime has a route");
pagesWait = 0;
pagesPhase++;
return false;
case 1:
// Left, wait, right, right…: within three seconds it has left its cell once.
slimeMoved |= slime != null && slime.Cell != new Vector2I(6, 15);
pagesWait += TickSeconds;
if (pagesWait < 3)
return false;
Check(slimeMoved, "the slime walks its route on its own");
coinsBeforeChest = Inventory.CheckCount("money/coin");
View.PlacePlayer(28, 3, RpgDirection.Right);
Tap(Key.E);
pagesPhase++;
return false;
case 2:
if (AdvanceDialog())
return false;
Check(Inventory.CheckCount("money/coin") == coinsBeforeChest + 10, "the chest gave ten coins");
Check(chest?.PageIndex == 1 && chest.Current.Tile == 15, "the flag switched the chest to its open page");
Check(chest?.Current?.Commands?.Count == 1, "the open page has its own commands");
pagesPhase++;
return true;
default:
return true;
}
}
private bool AdvanceDialog()
{
var box = Box;
if (box == null || !box.IsRunning)
return false;
if (box.IsWaitingAnswer)
box.TrySelectVariant(choices.Count > 0 ? choices.Dequeue() : 0);
else
box.NextAction();
return true;
}
private static void Tap(Key key)
{
Input.ParseInputEvent(new InputEventKey { PhysicalKeycode = key, Keycode = key, Pressed = true });
Input.ParseInputEvent(new InputEventKey { PhysicalKeycode = key, Keycode = key, Pressed = false });
}
private void Next()
{
step++;
waitTicks = 0;
}
private void Check(bool condition, string what)
{
if (condition)
{
passed++;
GameLogger.Log("TEST", $"[PASS] {what}");
}
else
{
Fail(what);
}
}
private void Fail(string what)
{
failures.Add(what);
GameLogger.Log("TEST", $"[FAIL] {what}", LogLevel.Error);
}
private void Shot(string name)
{
if (string.IsNullOrEmpty(shotsDir) || DisplayServer.GetName() == "headless")
return;
DirAccess.MakeDirRecursiveAbsolute(shotsDir);
ViewportCapture.SavePng(GetViewport(), shotsDir.TrimEnd('/') + "/rpg_plus_" + name + ".png");
}
private void Finish()
{
step = -1;
Godot.Engine.TimeScale = 1;
GameLogger.Log("TEST", $"[TEST_RESULT] name=rpg_plus passed={passed} failed={failures.Count}");
GetTree().Quit(failures.Count == 0 ? 0 : 1);
}
}
}
@@ -0,0 +1,32 @@
namespace Cthangover.Mods.RpgPlus.Tests
{
using System.Linq;
using Cthangover.Core.Mods;
using Cthangover.Core.Utils;
using Godot;
/// <summary>Mounts <see cref="RpgPlusTestDriver"/> when the game runs with <c>--itest=rpg_plus</c>.</summary>
public class RpgPlusTestInitializer : IModInitializer
{
private static bool driverQueued;
/// <inheritdoc />
public void OnModLoaded(string modId)
{
}
/// <inheritdoc />
public void OnModResourcesReady()
{
var args = OS.GetCmdlineUserArgs();
if (driverQueued || !args.Any(a => a == "--itest=rpg_plus"))
return;
driverQueued = true;
var shots = args.Where(a => a.StartsWith("--shots=", System.StringComparison.Ordinal))
.Select(a => a.Substring("--shots=".Length))
.FirstOrDefault();
GameLogger.Log("TEST", $"RpgPlusTestInitializer: activating rpg_plus, shots={shots ?? "off"}");
(Godot.Engine.GetMainLoop() as SceneTree)?.Root?.CallDeferred("add_child", new RpgPlusTestDriver(shots));
}
}
}