NPCs can play each other

Some timing tweaks and TODOs.
Cluster global state tighter together.
This commit is contained in:
Sage Vaillancourt 2025-02-11 13:50:03 -05:00
parent b9d25e18d8
commit 1926960c86
5 changed files with 59 additions and 42 deletions

View File

@ -23,7 +23,7 @@ function Announcer.new()
}, { __index = Announcer }) }, { __index = Announcer })
end end
local DurationMs <const> = 3000 local DurationMs <const> = 2000
function Announcer:popIn() function Announcer:popIn()
self.animatorY = AnnouncerAnimatorInY self.animatorY = AnnouncerAnimatorInY

View File

@ -17,6 +17,8 @@
---@field onThirdOut fun() ---@field onThirdOut fun()
Baserunning = {} Baserunning = {}
-- TODO: Implement slides. Would require making fielders' gloves "real objects" whose state is tracked.
---@param announcer any ---@param announcer any
---@return Baserunning ---@return Baserunning
function Baserunning.new(announcer, onThirdOut) function Baserunning.new(announcer, onThirdOut)
@ -196,6 +198,9 @@ function Baserunning:updateRunner(runner, runnerIndex, appliedSpeed, deltaSecond
end end
end end
-- TODO: Make this less "sticky" for the user.
-- Currently it can be a little hard to run *past* a base.
local autoRun = (nearestBaseDistance > 40 or runner.forcedTo) and mult * autoRunSpeed local autoRun = (nearestBaseDistance > 40 or runner.forcedTo) and mult * autoRunSpeed
or nearestBaseDistance < 5 and 0 or nearestBaseDistance < 5 and 0
or (nearestBase == runner.nextBase and autoRunSpeed or -1 * autoRunSpeed) or (nearestBase == runner.nextBase and autoRunSpeed or -1 * autoRunSpeed)

View File

@ -5,6 +5,8 @@
--- speed: number, --- speed: number,
--- } --- }
-- TODO: Run down baserunners in a pickle.
-- selene: allow(unscoped_variables) -- selene: allow(unscoped_variables)
---@class Fielding ---@class Fielding
---@field fielders table<string, Fielder> ---@field fielders table<string, Fielder>
@ -117,6 +119,9 @@ function Fielding:updateFielderPositions(ball, deltaSeconds)
fielderTouchingBall = fielder fielderTouchingBall = fielder
end end
end end
-- TODO: The need is growing for a distinction between touching the ball and holding the ball.
-- Or, at least, fielders need to start *stopping* the ball when they make contact with it.
-- Right now, a line-drive *through* first will be counted as an out.
self.fielderTouchingBall = fielderTouchingBall self.fielderTouchingBall = fielderTouchingBall
return fielderTouchingBall return fielderTouchingBall
end end

View File

@ -42,15 +42,23 @@ local gfx <const>, C <const> = playdate.graphics, C
local announcer = Announcer.new() local announcer = Announcer.new()
local fielding = Fielding.new() local fielding = Fielding.new()
-- TODO: Find a way to get baserunning and npc instantiated closer to the top, here.
-- Currently difficult because they depend on nextHalfInning/each other.
---@alias SimpleAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) } ------------------
-- GLOBAL STATE --
------------------
local deltaSeconds = 0 local deltaSeconds = 0
local ball = Ball.new(gfx.animator) local ball = Ball.new(gfx.animator)
---@alias Team { score: number, benchPosition: XyPair } local batBase <const> = utils.xy(C.Center.x - 34, 215)
local batTip <const> = utils.xy(0, 0)
local batAngleDeg = C.CrankOffsetDeg
local catcherThrownBall = false
---@alias Team { score: number, benchPosition: XyPair }
---@type table<string, Team> ---@type table<string, Team>
local teams <const> = { local teams <const> = {
home = { home = {
@ -63,25 +71,28 @@ local teams <const> = {
}, },
} }
local UserTeam <const> = teams.away
local battingTeam = teams.away
local battingTeamSprites = AwayTeamSprites
local fieldingTeamSprites = HomeTeamSprites
local runnerBlipper = battingTeam == teams.away and AwayTeamBlipper or HomeTeamBlipper
local inning = 1 local inning = 1
local battingTeam = teams.away
local offenseState = C.Offense.batting local offenseState = C.Offense.batting
-- 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 = 0
local catcherThrownBall = false -- These are only sort-of global state. They are purely graphical,
-- but they need to be kept in sync with the rest of the globals.
local runnerBlipper = battingTeam == teams.away and AwayTeamBlipper or HomeTeamBlipper
local battingTeamSprites = AwayTeamSprites
local fieldingTeamSprites = HomeTeamSprites
local batBase <const> = utils.xy(C.Center.x - 34, 215) -------------------------
local batTip <const> = utils.xy(0, 0) -- END OF GLOBAL STATE --
-------------------------
local batAngleDeg = C.CrankOffsetDeg local UserTeam <const> = teams.away
---@alias SimpleAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) }
---@alias Pitch { x: SimpleAnimator, y: SimpleAnimator, z: SimpleAnimator | nil } ---@alias Pitch { x: SimpleAnimator, y: SimpleAnimator, z: SimpleAnimator | nil }
---@type Pitch[] ---@type Pitch[]
@ -113,8 +124,12 @@ local Pitches <const> = {
}, },
} }
---@return boolean userIsOnSide, boolean playerIsOnOtherSide ---@return boolean userIsOnSide, boolean userIsOnOtherSide
local function userIsOn(side) local function userIsOn(side)
if UserTeam == nil then
-- Both teams are NPC-driven
return false, false
end
local ret local ret
if UserTeam == battingTeam then if UserTeam == battingTeam then
ret = side == C.Sides.offense ret = side == C.Sides.offense
@ -124,13 +139,7 @@ local function userIsOn(side)
return ret, not ret return ret, not ret
end end
--- Launches the ball from its current position to the given destination. ---@type LaunchBall
---@param destX number
---@param destY number
---@param easingFunc EasingFunc
---@param flyTimeMs number | nil
---@param floaty boolean | 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:reset() throwMeter:reset()
ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler) ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
@ -174,10 +183,15 @@ local function nextHalfInning()
announcer:say("SWITCHING SIDES...") announcer:say("SWITCHING SIDES...")
end end
-- TODO: Make the overlay handle its own dang delay. if gameOver then
-- Delay to keep end-of-inning on the scoreboard for a few seconds announcer:say("AND THAT'S THE BALL GAME!")
playdate.timer.new(3000, function() else
fielding:resetFielderPositions()
if battingTeam == teams.home then
inning = inning + 1
end
battingTeam = currentlyFieldingTeam battingTeam = currentlyFieldingTeam
playdate.timer.new(2000, function()
if battingTeam == teams.home then if battingTeam == teams.home then
battingTeamSprites = HomeTeamSprites battingTeamSprites = HomeTeamSprites
runnerBlipper = HomeTeamBlipper runnerBlipper = HomeTeamBlipper
@ -187,15 +201,8 @@ local function nextHalfInning()
fieldingTeamSprites = HomeTeamSprites fieldingTeamSprites = HomeTeamSprites
runnerBlipper = AwayTeamBlipper runnerBlipper = AwayTeamBlipper
end end
if gameOver then
announcer:say("AND THAT'S THE BALL GAME!")
else
fielding:resetFielderPositions()
if battingTeam == teams.home then
inning = inning + 1
end
end
end) end)
end
end end
local baserunning = Baserunning.new(announcer, nextHalfInning) local baserunning = Baserunning.new(announcer, nextHalfInning)
@ -443,7 +450,7 @@ local function updateGameState()
end end
if fielderHoldingBall then if fielderHoldingBall then
local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall) local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall)
if userOnOffense then if not userOnDefense then
npc:fielderAction(offenseState, fielderHoldingBall, outedSomeRunner, ball, launchBall) npc:fielderAction(offenseState, fielderHoldingBall, outedSomeRunner, ball, launchBall)
end end
end end

View File

@ -48,7 +48,7 @@ function Npc:runningSpeed(ball)
local runner1 = self.runners[1] local runner1 = self.runners[1]
local ballIsFar = utils.distanceBetweenZ(ball.x, ball.y, ball.z, runner1.x, runner1.y, 0) > 250 local ballIsFar = utils.distanceBetweenZ(ball.x, ball.y, ball.z, runner1.x, runner1.y, 0) > 300
if ballIsFar or runner1.forcedTo then if ballIsFar or runner1.forcedTo then
return baseRunningSpeed return baseRunningSpeed