I could sworn I had released one, but I can't seem to find it. The script is below; I have no recollection on how good or bad it is (it is that old). The major issue with animal manipulation in S2 is that you have very few opportunities to do so compared to S1: you can only manipulate them by adding or deleting boss explosions (which is annoying because you need to use something like TASMovieEditor or have to redo everything from the explosion to the capsule hit) or by deleting or adding additional animals.
Both animals and boss explosions can be deleted by pausing a game so that the frame when the byte at FFFFFE0F has the low 3 bits clear (in RAM watch, make it a hex value, the rightmost digit must be 0 or 8). So pause right before, unpause right after.
To add animals or explosions, you want to pause in such a way that these frames happen more often when the game is unpaused; for example, pausing right after them, and unpausing right before them.
Anyway, the script:
Language: lua
local object_size = 0x40
local Object_RAM = 0xFFFFB000
local Dynamic_Object_RAM = Object_RAM + 16 * object_size
-- Object SST constants.
local render_flags = 0x1
local x_pos = 0x8
local y_pos = 0xC
local x_vel = 0x10
local y_vel = 0x12
local mapping_frame = 0x1A
local y_radius = 0x16
local x_radius = 0x17
local anim_frame_duration = 0x1E
local routine = 0x24
local routine_secondary = 0x25
local animal_ground_routine_base = 0x30
local animal_ground_x_vel = 0x32
local animal_ground_y_vel = 0x34
-- A few important RAM locations.
local RNG_seed = 0xFFFFF636
local Current_Zone = 0xFFFFFE10
local Vint_runcount = 0xFFFFFE0C
local Collision_addr = 0xFFFFF796
local Level_Layout = 0xFFFF8000
local ColCurveMap = 0x00042D50
local ColArray = 0x00042E50
local Camera_X_pos = 0xFFFFEE00
local Camera_Y_pos = 0xFFFFEE04
-- Function to find the main capsule object
local function find_capsule()
for addr=Dynamic_Object_RAM,Object_RAM + 0x2000,object_size do
if memory.readbyte(addr) == 0x3e and memory.readbyte(addr+routine) == 2 then
return addr
end
end
return nil
end
-- Function to enumerate all animals that matter for the capsule
local function enum_animals()
local animals = {}
for addr=Dynamic_Object_RAM,Object_RAM + 0x2000,object_size do
if memory.readbyte(addr)==0x28 and memory.readbyte(addr+routine) > 0 then
table.insert(animals, addr)
end
end
return animals
end
local function speed2color(speed, min, max)
local green = math.floor(((speed - min) * 255) / (max - min))
local red = math.floor(((max - speed) * 255) / (max - min))
local blue, alpha = 0, 255
return {red, green, blue, alpha}
end
local minspd, maxspd = 0x140, 0x300
-- Names for animals.
local animals = { [0]={"Rabbit" , speed2color(0x200, minspd, maxspd), -0x200, -0x400},
[1]={"Chicken" , speed2color(0x200, minspd, maxspd), -0x200, -0x300},
[2]={"Penguin" , speed2color(0x180, minspd, maxspd), -0x180, -0x300},
[3]={"Seal" , speed2color(0x140, minspd, maxspd), -0x140, -0x180},
[4]={"Pig" , speed2color(0x1C0, minspd, maxspd), -0x1C0, -0x300},
[5]={"Flicky" , speed2color(0x300, minspd, maxspd), -0x300, -0x400},
[6]={"Squirrel", speed2color(0x280, minspd, maxspd), -0x280, -0x380},
[7]={"Eagle" , speed2color(0x280, minspd, maxspd), -0x280, -0x300},
[8]={"Mouse" , speed2color(0x200, minspd, maxspd), -0x200, -0x380},
[9]={"Beaver" , speed2color(0x2C0, minspd, maxspd), -0x2C0, -0x300},
[10]={"Turtle" , speed2color(0x140, minspd, maxspd), -0x140, -0x200},
[11]={"Bear" , speed2color(0x200, minspd, maxspd), -0x200, -0x300}}
-- Map between zone ID and animal names.
local typelist = { [0]={[0]= 6, [1]= 5}, -- EHZ
[1]={[0]= 6, [1]= 5}, -- Zone 1
[2]={[0]= 6, [1]= 5}, -- WZ
[3]={[0]= 6, [1]= 5}, -- Zone 3
[4]={[0]= 9, [1]= 7}, -- MTZ
[5]={[0]= 9, [1]= 7}, -- MTZ
[6]={[0]= 9, [1]= 7}, -- WFZ
[7]={[0]= 9, [1]= 7}, -- HTZ
[8]={[0]= 8, [1]= 3}, -- HPZ
[9]={[0]= 8, [1]= 3}, -- Zone 9
[10]={[0]= 2, [1]= 3}, -- OOZ
[11]={[0]= 8, [1]= 1}, -- MCZ
[12]={[0]=11, [1]= 5}, -- CNZ
[13]={[0]= 0, [1]= 7}, -- CPZ
[14]={[0]= 4, [1]= 1}, -- DEZ
[15]={[0]= 2, [1]= 5}, -- ARZ
[16]={[0]=10, [1]= 1}} -- SCZ
-- Adddress of capsule switch in RAM.
local main_capsule,capsule_body,capsule_switch = nil
-- Internal variables.
local seed = 0
local last_seed = -1
local last_vbla = -1
-- Lua version of the Sonic 2 random number generator.
local function RandomNumber()
local d1 = seed or 0x2A6D365A
local d0 = d1 -- move.l d1,d0
d1 = SHIFT(d1, -2) -- asl.l #2,d1
d1 = d1 + d0 -- add.l d0,d1
d1 = SHIFT(d1, -3) -- asl.l #3,d1
d1 = d1 + d0 -- add.l d0,d1
d0 = OR(AND(d1, 0x0000FFFF), AND(d0, 0xFFFF0000)) -- move.w d1,d0
d1 = OR(SHIFT(AND(d1, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16)) -- swap d1
d0 = OR(AND(d1 + d0, 0x0000FFFF), AND(d0, 0xFFFF0000)) -- add.w d1,d0
d1 = OR(SHIFT(AND(d0, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16)) -- move.w d0,d1 \n swap d1
seed = d1 -- move.l d1,(RNG_seed).w
return d0
end
-- Lua version of the Sonic 2 function to find the nearest tile.
local function Floor_ChkTile(objRender, objX, objY)
-- move.w d2,d0 ; y_pos
-- add.w d0,d0
-- andi.w #$F00,d0 ; rounded 2*y_pos
-- move.w d3,d1 ; x_pos
-- lsr.w #3,d1
-- move.w d1,d4
-- lsr.w #4,d1 ; x_pos/128 = x_of_chunk
-- andi.w #$7F,d1
-- add.w d1,d0 ; d0 is relevant chunk ID now
local d4 = SHIFT(objX, 3)
local index = AND(AND(SHIFT(objY, -1), 0xF00) + AND(SHIFT(d4, 4), 0x7F),0xFFFF)
-- moveq #-1,d1
-- clr.w d1
-- lea (Level_Layout).w,a1
-- move.b (a1,d0.w),d1 ; move 128*128 chunk ID to d1
local chunknum = memory.readbyte(Level_Layout + index)
-- add.w d1,d1
-- move.w word_1E5D0(pc,d1.w),d1
-- move.w d2,d0 ; y_pos
-- andi.w #$70,d0
-- add.w d0,d1
-- andi.w #$E,d4 ; x_pos/8
-- add.w d4,d1
local chunkaddr = 0xFFFF0000 + AND(memory.readword(0x1E5D0 + 2*chunknum) + AND(objY,0x70) + AND(d4, 0xE), 0xFFFF)
-- movea.l d1,a1 ; address of block ID
-- rts
return chunkaddr
end
-- Lua version of the Sonic 2 secondary function to find the floor.
local function FindFloor2(objRender, objX, objY, solidbit, delta, initangle, flipmask)
local angle = initangle
local tileaddr = Floor_ChkTile(objRender, objX, objY) -- bsr.s Floor_ChkTile
local floordist = 0
local tile = memory.readword(tileaddr) -- move.w (a1),d0 \n move.w d0,d4
-- andi.w #$3FF,d0 \n beq.s loc_1E88A \n btst d5,d4 \n bne.s loc_1E898
local tileid = AND(tile, 0x3FF)
if tileid ~= 0 and AND(tile, solidbit) ~= 0 then
-- loc_1E898:
local colptr = memory.readlong(Collision_addr) -- movea.l (Collision_addr).w,a2
local block = memory.readbyte(colptr + tileid) -- move.b (a2,d0.w),d0
if block == 0 then -- beq.s loc_1E88A
-- loc_1E88A
return 0xF - AND(objY, 0xF), tileaddr, angle
end
angle = memory.readbytesigned(ColCurveMap + block) -- lea (ColCurveMap).l,a2 \n move.b (a2,d0.w),(a4)
block = SHIFT(block, -4) -- lsl.w #4,d0
local xcopy = objX -- move.w d3,d1
if AND(tile, BIT(0xA)) ~= 0 then -- btst #$A,d4 \n beq.s +
xcopy = -objX-1 -- not.w d1
angle = -angle -- neg.b (a4)
end
-- +
if AND(tile, BIT(0xB)) ~= 0 then --- btst #$B,d4 \n beq.s +
angle = -0x80 - angle -- addi.b #$40,(a4) \n neg.b (a4) \n subi.b #$40,(a4)
end
-- +
xcopy = AND(objX, 0xF) + block -- andi.w #$F,d1 \n add.w d0,d1
local colhgt = memory.readbytesigned(ColArray + xcopy) -- lea (ColArray).l,a2 \n move.b (a2,d1.w),d0 \n ext.w d0
tile = XOR(tile, flipmask) -- eor.w d6,d4
if AND(tile, BIT(0xB)) ~= 0 then -- btst #$B,d4 \n beq.s +
colhgt = -colhgt -- neg.w d0
end
-- +
if colhgt == 0 then -- tst.w d0 \n beq.s loc_1E88A
-- loc_1E88A
return 0xF - AND(objY, 0xF), tileaddr, angle
elseif colhgt < 0 then -- bmi.s loc_1E900
-- loc_1E900:
local dist = AND(objY, 0xF) -- move.w d2,d1 \n andi.w #$F,d1
if dist + colhgt < 0 then -- add.w d1,d0 \n bpl.w loc_1E88A
return -dist-1, tileaddr, angle -- not.w d1 \n rts
end
else
local dist = AND(objY, 0xF) -- move.w d2,d1 \n andi.w #$F,d1
return 0xF - (dist + colhgt), tileaddr, angle -- add.w d1,d0 \n move.w #$F,d1 \n sub.w d0,d1 \n rts
end
end
-- loc_1E88A:
-- move.w #$F,d1
-- move.w d2,d0
-- andi.w #$F,d0
-- sub.w d0,d1
-- rts
return 0xF - AND(objY, 0xF), tileaddr, angle
end
-- Lua version of the Sonic 2 function to find the floor.
local function FindFloor(objRender, objX, objY, solidbit, delta, initangle, flipmask)
local angle = initangle
local tileaddr = Floor_ChkTile(objRender, objX, objY) -- bsr.s Floor_ChkTile
local floordist = 0
local tile = memory.readword(tileaddr) -- move.w (a1),d0 \n move.w d0,d4
-- andi.w #$3FF,d0 \n beq.s loc_1E7E2 \n btst d5,d4 \n bne.s loc_1E7F0
local tileid = AND(tile, 0x3FF)
if tileid ~= 0 and AND(tile, solidbit) ~= 0 then
-- loc_1E7F0:
local colptr = memory.readlong(Collision_addr) -- movea.l (Collision_addr).w,a2
local block = memory.readbyte(colptr + tileid) -- move.b (a2,d0.w),d0 \n andi.w #$FF,d0
if block == 0 then -- beq.s loc_1E7E2
-- loc_1E7E2
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
return floordist + 0x10, tileaddr, angle
end
angle = memory.readbytesigned(ColCurveMap + block) -- lea (ColCurveMap).l,a2 \n move.b (a2,d0.w),(a4)
block = SHIFT(block, -4) -- lsl.w #4,d0
local xcopy = objX -- move.w d3,d1
if AND(tile, BIT(0xA)) ~= 0 then -- btst #$A,d4 \n beq.s +
xcopy = -objX-1 -- not.w d1
angle = -angle -- neg.b (a4)
end
-- +
if AND(tile, BIT(0xB)) ~= 0 then --- btst #$B,d4 \n beq.s +
angle = -0x80 - angle -- addi.b #$40,(a4) \n neg.b (a4) \n subi.b #$40,(a4)
end
-- +
xcopy = AND(objX, 0xF) + block -- andi.w #$F,d1 \n add.w d0,d1
local colhgt = memory.readbytesigned(ColArray + xcopy) -- lea (ColArray).l,a2 \n move.b (a2,d1.w),d0 \n ext.w d0
tile = XOR(tile, flipmask) -- eor.w d6,d4
if AND(tile, BIT(0xB)) ~= 0 then -- btst #$B,d4 \n beq.s +
colhgt = -colhgt -- neg.w d0
end
-- +
if colhgt == 0 then -- tst.w d0 \n beq.s loc_1E7E2
-- loc_1E7E2
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
return floordist + 0x10, tileaddr, angle
elseif colhgt < 0 then -- bmi.s loc_1E85E
-- loc_1E85E:
local dist = AND(objY, 0xF) -- move.w d2,d1 \n andi.w #$F,d1
if dist + colhgt < 0 then -- add.w d1,d0 \n bpl.w loc_1E7E2
-- loc_1E86A:
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask)
return floordist - 0x10, tileaddr, angle
end
elseif colhgt == 0x10 then -- cmpi.b #$10,d0 \n beq.s loc_1E86A
-- loc_1E86A:
-- sub.w a3,d2
-- bsr.w FindFloor2
-- add.w a3,d2
-- subi.w #$10,d1
-- rts
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask)
return floordist - 0x10, tileaddr, angle
else
-- move.w d2,d1
-- andi.w #$F,d1
-- add.w d1,d0
-- move.w #$F,d1
-- sub.w d0,d1
-- rts
return 0xF - (AND(objY, 0xF) + colhgt), tileaddr, angle
end
end
-- loc_1E7E2:
-- add.w a3,d2
-- bsr.w FindFloor2 ; try tile below the nearest
-- sub.w a3,d2
-- addi.w #$10,d1 ; return distance to floor
-- rts
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
return floordist + 0x10, tileaddr, angle
end
-- Lua version of the Sonic 2 function to find distance to the floor.
local function ObjCheckFloorDist(objX, objY, objH, objR)
--local objX = memory.readword(obj+x_pos) -- move.w x_pos(a0),d3
--local objY = memory.readword(obj+y_pos) -- move.w y_pos(a0),d2
--local objH = memory.readbytesigned(obj+y_radius) -- moveq #0,d0 \n move.b y_radius(a0),d0 \n ext.w d0
--local objR = memory.readbyte(obj+render_flags)
--objY = objY + objH -- add.w d0,d2
-- lea (v_anglebuffer).w,a4
-- move.b #0,(a4)
-- movea.w #$10,a3
-- move.w #0,d6
-- moveq #$D,d5
local floordist, tileaddr, angle = FindFloor(objR, objX, objY + objH, BIT(0xC), 0x10, 0, 0) -- bsr.w FindFloor
if AND(angle, 1) ~= 0 then
angle = 0 -- move.b (v_anglebuffer).w,d3 \n btst #0,d3 \n beq.s locret_14E4E \n move.b #0,d3
end
return floordist, tileaddr, angle -- locret_14E4E: rts
end
-- Lua version of the Sonic 2 function to make an object "fall".
local function ObjectMoveAndFall(objX, objY, objVX, objVY)
--local memory.readlong(obj+x_pos) -- move.l x_pos(a0),d2
--local memory.readlong(obj+y_pos) -- move.l y_pos(a0),d3
--local memory.readwordsigned(obj+x_vel) -- move.w x_vel(a0),d0 \n ext.l d0
objX = AND(objX + SHIFT(objVX, -8), 0xFFFFFFFF) -- asl.l #8,d0 \n add.l d0,d2
--local memory.readwordsigned(obj+y_vel) -- move.w y_vel(a0),d0 \n ext.l d0
objY = AND(objY + SHIFT(objVY, -8), 0xFFFFFFFF) -- asl.l #8,d0 \n add.l d0,d3
objVY = objVY + 0x38 -- addi.w #$38,y_vel(a0)
return objX, objY, objVX, objVY -- move.l d2,x_pos(a0) \n move.l d3,y_pos(a0) \n rts
end
-- Lua version of the Sonic 2 function loc_11ADE.
local function animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla)
-- tst.b render_flags(a0)
-- bpl.w DeleteObject
objX, objY, objVX, objVY = ObjectMoveAndFall(objX, objY, objVX, objVY) -- bsr.w ObjectMoveAndFall
if objVY >= 0 then -- tst.w y_vel(a0) \n bmi.s +
local floordist = ObjCheckFloorDist(SHIFT(objX, 16), SHIFT(objY, 16), objH, objR) -- jsr (ObjCheckFloorDist).l
if floordist < 0 then -- tst.w d1 \n bpl.s +
objY = objY + floordist -- add.w d1,y_pos(a0)
objVX = objNVX -- move.w animal_ground_x_vel(a0),x_vel(a0)
objVY = objNVY -- move.w animal_ground_y_vel(a0),y_vel(a0)
-- move.b #1,mapping_frame(a0)
-- move.b animal_ground_routine_base(a0),d0
-- add.b d0,d0
-- addq.b #4,d0
-- move.b d0,routine(a0)
-- tst.b objoff_38(a0)
-- beq.s +
if AND(vbla, BIT(4)) ~= 0 then -- btst #4,(Vint_runcount+3).w \n beq.s +
objVX = -objVX -- neg.w x_vel(a0)
objR = XOR(objR, BIT(0)) -- bchg #0,render_flags(a0)
end
end
end
-- +
-- bra.w DisplaySprite
return objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla
end
local function simulate_animal_delay_fall_escape(obj, objX, objY, objVX, objVY, objNVX, objNVY, objWait, vbla, capX)
local objH = 0xC
local objR = 0x85
local objW = 8
local initvbla = vbla
-- If we have a real object:
if obj ~= nil then
objX = memory.readlong(obj+x_pos)
objY = memory.readlong(obj+y_pos)
objVX = memory.readwordsigned(obj+x_vel)
objVY = memory.readwordsigned(obj+y_vel)
objNVX = memory.readwordsigned(obj+animal_ground_x_vel)
objNVY = memory.readwordsigned(obj+animal_ground_y_vel)
objWait = memory.readword(obj+0x36) - 1
end
-- Compute time to get offscreen
local le = SHIFT(Camera_X_pos - objW, -8)
local re = SHIFT(Camera_X_pos + 320 + objW, -8)
if objX <= le or objX >= re then
return 0, vbla
end
-- Simulate wait if needed
if objWait > 0 then
vbla = vbla + objWait + 1
end
-- Simulate falling
while objVX == 0 do
vbla = vbla + 1
objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla =
animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla)
end
-- Lets use relative position here.
objX = SHIFT(objX, 8)
local lastvbla = vbla
if objVX > 0 then
vbla = vbla + math.abs(math.floor((re - objX + objVX - 1) / objVX)) + 1
else
vbla = vbla + math.abs(math.floor((objX - le - objVX - 1) / -objVX)) + 1
end
return objVX, vbla
end
function print_animal_objects(vbla)
local animal_list = enum_animals()
if #animal_list > 0 then
row = 4
gui.drawtext(4, row, "Spawned animals:")
row = row + 8
gui.drawtext(12, row, "Animal Wait D Exit at")
row = row + 8
local capX = memory.readword(capsule_body+x_pos)
local list1 = {}
local list2 = {}
for i,m in pairs(animal_list) do
local ty = memory.readbyte(m+0x30)
local delay = memory.readword(m+0x36)
local vx, endvbla = simulate_animal_delay_fall_escape(m, nil, nil, nil, nil, nil,
nil, nil, vbla, capX)
local dir = (vx > 0 and ">") or "<"
local endpt = movie.framecount() + endvbla - vbla + 1
if m < capsule_switch then
endpt = endpt - 1
end
table.insert((delay == 0 and list2) or list1, {endpt, function(rw)
gui.drawtext(12, rw,
string.format("%-8s %4d %s %7d", animals[ty][1], delay, dir,
endpt), animals[ty][2])
end})
end
table.sort(list1, function(item1, item2)
return item1[1] < item2[1]
end)
table.sort(list2, function(item1, item2)
return item1[1] < item2[1]
end)
for i,m in pairs(list1) do
m[2](row)
row = row + 8
end
gui.drawtext(12, row, "========= Free =========")
row = row + 8
for i,m in pairs(list2) do
m[2](row)
row = row + 8
end
end
end
function predict_animals()
-- Try to find a capsule
main_capsule = find_capsule()
if main_capsule == nil then
-- If it was not found, so update variables and leave
last_seed = memory.readlong(RNG_seed)
last_vbla = memory.readbyte(Vint_runcount+3)
return
end
capsule_body = 0xFFFF0000 + memory.readword(main_capsule+0x3C)
capsule_switch = 0xFFFF0000 + memory.readword(main_capsule+0x38)
-- Update random seed to RAM value
seed = memory.readlong(RNG_seed)
-- Capsule switch's routine counter
local rout = memory.readbyte(main_capsule+routine_secondary)
-- V-Int counter, used for pseudo-random numbers
local vbla = memory.readbyte(Vint_runcount+3)
-- Sometimes, the V-Int counter does not update; doing this "manual"
-- update corrects the issue, which causes misprediction.
if last_vbla == vbla then
vbla = vbla + 1
end
local savevbla = vbla
-- Special case:
if rout == 0 and memory.readword(capsule_switch+0x32) ~= 1 then
-- Switch not pressed
gui.drawtext(4, 4, "Capsule not broken yet.")
last_seed = seed
last_vbla = savevbla
return
end
local row = 0
-- Draw container box.
gui.drawbox(0, 0, 223, 28*8-1, { 0, 0, 0, 128}, {255, 255, 255, 255})
if memory.readbyte(capsule_body+routine) == 0xA then
-- All animals spawned and moving to leave screen
gui.drawtext(128, 12, "Waiting for animals\nto leave screen.", {255, 255, 0, 255})
print_animal_objects(vbla)
last_seed = seed
last_vbla = savevbla
return
end
-- Get animal list for current zone.
local zone = memory.readbyte(Current_Zone)
local types = typelist[zone]
local le = Camera_X_pos - 8
local re = Camera_X_pos + 320 + 8
if rout == 0 then
-- Waiting for initial animals to be created.
row = 20
gui.drawtext(4, row, "Initial animals:")
row = row + 8
gui.drawtext(12, row, "Animal Wait D Exit at")
row = row + 8
local capX = memory.readword(main_capsule+x_pos)
local capY = memory.readword(main_capsule+y_pos)
row = row + 56
-- Initial animals
for i = 1, 8 do
-- Random number determines kind of animal.
local rand = RandomNumber()
local ty = types[AND(rand, 1)]
local objX = SHIFT(capX - 0x1C + 7 * i, -16)
local objY = SHIFT(capY, -16)
local delay = 0x9A + 8 - 8 * i
if le <= objX and objX <= re then
local vx, endvbla = simulate_animal_delay_fall_escape(nil, objX, objY, 0, -0x400,
animals[ty][3], animals[ty][4],
delay, vbla, capX)
local dir = (vx > 0 and ">") or "<"
gui.drawtext(12, row,
string.format("%-8s %4d %s %7d", animals[ty][1], delay, dir,
movie.framecount() + endvbla - vbla),
animals[ty][2])
end
row = row - 8
end
end
print_animal_objects(vbla)
if rout >= 2 then
-- Single animals are being generated.
-- Pseudo-random animal generation
row = 4
local saverow = row
row = row + 8
gui.drawtext(124, row, "Animal Spawn D Exit at")
row = row + 8
local animals_left = 0
local dt = 7 - AND(vbla, 7)
local timer = (memory.readbyte(capsule_body+routine_secondary) == 2 and memory.readword(capsule_body+anim_frame_duration)) or 0xB4
local capX = memory.readword(capsule_body+x_pos)
local capY = memory.readword(capsule_body+y_pos)
for i = 1, timer + 1 do
if AND(vbla + i - 1, 7) == 0 then
-- First random number is used to determine the position
-- offset from the switch.
local rand = AND(RandomNumber(), 0x1F) - 6
local pos = (seed >= 0 and rand) or -rand
-- Second random number determines animal type.
rand = RandomNumber()
local ty = types[AND(rand, 1)]
if i - 2 > 0 then
local objX = SHIFT(capX + pos, -16)
local objY = SHIFT(capY, -16)
local delay = i + 12
local vx, endvbla = simulate_animal_delay_fall_escape(nil, objX, objY, 0, -0x400,
animals[ty][3], animals[ty][4],
delay, vbla, capX)
local dir = (vx > 0 and ">") or "<"
if le <= objX and objX <= re then
gui.drawtext(124, row,
string.format("%-8s %4d %s %7d", animals[ty][1], delay-14, dir,
movie.framecount() + endvbla - vbla + (rout < 0xC and 1 or 0)),
animals[ty][2])
row = row + 8
end
animals_left = animals_left + 1
end
end
end
-- If we have animals still to be generated, and we are not in the
-- process of making explosions, we have an opportunity to change
-- if the next animal will be loaded or not.
if animals_left > 0 then
gui.drawtext(112, 180,
string.format("Next oportunity to prevent\nanimal spawn: %d frame%s",
dt, (dt == 1 and "") or "s"), {255, 255, 0, 255})
gui.drawtext(116, saverow, string.format("Animal spawns: %d left", animals_left))
end
end
last_seed = memory.readlong(RNG_seed)
last_vbla = savevbla
return
end
gens.registerafter(predict_animals)
savestate.registerload(predict_animals)