Convert main into a Game object.
Much BATTER encapsulation of its dependencies and mutable state. Only the team scores are still global and mutable, but that shouldn't be too hard to fix.
This commit is contained in:
parent
bb95ef5a63
commit
51855e13cf
|
@ -58,6 +58,7 @@ end
|
||||||
---@param floaty boolean | nil
|
---@param floaty boolean | nil
|
||||||
---@param customBallScaler pd_animator | nil
|
---@param customBallScaler pd_animator | nil
|
||||||
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||||
|
throwMeter:reset()
|
||||||
self.heldBy = nil
|
self.heldBy = nil
|
||||||
|
|
||||||
if not flyTimeMs then
|
if not flyTimeMs then
|
||||||
|
|
|
@ -151,9 +151,9 @@ end
|
||||||
-- TODO? Start moving target fielders close sooner?
|
-- TODO? Start moving target fielders close sooner?
|
||||||
---@param field Fielding
|
---@param field Fielding
|
||||||
---@param targetBase Base
|
---@param targetBase Base
|
||||||
---@param launchBall LaunchBall
|
---@param ball { launch: LaunchBall }
|
||||||
---@param throwFlyMs number
|
---@param throwFlyMs number
|
||||||
local function userThrowToCoroutine(field, targetBase, launchBall, throwFlyMs)
|
local function userThrowToCoroutine(field, targetBase, ball, throwFlyMs)
|
||||||
while true do
|
while true do
|
||||||
if field.fielderHoldingBall == nil then
|
if field.fielderHoldingBall == nil then
|
||||||
coroutine.yield()
|
coroutine.yield()
|
||||||
|
@ -163,7 +163,7 @@ local function userThrowToCoroutine(field, targetBase, launchBall, throwFlyMs)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
closestFielder.target = targetBase
|
closestFielder.target = targetBase
|
||||||
launchBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs)
|
ball:launch(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs)
|
||||||
Fielding.markIneligible(field.fielderHoldingBall)
|
Fielding.markIneligible(field.fielderHoldingBall)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -174,12 +174,12 @@ end
|
||||||
--- Buffer in a fielder throw action.
|
--- Buffer in a fielder throw action.
|
||||||
---@param self table
|
---@param self table
|
||||||
---@param targetBase Base
|
---@param targetBase Base
|
||||||
---@param launchBall LaunchBall
|
---@param ball { launch: LaunchBall }
|
||||||
---@param throwFlyMs number
|
---@param throwFlyMs number
|
||||||
function Fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
function Fielding:userThrowTo(targetBase, ball, throwFlyMs)
|
||||||
local maxTryTimeMs = 5000
|
local maxTryTimeMs = 5000
|
||||||
actionQueue:upsert("userThrowTo", maxTryTimeMs, function()
|
actionQueue:upsert("userThrowTo", maxTryTimeMs, function()
|
||||||
userThrowToCoroutine(self, targetBase, launchBall, throwFlyMs)
|
userThrowToCoroutine(self, targetBase, ball, throwFlyMs)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ function getDrawOffset(ballX, ballY)
|
||||||
return offsetX * 1.3, offsetY * 1.5
|
return offsetX * 1.3, offsetY * 1.5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class Blipper
|
||||||
|
---@field draw fun(self: self, disableBlipping: boolean, x: number, y: number)
|
||||||
-- selene: allow(unscoped_variables)
|
-- selene: allow(unscoped_variables)
|
||||||
blipper = {}
|
blipper = {}
|
||||||
|
|
||||||
|
@ -39,9 +41,3 @@ function blipper.new(msInterval, smiling, lowHat)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
--selene: allow(unscoped_variables)
|
|
||||||
HomeTeamBlipper = blipper.new(100, HomeTeamSprites.smiling, HomeTeamSprites.lowHat)
|
|
||||||
|
|
||||||
--selene: allow(unscoped_variables)
|
|
||||||
AwayTeamBlipper = blipper.new(100, AwayTeamSprites.smiling, AwayTeamSprites.lowHat)
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
-- selene: allow(unscoped_variables)
|
-- selene: allow(unscoped_variables)
|
||||||
---@class MainMenu
|
---@class MainMenu
|
||||||
MainMenu = {
|
MainMenu = {
|
||||||
mainGameUpdateFunction = nil,
|
---@type { new: fun(settings: Settings): { update: fun(self) } }
|
||||||
---@type fun(settings: Settings)
|
next = nil,
|
||||||
mainGameInitFunction = nil,
|
|
||||||
}
|
}
|
||||||
-- selene: allow(shadowing)
|
-- selene: allow(shadowing)
|
||||||
local gfx = playdate.graphics
|
local gfx = playdate.graphics
|
||||||
|
@ -12,24 +11,25 @@ local gfx = playdate.graphics
|
||||||
local StartFont <const> = gfx.font.new("fonts/Roobert-20-Medium.pft")
|
local StartFont <const> = gfx.font.new("fonts/Roobert-20-Medium.pft")
|
||||||
|
|
||||||
--- Take control of playdate.update
|
--- Take control of playdate.update
|
||||||
---@param config MainMenu Function that controls the main gameplay loop.
|
|
||||||
--- Will replace playdate.update when the menu is done.
|
--- Will replace playdate.update when the menu is done.
|
||||||
function MainMenu.start(config)
|
---@param next { new: fun(settings: Settings): { update: fun(self) } }
|
||||||
MainMenu.mainGameUpdateFunction = config.mainGameUpdateFunction
|
function MainMenu.start(next)
|
||||||
MainMenu.mainGameInitFunction = config.mainGameInitFunction
|
MainMenu.next = next
|
||||||
playdate.update = MainMenu.update
|
playdate.update = MainMenu.update
|
||||||
end
|
end
|
||||||
|
|
||||||
local inningCountSelection = 3
|
local inningCountSelection = 3
|
||||||
|
|
||||||
local function startGame()
|
local function startGame()
|
||||||
MainMenu.mainGameInitFunction({
|
local next = MainMenu.next.new({
|
||||||
finalInning = inningCountSelection,
|
finalInning = inningCountSelection,
|
||||||
homeTeamSprites = HomeTeamSprites,
|
homeTeamSprites = HomeTeamSprites,
|
||||||
awayTeamSprites = AwayTeamSprites,
|
awayTeamSprites = AwayTeamSprites,
|
||||||
})
|
})
|
||||||
playdate.resetElapsedTime()
|
playdate.resetElapsedTime()
|
||||||
playdate.update = MainMenu.mainGameUpdateFunction
|
playdate.update = function()
|
||||||
|
next:update()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function pausingEaser(baseEaser)
|
local function pausingEaser(baseEaser)
|
||||||
|
|
523
src/main.lua
523
src/main.lua
|
@ -11,6 +11,7 @@ import 'CoreLibs/ui.lua'
|
||||||
--- @alias EasingFunc fun(number, number, number, number): number
|
--- @alias EasingFunc fun(number, number, number, number): number
|
||||||
|
|
||||||
--- @alias LaunchBall fun(
|
--- @alias LaunchBall fun(
|
||||||
|
--- self: self,
|
||||||
--- destX: number,
|
--- destX: number,
|
||||||
--- destY: number,
|
--- destY: number,
|
||||||
--- easingFunc: EasingFunc,
|
--- easingFunc: EasingFunc,
|
||||||
|
@ -44,34 +45,6 @@ import 'npc.lua'
|
||||||
-- selene: allow(shadowing)
|
-- selene: allow(shadowing)
|
||||||
local gfx <const>, C <const> = playdate.graphics, C
|
local gfx <const>, C <const> = playdate.graphics, C
|
||||||
|
|
||||||
local announcer = Announcer.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.
|
|
||||||
|
|
||||||
------------------
|
|
||||||
-- GLOBAL STATE --
|
|
||||||
------------------
|
|
||||||
|
|
||||||
--- Well, maybe not "Settings", but passive state that probably won't change much, if at all, during a game.
|
|
||||||
---@class Settings
|
|
||||||
local settings = {
|
|
||||||
finalInning = 3,
|
|
||||||
---@type SpriteCollection
|
|
||||||
awayTeamSprites = nil,
|
|
||||||
---@type SpriteCollection
|
|
||||||
homeTeamSprites = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
local deltaSeconds = 0
|
|
||||||
local ball = Ball.new(gfx.animator)
|
|
||||||
|
|
||||||
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 }
|
---@alias Team { score: number, benchPosition: XyPair }
|
||||||
---@type table<string, Team>
|
---@type table<string, Team>
|
||||||
local teams <const> = {
|
local teams <const> = {
|
||||||
|
@ -85,49 +58,133 @@ local teams <const> = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local inning = 1
|
--- Well, maybe not "Settings", but passive state that probably won't change much, if at all, during a game.
|
||||||
|
---@class Settings
|
||||||
local battingTeam = teams.away
|
---@field finalInning number
|
||||||
local offenseState = C.Offense.batting
|
---@field userTeam Team | nil
|
||||||
|
---@field awayTeamSprites SpriteCollection
|
||||||
|
---@field homeTeamSprites SpriteCollection
|
||||||
|
|
||||||
|
---@class MutableState
|
||||||
|
---@field deltaSeconds number
|
||||||
|
---@field ball Ball
|
||||||
|
---@field battingTeam Team
|
||||||
|
---@field catcherThrownBall boolean
|
||||||
|
---@field offenseState OffenseState
|
||||||
|
---@field inning number
|
||||||
|
---@field batBase XyPair
|
||||||
|
---@field batTip XyPair
|
||||||
|
---@field batAngleDeg number
|
||||||
-- 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
|
---@field secondsSinceLastRunnerMove number
|
||||||
local secondsSincePitchAllowed = 0
|
---@field secondsSincePitchAllowed number
|
||||||
|
|
||||||
-- These are only sort-of global state. They are purely graphical,
|
-- 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.
|
-- but they need to be kept in sync with the rest of the globals.
|
||||||
local runnerBlipper
|
---@field runnerBlipper Blipper
|
||||||
local battingTeamSprites
|
---@field battingTeamSprites SpriteCollection
|
||||||
local fieldingTeamSprites
|
---@field fieldingTeamSprites SpriteCollection
|
||||||
|
|
||||||
-------------------------
|
---@class Game
|
||||||
-- END OF GLOBAL STATE --
|
---@field private settings Settings
|
||||||
-------------------------
|
---@field private announcer Announcer
|
||||||
|
---@field private fielding Fielding
|
||||||
|
---@field private baserunning Baserunning
|
||||||
|
---@field private npc Npc
|
||||||
|
---@field private homeTeamBlipper Blipper
|
||||||
|
---@field private awayTeamBlipper Blipper
|
||||||
|
---@field private state MutableState
|
||||||
|
-- selene: allow(unscoped_variables)
|
||||||
|
Game = {}
|
||||||
|
|
||||||
local UserTeam <const> = teams.away
|
---@param settings Settings
|
||||||
|
---@param announcer Announcer | nil
|
||||||
|
---@param fielding Fielding | nil
|
||||||
|
---@param baserunning Baserunning | nil
|
||||||
|
---@param npc Npc | nil
|
||||||
|
---@param state MutableState | nil
|
||||||
|
---@return Game
|
||||||
|
function Game.new(settings, announcer, fielding, baserunning, npc, state)
|
||||||
|
announcer = announcer or Announcer.new()
|
||||||
|
fielding = fielding or Fielding.new()
|
||||||
|
settings.userTeam = teams.away
|
||||||
|
|
||||||
|
local homeTeamBlipper = blipper.new(100, settings.homeTeamSprites.smiling, settings.homeTeamSprites.lowHat)
|
||||||
|
local awayTeamBlipper = blipper.new(100, settings.awayTeamSprites.smiling, settings.awayTeamSprites.lowHat)
|
||||||
|
local battingTeam = teams.away
|
||||||
|
local runnerBlipper = battingTeam == teams.away and awayTeamBlipper or homeTeamBlipper
|
||||||
|
local ball = Ball.new(gfx.animator)
|
||||||
|
|
||||||
|
local o = setmetatable({
|
||||||
|
settings = settings,
|
||||||
|
announcer = announcer,
|
||||||
|
fielding = fielding,
|
||||||
|
state = state or {
|
||||||
|
batBase = utils.xy(C.Center.x - 34, 215),
|
||||||
|
batTip = utils.xy(0, 0),
|
||||||
|
batAngleDeg = C.CrankOffsetDeg,
|
||||||
|
deltaSeconds = 0,
|
||||||
|
ball = ball,
|
||||||
|
battingTeam = battingTeam,
|
||||||
|
offenseState = C.Offense.batting,
|
||||||
|
inning = 1,
|
||||||
|
catcherThrownBall = false,
|
||||||
|
secondsSinceLastRunnerMove = 0,
|
||||||
|
secondsSincePitchAllowed = 0,
|
||||||
|
battingTeamSprites = settings.awayTeamSprites,
|
||||||
|
fieldingTeamSprites = settings.homeTeamSprites,
|
||||||
|
homeTeamBlipper = homeTeamBlipper,
|
||||||
|
awayTeamBlipper = awayTeamBlipper,
|
||||||
|
runnerBlipper = runnerBlipper,
|
||||||
|
},
|
||||||
|
}, { __index = Game })
|
||||||
|
|
||||||
|
o.baserunning = baserunning or Baserunning.new(announcer, function()
|
||||||
|
o:nextHalfInning()
|
||||||
|
end)
|
||||||
|
o.npc = npc or Npc.new(o.baserunning.runners, o.fielding.fielders)
|
||||||
|
|
||||||
|
o.fielding:resetFielderPositions(teams.home.benchPosition)
|
||||||
|
playdate.timer.new(2000, function()
|
||||||
|
ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
BootTune:play()
|
||||||
|
BootTune:setFinishCallback(function()
|
||||||
|
TinnyBackground:play()
|
||||||
|
end)
|
||||||
|
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
---@alias SimpleAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) }
|
---@alias SimpleAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) }
|
||||||
---@alias Pitch { x: SimpleAnimator, y: SimpleAnimator, z: SimpleAnimator | nil }
|
---@alias Pitch fun(ball: Ball): { x: SimpleAnimator, y: SimpleAnimator, z: SimpleAnimator | nil }
|
||||||
|
|
||||||
---@type Pitch[]
|
---@type Pitch[]
|
||||||
local Pitches <const> = {
|
local Pitches <const> = {
|
||||||
-- Fastball
|
-- Fastball
|
||||||
{
|
function()
|
||||||
|
return {
|
||||||
x = gfx.animator.new(0, C.PitchStartX, C.PitchStartX, playdate.easingFunctions.linear),
|
x = gfx.animator.new(0, C.PitchStartX, C.PitchStartX, playdate.easingFunctions.linear),
|
||||||
y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||||
},
|
}
|
||||||
|
end,
|
||||||
-- Curve ball
|
-- Curve ball
|
||||||
{
|
function()
|
||||||
|
return {
|
||||||
x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX + 20, C.PitchStartX, utils.easingHill),
|
x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX + 20, C.PitchStartX, utils.easingHill),
|
||||||
y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||||
},
|
}
|
||||||
|
end,
|
||||||
-- Slider
|
-- Slider
|
||||||
{
|
function()
|
||||||
|
return {
|
||||||
x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX - 20, C.PitchStartX, utils.easingHill),
|
x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX - 20, C.PitchStartX, utils.easingHill),
|
||||||
y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||||
},
|
}
|
||||||
|
end,
|
||||||
-- Wobbbleball
|
-- Wobbbleball
|
||||||
{
|
function(ball)
|
||||||
|
return {
|
||||||
x = {
|
x = {
|
||||||
currentValue = function()
|
currentValue = function()
|
||||||
return C.PitchStartX + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStartY) / 10))
|
return C.PitchStartX + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStartY) / 10))
|
||||||
|
@ -135,17 +192,18 @@ local Pitches <const> = {
|
||||||
reset = function() end,
|
reset = function() end,
|
||||||
},
|
},
|
||||||
y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
|
||||||
},
|
}
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
---@return boolean userIsOnSide, boolean userIsOnOtherSide
|
---@return boolean userIsOnSide, boolean userIsOnOtherSide
|
||||||
local function userIsOn(side)
|
function Game:userIsOn(side)
|
||||||
if UserTeam == nil then
|
if self.settings.userTeam == nil then
|
||||||
-- Both teams are NPC-driven
|
-- Both teams are NPC-driven
|
||||||
return false, false
|
return false, false
|
||||||
end
|
end
|
||||||
local ret
|
local ret
|
||||||
if UserTeam == battingTeam then
|
if self.settings.userTeam == self.state.battingTeam then
|
||||||
ret = side == C.Sides.offense
|
ret = side == C.Sides.offense
|
||||||
else
|
else
|
||||||
ret = side == C.Sides.defense
|
ret = side == C.Sides.defense
|
||||||
|
@ -153,86 +211,77 @@ local function userIsOn(side)
|
||||||
return ret, not ret
|
return ret, not ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type LaunchBall
|
|
||||||
local function launchBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
|
||||||
throwMeter:reset()
|
|
||||||
ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param pitchFlyTimeMs number | nil
|
---@param pitchFlyTimeMs number | nil
|
||||||
---@param pitchTypeIndex number | nil
|
---@param pitchTypeIndex number | nil
|
||||||
local function pitch(pitchFlyTimeMs, pitchTypeIndex)
|
function Game:pitch(pitchFlyTimeMs, pitchTypeIndex)
|
||||||
Fielding.markIneligible(fielding.fielders.pitcher)
|
Fielding.markIneligible(self.fielding.fielders.pitcher)
|
||||||
ball.heldBy = nil
|
self.state.ball.heldBy = nil
|
||||||
catcherThrownBall = false
|
self.state.catcherThrownBall = false
|
||||||
offenseState = C.Offense.batting
|
self.state.offenseState = C.Offense.batting
|
||||||
|
|
||||||
local current = Pitches[pitchTypeIndex]
|
local current = Pitches[pitchTypeIndex](self.state.ball)
|
||||||
ball.xAnimator = current.x
|
self.state.ball.xAnimator = current.x
|
||||||
ball.yAnimator = current.y or Pitches[1].y
|
self.state.ball.yAnimator = current.y or Pitches[1](self.state.ball).y
|
||||||
|
|
||||||
-- TODO: This would need to be sanely replaced in launchBall() etc.
|
-- TODO: This would need to be sanely replaced in ball:launch() etc.
|
||||||
-- if current.z then
|
-- if current.z then
|
||||||
-- ball.floatAnimator = current.z
|
-- ball.floatAnimator = current.z
|
||||||
-- ball.floatAnimator:reset()
|
-- ball.floatAnimator:reset()
|
||||||
-- end
|
-- end
|
||||||
|
|
||||||
if pitchFlyTimeMs then
|
if pitchFlyTimeMs then
|
||||||
ball.xAnimator:reset(pitchFlyTimeMs)
|
self.state.ball.xAnimator:reset(pitchFlyTimeMs)
|
||||||
ball.yAnimator:reset(pitchFlyTimeMs)
|
self.state.ball.yAnimator:reset(pitchFlyTimeMs)
|
||||||
else
|
else
|
||||||
ball.xAnimator:reset()
|
self.state.ball.xAnimator:reset()
|
||||||
ball.yAnimator:reset()
|
self.state.ball.yAnimator:reset()
|
||||||
end
|
end
|
||||||
|
|
||||||
secondsSincePitchAllowed = 0
|
self.state.secondsSincePitchAllowed = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local function nextHalfInning()
|
function Game:nextHalfInning()
|
||||||
pitchTracker:reset()
|
pitchTracker:reset()
|
||||||
local currentlyFieldingTeam = battingTeam == teams.home and teams.away or teams.home
|
local currentlyFieldingTeam = self.state.battingTeam == teams.home and teams.away or teams.home
|
||||||
local gameOver = inning == settings.finalInning and teams.away.score ~= teams.home.score
|
local gameOver = self.state.inning == self.settings.finalInning and teams.away.score ~= teams.home.score
|
||||||
if not gameOver then
|
if not gameOver then
|
||||||
fielding:celebrate()
|
self.fielding:celebrate()
|
||||||
secondsSinceLastRunnerMove = -7
|
self.state.secondsSinceLastRunnerMove = -7
|
||||||
fielding:benchTo(currentlyFieldingTeam.benchPosition)
|
self.fielding:benchTo(currentlyFieldingTeam.benchPosition)
|
||||||
announcer:say("SWITCHING SIDES...")
|
self.announcer:say("SWITCHING SIDES...")
|
||||||
end
|
end
|
||||||
|
|
||||||
if gameOver then
|
if gameOver then
|
||||||
announcer:say("AND THAT'S THE BALL GAME!")
|
self.announcer:say("AND THAT'S THE BALL GAME!")
|
||||||
else
|
else
|
||||||
fielding:resetFielderPositions()
|
self.fielding:resetFielderPositions()
|
||||||
if battingTeam == teams.home then
|
if self.state.battingTeam == teams.home then
|
||||||
inning = inning + 1
|
self.state.inning = self.state.inning + 1
|
||||||
end
|
end
|
||||||
battingTeam = currentlyFieldingTeam
|
self.state.battingTeam = currentlyFieldingTeam
|
||||||
playdate.timer.new(2000, function()
|
playdate.timer.new(2000, function()
|
||||||
if battingTeam == teams.home then
|
if self.state.battingTeam == teams.home then
|
||||||
battingTeamSprites = settings.homeTeamSprites
|
self.state.battingTeamSprites = self.settings.homeTeamSprites
|
||||||
runnerBlipper = HomeTeamBlipper
|
self.state.runnerBlipper = self.homeTeamBlipper
|
||||||
fieldingTeamSprites = settings.awayTeamSprites
|
self.state.fieldingTeamSprites = self.settings.awayTeamSprites
|
||||||
else
|
else
|
||||||
battingTeamSprites = settings.awayTeamSprites
|
self.state.battingTeamSprites = self.settings.awayTeamSprites
|
||||||
fieldingTeamSprites = settings.homeTeamSprites
|
self.state.fieldingTeamSprites = self.settings.homeTeamSprites
|
||||||
runnerBlipper = AwayTeamBlipper
|
self.state.runnerBlipper = self.awayTeamBlipper
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local baserunning = Baserunning.new(announcer, nextHalfInning)
|
|
||||||
local npc = Npc.new(baserunning.runners, fielding.fielders)
|
|
||||||
|
|
||||||
---@param scoredRunCount number
|
---@param scoredRunCount number
|
||||||
local function score(scoredRunCount)
|
function Game:score(scoredRunCount)
|
||||||
battingTeam.score = battingTeam.score + scoredRunCount
|
self.state.battingTeam.score = self.state.battingTeam.score + scoredRunCount
|
||||||
announcer:say("SCORE!")
|
self.announcer:say("SCORE!")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param throwFlyMs number
|
---@param throwFlyMs number
|
||||||
---@return boolean didThrow
|
---@return boolean didThrow
|
||||||
local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
function Game:buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
||||||
local targetBase
|
local targetBase
|
||||||
if playdate.buttonIsPressed(playdate.kButtonLeft) then
|
if playdate.buttonIsPressed(playdate.kButtonLeft) then
|
||||||
targetBase = C.Bases[C.Third]
|
targetBase = C.Bases[C.Third]
|
||||||
|
@ -246,62 +295,67 @@ local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Power for this throw has already been determined
|
self.fielding:userThrowTo(targetBase, self.state.ball, throwFlyMs)
|
||||||
throwMeter:reset()
|
self.state.secondsSinceLastRunnerMove = 0
|
||||||
|
self.state.offenseState = C.Offense.running
|
||||||
fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
|
||||||
secondsSinceLastRunnerMove = 0
|
|
||||||
offenseState = C.Offense.running
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function nextBatter()
|
function Game:nextBatter()
|
||||||
secondsSincePitchAllowed = -3
|
self.state.secondsSincePitchAllowed = -3
|
||||||
baserunning.batter = nil
|
self.baserunning.batter = nil
|
||||||
playdate.timer.new(2000, function()
|
playdate.timer.new(2000, function()
|
||||||
pitchTracker:reset()
|
pitchTracker:reset()
|
||||||
if not baserunning.batter then
|
if not self.baserunning.batter then
|
||||||
baserunning:pushNewBatter()
|
self.baserunning:pushNewBatter()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function walk()
|
function Game:walk()
|
||||||
announcer:say("Walk!")
|
self.announcer:say("Walk!")
|
||||||
-- TODO? Use baserunning:convertBatterToRunner()
|
-- TODO? Use self.baserunning:convertBatterToRunner()
|
||||||
baserunning.batter.nextBase = C.Bases[C.First]
|
self.baserunning.batter.nextBase = C.Bases[C.First]
|
||||||
baserunning.batter.prevBase = C.Bases[C.Home]
|
self.baserunning.batter.prevBase = C.Bases[C.Home]
|
||||||
offenseState = C.Offense.walking
|
self.state.offenseState = C.Offense.walking
|
||||||
baserunning.batter = nil
|
self.baserunning.batter = nil
|
||||||
baserunning:updateForcedRunners()
|
self.baserunning:updateForcedRunners()
|
||||||
nextBatter()
|
self:nextBatter()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function strikeOut()
|
function Game:strikeOut()
|
||||||
local outBatter = baserunning.batter
|
local outBatter = self.baserunning.batter
|
||||||
baserunning.batter = nil
|
self.baserunning.batter = nil
|
||||||
baserunning:outRunner(outBatter --[[@as Runner]], "Strike out!")
|
self.baserunning:outRunner(outBatter --[[@as Runner]], "Strike out!")
|
||||||
nextBatter()
|
self:nextBatter()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param batDeg number
|
---@param batDeg number
|
||||||
local function updateBatting(batDeg, batSpeed)
|
function Game:updateBatting(batDeg, batSpeed)
|
||||||
local batAngle = math.rad(batDeg)
|
local batAngle = math.rad(batDeg)
|
||||||
-- TODO: animate bat-flip or something
|
-- TODO: animate bat-flip or something
|
||||||
batBase.x = baserunning.batter and (baserunning.batter.x + C.BatterHandPos.x) or 0
|
self.state.batBase.x = self.baserunning.batter and (self.baserunning.batter.x + C.BatterHandPos.x) or 0
|
||||||
batBase.y = baserunning.batter and (baserunning.batter.y + C.BatterHandPos.y) or 0
|
self.state.batBase.y = self.baserunning.batter and (self.baserunning.batter.y + C.BatterHandPos.y) or 0
|
||||||
batTip.x = batBase.x + (C.BatLength * math.sin(batAngle))
|
self.state.batTip.x = self.state.batBase.x + (C.BatLength * math.sin(batAngle))
|
||||||
batTip.y = batBase.y + (C.BatLength * math.cos(batAngle))
|
self.state.batTip.y = self.state.batBase.y + (C.BatLength * math.cos(batAngle))
|
||||||
|
|
||||||
if
|
if
|
||||||
batSpeed > 0
|
batSpeed > 0
|
||||||
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, C.Screen.H)
|
and utils.pointDirectlyUnderLine(
|
||||||
and ball.y < 232
|
self.state.ball.x,
|
||||||
|
self.state.ball.y,
|
||||||
|
self.state.batBase.x,
|
||||||
|
self.state.batBase.y,
|
||||||
|
self.state.batTip.x,
|
||||||
|
self.state.batTip.y,
|
||||||
|
C.Screen.H
|
||||||
|
)
|
||||||
|
and self.state.ball.y < 232
|
||||||
then
|
then
|
||||||
-- Hit!
|
-- Hit!
|
||||||
BatCrackReverb:play()
|
BatCrackReverb:play()
|
||||||
offenseState = C.Offense.running
|
self.state.offenseState = C.Offense.running
|
||||||
local ballAngle = batAngle + math.rad(90)
|
local ballAngle = batAngle + math.rad(90)
|
||||||
|
|
||||||
local mult = math.abs(batSpeed / 15)
|
local mult = math.abs(batSpeed / 15)
|
||||||
|
@ -311,52 +365,54 @@ local function updateBatting(batDeg, batSpeed)
|
||||||
ballVelX = ballVelX * -1
|
ballVelX = ballVelX * -1
|
||||||
ballVelY = ballVelY * -1
|
ballVelY = ballVelY * -1
|
||||||
end
|
end
|
||||||
local ballDestX = ball.x + (ballVelX * C.BattingPower)
|
local ballDestX = self.state.ball.x + (ballVelX * C.BattingPower)
|
||||||
local ballDestY = ball.y + (ballVelY * C.BattingPower)
|
local ballDestY = self.state.ball.y + (ballVelY * C.BattingPower)
|
||||||
pitchTracker:reset()
|
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)
|
self.state.ball:launch(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler)
|
||||||
-- 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.
|
||||||
|
|
||||||
if utils.isFoulBall(ballDestX, ballDestY) then
|
if utils.isFoulBall(ballDestX, ballDestY) then
|
||||||
announcer:say("Foul ball!")
|
self.announcer:say("Foul ball!")
|
||||||
pitchTracker.strikes = math.min(pitchTracker.strikes + 1, 2)
|
pitchTracker.strikes = math.min(pitchTracker.strikes + 1, 2)
|
||||||
-- TODO: Have a fielder chase for the fly-out
|
-- TODO: Have a fielder chase for the fly-out
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
baserunning:convertBatterToRunner()
|
self.baserunning:convertBatterToRunner()
|
||||||
|
|
||||||
fielding:haveSomeoneChase(ballDestX, ballDestY)
|
self.fielding:haveSomeoneChase(ballDestX, ballDestY)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param appliedSpeed number | fun(runner: Runner): number
|
---@param appliedSpeed number | fun(runner: Runner): number
|
||||||
---@return boolean someRunnerMoved
|
---@return boolean someRunnerMoved
|
||||||
local function updateNonBatterRunners(appliedSpeed, forcedOnly)
|
function Game:updateNonBatterRunners(appliedSpeed, forcedOnly)
|
||||||
local runnerMoved, runnersScored = baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSeconds)
|
local runnerMoved, runnersScored =
|
||||||
|
self.baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, self.state.deltaSeconds)
|
||||||
if runnersScored ~= 0 then
|
if runnersScored ~= 0 then
|
||||||
score(runnersScored)
|
self:score(runnersScored)
|
||||||
end
|
end
|
||||||
return runnerMoved
|
return runnerMoved
|
||||||
end
|
end
|
||||||
|
|
||||||
local function userPitch(throwFly)
|
---@param throwFly number
|
||||||
local aButton = playdate.buttonIsPressed(playdate.kButtonA)
|
function Game:userPitch(throwFly)
|
||||||
local bButton = playdate.buttonIsPressed(playdate.kButtonB)
|
local aPressed = playdate.buttonIsPressed(playdate.kButtonA)
|
||||||
if not aButton and not bButton then
|
local bPressed = playdate.buttonIsPressed(playdate.kButtonB)
|
||||||
pitch(throwFly, 1)
|
if not aPressed and not bPressed then
|
||||||
elseif aButton and not bButton then
|
self:pitch(throwFly, 1)
|
||||||
pitch(throwFly, 2)
|
elseif aPressed and not bPressed then
|
||||||
elseif not aButton and bButton then
|
self:pitch(throwFly, 2)
|
||||||
pitch(throwFly, 3)
|
elseif not aPressed and bPressed then
|
||||||
elseif aButton and bButton then
|
self:pitch(throwFly, 3)
|
||||||
pitch(throwFly, 4)
|
elseif aPressed and bPressed then
|
||||||
|
self:pitch(throwFly, 4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function updateGameState()
|
function Game:updateGameState()
|
||||||
deltaSeconds = playdate.getElapsedTime() or 0
|
self.state.deltaSeconds = playdate.getElapsedTime() or 0
|
||||||
playdate.resetElapsedTime()
|
playdate.resetElapsedTime()
|
||||||
local crankChange = playdate.getCrankChange() --[[@as number]]
|
local crankChange = playdate.getCrankChange() --[[@as number]]
|
||||||
local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * C.CrankPower)
|
local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * C.CrankPower)
|
||||||
|
@ -364,115 +420,117 @@ local function updateGameState()
|
||||||
crankLimited = crankLimited * -1
|
crankLimited = crankLimited * -1
|
||||||
end
|
end
|
||||||
|
|
||||||
ball:updatePosition()
|
self.state.ball:updatePosition()
|
||||||
|
|
||||||
local userOnOffense, userOnDefense = userIsOn(C.Sides.offense)
|
local userOnOffense, userOnDefense = self:userIsOn(C.Sides.offense)
|
||||||
|
|
||||||
if userOnDefense then
|
if userOnDefense then
|
||||||
throwMeter:applyCharge(deltaSeconds, crankLimited)
|
throwMeter:applyCharge(self.state.deltaSeconds, crankLimited)
|
||||||
end
|
end
|
||||||
|
|
||||||
if offenseState == C.Offense.batting then
|
if self.state.offenseState == C.Offense.batting then
|
||||||
if ball.y < C.StrikeZoneStartY then
|
if self.state.ball.y < C.StrikeZoneStartY then
|
||||||
pitchTracker.recordedPitchX = nil
|
pitchTracker.recordedPitchX = nil
|
||||||
elseif not pitchTracker.recordedPitchX then
|
elseif not pitchTracker.recordedPitchX then
|
||||||
pitchTracker.recordedPitchX = ball.x
|
pitchTracker.recordedPitchX = self.state.ball.x
|
||||||
end
|
end
|
||||||
|
|
||||||
local pitcher = fielding.fielders.pitcher
|
local pitcher = self.fielding.fielders.pitcher
|
||||||
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitcherStartPos.x, C.PitcherStartPos.y) < C.BaseHitbox then
|
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitcherStartPos.x, C.PitcherStartPos.y) < C.BaseHitbox then
|
||||||
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
self.state.secondsSincePitchAllowed = self.state.secondsSincePitchAllowed + self.state.deltaSeconds
|
||||||
end
|
end
|
||||||
|
|
||||||
if secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not catcherThrownBall then
|
if self.state.secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not self.state.catcherThrownBall then
|
||||||
local outcome = pitchTracker:updatePitchCounts()
|
local outcome = pitchTracker:updatePitchCounts()
|
||||||
if outcome == PitchOutcomes.StrikeOut then
|
if outcome == PitchOutcomes.StrikeOut then
|
||||||
strikeOut()
|
self:strikeOut()
|
||||||
elseif outcome == PitchOutcomes.Walk then
|
elseif outcome == PitchOutcomes.Walk then
|
||||||
walk()
|
self:walk()
|
||||||
end
|
end
|
||||||
-- Catcher has the ball. Throw it back to the pitcher
|
-- Catcher has the ball. Throw it back to the pitcher
|
||||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
self.state.ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||||
catcherThrownBall = true
|
self.state.catcherThrownBall = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local batSpeed
|
local batSpeed
|
||||||
if userOnOffense then
|
if userOnOffense then
|
||||||
batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
|
self.state.batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
|
||||||
batSpeed = crankLimited
|
batSpeed = crankLimited
|
||||||
else
|
else
|
||||||
batAngleDeg = npc:updateBatAngle(ball, catcherThrownBall, deltaSeconds)
|
self.state.batAngleDeg =
|
||||||
batSpeed = npc:batSpeed() * deltaSeconds
|
self.npc:updateBatAngle(self.state.ball, self.state.catcherThrownBall, self.state.deltaSeconds)
|
||||||
|
batSpeed = self.npc:batSpeed() * self.state.deltaSeconds
|
||||||
end
|
end
|
||||||
|
|
||||||
updateBatting(batAngleDeg, batSpeed)
|
self:updateBatting(self.state.batAngleDeg, batSpeed)
|
||||||
|
|
||||||
-- Walk batter to the plate
|
-- Walk batter to the plate
|
||||||
-- TODO: Ensure batter can't be nil, here
|
-- TODO: Ensure batter can't be nil, here
|
||||||
baserunning:updateRunner(baserunning.batter, nil, crankLimited, deltaSeconds)
|
self.baserunning:updateRunner(self.baserunning.batter, nil, crankLimited, self.state.deltaSeconds)
|
||||||
|
|
||||||
if secondsSincePitchAllowed > C.PitchAfterSeconds then
|
if self.state.secondsSincePitchAllowed > C.PitchAfterSeconds then
|
||||||
if userOnDefense then
|
if userOnDefense then
|
||||||
local throwFly = throwMeter:readThrow()
|
local throwFly = throwMeter:readThrow()
|
||||||
if throwFly and not buttonControlledThrow(throwFly, true) then
|
if throwFly and not self:buttonControlledThrow(throwFly, true) then
|
||||||
userPitch(throwFly)
|
self:userPitch(throwFly)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
pitch(C.PitchFlyMs / npc:pitchSpeed(), math.random(#Pitches))
|
self:pitch(C.PitchFlyMs / self.npc:pitchSpeed(), math.random(#Pitches))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif offenseState == C.Offense.running then
|
elseif self.state.offenseState == C.Offense.running then
|
||||||
local appliedSpeed = userOnOffense and crankLimited
|
local appliedSpeed = userOnOffense and crankLimited
|
||||||
or function(runner)
|
or function(runner)
|
||||||
return npc:runningSpeed(runner, ball)
|
return self.npc:runningSpeed(runner, self.state.ball)
|
||||||
end
|
end
|
||||||
if updateNonBatterRunners(appliedSpeed) then
|
if self:updateNonBatterRunners(appliedSpeed) then
|
||||||
secondsSinceLastRunnerMove = 0
|
self.state.secondsSinceLastRunnerMove = 0
|
||||||
else
|
else
|
||||||
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
self.state.secondsSinceLastRunnerMove = self.state.secondsSinceLastRunnerMove + self.state.deltaSeconds
|
||||||
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
if self.state.secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
||||||
-- End of play. Throw the ball back to the pitcher
|
-- End of play. Throw the ball back to the pitcher
|
||||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
self.state.ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||||
fielding:markAllIneligible() -- This is ugly, and ideally would not be necessary if Fielding handled the return throw directly.
|
self.fielding:markAllIneligible() -- This is ugly, and ideally would not be necessary if Fielding handled the return throw directly.
|
||||||
fielding:resetFielderPositions()
|
self.fielding:resetFielderPositions()
|
||||||
offenseState = C.Offense.batting
|
self.state.offenseState = C.Offense.batting
|
||||||
-- TODO: Remove, or replace with nextBatter()
|
-- TODO: Remove, or replace with nextBatter()
|
||||||
if not baserunning.batter then
|
if not self.baserunning.batter then
|
||||||
baserunning:pushNewBatter()
|
self.baserunning:pushNewBatter()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif offenseState == C.Offense.walking then
|
elseif self.state.offenseState == C.Offense.walking then
|
||||||
if not updateNonBatterRunners(C.WalkedRunnerSpeed, true) then
|
if not self:updateNonBatterRunners(C.WalkedRunnerSpeed, true) then
|
||||||
offenseState = C.Offense.batting
|
self.state.offenseState = C.Offense.batting
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local fielderHoldingBall = fielding:updateFielderPositions(ball, deltaSeconds)
|
local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
|
||||||
|
|
||||||
if userOnDefense then
|
if userOnDefense then
|
||||||
local throwFly = throwMeter:readThrow()
|
local throwFly = throwMeter:readThrow()
|
||||||
if throwFly then
|
if throwFly then
|
||||||
buttonControlledThrow(throwFly)
|
self:buttonControlledThrow(throwFly)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if fielderHoldingBall then
|
if fielderHoldingBall then
|
||||||
local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall)
|
local outedSomeRunner = self.baserunning:outEligibleRunners(fielderHoldingBall)
|
||||||
if not userOnDefense and offenseState == C.Offense.running then
|
if not userOnDefense and self.state.offenseState == C.Offense.running then
|
||||||
npc:fielderAction(fielderHoldingBall, outedSomeRunner, ball, launchBall)
|
self.npc:fielderAction(fielderHoldingBall, outedSomeRunner, self.state.ball)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
baserunning:walkAwayOutRunners(deltaSeconds)
|
self.baserunning:walkAwayOutRunners(self.state.deltaSeconds)
|
||||||
actionQueue:runWaiting(deltaSeconds)
|
actionQueue:runWaiting(self.state.deltaSeconds)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Swappable update() for main menu, etc.
|
-- TODO: Swappable update() for main menu, etc.
|
||||||
|
|
||||||
function mainGameUpdate()
|
function Game:update()
|
||||||
playdate.timer.updateTimers()
|
playdate.timer.updateTimers()
|
||||||
gfx.animation.blinker.updateAll()
|
gfx.animation.blinker.updateAll()
|
||||||
updateGameState()
|
self:updateGameState()
|
||||||
|
local ball = self.state.ball
|
||||||
|
|
||||||
gfx.clear()
|
gfx.clear()
|
||||||
gfx.setColor(gfx.kColorBlack)
|
gfx.setColor(gfx.kColorBlack)
|
||||||
|
@ -482,30 +540,30 @@ function mainGameUpdate()
|
||||||
|
|
||||||
GrassBackground:draw(-400, -240)
|
GrassBackground:draw(-400, -240)
|
||||||
|
|
||||||
local ballIsHeld = fielding:drawFielders(fieldingTeamSprites, ball)
|
local ballIsHeld = self.fielding:drawFielders(self.state.fieldingTeamSprites, ball)
|
||||||
|
|
||||||
if offenseState == C.Offense.batting then
|
if self.state.offenseState == C.Offense.batting then
|
||||||
gfx.setLineWidth(5)
|
gfx.setLineWidth(5)
|
||||||
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
|
gfx.drawLine(self.state.batBase.x, self.state.batBase.y, self.state.batTip.x, self.state.batTip.y)
|
||||||
end
|
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(self.baserunning.runners) do
|
||||||
if runner == baserunning.batter then
|
if runner == self.baserunning.batter then
|
||||||
if batAngleDeg > 50 and batAngleDeg < 200 then
|
if self.state.batAngleDeg > 50 and self.state.batAngleDeg < 200 then
|
||||||
battingTeamSprites.back:draw(runner.x, runner.y - playerHeightOffset)
|
self.state.battingTeamSprites.back:draw(runner.x, runner.y - playerHeightOffset)
|
||||||
else
|
else
|
||||||
battingTeamSprites.smiling:draw(runner.x, runner.y - playerHeightOffset)
|
self.state.battingTeamSprites.smiling:draw(runner.x, runner.y - playerHeightOffset)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- TODO? Change blip speed depending on runner speed?
|
-- TODO? Change blip speed depending on runner speed?
|
||||||
runnerBlipper:draw(false, runner.x, runner.y - playerHeightOffset)
|
self.state.runnerBlipper:draw(false, runner.x, runner.y - playerHeightOffset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, runner in pairs(baserunning.outRunners) do
|
for _, runner in pairs(self.baserunning.outRunners) do
|
||||||
battingTeamSprites.frowning:draw(runner.x, runner.y - playerHeightOffset)
|
self.state.battingTeamSprites.frowning:draw(runner.x, runner.y - playerHeightOffset)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not ballIsHeld then
|
if not ballIsHeld then
|
||||||
|
@ -520,45 +578,20 @@ function mainGameUpdate()
|
||||||
|
|
||||||
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) > 10 then
|
||||||
drawMinimap(baserunning.runners, fielding.fielders)
|
drawMinimap(self.baserunning.runners, self.fielding.fielders)
|
||||||
end
|
end
|
||||||
drawScoreboard(0, C.Screen.H * 0.77, teams, baserunning.outs, battingTeam, inning)
|
drawScoreboard(0, C.Screen.H * 0.77, teams, self.baserunning.outs, self.state.battingTeam, self.state.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)
|
self.announcer:draw(C.Center.x, 10)
|
||||||
|
|
||||||
if playdate.isCrankDocked() then
|
if playdate.isCrankDocked() then
|
||||||
playdate.ui.crankIndicator:draw()
|
playdate.ui.crankIndicator:draw()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param s Settings
|
playdate.display.setRefreshRate(50)
|
||||||
local function mainGameInit(s)
|
gfx.setBackgroundColor(gfx.kColorWhite)
|
||||||
settings = s
|
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
||||||
fielding:resetFielderPositions(teams.home.benchPosition)
|
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
||||||
playdate.timer.new(2000, function()
|
|
||||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, false)
|
|
||||||
end)
|
|
||||||
BootTune:play()
|
|
||||||
BootTune:setFinishCallback(function()
|
|
||||||
TinnyBackground:play()
|
|
||||||
end)
|
|
||||||
battingTeamSprites = settings.awayTeamSprites
|
|
||||||
fieldingTeamSprites = settings.homeTeamSprites
|
|
||||||
HomeTeamBlipper = blipper.new(100, settings.homeTeamSprites.smiling, settings.homeTeamSprites.lowHat)
|
|
||||||
AwayTeamBlipper = blipper.new(100, settings.awayTeamSprites.smiling, settings.awayTeamSprites.lowHat)
|
|
||||||
runnerBlipper = battingTeam == teams.away and AwayTeamBlipper or HomeTeamBlipper
|
|
||||||
end
|
|
||||||
|
|
||||||
local function init()
|
MainMenu.start(Game)
|
||||||
playdate.display.setRefreshRate(50)
|
|
||||||
gfx.setBackgroundColor(gfx.kColorWhite)
|
|
||||||
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
|
||||||
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
|
||||||
|
|
||||||
MainMenu.start({
|
|
||||||
mainGameUpdateFunction = mainGameUpdate,
|
|
||||||
mainGameInitFunction = mainGameInit,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
init()
|
|
||||||
|
|
15
src/npc.lua
15
src/npc.lua
|
@ -119,8 +119,8 @@ end
|
||||||
---@param fielders Fielder[]
|
---@param fielders Fielder[]
|
||||||
---@param fielder Fielder
|
---@param fielder Fielder
|
||||||
---@param runners Runner[]
|
---@param runners Runner[]
|
||||||
---@param launchBall LaunchBall
|
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
|
||||||
local function tryToMakeAPlay(fielders, fielder, runners, ball, launchBall)
|
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 and targetY ~= nil then
|
||||||
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY, function(grabCandidate)
|
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY, function(grabCandidate)
|
||||||
|
@ -130,7 +130,7 @@ local function tryToMakeAPlay(fielders, fielder, runners, ball, launchBall)
|
||||||
if nearestFielder == fielder then
|
if nearestFielder == fielder then
|
||||||
ball.heldBy = fielder
|
ball.heldBy = fielder
|
||||||
else
|
else
|
||||||
launchBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
||||||
Fielding.markIneligible(nearestFielder)
|
Fielding.markIneligible(nearestFielder)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -138,16 +138,15 @@ end
|
||||||
|
|
||||||
---@param fielder Fielder
|
---@param fielder Fielder
|
||||||
---@param outedSomeRunner boolean
|
---@param outedSomeRunner boolean
|
||||||
---@param ball { x: number, y: number, heldBy: Fielder | nil }
|
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
|
||||||
---@param launchBall LaunchBall
|
function Npc:fielderAction(fielder, outedSomeRunner, ball)
|
||||||
function Npc:fielderAction(fielder, outedSomeRunner, ball, launchBall)
|
|
||||||
if outedSomeRunner then
|
if outedSomeRunner then
|
||||||
-- Delay a little before the next play
|
-- Delay a little before the next play
|
||||||
playdate.timer.new(750, function()
|
playdate.timer.new(750, function()
|
||||||
tryToMakeAPlay(self.fielders, fielder, self.runners, ball, launchBall)
|
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
tryToMakeAPlay(self.fielders, fielder, self.runners, ball, launchBall)
|
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue