Zombie: Difference between revisions

5,640 bytes added ,  6 days ago
no edit summary
No edit summary
No edit summary
Line 16:
== Differences in hard version ==
 
=== Major differences ===
[[File:Zombie follow wall comparison.gif|frame|Normal (left) vs. hard (right) targeting through a wall]]
 
* Start targeting at distance of 159 pixels (instead of 64 pixels for normal). They continue targeting while there is a target within 179 pixels (instead of 69 pixels for normal).
* Moves at a speed of 1 px/frame when wandering and following wall (instead of 0.5 px/frame for normal). The speed when targeting is 1 px/frame for both versions.
 
=== Minor differences ===
 
[[File:Zombie follow wall comparison.gif|frame|Normal (left) vs. hard (right) targeting through a wall]]
 
* Position update logic is different when targeting. When targeting through a wall, normal zombies will get stuck, but the hard version will still follow along the wall.
* The angle to rotate when following a wall is stored in a variable instead of being hardcoded. However, this variable is never initialized, so it will retain whatever value happened to be in RAM previously. This generally results in the zombie sticking to the wall instead of following it.
Line 27 ⟶ 32:
 
Zombies have a four frame animation cycle, with 8 frames in 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 zombie is moving. If the zombie is moving diagonal, it uses the left or right animation.
 
== Bugs ==
 
* Hard zombies do not generally follow walls like they should. See [[#Differences in hard version|Differences in hard version]].
* The hard zombies have a partially implemented feature that appears to be something that would make them stop targeting for a certain amount of time. In its incomplete state, it generally does not affect their behavior, but if a zombie stays following a wall for around 4.5 minutes, the timer will underflow. This will cause the zombie to stop targeting for around another 4.5 minutes until the timer reaches zero again.
 
== RAM map ==
Line 75 ⟶ 85:
| $22 || 2 || sprite type || collidedSpriteType || Type of sprite collided with
|-
| $24 || 2 || unusedint16 || unused24dontTargetTimer || ValueNumber isof setframes andto used,ignore buttargeting appearsfor to(basically have no effectunused) (hard only)
|-
| $26 || 2 || int16 || tempX || Temp X position (hard only)
Line 92 ⟶ 102:
== Pseudocode ==
 
moveAmount_Normal = {
moveAmount = { { 0, 0 },
{ 0, -1 0 },
{ 10, -1 },
{ 1, 0-1 },
{ 1, 10 },
{ 01, 1 },
{ { -10, 1 },
{ -1, 01 },
{ -1, -1 }0 },
{ -1, -1 }
}
moveAmount_Hard = {
{ 0, 0 },
{ 0, -2 },
{ 2, -2 },
{ 2, 0 },
{ 2, 2 },
{ 0, 2 },
{ -2, 2 },
{ -2, 0 },
{ -2, -2 }
}
newPositionIsBlocked() {
Line 107 ⟶ 131:
}
wanderInRandomDirection_Normal() {
wanderInRandomDirection() {
this.direction = ({{ROM name|$80:9D39}}() % 4) * 2 + 1
this.setStateToWanderingsetStateToWandering_Normal()
}
setStateToWandering_Normal() {
setStateToWandering() {
this.state = this.wanderingStatewanderingState_Normal
this.wanderingStatewanderingState_Normal()
}
wanderingState_Normal() {
wanderingState() {
this.newX = this.x + this.moveAmountmoveAmount_Normal[this.direction][0]
this.newY = this.y + this.moveAmountmoveAmount_Normal[this.direction][1]
if (!{{ROM name|$80:AE97}}(this.newX, this.newY)) {
if (!{{ROM name|$80:BF67}}(this.sprite, this.newX, this.newY)) {
Line 128 ⟶ 152:
}
} else {
this.followWallfollowWall_Normal()
}
}
followWallfollowWall_Normal() {
this.direction = ((this.direction - 1) + 2) % 8 + 1 // Rotate 90 degrees clockwise
this.setStateToFollowingWallsetStateToFollowingWall_Normal()
}
setStateToFollowingWall_Normal() {
setStateToFollowingWall() {
this.state = this.followingWallStatefollowingWallState_Normal
}
followingWallState_Normal() {
followingWallState() {
this.newDirection = ((this.direction - 1) - 2) % 8 + 1 // Rotate 90 degrees counterclockwise
this.newX = this.x + this.moveAmountmoveAmount_Normal[this.newDirection][0]
this.newY = this.y + this.moveAmountmoveAmount_Normal[this.newDirection][1]
if (!this.newPositionIsBlocked()) {
this.direction = this.newDirection
}
this.newX = this.x + this.moveAmountmoveAmount_Normal[this.direction][0]
this.newY = this.y + this.moveAmountmoveAmount_Normal[this.direction][1]
if (!this.newPositionIsBlocked()) {
this.x = this.newX
Line 157 ⟶ 181:
this.sprite.y = this.newY
} else {
this.followWallfollowWall_Normal()
}
}
setStateToTargeting_Normal() {
setStateToTargeting() {
this.state = this.targetingStatetargetingState_Normal
}
targetingState_Normal() {
targetingState() {
distance, this.targetSprite = {{ROM name|$80:B123}}(this.x, this.y)
if (distance >= 70) {
this.wanderInRandomDirectionwanderInRandomDirection_Normal()
return
}
Line 175 ⟶ 199:
this.direction = {{ROM name|$80:B22A}}(this.sprite, this.targetSprite)
if (this.direction == 0) {
this.wanderInRandomDirectionwanderInRandomDirection_Normal()
return
}
Line 181 ⟶ 205:
for (i = 0; i < 2; i++) {
this.newX = this.x + this.moveAmountmoveAmount_Normal[this.targetDirection][0]
this.newY = this.y + this.moveAmountmoveAmount_Normal[this.targetDirection][1]
if (!this.newPositionIsBlocked()) {
this.x = this.newX
Line 192 ⟶ 216:
}
checkForTarget_Normal() {
checkForTarget() {
distance = {{ROM name|$80:B123}}(this.x, this.y)
if (distance < 65) {
this.setStateToTargetingsetStateToTargeting_Normal()
return
}
Line 206 ⟶ 230:
killed() {
{{ROM name|$80:B2A5C7D9}}(this.collidedSpriteType & 0x8000, 100)
this.deathStatus = 0xF5F5
}
Line 226 ⟶ 250:
}
animations = { { 0xCABC, 0xCADD, 0xCAFE, 0xCB1F },
{ 0xCB400xCABC, 0xCB610xCADD, 0xCB820xCAFE, 0xCBA30xCB1F },
{ 0xCA280xCB40, 0xCA510xCB61, 0xCA720xCB82, 0xCA9B0xCBA3 },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCABC0xCA28, 0xCADD0xCA51, 0xCAFE0xCA72, 0xCB1F0xCA9B },
{ 0xCA280xCABC, 0xCA510xCADD, 0xCA720xCAFE, 0xCA9B0xCB1F },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B } },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B }
}
init() {
Line 251 ⟶ 277:
}
mainNormalmain_Normal() {
{{RAM name|$7E:00DE}} += 0x14
this.cyclesUntilAnimUpdate = 7
this.init()
{{ROM name|$81:832C}}(this.spawnAnimationspawnAnimation_Normal)
this.sprite.type = MONSTER
this.wanderInRandomDirectionwanderInRandomDirection_Normal()
this.health = 0
{{ROM name|$80:8475}}(this.collisionHandler)
Line 264 ⟶ 290:
while (this.deathStatus == 0) {
{{ROM name|$80:8353}}(2)
this.checkForTargetcheckForTarget_Normal()
this.state()
this.updateAnimation()
Line 279 ⟶ 305:
}
spawnAnimation_Normal = {
spawnAnimation = { { 0xC98A, 10 }, { 0xC993, 10 }, { 0xC9A4, 10 }, { 0xC9B5, 10 },
{ 0xC98A, 10 }, { 0xC9D60xC993, 10 }, { 0xC9FF0xC9A4, 10 }, { 00xC9B5, 0 }10 },
{ 0xC9D6, 10 }, { 0xC9FF, 10 }, { 0, 0 }
}
collisionHandler(spriteType) {
Line 305 ⟶ 333:
}
return false
}
}
main_Hard() {
{{RAM name|$7E:00DE}} += 0x14
this.cyclesUntilAnimUpdate = 7
this.init()
{{ROM name|$81:832C}}(this.spawnAnimation_Hard)
this.sprite.type = MONSTER
this.wanderInRandomDirection_Hard()
this.health = 0
this.dontTargetTimer = -1
{{ROM name|$80:8475}}(this.collisionHandler)
this.deathStatus = 0
while (this.deathStatus == 0) {
{{ROM name|$80:8353}}(2)
if (this.collidedSpriteType != 0) {
this.sprite.useAlternatePalette = false
this.collidedSpriteType = 0
} else {
this.checkForTarget_Hard()
}
this.state()
this.updateAnimation()
}
if (this.deathStatus == 0xF5F5) {
{{RAM name|$7E:1F64}} += 1
{{ROM name|$81:83A3}}(mummy.deathAnimation, this.deathStatus, 0x90)
}
{{RAM name|$7E:00DE}} -= 0x14
while ({{RAM name|$7E:00DE}} < 0) { }
{{ROM name|$80:BE41}}(this.sprite)
}
spawnAnimation_Hard = {
{ 0xC98A, 10 }, { 0xC993, 10 }, { 0xC9A4, 10 }, { 0xC9B5, 10 },
{ 0xC9D6, 10 }, { 0xC9FF, 10 }, { 0, 0 }
}
// Unused. Exactly equivalent to the other animations table
animations_Hard = {
{ 0xCABC, 0xCADD, 0xCAFE, 0xCB1F },
{ 0xCB40, 0xCB61, 0xCB82, 0xCBA3 },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCABC, 0xCADD, 0xCAFE, 0xCB1F },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B },
{ 0xCA28, 0xCA51, 0xCA72, 0xCA9B }
}
setStateToWandering_Hard() {
this.state = this.wanderingState_Hard
this.wanderingState_Hard()
}
wanderingState_Hard() {
this.newX = this.x + this.moveAmount_Hard[this.direction][0]
this.newY = this.y + this.moveAmount_Hard[this.direction][1]
if (!{{ROM name|$80:AE97}}(this.newX, this.newY)) {
if (!{{ROM name|$80:BF67}}(this.sprite, this.newX, this.newY)) {
this.x = this.newX
this.sprite.x = this.newX
this.y = this.newY
this.sprite.y = this.newY
}
} else {
this.followWall_Hard()
}
}
tryToMoveWhileTargeting() {
this.tempX = this.x
this.tempY = this.y
if (!{{ROM name|$80:AE97}}(this.newX, this.y) &&
!{{ROM name|$80:BF67}}(this.sprite, this.newX, this.y)) {
this.x = this.newX
}
if (!{{ROM name|$80:AE97}}(this.x, this.newY) &&
!{{ROM name|$80:BF67}}(this.sprite, this.x, this.newY)) {
this.y = this.newY
}
if (this.x != this.tempX || this.y != this.tempY) {
this.dontTargetTimer = 0
return false
} else {
return true
}
}
followWall_Hard() {
this.direction = ((this.direction - 1) + this.wallRotateAngle) % 8 + 1
this.setStateToFollowingWall_Hard()
}
wanderInRandomDirection_Hard() {
this.direction = ({{ROM name|$80:9D39}}() % 4) * 2 + 1
this.setStateToWandering_Hard()
}
setStateToFollowingWall_Hard() {
this.state = this.followingWallState_Hard
}
followingWallState_Hard() {
this.dontTargetTimer -= 1
this.newDirection = ((this.direction - 1) - this.wallRotateAngle) % 8 + 1
this.newX = this.x + this.moveAmount_Hard[this.newDirection][0]
this.newY = this.y + this.moveAmount_Hard[this.newDirection][1]
if (!this.newPositionIsBlocked()) {
this.direction = this.newDirection
}
this.newX = this.x + this.moveAmount_Hard[this.direction][0]
this.newY = this.y + this.moveAmount_Hard[this.direction][1]
if (!this.newPositionIsBlocked()) {
this.x = this.newX
this.sprite.x = this.newX
this.y = this.newY
this.sprite.y = this.newY
} else {
this.followWall_Hard()
}
}
unused818ACA() {
this.dontTargetTimer = 0
this.wanderInRandomDirection_Hard()
}
setStateToTargeting_Hard() {
this.state = this.targetingState_Hard
}
targetingState_Hard() {
distance, this.targetSprite = {{ROM name|$80:B123}}(this.x, this.y)
if (distance >= 180) {
this.wanderInRandomDirection_Hard()
return
}
{{ROM name|$80:B3F1}}(this.sprite, this.targetSprite)
this.direction = {{ROM name|$80:B22A}}(this.sprite, this.targetSprite)
if (this.direction == 0) {
this.wanderInRandomDirection_Hard()
return
}
this.targetDirection = this.direction
this.moveCounter = 1
while (this.moveCounter > 0) {
this.newX = this.x + this.moveAmount_Hard[this.targetDirection][0]
this.newY = this.y + this.moveAmount_Hard[this.targetDirection][1]
if (this.tryToMoveWhileTargeting()) {
this.endTargeting()
return
}
this.sprite.x = this.x
this.sprite.y = this.y
this.moveCounter -= 1
}
}
endTargeting() {
this.dontTargetTimer = 0
this.wanderInRandomDirection_Hard()
}
checkForTarget_Hard() {
this.dontTargetTimer -= 1
if (this.dontTargetTimer >= 0) {
return
}
distance = {{ROM name|$80:B123}}(this.x, this.y)
if (distance < 160) {
this.setStateToTargeting_Hard()
return
}
direction = {{ROM name|$80:B2A5}}(208, this.x, this.y)
if (direction == 0) {
this.deathStatus -= 1
}
}