From 0b126919acf77bc51d8b013659e514e97bddff83 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Fri, 31 Jan 2025 23:34:35 -0500 Subject: [PATCH] Better batter-outs, scoreboard, sounds and frowns. A bunch more refactors, SCREAMING_SNAKE constants, graphics.lua, etc. --- src/graphics.lua | 22 ++ src/images/game/player-frown.png | Bin 0 -> 712 bytes src/main.lua | 458 ++++++++++++++++++------------- src/sounds/bat-crack-reverb.wav | Bin 0 -> 2108 bytes src/utils.lua | 24 ++ 5 files changed, 317 insertions(+), 187 deletions(-) create mode 100644 src/graphics.lua create mode 100644 src/images/game/player-frown.png create mode 100644 src/sounds/bat-crack-reverb.wav diff --git a/src/graphics.lua b/src/graphics.lua new file mode 100644 index 0000000..0970d64 --- /dev/null +++ b/src/graphics.lua @@ -0,0 +1,22 @@ +local ballBuffer = 5 + +--- Assumes that background image is of size +--- XXX +--- XOX +--- Where each character is the size of the screen, and 'O' is the default view. +function getDrawOffset(screenW, screenH, ballX, ballY) + local offsetX, offsetY + if ballY < ballBuffer then + offsetY = math.max(ballBuffer, -1 * (ballY - ballBuffer)) + else + offsetY = 0 + end + if ballX > 0 and ballX < (screenW - ballBuffer) then + offsetX = 0 + elseif ballX < ballBuffer then + offsetX = math.max(-1 * screenW, -1 * (ballX - ballBuffer)) + elseif ballX > (screenW - ballBuffer) then + offsetX = math.min(screenW * 2, -1 * (ballX - ballBuffer)) + end + return offsetX, offsetY +end \ No newline at end of file diff --git a/src/images/game/player-frown.png b/src/images/game/player-frown.png new file mode 100644 index 0000000000000000000000000000000000000000..3d3ec97b47bb1a9eb43fcf1c886c37188cbb0480 GIT binary patch literal 712 zcmV;(0yq7MP)EX>4Tx04R}tkv&MmP!xqvTcsiu2P=pa%ut=|q9VH0DionYs1;guFnQ@8G-*gu zTpR`0f`dPcRR10C4=2nH^D|{HiAZ8Jfn5oZ+VhW!1bx++?cQKyj-S=npDS49tK7lySbi*RvAfDc| zbk6(4VOEk9;&bA0gDyz?$aUG}H_klWYY7*VPc`!!Ey()lA#h$5l0nOqkMnX zWrgz=XSGset$XqphVt6VGS_KEki;UEAVPqQ8p^1^LX38e6ccGWPk8u;9luB}nOtQs zax9<<6_Voz|AXJNH4BpyZc-=#bidg4$0!io1)6o+{yw(t<_QpZ2ClTWzuEw1K1r{) zwdfJhzYSbmw>5bWxZDATo^;8O94SD{Unl_YXY@@uVBi+$U32HwI>+e)kfB+nZh(VB zV5~^l>mKj!>73iYJ+1lu0Qnqp+-@3z5C8xG24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2k8MH6+0VbzscMH000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0001=Nkllx3E z$J(M+x_yipkN%Xg#I!abE{y%97@b6zxr2SB(6rui0?j$k6&Lsdmv;79Lfj5+O{BE9 u*-J?J0Uhalyz`o+M +local FIRST, SECOND, THIRD, HOME = 1, 2, 3, 4 + +---@type Base[] local bases = { - first = { x = screenW * 0.93, y = screenH * 0.52 }, - second = { x = screenW * 0.47, y = screenH * 0.19 }, - third = { x = screenW * 0.03, y = screenH * 0.52 }, - home = { x = screenW * 0.474, y = screenH * 0.79 } + xy(SCREEN.W * 0.93, SCREEN.H * 0.52), + xy(SCREEN.W * 0.47, SCREEN.H * 0.19), + xy(SCREEN.W * 0.03, SCREEN.H * 0.52), + xy(SCREEN.W * 0.474, SCREEN.H * 0.79) } ---@type table local nextBaseMap = { - [bases.first] = bases.second, - [bases.second] = bases.third, - [bases.third] = bases.home + [bases[FIRST]] = bases[SECOND], + [bases[SECOND]] = bases[THIRD], + [bases[THIRD]] = bases[HOME] } +function newFielder(speed) + return { + speed = speed + } +end + ---@type table local fielders = { - first = { - speed = 40, - }, - second = { - speed = 40, - }, - shortstop = { - speed = 40, - }, - third = { - speed = 40, - }, - pitcher = { - speed = 30, - }, - catcher = { - speed = 20, - }, - left = { - speed = 40, - }, - center = { - speed = 40, - }, - right = { - speed = 40, - } + first = newFielder(40), + second = newFielder(40), + shortstop = newFielder(40), + third = newFielder(40), + pitcher = newFielder(30), + catcher = newFielder(20), + left = newFielder(40), + center = newFielder(40), + right = newFielder(40) +} + +local PITCHER_POS = { + x = SCREEN.W * 0.48, + y = SCREEN.H * 0.40 } --- Resets the target positions of all fielders to their defaults (at their field positions). @@ -120,92 +126,103 @@ local fielders = { function resetFielderPositions(fromOffTheField) if fromOffTheField then for _,fielder in pairs(fielders) do - fielder.x = centerX - fielder.y = screenH + fielder.x = CENTER.x + fielder.y = SCREEN.H end end - fielders.first.target = { x = screenW - 65, y = screenH * 0.48 } - fielders.second.target = { x = screenW * 0.70, y = screenH * 0.30 } - fielders.shortstop.target = { x = screenW * 0.30, y = screenH * 0.30 } - fielders.third.target = { x = screenW * 0.1, y = screenH * 0.48 } - fielders.pitcher.target = { x = screenW * 0.48, y = screenH * 0.40 } - fielders.catcher.target = { x = screenW * 0.475, y = screenH * 0.92 } - fielders.left.target = { x = screenW * -1, y = screenH * -0.2 } - fielders.center.target = { x = centerX, y = screenH * -0.4 } - fielders.right.target = { x = screenW * 2, y = screenH * fielders.left.target.y } + fielders.first.target = xy(SCREEN.W - 65, SCREEN.H * 0.48) + fielders.second.target = xy(SCREEN.W * 0.70, SCREEN.H * 0.30) + fielders.shortstop.target = xy(SCREEN.W * 0.30, SCREEN.H * 0.30) + fielders.third.target = xy(SCREEN.W * 0.1, SCREEN.H * 0.48) + fielders.pitcher.target = xy(PITCHER_POS.x, PITCHER_POS.y) + fielders.catcher.target = xy(SCREEN.W * 0.475, SCREEN.H * 0.92) + fielders.left.target = xy(SCREEN.W * -1, SCREEN.H * -0.2) + fielders.center.target = xy(CENTER.x, SCREEN.H * -0.4) + fielders.right.target = xy(SCREEN.W * 2, SCREEN.H * fielders.left.target.y) end -resetFielderPositions(true) -local playerStartingX = bases.home.x - 40 -local playerStartingY = bases.home.y - 3 +local PLAYER_STARTING_X = bases[HOME].x - 40 +local PLAYER_STARTING_Y = bases[HOME].y - 3 --- @type Runner[] local runners = { } +--- @type Runner[] +local outRunners = { } + +local nameI = 1 +local runnerNames = {"Barbara", "Steve", "Jerry", "Beatrice"} + ---@return Runner function newRunner() local new = { - x = playerStartingX, - y = playerStartingY, + x = PLAYER_STARTING_X, + y = PLAYER_STARTING_Y, nextBase = nil, prevBase = nil, + name = runnerNames[nameI] } + nameI = nameI + 1 runners[#runners + 1] = new return new end ---@type Runner | nil local batter = newRunner() +batter.nextBase = bases[FIRST] +batter.forcedTo = bases[FIRST] -function throwBall(destX, destY, easingFunc, flyTimeMs) +function throwBall(destX, destY, easingFunc, flyTimeMs, floaty) if not flyTimeMs then - flyTimeMs = distanceBetween(ballX, ballY, destX, destY) * 5 + flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5 end ballSizeAnimator:reset(flyTimeMs) - hitAnimatorY = gfx.animator.new(flyTimeMs, ballY, destY, easingFunc) - hitAnimatorX = gfx.animator.new(flyTimeMs, ballX, destX, easingFunc) + 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 end function pitch() - pitchAnimator:reset() - ballY = ballStartY - ballX = 200 currentMode = MODES.batting - - backgroundPan.y = 0 - backgroundPan.x = 0 + ballAnimatorX = gfx.animator.new(0, PITCH_START_X, PITCH_START_X, playdate.easingFunctions.linear) + ballAnimatorY = pitchAnimator + pitchAnimator:reset() end function playdate.AButtonDown() + if not batter then + return + end pitch() end function playdate.upButtonDown() - batBaseY = batBaseY - 1 + batBase.y = batBase.y - 1 end function playdate.downButtonDown() - batBaseY = batBaseY + 1 + batBase.y = batBase.y + 1 end function playdate.rightButtonDown() - batBaseX = batBaseX + 1 + batBase.x = batBase.x + 1 end function playdate.leftButtonDown() - batBaseX = batBaseX - 1 + batBase.x = batBase.x - 1 end -local pitchClockSec = 99 -local elapsedTime = 0 -local crankChange +local elapsedSec = 0 +local crankChange = 0 -local baseHitbox = 13 +local BASE_HITBOX = 13 --- Returns the base being touched by the runner at (x,y), or nil, if no base is being touched function isTouchingBase(x, y) - for _,base in pairs(bases) do - if distanceBetween(x, y, base.x, base.y) < baseHitbox then + for _,base in ipairs(bases) do + if distanceBetween(x, y, base.x, base.y) < BASE_HITBOX then return base end end @@ -213,18 +230,26 @@ function isTouchingBase(x, y) return nil end -local ballCatchHitbox = 3 +local BALL_CATCH_HITBOX = 3 function isTouchingBall(x, y) - local ballDistance = distanceBetween(x, y, ballX, ballY) - return ballDistance < ballCatchHitbox + local ballDistance = distanceBetween(x, y, ball.x, ball.y) + return ballDistance < BALL_CATCH_HITBOX end function updateForcedTos() end +local outs = 0 +local homeScore = 0 +local awayScore = 0 + function outRunner(runnerIndex) + print("You're out, runner" .. runnerIndex .. "!") + outs = math.min(3, outs + 1) + outRunners[#outRunners+1] = runners[runnerIndex] table.remove(runners, runnerIndex) updateForcedTos() + fielderDanceAnimator:reset() end function updateFielders() @@ -241,8 +266,8 @@ function updateFielders() local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y) if distance > 1 then - fielder.x = fielder.x - (x * fielder.speed * deltaTime) - fielder.y = fielder.y - (y * fielder.speed * deltaTime) + fielder.x = fielder.x - (x * fielder.speed * deltaSeconds) + fielder.y = fielder.y - (y * fielder.speed * deltaSeconds) else if fielder.onArrive then fielder.onArrive() @@ -251,15 +276,18 @@ function updateFielders() end end - if isTouchingBall(fielder.x, fielder.y) then + if currentMode == MODES.running and isTouchingBall(fielder.x, fielder.y) then local touchedBase = isTouchingBase(fielder.x, fielder.y) for i,runner in pairs(runners) do local runnerOnBase = touchingBaseCache.get(runner) if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then + print("Force out of runner:") + printTable(runner) outRunner(i) elseif not runnerOnBase then local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y) - if fielderDistance < tagDistance then + if fielderDistance < TAG_DISTANCE then + print("Tagged out!") outRunner(i) end end @@ -291,7 +319,7 @@ function updateRunners() end local prevX, prevY = runner.x, runner.y -- TODO: Drift toward nearest base? - local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaTime or 0 + local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaSeconds or 0 mult = autoRun + (crankChange / 20) runner.x = runner.x - (x * mult) runner.y = runner.y - (y * mult) @@ -328,7 +356,7 @@ end ---@return Base[] function getForcedOutTargets() local targets = {} - for _,base in pairs(bases) do + for _,base in ipairs(bases) do local runnerTargetingBase = getRunnerTargeting(base) if runnerTargetingBase then targets[#targets+1] = base @@ -337,10 +365,11 @@ function getForcedOutTargets() end end return targets - -- return { bases.first } + -- return { bases[FIRST] } end --- Returns the position,distance of the basest closest to the runner furthest from a base +---@return { x: number, y: number } | nil, number | nil function getBaseOfStrandedRunner() local farRunnersBase, farDistance for _,runner in pairs(runners) do @@ -364,45 +393,47 @@ end function getNextThrowTarget() -- TODO: Handle missed throws, check for fielders at target, etc. local targets = getForcedOutTargets() - print("forcedOutTargets:") - printTable(targets) if #targets ~= 0 then return targets[1].x, targets[1].y end - -- local baseCloseToStrandedRunner = getBaseOfStrandedRunner() - -- return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y + local baseCloseToStrandedRunner = getBaseOfStrandedRunner() + if baseCloseToStrandedRunner then + return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y + end end -local resetFieldersAfterSeconds = 5 +local resetFieldersAfterSeconds = 4 local secondsSinceLastRunnerMove = 0 -function updateGameState() - deltaTime = playdate.getElapsedTime() or 0 - playdate.resetElapsedTime() - elapsedTime = elapsedTime + deltaTime +local pitchAfterSeconds = 4 +local secondsSincePitchAllowed = -5 - -- if elapsedTime > pitchClockSec then - -- elapsedTime = 0 - -- pitch() - -- end +function init() + playdate.display.setRefreshRate(50) + gfx.setBackgroundColor(gfx.kColorWhite) + playdate.setMenuImage(gfx.image.new("images/game/menu-image.png")) + resetFielderPositions(true) +end - if currentMode == MODES.running then - ballX = hitAnimatorX:currentValue() - ballY = hitAnimatorY:currentValue() - ballSize = ballSizeAnimator:currentValue() - elseif ballY < 999 then - ballY = pitchAnimator:currentValue() - ballSize = 6 - end +function updateBatting() + secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds + if secondsSincePitchAllowed > pitchAfterSeconds then + pitch() + secondsSincePitchAllowed = 0 + end + if ball.y < BALL_OFFSCREEN then + ball.y = ballAnimatorY:currentValue() + ball.size = 6 + end - local batAngle = math.rad(playdate.getCrankPosition() + 90) - batTipX = batBaseX + (batLength * math.sin(batAngle)) - batTipY = batBaseY + (batLength * math.cos(batAngle)) + local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG) + batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle)) + batTip.y = batBase.y + (BAT_LENGTH * math.cos(batAngle)) - crankChange, acceleratedChange = playdate.getCrankChange() - if currentMode == MODES.batting and acceleratedChange >= 0 and - pointDirectlyUnderLine(ballX, ballY, batBaseX, batBaseY, batTipX, batTipY, screenH) then + if acceleratedChange >= 0 and + pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, SCREEN.H) then + batCrackSound:play() currentMode = MODES.running ballAngle = batAngle + math.rad(90) @@ -413,13 +444,13 @@ function updateGameState() ballVelX = ballVelX * -1 ballVelY = ballVelY * -1 end - ballDestX = ballX + (ballVelX * hitMult) - ballDestY = ballY + (ballVelY * hitMult) + ballDestX = ball.x + (ballVelX * HIT_MULT) + ballDestY = ball.y + (ballVelY * HIT_MULT) throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000) - fielders.first.target = bases.first - batter.nextBase = bases.first - batter.forcedTo = bases.first + fielders.first.target = bases[FIRST] + batter.nextBase = bases[FIRST] + batter.forcedTo = bases[FIRST] batter = nil -- Demote batter to a mere runner local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY) @@ -427,80 +458,133 @@ function updateGameState() chasingFielder.onArrive = function() local targetX, targetY = getNextThrowTarget() if targetX ~= nil then - throwBall(targetX, targetY, playdate.easingFunctions.linear) + throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true) chasingFielder.onArrive = nil end end end +end - if currentMode == MODES.running then - if updateRunners() then - secondsSinceLastRunnerMove = 0 - else - secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaTime - if secondsSinceLastRunnerMove > resetFieldersAfterSeconds then - resetFielderPositions(false) - currentMode = MODES.batting - batter = newRunner() - end - end +function updateRunning() + secondsSincePitchAllowed = 0 + ball.size = ballSizeAnimator:currentValue() + if updateRunners() then + secondsSinceLastRunnerMove = 0 + else + secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds + if secondsSinceLastRunnerMove > resetFieldersAfterSeconds then + throwBall(PITCH_START_X, PITCH_START_Y, playdate.easingFunctions.linear, nil, true) + resetFielderPositions(false) + currentMode = MODES.batting + batter = newRunner() + batter.nextBase = bases[FIRST] + batter.forcedTo = bases[FIRST] + end + end +end + +function updateOutRunners() + for i, runner in ipairs(outRunners) do + if runner.x < SCREEN.W + 50 and runner.y < SCREEN.H + 50 then + runner.x = runner.x + (deltaSeconds * 25) + runner.y = runner.y + (deltaSeconds * 25) + else + table.remove(outRunners, i) + end + end +end + +function updateGameState() + deltaSeconds = playdate.getElapsedTime() or 0 + playdate.resetElapsedTime() + elapsedSec = elapsedSec + deltaSeconds + crankChange, acceleratedChange = playdate.getCrankChange() --[[@as number, number]] + + ball.x = ballAnimatorX:currentValue() + ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() + + if currentMode == MODES.batting then + updateBatting() + elseif currentMode == MODES.running then + updateRunning() end + updateFielders() + updateOutRunners() +end + +local OUT_BUBBLE_SIZE = 6 + +function drawScoreboard() + gfx.setDrawOffset(0, 0) + local y = SCREEN.H * 0.95 + local x = SCREEN.W * 0.05 + + gfx.setLineWidth(1) + gfx.setColor(gfx.kColorBlack) + gfx.fillRect(x - 15, y - 44, 73, 55) + + gfx.setColor(gfx.kColorWhite) + for i=outs,2 do + gfx.drawCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE) + end + for i=0,(outs - 1) do + gfx.fillCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE) + end + + local originalDrawMode = gfx.getImageDrawMode() + gfx.setImageDrawMode(playdate.graphics.kDrawModeInverted) + gfx.drawText("Home: " .. homeScore, x - 7, y - 40, 100, 20) + gfx.drawText("Away: " .. awayScore, x - 7, y - 25, 100, 20) + gfx.setImageDrawMode(originalDrawMode) end function playdate.update() updateGameState() playdate.graphics.animation.blinker.updateAll() - -- TODO: Show baserunning minimap when panning? - local ballBuffer = 5 - if ballY < ballBuffer then - backgroundPan.y = math.max(ballBuffer, -1 * (ballY - ballBuffer)) - else - backgroundPan.y = 0 - end - if ballX < ballBuffer then - backgroundPan.x = math.max(-400, -1 * (ballX - ballBuffer)) - elseif ballX > (screenW - ballBuffer) then - backgroundPan.x = math.min(800, -1 * (ballX - ballBuffer)) - end - - if ballX > 0 and ballX < (screenW - ballBuffer) then - backgroundPan.x = 0 - end - gfx.clear() - grassBackground:draw(backgroundPan.x - 400, backgroundPan.y - 240) + + if ball.x < BALL_OFFSCREEN then + -- TODO: Show baserunning minimap when panning? + local offsetX, offsetY = getDrawOffset(SCREEN.W, SCREEN.H, ball.x, ball.y) + gfx.setDrawOffset(offsetX, offsetY) + end + + grassBackground:draw(-400, -240) gfx.setColor(gfx.kColorBlack) gfx.setLineWidth(2) - gfx.drawCircleAtPoint(ballX + backgroundPan.x, ballY + backgroundPan.y, ballSize) + gfx.drawCircleAtPoint(ball.x, ball.y, ball.size) + + local fielderDanceHeight = fielderDanceAnimator:currentValue() for _,fielder in pairs(fielders) do - gfx.fillRect(fielder.x + backgroundPan.x, fielder.y + backgroundPan.y, 14, 25) + gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25) end - gfx.setLineWidth(5) - if currentMode == MODES.batting then + gfx.setLineWidth(5) gfx.drawLine( - batBaseX + backgroundPan.x, batBaseY + backgroundPan.y, - batTipX + backgroundPan.x, batTipY + backgroundPan.y + batBase.x, batBase.y, + batTip.x, batTip.y ) end - if playdate.isCrankDocked() or (crankChange < 2 and currentMode == MODES.running) then + if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == MODES.running) then playdate.ui.crankIndicator:draw() end -- TODO? Change blip speed depending on runner speed? for _,runner in pairs(runners) do -- TODO? Scale sprites down as y increases - playerImageBlipper:draw( - false, - runner.x + backgroundPan.x, - runner.y + backgroundPan.y - ) + playerImageBlipper:draw(false, runner.x, runner.y) + end + for _,runner in pairs(outRunners) do + playerFrown:draw(runner.x, runner.y) end + drawScoreboard() end + +init() \ No newline at end of file diff --git a/src/sounds/bat-crack-reverb.wav b/src/sounds/bat-crack-reverb.wav new file mode 100644 index 0000000000000000000000000000000000000000..3405efe33be088e70f718938175ac5eb3e485d9e GIT binary patch literal 2108 zcmeH`%}?8Q7{`B3!mypt+Iee8K#D_36KEyp1!lGjfvnw%CXLW_FQ-waP8=pN;IL{J zJAiOr7&b5|Z!He=;E)t0T@SI7wE-?9u#Vm`K+}$}%Qo$@mctfR`xkcO^IV?qBR!Xo z^bNds{kj(h;3MDdckX^UZPfz+Z~y^+4uBiC8v#}U1$cS!?&#RGsu5pa1mN@0=~2-5 zX{_KM4Ni~$+I=}ro*-J$^swEwej$z6@#ipYqK)B88ybDY@vz-IB@pO_tBAjyE2r}CiqFor#mw<2r@ErL@5vBam<6e+2C;w$jpQh#;aL4-C|&= zIZad9v2#tIAc83#ImBT5=nevJeaoX$g>LVzX)B5;##h2wrDi6k_By{PTSdSlm+!mT z4LAJv`wF>F5h_ouFAho?y~q-L*jk8)B>!>q!ZKNpzxxCQgmVAW?Jlhyh`qen;)g-zFrTAQcJ1Y+EG z_QXWG&1qdG$)ERu|Sx`5Ag0b?gg2gUGaVBCYS&SR~ zI_9ZQ$eK!%w)I^2ip1ie^AK^ZZlgt-%s`JP=D5_da`^3ZxV67^Knte6w&(FFG>=r;eg) zrM>gm3MDZmo3zK!nY|QtLv*V4oLo-p%V0fRnaSLIQ)0EnRh3M23~y_+&rNiI#UVMz zLQf}j7$1(8j{8CUL#AxwBDS-)dd1eDOeeW$r#1A(sx}mYeN58-n+dDpi91T!5e)sv zw0W7wi-7}FCtK4K1x-hdjA3#AY1dqqNcTOtmyQxKRL35E!u7wf7^GH@xM&w!I!c&H zAZkS0e3($8uTXp9#nmfSr>cZ=Txp6y0taebqIjnsqF0wJ`tsx&3RM$#SXZ%cmwXkO z+U-|tAP}7ja4bi0)r5_CHnp7WIdUwo+7cxE8W#LFbOZ4Egft3z&bfhe1Lp?*2Lt~C Du?~3+ literal 0 HcmV?d00001 diff --git a/src/utils.lua b/src/utils.lua index 67af74a..07cd0f4 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1,6 +1,30 @@ import 'CoreLibs/animation.lua' import 'CoreLibs/graphics.lua' +-- number = ((number * 2) - 1) +-- return number * number + +-- c = c + 0.0 -- convert to float to prevent integer overflow +-- return c * t / d + b + +function easingHill(t, b, c, d) + c = c + 0.0 -- convert to float to prevent integer overflow + t = t / d + t = ((t * 2) - 1) + t = t * t + return (c * t) + b +end + +---@param x number +---@param y number +---@return XYPair +function xy(x, y) + return { + x = x, + y = y + } +end + --- Returns the normalized vector as two values, plus the distance between the given points. ---@return number, number, number function normalizeVector(x1, y1, x2, y2)