local npcBatDeg = 0 local BaseNpcBatSpeed = 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 catcherThrownBall boolean ---@param deltaSec number ---@return number function Npc:updateBatAngle(ball, catcherThrownBall, deltaSec) if not catcherThrownBall 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 = 200 end return npcBatDeg end function Npc:batSpeed() return npcBatSpeed end ---@return number function Npc:runningSpeed() if #self.runners == 0 then return 0 end local touchedBase = utils.isTouchingBase(self.runners[1].x, self.runners[1].y) if not touchedBase or touchedBase == C.Bases[C.Home] then return 10 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 launchBall LaunchBall local function tryToMakeAPlay(fielders, fielder, runners, ball, launchBall) local targetX, targetY = getNextOutTarget(runners) if targetX ~= nil and targetY ~= nil then local nearestFielder = utils.getNearestOf(fielders, targetX, targetY) nearestFielder.target = utils.xy(targetX, targetY) if nearestFielder == fielder then ball.heldBy = fielder else launchBall(targetX, targetY, playdate.easingFunctions.linear, nil, true) end end end ---@param offenseState OffenseState ---@param fielder Fielder ---@param outedSomeRunner boolean ---@param ball { x: number, y: number, heldBy: Fielder | nil } ---@param launchBall LaunchBall function Npc:fielderAction(offenseState, fielder, outedSomeRunner, ball, launchBall) if offenseState ~= C.Offense.running then return end if outedSomeRunner then -- Delay a little before the next play playdate.timer.new(750, function() tryToMakeAPlay(self.fielders, fielder, self.runners, ball, launchBall) end) else tryToMakeAPlay(self.fielders, fielder, self.runners, ball, launchBall) end end ---@return number function Npc:pitchSpeed() return 2 end if not playdate then return Npc end