refactor bolt logic
This commit is contained in:
parent
96d4d1f7d3
commit
40834bfa87
@ -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
|
||||
|
||||
|
24
src/bolt-mods/grow.lua
Normal file
24
src/bolt-mods/grow.lua
Normal file
@ -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
|
@ -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 = {}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
113
src/bolt.lua
Normal file
113
src/bolt.lua
Normal file
@ -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
|
93
src/controls-manager.lua
Normal file
93
src/controls-manager.lua
Normal file
@ -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
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user