From b77c07eef0a645503ee32e40d2da98e979ad7424 Mon Sep 17 00:00:00 2001 From: neckrat Date: Wed, 15 Apr 2026 13:42:33 +0300 Subject: [PATCH] new circleVectors (midpoint circle algorithm) and pathToClosestCharacter function --- lib/character/behaviors/ai.lua | 100 +++++++++++++++++++++++---------- main.lua | 9 ++- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/lib/character/behaviors/ai.lua b/lib/character/behaviors/ai.lua index 4b94d69..6258ea5 100644 --- a/lib/character/behaviors/ai.lua +++ b/lib/character/behaviors/ai.lua @@ -25,45 +25,85 @@ local function closestCharacter(char) return charTarget end ---- Возвращает все точки в радиусе в виде векторов +-- --- Возвращает все точки в радиусе в виде векторов (должен по крайней мере) +-- --- @param radius integer +-- --- @param center Vec3 +-- --- @return Vec3[] +-- local function circleVectors(center, radius) +-- local vecs = {} +-- local res = {} +-- for t = 0, 2 * math.pi, EPSILON do +-- local x = math.cos(t) * radius + center.x +-- local y = math.sin(t) * radius + center.y +-- table.insert(vecs, Vec3 { math.floor(x), math.floor(y) }) +-- end +-- for _, v in pairs(vecs) do +-- local i = 1 +-- while i <= #res and (res[i].x ~= v.x or res[i].y ~= v.y) do +-- i = i + 1 +-- end +-- if i == #res + 1 or #res == 0 then +-- table.insert(res, v) +-- print('[AI]: circle vecs:', v) +-- end +-- end +-- return res +-- end + +--- Возвращает все точки в радиусе в виде векторов (должен по крайней мере) --- @param radius integer -local function circleVectors(radius) - local cam = Tree.level.camera - local vecs = {} - for t = 0, 2 * math.pi, EPSILON do - local x = math.sin(t) * radius - local y = math.cos(t) * radius - vecs[cam:toWorldPosition(Vec3 { x, y })] = true +--- @param center Vec3 +--- @return Vec3[] +local function circleVectors(center, radius) + local dx, dy, err = radius, 0, 1 - radius + local vecs, res = {}, {} + while dx >= dy do + table.insert(vecs, Vec3 { center.x + dx, center.y + dy }) + table.insert(vecs, Vec3 { center.x - dx, center.y + dy }) + table.insert(vecs, Vec3 { center.x + dx, center.y - dy }) + table.insert(vecs, Vec3 { center.x - dx, center.y - dy }) + table.insert(vecs, Vec3 { center.x + dy, center.y + dx }) + table.insert(vecs, Vec3 { center.x - dy, center.y + dx }) + table.insert(vecs, Vec3 { center.x + dy, center.y - dx }) + table.insert(vecs, Vec3 { center.x - dy, center.y - dx }) + dy = dy + 1 + if err < 0 then + err = err + 2 * dy + 1 + else + dx, err = dx - 1, err + 2 * (dy - dx) + 1 + end end - return utils.keys(vecs) + for _, v in pairs(vecs) do + local i = 1 + while i <= #res and (res[i].x ~= v.x or res[i].y ~= v.y) do + i = i + 1 + end + if i == #res + 1 or #res == 0 then + table.insert(res, v) + print('[AI]: circle vecs:', v) + end + end + return vecs end --- @param owner Character ---- @param space integer здесь мы должны сами определять, сколько должны не доходить до персонажа (1 <= n) +--- @param radius integer здесь мы должны сами определять, сколько должны не доходить до персонажа (1 <= n) --- @return Vec3|nil -local function pathToClosestCharacter(owner, space) +local function pathToClosestCharacter(owner, radius) local charTarget = closestCharacter(owner) local targetPosition, ownerPosition = charTarget:has(Tree.behaviors.positioned), owner:has(Tree.behaviors.positioned) if not targetPosition or not ownerPosition then return end - local target = Vec3 {} - print(ownerPosition.position, targetPosition.position) - local path = pf(ownerPosition.position, targetPosition.position) - for c in path:values() do - print(c) + + local circleVecs = circleVectors(targetPosition.position, radius) + local target = circleVecs[#circleVecs] + local path = pf(ownerPosition.position, target) + for i, c in ipairs(circleVecs) do + local newPath = pf(ownerPosition.position, c) + if newPath:size() < path:size() then + path = newPath + target = c + end end - print(path) - space = math.min(space, path:size()) - print(space, path:size()) - for _ = 0, space - 1 do - path:pop_back() - end - if path:size() ~= 0 then - target = path:pop_back() - else - target = ownerPosition.position - end - print(target, targetPosition.position) - --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти return target end @@ -72,7 +112,7 @@ local aiNature = { ["dev_warrior"] = function(self) return function(callback) -- почему так, описано в Task self.owner:try(Tree.behaviors.spellcaster, function(spellB) - self.target = pathToClosestCharacter(self.owner, 2) + self.target = pathToClosestCharacter(self.owner, 1) local task1 = spellB.spellbook[1]:cast(self.owner, self.target) if task1 then task1( diff --git a/main.lua b/main.lua index c9d6422..e0e5446 100644 --- a/main.lua +++ b/main.lua @@ -41,7 +41,7 @@ function love.load() :addBehavior { Tree.behaviors.residentsleeper.new(), Tree.behaviors.stats.new(nil, nil, 3), - Tree.behaviors.positioned.new(Vec3 { 7, 1 }), + Tree.behaviors.positioned.new(Vec3 { 7, 2 }), Tree.behaviors.tiled.new(), Tree.behaviors.sprite.new(Tree.assets.files.sprites.character), Tree.behaviors.shadowcaster.new(), @@ -128,9 +128,14 @@ function love.draw() love.graphics.setColor(1, 1, 1) love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("small")) + local mousePosX, mousePosY = love.mouse.getPosition() + local mousePos = Tree.level.camera:toWorldPosition(Vec3 { mousePosX, mousePosY }):floor() local stats = "fps: " .. love.timer.getFPS() .. - " lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000) + " lt: " .. lt .. + " dt: " .. dt .. + " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000) .. + " mouse pos: " .. tostring(mousePos) love.graphics.print(stats, 10, 10) local t2 = love.timer.getTime()