From 2d812f20469055db93f9126f4ffcb0a66ffaec19 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Tue, 18 Feb 2025 13:58:44 -0500 Subject: [PATCH] Add basic home-run handling Still needs to pan the camera back from the home run while the runners circle the bases. Also add a wrapping-pattern.png, though I'm not sure if it's actually used? --- src/baserunning.lua | 3 ++- src/constants.lua | 25 ++++++++++------- src/fielding.lua | 5 +++- src/images/launcher/wrapping-pattern.png | Bin 0 -> 10237 bytes src/main.lua | 33 +++++++++++++++-------- src/utils.lua | 11 +++++++- 6 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 src/images/launcher/wrapping-pattern.png diff --git a/src/baserunning.lua b/src/baserunning.lua index 661aeea..b8a74c4 100644 --- a/src/baserunning.lua +++ b/src/baserunning.lua @@ -16,7 +16,8 @@ ---@field onThirdOut fun() Baserunning = {} --- TODO: Implement slides. Would require making fielders' gloves "real objects" whose state is tracked. +-- TODO: Implement slides? Would require making fielders' gloves "real objects" whose state is tracked. +-- TODO: Don't allow runners to occupy the same base! ---@param announcer Announcer ---@return Baserunning diff --git a/src/constants.lua b/src/constants.lua index a5ecd5f..4ac30ca 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -98,6 +98,7 @@ C.Offense = { batting = "batting", running = "running", walking = "walking", + homeRun = "homeRun", } ---@alias Side "offense" | "defense" @@ -124,17 +125,23 @@ C.WalkedRunnerSpeed = 10 C.ResetFieldersAfterSeconds = 2.5 C.OutfieldWall = { - { x = 0, y = 137 }, - { x = 233, y = 32 }, - { x = 450, y = 29 }, - { x = 550, y = 59 }, - { x = 739, y = 64 }, - { x = 850, y = 19 }, - { x = 1100, y = 31 }, - { x = 1185, y = 181 }, - { x = 1201, y = 224 }, + { x = -400, y = -103 }, + { x = -167, y = -208 }, + { x = 50, y = -211 }, + { x = 150, y = -181 }, + { x = 339, y = -176 }, + { x = 450, y = -221 }, + { x = 700, y = -209 }, + { x = 785, y = -59 }, + { x = 801, y = -16 }, } +C.BottomOfOutfieldWall = {} + +for i, v in ipairs(C.OutfieldWall) do + C.BottomOfOutfieldWall[i] = utils.xy(v.x, v.y + 40) +end + if not playdate then return C end diff --git a/src/fielding.lua b/src/fielding.lua index 4b0a9c8..b524f23 100644 --- a/src/fielding.lua +++ b/src/fielding.lua @@ -80,7 +80,10 @@ end ---@return boolean inCatchingRange local function updateFielderPosition(deltaSeconds, fielder, ballPos) if fielder.target ~= nil then - if not utils.moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then + if + utils.pointIsSquarelyAboveLine(fielder, C.BottomOfOutfieldWall) + or not utils.moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) + then fielder.target = nil end end diff --git a/src/images/launcher/wrapping-pattern.png b/src/images/launcher/wrapping-pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..54f73c73c04633cef4238b20a96f41d8e3277827 GIT binary patch literal 10237 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 literal 0 HcmV?d00001 diff --git a/src/main.lua b/src/main.lua index 6d19a50..2d70da1 100644 --- a/src/main.lua +++ b/src/main.lua @@ -416,8 +416,7 @@ function Game:updateBatting(batDeg, batSpeed) local ballDestX = self.state.ball.x + (ballVelX * C.BattingPower) local ballDestY = self.state.ball.y + (ballVelY * C.BattingPower) pitchTracker:reset() - local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill) - self.state.ball:launch(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler) + local flyTimeMs = 2000 -- TODO? A dramatic eye-level view on a home-run could be sick. local battingTeamStats = self:battingTeamCurrentInning() battingTeamStats.hits[#battingTeamStats.hits + 1] = utils.xy(ballDestX, ballDestY) @@ -428,8 +427,21 @@ function Game:updateBatting(batDeg, batSpeed) -- TODO: Have a fielder chase for the fly-out return end - self.baserunning:convertBatterToRunner() + if utils.pointIsSquarelyAboveLine(utils.xy(ballDestX, ballDestY), C.OutfieldWall) then + playdate.timer.new(flyTimeMs, function() + -- Verify that the home run wasn't intercepted + if utils.within(1, self.state.ball.x, ballDestX) and utils.within(1, self.state.ball.y, ballDestY) then + self.announcer:say("HOME RUN!") + self.state.offenseState = C.Offense.homeRun + end + end) + end + + local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill) + self.state.ball:launch(ballDestX, ballDestY, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler) + + self.baserunning:convertBatterToRunner() self.fielding:haveSomeoneChase(ballDestX, ballDestY) end end @@ -490,14 +502,7 @@ function Game:updateGameState() end if self.state.secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not self.state.pitchIsOver then - local outcome = pitchTracker:updatePitchCounts(self.state.didSwing) - local currentPitchingStats = self:fieldingTeamCurrentInning().pitching - if outcome == PitchOutcomes.Strike or outcome == PitchOutcomes.StrikeOut then - currentPitchingStats.strikes = currentPitchingStats.strikes + 1 - end - if outcome == PitchOutcomes.Ball or outcome == PitchOutcomes.Walk then - currentPitchingStats.balls = currentPitchingStats.balls + 1 - end + local outcome = pitchTracker:updatePitchCounts(self.state.didSwing, self:fieldingTeamCurrentInning()) if outcome == PitchOutcomes.StrikeOut then self:strikeOut() elseif outcome == PitchOutcomes.Walk then @@ -560,6 +565,12 @@ function Game:updateGameState() if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true) then self.state.offenseState = C.Offense.batting end + elseif self.state.offenseState == C.Offense.homeRun then + self:updateNonBatterRunners(C.WalkedRunnerSpeed * 3, false) + if #self.baserunning.runners == 0 then + self.state.offenseState = C.Offense.batting + self.baserunning:pushNewBatter() + end end local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds) diff --git a/src/utils.lua b/src/utils.lua index 3932358..5c1d4ef 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -83,6 +83,10 @@ function utils.moveAtSpeed(mover, speed, target) return false end +function utils.within(within, n1, n2) + return math.abs(n1 - n2) < within +end + ---@generic T ---@param array T[] ---@param condition fun(T): boolean @@ -266,19 +270,24 @@ pitchTracker = { end, ---@param didSwing boolean - updatePitchCounts = function(self, didSwing) + ---@param fieldingTeamInningData TeamInningData + updatePitchCounts = function(self, didSwing, fieldingTeamInningData) if not self.recordedPitchX then return end + local currentPitchingStats = fieldingTeamInningData.pitching + if didSwing or self.recordedPitchX > C.StrikeZoneStartX and self.recordedPitchX < C.StrikeZoneEndX then self.strikes = self.strikes + 1 + currentPitchingStats.strikes = currentPitchingStats.strikes + 1 if self.strikes >= 3 then self:reset() return PitchOutcomes.StrikeOut end else self.balls = self.balls + 1 + currentPitchingStats.balls = currentPitchingStats.balls + 1 if self.balls >= 4 then self:reset() return PitchOutcomes.Walk