Clone: Difference between revisions

From ZAMN Hacking
Content deleted Content added
Created page with "{{Infobox Monster|img=Clone.png|hp=1|points=20}} {{Infobox Entity|entity_pointer=$81:8E89 (normal)<br/>$81:8F1F (hard)}} The clone is a respawning monster. Category:Respawning monster Category:Monster Category:Entity"
 
mNo edit summary
 
(23 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Infobox Monster|img=Clone.png|hp=1|points=20}}
{{Infobox Monster
|img=Clone.png
{{Infobox Entity|entity_pointer=$81:8E89 (normal)<br/>$81:8F1F (hard)}}
|hp=1
The clone is a [[respawning monster]].
|points=20
|special=Mimics players
|resists=-
|weak_to=-
}}

{{Infobox Entity
|entity_pointer=$81:8E89
|pointer1_type=normal
|entity_pointer2=$81:8F1F
|pointer2_type=hard
}}

The clone is a [[respawning monster]]. Clones attack via contact damage, can mimic the player's character and aren't resistant or vulnerable to any weapon. They can be commonly found in the [[grass]] tileset, and less frequently in the [[castle]] and [[sand]] tilesets. Clones only have 1 hit point and award 20 points to the player when killed. There are two different types of clones: normal and hard.

== Behavior ==

=== Cloning ===

Both types of clones start by picking a player to clone. If there is only one player in the game, that player is selected. If there are two players in the game, one of them is selected at random. If there are no players left in the game, the clone will not spawn.

=== Normal clone ===

Normal clones can be in one of two states:
* '''Targeting:''' Move towards the nearest player at a speed of 0.75 px/frame. This only targets players, not victims or [[decoys]]. If there is no player within 239 pixels on a single axis while in this state, then the clone will despawn.
* '''Mimicking:''' Move in the direction of the cloned player's d-pad input at a speed of 0.75 px/frame. Stand still if the player is not pressing any direction on the d-pad.

Clones start in the targeting state, but will switch to the mimicking state after 1 frame. Afterwards, the clone will repeatedly switch to the opposite state after a random amount of time between 1 and 255 frames (about 4.25 seconds).

{{BPMN_embed|BPMN:Clone state diagram|135}}

=== Hard clone ===

Hard clones always target the nearest player. They move at a speed of 1.5 px/frame, twice as fast as the normal clone. They will also despawn if there is no player within 239 pixels on a single axis.

== Animation ==

Clones have a 4 frame animation cycle, with 7 frames between each animation frame. There is a separate set of animation frames for each of the 4 orthogonal directions, chosen based on which direction the clone is moving. There is also a separate set of animations for Zeke and Julie, chosen based on the character of the player who is being cloned. If the clone is moving diagonal, it uses the left or right animation. If the clone is not moving, it uses a single right-facing sprite for all 4 animation frames.

== Bugs ==

* [[Fire extinguisher|Freezing]] or [[Bubble gun|bubbling]] a clone, then letting it despawn will count as killing it. This will increase their kill count in the final tally and award the player the corresponding amount of points. This is because the logic that determines if a clone was killed or not checks if it was ever hit by a weapon, not if it lost all its HP.
* The normal clone can randomly get a value of 0 for the amount of time in between state changes. If this happens, the value will underflow and it will take a full 65536 frames (about 18.2 minutes) for it to change states again.

== Trivia ==

* There are 28 clone spawners and 6 hard clone spawners in the game.
* Clones are likely based on the pod people from the 1956 movie [https://en.wikipedia.org/wiki/Invasion_of_the_Body_Snatchers Invasion of the Body Snatchers].

== RAM map ==

=== Entity arguments ===

{| class="wikitable"
|-
! Address !! Length !! [[Data types|Type]] !! Name !! Description
|-
| $00 || 2 || int16 || x || X position
|-
| $02 || 2 || int16 || y || Y position
|}

=== Entity memory ===

{| class="wikitable"
|-
! Address !! Length !! [[Data types|Type]] !! Name !! Description
|-
| $08 || 2 || pointer16 || sprite || Pointer to sprite
|-
| $0A || 2 || uint16 || framesUntilAnimUpdate || Number of frames until next animation frame
|-
| $0C || 2 || uint16 || animationFrame || Current animation frame (x2 during spawn animation)
|-
| $0E || 2 || direction || direction || Current direction
|-
| $10 || 2 || unused || || Unused
|-
| $12 || 2 || int16 || xPixels || X position in pixels (derived from $16)
|-
| $14 || 2 || int16 || yPixels || Y position in pixels (derived from $18)
|-
| $16 || 2 || int16 x4 || x || X position
|-
| $18 || 2 || int16 x4 || y || Y position
|-
| $1A || 2 || int16 || newXPixels || New X position in pixels
|-
| $1C || 2 || int16 || newYPixels || New Y position in pixels
|-
| $1E || 2 || int16 x4 || newX || New X position
|-
| $20 || 2 || int16 x4 || newY || New Y position
|-
| $22 || 2 || int16 0-based || health || Health
|-
| $24 || 2 || uint16 || mimicking || Current state (0 = targeting, 1 = mimicking)
|-
| $26 || 2 || uint16 || framesUntilStateSwitch || Number of frames until state change
|-
| $28 || 2 || pointer16 || spawnAnimation || Pointer to spawn animation
|-
| $2A || 2 || pointer16 || walkingAnimations || Pointer to walking animations table
|-
| $2C || 2 || uint16 x2 || clonedPlayer || Which player is being cloned
|-
| $2E || 2 || boolean || dead || Dead
|-
| $30 || 2 || sprite type || collidedSpriteType || Type of weapon shot sprite collided with
|- class="breakrow"
| $7E || 2 || int16 0-based || freezeTimer || Amount of time to stay [[Fire extinguisher|frozen]]
|}

{{Pseudocode header}}
init() { // $81:8CD4
{{ROM name|$81:8000}}()
this.x = args.x * 4
this.y = args.y * 4
this.sprite.tileData = $90:9F0D
this.sprite.visible = true
this.framesUntilAnimUpdate = 7
this.animationFrame = 0
this.sprite.tileData.bank = 0x90
this.framesUntilStateSwitch = 1
{{ROM name|$80:CC3B}}(0x1A)
this.animationFrame = 0
while (this.animationFrame < 11) {
this.sprite.tileData.lowBytes = spawnAnimation[this.animationFrame]
{{ROM name|$80:8353}}(10)
this.animationFrame += 1
}
this.health = 0
this.sprite.type = MONSTER
this.sprite.alternatePalette = 6
{{ROM name|$80:8475}}(collisionHandler)
this.mimicking = false
this.collidedSpriteType = 0
this.animationFrame = 0
this.dead = false
this.freezeTimer = 0
}
determineClonedPlayer() { // $81:8D5A
clonedCharacter = null
for (player = 1; player >= 0; player -= 1) {
if (!{{RAM name|$7E:1E88}}[player]) {
clonedCharacter = {{RAM name|$7E:1E84}}[(player + 1) % 2]
break
}
}
if (clonedCharacter == null) {
clonedCharacter = {{ROM name|$80:9D39}}() % 2
}
this.walkingAnimations = walkingAnimationsPerCharacter[clonedCharacter]
this.spawnAnimation = spawnAnimationPerCharacter[clonedCharacter]
if ({{RAM name|$7E:1E88}}[0] && {{RAM name|$7E:1E84}}[0] == clonedCharacter) {
this.clonedPlayer = 0
} else {
this.clonedPlayer = 1
}
}
move(direction) { // $81:8DA1
this.newX = this.x + moveAmount[direction][0]
this.newXPixels = this.newX / 4
this.newY = this.y + moveAmount[direction][1]
this.newYPixels = this.newY / 4
if (!{{ROM name|$80:AE97}}(this.newXPixels, this.yPixels) &&
!{{ROM name|$80:BF67}}(this.sprite, this.newXPixels, this.yPixels)) {
this.x = this.newX
this.xPixels = this.newXPixels
}
if (!{{ROM name|$80:AE97}}(this.xPixels, this.newYPixels) &&
!{{ROM name|$80:BF67}}(this.sprite, this.xPixels, this.newYPixels)) {
this.y = this.newY
this.yPixels = this.newYPixels
}
this.sprite.x = this.xPixels
this.sprite.y = this.yPixels
if (this.direction < DOWN_LEFT) {
this.sprite.flipX = false
} else {
this.sprite.flipX = true
}
}
moveAmount = { // $81:8E1D
{ 0, 0 },
{ 0, -3 },
{ 3, -3 },
{ 3, 0 },
{ 3, 3 },
{ 0, 3 },
{ -3, 3 },
{ -3, 0 },
{ -3, -3 }
}
updateAnimation() { // $81:8E41
this.framesUntilAnimUpdate -= 1
if (this.framesUntilAnimUpdate == 0) {
this.framesUntilAnimUpdate = 7
this.animationFrame = (this.animationFrame + 1) % 4
this.sprite.tileData.lowBytes = walkingAnimations[this.direction][this.animationFrame]
}
}
target() { // $81:8E62
this.xPixels = this.x / 4
this.yPixels = this.y / 4
this.direction = {{ROM name|$80:B2A5}}(240, this.xPixels, this.yPixels)
move(this.direction)
}
mimick() { // $81:8E7E
this.direction = {{RAM name|$7E:0072}}[this.clonedPlayer]
move(this.direction)
}
main_Normal() { // $81:8E89
if (!{{RAM name|$7E:1E88}}[0] && !{{RAM name|$7E:1E88}}[1]) {
return
}
{{RAM name|$7E:00DE}} += 0x18
determineClonedPlayer()
init()
while (true) {
{{ROM name|$80:8353}}(1)
if (this.dead) {
break
}
updateAnimation()
if (this.mimicking) {
mimick()
} else {
target()
if (this.direction == NONE) {
break
}
}
this.framesUntilStateSwitch -= 1
if (this.framesUntilStateSwitch == 0) {
this.mimicking = !this.mimicking
this.framesUntilStateSwitch = {{ROM name|$80:9D39}}()
}
}
if (this.collidedSpriteType != 0) {
{{ROM name|$80:C7D9}}(this.collidedSpriteType & 0x8000, 20)
{{RAM name|$7E:1F76}} += 1
{{ROM name|$81:83A3}}(deathAnimation_Normal, 0xF5F5, 0x90)
}
{{ROM name|$80:BE41}}(this.sprite)
{{RAM name|$7E:00DE}} -= 0x18
while ({{RAM name|$7E:00DE}} < 0) { }
}
deathAnimation_Normal = { // $81:8F03
{ 0xA01D, 5 }, { 0xA06E, 5 }, { 0xA0A7, 5 }, { 0xA0D8, 5 },
{ 0xA0F9, 5 }, { 0xA11A, 5 }, { 0, 0 }
}
main_Hard() { // $81:8F1F
if (!{{RAM name|$7E:1E88}}[0] && !{{RAM name|$7E:1E88}}[1]) {
return
}
{{RAM name|$7E:00DE}} += 0x18
determineClonedPlayer()
init()
while (true) {
{{ROM name|$80:8353}}(1)
if (this.health < 0) {
break
}
updateAnimation()
target()
if (this.direction == NONE) {
break
}
target()
if (this.direction == NONE) {
break
}
}
if (this.collidedSpriteType != 0) {
{{ROM name|$80:C7D9}}(this.collidedSpriteType & 0x8000, 20)
{{RAM name|$7E:1F76}} += 1
{{ROM name|$81:83A3}}(deathAnimation_Hard, 0xF5F5, 0x90)
}
{{ROM name|$80:BE41}}(this.sprite)
{{RAM name|$7E:00DE}} -= 0x18
while ({{RAM name|$7E:00DE}} < 0) { }
}
deathAnimation_Hard = { // $81:8F83
{ 0xA01D, 5 }, { 0xA06E, 5 }, { 0xA0A7, 5 }, { 0xA0D8, 5 },
{ 0xA0F9, 5 }, { 0xA11A, 5 }, { 0, 0 }
}
walkingAnimationsPerCharacter = { walkingAnimationsZeke, walkingAnimationsJulie } // $81:8F9F
walkingAnimationsZeke = { // $81:8FA3
{ 0x90ED, 0x90ED, 0x90ED, 0x90ED },
{ 0x9210, 0x9229, 0x9242, 0x925B },
{ 0x9138, 0x9159, 0x9172, 0x9193 },
{ 0x9138, 0x9159, 0x9172, 0x9193 },
{ 0x9138, 0x9159, 0x9172, 0x9193 },
{ 0x91AC, 0x91C5, 0x91DE, 0x91F7 },
{ 0x9138, 0x9159, 0x9172, 0x9193 },
{ 0x9138, 0x9159, 0x9172, 0x9193 },
{ 0x9138, 0x9159, 0x9172, 0x9193 }
}
walkingAnimationsJulie = { // $81:8FEB
{ 0x8285, 0x8285, 0x8285, 0x8285 },
{ 0x83B8, 0x83D1, 0x83EA, 0x8403 },
{ 0x82E0, 0x8301, 0x831A, 0x833B },
{ 0x82E0, 0x8301, 0x831A, 0x833B },
{ 0x82E0, 0x8301, 0x831A, 0x833B },
{ 0x8354, 0x836D, 0x8386, 0x839F },
{ 0x82E0, 0x8301, 0x831A, 0x833B },
{ 0x82E0, 0x8301, 0x831A, 0x833B },
{ 0x82E0, 0x8301, 0x831A, 0x833B }
}
spawnAnimationPerCharacter = { spawnAnimationZeke, spawnAnimationJulie } // $81:9033
spawnAnimationZeke = { // $81:9037
0x9F0D, 0x9F16, 0x9F27, 0x9F48, 0x9F69, 0x9F92, 0x9FC3, 0x9FF4,
0x9627, 0x9640, 0x9659
}
spawnAnimationJulie = { // $81:904D
0x9F0D, 0x9F16, 0x9F27, 0x9F48, 0x9F69, 0x9F92, 0x9FC3, 0x9FF4,
0x87C7, 0x87E0, 0x87F9
}
collisionHandler(spriteType) { // $81:9063
if (spriteType < SQUIRT_GUN) {
return false
}
this.collidedSpriteType = spriteType
weaponType = spriteType & 0x7FFF
if (weaponType == MARTIAN_BUBBLE_GUN) {
return {{ROM name|$81:83C6}}()
} else if (weaponType == FIRE_EXTINGUISHER) {
return {{ROM name|$81:847E}}()
} else {
newHealth = this.health - {{ROM name|$81:8561}}[weaponType - SQUIRT_GUN]
if (newHealth < 0) {
this.dead = true
this.health = newHealth
this.freezeTimer = 0
return true
} else if (newHealth != this.health {
this.health = newHealth
return {{ROM name|$81:8506}}()
}
return false
}
}
{{Pseudocode footer}}


[[Category:Respawning monster]]
[[Category:Respawning monster]]
[[Category:Monster]]
[[Category:Bug]]
[[Category:Entity]]

Latest revision as of 18:47, 17 August 2024

Monster data
HP 1
Points 20
Special Mimics players
Resists -
Weak to -
Entity data
Entity pointer $81:8E89 (normal)
$81:8F1F (hard)

The clone is a respawning monster. Clones attack via contact damage, can mimic the player's character and aren't resistant or vulnerable to any weapon. They can be commonly found in the grass tileset, and less frequently in the castle and sand tilesets. Clones only have 1 hit point and award 20 points to the player when killed. There are two different types of clones: normal and hard.

Behavior[edit]

Cloning[edit]

Both types of clones start by picking a player to clone. If there is only one player in the game, that player is selected. If there are two players in the game, one of them is selected at random. If there are no players left in the game, the clone will not spawn.

Normal clone[edit]

Normal clones can be in one of two states:

  • Targeting: Move towards the nearest player at a speed of 0.75 px/frame. This only targets players, not victims or decoys. If there is no player within 239 pixels on a single axis while in this state, then the clone will despawn.
  • Mimicking: Move in the direction of the cloned player's d-pad input at a speed of 0.75 px/frame. Stand still if the player is not pressing any direction on the d-pad.

Clones start in the targeting state, but will switch to the mimicking state after 1 frame. Afterwards, the clone will repeatedly switch to the opposite state after a random amount of time between 1 and 255 frames (about 4.25 seconds).

Hard clone[edit]

Hard clones always target the nearest player. They move at a speed of 1.5 px/frame, twice as fast as the normal clone. They will also despawn if there is no player within 239 pixels on a single axis.

Animation[edit]

Clones have a 4 frame animation cycle, with 7 frames between each animation frame. There is a separate set of animation frames for each of the 4 orthogonal directions, chosen based on which direction the clone is moving. There is also a separate set of animations for Zeke and Julie, chosen based on the character of the player who is being cloned. If the clone is moving diagonal, it uses the left or right animation. If the clone is not moving, it uses a single right-facing sprite for all 4 animation frames.

Bugs[edit]

  • Freezing or bubbling a clone, then letting it despawn will count as killing it. This will increase their kill count in the final tally and award the player the corresponding amount of points. This is because the logic that determines if a clone was killed or not checks if it was ever hit by a weapon, not if it lost all its HP.
  • The normal clone can randomly get a value of 0 for the amount of time in between state changes. If this happens, the value will underflow and it will take a full 65536 frames (about 18.2 minutes) for it to change states again.

Trivia[edit]

  • There are 28 clone spawners and 6 hard clone spawners in the game.
  • Clones are likely based on the pod people from the 1956 movie Invasion of the Body Snatchers.

RAM map[edit]

Entity arguments[edit]

Address Length Type Name Description
$00 2 int16 x X position
$02 2 int16 y Y position

Entity memory[edit]

Address Length Type Name Description
$08 2 pointer16 sprite Pointer to sprite
$0A 2 uint16 framesUntilAnimUpdate Number of frames until next animation frame
$0C 2 uint16 animationFrame Current animation frame (x2 during spawn animation)
$0E 2 direction direction Current direction
$10 2 unused Unused
$12 2 int16 xPixels X position in pixels (derived from $16)
$14 2 int16 yPixels Y position in pixels (derived from $18)
$16 2 int16 x4 x X position
$18 2 int16 x4 y Y position
$1A 2 int16 newXPixels New X position in pixels
$1C 2 int16 newYPixels New Y position in pixels
$1E 2 int16 x4 newX New X position
$20 2 int16 x4 newY New Y position
$22 2 int16 0-based health Health
$24 2 uint16 mimicking Current state (0 = targeting, 1 = mimicking)
$26 2 uint16 framesUntilStateSwitch Number of frames until state change
$28 2 pointer16 spawnAnimation Pointer to spawn animation
$2A 2 pointer16 walkingAnimations Pointer to walking animations table
$2C 2 uint16 x2 clonedPlayer Which player is being cloned
$2E 2 boolean dead Dead
$30 2 sprite type collidedSpriteType Type of weapon shot sprite collided with
$7E 2 int16 0-based freezeTimer Amount of time to stay frozen

Pseudocode

init() { // $81:8CD4
	createMonsterSprite()
	this.x = args.x * 4
	this.y = args.y * 4
	this.sprite.tileData = $90:9F0D
	this.sprite.visible = true
	this.framesUntilAnimUpdate = 7
	this.animationFrame = 0
	this.sprite.tileData.bank = 0x90
	this.framesUntilStateSwitch = 1
	playSound(0x1A)
	
	this.animationFrame = 0
	while (this.animationFrame < 11) {
		this.sprite.tileData.lowBytes = spawnAnimation[this.animationFrame]
		waitFrames(10)
		this.animationFrame += 1
	}
	
	this.health = 0
	this.sprite.type = MONSTER
	this.sprite.alternatePalette = 6
	setCollisionHandler(collisionHandler)
	this.mimicking = false
	this.collidedSpriteType = 0
	this.animationFrame = 0
	this.dead = false
	this.freezeTimer = 0
}

determineClonedPlayer() { // $81:8D5A
	clonedCharacter = null

	for (player = 1; player >= 0; player -= 1) {
		if (!playerInGame[player]) {
			clonedCharacter = characterForPlayer[(player + 1) % 2]
			break
		}
	}
	if (clonedCharacter == null) {
		clonedCharacter = getRandomByte() % 2
	}
	
	this.walkingAnimations = walkingAnimationsPerCharacter[clonedCharacter]
	this.spawnAnimation = spawnAnimationPerCharacter[clonedCharacter]
	if (playerInGame[0] && characterForPlayer[0] == clonedCharacter) {
		this.clonedPlayer = 0
	} else {
		this.clonedPlayer = 1
	}
}

move(direction) { // $81:8DA1
	this.newX = this.x + moveAmount[direction][0]
	this.newXPixels = this.newX / 4
	this.newY = this.y + moveAmount[direction][1]
	this.newYPixels = this.newY / 4
	
	if (!bgSolidToMonsters(this.newXPixels, this.yPixels) &&
	    !touchingSpriteSolidToMonsters(this.sprite, this.newXPixels, this.yPixels)) {
		this.x = this.newX
		this.xPixels = this.newXPixels
	}
	if (!bgSolidToMonsters(this.xPixels, this.newYPixels) &&
	    !touchingSpriteSolidToMonsters(this.sprite, this.xPixels, this.newYPixels)) {
		this.y = this.newY
		this.yPixels = this.newYPixels
	}
	
	this.sprite.x = this.xPixels
	this.sprite.y = this.yPixels
	if (this.direction < DOWN_LEFT) {
		this.sprite.flipX = false
	} else {
		this.sprite.flipX = true
	}
}

moveAmount = { // $81:8E1D
	{  0,  0 },
	{  0, -3 },
	{  3, -3 },
	{  3,  0 },
	{  3,  3 },
	{  0,  3 },
	{ -3,  3 },
	{ -3,  0 },
	{ -3, -3 }
}

updateAnimation() { // $81:8E41
	this.framesUntilAnimUpdate -= 1
	if (this.framesUntilAnimUpdate == 0) {
		this.framesUntilAnimUpdate = 7
		this.animationFrame = (this.animationFrame + 1) % 4
		this.sprite.tileData.lowBytes = walkingAnimations[this.direction][this.animationFrame]
	}
}

target() { // $81:8E62
	this.xPixels = this.x / 4
	this.yPixels = this.y / 4
	this.direction = findPlayerInRangeDirection(240, this.xPixels, this.yPixels)
	move(this.direction)
}

mimick() { // $81:8E7E
	this.direction = playerDPadDirections[this.clonedPlayer]
	move(this.direction)
}

main_Normal() { // $81:8E89
	if (!playerInGame[0] && !playerInGame[1]) {
		return
	}
	$7E:00DE += 0x18
	determineClonedPlayer()
	init()
	
	while (true) {
		waitFrames(1)
		if (this.dead) {
			break
		}
		
		updateAnimation()
		if (this.mimicking) {
			mimick()
		} else {
			target()
			if (this.direction == NONE) {
				break
			}
		}
		
		this.framesUntilStateSwitch -= 1
		if (this.framesUntilStateSwitch == 0) {
			this.mimicking = !this.mimicking
			this.framesUntilStateSwitch = getRandomByte()
		}
	}
	
	if (this.collidedSpriteType != 0) {
		givePoints(this.collidedSpriteType & 0x8000, 20)
		clonesKilled += 1
		showMonsterDeath(deathAnimation_Normal, 0xF5F5, 0x90)
	}
	
	deleteSprite(this.sprite)
	$7E:00DE -= 0x18
	while ($7E:00DE < 0) { }
}

deathAnimation_Normal = { // $81:8F03
	{ 0xA01D, 5 }, { 0xA06E, 5 }, { 0xA0A7, 5 }, { 0xA0D8, 5 },
	{ 0xA0F9, 5 }, { 0xA11A, 5 }, { 0, 0 }
}

main_Hard() { // $81:8F1F
	if (!playerInGame[0] && !playerInGame[1]) {
		return
	}
	$7E:00DE += 0x18
	determineClonedPlayer()
	init()

	while (true) {
		waitFrames(1)
		if (this.health < 0) {
			break
		}
		
		updateAnimation()
		target()
		if (this.direction == NONE) {
			break
		}
		target()
		if (this.direction == NONE) {
			break
		}
	}
	
	if (this.collidedSpriteType != 0) {
		givePoints(this.collidedSpriteType & 0x8000, 20)
		clonesKilled += 1
		showMonsterDeath(deathAnimation_Hard, 0xF5F5, 0x90)
	}
	
	deleteSprite(this.sprite)
	$7E:00DE -= 0x18
	while ($7E:00DE < 0) { }
}

deathAnimation_Hard = { // $81:8F83
	{ 0xA01D, 5 }, { 0xA06E, 5 }, { 0xA0A7, 5 }, { 0xA0D8, 5 },
	{ 0xA0F9, 5 }, { 0xA11A, 5 }, { 0, 0 }
}

walkingAnimationsPerCharacter = { walkingAnimationsZeke, walkingAnimationsJulie } // $81:8F9F
walkingAnimationsZeke = { // $81:8FA3
	{ 0x90ED, 0x90ED, 0x90ED, 0x90ED },
	{ 0x9210, 0x9229, 0x9242, 0x925B },
	{ 0x9138, 0x9159, 0x9172, 0x9193 },
	{ 0x9138, 0x9159, 0x9172, 0x9193 },
	{ 0x9138, 0x9159, 0x9172, 0x9193 },
	{ 0x91AC, 0x91C5, 0x91DE, 0x91F7 },
	{ 0x9138, 0x9159, 0x9172, 0x9193 },
	{ 0x9138, 0x9159, 0x9172, 0x9193 },
	{ 0x9138, 0x9159, 0x9172, 0x9193 }
}
walkingAnimationsJulie = { // $81:8FEB
	{ 0x8285, 0x8285, 0x8285, 0x8285 },
	{ 0x83B8, 0x83D1, 0x83EA, 0x8403 },
	{ 0x82E0, 0x8301, 0x831A, 0x833B },
	{ 0x82E0, 0x8301, 0x831A, 0x833B },
	{ 0x82E0, 0x8301, 0x831A, 0x833B },
	{ 0x8354, 0x836D, 0x8386, 0x839F },
	{ 0x82E0, 0x8301, 0x831A, 0x833B },
	{ 0x82E0, 0x8301, 0x831A, 0x833B },
	{ 0x82E0, 0x8301, 0x831A, 0x833B }
}

spawnAnimationPerCharacter = { spawnAnimationZeke, spawnAnimationJulie } // $81:9033
spawnAnimationZeke = { // $81:9037
	0x9F0D, 0x9F16, 0x9F27, 0x9F48, 0x9F69, 0x9F92, 0x9FC3, 0x9FF4,
	0x9627, 0x9640, 0x9659
}
spawnAnimationJulie = { // $81:904D
	0x9F0D, 0x9F16, 0x9F27, 0x9F48, 0x9F69, 0x9F92, 0x9FC3, 0x9FF4,
	0x87C7, 0x87E0, 0x87F9
}

collisionHandler(spriteType) { // $81:9063
	if (spriteType < SQUIRT_GUN) {
		return false
	}
	this.collidedSpriteType = spriteType
	
	weaponType = spriteType & 0x7FFF
	if (weaponType == MARTIAN_BUBBLE_GUN) {
		return bubbleMonster()
	} else if (weaponType == FIRE_EXTINGUISHER) {
		return freezeMonster()
	} else {
		newHealth = this.health - weaponDamage[weaponType - SQUIRT_GUN]
		if (newHealth < 0) {
			this.dead = true
			this.health = newHealth
			this.freezeTimer = 0
			return true
		} else if (newHealth != this.health {
			this.health = newHealth
			return showDamageAnimation()
		}
		return false
	}
}