Routines |
Prev: B89C | Up: Map | Next: BACD |
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 |