Implement grounders and fly-outs.
Add a slight delay on npc fielder actions. Speed up intro when userTeam == nil
This commit is contained in:
parent
decd1f7080
commit
4b9a94c2c2
33
src/ball.lua
33
src/ball.lua
|
@ -5,6 +5,7 @@
|
||||||
---@field size number
|
---@field size number
|
||||||
---@field heldBy Fielder | nil
|
---@field heldBy Fielder | nil
|
||||||
---@field catchable boolean
|
---@field catchable boolean
|
||||||
|
---@field isFlyBall boolean
|
||||||
---@field xAnimator SimpleAnimator
|
---@field xAnimator SimpleAnimator
|
||||||
---@field yAnimator SimpleAnimator
|
---@field yAnimator SimpleAnimator
|
||||||
---@field sizeAnimator SimpleAnimator
|
---@field sizeAnimator SimpleAnimator
|
||||||
|
@ -12,6 +13,10 @@
|
||||||
---@field private animatorLib pd_animator_lib
|
---@field private animatorLib pd_animator_lib
|
||||||
Ball = {}
|
Ball = {}
|
||||||
|
|
||||||
|
local function defaultFloatAnimator(animatorLib)
|
||||||
|
return animatorLib.new(2000, -60, 0, utils.easingHill)
|
||||||
|
end
|
||||||
|
|
||||||
---@param animatorLib pd_animator_lib
|
---@param animatorLib pd_animator_lib
|
||||||
---@return Ball
|
---@return Ball
|
||||||
function Ball.new(animatorLib)
|
function Ball.new(animatorLib)
|
||||||
|
@ -30,15 +35,14 @@ function Ball.new(animatorLib)
|
||||||
-- TODO? Replace these with a ballAnimatorZ?
|
-- TODO? Replace these with a ballAnimatorZ?
|
||||||
-- ...that might lose some of the magic of both. Compromise available? idk
|
-- ...that might lose some of the magic of both. Compromise available? idk
|
||||||
sizeAnimator = utils.staticAnimator(C.SmallestBallRadius),
|
sizeAnimator = utils.staticAnimator(C.SmallestBallRadius),
|
||||||
floatAnimator = animatorLib.new(2000, -60, 0, utils.easingHill),
|
floatAnimator = defaultFloatAnimator(animatorLib),
|
||||||
}, { __index = Ball })
|
}, { __index = Ball })
|
||||||
end
|
end
|
||||||
|
|
||||||
function Ball:updatePosition()
|
---@param deltaSeconds number
|
||||||
|
function Ball:updatePosition(deltaSeconds)
|
||||||
if self.heldBy then
|
if self.heldBy then
|
||||||
self.x = self.heldBy.x
|
utils.moveAtSpeedZ(self, 100 * deltaSeconds, { x = self.heldBy.x, y = self.heldBy.y, z = C.GloveZ })
|
||||||
self.y = self.heldBy.y
|
|
||||||
self.z = C.GloveZ
|
|
||||||
self.size = C.SmallestBallRadius
|
self.size = C.SmallestBallRadius
|
||||||
else
|
else
|
||||||
self.x = self.xAnimator:currentValue()
|
self.x = self.xAnimator:currentValue()
|
||||||
|
@ -46,7 +50,11 @@ function Ball:updatePosition()
|
||||||
-- TODO: This `+ z` is more graphics logic than physics logic
|
-- TODO: This `+ z` is more graphics logic than physics logic
|
||||||
self.y = self.yAnimator:currentValue() + z
|
self.y = self.yAnimator:currentValue() + z
|
||||||
self.z = z
|
self.z = z
|
||||||
self.size = self.sizeAnimator:currentValue()
|
if self.z < 2 and self.isFlyBall then
|
||||||
|
print("Ball hit the ground!")
|
||||||
|
self.isFlyBall = false
|
||||||
|
end
|
||||||
|
self.size = C.SmallestBallRadius + math.max(0, (self.floatAnimator:currentValue() - C.GloveZ) / 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,9 +71,11 @@ end
|
||||||
---@param easingFunc EasingFunc
|
---@param easingFunc EasingFunc
|
||||||
---@param flyTimeMs number | nil
|
---@param flyTimeMs number | nil
|
||||||
---@param floaty boolean | nil
|
---@param floaty boolean | nil
|
||||||
---@param customBallScaler pd_animator | nil
|
---@param customFloater pd_animator | nil
|
||||||
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
---@param isHit boolean
|
||||||
|
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customFloater, isHit)
|
||||||
self.heldBy = nil
|
self.heldBy = nil
|
||||||
|
self.isFlyBall = isHit
|
||||||
|
|
||||||
-- Prevent silly insta-catches
|
-- Prevent silly insta-catches
|
||||||
self:markUncatchable()
|
self:markUncatchable()
|
||||||
|
@ -74,10 +84,11 @@ function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScal
|
||||||
flyTimeMs = utils.distanceBetween(self.x, self.y, destX, destY) * C.DefaultLaunchPower
|
flyTimeMs = utils.distanceBetween(self.x, self.y, destX, destY) * C.DefaultLaunchPower
|
||||||
end
|
end
|
||||||
|
|
||||||
if customBallScaler then
|
if customFloater then
|
||||||
self.sizeAnimator = customBallScaler
|
self.floatAnimator = customFloater
|
||||||
else
|
else
|
||||||
self.sizeAnimator = self.animatorLib.new(flyTimeMs, 9, C.SmallestBallRadius, utils.easingHill)
|
self.sizeAnimator = self.animatorLib.new(flyTimeMs, C.SmallestBallRadius, 9, utils.easingHill)
|
||||||
|
self.floatAnimator = defaultFloatAnimator(self.animatorLib)
|
||||||
end
|
end
|
||||||
self.yAnimator = self.animatorLib.new(flyTimeMs, self.y, destY, easingFunc)
|
self.yAnimator = self.animatorLib.new(flyTimeMs, self.y, destY, easingFunc)
|
||||||
self.xAnimator = self.animatorLib.new(flyTimeMs, self.x, destX, easingFunc)
|
self.xAnimator = self.animatorLib.new(flyTimeMs, self.x, destX, easingFunc)
|
||||||
|
|
|
@ -176,6 +176,10 @@ function Baserunning:pushNewBatter()
|
||||||
return new
|
return new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Baserunning:getNewestRunner()
|
||||||
|
return self.runners[#self.runners]
|
||||||
|
end
|
||||||
|
|
||||||
---@param runnerIndex number
|
---@param runnerIndex number
|
||||||
function Baserunning:runnerScored(runnerIndex)
|
function Baserunning:runnerScored(runnerIndex)
|
||||||
self.scoredRunners[#self.scoredRunners + 1] = self.runners[runnerIndex]
|
self.scoredRunners[#self.scoredRunners + 1] = self.runners[runnerIndex]
|
||||||
|
|
|
@ -99,6 +99,7 @@ C.Offense = {
|
||||||
running = "running",
|
running = "running",
|
||||||
walking = "walking",
|
walking = "walking",
|
||||||
homeRun = "homeRun",
|
homeRun = "homeRun",
|
||||||
|
fliedOut = "running",
|
||||||
}
|
}
|
||||||
|
|
||||||
---@alias Side "offense" | "defense"
|
---@alias Side "offense" | "defense"
|
||||||
|
|
|
@ -67,7 +67,8 @@ end
|
||||||
|
|
||||||
--- Resets the target positions of all fielders to their defaults (at their field positions).
|
--- Resets the target positions of all fielders to their defaults (at their field positions).
|
||||||
---@param fromOffTheField XyPair | nil If provided, also sets all runners' current position to one centralized location.
|
---@param fromOffTheField XyPair | nil If provided, also sets all runners' current position to one centralized location.
|
||||||
function Fielding:resetFielderPositions(fromOffTheField)
|
---@param immediate boolean | nil
|
||||||
|
function Fielding:resetFielderPositions(fromOffTheField, immediate)
|
||||||
if fromOffTheField then
|
if fromOffTheField then
|
||||||
for _, fielder in pairs(self.fielders) do
|
for _, fielder in pairs(self.fielders) do
|
||||||
fielder.x = fromOffTheField.x
|
fielder.x = fromOffTheField.x
|
||||||
|
@ -84,6 +85,13 @@ function Fielding:resetFielderPositions(fromOffTheField)
|
||||||
self.fielders.left.targets = { utils.xy(C.Screen.W * -0.6, C.Screen.H * -0.1) }
|
self.fielders.left.targets = { utils.xy(C.Screen.W * -0.6, C.Screen.H * -0.1) }
|
||||||
self.fielders.center.targets = { utils.xy(C.Center.x, C.Screen.H * -0.4) }
|
self.fielders.center.targets = { utils.xy(C.Center.x, C.Screen.H * -0.4) }
|
||||||
self.fielders.right.targets = { utils.xy(C.Screen.W * 1.6, self.fielders.left.targets[1].y) }
|
self.fielders.right.targets = { utils.xy(C.Screen.W * 1.6, self.fielders.left.targets[1].y) }
|
||||||
|
|
||||||
|
if immediate then
|
||||||
|
for _, fielder in pairs(self.fielders) do
|
||||||
|
fielder.x = fielder.targets[1].x
|
||||||
|
fielder.y = fielder.targets[1].y
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param deltaSeconds number
|
---@param deltaSeconds number
|
||||||
|
@ -96,7 +104,7 @@ local function updateFielderPosition(deltaSeconds, fielder, ball)
|
||||||
local currentTarget = fielder.targets[#fielder.targets]
|
local currentTarget = fielder.targets[#fielder.targets]
|
||||||
local willMove = utils.moveAtSpeed(nextFielderPos, fielder.speed * deltaSeconds, currentTarget)
|
local willMove = utils.moveAtSpeed(nextFielderPos, fielder.speed * deltaSeconds, currentTarget)
|
||||||
|
|
||||||
if willMove and utils.pointIsAboveLine(nextFielderPos, C.BottomOfOutfieldWall) then
|
if willMove and utils.pointIsAboveLine(nextFielderPos, C.BottomOfOutfieldWall, 40) then
|
||||||
local targetCount = #fielder.targets
|
local targetCount = #fielder.targets
|
||||||
-- Back up a little
|
-- Back up a little
|
||||||
fielder.targets[targetCount + 2] = utils.xy(fielder.x, fielder.y + 5)
|
fielder.targets[targetCount + 2] = utils.xy(fielder.x, fielder.y + 5)
|
||||||
|
@ -125,11 +133,12 @@ end
|
||||||
|
|
||||||
--- Selects the nearest fielder to move toward the given coordinates.
|
--- Selects the nearest fielder to move toward the given coordinates.
|
||||||
--- Other fielders should attempt to cover their bases
|
--- Other fielders should attempt to cover their bases
|
||||||
---@param ballDestX number
|
---@param ball Point3d
|
||||||
---@param ballDestY number
|
---@param ballDest XyPair
|
||||||
function Fielding:haveSomeoneChase(ballDestX, ballDestY)
|
function Fielding:haveSomeoneChase(ball, ballDest)
|
||||||
local chasingFielder = utils.getNearestOf(self.fielders, ballDestX, ballDestY)
|
local chasingFielder = utils.getNearestOf(self.fielders, ballDest.x, ballDest.y)
|
||||||
chasingFielder.targets = { utils.xy(ballDestX, ballDestY) }
|
-- Start moving toward the ball directly after reaching ballDest
|
||||||
|
chasingFielder.targets = { ball, ballDest }
|
||||||
|
|
||||||
for _, base in ipairs(C.Bases) do
|
for _, base in ipairs(C.Bases) do
|
||||||
local nearest = utils.getNearestOf(self.fielders, base.x, base.y, function(fielder)
|
local nearest = utils.getNearestOf(self.fielders, base.x, base.y, function(fielder)
|
||||||
|
@ -146,19 +155,24 @@ end
|
||||||
--- **Also updates `ball.heldby`**
|
--- **Also updates `ball.heldby`**
|
||||||
---@param ball Ball
|
---@param ball Ball
|
||||||
---@param deltaSeconds number
|
---@param deltaSeconds number
|
||||||
---@return Fielder | nil fielderHoldingBall nil if no fielder is currently touching the ball
|
---@return Fielder | nil, boolean fielderHoldingBall nil if no fielder is currently touching the ball, true if caught a fly ball
|
||||||
function Fielding:updateFielderPositions(ball, deltaSeconds)
|
function Fielding:updateFielderPositions(ball, deltaSeconds)
|
||||||
local fielderHoldingBall
|
local fielderHoldingBall
|
||||||
|
local caughtAFlyBall = false
|
||||||
for _, fielder in pairs(self.fielders) do
|
for _, fielder in pairs(self.fielders) do
|
||||||
-- TODO: Base this catch on fielder skill?
|
-- TODO: Base this catch on fielder skill?
|
||||||
local canCatch = updateFielderPosition(deltaSeconds, fielder, ball)
|
local canCatch = updateFielderPosition(deltaSeconds, fielder, ball)
|
||||||
if canCatch then
|
if canCatch then
|
||||||
fielderHoldingBall = fielder
|
fielderHoldingBall = fielder
|
||||||
ball.heldBy = fielder -- How much havoc will this wreak?
|
ball.heldBy = fielder -- How much havoc will this wreak?
|
||||||
|
if ball.isFlyBall then
|
||||||
|
ball.isFlyBall = false
|
||||||
|
caughtAFlyBall = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.fielderHoldingBall = fielderHoldingBall
|
self.fielderHoldingBall = fielderHoldingBall
|
||||||
return fielderHoldingBall
|
return fielderHoldingBall, caughtAFlyBall
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO? Start moving target fielders close sooner?
|
-- TODO? Start moving target fielders close sooner?
|
||||||
|
|
67
src/main.lua
67
src/main.lua
|
@ -64,7 +64,9 @@ import 'draw/transitions.lua'
|
||||||
-- TODO: Customizable field structure. E.g. stands and ads etc.
|
-- TODO: Customizable field structure. E.g. stands and ads etc.
|
||||||
|
|
||||||
---@type pd_graphics_lib
|
---@type pd_graphics_lib
|
||||||
local gfx <const>, C <const> = playdate.graphics, C
|
local gfx <const> = playdate.graphics
|
||||||
|
|
||||||
|
local C <const> = C
|
||||||
|
|
||||||
---@alias Team { benchPosition: XyPair }
|
---@alias Team { benchPosition: XyPair }
|
||||||
---@type table<TeamId, Team>
|
---@type table<TeamId, Team>
|
||||||
|
@ -153,8 +155,8 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
|
||||||
end)
|
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, settings.userTeam == nil)
|
||||||
playdate.timer.new(2000, function()
|
playdate.timer.new(settings.userTeam == nil and 10 or 2000, function()
|
||||||
o:returnToPitcher()
|
o:returnToPitcher()
|
||||||
end)
|
end)
|
||||||
o.characters = Characters.new(settings.homeTeamSpriteGroup, settings.awayTeamSpriteGroup)
|
o.characters = Characters.new(settings.homeTeamSpriteGroup, settings.awayTeamSpriteGroup)
|
||||||
|
@ -393,12 +395,13 @@ function Game:updateBatting(offenseHandler)
|
||||||
|
|
||||||
-- Hit!
|
-- Hit!
|
||||||
-- TODO: animate bat-flip or something
|
-- TODO: animate bat-flip or something
|
||||||
|
local isFlyBall = math.random() > 0.5
|
||||||
self:saveToFile()
|
self:saveToFile()
|
||||||
BatCrackReverb:play()
|
BatCrackReverb:play()
|
||||||
self.state.offenseState = C.Offense.running
|
self.state.offenseState = C.Offense.running
|
||||||
|
|
||||||
pitchTracker:reset()
|
pitchTracker:reset()
|
||||||
local flyTimeMs = 2000
|
local flyTimeMs = 8000
|
||||||
-- TODO? A dramatic eye-level view on a home-run could be sick.
|
-- TODO? A dramatic eye-level view on a home-run could be sick.
|
||||||
local battingTeamStats = self:battingTeamCurrentInning()
|
local battingTeamStats = self:battingTeamCurrentInning()
|
||||||
battingTeamStats.hits[#battingTeamStats.hits + 1] = ballDest
|
battingTeamStats.hits[#battingTeamStats.hits + 1] = ballDest
|
||||||
|
@ -410,28 +413,38 @@ function Game:updateBatting(offenseHandler)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local isHomeRun = utils.pointIsAboveLine(ballDest, C.OutfieldWall)
|
local isPastOutfieldWall, nearbyPointAbove = utils.pointIsAboveLine(ballDest, C.OutfieldWall)
|
||||||
if isHomeRun then
|
|
||||||
playdate.timer.new(flyTimeMs, function()
|
if isPastOutfieldWall then
|
||||||
-- Verify that the home run wasn't intercepted
|
if not isFlyBall then
|
||||||
if utils.distanceBetweenPoints(ball, ballDest) < 2 then
|
-- Grounder at the wall!
|
||||||
self.announcer:say("HOME RUN!")
|
ballDest.y = nearbyPointAbove.y - 8
|
||||||
self.state.offenseState = C.Offense.homeRun
|
else
|
||||||
-- Linger on the home-run ball for a moment, before panning to the bases.
|
-- Home run!
|
||||||
playdate.timer.new(1000, function()
|
playdate.timer.new(flyTimeMs, function()
|
||||||
self.panner:panTo(utils.xy(0, 0), function()
|
-- Verify that the home run wasn't intercepted
|
||||||
return self:pitcherIsReady()
|
if utils.distanceBetweenPoints(ball, ballDest) < 2 then
|
||||||
|
self.announcer:say("HOME RUN!")
|
||||||
|
self.state.offenseState = C.Offense.homeRun
|
||||||
|
-- Linger on the home-run ball for a moment, before panning to the bases.
|
||||||
|
playdate.timer.new(1000, function()
|
||||||
|
self.panner:panTo(utils.xy(0, 0), function()
|
||||||
|
return self:pitcherIsReady()
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end
|
||||||
end
|
end)
|
||||||
end)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
|
local ballHeightAnimator = isFlyBall
|
||||||
ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
|
and gfx.animator.new(flyTimeMs, C.GloveZ, 10 + (2 * mult * mult * 0.5), utils.hitEasingHill)
|
||||||
|
or gfx.animator.new(flyTimeMs, 2 * (mult * mult), 0, utils.createBouncer(4))
|
||||||
|
|
||||||
|
ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, ballHeightAnimator, true)
|
||||||
|
|
||||||
self.baserunning:convertBatterToRunner()
|
self.baserunning:convertBatterToRunner()
|
||||||
self.fielding:haveSomeoneChase(ballDest.x, ballDest.y)
|
self.fielding:haveSomeoneChase(ball, ballDest)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param appliedSpeed number | fun(runner: Runner): number
|
---@param appliedSpeed number | fun(runner: Runner): number
|
||||||
|
@ -498,7 +511,15 @@ function Game:updateGameState()
|
||||||
playdate.resetElapsedTime()
|
playdate.resetElapsedTime()
|
||||||
self.state.ball:updatePosition()
|
self.state.ball:updatePosition()
|
||||||
|
|
||||||
local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
|
local fielderHoldingBall, caughtAFlyBall =
|
||||||
|
self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
|
||||||
|
if caughtAFlyBall then
|
||||||
|
local fliedOut = self.baserunning:getNewestRunner()
|
||||||
|
self.baserunning:outRunner(fliedOut, "Fly out!")
|
||||||
|
self.state.offenseState = C.Offense.fliedOut
|
||||||
|
self.baserunning:pushNewBatter()
|
||||||
|
pitchTracker.secondsSinceLastPitch = -1
|
||||||
|
end
|
||||||
|
|
||||||
local offenseHandler, defenseHandler = self:currentInputHandlers()
|
local offenseHandler, defenseHandler = self:currentInputHandlers()
|
||||||
|
|
||||||
|
@ -574,7 +595,7 @@ function Game:update()
|
||||||
end
|
end
|
||||||
|
|
||||||
gfx.setDrawOffset(0, 0)
|
gfx.setDrawOffset(0, 0)
|
||||||
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
|
if math.abs(offsetX) > 10 or math.abs(offsetY) > 20 then
|
||||||
drawMinimap(self.baserunning.runners, self.fielding.fielders)
|
drawMinimap(self.baserunning.runners, self.fielding.fielders)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
48
src/npc.lua
48
src/npc.lua
|
@ -1,6 +1,6 @@
|
||||||
local npcBatDeg = 0
|
local npcBatDeg = 0
|
||||||
local BaseNpcBatSpeed <const> = 1500
|
local BaseNpcBatSpeed <const> = 1000
|
||||||
local npcBatSpeed = 1500
|
local npcBatSpeed = BaseNpcBatSpeed
|
||||||
|
|
||||||
---@class Npc: InputHandler
|
---@class Npc: InputHandler
|
||||||
---@field runners Runner[]
|
---@field runners Runner[]
|
||||||
|
@ -27,18 +27,24 @@ function Npc.update() end
|
||||||
---@param deltaSec number
|
---@param deltaSec number
|
||||||
---@return number batAngleDeg, number batSpeed
|
---@return number batAngleDeg, number batSpeed
|
||||||
function Npc:updateBatAngle(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)
|
||||||
|
and (ball.x > C.Center.x - 12)
|
||||||
|
then
|
||||||
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
|
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
|
||||||
else
|
else
|
||||||
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
|
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
|
||||||
npcBatDeg = 230
|
npcBatDeg = utils.moveAtSpeed1d(npcBatDeg, deltaSec * BaseNpcBatSpeed, 230 - 360)
|
||||||
end
|
end
|
||||||
return npcBatDeg, (self:batSpeed() * deltaSec)
|
return npcBatDeg, (self:batSpeed() * deltaSec)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return number
|
---@return number
|
||||||
function Npc:batSpeed()
|
function Npc:batSpeed()
|
||||||
return npcBatSpeed / 1.5
|
return npcBatSpeed * 1.25
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return number flyTimeMs, number pitchId, number accuracy
|
---@return number flyTimeMs, number pitchId, number accuracy
|
||||||
|
@ -131,14 +137,16 @@ end
|
||||||
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
|
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
|
||||||
local function tryToMakeAPlay(fielders, fielder, runners, ball)
|
local function tryToMakeAPlay(fielders, fielder, runners, ball)
|
||||||
local targetX, targetY = getNextOutTarget(runners)
|
local targetX, targetY = getNextOutTarget(runners)
|
||||||
if targetX ~= nil and targetY ~= nil then
|
if targetX == nil or targetY == nil then
|
||||||
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY)
|
return
|
||||||
nearestFielder.targets = { utils.xy(targetX, targetY) }
|
end
|
||||||
if nearestFielder == fielder then
|
|
||||||
ball.heldBy = fielder
|
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY)
|
||||||
else
|
nearestFielder.targets = { utils.xy(targetX, targetY) }
|
||||||
ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
if nearestFielder == fielder then
|
||||||
end
|
ball.heldBy = fielder
|
||||||
|
else
|
||||||
|
ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -149,14 +157,14 @@ function Npc:fielderAction(fielder, outedSomeRunner, ball)
|
||||||
if not fielder then
|
if not fielder then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if outedSomeRunner then
|
local playDelay = outedSomeRunner and 0.5 or 0.1
|
||||||
-- Delay a little before the next play
|
actionQueue:newOnly("npcFielderAction", 2000, function()
|
||||||
playdate.timer.new(750, function()
|
local dt = 0
|
||||||
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
while dt < playDelay do
|
||||||
end)
|
dt = dt + coroutine.yield()
|
||||||
else
|
end
|
||||||
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return number
|
---@return number
|
||||||
|
|
|
@ -20,6 +20,25 @@ function utils.easingHill(t, b, c, d)
|
||||||
return (c * t) + b
|
return (c * t) + b
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function utils.hitEasingHill(t, b, c, d)
|
||||||
|
c = c + 0.0 -- convert to float to prevent integer overflow
|
||||||
|
t = 1 - (t / d)
|
||||||
|
local extraDrop = -C.GloveZ * t
|
||||||
|
t = ((t * 2) - 1)
|
||||||
|
t = t * t
|
||||||
|
return (c * t) + b + extraDrop
|
||||||
|
end
|
||||||
|
|
||||||
|
function utils.createBouncer(bounceCount)
|
||||||
|
return function(t, b, c, d)
|
||||||
|
c = c + 0.0 -- convert to float to prevent integer overflow
|
||||||
|
local percComplete = t / d
|
||||||
|
local x = percComplete * math.pi * bounceCount
|
||||||
|
local weird = -math.abs((2 / x) * math.sin(x)) / math.pi * 2
|
||||||
|
return b + c + (c * weird)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- @alias StaticAnimator {
|
--- @alias StaticAnimator {
|
||||||
--- currentValue: fun(self): number;
|
--- currentValue: fun(self): number;
|
||||||
--- reset: fun(self, durationMs: number | nil);
|
--- reset: fun(self, durationMs: number | nil);
|
||||||
|
@ -63,6 +82,29 @@ function utils.normalizeVector(x1, y1, x2, y2)
|
||||||
return x / distance, y / distance, distance
|
return x / distance, y / distance, distance
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function utils.normalizeVectorZ(x1, y1, z1, x2, y2, z2)
|
||||||
|
local distance, x, y, z = utils.distanceBetweenZ(x1, y1, z1, x2, y2, z2)
|
||||||
|
return x / distance, y / distance, z / distance, distance
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param current number
|
||||||
|
---@param speed number Must not be negative!
|
||||||
|
---@param target number
|
||||||
|
---@return number newValue, boolean didMove
|
||||||
|
function utils.moveAtSpeed1d(current, speed, target)
|
||||||
|
local distance = math.abs(current - target)
|
||||||
|
if distance == 0 then
|
||||||
|
return target, false
|
||||||
|
end
|
||||||
|
if distance < speed then
|
||||||
|
return target, true
|
||||||
|
end
|
||||||
|
if target > current then
|
||||||
|
return current + speed, true
|
||||||
|
end
|
||||||
|
return current - speed, true
|
||||||
|
end
|
||||||
|
|
||||||
--- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time.
|
--- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time.
|
||||||
--- Stops when within 1. Returns true only if the object did actually move.
|
--- Stops when within 1. Returns true only if the object did actually move.
|
||||||
---@param mover { x: number, y: number }
|
---@param mover { x: number, y: number }
|
||||||
|
@ -88,6 +130,31 @@ function utils.moveAtSpeed(mover, speed, target, tau)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param mover Point3d
|
||||||
|
---@param speed number
|
||||||
|
---@param target Point3d
|
||||||
|
---@param tau number | nil
|
||||||
|
---@return boolean isStillMoving
|
||||||
|
function utils.moveAtSpeedZ(mover, speed, target, tau)
|
||||||
|
local x, y, distance = utils.normalizeVectorZ(mover.x, mover.y, mover.z, target.x, target.y, target.z)
|
||||||
|
|
||||||
|
if distance == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if distance > (tau or 1) then
|
||||||
|
mover.x = mover.x - (x * speed)
|
||||||
|
mover.y = mover.y - (y * speed)
|
||||||
|
mover.z = mover.z - (z * speed)
|
||||||
|
else
|
||||||
|
mover.x = target.x
|
||||||
|
mover.y = target.y
|
||||||
|
mover.z = target.z
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
---@generic T
|
---@generic T
|
||||||
---@param array T[]
|
---@param array T[]
|
||||||
---@param condition fun(T): boolean
|
---@param condition fun(T): boolean
|
||||||
|
@ -188,22 +255,26 @@ end
|
||||||
--- If left of first linePoint and above it, returns true. Similarly if right of the last linePoint.
|
--- If left of first linePoint and above it, returns true. Similarly if right of the last linePoint.
|
||||||
---@param point XyPair
|
---@param point XyPair
|
||||||
---@param linePoints XyPair[]
|
---@param linePoints XyPair[]
|
||||||
---@return boolean
|
---@return boolean, XyPair | nil nearbyPointAbove
|
||||||
function utils.pointIsAboveLine(point, linePoints)
|
function utils.pointIsAboveLine(point, linePoints, by)
|
||||||
if point.x < linePoints[1].x and point.y < linePoints[1].y then
|
by = by or 0
|
||||||
return true
|
local pointY = point.y + by
|
||||||
|
if point.x < linePoints[1].x and pointY < linePoints[1].y then
|
||||||
|
return true, linePoints[1]
|
||||||
end
|
end
|
||||||
for i = 2, #linePoints do
|
for i = 2, #linePoints do
|
||||||
local prev = linePoints[i - 1]
|
local prev = linePoints[i - 1]
|
||||||
local next = linePoints[i]
|
local next = linePoints[i]
|
||||||
if point.x >= prev.x and point.x <= next.x then
|
if point.x >= prev.x and point.x <= next.x then
|
||||||
return not utils.pointUnderLine(point.x, point.y, prev.x, prev.y, next.x, next.y)
|
if not utils.pointUnderLine(point.x, pointY, prev.x, prev.y, next.x, next.y) then
|
||||||
|
return true, prev
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if point.x > linePoints[#linePoints].x and point.y < linePoints[#linePoints].y then
|
if point.x > linePoints[#linePoints].x and pointY < linePoints[#linePoints].y then
|
||||||
return true
|
return true, linePoints[#linePoints]
|
||||||
end
|
end
|
||||||
return false
|
return false, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns true only if the point is below the given line.
|
--- Returns true only if the point is below the given line.
|
||||||
|
|
Loading…
Reference in New Issue