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,42 +368,39 @@ 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
if not ballWasHit then
return
end
-- Hit! -- Hit!
BatCrackReverb:play() BatCrackReverb:play()
self.state.offenseState = C.Offense.running self.state.offenseState = C.Offense.running
local ballAngle = batAngle + math.rad(90) local ballAngle = batAngle + math.rad(90)
local mult = math.abs(batSpeed / 15) local mult = math.abs(batSpeed / 15)
local ballVelX = mult * C.BattingPower * 10 * math.sin(ballAngle) local ballVelX = mult * 10 * math.sin(ballAngle)
local ballVelY = mult * C.BattingPower * 5 * math.cos(ballAngle) local ballVelY = mult * 5 * math.cos(ballAngle)
if ballVelY > 0 then if ballVelY > 0 then
ballVelX = ballVelX * -1 ballVelX = ballVelX * -1
ballVelY = ballVelY * -1 ballVelY = ballVelY * -1
end end
local ballDest = utils.xy(ball.x + ballVelX, ball.y + ballVelY) local ballDest =
utils.xy(self.state.ball.x + (ballVelX * C.BattingPower), self.state.ball.y + (ballVelY * C.BattingPower))
pitchTracker:reset() pitchTracker:reset()
local flyTimeMs = 2000 local flyTimeMs = 2000
@ -448,7 +418,9 @@ function Game:updateBatting(offenseHandler)
if utils.pointIsSquarelyAboveLine(utils.xy(ballDest.x, ballDest.y), C.OutfieldWall) then if utils.pointIsSquarelyAboveLine(utils.xy(ballDest.x, ballDest.y), C.OutfieldWall) then
playdate.timer.new(flyTimeMs, function() playdate.timer.new(flyTimeMs, function()
-- Verify that the home run wasn't intercepted -- 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 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.announcer:say("HOME RUN!")
self.state.offenseState = C.Offense.homeRun self.state.offenseState = C.Offense.homeRun
-- Linger on the home-run ball for a moment, before panning to the bases. -- Linger on the home-run ball for a moment, before panning to the bases.
@ -462,13 +434,15 @@ function Game:updateBatting(offenseHandler)
end end
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill) 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.state.ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
self.baserunning:convertBatterToRunner() self.baserunning:convertBatterToRunner()
self.fielding:haveSomeoneChase(ballDest.x, ballDest.y) self.fielding:haveSomeoneChase(ballDest.x, ballDest.y)
end
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,11 +471,48 @@ function Game:returnToPitcher()
end) end)
end end
---@param defenseHandler InputHandler ---@param throwFly number
function Game:updatePitching(defenseHandler) function Game:userPitch(throwFly, accuracy)
local aPressed = playdate.buttonIsPressed(playdate.kButtonA)
local bPressed = playdate.buttonIsPressed(playdate.kButtonB)
if not aPressed and not bPressed then
self:pitch(throwFly, 1, accuracy)
elseif aPressed and not bPressed then
self:pitch(throwFly, 2, accuracy)
elseif not aPressed and bPressed then
self:pitch(throwFly, 3, accuracy)
elseif aPressed and bPressed then
self:pitch(throwFly, 4, accuracy)
end
end
function Game:updateGameState()
self.state.deltaSeconds = playdate.getElapsedTime() or 0
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()
local userOnOffense, userOnDefense = self:userIsOn("offense")
local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
if fielderHoldingBall then
local outedSomeRunner = self.baserunning:outEligibleRunners(fielderHoldingBall)
if not userOnDefense and self.state.offenseState == C.Offense.running then
self.npc:fielderAction(fielderHoldingBall, outedSomeRunner, self.state.ball)
end
end
if self.state.offenseState == C.Offense.batting then
pitchTracker:recordIfPassed(self.state.ball) pitchTracker:recordIfPassed(self.state.ball)
if self:pitcherIsOnTheMound() then local pitcher = self.fielding.fielders.pitcher
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitcherStartPos.x, C.PitcherStartPos.y) < C.BaseHitbox then
pitchTracker.secondsSinceLastPitch = pitchTracker.secondsSinceLastPitch + self.state.deltaSeconds pitchTracker.secondsSinceLastPitch = pitchTracker.secondsSinceLastPitch + self.state.deltaSeconds
end end
@ -517,33 +528,45 @@ function Game:updatePitching(defenseHandler)
self.state.didSwing = false self.state.didSwing = false
end end
if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then local batSpeed
self:pitch(defenseHandler:pitch()) 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 end
end
function Game:updateGameState() self:updateBatting(self.state.batAngleDeg, batSpeed)
playdate.timer.updateTimers()
gfx.animation.blinker.updateAll()
self.state.deltaSeconds = playdate.getElapsedTime() or 0
playdate.resetElapsedTime()
self.state.ball:updatePosition()
local offenseHandler, defenseHandler = self:currentInputHandlers()
local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
if self.state.offenseState == C.Offense.batting then
self:updatePitching(defenseHandler)
self:updateBatting(offenseHandler)
-- Walk batter to the plate -- Walk batter to the plate
self.baserunning:updateRunner(self.baserunning.batter, nil, 0, false, self.state.deltaSeconds) self.baserunning:updateRunner(
elseif self.state.offenseState == C.Offense.running then self.baserunning.batter,
local appliedSpeed = function(runner) nil,
return offenseHandler:runningSpeed(runner, self.state.ball) userOnOffense and crankLimited or 0,
end 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