From 088156f8800c15ea19c6c87697c7f1179aca017b Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Fri, 5 Aug 2022 22:16:25 +0000 Subject: [PATCH] add bolt projections --- .luarc.json | 3 +- src/lib/bump.lua | 12 ++++- src/states/main.lua | 1 + src/systems/bolt.lua | 90 +++++++++++++++++++++++++++++++++++++ src/systems/multiplayer.lua | 2 +- src/systems/physics.lua | 50 ++++++++++++++++++++- src/systems/player.lua | 41 +++++++++-------- 7 files changed, 172 insertions(+), 27 deletions(-) create mode 100644 src/systems/bolt.lua diff --git a/.luarc.json b/.luarc.json index 7a93d1a..fc3c669 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,7 +1,8 @@ { "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "Lua.diagnostics.disable": [ - "param-type-mismatch" + "param-type-mismatch", + "cast-local-type" ], "Lua.runtime.version": "LuaJIT", "Lua.workspace.checkThirdParty": false, diff --git a/src/lib/bump.lua b/src/lib/bump.lua index 6dabca7..af2509e 100644 --- a/src/lib/bump.lua +++ b/src/lib/bump.lua @@ -404,6 +404,7 @@ end local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter) local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2) local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1 + local nx1,ny1 local visited, itemInfo, itemInfoLen = {},{},0 for i=1,len do cell = cells[i] @@ -414,12 +415,19 @@ local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter) rect = self.rects[item] l,t,w,h = rect.x,rect.y,rect.w,rect.h - ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1) + ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1) if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then -- the sorting is according to the t of an infinite line, not the segment tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge) itemInfoLen = itemInfoLen + 1 - itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)} + itemInfo[itemInfoLen] = { + item = item, + ti1 = ti1, + ti2 = ti2, + nx = nx1, + ny = ny1, + weight = min(tii0,tii1) + } end end end diff --git a/src/states/main.lua b/src/states/main.lua index f7f81c0..3d86cc0 100644 --- a/src/states/main.lua +++ b/src/states/main.lua @@ -22,6 +22,7 @@ function MainState:enter(_, host_socket) require("systems.map"), require("systems.sprite"), require("systems.player"), + require("systems.bolt"), require("systems.screen-scaler") } diff --git a/src/systems/bolt.lua b/src/systems/bolt.lua new file mode 100644 index 0000000..e39294f --- /dev/null +++ b/src/systems/bolt.lua @@ -0,0 +1,90 @@ +local Bolt = {} +local Vec = require("lib.brinevector") + +-- BUG: trajectory projection doesn't take into account the bolt collider + +local BOLT_COLLIDER = {-3, -3, 3, 3} + +function Bolt:update(dt) + for _, bolt in ipairs(self.pool.groups.bolt.entities) do + if bolt.vel.length < 20 and not bolt.pickupable then + bolt.pickupable = true + bolt.sprite.variant = "idle" + bolt.vel.x = 0 + bolt.vel.y = 0 + end + end +end + +function Bolt:projectTrajectory(pos, step, count) + local physics = self.pool:getSystem(require("systems.physics")) + local step_size = step.length + local distance = step_size * (count-1) + local key_points = physics:project(pos, step.normalized, distance) + + local trajectory = {} + table.insert(trajectory, pos) + + local current_segment = 1 + local current_distance = -step_size + local segment_direction, segment_size + local last_point = key_points[1] - step + do + local diff = key_points[2] - key_points[1] + segment_size = diff.length + segment_direction = diff.normalized + end + + -- For every point that we want to place along our projected line + for _=1, count do + -- First check if the point about to be placed would be outside current segment + while current_distance+step_size > segment_size do + -- Check if there are any unused segments left + if current_segment < (#key_points-1) then + -- Switch next segment + current_distance = current_distance - segment_size + + current_segment = current_segment + 1 + local diff = key_points[current_segment+1] - key_points[current_segment] + segment_size = diff.length + segment_direction = diff.normalized + + -- Adjust where the next point is going to be placed, so it's correct + local to_key_point = (key_points[current_segment]-last_point).length + last_point = key_points[current_segment] - segment_direction * to_key_point + + -- If there are not segments left, just treat the last segment as infinite + else + segment_size = math.huge + end + end + + -- Place point along segment + local point = last_point + segment_direction * step_size + table.insert(trajectory, point) + current_distance = current_distance + step_size + last_point = point + end + + return trajectory +end + +function Bolt:boltShot(player, bolt) + bolt.pickupable = nil + bolt.collider = BOLT_COLLIDER + bolt.bolt = true + bolt.sprite.variant = "active" + bolt.hidden = nil + self.pool:queue(bolt) +end + +function Bolt:storeBolt(player, bolt) + bolt.pickupable = nil + bolt.collider = nil + bolt.bolt = nil + bolt.sprite.variant = "idle" + bolt.hidden = true + self.pool:queue(bolt) +end + +return Bolt diff --git a/src/systems/multiplayer.lua b/src/systems/multiplayer.lua index 0a9a4fc..8b88f20 100644 --- a/src/systems/multiplayer.lua +++ b/src/systems/multiplayer.lua @@ -116,7 +116,7 @@ function Multiplayer:handlePeerMessage(peer, cmd, ...) local player = self:getPlayerEntityById(id) if player and player.peer_index == peer:index() then - self.pool:emit("tryShootinBolt", player) + self.pool:emit("tryShootingBolt", player) end else print("DEBUG INFORMATION:") diff --git a/src/systems/physics.lua b/src/systems/physics.lua index 8426cef..907b2d0 100644 --- a/src/systems/physics.lua +++ b/src/systems/physics.lua @@ -1,5 +1,6 @@ local Physics = {} local bump = require("lib.bump") +local pprint = require("lib.pprint") local Vec = require("lib.brinevector") -- TODO: Tweak bump world `cellSize` at runtime, when the map switches @@ -7,7 +8,7 @@ local Vec = require("lib.brinevector") function Physics:init() self.bump = bump.newWorld() self.boundCollisionFilter = function(...) - return self:_collisionFilter(...) + return self:collisionFilter(...) end end @@ -35,7 +36,7 @@ function Physics:removeFromGroup(group, e) end end -function Physics:_collisionFilter(entity, other) +function Physics:collisionFilter(entity, other) if entity.hidden then return end if other.hidden then return end @@ -104,4 +105,49 @@ function Physics:update(dt) end end +function Physics:castRay(from, to) + local items = self.bump:querySegmentWithCoords(from.x, from.y, to.x, to.y, function(item) + return not item.vel + end) + for _, item in ipairs(items) do + if item.nx ~= 0 or item.ny ~= 0 then + local pos = Vec(item.x1, item.y1) + local normal = Vec(item.nx, item.ny) + return pos, normal + end + end +end + +function Physics:project(from, direction, distance) + local key_points = {} + table.insert(key_points, from) + + local distance_traveled = 0 + local position = from + -- While the ray hasen't traveled the required amount + while math.abs(distance_traveled - distance) > 0.01 do + -- Check if it collides with anything + local destination = position + direction * (distance-distance_traveled) + local intersect, normal = self:castRay(position, destination) + local new_position = intersect or destination + -- Update how much the ray has moved in total + distance_traveled = distance_traveled + (position - new_position).length + position = new_position + + -- If it collided change its travel direction + if normal then + -- sx and sy and just number for flipped the direction when something + -- bounces. + -- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy + direction.x = direction.x * (1 - 2*math.abs(normal.x)) + direction.y = direction.y * (1 - 2*math.abs(normal.y)) + end + + -- Save this point where it stopped/collided + table.insert(key_points, new_position) + end + + return key_points +end + return Physics diff --git a/src/systems/player.lua b/src/systems/player.lua index 672a772..377333e 100644 --- a/src/systems/player.lua +++ b/src/systems/player.lua @@ -4,7 +4,7 @@ local Vec = require("lib.brinevector") local controls = data.controls -local BOLT_COLLIDER = {-3, -3, 3, 3} +local AIMED_BOLT_DISTANCE = 15 local function getDirection(up_key, down_key, left_key, right_key) local dx = (love.keyboard.isDown(right_key) and 1 or 0) @@ -97,12 +97,7 @@ function Player:update(dt) -- If the player is nearby a non-moving bolt, pick it up for _, bolt in ipairs(self.pool.groups.bolt.entities) do if (e.pos-bolt.pos).length < 20 and bolt.vel.length < 3 then - bolt.collider = nil - bolt.bolt = nil - bolt.sprite.variant = "idle" - bolt.hidden = true - self.pool:queue(bolt) - table.insert(e.bolts, bolt) + self.pool:emit("storeBolt", e, bolt) end end @@ -122,7 +117,7 @@ function Player:update(dt) local bolt = e.bolts[1] if e.aim_dir.x ~= 0 or e.aim_dir.y ~= 0 then bolt.hidden = nil - bolt.pos = e.pos + e.aim_dir * 15 + bolt.pos = e.pos + e.aim_dir * AIMED_BOLT_DISTANCE else bolt.hidden = true end @@ -140,7 +135,7 @@ function Player:tryShootingBolt(player) #player.bolts > 0 then player.bolt_cooldown_timer = player.bolt_cooldown - self:shootBolt(player) + self.pool:emit("shootBolt", player) return true end end @@ -149,21 +144,12 @@ function Player:shootBolt(player) local bolt = table.remove(player.bolts, 1) if not bolt then return end - bolt.pos = player.pos + player.aim_dir * 30 + bolt.pos = player.pos + player.aim_dir * AIMED_BOLT_DISTANCE bolt.vel = player.aim_dir * player.bolt_speed - bolt.bolt = true - bolt.sprite.variant = "active" - bolt.collider = BOLT_COLLIDER - bolt.hidden = nil - self.pool:queue(bolt) + self.pool:emit("boltShot", player, bolt) end function Player:storeBolt(player, bolt) - bolt.collider = nil - bolt.sprite.variant = "idle" - bolt.hidden = true - bolt.bolt = nil - self.pool:queue(bolt) table.insert(player.bolts, bolt) end @@ -197,10 +183,23 @@ function Player:spawnPlayer() friction = 0.98, max_speed = 400, } - self:storeBolt(player, bolt) + self.pool:emit("storeBolt", player, bolt) end return player end +function Player:draw() + for _, e in ipairs(self.pool.groups.controllable_player.entities) do + if e.aim_dir.x ~= 0 or e.aim_dir.y ~= 0 then + local boltSystem = self.pool:getSystem(require("systems.bolt")) + local pos = e.pos + e.aim_dir * AIMED_BOLT_DISTANCE*0 + local vel = (e.aim_dir * e.bolt_speed).normalized * AIMED_BOLT_DISTANCE*1 + for _, position in ipairs(boltSystem:projectTrajectory(pos, vel, 5)) do + love.graphics.circle("line", position.x, position.y, 5) + end + end + end +end + return Player