Pan back from home runs
Move secondsSinceLastRunnerMove into Baserunning Tweak draw offset logic - a little jumpy, but better at following the long ball Taller GrassBackground Move secondsSinceLastPitch into pitchTracker Extract pitchTracker and throwMeter Slightly more truthful utils.moveAtSpeed()
This commit is contained in:
parent
aebbc35bac
commit
ad82035ccc
|
@ -12,6 +12,8 @@
|
|||
---@field scoredRunners Runner[]
|
||||
---@field batter Runner | nil
|
||||
---@field outs number
|
||||
-- TODO: Replace with timer, repeatedly reset, instead of constantly setting to 0
|
||||
---@field secondsSinceLastRunnerMove number
|
||||
---@field announcer Announcer
|
||||
---@field onThirdOut fun()
|
||||
Baserunning = {}
|
||||
|
@ -237,9 +239,9 @@ end
|
|||
--- Update non-batter runners.
|
||||
--- Returns true only if at least one of the given runners moved during this update
|
||||
---@param appliedSpeed number | fun(runner: Runner): number
|
||||
---@return boolean someRunnerMoved, number runnersScored
|
||||
---@return boolean runnersStillMoving, number runnersScored, number secondsSinceLastMove
|
||||
function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSeconds)
|
||||
local someRunnerMoved = false
|
||||
local runnersStillMoving = false
|
||||
local runnersScored = 0
|
||||
|
||||
local speedIsFunction = type(appliedSpeed) == "function"
|
||||
|
@ -251,18 +253,21 @@ function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSecon
|
|||
speed = appliedSpeed(runner)
|
||||
end
|
||||
local thisRunnerMoved, thisRunnerScored = self:updateRunner(runner, runnerIndex, speed, deltaSeconds)
|
||||
someRunnerMoved = someRunnerMoved or thisRunnerMoved
|
||||
runnersStillMoving = runnersStillMoving or thisRunnerMoved
|
||||
if thisRunnerScored then
|
||||
runnersScored = runnersScored + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if someRunnerMoved then
|
||||
if runnersStillMoving then
|
||||
self.secondsSinceLastRunnerMove = 0
|
||||
self:updateForcedRunners()
|
||||
else
|
||||
self.secondsSinceLastRunnerMove = self.secondsSinceLastRunnerMove + deltaSeconds
|
||||
end
|
||||
|
||||
return someRunnerMoved, runnersScored
|
||||
return runnersStillMoving, runnersScored, self.secondsSinceLastRunnerMove
|
||||
end
|
||||
|
||||
-- luacheck: ignore
|
||||
|
|
|
@ -70,8 +70,8 @@ C.BallOffscreen = 999
|
|||
C.PitchAfterSeconds = 6
|
||||
C.ReturnToPitcherAfterSeconds = 2.4
|
||||
C.PitchFlyMs = 1050
|
||||
C.PitchStartX = 195
|
||||
C.PitchStartY, C.PitchEndY = 105, 240
|
||||
C.PitchStart = utils.xy(195, 105)
|
||||
C.PitchEndY = 240
|
||||
|
||||
C.DefaultLaunchPower = 4
|
||||
|
||||
|
@ -91,7 +91,7 @@ C.SmallestBallRadius = 6
|
|||
|
||||
C.BatLength = 35
|
||||
|
||||
---@alias OffenseState "batting" | "running" | "walking"
|
||||
---@alias OffenseState "batting" | "running" | "walking" | "homeRun"
|
||||
--- An enum for what state the offense is in
|
||||
---@type table<string, OffenseState>
|
||||
C.Offense = {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
---@class Panner
|
||||
Panner = {}
|
||||
|
||||
local function panCoroutine(ball)
|
||||
local offset = utils.xy(getDrawOffset(ball.x, ball.y))
|
||||
while true do
|
||||
local target, deltaSeconds = coroutine.yield(offset.x, offset.y)
|
||||
if target == nil then
|
||||
offset = utils.xy(getDrawOffset(ball.x, ball.y))
|
||||
else
|
||||
while utils.moveAtSpeed(offset, 200 * deltaSeconds, target, 20) do
|
||||
target, deltaSeconds = coroutine.yield(offset.x, offset.y)
|
||||
end
|
||||
-- -- Pan back to ball
|
||||
-- while utils.moveAtSpeed(offset, 200 * deltaSeconds, ball, 20) do
|
||||
-- target, deltaSeconds = coroutine.yield(offset.x, offset.y)
|
||||
-- end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param ball XyPair
|
||||
function Panner.new(ball)
|
||||
return setmetatable({
|
||||
coroutine = coroutine.create(function()
|
||||
panCoroutine(ball)
|
||||
end),
|
||||
panTarget = nil,
|
||||
}, { __index = Panner })
|
||||
end
|
||||
|
||||
---@param deltaSeconds number
|
||||
---@return number offsetX, number offsetY
|
||||
function Panner:get(deltaSeconds)
|
||||
if self.holdUntil and self.holdUntil() then
|
||||
self:reset()
|
||||
end
|
||||
local _, offsetX, offsetY = coroutine.resume(self.coroutine, self.panTarget, deltaSeconds)
|
||||
return offsetX, offsetY
|
||||
end
|
||||
|
||||
---@param panTarget XyPair
|
||||
---@param holdUntil fun(): boolean
|
||||
function Panner:panTo(panTarget, holdUntil)
|
||||
self.panTarget = panTarget
|
||||
self.holdUntil = holdUntil
|
||||
end
|
||||
|
||||
function Panner:reset()
|
||||
self.holdUntil = nil
|
||||
self.panTarget = nil
|
||||
end
|
|
@ -5,8 +5,9 @@
|
|||
--- @field target XyPair | nil
|
||||
--- @field speed number
|
||||
|
||||
-- luacheck: ignore 631
|
||||
---@class Fielding
|
||||
---@field fielders table<string, Fielder>
|
||||
---@field fielders { first: Fielder, second: Fielder, shortstop: Fielder, third: Fielder, pitcher: Fielder, catcher: Fielder, left: Fielder, center: Fielder, right: Fielder }
|
||||
---@field fielderHoldingBall Fielder | nil
|
||||
Fielding = {}
|
||||
|
||||
|
@ -113,7 +114,7 @@ end
|
|||
---@param deltaSeconds number
|
||||
---@return Fielder | nil fielderHoldingBall nil if no fielder is currently touching the ball
|
||||
function Fielding:updateFielderPositions(ball, deltaSeconds)
|
||||
local fielderHoldingBall = nil
|
||||
local fielderHoldingBall
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
local inCatchingRange = updateFielderPosition(deltaSeconds, fielder, ball)
|
||||
if inCatchingRange and fielder.catchEligible then
|
||||
|
@ -137,10 +138,14 @@ function Fielding.markIneligible(fielder)
|
|||
end)
|
||||
end
|
||||
|
||||
function Fielding:markAllIneligible()
|
||||
function Fielding:markAllEligible(eligible)
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
fielder.catchEligible = false
|
||||
fielder.catchEligible = eligible
|
||||
end
|
||||
end
|
||||
|
||||
function Fielding:resetEligibility()
|
||||
self:markAllEligible(false)
|
||||
playdate.timer.new(750, function()
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
fielder.catchEligible = true
|
||||
|
|
|
@ -7,7 +7,8 @@ function getDrawOffset(ballX, ballY)
|
|||
if ballY > C.Screen.H or ballX >= C.BallOffscreen then
|
||||
return 0, 0
|
||||
end
|
||||
offsetY = math.max(0, -1 * ballY)
|
||||
-- Keep the ball approximately in the center, once it's past C.Center.y - 30
|
||||
offsetY = math.max(0, (-1 * ballY) + C.Center.y - 30)
|
||||
|
||||
if ballX > 0 and ballX < C.Screen.W then
|
||||
offsetX = 0
|
||||
|
@ -17,7 +18,7 @@ function getDrawOffset(ballX, ballY)
|
|||
offsetX = math.min(C.Screen.W * 2, (ballX * -1) + C.Screen.W)
|
||||
end
|
||||
|
||||
return offsetX * 1.3, offsetY * 1.5
|
||||
return offsetX * 1.3, offsetY
|
||||
end
|
||||
|
||||
---@class Blipper
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 43 KiB |
114
src/main.lua
114
src/main.lua
|
@ -29,6 +29,7 @@ import 'assets.lua'
|
|||
import 'draw/box-score.lua'
|
||||
import 'draw/fielder.lua'
|
||||
import 'draw/overlay.lua'
|
||||
import 'draw/panner.lua'
|
||||
import 'draw/player.lua'
|
||||
import 'draw/transitions.lua'
|
||||
|
||||
|
@ -42,6 +43,7 @@ import 'dbg.lua'
|
|||
import 'fielding.lua'
|
||||
import 'graphics.lua'
|
||||
import 'npc.lua'
|
||||
import 'pitching.lua'
|
||||
-- stylua: ignore end
|
||||
|
||||
-- TODO: Customizable field structure. E.g. stands and ads etc.
|
||||
|
@ -80,9 +82,6 @@ local teams <const> = {
|
|||
---@field batBase XyPair
|
||||
---@field batTip XyPair
|
||||
---@field batAngleDeg number
|
||||
-- TODO: Replace with timers, repeatedly reset, instead of constantly setting to 0
|
||||
---@field secondsSinceLastRunnerMove number
|
||||
---@field secondsSincePitchAllowed number
|
||||
-- These are only sort-of global state. They are purely graphical,
|
||||
-- but they need to be kept in sync with the rest of the globals.
|
||||
---@field runnerBlipper Blipper
|
||||
|
@ -97,6 +96,7 @@ local teams <const> = {
|
|||
---@field private npc Npc
|
||||
---@field private homeTeamBlipper Blipper
|
||||
---@field private awayTeamBlipper Blipper
|
||||
---@field private panner Panner
|
||||
---@field private state MutableState
|
||||
Game = {}
|
||||
|
||||
|
@ -124,6 +124,7 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
|
|||
fielding = fielding,
|
||||
homeTeamBlipper = homeTeamBlipper,
|
||||
awayTeamBlipper = awayTeamBlipper,
|
||||
panner = Panner.new(ball),
|
||||
state = state or {
|
||||
batBase = utils.xy(C.Center.x - 34, 215),
|
||||
batTip = utils.xy(0, 0),
|
||||
|
@ -135,8 +136,6 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
|
|||
inning = 1,
|
||||
pitchIsOver = false,
|
||||
didSwing = false,
|
||||
secondsSinceLastRunnerMove = 0,
|
||||
secondsSincePitchAllowed = 0,
|
||||
battingTeamSprites = settings.awayTeamSprites,
|
||||
fieldingTeamSprites = settings.homeTeamSprites,
|
||||
runnerBlipper = runnerBlipper,
|
||||
|
@ -151,7 +150,7 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
|
|||
|
||||
o.fielding:resetFielderPositions(teams.home.benchPosition)
|
||||
playdate.timer.new(2000, function()
|
||||
ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, false)
|
||||
o:returnToPitcher()
|
||||
end)
|
||||
|
||||
BootTune:play()
|
||||
|
@ -170,22 +169,22 @@ local Pitches <const> = {
|
|||
-- Fastball
|
||||
function()
|
||||
return {
|
||||
x = gfx.animator.new(0, C.PitchStartX, C.PitchStartX, playdate.easingFunctions.linear),
|
||||
y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
x = gfx.animator.new(0, C.PitchStart.x, C.PitchStart.x, playdate.easingFunctions.linear),
|
||||
y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
}
|
||||
end,
|
||||
-- Curve ball
|
||||
function()
|
||||
return {
|
||||
x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX + 20, C.PitchStartX, utils.easingHill),
|
||||
y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
x = gfx.animator.new(C.PitchFlyMs, C.PitchStart.x + 20, C.PitchStart.x, utils.easingHill),
|
||||
y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
}
|
||||
end,
|
||||
-- Slider
|
||||
function()
|
||||
return {
|
||||
x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX - 20, C.PitchStartX, utils.easingHill),
|
||||
y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
x = gfx.animator.new(C.PitchFlyMs, C.PitchStart.x - 20, C.PitchStart.x, utils.easingHill),
|
||||
y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
}
|
||||
end,
|
||||
-- Wobbbleball
|
||||
|
@ -193,11 +192,11 @@ local Pitches <const> = {
|
|||
return {
|
||||
x = {
|
||||
currentValue = function()
|
||||
return C.PitchStartX + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStartY) / 10))
|
||||
return C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
|
||||
end,
|
||||
reset = function() end,
|
||||
},
|
||||
y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
}
|
||||
end,
|
||||
}
|
||||
|
@ -260,7 +259,18 @@ function Game:pitch(pitchFlyTimeMs, pitchTypeIndex)
|
|||
self.state.ball.yAnimator:reset()
|
||||
end
|
||||
|
||||
self.state.secondsSincePitchAllowed = 0
|
||||
pitchTracker.secondsSinceLastPitch = 0
|
||||
end
|
||||
|
||||
function Game:pitcherIsReady()
|
||||
local pitcher = self.fielding.fielders.pitcher
|
||||
local pitcherIsOnTheMound = utils.distanceBetweenPoints(pitcher, C.PitcherStartPos) < C.BaseHitbox
|
||||
return pitcherIsOnTheMound
|
||||
and (
|
||||
self.state.ball.heldBy == pitcher
|
||||
or utils.distanceBetweenPoints(pitcher, self.state.ball) < C.BallCatchHitbox
|
||||
or utils.distanceBetweenPoints(self.state.ball, C.PitchStart) < 2
|
||||
)
|
||||
end
|
||||
|
||||
function Game:nextHalfInning()
|
||||
|
@ -279,7 +289,6 @@ function Game:nextHalfInning()
|
|||
end
|
||||
|
||||
Fielding.celebrate()
|
||||
self.state.secondsSinceLastRunnerMove = -7
|
||||
self.fielding:benchTo(self:getFieldingTeam().benchPosition)
|
||||
self.announcer:say("SWITCHING SIDES...")
|
||||
|
||||
|
@ -338,14 +347,14 @@ function Game:buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
|||
end
|
||||
|
||||
self.fielding:userThrowTo(targetBase, self.state.ball, throwFlyMs)
|
||||
self.state.secondsSinceLastRunnerMove = 0
|
||||
self.baserunning.secondsSinceLastRunnerMove = 0
|
||||
self.state.offenseState = C.Offense.running
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function Game:nextBatter()
|
||||
self.state.secondsSincePitchAllowed = -3
|
||||
pitchTracker.secondsSinceLastPitch = -3
|
||||
self.baserunning.batter = nil
|
||||
playdate.timer.new(2000, function()
|
||||
pitchTracker:reset()
|
||||
|
@ -390,6 +399,7 @@ function Game:updateBatting(batDeg, batSpeed)
|
|||
|
||||
if
|
||||
batSpeed > 0
|
||||
and self.state.ball.y < 232
|
||||
and utils.pointDirectlyUnderLine(
|
||||
self.state.ball.x,
|
||||
self.state.ball.y,
|
||||
|
@ -399,7 +409,6 @@ function Game:updateBatting(batDeg, batSpeed)
|
|||
self.state.batTip.y,
|
||||
C.Screen.H
|
||||
)
|
||||
and self.state.ball.y < 232
|
||||
then
|
||||
-- Hit!
|
||||
BatCrackReverb:play()
|
||||
|
@ -434,6 +443,12 @@ function Game:updateBatting(batDeg, batSpeed)
|
|||
if utils.within(1, self.state.ball.x, ballDestX) and utils.within(1, self.state.ball.y, ballDestY) then
|
||||
self.announcer:say("HOME RUN!")
|
||||
self.state.offenseState = C.Offense.homeRun
|
||||
-- Linger on the home-run ball for a moment, before panning to the bases.
|
||||
playdate.timer.new(1000, function()
|
||||
self.panner:panTo(utils.xy(0, 0), function()
|
||||
return self:pitcherIsReady()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
@ -447,14 +462,20 @@ function Game:updateBatting(batDeg, batSpeed)
|
|||
end
|
||||
|
||||
---@param appliedSpeed number | fun(runner: Runner): number
|
||||
---@return boolean someRunnerMoved
|
||||
---@return boolean runnersStillMoving, number secondsSinceLastRunnerMove
|
||||
function Game:updateNonBatterRunners(appliedSpeed, forcedOnly)
|
||||
local runnerMoved, runnersScored =
|
||||
local runnersStillMoving, runnersScored, secondsSinceLastRunnerMove =
|
||||
self.baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, self.state.deltaSeconds)
|
||||
if runnersScored ~= 0 then
|
||||
self:score(runnersScored)
|
||||
end
|
||||
return runnerMoved
|
||||
return runnersStillMoving, secondsSinceLastRunnerMove
|
||||
end
|
||||
|
||||
function Game:returnToPitcher()
|
||||
self.fielding:resetEligibility()
|
||||
self.fielding.fielders.pitcher.catchEligible = true
|
||||
self.state.ball:launch(C.PitchStart.x, C.PitchStart.y, playdate.easingFunctions.linear, nil, true)
|
||||
end
|
||||
|
||||
---@param throwFly number
|
||||
|
@ -494,18 +515,17 @@ function Game:updateGameState()
|
|||
|
||||
local pitcher = self.fielding.fielders.pitcher
|
||||
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitcherStartPos.x, C.PitcherStartPos.y) < C.BaseHitbox then
|
||||
self.state.secondsSincePitchAllowed = self.state.secondsSincePitchAllowed + self.state.deltaSeconds
|
||||
pitchTracker.secondsSinceLastPitch = pitchTracker.secondsSinceLastPitch + self.state.deltaSeconds
|
||||
end
|
||||
|
||||
if self.state.secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not self.state.pitchIsOver then
|
||||
if pitchTracker.secondsSinceLastPitch > C.ReturnToPitcherAfterSeconds and not self.state.pitchIsOver then
|
||||
local outcome = pitchTracker:updatePitchCounts(self.state.didSwing, self:fieldingTeamCurrentInning())
|
||||
if outcome == PitchOutcomes.StrikeOut then
|
||||
self:strikeOut()
|
||||
elseif outcome == PitchOutcomes.Walk then
|
||||
self:walk()
|
||||
end
|
||||
-- Catcher has the ball. Throw it back to the pitcher
|
||||
self.state.ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||
self:returnToPitcher()
|
||||
self.state.pitchIsOver = true
|
||||
self.state.didSwing = false
|
||||
end
|
||||
|
@ -525,7 +545,7 @@ function Game:updateGameState()
|
|||
-- Walk batter to the plate
|
||||
self.baserunning:updateRunner(self.baserunning.batter, nil, crankLimited, self.state.deltaSeconds)
|
||||
|
||||
if self.state.secondsSincePitchAllowed > C.PitchAfterSeconds then
|
||||
if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then
|
||||
if userOnDefense then
|
||||
local throwFly = throwMeter:readThrow()
|
||||
if throwFly and not self:buttonControlledThrow(throwFly, true) then
|
||||
|
@ -540,32 +560,32 @@ function Game:updateGameState()
|
|||
or function(runner)
|
||||
return self.npc:runningSpeed(runner, self.state.ball)
|
||||
end
|
||||
if self:updateNonBatterRunners(appliedSpeed) then
|
||||
self.state.secondsSinceLastRunnerMove = 0
|
||||
else
|
||||
self.state.secondsSinceLastRunnerMove = self.state.secondsSinceLastRunnerMove + self.state.deltaSeconds
|
||||
if self.state.secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
||||
-- End of play. Throw the ball back to the pitcher
|
||||
self.state.ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||
-- This is ugly, and ideally would not be necessary if Fielding handled the return throw directly.
|
||||
self.fielding:markAllIneligible()
|
||||
self.fielding:resetFielderPositions()
|
||||
self.state.offenseState = C.Offense.batting
|
||||
-- TODO: Remove, or replace with nextBatter()
|
||||
if not self.baserunning.batter then
|
||||
self.baserunning:pushNewBatter()
|
||||
end
|
||||
end
|
||||
local _, secondsSinceLastRunnerMove = self:updateNonBatterRunners(appliedSpeed, false)
|
||||
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
||||
-- End of play. Throw the ball back to the pitcher
|
||||
self.state.offenseState = C.Offense.batting
|
||||
self:returnToPitcher()
|
||||
end
|
||||
elseif self.state.offenseState == C.Offense.walking then
|
||||
if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true) then
|
||||
self.state.offenseState = C.Offense.batting
|
||||
end
|
||||
elseif self.state.offenseState == C.Offense.homeRun then
|
||||
self:updateNonBatterRunners(C.WalkedRunnerSpeed * 3, false)
|
||||
self:updateNonBatterRunners(C.WalkedRunnerSpeed * 2, false)
|
||||
if #self.baserunning.runners == 0 then
|
||||
self.state.offenseState = C.Offense.batting
|
||||
self.baserunning:pushNewBatter()
|
||||
-- Give the player a moment to enjoy their home run.
|
||||
self.fielding:resetFielderPositions()
|
||||
playdate.timer.new(1500, function()
|
||||
self:returnToPitcher()
|
||||
end)
|
||||
actionQueue:upsert("waitForPitcherToHaveBall", 10000, function()
|
||||
while not self:pitcherIsReady() do
|
||||
coroutine.yield()
|
||||
end
|
||||
self.fielding:markAllEligible(true)
|
||||
self.state.offenseState = C.Offense.batting
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -597,10 +617,10 @@ function Game:update()
|
|||
gfx.clear()
|
||||
gfx.setColor(gfx.kColorBlack)
|
||||
|
||||
local offsetX, offsetY = getDrawOffset(ball.x, ball.y)
|
||||
local offsetX, offsetY = self.panner:get(self.state.deltaSeconds)
|
||||
gfx.setDrawOffset(offsetX, offsetY)
|
||||
|
||||
GrassBackground:draw(-400, -240)
|
||||
GrassBackground:draw(-400, -720)
|
||||
|
||||
---@type { y: number, drawAction: fun() }[]
|
||||
local characterDraws = {}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
PitchOutcomes = {
|
||||
StrikeOut = "StrikeOut",
|
||||
Walk = "Walk",
|
||||
}
|
||||
|
||||
pitchTracker = {
|
||||
--- Position of the pitch, or nil, if one has not been recorded.
|
||||
---@type number | nil
|
||||
recordedPitchX = nil,
|
||||
|
||||
-- TODO: Replace with timer, repeatedly reset, instead of constantly setting to 0
|
||||
secondsSinceLastPitch = 0,
|
||||
|
||||
strikes = 0,
|
||||
balls = 0,
|
||||
}
|
||||
|
||||
function pitchTracker:reset()
|
||||
self.strikes = 0
|
||||
self.balls = 0
|
||||
end
|
||||
|
||||
function pitchTracker:recordIfPassed(ball)
|
||||
if ball.y < C.StrikeZoneStartY then
|
||||
self.recordedPitchX = nil
|
||||
elseif not pitchTracker.recordedPitchX then
|
||||
self.recordedPitchX = ball.x
|
||||
end
|
||||
end
|
||||
|
||||
---@param didSwing boolean
|
||||
---@param fieldingTeamInningData TeamInningData
|
||||
function pitchTracker:updatePitchCounts(didSwing, fieldingTeamInningData)
|
||||
if not self.recordedPitchX then
|
||||
return
|
||||
end
|
||||
|
||||
local currentPitchingStats = fieldingTeamInningData.pitching
|
||||
|
||||
if didSwing or self.recordedPitchX > C.StrikeZoneStartX and self.recordedPitchX < C.StrikeZoneEndX then
|
||||
self.strikes = self.strikes + 1
|
||||
currentPitchingStats.strikes = currentPitchingStats.strikes + 1
|
||||
if self.strikes >= 3 then
|
||||
self:reset()
|
||||
return PitchOutcomes.StrikeOut
|
||||
end
|
||||
else
|
||||
self.balls = self.balls + 1
|
||||
currentPitchingStats.balls = currentPitchingStats.balls + 1
|
||||
if self.balls >= 4 then
|
||||
self:reset()
|
||||
return PitchOutcomes.Walk
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------
|
||||
-- Throw Meter --
|
||||
-----------------
|
||||
|
||||
throwMeter = {
|
||||
value = 0,
|
||||
}
|
||||
|
||||
function throwMeter:reset()
|
||||
self.value = 0
|
||||
end
|
||||
|
||||
---@return number | nil flyTimeMs Returns nil when a throw is NOT requested.
|
||||
function throwMeter:readThrow()
|
||||
if self.value > C.ThrowMeterMax then
|
||||
return (C.PitchFlyMs / (self.value / C.ThrowMeterMax))
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Applies the given charge, but drains some meter for how much time has passed
|
||||
---@param deltaSeconds number
|
||||
---@param chargeAmount number
|
||||
function throwMeter:applyCharge(deltaSeconds, chargeAmount)
|
||||
self.value = math.max(0, self.value - (deltaSeconds * C.ThrowMeterDrainPerSec))
|
||||
self.value = self.value + math.abs(chargeAmount * C.UserThrowPower)
|
||||
end
|
104
src/utils.lua
104
src/utils.lua
|
@ -70,17 +70,24 @@ end
|
|||
---@param mover { x: number, y: number }
|
||||
---@param speed number
|
||||
---@param target { x: number, y: number }
|
||||
---@return boolean
|
||||
function utils.moveAtSpeed(mover, speed, target)
|
||||
---@param tau number | nil
|
||||
---@return boolean isStillMoving
|
||||
function utils.moveAtSpeed(mover, speed, target, tau)
|
||||
local x, y, distance = utils.normalizeVector(mover.x, mover.y, target.x, target.y)
|
||||
|
||||
if distance > 1 then
|
||||
mover.x = mover.x - (x * speed)
|
||||
mover.y = mover.y - (y * speed)
|
||||
return true
|
||||
if distance == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
return false
|
||||
if distance > (tau or 1) then
|
||||
mover.x = mover.x - (x * speed)
|
||||
mover.y = mover.y - (y * speed)
|
||||
else
|
||||
mover.x = target.x
|
||||
mover.y = target.y
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function utils.within(within, n1, n2)
|
||||
|
@ -249,87 +256,6 @@ function utils.totalScores(stats)
|
|||
return homeScore, awayScore
|
||||
end
|
||||
|
||||
PitchOutcomes = {
|
||||
StrikeOut = "StrikeOut",
|
||||
Walk = "Walk",
|
||||
}
|
||||
|
||||
pitchTracker = {
|
||||
--- Position of the pitch, or nil, if one has not been recorded.
|
||||
---@type number | nil
|
||||
recordedPitchX = nil,
|
||||
|
||||
strikes = 0,
|
||||
balls = 0,
|
||||
}
|
||||
|
||||
function pitchTracker:reset()
|
||||
self.strikes = 0
|
||||
self.balls = 0
|
||||
end
|
||||
|
||||
function pitchTracker:recordIfPassed(ball)
|
||||
if ball.y < C.StrikeZoneStartY then
|
||||
self.recordedPitchX = nil
|
||||
elseif not pitchTracker.recordedPitchX then
|
||||
self.recordedPitchX = ball.x
|
||||
end
|
||||
end
|
||||
|
||||
---@param didSwing boolean
|
||||
---@param fieldingTeamInningData TeamInningData
|
||||
function pitchTracker:updatePitchCounts(didSwing, fieldingTeamInningData)
|
||||
if not self.recordedPitchX then
|
||||
return
|
||||
end
|
||||
|
||||
local currentPitchingStats = fieldingTeamInningData.pitching
|
||||
|
||||
if didSwing or self.recordedPitchX > C.StrikeZoneStartX and self.recordedPitchX < C.StrikeZoneEndX then
|
||||
self.strikes = self.strikes + 1
|
||||
currentPitchingStats.strikes = currentPitchingStats.strikes + 1
|
||||
if self.strikes >= 3 then
|
||||
self:reset()
|
||||
return PitchOutcomes.StrikeOut
|
||||
end
|
||||
else
|
||||
self.balls = self.balls + 1
|
||||
currentPitchingStats.balls = currentPitchingStats.balls + 1
|
||||
if self.balls >= 4 then
|
||||
self:reset()
|
||||
return PitchOutcomes.Walk
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------
|
||||
-- Throw Meter --
|
||||
-----------------
|
||||
|
||||
throwMeter = {
|
||||
value = 0,
|
||||
}
|
||||
|
||||
function throwMeter:reset()
|
||||
self.value = 0
|
||||
end
|
||||
|
||||
---@return number | nil flyTimeMs Returns nil when a throw is NOT requested.
|
||||
function throwMeter:readThrow()
|
||||
if self.value > C.ThrowMeterMax then
|
||||
return (C.PitchFlyMs / (self.value / C.ThrowMeterMax))
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Applies the given charge, but drains some meter for how much time has passed
|
||||
---@param delta number
|
||||
---@param chargeAmount number
|
||||
function throwMeter:applyCharge(delta, chargeAmount)
|
||||
self.value = math.max(0, self.value - (delta * C.ThrowMeterDrainPerSec))
|
||||
self.value = self.value + math.abs(chargeAmount * C.UserThrowPower)
|
||||
end
|
||||
|
||||
if not playdate then
|
||||
return utils, { pitchTracker = pitchTracker, PitchOutcomes = PitchOutcomes, throwMeter = throwMeter }
|
||||
return utils
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue