162 lines
4.7 KiB
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
|