extract-input-controllers #4
198
src/main.lua
198
src/main.lua
|
@ -13,6 +13,13 @@ 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,
|
||||||
|
@ -39,6 +46,7 @@ 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'
|
||||||
|
@ -98,7 +106,8 @@ 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 Npc
|
---@field private npc InputHandler
|
||||||
|
---@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
|
||||||
|
@ -109,7 +118,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 Npc | nil
|
---@param npc InputHandler | 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)
|
||||||
|
@ -151,6 +160,9 @@ 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)
|
||||||
|
@ -198,6 +210,17 @@ 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
|
||||||
|
@ -308,18 +331,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.Bases[C.Third]
|
targetBase = C.Third
|
||||||
elseif playdate.buttonIsPressed(playdate.kButtonUp) then
|
elseif playdate.buttonIsPressed(playdate.kButtonUp) then
|
||||||
targetBase = C.Bases[C.Second]
|
targetBase = C.Second
|
||||||
elseif playdate.buttonIsPressed(playdate.kButtonRight) then
|
elseif playdate.buttonIsPressed(playdate.kButtonRight) then
|
||||||
targetBase = C.Bases[C.First]
|
targetBase = C.First
|
||||||
elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then
|
elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then
|
||||||
targetBase = C.Bases[C.Home]
|
targetBase = C.Home
|
||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
self.fielding:userThrowTo(targetBase, self.state.ball, throwFlyMs)
|
self.fielding:userThrowTo(C.Bases[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
|
||||||
|
|
||||||
|
@ -358,8 +381,12 @@ end
|
||||||
local SwingBackDeg <const> = 30
|
local SwingBackDeg <const> = 30
|
||||||
local SwingForwardDeg <const> = 170
|
local SwingForwardDeg <const> = 170
|
||||||
|
|
||||||
---@param batDeg number
|
---@param offenseHandler InputHandler
|
||||||
function Game:updateBatting(batDeg, batSpeed)
|
function Game:updateBatting(offenseHandler)
|
||||||
|
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
|
||||||
|
@ -368,39 +395,42 @@ function Game:updateBatting(batDeg, batSpeed)
|
||||||
-- 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
|
||||||
self.state.batBase.x = self.baserunning.batter and (self.baserunning.batter.x + C.BatterHandPos.x) or 0
|
local batter = self.baserunning.batter
|
||||||
self.state.batBase.y = self.baserunning.batter and (self.baserunning.batter.y + C.BatterHandPos.y) or 0
|
self.state.batBase.x = batter and (batter.x + C.BatterHandPos.x) or -999
|
||||||
|
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))
|
||||||
|
|
||||||
if
|
local ballWasHit = batSpeed > 0
|
||||||
batSpeed > 0
|
and ball.y < 232
|
||||||
and self.state.ball.y < 232
|
|
||||||
and utils.pointDirectlyUnderLine(
|
and utils.pointDirectlyUnderLine(
|
||||||
self.state.ball.x,
|
ball.x,
|
||||||
self.state.ball.y,
|
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 * 10 * math.sin(ballAngle)
|
local ballVelX = mult * C.BattingPower * 10 * math.sin(ballAngle)
|
||||||
local ballVelY = mult * 5 * math.cos(ballAngle)
|
local ballVelY = mult * C.BattingPower * 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 =
|
local ballDest = utils.xy(ball.x + ballVelX, ball.y + ballVelY)
|
||||||
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
|
||||||
|
@ -418,9 +448,7 @@ function Game:updateBatting(batDeg, batSpeed)
|
||||||
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
|
if utils.within(1, ball.x, ballDest.x) and utils.within(1, ball.y, ballDest.y) then
|
||||||
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.
|
||||||
|
@ -434,15 +462,13 @@ function Game:updateBatting(batDeg, batSpeed)
|
||||||
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)
|
||||||
self.state.ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
|
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 =
|
||||||
|
@ -471,48 +497,11 @@ function Game:returnToPitcher()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param throwFly number
|
---@param defenseHandler InputHandler
|
||||||
function Game:userPitch(throwFly, accuracy)
|
function Game:updatePitching(defenseHandler)
|
||||||
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)
|
||||||
|
|
||||||
local pitcher = self.fielding.fielders.pitcher
|
if self:pitcherIsOnTheMound() then
|
||||||
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
|
||||||
|
|
||||||
|
@ -528,45 +517,33 @@ function Game:updateGameState()
|
||||||
self.state.didSwing = false
|
self.state.didSwing = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local batSpeed
|
if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then
|
||||||
if userOnOffense then
|
self:pitch(defenseHandler:pitch())
|
||||||
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
|
||||||
|
|
||||||
self:updateBatting(self.state.batAngleDeg, batSpeed)
|
function Game:updateGameState()
|
||||||
|
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:updateRunner(self.baserunning.batter, nil, 0, false, self.state.deltaSeconds)
|
||||||
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
|
elseif self.state.offenseState == C.Offense.running then
|
||||||
local appliedSpeed = userOnOffense and crankLimited
|
local appliedSpeed = function(runner)
|
||||||
or function(runner)
|
return offenseHandler:runningSpeed(runner, self.state.ball)
|
||||||
return self.npc:runningSpeed(runner, self.state.ball)
|
|
||||||
end
|
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
|
||||||
|
@ -574,15 +551,11 @@ function Game:updateGameState()
|
||||||
self:returnToPitcher()
|
self:returnToPitcher()
|
||||||
end
|
end
|
||||||
|
|
||||||
if userOnDefense then
|
local outedSomeRunner = false
|
||||||
local powerRatio = throwMeter:readThrow(crankChange)
|
if fielderHoldingBall then
|
||||||
if powerRatio then
|
outedSomeRunner = self.baserunning:outEligibleRunners(fielderHoldingBall)
|
||||||
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
|
||||||
|
@ -608,10 +581,7 @@ 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)
|
||||||
|
@ -628,6 +598,8 @@ 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
|
||||||
|
@ -635,7 +607,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],
|
||||||
self.state.ball,
|
ball,
|
||||||
fielder.x,
|
fielder.x,
|
||||||
fielder.y + danceOffset
|
fielder.y + danceOffset
|
||||||
)
|
)
|
||||||
|
|
17
src/npc.lua
17
src/npc.lua
|
@ -2,7 +2,7 @@ local npcBatDeg = 0
|
||||||
local BaseNpcBatSpeed <const> = 1500
|
local BaseNpcBatSpeed <const> = 1500
|
||||||
local npcBatSpeed = 1500
|
local npcBatSpeed = 1500
|
||||||
|
|
||||||
---@class Npc
|
---@class Npc: InputHandler
|
||||||
---@field runners Runner[]
|
---@field runners Runner[]
|
||||||
---@field fielders Fielder[]
|
---@field fielders Fielder[]
|
||||||
-- selene: allow(unscoped_variables)
|
-- selene: allow(unscoped_variables)
|
||||||
|
@ -18,26 +18,32 @@ 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
|
---@return number batAngleDeg, number batSpeed
|
||||||
-- luacheck: no unused
|
-- luacheck: no unused
|
||||||
function Npc:updateBatAngle(ball, pitchIsOver, deltaSec)
|
function Npc:updateBat(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
|
return npcBatDeg, (self:batSpeed() * deltaSec)
|
||||||
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
|
||||||
|
@ -138,6 +144,9 @@ 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()
|
||||||
|
|
|
@ -54,12 +54,13 @@ 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,
|
||||||
-- Wobbbleball
|
-- Wobbleball
|
||||||
function(accuracy, ball)
|
function(accuracy, ball)
|
||||||
|
local missBy = getPitchMissBy(accuracy)
|
||||||
return {
|
return {
|
||||||
x = {
|
x = {
|
||||||
currentValue = function()
|
currentValue = function()
|
||||||
return getPitchMissBy(accuracy)
|
return missBy
|
||||||
+ 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,
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
---@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
|
Loading…
Reference in New Issue