local Player = {} local data = require("data") local Vec = require("lib.brinevector") local controls = data.controls local AIMED_BOLT_DISTANCE = 15 local BOLT_AMOUNT = 1 local function getDirection(up_key, down_key, left_key, right_key) local dx = (love.keyboard.isDown(right_key) and 1 or 0) - (love.keyboard.isDown(left_key) and 1 or 0) local dy = (love.keyboard.isDown(down_key) and 1 or 0) - (love.keyboard.isDown(up_key) and 1 or 0) return Vec(dx, dy).normalized end Player.required_groups = {"player", "controllable_player", "bolt"} function Player:getMoveDirection() return getDirection( controls.move_up, controls.move_down, controls.move_left, controls.move_right ) end function Player:getAimDirection(player) if self.use_mouse_aim then local ScreenScaler = self.pool:getSystem(require("systems.screen-scaler")) local dir = Vec(ScreenScaler:getMousePosition()) - player.pos return dir.normalized -- local MAX_DIRECTIONS = 8 -- local angle_segment = math.pi*2/MAX_DIRECTIONS -- local new_angle = math.floor(dir.angle/angle_segment+0.5)*angle_segment -- return Vec(math.cos(new_angle), math.sin(new_angle)) else return getDirection( controls.aim_up, controls.aim_down, controls.aim_left, controls.aim_right ) end end function Player:addToGroup(group, e) if group == "player" then e.bolt_cooldown_timer = e.bolt_cooldown_timer or 0 e.aim_dir = e.aim_dir or Vec() e.move_dir = e.move_dir or Vec() end end function Player:update(dt) if love.keyboard.isDown(controls.aim_up, controls.aim_down, controls.aim_left, controls.aim_right) then self.use_mouse_aim = false end -- Update controls for "my" players local move_direction = self:getMoveDirection() for _, e in ipairs(self.pool.groups.controllable_player.entities) do -- Update where they are aiming/looking local aim_direction = self:getAimDirection(e) if e.aim_dir ~= aim_direction then e.aim_dir = aim_direction self.pool:emit("playerAimed", e) end -- Update acceleration to make the player move if e.move_dir ~= move_direction then e.move_dir = move_direction self.pool:emit("playerMoved", e) end -- Check if the player tried to shoot a bolt if love.keyboard.isDown(controls.shoot) then if self:tryShootingBolt(e) then self.pool:emit("playerShot", e) end end end -- Update each player for _, e in ipairs(self.pool.groups.player.entities) do e.acc = e.move_dir * e.speed -- Decrease bolt cooldown timer if e.bolt_cooldown_timer > 0 then e.bolt_cooldown_timer = math.max(0, e.bolt_cooldown_timer - dt) end -- 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 self.pool:emit("storeBolt", e, bolt) end end if e.acc.x ~= 0 or e.acc.y ~= 0 then e.sprite.variant = "walk" e.sprite.wobble = true else e.sprite.variant = "idle" e.sprite.wobble = false end if e.acc.x < 0 then e.sprite.flip = true elseif e.acc.x > 0 then e.sprite.flip = false end if #e.bolts > 0 then 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 * AIMED_BOLT_DISTANCE else bolt.hidden = true end end end end function Player:mousemoved() self.use_mouse_aim = true end function Player:tryShootingBolt(player) if (player.aim_dir.x ~= 0 or player.aim_dir.y ~= 0) and player.bolt_cooldown_timer == 0 and #player.bolts > 0 then player.bolt_cooldown_timer = player.bolt_cooldown self.pool:emit("shootBolt", player) return true end end function Player:shootBolt(player) local bolt = table.remove(player.bolts, 1) if not bolt then return end bolt.pos = player.pos + player.aim_dir * AIMED_BOLT_DISTANCE bolt.vel = player.aim_dir * player.bolt_speed bolt.owner = player self.pool:emit("boltShot", player, bolt) end function Player:storeBolt(player, bolt) bolt.owner = nil table.insert(player.bolts, bolt) end function Player:hitPlayer(player, bolt) self:resetMap() end local function createBolt() return { pos = Vec(), vel = Vec(), acc = Vec(), sprite = { name = "bolt", variant = "idle", }, friction = 0.98, max_speed = 400, } end function Player:resetMap() local map = self.pool:getSystem(require("systems.map")) local spawnpoints = map:listSpawnpoints() for _, bolt in ipairs(self.pool.groups.bolt.entities) do self.pool:removeEntity(bolt) end for i, player in ipairs(self.pool.groups.player.entities) do for _, bolt in ipairs(player.bolts) do self.pool:removeEntity(bolt) end local spawnpoint = spawnpoints[(i - 1) % #spawnpoints + 1] player.pos = spawnpoint player.bolts = {} for _=1, BOLT_AMOUNT do local bolt = self.pool:queue(createBolt()) self.pool:emit("storeBolt", player, bolt) end end end function Player:spawnPlayer(pos) if not pos then local map = self.pool:getSystem(require("systems.map")) local spawnpoints = map:listSpawnpoints() local players = self.pool.groups.player.entities pos = spawnpoints[#players % #spawnpoints + 1] end local player = self.pool:queue{ pos = pos, vel = Vec(0, 0), acc = Vec(), sprite = { name = "player", variant = "walk" }, friction = 0.998, speed = 800, bolt_speed = 2000, bolts = {}, bolt_cooldown = 0.1, collider = {-5, -5, 4, 8} } for _=1, BOLT_AMOUNT do local bolt = self.pool:queue(createBolt()) 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) and #e.bolts > 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 local point_amount = 8 local points = boltSystem:projectTrajectory(pos, vel, point_amount+3) for i=3, point_amount+3-1 do local ghost = data.sprites["bolt-ghost"] local point = points[i] local frame = ghost.variants.default[1] love.graphics.setColor(1, 1, 1, (point_amount-(i-3))/point_amount) love.graphics.draw( frame.image, point.x, point.y, nil, nil, nil, ghost.width/2, ghost.height/2 ) end end end end return Player