Add simple main menu (disabled for now)
String names on Logo assets Add dbg.drawLine() - to use when defining outfield boundaries. Allow flipped fielder draws.
This commit is contained in:
parent
e710a79d9c
commit
8943eef73f
|
@ -0,0 +1 @@
|
|||
std = "lua53"
|
|
@ -1,36 +1,28 @@
|
|||
---@alias ActionResult {}
|
||||
|
||||
---@type table<string, ActionResult>
|
||||
-- selene: allow(unscoped_variables)
|
||||
ActionResult = {
|
||||
Succeeded = {},
|
||||
Failed = {},
|
||||
NeedsMoreTime = {},
|
||||
}
|
||||
|
||||
-- selene: allow(unscoped_variables)
|
||||
actionQueue = {
|
||||
---@type ({ action: Action, expireTimeMs: number })[]
|
||||
---@type table<any, { coroutine: thread, expireTimeMs: number }>
|
||||
queue = {},
|
||||
}
|
||||
|
||||
---@alias Action fun(deltaSeconds: number): ActionResult
|
||||
---@alias Action fun(deltaSeconds: number)
|
||||
|
||||
--selene: allow(incorrect_standard_library_use)
|
||||
local close = coroutine.close
|
||||
|
||||
--- Added actions will be called on every runWaiting() update.
|
||||
--- They will continue to be executed until they return Succeeded or Failed instead of NeedsMoreTime.
|
||||
---
|
||||
--- Replaces any existing action with the given name.
|
||||
--- Replaces any existing action with the given id.
|
||||
--- If the initial call of action() doesn't return NeedsMoreTime, this function will not bother adding it to the queue.
|
||||
---@param name string
|
||||
---@param id any
|
||||
---@param maxTimeMs number
|
||||
---@param action Action
|
||||
function actionQueue:upsert(name, maxTimeMs, action)
|
||||
if action(0) ~= ActionResult.NeedsMoreTime then
|
||||
return
|
||||
function actionQueue:upsert(id, maxTimeMs, action)
|
||||
if self.queue[id] then
|
||||
close(self.queue[id].coroutine)
|
||||
end
|
||||
|
||||
self.queue[name] = {
|
||||
action = action,
|
||||
self.queue[id] = {
|
||||
coroutine = coroutine.create(action),
|
||||
expireTimeMs = maxTimeMs + playdate.getCurrentTimeMilliseconds(),
|
||||
}
|
||||
end
|
||||
|
@ -39,10 +31,16 @@ end
|
|||
--- Actions that return NeedsMoreTime will not be removed from the queue unless they have expired.
|
||||
function actionQueue:runWaiting(deltaSeconds)
|
||||
local currentTimeMs = playdate.getCurrentTimeMilliseconds()
|
||||
for name, actionObject in pairs(self.queue) do
|
||||
local result = actionObject.action(deltaSeconds)
|
||||
if result ~= ActionResult.NeedsMoreTime or currentTimeMs > actionObject.expireTimeMs then
|
||||
self.queue[name] = nil
|
||||
|
||||
for id, actionObject in pairs(self.queue) do
|
||||
coroutine.resume(actionObject.coroutine, deltaSeconds)
|
||||
|
||||
if currentTimeMs > actionObject.expireTimeMs then
|
||||
close(actionObject.coroutine)
|
||||
end
|
||||
|
||||
if coroutine.status(actionObject.coroutine) == "dead" then
|
||||
self.queue[id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,9 @@ PlayerFrown = playdate.graphics.image.new("images/game/PlayerFrown.png")
|
|||
GloveHoldingBall = playdate.graphics.image.new("images/game/GloveHoldingBall.png")
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
GameLogo = playdate.graphics.image.new("images/game/GameLogo.png")
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Hat = playdate.graphics.image.new("images/game/Hat.png")
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
|
@ -57,30 +60,31 @@ TinnyBackground = playdate.sound.sampleplayer.new("music/TinnyBackground.wav")
|
|||
Logos = {
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Cats = playdate.graphics.image.new("images/game/logos/Cats.png"),
|
||||
{ name = "Base", image = playdate.graphics.image.new("images/game/logos/Base.png") },
|
||||
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Hearts = playdate.graphics.image.new("images/game/logos/Hearts.png"),
|
||||
{ name = "Cats", image = playdate.graphics.image.new("images/game/logos/Cats.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Checkmarks = playdate.graphics.image.new("images/game/logos/Checkmarks.png"),
|
||||
{ name = "Hearts", image = playdate.graphics.image.new("images/game/logos/Hearts.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Smiles = playdate.graphics.image.new("images/game/logos/Smiles.png"),
|
||||
{ name = "Checkmarks", image = playdate.graphics.image.new("images/game/logos/Checkmarks.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
FingerGuns = playdate.graphics.image.new("images/game/logos/FingerGuns.png"),
|
||||
{ name = "Smiles", image = playdate.graphics.image.new("images/game/logos/Smiles.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Base = playdate.graphics.image.new("images/game/logos/Base.png"),
|
||||
{ name = "FingerGuns", image = playdate.graphics.image.new("images/game/logos/FingerGuns.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Frown = playdate.graphics.image.new("images/game/logos/Frown.png"),
|
||||
{ name = "Frown", image = playdate.graphics.image.new("images/game/logos/Frown.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Arrows = playdate.graphics.image.new("images/game/logos/Arrows.png"),
|
||||
{ name = "Arrows", image = playdate.graphics.image.new("images/game/logos/Arrows.png") },
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Turds = playdate.graphics.image.new("images/game/logos/Turds.png"),
|
||||
{ name = "Turds", image = playdate.graphics.image.new("images/game/logos/Turds.png") },
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
!(function dirLookup(dir, extension, newFunc, sep)
|
||||
!(function dirLookup(dir, extension, newFunc, sep, handle)
|
||||
sep = sep or "\n"
|
||||
handle = handle ~= nil and handle or function(varName, value)
|
||||
return varName .. ' = ' .. value
|
||||
end
|
||||
--Open directory look for files, save data in p. By giving '-type f' as parameter, it returns all files.
|
||||
local p = io.popen('find src/' .. dir .. ' -type f -maxdepth 1')
|
||||
|
||||
|
@ -11,7 +14,7 @@
|
|||
file = file:gsub("src/", "")
|
||||
assetCode = assetCode .. '--selene: allow(unused_variable)\n'
|
||||
assetCode = assetCode .. '--selene: allow(unscoped_variables)\n'
|
||||
assetCode = assetCode .. varName .. ' = ' .. newFunc .. '("' .. file .. '")' .. sep
|
||||
assetCode = assetCode .. handle(varName, newFunc .. '("' .. file .. '")') .. sep
|
||||
end
|
||||
end
|
||||
return assetCode
|
||||
|
@ -27,5 +30,11 @@ end)!!(generatedFileWarning())
|
|||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
Logos = {
|
||||
!!(dirLookup('images/game/logos', 'png', 'playdate.graphics.image.new', ",\n"))
|
||||
--selene: allow(unused_variable)
|
||||
--selene: allow(unscoped_variables)
|
||||
{ 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)
|
||||
return '{ name = "' .. varName .. '", image = ' .. value .. ' }'
|
||||
end))
|
||||
}
|
||||
|
|
10
src/dbg.lua
10
src/dbg.lua
|
@ -38,6 +38,16 @@ function dbg.loadTheBases(br)
|
|||
br.runners[4].nextBase = C.Bases[C.Home]
|
||||
end
|
||||
|
||||
-- selene: allow(unused_variable)
|
||||
---@param points XyPair[]
|
||||
function dbg.drawLine(points)
|
||||
for i = 2, #points do
|
||||
local prev = points[i - 1]
|
||||
local next = points[i]
|
||||
playdate.graphics.drawLine(prev.x, prev.y, next.x, next.y)
|
||||
end
|
||||
end
|
||||
|
||||
if not playdate then
|
||||
return dbg
|
||||
end
|
||||
|
|
|
@ -5,14 +5,14 @@ local GloveOffX, GloveOffY <const> = GloveSizeX / 2, GloveSizeY / 2
|
|||
---@param fielderX number
|
||||
---@param fielderY number
|
||||
---@return boolean isHoldingBall
|
||||
local function drawFielderGlove(ball, fielderX, fielderY)
|
||||
local function drawFielderGlove(ball, fielderX, fielderY, flip)
|
||||
local distanceFromBall = utils.distanceBetweenZ(fielderX, fielderY, 0, ball.x, ball.y, ball.z)
|
||||
local shoulderX, shoulderY = fielderX + 10, fielderY - 5
|
||||
if distanceFromBall > 20 then
|
||||
Glove:draw(shoulderX, shoulderY)
|
||||
Glove:draw(shoulderX, shoulderY, flip)
|
||||
return false
|
||||
else
|
||||
GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY)
|
||||
GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY, flip)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
@ -22,7 +22,7 @@ end
|
|||
---@param x number
|
||||
---@param y number
|
||||
---@return boolean isHoldingBall
|
||||
function drawFielder(playerSprites, ball, x, y)
|
||||
playerSprites.smiling:draw(x, y - 20)
|
||||
function drawFielder(playerSprites, ball, x, y, flip)
|
||||
playerSprites.smiling:draw(x, y - 20, flip)
|
||||
return drawFielderGlove(ball, x, y)
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ function maybeDrawInverted(image, x, y, drawInverted)
|
|||
gfx.setImageDrawMode(drawMode)
|
||||
end
|
||||
|
||||
--- TODO: Custom names on jerseys?
|
||||
---@return SpriteCollection
|
||||
---@param base pd_image
|
||||
---@param isDark boolean
|
||||
|
@ -52,7 +53,20 @@ function buildCollection(base, back, logo, isDark)
|
|||
end
|
||||
|
||||
--selene: allow(unscoped_variables)
|
||||
AwayTeamSprites = buildCollection(DarkPlayerBase, DarkPlayerBack, Logos.Base, true)
|
||||
---@type SpriteCollection
|
||||
AwayTeamSprites = nil
|
||||
|
||||
--selene: allow(unscoped_variables)
|
||||
HomeTeamSprites = buildCollection(LightPlayerBase, LightPlayerBack, Logos.Frown, false)
|
||||
---@type SpriteCollection
|
||||
HomeTeamSprites = nil
|
||||
|
||||
function replaceAwayLogo(logo)
|
||||
AwayTeamSprites = buildCollection(DarkPlayerBase, DarkPlayerBack, logo, true)
|
||||
end
|
||||
|
||||
function replaceHomeLogo(logo)
|
||||
HomeTeamSprites = buildCollection(LightPlayerBase, LightPlayerBack, logo, false)
|
||||
end
|
||||
|
||||
replaceAwayLogo(Logos[1].image)
|
||||
replaceHomeLogo(Logos[2].image)
|
||||
|
|
|
@ -153,11 +153,11 @@ end
|
|||
---@param targetBase Base
|
||||
---@param launchBall LaunchBall
|
||||
---@param throwFlyMs number
|
||||
---@return ActionResult
|
||||
local function userThrowToImpl(field, targetBase, launchBall, throwFlyMs)
|
||||
local function userThrowToCoroutine(field, targetBase, launchBall, throwFlyMs)
|
||||
while true do
|
||||
if field.fielderHoldingBall == nil then
|
||||
return ActionResult.NeedsMoreTime
|
||||
end
|
||||
coroutine.yield()
|
||||
else
|
||||
local closestFielder = utils.getNearestOf(field.fielders, targetBase.x, targetBase.y, function(fielder)
|
||||
return fielder ~= field.fielderHoldingBall -- presumably, this is who will be doing the throwing
|
||||
end)
|
||||
|
@ -165,7 +165,10 @@ local function userThrowToImpl(field, targetBase, launchBall, throwFlyMs)
|
|||
closestFielder.target = targetBase
|
||||
launchBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs)
|
||||
Fielding.markIneligible(field.fielderHoldingBall)
|
||||
return ActionResult.Succeeded
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Buffer in a fielder throw action.
|
||||
|
@ -176,7 +179,7 @@ end
|
|||
function Fielding:userThrowTo(targetBase, launchBall, throwFlyMs)
|
||||
local maxTryTimeMs = 5000
|
||||
actionQueue:upsert("userThrowTo", maxTryTimeMs, function()
|
||||
return userThrowToImpl(self, targetBase, launchBall, throwFlyMs)
|
||||
userThrowToCoroutine(self, targetBase, launchBall, throwFlyMs)
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,123 @@
|
|||
-- selene: allow(unscoped_variables)
|
||||
---@class MainMenu
|
||||
MainMenu = {
|
||||
mainGameUpdateFunction = nil,
|
||||
mainGameInitFunction = nil,
|
||||
}
|
||||
-- selene: allow(shadowing)
|
||||
local gfx = playdate.graphics
|
||||
|
||||
-- selene: allow(shadowing)
|
||||
local StartFont <const> = gfx.font.new("fonts/Roobert-20-Medium.pft")
|
||||
|
||||
--- Take control of playdate.update
|
||||
---@param config MainMenu Function that controls the main gameplay loop.
|
||||
--- Will replace playdate.update when the menu is done.
|
||||
function MainMenu.start(config)
|
||||
MainMenu.mainGameUpdateFunction = config.mainGameUpdateFunction
|
||||
MainMenu.mainGameInitFunction = config.mainGameInitFunction
|
||||
playdate.update = MainMenu.update
|
||||
end
|
||||
|
||||
local function startGame()
|
||||
MainMenu.mainGameInitFunction()
|
||||
playdate.update = MainMenu.mainGameUpdateFunction
|
||||
end
|
||||
|
||||
local function pausingEaser(baseEaser)
|
||||
--- t: elapsedTime
|
||||
--- d: duration
|
||||
return function(t, b, c, d)
|
||||
local percDone = t / d
|
||||
if percDone > 0.9 then
|
||||
t = d
|
||||
elseif percDone < 0.1 then
|
||||
t = 0
|
||||
else
|
||||
t = (percDone - 0.1) * 1.25 * d
|
||||
end
|
||||
return baseEaser(t, b, c, d)
|
||||
end
|
||||
end
|
||||
|
||||
local animatorX = gfx.animator.new(2000, 30, 350, pausingEaser(playdate.easingFunctions.linear))
|
||||
animatorX.repeatCount = -1
|
||||
animatorX.reverses = true
|
||||
|
||||
local animatorY = gfx.animator.new(2000, 60, 200, pausingEaser(utils.easingHill))
|
||||
animatorY.repeatCount = -1
|
||||
animatorY.reverses = true
|
||||
|
||||
local crankStartPos = nil
|
||||
|
||||
---@generic T
|
||||
---@param array T[]
|
||||
---@param crankPosition number
|
||||
---@return T
|
||||
local function arrayElementFromCrank(array, crankPosition)
|
||||
local i = math.ceil(#array * (crankPosition + 0.001) / 360)
|
||||
return array[i]
|
||||
end
|
||||
|
||||
local currentLogo = nil
|
||||
|
||||
local inningCountSelection = 3
|
||||
|
||||
function playdate.upButtonDown()
|
||||
inningCountSelection = inningCountSelection + 1
|
||||
end
|
||||
|
||||
function playdate.downButtonDown()
|
||||
inningCountSelection = math.max(1, inningCountSelection - 1)
|
||||
end
|
||||
|
||||
local itr = 0
|
||||
|
||||
local t = playdate.timer.new(1000)
|
||||
t:reset()
|
||||
function t.updateCallback()
|
||||
itr = itr + 1
|
||||
end
|
||||
|
||||
function MainMenu.update()
|
||||
playdate.timer.updateTimers()
|
||||
crankStartPos = crankStartPos or playdate.getCrankPosition()
|
||||
|
||||
if playdate.getCrankChange() ~= 0 then
|
||||
local crankOffset = (crankStartPos - playdate.getCrankPosition()) % 360
|
||||
currentLogo = arrayElementFromCrank(Logos, crankOffset).image
|
||||
replaceAwayLogo(currentLogo)
|
||||
end
|
||||
|
||||
gfx.clear()
|
||||
if playdate.buttonIsPressed(playdate.kButtonA) then
|
||||
startGame()
|
||||
end
|
||||
|
||||
gfx.drawText(tostring(itr), 200, 120)
|
||||
GameLogo:drawCentered(C.Center.x, 50)
|
||||
|
||||
StartFont:drawTextAligned("Press A to start!", C.Center.x, 140, kTextAlignment.center)
|
||||
gfx.drawTextAligned("with " .. inningCountSelection .. " innings", C.Center.x, 190, kTextAlignment.center)
|
||||
|
||||
local ball = {
|
||||
x = animatorX:currentValue(),
|
||||
y = animatorY:currentValue(),
|
||||
z = 6,
|
||||
size = 6,
|
||||
}
|
||||
|
||||
local ballIsHeld = drawFielder(AwayTeamSprites, ball, 30, 200)
|
||||
ballIsHeld = drawFielder(HomeTeamSprites, ball, 350, 200, playdate.graphics.kImageFlippedX) or ballIsHeld
|
||||
|
||||
-- drawFielder(AwayTeamSprites, { x = 0, y = 0, z = 0 }, ball.x, ball.y)
|
||||
if not ballIsHeld then
|
||||
gfx.setLineWidth(2)
|
||||
|
||||
gfx.setColor(gfx.kColorWhite)
|
||||
gfx.fillCircleAtPoint(ball.x, ball.y, ball.size)
|
||||
|
||||
gfx.setColor(gfx.kColorBlack)
|
||||
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
||||
end
|
||||
end
|
40
src/main.lua
40
src/main.lua
|
@ -27,6 +27,8 @@ import 'draw/player.lua'
|
|||
import 'draw/overlay.lua'
|
||||
import 'draw/fielder.lua'
|
||||
|
||||
import 'main-menu.lua'
|
||||
|
||||
import 'action-queue.lua'
|
||||
import 'announcer.lua'
|
||||
import 'ball.lua'
|
||||
|
@ -37,6 +39,8 @@ import 'graphics.lua'
|
|||
import 'npc.lua'
|
||||
-- stylua: ignore end
|
||||
|
||||
-- TODO: Customizable field structure. E.g. stands and ads etc.
|
||||
|
||||
-- selene: allow(shadowing)
|
||||
local gfx <const>, C <const> = playdate.graphics, C
|
||||
|
||||
|
@ -82,9 +86,9 @@ local secondsSincePitchAllowed = 0
|
|||
|
||||
-- These are only sort-of global state. They are purely graphical,
|
||||
-- but they need to be kept in sync with the rest of the globals.
|
||||
local runnerBlipper = battingTeam == teams.away and AwayTeamBlipper or HomeTeamBlipper
|
||||
local battingTeamSprites = AwayTeamSprites
|
||||
local fieldingTeamSprites = HomeTeamSprites
|
||||
local runnerBlipper
|
||||
local battingTeamSprites
|
||||
local fieldingTeamSprites
|
||||
|
||||
-------------------------
|
||||
-- END OF GLOBAL STATE --
|
||||
|
@ -455,7 +459,7 @@ end
|
|||
|
||||
-- TODO: Swappable update() for main menu, etc.
|
||||
|
||||
function playdate.update()
|
||||
function mainGameUpdate()
|
||||
playdate.timer.updateTimers()
|
||||
gfx.animation.blinker.updateAll()
|
||||
updateGameState()
|
||||
|
@ -517,13 +521,8 @@ function playdate.update()
|
|||
end
|
||||
end
|
||||
|
||||
local function init()
|
||||
playdate.display.setRefreshRate(50)
|
||||
gfx.setBackgroundColor(gfx.kColorWhite)
|
||||
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
||||
local function mainGameInit()
|
||||
fielding:resetFielderPositions(teams.home.benchPosition)
|
||||
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
||||
|
||||
playdate.timer.new(2000, function()
|
||||
launchBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, false)
|
||||
end)
|
||||
|
@ -531,6 +530,27 @@ local function init()
|
|||
BootTune:setFinishCallback(function()
|
||||
TinnyBackground:play()
|
||||
end)
|
||||
battingTeamSprites = AwayTeamSprites
|
||||
fieldingTeamSprites = HomeTeamSprites
|
||||
HomeTeamBlipper = blipper.new(100, HomeTeamSprites.smiling, HomeTeamSprites.lowHat)
|
||||
AwayTeamBlipper = blipper.new(100, AwayTeamSprites.smiling, AwayTeamSprites.lowHat)
|
||||
runnerBlipper = battingTeam == teams.away and AwayTeamBlipper or HomeTeamBlipper
|
||||
end
|
||||
|
||||
local function init()
|
||||
playdate.display.setRefreshRate(50)
|
||||
gfx.setBackgroundColor(gfx.kColorWhite)
|
||||
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
||||
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
||||
|
||||
-- TODO: A lot of stuff ends up hinky here, because animators are ticking from the moment they initialize.
|
||||
-- TODO: Much needs to be redesigned to only init when a game is *actually* starting.
|
||||
-- MainMenu.start({
|
||||
-- mainGameUpdateFunction = mainGameUpdate,
|
||||
-- mainGameInitFunction = mainGameInit,
|
||||
-- })
|
||||
playdate.update = mainGameUpdate
|
||||
mainGameInit()
|
||||
end
|
||||
|
||||
init()
|
||||
|
|
|
@ -164,6 +164,22 @@ function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, li
|
|||
return utils.pointUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2)
|
||||
end
|
||||
|
||||
--- Returns true if the given point is anywhere above the given line, with no upper bound.
|
||||
--- This, if used for home run calculations, would not take into account balls that curve around the foul poles.
|
||||
---@param point XyPair
|
||||
---@param linePoints XyPair[]
|
||||
---@return boolean
|
||||
function utils.pointIsSquarelyAboveLine(point, linePoints)
|
||||
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)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns true only if the point is below the given line.
|
||||
---@return boolean
|
||||
function utils.pointUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2)
|
||||
|
|
Loading…
Reference in New Issue