浏览代码

Merge branch 'master' of github.com:tie372/groupGore

bjorn 11 年之前
父节点
当前提交
60e07a807c

+ 1 - 1
data/buff/bloodlust.lua

@@ -33,7 +33,7 @@ function Bloodlust:update()
     self.owner:heal({amount = self.heal * self.rate})
     self.healTimer = math.round(Bloodlust.rate / tickRate)
     self.timer = self.timer - self.rate
-    if self.timer <= 0 then ctx.buffs:remove(self.owner, 'bloodlust') end
+    if self.timer <= 0 then ctx.buffs:remove(self.owner, self.code) end
   end
 end
 

+ 0 - 22
data/buff/shadowform.lua

@@ -1,22 +0,0 @@
-local Shadowform = {}
-
-Shadowform.name = 'Shadowform'
-Shadowform.code = 'shadowform'
-Shadowform.text = 'Eva is more dangerous but more vulnerable.'
-
-function Shadowform:activate()
-  self.depth = 4
-  if ctx.view then ctx.view:register(self) end
-end
-
-function Shadowform:deactivate()
-  if ctx.view then ctx.view:unregister(self) end
-end
-
-function Shadowform:draw()
-  love.graphics.setColor(0, 0, 0, 100)
-  local x, y = self.owner:drawPosition()
-  love.graphics.circle('fill', x, y, 26)
-end
-
-return Shadowform

+ 0 - 24
data/buff/smokescreen.lua

@@ -1,24 +0,0 @@
-local Smokescreen = {}
-
-----------------
--- Meta
-----------------
-Smokescreen.name = 'Smokescreen'
-Smokescreen.code = 'smokescreen'
-Smokescreen.text = 'This unit is slowed.'
-Smokescreen.hide = false
-
-
-----------------
--- Data
-----------------
-function Smokescreen:activate()
-  self.amount = self.owner.maxSpeed * .4
-  self.owner.maxSpeed = self.owner.maxSpeed - self.amount
-end
-
-function Smokescreen:deactivate()
-  self.owner.maxSpeed = self.owner.maxSpeed + self.amount
-end
-
-return Smokescreen

+ 31 - 0
data/buff/subterfuge.lua

@@ -0,0 +1,31 @@
+local Subterfuge = {}
+
+----------------
+-- Meta
+----------------
+Subterfuge.name = 'Subterfuge'
+Subterfuge.code = 'subterfuge'
+Subterfuge.text = 'Eva is cloaked because she killed someone.'
+Subterfuge.hide = false
+
+----------------
+-- Data
+----------------
+Subterfuge.duration = 1.5
+
+function Subterfuge:activate()
+	self.timer = Subterfuge.duration
+end
+
+function Subterfuge:update()
+	self.owner.cloak = 1
+	self.timer = timer.rot(self.timer, function()
+		ctx.buffs:remove(self.owner, self.code)
+	end)
+end
+
+function Subterfuge:stack()
+	self.timer = self.duration
+end
+
+return Subterfuge

+ 4 - 4
data/class/eva.lua

@@ -6,7 +6,7 @@ Eva.code = 'eva'
 -- Stats
 ----------------
 Eva.health = 180
-Eva.speed  = 195
+Eva.speed  = 225
 
 
 ----------------
@@ -26,10 +26,10 @@ Eva.quote = '???'
 ----------------
 Eva.slots = {}
 
-Eva.slots[1] = data.weapon.knife
-Eva.slots[2] = data.skill.shadowdash
+Eva.slots[1] = data.weapon.dagger
+Eva.slots[2] = data.skill.dusk
 Eva.slots[3] = data.skill.smokescreen
-Eva.slots[4] = data.skill.shadowform
+Eva.slots[4] = data.skill.subterfuge
 Eva.slots[5] = data.skill.backstab
 
 return Eva

+ 4 - 2
data/hud/hudplayers.lua

@@ -6,10 +6,12 @@ local w, h = g.width, g.height
 function HudPlayers:draw()
   g.setFont('aeromatics', h(.02))
   ctx.players:each(function(p)
-    if p.team == purple then g.setColor(190, 160, 220, p.alpha * 255)
-    elseif p.team == orange then g.setColor(240, 160, 140, p.alpha * 255) end
     local vx, vy = math.lerp(ctx.view.prevx, ctx.view.x, tickDelta / tickRate), math.lerp(ctx.view.prevy, ctx.view.y, tickDelta / tickRate)
     local px, py = p:drawPosition()
+    g.setColor(0, 0, 0, 100)
+    g.printCenter(p.username, (px - vx) * ctx.view.scale + 1, ((py - vy) * ctx.view.scale) - 60 + 1)
+    if p.team == purple then g.setColor(190, 160, 220, p.alpha * 255)
+    elseif p.team == orange then g.setColor(240, 160, 140, p.alpha * 255) end
     g.printCenter(p.username, (px - vx) * ctx.view.scale, ((py - vy) * ctx.view.scale) - 60)
 
     if not p.ded then

+ 0 - 0
data/media/graphics/icons/knife.png → data/media/graphics/icons/dagger.png


+ 0 - 0
data/media/graphics/icons/shadowdash.png → data/media/graphics/icons/dusk.png


+ 0 - 0
data/media/graphics/icons/shadowform.png → data/media/graphics/icons/subterfuge.png


+ 14 - 14
data/menu/menulogin.lua

@@ -4,20 +4,18 @@ local g = love.graphics
 local w, h = g.width, g.height
 
 function MenuLogin:load()
-  ctx.ribbon.count = 3
+  ctx.ribbon.count = 2
   ctx.ribbon.margin = h(.1)
 
   ctx.input:clear()
-  ctx.input:addInput('username', username or love.filesystem.read('username') or 'username')
-  ctx.input:addInput('password', 'password')
+  ctx.input:addInput('nickname', username or love.filesystem.read('username') or 'nick')
 end
 
 function MenuLogin:mousepressed(x, y, button)
   local ribbon = ctx.ribbon:test(x, y)
 
-  if ribbon == 1 then ctx.input:focusInput('username')
-  elseif ribbon == 2 then ctx.input:focusInput('password')
-  elseif ribbon == 3 then self:login() end
+  if ribbon == 1 then ctx.input:focusInput('nickname')
+  elseif ribbon == 2 then self:login() end
 end
 
 function MenuLogin:keypressed(key)
@@ -31,19 +29,21 @@ function MenuLogin:draw()
   g.setFont('BebasNeue', h(.065))
   g.setColor(160, 160, 160)
   
-  if input.focused == 'username' then g.setColor(220, 220, 220) else g.setColor(160, 160, 160) end
-  g.printCenter('Username', w(.05), anchor - ctx.ribbon.margin, false, true)
-  g.printCenter(input:val('username'), w(.4), anchor - ctx.ribbon.margin, false, true)
+  if input.focused == 'nickname' then g.setColor(220, 220, 220) else g.setColor(160, 160, 160) end
+  g.printCenter('Nickname', w(.05), anchor - ctx.ribbon.margin / 2, false, true)
+  g.printCenter(input:val('nickname'), w(.4), anchor - ctx.ribbon.margin / 2, false, true)
   
-  if input.focused == 'password' then g.setColor(220, 220, 220) else g.setColor(160, 160, 160) end
+  --[[if input.focused == 'password' then g.setColor(220, 220, 220) else g.setColor(160, 160, 160) end
   g.printCenter('Password', w(.05), anchor, false, true)
-  g.printCenter(string.rep('•', #input:val('password')), w(.4), anchor, false, true)
+  g.printCenter(string.rep('•', #input:val('password')), w(.4), anchor, false, true)]]
   
   love.graphics.setColor(160, 160, 160)
-  g.printCenter('Login', w(.05), anchor + h(.1), false, true)
+  g.printCenter('Enter', w(.05), anchor + ctx.ribbon.margin / 2, false, true)
 end
 
 function MenuLogin:login()
-  username = ctx.input:val('username')
-  ctx:push(ctx.main)
+  username = ctx.input:val('nickname')
+  if #username > 0 then
+    ctx:push(ctx.main)
+  end
 end

+ 36 - 0
data/skill/dusk.lua

@@ -0,0 +1,36 @@
+local Dusk = {}
+
+Dusk.name = 'Dusk'
+Dusk.code = 'dusk'
+Dusk.text = 'Move through the shadows.'
+Dusk.type = 'skill'
+
+Dusk.cooldown = 8
+
+function Dusk:activate(owner)
+  self.timer = 0
+  self.stacks = 2
+end
+
+function Dusk:update(owner)
+  self.timer = timer.rot(self.timer, function()
+  	if self.stacks == 0 then self.stacks = 2 end
+  end)
+end
+
+function Dusk:canFire(owner)
+  return self.timer == 0 and self.stacks > 0
+end
+
+function Dusk:fire(owner, mx, my)
+  ctx.spells:activate(owner.id, data.spell.dusk, mx, my)
+  self.stacks = self.stacks - 1
+  self.timer = self.stacks == 0 and self.cooldown or .15
+end
+
+function Dusk:value(owner)
+	if self.stacks > 0 then return self.timer / .15 end
+  return self.timer / self.cooldown
+end
+
+return Dusk

+ 0 - 51
data/skill/shadowdash.lua

@@ -1,51 +0,0 @@
-local ShadowDash = {}
-
-ShadowDash.name = 'Shadow Dash'
-ShadowDash.code = 'shadowdash'
-ShadowDash.text = 'Shadow Dash'
-ShadowDash.type = 'skill'
-
-ShadowDash.cooldown = 5
-
-function ShadowDash:activate(shadowDash)
-  shadowDash.timer = 0
-  shadowDash.reuse = 0
-end
-
-function ShadowDash:update(shadowDash)
-  shadowDash.timer = timer.rot(shadowDash.timer)
-  shadowDash.reuse = timer.rot(shadowDash.reuse, function()
-    shadowDash.timer = shadowDash.cooldown
-  end)
-end
-
-function ShadowDash:canFire(shadowDash)
-  return shadowDash.timer == 0
-end
-
-function ShadowDash:fire(shadowDash)
-  ctx.spells:activate(self.id, data.spell.shadowdash)
-  if shadowDash.reuse == 0 then
-    shadowDash.timer = .5
-    shadowDash.reuse = 2.5
-  else
-    shadowDash.timer = shadowDash.cooldown
-    shadowDash.reuse = 0
-  end
-end
-
-function ShadowDash:value(shadowDash)
-  if shadowDash.timer ~= 0 then
-    if shadowDash.reuse ~= 0 then return shadowDash.timer / .5 end
-    return shadowDash.timer / shadowDash.cooldown
-  else
-    return shadowDash.reuse / 2
-  end
-end
-
-function ShadowDash:draw(shadowDash)
-  love.graphics.setColor(255, 255, 255, 100)
-  love.graphics.draw(self.class.sprite, self.x + math.dx(data.spell.shadowdash.distance, self.angle), self.y + math.dy(data.spell.shadowdash.distance, self.angle), self.angle, 1, 1, self.class.anchorx, self.class.anchory)
-end
-
-return ShadowDash

+ 0 - 33
data/skill/shadowform.lua

@@ -1,33 +0,0 @@
-local Shadowform = {}
-
-Shadowform.name = 'Shadowform'
-Shadowform.code = 'shadowform'
-Shadowform.text = 'Become one with the shadows.'
-Shadowform.type = 'skill'
-
-Shadowform.cooldown = 9
-
-function Shadowform:activate(shadowform)
-  shadowform.timer = 0
-end
-
-function Shadowform:update(shadowform)
-  shadowform.timer = timer.rot(shadowform.timer, function()
-    ctx.buffs:remove(self, 'shadowform')
-  end)
-end
-
-function Shadowform:canFire(shadowform)
-  return shadowform.timer == 0
-end
-
-function Shadowform:fire(shadowform)
-  ctx.buffs:add(self, 'shadowform')
-  shadowform.timer = shadowform.cooldown
-end
-
-function Shadowform:value(shadowform)
-  return shadowform.timer / shadowform.cooldown
-end
-
-return Shadowform

+ 13 - 12
data/skill/smokescreen.lua

@@ -2,30 +2,31 @@ local Smokescreen = {}
 
 Smokescreen.name = 'Smokescreen'
 Smokescreen.code = 'smokescreen'
-Smokescreen.text = 'Slow and blind enemies.'
+Smokescreen.text = 'Hide under a cover of smoke.'
 Smokescreen.type = 'skill'
 
+Smokescreen.needsMouse = true
 Smokescreen.cooldown = 14
 
-function Smokescreen:activate(smokescreen)
-  smokescreen.timer = 0
+function Smokescreen:activate(owner)
+  self.timer = 0
 end
 
-function Smokescreen:update(smokescreen)
-  smokescreen.timer = timer.rot(smokescreen.timer)
+function Smokescreen:update(owner)
+  self.timer = timer.rot(self.timer)
 end
 
-function Smokescreen:canFire(smokescreen)
-  return smokescreen.timer == 0
+function Smokescreen:canFire(owner)
+  return self.timer == 0
 end
 
-function Smokescreen:fire(smokescreen)
-  ctx.spells:activate(self.id, data.spell.smokescreen)
-  smokescreen.timer = smokescreen.cooldown
+function Smokescreen:fire(owner, mx, my)
+  ctx.spells:activate(owner.id, data.spell.smokescreen, mx, my)
+  self.timer = self.cooldown
 end
 
-function Smokescreen:value(smokescreen)
-  return smokescreen.timer / smokescreen.cooldown
+function Smokescreen:value(owner)
+  return self.timer / self.cooldown
 end
 
 return Smokescreen

+ 22 - 0
data/skill/subterfuge.lua

@@ -0,0 +1,22 @@
+local Subterfuge = {}
+
+Subterfuge.name = 'Subterfuge'
+Subterfuge.code = 'subterfuge'
+Subterfuge.text = 'When you get a kill you cloak.'
+Subterfuge.type = 'passive'
+
+function Subterfuge:activate(owner)
+	ctx.event:on(evtDead, function(data)
+		if data.kill == owner.id then
+			ctx.buffs:add(owner, 'subterfuge')
+		end
+	end)
+end
+
+function Subterfuge:value(owner)
+  local buff = ctx.buffs:get(owner, 'subterfuge')
+  if buff then return buff.timer / buff.duration end
+  return 0
+end
+
+return Subterfuge

+ 30 - 0
data/spell/dagger.lua

@@ -0,0 +1,30 @@
+local Dagger = {}
+Dagger.code = 'dagger'
+Dagger.hp = .5
+
+function Dagger:activate(owner)
+  self.hp = Dagger.hp
+  
+  self.angle = self.owner.angle
+  self.x, self.y = self.owner.x + math.dx(35, self.angle), self.owner.y + math.dy(35, self.angle)
+  self.target = ctx.collision:circleTest(self.x, self.y, 14, {
+    tag = 'player',
+    fn = function(p) return p.team ~= self.owner.team end
+  })
+  
+  local backstab = false
+  if self.target then
+    backstab = math.abs(math.anglediff(self.target.angle, math.direction(self.target.x, self.target.y, self.owner.x, self.owner.y))) > math.pi / 2
+    local damage = data.weapon.dagger.damage
+    if backstab then damage = target.health end
+    ctx.net:emit(evtDamage, {id = self.target.id, amount = damage, from = self.owner.id, tick = tick})
+  end
+  
+  ctx.event:emit('sound.play', {sound = backstab and 'backstab' or 'slash'})
+end
+
+function Dagger:update(owner)
+  self.hp = timer.rot(self.hp, function() ctx.spells:deactivate(self) end)
+end
+
+return Dagger

+ 34 - 0
data/spell/dusk.lua

@@ -0,0 +1,34 @@
+local Dusk = {}
+
+Dusk.code = 'dusk'
+Dusk.distance = 160
+Dusk.speed = Dusk.distance * 10
+
+function Dusk:activate()
+  self.angle = self.owner.angle
+  self.health = Dusk.health
+  ctx.event:emit('sound.play', {sound = 'dash'})
+end
+
+function Dusk:update()
+  self.owner.x = self.owner.x + math.dx(self.speed * tickRate, self.angle)
+  self.owner.y = self.owner.y + math.dy(self.speed * tickRate, self.angle)
+  ctx.event:emit('collision.move', {object = self.owner, x = self.owner.x, y = self.owner.y})
+  ctx.collision:resolve(self.owner)
+  if self.owner.inputs then
+    table.insert(self.owner.inputs, {
+      tick = tick + 1,
+      reposition = {
+        x = self.owner.x,
+        y = self.owner.y
+      }
+    })
+  end
+
+  self.distance = self.distance - (self.speed * tickRate)
+  if self.distance <= 0 then
+    ctx.spells:deactivate(self)
+  end
+end
+
+return Dusk

+ 0 - 43
data/spell/knife.lua

@@ -1,43 +0,0 @@
-local Knife = {}
-Knife.code = 'knife'
-Knife.hp = .5
-
-function Knife:activate()
-  self.hp = Knife.hp
-  
-  self.angle = self.owner.angle
-  self.x, self.y = self.owner.x + math.dx(35, self.angle), self.owner.y + math.dy(35, self.angle)
-  self.target = ctx.collision:circleTest(self.x, self.y, 14, {
-    tag = 'player',
-    fn = function(p) return p.team ~= self.owner.team end
-  })
-  
-  local backstab = false
-  local shadowform = ctx.buffs:get(self.owner, 'shadowform')
-  if self.target then
-    backstab = math.abs(math.anglediff(self.target.angle, math.direction(self.target.x, self.target.y, self.owner.x, self.owner.y))) > math.pi / 2
-    local damage = data.weapon.knife.damage
-    if backstab then
-      local multiplier = 2
-      if shadowform then multiplier = multiplier + (1 - (self.target.health / self.target.maxHealth)) * 2 end
-      damage = damage * multiplier
-    end
-    ctx.net:emit(evtDamage, {id = self.target.id, amount = damage, from = self.owner.id, tick = tick})
-  end
-  
-  ctx.event:emit('sound.play', {sound = backstab and 'backstab' or 'slash'})
-end
-
-function Knife:update()
-  self.hp = timer.rot(self.hp, function() ctx.spells:deactivate(self) end)
-end
-
-function Knife:draw()
-  local alpha = 255 * (self.hp / Knife.hp) * self.owner.alpha
-  love.graphics.setColor(self.target and {0, 255, 0, alpha * (100 / 255)} or {255, 0, 0, alpha * (100 / 255)})
-  love.graphics.circle('fill', self.x, self.y, 12)
-  love.graphics.setColor(self.target and {0, 255, 0, alpha} or {255, 0, 0, alpha})
-  love.graphics.circle('line', self.x, self.y, 12)
-end
-
-return Knife

+ 0 - 29
data/spell/shadowdash.lua

@@ -1,29 +0,0 @@
-local ShadowDash = {}
-ShadowDash.code = 'shadowdash'
-ShadowDash.distance = 160
-ShadowDash.hp = .1
-
-function ShadowDash:activate()
-  self.hp = ShadowDash.hp
-  self.angle = self.owner.angle
-  ctx.event:emit('sound.play', {sound = 'dash'})
-  if ctx.buffs:get(self.owner, 'shadowform') then
-    self.owner.x = self.owner.x + math.dx(self.distance, self.angle)
-    self.owner.y = self.owner.y + math.dy(self.distance, self.angle)
-    ctx.collision:update()
-    ctx.spells:deactivate(self)
-  end
-end
-
-function ShadowDash:update()
-  self.hp = timer.rot(self.hp, function() ctx.spells:deactivate(self) end)
-  self.owner.x = self.owner.x + math.dx(self.distance / ShadowDash.hp * tickRate, self.angle)
-  self.owner.y = self.owner.y + math.dy(self.distance / ShadowDash.hp * tickRate, self.angle)
-  ctx.collision:update()
-end
-
-function ShadowDash:draw()
-  --
-end
-
-return ShadowDash

+ 1 - 1
data/spell/smg.lua

@@ -16,7 +16,7 @@ SMG.activate = function(self)
   dx, dy = data.weapon.smg.tipx, data.weapon.smg.tipy
   self.x = self.x + math.dx(dx, dir) - math.dy(dy, dir)
   self.y = self.y + math.dy(dx, dir) + math.dx(dy, dir)
-  
+
   self.angle = self.owner.angle
   self.angle = self.angle - (data.weapon.smg.spread / 2) + (love.math.random() * data.weapon.smg.spread)
   local hit, dis = ctx.collision:lineTest(self.x, self.y, self.x + math.dx(900, self.angle), self.y + math.dy(900, self.angle), {tag = 'wall', first = true})

+ 9 - 17
data/spell/smokescreen.lua

@@ -1,31 +1,23 @@
-Smokescreen = {}
+local Smokescreen = {}
 
 Smokescreen.code = 'smokescreen'
 Smokescreen.duration = 6
-Smokescreen.radius = 145
+Smokescreen.radius = 160
 Smokescreen.image = data.media.graphics.effects.smoke
 
-function Smokescreen:activate()
+function Smokescreen:activate(mx, my)
   self.timer = self.duration
   self.angle = love.math.random() * math.pi * 2
-  self.x = self.owner.x
-  self.y = self.owner.y
-end
-
-function Smokescreen:deactivate()
-  ctx.players:each(function(p)
-    if ctx.buffs:get(p, 'smokescreen') then ctx.buffs:remove(p, 'smokescreen') end
-  end)
+  self.x, self.y = mx, my
 end
 
 function Smokescreen:update()
-  ctx.players:each(function(p)
-    if p.team ~= self.owner.team and math.distance(self.x, self.y, p.x, p.y) < self.radius then
-      if not ctx.buffs:get(p, 'smokescreen') then ctx.buffs:add(p, 'smokescreen') end
-    else
-      if ctx.buffs:get(p, 'smokescreen') then ctx.buffs:remove(p, 'smokescreen') end
+  if self.owner.cloak < 1 then
+    if math.distance(self.x, self.y, self.owner.x, self.owner.y) < self.radius then
+      self.owner.cloak = math.min(self.owner.cloak + (3 * tickRate), 1)
     end
-  end)
+  end
+
   self.timer = timer.rot(self.timer, function() ctx.spells:deactivate(self) end)
 end
 

+ 32 - 0
data/weapon/dagger.lua

@@ -0,0 +1,32 @@
+local Weapon = {}
+
+Weapon.name = 'Dagger'
+Weapon.code = 'dagger'
+Weapon.text = 'Stabby'
+Weapon.type = 'weapon'
+
+Weapon.damage = 45
+Weapon.cooldown = .8
+
+function Weapon:activate(owner)
+  self.timer = 0
+end
+
+function Weapon:update(owner)
+  self.timer = timer.rot(self.timer)
+end
+
+function Weapon:canFire(owner)
+  return self.timer == 0
+end
+
+function Weapon:fire(owner)
+  ctx.spells:activate(self.id, data.spell.dagger)
+  self.timer = self.cooldown
+end
+
+function Weapon:draw(owner)
+  --
+end
+
+return Weapon

+ 0 - 32
data/weapon/knife.lua

@@ -1,32 +0,0 @@
-local Knife = {}
-
-Knife.name = 'Knife'
-Knife.code = 'knife'
-Knife.text = 'Stabby'
-Knife.type = 'weapon'
-
-Knife.damage = 45
-Knife.cooldown = .8
-
-function Knife:activate(knife)
-  knife.timer = 0
-end
-
-function Knife:update(knife)
-  knife.timer = timer.rot(knife.timer)
-end
-
-function Knife:canFire(knife)
-  return knife.timer == 0
-end
-
-function Knife:fire(knife)
-  ctx.spells:activate(self.id, data.spell.knife)
-  knife.timer = knife.cooldown
-end
-
-function Knife:draw(knife)
-  --
-end
-
-return Knife

+ 3 - 1
lib/core/collision.lua

@@ -63,9 +63,11 @@ function Collision:resolve(obj)
   shape.owner = obj
   obj.shape = shape
   self.hc:update()
-  self.hc:remove(shape)
 
   obj.shape = oldShape
+  obj.shape:moveTo(shape:center())
+
+  self.hc:remove(shape)
 end
 
 function Collision:pointTest(x, y, options)

+ 2 - 2
lib/core/spells.lua

@@ -6,12 +6,12 @@ function Spells:init()
   if ctx.view then ctx.view:register(self) end
 end
 
-function Spells:activate(owner, kind)
+function Spells:activate(owner, kind, ...)
   local s = new(kind)
   s.owner = ctx.players:get(owner)
   self.spells[#self.spells + 1] = s
   s._idx = #self.spells
-  s:activate()
+  s:activate(...)
   return s
 end
 

+ 7 - 7
lib/netserver.lua

@@ -8,16 +8,16 @@ NetServer.signatures[evtSync] = {
   {'id', '4bits'},
   {'tick', '16bits'},
   {'ack', '16bits'},
-  {'x', '12bits'},
-  {'y', '12bits'},
+  {'x', '12bits'}, {'y', '12bits'},
   {'angle', '10bits'},
-  {'health', '10bits'},
-  {'shield', '10bits'},
-  {'weapon', '3bits'},
-  {'skill', '3bits'},
+  {'health', '10bits'}, {'shield', '10bits'},
+  {'weapon', '3bits'}, {'skill', '3bits'},
   delta = {'x', 'y', 'angle', 'health', 'shield', 'weapon', 'skill'}
 }
-NetServer.signatures[evtFire] = {{'id', '4bits'}, {'slot', '3bits'}}
+NetServer.signatures[evtFire] = {
+  {'id', '4bits'}, {'slot', '3bits'}, {'mx', '12bits'}, {'my', '12bits'},
+  delta = {{'mx', 'my'}}
+}
 NetServer.signatures[evtDamage] = {{'id', '4bits'}, {'amount', 'string'}, {'from', '4bits'}}
 NetServer.signatures[evtDead] = {{'id', '4bits'}, {'kill', '4bits'}, {'assists', {{'id', '4bits'}}}}
 NetServer.signatures[evtSpawn] = {{'id', '4bits'}}

+ 17 - 6
lib/player.lua

@@ -25,6 +25,8 @@ Player.collision = {
 -- Core
 ----------------
 function Player:init()
+  self.meta = {__index = self}
+  
   self.id = nil
   self.username = ''
   self.class = nil
@@ -45,6 +47,7 @@ function Player:init()
 
   self.lifesteal = 0
   self.haste = 0
+  self.cloak = 0
 
   self.depth = 0
   self.recoil = 0
@@ -68,13 +71,17 @@ function Player:activate()
   self.depth = -self.id
 end
 
-Player.update = f.empty
+function Player:update()
+  if self.recoil > 0 then self.recoil = math.lerp(self.recoil, 0, math.min(5 * tickRate, 1)) end
+  self.cloak = timer.rot(self.cloak)
+end
 
 function Player:draw()
   local g, c = love.graphics, self.class
-  g.setColor(0, 0, 0, self.alpha * 50)
+  local alpha = self.alpha * (1 - (self.cloak / (self.team == ctx.players:get(ctx.id).team and 2 or 1)))
+  g.setColor(0, 0, 0, alpha * 50)
   g.draw(c.sprite, self.x + 4, self.y + 4, self.angle, 1, 1, c.anchorx, c.anchory)
-  g.setColor(self.team == purple and {190, 160, 200, self.alpha * 255} or {240, 160, 140, self.alpha * 255})
+  g.setColor(self.team == purple and {190, 160, 200, alpha * 255} or {240, 160, 140, alpha * 255})
   g.draw(c.sprite, self.x, self.y, self.angle, 1, 1, c.anchorx, c.anchory)
   f.exe(self.slots[self.weapon].draw, self.slots[self.weapon], self)
   f.exe(self.slots[self.skill].draw, self.slots[self.skill], self)
@@ -85,6 +92,10 @@ end
 -- Behavior
 ----------------
 function Player:move(input)
+  if input.reposition then
+    self.x, self.y = input.reposition.x, input.reposition.y
+  end
+
   local w, a, s, d = input.w, input.a, input.s, input.d
   if not (w or a or s or d) then return end
   
@@ -100,7 +111,7 @@ function Player:move(input)
   local dir = (dx + dy) / 2
   local len = (self.class.speed + self.haste) * tickRate
   self.x, self.y = self.x + math.dx(len, dir), self.y + math.dy(len, dir)
-  
+
   self.x = math.clamp(self.x, 0, ctx.map.width)
   self.y = math.clamp(self.y, 0, ctx.map.height)
 
@@ -135,16 +146,16 @@ function Player:slot(input)
   end
   
   if input.l and weapon:canFire(self) then
+    weapon:fire(self, input.x, input.y)
     ctx.net:emit(evtFire, {id = self.id, slot = self.weapon})
   end
   
   if input.r and skill:canFire(self) then
+    skill:fire(self, input.x, input.y)
     ctx.net:emit(evtFire, {id = self.id, slot = self.skill})
   end
 
   if input.reload then weapon:reload(self) end
-  
-  if self.recoil > 0 then self.recoil = math.lerp(self.recoil, 0, math.min(5 * tickRate, 1)) end
 end
 
 Player.hurt = f.empty

+ 4 - 4
lib/playerdummy.lua

@@ -4,14 +4,14 @@ local function drawTick() return tick - (interp / tickRate) end
 
 function PlayerDummy:activate()
   self.history = {}
-  self.historyMeta = {__index = self}
 
   Player.activate(self)
 end
 
 function PlayerDummy:update()
-  if self.recoil > 0 then self.recoil = math.lerp(self.recoil, 0, math.min(5 * tickRate, 1)) end
   self:slot()
+  
+  Player.update(self)
 end
 
 function PlayerDummy:get(t)
@@ -23,7 +23,7 @@ function PlayerDummy:get(t)
       y = self.y,
       angle = self.angle,
       tick = tick,
-    }, self.historyMeta)
+    }, self.meta)
   end
 
   while self.history[1].tick < tick - 1 / tickRate and #self.history > 2 do
@@ -61,7 +61,7 @@ function PlayerDummy:trace(data)
     y = data.y,
     angle = data.angle,
     tick = data.tick
-  }, self.historyMeta))
+  }, self.meta))
 
   self.x, self.y, self.angle = data.x, data.y, data.angle
   self.health, self.shield = data.health or self.health, data.shield or self.shield

+ 8 - 5
lib/playermain.lua

@@ -1,8 +1,9 @@
 PlayerMain = extend(Player)
 
 function PlayerMain:activate()
-  self.prev = setmetatable({}, {__index = self})
+  self.prev = setmetatable({}, self.meta)
   self.inputs = {}
+  self.displaces = {}
 
   self.alpha = 1
   
@@ -16,7 +17,6 @@ function PlayerMain:activate()
 end
 
 function PlayerMain:get(t)
-  assert(t == tick or t == tick - 1)
   if t == tick then
     return self
   else
@@ -45,7 +45,9 @@ function PlayerMain:update()
     self.heartbeatSound:pause()
   end
  
-  ctx.net:buffer(msgInput, self.inputs[#self.inputs])
+  ctx.net:buffer(msgInput, input)
+
+  Player.update(self)
 end
 
 function PlayerMain:draw()
@@ -61,13 +63,14 @@ function PlayerMain:trace(data)
   while #self.inputs > 0 and self.inputs[1].tick < data.ack + 1 do
     table.remove(self.inputs, 1)
   end
- 
+
   -- Server reconciliation: Apply inputs that occurred after the ack.
   for i = 1, #self.inputs do
     self:move(self.inputs[i])
   end
 
-  ctx.event:emit('collision.move', {object = self, x = self.x, y = self.y})
+  ctx.collision:resolve(self)
+  self.x, self.y = self.shape:center()
 end
 
 function PlayerMain:readInput()

+ 5 - 3
lib/players.lua

@@ -19,9 +19,11 @@ function Players:init()
   end)
   
   ctx.event:on(evtFire, function(data)
-    local p = self:get(data.id)
-    local slot = p.slots[data.slot]
-    slot:fire(p)
+    if ctx.id and data.id ~= ctx.id then
+      local p = self:get(data.id)
+      local slot = p.slots[data.slot]
+      slot:fire(p, data.mx, data.my)
+    end
   end)
   
   ctx.event:on(evtDamage, function(data)

+ 41 - 14
lib/playerserver.lua

@@ -6,14 +6,30 @@ function PlayerServer:activate()
   self.lastHurt = tick
   self.hurtHistory = {}
   self.helpHistory = {}
-  
+
+  self.history = {}
+
   self.ack = tick
 
   Player.activate(self)
 end
 
 function PlayerServer:get(t)
-  return self
+  if not self.history then return self end
+
+  while self.history[1] and self.history[1].tick < tick - 2 / tickRate do
+    table.remove(self.history, 1)
+  end
+
+  if #self.history == 0 then return self end
+
+  if self.history[#self.history].tick < t then return self end
+
+  for i = #self.history, 1, -1 do
+    if self.history[i].tick <= t then return self.history[i] end
+  end
+  
+  return self.history[1]
 end
 
 function PlayerServer:update()
@@ -29,25 +45,30 @@ function PlayerServer:update()
       self:heal({amount = self.maxHealth * percentage * tickRate})
     end
   end
+
+  Player.update(self)
 end
 
 function PlayerServer:trace(data, ping)
-  local rewindTo = data.tick - ((ping / 1000) + interp) / tickRate
   if data.tick > self.ack then
     
     -- Lag compensation
-    --[[local oldPos = {}
+    local target = data.tick - ((ping / 1000) + interp) / tickRate
+    local t1 = math.floor(target)
+    local factor = target - t1
+    local oldPos = {}
     ctx.players:each(function(p)
       if p.id ~= self.id then
         oldPos[p.id] = {p.x, p.y}
-        local lerpd = ctx.players:get(p.id, rewindTo)
-        if lerpd then
-          p.x = lerpd.x
-          p.y = lerpd.y
-          p.shape:moveTo(p.x, p.y)
-        end
+        local s1, s2 = p:get(t1), p:get(t1 + 1)
+        s1 = {x = s1.x, y = s1.y}
+        s2 = {x = s2.x, y = s2.y}
+        local lerpd = table.interpolate(s1, s2, factor)
+        p.x = lerpd.x
+        p.y = lerpd.y
+        ctx.event:emit('collision.move', {object = p, x = p.x, y = p.y})
       end
-    end)]]
+    end)
 
     self.ack = data.tick
     
@@ -57,6 +78,12 @@ function PlayerServer:trace(data, ping)
       self:slot(data)
     end
 
+    table.insert(self.history, setmetatable({
+      x = self.x,
+      y = self.y,
+      tick = data.tick
+    }, self.meta))
+
     -- sync
     local msg = {}
     msg.x = math.round(self.x)
@@ -79,12 +106,12 @@ function PlayerServer:trace(data, ping)
     ctx.net:emit(evtSync, msg)
 
     -- Undo lag compensation
-    --[[ctx.players:each(function(p)
+    ctx.players:each(function(p)
       if oldPos[p.id] then
         p.x, p.y = unpack(oldPos[p.id])
-        p.shape:moveTo(p.x, p.y)
+        ctx.event:emit('collision.move', {object = p, x = p.x, y = p.y})
       end
-    end)]]
+    end)
   end
 end