Compare commits

..

No commits in common. "30aa5bd6c62c7533d430a87ed70b7b38c1f34eb8" and "b44756ff57367f7f3393b667bb52a796cd16a98f" have entirely different histories.

4 changed files with 164 additions and 210 deletions

View File

@ -13,13 +13,6 @@ import 'CoreLibs/utilities/where.lua'
--- @alias EasingFunc fun(number, number, number, number): number --- @alias EasingFunc fun(number, number, number, number): number
---@class InputHandler
---@field update fun(self, deltaSeconds: number)
---@field updateBat fun(self, ball: Ball, pitchIsOver: boolean, deltaSeconds: number)
---@field runningSpeed fun(self, runner: Runner, ball: Ball)
---@field pitch fun(self)
---@field fielderAction fun(self, fielderHoldingBall: Fielder | nil, outedSomeRunner: boolean, ball: Ball)
--- @alias LaunchBall fun( --- @alias LaunchBall fun(
--- self: self, --- self: self,
--- destX: number, --- destX: number,
@ -46,7 +39,6 @@ import 'graphics.lua'
import 'npc.lua' import 'npc.lua'
import 'pitching.lua' import 'pitching.lua'
import 'statistics.lua' import 'statistics.lua'
import 'user-input.lua'
import 'draw/box-score.lua' import 'draw/box-score.lua'
import 'draw/fans.lua' import 'draw/fans.lua'
@ -106,8 +98,7 @@ local teams <const> = {
---@field private announcer Announcer ---@field private announcer Announcer
---@field private fielding Fielding ---@field private fielding Fielding
---@field private baserunning Baserunning ---@field private baserunning Baserunning
---@field private npc InputHandler ---@field private npc Npc
---@field private userInput InputHandler
---@field private homeTeamBlipper Blipper ---@field private homeTeamBlipper Blipper
---@field private awayTeamBlipper Blipper ---@field private awayTeamBlipper Blipper
---@field private panner Panner ---@field private panner Panner
@ -118,7 +109,7 @@ Game = {}
---@param announcer Announcer | nil ---@param announcer Announcer | nil
---@param fielding Fielding | nil ---@param fielding Fielding | nil
---@param baserunning Baserunning | nil ---@param baserunning Baserunning | nil
---@param npc InputHandler | nil ---@param npc Npc | nil
---@param state MutableState | nil ---@param state MutableState | nil
---@return Game ---@return Game
function Game.new(settings, announcer, fielding, baserunning, npc, state) function Game.new(settings, announcer, fielding, baserunning, npc, state)
@ -160,9 +151,6 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
o.baserunning = baserunning or Baserunning.new(announcer, function() o.baserunning = baserunning or Baserunning.new(announcer, function()
o:nextHalfInning() o:nextHalfInning()
end) end)
o.userInput = UserInput.new(function(throwFly, forbidThrowHome)
return o:buttonControlledThrow(throwFly, forbidThrowHome)
end)
o.npc = npc or Npc.new(o.baserunning.runners, o.fielding.fielders) o.npc = npc or Npc.new(o.baserunning.runners, o.fielding.fielders)
o.fielding:resetFielderPositions(teams.home.benchPosition) o.fielding:resetFielderPositions(teams.home.benchPosition)
@ -210,17 +198,6 @@ function Game:userIsOn(side)
return ret, not ret return ret, not ret
end end
---@return InputHandler offense, InputHandler defense
function Game:currentInputHandlers()
local userOnOffense, userOnDefense = self:userIsOn("offense")
local offenseInput = userOnOffense and self.userInput or self.npc
local defenseInput = userOnDefense and self.userInput or self.npc
offenseInput:update(self.state.deltaSeconds)
defenseInput:update(self.state.deltaSeconds)
return offenseInput, defenseInput
end
---@param pitchFlyTimeMs number | nil ---@param pitchFlyTimeMs number | nil
---@param pitchTypeIndex number | nil ---@param pitchTypeIndex number | nil
---@param accuracy number The closer to 1.0, the better ---@param accuracy number The closer to 1.0, the better
@ -331,18 +308,18 @@ end
function Game:buttonControlledThrow(throwFlyMs, forbidThrowHome) function Game:buttonControlledThrow(throwFlyMs, forbidThrowHome)
local targetBase local targetBase
if playdate.buttonIsPressed(playdate.kButtonLeft) then if playdate.buttonIsPressed(playdate.kButtonLeft) then
targetBase = C.Third targetBase = C.Bases[C.Third]
elseif playdate.buttonIsPressed(playdate.kButtonUp) then elseif playdate.buttonIsPressed(playdate.kButtonUp) then
targetBase = C.Second targetBase = C.Bases[C.Second]
elseif playdate.buttonIsPressed(playdate.kButtonRight) then elseif playdate.buttonIsPressed(playdate.kButtonRight) then
targetBase = C.First targetBase = C.Bases[C.First]
elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then
targetBase = C.Home targetBase = C.Bases[C.Home]
else else
return false return false
end end
self.fielding:userThrowTo(C.Bases[targetBase], self.state.ball, throwFlyMs) self.fielding:userThrowTo(targetBase, self.state.ball, throwFlyMs)
self.baserunning.secondsSinceLastRunnerMove = 0 self.baserunning.secondsSinceLastRunnerMove = 0
self.state.offenseState = C.Offense.running self.state.offenseState = C.Offense.running
@ -381,12 +358,8 @@ end
local SwingBackDeg <const> = 30 local SwingBackDeg <const> = 30
local SwingForwardDeg <const> = 170 local SwingForwardDeg <const> = 170
---@param offenseHandler InputHandler ---@param batDeg number
function Game:updateBatting(offenseHandler) function Game:updateBatting(batDeg, batSpeed)
local ball = self.state.ball
local batDeg, batSpeed = offenseHandler:updateBat(ball, self.state.pitchIsOver, self.state.deltaSeconds)
self.state.batAngleDeg = batDeg
if not self.state.pitchIsOver and batDeg > SwingBackDeg and batDeg < SwingForwardDeg then if not self.state.pitchIsOver and batDeg > SwingBackDeg and batDeg < SwingForwardDeg then
self.state.didSwing = true self.state.didSwing = true
end end
@ -395,80 +368,81 @@ function Game:updateBatting(offenseHandler)
-- and letting the user find a crank position and direction that works for them -- and letting the user find a crank position and direction that works for them
local batAngle = math.rad(batDeg) local batAngle = math.rad(batDeg)
-- TODO: animate bat-flip or something -- TODO: animate bat-flip or something
local batter = self.baserunning.batter self.state.batBase.x = self.baserunning.batter and (self.baserunning.batter.x + C.BatterHandPos.x) or 0
self.state.batBase.x = batter and (batter.x + C.BatterHandPos.x) or -999 self.state.batBase.y = self.baserunning.batter and (self.baserunning.batter.y + C.BatterHandPos.y) or 0
self.state.batBase.y = batter and (batter.y + C.BatterHandPos.y) or -999
self.state.batTip.x = self.state.batBase.x + (C.BatLength * math.sin(batAngle)) self.state.batTip.x = self.state.batBase.x + (C.BatLength * math.sin(batAngle))
self.state.batTip.y = self.state.batBase.y + (C.BatLength * math.cos(batAngle)) self.state.batTip.y = self.state.batBase.y + (C.BatLength * math.cos(batAngle))
local ballWasHit = batSpeed > 0 if
and ball.y < 232 batSpeed > 0
and self.state.ball.y < 232
and utils.pointDirectlyUnderLine( and utils.pointDirectlyUnderLine(
ball.x, self.state.ball.x,
ball.y, self.state.ball.y,
self.state.batBase.x, self.state.batBase.x,
self.state.batBase.y, self.state.batBase.y,
self.state.batTip.x, self.state.batTip.x,
self.state.batTip.y, self.state.batTip.y,
C.Screen.H C.Screen.H
) )
then
-- Hit!
BatCrackReverb:play()
self.state.offenseState = C.Offense.running
local ballAngle = batAngle + math.rad(90)
if not ballWasHit then local mult = math.abs(batSpeed / 15)
return local ballVelX = mult * 10 * math.sin(ballAngle)
end local ballVelY = mult * 5 * math.cos(ballAngle)
if ballVelY > 0 then
ballVelX = ballVelX * -1
ballVelY = ballVelY * -1
end
-- Hit! local ballDest =
BatCrackReverb:play() utils.xy(self.state.ball.x + (ballVelX * C.BattingPower), self.state.ball.y + (ballVelY * C.BattingPower))
self.state.offenseState = C.Offense.running
local ballAngle = batAngle + math.rad(90) pitchTracker:reset()
local mult = math.abs(batSpeed / 15) local flyTimeMs = 2000
local ballVelX = mult * C.BattingPower * 10 * math.sin(ballAngle) -- TODO? A dramatic eye-level view on a home-run could be sick.
local ballVelY = mult * C.BattingPower * 5 * math.cos(ballAngle) local battingTeamStats = self:battingTeamCurrentInning()
if ballVelY > 0 then battingTeamStats.hits[#battingTeamStats.hits + 1] = ballDest
ballVelX = ballVelX * -1
ballVelY = ballVelY * -1
end
local ballDest = utils.xy(ball.x + ballVelX, ball.y + ballVelY) 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
pitchTracker:reset() if utils.pointIsSquarelyAboveLine(utils.xy(ballDest.x, ballDest.y), C.OutfieldWall) then
local flyTimeMs = 2000 playdate.timer.new(flyTimeMs, function()
-- TODO? A dramatic eye-level view on a home-run could be sick. -- Verify that the home run wasn't intercepted
local battingTeamStats = self:battingTeamCurrentInning() if
battingTeamStats.hits[#battingTeamStats.hits + 1] = ballDest utils.within(1, self.state.ball.x, ballDest.x) and utils.within(1, self.state.ball.y, ballDest.y)
then
if utils.isFoulBall(ballDest.x, ballDest.y) then self.announcer:say("HOME RUN!")
self.announcer:say("Foul ball!") self.state.offenseState = C.Offense.homeRun
pitchTracker.strikes = math.min(pitchTracker.strikes + 1, 2) -- Linger on the home-run ball for a moment, before panning to the bases.
-- TODO: Have a fielder chase for the fly-out playdate.timer.new(1000, function()
return self.panner:panTo(utils.xy(0, 0), function()
end return self:pitcherIsReady()
end)
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, ball.x, ballDest.x) and utils.within(1, 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.
playdate.timer.new(1000, function()
self.panner:panTo(utils.xy(0, 0), function()
return self:pitcherIsReady()
end) end)
end) end
end end)
end) end
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
self.state.ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
self.baserunning:convertBatterToRunner()
self.fielding:haveSomeoneChase(ballDest.x, ballDest.y)
end end
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
self.baserunning:convertBatterToRunner()
self.fielding:haveSomeoneChase(ballDest.x, ballDest.y)
end end
---@param appliedSpeed number | fun(runner: Runner): number ---@param appliedSpeed number | fun(runner: Runner): number
---
---@return boolean runnersStillMoving, number secondsSinceLastRunnerMove ---@return boolean runnersStillMoving, number secondsSinceLastRunnerMove
function Game:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun) function Game:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun)
local runnersStillMoving, runnersScored, secondsSinceLastRunnerMove = local runnersStillMoving, runnersScored, secondsSinceLastRunnerMove =
@ -497,53 +471,102 @@ function Game:returnToPitcher()
end) end)
end end
---@param defenseHandler InputHandler ---@param throwFly number
function Game:updatePitching(defenseHandler) function Game:userPitch(throwFly, accuracy)
pitchTracker:recordIfPassed(self.state.ball) local aPressed = playdate.buttonIsPressed(playdate.kButtonA)
local bPressed = playdate.buttonIsPressed(playdate.kButtonB)
if self:pitcherIsOnTheMound() then if not aPressed and not bPressed then
pitchTracker.secondsSinceLastPitch = pitchTracker.secondsSinceLastPitch + self.state.deltaSeconds self:pitch(throwFly, 1, accuracy)
end elseif aPressed and not bPressed then
self:pitch(throwFly, 2, accuracy)
if pitchTracker.secondsSinceLastPitch > C.ReturnToPitcherAfterSeconds and not self.state.pitchIsOver then elseif not aPressed and bPressed then
local outcome = pitchTracker:updatePitchCounts(self.state.didSwing, self:fieldingTeamCurrentInning()) self:pitch(throwFly, 3, accuracy)
if outcome == PitchOutcomes.StrikeOut then elseif aPressed and bPressed then
self:strikeOut() self:pitch(throwFly, 4, accuracy)
elseif outcome == PitchOutcomes.Walk then
self:walk()
end
self:returnToPitcher()
self.state.pitchIsOver = true
self.state.didSwing = false
end
if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then
self:pitch(defenseHandler:pitch())
end end
end end
function Game:updateGameState() function Game:updateGameState()
playdate.timer.updateTimers()
gfx.animation.blinker.updateAll()
self.state.deltaSeconds = playdate.getElapsedTime() or 0 self.state.deltaSeconds = playdate.getElapsedTime() or 0
playdate.resetElapsedTime() playdate.resetElapsedTime()
local crankChange = playdate.getCrankChange() --[[@as number]]
local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * C.CrankPower)
if crankChange < 0 then
crankLimited = crankLimited * -1
end
self.state.ball:updatePosition() self.state.ball:updatePosition()
local offenseHandler, defenseHandler = self:currentInputHandlers() local userOnOffense, userOnDefense = self:userIsOn("offense")
local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds) local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
if self.state.offenseState == C.Offense.batting then if fielderHoldingBall then
self:updatePitching(defenseHandler) local outedSomeRunner = self.baserunning:outEligibleRunners(fielderHoldingBall)
self:updateBatting(offenseHandler) if not userOnDefense and self.state.offenseState == C.Offense.running then
self.npc:fielderAction(fielderHoldingBall, outedSomeRunner, self.state.ball)
end
end
-- Walk batter to the plate if self.state.offenseState == C.Offense.batting then
self.baserunning:updateRunner(self.baserunning.batter, nil, 0, false, self.state.deltaSeconds) pitchTracker:recordIfPassed(self.state.ball)
elseif self.state.offenseState == C.Offense.running then
local appliedSpeed = function(runner) local pitcher = self.fielding.fielders.pitcher
return offenseHandler:runningSpeed(runner, self.state.ball) if utils.distanceBetween(pitcher.x, pitcher.y, C.PitcherStartPos.x, C.PitcherStartPos.y) < C.BaseHitbox then
pitchTracker.secondsSinceLastPitch = pitchTracker.secondsSinceLastPitch + self.state.deltaSeconds
end end
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
self:returnToPitcher()
self.state.pitchIsOver = true
self.state.didSwing = false
end
local batSpeed
if userOnOffense then
self.state.batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
batSpeed = crankLimited
else
self.state.batAngleDeg =
self.npc:updateBatAngle(self.state.ball, self.state.pitchIsOver, self.state.deltaSeconds)
batSpeed = self.npc:batSpeed() * self.state.deltaSeconds
end
self:updateBatting(self.state.batAngleDeg, batSpeed)
-- Walk batter to the plate
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 = throwMeter:readThrow(crankChange)
if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio
if throwFly and not self:buttonControlledThrow(throwFly, true) then
self:userPitch(throwFly, accuracy)
end
end
else
self:pitch(C.PitchFlyMs / self.npc:pitchSpeed(), math.random(#Pitches))
end
end
elseif self.state.offenseState == C.Offense.running then
local appliedSpeed = userOnOffense and crankLimited
or function(runner)
return self.npc:runningSpeed(runner, self.state.ball)
end
local _, secondsSinceLastRunnerMove = self:updateNonBatterRunners(appliedSpeed, false, false) local _, secondsSinceLastRunnerMove = self:updateNonBatterRunners(appliedSpeed, false, false)
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
-- End of play. Throw the ball back to the pitcher -- End of play. Throw the ball back to the pitcher
@ -551,11 +574,15 @@ function Game:updateGameState()
self:returnToPitcher() self:returnToPitcher()
end end
local outedSomeRunner = false if userOnDefense then
if fielderHoldingBall then local powerRatio = throwMeter:readThrow(crankChange)
outedSomeRunner = self.baserunning:outEligibleRunners(fielderHoldingBall) if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio
if throwFly then
self:buttonControlledThrow(throwFly)
end
end
end end
defenseHandler:fielderAction(fielderHoldingBall, outedSomeRunner, self.state.ball)
elseif self.state.offenseState == C.Offense.walking then elseif self.state.offenseState == C.Offense.walking then
if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true, true) then if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true, true) then
self.state.offenseState = C.Offense.batting self.state.offenseState = C.Offense.batting
@ -581,7 +608,10 @@ function Game:updateGameState()
end end
function Game:update() function Game:update()
playdate.timer.updateTimers()
gfx.animation.blinker.updateAll()
self:updateGameState() self:updateGameState()
local ball = self.state.ball
gfx.clear() gfx.clear()
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)
@ -598,8 +628,6 @@ function Game:update()
characterDraws[#characterDraws + 1] = { y = y, drawAction = drawAction } characterDraws[#characterDraws + 1] = { y = y, drawAction = drawAction }
end end
local ball = self.state.ball
local danceOffset = FielderDanceAnimator:currentValue() local danceOffset = FielderDanceAnimator:currentValue()
---@type Fielder | nil ---@type Fielder | nil
local ballHeldBy local ballHeldBy
@ -607,7 +635,7 @@ function Game:update()
addDraw(fielder.y + danceOffset, function() addDraw(fielder.y + danceOffset, function()
local ballHeldByThisFielder = drawFielder( local ballHeldByThisFielder = drawFielder(
self.state.fieldingTeamSprites[fielder.spriteIndex], self.state.fieldingTeamSprites[fielder.spriteIndex],
ball, self.state.ball,
fielder.x, fielder.x,
fielder.y + danceOffset fielder.y + danceOffset
) )

View File

@ -2,7 +2,7 @@ local npcBatDeg = 0
local BaseNpcBatSpeed <const> = 1500 local BaseNpcBatSpeed <const> = 1500
local npcBatSpeed = 1500 local npcBatSpeed = 1500
---@class Npc: InputHandler ---@class Npc
---@field runners Runner[] ---@field runners Runner[]
---@field fielders Fielder[] ---@field fielders Fielder[]
-- selene: allow(unscoped_variables) -- selene: allow(unscoped_variables)
@ -18,32 +18,26 @@ function Npc.new(runners, fielders)
}, { __index = Npc }) }, { __index = Npc })
end end
function Npc.update() end
-- TODO: FAR more nuanced NPC batting. -- TODO: FAR more nuanced NPC batting.
---@param ball XyPair ---@param ball XyPair
---@param pitchIsOver boolean ---@param pitchIsOver boolean
---@param deltaSec number ---@param deltaSec number
---@return number batAngleDeg, number batSpeed ---@return number
-- luacheck: no unused -- luacheck: no unused
function Npc:updateBat(ball, pitchIsOver, deltaSec) 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 if not pitchIsOver and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed) npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
else else
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
npcBatDeg = 230 npcBatDeg = 230
end end
return npcBatDeg, (self:batSpeed() * deltaSec) return npcBatDeg
end end
function Npc:batSpeed() function Npc:batSpeed()
return npcBatSpeed / 1.5 return npcBatSpeed / 1.5
end end
function Npc:pitch()
return C.PitchFlyMs / self:pitchSpeed(), math.random(#Pitches), 0.9
end
local baseRunningSpeed = 25 local baseRunningSpeed = 25
---@param runner Runner ---@param runner Runner
@ -144,9 +138,6 @@ end
---@param outedSomeRunner boolean ---@param outedSomeRunner boolean
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall } ---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
function Npc:fielderAction(fielder, outedSomeRunner, ball) function Npc:fielderAction(fielder, outedSomeRunner, ball)
if not fielder then
return
end
if outedSomeRunner then if outedSomeRunner then
-- Delay a little before the next play -- Delay a little before the next play
playdate.timer.new(750, function() playdate.timer.new(750, function()

View File

@ -54,13 +54,12 @@ Pitches = {
y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear), y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
} }
end, end,
-- Wobbleball -- Wobbbleball
function(accuracy, ball) function(accuracy, ball)
local missBy = getPitchMissBy(accuracy)
return { return {
x = { x = {
currentValue = function() currentValue = function()
return missBy return getPitchMissBy(accuracy)
+ C.PitchStart.x + C.PitchStart.x
+ (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10)) + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
end, end,

View File

@ -1,64 +0,0 @@
---@class UserInput: InputHandler
---@field buttonControlledThrow: fun(throwFlyMs: number, forbidThrowHome: boolean): boolean didThrow
UserInput = {}
function UserInput.new(buttonControlledThrow)
return setmetatable({
buttonControlledThrow = buttonControlledThrow,
}, { __index = UserInput })
end
function UserInput:update()
self.crankChange = playdate.getCrankChange()
local crankLimited = self.crankChange == 0 and 0 or (math.log(math.abs(self.crankChange)) * C.CrankPower)
self.crankLimited = math.abs(crankLimited)
end
function UserInput:updateBat()
local batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
local batSpeed = self.crankLimited
return batAngleDeg, batSpeed
end
function UserInput:runningSpeed()
return self.crankLimited
end
---@param throwFlyMs number
---@param accuracy number | nil
---@return number | nil pitchFlyTimeMs, number | nil pitchTypeIndex, number | nil accuracy
local function userPitch(throwFlyMs, accuracy)
local aPressed = playdate.buttonIsPressed(playdate.kButtonA)
local bPressed = playdate.buttonIsPressed(playdate.kButtonB)
if not aPressed and not bPressed then
return throwFlyMs, 1, accuracy
elseif aPressed and not bPressed then
return throwFlyMs, 2, accuracy
elseif not aPressed and bPressed then
return throwFlyMs, 3, accuracy
elseif aPressed and bPressed then
return throwFlyMs, 4, accuracy
end
return nil, nil, nil
end
function UserInput:pitch()
local powerRatio, accuracy = throwMeter:readThrow(self.crankChange)
if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio
if throwFly and not self.buttonControlledThrow(throwFly, true) then
return userPitch(throwFly, accuracy)
end
end
end
function UserInput:fielderAction()
local powerRatio = throwMeter:readThrow(self.crankChange)
if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio
if throwFly then
self.buttonControlledThrow(throwFly, false)
end
end
end