Skip to content

Instantly share code, notes, and snippets.

@a327ex
Created January 22, 2020 15:29
Show Gist options
  • Save a327ex/658773c24afc2d5d0bc95901bca6f788 to your computer and use it in GitHub Desktop.
Save a327ex/658773c24afc2d5d0bc95901bca6f788 to your computer and use it in GitHub Desktop.
function init()
white = {1, 1, 1, 1}
black = {0, 0, 0, 1}
red = {1.0, 0.1, 0.2, 1}
combine = g.newShader("combine.frag")
aesthetic = g.newShader("aesthetic.frag")
aesthetic_canvas = g.newCanvas(gw, gh)
displacement_canvas = g.newCanvas(gw, gh)
game_canvas = g.newCanvas(gw, gh)
shockwave = g.newImage("res/shockwave_displacement.png")
new_animation("hit1", 96, 47)
new_animation("smoke1", 50, 50, {1})
new_animation("firehit1", 200, 200)
new_animation("firehit2", 200, 200)
new_animation("firehit3", 200, 200)
new_animation("radial1", 200, 200)
new_animation("radial2", 200, 200)
new_animation("fire1", 192, 108, {1, 3, 5, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19})
new_animation("disappear1", 100, 100)
new_animation("disappear2", 100, 100)
displacements = {}
projectiles = {}
effects = {}
enemies = {}
ui = {}
ammo_bars = {}
hp_bars = {}
ui_effects = {}
player = Player(gw/2, gh - 6)
x1, x2 = gw/2 - gw/4, gw/2 + gw/4
timer:every(1, function() table.insert(enemies, Enemy1(rng:float(x1 + 32, x2 - 32), -32)) end)
--[[
for i = 1, 50 do
table.insert(enemies, Enemy1(rng:float(x1 + 32, x2 - 32), rng:float(32, gh - 128)))
end
]]--
local h = (gh - 55)
local bh = h/player.max_ammo
for i = 1, player.max_ammo do
table.insert(ammo_bars, UIBar(x2 + 24, 50 + (i-1)*(bh), bh - 4, "ammo"))
end
player_ammo_ui = PlayerAmmoUI(x2 + 24, 22)
local hh = (gh/2.5)/player.max_hp
for i = 1, player.max_hp do
timer:after((i-1)*0.02, function()
table.insert(hp_bars, UIBar(x1 - 24, 55 + (i-1)*(hh), hh - 4, "hp"))
end)
end
player_hp_ui = PlayerHPUI(x1 - 24, 22)
end
function update(dt)
player:update(dt)
update_objects(displacements, dt)
update_objects(projectiles, dt)
update_objects(enemies, dt)
update_objects(effects, dt)
update_objects(ammo_bars, dt)
update_objects(hp_bars, dt)
update_objects(ui_effects, dt)
player_ammo_ui:update(dt)
player_hp_ui:update(dt)
end
function draw()
g.setCanvas(displacement_canvas)
g.clear()
g.setColor(0.5, 0.5, 0.5, 1)
g.rectangle("fill", 0, 0, gw, gh)
draw_objects(displacements)
g.setCanvas()
g.setCanvas(game_canvas)
g.clear()
g.setColor(white)
camera:attach()
draw_objects(projectiles)
draw_objects(enemies)
draw_objects(effects)
player:draw()
line(x1, gh/2, math.pi/2, gh + 16, 4)
line(x2, gh/2, math.pi/2, gh + 16, 4)
local w = gw - x2
rectf(x1/2, gh/2, x1, gh, nil, nil, black)
rectf(x2 + w/2, gh/2, w, gh, nil, nil, black)
draw_objects(ammo_bars)
draw_objects(hp_bars)
player_ammo_ui:draw()
player_hp_ui:draw()
draw_objects(ui_effects)
camera:detach()
g.setCanvas()
-- Apply aesthetic
g.setCanvas(aesthetic_canvas)
g.clear()
aesthetic:send("displacement_map", displacement_canvas)
g.setShader(aesthetic)
draw_canvas(game_canvas, 0, 0, 0, 1, 1)
g.setShader()
g.setColor(white)
g.setCanvas()
draw_canvas(aesthetic_canvas, 0, 0, 0, sx, sy)
end
Player = Class:extend()
function Player:new(x, y)
self.timer = Timer()
self.x, self.y = x, y
self.rs = 16
self.sx, self.sy = 1, 1
self.e1x, self.e1y = -self.rs/2.5, -self.rs/2.5
self.e2x, self.e2y = self.rs/2.5, -self.rs/2.5
self.e1ox, self.e1oy = 0, 0
self.e2ox, self.e2oy = 0, 0
self.r = 0
self.last_r = 0
self.scale_spring = Spring(1)
self.timer:everyi(2, function() self.timer:tween(1, self, {sx = 1.025, sy = 1.025}, cubic_in, function() self.timer:tween(1, self, {sx = 1, sy = 1}, linear) end) end)
self.rso = 0; self.timer:everyi({0.1, 0.2}, function() self.rso = rng:float(-1, 1) end)
self.attack_spring = Spring(1)
self.attack_timer = 100
self.attack_pulse_timer = 0
self.attack_cd = 0.06
self.shape = HC.circle(self.x, self.y, 16)
self.shape.parent = self
self.base_ammo = 35
self.max_ammo = self.base_ammo
self.ammo = self.max_ammo
self.max_hp = 10
self.hp = self.max_hp
self.reloading = false
self.reload_timer = 0
self.reload_cd = 0.6
self.reload_spring = Spring(1)
self.ammo_spend_accumulator = 0
self.mods = {}
self.mods.double = false
self.mods.triple = false
self.mods.volley = false
self.mods.spread = false
self.mods.burst = false
self.burst_amount = 3
self.mods.homing = false
self.mods.speed = false
self.mods.accel = false
self.mods.decel = false
self.mods.ricochet = false
self.ricochet_amount = 2
self.mods.scatter = false
self.scatter_amount = 1
self.mods.split = false
self.mods.chain = false
self.chain_amount = 3
self.mods.pierce = false
self.pierce_amount = 1
self.mods.fork = false
self.mods.cross = false
self.mods.blast = false
self.blast_meter = 0
self.blast_cd = 4
self.blast_timer = 4
self.mods.burn = false
self.mods.finale = false
self.finale_count = 0
self.finale_amount = 5
self.mods.weaken = false
self.mods.glitch = false
self.mods.slow = false
self.mods.haste = false
self.mods.stun = false
self.mods.sphere = false
self.mods.spawner = false
end
function Player:spend_ammo(a)
if self.ammo <= 0 then return end
self.ammo_spend_accumulator = self.ammo_spend_accumulator + a
while self.ammo_spend_accumulator >= 1 and self.ammo > 0 do
self.ammo_spend_accumulator = self.ammo_spend_accumulator - 1
ammo_bars[self.ammo]:spend()
self.ammo = self.ammo - 1
if self.ammo < 0 then self.ammo = 0 end
player_ammo_ui:jiggle()
end
return true
end
function Player:shoot(x, y)
local mods = copy(self.mods)
local v = rng:float(600, 700)
local lo = rng:float(-4, 4)
local r = self.r
if self.mods.spread then r = r + rng:float(-math.pi/16, math.pi/16) end
if self.mods.sphere then
table.insert(effects, CircleEffect(x + lo*math.cos(r + math.pi/2), y + lo*math.sin(r + math.pi/2), 8))
self.attack_spring:pull(0.25)
end
for i = 1, 4 do table.insert(effects, EllipseParticle(x, y, self.r + rng:float(-math.pi/2, math.pi/2), rng:float(100, 400))) end
table.insert(effects, ShootCapsule(x, y, self.r + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300)))
table.insert(effects, ShootCircle(x + lo*math.cos(r + math.pi/2), y + lo*math.sin(r + math.pi/2)))
local finale = false
if self.mods.finale then
self.finale_count = self.finale_count + 1
if self.finale_count > self.finale_amount then
self.finale_count = 0
finale = true
table.insert(effects, CircleEffect(self.x, self.y, self.s*self.rs))
self.attack_spring:pull(0.25)
end
end
local mods = copy(self.mods)
if self.blasting then mods.blasting = true end
if finale then
mods.final = true
mods.pierce = true
mods.pierce_amount = 12
end
table.insert(projectiles, Projectile(x + lo*math.cos(r + math.pi/2), y + lo*math.sin(r + math.pi/2), v, r, mods))
if self.blasting then
self.blast_meter = self.blast_meter - 10
if self.blast_meter <= 0 then
self.blast_meter = 0
self.blasting = false
self.blast_timer = 0
end
table.insert(effects, CircleEffect(self.x, self.y, self.s*self.rs))
self.attack_spring:pull(0.25)
end
end
function Player:update(dt)
self.scale_spring:update(dt)
self.attack_spring:update(dt)
self.reload_spring:update(dt)
self.s = self.scale_spring.x*self.attack_spring.x*self.reload_spring.x
self.r = angle_to_mouse(self.x, self.y)
-- double, triple and volley change shooting_positions
self.shooting_positions = {}
local s = self.s*1.5*self.rs
if self.mods.volley then
if self.mods.double and self.mods.triple then
local ofs = {-64, -56, -48, -40, -32, -16, -8, 0, 8, 16, 32, 40, 48, 56, 64}
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end
elseif self.mods.double and not self.mods.triple then
local ofs = {-28, -20, -4, 4, 20, 28}
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end
elseif self.mods.triple and not self.mods.double then
local ofs = {-32, -24, -16, -8, 0, 8, 16, 24, 32}
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end
else
local ofs = {-24, 0, 24}
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end
end
elseif self.mods.triple then
if self.mods.double then
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/4), y = self.y + s*math.sin(self.r - math.pi/4)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/8), y = self.y + s*math.sin(self.r - math.pi/8)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r), y = self.y + s*math.sin(self.r)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/8), y = self.y + s*math.sin(self.r + math.pi/8)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/4), y = self.y + s*math.sin(self.r + math.pi/4)})
else
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/8), y = self.y + s*math.sin(self.r - math.pi/8)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r), y = self.y + s*math.sin(self.r)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/8), y = self.y + s*math.sin(self.r + math.pi/8)})
end
elseif self.mods.double then
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/18), y = self.y + s*math.sin(self.r - math.pi/18)})
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/18), y = self.y + s*math.sin(self.r + math.pi/18)})
else
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r), y = self.y + s*math.sin(self.r)})
end
local cdm = 1
if self.mods.double then cdm = cdm*1.2 end
if self.mods.triple then cdm = cdm*1.6 end
if self.mods.volley then cdm = cdm*2 end
if self.mods.spread then cdm = cdm*0.75 end
if self.mods.burst then cdm = cdm*3 end
if self.blasting then cdm = cdm*4 end
if self.mods.sphere then cdm = cdm*4 end
local am = 1
if self.mods.double then am = am*0.5 end
if self.mods.triple then am = am*0.5 end
if self.mods.double and self.mods.triple then am = am*3 end
if self.mods.volley then am = am*0.5 end
if self.mods.volley and self.mods.double then am = am*2 end
if self.mods.volley and self.mods.triple then am = am*2 end
if self.mods.spread then am = am*0.25 end
if self.mods.burst then am = am*0.5 end
if self.mods.sphere then am = am*4 end
self.attack_timer = self.attack_timer + dt
if m.isDown(1) and not m.isDown(2) then
if self.attack_timer > self.attack_cd*cdm then
self.attack_timer = 0
self.scale_spring:pull(0.1)
if self.mods.burst then
for i = 1, self.burst_amount do
timer:after((i-1)*(math.min(self.attack_cd*cdm/4, 0.12)), function()
for _, sp in ipairs(self.shooting_positions) do
if self:spend_ammo(am) then
self:shoot(sp.x, sp.y)
end
end
end)
end
else
for _, sp in ipairs(self.shooting_positions) do
if self:spend_ammo(am) then
self:shoot(sp.x, sp.y)
end
end
end
end
self.attack_pulse_timer = self.attack_pulse_timer + dt
if self.attack_pulse_timer > 4*self.attack_cd*cdm then
self.attack_pulse_timer = 0
self.attack_spring:pull(0.25)
end
end
if not self.blasting then
self.blast_timer = self.blast_timer + dt
end
local r = math.deg(self.r)
local mx = camera:get_mouse_position()
local rd = 1
if mx > self.x then rd = -1 else rd = 1 end
if mouse_pressed(2) then
self.reloading = true
self.reload_spring:pull(0.4)
self.reload_text = ReloadText(self.x + rd*62, self.y - 14, self.reload_cd, rd*math.pi/32)
table.insert(effects, self.reload_text)
end
if mouse_released(2) then
self.reloading = false
self.reload_spring:pull(0.2)
if self.reload_text then
self.reload_text:die()
self.reload_text = nil
end
end
if self.reloading then
self.reload_timer = self.reload_timer + dt
if self.reload_text then self.reload_text.t = self.reload_timer/self.reload_cd end
player_ammo_ui.t = self.reload_timer/self.reload_cd
if self.reload_timer > self.reload_cd then
for i = 1, #ammo_bars do ammo_bars[i]:refresh() end
player_ammo_ui:refresh()
table.insert(effects, CircleEffect(self.x, self.y, self.s*self.rs))
self.reload_spring:pull(0.4)
self.reload_timer = 0
self.ammo = self.max_ammo
if self.reload_text then
self.reload_text:die()
self.reload_text = nil
end
end
else self.reload_timer = 0 end
self.shape:moveTo(self.x, self.y)
end
function Player:draw()
push(self.x, self.y, 0, self.s*self.sx, self.s*self.sy)
if self.mods.blast then circlef(self.x, self.y, remap(self.blast_meter, 0, 50, 0, self.rs + self.rso)) end
circle(self.x, self.y, self.rs + self.rso, 2)
pop()
for _, sp in ipairs(self.shooting_positions) do circlef(sp.x, sp.y, 3) end
end
function Player:hit(damage)
hp_bars[self.hp]:spend2()
self.hp = self.hp - 1
slow(0.5, 0.5)
flash(2, black)
end
function get_closest_unhit_enemy(enemies_hit, x, y)
local min_d, min_i = 1000000, 0
for i, enemy in ipairs(enemies) do
if not any(enemies_hit, enemy.id) then
local d = distance(x, y, enemy.x, enemy.y)
if d < min_d then
min_d = d
min_i = i
end
end
end
return enemies[min_i]
end
function get_close_enemies(x, y, d)
local out = {}
for _, e in ipairs(enemies) do
if distance(e.x, e.y, x, y) < d then
table.insert(out, e)
end
end
return out
end
Projectile = Class:extend()
function Projectile:new(x, y, v, r, mods, enemies_hit)
self.timer = Timer()
self.mods = mods
self.x, self.y = x, y
self.v, self.r = v, r
if self.mods.sphere then self.v = v/8 end
self.vx, self.vy = self.v*math.cos(self.r), self.v*math.sin(self.r)
self.sx, self.sy = 1, 1
self.w, self.h = 16, 4
if self.mods.blasting then self.w, self.h = 24, 6 end
if self.mods.final then self.w, self.h = 24, 6 end
self.shape = HC.rectangle(self.x - self.w/2, self.y - self.h/2, self.w, self.h)
self.shape.parent = self
self.enemies_hit = enemies_hit or {}
self.damage = rng:int(40, 60)
if self.mods.blasting then self.damage = 4*self.damage end
if self.mods.final then self.damage = 3*self.damage end
self.accel_vm = 1
self.decel_vm = 1
self.speed_vm = 1
self.scatter_vm = 1
if self.mods.accel then self.accel_vm = 0; self.timer:tween(0.5, self, {accel_vm = 1.5}, linear) end
if self.mods.decel then self.decel_vm = 1.5; self.timer:tween(0.5, self, {decel_vm = 0.25}, linear) end
if self.mods.speed then self.speed_vm = 1.5 end
if self.mods.ricochet then self.ricochet_amount = mods.ricochet_amount or player.ricochet_amount end
if self.mods.scatter then self.scatter_amount = mods.scatter_amount or player.scatter_amount end
if self.mods.chain then self.chain_amount = mods.chain_amount or player.chain_amount end
if self.mods.pierce then self.pierce_amount = mods.pierce_amount or player.pierce_amount end
if self.mods.sphere then self.sphere_cd = 0.3; self.sphere_timer = 0; self.sphere_range = 128 end
if self.mods.spawner then
self.spawner_timer = 0
self.spawner_cd = rng:float(0.1, 0.3)
if self.mods.sphere then self.spawner_cd = self.spawner_cd*8 end
if self.mods.accel then self.spawner_cd = self.spawner_cd*0.75 end
if self.mods.decel then self.spawner_cd = self.spawner_cd*2 end
if self.mods.speed then self.spawner_cd = self.spawner_cd*0.5 end
end
end
function Projectile:update(dt)
self.timer:update(dt)
local vx, vy = 0, 0
if self.mods.homing then
local target = get_closest_unhit_enemy(self.enemies_hit, self.x, self.y)
if target then
local phx, phy = normalize(self.vx, self.vy)
local r = math.atan2(target.y - self.y, target.x - self.x)
local tthx, tthy = normalize(math.cos(r), math.sin(r))
local fhx, fhy = normalize(phx + 0.1*tthx, phy + 0.1*tthy)
self.homing_vx, self.homing_vy = self.v*fhx, self.v*fhy
else self.homing_vx, self.homing_vy = nil, nil end
self.vx = (self.homing_vx or self.v*math.cos(self.r))*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
self.vy = (self.homing_vy or self.v*math.sin(self.r))*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
else
self.vx = self.v*math.cos(self.r)*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
self.vy = self.v*math.sin(self.r)*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
end
self.x = self.x + self.vx*dt
self.y = self.y + self.vy*dt
self.r = math.atan2(self.vy, self.vx)
if self.x < x1 then self:wall(0, x1, nil, math.pi - self.r); self.vx = -self.vx; self.x = x1 + 2; self.r = math.pi - self.r end
if self.x > x2 then self:wall(math.pi, x2, nil, math.pi - self.r); self.vx = -self.vx; self.x = x2 - 2; self.r = math.pi - self.r end
if self.y < 0 then self:wall(math.pi/2, nil, nil, 2*math.pi - self.r); self.vy = -self.vy; self.y = 2; self.r = 2*math.pi - self.r end
if self.y > gh then self:wall(-math.pi/2, nil, nil, 2*math.pi - self.r); self.vy = -self.vy; self.y = gh - 2; self.r = 2*math.pi - self.r end
self.shape:moveTo(self.x, self.y)
self.shape:setRotation(self.r)
if self.mods.sphere then
self.sphere_timer = self.sphere_timer + dt
if self.sphere_timer > self.sphere_cd then
self.sphere_timer = 0
local targets = get_close_enemies(self.x, self.y, self.sphere_range)
if targets then
local target = rng:table(targets)
if target then
target:hit(self.damage)
table.insert(effects, LightningLine(self.x, self.y, target.x, target.y + target.v/10))
table.insert(effects, HitEffect(target.x, target.y + target.v/10))
for i = 1, 2 do table.insert(effects, ExplosionParticle(target.x, target.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end
end
end
end
end
if self.mods.spawner then
self.spawner_timer = self.spawner_timer + dt
if self.spawner_timer > self.spawner_cd then
self.spawner_timer = 0
table.insert(projectiles, SecondaryProjectile(self.x, self.y, self.v, self.r - math.pi/2))
table.insert(projectiles, SecondaryProjectile(self.x, self.y, self.v, self.r + math.pi/2))
for i = 1, 4 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(50, 200))) end
table.insert(effects, ShootCircle(self.x, self.y))
end
end
end
function Projectile:draw()
local s = 1
if self.mods.blasting or self.mods.sphere then s = rng:float(1, 1.1) end
push(self.x, self.y, self.r, s*self.sx, s*self.sy)
if self.mods.sphere then
circlef(self.x, self.y, self.w/4 + rng:float(-1, 1))
circle(self.x, self.y, self.sphere_range, 2, {1, 1, 1, 0.04})
else rectf(self.x, self.y, self.w, self.h, nil, nil, white) end
pop()
end
function Projectile:wall(r, x, y, rr)
if (self.mods.ricochet and self.ricochet_amount > 0) or (self.mods.scatter and self.scatter_amount > 0) then
if self.mods.ricochet then self.ricochet_amount = self.ricochet_amount - 1 end
if self.mods.scatter then self.scatter_amount = self.scatter_amount - 1; self.scatter_vm = self.scatter_vm*1.5 end
for i = 1, 2 do table.insert(effects, EllipseParticle(x or self.x, y or self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end
table.insert(effects, ShootCircle(x or self.x, y or self.y))
else
self.dead = true
for i = 1, 2 do table.insert(effects, EllipseParticle(x or self.x, y or self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end
table.insert(effects, ShootCircle(x or self.x, y or self.y))
if self.mods.scatter then
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
for i = 1, rng:int(2, 8) do
table.insert(projectiles, SecondaryProjectile(self.x, self.y, 1.25*v, rr + rng:float(-math.pi/4, math.pi/4)))
end
end
if self.mods.split then
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
local mods = copy(self.mods)
mods.split = false
table.insert(projectiles, Projectile(self.x, self.y, v, r + math.pi/4, mods))
table.insert(projectiles, Projectile(self.x, self.y, v, r - math.pi/4, mods))
end
end
end
function Projectile:hit(enemy)
if any(self.enemies_hit, enemy.id) then return end
if self.mods.chain or self.mods.pierce or self.mods.sphere then
table.insert(self.enemies_hit, enemy.id)
if self.mods.chain then
self.chain_amount = self.chain_amount - 1
table.insert(effects, CircleEffect2(enemy.x, enemy.y, enemy.w))
for i = 1, 2 do table.insert(effects, ExplosionParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end
local target = get_closest_unhit_enemy(self.enemies_hit, self.x, self.y)
if target then self.r = math.atan2((target.y + 0.5*target.v) - self.y, target.x - self.x)
else self:die() end
if self.chain_amount < 0 then self:die() end
elseif self.mods.pierce then
self.pierce_amount = self.pierce_amount - 1
table.insert(effects, CircleEffect2(enemy.x, enemy.y, enemy.w))
for i = 1, 2 do table.insert(effects, ExplosionParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end
if self.pierce_amount < 0 then self:die() end
elseif self.mods.sphere then
table.insert(effects, CircleEffect2(enemy.x, enemy.y, enemy.w))
for i = 1, 2 do table.insert(effects, ExplosionParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end
end
else self:die() end
if self.mods.fork then
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
local mods = copy(self.mods)
mods.fork = false
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - math.pi/4, mods, {enemy.id}))
table.insert(projectiles, Projectile(self.x, self.y, v, self.r + math.pi/4, mods, {enemy.id}))
end
if self.mods.cross then
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm
local mods = copy(self.mods)
mods.cross = false
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - math.pi/2, mods, {enemy.id}))
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - 2*math.pi/2, mods, {enemy.id}))
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - 3*math.pi/2, mods, {enemy.id}))
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - 4*math.pi/2, mods, {enemy.id}))
end
if self.mods.blast then
if not player.blasting and player.blast_timer > player.blast_cd then
player.blast_meter = player.blast_meter + 5
if player.blast_meter > 50 then
player.blasting = true
player.blast_meter = 50
table.insert(effects, InfoText(player.x, player.y - 36, "BLAST!", 0.6))
end
end
if self.mods.blasting then
table.insert(effects, ExplosionCircle(enemy.x, enemy.y))
table.insert(displacements, Shockwave(enemy.x, enemy.y))
for i = 1, rng:int(8, 12) do table.insert(effects, ExplosionParticle(enemy.x, enemy.y, rng:float(0, 2*math.pi), rng:float(300, 600))) end
local targets = get_close_enemies(enemy.x, enemy.y, 64)
for i, t in ipairs(targets) do
timer:after((i-1)*0.05, function()
t:hit(2*self.damage)
table.insert(effects, ExplosionCircle(enemy.x, enemy.y, 0.5))
for i = 1, rng:int(4, 6) do table.insert(effects, ExplosionParticle(enemy.x, enemy.y, rng:float(0, 2*math.pi), rng:float(200, 400))) end
end)
end
end
end
if self.mods.weaken then if rng:bool(17) then enemy:weaken() end end
if self.mods.glitch then if rng:bool(17) then enemy:glitch() end end
if self.mods.slow then if rng:bool(17) then enemy:slow() end end
if self.mods.haste then if rng:bool(17) then enemy:hasten() end end
if self.mods.stun then if rng:bool(17) then enemy:stun() end end
return true
end
function Projectile:die()
self.dead = true
for i = 1, 2 do table.insert(effects, EllipseParticle(x or self.x, y or self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end
table.insert(effects, ShootCircle(x or self.x, y or self.y))
end
function Projectile:die2()
self.dead = true
for i = 1, 2 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 200))) end
table.insert(effects, ShootCircle(self.x, self.y))
end
Enemy1 = Class:extend()
function Enemy1:new(x, y)
self.id = rng:uid()
self.timer = Timer()
self.x, self.y = x, y
self.w = 16
self.r = 0
self.sx, self.sy = 1, 1
self.d = 1
self.v = 50
self.vs = {}
local r = 0
for i = 1, 8 do
local w = rng:float(0.95, 1.05)
self.vs[2*(i-1)+1] = self.w*w*math.cos(r)
self.vs[2*i] = self.w*w*math.sin(r)
r = r + math.pi/4
end
self.vr = rng:float(-4*math.pi, 4*math.pi)
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs)))
self.shape.parent = self
self.hp = 600
self.hit_spring = Spring(1)
self.weak_spring = Spring(1)
self.vm = 1
end
function Enemy1:update(dt)
self.hit_spring:update(dt)
self.weak_spring:update(dt)
self.timer:update(dt)
self.vm = 1
if self.slowed then self.vm = 0.5 end
if self.haste then self.vm = 1.5 end
if self.stunned then self.vm = 0 end
self.y = self.y + self.v*self.vm*dt
self.r = self.r + self.vr*dt
self.shape:moveTo(self.x, self.y)
self.shape:setRotation(self.r)
for other in pairs(HC.neighbors(self.shape)) do
if other.parent:is(Projectile) then
local collides = self.shape:collidesWith(other)
if collides then
if other.parent:hit(self) then
self:hit(other.parent.damage, other.parent.r, other.parent)
table.insert(effects, HitEffect(other.parent.x, other.parent.y))
end
end
elseif other.parent:is(SecondaryProjectile) then
local collides = self.shape:collidesWith(other)
if collides then
self:hit(other.parent.damage, other.parent.r)
other.parent:die()
table.insert(effects, HitEffect(other.parent.x, other.parent.y))
end
end
end
if self.y > gh then
self.dead = true
table.insert(effects, DeathCircle(self.x, self.y, 2*self.w, red))
table.insert(effects, CircleEffect(self.x, self.y, self.w, red))
player:hit(1)
flash(1, black)
end
end
function Enemy1:draw()
if self.y > gh + 64 then return end
local color = red
if self.hit_flash then color = white end
if self.burning then color = white end
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy)
polygon(to_polygon(self.x, self.y, self.vs), 2, color)
pop()
push(self.x, self.y, 0, self.weak_spring.x*self.hit_spring.x*self.sx, self.weak_spring.x*self.hit_spring.x*self.sy)
if self.weak then
local weak_o = 0.125*self.w*math.sin(10*time)
local s = {1, 1, 1, self.weak_a}
line(self.x - self.weak_s - weak_o, self.y - self.weak_s - weak_o, math.pi/4, self.weak_s/4, 2, s)
line(self.x + self.weak_s + weak_o, self.y - self.weak_s - weak_o, 3*math.pi/4, self.weak_s/4, 2, s)
line(self.x + self.weak_s + weak_o, self.y + self.weak_s + weak_o, math.pi/4, self.weak_s/4, 2, s)
line(self.x - self.weak_s - weak_o, self.y + self.weak_s + weak_o, 3*math.pi/4, self.weak_s/4, 2, s)
g.setColor(1, 1, 1, 1)
end
pop()
end
function Enemy1:hit(damage, r, projectile)
if self.dead then return end
self.sx, self.sy = 1.35, 1.35
self.hit_flash = true
self.timer:tween(0.1, self, {sx = 1, sy = 1}, linear, function() self.hit_flash = false end, "hit")
if self.weak then damage = 2*damage end
self.hp = self.hp - damage
if self.hp <= 0 then
self.dead = true
if self.stun_effect then self.stun_effect:die() end
table.insert(effects, DeathCircle(self.x, self.y, 2*self.w))
for i = 1, 4 do table.insert(effects, DustParticle(self.x, self.y, white)) end
if projectile then
if projectile.mods.burn then
table.insert(effects, AnimatedEffect(self.x + 6, self.y - 6, "radial1", 0.01, nil, nil))
local targets = get_close_enemies(self.x, self.y, 52)
for i, t in ipairs(targets) do
timer:after((i-1)*0.05, function()
t:burn()
end)
end
end
end
end
end
function Enemy1:burn()
self.burning = true
self.timer:everyi(0.4, function()
self.hit_spring:pull(0.1)
self:hit(20)
table.insert(effects, AnimatedEffect(self.x + 3, self.y - 3, "radial1", 0.01, nil, nil, 0.5, 0.5))
end, 5, function() self.burning = false end, "burn")
end
function Enemy1:weaken()
self.weak = true
self.weak_s = 1.6*self.w
self.weak_a = 1
self.weak_spring:pull(0.1)
self.timer:tween(0.1, self, {weak_s = 1.3*self.w}, linear, function()
self.timer:after(10, function()
self.timer:tween(0.2, self, {weak_s = 2.6*self.w, weak_a = 0}, linear, function()
self.weak = false
end, "weaktt")
end, "weaka")
end, "weakt")
end
function Enemy1:glitch()
self.glitched = true
self.timer:everyi(0.3, function()
self:hit(40)
for j = 1, 3 do
timer:after((j-1)*(0.3/3), function()
table.insert(displacements, DisplacementBlock(self.x + rng:float(-self.w, self.w), self.y + rng:float(-self.w, self.w), rng:float(self.w/2, 2*self.w), rng:float(self.w/2, 2*self.w), 0.15, 0.3))
table.insert(effects, Block(self.x + rng:float(-self.w, self.w), self.y + rng:float(-self.w, self.w), rng:float(self.w/2, 1.5*self.w), rng:float(self.w/2, 1.5*self.w), 0.15, 0.3))
end)
end
end, 20, nil, "glitche")
self.timer:after(6, function() self.glitched = false end, "glitcha")
end
function Enemy1:slow()
self.slowed = true
self.timer:everyi(0.5, function()
self:hit(0)
for i = 1, 6 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(150, 300))) end
table.insert(effects, AnimatedEffect(self.x, self.y + 4, rng:table({"disappear1", "disappear2"}), 0.02, nil, math.pi/2, 1, 1))
end, 15, function() self.slowed = false end, "slowe")
end
function Enemy1:hasten()
self.haste = true
self.timer:everyi(0.5, function()
self:hit(0)
for i = 1, 6 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(150, 300))) end
table.insert(effects, AnimatedEffect(self.x, self.y + 4, rng:table({"disappear1", "disappear2"}), 0.02, nil, 0, 1, 1))
end, 15, function() self.haste = false end, "hastee")
end
function Enemy1:stun()
if self.stunned then return end
self.stunned = true
self:hit(0)
self.stun_effect = StunEffect(self.x, self.y, self)
table.insert(effects, self.stun_effect)
self.timer:after(3, function()
self.stunned = false
self.stun_effect:die()
end, "stuna")
end
SecondaryProjectile = Class:extend()
function SecondaryProjectile:new(x, y, v, r)
self.x, self.y = x, y
self.v, self.r = v, r
self.vx, self.vy = self.v*math.cos(self.r), self.v*math.sin(self.r)
self.sx, self.sy = 1, 1
self.w, self.h = 9, 3
self.shape = HC.rectangle(self.x - self.w/2, self.y - self.h/2, self.w, self.h)
self.shape.parent = self
self.damage = rng:int(10, 20)
end
function SecondaryProjectile:update(dt)
self.x = self.x + self.vx*dt
self.y = self.y + self.vy*dt
self.r = math.atan2(self.vy, self.vx)
if self.x < x1 then self:die(x1, self.y) end
if self.x > x2 then self:die(x2, self.y) end
if self.y < 0 then self:die(self.x, 0) end
if self.y > gh then self:die(self.x, gh) end
self.shape:moveTo(self.x, self.y)
self.shape:setRotation(self.r)
end
function SecondaryProjectile:draw()
push(self.x, self.y, self.r, self.sx, self.sy)
rectf(self.x, self.y, self.w, self.h, nil, nil, white)
pop()
end
function SecondaryProjectile:die(x, y)
self.dead = true
table.insert(effects, ShootCircle(x or self.x, y or self.y, 8))
end
LightningLine = Class:extend()
function LightningLine:new(x1, y1, x2, y2)
self.timer = Timer()
self.lines = {}
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
table.insert(self.lines, {x1 = self.x1, y1 = self.y1, x2 = self.x2, y2 = self.y2})
table.insert(effects, ShootCircle(x1, y1, 8))
self.lw = 3
self.generations = 4
self.max_offset = 12
self:generate()
self.timer:after(0.1, function() self.dead = true end)
end
function LightningLine:update(dt)
self.timer:update(dt)
end
function LightningLine:generate()
local offset_amount = self.max_offset
local lines = self.lines
for j = 1, self.generations do
for i = #lines, 1, -1 do
local x1, y1 = lines[i].x1, lines[i].y1
local x2, y2 = lines[i].x2, lines[i].y2
table.remove(lines, i)
local x, y = (x1 + x2)/2, (y1 + y2)/2
local px, py = perpendicular(normalize(x2 - x1, y2 - y1))
x = x + px*rng:float(-offset_amount, offset_amount)
y = y + py*rng:float(-offset_amount, offset_amount)
table.insert(lines, {x1 = x1, y1 = y1, x2 = x, y2 = y})
table.insert(lines, {x1 = x, y1 = y, x2 = x2, y2 = y2})
end
offset_amount = offset_amount/2
end
end
function LightningLine:draw()
for i, line in ipairs(self.lines) do
g.setLineWidth(self.lw)
g.line(line.x1, line.y1, line.x2, line.y2)
end
g.setLineWidth(1)
g.setColor(1, 1, 1, 1)
end
StunEffect = Class:extend()
function StunEffect:new(x, y, enemy)
self.x, self.y = x, y
self.sx, self.sy = 4, 4
self.a = 0
self.vs = enemy.vs
self.enemy = enemy
self.scale_spring = Spring(1)
timer:tween(0.2, self, {a = 1, sx = 1, sy = 1}, linear, function()
self.sx, self.sy = 1, 1
self.a = 1
self.scale_spring:pull(-0.5)
for i = 1, 2 do table.insert(effects, DustParticle(self.x, self.y, white)) end
end)
end
function StunEffect:update(dt)
self.scale_spring:update(dt)
self.r = self.enemy.r
end
function StunEffect:draw()
push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy)
polygon(to_polygon(self.x, self.y, self.vs), 3, {1, 1, 1, self.a})
pop()
end
function StunEffect:die()
table.insert(effects, CircleEffect(self.x, self.y, self.enemy.w))
for i = 1, 6 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(150, 300))) end
timer:tween(0.2, self, {a = 0, sx = 4, sy = 4}, linear, function() self.dead = true end)
end
Block = Class:extend()
function Block:new(x, y, w, h, d1, d2)
self.x, self.y = x, y
self.w, self.h = w, h
timer:after({d1 or 0.05, d2 or 0.4}, function() self.dead = true end)
end
function Block:update(dt)
end
function Block:draw()
g.setColor(1, 1, 1, 1)
g.rectangle("fill", self.x - self.w/2, self.y - self.h/2, self.w, self.h)
end
DisplacementBlock = Class:extend()
function DisplacementBlock:new(x, y, w, h, d1, d2)
self.x, self.y = x, y
self.w, self.h = w, h
local r = rng:float(-32, 32)/255
self.color = {0.5 + r, 0.5 + r, 0.5 + r}
timer:after({d1 or 0.05, d2 or 0.4}, function() self.dead = true end)
end
function DisplacementBlock:update(dt)
end
function DisplacementBlock:draw()
g.setColor(self.color)
g.rectangle("fill", self.x - self.w/2, self.y - self.h/2, self.w, self.h)
g.setColor(white)
end
SlowParticle = Class:extend()
function SlowParticle:new(x, y)
self.x, self.y = x, y
self.sx, self.sy = 0, 0
self.r = math.pi/2
self.v = rng:float(100, 175)
self.rs = rng:float(3, 6)
timer:tween(rng:float(0.04, 0.06), self, {sx = 0.75, sy = 0.75}, cubic_in_out, function()
timer:tween(rng:float(0.3, 0.5), self, {sx = 0, sy = 0}, linear, function() self.dead = true end)
end)
end
function SlowParticle:update(dt)
self.x = self.x + self.v*math.cos(self.r)*dt
self.y = self.y + self.v*math.sin(self.r)*dt
end
function SlowParticle:draw()
push(self.x, self.y, 0, self.sx, self.sy)
circlef(self.x, self.y, self.rs)
-- line(self.x, self.y, self.r, 15, 3.5)
pop()
end
PlayerHPUI = Class:extend()
function PlayerHPUI:new(x, y)
self.timer = Timer()
self.x, self.y = x, y
self.sx, self.sy = 1.25, 1.25
self.scale_spring = Spring(1)
self.timer:everyi(2, function() self.timer:tween(1, self, {sx = 1.1, sy = 1.1}, cubic_in, function() self.timer:tween(1, self, {sx = 1, sy = 1}, linear) end) end)
end
function PlayerHPUI:update(dt)
self.timer:update(dt)
self.scale_spring:update(dt)
self.hp = player.hp
self.max_hp = player.max_hp
end
function PlayerHPUI:draw()
push(self.x, self.y, 0, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy)
draw_text(self.hp, self.x, self.y, 0, 1, 1, font_medium)
pop()
end
function PlayerHPUI:refresh()
self.scale_spring:pull(0.5)
end
function PlayerHPUI:jiggle()
self.scale_spring:pull(0.2)
end
PlayerAmmoUI = Class:extend()
function PlayerAmmoUI:new(x, y)
self.timer = Timer()
self.x, self.y = x, y
self.sx, self.sy = 1.25, 1.25
self.oy = 0
self.scale_spring = Spring(1)
self.timer:tween(0.1, self, {sx = 1, sy = 1}, cubic_in, function() self.sx, self.sy = 1, 1 end)
self.timer:after(0.1, function()
self.timer:everyi(2, function() self.timer:tween(1, self, {sx = 1.1, sy = 1.1}, cubic_in, function() self.timer:tween(1, self, {sx = 1, sy = 1}, linear) end) end)
end)
end
function PlayerAmmoUI:update(dt)
self.timer:update(dt)
self.scale_spring:update(dt)
self.ammo = player.ammo
self.max_ammo = player.max_ammo
end
function PlayerAmmoUI:draw()
if player.reloading then
if player.reload_text then
if not player.reload_text.visible then return end
end
end
push(self.x, self.y + self.oy, 0, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy)
draw_text(self.ammo, self.x, self.y + self.oy, 0, 1, 1, font_medium)
pop()
if player.reloading then
local x = self.x
local y1
for i = 1, #ammo_bars do
if ammo_bars[i].spent then
y1 = ammo_bars[i].y
break
end
end
local y2 = ammo_bars[#ammo_bars].y
if y1 then
g.setLineWidth(4)
g.line(x, y1, x, y1 + (y2-y1)*self.t)
g.setLineWidth(1)
end
end
end
function PlayerAmmoUI:refresh()
self.t = 0
self.scale_spring:pull(0.5)
end
function PlayerAmmoUI:jiggle()
self.scale_spring:pull(0.2)
end
UIBar = Class:extend()
function UIBar:new(x, y, h, type)
self.type = type
self.timer = Timer()
self.x, self.y = x, y
self.sx, self.sy = 1.25, 1.25
self.w = 16
self.ow = 16
self.h = h
self.spent = false
self.scale_spring = Spring(1)
self.timer:tween(0.1, self, {sx = 1, sy = 1}, cubic_in, function() self.sx, self.sy = 1, 1 end, "refreshs")
self.timer:after(0.1, function()
self.timer:everyi(2, function() self.timer:tween(1, self, {sx = 1.1, sy = 1.1}, cubic_in, function() self.timer:tween(1, self, {sx = 1, sy = 1}, linear) end) end)
end)
end
function UIBar:update(dt)
self.timer:update(dt)
self.scale_spring:update(dt)
end
function UIBar:draw()
if self.type == "ammo" then
if player.reloading then
if player.reload_text then
if not player.reload_text.visible then return end
end
end
end
push(self.x, self.y, 0, self.sx*self.scale_spring.x, self.sy*self.scale_spring.x)
if self.fake_spent then rect(self.x, self.y, self.w, self.h, nil, nil, 2, white)
else rectf(self.x, self.y, self.w, self.h, nil, nil, white) end
pop()
end
function UIBar:refresh()
self.spent = false
self.fake_spent = false
self.scale_spring:pull(0.4)
self.timer:tween(0.05, self, {w = self.ow}, linear, function() self.w = self.ow end, "refresh")
end
function UIBar:spend()
self.spent = true
self.scale_spring:pull(0.4)
self.timer:tween(0.05, self, {w = 0}, linear, function() self.w = 0 end, "spend")
table.insert(ui_effects, ShootCircle(self.x, self.y))
table.insert(ui_effects, FadingShootCapsule(self.x, self.y, rng:float(-math.pi/4, 0), rng:float(50, 150)))
end
function UIBar:fake_spend()
self.fake_spent = true
self.scale_spring:pull(0.4)
end
function UIBar:spend2()
self.spent = true
self.scale_spring:pull(0.4)
table.insert(ui_effects, ShootCircle(self.x, self.y, 24))
self.timer:tween(0.1, self, {w = 0}, linear, function() self.w = 0 end, "spend")
for i = 1, 8 do table.insert(ui_effects, DeathParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 400))) end
end
CircleEffect2 = Class:extend()
function CircleEffect2:new(x, y, r)
self.x, self.y = x, y
self.r = r
self.a = 1
self.lw = 6
timer:tween(0.2, self, {r = 4*self.r, lw = 1}, linear, function() self.dead = true end)
timer:after(0.15, function() timer:tween(0.05, self, {a = 0}, linear) end)
end
function CircleEffect2:update(dt)
end
function CircleEffect2:draw()
g.setColor(1, 1, 1, self.a)
g.setLineWidth(self.lw)
g.circle("line", self.x, self.y, self.r)
g.setLineWidth(1)
g.setColor(1, 1, 1, 1)
end
CircleEffect = Class:extend()
function CircleEffect:new(x, y, r, color)
self.x, self.y = x, y
self.r = r
self.lw = 8
self.color = color or white
timer:tween(0.15, self, {r = 4*self.r, lw = 1}, linear, function() self.dead = true end)
end
function CircleEffect:update(dt)
end
function CircleEffect:draw()
circle(self.x, self.y, self.r, self.lw, self.color)
end
ExplosionCircle = Class:extend()
function ExplosionCircle:new(x, y, s)
self.x, self.y = x, y
self.rs = 0
self.scale_spring = Spring(1)
camera:shake(3*(s or 1), 0.5*(s or 1))
timer:tween(0.1, self, {rs = (s or 1)*36}, cubic_in_out, function()
self.scale_spring:pull(0.2)
timer:tween(0.2, self, {rs = 0}, linear, function() self.dead = true end)
end)
end
function ExplosionCircle:update(dt)
self.scale_spring:update(dt)
end
function ExplosionCircle:draw()
push(self.x, self.y, 0, self.scale_spring.x, self.scale_spring.x)
g.setColor(white)
circlef(self.x, self.y, 1*self.rs)
g.setColor(1, 1, 1, 0.062)
circlef(self.x, self.y, 2*self.rs)
g.setColor(1, 1, 1, 1)
pop()
end
Shockwave = Class:extend()
function Shockwave:new(x, y)
self.timer = Timer()
self.x, self.y = x, y
self.sx, self.sy = 0.05, 0.05
self.a = 1
self.timer:tween(0.5, self, {sx = 0.75, sy = 0.75, a = 0}, linear, function() self.dead = true end)
end
function Shockwave:update(dt)
self.timer:update(dt)
end
function Shockwave:draw()
g.setColor(1, 1, 1, self.a)
g.draw(shockwave, self.x, self.y, 0, self.sx, self.sy, shockwave:getWidth()/2, shockwave:getHeight()/2)
g.setColor(1, 1, 1, 1)
end
InfoText = Class:extend()
function InfoText:new(x, y, text, duration, r, glitch)
self.timer = Timer()
self.x, self.y = x, y
self.r = r or 0
self.sx, self.sy = 1, 1
self.characters = {}
self.visuals = {}
for i = 1, #text do table.insert(self.visuals, 1) end
self.visible = true
self.font = font_medium
self.w, self.h = self.font:getWidth(text), self.font:getHeight()
self.scale_spring = Spring(1)
self.scale_spring:pull(0.15)
self.t = 0
local characters = {}
for i = 1, #text do table.insert(characters, text:sub(i, i)) end
for i = 1, #characters do
self.timer:after((i-1)*(0.15/6), function()
self.visuals[i] = 3
if self.visuals[i-1] then self.visuals[i-1] = 2 end
if self.visuals[i-2] then self.visuals[i-2] = 1 end
table.insert(self.characters, characters[i])
end)
end
self.timer:after(0.15, function()
self.visuals = {1, 1, 1, 1, 1, 1}
self.timer:every(0.05, function() self.visible = not self.visible end, math.floor((duration - 0.15)/0.05))
self.timer:after((duration - 0.15), function() self.visible = true; self.dead = true end)
self.timer:every(0.035, function()
local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ"
for i, c in ipairs(self.characters) do
if rng:bool(10) then
local r = rng:int(1, #random_characters)
self.characters[i] = random_characters:sub(r, r)
end
if rng:bool(10) then self.visuals[i] = rng:int(1, 4) end
end
end, math.floor((duration - 0.15)/0.035))
end)
end
function InfoText:update(dt)
self.timer:update(dt)
self.scale_spring:update(dt)
end
function InfoText:draw()
if not self.visible then return end
local w, h = 0, 0
local x, y = self.x - self.w/2, self.y - self.h/2
g.setFont(self.font)
push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy)
for i = 1, #self.characters do
local cw, ch = self.font:getWidth(self.characters[i]), self.font:getHeight()
if self.visuals[i] == 1 then
g.setColor(white)
g.print(self.characters[i], x + w, y + h)
elseif self.visuals[i] == 2 then
g.setColor(white)
rectf(x + w + cw/2, y + h + ch/2, cw, ch)
g.setColor(black)
g.print(self.characters[i], x + w, y + h)
elseif self.visuals[i] == 3 then
g.setColor(white)
rectf(x + w + cw/2, y + h + ch/2, cw, ch)
elseif self.visuals[i] == 4 then
g.setColor(black)
rectf(x + w + cw/2, y + h + ch/2, cw, ch)
end
w = w + self.font:getWidth(self.characters[i])
end
g.setColor(white)
g.rectangle("fill", x, y - 5, self.w*self.t, 3)
pop()
end
ReloadText = Class:extend()
function ReloadText:new(x, y, duration, r)
self.timer = Timer()
self.x, self.y = x, y
self.r = r
self.sx, self.sy = 1, 1
self.characters = {}
self.visuals = {1, 1, 1, 1, 1, 1}
self.visible = true
self.font = font_medium
self.w, self.h = self.font:getWidth("RELOAD"), self.font:getHeight()
self.scale_spring = Spring(1)
self.scale_spring:pull(0.15)
self.t = 0
local characters = {"R", "E", "L", "O", "A", "D"}
for i = 1, 6 do
self.timer:after((i-1)*(0.15/6), function()
self.visuals[i] = 3
if self.visuals[i-1] then self.visuals[i-1] = 2 end
if self.visuals[i-2] then self.visuals[i-2] = 1 end
table.insert(self.characters, characters[i])
end)
end
self.timer:after(0.15, function()
self.visuals = {1, 1, 1, 1, 1, 1}
self.timer:every(0.05, function() self.visible = not self.visible end, math.floor((duration - 0.15)/0.05))
self.timer:after((duration - 0.15), function() self.visible = true end)
self.timer:every(0.035, function()
local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ"
for i, c in ipairs(self.characters) do
if rng:bool(10) then
local r = rng:int(1, #random_characters)
self.characters[i] = random_characters:sub(r, r)
end
if rng:bool(10) then self.visuals[i] = rng:int(1, 4) end
end
end, math.floor((duration - 0.15)/0.035))
end)
end
function ReloadText:update(dt)
self.timer:update(dt)
self.scale_spring:update(dt)
end
function ReloadText:draw()
if not self.visible then return end
local w, h = 0, 0
local x, y = self.x - self.w/2, self.y - self.h/2
g.setFont(self.font)
push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy)
for i = 1, #self.characters do
local cw, ch = self.font:getWidth(self.characters[i]), self.font:getHeight()
if self.visuals[i] == 1 then
g.setColor(white)
g.print(self.characters[i], x + w, y + h)
elseif self.visuals[i] == 2 then
g.setColor(white)
rectf(x + w + cw/2, y + h + ch/2, cw, ch)
g.setColor(black)
g.print(self.characters[i], x + w, y + h)
elseif self.visuals[i] == 3 then
g.setColor(white)
rectf(x + w + cw/2, y + h + ch/2, cw, ch)
elseif self.visuals[i] == 4 then
g.setColor(black)
rectf(x + w + cw/2, y + h + ch/2, cw, ch)
end
w = w + self.font:getWidth(self.characters[i])
end
g.setColor(white)
g.rectangle("fill", x, y - 5, self.w*self.t, 3)
pop()
end
function ReloadText:die()
self.dead = true
end
RefreshEffect = Class:extend()
function RefreshEffect:new(x, y, w, h)
self.x, self.y = x, y
self.w, self.h = w, h
self.oy = h/3
timer:tween(0.15, self, {h = 0}, linear, function() self.dead = true end)
end
function RefreshEffect:update(dt)
end
function RefreshEffect:draw()
g.rectangle("fill", self.x - self.w/2, self.y - self.oy, self.w, self.h)
end
DamageNumber = Class:extend()
function DamageNumber:new(x, y, vx, vy, t)
self.x, self.y = x, y
self.sx, self.sy = 1.00, 1.00
self.vx, self.vy = vx, vy
self.t = t
self.scale_spring = Spring(1)
self.scale_spring:pull(0.25)
self.r = 0
-- if vx > 0 then self.vr = rng:float(2*math.pi, 4*math.pi) else self.vr = rng:float(-4*math.pi, -2*math.pi) end
timer:after(0.25, function()
timer:tween(0.05, self, {sx = 0, sy = 0}, linear, function() self.dead = true end)
end)
end
function DamageNumber:update(dt)
self.scale_spring:update(dt)
self.vy = self.vy + 400*dt
self.x = self.x + self.vx*dt
self.y = self.y + self.vy*dt
-- self.r = self.r + self.vr*dt
end
function DamageNumber:draw()
push(self.x, self.y, self.r, 1.25*self.scale_spring.x*self.sx, 1.25*self.scale_spring.x*self.sy)
draw_text(self.t, self.x, self.y, 0, 1, 1, font_small)
pop()
end
HitEffect = Class:extend()
function HitEffect:new(x, y)
self.x, self.y = x, y
self.r = rng:float(0, 2*math.pi)
self.animation = Animation(0.025, get_animation_frames("hit1"), "once", {[0] = function() self.dead = true end})
self.sx, self.sy = 1.2, 1.2
timer:tween(0.025*get_animation_frames("hit1"), self, {sx = 1, sy = 1}, linear)
end
function HitEffect:update(dt)
self.animation:update(dt)
end
function HitEffect:draw()
draw_animation("hit1", self.animation:get_current_frame(), self.x, self.y, self.r, 1.35*self.sx, 1.35*self.sy)
end
ChargeParticle = Class:extend()
function ChargeParticle:new(x, y, rs)
local r = rng:float(0, 2*math.pi)
local d = rng:float(0.75*rs, 1.25*rs)
self.x, self.y = x + d*math.cos(r), y + d*math.sin(r)
self.rs = rng:float(6, 10)
timer:tween(rng:float(0.5, 1.5), self, {x = x, y = y}, linear, function() self.dead = true end)
end
function ChargeParticle:update(dt)
end
function ChargeParticle:draw()
circlef(self.x, self.y, self.rs, white)
end
BurnParticle = Class:extend()
function BurnParticle:new(x, y)
self.x, self.y = x, y
self.animation = Animation(1, get_animation_frames("smoke1"), "once")
self.sx, self.sy = 0, 0
self.r = rng:float(0, 2*math.pi)
self.v = rng:float(50, 150)
self.rs = 0
self.vr = rng:float(0, 2*math.pi)
timer:tween(rng:float(0.04, 0.06), self, {sx = 0.5, sy = 0.5}, cubic_in_out, function()
timer:tween(rng:float(0.2, 0.4), self, {sx = 0, sy = 0}, linear, function() self.dead = true end)
end)
end
function BurnParticle:update(dt)
self.animation:update(dt)
self.x = self.x + self.v*math.cos(self.r)*dt
self.y = self.y + self.v*math.sin(self.r)*dt
self.rs = self.rs + self.vr*dt
end
function BurnParticle:draw()
draw_animation("smoke1", self.animation:get_current_frame(), self.x, self.y, self.rs, 3*self.sx, 3*self.sy)
end
DustParticle = Class:extend()
function DustParticle:new(x, y, color)
self.x, self.y = x, y
self.animation = Animation(1, get_animation_frames("smoke1"), "once")
self.color = color or red
self.sx, self.sy = 0, 0
self.v = rng:float(100, 220)
self.r = rng:float(0, 2*math.pi)
self.rs = 0
self.vr = rng:float(0, 2*math.pi)
timer:after(0.1, function() self.color = color or white end)
timer:tween(rng:float(0.04, 0.06), self, {sx = 0.7, sy = 0.7}, cubic_in_out, function()
timer:tween(rng:float(0.3, 0.4), self, {sx = 0, sy = 0, v = 0}, linear, function() self.dead = true end)
end)
end
function DustParticle:update(dt)
self.animation:update(dt)
self.rs = self.rs + self.vr*dt
self.x, self.y = self.x + self.v*math.cos(self.r)*dt, self.y + self.v*math.sin(self.r)*dt
end
function DustParticle:draw()
g.setShader(combine)
g.setColor(self.color)
draw_animation("smoke1", self.animation:get_current_frame(), self.x, self.y, self.rs, 3.5*self.sx, 3.5*self.sy)
g.setShader()
g.setColor(white)
end
AnimatedEffect = Class:extend()
function AnimatedEffect:new(x, y, name, delay, loop_mode, r, sx, sy)
self.x, self.y = x, y
self.r, self.sx, self.sy = r or 0, sx or 1, sy or 1
self.name = name
self.delay = delay
self.loop_mode = loop_mode or "once"
self.animation = Animation(delay, get_animation_frames(name), self.loop_mode, {[0] = function() self.dead = true end})
end
function AnimatedEffect:update(dt)
self.animation:update(dt)
end
function AnimatedEffect:draw()
draw_animation(self.name, self.animation:get_current_frame(), self.x, self.y, self.r, self.sx, self.sy)
end
EllipseParticle = Class:extend()
function EllipseParticle:new(x, y, r, v)
self.x, self.y = x, y
self.r = r
self.v = v
self.w, self.h = 9, 3
timer:tween({0.2, 0.5}, self, {v = 0}, linear, function() self.dead = true end)
end
function EllipseParticle:update(dt)
self.x = self.x + self.v*math.cos(self.r)*dt
self.y = self.y + (self.v*math.sin(self.r) + 100)*dt
self.w = remap(self.v, 0, 400, 0, 9)
self.h = remap(self.v, 0, 400, 0, 3)
end
function EllipseParticle:draw()
push(self.x, self.y, math.atan2(self.v*math.sin(self.r) + 100, self.v*math.cos(self.r)))
ellipsef(self.x, self.y, self.w, self.h)
pop()
end
ExplosionParticle = Class:extend()
function ExplosionParticle:new(x, y, r, v)
self.x, self.y = x, y
self.r = r
self.v = v
self.w, self.h = 9, 3
timer:tween({0.2, 0.5}, self, {v = 0}, linear, function() self.dead = true end)
end
function ExplosionParticle:update(dt)
self.x = self.x + self.v*math.cos(self.r)*dt
self.y = self.y + self.v*math.sin(self.r)*dt
self.w = remap(self.v, 0, 400, 0, 9)
self.h = remap(self.v, 0, 400, 0, 3)
end
function ExplosionParticle:draw()
push(self.x, self.y, math.atan2(self.v*math.sin(self.r), self.v*math.cos(self.r)))
ellipsef(self.x, self.y, self.w, self.h)
pop()
end
DeathCircle = Class:extend()
function DeathCircle:new(x, y, r, color)
self.x, self.y = x, y
self.r = r
self.color = color or white
timer:tween(0.26, self, {r = 0}, cubic_in_out, function() self.dead = true end)
end
function DeathCircle:update(dt)
end
function DeathCircle:draw()
circlef(self.x, self.y, self.r, self.color)
end
DeathParticle = Class:extend()
function DeathParticle:new(x, y, r, v, color)
self.x, self.y = x, y
self.r = r
self.v = v
self.w, self.h = 14, 4.5
self.color = color or white
timer:tween({0.2, 0.4}, self, {v = 0}, linear, function() self.dead = true end)
end
function DeathParticle:update(dt)
self.x = self.x + self.v*math.cos(self.r)*dt
self.y = self.y + (self.v*math.sin(self.r) + 0)*dt
self.w = remap(self.v, 0, 400, 0, 14)
self.h = remap(self.v, 0, 400, 0, 4.5)
end
function DeathParticle:draw()
push(self.x, self.y, math.atan2(self.v*math.sin(self.r) + 0, self.v*math.cos(self.r)))
rectf(self.x, self.y, self.w, self.h, nil, nil, self.color)
pop()
end
ShootCircle = Class:extend()
function ShootCircle:new(x, y, rs)
self.x, self.y = x, y
self.rs = rs or 12
timer:tween(0.1, self, {rs = 0}, linear, function() self.dead = true end)
end
function ShootCircle:update(dt)
end
function ShootCircle:draw()
circlef(self.x, self.y, self.rs)
end
ShootCapsule = Class:extend()
function ShootCapsule:new(x, y, r, v)
self.x, self.y = x, y
self.vx, self.vy = v*math.cos(r), v*math.sin(r)
self.w, self.h = 6, 3
self.r = 0
self.vr = rng:float(-4*math.pi, 4*math.pi)
end
function ShootCapsule:update(dt)
self.vy = self.vy + 600*dt
self.x = self.x + self.vx*dt
self.y = self.y + self.vy*dt
self.r = self.r + self.vr*dt
if self.y > gh or self.y < 0 or self.x > gw or self.x < 0 then
self.dead = true
table.insert(ui_effects, ShootCircle(self.x, self.y))
end
end
function ShootCapsule:draw()
push(self.x, self.y, self.r)
rectf(self.x, self.y, self.w, self.h)
pop()
end
FadingShootCapsule = Class:extend()
function FadingShootCapsule:new(x, y, r, v)
self.x, self.y = x, y
self.vx, self.vy = v*math.cos(r), v*math.sin(r)
self.w, self.h = 6, 3
self.r = 0
self.vr = rng:float(-4*math.pi, 4*math.pi)
self.color = {1, 1, 1, 1}
timer:after(0.1, function()
timer:tween({0.2, 0.5}, self.color, {[4] = 0}, linear, function() self.dead = true end)
end)
end
function FadingShootCapsule:update(dt)
self.vy = self.vy + 600*dt
self.x = self.x + self.vx*dt
self.y = self.y + self.vy*dt
self.r = self.r + self.vr*dt
if self.y > gh or self.y < 0 or self.x > gw or self.x < 0 then
self.dead = true
table.insert(ui_effects, ShootCircle(self.x, self.y))
end
end
function FadingShootCapsule:draw()
push(self.x, self.y, self.r)
rectf(self.x, self.y, self.w, self.h, nil, nil, self.color)
pop()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment