Prev: B89C Up: Map Next: BACD
B916: Render mask buffer
This renders the mask buffer. The game uses a series of RLE encoded masks on characters to cut away pixels belonging to foreground objects. This ensures that characters aren't drawn atop of walls, huts and fences. This routine works out which masks intersect with the current player position then overlays them into the mask buffer at $8100.
At 32x40 pixels the mask buffer is small: sized to fit a single character.
Used by the routine at plot_sprites.
Set all the bits in the mask buffer at $8100..$819F. A clear bit in this buffer means transparent; a set bit means opaque.
render_mask_buffer B916 LD HL,$8100 Point HL at the mask buffer
B919 LD (HL),$FF Set its first byte to $FF
B91B LD DE,$8101 Do a rolling fill: copy the first byte to the second and so on until the buffer is filled
B91E LD BC,$009F
B921 LDIR
B923 LD A,($68A0) Get the global current room index
B926 AND A Are we outdoors?
B927 JR Z,rmb_outdoors Jump if so
We're indoors - use the interior mask structures.
rmb_indoors B929 LD HL,$81DA Point HL at interior_mask_data_count
B92C LD A,(HL) Fetch the count of interior masks
B92D AND A Is the count zero?
B92E RET Z Return if so - there are no masks to render
B92F LD B,A B is now our outermost loop counter
B930 INC HL Point HL at interior_mask_data[0] + 2 bytes (mask.bounds.x1)
B931 INC HL
B932 INC HL
B933 JR rmb_per_mask_loop
We're outdoors - use the exterior mask structures.
Bug: The mask count of 59 here doesn't match the length of exterior_mask_data[] which is only 58 entries long.
rmb_outdoors B935 LD B,$3B Set B for 59 iterations
B937 LD HL,$EC03 Point HL at exterior_mask_data[0] + 2 bytes (mask.bounds.x1)
Fill the mask buffer with the union of all matching masks.
Skip any masks which don't overlap the character. 'mask.bounds' is a bounding on the map image (in isometric projected map space). 'mask.pos' is a map coordinate (in map space). We use these to cull those masks which don't intersect with the character being rendered and those which are behind the character on the map.
Start loop
rmb_per_mask_loop B93A PUSH BC Preserve the mask loop counter
B93B PUSH HL Preserve the mask data pointer
X axis part.
B93C LD A,($81B5) Compute iso_pos_x - 1
B93F DEC A
Reject if the vischar's left edge is beyond the mask's right edge.
B940 CP (HL) Is (iso_pos_x - 1) >= mask.bounds.x1?
B941 JP NC,rmb_pop_next Jump if so (process the next mask)
Reject if the vischar's right edge is beyond the mask's left edge.
B944 ADD A,$04 Compute (iso_pos_x - 1) + 4
B946 DEC HL Point HL at mask.bounds.x0
B947 CP (HL) Is (iso_pos_x - 1) + 4 < mask.bounds.x0?
B948 JP C,rmb_pop_next Jump if so (process the next mask)
Y axis part.
B94B INC HL Point HL at mask.bounds.y1
B94C INC HL
B94D INC HL
B94E LD A,($81B6) Compute iso_pos_y - 1
B951 DEC A
Reject if the vischar's top edge is beyond the mask's bottom edge.
B952 CP (HL) Is (iso_pos_y - 1) >= mask.bounds.y1?
B953 JP NC,rmb_pop_next Jump if so (process the next mask)
Reject if the vischar's bottom edge is beyond the mask's top edge.
B956 ADD A,$05 Compute (iso_pos_y - 1) + 5
B958 DEC HL Point HL at mask.bounds.y0
B959 CP (HL) Is (iso_pos_y - 1) + 5 < mask.bounds.y0?
B95A JP C,rmb_pop_next Jump if so (process the next mask)
Skip masks which the character is in front of. A character is in front of a mask when either of its coordinates are less than mask.pos.
tinypos_stash contains the vischar's mi.pos scaled as required.
Reject if the character's X coordinate is not beyond mask.pos.x.
B95D INC HL Advance HL to mask.pos.x
B95E INC HL
B95F LD A,($81B2) Fetch tinypos_stash_x
B962 CP (HL) Is tinypos_stash_x <= mask.pos.x?
B963 JP Z,rmb_pop_next Jump if equal
B966 JP C,rmb_pop_next Or jump if less than
Reject if the character's Y coordinate is not beyond mask.pos.y.
B969 INC HL Advance HL to mask.pos.y
B96A LD A,($81B3) Fetch tinypos_stash_y
B96D CP (HL) Is tinypos_stash_y < mask.pos.y?
B96E JP C,rmb_pop_next Jump if so
Reject if the character's height is beyond mask.pos.height.
B971 INC HL Advance HL to mask.pos.height
B972 LD A,($81B4) Fetch tinypos_stash_height
B975 AND A If tinypos_stash_height is non-zero: add one
B976 JR Z,render_mask_buffer_0
B978 DEC A
render_mask_buffer_0 B979 CP (HL) Is tinypos_stash_height >= mask.pos.height?
B97A JP NC,rmb_pop_next Jump if so (process the next mask)
B97D LD A,L Step HL back to mask.bounds.x0
B97E SUB $06
B980 LD L,A
B981 JR NC,render_mask_buffer_1
B983 DEC H
The mask is valid: now calculate the clipped dimensions.
X axis part.
render_mask_buffer_1 B984 LD A,($81B5) Fetch iso_pos_x
B987 LD C,A Copy it to C for use later
B988 CP (HL) Is iso_pos_x >= mask.bounds.x0?
B989 JP C,render_mask_buffer_3 Jump if so
If we arrive here then mask.bounds.x0 is to the left of iso_pos_x. This means that the mask starts beyond the left edge of our render buffer.
Set mask_left_skip to <left hand skip> and mask_run_width to <maximum width???>.
B98C SUB (HL) Set left hand skip to (iso_pos_x - mask.bounds.x0)
B98D LD ($B837),A
B990 INC HL Advance HL to mask.bounds.x1
B991 LD A,(HL) Set run width to (mask.bounds.x1 - iso_pos_x)
B992 SUB C
B993 CP $03 The run width must not exceed 4 (byte width of the render buffer)
B995 JR C,render_mask_buffer_2
B997 LD A,$03
render_mask_buffer_2 B999 INC A
B99A LD ($B83A),A Store run width (how much of the mask to draw)
B99D JR render_mask_buffer_5 (else)
If we arrive here then mask.bounds.x0 is to the right of iso_pos_x.
Set mask_left_skip to zero and mask_run_width to <maximum width???>.
render_mask_buffer_3 B99F LD B,(HL) Fetch mask.bounds.x0
B9A0 XOR A Set left hand skip to zero
B9A1 LD ($B837),A
B9A4 LD A,B Calculate maximum remaining space: (iso_pos_x + 4 - mask.bounds.x0)
B9A5 SUB C
B9A6 LD C,A
B9A7 LD A,$04
B9A9 SUB C
B9AA LD C,A
B9AB INC HL Advance HL to mask.bounds.x1
B9AC LD A,(HL) Calculate total mask width: (mask.bounds.x1 - mask.bounds.x0) + 1
B9AD SUB B
B9AE INC A
B9AF CP C Choose the minimum of the two possible run widths
B9B0 JR C,render_mask_buffer_4
B9B2 LD A,C
render_mask_buffer_4 B9B3 LD ($B83A),A Store mask_run_width
Y axis part.
render_mask_buffer_5 B9B6 INC HL
B9B7 LD A,($81B6) Fetch iso_pos_y
B9BA LD C,A Copy it to C for use later
B9BB CP (HL) Is iso_pos_y >= mask.bounds.y0?
B9BC JP C,render_mask_buffer_7 Jump if so
If we arrive here then mask.bounds.y0 is above iso_pos_y. This means that the mask starts beyond the top edge of our render buffer.
Set mask_top_skip to <top skip> and mask_run_height to <maximum height???>.
B9BF SUB (HL) Set top skip to (iso_pos_y - mask.bounds.y0)
B9C0 LD ($B838),A
B9C3 INC HL Advance HL to mask.bounds.y1
B9C4 LD A,(HL) Set run height to (mask.bounds.y1 - iso_pos_y)
B9C5 SUB C
B9C6 CP $04 The run height must not exceed 5 (UDG height of the render buffer)
B9C8 JR C,render_mask_buffer_6
B9CA LD A,$04
render_mask_buffer_6 B9CC INC A
B9CD LD ($B839),A Store run height (how much of the mask to draw)
B9D0 JR render_mask_buffer_9 (else)
If we arrive here then mask.bounds.y0 is below iso_pos_y.
Set mask_top_skip to zero and mask_run_height to <maximum height???>.
render_mask_buffer_7 B9D2 LD B,(HL) Fetch mask.bounds.y0
B9D3 XOR A Set top skip to zero
B9D4 LD ($B838),A
B9D7 LD A,B Calculate maximum remaining space: (iso_pos_y + 5 - mask.bounds.y0)
B9D8 SUB C
B9D9 LD C,A
B9DA LD A,$05
B9DC SUB C
B9DD LD C,A
B9DE INC HL Advance HL to mask.bounds.y1
B9DF LD A,(HL) Calculate total mask height: (mask.bounds.y1 - mask.bounds.y0) + 1
B9E0 SUB B
B9E1 INC A
B9E2 CP C Choose the minimum of the two possible run heights
B9E3 JR C,render_mask_buffer_8
B9E5 LD A,C
render_mask_buffer_8 B9E6 LD ($B839),A Store mask_run_height
Calculate the initial mask buffer pointer.
render_mask_buffer_9 B9E9 DEC HL Step HL back to mask.bounds.y0
B9EA LD BC,$0000 Initialise (x,y) vars to zero
When the mask has a top or left hand gap, calculate that.
B9ED LD A,($B838) If mask_top_skip is zero, calculate buf_top_skip = (mask.bounds.y0 - iso_pos_y)
B9F0 AND A
B9F1 JR NZ,render_mask_buffer_10
B9F3 LD A,($81B6)
B9F6 NEG
B9F8 ADD A,(HL)
B9F9 LD C,A
render_mask_buffer_10 B9FA DEC HL Step back to mask.bounds.x0
B9FB DEC HL
B9FC LD A,($B837) If mask_left_skip is zero, calculate buf_left_skip = (mask.bounds.x0 - iso_pos_x)
B9FF AND A
BA00 JR NZ,render_mask_buffer_11
BA02 LD A,($81B5)
BA05 NEG
BA07 ADD A,(HL)
BA08 LD B,A
render_mask_buffer_11 BA09 DEC HL Step back to mask.index
BA0A LD A,(HL) Load it
BA0B EX AF,AF' Bank it
buf_top_skip is in C. buf_left_skip is in B. The multiplier 32 is MASK_BUFFER_ROWBYTES.
BA0C LD A,C Calculate A = (buf_top_skip * 32 + buf_left_skip)
BA0D ADD A,A
BA0E ADD A,A
BA0F ADD A,A
BA10 ADD A,A
BA11 ADD A,A
BA12 ADD A,B
BA13 LD HL,$8100 Add A to mask_buffer to get the mask buffer pointer
BA16 ADD A,L
BA17 LD L,A
BA18 LD ($81A0),HL Save the mask buffer pointer
BA1B EX AF,AF' Unbank index
BA1C ADD A,A Point DE at mask_pointers[index]
BA1D LD C,A
BA1E LD B,$00
BA20 LD HL,$EBC5
BA23 ADD HL,BC
BA24 LD E,(HL)
BA25 INC HL
BA26 LD D,(HL)
BA27 LD HL,($B839) Load (L,H) with mask_run_height,mask_run_width
BA2A LD A,L Self modify clip height loop
BA2B LD ($BA70),A
BA2E LD A,H Self modify clip width loop
BA2F LD ($BA72),A
BA32 LD A,(DE) Self modify mask row skip = (mask width) - mask_run_width
BA33 SUB H
BA34 LD ($BA90),A
BA37 LD A,$20 Self modify buffer row skip = MASK_BUFFER_ROWBYTES - mask_run_width
BA39 SUB H
BA3A LD ($BABA),A
Skip the initial clipped mask bytes. The masks are RLE compressed so we can't jump directly to the first byte we need.
First, calculate the total number of mask tiles to skip.
BA3D PUSH DE Preserve mask data pointer
BA3E LD A,(DE) Fetch the mask's width
BA3F LD E,A
BA40 LD A,($B838) Fetch mask_top_skip
BA43 CALL multiply Multiply mask_top_skip by mask_width. Result is returned in HL. Note: D and B are zeroed
BA46 LD A,($B837) Fetch mask_left_skip
BA49 LD E,A
BA4A ADD HL,DE Add mask_left_skip to HL
BA4B POP DE Restore mask data pointer
BA4C INC HL + 1 [Explain]
Next, loop until we've stepped over that number of mask tiles.
Start loop
rmb_more_to_skip BA4D LD A,(DE) Read a byte. It could be a repeat count or a tile index
BA4E AND A Is the MASK_RUN_FLAG set?
BA4F JP P,rmb_skipping_index Jump if clear: it's a tile index (JP P => positive)
BA52 AND $7F Otherwise mask it off, giving the repeat count
BA54 INC DE Advance the mask data pointer (over the count)
BA55 LD C,A Decrease mask_skip by repeat count (B is zeroed by multiply call above)
BA56 SBC HL,BC
BA58 JR C,rmb_skip_went_negative Jump if it went negative
BA5A INC DE Otherwise advance the mask data pointer (over the value) (doesn't affect flags)
BA5B JR NZ,rmb_more_to_skip ...loop while mask_skip > 0
BA5D XOR A Otherwise mask_skip must be zero. Zero the counter
BA5E JR rmb_start_drawing Jump to rmb_start_drawing
rmb_skipping_index BA60 INC DE Advance the mask data pointer
BA61 DEC HL Decrement mask_skip
BA62 LD A,L ...loop while mask_skip > 0
BA63 OR H
BA64 JP NZ,rmb_more_to_skip
BA67 JR rmb_start_drawing Jump to rmb_start_drawing
rmb_skip_went_negative BA69 LD A,L How far did we overshoot?
BA6A NEG Negate it to create a positive mask emit(?) count
A = Count of blocks to emit. DE = Points to a value (if -ve case) .. but the upcoming code expects a flag byte...
rmb_start_drawing BA6C LD HL,($81A0) Get the mask buffer pointer
BA6F LD C,$01 Set C for (self modified height) iterations
Start loop
render_mask_buffer_12 BA71 LD B,$01 Set B for (self modified width) iterations
Start loop
Commentary: The banking of A is hard to follow here. It's difficult to see if this section is entered with a skip value whether it will be handled correctly.
render_mask_buffer_13 BA73 EX AF,AF' Bank the <repeat length>
BA74 LD A,(DE) Read a byte. It could be a repeat count or a tile index
BA75 AND A Is the MASK_RUN_FLAG set?
BA76 JP P,render_mask_buffer_14 Jump if clear: it's a tile index (JP P => positive)
BA79 AND $7F Otherwise mask it off, giving the repeat count
BA7B EX AF,AF' Bank the repeat count; unbank the <repeat length> REPLACING IT?
BA7C INC DE Advance the mask data pointer
BA7D LD A,(DE) Read the next byte (a tile)
Shortcut tile 0 which is blank.
render_mask_buffer_14 BA7E AND A Is it tile zero?
BA7F CALL NZ,mask_against_tile Call mask_against_tile if not
BA82 INC L Advance mask buffer pointer (a tile was written)
BA83 EX AF,AF' Unbank the repeat count OR <repeat length> CONFUSING
Advance the mask pointer when the repeat count reaches zero.
BA84 AND A Is it zero?
BA85 JR Z,render_mask_buffer_15 Jump if so
BA87 DEC A Otherwise decrement the repeat count
BA88 JR Z,render_mask_buffer_15 Jump if it hit zero
BA8A DEC DE Otherwise undo the next instruction
render_mask_buffer_15 BA8B INC DE Advance the mask data pointer
BA8C DJNZ render_mask_buffer_13 ...loop (width)
BA8E PUSH BC Preserve the loop counter (B will be zero here, C = y)
BA8F LD B,$01 Set B for (self modified, right hand skip) iterations
BA91 EX AF,AF' Bank the repeat count while we test B
BA92 LD A,B Is the right hand skip zero?
BA93 AND A
BA94 JP Z,rmb_next_line Jump if so (process next mask)
BA97 EX AF,AF' Unbank the repeat count
BA98 AND A Is it zero?
BA99 JR NZ,rmb_trailskip_dive_in Jump if not: (CHECK) must be continuing with a nonzero repeat count
Start loop
rmb_trailskip_more_to_skip BA9B LD A,(DE) Read a byte. It could be a repeat count or a tile index
BA9C AND A Is the MASK_RUN_FLAG set?
BA9D JP P,rmb_something Jump if clear: it's a tile index (JP P => positive)
It's a repeat.
BAA0 AND $7F Otherwise mask it off, giving the repeat count
BAA2 INC DE Advance the mask data pointer
(resume point)
rmb_trailskip_dive_in BAA3 LD C,A right_skip = A = (right_skip - A)
BAA4 LD A,B
BAA5 SUB C
BAA6 LD B,A
BAA7 JR C,rmb_trailskip_negative Jump if it went negative
BAA9 INC DE Advance the mask data pointer (doesn't affect flags)
BAAA JR NZ,rmb_trailskip_more_to_skip if (right_skip > 0) goto START OF LOOP
BAAC EX AF,AF' bank // could jump $BAB8 instead
BAAD JR rmb_next_line Otherwise right_skip must be zero, jump to rmb_next_line
rmb_something BAAF INC DE Advance the mask data pointer
BAB0 DJNZ rmb_trailskip_more_to_skip ...loop
BAB2 XOR A Reset counter (WHAT IS IT?)
BAB3 EX AF,AF' bank // could jump $BAB8 instead
BAB4 JR rmb_next_line Jump to rmb_next_line
rmb_trailskip_negative BAB6 NEG Negate it to create a positive mask emit(?) count
BAB8 EX AF,AF' bank
rmb_next_line BAB9 LD A,$20 Advance HL by (self modified skip)
BABB ADD A,L
BABC LD L,A
BABD EX AF,AF' Unbank the <repeat length> (re-banked when loop continues)
BABE POP BC Restore the height counter
BABF DEC C ...loop (height)
BAC0 JP NZ,render_mask_buffer_12
rmb_pop_next BAC3 POP HL Restore the mask data pointer
BAC4 POP BC Restore the mask loop counter
BAC5 LD DE,$0008 Advance HL to the next mask_t
BAC8 ADD HL,DE
BAC9 DEC B ...loop
BACA JP NZ,rmb_per_mask_loop
Bug: The RET instruction is missing from the end of the routine. If unfixed the routine will harmlessly fall through into multiply.
Prev: B89C Up: Map Next: BACD