Extract almost all NPC fielding logic

This commit is contained in:
Sage Vaillancourt 2025-02-08 19:04:10 -05:00
parent 80c15161e3
commit 30f2eada72
4 changed files with 81 additions and 78 deletions

View File

@ -94,7 +94,9 @@ C.ThrowMeterDrainPerSec = 150
--- Controls how hard the ball can be hit, and --- Controls how hard the ball can be hit, and
--- how fast the ball can be thrown. --- how fast the ball can be thrown.
C.CrankPower = 3 C.CrankPower = 10
C.PitchPower = 0.3
--- How fast baserunners move after a walk --- How fast baserunners move after a walk
C.WalkedRunnerSpeed = 10 C.WalkedRunnerSpeed = 10

View File

@ -85,7 +85,7 @@ local teams <const> = {
}, },
} }
local PlayerTeam <const> = teams.home local PlayerTeam <const> = teams.away
local battingTeam = teams.away local battingTeam = teams.away
local outs = 0 local outs = 0
local inning = 1 local inning = 1
@ -206,18 +206,10 @@ local function pitch(pitchFlyTimeMs, pitchTypeIndex)
secondsSincePitchAllowed = 0 secondsSincePitchAllowed = 0
end end
---@param base Base
---@return Runner | nil
local function getRunnerWithNextBase(base)
return utils.first(runners, function(runner)
return runner.nextBase == base
end)
end
local function updateForcedRunners() local function updateForcedRunners()
local stillForced = true local stillForced = true
for _, base in ipairs(C.Bases) do for _, base in ipairs(C.Bases) do
local runnerTargetingBase = getRunnerWithNextBase(base) local runnerTargetingBase = utils.getRunnerWithNextBase(runners, base)
if runnerTargetingBase then if runnerTargetingBase then
if stillForced then if stillForced then
runnerTargetingBase.forcedTo = base runnerTargetingBase.forcedTo = base
@ -287,66 +279,6 @@ local function score(runnerIndex)
announcer:say("SCORE!") announcer:say("SCORE!")
end end
---@return Base[]
local function npcGetForcedOutTargets()
local targets = {}
for _, base in ipairs(C.Bases) do
local runnerTargetingBase = getRunnerWithNextBase(base)
if runnerTargetingBase then
targets[#targets + 1] = base
else
return targets
end
end
return targets
end
--- Returns the position,distance of the basest closest to the runner furthest from a base
---@return Base | nil, number | nil
local function getBaseOfStrandedRunner()
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
---@return number|nil, number|nil
local function npcGetNextOutTarget()
-- TODO: Handle missed throws, check for fielders at target, etc.
local targets = npcGetForcedOutTargets()
if #targets ~= 0 then
return targets[#targets].x, targets[#targets].y
end
local baseCloseToStrandedRunner = getBaseOfStrandedRunner()
if baseCloseToStrandedRunner then
return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y
end
end
---@param fielder Fielder
local function npcTryToMakeAnOut(fielder)
local targetX, targetY = npcGetNextOutTarget()
if targetX ~= nil and targetY ~= nil then
local nearestFielder = utils.getNearestOf(Field.fielders, targetX, targetY)
nearestFielder.target = utils.xy(targetX, targetY)
if nearestFielder == fielder then
ball.heldBy = fielder
else
throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
end
end
end
local function readThrow() local function readThrow()
if throwMeter > C.ThrowMeterMax then if throwMeter > C.ThrowMeterMax then
return (C.PitchFlyMs / (throwMeter / C.ThrowMeterMax)) return (C.PitchFlyMs / (throwMeter / C.ThrowMeterMax))
@ -404,16 +336,17 @@ local function outEligibleRunners(fielder)
return didOutRunner return didOutRunner
end end
local function updateNpcFielder(fielder, outedSomeRunner) local function npcFielderAction(fielder, outedSomeRunner)
if offenseMode ~= C.Offense.running then if offenseMode ~= C.Offense.running then
return return
end end
if outedSomeRunner then if outedSomeRunner then
-- Delay a little before the next play
playdate.timer.new(750, function() playdate.timer.new(750, function()
npcTryToMakeAnOut(fielder) npc.tryToMakeAPlay(fielder, runners, ball, throwBall)
end) end)
else else
npcTryToMakeAnOut(fielder) npc.tryToMakeAPlay(fielder, runners, ball, throwBall)
end end
end end
@ -617,7 +550,7 @@ local function updateGameState()
if playerOnDefense then if playerOnDefense then
throwMeter = math.max(0, throwMeter - (deltaSeconds * C.ThrowMeterDrainPerSec)) throwMeter = math.max(0, throwMeter - (deltaSeconds * C.ThrowMeterDrainPerSec))
throwMeter = throwMeter + math.abs(crankLimited) throwMeter = throwMeter + math.abs(crankLimited * C.PitchPower)
end end
if offenseMode == C.Offense.batting then if offenseMode == C.Offense.batting then
@ -699,7 +632,7 @@ local function updateGameState()
buttonControlledThrow(Field.fielders.pitcher, throwFly) buttonControlledThrow(Field.fielders.pitcher, throwFly)
end end
else else
updateNpcFielder(fielder, outedSomeRunner) npcFielderAction(fielder, outedSomeRunner)
end end
end end

View File

@ -30,6 +30,66 @@ function npc.runningSpeed(runners)
return 0 return 0
end end
if not playdate then ---@return Base[]
return utils 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 basest closest to the runner furthest from a base
---@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
---@return number|nil, number|nil
function npc.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 fielder Fielder
function npc.tryToMakeAPlay(fielder, runners, ball, throwBall)
local targetX, targetY = npc.getNextOutTarget(runners)
if targetX ~= nil and targetY ~= nil then
local nearestFielder = utils.getNearestOf(Field.fielders, targetX, targetY)
nearestFielder.target = utils.xy(targetX, targetY)
if nearestFielder == fielder then
ball.heldBy = fielder
else
throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
end
end
end
if not playdate then
return npc
end end

View File

@ -142,6 +142,14 @@ function utils.isTouchingBase(x, y)
end) end)
end end
---@param base Base
---@return Runner | nil runner The runner whose next base matches the given base.
function utils.getRunnerWithNextBase(runners, base)
return utils.first(runners, function(runner)
return runner.nextBase == base
end)
end
--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound --- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound
--- @return boolean --- @return boolean
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound) function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)