Fix some NPC positioning.
Basic foul-ball implementation. Some balance tweaks. Generally faster play. Move pitchMeter to utils. Player -> User when talking about the human player instead of a baseball player. Slightly delay scoreboard changes. Animate ball-strike entry and exit.
This commit is contained in:
parent
534a16ad67
commit
f67d6262ac
|
@ -44,7 +44,7 @@ function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScal
|
||||||
self.heldBy = nil
|
self.heldBy = nil
|
||||||
|
|
||||||
if not flyTimeMs then
|
if not flyTimeMs then
|
||||||
flyTimeMs = utils.distanceBetween(self.x, self.y, destX, destY) * 5
|
flyTimeMs = utils.distanceBetween(self.x, self.y, destX, destY) * C.DefaultLaunchPower
|
||||||
end
|
end
|
||||||
|
|
||||||
if customBallScaler then
|
if customBallScaler then
|
||||||
|
|
|
@ -209,7 +209,7 @@ end
|
||||||
--- Returns true only if at least one of the given runners moved during this update
|
--- Returns true only if at least one of the given runners moved during this update
|
||||||
---@param appliedSpeed number
|
---@param appliedSpeed number
|
||||||
---@return boolean someRunnerMoved, number runnersScored
|
---@return boolean someRunnerMoved, number runnersScored
|
||||||
function Baserunning:updateRunning(appliedSpeed, forcedOnly, deltaSeconds)
|
function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSeconds)
|
||||||
local someRunnerMoved = false
|
local someRunnerMoved = false
|
||||||
local runnersScored = 0
|
local runnersScored = 0
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ C.FieldHeight = C.Bases[C.Home].y - C.Bases[C.Second].y
|
||||||
|
|
||||||
-- Pseudo-base for batter to target
|
-- Pseudo-base for batter to target
|
||||||
C.RightHandedBattersBox = {
|
C.RightHandedBattersBox = {
|
||||||
x = C.Bases[C.Home].x - 35,
|
x = C.Bases[C.Home].x - 30,
|
||||||
y = C.Bases[C.Home].y,
|
y = C.Bases[C.Home].y,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,21 +42,40 @@ C.NextBaseMap = {
|
||||||
[C.Bases[C.Third]] = C.Bases[C.Home],
|
[C.Bases[C.Third]] = C.Bases[C.Home],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
C.LeftFoulLine = {
|
||||||
|
x1 = C.Center.x,
|
||||||
|
y1 = 220,
|
||||||
|
x2 = -1800,
|
||||||
|
y2 = -465,
|
||||||
|
}
|
||||||
|
|
||||||
|
C.RightFoulLine = {
|
||||||
|
x1 = C.Center.x,
|
||||||
|
y1 = 218,
|
||||||
|
x2 = 2120,
|
||||||
|
y2 = -465,
|
||||||
|
}
|
||||||
|
|
||||||
--- Angle to align the bat to
|
--- Angle to align the bat to
|
||||||
C.CrankOffsetDeg = 90
|
C.CrankOffsetDeg = 90
|
||||||
|
|
||||||
C.DanceBounceMs = 500
|
C.DanceBounceMs = 500
|
||||||
C.DanceBounceCount = 4
|
C.DanceBounceCount = 4
|
||||||
|
|
||||||
|
C.ScoreboardDelayMs = 2000
|
||||||
|
|
||||||
--- Used to draw the ball well out of bounds, and
|
--- Used to draw the ball well out of bounds, and
|
||||||
--- generally as a check for whether or not it's in play.
|
--- generally as a check for whether or not it's in play.
|
||||||
C.BallOffscreen = 999
|
C.BallOffscreen = 999
|
||||||
|
|
||||||
C.PitchAfterSeconds = 7
|
C.PitchAfterSeconds = 6
|
||||||
|
C.ReturnToPitcherAfterSeconds = 2.4
|
||||||
C.PitchFlyMs = 1050
|
C.PitchFlyMs = 1050
|
||||||
C.PitchStartX = 195
|
C.PitchStartX = 190
|
||||||
C.PitchStartY, C.PitchEndY = 105, 240
|
C.PitchStartY, C.PitchEndY = 105, 240
|
||||||
|
|
||||||
|
C.DefaultLaunchPower = 4
|
||||||
|
|
||||||
--- The max distance at which a fielder can tag out a runner.
|
--- The max distance at which a fielder can tag out a runner.
|
||||||
C.TagDistance = 15
|
C.TagDistance = 15
|
||||||
|
|
||||||
|
@ -69,7 +88,7 @@ C.BattingPower = 20
|
||||||
|
|
||||||
C.SmallestBallRadius = 6
|
C.SmallestBallRadius = 6
|
||||||
|
|
||||||
C.BatLength = 50
|
C.BatLength = 35
|
||||||
|
|
||||||
-- TODO: enums implemented this way are probably going to be difficult to serialize!
|
-- TODO: enums implemented this way are probably going to be difficult to serialize!
|
||||||
|
|
||||||
|
@ -95,16 +114,16 @@ C.PitcherStartPos = {
|
||||||
y = C.Screen.H * 0.40,
|
y = C.Screen.H * 0.40,
|
||||||
}
|
}
|
||||||
|
|
||||||
C.ThrowMeterMax = 15
|
C.ThrowMeterMax = 10
|
||||||
C.ThrowMeterDrainPerSec = 150
|
C.ThrowMeterDrainPerSec = 150
|
||||||
|
|
||||||
--- Controls how hard the ball can be hit, and
|
--- Controls how hard the ball can be hit, and
|
||||||
--- how fast the ball can be thrown.
|
--- how fast the ball can be thrown.
|
||||||
C.CrankPower = 10
|
C.CrankPower = 10
|
||||||
|
|
||||||
C.PitchPower = 0.3
|
C.UserThrowPower = 0.3
|
||||||
|
|
||||||
--- How fast baserunners move after a walk
|
--- How fast baserunners move after a walk
|
||||||
C.WalkedRunnerSpeed = 10
|
C.WalkedRunnerSpeed = 10
|
||||||
|
|
||||||
C.ResetFieldersAfterSeconds = 7
|
C.ResetFieldersAfterSeconds = 2.5
|
||||||
|
|
|
@ -38,11 +38,32 @@ local BallStrikeMarginY <const> = 4
|
||||||
local BallStrikeWidth <const> = 60
|
local BallStrikeWidth <const> = 60
|
||||||
local BallStrikeHeight <const> = (BallStrikeMarginY * 2) + ScoreFont:getHeight()
|
local BallStrikeHeight <const> = (BallStrikeMarginY * 2) + ScoreFont:getHeight()
|
||||||
|
|
||||||
|
local BallStrikeAnimatorIn <const> =
|
||||||
|
playdate.graphics.animator.new(500, BallStrikeHeight, 0, playdate.easingFunctions.outBounce)
|
||||||
|
|
||||||
|
local BallStrikeAnimatorOut <const> =
|
||||||
|
playdate.graphics.animator.new(500, 0, BallStrikeHeight, playdate.easingFunctions.linear)
|
||||||
|
|
||||||
|
-- Start out of frame.
|
||||||
|
local currentBallStrikeAnimator = utils.staticAnimator(20)
|
||||||
|
|
||||||
function drawBallsAndStrikes(x, y, balls, strikes)
|
function drawBallsAndStrikes(x, y, balls, strikes)
|
||||||
if balls == 0 and strikes == 0 then
|
if balls == 0 and strikes == 0 then
|
||||||
|
if currentBallStrikeAnimator == BallStrikeAnimatorIn then
|
||||||
|
currentBallStrikeAnimator = BallStrikeAnimatorOut
|
||||||
|
currentBallStrikeAnimator:reset()
|
||||||
|
end
|
||||||
|
if currentBallStrikeAnimator:ended() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
if balls + strikes == 1 and currentBallStrikeAnimator ~= BallStrikeAnimatorIn then
|
||||||
|
-- First pitch - should pop in now.
|
||||||
|
currentBallStrikeAnimator = BallStrikeAnimatorIn
|
||||||
|
currentBallStrikeAnimator:reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
y = y + currentBallStrikeAnimator:currentValue()
|
||||||
gfx.setColor(gfx.kColorBlack)
|
gfx.setColor(gfx.kColorBlack)
|
||||||
gfx.fillRect(x, y, BallStrikeWidth, BallStrikeHeight)
|
gfx.fillRect(x, y, BallStrikeWidth, BallStrikeHeight)
|
||||||
local originalDrawMode = gfx.getImageDrawMode()
|
local originalDrawMode = gfx.getImageDrawMode()
|
||||||
|
@ -72,11 +93,19 @@ function getIndicators(teams, battingTeam)
|
||||||
return "", IndicatorWidth, Indicator, 0
|
return "", IndicatorWidth, Indicator, 0
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
local stats = {
|
||||||
local homeScore = teams.home.score
|
homeScore = 0,
|
||||||
local awayScore = teams.away.score
|
awayScore = 0,
|
||||||
|
outs = 0,
|
||||||
|
inning = 1,
|
||||||
|
battingTeam = nil,
|
||||||
|
}
|
||||||
|
|
||||||
local homeIndicator, homeOffset, awayIndicator, awayOffset = getIndicators(teams, battingTeam)
|
function drawScoreboardImpl(x, y, teams)
|
||||||
|
local homeScore = stats.homeScore
|
||||||
|
local awayScore = stats.awayScore
|
||||||
|
|
||||||
|
local homeIndicator, homeOffset, awayIndicator, awayOffset = getIndicators(teams, stats.battingTeam)
|
||||||
|
|
||||||
local homeScoreText = homeIndicator .. "HOME " .. (homeScore > 9 and homeScore or " " .. homeScore)
|
local homeScoreText = homeIndicator .. "HOME " .. (homeScore > 9 and homeScore or " " .. homeScore)
|
||||||
local awayScoreText = awayIndicator .. "AWAY " .. (awayScore > 9 and awayScore or " " .. awayScore)
|
local awayScoreText = awayIndicator .. "AWAY " .. (awayScore > 9 and awayScore or " " .. awayScore)
|
||||||
|
@ -96,7 +125,7 @@ function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
||||||
ScoreFont:drawText(homeScoreText, x + ScoreboardMarginX + homeOffset, y + 6)
|
ScoreFont:drawText(homeScoreText, x + ScoreboardMarginX + homeOffset, y + 6)
|
||||||
ScoreFont:drawText(awayScoreText, x + ScoreboardMarginX + awayOffset, y + 22)
|
ScoreFont:drawText(awayScoreText, x + ScoreboardMarginX + awayOffset, y + 22)
|
||||||
local inningOffsetX = (x + ScoreboardMarginX + IndicatorWidth) + (4 * 2.5 * OutBubbleRadius)
|
local inningOffsetX = (x + ScoreboardMarginX + IndicatorWidth) + (4 * 2.5 * OutBubbleRadius)
|
||||||
ScoreFont:drawText(inning, inningOffsetX, y + 39)
|
ScoreFont:drawText(tostring(stats.inning), inningOffsetX, y + 39)
|
||||||
|
|
||||||
gfx.setImageDrawMode(originalDrawMode)
|
gfx.setImageDrawMode(originalDrawMode)
|
||||||
|
|
||||||
|
@ -107,10 +136,34 @@ function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
||||||
return (x + ScoreboardMarginX + OutBubbleRadius + IndicatorWidth) + circleOffset, y + 46, OutBubbleRadius
|
return (x + ScoreboardMarginX + OutBubbleRadius + IndicatorWidth) + circleOffset, y + 46, OutBubbleRadius
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = outs, 2 do
|
for i = stats.outs, 2 do
|
||||||
gfx.drawCircleAtPoint(circleParams(i))
|
gfx.drawCircleAtPoint(circleParams(i))
|
||||||
end
|
end
|
||||||
for i = 0, (outs - 1) do
|
for i = 0, (stats.outs - 1) do
|
||||||
gfx.fillCircleAtPoint(circleParams(i))
|
gfx.fillCircleAtPoint(circleParams(i))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local newStats = stats
|
||||||
|
|
||||||
|
function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
||||||
|
if
|
||||||
|
newStats.homeScore ~= teams.home.score
|
||||||
|
or newStats.awayScore ~= teams.away.score
|
||||||
|
or newStats.outs ~= outs
|
||||||
|
or newStats.inning ~= inning
|
||||||
|
or newStats.battingTeam ~= battingTeam
|
||||||
|
then
|
||||||
|
newStats = {
|
||||||
|
homeScore = teams.home.score,
|
||||||
|
awayScore = teams.away.score,
|
||||||
|
outs = outs,
|
||||||
|
inning = inning,
|
||||||
|
battingTeam = battingTeam,
|
||||||
|
}
|
||||||
|
playdate.timer.new(C.ScoreboardDelayMs, function()
|
||||||
|
stats = newStats
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
drawScoreboardImpl(x, y, teams)
|
||||||
|
end
|
||||||
|
|
|
@ -124,7 +124,7 @@ end
|
||||||
---@param launchBall LaunchBall
|
---@param launchBall LaunchBall
|
||||||
---@param throwFlyMs number
|
---@param throwFlyMs number
|
||||||
---@return ActionResult
|
---@return ActionResult
|
||||||
local function playerThrowToImpl(field, targetBase, launchBall, throwFlyMs)
|
local function userThrowToImpl(field, targetBase, launchBall, throwFlyMs)
|
||||||
if field.fielderTouchingBall == nil then
|
if field.fielderTouchingBall == nil then
|
||||||
return ActionResult.NeedsMoreTime
|
return ActionResult.NeedsMoreTime
|
||||||
end
|
end
|
||||||
|
@ -142,9 +142,9 @@ end
|
||||||
---@param targetBase Base
|
---@param targetBase Base
|
||||||
---@param launchBall LaunchBall
|
---@param launchBall LaunchBall
|
||||||
---@param throwFlyMs number
|
---@param throwFlyMs number
|
||||||
function Fielding:playerThrowTo(targetBase, launchBall, throwFlyMs)
|
function Fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
||||||
local maxTryTimeMs = 5000
|
local maxTryTimeMs = 5000
|
||||||
actionQueue:upsert("playerThrowTo", maxTryTimeMs, function()
|
actionQueue:upsert("userThrowTo", maxTryTimeMs, function()
|
||||||
return playerThrowToImpl(self, targetBase, launchBall, throwFlyMs)
|
return userThrowToImpl(self, targetBase, launchBall, throwFlyMs)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
80
src/main.lua
80
src/main.lua
|
@ -62,20 +62,18 @@ local teams <const> = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local PlayerTeam <const> = teams.away
|
local UserTeam <const> = teams.away
|
||||||
local battingTeam = teams.away
|
local battingTeam = teams.away
|
||||||
local inning = 1
|
local inning = 1
|
||||||
local offenseState = C.Offense.batting
|
local offenseState = C.Offense.batting
|
||||||
|
|
||||||
local throwMeter = 0
|
|
||||||
|
|
||||||
-- TODO: Replace with timers, repeatedly reset, instead of constantly setting to 0
|
-- TODO: Replace with timers, repeatedly reset, instead of constantly setting to 0
|
||||||
local secondsSinceLastRunnerMove = 0
|
local secondsSinceLastRunnerMove = 0
|
||||||
local secondsSincePitchAllowed = -5
|
local secondsSincePitchAllowed = -5
|
||||||
|
|
||||||
local catcherThrownBall = false
|
local catcherThrownBall = false
|
||||||
|
|
||||||
local BatterHandPos <const> = utils.xy(10, 15)
|
local BatterHandPos <const> = utils.xy(25, 15)
|
||||||
|
|
||||||
local batBase <const> = utils.xy(C.Center.x - 34, 215)
|
local batBase <const> = utils.xy(C.Center.x - 34, 215)
|
||||||
local batTip <const> = utils.xy(0, 0)
|
local batTip <const> = utils.xy(0, 0)
|
||||||
|
@ -118,10 +116,10 @@ local Pitches <const> = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@return boolean playerIsOnSide, boolean playerIsOnOtherSide
|
---@return boolean userIsOnSide, boolean playerIsOnOtherSide
|
||||||
local function playerIsOn(side)
|
local function userIsOn(side)
|
||||||
local ret
|
local ret
|
||||||
if PlayerTeam == battingTeam then
|
if UserTeam == battingTeam then
|
||||||
ret = side == C.Sides.offense
|
ret = side == C.Sides.offense
|
||||||
else
|
else
|
||||||
ret = side == C.Sides.defense
|
ret = side == C.Sides.defense
|
||||||
|
@ -137,7 +135,7 @@ end
|
||||||
---@param floaty boolean | nil
|
---@param floaty boolean | nil
|
||||||
---@param customBallScaler pd_animator | nil
|
---@param customBallScaler pd_animator | nil
|
||||||
local function launchBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
local function launchBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||||
throwMeter = 0
|
throwMeter:reset()
|
||||||
ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -203,13 +201,6 @@ local function score(scoredRunCount)
|
||||||
announcer:say("SCORE!")
|
announcer:say("SCORE!")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function readThrow()
|
|
||||||
if throwMeter > C.ThrowMeterMax then
|
|
||||||
return (C.PitchFlyMs / (throwMeter / C.ThrowMeterMax))
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param throwFlyMs number
|
---@param throwFlyMs number
|
||||||
---@return boolean didThrow
|
---@return boolean didThrow
|
||||||
local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
||||||
|
@ -227,9 +218,9 @@ local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Power for this throw has already been determined
|
-- Power for this throw has already been determined
|
||||||
throwMeter = 0
|
throwMeter:reset()
|
||||||
|
|
||||||
fielding:playerThrowTo(targetBase, launchBall, throwFlyMs)
|
fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
||||||
secondsSinceLastRunnerMove = 0
|
secondsSinceLastRunnerMove = 0
|
||||||
offenseState = C.Offense.running
|
offenseState = C.Offense.running
|
||||||
|
|
||||||
|
@ -237,6 +228,7 @@ local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function nextBatter()
|
local function nextBatter()
|
||||||
|
secondsSincePitchAllowed = -3
|
||||||
baserunning.batter = nil
|
baserunning.batter = nil
|
||||||
playdate.timer.new(2000, function()
|
playdate.timer.new(2000, function()
|
||||||
pitchTracker:reset()
|
pitchTracker:reset()
|
||||||
|
@ -277,6 +269,7 @@ local function updateBatting(batDeg, batSpeed)
|
||||||
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, C.Screen.H)
|
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, C.Screen.H)
|
||||||
and ball.y < 232
|
and ball.y < 232
|
||||||
then
|
then
|
||||||
|
-- Hit!
|
||||||
BatCrackReverb:play()
|
BatCrackReverb:play()
|
||||||
offenseState = C.Offense.running
|
offenseState = C.Offense.running
|
||||||
local ballAngle = batAngle + math.rad(90)
|
local ballAngle = batAngle + math.rad(90)
|
||||||
|
@ -290,10 +283,17 @@ local function updateBatting(batDeg, batSpeed)
|
||||||
end
|
end
|
||||||
local ballDestX = ball.x + (ballVelX * C.BattingPower)
|
local ballDestX = ball.x + (ballVelX * C.BattingPower)
|
||||||
local ballDestY = ball.y + (ballVelY * C.BattingPower)
|
local ballDestY = ball.y + (ballVelY * C.BattingPower)
|
||||||
-- Hit!
|
pitchTracker:reset()
|
||||||
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)
|
||||||
launchBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler)
|
launchBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler)
|
||||||
|
|
||||||
|
if utils.isFoulBall(ballDestX, ballDestY) then
|
||||||
|
announcer:say("Foul ball!")
|
||||||
|
pitchTracker.strikes = math.min(pitchTracker.strikes + 1, 2)
|
||||||
|
-- TODO: Have a fielder chase for the fly-out
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
baserunning.batter.nextBase = C.Bases[C.First]
|
baserunning.batter.nextBase = C.Bases[C.First]
|
||||||
baserunning.batter.prevBase = C.Bases[C.Home]
|
baserunning.batter.prevBase = C.Bases[C.Home]
|
||||||
baserunning:updateForcedRunners()
|
baserunning:updateForcedRunners()
|
||||||
|
@ -307,14 +307,14 @@ end
|
||||||
---@param appliedSpeed number
|
---@param appliedSpeed number
|
||||||
---@return boolean someRunnerMoved
|
---@return boolean someRunnerMoved
|
||||||
local function updateNonBatterRunners(appliedSpeed, forcedOnly)
|
local function updateNonBatterRunners(appliedSpeed, forcedOnly)
|
||||||
local runnerMoved, runnersScored = baserunning:updateRunning(appliedSpeed, forcedOnly, deltaSeconds)
|
local runnerMoved, runnersScored = baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSeconds)
|
||||||
if runnersScored ~= 0 then
|
if runnersScored ~= 0 then
|
||||||
score(runnersScored)
|
score(runnersScored)
|
||||||
end
|
end
|
||||||
return runnerMoved
|
return runnerMoved
|
||||||
end
|
end
|
||||||
|
|
||||||
local function playerPitch(throwFly)
|
local function userPitch(throwFly)
|
||||||
local aButton = playdate.buttonIsPressed(playdate.kButtonA)
|
local aButton = playdate.buttonIsPressed(playdate.kButtonA)
|
||||||
local bButton = playdate.buttonIsPressed(playdate.kButtonB)
|
local bButton = playdate.buttonIsPressed(playdate.kButtonB)
|
||||||
if not aButton and not bButton then
|
if not aButton and not bButton then
|
||||||
|
@ -348,11 +348,10 @@ local function updateGameState()
|
||||||
ball.size = ball.sizeAnimator:currentValue()
|
ball.size = ball.sizeAnimator:currentValue()
|
||||||
end
|
end
|
||||||
|
|
||||||
local playerOnOffense, playerOnDefense = playerIsOn(C.Sides.offense)
|
local userOnOffense, userOnDefense = userIsOn(C.Sides.offense)
|
||||||
|
|
||||||
if playerOnDefense then
|
if userOnDefense then
|
||||||
throwMeter = math.max(0, throwMeter - (deltaSeconds * C.ThrowMeterDrainPerSec))
|
throwMeter:applyCharge(deltaSeconds, crankLimited)
|
||||||
throwMeter = throwMeter + math.abs(crankLimited * C.PitchPower)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if offenseState == C.Offense.batting then
|
if offenseState == C.Offense.batting then
|
||||||
|
@ -363,23 +362,24 @@ local function updateGameState()
|
||||||
end
|
end
|
||||||
|
|
||||||
local pitcher = fielding.fielders.pitcher
|
local pitcher = fielding.fielders.pitcher
|
||||||
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitchStartX, C.PitchStartY) < C.BaseHitbox then
|
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitcherStartPos.x, C.PitcherStartPos.y) < C.BaseHitbox then
|
||||||
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
||||||
end
|
end
|
||||||
|
|
||||||
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
|
if secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not catcherThrownBall then
|
||||||
local outcome = pitchTracker:updatePitchCounts()
|
local outcome = pitchTracker:updatePitchCounts()
|
||||||
if outcome == PitchOutcomes.StrikeOut then
|
if outcome == PitchOutcomes.StrikeOut then
|
||||||
strikeOut()
|
strikeOut()
|
||||||
elseif outcome == PitchOutcomes.Walk then
|
elseif outcome == PitchOutcomes.Walk then
|
||||||
walk()
|
walk()
|
||||||
end
|
end
|
||||||
|
-- Catcher has the ball. Throw it back to the pitcher
|
||||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||||
catcherThrownBall = true
|
catcherThrownBall = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local batSpeed
|
local batSpeed
|
||||||
if playerOnOffense then
|
if userOnOffense then
|
||||||
batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
|
batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
|
||||||
batSpeed = crankLimited
|
batSpeed = crankLimited
|
||||||
else
|
else
|
||||||
|
@ -394,25 +394,27 @@ local function updateGameState()
|
||||||
baserunning:updateRunner(baserunning.batter, nil, crankLimited, deltaSeconds)
|
baserunning:updateRunner(baserunning.batter, nil, crankLimited, deltaSeconds)
|
||||||
|
|
||||||
if secondsSincePitchAllowed > C.PitchAfterSeconds then
|
if secondsSincePitchAllowed > C.PitchAfterSeconds then
|
||||||
if playerOnDefense then
|
if userOnDefense then
|
||||||
local throwFly = readThrow()
|
local throwFly = throwMeter:readThrow()
|
||||||
if throwFly and not buttonControlledThrow(throwFly, true) then
|
if throwFly and not buttonControlledThrow(throwFly, true) then
|
||||||
playerPitch(throwFly)
|
userPitch(throwFly)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
pitch(C.PitchFlyMs, math.random(#Pitches))
|
pitch(C.PitchFlyMs / npc:pitchSpeed(), math.random(#Pitches))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif offenseState == C.Offense.running then
|
elseif offenseState == C.Offense.running then
|
||||||
local appliedSpeed = playerOnOffense and crankLimited or npc:runningSpeed()
|
local appliedSpeed = userOnOffense and crankLimited or npc:runningSpeed()
|
||||||
if updateNonBatterRunners(appliedSpeed) then
|
if updateNonBatterRunners(appliedSpeed) then
|
||||||
secondsSinceLastRunnerMove = 0
|
secondsSinceLastRunnerMove = 0
|
||||||
else
|
else
|
||||||
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
||||||
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
||||||
|
-- End of play. Throw the ball back to the pitcher
|
||||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||||
fielding:resetFielderPositions()
|
fielding:resetFielderPositions()
|
||||||
offenseState = C.Offense.batting
|
offenseState = C.Offense.batting
|
||||||
|
-- TODO: Remove, or replace with nextBatter()
|
||||||
if not baserunning.batter then
|
if not baserunning.batter then
|
||||||
baserunning.batter = baserunning:newRunner()
|
baserunning.batter = baserunning:newRunner()
|
||||||
end
|
end
|
||||||
|
@ -426,15 +428,15 @@ local function updateGameState()
|
||||||
|
|
||||||
local fielderHoldingBall = fielding:updateFielderPositions(ball, deltaSeconds)
|
local fielderHoldingBall = fielding:updateFielderPositions(ball, deltaSeconds)
|
||||||
|
|
||||||
if playerOnDefense then
|
if userOnDefense then
|
||||||
local throwFly = readThrow()
|
local throwFly = throwMeter:readThrow()
|
||||||
if throwFly then
|
if throwFly then
|
||||||
buttonControlledThrow(throwFly)
|
buttonControlledThrow(throwFly)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if fielderHoldingBall then
|
if fielderHoldingBall then
|
||||||
local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall)
|
local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall)
|
||||||
if playerOnOffense then
|
if userOnOffense then
|
||||||
npc:fielderAction(offenseState, fielderHoldingBall, outedSomeRunner, ball, launchBall)
|
npc:fielderAction(offenseState, fielderHoldingBall, outedSomeRunner, ball, launchBall)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -467,10 +469,6 @@ function playdate.update()
|
||||||
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
|
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
if playdate.isCrankDocked() then
|
|
||||||
playdate.ui.crankIndicator:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
local playerHeightOffset = 10
|
local playerHeightOffset = 10
|
||||||
-- TODO? Scale sprites down as y increases
|
-- TODO? Scale sprites down as y increases
|
||||||
for _, runner in pairs(baserunning.runners) do
|
for _, runner in pairs(baserunning.runners) do
|
||||||
|
@ -507,6 +505,10 @@ function playdate.update()
|
||||||
drawScoreboard(0, C.Screen.H * 0.77, teams, baserunning.outs, battingTeam, inning)
|
drawScoreboard(0, C.Screen.H * 0.77, teams, baserunning.outs, battingTeam, inning)
|
||||||
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
|
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
|
||||||
announcer:draw(C.Center.x, 10)
|
announcer:draw(C.Center.x, 10)
|
||||||
|
|
||||||
|
if playdate.isCrankDocked() then
|
||||||
|
playdate.ui.crankIndicator:draw()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function init()
|
local function init()
|
||||||
|
|
|
@ -133,6 +133,11 @@ function Npc:fielderAction(offenseState, fielder, outedSomeRunner, ball, launchB
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function Npc:pitchSpeed()
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
|
||||||
if not playdate then
|
if not playdate then
|
||||||
return Npc
|
return Npc
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,16 +22,25 @@ function utils.easingHill(t, b, c, d)
|
||||||
return (c * t) + b
|
return (c * t) + b
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @alias StaticAnimator {
|
||||||
|
--- currentValue: fun(self): number;
|
||||||
|
--- reset: fun(self, durationMs: number | nil);
|
||||||
|
--- ended: fun(self): boolean;
|
||||||
|
--- }
|
||||||
|
|
||||||
--- Build an "animator" whose `:currentValue()` always returns the given value.
|
--- Build an "animator" whose `:currentValue()` always returns the given value.
|
||||||
--- Essentially an "empty object" pattern for initial object positions.
|
--- Essentially an "empty object" pattern for initial object positions.
|
||||||
---@param value number
|
---@param value number
|
||||||
---@return SimpleAnimator
|
---@return StaticAnimator
|
||||||
function utils.staticAnimator(value)
|
function utils.staticAnimator(value)
|
||||||
return {
|
return {
|
||||||
currentValue = function(_)
|
currentValue = function(_)
|
||||||
return value
|
return value
|
||||||
end,
|
end,
|
||||||
reset = function(_) end,
|
reset = function(_) end,
|
||||||
|
ended = function(_)
|
||||||
|
return true
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -143,7 +152,7 @@ function utils.getRunnerWithNextBase(runners, base)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound
|
--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound.
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
|
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
|
||||||
-- This check currently assumes right-handedness.
|
-- This check currently assumes right-handedness.
|
||||||
|
@ -152,6 +161,12 @@ function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, li
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return utils.pointUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true only if the point is below the given line.
|
||||||
|
---@return boolean
|
||||||
|
function utils.pointUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2)
|
||||||
local m = (lineY2 - lineY1) / (lineX2 - lineX1)
|
local m = (lineY2 - lineY1) / (lineX2 - lineX1)
|
||||||
|
|
||||||
-- y = mx + b
|
-- y = mx + b
|
||||||
|
@ -164,8 +179,19 @@ function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, li
|
||||||
return yDelta <= 0
|
return yDelta <= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Returns true if a ball landing at destX,destY will be foul.
|
||||||
|
---@param destX number
|
||||||
|
---@param destY number
|
||||||
|
function utils.isFoulBall(destX, destY)
|
||||||
|
local leftLine = C.LeftFoulLine
|
||||||
|
local rightLine = C.RightFoulLine
|
||||||
|
|
||||||
|
return utils.pointUnderLine(destX, destY, leftLine.x1, leftLine.y1, leftLine.x2, leftLine.y2)
|
||||||
|
or utils.pointUnderLine(destX, destY, rightLine.x1, rightLine.y1, rightLine.x2, rightLine.y2)
|
||||||
|
end
|
||||||
|
|
||||||
--- Returns the nearest position object from the given point, as well as its distance from that point
|
--- Returns the nearest position object from the given point, as well as its distance from that point
|
||||||
---@generic T : {x: number, y: number | nil}
|
---@generic T : { x: number, y: number | nil }
|
||||||
---@param array T[]
|
---@param array T[]
|
||||||
---@param x number
|
---@param x number
|
||||||
---@param y number
|
---@param y number
|
||||||
|
@ -231,6 +257,35 @@ pitchTracker = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not playdate then
|
-----------------
|
||||||
return utils, { pitchTracker = pitchTracker, PitchOutcomes = PitchOutcomes }
|
-- Throw Meter --
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
-- selene: allow(unscoped_variables)
|
||||||
|
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 }
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue