Clone: Difference between revisions

7,542 bytes added ,  9 days ago
no edit summary
No edit summary
No edit summary
(8 intermediate revisions by the same user not shown)
Line 3:
The clone is a [[respawning monster]]. There are two different types of clones: normal and hard.
 
== Cloning behaviorBehavior ==
 
=== 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:
Line 17 ⟶ 19:
{{BPMN_embed|BPMN:Clone state diagram}}
 
=== 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.
Line 36 ⟶ 38:
{| class="wikitable"
|-
! Address !! Length !! [[Data types|Type]] !! Name !! Description
|-
| $00 || 2 || int16 || x || X position
|-
| $02 || 2 || int16 || y || Y position
|}
 
Line 47 ⟶ 49:
{| 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 || unusedint16 0-based || ValuefreezeTimer is|| setAmount butof nottime usedto stay [[Fire extinguisher|frozen]]
|}
 
== Pseudocode ==
 
init() {
{{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 = this.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}}(this.collisionHandler)
this.mimicking = false
this.collidedSpriteType = 0
this.animationFrame = 0
this.dead = false
this.freezeTimer = 0
}
determineClonedPlayer() {
clonedCharacter = null
player = 1
while (player >= 0) {
if (!{{RAM name|$7E:1E88}}[player]) {
clonedCharacter = {{RAM name|$7E:1E84}}[(player + 1) % 2]
break
}
player -= 1
}
if (clonedCharacter = null) {
clonedCharacter = {{ROM name|$80:9D39}}() % 2
}
this.walkingAnimations = this.walkingAnimationsPerCharacter[clonedCharacter]
this.spawnAnimation = this.spawnAnimationPerCharacter[clonedCharacter]
if ({{RAM name|$7E:1E88}}[0] && {{RAM name|$7E:1E84}}[0] == clonedCharacter) {
this.clonedPlayer = 0
} else {
this.clonedPlayer = 1
}
}
move(direction) {
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 = { { 0, 0 },
{ 0, -3 },
{ 3, -3 },
{ 3, 0 },
{ 3, 3 },
{ 0, 3 },
{ -3, 3 },
{ -3, 0 },
{ -3, -3 } }
updateAnimation() {
this.framesUntilAnimUpdate -= 1
if (this.framesUntilAnimUpdate == 0) {
this.framesUntilAnimUpdate = 7
this.animationFrame = (this.animationFrame + 1) % 4
this.sprite.tileData.lowBytes = this.walkingAnimations[this.direction][this.animationFrame]
}
}
target() {
this.xPixels = this.x / 4
this.yPixels = this.y / 4
this.direction = {{ROM name|$80:B2A5}}(240, this.xPixels, this.yPixels)
this.move(this.direction)
}
mimick() {
this.direction = {{RAM name|$7E:0072}}[this.clonedPlayer]
this.move(this.direction)
}
main_Normal() {
if (!{{RAM name|$7E:1E88}}[0] && !{{RAM name|$7E:1E88}}[1]) {
return
}
{{RAM name|$7E:00DE}} += 0x18
this.determineClonedPlayer()
this.init()
while (true) {
{{ROM name|$80:8353}}(1)
if (this.dead) {
break
}
this.updateAnimation()
if (this.mimicking) {
this.mimick()
} else {
this.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}}(this.deathAnimation_Normal, 0xF5F5, 0x90)
}
{{ROM name|$80:BE41}}(this.sprite)
{{RAM name|$7E:00DE}} -= 0x18
while ({{RAM name|$7E:00DE}} < 0) { }
}
deathAnimation_Normal = { { 0xA01D, 5 }, { 0xA06E, 5 }, { 0xA0A7, 5 }, { 0xA0D8, 5 },
{ 0xA0F9, 5 }, { 0xA11A, 5 }, { 0, 0 } }
main_Hard() {
if (!{{RAM name|$7E:1E88}}[0] && !{{RAM name|$7E:1E88}}[1]) {
return
}
{{RAM name|$7E:00DE}} += 0x18
this.determineClonedPlayer()
this.init()
while (true) {
{{ROM name|$80:8353}}(1)
if (this.health < 0) {
break
}
this.updateAnimation()
this.target()
if (this.direction == NONE) {
break
}
this.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}}(this.deathAnimation_Hard, 0xF5F5, 0x90)
}
{{ROM name|$80:BE41}}(this.sprite)
{{RAM name|$7E:00DE}} -= 0x18
while ({{RAM name|$7E:00DE}} < 0) { }
}
deathAnimation_Hard = { { 0xA01D, 5 }, { 0xA06E, 5 }, { 0xA0A7, 5 }, { 0xA0D8, 5 },
{ 0xA0F9, 5 }, { 0xA11A, 5 }, { 0, 0 } }
walkingAnimationsPerCharacter = { this.walkingAnimationsZeke, this.walkingAnimationsJulie }
walkingAnimationsZeke = { { 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 = { { 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 = { this.spawnAnimationZeke, this.spawnAnimationJulie }
spawnAnimationZeke = { 0x9F0D, 0x9F16, 0x9F27, 0x9F48, 0x9F69, 0x9F92, 0x9FC3, 0x9FF4,
0x9627, 0x9640, 0x9659 }
spawnAnimationJulie = { 0x9F0D, 0x9F16, 0x9F27, 0x9F48, 0x9F69, 0x9F92, 0x9FC3, 0x9FF4,
0x87C7, 0x87E0, 0x87F9 }
collisionHandler(spriteType) {
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
}
}
 
[[Category:Respawning monster]]
[[Category:Monster]]
[[Category:Entity]]