Player pitching, npc batting, and gloves
Or at least a basic implementation thereof
This commit is contained in:
parent
779b13d56b
commit
57625a9b80
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
130
src/main.lua
130
src/main.lua
|
@ -58,6 +58,11 @@ local PlayerFrown <const> = gfx.image.new("images/game/player-frown.png") --[[@a
|
||||||
local PlayerSmile <const> = gfx.image.new("images/game/player.png") --[[@as pd_image]]
|
local PlayerSmile <const> = gfx.image.new("images/game/player.png") --[[@as pd_image]]
|
||||||
local PlayerBack <const> = gfx.image.new("images/game/player-back.png") --[[@as pd_image]]
|
local PlayerBack <const> = gfx.image.new("images/game/player-back.png") --[[@as pd_image]]
|
||||||
|
|
||||||
|
local Glove <const> = gfx.image.new("images/game/glove.png") --[[@as pd_image]]
|
||||||
|
local GloveHoldingBall <const> = gfx.image.new("images/game/glove-holding-ball.png") --[[@as pd_image]]
|
||||||
|
local GloveSizeX, GloveSizeY <const> = Glove:getSize()
|
||||||
|
local GloveOffX, GloveOffY <const> = GloveSizeX / 2, GloveSizeY / 2
|
||||||
|
|
||||||
local PlayerImageBlipper <const> = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
|
local PlayerImageBlipper <const> = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
|
||||||
|
|
||||||
local DanceBounceMs <const> = 500
|
local DanceBounceMs <const> = 500
|
||||||
|
@ -79,7 +84,7 @@ local PitchStartY <const>, PitchEndY <const> = 105, 240
|
||||||
local ballAnimatorY = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
local ballAnimatorY = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
||||||
local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
||||||
|
|
||||||
---@alias PseudoAnimator { currentValue: fun(self): number; reset: fun(self) }
|
---@alias PseudoAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) }
|
||||||
---@alias Pitch { x: PseudoAnimator, y: PseudoAnimator, z: PseudoAnimator | nil }
|
---@alias Pitch { x: PseudoAnimator, y: PseudoAnimator, z: PseudoAnimator | nil }
|
||||||
|
|
||||||
---@type Pitch[]
|
---@type Pitch[]
|
||||||
|
@ -156,7 +161,7 @@ local teams <const> = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local PlayerTeam <const> = teams.away
|
local PlayerTeam <const> = teams.home
|
||||||
local battingTeam = teams.away
|
local battingTeam = teams.away
|
||||||
local outs = 0
|
local outs = 0
|
||||||
local inning = 1
|
local inning = 1
|
||||||
|
@ -217,7 +222,7 @@ local fielders <const> = {
|
||||||
shortstop = newFielder("Shortstop", 40),
|
shortstop = newFielder("Shortstop", 40),
|
||||||
third = newFielder("Third", 40),
|
third = newFielder("Third", 40),
|
||||||
pitcher = newFielder("Pitcher", 30),
|
pitcher = newFielder("Pitcher", 30),
|
||||||
catcher = newFielder("Catcher", 20),
|
catcher = newFielder("Catcher", 35),
|
||||||
left = newFielder("Left", 40),
|
left = newFielder("Left", 40),
|
||||||
center = newFielder("Center", 40),
|
center = newFielder("Center", 40),
|
||||||
right = newFielder("Right", 40),
|
right = newFielder("Right", 40),
|
||||||
|
@ -316,7 +321,8 @@ local secondsSincePitchAllowed = -5
|
||||||
|
|
||||||
local catcherThrownBall = false
|
local catcherThrownBall = false
|
||||||
|
|
||||||
function pitch()
|
---@param pitchFlyTimeMs number | nil
|
||||||
|
function pitch(pitchFlyTimeMs)
|
||||||
catcherThrownBall = false
|
catcherThrownBall = false
|
||||||
offenseMode = Offense.batting
|
offenseMode = Offense.batting
|
||||||
|
|
||||||
|
@ -330,15 +336,17 @@ function pitch()
|
||||||
-- ballFloatAnimator:reset()
|
-- ballFloatAnimator:reset()
|
||||||
-- end
|
-- end
|
||||||
|
|
||||||
|
if pitchFlyTimeMs then
|
||||||
|
ballAnimatorX:reset(pitchFlyTimeMs)
|
||||||
|
ballAnimatorY:reset(pitchFlyTimeMs)
|
||||||
|
else
|
||||||
ballAnimatorX:reset()
|
ballAnimatorX:reset()
|
||||||
ballAnimatorY:reset()
|
ballAnimatorY:reset()
|
||||||
|
end
|
||||||
|
|
||||||
secondsSincePitchAllowed = 0
|
secondsSincePitchAllowed = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local crankChange = 0
|
|
||||||
local acceleratedChange = 0
|
|
||||||
|
|
||||||
local BaseHitbox = 10
|
local BaseHitbox = 10
|
||||||
|
|
||||||
--- Returns the base being touched by the player at (x,y), or nil, if no base is being touched
|
--- Returns the base being touched by the player at (x,y), or nil, if no base is being touched
|
||||||
|
@ -360,10 +368,10 @@ local BallCatchHitbox = 3
|
||||||
--- Returns true only if the given point is touching the ball at its current position
|
--- Returns true only if the given point is touching the ball at its current position
|
||||||
---@param x number
|
---@param x number
|
||||||
---@param y number
|
---@param y number
|
||||||
---@return boolean
|
---@return boolean, number
|
||||||
function isTouchingBall(x, y)
|
function isTouchingBall(x, y)
|
||||||
local ballDistance = utils.distanceBetween(x, y, ball.x, ball.y)
|
local ballDistance = utils.distanceBetween(x, y, ball.x, ball.y)
|
||||||
return ballDistance < BallCatchHitbox
|
return ballDistance < BallCatchHitbox, ballDistance
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param base Base
|
---@param base Base
|
||||||
|
@ -626,14 +634,14 @@ end
|
||||||
---@type number
|
---@type number
|
||||||
local batAngleDeg
|
local batAngleDeg
|
||||||
|
|
||||||
function updateBatting()
|
---@param batDeg number
|
||||||
|
function updateBatting(batDeg, batSpeed)
|
||||||
if ball.y < BallOffscreen then
|
if ball.y < BallOffscreen then
|
||||||
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
|
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
|
||||||
ball.size = SmallestBallRadius -- ballFloatAnimator:currentValue()
|
ball.size = SmallestBallRadius -- ballFloatAnimator:currentValue()
|
||||||
end
|
end
|
||||||
|
|
||||||
batAngleDeg = (playdate.getCrankPosition() + CrankOffsetDeg) % 360
|
local batAngle = math.rad(batDeg)
|
||||||
local batAngle = math.rad(batAngleDeg)
|
|
||||||
-- TODO: animate bat-flip or something
|
-- TODO: animate bat-flip or something
|
||||||
batBase.x = batter and (batter.x + BatOffset.x) or 0
|
batBase.x = batter and (batter.x + BatOffset.x) or 0
|
||||||
batBase.y = batter and (batter.y + BatOffset.y) or 0
|
batBase.y = batter and (batter.y + BatOffset.y) or 0
|
||||||
|
@ -641,7 +649,7 @@ function updateBatting()
|
||||||
batTip.y = batBase.y + (BatLength * math.cos(batAngle))
|
batTip.y = batBase.y + (BatLength * math.cos(batAngle))
|
||||||
|
|
||||||
if
|
if
|
||||||
acceleratedChange >= 0 -- > 0
|
batSpeed >= 0 -- > 0
|
||||||
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, Screen.H)
|
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, Screen.H)
|
||||||
and ball.y < 232 --not isTouchingBall(fielders.catcher.x, fielders.catcher.y)
|
and ball.y < 232 --not isTouchingBall(fielders.catcher.x, fielders.catcher.y)
|
||||||
then
|
then
|
||||||
|
@ -649,7 +657,7 @@ function updateBatting()
|
||||||
offenseMode = Offense.running
|
offenseMode = Offense.running
|
||||||
local ballAngle = batAngle + math.rad(90)
|
local ballAngle = batAngle + math.rad(90)
|
||||||
|
|
||||||
local mult = math.abs(crankChange / 15)
|
local mult = math.abs(batSpeed / 15)
|
||||||
local ballVelX = mult * 10 * math.sin(ballAngle)
|
local ballVelX = mult * 10 * math.sin(ballAngle)
|
||||||
local ballVelY = mult * 5 * math.cos(ballAngle)
|
local ballVelY = mult * 5 * math.cos(ballAngle)
|
||||||
if ballVelY > 0 then
|
if ballVelY > 0 then
|
||||||
|
@ -712,10 +720,47 @@ function walkAwayOutRunners()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local npcBatDeg = 0
|
||||||
|
local NpcBatSpeed <const> = 1200
|
||||||
|
|
||||||
|
function npcBatAngle()
|
||||||
|
if not catcherThrownBall and ball.y > 190 and ball.y < 230 and (ball.x < Center.x + 15) then
|
||||||
|
npcBatDeg = npcBatDeg + (deltaSeconds * NpcBatSpeed)
|
||||||
|
else
|
||||||
|
npcBatDeg = 200
|
||||||
|
end
|
||||||
|
return npcBatDeg
|
||||||
|
end
|
||||||
|
|
||||||
|
function npcBatChange()
|
||||||
|
return deltaSeconds * NpcBatSpeed
|
||||||
|
end
|
||||||
|
|
||||||
|
function npcRunningSpeed()
|
||||||
|
if #runners == 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local touchedBase = isTouchingBase(runners[1].x, runners[1].y)
|
||||||
|
if not touchedBase or touchedBase == Bases[Home] then
|
||||||
|
return 35
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local pitchMeter = 0
|
||||||
|
local PitchMeterLimit = 25
|
||||||
|
|
||||||
|
function readPitch()
|
||||||
|
if pitchMeter > PitchMeterLimit then
|
||||||
|
return (PitchFlyMs / (pitchMeter / PitchMeterLimit))
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
function updateGameState()
|
function updateGameState()
|
||||||
deltaSeconds = playdate.getElapsedTime() or 0
|
deltaSeconds = playdate.getElapsedTime() or 0
|
||||||
playdate.resetElapsedTime()
|
playdate.resetElapsedTime()
|
||||||
crankChange, acceleratedChange = playdate.getCrankChange() --[[@as number, number]]
|
local crankChange = playdate.getCrankChange() --[[@as number, number]]
|
||||||
|
|
||||||
if ball.heldBy then
|
if ball.heldBy then
|
||||||
ball.x = ball.heldBy.x
|
ball.x = ball.heldBy.x
|
||||||
|
@ -726,22 +771,42 @@ function updateGameState()
|
||||||
end
|
end
|
||||||
|
|
||||||
if offenseMode == Offense.batting then
|
if offenseMode == Offense.batting then
|
||||||
|
local playerOnOffense = playerIsOn(Sides.offense)
|
||||||
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
||||||
|
|
||||||
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
|
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
|
||||||
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
|
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||||
catcherThrownBall = true
|
catcherThrownBall = true
|
||||||
|
else
|
||||||
|
pitchMeter = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not playerOnOffense then
|
||||||
|
pitchMeter = math.max(0, pitchMeter - (deltaSeconds * 150))
|
||||||
|
pitchMeter = pitchMeter + crankChange
|
||||||
|
if pitchMeter > PitchMeterLimit then
|
||||||
|
printTable({ pitchMeter = pitchMeter })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if secondsSincePitchAllowed > PitchAfterSeconds then
|
if secondsSincePitchAllowed > PitchAfterSeconds then
|
||||||
pitch()
|
if playerOnOffense then
|
||||||
|
pitch(PitchFlyMs)
|
||||||
|
else
|
||||||
|
local pitchFly = readPitch()
|
||||||
|
if pitchFly then
|
||||||
|
pitch(pitchFly)
|
||||||
end
|
end
|
||||||
updateBatting()
|
end
|
||||||
|
end
|
||||||
|
batAngleDeg = playerOnOffense and ((playdate.getCrankPosition() + CrankOffsetDeg) % 360) or npcBatAngle()
|
||||||
|
local batSpeed = playerOnOffense and crankChange or npcBatChange()
|
||||||
|
updateBatting(batAngleDeg, batSpeed)
|
||||||
-- TODO: Ensure batter can't be nil, here
|
-- TODO: Ensure batter can't be nil, here
|
||||||
updateRunner(batter, nil, crankChange)
|
updateRunner(batter, nil, crankChange)
|
||||||
elseif offenseMode == Offense.running then
|
elseif offenseMode == Offense.running then
|
||||||
if playerIsOn(Sides.defense) then
|
local appliedSpeed = playerIsOn(Sides.offense) and crankChange or npcRunningSpeed()
|
||||||
updateRunning(999)
|
if updateRunning(appliedSpeed) then
|
||||||
end
|
|
||||||
if updateRunning(crankChange) then
|
|
||||||
secondsSinceLastRunnerMove = 0
|
secondsSinceLastRunnerMove = 0
|
||||||
else
|
else
|
||||||
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
||||||
|
@ -761,6 +826,22 @@ end
|
||||||
-- TODO
|
-- TODO
|
||||||
function drawMinimap() end
|
function drawMinimap() end
|
||||||
|
|
||||||
|
---@param fielder Fielder
|
||||||
|
---@return boolean isHoldingBall
|
||||||
|
function drawFielderGlove(fielder)
|
||||||
|
printTable({ ballFloatValue = ballFloatAnimator:currentValue() })
|
||||||
|
local distanceFromBall =
|
||||||
|
utils.distanceBetweenZ(fielder.x, fielder.y, 0, ball.x, ball.y, ballFloatAnimator:currentValue())
|
||||||
|
local shoulderX, shoulderY = fielder.x + 10, fielder.y + FielderDanceAnimator:currentValue() + 5
|
||||||
|
if distanceFromBall > 30 then
|
||||||
|
Glove:draw(shoulderX, shoulderY)
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function playdate.update()
|
function playdate.update()
|
||||||
playdate.timer.updateTimers()
|
playdate.timer.updateTimers()
|
||||||
|
|
||||||
|
@ -779,8 +860,11 @@ function playdate.update()
|
||||||
GrassBackground:draw(-400, -240)
|
GrassBackground:draw(-400, -240)
|
||||||
|
|
||||||
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
||||||
|
local ballIsHeld = false
|
||||||
for _, fielder in pairs(fielders) do
|
for _, fielder in pairs(fielders) do
|
||||||
gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25)
|
local fielderY = fielder.y + fielderDanceHeight
|
||||||
|
gfx.fillRect(fielder.x, fielderY, 14, 25)
|
||||||
|
ballIsHeld = drawFielderGlove(fielder) or ballIsHeld
|
||||||
end
|
end
|
||||||
|
|
||||||
if offenseMode == Offense.batting then
|
if offenseMode == Offense.batting then
|
||||||
|
@ -809,6 +893,7 @@ function playdate.update()
|
||||||
PlayerFrown:draw(runner.x, runner.y)
|
PlayerFrown:draw(runner.x, runner.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not ballIsHeld then
|
||||||
gfx.setLineWidth(2)
|
gfx.setLineWidth(2)
|
||||||
|
|
||||||
gfx.setColor(gfx.kColorWhite)
|
gfx.setColor(gfx.kColorWhite)
|
||||||
|
@ -816,6 +901,7 @@ function playdate.update()
|
||||||
|
|
||||||
gfx.setColor(gfx.kColorBlack)
|
gfx.setColor(gfx.kColorBlack)
|
||||||
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
||||||
|
end
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -61,13 +61,21 @@ function utils.filter(array, condition)
|
||||||
return newArray
|
return newArray
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return number, number, number
|
---@return number distance, number x, number y
|
||||||
function utils.distanceBetween(x1, y1, x2, y2)
|
function utils.distanceBetween(x1, y1, x2, y2)
|
||||||
local a = x1 - x2
|
local a = x1 - x2
|
||||||
local b = y1 - y2
|
local b = y1 - y2
|
||||||
return math.sqrt((a * a) + (b * b)), a, b
|
return math.sqrt((a * a) + (b * b)), a, b
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return number distance, number x, number y
|
||||||
|
function utils.distanceBetweenZ(x1, y1, z1, x2, y2, z2)
|
||||||
|
local a = x1 - x2
|
||||||
|
local b = y1 - y2
|
||||||
|
local c = z1 - z2
|
||||||
|
return math.sqrt((a * a) + (b * b) + (c * c)), a, b
|
||||||
|
end
|
||||||
|
|
||||||
--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound
|
--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
|
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
|
||||||
|
|
Loading…
Reference in New Issue