Skip to navigation


Drawing ships: DOEXP

[NES version, Bank 1]

Name: DOEXP [Show more] Type: Subroutine Category: Drawing ships Summary: Draw an exploding ship Deep dive: Drawing explosion clouds Generating random numbers
Context: See this subroutine in context in the source code References: This subroutine is called as follows: * LL9 (Part 1 of 12) calls DOEXP * LL9 (Part 9 of 12) calls DOEXP * DrawExplosionBurst calls via EXS1

Other entry points: EXS1 Set (A X) = (A R) +/- random * cloud size
.EX2 LDA INWK+31 ; Set bits 5 and 7 of the ship's byte #31 to denote that ORA #%10100000 ; the ship is exploding and has been killed STA INWK+31 .dexp1 JMP HideExplosionBurst ; Hide the four sprites that make up the explosion burst ; and return from the subroutine using a tail call EQUB $00, $02 ; These bytes appear to be unused .DOEXP SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA INWK+6 ; Set T = z_lo STA T LDA INWK+7 ; Set A = z_hi, so (A T) = z CMP #32 ; If z_hi < 32, skip the next two instructions BCC P%+6 LDA #$FE ; Set A = 254 and jump to yy (this BNE is effectively a BNE yy ; JMP, as A is never zero) ASL T ; Shift (A T) left twice ROL A ASL T ROL A SEC ; And then shift A left once more, inserting a 1 into ROL A ; bit 0 ; Overall, the above multiplies A by 8 and makes sure it ; is at least 1, to leave a one-byte distance in A. We ; can use this as the distance for our cloud, to ensure ; that the explosion cloud is visible even for ships ; that blow up a long way away .yy STA Q ; Store the distance to the explosion in Q LDA INWK+34 ; Set A to the cloud counter from byte #34 of the ship's ; data block ADC #4 ; Add 4 to the cloud counter, so it ticks onwards every ; we redraw it BCS EX2 ; If the addition overflowed, jump up to EX2 to update ; the explosion flags and return from the subroutine STA INWK+34 ; Store the updated cloud counter in byte #34 of the ; ship data block JSR DVID4 ; Calculate the following: ; ; (P R) = 256 * A / Q ; = 256 * cloud counter / distance ; ; We are going to use this as our cloud size, so the ; further away the cloud, the smaller it is, and as the ; cloud counter ticks onward, the cloud expands SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA P ; Set A = P, so we now have: ; ; (A R) = 256 * cloud counter / distance CMP #$1C ; If A < 28, skip the next two instructions BCC P%+6 LDA #$FE ; Set A = 254 and skip the following (this BNE is BNE LABEL_1 ; effectively a JMP as A is never zero) ASL R ; Shift (A R) left three times to multiply by 8 ROL A ASL R ROL A ASL R ROL A ; Overall, the above multiplies (A R) by 8 to leave a ; one-byte cloud size in A, given by the following: ; ; A = 8 * cloud counter / distance .LABEL_1 STA cloudSize ; Store the cloud size in cloudSize so we can access it ; later LDA INWK+31 ; Clear bit 6 of the ship's byte #31 to denote that the AND #%10111111 ; explosion has not yet been drawn STA INWK+31 AND #%00001000 ; If bit 3 of the ship's byte #31 is clear, then nothing BEQ dexp1 ; is being drawn on-screen for this ship anyway, so ; return from the subroutine LDA INWK+7 ; If z_hi = 0 then jump to PTCLS to draw the explosion BEQ PTCLS ; cloud (but not the explosion burst, as the ship is too ; close for the burst sprites to look good) LDY INWK+34 ; Fetch byte #34 of the ship data block, which contains ; the cloud counter CPY #24 ; If Y >= 24 then jump to PTCLS to draw the explosion BCS PTCLS ; cloud (but not the explosion burst as the explosion is ; already past that point) ; If we get here then the exploding ship is not too ; close and we haven't yet counted past the initial part ; of the explosion, so we can show the explosion burst ; using the explosion sprites JMP DrawExplosionBurst ; Draw the exploding ship along with an explosion burst, ; returning from the subroutine using a tail call .PTCLS ; This part of the routine actually draws the explosion ; cloud JSR HideExplosionBurst ; Hide the four sprites that make up the explosion burst LDA cloudSize ; Fetch the cloud size that we stored above, and store STA Q ; it in Q LDA INWK+34 ; Fetch byte #34 of the ship data block, which contains ; the cloud counter BPL P%+4 ; If the cloud counter < 128, then we are in the first ; half of the cloud's existence, so skip the next ; instruction EOR #$FF ; Flip the value of A so that in the second half of the ; cloud's existence, A counts down instead of up LSR A ; Divide A by 16 so that is has a maximum value of 7 LSR A LSR A LSR A ORA #1 ; Make sure A is at least 1 and store it in U, to STA U ; give us the number of particles in the explosion for ; each vertex LDY #7 ; Fetch byte #7 of the ship blueprint, which contains LDA (XX0),Y ; the explosion count for this ship (i.e. the number of STA TGT ; vertices used as origins for explosion clouds) and ; store it in TGT LDA RAND+1 ; Fetch the current random number seed in RAND+1 and PHA ; store it on the stack, so we can re-randomise the ; seeds when we are done LDY #6 ; Set Y = 6 to point to the byte before the first vertex ; coordinate we stored on the XX3 heap above (we ; increment it below so it points to the first vertex) .EXL5 LDX #3 ; We are about to fetch a pair of coordinates from the ; XX3 heap, so set a counter in X for 4 bytes .dexp2 INY ; Increment the index in Y so it points to the next byte ; from the coordinate we are copying LDA XX3-7,Y ; Copy byte Y-7 from the XX3 heap to the X-th byte of K3 STA K3,X DEX ; Decrement the loop counter BPL dexp2 ; Keep copying vertex coordinates into K3 until we have ; copied all six coordinates ; The above loop copies the vertex coordinates from the ; XX3 heap to K3, reversing them as we go, so it sets ; the following: ; ; K3+3 = x_lo ; K3+2 = x_hi ; K3+1 = y_lo ; K3+0 = y_hi STY CNT ; Set CNT to the index that points to the next vertex on ; the XX3 heap ; This next part copies bytes #37 to #40 from the ship ; data block into the four random number seeds in RAND ; to RAND+3, EOR'ing them with the vertex index so they ; are different for every vertex. This enables us to ; generate random numbers for drawing each vertex that ; are random but repeatable, which we need when we ; redraw the cloud to remove it ; ; We set the values of bytes #37 to #40 randomly in the ; LL9 routine before calling DOEXP, so the explosion ; cloud is random but repeatable LDY #37 ; Set Y to act as an index into the ship data block for ; byte #37 LDA (INF),Y ; Set the seed at RAND to byte #37, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND ; vertex INY ; Increment Y to point to byte #38 LDA (INF),Y ; Set the seed at RAND+1 to byte #38, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND+1 ; vertex INY ; Increment Y to point to byte #39 LDA (INF),Y ; Set the seed at RAND+2 to byte #39, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND+2 ; vertex INY ; Increment Y to point to byte #40 LDA (INF),Y ; Set the seed at RAND+3 to byte #49, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND+3 ; vertex LDY U ; Set Y to the number of particles in the explosion for ; each vertex, which we stored in U above. We will now ; use this as a loop counter to iterate through all the ; particles in the explosion .EXL4 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 CLC ; This contains the code from the DORND2 routine, so LDA RAND ; this section is exactly equivalent to a JSR DORND2 ROL A ; call, but is slightly faster as it's been inlined TAX ; (so it sets A and X to random values, making sure ADC RAND+2 ; the C flag doesn't affect the outcome) STA RAND STX RAND+2 LDA RAND+1 TAX ADC RAND+3 STA RAND+1 STX RAND+3 STA ZZ ; Set ZZ to a random number LDA K3+1 ; Set (A R) = (y_hi y_lo) STA R ; = y LDA K3 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = y +/- random * cloud size BNE EX11 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to EX11 to do ; the next particle CPX Yx2M1 ; If X > the y-coordinate of the bottom of the screen BCS EX11 ; (which is in Yx2M1) then the particle is off the ; bottom of the screen, so jump to EX11 to do the next ; particle ; Otherwise X contains a random y-coordinate within the ; cloud STX Y1 ; Set Y1 = our random y-coordinate within the cloud LDA K3+3 ; Set (A R) = (x_hi x_lo) STA R LDA K3+2 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = x +/- random * cloud size BNE EX4 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to EX11 to do ; the next particle ; Otherwise X contains a random x-coordinate within the ; cloud LDA Y1 ; Set A = our random y-coordinate within the cloud JSR PIXEL ; Draw a point at screen coordinate (X, A) with the ; point size determined by the distance in ZZ .EX4 DEY ; Decrement the loop counter for the next particle BPL EXL4 ; Loop back to EXL4 until we have done all the particles ; in the cloud LDY CNT ; Set Y to the index that points to the next vertex on ; the XX3 heap CPY TGT ; If Y < TGT, which we set to the explosion count for BCC EXL5 ; this ship (i.e. the number of vertices used as origins ; for explosion clouds), loop back to EXL5 to do a cloud ; for the next vertex PLA ; Restore the current random number seed to RAND+1 that STA RAND+1 ; we stored at the start of the routine LDA K%+6 ; Store the z_lo coordinate for the planet (which will STA RAND+3 ; be pretty random) in the RAND+3 seed RTS ; Return from the subroutine .EX11 CLC ; This contains the code from the DORND2 routine, so LDA RAND ; this section is exactly equivalent to a JSR DORND2 ROL A ; call, but is slightly faster as it's been inlined TAX ; (so it sets A and X to random values, making sure ADC RAND+2 ; the C flag doesn't affect the outcome) STA RAND STX RAND+2 LDA RAND+1 TAX ADC RAND+3 STA RAND+1 STX RAND+3 JMP EX4 ; We just skipped a particle, so jump up to EX4 to do ; the next one .EXS1 ; This routine calculates the following: ; ; (A X) = (A R) +/- random * cloud size ; ; returning with the flags set for the high byte in A STA S ; Store A in S so we can use it later CLC ; This contains the code from the DORND2 routine, so LDA RAND ; this section is exactly equivalent to a JSR DORND2 ROL A ; call, but is slightly faster as it's been inlined TAX ; (so it sets A and X to random values, making sure ADC RAND+2 ; the C flag doesn't affect the outcome) STA RAND STX RAND+2 LDA RAND+1 TAX ADC RAND+3 STA RAND+1 STX RAND+3 ROL A ; Set A = A * 2 BCS EX5 ; If bit 7 of A was set (50% chance), jump to EX5 JSR FMLTU ; Set A = A * Q / 256 ; = random << 1 * projected cloud size / 256 ADC R ; Set (A X) = (S R) + A TAX ; = (S R) + random * projected cloud size ; ; where S contains the argument A, starting with the low ; bytes LDA S ; And then the high bytes ADC #0 RTS ; Return from the subroutine .EX5 JSR FMLTU ; Set T = A * Q / 256 STA T ; = random << 1 * projected cloud size / 256 LDA R ; Set (A X) = (S R) - T SBC T ; TAX ; where S contains the argument A, starting with the low ; bytes LDA S ; And then the high bytes SBC #0 RTS ; Return from the subroutine