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
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
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
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
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
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}}
local typelist = { [0]={[0]= 6, [1]= 5},
[1]={[0]= 6, [1]= 5},
[2]={[0]= 6, [1]= 5},
[3]={[0]= 6, [1]= 5},
[4]={[0]= 9, [1]= 7},
[5]={[0]= 9, [1]= 7},
[6]={[0]= 9, [1]= 7},
[7]={[0]= 9, [1]= 7},
[8]={[0]= 8, [1]= 3},
[9]={[0]= 8, [1]= 3},
[10]={[0]= 2, [1]= 3},
[11]={[0]= 8, [1]= 1},
[12]={[0]=11, [1]= 5},
[13]={[0]= 0, [1]= 7},
[14]={[0]= 4, [1]= 1},
[15]={[0]= 2, [1]= 5},
[16]={[0]=10, [1]= 1}}
local main_capsule,capsule_body,capsule_switch = nil
local seed = 0
local last_seed = -1
local last_vbla = -1
local function RandomNumber()
local d1 = seed or 0x2A6D365A
local d0 = d1
d1 = SHIFT(d1, -2)
d1 = d1 + d0
d1 = SHIFT(d1, -3)
d1 = d1 + d0
d0 = OR(AND(d1, 0x0000FFFF), AND(d0, 0xFFFF0000))
d1 = OR(SHIFT(AND(d1, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16))
d0 = OR(AND(d1 + d0, 0x0000FFFF), AND(d0, 0xFFFF0000))
d1 = OR(SHIFT(AND(d0, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16))
seed = d1
return d0
end
local function Floor_ChkTile(objRender, objX, objY)
local d4 = SHIFT(objX, 3)
local index = AND(AND(SHIFT(objY, -1), 0xF00) + AND(SHIFT(d4, 4), 0x7F),0xFFFF)
local chunknum = memory.readbyte(Level_Layout + index)
local chunkaddr = 0xFFFF0000 + AND(memory.readword(0x1E5D0 + 2*chunknum) + AND(objY,0x70) + AND(d4, 0xE), 0xFFFF)
return chunkaddr
end
local function FindFloor2(objRender, objX, objY, solidbit, delta, initangle, flipmask)
local angle = initangle
local tileaddr = Floor_ChkTile(objRender, objX, objY)
local floordist = 0
local tile = memory.readword(tileaddr)
local tileid = AND(tile, 0x3FF)
if tileid ~= 0 and AND(tile, solidbit) ~= 0 then
local colptr = memory.readlong(Collision_addr)
local block = memory.readbyte(colptr + tileid)
if block == 0 then
return 0xF - AND(objY, 0xF), tileaddr, angle
end
angle = memory.readbytesigned(ColCurveMap + block)
block = SHIFT(block, -4)
local xcopy = objX
if AND(tile, BIT(0xA)) ~= 0 then
xcopy = -objX-1
angle = -angle
end
if AND(tile, BIT(0xB)) ~= 0 then
angle = -0x80 - angle
end
xcopy = AND(objX, 0xF) + block
local colhgt = memory.readbytesigned(ColArray + xcopy)
tile = XOR(tile, flipmask)
if AND(tile, BIT(0xB)) ~= 0 then
colhgt = -colhgt
end
if colhgt == 0 then
return 0xF - AND(objY, 0xF), tileaddr, angle
elseif colhgt < 0 then
local dist = AND(objY, 0xF)
if dist + colhgt < 0 then
return -dist-1, tileaddr, angle
end
else
local dist = AND(objY, 0xF)
return 0xF - (dist + colhgt), tileaddr, angle
end
end
return 0xF - AND(objY, 0xF), tileaddr, angle
end
local function FindFloor(objRender, objX, objY, solidbit, delta, initangle, flipmask)
local angle = initangle
local tileaddr = Floor_ChkTile(objRender, objX, objY)
local floordist = 0
local tile = memory.readword(tileaddr)
local tileid = AND(tile, 0x3FF)
if tileid ~= 0 and AND(tile, solidbit) ~= 0 then
local colptr = memory.readlong(Collision_addr)
local block = memory.readbyte(colptr + tileid)
if block == 0 then
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
return floordist + 0x10, tileaddr, angle
end
angle = memory.readbytesigned(ColCurveMap + block)
block = SHIFT(block, -4)
local xcopy = objX
if AND(tile, BIT(0xA)) ~= 0 then
xcopy = -objX-1
angle = -angle
end
if AND(tile, BIT(0xB)) ~= 0 then
angle = -0x80 - angle
end
xcopy = AND(objX, 0xF) + block
local colhgt = memory.readbytesigned(ColArray + xcopy)
tile = XOR(tile, flipmask)
if AND(tile, BIT(0xB)) ~= 0 then
colhgt = -colhgt
end
if colhgt == 0 then
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
return floordist + 0x10, tileaddr, angle
elseif colhgt < 0 then
local dist = AND(objY, 0xF)
if dist + colhgt < 0 then
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask)
return floordist - 0x10, tileaddr, angle
end
elseif colhgt == 0x10 then
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask)
return floordist - 0x10, tileaddr, angle
else
return 0xF - (AND(objY, 0xF) + colhgt), tileaddr, angle
end
end
floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
return floordist + 0x10, tileaddr, angle
end
local function ObjCheckFloorDist(objX, objY, objH, objR)
local floordist, tileaddr, angle = FindFloor(objR, objX, objY + objH, BIT(0xC), 0x10, 0, 0)
if AND(angle, 1) ~= 0 then
angle = 0
end
return floordist, tileaddr, angle
end
local function ObjectMoveAndFall(objX, objY, objVX, objVY)
objX = AND(objX + SHIFT(objVX, -8), 0xFFFFFFFF)
objY = AND(objY + SHIFT(objVY, -8), 0xFFFFFFFF)
objVY = objVY + 0x38
return objX, objY, objVX, objVY
end
local function animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla)
objX, objY, objVX, objVY = ObjectMoveAndFall(objX, objY, objVX, objVY)
if objVY >= 0 then
local floordist = ObjCheckFloorDist(SHIFT(objX, 16), SHIFT(objY, 16), objH, objR)
if floordist < 0 then
objY = objY + floordist
objVX = objNVX
objVY = objNVY
if AND(vbla, BIT(4)) ~= 0 then
objVX = -objVX
objR = XOR(objR, BIT(0))
end
end
end
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 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
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
if objWait > 0 then
vbla = vbla + objWait + 1
end
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
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()
main_capsule = find_capsule()
if main_capsule == nil then
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)
seed = memory.readlong(RNG_seed)
local rout = memory.readbyte(main_capsule+routine_secondary)
local vbla = memory.readbyte(Vint_runcount+3)
if last_vbla == vbla then
vbla = vbla + 1
end
local savevbla = vbla
if rout == 0 and memory.readword(capsule_switch+0x32) ~= 1 then
gui.drawtext(4, 4, "Capsule not broken yet.")
last_seed = seed
last_vbla = savevbla
return
end
local row = 0
gui.drawbox(0, 0, 223, 28*8-1, { 0, 0, 0, 128}, {255, 255, 255, 255})
if memory.readbyte(capsule_body+routine) == 0xA then
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
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
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
for i = 1, 8 do
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
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
local rand = AND(RandomNumber(), 0x1F) - 6
local pos = (seed >= 0 and rand) or -rand
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 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)