Aug 26, 2024

Jajamaru-Kun (FC): Cutest Ninja Goblin Slayer

Manipulating the RAM can be pretty difficult, due to how the game was programmed. Every instance has a set of variables, up to 16 total. In some cases these share similar functions across all instances. The program loads those variables into z-page, essentially as arguments for various functions. Roughly 80% of all operations are done to these arguments, then the arguments are returned to the instance for posterity until the next frame. This wouldn't be that big of a deal, but some variables are practically useless, as the program re-calculates them every time. You can change Jajamaru's sprite ($0307), but it will spaz between the sprite you set and the sprite he's supposed to have. On the other hand, enemies ignore their sprite variable completely, along with their object variable ($03Y9).  

Nearly everything has a multiple bit states. That is, across any number of variables, one of the bits may be set to dictate what action the instance is undertaking. Most of the time, these variables can be manipulated on the fly. Some bits tell the game what direction the instance is moving, some tell it whether the instance is active at all. Jajamaru has a bit to say when he's idle.

The player's input is copied directly to Jajamaru's movement state. Instead of saying "he's moving left," or "he's moving right," it just says gp_left is detected or gp_right is detected. To keep things simple, the coders just translated this right over to the enemies and projectiles as well. However, projectiles do not have a use for A,B, Select or Start, so those bits were utilized to keep track of which object (not instance) spawned that projectile. This way the program knows which sprite to use.

AI behavior is semi-random. By default, they all move to the right. When they hit a wall, they turn around. If they did not just reach a wall, then their next actions are based on a randomized byte shared between them all, shuffled with the algorithm:

R = R ror 2 ^ R + 3;
if !demo && (stepcounter & 8) then R++;

I don't know why that last operation is skipped in demo mode. It doesn't seem that significant. After the randomizer is shuffled, if the result is #00, the enemy reverses direction. The randomizer is then shuffled again. This time, if the randomizer is a multiple of 16, the enemy will check if it can jump up to the next floor.

Even Namazu uses the randomizer. First, he uses it to pick a destination to walk toward. Then the randomizer is shuffled again. If it is a multiple of 4, he drops a bomb. His range increases slightly every 22 levels, but the difference may be trivial. He aggros at 30 seconds in the first three stages, then at 50 seconds in the next 3 stages, then at 70 seconds in every stage after that.

Since each floor has a set y-coordinate, the breakable floors are pretty simple. Starting at $045A, floors are defined by the left tile and right tile horizontal position, as well as the current state. If bit 7 is set, the player can pass through the floor. If bit 6 is set, the player can break the floor. If bit 0 is set, the floor hides an item. The item itself is randomized. There can only be one item on the screen, so to this end the game will destroy the item whenever another floor is broken, even if that floor doesn't hide an item.

80 Silver Coin
81 Gold Coin
82 Bombs
83 Komaru-kun (1up)
84 Potion
85 Mine Cart
86 Red Jewel (speed up)
87 Shuriken 

Items will remain until another item is revealed. Collectible items have a set duration once picked up. While powered up, no further items will be revealed until the effect has worn off. Even though the timer gets set when Gamapakkun is summoned, it is just residual from the last item and has no bearing on the frog's duration.

The timer is based strictly on  the stepcounter. The seconds tick down whenever stepcounter & #3f equals #00. This means if you pick up an item when stepcounter & #3f equals #3f, you've officially lost one whole second.

You can be stun-locked by enemies running into you. To prevent this, the game uses a repeat stun counter at $001D. If you get knocked back by an enemy before $001C reaches #3f, the stun counter increases. If it reaches #0a, the enemy is forced to turn around. Projectiles will still kill you and you can often spam shurikens between hits, so it's somewhat uncommon to actually be stun-locked.

The onibi will spawn if you stay on one floor for 20 seconds. You do not have to actually move to another floor. Simply jumping straight up into a gap will reset the counter.

An extra life is earned when you reach the hi-score of 20000 points. This is when $0060,61 (for player 1) or $0062,63 (for player 2) is greater than #07CF. Another life is granted at 50000 points.

Tallied souls are worth 100 points, increasing up to 600 points every 3 levels. Each second left on the clock is worth 10 points, increasing up to 60 points every 3 levels. Both of these tallies use the same algorithm, one just multiplies the result by 10.

Enemy AI is pretty simple. All enemies use nearly the same behavior algorithm. Enemies respond differently based on how many are left. They grow more aggressive when there are 3 left. The last remaining enemy will become aggressive, except in the first stage. The AI algorithm is basically as follows:

  1. Check if enemy is active (byte $3XE).
  2. Determine enemy object based on current stage and ID.
    enemy = (stage div 3) mod 7 + 1;
    if enemy == 7
        enemy = ID >= 7 ? ID - 6 : enemy;
    else
    if stage > 0 && ID == 8
        enemy = ++enemy == 7 ? 1 : enemy;
  3. Check if Jajamaru is alive and the enemy are both alive.
  4. Clear the enemy's virtual gamepad (stop moving).
  5. Check if Jajamaru is not riding Gamapaku.
  6. If this is the 8th enemy, become aggressive.
  7. If not on the first stage and only one enemy left, become the aggressive.
  8. Change direction if walking into a wall.
  9. If aggressive, attempt to move toward Jajamaru.
  10. If there is no wall, 1:256 chance to change direction randomly.
  11. With 1:256 chance skip to step 16.
  12. If  this is the 8th enemy, attack Jajamaru if shuriken is thrown
  13. Otherwise if on at least stage 5 or less than 3 enemies, attack with 2:32 chance
  14. If aggressive, attempt to reach Jajamaru's floor
  15. Otherwise if Karakassa, hop with 1:16 chance.
  16. Otherwise check if near a gap.
  17. If aggressive then jump over the gap.
  18. Otherwise jump with 3:4 chance
  19. If jump check failed, drop down safely with 1:2 chance
  20. Then check for collisions with PC.

At that point, the game runs code pretty much shared between Jajamaru and the enemies. 

First, the instance drops off-screen if it's dead. Otherwise it  handles the instance's stunned status. An enemy will be stunned for 18 (seconds) until stage 24, at which point stun time is halved. 

The game then checks if the instance is attacking. If not already in the attacking state, it checks if the attack input is set. If so, it checks if the cooldown timer is 0. Then it checks if the number of active attacks for all instances of the same object is less than some hard-coded limit. This applies to Jajamaru as well. For Jajamaru and Oyuki, the limit is 1 attack at a time, meaning even if there are 8 Oyuki alive, only one can attack. If you change the value for Jajamaru, you can actually fire off 2 shuriken at a time, restricted by the attack cooldown, If Jajamaru is attacking, it plays the throw sound and sets the attack sprite duration to 8 frames. The attack cooldown is a tad longer at 16 frames. 

After the enemy moves, it checks for collisions with Jajamaru's shuriken, if it is active and hasn't hit anything yet.


---This is the end for now. I feel like I was going to add more here, but I got tired of the game and moved on. Maybe I will revisit it some day, but I don't think I'm missing anything important. Maybe I'll add some notes about each item or each enemy's stats. For now, signing off.

No comments:

Post a Comment

©TheouAegis Productions™. Powered by Blogger.