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 0000000..1766f56 Binary files /dev/null and b/src/images/game/PerfectPowerBg.png differ diff --git a/src/images/game/PerfectPowerFlickerLeft.png b/src/images/game/PerfectPowerFlickerLeft.png new file mode 100644 index 0000000..9069fd7 Binary files /dev/null and b/src/images/game/PerfectPowerFlickerLeft.png differ diff --git a/src/images/game/PerfectPowerFlickerRight.png b/src/images/game/PerfectPowerFlickerRight.png new file mode 100644 index 0000000..cad3f6d Binary files /dev/null and b/src/images/game/PerfectPowerFlickerRight.png differ diff --git a/src/pitching.lua b/src/pitching.lua index 829035c..5983465 100644 --- a/src/pitching.lua +++ b/src/pitching.lua @@ -46,7 +46,8 @@ Pitches = { currentValue = function() return getPitchMissBy(accuracy) + C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10)) end, - reset = function() end, + reset = function() + end, }, y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear), } @@ -116,54 +117,66 @@ end throwMeter = { MinCharge = 25, - value = 0, idealPower = 50, + + --- Used at draw-time only. + ---@type number lastReadThrow = nil, --- Used at draw-time only. ---@type Fielder | nil lastThrower = nil, + + --- Used at draw-time only. + ---@type boolean + wasPerfect = false, } -function throwMeter:reset() - self.value = 0 -end - local crankQueue = {} +local MaxPowerRatio = 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