From 266c9ae84c3dc5c5aa6c660d8f4f8762e76cca08 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Sat, 29 Aug 2026 11:21:31 +0200 Subject: [PATCH] v2.1.0 - order relic options --- README.md | 13 ++- defs.asm | 3 + main.asm | 3 +- metadata.json | 2 +- options.asm | 52 +++++++--- play.asm | 266 +++++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 305 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 20878ef..884c89f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ cells, an animated exit portal, a raster color band behind the status row, and a fade to black between all screens. The menu floats over a dark maze backdrop under the big gold-washed title. -The options screen adds two optional twists: +The options screen adds three optional twists: - **worker gun** - player 1 shoots a bullet with fire/`space`; a hit enemy respawns on a random cell after a pause. @@ -38,6 +38,13 @@ The options screen adds two optional twists: denied until the bar on the status row drains (pixel by pixel) and the readout flips back from *wait* to *ready* (the cursor also turns red while recharging). +- **relic order** - numbered relics lie scattered over the corridors + and the worker has to gather them *in order*, 1 first, then 2, and + so on, before the portal will take him. Until the last one is his + the exit stays sealed (a dull grey, and bumping into it blips the + border red); the relic he is after next pulses white, and the + status row counts them off next to the portal glyph. Every level + adds one more: two on level 1 up to nine on level 8. Screens: TTG intro (ported from rabbitc64) -> pyramid title card (the pyramid, the trapped worker and the pulsing sun of Ra) -> main @@ -60,8 +67,8 @@ Test builds can boot straight into one state: ``` acme -DAUTOPLAY=1 -f cbm -o blessingofra.prg main.asm # also: AUTOOPTIONS, AUTOSTORY, AUTOPYRAMID, AUTOCREDITS, -# AUTOWIN, AUTOLOSE; add -DFORCEOPTS=1 to start with both -# options turned on +# AUTOWIN, AUTOLOSE; add -DFORCEOPTS=1 to start with all +# three options turned on ``` ## Files diff --git a/defs.asm b/defs.asm index 46f7332..c7a04c5 100644 --- a/defs.asm +++ b/defs.asm @@ -97,6 +97,8 @@ CH_POST = $e1 ; bolted plate: fixed pivot post CH_EXIT = $e2 ; the portal out (4 animated glyph frames) CH_FLOOR = $20 ; space: corridor CH_BAR = $e3 ; 7 chars: a bar cell 1-7 pixels full +CH_RELIC = $31 ; the relics wear their order: '1'-'9' +NRELIC = 9 ; most relics on a level (level 8) ; ---- the actors are hardware sprites ---- SP_RUN = 0 ; player 1, the worker @@ -113,6 +115,7 @@ COL_HILITE = YELLOW ; walls the cursor's spin would move COL_SCARAB = CYAN ; the scarabs COL_SCORP = LRED ; the scorpions COL_STING = PURPLE ; the venom sting +COL_RELIC = LBLUE ; the numbered relics (relic order option) STATCOL = BROWN ; background band behind the status row ; ---- game timing ---- diff --git a/main.asm b/main.asm index 864d112..4f8c592 100644 --- a/main.asm +++ b/main.asm @@ -14,7 +14,8 @@ ; intro.asm - TTG boot logo (ported from rabbitc64) ; pyramid.asm - title card: the pyramid and the sun of ra ; menu.asm - main menu over a dark maze backdrop -; options.asm - optional extras: worker gun, spin cooldown +; options.asm - optional extras: worker gun, spin cooldown, +; relic order ; story.asm - the legend + who steers what ; credits.asm - the crew (content from rabbitc64, new look) ; play.asm - maze generation + the game itself diff --git a/metadata.json b/metadata.json index 328bd8c..a015567 100644 --- a/metadata.json +++ b/metadata.json @@ -6,5 +6,5 @@ "site": "https://git.teletypegames.org/games/labyrintwo", "repo": "https://git.teletypegames.org/games/blessingofra", "license": "MIT", - "version": "2.0.0" + "version": "2.1.0" } diff --git a/options.asm b/options.asm index a92fa9f..fb25372 100644 --- a/options.asm +++ b/options.asm @@ -4,11 +4,14 @@ ; respawns elsewhere after a pause ; spin cooldown - ra needs a recharge between two spins, ; shown on the status row +; relic order - numbered relics have to be collected in +; order before the portal opens, one more +; of them on every level ; start level - which level a fresh run begins on (1-8) ; the settings live here and survive between levels ; ----------------------------------------------------------- -OPT_ITEMS = 4 +OPT_ITEMS = 5 options_init: jsr clear_screen @@ -17,7 +20,8 @@ options_init: +prtext 21, 6, LGREY, txt_oh1, txt_oh1e-txt_oh1 +prtext 22, 4, LGREY, txt_oh2, txt_oh2e-txt_oh2 - +prtext 23, 5, LGREY, txt_oh3, txt_oh3e-txt_oh3 + +prtext 23, 2, LGREY, txt_oh3, txt_oh3e-txt_oh3 + +prtext 24, 5, LGREY, txt_oh4, txt_oh4e-txt_oh4 lda #0 sta optsel @@ -56,7 +60,7 @@ ou_udrel: sta udheld ou_udend: - ; the three lines, the picked one in yellow + ; the option lines, the picked one in yellow ldx #0 oo_items: txa @@ -68,11 +72,14 @@ oo_items: cpx #OPT_ITEMS bne oo_items - ; the current values next to the three settings + ; the current value next to each setting ldx opt_shoot - lda #10 + lda #7 jsr put_oval ldx opt_cool + lda #10 + jsr put_oval + ldx opt_relic lda #13 jsr put_oval lda opt_level ; the start level as a digit @@ -83,6 +90,7 @@ oo_items: ; blinking '>' cursor in front of the picked line lda #$20 + sta SCREEN+7*40+7 sta SCREEN+10*40+7 sta SCREEN+13*40+7 sta SCREEN+16*40+7 @@ -110,10 +118,12 @@ ou_fire: jsr start_edge beq ou_done lda optsel - cmp #3 + cmp #4 beq ou_back - cmp #2 + cmp #3 beq ou_tlevel + cmp #2 + beq ou_trelic cmp #1 beq ou_tcool lda opt_shoot @@ -125,6 +135,11 @@ ou_tcool: eor #1 sta opt_cool jmp ou_tsfx +ou_trelic: + lda opt_relic + eor #1 + sta opt_relic + jmp ou_tsfx ou_tlevel: ldx opt_level ; cycle 1-8, wrapping back to 1 inx @@ -179,15 +194,15 @@ put_oval: jmp print_text ; ---- option tables ---- -oitem_lo: !byte txt_ogun, >txt_ocool, >txt_olev, >txt_oback -oitem_len: !byte 10, 13, 11, 4 -oitem_row: !byte 10, 13, 16, 19 +oitem_lo: !byte txt_ogun, >txt_ocool, >txt_orel, >txt_olev, >txt_oback +oitem_len: !byte 10, 13, 11, 11, 4 +oitem_row: !byte 7, 10, 13, 16, 19 ocur_lo: ; cursor cells: column 7 of those rows - !byte <(SCREEN+10*40+7), <(SCREEN+13*40+7) + !byte <(SCREEN+7*40+7), <(SCREEN+10*40+7), <(SCREEN+13*40+7) !byte <(SCREEN+16*40+7), <(SCREEN+19*40+7) ocur_hi: - !byte >(SCREEN+10*40+7), >(SCREEN+13*40+7) + !byte >(SCREEN+7*40+7), >(SCREEN+10*40+7), >(SCREEN+13*40+7) !byte >(SCREEN+16*40+7), >(SCREEN+19*40+7) ovtx_lo: !byte txt_off, >txt_on @@ -196,6 +211,7 @@ ovcol: !byte LGREY, LGREEN ; ---- option texts ---- txt_ogun: !scr "worker gun" txt_ocool: !scr "spin cooldown" +txt_orel: !scr "relic order" txt_olev: !scr "start level" txt_oback: !scr "back" txt_on: !scr "on " @@ -204,16 +220,20 @@ txt_oh1: !scr "gun: p1 fire shoots a bullet" txt_oh1e: txt_oh2: !scr "cooldown: spins need a recharge" txt_oh2e: -txt_oh3: !scr "start: a fresh run's first level" +txt_oh3: !scr "relics: grab the numbers in order" txt_oh3e: +txt_oh4: !scr "start: a fresh run's first level" +txt_oh4e: ; ---- the settings themselves (session-wide) ---- -!ifdef FORCEOPTS { ; test builds start with both turned on +!ifdef FORCEOPTS { ; test builds start with them all on opt_shoot: !byte 1 opt_cool: !byte 1 +opt_relic: !byte 1 } else { opt_shoot: !byte 0 ; 0 = classic game, 1 = the worker shoots opt_cool: !byte 0 ; 1 = spins need a recharge +opt_relic: !byte 0 ; 1 = collect the relics in order first } opt_level: !byte 1 ; a fresh run's first level: 1-8 -optsel: !byte 0 ; picked line: 0-3 +optsel: !byte 0 ; picked line: 0-4 diff --git a/play.asm b/play.asm index f9c0502..c39a055 100644 --- a/play.asm +++ b/play.asm @@ -22,6 +22,10 @@ ; random cell after RESPAWN_TIME frames ; opt_cool - a spin starts a COOL_TIME recharge, shown as ; a draining 1/8-step bar on the status row +; opt_relic - numbered relics are scattered over the cells; +; the worker must pick them up in order before +; the portal will take him, one more relic each +; level (2 on level 1 up to 9 on level 8) ; ; screen layout: status on row 0 (over the colored raster ; band), the centered lattice on rows 1-23, hints on row 24 @@ -37,7 +41,10 @@ play_init: sta SCREEN+6 lda #YELLOW sta COLRAM+6 + lda opt_relic ; relic mode keeps its own counter here + bne pi_goal +prtext 0, 34, LGREY, txt_goal, txt_goale-txt_goal +pi_goal: +prtext 24, 4, DGREY, txt_hint, txt_hinte-txt_hint lda mzsize ; this level's lattice: a square @@ -150,6 +157,7 @@ ps_exit: lda #COL_STING sta SPRCOL+SP_STING + jsr place_relics ; first: the enemies then avoid their cells jsr spawn_enemies ; input state: fire still held from the previous screen @@ -530,6 +538,165 @@ sn_free: etypecol: !byte COL_SCARAB, COL_SCORP +; ============================================================ +; the relics (relic order option) - numbered treasures on the +; maze cells; the worker must collect them in order before +; the portal will let him out +; ============================================================ + +; ---- scatter this level's relics over random cells: one +; more each level, 2 on level 1 up to 9 on level 8 ---- +place_relics: + lda #0 + sta rnext + sta rcount + sta ri + lda opt_relic + beq pl_done + lda mzsize ; level 1-8 -> 2-9 relics + sec + sbc #MZMIN-2 + sta rcount +pl_one: + jsr rand8 + and #$0f + cmp mzcw + bcs pl_one + asl ; cell column -> screen column + sec + adc offx + sta ttx +pl_ry: + jsr rand8 + and #$0f + cmp mzch + bcs pl_ry + asl + sec + adc offy + sta tty + lda ttx ; never on the worker's own starting cell... + cmp px + bne pl_free + lda tty + cmp py + beq pl_one +pl_free: + jsr relic_at ; ...nor on a cell already holding one + bcs pl_one + ldx ri + lda ttx + sta rlx,x + lda tty + sta rly,x + jsr draw_relic + inc ri + lda ri + cmp rcount + bne pl_one +pl_done: + rts + +; ---- stamp relic X on (ttx, tty): its order is its digit ---- +draw_relic: + txa + clc + adc #CH_RELIC + pha + ldx tty + jsr row_ptr + ldy ttx + pla + sta (scrlo),y + lda #COL_RELIC + sta (collo),y + rts + +; ---- C set when one of the ri placed relics sits on +; (ttx, tty) - the placement loop's duplicate check ---- +relic_at: + ldx ri + beq rlk_no +rlk_loop: + dex + lda rlx,x + cmp ttx + bne rlk_next + lda rly,x + cmp tty + beq rlk_yes +rlk_next: + cpx #0 + bne rlk_loop +rlk_no: + clc + rts +rlk_yes: + sec + rts + +; ---- the worker stepped onto a cell: if it holds the relic +; that is next in the order, it goes into his hands ---- +take_relic: + lda opt_relic + beq tk_done + ldx rnext + cpx rcount + bcs tk_done ; all of them already in hand + lda rlx,x + cmp px + bne tk_done + lda rly,x + cmp py + bne tk_done + ldx py ; the cell goes back to plain corridor + jsr row_ptr + ldy px + lda #CH_FLOOR + sta (scrlo),y + lda #BLACK + sta (collo),y + inc rnext + lda #4 ; a white border blip marks the find + sta bflash + lda #WHITE + sta bcolor + lda #SFX_SELECT + jmp sfx_play +tk_done: + rts + +; ---- C set when the portal will take the worker: always in +; the classic game, once every relic is his in relic mode ---- +exit_open: + lda opt_relic + beq eo_yes + lda rnext + cmp rcount + bcc eo_no +eo_yes: + sec + rts +eo_no: + clc + rts + +; ---- C set when the char in A can be walked or flown +; through: the corridor floor and the relics lying on it ---- +open_ch: + cmp #CH_FLOOR + beq oc_yes + cmp #CH_RELIC ; the relic digits sit on open floor + bcc oc_no + cmp #CH_RELIC+NRELIC + bcs oc_no +oc_yes: + sec + rts +oc_no: + clc + rts + ; ============================================================ ; one frame of play ; ============================================================ @@ -570,6 +737,7 @@ pu_nocool: jsr upd_cursor jsr upd_flash jsr upd_status + jsr upd_goal jsr upd_clock jmp upd_sprites pu_end: @@ -658,9 +826,9 @@ ur_try: ldy ttx lda (scrlo),y cmp #CH_EXIT - beq ur_win - cmp #CH_FLOOR - bne ur_stop ; a wall or a post: blocked + beq ur_exit + jsr open_ch + bcc ur_stop ; a wall or a post: blocked jsr enemy_at ; walking into an enemy still hurts bcs ur_dead lda es_on ; and so does the venom sting @@ -676,12 +844,17 @@ ur_go: sta px lda tty sta py + jmp take_relic ; the cell may hold the next relic ur_stop: rts -ur_win: +ur_exit: + jsr exit_open + bcc ur_sealed lda #1 sta endflag rts +ur_sealed: ; relics still out there: the portal is shut + jmp deny_blip ur_dead: lda #2 sta endflag @@ -703,8 +876,8 @@ upd_shot: jsr row_ptr ldy ttx lda (scrlo),y - cmp #CH_FLOOR - beq sh_open + jsr open_ch + bcs sh_open lda #0 ; a wall (or the portal) stops it sta s_on jmp sh_spawn @@ -881,7 +1054,7 @@ do_rotate: lda cool beq dr_ready dr_deny: - jmp rot_deny ; still recharging / a slot is taken + jmp deny_blip ; still recharging / a slot is taken dr_ready: lda kx ; nobody may stand in the four slots sta ttx @@ -961,8 +1134,10 @@ dr_ready: dr_nocool: lda #SFX_ROTATE jmp sfx_play -rot_deny: - lda #6 ; red border blip on a denied spin +; ---- red border blip + buzz: a denied spin, or the worker +; knocking on a portal the relics have not opened yet ---- +deny_blip: + lda #6 sta bflash lda #RED sta bcolor @@ -1126,8 +1301,8 @@ en_norun: jsr row_ptr ldy ttx lda (scrlo),y - cmp #CH_FLOOR - beq en_open + jsr open_ch + bcs en_open sec rts en_open: @@ -1247,8 +1422,8 @@ ust_norun: jsr row_ptr ldy ttx lda (scrlo),y - cmp #CH_FLOOR - beq ust_fly + jsr open_ch + bcs ust_fly lda #0 ; a wall swallows it sta es_on rts @@ -1268,6 +1443,8 @@ upd_flash: ldx exrow jsr row_ptr ldy excol + jsr exit_open + bcc uf_exlock lda framectr and #8 bne uf_exw @@ -1275,6 +1452,9 @@ upd_flash: jmp uf_exset uf_exw: lda #WHITE + jmp uf_exset +uf_exlock: + lda #DGREY ; sealed while a relic is still missing uf_exset: sta (collo),y lda rotflash ; the spun walls flash, then re-highlight @@ -1298,6 +1478,29 @@ uf_hot: lda #COL_HILITE uf_cset: sta SPRCOL+SP_CUR + ; fall through: the relic he is after next pulses white +relic_blink: + lda opt_relic + beq rb_done + ldx rnext + cpx rcount + bcs rb_done + lda rlx,x + sta ptmp + lda rly,x + tax + jsr row_ptr + ldy ptmp + lda framectr + and #8 + bne rb_white + lda #COL_RELIC + jmp rb_set +rb_white: + lda #WHITE +rb_set: + sta (collo),y +rb_done: rts ; ---- status row: the spin recharge readout (cooldown option) @@ -1350,6 +1553,38 @@ us_clr: bpl us_clr rts +; ---- relic mode's readout in the goal corner: relics in +; hand / relics on the level, next to the portal glyph; green +; once the last one is his ---- +upd_goal: + lda opt_relic + bne ug_show + rts +ug_show: + lda rnext + ora #$30 + sta SCREEN+34 + lda #$2f ; '/' + sta SCREEN+35 + lda rcount + ora #$30 + sta SCREEN+36 + lda #$20 + sta SCREEN+37 + lda #CH_EXIT + sta SCREEN+38 + jsr exit_open + lda #LRED + bcc ug_col + lda #LGREEN +ug_col: + ldx #4 +ug_loop: + sta COLRAM+34,x + dex + bpl ug_loop + rts + ; ---- the run clock, live on the status row ---- upd_clock: lda tmin @@ -1529,6 +1764,9 @@ exrow: !byte 0 ; screen row of the exit portal endflag: !byte 0 ; 0 = playing, 1 = escaped, 2 = caught cool: !byte 0 ; spin recharge frames left (cooldown option) rotflash:!byte 0 ; frames the freshly spun walls stay white +rcount: !byte 0 ; relics on this level (relic order option) +rnext: !byte 0 ; the one he must pick up next +ri: !byte 0 ; relic placement loop index s_on: !byte 0 ; bullet in flight? sx: !byte 0 ; bullet position and heading sy: !byte 0 @@ -1579,6 +1817,8 @@ ex: !fill NENEMIES, 0 ; enemy positions and headings ey: !fill NENEMIES, 0 edir: !fill NENEMIES, 0 edead: !fill NENEMIES, 0 ; respawn countdown, 0 = alive +rlx: !fill NRELIC, 0 ; relic positions (screen col / row) +rly: !fill NRELIC, 0 ; ---- maze generator buffers ---- visited: !fill NCELLS, 0