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
|
||||
|
||||
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
|
||||
|
||||
if customBallScaler then
|
||||
|
|
|
@ -209,7 +209,7 @@ end
|
|||
--- Returns true only if at least one of the given runners moved during this update
|
||||
---@param appliedSpeed number
|
||||
---@return boolean someRunnerMoved, number runnersScored
|
||||
function Baserunning:updateRunning(appliedSpeed, forcedOnly, deltaSeconds)
|
||||
function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSeconds)
|
||||
local someRunnerMoved = false
|
||||
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
|
||||
C.RightHandedBattersBox = {
|
||||
x = C.Bases[C.Home].x - 35,
|
||||
x = C.Bases[C.Home].x - 30,
|
||||
y = C.Bases[C.Home].y,
|
||||
}
|
||||
|
||||
|
@ -42,21 +42,40 @@ C.NextBaseMap = {
|
|||
[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
|
||||
C.CrankOffsetDeg = 90
|
||||
|
||||
C.DanceBounceMs = 500
|
||||
C.DanceBounceCount = 4
|
||||
|
||||
C.ScoreboardDelayMs = 2000
|
||||
|
||||
--- Used to draw the ball well out of bounds, and
|
||||
--- generally as a check for whether or not it's in play.
|
||||
C.BallOffscreen = 999
|
||||
|
||||
C.PitchAfterSeconds = 7
|
||||
C.PitchAfterSeconds = 6
|
||||
C.ReturnToPitcherAfterSeconds = 2.4
|
||||
C.PitchFlyMs = 1050
|
||||
C.PitchStartX = 195
|
||||
C.PitchStartX = 190
|
||||
C.PitchStartY, C.PitchEndY = 105, 240
|
||||
|
||||
C.DefaultLaunchPower = 4
|
||||
|
||||
--- The max distance at which a fielder can tag out a runner.
|
||||
C.TagDistance = 15
|
||||
|
||||
|
@ -69,7 +88,7 @@ C.BattingPower = 20
|
|||
|
||||
C.SmallestBallRadius = 6
|
||||
|
||||
C.BatLength = 50
|
||||
C.BatLength = 35
|
||||
|
||||
-- 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,
|
||||
}
|
||||
|
||||
C.ThrowMeterMax = 15
|
||||
C.ThrowMeterMax = 10
|
||||
C.ThrowMeterDrainPerSec = 150
|
||||
|
||||
--- Controls how hard the ball can be hit, and
|
||||
--- how fast the ball can be thrown.
|
||||
C.CrankPower = 10
|
||||
|
||||
C.PitchPower = 0.3
|
||||
C.UserThrowPower = 0.3
|
||||
|
||||
--- How fast baserunners move after a walk
|
||||
C.WalkedRunnerSpeed = 10
|
||||
|
||||
C.ResetFieldersAfterSeconds = 7
|
||||
C.ResetFieldersAfterSeconds = 2.5
|
||||
|
|
|
@ -38,11 +38,32 @@ local BallStrikeMarginY <const> = 4
|
|||
local BallStrikeWidth <const> = 60
|
||||
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)
|
||||
if balls == 0 and strikes == 0 then
|
||||
if currentBallStrikeAnimator == BallStrikeAnimatorIn then
|
||||
currentBallStrikeAnimator = BallStrikeAnimatorOut
|
||||
currentBallStrikeAnimator:reset()
|
||||
end
|
||||
if currentBallStrikeAnimator:ended() then
|
||||
return
|
||||
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.fillRect(x, y, BallStrikeWidth, BallStrikeHeight)
|
||||
local originalDrawMode = gfx.getImageDrawMode()
|
||||
|
@ -72,11 +93,19 @@ function getIndicators(teams, battingTeam)
|
|||
return "", IndicatorWidth, Indicator, 0
|
||||
end
|
||||
|
||||
function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
||||
local homeScore = teams.home.score
|
||||
local awayScore = teams.away.score
|
||||
local stats = {
|
||||
homeScore = 0,
|
||||
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 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(awayScoreText, x + ScoreboardMarginX + awayOffset, y + 22)
|
||||
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)
|
||||
|
||||
|
@ -107,10 +136,34 @@ function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
|||
return (x + ScoreboardMarginX + OutBubbleRadius + IndicatorWidth) + circleOffset, y + 46, OutBubbleRadius
|
||||
end
|
||||
|
||||
for i = outs, 2 do
|
||||
for i = stats.outs, 2 do
|
||||
gfx.drawCircleAtPoint(circleParams(i))
|
||||
end
|
||||
for i = 0, (outs - 1) do
|
||||
for i = 0, (stats.outs - 1) do
|
||||
gfx.fillCircleAtPoint(circleParams(i))
|
||||
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 throwFlyMs number
|
||||
---@return ActionResult
|
||||
local function playerThrowToImpl(field, targetBase, launchBall, throwFlyMs)
|
||||
local function userThrowToImpl(field, targetBase, launchBall, throwFlyMs)
|
||||
if field.fielderTouchingBall == nil then
|
||||
return ActionResult.NeedsMoreTime
|
||||
end
|
||||
|
@ -142,9 +142,9 @@ end
|
|||
---@param targetBase Base
|
||||
---@param launchBall LaunchBall
|
||||
---@param throwFlyMs number
|
||||
function Fielding:playerThrowTo(targetBase, launchBall, throwFlyMs)
|
||||
function Fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
||||
local maxTryTimeMs = 5000
|
||||
actionQueue:upsert("playerThrowTo", maxTryTimeMs, function()
|
||||
return playerThrowToImpl(self, targetBase, launchBall, throwFlyMs)
|
||||
actionQueue:upsert("userThrowTo", maxTryTimeMs, function()
|
||||
return userThrowToImpl(self, targetBase, launchBall, throwFlyMs)
|
||||
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 inning = 1
|
||||
local offenseState = C.Offense.batting
|
||||
|
||||
local throwMeter = 0
|
||||
|
||||
-- TODO: Replace with timers, repeatedly reset, instead of constantly setting to 0
|
||||
local secondsSinceLastRunnerMove = 0
|
||||
local secondsSincePitchAllowed = -5
|
||||
|
||||
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 batTip <const> = utils.xy(0, 0)
|
||||
|
@ -118,10 +116,10 @@ local Pitches <const> = {
|
|||
},
|
||||
}
|
||||
|
||||
---@return boolean playerIsOnSide, boolean playerIsOnOtherSide
|
||||
local function playerIsOn(side)
|
||||
---@return boolean userIsOnSide, boolean playerIsOnOtherSide
|
||||
local function userIsOn(side)
|
||||
local ret
|
||||
if PlayerTeam == battingTeam then
|
||||
if UserTeam == battingTeam then
|
||||
ret = side == C.Sides.offense
|
||||
else
|
||||
ret = side == C.Sides.defense
|
||||
|
@ -137,7 +135,7 @@ end
|
|||
---@param floaty boolean | nil
|
||||
---@param customBallScaler pd_animator | nil
|
||||
local function launchBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||
throwMeter = 0
|
||||
throwMeter:reset()
|
||||
ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||
end
|
||||
|
||||
|
@ -203,13 +201,6 @@ local function score(scoredRunCount)
|
|||
announcer:say("SCORE!")
|
||||
end
|
||||
|
||||
local function readThrow()
|
||||
if throwMeter > C.ThrowMeterMax then
|
||||
return (C.PitchFlyMs / (throwMeter / C.ThrowMeterMax))
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param throwFlyMs number
|
||||
---@return boolean didThrow
|
||||
local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
||||
|
@ -227,9 +218,9 @@ local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
|||
end
|
||||
|
||||
-- Power for this throw has already been determined
|
||||
throwMeter = 0
|
||||
throwMeter:reset()
|
||||
|
||||
fielding:playerThrowTo(targetBase, launchBall, throwFlyMs)
|
||||
fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
||||
secondsSinceLastRunnerMove = 0
|
||||
offenseState = C.Offense.running
|
||||
|
||||
|
@ -237,6 +228,7 @@ local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
|||
end
|
||||
|
||||
local function nextBatter()
|
||||
secondsSincePitchAllowed = -3
|
||||
baserunning.batter = nil
|
||||
playdate.timer.new(2000, function()
|
||||
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 ball.y < 232
|
||||
then
|
||||
-- Hit!
|
||||
BatCrackReverb:play()
|
||||
offenseState = C.Offense.running
|
||||
local ballAngle = batAngle + math.rad(90)
|
||||
|
@ -290,10 +283,17 @@ local function updateBatting(batDeg, batSpeed)
|
|||
end
|
||||
local ballDestX = ball.x + (ballVelX * 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)
|
||||
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.prevBase = C.Bases[C.Home]
|
||||
baserunning:updateForcedRunners()
|
||||
|
@ -307,14 +307,14 @@ end
|
|||
---@param appliedSpeed number
|
||||
---@return boolean someRunnerMoved
|
||||
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
|
||||
score(runnersScored)
|
||||
end
|
||||
return runnerMoved
|
||||
end
|
||||
|
||||
local function playerPitch(throwFly)
|
||||
local function userPitch(throwFly)
|
||||
local aButton = playdate.buttonIsPressed(playdate.kButtonA)
|
||||
local bButton = playdate.buttonIsPressed(playdate.kButtonB)
|
||||
if not aButton and not bButton then
|
||||
|
@ -348,11 +348,10 @@ local function updateGameState()
|
|||
ball.size = ball.sizeAnimator:currentValue()
|
||||
end
|
||||
|
||||
local playerOnOffense, playerOnDefense = playerIsOn(C.Sides.offense)
|
||||
local userOnOffense, userOnDefense = userIsOn(C.Sides.offense)
|
||||
|
||||
if playerOnDefense then
|
||||
throwMeter = math.max(0, throwMeter - (deltaSeconds * C.ThrowMeterDrainPerSec))
|
||||
throwMeter = throwMeter + math.abs(crankLimited * C.PitchPower)
|
||||
if userOnDefense then
|
||||
throwMeter:applyCharge(deltaSeconds, crankLimited)
|
||||
end
|
||||
|
||||
if offenseState == C.Offense.batting then
|
||||
|
@ -363,23 +362,24 @@ local function updateGameState()
|
|||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
|
||||
if secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not catcherThrownBall then
|
||||
local outcome = pitchTracker:updatePitchCounts()
|
||||
if outcome == PitchOutcomes.StrikeOut then
|
||||
strikeOut()
|
||||
elseif outcome == PitchOutcomes.Walk then
|
||||
walk()
|
||||
end
|
||||
-- Catcher has the ball. Throw it back to the pitcher
|
||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||
catcherThrownBall = true
|
||||
end
|
||||
|
||||
local batSpeed
|
||||
if playerOnOffense then
|
||||
if userOnOffense then
|
||||
batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
|
||||
batSpeed = crankLimited
|
||||
else
|
||||
|
@ -394,25 +394,27 @@ local function updateGameState()
|
|||
baserunning:updateRunner(baserunning.batter, nil, crankLimited, deltaSeconds)
|
||||
|
||||
if secondsSincePitchAllowed > C.PitchAfterSeconds then
|
||||
if playerOnDefense then
|
||||
local throwFly = readThrow()
|
||||
if userOnDefense then
|
||||
local throwFly = throwMeter:readThrow()
|
||||
if throwFly and not buttonControlledThrow(throwFly, true) then
|
||||
playerPitch(throwFly)
|
||||
userPitch(throwFly)
|
||||
end
|
||||
else
|
||||
pitch(C.PitchFlyMs, math.random(#Pitches))
|
||||
pitch(C.PitchFlyMs / npc:pitchSpeed(), math.random(#Pitches))
|
||||
end
|
||||
end
|
||||
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
|
||||
secondsSinceLastRunnerMove = 0
|
||||
else
|
||||
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
||||
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)
|
||||
fielding:resetFielderPositions()
|
||||
offenseState = C.Offense.batting
|
||||
-- TODO: Remove, or replace with nextBatter()
|
||||
if not baserunning.batter then
|
||||
baserunning.batter = baserunning:newRunner()
|
||||
end
|
||||
|
@ -426,15 +428,15 @@ local function updateGameState()
|
|||
|
||||
local fielderHoldingBall = fielding:updateFielderPositions(ball, deltaSeconds)
|
||||
|
||||
if playerOnDefense then
|
||||
local throwFly = readThrow()
|
||||
if userOnDefense then
|
||||
local throwFly = throwMeter:readThrow()
|
||||
if throwFly then
|
||||
buttonControlledThrow(throwFly)
|
||||
end
|
||||
end
|
||||
if fielderHoldingBall then
|
||||
local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall)
|
||||
if playerOnOffense then
|
||||
if userOnOffense then
|
||||
npc:fielderAction(offenseState, fielderHoldingBall, outedSomeRunner, ball, launchBall)
|
||||
end
|
||||
end
|
||||
|
@ -467,10 +469,6 @@ function playdate.update()
|
|||
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
|
||||
end
|
||||
|
||||
if playdate.isCrankDocked() then
|
||||
playdate.ui.crankIndicator:draw()
|
||||
end
|
||||
|
||||
local playerHeightOffset = 10
|
||||
-- TODO? Scale sprites down as y increases
|
||||
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)
|
||||
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
|
||||
announcer:draw(C.Center.x, 10)
|
||||
|
||||
if playdate.isCrankDocked() then
|
||||
playdate.ui.crankIndicator:draw()
|
||||
end
|
||||
end
|
||||
|
||||
local function init()
|
||||
|
|
|
@ -133,6 +133,11 @@ function Npc:fielderAction(offenseState, fielder, outedSomeRunner, ball, launchB
|
|||
end
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Npc:pitchSpeed()
|
||||
return 2
|
||||
end
|
||||
|
||||
if not playdate then
|
||||
return Npc
|
||||
end
|
||||
|
|
|
@ -22,16 +22,25 @@ function utils.easingHill(t, b, c, d)
|
|||
return (c * t) + b
|
||||
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.
|
||||
--- Essentially an "empty object" pattern for initial object positions.
|
||||
---@param value number
|
||||
---@return SimpleAnimator
|
||||
---@return StaticAnimator
|
||||
function utils.staticAnimator(value)
|
||||
return {
|
||||
currentValue = function(_)
|
||||
return value
|
||||
end,
|
||||
reset = function(_) end,
|
||||
ended = function(_)
|
||||
return true
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -143,7 +152,7 @@ function utils.getRunnerWithNextBase(runners, base)
|
|||
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
|
||||
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
|
||||
-- This check currently assumes right-handedness.
|
||||
|
@ -152,6 +161,12 @@ function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, li
|
|||
return false
|
||||
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)
|
||||
|
||||
-- y = mx + b
|
||||
|
@ -164,6 +179,17 @@ function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, li
|
|||
return yDelta <= 0
|
||||
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
|
||||
---@generic T : { x: number, y: number | nil }
|
||||
---@param array T[]
|
||||
|
@ -231,6 +257,35 @@ pitchTracker = {
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue