Evil doll axe: Difference between revisions
Piranhaplant (talk | contribs) No edit summary |
Piranhaplant (talk | contribs) No edit summary |
||
(6 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
{{Infobox Entity|entity_pointer=$81: |
{{Infobox Entity|img=Evil doll axe.png|entity_pointer=$81:B4EA}} |
||
An evil doll axe is an entity that is created by an [[evil doll]] whenever it throws an axe. |
An evil doll axe is an entity that is created by an [[evil doll]] whenever it throws an axe. |
||
Line 15: | Line 15: | ||
# The "down" direction is incorrectly mapped to "right" |
# The "down" direction is incorrectly mapped to "right" |
||
The result of these two bugs is that all orthogonal directions are mapped to the animation for "right", and all diagonal directions are mapped to the animation for "down-right". For orthogonal directions, this doesn't matter since they all share the same animation anyway. However, the down-left and up-left animations supposed to show the axe rotating counter-clockwise, but don't. |
The result of these two bugs is that all orthogonal directions are mapped to the animation for "right", and all diagonal directions are mapped to the animation for "down-right". For orthogonal directions, this doesn't matter since they all share the same animation anyway. However, the down-left and up-left animations supposed to show the axe rotating counter-clockwise, but don't. |
||
== Bugs == |
|||
* Axes only despawn when going off screen in multiplayer due to using a subroutine that is intended for player entities to keep themselves from going too far from the other player. |
|||
* The animation does not display correctly in some directions (see [[#Animation|Animation]]) |
|||
* The speed is 1 px/frame slower in the negative directions (up/left). This is because the velocity is applied to the positions using two separate add instructions, and the carry is not cleared between them. |
|||
* Death from sprites increments the "dead" variable to make it non-zero, but death from solid background tiles decrements it. It may be possible for these to cancel out and keep the axe alive. |
|||
== RAM map == |
== RAM map == |
||
Line 22: | Line 29: | ||
{| class="wikitable" |
{| class="wikitable" |
||
|- |
|- |
||
! Address !! Length !! Type !! Description |
! Address !! Length !! [[Data types|Type]] !! Name !! Description |
||
|- |
|- |
||
| $00 || 2 || int16 || X position |
| $00 || 2 || int16 || x || X position |
||
|- |
|- |
||
| $02 || 2 || int16 || Y position |
| $02 || 2 || int16 || y || Y position |
||
|- |
|- |
||
| $04 || 2 || int16 || X velocity |
| $04 || 2 || int16 || velocityX || X velocity |
||
|- |
|- |
||
| $06 || 2 || int16 || Y velocity |
| $06 || 2 || int16 || velocityY || Y velocity |
||
|} |
|} |
||
Line 37: | Line 44: | ||
{| class="wikitable" |
{| class="wikitable" |
||
|- |
|- |
||
! Address !! Length !! Type !! Description |
! Address !! Length !! [[Data types|Type]] !! Name !! Description |
||
|- |
|- |
||
| $08 || 2 || pointer16 || Pointer to sprite |
| $08 || 2 || pointer16 || sprite || Pointer to sprite |
||
|- |
|- |
||
| $0A || 2 || boolean || Axe has been destroyed |
| $0A || 2 || boolean || dead || Axe has been destroyed |
||
|- |
|- |
||
| $0C || 2 || int16 0-based || Health |
| $0C || 2 || int16 0-based || health || Health |
||
|- |
|- |
||
| $0E || 2 || pointer16 || |
| $0E || 2 || pointer16 || state || Current state subroutine pointer |
||
|- |
|- |
||
| $10 || 2 || int16 || Number of frames until next animation frame |
| $10 || 2 || int16 || framesUntilAnimUpdate || Number of frames until next animation frame |
||
|- |
|- |
||
| $12 || 2 || int16 0-based || Number of frames per animation frame |
| $12 || 2 || int16 0-based || framesPerAnimFrame || Number of frames per animation frame |
||
|- |
|- |
||
| $14 || 2 || uint16 || Current animation frame |
| $14 || 2 || uint16 || animationFrame || Current animation frame |
||
|- |
|- |
||
| $16 || 2 || enum || [[#Animation Directions|Animation direction]] |
| $16 || 2 || enum x4 || direction || [[#Animation Directions|Animation direction]] |
||
|- |
|- |
||
| $18 || 2 || int16 || X position |
| $18 || 2 || int16 || x || X position |
||
|- |
|- |
||
| $1A || 2 || unused || Unused |
| $1A || 2 || unused || || Unused |
||
|- |
|- |
||
| $1C || 2 || int16 || Y position |
| $1C || 2 || int16 || y || Y position |
||
|- |
|- |
||
| $1E || 2 || sprite type || Type of sprite collided with |
| $1E || 2 || sprite type || collidedSpriteType || Type of sprite collided with |
||
|- |
|- |
||
| $20 || 2 || int16 /2 || X velocity |
| $20 || 2 || int16 /2 || velocityX || X velocity |
||
|- |
|- |
||
| $22 || 2 || int16 /2 || Y velocity |
| $22 || 2 || int16 /2 || velocityY || Y velocity |
||
|- |
|- |
||
| $24 || 2 || pointer16 || Pointer to animation table |
| $24 || 2 || pointer16 || animations || Pointer to animation table |
||
|} |
|} |
||
Line 74: | Line 81: | ||
{| class="wikitable" |
{| class="wikitable" |
||
|- |
|- |
||
! Value !! Direction !! Bugged direction |
! Value !! Value x4 !! Direction !! Bugged direction |
||
|- |
|- |
||
| 0x00 || Up || Down |
| 0x00 || 0x00 || Up || Down |
||
|- |
|- |
||
| 0x04 || Up-right || Down-right |
| 0x01 || 0x04 || Up-right || Down-right |
||
|- |
|- |
||
| 0x08 || Right || Down |
| 0x02 || 0x08 || Right || Down |
||
|- |
|- |
||
| 0x0C || Down-right || |
| 0x03 || 0x0C || Down-right || |
||
|- |
|- |
||
| 0x10 || Down || |
| 0x04 || 0x10 || Down || |
||
|- |
|- |
||
| 0x14 || Down-left || Down-right |
| 0x05 || 0x14 || Down-left || Down-right |
||
|- |
|- |
||
| 0x18 || Left || Down |
| 0x06 || 0x18 || Left || Down |
||
|- |
|- |
||
| 0x1C || Up-left || Down-right |
| 0x07 || 0x1C || Up-left || Down-right |
||
|- |
|- |
||
| 0x20 || None || |
| 0x08 || 0x20 || None || |
||
|} |
|} |
||
{{Pseudocode header}} |
|||
[[Category:Entity]] |
|||
main() { // $81:B4EA |
|||
{{RAM name|$7E:00DE}} += 2 |
|||
init() |
|||
while (!this.dead) { |
|||
this.collidedSpriteType = NONE |
|||
{{ROM name|$80:8353}}(1) |
|||
this.state() |
|||
updateSprite() |
|||
} |
|||
{{RAM name|$7E:00DE}} -= 2 |
|||
while ({{RAM name|$7E:00DE}} < 0) { } |
|||
{{ROM name|$80:BE41}}(this.sprite) |
|||
} |
|||
init() { // $81:B523 |
|||
this.sprite = {{ROM name|$80:BE0C}}() |
|||
this.x = args.x |
|||
this.sprite.x = args.x |
|||
this.sprite.z = 0 |
|||
this.y = args.y |
|||
this.sprite.y = args.y |
|||
this.velocityX = args.velocityX * 2 |
|||
this.velocityY = args.velocityY * 2 |
|||
this.sprite.tileData = $90:CCEC |
|||
this.sprite.entity = {{RAM name|$7E:0008}} |
|||
this.sprite.type = MONSTER |
|||
this.sprite.visible = true |
|||
this.health = 1 |
|||
this.framesPerAnimFrame = 5 |
|||
this.framesUntilAnimUpdate = 0 |
|||
this.dead = false |
|||
this.animationFrame = 0 |
|||
this.animations = evilDoll.axeAnimations |
|||
determineDirection() |
|||
{{ROM name|$80:8475}}(collisionHandler) |
|||
setStateToNormal() |
|||
} |
|||
collisionHandler(spriteType) { // $81:B592 |
|||
if (spriteType == 7 || spriteType == 8) { |
|||
this.collidedSpriteType = spriteType |
|||
this.health -= 1 |
|||
if (this.health < 0) { |
|||
this.dead = true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
setStateToNormal() { // $81:B5AA |
|||
this.state = normalState |
|||
} |
|||
normalState() { // $81:B5B0 |
|||
if (this.framesUntilAnimUpdate == 0) { |
|||
this.animationFrame += 1 |
|||
if (this.animationFrame == 4) { |
|||
this.animationFrame = 0 |
|||
} |
|||
} |
|||
this.x += this.velocityX * 2 |
|||
this.x += this.velocityX < 0 ? 1 : 0 // From carry |
|||
this.y += this.velocityY * 2 |
|||
this.y += this.velocityY < 0 ? 1 : 0 // From carry |
|||
if ({{ROM name|$80:AF2C}}(this.x, this.y) || {{ROM name|$80:A8B3}}(this.x, this.y)) { |
|||
this.dead = true |
|||
} |
|||
} |
|||
updateSprite() { // $81:B5EA |
|||
this.framesUntilAnimUpdate -= 1 |
|||
if (this.framesUntilAnimUpdate < 0) { |
|||
this.framesUntilAnimUpdate = this.framesPerAnimFrame |
|||
} |
|||
this.sprite.x = this.x |
|||
this.sprite.y = this.y |
|||
this.sprite.flipX = this.animations[this.direction][this.animationFrame][0] |
|||
this.sprite.tileData.lowBytes = |
|||
evilDoll.spriteTable[this.animations[this.direction][this.animationFrame][1]] |
|||
this.sprite.tileData.bank = 0x90 |
|||
} |
|||
determineDirection() { // $81:B630 |
|||
index = 0 |
|||
if (this.velocityX == -1) { |
|||
index += 8 |
|||
} else if (this.velocityX != 0) { |
|||
index += 4 |
|||
} |
|||
if (this.yVelocity == -1) { |
|||
index += 3 |
|||
} else if (this.velocityY != 0) { |
|||
index += 2 |
|||
} |
|||
this.direction = directions[index] |
|||
} |
|||
directions = { // $81:B658 |
|||
8, 8, 4, 0 // None, None, Down, Up |
|||
4, 8, 3, 1 // Down, None, Down-right, Up-right ("Down" here should be "Right") |
|||
6, 8, 5, 7 // Left, None, Down-left, Up-left |
|||
} |
|||
{{Pseudocode footer}} |
|||
[[Category:Bug]] |
Latest revision as of 19:10, 17 August 2024
Entity data | |
---|---|
![]() | |
Entity pointer | $81:B4EA |
An evil doll axe is an entity that is created by an evil doll whenever it throws an axe.
Behavior[edit]
The axe moves at a velocity of 4 times the given input velocity per frame. In the base game, the input velocities are either 0, 1, or -1 on each axis, so it will move at a rate of 4 pixels per frame on each axis.
Axes start with 2 HP and will be instantly destroyed when they run out of health. However, axes only take damage from sprite types 7 and 8, which are a wall breaking from player punching it as a monster, and an unused type respectively. Axes will also be destroyed if they touch a background tile that is solid to weapons or if they go off screen in multiplayer. The off-screen condition does not apply in single player; they will continue to travel until they hit a solid tile or reach the edge of the level.
Animation[edit]
The axe animation has 4 different animation frames and there are 6 frames of time between each. The animation is supposed to differ slightly based on which direction the axe is moving, but there are two bugs in the implementation for this:
- Negative directions (up and left) are incorrectly mapped to the corresponding positive direction because the code only checks for the velocities being exactly equal to -1, but they have already been multiplied by 2 when this is checked.
- The "down" direction is incorrectly mapped to "right"
The result of these two bugs is that all orthogonal directions are mapped to the animation for "right", and all diagonal directions are mapped to the animation for "down-right". For orthogonal directions, this doesn't matter since they all share the same animation anyway. However, the down-left and up-left animations supposed to show the axe rotating counter-clockwise, but don't.
Bugs[edit]
- Axes only despawn when going off screen in multiplayer due to using a subroutine that is intended for player entities to keep themselves from going too far from the other player.
- The animation does not display correctly in some directions (see Animation)
- The speed is 1 px/frame slower in the negative directions (up/left). This is because the velocity is applied to the positions using two separate add instructions, and the carry is not cleared between them.
- Death from sprites increments the "dead" variable to make it non-zero, but death from solid background tiles decrements it. It may be possible for these to cancel out and keep the axe alive.
RAM map[edit]
Entity arguments[edit]
Address | Length | Type | Name | Description |
---|---|---|---|---|
$00 | 2 | int16 | x | X position |
$02 | 2 | int16 | y | Y position |
$04 | 2 | int16 | velocityX | X velocity |
$06 | 2 | int16 | velocityY | Y velocity |
Entity memory[edit]
Address | Length | Type | Name | Description |
---|---|---|---|---|
$08 | 2 | pointer16 | sprite | Pointer to sprite |
$0A | 2 | boolean | dead | Axe has been destroyed |
$0C | 2 | int16 0-based | health | Health |
$0E | 2 | pointer16 | state | Current state subroutine pointer |
$10 | 2 | int16 | framesUntilAnimUpdate | Number of frames until next animation frame |
$12 | 2 | int16 0-based | framesPerAnimFrame | Number of frames per animation frame |
$14 | 2 | uint16 | animationFrame | Current animation frame |
$16 | 2 | enum x4 | direction | Animation direction |
$18 | 2 | int16 | x | X position |
$1A | 2 | unused | Unused | |
$1C | 2 | int16 | y | Y position |
$1E | 2 | sprite type | collidedSpriteType | Type of sprite collided with |
$20 | 2 | int16 /2 | velocityX | X velocity |
$22 | 2 | int16 /2 | velocityY | Y velocity |
$24 | 2 | pointer16 | animations | Pointer to animation table |
Animation Directions[edit]
Value | Value x4 | Direction | Bugged direction |
---|---|---|---|
0x00 | 0x00 | Up | Down |
0x01 | 0x04 | Up-right | Down-right |
0x02 | 0x08 | Right | Down |
0x03 | 0x0C | Down-right | |
0x04 | 0x10 | Down | |
0x05 | 0x14 | Down-left | Down-right |
0x06 | 0x18 | Left | Down |
0x07 | 0x1C | Up-left | Down-right |
0x08 | 0x20 | None |
Pseudocode
main() { // $81:B4EA $7E:00DE += 2 init() while (!this.dead) { this.collidedSpriteType = NONE waitFrames(1) this.state() updateSprite() } $7E:00DE -= 2 while ($7E:00DE < 0) { } deleteSprite(this.sprite) } init() { // $81:B523 this.sprite = createSprite() this.x = args.x this.sprite.x = args.x this.sprite.z = 0 this.y = args.y this.sprite.y = args.y this.velocityX = args.velocityX * 2 this.velocityY = args.velocityY * 2 this.sprite.tileData = $90:CCEC this.sprite.entity = currentEntity this.sprite.type = MONSTER this.sprite.visible = true this.health = 1 this.framesPerAnimFrame = 5 this.framesUntilAnimUpdate = 0 this.dead = false this.animationFrame = 0 this.animations = evilDoll.axeAnimations determineDirection() setCollisionHandler(collisionHandler) setStateToNormal() } collisionHandler(spriteType) { // $81:B592 if (spriteType == 7 || spriteType == 8) { this.collidedSpriteType = spriteType this.health -= 1 if (this.health < 0) { this.dead = true } } return false } setStateToNormal() { // $81:B5AA this.state = normalState } normalState() { // $81:B5B0 if (this.framesUntilAnimUpdate == 0) { this.animationFrame += 1 if (this.animationFrame == 4) { this.animationFrame = 0 } } this.x += this.velocityX * 2 this.x += this.velocityX < 0 ? 1 : 0 // From carry this.y += this.velocityY * 2 this.y += this.velocityY < 0 ? 1 : 0 // From carry if (bgSolidToWeapons(this.x, this.y) || positionTooFarFromOtherPlayer(this.x, this.y)) { this.dead = true } } updateSprite() { // $81:B5EA this.framesUntilAnimUpdate -= 1 if (this.framesUntilAnimUpdate < 0) { this.framesUntilAnimUpdate = this.framesPerAnimFrame } this.sprite.x = this.x this.sprite.y = this.y this.sprite.flipX = this.animations[this.direction][this.animationFrame][0] this.sprite.tileData.lowBytes = evilDoll.spriteTable[this.animations[this.direction][this.animationFrame][1]] this.sprite.tileData.bank = 0x90 } determineDirection() { // $81:B630 index = 0 if (this.velocityX == -1) { index += 8 } else if (this.velocityX != 0) { index += 4 } if (this.yVelocity == -1) { index += 3 } else if (this.velocityY != 0) { index += 2 } this.direction = directions[index] } directions = { // $81:B658 8, 8, 4, 0 // None, None, Down, Up 4, 8, 3, 1 // Down, None, Down-right, Up-right ("Down" here should be "Right") 6, 8, 5, 7 // Left, None, Down-left, Up-left }