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 -- TODO: FAR more nuanced NPC batting. ---@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) nearestFielder.target = utils.xy(targetX, targetY) if nearestFielder == fielder then ball.heldBy = fielder else ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true) 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