mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
222 lines
7.3 KiB
Lua
222 lines
7.3 KiB
Lua
local U = require("tests.drivers.util")
|
|
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/game3_npc_player_collision"
|
|
|
|
local failures = 0
|
|
local function result(ok, label)
|
|
print((ok and "PASS " or "FAIL ") .. label)
|
|
if not ok then failures = failures + 1 end
|
|
return ok
|
|
end
|
|
|
|
local function finish()
|
|
if failures == 0 then
|
|
print("PASS game3_npc_player_collision")
|
|
love.event.quit(0)
|
|
else
|
|
print("FAIL game3_npc_player_collision failures=" .. failures)
|
|
love.event.quit(1)
|
|
end
|
|
end
|
|
|
|
local DELTA = {
|
|
up = { 0, -1 }, down = { 0, 1 }, left = { -1, 0 }, right = { 1, 0 },
|
|
}
|
|
local OPPOSITE = { up = "down", down = "up", left = "right", right = "left" }
|
|
local DIR_ORDER = { "right", "left", "down", "up" }
|
|
|
|
return function(game)
|
|
for _ = 1, 600 do
|
|
if game.phase == "boot" and game.boot then break end
|
|
U.wait(1)
|
|
end
|
|
|
|
game:_handleBootAction({ action = "new_game", name = "RED" })
|
|
U.wait(180)
|
|
|
|
local Runtime = require("src.core.game3.runtime")
|
|
local Map = require("src.core.game3.map")
|
|
local Objects = require("src.core.game3.objects")
|
|
local Collision = require("src.core.game3.collision")
|
|
local Player = require("src.core.game3.player")
|
|
|
|
local session = Runtime.getSession()
|
|
if not result(session ~= nil, "new game reached the field") then return finish() end
|
|
|
|
local mapId = "FR_PALLET_TOWN"
|
|
Map.load(nil, game, mapId, { x = 8, y = 10, facing = "down" })
|
|
game.session.x, game.session.y, game.session.facing = 8, 10, "down"
|
|
U.wait(60)
|
|
|
|
local wanderer, plan
|
|
for _, lid in ipairs(Objects.listActive()) do
|
|
local eo = Objects.find(lid)
|
|
if eo and tostring(eo.movement or ""):upper() == "WALK" then
|
|
local rx = (eo.radius and eo.radius.x) or 1
|
|
local ry = (eo.radius and eo.radius.y) or 1
|
|
for _, dir in ipairs(DIR_ORDER) do
|
|
local d = DELTA[dir]
|
|
local tx, ty = eo.cellX + d[1], eo.cellY + d[2]
|
|
local px, py = tx + d[1], ty + d[2]
|
|
if math.abs(tx - eo.homeX) <= rx and math.abs(ty - eo.homeY) <= ry
|
|
and Collision.canEnter(game, tx, ty, {}) and not Objects.at(tx, ty)
|
|
and Collision.canEnter(game, px, py, {}) and not Objects.at(px, py)
|
|
then
|
|
wanderer = eo
|
|
plan = { hx = eo.cellX, hy = eo.cellY, tx = tx, ty = ty, px = px, py = py,
|
|
into = OPPOSITE[dir], back = dir }
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if wanderer then break end
|
|
end
|
|
|
|
if not result(wanderer ~= nil and plan ~= nil, "found a wandering NPC with a free contested cell") then
|
|
return finish()
|
|
end
|
|
print(string.format("[driver] wanderer lid=%s at (%d,%d), contested cell (%d,%d), player start (%d,%d)",
|
|
tostring(wanderer.localId), wanderer.cellX, wanderer.cellY, plan.tx, plan.ty, plan.px, plan.py))
|
|
|
|
Map.load(nil, game, mapId, { x = plan.px, y = plan.py, facing = plan.into })
|
|
game.session.x, game.session.y, game.session.facing = plan.px, plan.py, plan.into
|
|
U.wait(60)
|
|
wanderer = Objects.find(wanderer.localId)
|
|
if not result(wanderer ~= nil, "wanderer survived the reload") then return finish() end
|
|
local homeX, homeY = plan.hx, plan.hy
|
|
wanderer.moving = false
|
|
wanderer.progress = 0
|
|
wanderer.cellX, wanderer.cellY = homeX, homeY
|
|
wanderer.targetX, wanderer.targetY = homeX, homeY
|
|
wanderer.px, wanderer.py = homeX * 16, homeY * 16
|
|
|
|
local overlaps, contested, midSteps, declines = 0, 0, 0, 0
|
|
local frozen, leashed = false, true
|
|
|
|
local function pin()
|
|
if leashed and wanderer.moving
|
|
and (wanderer.targetX ~= plan.tx or wanderer.targetY ~= plan.ty) then
|
|
wanderer.moving = false
|
|
wanderer.progress = 0
|
|
wanderer.targetX, wanderer.targetY = wanderer.cellX, wanderer.cellY
|
|
wanderer.px, wanderer.py = wanderer.cellX * 16, wanderer.cellY * 16
|
|
end
|
|
if not wanderer.moving and (wanderer.cellX ~= homeX or wanderer.cellY ~= homeY)
|
|
and not Objects.playerBlocks(homeX, homeY) then
|
|
wanderer.cellX, wanderer.cellY = homeX, homeY
|
|
wanderer.targetX, wanderer.targetY = homeX, homeY
|
|
wanderer.px, wanderer.py = homeX * 16, homeY * 16
|
|
end
|
|
wanderer.idleTimer = frozen and 600 or 0
|
|
end
|
|
|
|
local function declined()
|
|
return Player.moving and Player.targetX == plan.tx and Player.targetY == plan.ty
|
|
and not wanderer.moving and wanderer.cellX == homeX and wanderer.cellY == homeY
|
|
and wanderer.facing == plan.back
|
|
end
|
|
|
|
local function inspect()
|
|
if wanderer.cellX == Player.cellX and wanderer.cellY == Player.cellY then
|
|
overlaps = overlaps + 1
|
|
end
|
|
if Player.moving then
|
|
midSteps = midSteps + 1
|
|
if wanderer.moving and wanderer.targetX == Player.targetX
|
|
and wanderer.targetY == Player.targetY then
|
|
contested = contested + 1
|
|
end
|
|
if wanderer.moving and wanderer.targetX == Player.cellX
|
|
and wanderer.targetY == Player.cellY then
|
|
contested = contested + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local function walk(btn, frames)
|
|
for _ = 1, frames do
|
|
pin()
|
|
table.insert(game.input.pressQueue, btn)
|
|
game.input.state[btn] = true
|
|
coroutine.yield()
|
|
inspect()
|
|
if declined() then declines = declines + 1 end
|
|
end
|
|
game.input.state[btn] = false
|
|
end
|
|
|
|
local function settle()
|
|
for _ = 1, 40 do
|
|
if not Player.moving then return end
|
|
pin()
|
|
coroutine.yield()
|
|
inspect()
|
|
if declined() then declines = declines + 1 end
|
|
end
|
|
end
|
|
|
|
local function stepTo(gx, gy, onDecline)
|
|
local dx, dy = gx - Player.cellX, gy - Player.cellY
|
|
if dx == 0 and dy == 0 then return end
|
|
local btn = dx > 0 and "right" or dx < 0 and "left" or dy > 0 and "down" or "up"
|
|
local started = false
|
|
for _ = 1, 40 do
|
|
pin()
|
|
table.insert(game.input.pressQueue, btn)
|
|
game.input.state[btn] = true
|
|
coroutine.yield()
|
|
inspect()
|
|
if declined() then
|
|
declines = declines + 1
|
|
if onDecline and onDecline() then break end
|
|
end
|
|
if Player.moving then started = true elseif started then break end
|
|
end
|
|
game.input.state[btn] = false
|
|
settle()
|
|
end
|
|
|
|
for _ = 1, 12 do
|
|
stepTo(plan.tx, plan.ty)
|
|
stepTo(plan.px, plan.py)
|
|
end
|
|
|
|
leashed = false
|
|
for _ = 1, 6 do
|
|
walk(plan.into, 26)
|
|
walk(plan.back, 26)
|
|
end
|
|
settle()
|
|
leashed = true
|
|
if Player.cellX ~= plan.px or Player.cellY ~= plan.py then
|
|
Map.load(nil, game, mapId, { x = plan.px, y = plan.py, facing = plan.into })
|
|
game.session.x, game.session.y, game.session.facing = plan.px, plan.py, plan.into
|
|
U.wait(60)
|
|
wanderer = Objects.find(wanderer.localId)
|
|
if not result(wanderer ~= nil, "wanderer survived the second reload") then return finish() end
|
|
end
|
|
|
|
result(midSteps > 0, "player actually stepped (mid-step frames=" .. midSteps .. ")")
|
|
result(declines > 0, "NPC turned toward the player's destination and stayed put (declines=" .. declines .. ")")
|
|
result(overlaps == 0, "NPC never shared the player's cell (overlaps=" .. overlaps .. ")")
|
|
result(contested == 0,
|
|
"NPC never claimed the player's in-flight cells (contested=" .. contested .. ")")
|
|
|
|
local shot = false
|
|
for _ = 1, 40 do
|
|
stepTo(plan.tx, plan.ty, function()
|
|
if shot then return false end
|
|
frozen = true
|
|
pin()
|
|
shot = true
|
|
U.shot(game, DIR .. "/2307_01_npc_declines_player_cell.png")
|
|
return true
|
|
end)
|
|
if shot then break end
|
|
stepTo(plan.px, plan.py)
|
|
end
|
|
result(shot, "captured the NPC declining the player's destination")
|
|
U.wait(20)
|
|
|
|
finish()
|
|
end
|