Calling an AI Action as a subroutine?

I have the default shoot at player AI action, and then I have another script that I want to do some other stuff along with shooting at the player.

Rather than create redundant code, I gave the AI action shoot at player a label, and tried to call it as a subroutine in the other action.

It doesn't cause any errors, but it doesnt work. Nothing happens. It just acts like it shot at the player and moves on to the next action step.

I figured it should work, because I created a subroutine at the bottom of another AI script and called that during the script and that worked.
Does the subroutine have to be defined in the same script for it to work?
 

MistSonata

Moderator
Well, I was going to say that it might be a problem with the two scripts being in different banks, but it looks like they're all in the same bank. You could try calling the script with the label in bank $14 corresponding to the AI action you're trying to call, I suppose.

They're labelled like this:

Code:
AI_Action_00
	.include ROOT\System\AI_ActionRoutines\AI_Action_00.asm
	RTS
AI_Action_01:
.include ROOT\System\AI_ActionRoutines\AI_Action_01.asm
	RTS
AI_Action_02
	.include ROOT\System\AI_ActionRoutines\AI_Action_02.asm
	RTS
AI_Action_03:
	.include ROOT\System\AI_ActionRoutines\AI_Action_03.asm
	RTS
AI_Action_04:	
	.include ROOT\System\AI_ActionRoutines\AI_Action_04.asm
	RTS
AI_Action_05
	.include ROOT\System\AI_ActionRoutines\AI_Action_05.asm
	RTS
AI_Action_06
	.include ROOT\System\AI_ActionRoutines\AI_Action_06.asm
	RTS
AI_Action_07
	.include ROOT\System\AI_ActionRoutines\AI_Action_07.asm
	RTS
AI_Action_08
	.include ROOT\System\AI_ActionRoutines\AI_Action_08.asm
	RTS
AI_Action_09
	.include ROOT\System\AI_ActionRoutines\AI_Action_09.asm
	RTS
AI_Action_10
	.include ROOT\System\AI_ActionRoutines\AI_Action_10.asm
	RTS
AI_Action_11
	.include ROOT\System\AI_ActionRoutines\AI_Action_11.asm
	RTS
AI_Action_12
	.include ROOT\System\AI_ActionRoutines\AI_Action_12.asm
	RTS
AI_Action_13
	.include ROOT\System\AI_ActionRoutines\AI_Action_13.asm
	RTS
AI_Action_14
	.include ROOT\System\AI_ActionRoutines\AI_Action_14.asm
	RTS
AI_Action_15
	.include ROOT\System\AI_ActionRoutines\AI_Action_15.asm
	RTS
 

jorotroid

Member
From what you describe and that Mist pointed out they are in the same bank, it sounds like it should have worked. My first guess is that your AI script is reliant on X having the id of the object that is calling the AI, but when you called the routine from elsewhere in the code, you didn't set X to a value that made sense in that context.
 

MistSonata

Moderator
jorotroid said:
From what you describe and that Mist pointed out they are in the same bank, it sounds like it should have worked. My first guess is that your AI script is reliant on X having the id of the object that is calling the AI, but when you called the routine from elsewhere in the code, you didn't set X to a value that made sense in that context.

That's a good point, but the object ID is stored in the X register in HandleUpdateObjects before the game even switches to the object script bank. So long as you don't change what's in the X register before calling the routine in another script, it should work.

I'd recommend opening the rom up in Mesen and trying to see what it's doing in the debugger. If you can find the point in the program where it attempts to call the routine, you might be able to see where it goes and why it isn't working.

Though, in all honesty, there's plenty of space in that bank I think. Unless the script you're talking about is ridiculously long, I don't see the harm in having a small bit of redundancy.
 

Mugi

Member
i believe the reason he's doing this instead of dublicating the script is exacly because of space issues.
i saw a picture of his bank usage in discord a while ago, and his bank14 is full to the brim.

that said, i havent done jumping with AI scripts but i did do it with tile scripts, and with those it worked just fine (i jump into a tile warp script from multiple places.) so maybe jorotroid has a point that there's simply an issue with loaded values that
makes the script not work... i dont really know.
 

dale_coop

Moderator
Staff member
Agreed. Might be the object in X that is no more the monster...
chronicleroflegends, could you share your script?
 
Oh yea, Mugi is right. My bank 14 is toast (pictured below) and I still have more custom code I want to add. Thats actually why I also had the thread open on possibly opening up bank 18. But I wanted to reduce redundant code in 14 as well.

So what I was working on was an AI script to handle bosses, because I wanted boss behavior to be more complicated than normal enemies. I wanted to give them a completely independent set of behaviors controlled entirely by the boss script, and only use the interface for swapping between action states. So I would have actions 1-14 to use for normal enemies, and action 15 to handle all the bosses.

I wanted the first boss to be able to shoot at the player during one of its states, using the default shoot at player script. At the time, I thought it would be a good idea to add the shoot at player script as a subroutine to the end of the boss. Then I made the normal shoot at player script just a JSR call to that one. I guess it actually would have been better to leave it where it was and JSR from the boss script, but it should work the same either way.

Anyways here are the two scripts, The boss script is still an early WIP:

ShootAtPlayer.asm:
Code:
;; shoot towards player.
;; To save space, I am re-using the same code from the boss code.
JSR bossSRShootTowardPlayer

BossBehavior.asm:
Code:
;; Boss Behavior Script
;; By Chronicler Of Legends
;; This script is a control script for all bosses in the game.
;; NOTE: THIS IS A VERY LARGE SCRIPT, IT WILL TAKE UP A LOT OF SPACE.
;;
;; Because they have complex behavior that would take up multiple scripts on its own, we give the bosses
;; their own programming not controlled by the NESMaker GUI.
;; We just do not have enough room for boss behaviors in our 16 ai scripts.

;; This script is designed to be a start to control all of your games bosses. Some modification for specifics
;; will be necessary, but this framework should help.

;; Same script for all the bosses but different behaviors? How do we differentiate between them?
;; Well we need to define boss rooms. Each boss room will have a unique ID.
;; We will check the ID first and jump to the correct label depending on that.

;; Extra info:
;; If we are here, then monster is in x
;; How to trigger a screen:  TriggerScreen screenType


bossBehaviorStart:
;; Is the screen triggered? If so the boss is already defeated. Make sure it is gone.

bossScreenNotTriggered:
;; The screen is not triggered, so this is the first time encountering the boss. We need to set it up.

;; Which boss is it? Check the screen type.



bossIsBossOne:
;; First Boss

;; Do we need to setup the boss?
;LDA bossSetup
;CMP #$00
;BEQ bossIsBossOneSetup
;JMP bossIsBossOneBehavior

;bossIsBossOneSetup:
;; Setup variables for this boss.
;LDA #$00
;STA monstersSpawned

bossIsBossOneBehavior:
;; Boss Behaviors

;; Check the bosses action state, and do an action accordingly.
    LDA Object_action_step,x ;; Get and store the enemies action state
    AND #%00000111 
    STA temp
    
    ;; Point to the correct behaviors
    LDA temp
    CMP #$0
    BEQ bossIsBossOneBehaviorZero
    
    LDA temp
    CMP #$1
    BEQ bossIsBossOneBehaviorOne
    
    LDA temp
    CMP #$2
    BEQ bossIsBossOneBehaviorTwo
    
    JMP BossIsBossOneBehaviorUndefined
    
bossIsBossOneBehaviorZero:
    ;; Wait a few seconds
    JSR bossSRNull
    JMP bossCleanup
    
bossIsBossOneBehaviorOne:
    ;; Spawn slimes in this state, or shoot if too many slimes created.
    JSR bossSRSpawnSlimes
    JMP bossCleanup
    
bossIsBossOneBehaviorTwo:
    ;; Spawn a slime
    JSR bossSRNull
    JMP bossCleanup
    
BossIsBossOneBehaviorUndefined:
    ;; We shouldn't get here, but if we do, fix it by setting the action state to 0
    LDA #$00
    STA Object_action_step,x
    JMP bossCleanup



bossCleanup:
;; The screen is triggered, make sure to destroy the boss and remove any tiles related to them.



;; Boss Subroutines:
;; We have separated different actions down here to help keep the code clean.

bossSRNull:
    ;; Dont do anything
    RTS

    
    
bossSRSpawnSlimes:
    ;; BASED ON JSHERMAN's SPAWNING CODE! THANKS!
    ;; Spawns slimes in random positions up to a maximum limit.
    LDA monstersSpawned
    CMP #ENEMY_MAXSPAWN 
    BCS bossSRSpawnSlimes_DoNotSpawn
    
bossSRSpawnSlimes_Spawn:
    ;; Put our monster in a temp var for safekeeping
    TXA
    STA tempx
    
    ;; Get offset
    LDA Object_x_hi,x
    CLC
    ADC #$04 ;; arbitrary...would put an 8x8 proj in the center of a 16x16 object
    STA temp1
    CLC
    ADC #$04 ;; arbitrary...would put a 8x8 proj in the center of a 16x16 object
    LDA Object_y_hi,x
    STA temp2
    
    ;; Spawn a new monster
    INC monstersSpawned
    CreateObject temp1, temp2, #ENEMY_SPAWNID_SLIME, #$00, currentNametable
    RTS

bossSRSpawnSlimes_DoNotSpawn: 
    ;; Force the boss into the next action state.
    ;LDA #$02
    ;STA Object_action_step,x
    JSR bossSRShootTowardPlayer
    
    ;; Dont spawn any new enemies
    LDX tempx
    
    RTS
    
    
    
bossSRSpawnSlimeBarriers:
    ;; Check if we should spawn the barriers
    LDA monstersSpawned
    CMP #$03
    BEQ bossSRSpawnSlimeBarriers_spawn
    RTS
    
bossSRSpawnSlimeBarriers_spawn:
    ;CreateObject #$91, #$151, #ENEMY_SPAWNID_BAR1, #$00, currentNametable
    ;CreateObject #$81, #$91, #ENEMY_SPAWNID_BAR2, #$00, currentNametable
    ;CreateObject #$151, #$91, #ENEMY_SPAWNID_BAR2, #$00, currentNametable
    RTS
    
    
    
bossSRShootTowardPlayer:
    ;; Boss version of shoot towards player
    TXA
    STA tempx
    
    ;; get offset
    LDA Object_x_hi,x
    ;CLC
    ;ADC #$04 ;; arbitrary...would put an 8x8 proj in the center of a 16x16 object
    STA temp1
    
    LDA Object_y_hi,x
    CLC
    ADC #$10
    STA temp2
    LDA Object_scroll,x
    STA temp
    CreateObject temp1, temp2, #OBJ_MONSTER_PROJECTILE, #$00, temp ;; maybe use a different state for ignore physics?
    LDA #$00
    STA Object_movement,x
    ;; we will skip traditional movement 
    ;; and use direct speed instead.
    ;; will this cause deacceleration?
    ;; what we need is a bit to "ignore physics engine" which will ignore acc/dec
    
    LDA Object_x_hi,x
    sec
    sbc xScroll

    STA temp
    LDA Object_y_hi,x

    STA temp2
    TXA
    STA tempz ;; store the newly created object's x
    ;; shoot at player one - if there are two players, will we randomize somhow?
    LDX player1_object
    LDA Object_x_hi,x
    sec
    sbc xScroll
    CLC
    ADC #PLR_HCENTEROFFSET
    STA temp1
    LDA Object_y_hi,x
    CLC
    ADC #PLR_VCENTEROFFSET
    STA temp3
    LDX tempz ;; restore newly created projectile object.

    JSR MoveTowardsPlayer 
    LDX tempz

    LDA myHvel
    
    STA Object_h_speed_lo,x
    LDA #$00
    STA Object_h_speed_hi,x
    LDA myVvel
    STA Object_v_speed_lo,x
    LDA #$00
    STA Object_v_speed_hi,x
    
bossSRShootTowardPlayer_gotVAimSpeeds:  
    LDX tempx
    
    RTS
 
Ok... the problem is not the problem I thought it was. I just don't know whats happening.

I made shoot at player a separate subroutine that can be called.

When I call it with my boss monster, it works fine. Works exactly as intended.
When I call it with a normal test monster, it doesnt work. Or more specifically it will work sometimes. It will loop 5-6 times through the enemy actions and maybe one of those times it will fire a shot.

I have been tweaking it all day trying to get it to work, but I just cant make it happen.

here is the subroutine:
Code:
enemySubroutine_ShootTowardPlayer:
    TXA
    STA tempx
    
    ;; get offset
    LDA Object_x_hi,x
    ;CLC
    ;ADC #$04 ;; arbitrary...would put an 8x8 proj in the center of a 16x16 object
    STA temp1
    
    LDA Object_y_hi,x
    CLC
    ADC #$10
    STA temp2
    LDA Object_scroll,x
    STA temp
    CreateObject temp1, temp2, #OBJ_MONSTER_PROJECTILE, #$00, temp ;; maybe use a different state for ignore physics?
    LDA #$00
    STA Object_movement,x
    ;; we will skip traditional movement 
    ;; and use direct speed instead.
    ;; will this cause deacceleration?
    ;; what we need is a bit to "ignore physics engine" which will ignore acc/dec
    
    LDA Object_x_hi,x
    sec
    sbc xScroll

    STA temp
    LDA Object_y_hi,x

    STA temp2
    TXA
    STA tempz ;; store the newly created object's x
    ;; shoot at player one - if there are two players, will we randomize somhow?
    LDX player1_object
    LDA Object_x_hi,x
    sec
    sbc xScroll
    CLC
    ADC #PLR_HCENTEROFFSET
    STA temp1
    LDA Object_y_hi,x
    CLC
    ADC #PLR_VCENTEROFFSET
    STA temp3
    LDX tempz ;; restore newly created projectile object.

    JSR MoveTowardsPlayer 
    LDX tempz

    LDA myHvel
    
    STA Object_h_speed_lo,x
    LDA #$00
    STA Object_h_speed_hi,x
    LDA myVvel
    STA Object_v_speed_lo,x
    LDA #$00
    STA Object_v_speed_hi,x
    
enemySubroutine_ShootTowardPlayer_gotVAimSpeeds:  
    LDX tempx
    
    RTS
 

Mugi

Member
this might be related to something i am fighting with myself at the moment, and am in the process of trying to figure out.

i started poking at my wall climb tile in order to make it behave better (no more random situations where you cant jump off it) and made it only check the top collisions of the player depending on facing direction,
so that the wallgrab tile is now only grabbable from pretty much pixel pefrectly on that 16x16 area where you put the tile in.

this works perfectly for the lack of better word....... UNTIL you place more than one monster object on the screen.
after 2 or more monsters are on screen along with the player, the collision check code stops working for a reason that completely eludes me. (by design tiles check all collision points.) the tile still works but the check for only count the top left or top right corner of the player collision box just joes RIP at this point.

i believe it has something to do with the fact that the monsters on screen are running their own scripts which keep loading shit into X, and if i grab my wall tile just at the right moment, there's something else than the player in X
and the check fails. That said, NES is supposed to be running code in a single thread so technically this should not happen. The other theory is that since the engine is the clutter is is, it just slows down too much and messes things up.

i dont know.

i will let you know if i figure out whats causing this.
in the meantime, you could try and see if the number of objects on screen has an effect on the script behavior on your end.
 
Thanks for looking into it. I would definitely be interested in hearing what you find out.

Strangely though, I don't think its an issue with # of sprites on screen. In fact I have almost opposite behavior.

When fighting my boss, I also have 3 sub-enemies on screen. So I have 3 normal enemies, the player object, the boss enemy and possibly both the player and boss projectile all on screen at once. And yea, the boss battle is currently suffering from slowdown. The projectiles work fine though.

I have a 'test room' set up in the corner of my map which is basically just an empty box that I spawn the player in and 1 of whatever I am testing.
So with no other sprites on the screen except for 1 enemy and the player, it doesn't work.

So far it has just been a perplexing issue to me. My best guess so far is that something is corrupting X like you said. This subroutine is not the only thing I can't get to work.
On this particular enemy, I cannot even assign a simple JMP RESET as an action. It will only work if I assign it to action step zero. If I try and advance to step 1+ and do anything... it just refuses to work. Even deleted the enemy and remade it from scratch and still nothing.

Even gone to every other enemy I have placed in my game and made sure they are still working correctly and I didn't corrupt something playing around with the code. They all work like normal.
 

Mugi

Member
i've had random issues with setting enemy behaviors too and they just do nothing.
i never figured out the reason, but sounds like it's the exact same thing you have. The monster i was making was not able to use the action timer, or end action scripts. it only loaded end animation scripts.
i just left the enemy there in the enemy list and made a new one, which works lol.

but yeah, im definitely looking into this since i need it fixed on my end too.
 
*Quadruple Facepalm*

My solution: Read the code and actually understand what I am doing...

SO: In the default shoot at enemy script there is an offset to make it spawn roughly in the center of an enemy. It spawns it 10 pixels below the top of the enemy.
My test enemy was less than 10 pixels tall (Because I was lazy and didn't want to set up graphics)
The bullets were spawning in the floor and instantly deleting themselves.

Also, it seems you cannot just drop a subroutine into an ai action and call it a day.
If the enemy AI action ends in an RTS it seems to screw things up. So if you make it a subroutine, make sure that your AI script CALLS it, not IS it.
 

Mugi

Member
now well that's definitely a different thing than what i have going i believe :p
yeah, didnt think of that option, but glad you got it sorted out.
 
Top Bottom