Bat

From ZAMN Hacking
Revision as of 20:16, 14 July 2024 by Piranhaplant (talk | contribs)
Entity data
Entity pointer $82:F03E

The bat is an entity that is spawned by vampires.

Behavior

A bat starts out moving in a given direction specified in the entity arguments. The initial velocity of the bat is 6 px/frame in this direction. If the direction is diagonal, the velocity is 6 px/frame on both axes. Every frame, the bat will accelerate towards the nearest target at a rate of 1 px/frame2, but its speed will not exceed 5 px/frame on each axis. After the acceleration is applied, then the position is updated based on the current velocity.

Every 3 frames the bat will switch to the next animation frame. There are 2 different sprites that it alternates between.

A bat will die if any of the following conditions are met:

  • It contacts a tile that is solid to weapons
  • It touches a player, victim, or any weapon shot
  • It has been alive for 131 frames

Once the bat dies, it plays a 32 frame death animation, then the entity is destroyed.

Bugs

  • A bat starts at a speed of 6 px/frame on each axis, but the routine that updates the speed will not allow it to exceed 5 px/frame. Thus if a bat's speed ever goes below the initial 6 px/frame, it will not be able to reach it again. This is likely a mistake.

RAM map

Entity arguments

Address Length Type Name Description
$00 2 int16 x X position
$02 2 int16 y Y position
$04 2 direction x2 direction Direction

Entity memory

Address Length Type Name Description
$08 2 pointer16 sprite Pointer to sprite
$0A 2 int16 x X position
$0C 2 int16 y Y position
$0E 2 int16 velocityX X velocity
$10 2 int16 velocityY Y velocity
$12 2 unused unused12 Value is set, but never used
$14 2 boolean dead Dead
$16 2 uint16 animationFrame Low bit is the current animation frame
$18 2 uint16 0-based framesUntilAnimUpdate Number of frames until the next animation frame
$1A 2 uint16 0-based framesUntilDeath Number of frames until death

Pseudocode

main() {
	$7E:00DE += 8
	this.sprite = init()

	while (!this.dead) {
		waitFrames(1)
		updateAnimation()
		updatePosition()
	}

	this.sprite.tileData.bank = 0x8F
	showAnimation(deathAnimation)
	$7E:00DE -= 8
	while ($7E:00DE < 0) { }
	deleteSprite(this.sprite)
}

deathAnimation = { { 0xF6CB, 8 }, { 0xF6D4, 8 }, { 0xF6DD, 8 }, { 0xF6E6, 8 }, { 0 } }

updatePosition() {
	targetSprite = findTarget(this.x, this.y)
	targetDirection = getDirectionToTarget(this.sprite, targetSprite)

	newVelocityX = this.velocityX + accelerationAmouont[targetDirection][0]
	if (abs(newVelocityX) < 6) {
		this.velocityX = newVelocityX
	}

	newVelocityY = this.velocityY + accelerationAmouont[targetDirection][1]
	if (abs(newVelocityY) < 6) {
		this.velocityY = newVelocityY
	}

	this.x += this.velocityX
	this.y += this.velocityY

	if (bgSolidToWeapons(this.x, this.y)) {
		this.dead = true
	} else {
		this.sprite.x = this.x
		this.sprite.y = this.y
		this.framesUntilDeath -= 1
		if (this.framesUntilDeath < 0) {
			this.dead = true
		}
	}
}

accelerationAmouont = {
	{  0,  0 },
	{  0, -1 },
	{  1, -1 },
	{  1,  0 },
	{  1,  1 },
	{  0,  1 },
	{ -1,  1 },
	{ -1,  0 },
	{ -1, -1 }
}

updateAnimation() {
	this.framesUntilAnimUpdate -= 1
	if (this.framesUntilAnimUpdate < 0) {
		this.framesUntilAnimUpdate = 2

		this.animationFrame += 1
		this.sprite.tileData.lowBytes = animation[this.animationFrame % 2]
	}
}

animation = { 0xDDB0, 0xDDB9 }

init() {
	sprite = createSprite()
	this.x = args.x
	sprite.x = args.x
	sprite.z = 12
	this.y = args.y
	sprite.y = args.y

	this.unused12 = args.direction * 2
	this.velocityX = startingVelocity[args.direction][0]
	this.velocityY = startingVelocity[args.direction][1]
	sprite.tileData = $90:DDB0
	sprite.entity = currentEntity
	sprite.visible = true
	this.framesUntilDeath = 130
	this.dead = false
	this.animationFrame = 0
	this.framesUntilAnimUpdate = 0
	setCollisionHandler(collisionHandler)
	return sprite
}

startingVelocity = {
	{  0,  0 },
	{  0, -6 },
	{  6, -6 },
	{  6,  0 },
	{  6,  6 },
	{  0,  6 },
	{ -6,  6 },
	{ -6,  0 },
	{ -6, -6 }
}

collisionHandler(spriteType) {
	if (spriteType == VICTIM || spriteType == ZEKE || spriteType == JULIE ||
	    (spriteType & 0x7FFF) >= SQUIRT_GUN) {
		this.sprite.type = NONE
		this.dead = true
	}
}