Fix flickering on return-to-pitcher.

Some linting.
Prevent runners "sticking" to bases during walk/homeRun sequences.
Tweak logo to remove some trailing pixels.
This commit is contained in:
Sage Vaillancourt 2025-02-21 15:05:14 -05:00
parent 786f80b0df
commit 7b49603760
9 changed files with 94 additions and 40 deletions

View File

@ -25,6 +25,16 @@ function actionQueue:upsert(id, maxTimeMs, action)
}
end
function actionQueue:newOnly(id, maxTimeMs, action)
if self.queue[id] then
return
end
self.queue[id] = {
coroutine = coroutine.create(action),
expireTimeMs = maxTimeMs + playdate.getCurrentTimeMilliseconds(),
}
end
--- Must be called on every playdate.update() to check for (and run) any waiting tasks.
--- Actions that return NeedsMoreTime will not be removed from the queue unless they have expired.
function actionQueue:runWaiting(deltaSeconds)

View File

@ -179,7 +179,7 @@ end
---@param appliedSpeed number
---@param deltaSeconds number
---@return boolean runnerMoved, boolean runnerScored
function Baserunning:updateRunner(runner, runnerIndex, appliedSpeed, deltaSeconds)
function Baserunning:updateRunner(runner, runnerIndex, appliedSpeed, isAutoRun, deltaSeconds)
local autoRunSpeed = 20 * deltaSeconds
if not runner or not runner.nextBase then
@ -226,9 +226,12 @@ function Baserunning:updateRunner(runner, runnerIndex, appliedSpeed, deltaSecond
-- TODO: Make this less "sticky" for the user.
-- Currently it can be a little hard to run *past* a base.
local autoRun = (nearestBaseDistance > 40 or runner.forcedTo) and mult * autoRunSpeed
or nearestBaseDistance < 5 and 0
or (nearestBase == runner.nextBase and autoRunSpeed or -1 * autoRunSpeed)
local autoRun = 0
if not isAutoRun then
autoRun = (nearestBaseDistance > 40 or runner.forcedTo) and mult * autoRunSpeed
or nearestBaseDistance < 5 and 0
or (nearestBase == runner.nextBase and autoRunSpeed or -1 * autoRunSpeed)
end
mult = autoRun + (appliedSpeed / 20)
runner.x = runner.x - (x * mult)
@ -240,8 +243,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
---@param isAutoRun boolean If true, does not attempt to hug the bases
---@return boolean runnersStillMoving, number runnersScored, number secondsSinceLastMove
function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSeconds)
function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun, deltaSeconds)
local runnersStillMoving = false
local runnersScored = 0
@ -253,7 +257,7 @@ function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSecon
if speedIsFunction then
speed = appliedSpeed(runner)
end
local thisRunnerMoved, thisRunnerScored = self:updateRunner(runner, runnerIndex, speed, deltaSeconds)
local thisRunnerMoved, thisRunnerScored = self:updateRunner(runner, runnerIndex, speed, isAutoRun, deltaSeconds)
runnersStillMoving = runnersStillMoving or thisRunnerMoved
if thisRunnerScored then
runnersScored = runnersScored + 1

View File

@ -1,4 +1,4 @@
local gfx <const>, C <const> = playdate.graphics, C
local gfx <const> = playdate.graphics
fans = {}

View File

@ -15,7 +15,7 @@ function throwMeter:draw(x, y)
if self.lastReadThrow then
-- TODO: If meter has moved to a new fielder, empty it.
local ratio = 1
if not wasPerfect then
if not self.wasPerfect then
ratio = (self.lastReadThrow - throwMeter.MinCharge) / (self.idealPower - throwMeter.MinCharge)
end
local height = ThrowMeterHeight * ratio

View File

@ -31,7 +31,7 @@ local function newFielder(name, speed)
return {
name = name,
speed = speed * C.FielderRunMult,
spriteIndex = math.random(#HomeTeamSpriteGroup)
spriteIndex = math.random(#HomeTeamSpriteGroup),
}
end

View File

@ -32,12 +32,10 @@ function blipper.new(msInterval, spriteCollection)
blinker:start()
return {
blinker = blinker,
smiling = smiling,
lowHat = lowHat,
draw = function(self, disableBlipping, x, y, hasSpriteIndex)
local spriteBundle = spriteCollection[hasSpriteIndex.spriteIndex]
local currentImage = (disableBlipping or self.blinker.on) and spriteBundle.lowHat or spriteBundle.smiling
local offsetY = currentImage == lowHat and -1 or 0
local offsetY = currentImage == spriteBundle.lowHat and -1 or 0
currentImage:draw(x, y + offsetY)
end,
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -6,6 +6,7 @@ import 'CoreLibs/graphics.lua'
import 'CoreLibs/object.lua'
import 'CoreLibs/timer.lua'
import 'CoreLibs/ui.lua'
import 'CoreLibs/utilities/where.lua'
-- stylua: ignore end
--- @alias Scene { update: fun(self: self) }
@ -112,7 +113,7 @@ Game = {}
function Game.new(settings, announcer, fielding, baserunning, npc, state)
announcer = announcer or Announcer.new()
fielding = fielding or Fielding.new()
settings.userTeam = "away"
settings.userTeam = nil -- "away"
local homeTeamBlipper = blipper.new(100, settings.homeTeamSpriteGroup)
local awayTeamBlipper = blipper.new(100, settings.awayTeamSpriteGroup)
@ -388,25 +389,31 @@ function Game:updateBatting(batDeg, batSpeed)
ballVelX = ballVelX * -1
ballVelY = ballVelY * -1
end
local ballDestX = self.state.ball.x + (ballVelX * C.BattingPower)
local ballDestY = self.state.ball.y + (ballVelY * C.BattingPower)
local ballDest = utils.xy(
self.state.ball.x + (ballVelX * C.BattingPower),
-250 + self.state.ball.y + (ballVelY * C.BattingPower)
)
pitchTracker:reset()
local flyTimeMs = 2000
-- TODO? A dramatic eye-level view on a home-run could be sick.
local battingTeamStats = self:battingTeamCurrentInning()
battingTeamStats.hits[#battingTeamStats.hits + 1] = utils.xy(ballDestX, ballDestY)
battingTeamStats.hits[#battingTeamStats.hits + 1] = ballDest
if utils.isFoulBall(ballDestX, ballDestY) then
if utils.isFoulBall(ballDest.x, ballDest.y) then
self.announcer:say("Foul ball!")
pitchTracker.strikes = math.min(pitchTracker.strikes + 1, 2)
-- TODO: Have a fielder chase for the fly-out
return
end
if utils.pointIsSquarelyAboveLine(utils.xy(ballDestX, ballDestY), C.OutfieldWall) then
if utils.pointIsSquarelyAboveLine(utils.xy(ballDest.x, ballDest.y), C.OutfieldWall) then
playdate.timer.new(flyTimeMs, function()
-- Verify that the home run wasn't intercepted
if utils.within(1, self.state.ball.x, ballDestX) and utils.within(1, self.state.ball.y, ballDestY) then
if
utils.within(1, self.state.ball.x, ballDest.x) and utils.within(1, self.state.ball.y, ballDest.y)
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.
@ -420,18 +427,19 @@ function Game:updateBatting(batDeg, batSpeed)
end
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
self.state.ball:launch(ballDestX, ballDestY, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
self.state.ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
self.baserunning:convertBatterToRunner()
self.fielding:haveSomeoneChase(ballDestX, ballDestY)
self.fielding:haveSomeoneChase(ballDest.x, ballDest.y)
end
end
---@param appliedSpeed number | fun(runner: Runner): number
---
---@return boolean runnersStillMoving, number secondsSinceLastRunnerMove
function Game:updateNonBatterRunners(appliedSpeed, forcedOnly)
function Game:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun)
local runnersStillMoving, runnersScored, secondsSinceLastRunnerMove =
self.baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, self.state.deltaSeconds)
self.baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun, self.state.deltaSeconds)
if runnersScored ~= 0 then
self:score(runnersScored)
end
@ -440,7 +448,12 @@ end
function Game:returnToPitcher()
self.fielding:resetFielderPositions()
actionQueue:upsert("returnToPitcher", 60 * 1000, function()
if self:pitcherIsReady() then
return -- Don't then!
end
actionQueue:newOnly("returnToPitcher", 60 * 1000, function()
while not self:pitcherIsOnTheMound() do
coroutine.yield()
end
@ -521,11 +534,17 @@ function Game:updateGameState()
self:updateBatting(self.state.batAngleDeg, batSpeed)
-- Walk batter to the plate
self.baserunning:updateRunner(self.baserunning.batter, nil, userOnOffense and crankLimited or 0, self.state.deltaSeconds)
self.baserunning:updateRunner(
self.baserunning.batter,
nil,
userOnOffense and crankLimited or 0,
false,
self.state.deltaSeconds
)
if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then
if userOnDefense then
local powerRatio, accuracy, isPerfect = throwMeter:readThrow(crankChange)
local powerRatio, accuracy = throwMeter:readThrow(crankChange)
if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio
if throwFly and not self:buttonControlledThrow(throwFly, true) then
@ -541,7 +560,7 @@ function Game:updateGameState()
or function(runner)
return self.npc:runningSpeed(runner, self.state.ball)
end
local _, secondsSinceLastRunnerMove = self:updateNonBatterRunners(appliedSpeed, false)
local _, secondsSinceLastRunnerMove = self:updateNonBatterRunners(appliedSpeed, false, false)
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
-- End of play. Throw the ball back to the pitcher
self.state.offenseState = C.Offense.batting
@ -549,7 +568,7 @@ function Game:updateGameState()
end
if userOnDefense then
local powerRatio, accuracy, isPerfect = throwMeter:readThrow(crankChange)
local powerRatio = throwMeter:readThrow(crankChange)
if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio
if throwFly then
@ -558,11 +577,11 @@ function Game:updateGameState()
end
end
elseif self.state.offenseState == C.Offense.walking then
if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true) then
if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true, true) then
self.state.offenseState = C.Offense.batting
end
elseif self.state.offenseState == C.Offense.homeRun then
self:updateNonBatterRunners(C.WalkedRunnerSpeed * 2, false)
self:updateNonBatterRunners(C.WalkedRunnerSpeed * 2, false, true)
if #self.baserunning.runners == 0 then
-- Give the player a moment to enjoy their home run.
playdate.timer.new(1500, function()
@ -607,8 +626,12 @@ function Game:update()
local ballHeldBy
for _, fielder in pairs(self.fielding.fielders) do
addDraw(fielder.y + danceOffset, function()
local ballHeldByThisFielder =
drawFielder(self.state.fieldingTeamSprites[fielder.spriteIndex], self.state.ball, fielder.x, fielder.y + danceOffset)
local ballHeldByThisFielder = drawFielder(
self.state.fieldingTeamSprites[fielder.spriteIndex],
self.state.ball,
fielder.x,
fielder.y + danceOffset
)
if ballHeldByThisFielder then
ballHeldBy = fielder
end
@ -622,7 +645,10 @@ function Game:update()
if self.state.batAngleDeg > 50 and self.state.batAngleDeg < 200 then
self.state.battingTeamSprites[runner.spriteIndex].back:draw(runner.x, runner.y - playerHeightOffset)
else
self.state.battingTeamSprites[runner.spriteIndex].smiling:draw(runner.x, runner.y - playerHeightOffset)
self.state.battingTeamSprites[runner.spriteIndex].smiling:draw(
runner.x,
runner.y - playerHeightOffset
)
end
else
-- TODO? Change blip speed depending on runner speed?
@ -650,7 +676,7 @@ function Game:update()
self.state.battingTeamSprites[runner.spriteIndex].smiling:draw(runner.x, runner.y - playerHeightOffset)
end
if self:userIsOn('defense') then
if self:userIsOn("defense") then
throwMeter:drawNearFielder(ballHeldBy)
end

View File

@ -21,21 +21,36 @@ Pitches = {
-- Fastball
function(accuracy)
return {
x = gfx.animator.new(0, C.PitchStart.x, getPitchMissBy(accuracy) + C.PitchStart.x, playdate.easingFunctions.linear),
x = gfx.animator.new(
0,
C.PitchStart.x,
getPitchMissBy(accuracy) + 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(accuracy)
return {
x = gfx.animator.new(C.PitchFlyMs, getPitchMissBy(accuracy) + C.PitchStart.x + 20, C.PitchStart.x, utils.easingHill),
x = gfx.animator.new(
C.PitchFlyMs,
getPitchMissBy(accuracy) + 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(accuracy)
return {
x = gfx.animator.new(C.PitchFlyMs, getPitchMissBy(accuracy) + C.PitchStart.x - 20, C.PitchStart.x, utils.easingHill),
x = gfx.animator.new(
C.PitchFlyMs,
getPitchMissBy(accuracy) + C.PitchStart.x - 20,
C.PitchStart.x,
utils.easingHill
),
y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
}
end,
@ -44,10 +59,11 @@ Pitches = {
return {
x = {
currentValue = function()
return getPitchMissBy(accuracy) + C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
end,
reset = function()
return getPitchMissBy(accuracy)
+ 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.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
}