BatterUp/src/npc.lua

162 lines
4.7 KiB
Lua

local npcBatDeg = 0
local BaseNpcBatSpeed <const> = 1500
local npcBatSpeed = 1500
---@class Npc
---@field runners Runner[]
---@field fielders Fielder[]
-- selene: allow(unscoped_variables)
Npc = {}
---@param runners Runner[]
---@param fielders Fielder[]
---@return Npc
function Npc.new(runners, fielders)
return setmetatable({
runners = runners,
fielders = fielders,
}, { __index = Npc })
end
---@param ball XyPair
---@param pitchIsOver boolean
---@param deltaSec number
---@return number
-- luacheck: no unused
function Npc:updateBatAngle(ball, pitchIsOver, deltaSec)
if not pitchIsOver and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
else
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
npcBatDeg = 230
end
return npcBatDeg
end
function Npc:batSpeed()
return npcBatSpeed / 1.5
end
local baseRunningSpeed = 25
--- TODO: Individual runner control.
---@param runner Runner
---@param ball Point3d
---@return number
function Npc:runningSpeed(runner, ball)
if #self.runners == 0 then
return 0
end
local distanceFromBall = utils.distanceBetweenZ(ball.x, ball.y, ball.z, runner.x, runner.y, 0)
if distanceFromBall > 400 or runner.forcedTo then
return baseRunningSpeed
end
local touchedBase = utils.isTouchingBase(runner.x, runner.y)
if not touchedBase and runner.nextBase then
local distToNext = utils.distanceBetween(runner.x, runner.y, runner.nextBase.x, runner.nextBase.y)
local distToPrev = utils.distanceBetween(runner.x, runner.y, runner.prevBase.x, runner.prevBase.y)
if distToNext < distToPrev or distanceFromBall > 350 then
return baseRunningSpeed
else
return -1 * baseRunningSpeed
end
end
return 0
end
---@param runners Runner[]
---@return Base[]
local function getForcedOutTargets(runners)
local targets = {}
for _, base in ipairs(C.Bases) do
local runnerTargetingBase = utils.getRunnerWithNextBase(runners, base)
if runnerTargetingBase then
targets[#targets + 1] = base
else
return targets
end
end
return targets
end
--- Returns the position,distance of the base closest to the runner who is *furthest* from a base
---@param runners Runner[]
---@return Base | nil, number | nil
local function getBaseOfStrandedRunner(runners)
local farRunnersBase, farDistance
for _, runner in pairs(runners) do
--if runner ~= batter then
local nearestBase, distance = utils.getNearestOf(C.Bases, runner.x, runner.y)
if farRunnersBase == nil or farDistance < distance then
farRunnersBase = nearestBase
farDistance = distance
end
--end
end
return farRunnersBase, farDistance
end
--- Returns x,y of the out target
---@param runners Runner[]
---@return number|nil, number|nil
local function getNextOutTarget(runners)
-- TODO: Handle missed throws, check for fielders at target, etc.
local targets = getForcedOutTargets(runners)
if #targets ~= 0 then
return targets[#targets].x, targets[#targets].y
end
local baseCloseToStrandedRunner = getBaseOfStrandedRunner(runners)
if baseCloseToStrandedRunner then
return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y
end
end
---@param fielders Fielder[]
---@param fielder Fielder
---@param runners Runner[]
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
local function tryToMakeAPlay(fielders, fielder, runners, ball)
local targetX, targetY = getNextOutTarget(runners)
if targetX ~= nil and targetY ~= nil then
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY, function(grabCandidate)
return grabCandidate.catchEligible
end)
nearestFielder.target = utils.xy(targetX, targetY)
if nearestFielder == fielder then
ball.heldBy = fielder
else
ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
Fielding.markIneligible(nearestFielder)
end
end
end
---@param fielder Fielder
---@param outedSomeRunner boolean
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
function Npc:fielderAction(fielder, outedSomeRunner, ball)
if outedSomeRunner then
-- Delay a little before the next play
playdate.timer.new(750, function()
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
end)
else
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
end
end
---@return number
function Npc:pitchSpeed()
return 2
end
if not playdate then
return Npc
end