diff --git a/src/bolt-mods/bouncy.lua b/src/bolt-mods/bouncy.lua index e1ca20b..d4d6d76 100644 --- a/src/bolt-mods/bouncy.lua +++ b/src/bolt-mods/bouncy.lua @@ -2,7 +2,7 @@ local BouncyMod = {} BouncyMod.dont_die_on_touch_obstacle = true -function BouncyMod.on_shoot(bolt) +function BouncyMod.on_bolt_shoot(bolt) bolt.collider:setRestitution(1) end diff --git a/src/bolt-mods/grow.lua b/src/bolt-mods/grow.lua new file mode 100644 index 0000000..9a9f934 --- /dev/null +++ b/src/bolt-mods/grow.lua @@ -0,0 +1,24 @@ +local GrowthMod = {} + +local GROW_SPEED = 1 +local BOLT_SIZE = 4 + +function GrowthMod.on_bolt_create(bolt, player) +end + +function GrowthMod.on_bolt_shoot(bolt, player) + local attr = bolt.attributes + attr.growth = attr.growth or 0 + bolt.scale = 1 + attr.growth +end + +function GrowthMod.on_bolt_update(bolt, dt) + if bolt.dead then return end + local attr = bolt.attributes + + attr.growth = attr.growth + GROW_SPEED * dt + bolt.scale = 1 + attr.growth + bolt.collider:setRadius(BOLT_SIZE * bolt.scale) +end + +return GrowthMod diff --git a/src/bolt-mods/init.lua b/src/bolt-mods/init.lua index ebfdc9b..c0b3c32 100644 --- a/src/bolt-mods/init.lua +++ b/src/bolt-mods/init.lua @@ -2,13 +2,15 @@ local mods = { bouncy = require(... .. ".bouncy"), speed_bounce = require(... .. ".speed-bounce"), - retain_speed = require(... .. ".retain-speed") + retain_speed = require(... .. ".retain-speed"), + grow = require(... .. ".grow"), } local order = { mods.bouncy, mods.speed_bounce, - mods.retain_speed + mods.retain_speed, + mods.grow } local priority = {} diff --git a/src/bolt-mods/retain-speed.lua b/src/bolt-mods/retain-speed.lua index 4df45ce..73a4881 100644 --- a/src/bolt-mods/retain-speed.lua +++ b/src/bolt-mods/retain-speed.lua @@ -2,7 +2,7 @@ local RetainSpeedMod = {} local MIN_LIFETIME = 0.1 -function RetainSpeedMod.on_shoot(bolt, player) +function RetainSpeedMod.on_bolt_shoot(bolt, player) local attr = bolt.attributes if not attr.max_vel then return end @@ -15,7 +15,7 @@ function RetainSpeedMod.on_shoot(bolt, player) bolt.collider:setLinearVelocity(aim_dir.x * new_velocity, aim_dir.y * new_velocity) end -function RetainSpeedMod.on_update(bolt) +function RetainSpeedMod.on_bolt_update(bolt) if bolt.dead then return end local attr = bolt.attributes diff --git a/src/bolt-mods/speed-bounce.lua b/src/bolt-mods/speed-bounce.lua index 6e58f9e..4a7a78d 100644 --- a/src/bolt-mods/speed-bounce.lua +++ b/src/bolt-mods/speed-bounce.lua @@ -2,7 +2,7 @@ local SpeedBounceMod = {} local SPEEDUP_AMOUNT = 0.2 -function SpeedBounceMod.on_shoot(bolt) +function SpeedBounceMod.on_bolt_shoot(bolt) bolt.max_lifetime = bolt.max_lifetime / 2 bolt.collider:setRestitution(1 + SPEEDUP_AMOUNT) end diff --git a/src/bolt.lua b/src/bolt.lua new file mode 100644 index 0000000..4848abe --- /dev/null +++ b/src/bolt.lua @@ -0,0 +1,113 @@ +local Bolt = {} +local Vec = require("lib.brinevector") +local rgb = require("helpers.rgb") + +Bolt.__index = Bolt + +Bolt.DEFAULT_RADIUS = 4 +Bolt.DEFAULT_DURATION = 2.5 + +local ACTIVE_BOLT_COLOR = rgb(240, 20, 20) +local INACTIVE_BOLT_COLOR = rgb(200, 100, 100) + +function Bolt.new(bf_world) + local bolt = setmetatable({}, Bolt) + bolt.pos = Vec() + bolt.vel = Vec() + bolt.restitution = 0.1 + bolt.scale = 1 + bolt.max_lifetime = Bolt.DEFAULT_DURATION + bolt.active = false + bolt.owner = nil + bolt.pickupable = true + + bolt.bf_world = bf_world + return bolt +end + +function Bolt:update_restitution(restitution) + self.restitution = restitution + if self.collider then + self.collider:setRestitution(restitution) + end +end + +function Bolt:get_size() + return Bolt.DEFAULT_RADIUS * self.scale +end + +function Bolt:update_scale(scale) + self.scale = scale + if self.collider then + self.collider:setRadius(self:get_size()) + end +end + +function Bolt:set_collisions(enabled) + if enabled and not self.collider then + self.collider = self.bf_world:newCollider("Circle", { + self.pos.x, self.pos.y, self:get_size() + }) + self.collider:setLinearVelocity(self.vel.x, self.vel.y) + self.collider:setRestitution(self.restitution) + self.collider.fixture:setUserData("bolt") + end + + if self.collider then + self.collider:setActive(enabled) + end +end + +function Bolt:draw_active() + love.graphics.setColor(ACTIVE_BOLT_COLOR) + love.graphics.circle("line", self.pos.x, self.pos.y, self:get_size()) +end + +function Bolt:update_velocity(x, y) + self.vel.x = x + self.vel.y = y + + if self.collider then + self.collider:setLinearVelocity(x, y) + end +end + +function Bolt:update_position(x, y) + self.pos.x = x + self.pos.y = y + + if self.collider then + self.collider:setX(x) + self.collider:setY(y) + end +end + +function Bolt:update_physics(dt) + if self.collider then + self.pos.x = self.collider:getX() + self.pos.y = self.collider:getY() + + local velx, vely = self.collider:getLinearVelocity() + self.vel.x = velx + self.vel.y = vely + else + self.pos.x = self.pos.x + self.vel.x * dt + self.pos.y = self.pos.y + self.vel.y * dt + end +end + + +function Bolt:draw_inactive() + love.graphics.setColor(INACTIVE_BOLT_COLOR) + love.graphics.circle("line", self.pos.x, self.pos.y, self:get_size()) +end + +function Bolt:draw() + if self.active then + self:draw_active() + else + self:draw_inactive() + end +end + +return Bolt diff --git a/src/controls-manager.lua b/src/controls-manager.lua new file mode 100644 index 0000000..d7f48e1 --- /dev/null +++ b/src/controls-manager.lua @@ -0,0 +1,93 @@ +local ControlsManager = {} +local Vec = require("lib.brinevector") + +local JOYSTICK_DEADZONE = 0.1 + +local joystick_aim_dirs = {} + +local function boolToNum(bool) + return bool and 1 or 0 +end + +local function getDirectionKey(positive, negative) + return boolToNum(love.keyboard.isDown(positive)) - boolToNum(love.keyboard.isDown(negative)) +end + +-- TODO: Is asserting here necessary? It could be a good idead to remove this, because +-- it gets called multiple times per frame +local function assert_controller(ctrl) + local is_controller = type(ctrl) == "userdata" and + ctrl.typeOf and + ctrl:typeOf("Joystick") + assert(is_controller, "Expected joystick object") +end + +function ControlsManager.get_move_dir(player) + local ctrl = player.controls + if not ctrl then return Vec() end + + if ctrl == "keyboard" then + local dirx = getDirectionKey("d", "a") + local diry = getDirectionKey("s", "w") + return Vec(dirx, diry).normalized + else + assert_controller(ctrl) + local dirx = ctrl:getGamepadAxis("leftx") + local diry = ctrl:getGamepadAxis("lefty") + local size = dirx^2 + diry^2 + if size > JOYSTICK_DEADZONE then + return Vec(dirx, diry).normalized + else + return Vec(0, 0) + end + end +end + +function ControlsManager.get_aim_dir(player) + local ctrl = player.controls + if not ctrl then return Vec() end + + if ctrl == "keyboard" then + return (Vec(love.mouse.getPosition()) - player.pos).normalized + else + assert_controller(ctrl) + local dirx = ctrl:getGamepadAxis("rightx") + local diry = ctrl:getGamepadAxis("righty") + local size = dirx^2 + diry^2 + local dir + local id = ctrl:getID() + if size < JOYSTICK_DEADZONE and joystick_aim_dirs[id] then + dir = joystick_aim_dirs[id] + else + dir = Vec(dirx, diry).normalized + joystick_aim_dirs[id] = dir + end + return dir + end +end + +function ControlsManager.is_shoot_down(player) + local ctrl = player.controls + if not ctrl then return false end + + if ctrl == "keyboard" then + return love.keyboard.isDown("space") + else + assert_controller(ctrl) + return ctrl:isGamepadDown("leftshoulder") + end +end + +function ControlsManager.is_reload_down(player) + local ctrl = player.controls + if not ctrl then return false end + + if ctrl == "keyboard" then + return love.keyboard.isDown("r") + else + assert_controller(ctrl) + return ctrl:isGamepadDown("rightshoulder") + end +end + +return ControlsManager diff --git a/src/lib/breezefield/world.lua b/src/lib/breezefield/world.lua index 506121e..b1c64bf 100644 --- a/src/lib/breezefield/world.lua +++ b/src/lib/breezefield/world.lua @@ -92,7 +92,7 @@ function World:draw(alpha, draw_over) for _, c in pairs(self.colliders) do c:draw(alpha) if draw_over then - c:__draw__() + c:__draw__() end end end @@ -215,23 +215,25 @@ function World:newCollider(collider_type, shape_arguments, table_to_use) local _collider_type = COLLIDER_TYPES[collider_type:upper()] assert(_collider_type ~= nil, "unknown collider type: "..collider_type) collider_type = _collider_type + local shape if collider_type == 'Circle' then local x, y, r = unpack(shape_arguments) o.body = lp.newBody(self._world, x, y, "dynamic") - o.shape = lp.newCircleShape(r) + shape = lp.newCircleShape(r) elseif collider_type == "Rectangle" then local x, y, w, h = unpack(shape_arguments) o.body = lp.newBody(self._world, x, y, "dynamic") - o.shape = lp.newRectangleShape(w, h) + shape = lp.newRectangleShape(w, h) collider_type = "Polygon" else o.body = lp.newBody(self._world, 0, 0, "dynamic") - o.shape = lp['new'..collider_type..'Shape'](unpack(shape_arguments)) + shape = lp['new'..collider_type..'Shape'](unpack(shape_arguments)) end o.collider_type = collider_type - o.fixture = lp.newFixture(o.body, o.shape, 1) + o.fixture = lp.newFixture(o.body, shape, 1) + o.shape = o.fixture:getShape() o.fixture:setUserData(o) set_funcs(o, o.body) diff --git a/src/main.lua b/src/main.lua index cd811a8..2ad784e 100644 --- a/src/main.lua +++ b/src/main.lua @@ -2,8 +2,6 @@ local Gamestate = require("lib.hump.gamestate") local ScreenScaler = require("screen-scaler") pprint = require("lib.pprint") -PLAYER_SIZE = 10 - -- 640 x 360 ScreenScaler.setVirtualDimensions(16 * 40, 9 * 40) diff --git a/src/player.lua b/src/player.lua index a21983f..f02ff5c 100644 --- a/src/player.lua +++ b/src/player.lua @@ -4,89 +4,22 @@ local Player = {} Player.__index = Player Player.MAX_HEALTH = 3 +Player.PLAYER_SIZE = 10 -function Player:new(controls, collision_world, pos) +function Player.new(ctrl, collision_world, pos) local player = setmetatable({}, Player) + player.controls = ctrl player.pos = pos player.vel = Vec() - player.controls = controls - player.collider = collision_world:newCollider("Circle", { pos.x, pos.y, PLAYER_SIZE }) - player.pickedup_bolt_speed = nil player.shoot_cooldown = nil player.health = Player.MAX_HEALTH - player.has_bolt = true + player.held_bolt = true player.bolt_mods = {} - if controls == "joystick" then - local joysticks = love.joystick.getJoysticks() - if not joysticks[1] then - print("no joystick connected") - end - player.joystick = joysticks[1] - end + player.collider = collision_world:newCollider("Circle", { pos.x, pos.y, Player.PLAYER_SIZE }) + player.collider.fixture:setUserData("player") + return player end -local function boolToNum(bool) - return bool and 1 or 0 -end - -local function getDirectionKey(positive, negative) - return boolToNum(love.keyboard.isDown(positive)) - boolToNum(love.keyboard.isDown(negative)) -end - -function Player:get_move_dir() - if self.controls == "keyboard" then - local dirx = getDirectionKey("d", "a") - local diry = getDirectionKey("s", "w") - return Vec(dirx, diry).normalized - elseif self.controls == "joystick" and self.joystick then - local dirx = self.joystick:getGamepadAxis("leftx") - local diry = self.joystick:getGamepadAxis("lefty") - local size = dirx^2 + diry^2 - if size > 0.1 then - return Vec(dirx, diry).normalized - else - return Vec(0, 0) - end - else - return Vec(0, 0) - end -end - -function Player:get_aim_dir() - if self.controls == "keyboard" then - return (Vec(love.mouse.getPosition()) - self.pos).normalized - elseif self.controls == "joystick" and self.joystick then - local dirx = self.joystick:getGamepadAxis("rightx") - local diry = self.joystick:getGamepadAxis("righty") - local dir = Vec(dirx, diry).normalized - local size = dirx^2 + diry^2 - if size < 0.1 and self.last_aim_dir then - dir = self.last_aim_dir - end - self.last_aim_dir = dir - return dir - else - return Vec(0, 0) - end -end - -function Player:pressed_shoot() - if self.controls == "keyboard" then - return love.keyboard.isDown("space") - elseif self.controls == "joystick" and self.joystick then - return self.joystick:isGamepadDown("leftshoulder") - end -end - -function Player:pressed_retrieve_bolt() - if self.controls == "keyboard" then - return love.keyboard.isDown("r") - elseif self.controls == "joystick" and self.joystick then - return false - -- return self.joystick:isGamepadDown("leftshoulder") - end -end - return Player diff --git a/src/states/main.lua b/src/states/main.lua index c080ab5..b3fbea4 100644 --- a/src/states/main.lua +++ b/src/states/main.lua @@ -1,24 +1,261 @@ local MainState = {} local Player = require("player") +local Bolt = require("bolt") +local lerp = require("lib.lume").lerp +local rgb = require("helpers.rgb") local Vec = require("lib.brinevector") local bf = require("lib.breezefield") -local rgb = require("helpers.rgb") -local lerp = require("lib.lume").lerp -local BoltMods = require("bolt-mods") +local ControlsManager = require("controls-manager") -local BOLT_DIST_FROM_PLAYER = PLAYER_SIZE*2 -local BOLT_SIZE = 4 +-- TODO: Use hump.signal, for emitting events to bolt mods + +-- TODO: Mod idea: Experiment making bolts held by player interact with environment? +-- Like the hammer from "Getting Over It" + +-- TODO: Mod idea: Bolts have large recoil that you can use to move around quickly + +-- TODO: Should bolts be allowed to be reloaded early? + +-- TODO: Should a player be able to pickup enemy bolts? + +-- TODO: Somehow display bolt shoot cooldown + +-- TODO: Can a bolt mod have multiple variants/tiers + +-- TODO: If you have a multiples of the same mod, should they stack? local DRAW_COLLIDERS = false -local BOLT_DURATION = 2.5 local SHOOT_COOLDOWN = 1 local FULL_HEALTH_COLOR = rgb(20, 200, 20) local EMPTY_HEALTH_COLOR = rgb(180, 20, 20) -local ACTIVE_BOLT_COLOR = rgb(240, 20, 20) -local INACTIVE_BOLT_COLOR = rgb(200, 100, 100) +local function get_hold_distance(bolt) + return Player.PLAYER_SIZE + bolt:get_size() + 8 +end + +local function remove_by_value(array, value) + for i, v in ipairs(array) do + if v == value then + table.remove(array, i) + return + end + end +end + +function MainState:stop_match() + while #self.bolts > 0 do + self:destroy_bolt(self.bolts[1]) + end + for _, player in ipairs(self.players) do + self:destroy_player(player) + end +end + +function MainState:start_match() + self:stop_match() + + self:create_player("keyboard", Vec(60, 80)) + self:create_player(nil, Vec(600, 300)) +end + +function MainState:destroy_player(player) + remove_by_value(self.players, player) + self.world:removeCollider(player.collider) +end + +function MainState:destroy_bolt(bolt) + remove_by_value(self.bolts, bolt) + if bolt.collider then + self.world:removeCollider(bolt.collider) + end +end + +local function sort_bolt_mods(bolt_mods) + table.sort(bolt_mods, function(a, b) + return BoltMods.priority[a] < BoltMods.priority[b] + end) +end + +function MainState:create_player(controls, pos) + local player = Player.new(controls, self.world, pos) + player.bolt_mods = { + -- BoltMods.mods.growth, + -- BoltMods.mods.speed_bounce, + -- BoltMods.mods.bouncy, + -- BoltMods.mods.retain_speed, + } + sort_bolt_mods(player.bolt_mods) + player.held_bolt = self:create_bolt() + player.held_bolt.owner = player + table.insert(self.players, player) +end + +local function lerp_color(a, b, t) + return lerp(a[1], b[1], t), lerp(a[2], b[2], t), lerp(a[3], b[3], t) +end + +function MainState:create_bolt() + local bolt = Bolt.new(self.world) + table.insert(self.bolts, bolt) + return bolt +end + +function MainState:find_bolt_by_player(player) + for _, bolt in ipairs(self.bolts) do + if bolt.owner == player then + return bolt + end + end +end + +function MainState:player_shoot_bolt(player) + if not player.held_bolt then return end + + local now = love.timer.getTime() + if player.last_shot_time then + if now - player.last_shot_time < SHOOT_COOLDOWN then return end + end + + local bolt = player.held_bolt + local aim_dir = ControlsManager.get_aim_dir(player) + local DEFAULT_VELOCITY = 500 + + bolt.shot_at = now + bolt.active = true + bolt:set_collisions(true) + bolt:update_velocity(aim_dir.x * DEFAULT_VELOCITY, aim_dir.y * DEFAULT_VELOCITY) + + player.last_shot_time = now + player.held_bolt = nil + + -- TODO: + + --[[ + for _, mod in ipairs(player.bolt_mods) do + if mod.on_shoot then + mod.on_shoot(bolt, player) + end + end + + if bolt.scale ~= 1 then + bolt.collider:setRadius(BOLT_SIZE * bolt.scale) + end + ]]-- +end + +function MainState:player_reload_bolt(player) + if player.held_bolt then return end + + local bolt = self:find_bolt_by_player(player) + if not bolt then return end + + self:destroy_bolt(bolt) + player.held_bolt = self:create_bolt() + player.held_bolt.owner = player +end + +function MainState:is_bolt_being_held(bolt) + for _, player in ipairs(self.players) do + if player.held_bolt == bolt then + return true + end + end + return false +end + +function MainState:pickup_nearby_bolt(player) + local nearest_bolt, nearest_dist + do -- find nearest pickupable bolt + for _, bolt in ipairs(self.bolts) do + if not bolt.active and bolt.pickupable and not self:is_bolt_being_held(bolt) then + local dist = (bolt.pos - player.pos).length + if not nearest_bolt or dist < nearest_dist then + nearest_bolt = bolt + nearest_dist = dist + end + end + end + end + + if not nearest_bolt then return end + + if nearest_dist < get_hold_distance(nearest_bolt) then + if player.held_bolt then + self:destroy_bolt(player.held_bolt) + end + player.held_bolt = nearest_bolt + nearest_bolt:set_collisions(false) + end +end + +function MainState:get_bolt_by_collider(collider) + for _, bolt in ipairs(self.bolts) do + if bolt.collider and bolt.collider.fixture == collider then + return bolt + end + end +end + +function MainState:get_player_by_collider(collider) + for _, player in ipairs(self.players) do + if player.collider.fixture == collider then + return player + end + end +end + +function MainState:on_bolt_touch_obstacle(bolt) + local should_stop = true + -- TODO: + + --[[ + local player = bolt.owner + for _, mod in ipairs(player.bolt_mods) do + -- if mod.on_touch_obstacle then + -- mod.on_touch_obstacle(bolt, player) + -- end + if mod.dont_stop_on_touch_obstacle then + should_stop = false + end + end + ]]-- + + if should_stop then + bolt.active = false + end +end + +function MainState:on_player_touch_bolt(player, bolt) + player.health = player.health - 1 + self:destroy_bolt(bolt) + + local shot_by = bolt.owner + shot_by.held_bolt = self:create_bolt() + shot_by.held_bolt.owner = shot_by +end + +function MainState:on_begin_contact(a, b) + local a_data = a:getUserData() + local b_data = b:getUserData() + + -- Make ordering consistent + if a_data < b_data then + local c = a + local c_data = a_data + a = b + a_data = b_data + b = c + b_data = c_data + end + + if a_data == "player" and b_data == "bolt" then + self:on_player_touch_bolt(self:get_player_by_collider(a), self:get_bolt_by_collider(b)) + elseif a_data == "obstacle" and b_data == "bolt" then + self:on_bolt_touch_obstacle(self:get_bolt_by_collider(b)) + end +end function MainState:enter() love.graphics.setNewFont(72) @@ -81,89 +318,56 @@ function MainState:enter() self:start_match() end -function MainState:stop_match() - for _, bolt in ipairs(self.bolts) do - self:destroy_bolt(bolt) - end - for _, player in ipairs(self.players) do - self:destroy_player(player) - end -end - -function MainState:start_match() - self:stop_match() - - self:create_player("keyboard", Vec(60, 80)) - -- self:create_player("joystick", Vec(winw-50, 200)) - self:create_player("joystick", Vec(600, 300)) -end - -function MainState:find_bolt_by_player(player) - for _, bolt in ipairs(self.bolts) do - if bolt.shot_by == player then - return bolt - end - end -end - function MainState:update(dt) + if #self.players == 1 then + -- TODO: Use hump.timer instead of manually keeping track of timers + self.victory_timer = (self.victory_timer or 0) + dt + if self.victory_timer > 1 then + self.victory_timer = nil + self:start_match() + end + return + end + local now = love.timer.getTime() for _, bolt in ipairs(self.bolts) do - for _, mod in ipairs(bolt.shot_by.bolt_mods) do - if mod.on_update then - mod.on_update(bolt, dt) + -- TODO: use hump.signal + -- for _, mod in ipairs(bolt.shot_by.bolt_mods) do + -- if mod.on_update then + -- mod.on_update(bolt, dt) + -- end + -- end + + if bolt.active then + local lifetime = now - bolt.shot_at + if lifetime > bolt.max_lifetime then + bolt.active = false end end - local lifetime = now - bolt.created_at - if lifetime > bolt.max_lifetime then - bolt.dead = true - end - - if bolt.dead then + if not bolt.active then local dampening = 0.75 - local velx, vely = bolt.collider:getLinearVelocity() - bolt.collider:setLinearVelocity(velx * dampening, vely * dampening) + bolt:update_velocity(bolt.vel.x * dampening, bolt.vel.y * dampening) end end for _, player in ipairs(self.players) do - if player.has_bolt and player:pressed_shoot() then - player.last_shot_time = player.last_shot_time or 0 - if now - player.last_shot_time > SHOOT_COOLDOWN then - self:on_player_shoot(player) - player.last_shot_time = now - player.has_bolt = false - end + if ControlsManager.is_shoot_down(player) then + self:player_shoot_bolt(player) end - if not player.has_bolt and player:pressed_retrieve_bolt() then - local bolt = self:find_bolt_by_player(player) - if bolt and bolt.dead then - player.pickup_bolt_attributes = nil - player.has_bolt = true - self:destroy_bolt(bolt) - end + if ControlsManager.is_reload_down(player) then + self:player_reload_bolt(player) end - local move_dir = player:get_move_dir() + local move_dir = ControlsManager.get_move_dir(player) local acc = move_dir * 1700 player.vel = player.vel + acc * dt player.vel = player.vel * 0.8 player.collider:setLinearVelocity(player.vel.x, player.vel.y) - if not player.has_bolt then - for _, bolt in ipairs(self.bolts) do - if bolt.dead then - local distance = (bolt.pos - player.pos).length - if distance < BOLT_DIST_FROM_PLAYER then - player.pickup_bolt_attributes = bolt.attributes - player.has_bolt = true - self:destroy_bolt(bolt) - break - end - end - end + if not player.held_bolt then + self:pickup_nearby_bolt(player) end local was_dead = player.dead @@ -175,12 +379,11 @@ function MainState:update(dt) end end - if #self.players > 1 then + do -- update objects based on physics self.world:update(dt) for _, bolt in ipairs(self.bolts) do - bolt.pos.x = bolt.collider:getX() - bolt.pos.y = bolt.collider:getY() + bolt:update_physics(dt) end for _, player in ipairs(self.players) do @@ -191,169 +394,42 @@ function MainState:update(dt) local velx, vely = collider:getLinearVelocity() player.vel.x = velx player.vel.y = vely - end - else - self.victory_timer = (self.victory_timer or 0) + dt - if self.victory_timer > 1 then - self.victory_timer = nil - self:start_match() + + if player.held_bolt then + local bolt = player.held_bolt + local aim_dir = ControlsManager.get_aim_dir(player) + local hold_dist = get_hold_distance(bolt) + local pos = player.pos + aim_dir * hold_dist + bolt:update_position(pos.x, pos.y) + end end end +end - if love.keyboard.isDown("escape") then +function MainState:keypressed(key) + if key == "escape" then love.event.quit() end end -local function sort_bolt_mods(bolt_mods) - table.sort(bolt_mods, function(a, b) - return BoltMods.priority[a] < BoltMods.priority[b] - end) -end - -function MainState:create_player(controls, pos) - local player = Player:new(controls, self.world, pos) - player.bolt_mods = { - -- BoltMods.mods.speed_bounce, - BoltMods.mods.bouncy, - -- BoltMods.mods.retain_speed, - } - sort_bolt_mods(player.bolt_mods) - player.collider.fixture:setUserData("player") - table.insert(self.players, player) -end - -local function remove_by_value(array, value) - for i, v in ipairs(array) do - if v == value then - table.remove(array, i) - return - end - end -end - -function MainState:destroy_player(player) - remove_by_value(self.players, player) - self.world:removeCollider(player.collider) -end - -function MainState:destroy_bolt(bolt) - remove_by_value(self.bolts, bolt) - self.world:removeCollider(bolt.collider) -end - -function MainState:on_player_shoot(player) - local aim_dir = player:get_aim_dir() - local bolt = { - pos = player.pos + aim_dir * BOLT_DIST_FROM_PLAYER, - created_at = love.timer.getTime(), - shot_by = player, - max_lifetime = BOLT_DURATION, - attributes = player.pickup_bolt_attributes or {} - } - - local DEFAULT_VELOCITY = 500 - bolt.collider = self.world:newCollider("Circle", { bolt.pos.x, bolt.pos.y, BOLT_SIZE }) - bolt.collider:setLinearVelocity(aim_dir.x * DEFAULT_VELOCITY, aim_dir.y * DEFAULT_VELOCITY) - bolt.collider:setRestitution(0.1) - bolt.collider.fixture:setUserData("bolt") - - for _, mod in ipairs(player.bolt_mods) do - if mod.on_shoot then - mod.on_shoot(bolt, player) - end - end - - table.insert(self.bolts, bolt) -end - -function MainState:get_bolt_by_collider(collider) - for _, bolt in ipairs(self.bolts) do - if bolt.collider.fixture == collider then - return bolt - end - end -end - -function MainState:get_player_by_collider(collider) - for _, player in ipairs(self.players) do - if player.collider.fixture == collider then - return player - end - end -end - -function MainState:on_player_touch_bolt(player, bolt) - bolt.shot_by.has_bolt = true - - player.health = player.health - 1 - self:destroy_bolt(bolt) -end - -function MainState:on_bolt_touch_obstacle(bolt) - local should_die = true - local player = bolt.shot_by - for _, mod in ipairs(player.bolt_mods) do - if mod.on_touch_obstacle then - mod.on_touch_obstacle(bolt, player) - end - if mod.dont_die_on_touch_obstacle then - should_die = false - end - end - - if should_die then - bolt.dead = true - end -end - -function MainState:on_begin_contact(a, b) - local a_data = a:getUserData() - local b_data = b:getUserData() - - if a_data == "player" and b_data == "bolt" then - self:on_player_touch_bolt(self:get_player_by_collider(a), self:get_bolt_by_collider(b)) - elseif a_data == "bolt" and b_data == "player" then - self:on_player_touch_bolt(self:get_bolt_by_collider(a), self:get_player_by_collider(b)) - - elseif a_data == "bolt" and b_data == "obstacle" then - self:on_bolt_touch_obstacle(self:get_bolt_by_collider(a)) - elseif a_data == "obstacle" and b_data == "bolt" then - self:on_bolt_touch_obstacle(self:get_bolt_by_collider(b)) - - end -end - -local function lerp_color(a, b, t) - return lerp(a[1], b[1], t), lerp(a[2], b[2], t), lerp(a[3], b[3], t) -end - function MainState:draw() for _, player in ipairs(self.players) do love.graphics.setLineWidth(4) local health_percent = player.health / Player.MAX_HEALTH love.graphics.setColor(lerp_color(EMPTY_HEALTH_COLOR, FULL_HEALTH_COLOR, health_percent)) - love.graphics.circle("fill", player.pos.x, player.pos.y, PLAYER_SIZE) + love.graphics.circle("fill", player.pos.x, player.pos.y, Player.PLAYER_SIZE) love.graphics.setColor(1, 1, 1) - love.graphics.circle("line", player.pos.x, player.pos.y, PLAYER_SIZE) - - if player.has_bolt then - love.graphics.setLineWidth(3) - love.graphics.setColor(ACTIVE_BOLT_COLOR) - local bolt_pos = player.pos + player:get_aim_dir() * BOLT_DIST_FROM_PLAYER - love.graphics.circle("line", bolt_pos.x, bolt_pos.y, BOLT_SIZE) - end + love.graphics.circle("line", player.pos.x, player.pos.y, Player.PLAYER_SIZE) end love.graphics.setLineWidth(3) for _, bolt in ipairs(self.bolts) do - if bolt.dead then - love.graphics.setColor(INACTIVE_BOLT_COLOR) + if bolt.owner and bolt.owner.held_bolt == bolt then + bolt:draw_active() else - love.graphics.setColor(ACTIVE_BOLT_COLOR) + bolt:draw() end - love.graphics.circle("line", bolt.pos.x, bolt.pos.y, BOLT_SIZE) end love.graphics.setLineWidth(3) @@ -366,11 +442,13 @@ function MainState:draw() end end - local cursor_size = 10 - local mx, my = love.mouse.getPosition() - love.graphics.setColor(1, 1, 1) - love.graphics.line(mx-cursor_size, my, mx+cursor_size, my) - love.graphics.line(mx, my-cursor_size, mx, my+cursor_size) + do -- cursor + local cursor_size = 10 + local mx, my = love.mouse.getPosition() + love.graphics.setColor(1, 1, 1) + love.graphics.line(mx-cursor_size, my, mx+cursor_size, my) + love.graphics.line(mx, my-cursor_size, mx, my+cursor_size) + end if DRAW_COLLIDERS then love.graphics.setColor(0.8, 0.1, 0.8)