Created
January 22, 2020 15:29
-
-
Save a327ex/658773c24afc2d5d0bc95901bca6f788 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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