212 lines
6.3 KiB
Lua
212 lines
6.3 KiB
Lua
--- @alias Runner {
|
|
--- x: number,
|
|
--- y: number,
|
|
--- nextBase: Base,
|
|
--- prevBase: Base | nil,
|
|
--- forcedTo: Base | nil,
|
|
--- }
|
|
|
|
-- selene: allow(unscoped_variables)
|
|
baserunning = {
|
|
---@type Runner[]
|
|
runners = {},
|
|
|
|
---@type Runner[]
|
|
outRunners = {},
|
|
|
|
---@type Runner[]
|
|
scoredRunners = {},
|
|
|
|
---@type Runner | nil
|
|
batter = nil,
|
|
|
|
--- Since this object is what ultimately *mutates* the out count,
|
|
--- it seems sensible to store the value here.
|
|
outs = 0
|
|
}
|
|
|
|
---@param runner integer | Runner
|
|
---@param message string | nil
|
|
function baserunning:outRunner(runner, message)
|
|
self.outs = self.outs + 1
|
|
if type(runner) ~= "number" then
|
|
for i, maybe in ipairs(self.runners) do
|
|
if runner == maybe then
|
|
runner = i
|
|
end
|
|
end
|
|
end
|
|
if type(runner) ~= "number" then
|
|
error("Expected runner to have type 'number', but was: " .. type(runner))
|
|
end
|
|
self.outRunners[#self.outRunners + 1] = self.runners[runner]
|
|
table.remove(self.runners, runner)
|
|
|
|
self:updateForcedRunners()
|
|
|
|
announcer:say(message or "YOU'RE OUT!")
|
|
if self.outs < 3 then
|
|
return
|
|
end
|
|
|
|
-- TODO: outRunners/scoredRunners split
|
|
while #self.runners > 0 do
|
|
self.outRunners[#self.outRunners + 1] = table.remove(self.runners, #self.runners)
|
|
end
|
|
end
|
|
|
|
function baserunning:outEligibleRunners(fielder)
|
|
local touchedBase = utils.isTouchingBase(fielder.x, fielder.y)
|
|
local didOutRunner = false
|
|
for i, runner in pairs(self.runners) do
|
|
local runnerOnBase = utils.isTouchingBase(runner.x, runner.y)
|
|
if -- Force out
|
|
touchedBase
|
|
and runner.prevBase -- Make sure the runner is not standing at home
|
|
and runner.forcedTo == touchedBase
|
|
and touchedBase ~= runnerOnBase
|
|
-- Tag out
|
|
or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < C.TagDistance
|
|
then
|
|
self:outRunner(i)
|
|
didOutRunner = true
|
|
end
|
|
end
|
|
|
|
return didOutRunner
|
|
end
|
|
|
|
function baserunning:updateForcedRunners()
|
|
local stillForced = true
|
|
for _, base in ipairs(C.Bases) do
|
|
local runnerTargetingBase = utils.getRunnerWithNextBase(self.runners, base)
|
|
if runnerTargetingBase then
|
|
if stillForced then
|
|
runnerTargetingBase.forcedTo = base
|
|
else
|
|
runnerTargetingBase.forcedTo = nil
|
|
end
|
|
else
|
|
stillForced = false
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param deltaSeconds number
|
|
function baserunning:walkAwayOutRunners(deltaSeconds)
|
|
for i, runner in ipairs(self.outRunners) do
|
|
if runner.x < C.Screen.W + 50 and runner.y < C.Screen.H + 50 then
|
|
runner.x = runner.x + (deltaSeconds * 25)
|
|
runner.y = runner.y + (deltaSeconds * 25)
|
|
else
|
|
table.remove(self.outRunners, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
---@return Runner
|
|
function baserunning:newRunner()
|
|
local new = {
|
|
x = C.RightHandedBattersBox.x - 60,
|
|
y = C.RightHandedBattersBox.y + 60,
|
|
nextBase = C.RightHandedBattersBox,
|
|
prevBase = nil,
|
|
forcedTo = C.Bases[C.First],
|
|
}
|
|
self.runners[#self.runners + 1] = new
|
|
return new
|
|
end
|
|
|
|
baserunning.batter = baserunning:newRunner()
|
|
|
|
---@param self table
|
|
---@param runnerIndex integer
|
|
function baserunning:runnerScored(runnerIndex)
|
|
-- TODO: outRunners/scoredRunners split
|
|
self.outRunners[#self.outRunners + 1] = self.runners[runnerIndex]
|
|
table.remove(self.runners, runnerIndex)
|
|
end
|
|
|
|
--- Returns true only if the given runner moved during this update.
|
|
---@param runner Runner | nil
|
|
---@param runnerIndex integer | nil May only be nil if runner == batter
|
|
---@param appliedSpeed number
|
|
---@param deltaSeconds number
|
|
---@return boolean runnerMoved, boolean runnerScored
|
|
function baserunning:updateRunner(runner, runnerIndex, appliedSpeed, deltaSeconds)
|
|
local autoRunSpeed = 20 * deltaSeconds
|
|
|
|
if not runner or not runner.nextBase then
|
|
return false, false
|
|
end
|
|
|
|
local nearestBase, nearestBaseDistance = utils.getNearestOf(C.Bases, runner.x, runner.y)
|
|
|
|
if
|
|
nearestBaseDistance < 5
|
|
and runnerIndex ~= nil
|
|
and runner ~= self.batter --runner.prevBase
|
|
and runner.nextBase == C.Bases[C.Home]
|
|
and nearestBase == C.Bases[C.Home]
|
|
then
|
|
self:runnerScored(runnerIndex)
|
|
return true, true
|
|
end
|
|
|
|
local nb = runner.nextBase
|
|
local x, y, distance = utils.normalizeVector(runner.x, runner.y, nb.x, nb.y)
|
|
|
|
if distance < 2 then
|
|
runner.nextBase = C.NextBaseMap[runner.nextBase]
|
|
runner.forcedTo = nil
|
|
return false, false
|
|
end
|
|
|
|
local prevX, prevY = runner.x, runner.y
|
|
local mult = 1
|
|
if appliedSpeed < 0 then
|
|
if runner.prevBase then
|
|
mult = -1
|
|
else
|
|
-- Don't allow running backwards when approaching the plate
|
|
appliedSpeed = 0
|
|
end
|
|
end
|
|
|
|
local autoRun = (nearestBaseDistance > 40 or runner.forcedTo) and mult * autoRunSpeed
|
|
or nearestBaseDistance < 5 and 0
|
|
or (nearestBase == runner.nextBase and autoRunSpeed or -1 * autoRunSpeed)
|
|
|
|
mult = autoRun + (appliedSpeed / 20)
|
|
runner.x = runner.x - (x * mult)
|
|
runner.y = runner.y - (y * mult)
|
|
|
|
return prevX ~= runner.x or prevY ~= runner.y, false
|
|
end
|
|
|
|
--- Update non-batter runners.
|
|
--- Returns true only if at least one of the given runners moved during this update
|
|
---@param appliedSpeed number
|
|
---@return boolean someRunnerMoved, number runnersScored
|
|
function baserunning:updateRunning(appliedSpeed, forcedOnly, deltaSeconds)
|
|
local someRunnerMoved = false
|
|
local runnersScored = 0
|
|
|
|
-- TODO: Filter for the runner closest to the currently-held direction button
|
|
for runnerIndex, runner in ipairs(self.runners) do
|
|
if runner ~= self.batter and (not forcedOnly or runner.forcedTo) then
|
|
local thisRunnerMoved, thisRunnerScored = self:updateRunner(runner, runnerIndex, appliedSpeed, deltaSeconds)
|
|
someRunnerMoved = someRunnerMoved or thisRunnerMoved
|
|
if thisRunnerScored then
|
|
runnersScored = runnersScored + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
if someRunnerMoved then
|
|
self:updateForcedRunners()
|
|
end
|
|
|
|
return someRunnerMoved, runnersScored
|
|
end
|