Implement grounders and fly-outs.
Add a slight delay on npc fielder actions. Speed up intro when userTeam == nil
This commit is contained in:
parent
decd1f7080
commit
4b9a94c2c2
33
src/ball.lua
33
src/ball.lua
|
@ -5,6 +5,7 @@
|
|||
---@field size number
|
||||
---@field heldBy Fielder | nil
|
||||
---@field catchable boolean
|
||||
---@field isFlyBall boolean
|
||||
---@field xAnimator SimpleAnimator
|
||||
---@field yAnimator SimpleAnimator
|
||||
---@field sizeAnimator SimpleAnimator
|
||||
|
@ -12,6 +13,10 @@
|
|||
---@field private animatorLib pd_animator_lib
|
||||
Ball = {}
|
||||
|
||||
local function defaultFloatAnimator(animatorLib)
|
||||
return animatorLib.new(2000, -60, 0, utils.easingHill)
|
||||
end
|
||||
|
||||
---@param animatorLib pd_animator_lib
|
||||
---@return Ball
|
||||
function Ball.new(animatorLib)
|
||||
|
@ -30,15 +35,14 @@ function Ball.new(animatorLib)
|
|||
-- TODO? Replace these with a ballAnimatorZ?
|
||||
-- ...that might lose some of the magic of both. Compromise available? idk
|
||||
sizeAnimator = utils.staticAnimator(C.SmallestBallRadius),
|
||||
floatAnimator = animatorLib.new(2000, -60, 0, utils.easingHill),
|
||||
floatAnimator = defaultFloatAnimator(animatorLib),
|
||||
}, { __index = Ball })
|
||||
end
|
||||
|
||||
function Ball:updatePosition()
|
||||
---@param deltaSeconds number
|
||||
function Ball:updatePosition(deltaSeconds)
|
||||
if self.heldBy then
|
||||
self.x = self.heldBy.x
|
||||
self.y = self.heldBy.y
|
||||
self.z = C.GloveZ
|
||||
utils.moveAtSpeedZ(self, 100 * deltaSeconds, { x = self.heldBy.x, y = self.heldBy.y, z = C.GloveZ })
|
||||
self.size = C.SmallestBallRadius
|
||||
else
|
||||
self.x = self.xAnimator:currentValue()
|
||||
|
@ -46,7 +50,11 @@ function Ball:updatePosition()
|
|||
-- TODO: This `+ z` is more graphics logic than physics logic
|
||||
self.y = self.yAnimator:currentValue() + z
|
||||
self.z = z
|
||||
self.size = self.sizeAnimator:currentValue()
|
||||
if self.z < 2 and self.isFlyBall then
|
||||
print("Ball hit the ground!")
|
||||
self.isFlyBall = false
|
||||
end
|
||||
self.size = C.SmallestBallRadius + math.max(0, (self.floatAnimator:currentValue() - C.GloveZ) / 2)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -63,9 +71,11 @@ end
|
|||
---@param easingFunc EasingFunc
|
||||
---@param flyTimeMs number | nil
|
||||
---@param floaty boolean | nil
|
||||
---@param customBallScaler pd_animator | nil
|
||||
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||
---@param customFloater pd_animator | nil
|
||||
---@param isHit boolean
|
||||
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customFloater, isHit)
|
||||
self.heldBy = nil
|
||||
self.isFlyBall = isHit
|
||||
|
||||
-- Prevent silly insta-catches
|
||||
self:markUncatchable()
|
||||
|
@ -74,10 +84,11 @@ function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScal
|
|||
flyTimeMs = utils.distanceBetween(self.x, self.y, destX, destY) * C.DefaultLaunchPower
|
||||
end
|
||||
|
||||
if customBallScaler then
|
||||
self.sizeAnimator = customBallScaler
|
||||
if customFloater then
|
||||
self.floatAnimator = customFloater
|
||||
else
|
||||
self.sizeAnimator = self.animatorLib.new(flyTimeMs, 9, C.SmallestBallRadius, utils.easingHill)
|
||||
self.sizeAnimator = self.animatorLib.new(flyTimeMs, C.SmallestBallRadius, 9, utils.easingHill)
|
||||
self.floatAnimator = defaultFloatAnimator(self.animatorLib)
|
||||
end
|
||||
self.yAnimator = self.animatorLib.new(flyTimeMs, self.y, destY, easingFunc)
|
||||
self.xAnimator = self.animatorLib.new(flyTimeMs, self.x, destX, easingFunc)
|
||||
|
|
|
@ -176,6 +176,10 @@ function Baserunning:pushNewBatter()
|
|||
return new
|
||||
end
|
||||
|
||||
function Baserunning:getNewestRunner()
|
||||
return self.runners[#self.runners]
|
||||
end
|
||||
|
||||
---@param runnerIndex number
|
||||
function Baserunning:runnerScored(runnerIndex)
|
||||
self.scoredRunners[#self.scoredRunners + 1] = self.runners[runnerIndex]
|
||||
|
|
|
@ -99,6 +99,7 @@ C.Offense = {
|
|||
running = "running",
|
||||
walking = "walking",
|
||||
homeRun = "homeRun",
|
||||
fliedOut = "running",
|
||||
}
|
||||
|
||||
---@alias Side "offense" | "defense"
|
||||
|
|
|
@ -67,7 +67,8 @@ end
|
|||
|
||||
--- Resets the target positions of all fielders to their defaults (at their field positions).
|
||||
---@param fromOffTheField XyPair | nil If provided, also sets all runners' current position to one centralized location.
|
||||
function Fielding:resetFielderPositions(fromOffTheField)
|
||||
---@param immediate boolean | nil
|
||||
function Fielding:resetFielderPositions(fromOffTheField, immediate)
|
||||
if fromOffTheField then
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
fielder.x = fromOffTheField.x
|
||||
|
@ -84,6 +85,13 @@ function Fielding:resetFielderPositions(fromOffTheField)
|
|||
self.fielders.left.targets = { utils.xy(C.Screen.W * -0.6, C.Screen.H * -0.1) }
|
||||
self.fielders.center.targets = { utils.xy(C.Center.x, C.Screen.H * -0.4) }
|
||||
self.fielders.right.targets = { utils.xy(C.Screen.W * 1.6, self.fielders.left.targets[1].y) }
|
||||
|
||||
if immediate then
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
fielder.x = fielder.targets[1].x
|
||||
fielder.y = fielder.targets[1].y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param deltaSeconds number
|
||||
|
@ -96,7 +104,7 @@ local function updateFielderPosition(deltaSeconds, fielder, ball)
|
|||
local currentTarget = fielder.targets[#fielder.targets]
|
||||
local willMove = utils.moveAtSpeed(nextFielderPos, fielder.speed * deltaSeconds, currentTarget)
|
||||
|
||||
if willMove and utils.pointIsAboveLine(nextFielderPos, C.BottomOfOutfieldWall) then
|
||||
if willMove and utils.pointIsAboveLine(nextFielderPos, C.BottomOfOutfieldWall, 40) then
|
||||
local targetCount = #fielder.targets
|
||||
-- Back up a little
|
||||
fielder.targets[targetCount + 2] = utils.xy(fielder.x, fielder.y + 5)
|
||||
|
@ -125,11 +133,12 @@ end
|
|||
|
||||
--- Selects the nearest fielder to move toward the given coordinates.
|
||||
--- Other fielders should attempt to cover their bases
|
||||
---@param ballDestX number
|
||||
---@param ballDestY number
|
||||
function Fielding:haveSomeoneChase(ballDestX, ballDestY)
|
||||
local chasingFielder = utils.getNearestOf(self.fielders, ballDestX, ballDestY)
|
||||
chasingFielder.targets = { utils.xy(ballDestX, ballDestY) }
|
||||
---@param ball Point3d
|
||||
---@param ballDest XyPair
|
||||
function Fielding:haveSomeoneChase(ball, ballDest)
|
||||
local chasingFielder = utils.getNearestOf(self.fielders, ballDest.x, ballDest.y)
|
||||
-- Start moving toward the ball directly after reaching ballDest
|
||||
chasingFielder.targets = { ball, ballDest }
|
||||
|
||||
for _, base in ipairs(C.Bases) do
|
||||
local nearest = utils.getNearestOf(self.fielders, base.x, base.y, function(fielder)
|
||||
|
@ -146,19 +155,24 @@ end
|
|||
--- **Also updates `ball.heldby`**
|
||||
---@param ball Ball
|
||||
---@param deltaSeconds number
|
||||
---@return Fielder | nil fielderHoldingBall nil if no fielder is currently touching the ball
|
||||
---@return Fielder | nil, boolean fielderHoldingBall nil if no fielder is currently touching the ball, true if caught a fly ball
|
||||
function Fielding:updateFielderPositions(ball, deltaSeconds)
|
||||
local fielderHoldingBall
|
||||
local caughtAFlyBall = false
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
-- TODO: Base this catch on fielder skill?
|
||||
local canCatch = updateFielderPosition(deltaSeconds, fielder, ball)
|
||||
if canCatch then
|
||||
fielderHoldingBall = fielder
|
||||
ball.heldBy = fielder -- How much havoc will this wreak?
|
||||
if ball.isFlyBall then
|
||||
ball.isFlyBall = false
|
||||
caughtAFlyBall = true
|
||||
end
|
||||
end
|
||||
end
|
||||
self.fielderHoldingBall = fielderHoldingBall
|
||||
return fielderHoldingBall
|
||||
return fielderHoldingBall, caughtAFlyBall
|
||||
end
|
||||
|
||||
-- TODO? Start moving target fielders close sooner?
|
||||
|
|
67
src/main.lua
67
src/main.lua
|
@ -64,7 +64,9 @@ import 'draw/transitions.lua'
|
|||
-- TODO: Customizable field structure. E.g. stands and ads etc.
|
||||
|
||||
---@type pd_graphics_lib
|
||||
local gfx <const>, C <const> = playdate.graphics, C
|
||||
local gfx <const> = playdate.graphics
|
||||
|
||||
local C <const> = C
|
||||
|
||||
---@alias Team { benchPosition: XyPair }
|
||||
---@type table<TeamId, Team>
|
||||
|
@ -153,8 +155,8 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
|
|||
end)
|
||||
o.npc = npc or Npc.new(o.baserunning.runners, o.fielding.fielders)
|
||||
|
||||
o.fielding:resetFielderPositions(teams.home.benchPosition)
|
||||
playdate.timer.new(2000, function()
|
||||
o.fielding:resetFielderPositions(teams.home.benchPosition, settings.userTeam == nil)
|
||||
playdate.timer.new(settings.userTeam == nil and 10 or 2000, function()
|
||||
o:returnToPitcher()
|
||||
end)
|
||||
o.characters = Characters.new(settings.homeTeamSpriteGroup, settings.awayTeamSpriteGroup)
|
||||
|
@ -393,12 +395,13 @@ function Game:updateBatting(offenseHandler)
|
|||
|
||||
-- Hit!
|
||||
-- TODO: animate bat-flip or something
|
||||
local isFlyBall = math.random() > 0.5
|
||||
self:saveToFile()
|
||||
BatCrackReverb:play()
|
||||
self.state.offenseState = C.Offense.running
|
||||
|
||||
pitchTracker:reset()
|
||||
local flyTimeMs = 2000
|
||||
local flyTimeMs = 8000
|
||||
-- TODO? A dramatic eye-level view on a home-run could be sick.
|
||||
local battingTeamStats = self:battingTeamCurrentInning()
|
||||
battingTeamStats.hits[#battingTeamStats.hits + 1] = ballDest
|
||||
|
@ -410,28 +413,38 @@ function Game:updateBatting(offenseHandler)
|
|||
return
|
||||
end
|
||||
|
||||
local isHomeRun = utils.pointIsAboveLine(ballDest, C.OutfieldWall)
|
||||
if isHomeRun then
|
||||
playdate.timer.new(flyTimeMs, function()
|
||||
-- Verify that the home run wasn't intercepted
|
||||
if utils.distanceBetweenPoints(ball, ballDest) < 2 then
|
||||
self.announcer:say("HOME RUN!")
|
||||
self.state.offenseState = C.Offense.homeRun
|
||||
-- Linger on the home-run ball for a moment, before panning to the bases.
|
||||
playdate.timer.new(1000, function()
|
||||
self.panner:panTo(utils.xy(0, 0), function()
|
||||
return self:pitcherIsReady()
|
||||
local isPastOutfieldWall, nearbyPointAbove = utils.pointIsAboveLine(ballDest, C.OutfieldWall)
|
||||
|
||||
if isPastOutfieldWall then
|
||||
if not isFlyBall then
|
||||
-- Grounder at the wall!
|
||||
ballDest.y = nearbyPointAbove.y - 8
|
||||
else
|
||||
-- Home run!
|
||||
playdate.timer.new(flyTimeMs, function()
|
||||
-- Verify that the home run wasn't intercepted
|
||||
if utils.distanceBetweenPoints(ball, ballDest) < 2 then
|
||||
self.announcer:say("HOME RUN!")
|
||||
self.state.offenseState = C.Offense.homeRun
|
||||
-- Linger on the home-run ball for a moment, before panning to the bases.
|
||||
playdate.timer.new(1000, function()
|
||||
self.panner:panTo(utils.xy(0, 0), function()
|
||||
return self:pitcherIsReady()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
|
||||
ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, hitBallScaler)
|
||||
local ballHeightAnimator = isFlyBall
|
||||
and gfx.animator.new(flyTimeMs, C.GloveZ, 10 + (2 * mult * mult * 0.5), utils.hitEasingHill)
|
||||
or gfx.animator.new(flyTimeMs, 2 * (mult * mult), 0, utils.createBouncer(4))
|
||||
|
||||
ball:launch(ballDest.x, ballDest.y, playdate.easingFunctions.outQuint, flyTimeMs, nil, ballHeightAnimator, true)
|
||||
|
||||
self.baserunning:convertBatterToRunner()
|
||||
self.fielding:haveSomeoneChase(ballDest.x, ballDest.y)
|
||||
self.fielding:haveSomeoneChase(ball, ballDest)
|
||||
end
|
||||
|
||||
---@param appliedSpeed number | fun(runner: Runner): number
|
||||
|
@ -498,7 +511,15 @@ function Game:updateGameState()
|
|||
playdate.resetElapsedTime()
|
||||
self.state.ball:updatePosition()
|
||||
|
||||
local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
|
||||
local fielderHoldingBall, caughtAFlyBall =
|
||||
self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds)
|
||||
if caughtAFlyBall then
|
||||
local fliedOut = self.baserunning:getNewestRunner()
|
||||
self.baserunning:outRunner(fliedOut, "Fly out!")
|
||||
self.state.offenseState = C.Offense.fliedOut
|
||||
self.baserunning:pushNewBatter()
|
||||
pitchTracker.secondsSinceLastPitch = -1
|
||||
end
|
||||
|
||||
local offenseHandler, defenseHandler = self:currentInputHandlers()
|
||||
|
||||
|
@ -574,7 +595,7 @@ function Game:update()
|
|||
end
|
||||
|
||||
gfx.setDrawOffset(0, 0)
|
||||
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
|
||||
if math.abs(offsetX) > 10 or math.abs(offsetY) > 20 then
|
||||
drawMinimap(self.baserunning.runners, self.fielding.fielders)
|
||||
end
|
||||
|
||||
|
|
48
src/npc.lua
48
src/npc.lua
|
@ -1,6 +1,6 @@
|
|||
local npcBatDeg = 0
|
||||
local BaseNpcBatSpeed <const> = 1500
|
||||
local npcBatSpeed = 1500
|
||||
local BaseNpcBatSpeed <const> = 1000
|
||||
local npcBatSpeed = BaseNpcBatSpeed
|
||||
|
||||
---@class Npc: InputHandler
|
||||
---@field runners Runner[]
|
||||
|
@ -27,18 +27,24 @@ function Npc.update() end
|
|||
---@param deltaSec number
|
||||
---@return number batAngleDeg, number batSpeed
|
||||
function Npc:updateBatAngle(ball, pitchIsOver, deltaSec)
|
||||
if not pitchIsOver and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then
|
||||
if
|
||||
not pitchIsOver
|
||||
and ball.y > 200
|
||||
and ball.y < 230
|
||||
and (ball.x < C.Center.x + 15)
|
||||
and (ball.x > C.Center.x - 12)
|
||||
then
|
||||
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
|
||||
else
|
||||
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
|
||||
npcBatDeg = 230
|
||||
npcBatDeg = utils.moveAtSpeed1d(npcBatDeg, deltaSec * BaseNpcBatSpeed, 230 - 360)
|
||||
end
|
||||
return npcBatDeg, (self:batSpeed() * deltaSec)
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Npc:batSpeed()
|
||||
return npcBatSpeed / 1.5
|
||||
return npcBatSpeed * 1.25
|
||||
end
|
||||
|
||||
---@return number flyTimeMs, number pitchId, number accuracy
|
||||
|
@ -131,14 +137,16 @@ end
|
|||
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
|
||||
local function tryToMakeAPlay(fielders, fielder, runners, ball)
|
||||
local targetX, targetY = getNextOutTarget(runners)
|
||||
if targetX ~= nil and targetY ~= nil then
|
||||
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY)
|
||||
nearestFielder.targets = { utils.xy(targetX, targetY) }
|
||||
if nearestFielder == fielder then
|
||||
ball.heldBy = fielder
|
||||
else
|
||||
ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
||||
end
|
||||
if targetX == nil or targetY == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local nearestFielder = utils.getNearestOf(fielders, targetX, targetY)
|
||||
nearestFielder.targets = { utils.xy(targetX, targetY) }
|
||||
if nearestFielder == fielder then
|
||||
ball.heldBy = fielder
|
||||
else
|
||||
ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -149,14 +157,14 @@ function Npc:fielderAction(fielder, outedSomeRunner, ball)
|
|||
if not fielder then
|
||||
return
|
||||
end
|
||||
if outedSomeRunner then
|
||||
-- Delay a little before the next play
|
||||
playdate.timer.new(750, function()
|
||||
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
||||
end)
|
||||
else
|
||||
local playDelay = outedSomeRunner and 0.5 or 0.1
|
||||
actionQueue:newOnly("npcFielderAction", 2000, function()
|
||||
local dt = 0
|
||||
while dt < playDelay do
|
||||
dt = dt + coroutine.yield()
|
||||
end
|
||||
tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@return number
|
||||
|
|
|
@ -20,6 +20,25 @@ function utils.easingHill(t, b, c, d)
|
|||
return (c * t) + b
|
||||
end
|
||||
|
||||
function utils.hitEasingHill(t, b, c, d)
|
||||
c = c + 0.0 -- convert to float to prevent integer overflow
|
||||
t = 1 - (t / d)
|
||||
local extraDrop = -C.GloveZ * t
|
||||
t = ((t * 2) - 1)
|
||||
t = t * t
|
||||
return (c * t) + b + extraDrop
|
||||
end
|
||||
|
||||
function utils.createBouncer(bounceCount)
|
||||
return function(t, b, c, d)
|
||||
c = c + 0.0 -- convert to float to prevent integer overflow
|
||||
local percComplete = t / d
|
||||
local x = percComplete * math.pi * bounceCount
|
||||
local weird = -math.abs((2 / x) * math.sin(x)) / math.pi * 2
|
||||
return b + c + (c * weird)
|
||||
end
|
||||
end
|
||||
|
||||
--- @alias StaticAnimator {
|
||||
--- currentValue: fun(self): number;
|
||||
--- reset: fun(self, durationMs: number | nil);
|
||||
|
@ -63,6 +82,29 @@ function utils.normalizeVector(x1, y1, x2, y2)
|
|||
return x / distance, y / distance, distance
|
||||
end
|
||||
|
||||
function utils.normalizeVectorZ(x1, y1, z1, x2, y2, z2)
|
||||
local distance, x, y, z = utils.distanceBetweenZ(x1, y1, z1, x2, y2, z2)
|
||||
return x / distance, y / distance, z / distance, distance
|
||||
end
|
||||
|
||||
---@param current number
|
||||
---@param speed number Must not be negative!
|
||||
---@param target number
|
||||
---@return number newValue, boolean didMove
|
||||
function utils.moveAtSpeed1d(current, speed, target)
|
||||
local distance = math.abs(current - target)
|
||||
if distance == 0 then
|
||||
return target, false
|
||||
end
|
||||
if distance < speed then
|
||||
return target, true
|
||||
end
|
||||
if target > current then
|
||||
return current + speed, true
|
||||
end
|
||||
return current - speed, true
|
||||
end
|
||||
|
||||
--- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time.
|
||||
--- Stops when within 1. Returns true only if the object did actually move.
|
||||
---@param mover { x: number, y: number }
|
||||
|
@ -88,6 +130,31 @@ function utils.moveAtSpeed(mover, speed, target, tau)
|
|||
return true
|
||||
end
|
||||
|
||||
---@param mover Point3d
|
||||
---@param speed number
|
||||
---@param target Point3d
|
||||
---@param tau number | nil
|
||||
---@return boolean isStillMoving
|
||||
function utils.moveAtSpeedZ(mover, speed, target, tau)
|
||||
local x, y, distance = utils.normalizeVectorZ(mover.x, mover.y, mover.z, target.x, target.y, target.z)
|
||||
|
||||
if distance == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
if distance > (tau or 1) then
|
||||
mover.x = mover.x - (x * speed)
|
||||
mover.y = mover.y - (y * speed)
|
||||
mover.z = mover.z - (z * speed)
|
||||
else
|
||||
mover.x = target.x
|
||||
mover.y = target.y
|
||||
mover.z = target.z
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@generic T
|
||||
---@param array T[]
|
||||
---@param condition fun(T): boolean
|
||||
|
@ -188,22 +255,26 @@ end
|
|||
--- If left of first linePoint and above it, returns true. Similarly if right of the last linePoint.
|
||||
---@param point XyPair
|
||||
---@param linePoints XyPair[]
|
||||
---@return boolean
|
||||
function utils.pointIsAboveLine(point, linePoints)
|
||||
if point.x < linePoints[1].x and point.y < linePoints[1].y then
|
||||
return true
|
||||
---@return boolean, XyPair | nil nearbyPointAbove
|
||||
function utils.pointIsAboveLine(point, linePoints, by)
|
||||
by = by or 0
|
||||
local pointY = point.y + by
|
||||
if point.x < linePoints[1].x and pointY < linePoints[1].y then
|
||||
return true, linePoints[1]
|
||||
end
|
||||
for i = 2, #linePoints do
|
||||
local prev = linePoints[i - 1]
|
||||
local next = linePoints[i]
|
||||
if point.x >= prev.x and point.x <= next.x then
|
||||
return not utils.pointUnderLine(point.x, point.y, prev.x, prev.y, next.x, next.y)
|
||||
if not utils.pointUnderLine(point.x, pointY, prev.x, prev.y, next.x, next.y) then
|
||||
return true, prev
|
||||
end
|
||||
end
|
||||
end
|
||||
if point.x > linePoints[#linePoints].x and point.y < linePoints[#linePoints].y then
|
||||
return true
|
||||
if point.x > linePoints[#linePoints].x and pointY < linePoints[#linePoints].y then
|
||||
return true, linePoints[#linePoints]
|
||||
end
|
||||
return false
|
||||
return false, nil
|
||||
end
|
||||
|
||||
--- Returns true only if the point is below the given line.
|
||||
|
|
Loading…
Reference in New Issue