;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Panser (improved),  by imamelia
;;
;; This is a Panser, the fireball-spitting plant in SMB2 that stays in one place
;; or moves back and forth.  This one has different behaviors depending on its
;; palette; I tried to make it as close to the original sprite as possible.
;;
;; Uses first extra bit: YES
;;
;; If the first extra bit is clear, the Panser will use the palette specified in the
;; extra property byte 1.  If the first extra bit is set, it will use the palette specified
;; in the extra property byte 2.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; defines
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;incsrc subroutinedefsx.asm

!InitialWaitTime = $60	; the time the sprite should wait before spitting the first fireball
!Fireball = $3D		; set this to the sprite number of the fireball
!FireballYSpeed = $B8	; set this to the initial Y speed the fireball should have
!FireballXSpeed = $16	; set this to the initial X speed the fireball should have
!FireSound = $27		; the sound effect to play when spitting a fireball
!FireSoundBank = $1DFC	; the sound bank to use
!ExtraBits = $08		; the extra bits for the fireball

!UpperBoundX = $31	; one more than the maximum random X speed
!LowerBoundX = $00	; the minimum random X speed
!UpperBoundY = $20	; the maximum random Y speed
!LowerBoundY = $59	; one more than the minimum random Y speed
; Note on the Y speeds: These are negative values, so they are INVERSED.
; Therefore, the lower boundary should actually be HIGHER then the upper one.
; Think of it this way: What would the values be if you set bit 7 of them both,
; i.e. ORA #$80?

FireSpitTimer:			; the time to wait before spitting each fireball
db $58,$20,$58,$20,$20,$58,$20,$58	; I made the table 8 values long so you could
				; have more leeway for the timers

BehavioralProperties:		; some behavior settings for each palette
db $00,$00,$02,$00,$01,$03,$00,$00	; palette 8, 9, A, B, C, D, E, F
; Bit 0 - no movement
; Bit 1 - no X speed for the spawned fireballs
; Bit 2 - *random* XY speeds for the spawned fireballs
; Bit 3 - stay on ledges
; Bit 4 - follow the player (if set to move)
; Bit 5 - jump every now and then

Tilemap:
db $86,$84	; normal frame 1, normal frame 2/fireball-spitting frame

!XSpeed = $0A	; the X speed to give the sprite
!YSpeed = $D0	; the Y speed to give the sprite when it jumps (if set to do so)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; init routine wrapper
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

print "INIT ",pc	; xkas-style init routine marker
PHB		; preserve the current data bank
PHK		; push the program bank onto the stack
PLB		; so we can pull it back as the new data bank
JSR SpriteInit	; jump to the sprite's init routine
PLB		; pull the previous data bank back
RTL		; and return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; init routine
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SpriteInit:

;LDA $7FAB10,x	; check the extra bits
;AND #$04	; if the extra bit is set...
;BNE UseEP2	; use the extra property byte 2 to set the palette
;LDA $7FAB28,x	; if the extra bit is clear,
;BRA StoreByte	; use the extra property byte 1 to set the palette
;UseEP2:		;
;LDA $7FAB34,x	; extra property byte 2 table
;StoreByte:	;
;STA $15F6,x	;

LDA $15F6,x		;
LSR			; we still have the extra property byte in A, so...
AND #$07		; divide it by 2 and get an index from 00-07
TAY			; transfer this to Y
LDA BehavioralProperties,y	; load, well...it's pretty self-explanatory...
STA $1504,x		; store to a misc. sprite table for later

LDA #!InitialWaitTime	; start the fireball timer
STA $1540,x		; set the time before spitting the first fireball

JSL $01ACF9		; semi-random number generator
PHA			;
ORA #$3F			; set the jump timer to a random number
STA $1570,x		; (but it has to be at least 3F)
PLA			;
AND #$07		; values 00-07 (clear the upper 5 bits)
STA $1534,x		; start the timer index at a random number as well

JSR SubHorzPos		;
TYA			;
STA $157C,x		; make the sprite face the player initially

RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; main routine wrapper
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

print "MAIN ",pc	; xkas-style main routine marker
PHB		; preserve the current data bank
PHK		; push the program bank onto the stack
PLB		; so we can pull it back as the new data bank
JSR SpriteMain	; jump to the main sprite routine
PLB		; pull the previous data bank back
RTL		; and return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; main routine
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SpriteMain:

JSR SubPanserGFX	; draw the sprite

LDA $14C8,x	; check the sprite status
CMP #$08		; if it is not in its normal status...
BNE Return0	; return
LDA $9D		; check the sprite lock timer
BNE Return0	; if sprites are locked, return

JSR SubOffscreenX0	; call the offscreen processing code

JSR SubBPH	; a lame abbreviation of "behavioral property handler"

LDA $14		; take the sprite frame counter
LSR		;
LSR		;
LSR		; and divide it by 8
AND #$01	;
STA $1602,x	; to set the animation frame

LDY $1540,x	; BUT if the fireball timer
CPY #$11		; has dropped to 10 or lower...
BCS NotThereYet	; the Panser is about to spit a fireball,
LDA #$01		; so set a constant frame,
STA $1602,x	; the spitting one
NotThereYet:	;
CPY #$00		; if the timer has dropped to zero...
BNE EndMain	; reset it

LDA $1534,x	; take the timer table offset
INC		; increment it once
AND #$07	; clear out the top 5 bits, since we have only 8 values in the table
STA $1534,x	; and set the new table offset
TAY		; transfer this to Y...
LDA FireSpitTimer,y	; and set a new timer depending on this
STA $1540,x	;

JSR SubFireSpit	; jump to the fireball-spitting routine

EndMain:

JSL $01A7DC	; interact with the player
JSL $018032	; interact with other sprites

Return0:

RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; graphics routine
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SubPanserGFX:

LDY $1602,x
LDA Tilemap,y
STA $02

LDA $15F6,x
ORA $64
STA $03

JSL $20CA4F

LDA $00
STA $0300,y

LDA $01
STA $0301,y

LDA $02
STA $0302,y

LDA $03
STA $0303,y

LDY #$02
LDA #$00
JSL $01B7B3

RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; sprite-spawning routine
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

NoSpawn:

RTS

SubFireSpit:

LDA $15A0,x	; if the sprite is offscreen horizontally...
ORA $186C,x	; or offscreen vertically...
ORA $15D0,x	; or being eaten...
BNE NoSpawn	; don't spit a fireball

JSL $02A9DE	; find a free sprite slot for the new sprite
BMI NoSpawn	; if there are none free, return

LDA #!FireSound	; play a sound effect
STA !FireSoundBank	;

LDA #$08		; sprite state:
STA $14C8,y	; normal

PHX		;
TYX		;
LDA #!Fireball	; set the sprite number
STA $7FAB9E,x	;
PLX		;

LDA $E4,x	; set the sprite's X position:
STA $00E4,y	;
LDA $14E0,x	; same as the spawner
STA $14E0,y

LDA $D8,x	; set the sprite's Y position
SEC		;
SBC #$08		; 8 pixels up
STA $00D8,y	;
LDA $14D4,x	;
SBC #$00		;
STA $14D4,y	;

PHX		;
TYX		;
JSL $07F7D2	; clear all old sprite table values
JSL $0187A7	; get new values for a custom sprite
LDA #!ExtraBits	;
STA $7FAB10,x	; set the sprite's extra bits
PLX		;

PHY		;
JSR SubHorzPos	;
TYA		;
PLY		;
STA $157C,y	; set the sprite to face the player

LDA $1504,x	; check the behavioral settings
AND #$02	; if bit 1 is set...
BNE NoXFireball	; don't set any X speed for the fireballs

LDA #!FireballXSpeed
STA $00B6,y	; set the fireball's X speed

NoXFireball:	;

LDA #!FireballYSpeed
STA $00AA,y		; set the fireball's Y speed

LDA $1504,x		; final check:
AND #$04		; if bit 2 is set...
BEQ NoRandomSpeeds	; give the fireballs semi-random XY speeds

RandomGen1:	;
JSL $01ACF9	; random number generator
AND #$7F	;
CMP #!UpperBoundX	; if the result is too high...
BCS RandomGen1	;
CMP #!LowerBoundX	; or too low...
BCC RandomGen1	; try again
STA $00B6,y	; if not, store this to the sprite's X speed table

RandomGen2:	;
JSL $01ACF9	; random number generator
AND #$7F	;
CMP #!UpperBoundY	; if the result is too high...
BCC RandomGen2	;
CMP #!LowerBoundY	; or too low...
BCS RandomGen2	; try again
ORA #$80		; set bit 7 so that the value is always negative (and the fireball goes up)
STA $00AA,y	; if not, store this to the sprite's Y speed table

NoRandomSpeeds:	;

LDA $157C,y	; if the sprite's direction is left...
BEQ NoFlipX	;
LDA $00B6,y	; then flip its X speed
EOR #$FF		;
INC		;
STA $00B6,y	;
NoFlipX:

LDA #$03		; the sprite has gravity and its XY speeds were already set
STA $00C2,y	;

RTS


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; behavioral property handler routine (also handles speed updating)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SubBPH:		; I'll be using this acronym again in the future, so learn to love it.



LDA $1504,x	; behavioral properties
STA $00		; store to scratch RAM because they are used a lot here

AND #$10	; first, check to see if the sprite should follow the player
BEQ NoFollow	; if bit 4 of the BP table is set...
LDA $151C,x	;
BNE NoFollow	; and the sprite isn't already turning...
LDA $13		;
AND #$1F	; then check the frame counter
BNE NoFollow	;
JSR SubHorzPos	; and switch direction to face the player
TYA		; every few frames
STA $157C,x	;

NoFollow:		;

LDA $1588,x	; next, check the sprite's contact
AND #$03	; with walls
BEQ NoWallTouch	; if the sprite is touching a wall...

LDA $157C,x	;
EOR #$01		; flip its direction
STA $157C,x	;

NoWallTouch:

LDA $00		; check the properties
AND #$08	; if bit 3 isn't set...
BEQ MaybeInAir	; don't make the sprite stay on ledges

LDA $1588,x	; if the sprite is on the ground...
ORA $151C,x	; or if it is already turning...
BNE NoFlip	; then don't change direction

LDA $157C,x	;
EOR #$01		; flip the sprite's direction
STA $157C,x	;

LDA $B6,x	; and its speed
EOR #$FF		;
INC		;
STA $B6,x		;

INC $151C,x	; set the turning flag

NoFlip:

LDA $1588,x	;
AND #$04	; check sprite contact with the ground
BEQ Skip1		; skip the next few parts if the sprite is in the air
STZ $AA,x	; zero out the sprite's Y speed
STZ $151C,x	; clear the turn flag
BRA SetXSpeed	;

MaybeInAir:

LDA $1588,x	;
AND #$04	; check sprite contact with the ground
BEQ Skip1		; skip the next few parts if the sprite is in the air
LDA #$10		; give the sprite a little Y speed
STA $AA,x	; so that is has gravity

LDA $1570,x	; if the sprite's jump timer has run out...
BNE SetXSpeed	;
LDA #!YSpeed	; make the sprite jump upward
STA $AA,x	;
JSL $01ACF9	; get a random number
AND #$7F	;
CLC		;
ADC #$50	; from 50 to BF
STA $1570,x	; to use as a reset value for the jump timer

SetXSpeed:

LDA #!XSpeed	; load the sprite's X speed
LDY $157C,x	; load the sprite's direction
BEQ NoSwitchX	; if the sprite is facing left...
EOR #$FF		; then invert its X speed
INC		;
NoSwitchX:	;
STA $B6,x		; store the speed value to the X speed table

Skip1:

LDA $00		; check the property byte
AND #$20	; if bit 5 is not set...
BEQ NoJump	; then freeze the jump timer so that the sprite never jumps
DEC $1570,x	; if bit 5 is set, decrement the jump timer (we don't need to check
NoJump:		; to make sure it isn't zero, because the previous code already did)

LDA $00		; check the property byte again
AND #$01	; if bit 0 is set...
BNE Stationary	; don't update the sprite's position

JSL $01802A	; update sprite position based on speed values

Stationary:	;

RTS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; generic subroutines (retyped and uncommented)
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Table1:              db $0C,$1C
Table2:              db $01,$02
Table3:              db $40,$B0
Table6:              db $01,$FF
Table4:              db $30,$C0,$A0,$C0,$A0,$F0,$60,$90,$30,$C0,$A0,$80,$A0,$40,$60,$B0
Table5:              db $01,$FF,$01,$FF,$01,$FF,$01,$FF,$01,$FF,$01,$FF,$01,$00,$01,$FF

SubHorzPos:

LDY #$00
LDA $94
SEC
SBC $E4,x
STA $0F
LDA $95
SBC $14E0,x
BPL $01
INY
RTS

SubOffscreenX0:
LDA #$00
;BRA SubOffscreenMain
;SubOffscreenX1:
;LDA #$02
;BRA SubOffscreenMain
;SubOffscreenX2:
;LDA #$04
;BRA SubOffscreenMain
;SubOffscreenX3:
;LDA #$06
;BRA SubOffscreenMain
;SubOffscreenX4:
;LDA #$08
;BRA SubOffscreenMain
;SubOffscreenX5:
;LDA #$0A
;BRA SubOffscreenMain
;SubOffscreenX6:
;LDA #$0C
;BRA SubOffscreenMain
;SubOffscreenX7:
;LDA #$0E

SubOffscreenMain:

STA $03

JSR SubIsOffscreen
BEQ Return2

LDA $5B
LSR
BCS VerticalLevel
LDA $D8,x
CLC
ADC #$50
LDA $14D4,x
ADC #$00
CMP #$02
BPL EraseSprite
LDA $167A,x
AND #$04
BNE Return2
LDA $13
AND #$01
ORA $03
STA $01
TAY
LDA $1A
CLC
ADC Table4,y
ROL $00
CMP $E4,x
PHP
LDA $1B
LSR $00
ADC Table5,y
PLP
SBC $14E0,x
STA $00
LSR $01
BCC Label20
EOR #$80
STA $00
Label20:
LDA $00
BPL Return2

EraseSprite:
LDA $14C8,x
CMP #$08
BCC KillSprite
LDY $161A,x
CPY #$FF
BEQ KillSprite
LDA #$00
STA $1938,y
KillSprite:
STZ $14C8,x
Return2:
RTS

VerticalLevel:

LDA $167A,x
AND #$04
BNE Return2
LDA $13
LSR
BCS Return2
AND #$01
STA $01
TAY
LDA $1C
CLC
ADC Table3,y
ROL $00
CMP $D8,x
PHP
LDA $1D
LSR $00
ADC Table6,y
PLP
SBC $14D4,x
STA $00
LDY $02
BEQ Label22
EOR #$80
STA $00
Label22:
LDA $00
BPL Return2
BMI EraseSprite

SubIsOffscreen:
LDA $15A0,x
ORA $186C,x
RTS

