<q:application id="rpg" type="game"> <!-- RPG: Godot's "JRPG" demo (godotengine/godot-demo-projects, 2d/role_playing_game, MIT), transcribed. A slime walks a grassy map cell by cell, reads a key, talks to another slime and fights it, turn by turn: Attack, Defend, Flee. The art and the font are the demo's (assets/LICENSE.md), packed by scripts/art/rpg.py; README.md says what is where. --> <!-- How the last fight ended ('' before any), and the cell it was started from. --> <q:set name="outcome" value="" /> <q:set name="back_col" value="3" type="number" /> <q:set name="back_row" value="4" type="number" /> <!-- Beyond the demo: what the player carries, and what it took from the map, by name. --> <q:set name="keys" value="0" type="number" /> <q:set name="potions" value="0" type="number" /> <q:set name="taken" value="[]" type="array" /> <!-- The demo's ui_accept: Enter, Space, the joypad's A; and a click. --> <qg:input action="select" keys="Enter, Space, MouseLeft, JoyA" /> <!-- Every tile of the three layers is a picture of its own; the Grid layer's are the ones that stop a walker. --> <!-- beyond the demo: the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) --> <!-- on a phone: what the keys do, under a finger, in every scene --> <qg:touch-stick x="140" y="580" size="200" /> <qg:language lang="pt" name="Português"> <qg:phrase text="Hey, it's a good time to have a JRPG fight, right?" as="Ei, é uma boa hora para uma luta de JRPG, não acha?" /> <qg:phrase text="Let me introduce myself, I'm your OPPONENT." as="Deixa eu me apresentar: sou o seu OPONENTE." /> <qg:phrase text="Enough talking. Let's fight!" as="Chega de conversa. Vamos lutar!" /> <qg:phrase text="Just a key..." as="Só uma chave..." /> <qg:phrase text="I'll keep it anyway." as="Vou guardar mesmo assim." /> <qg:phrase text="A potion! In a fight, it gives back 5 life." as="Uma poção! Numa luta, ela devolve 5 de vida." /> <qg:phrase text="Congratulations, you won!" as="Parabéns, você venceu!" /> <qg:phrase text="Aha! I won, maybe you can try again next time." as="Rá! Eu venci, quem sabe você tenta de novo outra vez." /> <qg:phrase text="UNKNOWN" as="DESCONHECIDO" /> <qg:phrase text="OPPONENT" as="OPONENTE" /> <qg:phrase text="PLAYER" as="JOGADOR" /> <qg:phrase text="Player" as="Jogador" /> <qg:phrase text="Opponent" as="Oponente" /> <qg:phrase text="Attack" as="Atacar" /> <qg:phrase text="Defend" as="Defender" /> <qg:phrase text="Potion" as="Poção" /> <qg:phrase text="Flee" as="Fugir" /> <qg:phrase text="Resume" as="Continuar" /> <qg:phrase text="Music" as="Música" /> <qg:phrase text="Full screen" as="Tela cheia" /> <qg:phrase text="on" as="sim" /> <qg:phrase text="off" as="não" /> <qg:phrase text="potion" as="poção" /> <qg:phrase text="potions" as="poções" /> <qg:phrase text="key" as="chave" /> <qg:phrase text="keys" as="chaves" /> <!-- the fight's banner, drawn in Portuguese --> <qg:asset name="banner" src="assets/banner-pt.png" /></qg:language> <qg:language lang="es" name="Español"> <qg:phrase text="Hey, it's a good time to have a JRPG fight, right?" as="Oye, es un buen momento para una pelea de JRPG, ¿no crees?" /> <qg:phrase text="Let me introduce myself, I'm your OPPONENT." as="Déjame presentarme: soy tu OPONENTE." /> <qg:phrase text="Enough talking. Let's fight!" as="Basta de charla. ¡A pelear!" /> <qg:phrase text="Just a key..." as="Solo una llave..." /> <qg:phrase text="I'll keep it anyway." as="Me la quedo de todos modos." /> <qg:phrase text="A potion! In a fight, it gives back 5 life." as="¡Una poción! En una pelea, devuelve 5 de vida." /> <qg:phrase text="Congratulations, you won!" as="¡Felicidades, ganaste!" /> <qg:phrase text="Aha! I won, maybe you can try again next time." as="¡Ajá! Gané, tal vez puedas intentarlo de nuevo la próxima vez." /> <qg:phrase text="UNKNOWN" as="DESCONOCIDO" /> <qg:phrase text="OPPONENT" as="OPONENTE" /> <qg:phrase text="PLAYER" as="JUGADOR" /> <qg:phrase text="Player" as="Jugador" /> <qg:phrase text="Opponent" as="Oponente" /> <qg:phrase text="Attack" as="Atacar" /> <qg:phrase text="Defend" as="Defender" /> <qg:phrase text="Potion" as="Poción" /> <qg:phrase text="Flee" as="Huir" /> <qg:phrase text="Resume" as="Continuar" /> <qg:phrase text="Music" as="Música" /> <qg:phrase text="Full screen" as="Pantalla completa" /> <qg:phrase text="on" as="sí" /> <qg:phrase text="off" as="no" /> <qg:phrase text="potion" as="poción" /> <qg:phrase text="potions" as="pociones" /> <qg:phrase text="key" as="llave" /> <qg:phrase text="keys" as="llaves" /> <qg:asset name="banner" src="assets/banner-es.png" /></qg:language> <!-- Russian: letters Montserrat has not, so a font that has them (Xolonium, assets/LICENSE.md), and three plural forms: 1 зелье, 2 зелья, 5 зелий --> <qg:language lang="ru" name="Русский" font="assets/Xolonium-Regular.ttf"> <qg:phrase text="Hey, it's a good time to have a JRPG fight, right?" as="Эй, самое время для JRPG-драки, правда?" /> <qg:phrase text="Let me introduce myself, I'm your OPPONENT." as="Позволь представиться: я твой ПРОТИВНИК." /> <qg:phrase text="Enough talking. Let's fight!" as="Хватит болтать. В бой!" /> <qg:phrase text="Just a key..." as="Просто ключ..." /> <qg:phrase text="I'll keep it anyway." as="Всё равно возьму." /> <qg:phrase text="A potion! In a fight, it gives back 5 life." as="Зелье! В бою оно возвращает 5 жизни." /> <qg:phrase text="Congratulations, you won!" as="Поздравляю, ты победил!" /> <qg:phrase text="Aha! I won, maybe you can try again next time." as="Ага! Я победил. Может, в следующий раз получится." /> <qg:phrase text="UNKNOWN" as="НЕЗНАКОМЕЦ" /> <qg:phrase text="OPPONENT" as="ПРОТИВНИК" /> <qg:phrase text="PLAYER" as="ИГРОК" /> <qg:phrase text="Player" as="Игрок" /> <qg:phrase text="Opponent" as="Противник" /> <qg:phrase text="Attack" as="Атака" /> <qg:phrase text="Defend" as="Защита" /> <qg:phrase text="Potion" as="Зелье" /> <qg:phrase text="Flee" as="Бежать" /> <qg:phrase text="Resume" as="Продолжить" /> <qg:phrase text="Music" as="Музыка" /> <qg:phrase text="Full screen" as="Полный экран" /> <qg:phrase text="on" as="вкл" /> <qg:phrase text="off" as="выкл" /> <qg:phrase text="potion" as="зелье" /> <qg:phrase text="potions" as="зелий" one="зелье" few="зелья" /> <qg:phrase text="key" as="ключ" /> <qg:phrase text="keys" as="ключей" one="ключ" few="ключа" /> <qg:asset name="banner" src="assets/banner-ru.png" /></qg:language> <qg:tileset name="world" src="assets/tiles.png" tile="64" /> <qg:spritesheet name="pawns" src="assets/pawns.png" tile="64" /> <qg:spritesheet name="fighters" src="assets/fighters.png" tile="128" /> <qg:spritesheet name="arena" src="assets/arena.png" tile="1280x720" /> <!-- beyond the demo: a frame for the dialogue box that stretches to it (its 16-pixel edges stay as drawn), and "FIGHT!" as the fight starts, drawn in each language (scripts/art/rpg_extra.py, CC0) --> <qg:spritesheet name="frame" src="assets/frame.png" tile="48" slice="16" /> <qg:spritesheet name="banner" src="assets/banner-en.png" tile="480x120" /> <qg:prefab name="Banner" tag="banner" sheet="banner" hitbox="480x120" /> <!-- beyond the demo: music of our own (scripts/art/rpg_music.py, CC0) --> <qg:sound bus="music" name="meadow" src="assets/meadow.ogg" loop="true" /> <qg:sound bus="music" name="clash" src="assets/clash.ogg" loop="true" /> <qg:prefab name="Opponent" tag="opponent" sheet="pawns" frame="2" hitbox="48x48" /> <qg:prefab name="Key" tag="key" sheet="pawns" frame="3" hitbox="48x48" /> <qg:prefab name="Potion" tag="potion" sheet="pawns" frame="4" hitbox="48x48" /> <!-- ===== Exploration: grid_movement/exploration.tscn ===== --> <qg:scene name="exploration" width="1280" height="720" background="#62a033"> <qg:music sound="meadow" fade="0.6" volume="-4" /> <qg:tilemap tileset="world">1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 </qg:tilemap> <qg:tilemap tileset="world">0,0,0,0,0,0,0,0,0,0,0,2,3,3,3,4,0,0,0,00,0,0,0,0,0,0,0,0,0,0,5,6,6,6,7,0,0,0,00,0,8,9,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00,0,11,12,4,0,0,13,13,13,13,10,0,0,0,0,0,0,0,00,0,11,12,14,0,0,15,15,15,15,14,0,0,0,0,0,0,0,00,0,5,16,7,0,0,6,6,6,6,17,0,0,0,8,13,13,12,00,0,0,0,0,0,0,0,0,0,0,18,13,19,0,20,12,12,12,00,0,0,0,0,0,0,0,0,0,0,20,21,14,0,22,12,12,12,120,0,0,0,0,0,0,0,0,0,0,5,23,17,0,24,23,23,12,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 </qg:tilemap> <qg:tilemap tileset="world" collision="true">25,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,2829,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,31,2829,0,0,0,0,0,32,33,33,33,34,0,0,0,0,0,0,0,31,2829,0,0,0,0,0,31,25,26,26,35,0,0,0,0,0,0,32,36,2829,0,0,0,0,0,31,29,0,0,0,0,0,0,0,0,0,37,38,2829,0,0,0,0,0,31,39,33,33,34,0,0,0,0,0,0,40,27,2829,41,0,0,0,0,40,42,26,26,35,0,0,0,0,0,0,0,31,2843,44,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,2828,28,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,36,2828,28,43,44,44,44,44,34,0,0,0,0,0,0,0,0,0,37,38,2828,28,28,28,28,28,28,43,44,44,44,44,44,44,44,44,44,36,38,28 </qg:tilemap> <qg:instance prefab="Key" name="key" x="544" y="288" if="{'key' not in taken}" /> <qg:instance prefab="Potion" name="potion-1" x="96" y="96" if="{'potion-1' not in taken}" /> <qg:instance prefab="Potion" name="potion-2" x="928" y="608" if="{'potion-2' not in taken}" /> <qg:instance prefab="Opponent" x="800" y="480" /> <qg:character id="player" controller="grid" diagonal="true" step-frames="15" sheet="pawns" frame="0" x="224" y="288" hitbox="48x48"> <qg:animation name="idle" frames="0" fps="1" /> <qg:animation name="walk" frames="0" fps="1" /> <qg:animation name="bump" frames="1" fps="1" /> <qg:on-collision with="key"> <q:set name="taken" value="{taken + [other.name]}" /> <q:set name="keys" value="{keys + 1}" /> <qg:destroy target="other" /> <qg:say dialogue="key" /> </qg:on-collision> <qg:on-collision with="potion"> <q:set name="taken" value="{taken + [other.name]}" /> <q:set name="potions" value="{potions + 1}" /> <qg:destroy target="other" /> <qg:say dialogue="potion" /> </qg:on-collision> <qg:on-collision with="opponent"> <q:set name="back_col" value="{me.col}" /> <q:set name="back_row" value="{me.row}" /> <qg:say dialogue="npc" /> </qg:on-collision> </qg:character> <!-- dialogue/dialogue_data/*.json --> <qg:dialogue name="npc" font="assets/montserrat_extra_bold.otf" size="28" box="frame"> <qg:line who="UNKNOWN" text="Hey, it's a good time to have a JRPG fight, right?" /> <qg:line who="UNKNOWN" text="Let me introduce myself, I'm your OPPONENT." /> <qg:line who="OPPONENT" text="Enough talking. Let's fight!" /> <qg:on-end> <qg:goto-scene name="combat" fade="0.4" /> </qg:on-end> </qg:dialogue> <qg:dialogue name="key" font="assets/montserrat_extra_bold.otf" size="28" box="frame"> <qg:line who="PLAYER" text="Just a key..." /> <qg:line who="PLAYER" text="I'll keep it anyway." /> </qg:dialogue> <qg:dialogue name="potion" font="assets/montserrat_extra_bold.otf" size="28" box="frame"> <qg:line who="PLAYER" text="A potion! In a fight, it gives back 5 life." /> </qg:dialogue> <!-- What the player carries. --> <!-- beyond the demo: "1 potion", "2 potions", in the player's language and its plural forms --> <qg:hud position="top-left" font="assets/montserrat_extra_bold.otf" size="24"> <qg:text value="{str(potions) + ' ' + tr_n('potion', 'potions', potions)}" /> <qg:text value="{str(keys) + ' ' + tr_n('key', 'keys', keys)}" /> </qg:hud> <qg:dialogue name="won" font="assets/montserrat_extra_bold.otf" size="28" box="frame"> <qg:line who="OPPONENT" text="Congratulations, you won!" /> <qg:on-end><q:set name="outcome" value="" /></qg:on-end> </qg:dialogue> <qg:dialogue name="lost" font="assets/montserrat_extra_bold.otf" size="28" box="frame"> <qg:line who="OPPONENT" text="Aha! I won, maybe you can try again next time." /> <qg:on-end><q:set name="outcome" value="" /></qg:on-end> </qg:dialogue> <!-- beyond the demo: Escape (or the joypad's B) pauses, with the options; not while someone talks --> <qg:on-input action="cancel"> <q:if condition="{not talking()}"> <q:if condition="{paused()}"><qg:resume /><q:else><qg:pause /></q:else></q:if> </q:if> </qg:on-input> <qg:menu if="{paused() and not talking()}" size="32"> <qg:button label="Resume"><qg:resume /></qg:button> <qg:slider label="Music" bus="music" /> <qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button> </qg:menu> <!-- Back from a fight: where it started, and what the opponent says of it. --> <q:if condition="{outcome != ''}"> <qg:put target="player" x="{back_col * 64 + 32}" y="{back_row * 64 + 32}" /> <q:if condition="{outcome == 'won'}"><qg:say dialogue="won" /></q:if> <q:if condition="{outcome == 'lost'}"><qg:say dialogue="lost" /></q:if> </q:if> </qg:scene> <!-- ===== Combat: combat/combat.tscn, turn_queue.gd, combatant.gd, health.gd ===== --> <qg:scene name="combat" width="1280" height="720" background="#4a4a4a"> <qg:music sound="clash" fade="0.4" volume="-6" /> <!-- player.tscn: 10 life, 2 damage, 1 defense, 1 base armor; opponent.tscn: 7 life, 3 damage, 1 defense, no armor. --> <q:set name="life" value="10" type="number" /> <q:set name="armor" value="1" type="number" /> <q:set name="foe_life" value="7" type="number" /> <q:set name="foe_armor" value="0" type="number" /> <q:set name="turn" value="player" /> <q:set name="wait" value="0" type="number" /> <!-- the opponent's Timer: 0.25 s --> <!-- beyond the demo: a fighter just hit makes a face for half a second --> <q:set name="hurt" value="0" type="number" /> <q:set name="foe_hurt" value="0" type="number" /> <!-- health.gd: a hit takes the damage less the armor. --> <q:function name="end" params="result"> <q:set name="outcome" value="{result}" /> <qg:goto-scene name="exploration" fade="0.4" /> </q:function> <q:function name="foes_turn"> <q:set name="turn" value="opponent" /> <q:if condition="{foe_armor >= 0 + 1}"><q:set name="foe_armor" value="0" /></q:if> <q:set name="wait" value="15" /> </q:function> <qg:sprite sheet="arena" x="640" y="360" /> <qg:sprite sheet="fighters" frame="4" x="440" y="470" /> <qg:sprite sheet="fighters" frame="4" x="840" y="470" /> <qg:sprite sheet="fighters" frame="{1 if hurt > 0 else 0}" x="440" y="420" /> <qg:sprite sheet="fighters" frame="{3 if foe_hurt > 0 else 2}" x="840" y="420" /> <!-- beyond the demo: FIGHT! (in the player's language) as the scene fades in, and gone a second later --> <qg:instance prefab="Banner" name="banner" x="640" y="250" /> <qg:timer after="60"> <qg:tween target="banner" property="alpha" to="0" seconds="0.5"> <qg:on-end><qg:destroy target="me" /></qg:on-end> </qg:tween> </qg:timer> <qg:hud position="top-left" font="assets/montserrat_extra_bold.otf" size="32"> <qg:text value="{'Player'}" /> <qg:bar bind="life" max="10" width="320" height="20" color="#d81b60" /> </qg:hud> <qg:hud position="top-right" font="assets/montserrat_extra_bold.otf" size="32"> <qg:text value="{'Opponent'}" /> <qg:bar bind="foe_life" max="7" width="320" height="20" color="#d81b60" /> </qg:hud> <!-- ui.gd: the buttons work on the player's turn; beyond the demo, in the dialogues' frame. --> <qg:menu position="bottom-center" font="assets/montserrat_extra_bold.otf" size="36" box="frame" if="{turn == 'player'}"> <qg:button label="Attack"> <q:set name="foe_life" value="{foe_life - 2 + foe_armor}" /> <q:set name="foe_hurt" value="30" /> <qg:shake frames="10" strength="6" /> <q:if condition="{foe_life <= 0}"><q:call function="end" args="'won'" /></q:if> <q:if condition="{foe_life > 0}"><q:call function="foes_turn" /></q:if> </qg:button> <qg:button label="Defend"> <q:set name="armor" value="{armor + 1}" /> <q:call function="foes_turn" /> </qg:button> <!-- beyond the demo: a potion gives back 5 life (health.gd's heal, up to the most) --> <qg:button label="Potion" if="{potions > 0}"> <q:set name="potions" value="{potions - 1}" /> <q:set name="life" value="{min(10, life + 5)}" /> <q:call function="foes_turn" /> </qg:button> <qg:button label="Flee"> <q:call function="end" args="'lost'" /> </qg:button> </qg:menu> <!-- opponent.gd: a quarter of a second into its turn, it attacks. --> <qg:timer every="1"> <q:if condition="{hurt > 0}"><q:set name="hurt" value="{hurt - 1}" /></q:if> <q:if condition="{foe_hurt > 0}"><q:set name="foe_hurt" value="{foe_hurt - 1}" /></q:if> <q:if condition="{turn == 'opponent' and wait > 0}"> <q:set name="wait" value="{wait - 1}" /> <q:if condition="{wait == 0}"> <q:set name="life" value="{life - 3 + armor}" /> <q:set name="hurt" value="30" /> <qg:shake frames="10" strength="6" /> <q:if condition="{life <= 0}"><q:call function="end" args="'lost'" /></q:if> <q:if condition="{life > 0}"> <q:set name="turn" value="player" /> <q:if condition="{armor >= 1 + 1}"><q:set name="armor" value="1" /></q:if> </q:if> </q:if> </q:if> </qg:timer> </qg:scene></q:application>