diff --git a/Makefile b/Makefile index 7ab9abb..21a7fc3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SOURCE_FILES := src/constants.lua src/draw/* src/utils.lua src/dbg.lua src/npc.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua +SOURCE_FILES := src/utils.lua src/constants.lua src/draw/* src/dbg.lua src/npc.lua src/announcer.lua src/graphics.lua src/main.lua all: pdc src BatterUp.pdx diff --git a/src/constants.lua b/src/constants.lua index 663edeb..e112d31 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -11,3 +11,88 @@ C.Center = utils.xy(C.Screen.W / 2, C.Screen.H / 2) C.StrikeZoneStartX = C.Center.x - 16 C.StrikeZoneEndX = C.StrikeZoneStartX + 24 C.StrikeZoneStartY = C.Screen.H - 35 + +--- @alias Base { +--- x: number, +--- y: number, +--- } + +---@type Base[] +C.Bases = { + utils.xy(C.Screen.W * 0.93, C.Screen.H * 0.52), + utils.xy(C.Screen.W * 0.47, C.Screen.H * 0.19), + utils.xy(C.Screen.W * 0.03, C.Screen.H * 0.52), + utils.xy(C.Screen.W * 0.474, C.Screen.H * 0.79), +} + +C.First, C.Second, C.Third, C.Home = 1, 2, 3, 4 +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, + y = C.Bases[C.Home].y, +} + +---@type table +C.NextBaseMap = { + [C.RightHandedBattersBox] = nil, -- Runner should not escape the box before a hit! + [C.Bases[C.First]] = C.Bases[C.Second], + [C.Bases[C.Second]] = C.Bases[C.Third], + [C.Bases[C.Third]] = C.Bases[C.Home], +} + +--- Angle to align the bat to +C.CrankOffsetDeg = 90 + +C.DanceBounceMs = 500 +C.DanceBounceCount = 4 + +--- 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.PitchFlyMs = 1050 +C.PitchStartX = 195 +C.PitchStartY, C.PitchEndY = 105, 240 + +--- The max distance at which a fielder can tag out a runner. +C.TagDistance = 15 + +--- The max distance at which a runner can be considered on base. +C.BaseHitbox = 10 + +C.BattingPower = 20 + +C.SmallestBallRadius = 6 + +C.BatLength = 50 + +--- An enum for what state the offense is in +C.Offense = { + batting = {}, + running = {}, + walking = {}, +} + +--- An enum for which side (offense or defense) a team is on. +C.Sides = { + offense = {}, + defense = {}, +} + +C.PitcherStartPos = { + x = C.Screen.W * 0.48, + y = C.Screen.H * 0.40, +} + +C.ThrowMeterMax = 15 +C.ThrowMeterDrainPerSec = 150 + +--- Controls how hard the ball can be hit, and +--- how fast the ball can be thrown. +C.CrankPower = 10 + +--- How fast baserunners move after a walk +C.WalkedRunnerSpeed = 10 diff --git a/src/dbg.lua b/src/dbg.lua index 6206c8a..1cba6fc 100644 --- a/src/dbg.lua +++ b/src/dbg.lua @@ -20,21 +20,21 @@ end -- Only works if called with the bases empty (i.e. the only runner should be the batter. -- selene: allow(unused_variable) -function dbg.loadTheBases(baseConstants, runners) +function dbg.loadTheBases(runners) newRunner() newRunner() newRunner() - runners[2].x = baseConstants[1].x - runners[2].y = baseConstants[1].y - runners[2].nextBase = baseConstants[2] + runners[2].x = C.Bases[C.First].x + runners[2].y = C.Bases[C.First].y + runners[2].nextBase = C.Bases[C.Second] - runners[3].x = baseConstants[2].x - runners[3].y = baseConstants[2].y - runners[3].nextBase = baseConstants[3] + runners[3].x = C.Bases[C.Second].x + runners[3].y = C.Bases[C.Second].y + runners[3].nextBase = C.Bases[C.Third] - runners[4].x = baseConstants[3].x - runners[4].y = baseConstants[3].y - runners[4].nextBase = baseConstants[4] + runners[4].x = C.Bases[C.Third].x + runners[4].y = C.Bases[C.Third].y + runners[4].nextBase = C.Bases[C.Home] end if not playdate then diff --git a/src/scoreboard.lua b/src/draw/overlay.lua similarity index 78% rename from src/scoreboard.lua rename to src/draw/overlay.lua index 58e7336..6e1340a 100644 --- a/src/scoreboard.lua +++ b/src/draw/overlay.lua @@ -2,27 +2,31 @@ local gfx = playdate.graphics local ScoreFont = playdate.graphics.font.new("fonts/font-full-circle.pft") -local OutBubbleRadius = 5 -local ScoreboardMarginX = 6 -local ScoreboardMarginRight = 4 -local ScoreboardHeight = 55 -local Indicator = "> " -local IndicatorWidth = ScoreFont:getTextWidth(Indicator) + +local MinimapBackground = gfx.image.new("images/game/minimap.png") --[[@as pd_image]] + +local MinimapSizeX, MinimapSizeY = MinimapBackground:getSize() +local MinimapPosX, MinimapPosY = C.Screen.W - MinimapSizeX, C.Screen.H - MinimapSizeY + +local MinimapMultX = 0.75 * MinimapSizeX / C.Screen.W +local MinimapOffsetX = MinimapPosX + 5 +local MinimapMultY = 0.70 * MinimapSizeY / C.FieldHeight +local MinimapOffsetY = MinimapPosY - 15 + +function drawMinimap(runners) + MinimapBackground:draw(MinimapPosX, MinimapPosY) + gfx.setColor(gfx.kColorBlack) + for _, runner in pairs(runners) do + local x = (MinimapMultX * runner.x) + MinimapOffsetX + local y = (MinimapMultY * runner.y) + MinimapOffsetY + gfx.fillRect(x, y, 8, 8) + end +end local BallStrikeMarginY = 4 local BallStrikeWidth = 60 local BallStrikeHeight = (BallStrikeMarginY * 2) + ScoreFont:getHeight() ----@param teams any ----@param battingTeam any ----@return string, number, string, number -function getIndicators(teams, battingTeam) - if teams.home == battingTeam then - return Indicator, 0, "", IndicatorWidth - end - return "", IndicatorWidth, Indicator, 0 -end - function drawBallsAndStrikes(x, y, balls, strikes) if balls == 0 and strikes == 0 then return @@ -40,6 +44,23 @@ function drawBallsAndStrikes(x, y, balls, strikes) gfx.setImageDrawMode(originalDrawMode) end +local OutBubbleRadius = 5 +local ScoreboardMarginX = 6 +local ScoreboardMarginRight = 4 +local ScoreboardHeight = 55 +local Indicator = "> " +local IndicatorWidth = ScoreFont:getTextWidth(Indicator) + +---@param teams any +---@param battingTeam any +---@return string, number, string, number +function getIndicators(teams, battingTeam) + if teams.home == battingTeam then + return Indicator, 0, "", IndicatorWidth + end + return "", IndicatorWidth, Indicator, 0 +end + function drawScoreboard(x, y, teams, outs, battingTeam, inning) local homeScore = teams.home.score local awayScore = teams.away.score diff --git a/src/main.lua b/src/main.lua index 58e00a1..9c88885 100644 --- a/src/main.lua +++ b/src/main.lua @@ -7,16 +7,6 @@ import 'CoreLibs/object.lua' import 'CoreLibs/timer.lua' import 'CoreLibs/ui.lua' ---- @alias XYPair { ---- x: number, ---- y: number, ---- } - ---- @alias Base { ---- x: number, ---- y: number, ---- } - --- @alias Runner { --- x: number, --- y: number, @@ -41,44 +31,37 @@ import 'announcer.lua' import 'dbg.lua' import 'graphics.lua' import 'npc.lua' -import 'scoreboard.lua' +import 'draw/overlay' import 'draw/fielder' -- stylua: ignore end -- selene: allow(shadowing) local gfx = playdate.graphics +-- selene: allow(shadowing) +local C = C + local BootTune = playdate.sound.sampleplayer.new("sounds/boot-tune.wav") -- local BootTune = playdate.sound.sampleplayer.new("sounds/boot-tune-organy.wav") local TinnyBackground = playdate.sound.sampleplayer.new("sounds/tinny-background.wav") local BatCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.wav") local GrassBackground = gfx.image.new("images/game/grass.png") --[[@as pd_image]] -local Minimap = gfx.image.new("images/game/minimap.png") --[[@as pd_image]] local PlayerFrown = gfx.image.new("images/game/player-frown.png") --[[@as pd_image]] local PlayerSmile = gfx.image.new("images/game/player.png") --[[@as pd_image]] local PlayerBack = gfx.image.new("images/game/player-back.png") --[[@as pd_image]] local PlayerImageBlipper = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png") -local DanceBounceMs = 500 -local DanceBounceCount = 4 - local FielderDanceAnimator = gfx.animator.new(1, 10, 0, utils.easingHill) -FielderDanceAnimator.repeatCount = DanceBounceCount - 1 +FielderDanceAnimator.repeatCount = C.DanceBounceCount - 1 -- selene: allow(unused_variable) function fieldersDance() - FielderDanceAnimator:reset(DanceBounceMs) + FielderDanceAnimator:reset(C.DanceBounceMs) end -local BallOffscreen = 999 - -local PitchFlyMs = 1050 -local PitchStartX = 195 -local PitchStartY , PitchEndY = 105, 240 - -local ballAnimatorY = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear) -local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear) +local ballAnimatorY = gfx.animator.new(0, C.BallOffscreen, C.BallOffscreen, playdate.easingFunctions.linear) +local ballAnimatorX = gfx.animator.new(0, C.BallOffscreen, C.BallOffscreen, playdate.easingFunctions.linear) ---@alias PseudoAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) } ---@alias Pitch { x: PseudoAnimator, y: PseudoAnimator, z: PseudoAnimator | nil } @@ -87,64 +70,44 @@ local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate local Pitches = { -- Fastball { - x = gfx.animator.new(0, PitchStartX, PitchStartX, playdate.easingFunctions.linear), - y = gfx.animator.new(PitchFlyMs / 1.3, PitchStartY, PitchEndY, 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), }, -- Curve ball { - x = gfx.animator.new(PitchFlyMs, PitchStartX + 20, PitchStartX, utils.easingHill), - y = gfx.animator.new(PitchFlyMs, PitchStartY, PitchEndY, playdate.easingFunctions.linear), + 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), }, -- Slider { - x = gfx.animator.new(PitchFlyMs, PitchStartX - 20, PitchStartX, utils.easingHill), - y = gfx.animator.new(PitchFlyMs, PitchStartY, PitchEndY, playdate.easingFunctions.linear), + 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), }, -- Wobbbleball { x = { currentValue = function() - return PitchStartX + (10 * math.sin((ballAnimatorY:currentValue() - PitchStartY) / 10)) + return C.PitchStartX + (10 * math.sin((ballAnimatorY:currentValue() - C.PitchStartY) / 10)) end, reset = function() end, }, - y = gfx.animator.new(PitchFlyMs * 1.3, PitchStartY, PitchEndY, playdate.easingFunctions.linear), + y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear), }, } -local CrankOffsetDeg = 90 -local BatOffset = utils.xy(10, 25) +local BatterHandPos = utils.xy(10, 25) local batBase = utils.xy(C.Center.x - 34, 215) local batTip = utils.xy(0, 0) -local TagDistance = 15 - -local SmallestBallRadius = 6 - local ball = { x = C.Center.x --[[@as number]], y = C.Center.y --[[@as number]], z = 0, - size = SmallestBallRadius, + size = C.SmallestBallRadius, heldBy = nil --[[@type Runner | nil]], } -local BatLength = 50 - -local Offense = { - batting = {}, - running = {}, - walking = {}, -} - -local Sides = { - offense = {}, - defense = {}, -} - -local offenseMode = Offense.batting - ---@alias Team { score: number, benchPosition: XYPair } ---@type table @@ -163,14 +126,15 @@ local PlayerTeam = teams.home local battingTeam = teams.away local outs = 0 local inning = 1 +local offenseMode = C.Offense.batting ---@return boolean playerIsOnSide, boolean playerIsOnOtherSide function playerIsOn(side) local ret if PlayerTeam == battingTeam then - ret = side == Sides.offense + ret = side == C.Sides.offense else - ret = side == Sides.defense + ret = side == C.Sides.defense end return ret, not ret end @@ -179,33 +143,10 @@ end -- ...that might lose some of the magic of both. Compromise available? idk local ballFloatAnimator = gfx.animator.new(2000, -60, 0, utils.easingHill) local BallSizeMs = 2000 -local ballSizeAnimator = gfx.animator.new(BallSizeMs, 9, SmallestBallRadius, utils.easingHill) - -local HitMult = 20 +local ballSizeAnimator = gfx.animator.new(BallSizeMs, 9, C.SmallestBallRadius, utils.easingHill) local deltaSeconds = 0 -local First , Second , Third , Home = 1, 2, 3, 4 - ----@type Base[] -local Bases = { - utils.xy(C.Screen.W * 0.93, C.Screen.H * 0.52), - utils.xy(C.Screen.W * 0.47, C.Screen.H * 0.19), - utils.xy(C.Screen.W * 0.03, C.Screen.H * 0.52), - utils.xy(C.Screen.W * 0.474, C.Screen.H * 0.79), -} - --- Pseudo-base for batter to target -local RightHandedBattersBox = utils.xy(Bases[Home].x - 35, Bases[Home].y) - ----@type table -local NextBaseMap = { - [RightHandedBattersBox] = nil, -- Runner should not escape the box before a hit! - [Bases[First]] = Bases[Second], - [Bases[Second]] = Bases[Third], - [Bases[Third]] = Bases[Home], -} - ---@param name string ---@param speed number ---@return Fielder @@ -229,11 +170,6 @@ local fielders = { right = newFielder("Right", 40), } -local PitcherStartPos = { - x = C.Screen.W * 0.48, - y = C.Screen.H * 0.40, -} - --- Actually only benches the infield, because outfielders are far away! ---@param position XYPair function benchAllFielders(position) @@ -259,16 +195,13 @@ function resetFielderPositions(fromOffTheField) fielders.second.target = utils.xy(C.Screen.W * 0.70, C.Screen.H * 0.30) fielders.shortstop.target = utils.xy(C.Screen.W * 0.30, C.Screen.H * 0.30) fielders.third.target = utils.xy(C.Screen.W * 0.1, C.Screen.H * 0.48) - fielders.pitcher.target = utils.xy(PitcherStartPos.x, PitcherStartPos.y) + fielders.pitcher.target = utils.xy(C.PitcherStartPos.x, C.PitcherStartPos.y) fielders.catcher.target = utils.xy(C.Screen.W * 0.475, C.Screen.H * 0.92) fielders.left.target = utils.xy(C.Screen.W * -1, C.Screen.H * -0.2) fielders.center.target = utils.xy(C.Center.x, C.Screen.H * -0.4) fielders.right.target = utils.xy(C.Screen.W * 2, fielders.left.target.y) end -local BatterStartingX = Bases[Home].x - 40 -local BatterStartingY = Bases[Home].y - 3 - --- @type Runner[] local runners = {} @@ -278,11 +211,11 @@ local outRunners = {} ---@return Runner function newRunner() local new = { - x = BatterStartingX - 60, - y = BatterStartingY + 60, - nextBase = RightHandedBattersBox, + x = C.RightHandedBattersBox.x - 60, + y = C.RightHandedBattersBox.y + 60, + nextBase = C.RightHandedBattersBox, prevBase = nil, - forcedTo = Bases[First], + forcedTo = C.Bases[C.First], } runners[#runners + 1] = new return new @@ -292,7 +225,6 @@ end local batter = newRunner() local throwMeter = 0 -local PitchMeterLimit = 15 --- "Throws" the ball from its current position to the given destination. ---@param destX number @@ -302,35 +234,35 @@ local PitchMeterLimit = 15 ---@param floaty boolean | nil ---@param customBallScaler pd_animator | nil function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler) + ball.heldBy = nil + throwMeter = 0 + if not flyTimeMs then flyTimeMs = utils.distanceBetween(ball.x, ball.y, destX, destY) * 5 end - ball.heldBy = nil + if customBallScaler then ballSizeAnimator = customBallScaler else -- TODO? Scale based on distance? - ballSizeAnimator = gfx.animator.new(flyTimeMs, 9, SmallestBallRadius, utils.easingHill) + ballSizeAnimator = gfx.animator.new(flyTimeMs, 9, C.SmallestBallRadius, utils.easingHill) end ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc) ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc) if floaty then ballFloatAnimator:reset(flyTimeMs) end - throwMeter = 0 end -local PitchAfterSeconds = 7 -- TODO: Replace with a timer, repeatedly reset instead of setting to 0 local secondsSincePitchAllowed = -5 - local catcherThrownBall = false ---@param pitchFlyTimeMs number | nil ---@param pitchTypeIndex number | nil function pitch(pitchFlyTimeMs, pitchTypeIndex) catcherThrownBall = false - offenseMode = Offense.batting + offenseMode = C.Offense.batting local current = Pitches[pitchTypeIndex] ballAnimatorX = current.x @@ -353,15 +285,13 @@ function pitch(pitchFlyTimeMs, pitchTypeIndex) secondsSincePitchAllowed = 0 end -local BaseHitbox = 10 - --- Returns the base being touched by the player at (x,y), or nil, if no base is being touched ---@param x number ---@param y number ---@return Base | nil function isTouchingBase(x, y) - return utils.first(Bases, function(base) - return utils.distanceBetween(x, y, base.x, base.y) < BaseHitbox + return utils.first(C.Bases, function(base) + return utils.distanceBetween(x, y, base.x, base.y) < C.BaseHitbox end) end @@ -386,7 +316,7 @@ end function updateForcedRunners() local stillForced = true - for _, base in ipairs(Bases) do + for _, base in ipairs(C.Bases) do local runnerTargetingBase = getRunnerWithNextBase(base) if runnerTargetingBase then if stillForced then @@ -462,7 +392,7 @@ end ---@return Base[] function getForcedOutTargets() local targets = {} - for _, base in ipairs(Bases) do + for _, base in ipairs(C.Bases) do local runnerTargetingBase = getRunnerWithNextBase(base) if runnerTargetingBase then targets[#targets + 1] = base @@ -479,7 +409,7 @@ function getBaseOfStrandedRunner() local farRunnersBase, farDistance for _, runner in pairs(runners) do if runner ~= batter then - local nearestBase, distance = utils.getNearestOf(Bases, runner.x, runner.y) + local nearestBase, distance = utils.getNearestOf(C.Bases, runner.x, runner.y) if farRunnersBase == nil or farDistance < distance then farRunnersBase = nearestBase farDistance = distance @@ -520,8 +450,8 @@ function tryToMakeAnOut(fielder) end function readThrow() - if throwMeter > PitchMeterLimit then - return (PitchFlyMs / (throwMeter / PitchMeterLimit)) + if throwMeter > C.ThrowMeterMax then + return (C.PitchFlyMs / (throwMeter / C.ThrowMeterMax)) end return nil end @@ -532,13 +462,13 @@ end function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome) local targetBase if playdate.buttonIsPressed(playdate.kButtonLeft) then - targetBase = Bases[Third] + targetBase = C.Bases[C.Third] elseif playdate.buttonIsPressed(playdate.kButtonUp) then - targetBase = Bases[Second] + targetBase = C.Bases[C.Second] elseif playdate.buttonIsPressed(playdate.kButtonRight) then - targetBase = Bases[First] + targetBase = C.Bases[C.First] elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then - targetBase = Bases[Home] + targetBase = C.Bases[C.Home] else return false end @@ -550,7 +480,7 @@ function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome) throwBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs) closestFielder.target = targetBase secondsSinceLastRunnerMove = 0 - offenseMode = Offense.running + offenseMode = C.Offense.running return true end @@ -566,7 +496,7 @@ function outEligibleRunners(fielder) and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase -- Tag out - or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance + or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < C.TagDistance then outRunner(i) didOutRunner = true @@ -577,7 +507,7 @@ function outEligibleRunners(fielder) end function updateNpcFielder(fielder, outedSomeRunner) - if offenseMode ~= Offense.running then + if offenseMode ~= C.Offense.running then return end if outedSomeRunner then @@ -603,7 +533,7 @@ function updateFielder(fielder) local outedSomeRunner = outEligibleRunners(fielder) - if playerIsOn(Sides.defense) then + if playerIsOn(C.Sides.defense) then local throwFly = readThrow() if throwFly then buttonControlledThrow(fielders.pitcher, throwFly) @@ -626,14 +556,14 @@ function updateRunner(runner, runnerIndex, appliedSpeed) return false end - local nearestBase, nearestBaseDistance = utils.getNearestOf(Bases, runner.x, runner.y) + local nearestBase, nearestBaseDistance = utils.getNearestOf(C.Bases, runner.x, runner.y) if nearestBaseDistance < 5 and runnerIndex ~= nil and runner ~= batter --runner.prevBase - and runner.nextBase == Bases[Home] - and nearestBase == Bases[Home] + and runner.nextBase == C.Bases[C.Home] + and nearestBase == C.Bases[C.Home] then score(runnerIndex) end @@ -642,7 +572,7 @@ function updateRunner(runner, runnerIndex, appliedSpeed) local x, y, distance = utils.normalizeVector(runner.x, runner.y, nb.x, nb.y) if distance < 2 then - runner.nextBase = NextBaseMap[runner.nextBase] + runner.nextBase = C.NextBaseMap[runner.nextBase] runner.forcedTo = nil return false end @@ -683,10 +613,10 @@ end function walk() announcer:say("Walk!") - fielders.first.target = Bases[First] - batter.nextBase = Bases[First] - batter.prevBase = Bases[Home] - offenseMode = Offense.walking + fielders.first.target = C.Bases[C.First] + batter.nextBase = C.Bases[C.First] + batter.prevBase = C.Bases[C.Home] + offenseMode = C.Offense.walking batter = nil updateForcedRunners() nextBatter() @@ -701,25 +631,25 @@ end ---@param batDeg number function updateBatting(batDeg, batSpeed) - if ball.y < BallOffscreen then + if ball.y < C.BallOffscreen then ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() - ball.size = SmallestBallRadius -- ballFloatAnimator:currentValue() + ball.size = C.SmallestBallRadius -- ballFloatAnimator:currentValue() end local batAngle = math.rad(batDeg) -- TODO: animate bat-flip or something - batBase.x = batter and (batter.x + BatOffset.x) or 0 - batBase.y = batter and (batter.y + BatOffset.y) or 0 - batTip.x = batBase.x + (BatLength * math.sin(batAngle)) - batTip.y = batBase.y + (BatLength * math.cos(batAngle)) + batBase.x = batter and (batter.x + BatterHandPos.x) or 0 + batBase.y = batter and (batter.y + BatterHandPos.y) or 0 + batTip.x = batBase.x + (C.BatLength * math.sin(batAngle)) + batTip.y = batBase.y + (C.BatLength * math.cos(batAngle)) if batSpeed > 0 and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, C.Screen.H) - and ball.y < 232 --not isTouchingBall(fielders.catcher.x, fielders.catcher.y) + and ball.y < 232 then BatCrackSound:play() - offenseMode = Offense.running + offenseMode = C.Offense.running local ballAngle = batAngle + math.rad(90) local mult = math.abs(batSpeed / 15) @@ -729,23 +659,17 @@ function updateBatting(batDeg, batSpeed) ballVelX = ballVelX * -1 ballVelY = ballVelY * -1 end - local ballDestX = ball.x + (ballVelX * HitMult) - local ballDestY = ball.y + (ballVelY * HitMult) + local ballDestX = ball.x + (ballVelX * C.BattingPower) + local ballDestY = ball.y + (ballVelY * C.BattingPower) -- Hit! - throwBall( - ballDestX, - ballDestY, - playdate.easingFunctions.outQuint, - 2000, - nil, - gfx.animator.new(2000, 9 + (mult * mult * 0.5), SmallestBallRadius, utils.easingHill) - ) + local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill) + throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler) - fielders.first.target = Bases[First] - batter.nextBase = Bases[First] - batter.prevBase = Bases[Home] + fielders.first.target = C.Bases[C.First] + batter.nextBase = C.Bases[C.First] + batter.prevBase = C.Bases[C.Home] updateForcedRunners() - batter.forcedTo = Bases[First] + batter.forcedTo = C.Bases[C.First] batter = nil -- Demote batter to a mere runner local chasingFielder = utils.getNearestOf(fielders, ballDestX, ballDestY) @@ -801,7 +725,7 @@ function updateGameState() deltaSeconds = playdate.getElapsedTime() or 0 playdate.resetElapsedTime() local crankChange = playdate.getCrankChange() --[[@as number]] - local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * 10) + local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * C.CrankPower) if crankChange < 0 then crankLimited = crankLimited * -1 end @@ -815,21 +739,22 @@ function updateGameState() ball.y = ballAnimatorY:currentValue() + ball.z end - local playerOnOffense, playerOnDefense = playerIsOn(Sides.offense) + local playerOnOffense, playerOnDefense = playerIsOn(C.Sides.offense) if playerOnDefense then - throwMeter = math.max(0, throwMeter - (deltaSeconds * 150)) + throwMeter = math.max(0, throwMeter - (deltaSeconds * C.ThrowMeterDrainPerSec)) throwMeter = throwMeter + math.abs(crankLimited) end - if offenseMode == Offense.batting then + if offenseMode == C.Offense.batting then if ball.y < C.StrikeZoneStartY then pitchTracker.recordedPitchX = nil elseif not pitchTracker.recordedPitchX then pitchTracker.recordedPitchX = ball.x end - if utils.distanceBetween(fielders.pitcher.x, fielders.pitcher.y, PitchStartX, PitchStartY) < BaseHitbox then + local pitcher = fielders.pitcher + if utils.distanceBetween(pitcher.x, pitcher.y, C.PitchStartX, C.PitchStartY) < C.BaseHitbox then secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds end @@ -840,13 +765,13 @@ function updateGameState() elseif outcome == PitchOutcomes.Walk then walk() end - throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true) + throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true) catcherThrownBall = true end local batSpeed if playerOnOffense then - batAngleDeg = (playdate.getCrankPosition() + CrankOffsetDeg) % 360 + batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360 batSpeed = crankLimited else batAngleDeg = npc.updateBatAngle(ball, catcherThrownBall, deltaSeconds) @@ -858,35 +783,35 @@ function updateGameState() -- TODO: Ensure batter can't be nil, here updateRunner(batter, nil, crankLimited) - if secondsSincePitchAllowed > PitchAfterSeconds then + if secondsSincePitchAllowed > C.PitchAfterSeconds then if playerOnDefense then local throwFly = readThrow() - if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then + if throwFly and not buttonControlledThrow(pitcher, throwFly, true) then playerPitch(throwFly) end else - pitch(PitchFlyMs, math.random(#Pitches)) + pitch(C.PitchFlyMs, math.random(#Pitches)) end end - elseif offenseMode == Offense.running then - local appliedSpeed = playerOnOffense and crankLimited or npc.runningSpeed(Bases, runners) + elseif offenseMode == C.Offense.running then + local appliedSpeed = playerOnOffense and crankLimited or npc.runningSpeed(runners) if updateRunning(appliedSpeed) then secondsSinceLastRunnerMove = 0 else secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds if secondsSinceLastRunnerMove > ResetFieldersAfterSeconds then - throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true) + throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true) resetFielderPositions() - offenseMode = Offense.batting + offenseMode = C.Offense.batting if not batter then batter = newRunner() end end end - elseif offenseMode == Offense.walking then + elseif offenseMode == C.Offense.walking then updateForcedRunners() - if not updateRunning(10, true) then - offenseMode = Offense.batting + if not updateRunning(C.WalkedRunnerSpeed, true) then + offenseMode = C.Offense.batting end end @@ -896,26 +821,6 @@ function updateGameState() walkAwayOutRunners() end -local MinimapSizeX, MinimapSizeY = Minimap:getSize() -local MinimapPosX, MinimapPosY = C.Screen.W - MinimapSizeX, C.Screen.H - MinimapSizeY - -local FieldHeight = Bases[Home].y - Bases[Second].y - -local MinimapMultX = 0.75 * MinimapSizeX / C.Screen.W -local MinimapOffsetX = MinimapPosX + 5 -local MinimapMultY = 0.70 * MinimapSizeY / FieldHeight -local MinimapOffsetY = MinimapPosY - 15 - -function drawMinimap() - Minimap:draw(MinimapPosX, MinimapPosY) - gfx.setColor(gfx.kColorBlack) - for _, runner in pairs(runners) do - local x = (MinimapMultX * runner.x) + MinimapOffsetX - local y = (MinimapMultY * runner.y) + MinimapOffsetY - gfx.fillRect(x, y, 8, 8) - end -end - function playdate.update() playdate.timer.updateTimers() gfx.animation.blinker.updateAll() @@ -925,7 +830,7 @@ function playdate.update() gfx.setColor(gfx.kColorBlack) local offsetX, offsetY = 0, 0 - if ball.x < BallOffscreen then + if ball.x < C.BallOffscreen then offsetX, offsetY = getDrawOffset(C.Screen.W, C.Screen.H, ball.x, ball.y) gfx.setDrawOffset(offsetX, offsetY) end @@ -938,7 +843,7 @@ function playdate.update() ballIsHeld = drawFielder(ball, fielder.x, fielder.y + fielderDanceHeight) or ballIsHeld end - if offenseMode == Offense.batting then + if offenseMode == C.Offense.batting then gfx.setLineWidth(5) gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y) end @@ -976,7 +881,7 @@ function playdate.update() gfx.setDrawOffset(0, 0) if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then - drawMinimap() + drawMinimap(runners) end drawScoreboard(0, C.Screen.H * 0.77, teams, outs, battingTeam, inning) drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes) @@ -991,7 +896,7 @@ function init() playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO? playdate.timer.new(2000, function() - throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, false) + throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, false) end) BootTune:play() BootTune:setFinishCallback(function() diff --git a/src/npc.lua b/src/npc.lua index cb0f958..de71bee 100644 --- a/src/npc.lua +++ b/src/npc.lua @@ -19,12 +19,12 @@ function npc.batSpeed() return npcBatSpeed end -function npc.runningSpeed(baseConstants, runners) +function npc.runningSpeed(runners) if #runners == 0 then return 0 end local touchedBase = isTouchingBase(runners[1].x, runners[1].y) - if not touchedBase or touchedBase == baseConstants[4] then + if not touchedBase or touchedBase == C.Bases[C.Home] then return 10 end return 0 diff --git a/src/utils.lua b/src/utils.lua index 5a352ab..a0c2531 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1,8 +1,3 @@ --- stylua: ignore start -import 'CoreLibs/animation.lua' -import 'CoreLibs/graphics.lua' --- stylua: ignore end - -- selene: allow(unscoped_variables) utils = {} @@ -14,6 +9,11 @@ function utils.easingHill(t, b, c, d) return (c * t) + b end +--- @alias XYPair { +--- x: number, +--- y: number, +--- } + ---@param x number ---@param y number ---@return XYPair