From 56c0c27d751b7a3178624e4307e378d0df08f7b3 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Thu, 20 Feb 2025 13:56:57 -0500 Subject: [PATCH] Add perfect-power indicator to throwMeter Some other tightening-up in there. E.g. clears the lastReadThrow when on a new fielder. Add type annotations to assets files. --- src/assets.lua | 43 +++++++++++++ src/assets.lua2p | 11 ++-- src/ball.lua | 1 - src/draw/throw-meter.lua | 24 ++++++-- src/images/game/PerfectPowerBg.png | Bin 0 -> 654 bytes src/images/game/PerfectPowerFlickerLeft.png | Bin 0 -> 611 bytes src/images/game/PerfectPowerFlickerRight.png | Bin 0 -> 614 bytes src/pitching.lua | 60 +++++++++++-------- 8 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 src/images/game/PerfectPowerBg.png create mode 100644 src/images/game/PerfectPowerFlickerLeft.png create mode 100644 src/images/game/PerfectPowerFlickerRight.png diff --git a/src/assets.lua b/src/assets.lua index ed4e298..71f0ddb 100644 --- a/src/assets.lua +++ b/src/assets.lua @@ -2,66 +2,109 @@ -- Instead, edit the source file directly: assets.lua2p. -- luacheck: ignore +---@type pd_image DarkPlayerBack = playdate.graphics.image.new("images/game/DarkPlayerBack.png") -- luacheck: ignore +---@type pd_image Glove = playdate.graphics.image.new("images/game/Glove.png") -- luacheck: ignore +---@type pd_image +PerfectPowerFlickerRight = playdate.graphics.image.new("images/game/PerfectPowerFlickerRight.png") +-- luacheck: ignore +---@type pd_image +DarkSkinFan = playdate.graphics.image.new("images/game/DarkSkinFan.png") +-- luacheck: ignore +---@type pd_image PlayerFrown = playdate.graphics.image.new("images/game/PlayerFrown.png") -- luacheck: ignore +---@type pd_image +PerfectPowerFlickerLeft = playdate.graphics.image.new("images/game/PerfectPowerFlickerLeft.png") +-- luacheck: ignore +---@type pd_image BigBat = playdate.graphics.image.new("images/game/BigBat.png") -- luacheck: ignore +---@type pd_image +LightSkinFan = playdate.graphics.image.new("images/game/LightSkinFan.png") +-- luacheck: ignore +---@type pd_image GloveHoldingBall = playdate.graphics.image.new("images/game/GloveHoldingBall.png") -- luacheck: ignore +---@type pd_image GameLogo = playdate.graphics.image.new("images/game/GameLogo.png") -- luacheck: ignore +---@type pd_image Hat = playdate.graphics.image.new("images/game/Hat.png") -- luacheck: ignore +---@type pd_image DarkPlayerBase = playdate.graphics.image.new("images/game/DarkPlayerBase.png") -- luacheck: ignore +---@type pd_image MenuImage = playdate.graphics.image.new("images/game/MenuImage.png") -- luacheck: ignore +---@type pd_image PlayerSmile = playdate.graphics.image.new("images/game/PlayerSmile.png") -- luacheck: ignore +---@type pd_image Minimap = playdate.graphics.image.new("images/game/Minimap.png") -- luacheck: ignore +---@type pd_image GrassBackground = playdate.graphics.image.new("images/game/GrassBackground.png") -- luacheck: ignore +---@type pd_image GrassBackgroundSmall = playdate.graphics.image.new("images/game/GrassBackgroundSmall.png") -- luacheck: ignore +---@type pd_image LightPlayerBase = playdate.graphics.image.new("images/game/LightPlayerBase.png") -- luacheck: ignore +---@type pd_image +PerfectPowerBg = playdate.graphics.image.new("images/game/PerfectPowerBg.png") +-- luacheck: ignore +---@type pd_image LightPlayerBack = playdate.graphics.image.new("images/game/LightPlayerBack.png") -- luacheck: ignore +---@type pd_sampleplayer BatCrackReverb = playdate.sound.sampleplayer.new("sounds/BatCrackReverb.wav") -- luacheck: ignore +---@type pd_sampleplayer BootTune = playdate.sound.sampleplayer.new("music/BootTune.wav") -- luacheck: ignore +---@type pd_sampleplayer BootTuneOrgany = playdate.sound.sampleplayer.new("music/BootTuneOrgany.wav") -- luacheck: ignore +---@type pd_sampleplayer TinnyBackground = playdate.sound.sampleplayer.new("music/TinnyBackground.wav") -- luacheck: ignore +---@type pd_sampleplayer MenuMusic = playdate.sound.sampleplayer.new("music/MenuMusic.wav") Logos = { { name = "Base", image = playdate.graphics.image.new("images/game/logos/Base.png") }, -- luacheck: ignore +---@type pd_image { name = "Cats", image = playdate.graphics.image.new("images/game/logos/Cats.png") }, -- luacheck: ignore +---@type pd_image { name = "Hearts", image = playdate.graphics.image.new("images/game/logos/Hearts.png") }, -- luacheck: ignore +---@type pd_image { name = "Checkmarks", image = playdate.graphics.image.new("images/game/logos/Checkmarks.png") }, -- luacheck: ignore +---@type pd_image { name = "Smiles", image = playdate.graphics.image.new("images/game/logos/Smiles.png") }, -- luacheck: ignore +---@type pd_image { name = "FingerGuns", image = playdate.graphics.image.new("images/game/logos/FingerGuns.png") }, -- luacheck: ignore +---@type pd_image { name = "Frown", image = playdate.graphics.image.new("images/game/logos/Frown.png") }, -- luacheck: ignore +---@type pd_image { name = "Arrows", image = playdate.graphics.image.new("images/game/logos/Arrows.png") }, -- luacheck: ignore +---@type pd_image { name = "Turds", image = playdate.graphics.image.new("images/game/logos/Turds.png") }, } diff --git a/src/assets.lua2p b/src/assets.lua2p index a589b91..8f3db59 100644 --- a/src/assets.lua2p +++ b/src/assets.lua2p @@ -1,4 +1,4 @@ -!(function dirLookup(dir, extension, newFunc, sep, handle) +!(function dirLookup(dir, extension, newFunc, type, sep, handle) sep = sep or "\n" handle = handle ~= nil and handle or function(varName, value) return varName .. ' = ' .. value @@ -13,6 +13,7 @@ local varName = file:gsub(".*/(.*)." .. extension, "%1") file = file:gsub("src/", "") assetCode = assetCode .. '-- luacheck: ignore\n' + assetCode = assetCode .. '---@type ' .. type ..'\n' assetCode = assetCode .. handle(varName, newFunc .. '("' .. file .. '")') .. sep end end @@ -23,13 +24,13 @@ function generatedFileWarning() return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p." end)!!(generatedFileWarning()) -!!(dirLookup('images/game', 'png', 'playdate.graphics.image.new')) -!!(dirLookup('sounds', 'wav', 'playdate.sound.sampleplayer.new')) -!!(dirLookup('music', 'wav', 'playdate.sound.sampleplayer.new')) +!!(dirLookup('images/game', 'png', 'playdate.graphics.image.new', 'pd_image')) +!!(dirLookup('sounds', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer')) +!!(dirLookup('music', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer')) Logos = { { name = "Base", image = playdate.graphics.image.new("images/game/logos/Base.png") }, -!!(dirLookup('images/game/logos -not -name "Base.png"', 'png', 'playdate.graphics.image.new', ",\n", function(varName, value) +!!(dirLookup('images/game/logos -not -name "Base.png"', 'png', 'playdate.graphics.image.new', 'pd_image', ",\n", function(varName, value) return '{ name = "' .. varName .. '", image = ' .. value .. ' }' end)) } diff --git a/src/ball.lua b/src/ball.lua index 392b5b1..bf2ad2c 100644 --- a/src/ball.lua +++ b/src/ball.lua @@ -65,7 +65,6 @@ end ---@param floaty boolean | nil ---@param customBallScaler pd_animator | nil function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler) - throwMeter:reset() self.heldBy = nil -- Prevent silly insta-catches diff --git a/src/draw/throw-meter.lua b/src/draw/throw-meter.lua index 13ae750..cd0973e 100644 --- a/src/draw/throw-meter.lua +++ b/src/draw/throw-meter.lua @@ -4,20 +4,33 @@ local gfx = playdate.graphics local ThrowMeterHeight = 50 local ThrowMeterLingerSec = 1.5 +local flickerTimer = gfx.animation.blinker.new(50, 50, true) +flickerTimer:start() + ---@param x number ---@param y number function throwMeter:draw(x, y) gfx.setLineWidth(1) gfx.drawRect(x, y, 14, ThrowMeterHeight) if self.lastReadThrow then - -- TODO: If ratio is "perfect", show some additional effect -- TODO: If meter has moved to a new fielder, empty it. - local ratio = (self.lastReadThrow - throwMeter.MinCharge) / (self.idealPower - throwMeter.MinCharge) + local ratio = 1 + if not wasPerfect then + ratio = (self.lastReadThrow - throwMeter.MinCharge) / (self.idealPower - throwMeter.MinCharge) + end local height = ThrowMeterHeight * ratio gfx.fillRect(x + 2, y + ThrowMeterHeight - height, 10, height) + -- TODO: Dither or bend if the user throws too hard + -- Or maybe dither if it's too soft - bend if it's too hard + if self.wasPerfect then + PerfectPowerBg:draw(x - 11, y - 9) + if flickerTimer.on then + PerfectPowerFlickerLeft:draw(x - 11, y - 9) + else + PerfectPowerFlickerRight:draw(x - 11, y - 9) + end + end end - -- TODO: Dither or bend if the user throws too hard - -- Or maybe dither if it's too soft - bend if it's too hard end function throwMeter:drawNearFielder(fielder) @@ -25,6 +38,9 @@ function throwMeter:drawNearFielder(fielder) return end if fielder then + if fielder ~= self.lastThrower then + self.lastReadThrow = nil + end self.lastThrower = fielder actionQueue:upsert("throwMeterLinger", 200 + ThrowMeterLingerSec * 1000, function() local dt = 0 diff --git a/src/images/game/PerfectPowerBg.png b/src/images/game/PerfectPowerBg.png new file mode 100644 index 0000000000000000000000000000000000000000..1766f5697c00f230fb1caa1b457ab18e0bc8ab19 GIT binary patch literal 654 zcmV;90&)F`P)EX>4Tx04R}tkv&MmKp2MKrivmh4rUN>$WWc^q9Ts93Pq?8YK2xEOfLO{CJjl7 zi=*ILaPVib>fqw6tAnc`2>yV$3r>nIQsQ?>p+$@bclYq#_rBbH2MEn7)9s!Fpc{^r zNhPIRepTvwg#ZyzS|x?q=A1025jei?5#sw@oM#2s{W+pq!Dc{6B2F;Va)>vGXEq&^ z^FFc2%8EvOPCQ}J1&JTIuKN7Ox#Y3HGes+#nI{&BrBWBGUChdsPCQK<({zLKg`CeC z=Pk~9wa(i24udX>Hb4i16w z5@oLkyt}`EfIRF3v8FWQhbVF}#ZDnqB07G(RVRU6= zAa`kWXdp*PO;A^X4i^9b0B1==K~y-)&6L3kz%U3y_lE!f%kEUzq1ahWT~0zl(VB^AV$L^3ieGGYn>JGeiGslb+KhqOm#hXH0T ou;XW|HEkJD{yr?U>LSbH0m2V79Yt90Y5)KL07*qoM6N<$f}nmFLI3~& literal 0 HcmV?d00001 diff --git a/src/images/game/PerfectPowerFlickerLeft.png b/src/images/game/PerfectPowerFlickerLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..9069fd7bcd49e55f444b357e74f9f6c78f085fa9 GIT binary patch literal 611 zcmV-p0-XJcP)EX>4Tx04R}tkv&MmKp2MKrivmh4rUN>$WWc^q9Ts93Pq?8YK2xEOfLO{CJjl7 zi=*ILaPVib>fqw6tAnc`2>yV$3r>nIQsQ?>p+$@bclYq#_rBbH2MEn7)9s!Fpc{^r zNhPIRepTvwg#ZyzS|x?q=A1025jei?5#sw@oM#2s{W+pq!Dc{6B2F;Va)>vGXEq&^ z^FFc2%8EvOPCQ}J1&JTIuKN7Ox#Y3HGes+#nI{&BrBWBGUChdsPCQK<({zLKg`CeC z=Pk~9wa(i24udX>Hb4i16w z5@oLkyt}`EX>4Tx04R}tkv&MmKp2MKrivmh4rUN>$WWc^q9Ts93Pq?8YK2xEOfLO{CJjl7 zi=*ILaPVib>fqw6tAnc`2>yV$3r>nIQsQ?>p+$@bclYq#_rBbH2MEn7)9s!Fpc{^r zNhPIRepTvwg#ZyzS|x?q=A1025jei?5#sw@oM#2s{W+pq!Dc{6B2F;Va)>vGXEq&^ z^FFc2%8EvOPCQ}J1&JTIuKN7Ox#Y3HGes+#nI{&BrBWBGUChdsPCQK<({zLKg`CeC z=Pk~9wa(i24udX>Hb4i16w z5@oLkyt}` = 1.5 + --- Returns nil when a throw is NOT requested. ---@param chargeAmount number ---@return number | nil powerRatio, number | nil accuracy, boolean isPerfect function throwMeter:readThrow(chargeAmount) - local ret = self:applyCharge(chargeAmount) - if ret then - local ratio = ret / self.idealPower - local accuracy - if ratio >= 1 then - accuracy = 1 / ratio / 2 - else - accuracy = 1 -- Accuracy is perfect on slow throws - end - return ratio * 1.5, accuracy, math.abs(ratio - 1) < 0.05 + local power = self:readCharge(chargeAmount) + if not power then + return nil, nil, false end - return nil, nil, false + + local ratio = math.min(power / self.idealPower, MaxPowerRatio) + self.wasPerfect = math.abs(ratio - 1) < 0.05 + + local accuracy = 1 + -- Only throw off accuracy on slow throws + if ratio >= 1 and not self.wasPerfect then + accuracy = 1 / ratio + end + + return ratio * 1.5, accuracy, self.wasPerfect end +local CrankRecordSec = 0.33 + --- If (within approx. a third of a second) the crank has moved more than 45 degrees, call that a throw. ---@param chargeAmount number -function throwMeter:applyCharge(chargeAmount) +---@return number | nil +function throwMeter:readCharge(chargeAmount) if chargeAmount == 0 then - return + return nil end + local currentTimeMs = playdate.getCurrentTimeMilliseconds() - local removedOne = false - while #crankQueue ~= 0 and (currentTimeMs - crankQueue[1].time) > 0.33 do + local minTimeHasPassed = false + while #crankQueue ~= 0 and (currentTimeMs - crankQueue[1].time) > CrankRecordSec do table.remove(crankQueue, 1) - removedOne = true -- At least 1/3 second has passed + minTimeHasPassed = true end + crankQueue[#crankQueue + 1] = { time = currentTimeMs, chargeAmount = math.abs(chargeAmount) } - if not removedOne then + if not minTimeHasPassed then return nil end @@ -174,7 +187,6 @@ function throwMeter:applyCharge(chargeAmount) if currentCharge > throwMeter.MinCharge then self.lastReadThrow = currentCharge - print(tostring(currentCharge) .. " from " .. #crankQueue) crankQueue = {} return currentCharge else