Created
February 9, 2020 21:09
-
-
Save a327ex/526242cd742b9914fc1a3b6ad991ddb9 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} | |
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) | |
new_animation("transition1", 480, 270) | |
new_animation("transition2", 480, 270) | |
new_animation("transition3", 480, 270) | |
new_animation("transition4", 480, 270) | |
displacements = {} | |
projectiles = {} | |
effects = {} | |
pre_effects = {} | |
enemies = {} | |
ui = {} | |
ammo_bars = {} | |
hp_bars = {} | |
ui_effects = {} | |
cover = {} | |
x1, x2 = gw/2 - gw/4, gw/2 + gw/4 | |
y1, y2 = 0, gh | |
w = x2 - x1 | |
h = y2 - y1 | |
director = Director() | |
timer:every({0.1, 0.8}, function() table.insert(displacements, DisplacementBlock(rng:float(x1, x2), rng:float(y1, y2), rng:float(w/8, w/2), rng:float(h/16, h/8), 0.05, 0.2, rng:float(1, 4))) end) | |
table.insert(cover, BlackRectangle(0, 0, x1, y2)) | |
table.insert(cover, BlackRectangle(x2, 0, gw-x2, y2)) | |
table.insert(ui, UILine(40, x1, y1 - 64, x1, y2 + 64)) | |
table.insert(ui, UILine(40, x2, y1 - 64, x2, y2 + 64)) | |
--[[ | |
timer:after(3, function() | |
main_menu() | |
end) | |
]]-- | |
create_stage_objects() | |
player.can_shoot = true | |
end | |
function main_menu() | |
timer:after(1, function() | |
local x, y = x1 + w/2, h/2 + 20 | |
table.insert(effects, RuneWord(x1 + 1.5*w/10, y1 + 100, w/10, 110, "RUNEHACK")) | |
timer:after(0.25, function() | |
table.insert(effects, RuneButton(x, y, 60, "new_run", 0, -1, x, y + 57, function() to_new_run() end)) | |
timer:after(0.25, function() | |
table.insert(effects, RuneButton(x - 30, y + 90, 60, "options", -1, 0, x, y + 57, function() to_options() end)) | |
timer:after(0.25, function() | |
table.insert(effects, RuneButton(x + 30, y + 90, 60, "quit", 1, 0, x, y + 57, function() to_quit() end)) | |
end) | |
end) | |
end) | |
end) | |
end | |
function to_new_run() | |
table.insert(effects, AnimatedEffect(gw/2, gh/2, "transition1", 0.02, "once", 0, 2, 2, nil, 10)) | |
displace_screen(0.04, get_animation_frames("transition1")/2) | |
timer:after(0.03*get_animation_frames("transition1"), function() | |
for _, o in ipairs(effects) do if o:is(RuneButton) or o:is(RuneWord) then o.dead = true end end | |
for _, o in ipairs(ui) do if o:is(RuneText) then o.dead = true end end | |
table.insert(effects, AnimatedEffect(gw/2, gh/2, "transition2", 0.02, "once", 0, 2, 2, nil, 10)) | |
displace_screen(0.04, get_animation_frames("transition2")/2) | |
timer:after(0.02*math.floor(get_animation_frames("transition2")/2), function() | |
new_run() | |
end) | |
end) | |
end | |
function new_run() | |
create_stage_objects() | |
director:new_round(1, 8, "passives") | |
player.can_shoot = true | |
end | |
function create_stage_objects() | |
if not player then player = Player(gw/2, gh - 6) end | |
local h = (gh - 55) | |
local bh = h/player.max_ammo | |
for i = 1, player.max_ammo do | |
timer:after((i-1)*0.03, function() | |
table.insert(ammo_bars, UIBar(x2 + 24, 55 + (i-1)*(bh), bh - 4, "ammo")) | |
end) | |
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.045, function() | |
table.insert(hp_bars, UIBar(x1 - 24, 55 + (i-1)*(hh), hh - 4, "hp")) | |
end) | |
end | |
player_hp_ui = PlayerHPUI(x1 - 24, 22) | |
timer:after(0.045*(player.max_hp + 1), function() player_resource_ui = PlayerResourceUI(x1 - 24, 22 + gh/2) end) | |
end | |
function destroy_stage_objects() | |
player.can_shoot = false | |
player.visible = false | |
for i = 1, #ammo_bars do ammo_bars[i]:spend() end | |
for i = 1, #hp_bars do hp_bars[i]:spend2() end | |
player_ammo_ui:destroy() | |
player_hp_ui:destroy() | |
player_resource_ui:destroy() | |
ammo_bars = {} | |
hp_bars = {} | |
player_ammo_ui = nil | |
player_hp_ui = nil | |
player_resource_ui = nil | |
end | |
function to_reward(reward_type, round_info) | |
table.insert(effects, AnimatedEffect(gw/2, gh/2, "transition3", 0.02, "stay", 0, 2, 2, nil, 10, 3.5)) | |
displace_screen(0.04, get_animation_frames("transition3")/2) | |
timer:after(0.03*get_animation_frames("transition3"), function() | |
local info_texts = {} | |
table.insert(info_texts, "ROUND SUMMARY") | |
table.insert(info_texts, "ENEMIES KILLED: " .. round_info.enemies) | |
table.insert(info_texts, "HP LOST: " .. round_info.hp) | |
table.insert(info_texts, "RESOURCES GAINED: " .. round_info.resources) | |
for i = 1, 4 do | |
timer:after((i-1)*0.25, function() | |
table.insert(ui, TextLine(x1 + 40, y1 + 100 + i*font_medium:getHeight(), info_texts[i], font_medium, black, white)) | |
end) | |
end | |
destroy_stage_objects() | |
timer:after(2.5, function() | |
table.insert(effects, AnimatedEffect(gw/2, gh/2, "transition4", 0.02, "once", 0, 2, 2, nil, 10)) | |
displace_screen(0.04, get_animation_frames("transition4")/2) | |
timer:after(0.02*math.floor(get_animation_frames("transition4")/2), function() | |
for _, o in ipairs(ui) do if o:is(TextLine) then o.dead = true end end | |
reward(reward_type) | |
end) | |
end) | |
end) | |
end | |
function displace_screen(interval, amount) | |
for i = 1, amount do | |
timer:after((i-1)*interval, function() | |
table.insert(displacements, DisplacementBlock(rng:float(x1, x2), rng:float(y1, y2), rng:float(w/4, w/2), rng:float(h/16, h/8), 0.05, 0.2, rng:float(1, 4))) | |
end) | |
end | |
end | |
function reward(type) | |
local passives = {"double", "triple", "volley", "spread", "burst", "homing", "speed", "accel", "decel", "ricochet", "scatter", "split", "chain", "pierce", "fork", "cross", "blast", "burn", "finale", "weaken", "glitch", "slow", "haste", "stun", "sphere", "spawner"} | |
if type == "passives" then | |
local x, y = x1 + w/2, h/2 + 20 | |
local p1 = table.remove(passives, rng:int(1, #passives)) | |
local p2 = table.remove(passives, rng:int(1, #passives)) | |
local p3 = table.remove(passives, rng:int(1, #passives)) | |
timer:after(0.25, function() | |
table.insert(effects, PassiveRuneButton(x, y, 60, p1, x, y + 57, function() choose_passive(p1) end)) | |
timer:after(0.25, function() | |
table.insert(effects, PassiveRuneButton(x - 30, y + 90, 60, p2, x, y + 57, function() choose_passive(p2) end)) | |
timer:after(0.25, function() | |
table.insert(effects, PassiveRuneButton(x + 30, y + 90, 60, p3, x, y + 57, function() choose_passive(p3) end)) | |
end) | |
end) | |
end) | |
end | |
end | |
function choose_passive(type) | |
table.insert(effects, AnimatedEffect(gw/2, gh/2, "transition1", 0.02, "once", 0, 2, 2, nil, 10)) | |
for i = 1, get_animation_frames("transition1") do timer:after((i-1)*0.02, function() table.insert(displacements, DisplacementBlock(rng:float(x1, x2), rng:float(y1, y2), rng:float(0, x2-x1), rng:float(0, y2-y1), 0.05, 0.2, rng:float(1, 4))) end) end | |
timer:after(0.25, function() table.insert(ui, TransitionRune(x1 + w/2, y1 + h/2, w/2, w/2, type)) end) | |
timer:after(0.03*get_animation_frames("transition1"), function() | |
for _, o in ipairs(effects) do if o:is(PassiveRuneButton) then o.dead = true end end | |
for _, o in ipairs(ui) do if o:is(RuneText) then o.dead = true end end | |
table.insert(effects, AnimatedEffect(gw/2, gh/2, "transition2", 0.02, "once", 0, 2, 2, nil, 10)) | |
for i = 1, get_animation_frames("transition2") do timer:after((i-1)*0.02, function() table.insert(displacements, DisplacementBlock(rng:float(x1, x2), rng:float(y1, y2), rng:float(0, x2-x1), rng:float(0, y2-y1), 0.05, 0.2, rng:float(1, 4))) end) end | |
timer:after(0.02*math.floor(get_animation_frames("transition2")/2), function() | |
for _, o in ipairs(ui) do if o:is(TransitionRune) then o.dead = true end end | |
player.mods[type] = true | |
player:reset() | |
create_stage_objects() | |
director:new_round(director.difficulty + 1, 8, "passives") | |
end) | |
end) | |
end | |
Director = Class:extend() | |
function Director:new() | |
self.difficulty_points = {} | |
self.difficulty_points[1] = 12 | |
for i = 2, 1024, 4 do | |
self.difficulty_points[i] = self.difficulty_points[i-1] + 4 | |
self.difficulty_points[i+1] = self.difficulty_points[i] | |
self.difficulty_points[i+2] = math.floor(self.difficulty_points[i+1]/1.5) | |
self.difficulty_points[i+3] = math.floor(self.difficulty_points[i+2]*2) | |
end | |
self.round_timer = 0 | |
self.running = false | |
self.round_killed_enemies = 0 | |
self.round_hp_lost = 0 | |
self.round_resources_gained = 0 | |
end | |
function Director:update(dt) | |
if not self.running then return end | |
self.round_timer = self.round_timer + dt | |
for i, e in ipairs(self.enemy_spawn_times) do | |
if self.round_timer > e then | |
table.insert(enemies, _G[self.enemy_list[i].name](rng:float(x1 + 32, x2 - 32), y1 - 32, self.enemy_list[i].hp_multiplier or 1)) | |
table.remove(self.enemy_spawn_times, i) | |
break | |
end | |
end | |
if self.round_timer > self.round_duration then | |
self.round_timer = math.min(self.round_timer, self.round_duration) | |
self.round_over = true | |
end | |
if self.round_over and #enemies <= 0 then | |
self.round_over = nil | |
self.running = false | |
self.round_timer = 0 | |
local round_info = {enemies = self.round_killed_enemies, hp = self.round_hp_lost, resources = self.round_resources_gained} | |
self.round_killed_enemies = 0 | |
self.round_hp_lost = 0 | |
self.round_resources_gained = 0 | |
timer:after(0.5, function() | |
to_reward(self.round_reward, round_info) | |
end) | |
end | |
end | |
function Director:draw() | |
if not self.running then return end | |
g.setLineWidth(6) | |
g.line(x1 + (x2-x1)*(self.round_timer/self.round_duration), y1, x2, y1) | |
g.setLineWidth(2) | |
for _, e in ipairs(self.enemy_spawn_times) do | |
g.line(x1 + (x2-x1)*(e/self.round_duration), y1, x1 + (x2-x1)*(e/self.round_duration), y1 + 6) | |
end | |
end | |
function Director:new_round(difficulty, duration, reward) | |
self.running = true | |
self.difficulty = difficulty | |
self.round_duration = duration | |
self.round_difficulty = difficulty | |
self.round_reward = reward | |
local points = self.difficulty_points[difficulty] | |
self.enemy_list = {} | |
while points > 0 do | |
local enemy, points_spent = self:get_next_enemy(difficulty, points) | |
points = points - points_spent | |
table.insert(self.enemy_list, enemy) | |
end | |
self.enemy_spawn_times = {} | |
for i = 1, #self.enemy_list do self.enemy_spawn_times[i] = rng:float(0, duration) end | |
table.sort(self.enemy_spawn_times, function(a, b) return a < b end) | |
end | |
function Director:get_next_enemy(difficulty, points) | |
if difficulty == 1 then | |
return {name = "Rock", hp_multiplier = 1}, 2 | |
elseif difficulty == 2 then | |
return {name = "Rock", hp_multiplier = 1.5}, 2 | |
elseif difficulty == 3 then | |
return {name = "Rock", hp_multiplier = 2}, 2 | |
end | |
end | |
function Director:killed_enemy() | |
self.round_killed_enemies = self.round_killed_enemies + 1 | |
end | |
function Director:lost_hp() | |
self.round_hp_lost = self.round_hp_lost + 1 | |
end | |
function Director:gained_resource(n) | |
self.round_resources_gained = self.round_resources_gained + n | |
end | |
function to_options() | |
end | |
function to_quit() | |
end | |
function update(dt) | |
if keyboard_pressed("k") then test_action() end | |
if player then player:update(dt) end | |
update_objects(displacements, dt) | |
update_objects(projectiles, dt) | |
update_objects(enemies, dt) | |
update_objects(pre_effects, dt) | |
update_objects(effects, dt) | |
update_objects(cover, dt) | |
update_objects(ammo_bars, dt) | |
update_objects(hp_bars, dt) | |
update_objects(ui_effects, dt) | |
update_objects(ui, dt) | |
if player_ammo_ui then player_ammo_ui:update(dt) end | |
if player_hp_ui then player_hp_ui:update(dt) end | |
if player_resource_ui then player_resource_ui:update(dt) end | |
director: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(pre_effects) | |
draw_objects(projectiles) | |
draw_objects(enemies) | |
table.sort(effects, function(a, b) return (a.z or 0) < (b.z or 0) end) | |
draw_objects(effects) | |
if player then player:draw() end | |
draw_objects(cover) | |
draw_objects(ammo_bars) | |
draw_objects(hp_bars) | |
if player_ammo_ui then player_ammo_ui:draw() end | |
if player_hp_ui then player_hp_ui:draw() end | |
if player_resource_ui then player_resource_ui:draw() end | |
draw_objects(ui_effects) | |
table.sort(ui, function(a, b) return (a.z or 0) < (b.z or 0) end) | |
draw_objects(ui) | |
director:draw() | |
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 | |
PassiveRuneButton = Class:extend() | |
function PassiveRuneButton:new(x, y, w, type, px, py, action) | |
self.x, self.y = x, y | |
self.px, self.py = px or x, py or y | |
self.w = w | |
self.type = type | |
self.vs = {-w/2, -w/2, 0, -2*w/2, w/2, -w/2, w/2, w/2, 0, 2*w/2, -w/2, w/2} | |
self.scale_spring = Spring(1) | |
self.polygon = to_polygon(self.x, self.y, self.vs) | |
self.visible = true | |
self.scale_spring:pull(0.3) | |
self.lw = 6 | |
self.action = action | |
timer:tween(0.25, self, {lw = 2}, linear) | |
timer:everyi(0.05, function() self.visible = not self.visible end, 10, function() self.visible = true end) | |
self:displace() | |
end | |
function PassiveRuneButton:update(dt) | |
self.scale_spring:update(dt) | |
local mx, my = camera:get_mouse_position() | |
if mlib.polygon.checkPoint(mx, my, self.polygon) then | |
if not self.hot then | |
self.hot = true | |
self.scale_spring:pull(0.25) | |
timer:everyi(0.05, function() self.visible = not self.visible end, 3, function() self.visible = true end) | |
self.rune_text = RuneText(x1 + 40, y1 + 40, self.type) | |
table.insert(ui, self.rune_text) | |
self:displace() | |
end | |
else | |
if self.hot then | |
self.hot = false | |
self.scale_spring:pull(0.1) | |
timer:everyi(0.05, function() self.visible = not self.visible end, 6, function() self.visible = true end) | |
self.rune_text:die() | |
self.rune_text = nil | |
else | |
if self.rune_text then | |
self.rune_text:die() | |
self.rune_text = nil | |
end | |
end | |
end | |
if self.hot and mouse_pressed(1) then | |
self.scale_spring:pull(0.3) | |
self.action() | |
end | |
end | |
function PassiveRuneButton:draw() | |
if not self.visible then return end | |
push(self.px, self.py, 0, self.scale_spring.x, self.scale_spring.x) | |
if self.hot then | |
self.z = 1 | |
polygonf(self.polygon, white) | |
polygon(self.polygon, 3*self.lw, black) | |
draw_rune(self.x, self.y, self.w, self.w, self.type, 4, black) | |
else | |
self.z = 0 | |
polygon(self.polygon, self.lw, color) | |
draw_rune(self.x, self.y, self.w, self.w, self.type, nil, white) | |
end | |
pop() | |
g.setColor(white) | |
end | |
function PassiveRuneButton:displace(m) | |
for i = 1, rng:int(4, 6) do | |
table.insert(displacements, DisplacementBlock(self.x, self.y + rng:float(-0.75*self.w, 0.75*self.w), rng:float(0.75*self.w, 2*self.w), rng:float(0.1*self.w, 0.25*self.w), 0.05*(m or 1), 0.25*(m or 1))) | |
end | |
end | |
RuneButton = Class:extend() | |
function RuneButton:new(x, y, w, type, tx, ty, px, py, action) | |
self.x, self.y = x, y | |
self.px, self.py = px or x, py or y | |
self.w = w | |
self.tx, self.ty = tx, ty | |
self.type = type | |
self.vs = {-w/2, -w/2, 0, -2*w/2, w/2, -w/2, w/2, w/2, 0, 2*w/2, -w/2, w/2} | |
self.scale_spring = Spring(1) | |
self.polygon = to_polygon(self.x, self.y, self.vs) | |
self.visible = true | |
self.scale_spring:pull(0.3) | |
self.lw = 6 | |
self.action = action | |
timer:tween(0.25, self, {lw = 2}, linear) | |
timer:everyi(0.05, function() self.visible = not self.visible end, 10, function() self.visible = true end) | |
self:displace() | |
end | |
function RuneButton:update(dt) | |
self.scale_spring:update(dt) | |
local mx, my = camera:get_mouse_position() | |
if mlib.polygon.checkPoint(mx, my, self.polygon) then | |
if not self.hot then | |
self.hot = true | |
self.scale_spring:pull(0.25) | |
timer:everyi(0.05, function() self.visible = not self.visible end, 3, function() self.visible = true end) | |
local w, h | |
h = font_medium:getHeight() | |
if self.type == "new_run" then w = font_medium:getWidth("NEW RUN") | |
elseif self.type == "options" then w = font_medium:getWidth("OPTIONS") | |
elseif self.type == "quit" then w = font_medium:getWidth("QUIT") end | |
local x, y | |
if self.tx == -1 then x = self.x - self.w - w + w/4 | |
else x = self.x + self.tx*self.w - w/2 end | |
self.rune_text = RuneText(x, self.y + self.ty*1.5*self.w - h/2, self.type) | |
table.insert(ui, self.rune_text) | |
self:displace() | |
end | |
else | |
if self.hot then | |
self.hot = false | |
self.scale_spring:pull(0.1) | |
timer:everyi(0.05, function() self.visible = not self.visible end, 6, function() self.visible = true end) | |
self.rune_text:die() | |
self.rune_text = nil | |
else | |
if self.rune_text then | |
self.rune_text:die() | |
self.rune_text = nil | |
end | |
end | |
end | |
if self.hot and mouse_pressed(1) then | |
self.scale_spring:pull(0.3) | |
self.action() | |
end | |
end | |
function RuneButton:draw() | |
if not self.visible then return end | |
push(self.px, self.py, 0, self.scale_spring.x, self.scale_spring.x) | |
if self.hot then | |
self.z = 1 | |
polygonf(self.polygon, white) | |
polygon(self.polygon, 3*self.lw, black) | |
draw_rune(self.x, self.y, self.w, self.w, self.type, 4, black) | |
else | |
self.z = 0 | |
polygon(self.polygon, self.lw, color) | |
draw_rune(self.x, self.y, self.w, self.w, self.type, nil, white) | |
end | |
pop() | |
g.setColor(white) | |
end | |
function RuneButton:displace(m) | |
for i = 1, rng:int(4, 6) do | |
table.insert(displacements, DisplacementBlock(self.x, self.y + rng:float(-0.75*self.w, 0.75*self.w), rng:float(0.75*self.w, 2*self.w), rng:float(0.1*self.w, 0.25*self.w), 0.05*(m or 1), 0.25*(m or 1))) | |
end | |
end | |
UILine = Class:extend() | |
function UILine:new(s, x1, y1, x2, y2) | |
self.points = {} | |
self.visible = true | |
timer:after(0.1, function() timer:everyi(0.075, function() self.visible = not self.visible end, 5, function() self.visible = true end) end) | |
local j = 0 | |
local d = distance(x1, y1, x2, y2) | |
local r = math.atan2(y2 - y1, x2 - x1) | |
local n = math.ceil(d/s) | |
local j = 0 | |
for i = 1, n do | |
timer:after(j*0.04, function() | |
table.insert(self.points, x1 + (i-1)*s*math.cos(r)) | |
table.insert(self.points, y1 + (i-1)*s*math.sin(r)) | |
end) | |
j = j + 1 | |
end | |
end | |
function UILine:update(dt) | |
end | |
function UILine:draw() | |
if not self.visible then return end | |
if #self.points >= 4 then | |
g.setLineWidth(2) | |
g.line(self.points) | |
g.setLineWidth(1) | |
end | |
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.visible = true | |
self.attack_spring = Spring(1) | |
self.attack_timer = 100 | |
self.attack_pulse_timer = 0 | |
self.attack_cd = 0.15 | |
self.shape = HC.circle(self.x, self.y, 16) | |
self.shape.parent = self | |
self.base_ammo = 15 | |
self.max_ammo = self.base_ammo | |
self.ammo = self.max_ammo | |
self.max_hp = 10 | |
self.hp = self.max_hp | |
self.resource = 0 | |
self.can_shoot = false | |
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 | |
self.mods.turret = false | |
end | |
function Player:reset() | |
self.reloading = false | |
self.visible = true | |
self.ammo = self.max_ammo | |
self.can_shoot = true | |
end | |
function Player:change_resource(n) | |
self.resource = self.resource + n | |
player_resource_ui:jiggle() | |
director:gained_resource(n) | |
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:set_shoot_positions(x, y, s) | |
local s = self.s*s*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 = x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = 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 = x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = 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 = x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = 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 = x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = 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 = x + s*math.cos(self.r - math.pi/4), y = y + s*math.sin(self.r - math.pi/4)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r - math.pi/8), y = y + s*math.sin(self.r - math.pi/8)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r), y = y + s*math.sin(self.r)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r + math.pi/8), y = y + s*math.sin(self.r + math.pi/8)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r + math.pi/4), y = y + s*math.sin(self.r + math.pi/4)}) | |
else | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r - math.pi/8), y = y + s*math.sin(self.r - math.pi/8)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r), y = y + s*math.sin(self.r)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r + math.pi/8), y = y + s*math.sin(self.r + math.pi/8)}) | |
end | |
elseif self.mods.double then | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r - math.pi/18), y = y + s*math.sin(self.r - math.pi/18)}) | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r + math.pi/18), y = y + s*math.sin(self.r + math.pi/18)}) | |
else | |
table.insert(self.shooting_positions, {x = x + s*math.cos(self.r), y = y + s*math.sin(self.r)}) | |
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 = {} | |
self:set_shoot_positions(self.x, self.y, 1.5) | |
if self.mods.turret then self:set_shoot_positions(self.x - w/10, self.y, 1.2) end | |
local cdm = 1 | |
if self.mods.double then cdm = cdm*1.25 end | |
if self.mods.triple then cdm = cdm*1.67 end | |
if self.mods.volley then cdm = cdm*2 end | |
if self.mods.spread then cdm = cdm*0.8 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 and self.can_shoot 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 j, sp in ipairs(self.shooting_positions) do | |
if j > 1 or self:spend_ammo(am) then | |
self:shoot(sp.x, sp.y) | |
end | |
end | |
end) | |
end | |
else | |
for j, sp in ipairs(self.shooting_positions) do | |
if j > 1 or 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 | |
local m = 1 | |
if self.mods.turret and rd == -1 then m = 1.8 end | |
if mouse_pressed(2) and self.can_shoot then | |
self.reloading = true | |
self.reload_spring:pull(0.4) | |
self.reload_text = ReloadText(self.x + m*rd*62, self.y - 14, self.reload_cd, rd*math.pi/32) | |
table.insert(effects, self.reload_text) | |
end | |
if mouse_released(2) and self.can_shoot 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 and self.can_shoot 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() | |
if not self.visible then return end | |
if self.reloading then | |
if self.reload_text then | |
if not self.reload_text.visible then return end | |
end | |
end | |
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() | |
if self.mods.turret then | |
push(self.x - w/10, self.y, 0, self.s*self.sx, self.s*self.sy) | |
circle(self.x - w/10, self.y, 0.8*(self.rs + self.rso), 2) | |
pop() | |
end | |
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 | |
director:lost_hp() | |
slow(0.5, 0.5) | |
flash(2, black) | |
end | |
function Player:hit2() | |
hp_bars[self.hp]:spend2() | |
self.hp = self.hp - 1 | |
director:lost_hp() | |
slow(0.5, 0.5) | |
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.avx, self.avy = 0, 0 | |
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 vm = self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
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))*vm | |
self.vy = (self.homing_vy or self.v*math.sin(self.r))*vm | |
else | |
self.vx = self.v*math.cos(self.r)*vm | |
self.vy = self.v*math.sin(self.r)*vm | |
end | |
self.x = self.x + (self.vx + self.avx)*dt | |
self.y = self.y + (self.vy + self.avy)*dt | |
self.r = math.atan2(self.vy + self.avy, self.vx + self.avx) | |
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 | |
Enemy = Class:extend() | |
function Enemy:enemy_new(x, y, v, hp) | |
self.id = rng:uid() | |
self.x, self.y, self.r, self.sx, self.sy = x, y, 0, 1, 1 | |
self.timer = Timer() | |
self.v = v | |
self.hp = hp | |
self.vm = 1 | |
self.hit_spring = Spring(1) | |
self.weak_spring = Spring(1) | |
end | |
function Enemy:enemy_update(dt) | |
self.timer:update(dt) | |
self.hit_spring:update(dt) | |
self.weak_spring: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 | |
end | |
function Enemy:collisions(dt) | |
if not self.shape then return end | |
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 | |
if self:is(BreakoutBlock) or self:is(Blocker) then | |
table.insert(effects, DeathCircle(self.x, self.y, self.w/2, white)) | |
table.insert(effects, CircleEffect(self.x, self.y, self.w/4, white)) | |
else | |
table.insert(effects, DeathCircle(self.x, self.y, 2*self.w, white)) | |
table.insert(effects, CircleEffect(self.x, self.y, self.w, white)) | |
end | |
if self.on_bottom_death then | |
self:on_bottom_death() | |
else | |
player:hit(1) | |
flash(1, black) | |
end | |
end | |
end | |
function Enemy:enemy_draw() | |
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 Enemy:hit(damage, r, projectile, m) | |
if self.dead then return end | |
if self.invulnerable then | |
if self.reflect then self:reflect(projectile) end | |
return | |
end | |
if self:is(BigRock) or self:is(TBlock) or self:is(Nuke) or self:is(Shielder) then self.sx, self.sy = 1.15*(m or 1), 1.15*(m or 1) | |
elseif self:is(Blocker) then self.sx, self.sy = 1.05*(m or 1), 1.05*(m or 1) | |
else self.sx, self.sy = 1.35*(m or 1), 1.35*(m or 1) end | |
self.hit_flash = true | |
self.timer:tween(0.1, self, {sx = 1, sy = 1}, linear, function() self.hit_flash = false; self.sx = 1; self.sy = 1 end, "hit") | |
if self.weak then damage = 2*damage end | |
self.hp = self.hp - damage | |
if self.on_hit then self:on_hit(damage, projectile) end | |
if self.hp <= 150 and self:is(Carrier) then self:free() end | |
if self.hp <= 0 then | |
self.dead = true | |
director:killed_enemy() | |
player:change_resource(math.ceil(rng:int(2, 4)*self.hpm)) | |
if self.on_hit_death then self:on_hit_death() end | |
if self.stun_effect then self.stun_effect:die() end | |
if self:is(BreakoutBlock) or self:is(Square) then table.insert(effects, DeathRect(self.x, self.y, 1.5*self.w, 1.5*(self.h or self.w))) | |
elseif self:is(Blocker) then for i = 1, 5 do table.insert(effects, DeathCircle(self.x - self.w/2 + (i-1)*self.w/5 + self.w/10, self.y, self.h)) end | |
else table.insert(effects, DeathCircle(self.x, self.y, 2*self.w)) end | |
if self:is(BigRock) then for i = 1, 4 do table.insert(effects, DustParticle(self.x, self.y, 1.5)) end | |
elseif self:is(Carrierlite) then for i = 1, 1 do table.insert(effects, DustParticle(self.x, self.y)) end | |
elseif self:is(BreakoutBlock) or self:is(Square) then | |
for i = 1, 3 do table.insert(effects, RectParticle(self.x, self.y)) end | |
for i = 1, 3 do table.insert(effects, RectParticle(self.x, self.y, 1.5)) end | |
elseif self:is(Blocker) then | |
for j = 1, 5 do | |
for i = 1, 4 do table.insert(effects, DustParticle(self.x - self.w/2 + (j-1)*self.w/5 + self.w/10, self.y)) end | |
end | |
else for i = 1, 4 do table.insert(effects, DustParticle(self.x, self.y)) end 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, 104) | |
for i, t in ipairs(targets) do | |
timer:after((i-1)*0.05, function() | |
t:burn() | |
end) | |
end | |
end | |
end | |
if self:is(BigRock) then | |
for i = 1, 4 do | |
timer:after((i-1)*0.05, function() | |
table.insert(enemies, Rock(self.x + rng:float(-24, 24), self.y + rng:float(-24, 24), self.hpm)) | |
end) | |
end | |
end | |
end | |
end | |
function Enemy: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 + self.v/10, "radial1", 0.01, nil, nil, 0.5, 0.5)) | |
end, 5, function() self.burning = false end, "burn") | |
end | |
function Enemy: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 Enemy:glitch() | |
self.glitched = true | |
self.timer:everyi(0.3, function() | |
self:hit(40, nil, nil, 1.15) | |
for j = 1, 3 do | |
self.timer:after((j-1)*(0.3/3), function() | |
local x, y = self.x + rng:float(-self.w, self.w), self.y + rng:float(-self.w, self.w) | |
for i = 1, 2 do table.insert(effects, EllipseParticle(x, y, rng:float(0, 2*math.pi), rng:float(100, 300))) end | |
table.insert(displacements, DisplacementBlock(x, y, rng:float(self.w/2, 1.5*self.w), rng:float(self.w/2, self.w), 0.1, 0.25)) | |
table.insert(effects, Block(x, y, rng:float(self.w/2, 1.5*self.w), rng:float(self.w/2, self.w), 0.1, 0.25)) | |
end) | |
end | |
end, 20, nil, "glitche") | |
self.timer:after(6, function() self.glitched = false end, "glitcha") | |
end | |
function Enemy: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 | |
local s = remap(self.w, 0, 64, 0, 3) | |
table.insert(effects, AnimatedEffect(self.x, self.y + 4, rng:table({"disappear1", "disappear2"}), 0.02, nil, math.pi/2, s, s)) | |
end, 15, function() self.slowed = false end, "slowe") | |
end | |
function Enemy: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 | |
local s = remap(self.w, 0, 64, 0, 3) | |
table.insert(effects, AnimatedEffect(self.x, self.y + 4, rng:table({"disappear1", "disappear2"}), 0.02, nil, 0, s, s)) | |
end, 15, function() self.haste = false end, "hastee") | |
end | |
function Enemy: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 | |
function get_close_projectiles(x, y, d) | |
local out = {} | |
for _, e in ipairs(projectiles) do | |
if distance(e.x, e.y, x, y) < d then | |
table.insert(out, e) | |
end | |
end | |
return out | |
end | |
function test_action() | |
--[[ | |
for i = 1, 10 do | |
for j = 1, 5 do | |
if rng:bool(75) then | |
table.insert(enemies, BreakoutBlock(x1 + (i-1)*(w/10) + w/20, y1 - 7*math.floor(w/30) + math.floor(w/60) + (j-1)*(math.floor(w/30)))) | |
end | |
end | |
end | |
]]-- | |
-- table.insert(enemies, Square(rng:float(x1 + 32, x2 - 32), y1 - 32)) | |
-- table.insert(enemies, BreakoutBlock(x1 + rng:float(32, w - 32), y1 - w/10)) | |
-- table.insert(enemies, TBlock(rng:float(x1 + 64, x2 - 64), y1 - 24*4 - 12)) | |
table.insert(enemies, Blocker(rng:float(x1 + 128, x2 - 128), y1 - 64)) | |
end | |
-- segmented rotating worm | |
-- enemy that moves to shield other enemies | |
-- small medic enemy that attaches to other enemies to heal them and has to be sniped while it’s moving between them | |
-- little upgrade triangle of icons appears, selecting them doesn't seem to work, music starts up, boss reveals its disguise! | |
-- floaty enemy that zips around the screen like a balloon when shot | |
-- pair of enemies that create a bullet nullifying laser between them which dissipates when you kill one of them | |
Blocker = Class:extend() | |
Blocker:implement(Enemy) | |
function Blocker:new(x, y, hpm) | |
self.x, self.y = x, y | |
self.w, self.h = 256, 64 | |
local w, h = self.w, self.h | |
self.vs = {-0.5*w, -0.5*h, -0.4*w, -0.5*h, -0.4*w, -0.1*h, 0.4*w, -0.1*h, 0.4*w, -0.5*h, 0.5*w, -0.5*h, 0.5*w, 0.5*h, 0.4*w, 0.1*h, -0.4*w, 0.1*h, -0.5*w, 0.5*h} | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 25, 4000*self.hpm) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.timer:everyi({0.02, 0.06}, function() | |
table.insert(pre_effects, CarrierParticle(self.x - self.w/2 + rng:float(-0.025*w, 0.025*w) + 0.05*self.w, self.y - 0.4*h, rng:float(50, 125), rng:float(-math.pi/2 - math.pi/8, -math.pi/2 + math.pi/8))) | |
table.insert(pre_effects, CarrierParticle(self.x + self.w/2 + rng:float(-0.025*w, 0.025*w) - 0.05*self.w, self.y - 0.4*h, rng:float(50, 125), rng:float(-math.pi/2 - math.pi/8, -math.pi/2 + math.pi/8))) | |
end) | |
end | |
function Blocker:update(dt) | |
self:enemy_update(dt) | |
self.y = self.y + self.v*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Blocker:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.vr, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
polygonf(to_polygon(self.x, self.y, self.vs), black) | |
polygon(to_polygon(self.x, self.y, self.vs), self.lw, white) | |
pop() | |
self:enemy_draw() | |
end | |
Shielder = Class:extend() | |
Shielder:implement(Enemy) | |
function Shielder:new(x, y, hpm) | |
self.x, self.y = x, y | |
self.rs = 16 | |
self.w = 16 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 33, 1000*self.hpm) | |
self.shape = HC.circle(self.x, self.y, self.rs) | |
self.shape.parent = self | |
end | |
function Shielder:update(dt) | |
self:enemy_update(dt) | |
self.y = self.y + self.v*dt | |
if self.regen then | |
self.hp = self.hp + 2000*dt | |
self.hp = math.min(self.hp, 1000) | |
end | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Shielder:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.vr, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
circlef(self.x, self.y, self.rs + rng:float(-0.5, 0.5)) | |
circle(self.x, self.y, remap(self.hp, 0, 1000, 1, 3)*(self.rs + rng:float(-0.5, 0.5)), self.lw) | |
pop() | |
self:enemy_draw() | |
end | |
function Shielder:on_hit() | |
self.regen = false | |
self.timer:after(0.75, function() self.regen = true end, "shielder_hit") | |
end | |
Reflecter = Class:extend() | |
Reflecter:implement(Enemy) | |
function Reflecter:new(x, y, hpm) | |
self.x, self.y = x, y | |
self.w, self.h = 16, 16 | |
local w, h = self.w, self.h | |
self.vs = {-w/4, -h, w/4, -h, w/4, -h/2, w/2, -h/2, w/2, -h/4, w, -h/4, w, h/4, w/2, h/4, w/2, h/2, w/4, h/2, w/4, h, -w/4, h, -w/4, h/2, -w/2, h/2, -w/2, h/4, -w, h/4, -w, -h/4, -w/2, -h/4, -w/2, -h/2, -w/4, -h/2} | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 50, 200*self.hpm) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
self.bv = self.v | |
self.r = math.pi/2 | |
self.vr = self.r | |
self.vvr = rng:float(-math.pi/2, math.pi/2) | |
self.it = 0 | |
self.icd = 0.08 | |
end | |
function Reflecter:update(dt) | |
self:enemy_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.vr = self.vr + self.vvr*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
if self.invulnerable then | |
self.it = self.it + dt | |
if self.it > self.icd then | |
self.it = 0 | |
for i = 1, 1 do table.insert(pre_effects, ReflecterParticle(self.x, self.y, rng:float(25, 200), rng:float(0, 2*math.pi))) end | |
end | |
end | |
end | |
function Reflecter:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
if self.invulnerable then self.lw = 6 end | |
push(self.x, self.y, self.vr, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
polygonf(to_polygon(self.x, self.y, self.vs), black) | |
polygon(to_polygon(self.x, self.y, self.vs), self.lw, white) | |
rectf(self.x, self.y, self.w, self.h, nil, nil, black) | |
rect(self.x, self.y, self.w, self.h, nil, nil, self.lw, white) | |
pop() | |
self:enemy_draw() | |
end | |
function Reflecter:on_hit(damage, projectile) | |
self.invulnerable = true | |
self.timer:tween(0.2, self, {v = 0, vr = rng:table({5*math.pi/4, -math.pi/4}), vvr = 0}, linear, function() | |
self.timer:after(2, function() | |
self.timer:tween(0.2, self, {v = self.bv, vr = math.pi/2, vvr = rng:float(-math.pi/2, math.pi/2)}, linear, function() | |
self.invulnerable = false | |
end) | |
end) | |
end) | |
self:reflect(projectile) | |
end | |
function Reflecter:reflect(projectile) | |
local r = projectile.r + math.pi + rng:float(-math.pi/8, math.pi/8) | |
table.insert(effects, ShootCircle(self.x, self.y)) | |
table.insert(projectiles, SecondaryProjectile(self.x + self.w*math.cos(r), self.y + self.w*math.sin(r), rng:float(0.8*projectile.v, 1.2*projectile.v), r)) | |
table.insert(projectiles, SecondaryProjectile(self.x + self.w*math.cos(r), self.y + self.w*math.sin(r), rng:float(0.8*projectile.v, 1.2*projectile.v), r)) | |
end | |
ReflecterParticle = Class:extend() | |
function ReflecterParticle:new(x, y, v, r) | |
self.x, self.y = x, y | |
self.v = v | |
self.r = r | |
self.w, self.h = rng:float(12, 16), rng:float(4, 8) | |
local d = rng:float(0.9, 1.6) | |
timer:tween(d, self, {v = 0, w = 0, h = 0}, linear, function() self.dead = true end) | |
timer:after(d/3, function() | |
timer:tween(d/3, self, {r = self.r + rng:float(-math.pi/4, math.pi/4)}, linear, function() | |
timer:tween(d/3, self, {r = self.r + rng:float(-math.pi/8, math.pi/8)}, linear, function() | |
self.dead = true | |
end) | |
end) | |
end) | |
end | |
function ReflecterParticle: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 ReflecterParticle:draw() | |
push(self.x, self.y, self.r) | |
rectf(self.x, self.y, self.w, self.h, self.h/2, self.h/2) | |
pop() | |
end | |
Nuke = Class:extend() | |
Nuke:implement(Enemy) | |
function Nuke:new(x, y, hpm) | |
self.x, self.y = x, y | |
self.w, self.h = 24, 48 | |
local w, h = self.w, self.h | |
self.vs = {0, h, -0.5*w, 0.8*h, -0.5*w, -0.4*h, -w, -0.6*h, -w, -h, -0.5*w, -0.75*h, 0.5*w, -0.75*h, w, -h, w, -0.6*h, 0.5*w, -0.4*h, 0.5*w, 0.8*h} | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 10, 6000*self.hpm) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
self.timer:everyi({0.02, 0.06}, function() | |
table.insert(pre_effects, CarrierParticle(self.x + rng:float(-0.3*w, 0.3*w), self.y - 0.65*h, rng:float(50, 125), rng:float(-math.pi/2 - math.pi/8, -math.pi/2 + math.pi/8))) | |
end) | |
end | |
function Nuke:update(dt) | |
self:enemy_update(dt) | |
self.y = self.y + self.v*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Nuke:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
polygonf(to_polygon(self.x, self.y, self.vs), black) | |
polygon(to_polygon(self.x, self.y, self.vs), self.lw, white) | |
pop() | |
self:enemy_draw() | |
end | |
function Nuke:on_hit_death() | |
flash(1, {1, 1, 1, 0.4}) | |
for i = 1, 4 do | |
timer:after((i-1)*0.2, function() | |
local rx, ry = rng:float(-self.w/2, self.w/2), rng:float(-self.w/2, self.w/2) | |
camera:shake(6, 0.25) | |
table.insert(effects, ExplosionCircle2(self.x + rx, self.y + ry, 1.5*self.w)) | |
for i = 1, rng:int(8, 12) do table.insert(effects, EllipseParticle(self.x + rx, self.y + ry, rng:float(0, 2*math.pi), rng:float(200, 800))) end | |
end) | |
end | |
end | |
function Nuke:on_bottom_death() | |
flash(2, black) | |
table.insert(effects, ExplosionCircle3(self.x, self.y)) | |
end | |
NukeParticle = Class:extend() | |
function NukeParticle:new(x, y, v, r) | |
self.x, self.y = x, y | |
self.v = v | |
self.r = r | |
self.w, self.h = rng:float(20, 28), rng:float(8, 16) | |
timer:tween(rng:float(0.2, 0.6), self, {v = 0, w = 0, h = 0}, linear, function() self.dead = true end) | |
end | |
function NukeParticle: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 NukeParticle:draw() | |
push(self.x, self.y, self.r) | |
rectf(self.x, self.y, self.w, self.h, self.h/2, self.h/2) | |
pop() | |
end | |
NukeParticle2 = Class:extend() | |
function NukeParticle2:new(x, y, v, r) | |
self.x, self.y = x, y | |
self.v = v | |
self.r = r | |
self.w, self.h = rng:float(20, 28), rng:float(8, 16) | |
local d = rng:float(0.6, 1.2) | |
timer:tween(d, self, {v = 0, w = 0, h = 0}, linear, function() self.dead = true end) | |
timer:after(d/3, function() | |
timer:tween(d/3, self, {r = self.r + rng:float(-math.pi/4, math.pi/4)}, linear, function() | |
timer:tween(d/3, self, {r = self.r + rng:float(-math.pi/8, math.pi/8)}, linear, function() | |
self.dead = true | |
end) | |
end) | |
end) | |
end | |
function NukeParticle2: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 NukeParticle2:draw() | |
push(self.x, self.y, self.r) | |
rectf(self.x, self.y, self.w, self.h, self.h/2, self.h/2) | |
pop() | |
end | |
NukeCircle = Class:extend() | |
function NukeCircle:new(x, y) | |
self.x, self.y = x, y | |
self.rs = 0 | |
timer:everyi(2.2/5, function() | |
table.insert(effects, CircleEffect(self.x, self.y, self.rs)) | |
camera:shake(16, 0.25) | |
player:hit2() | |
for i = 1, 4 do | |
timer:after((i-1)*0.11, function() | |
local rx, ry = rng:float(-self.rs/2, self.rs/2), rng:float(-self.rs/2, self.rs/2) | |
camera:shake(4, 0.25) | |
table.insert(effects, ExplosionCircle2(self.x + rx, self.y + ry, self.rs/1.5)) | |
for i = 1, rng:int(8, 12) do table.insert(effects, NukeParticle(self.x + rx, self.y + ry, rng:float(200, 800), rng:float(0, 2*math.pi))) end | |
end) | |
end | |
end, 4) | |
timer:tween(0.2, self, {rs = 64}, cubic_in_out, function() | |
timer:tween(2, self, {rs = self.rs + 64, pcd = 0.02}, linear, function() | |
timer:tween(0.2, self, {rs = 0, pcd = 1}, cubic_in_out, function() | |
timer:after(0.4, function() | |
for i = 1, 110 do timer:after((i-1)*0.02, function() table.insert(effects, NukeParticle2(self.x, self.y, rng:float(400, 800), rng:float(0, 2*math.pi))) end) end | |
timer:tween(4, self, {pcd = 0.4}, linear, function() | |
self.dead = true | |
player:hit2() | |
end) | |
flash_tween(4, {1, 1, 1, 1}, {1, 1, 1, 0}) | |
slow(0.8, 2) | |
camera:shake(24, 4) | |
table.insert(effects, ExplosionCircle2(self.x, self.y, 256, 16)) | |
for i = 1, rng:int(16, 24) do table.insert(effects, NukeParticle(self.x, self.y, rng:float(400, 1600), rng:float(0, 2*math.pi))) end | |
end) | |
end) | |
end) | |
end) | |
self.ptimer = 0 | |
self.pcd = 0.08 | |
end | |
function NukeCircle:update(dt) | |
self.ptimer = self.ptimer + dt | |
if self.ptimer > self.pcd then | |
self.ptimer = 0 | |
for i = 1, 4 do table.insert(effects, NukeParticle(self.x, self.y, remap(self.rs, 0, 128, 0, 2)*rng:float(600, 800), rng:float(0, 2*math.pi))) end | |
end | |
end | |
function NukeCircle:draw() | |
circlef(self.x, self.y, self.rs + rng:float(-self.rs/40, self.rs/40)) | |
end | |
ExplosionCircle3 = Class:extend() | |
function ExplosionCircle3:new(x, y) | |
self.x, self.y = x, y | |
self.lw = 8 | |
self.rs = 0 | |
camera:shake(4, 1) | |
flash_tween(1, {1, 1, 1, 0.3}, {1, 1, 1, 0}) | |
timer:tween(1, self, {rs = 1000, lw = 2000}, cubic_in, function() | |
camera:shake(8, 2) | |
timer:tween(2, self, {lw = 0}, cubic_out, function() self.dead = true end) | |
table.insert(effects, NukeCircle(self.x, self.y)) | |
end) | |
end | |
function ExplosionCircle3:update(dt) | |
end | |
function ExplosionCircle3:draw() | |
circle(self.x, self.y, self.rs, self.lw) | |
end | |
TBlock = Class:extend() | |
TBlock:implement(Enemy) | |
function TBlock:new(x, y, hpm) | |
self.x, self.y = x, y | |
self.w, self.h = 24, 24 | |
self.type = rng:int(1, 9) | |
local x, y = x, y | |
local w, h = self.w, self.h | |
if self.type == 1 then | |
self.vs = {x, y, x + w, y, x + w, y + h, x + 2*w, y + h, x + 2*w, y + 2*h, x + w, y + 2*h, x + w, y + 3*h, x, y + 3*h, x, y + 2*h, x - w, y + 2*h, x - w, y + h, x, y + h} | |
elseif self.type == 2 then | |
self.vs = {x, y, x + w, y, x + w, y + h, x + 2*w, y + h, x + 2*w, y + 2*h, x - w, y + 2*h, x - w, y + h, x, y + h} | |
elseif self.type == 3 then | |
self.vs = {x, y, x + w, y, x + w, y + 2*h, x + 2*w, y + 2*h, x + 2*w, y + 3*h, x - w, y + 3*h, x - w, y + 2*h, x, y + 2*h} | |
elseif self.type == 4 then | |
self.vs = {x, y, x + w, y, x + w, y + 2*h, x + 2*w, y + 2*h, x + 2*w, y + 3*h, x, y + 3*h} | |
elseif self.type == 5 then | |
self.vs = {x, y, x + w, y, x + w, y + 4*h, x, y + 4*h} | |
elseif self.type == 6 then | |
self.vs = {x, y, x + 2*w, y, x + 2*w, y + 2*h, x, y + 2*h} | |
elseif self.type == 7 then | |
self.vs = {x, y, x + w, y, x + w, y + h, x + 2*w, y + h, x + 2*w, y + 2*h, x, y + 2*h} | |
elseif self.type == 8 then | |
self.vs = {x, y, x + 2*w, y, x + 2*w, y + h, x, y + h} | |
elseif self.type == 9 then | |
self.vs = {x, y, x + 2*w, y, x + 2*w, y + h, x + w, y + h, x + w, y + 3*h, x - w, y + 3*h, x - w, y + 2*h, x, y + 2*h} | |
end | |
local cx, cy = mlib.polygon.getCentroid(self.vs) | |
for i = 1, #self.vs, 2 do | |
self.vs[i] = self.vs[i] - cx | |
self.vs[i+1] = self.vs[i+1] - cy | |
end | |
-- for i = 1, #self.vs, 2 do self.vs[i], self.vs[i+1] = rotate_point(self.vs[i], self.vs[i+1], r, cx, cy) end | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 0, 1000*self.hpm) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
self.r = rng:table({0, math.pi/2, math.pi, 3*math.pi/2}) | |
self.shape:setRotation(self.r) | |
self.timer:everyi(1, function() self.y = self.y + self.h end) | |
end | |
function TBlock:update(dt) | |
self:enemy_update(dt) | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function TBlock:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
for i = 1, #self.vs-2, 2 do | |
line12(self.x + self.vs[i], self.y + self.vs[i+1], self.x + self.vs[i+2], self.y + self.vs[i+3], self.lw, white) | |
if i == #self.vs-2 then line12(self.x + self.vs[i], self.y + self.vs[i+1], self.x + self.vs[1], self.y + self.vs[2], self.lw, white) end | |
end | |
pop() | |
self:enemy_draw() | |
end | |
SquareBlock = Class:extend() | |
SquareBlock:implement(Enemy) | |
function SquareBlock:new(x, y, hpm, links) | |
self.x, self.y = x, y | |
self.w, self.h = 32, 32 | |
self.lw = 2 | |
self.links = links | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 0, 1000*self.hpm) | |
self.shape = HC.rectangle(self.x, self.y, self.w, self.h) | |
self.shape.parent = self | |
self.timer:everyi(1, function() self.y = self.y + self.h end) | |
end | |
function SquareBlock:update(dt) | |
self:enemy_update(dt) | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function SquareBlock:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
rect(self.x, self.y, self.w, self.h, nil, nil, self.lw) | |
pop() | |
self:enemy_draw() | |
end | |
function SquareBlock:on_hit(damage, projectile) | |
for _, link in ipairs(self.links) do | |
link:hit(damage, nil, projectile) | |
end | |
end | |
Bouncer = Class:extend() | |
Bouncer:implement(Enemy) | |
function Bouncer:new(x, y, hpm) | |
self.rs = 10 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 0, 1000*self.hpm) | |
self.shape = HC.circle(self.x, self.y, self.rs) | |
self.shape.parent = self | |
self.w = self.rs | |
self.rspring = Spring(1) | |
self.v = 150 | |
self.bv = 150 | |
self.r = math.pi/2 | |
self.tr = 0 | |
if self.x < x1 + w/2 then self.tr = math.pi + math.pi/8 | |
else self.tr = -math.pi/8 end | |
self.timer:after(0.5, function() | |
self.timer:tween(0.2, self, {r = self.tr, v = 2*self.bv}, linear, function() | |
self.r = self.tr | |
end) | |
end) | |
self.n = 0 | |
end | |
function Bouncer:update(dt) | |
self:enemy_update(dt) | |
self.rspring:update(dt) | |
if self.x < x1 then | |
self.x = x1 + 2 | |
self.r = math.pi - self.r | |
self.v = 1.5*self.bv | |
self.rspring:pull(0.5) | |
self.n = self.n + 1 | |
if self.n == 3 then | |
self.n = 0 | |
self.v = 0 | |
self.timer:after(3, function() | |
self.v = 1.5*self.bv | |
self.rspring:pull(0.5) | |
self.timer:tween(0.4, self, {r = math.pi/2, v = self.bv}, linear, function() | |
self.r = math.pi/2 | |
self.timer:tween(0.4, self, {r = self.tr, v = 1.5*self.bv}, linear, function() | |
self.r = self.tr | |
end) | |
end) | |
end) | |
else | |
self.timer:tween(0.4, self, {r = math.pi/2, v = self.bv}, linear, function() | |
self.r = math.pi/2 | |
self.timer:tween(0.4, self, {r = self.tr, v = 1.5*self.bv}, linear, function() | |
self.r = self.tr | |
end) | |
end) | |
end | |
end | |
if self.x > x2 then | |
self.x = x2 - 2 | |
self.r = math.pi - self.r | |
self.v = 1.5*self.bv | |
self.rspring:pull(0.5) | |
self.n = self.n + 1 | |
if self.n == 3 then | |
self.n = 0 | |
self.v = 0 | |
self.timer:after(3, function() | |
self.v = 1.5*self.bv | |
self.rspring:pull(0.5) | |
self.timer:tween(0.4, self, {r = math.pi/2, v = self.bv}, linear, function() | |
self.r = math.pi/2 | |
self.timer:tween(0.4, self, {r = self.tr, v = 1.5*self.bv}, linear, function() | |
self.r = self.tr | |
end) | |
end) | |
end) | |
else | |
self.timer:tween(0.4, self, {r = math.pi/2, v = self.bv}, linear, function() | |
self.r = math.pi/2 | |
self.timer:tween(0.4, self, {r = self.tr, v = 1.5*self.bv}, linear, function() | |
self.r = self.tr | |
end) | |
end) | |
end | |
end | |
self.x = self.x + self.v*math.cos(self.r)*dt | |
self.y = self.y + self.v*math.sin(self.r)*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Bouncer:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.rspring.x*self.hit_spring.x*self.sx, self.rspring.x*self.hit_spring.x*self.sy) | |
-- local mx, my = remap(self.v*math.cos(self.r), -1, 1), remap() | |
ellipse(self.x, self.y, self.rs, self.rs, self.lw) | |
pop() | |
self:enemy_draw() | |
end | |
Carrier = Class:extend() | |
Carrier:implement(Enemy) | |
function Carrier:new(x, y, hpm) | |
self.w, self.h = 24, 20 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 0, 600*self.hpm) | |
self.shape = HC.rectangle(self.x, self.y, self.w, self.h) | |
self.shape.parent = self | |
self.vs = {self.w, 0, -self.w/2, -self.h/2, -self.w/2, self.h/2} | |
self.r = math.pi/2 | |
self.v = 25 | |
self.cspring = Spring(1) | |
self.timer:everyi({0.05, 0.15}, function() | |
table.insert(pre_effects, CarrierParticle(self.x, self.y, rng:float(50, 125), rng:float(-math.pi/2 - math.pi/8, -math.pi/2 + math.pi/8))) | |
self.cspring:pull(0.1) | |
end) | |
self.rspring = Spring(0, 10, 1) | |
self.timer:everyi(2, function() | |
self.timer:during(0.5, function() | |
self.rspring:pull(1/60) | |
end) | |
end, nil, nil, "rs") | |
self.g = 0 | |
self.rr = self.w | |
end | |
function Carrier:update(dt) | |
self:enemy_update(dt) | |
self.cspring:update(dt) | |
self.rspring:update(dt) | |
if not self.freed then self.r = math.pi/2 + self.rspring.x*math.pi/4 end | |
self.x = self.x + self.v*math.cos(self.r)*dt | |
self.y = self.y + (self.v*math.sin(self.r) + self.g)*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Carrier:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 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), self.lw, white) | |
push(self.x - self.w/4, self.y, 0, self.cspring.x, self.cspring.x) | |
local rr = rng:float(-1, 1) | |
circlef(self.x - self.w/2, self.y, 0.55*self.rr + rr, black) | |
circle(self.x - self.w/2, self.y, 0.55*self.rr + rr, self.lw, white) | |
pop() | |
pop() | |
self:enemy_draw() | |
end | |
function Carrier:free() | |
if self.freed then return end | |
self.freed = true | |
self.timer:tween(0.1, self, {rr = 3*self.rr}, cubic_in_out, function() | |
self.timer:cancel("rs") | |
self.timer:tween(0.2, self, {rr = 0, r = math.pi/2}, linear, function() | |
for i = 1, 2 do table.insert(effects, DustParticle(self.x, self.y)) end | |
for i = 1, 10 do table.insert(enemies, Carrierlite(self.x, self.y - self.w/2, 1, rng:float(50, 200), rng:float(-3*math.pi/4, -math.pi/4))) end | |
self.g = 400 | |
table.insert(effects, DeathCircle(self.x, self.y - self.w/2, self.w, white)) | |
table.insert(effects, CircleEffect(self.x, self.y - self.w/2, self.w/2, white)) | |
table.insert(effects, ExplosionCircle2(self.x, self.y - self.w/2, self.w)) | |
self.timer:tween(0.3, self, {g = 150}, linear) | |
end) | |
end) | |
end | |
Carrierlite = Class:extend() | |
Carrierlite:implement(Enemy) | |
function Carrierlite:new(x, y, hpm, v, r) | |
self.w = rng:float(2.5, 4) | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, v, 50*self.hpm) | |
self.shape = HC.circle(self.x, self.y, self.w) | |
self.shape.parent = self | |
self.v = v | |
self.r = r | |
self.vx, self.vy = self.v*math.cos(self.r), self.v*math.sin(self.r) | |
self.timer:tween(4, self, {v = self.v/8}, linear) | |
end | |
function Carrierlite:update(dt) | |
self:enemy_update(dt) | |
self.vx, self.vy = self.v*math.cos(self.r), self.v*math.sin(self.r) | |
if self.x < x1 then self.vx = -self.vx; self.x = x1 + 2; self.r = math.pi - self.r end | |
if self.x > x2 then self.vx = -self.vx; self.x = x2 - 2; self.r = math.pi - self.r end | |
self.x = self.x + self.vx*dt | |
self.y = self.y + (self.vy + 50)*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Carrierlite:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.rr, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
circle(self.x, self.y, self.w + rng:float(-1, 1), self.lw) | |
pop() | |
self:enemy_draw() | |
end | |
CarrierParticle = Class:extend() | |
function CarrierParticle:new(x, y, v, r) | |
self.x, self.y = x, y | |
self.d = r | |
self.v = v | |
self.r = 0 | |
timer:tween(0.1, self, {r = rng:float(3, 6)}, cubic_in_out, function() | |
timer:tween(0.25, self, {r = 0, d = -math.pi/2}, linear, function() self.dead = true end) | |
end) | |
end | |
function CarrierParticle:update(dt) | |
self.x = self.x + self.v*math.cos(self.d)*dt | |
self.y = self.y + self.v*math.sin(self.d)*dt | |
end | |
function CarrierParticle:draw() | |
circlef(self.x, self.y, self.r) | |
end | |
Square = Class:extend() | |
Square:implement(Enemy) | |
function Square:new(x, y, hpm) | |
self.w = w/12 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 0, 400*self.hpm) | |
self.shape = HC.rectangle(self.x, self.y, self.w, self.w) | |
self.shape.parent = self | |
self.timer:everyi(1, function() | |
local v = rng:weighted_pick(50, 25, 25) | |
if v == 1 then | |
local ny = self.y + w/12 | |
self.timer:tween(0.1, self, {y = ny}, linear) | |
elseif v == 2 then | |
local nx = self.x - w/12 | |
if nx <= x1 + w/8 then nx = self.x + w/12 end | |
self.timer:tween(0.1, self, {x = nx}, linear) | |
elseif v == 3 then | |
local nx = self.x + w/12 | |
if nx >= x2 - w/8 then nx = self.x - w/12 end | |
self.timer:tween(0.1, self, {x = nx}, linear) | |
end | |
end) | |
end | |
function Square:update(dt) | |
self:enemy_update(dt) | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Square:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
rectf(self.x, self.y, self.w, self.w, nil, nil, black) | |
rect(self.x, self.y, self.w, self.w, nil, nil, self.lw, white) | |
pop() | |
self:enemy_draw() | |
end | |
BreakoutBlock = Class:extend() | |
BreakoutBlock:implement(Enemy) | |
function BreakoutBlock:new(x, y, hpm) | |
self.x, self.y = x, y | |
self.w, self.h = w/10, math.floor(w/30) | |
self.lw = 2 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 0, 400*self.hpm) | |
self.shape = HC.rectangle(self.x, self.y, self.w, self.h) | |
self.shape.parent = self | |
self.timer:everyi(1, function() self.y = self.y + self.h end) | |
end | |
function BreakoutBlock:update(dt) | |
self:enemy_update(dt) | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function BreakoutBlock:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
rect(self.x, self.y, self.w, self.h, nil, nil, self.lw) | |
pop() | |
self:enemy_draw() | |
end | |
Absorber = Class:extend() | |
Absorber:implement(Enemy) | |
function Absorber:new(x, y, hpm) | |
self.w = 12 | |
self.lw = 2 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 50, 100000*self.hpm) | |
self.shape = HC.circle(self.x, self.y, self.w) | |
self.shape.parent = self | |
self.absorb_hp = 0 | |
self.absorbed_projectiles = 0 | |
self.absorbed_timer = 0 | |
end | |
function Absorber:update(dt) | |
self:enemy_update(dt) | |
self.y = self.y + self.v*self.vm*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
for _, p in ipairs(get_close_projectiles(self.x, self.y, 400)) do | |
local r = math.atan2(self.y - p.y, self.x - p.x) | |
p.avx = 0.2*p.v*math.cos(r) | |
p.avy = 0.2*p.v*math.sin(r) | |
end | |
self.absorbed_timer = self.absorbed_timer + dt | |
if self.absorbed_projectiles > 0 then | |
if self.absorbed_timer > 1.5/self.absorbed_projectiles then | |
self.absorbed_timer = 0 | |
table.insert(effects, AbsorbParticle(self)) | |
end | |
end | |
end | |
function Absorber:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
local r = remap(self.absorb_hp, 0, 1000, 0, 2) | |
local rr = rng:float(-r, r) | |
circle(self.x, self.y, self.w + rr, self.lw) | |
circlef(self.x, self.y, math.min(remap(self.absorb_hp, 0, 1000, 0, self.w), self.w) + rr) | |
pop() | |
self:enemy_draw() | |
end | |
function Absorber:on_hit(damage) | |
self.absorb_hp = self.absorb_hp + damage | |
self.absorbed_projectiles = self.absorbed_projectiles + 1 | |
table.insert(effects, AbsorbEffect(self)) | |
self.w = self.w + self.absorb_hp/750 | |
for i = 1, 4 do table.insert(effects, AbsorbParticle(self)) end | |
if self.absorb_hp > 1000 then | |
self.dead = true | |
flash(1, {1, 1, 1, 0.4}) | |
for i = 1, 4 do | |
timer:after((i-1)*0.2, function() | |
local rx, ry = rng:float(-self.w/2, self.w/2), rng:float(-self.w/2, self.w/2) | |
camera:shake(6, 0.25) | |
table.insert(effects, ExplosionCircle2(self.x + rx, self.y + ry, 1.5*self.w)) | |
for i = 1, rng:int(8, 12) do table.insert(effects, EllipseParticle(self.x + rx, self.y + ry, rng:float(0, 2*math.pi), rng:float(200, 800))) end | |
local n = math.ceil(self.absorbed_projectiles/2) | |
for i = 1, n do table.insert(projectiles, SecondaryProjectile(self.x, self.y, rng:float(600, 1000), rng:float(0, 2*math.pi))) end | |
end) | |
end | |
end | |
end | |
ExplosionCircle2 = Class:extend() | |
function ExplosionCircle2:new(x, y, w, m) | |
self.x, self.y = x, y | |
self.w = 0 | |
timer:tween(0.1, self, {w = w}, cubic_in_out, function() | |
timer:tween(0.15*(m or 1), self, {w = 0}, linear, function() self.dead = true end) | |
end) | |
end | |
function ExplosionCircle2:update(dt) | |
end | |
function ExplosionCircle2:draw() | |
g.setColor(white) | |
circlef(self.x, self.y, self.w) | |
g.setColor(1, 1, 1, 0.096) | |
circlef(self.x, self.y, 2*self.w) | |
g.setColor(1, 1, 1, 1) | |
end | |
AbsorbParticle = Class:extend() | |
function AbsorbParticle:new(parent) | |
self.parent = parent | |
self.r = rng:float(0, 2*math.pi) | |
self.d = rng:float(2*parent.w, 4*parent.w) | |
self.x, self.y = parent.x + self.d*math.cos(self.r), parent.y + self.d*math.sin(self.r) | |
self.w = 0 | |
timer:tween(0.4, self, {w = rng:float(parent.w/8, parent.w/4), d = 0}, linear, function() self.dead = true end) | |
end | |
function AbsorbParticle:update(dt) | |
if self.parent.dead then self.dead = true; return end | |
self.x, self.y = self.parent.x + self.d*math.cos(self.r), self.parent.y + self.d*math.sin(self.r) | |
end | |
function AbsorbParticle:draw() | |
circlef(self.x, self.y, self.w + rng:float(-self.w/8, self.w/8)) | |
end | |
AbsorbEffect = Class:extend() | |
function AbsorbEffect:new(parent) | |
self.parent = parent | |
self.x, self.y = self.parent.x, self.parent.y | |
self.w = 4*parent.w | |
self.lw = 1 | |
self.color = {1, 1, 1, 0} | |
timer:tween(0.25, self.color, {[4] = 1}, linear) | |
timer:tween(0.25, self, {lw = 3, w = 0}, linear, function() self.dead = true end) | |
end | |
function AbsorbEffect:update(dt) | |
if self.parent.dead then self.dead = true; return end | |
self.x, self.y = self.parent.x, self.parent.y | |
end | |
function AbsorbEffect:draw() | |
circle(self.x, self.y, self.w, self.lw, self.color) | |
end | |
Magnet = Class:extend() | |
Magnet:implement(Enemy) | |
function Magnet:new(x, y, hpm) | |
self.w = 16 | |
self.lw = 2 | |
self.ix = x | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 25, 600*self.hpm) | |
self.shape = HC.rectangle(self.x, self.y, 3*self.w, 1.5*self.w) | |
self.shape.parent = self | |
self.px, self.py = self.x, self.y | |
end | |
function Magnet:update(dt) | |
self:enemy_update(dt) | |
self.x = self.ix + 24*math.sin(2*time) | |
self.y = self.y + self.v*self.vm*dt | |
local vx, vy = self.x - self.px, self.y - self.py | |
self.r = vx/8 | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
for _, p in ipairs(get_close_projectiles(self.x, self.y, 500)) do | |
local d = distance(self.x, self.y, p.x, p.y) | |
if d < 100 then | |
local r = math.atan2(p.y - self.y, p.x - self.x) | |
p.avx = 0.25*p.v*math.cos(r) | |
p.avy = 0.25*p.v*math.sin(r) | |
if d < 75 then table.insert(effects, EllipseParticle(p.x, p.y, rng:float(0, 2*math.pi), rng:float(50, 100))) end | |
else | |
local r = math.atan2(self.y - p.y, self.x - p.x) | |
p.avx = 0.02*p.v*math.cos(r) | |
p.avy = 0.02*p.v*math.sin(r) | |
end | |
end | |
self.px, self.py = self.x, self.y | |
end | |
function Magnet:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 end | |
push(self.x, self.y, self.r, self.hit_spring.x*self.sx, self.hit_spring.x*self.sy) | |
circle(self.x, self.y, 0.75*self.w, self.lw) | |
arc("open", self.x - 1.1*self.w, self.y, 0.4*self.w, math.pi/2, -math.pi/2, self.lw) | |
line12(self.x - 1.1*self.w, self.y - 0.4*self.w, self.x - 1.5*self.w, self.y - 0.4*self.w, self.lw) | |
line12(self.x - 1.1*self.w, self.y + 0.4*self.w, self.x - 1.5*self.w, self.y + 0.4*self.w, self.lw) | |
push(self.x + 1.1*self.w, self.y, 0, -1, 1) arc("open", self.x + 1.1*self.w, self.y, 0.4*self.w, math.pi/2, -math.pi/2, self.lw) pop() | |
line12(self.x + 1.1*self.w, self.y - 0.4*self.w, self.x + 1.5*self.w, self.y - 0.4*self.w, self.lw) | |
line12(self.x + 1.1*self.w, self.y + 0.4*self.w, self.x + 1.5*self.w, self.y + 0.4*self.w, self.lw) | |
rectf(self.x - 1.5*self.w, self.y - 0.4*self.w, 5, 5, nil, nil) | |
rectf(self.x - 1.5*self.w, self.y + 0.4*self.w, 5, 5, nil, nil) | |
rectf(self.x + 1.5*self.w, self.y - 0.4*self.w, 5, 5, nil, nil) | |
rectf(self.x + 1.5*self.w, self.y + 0.4*self.w, 5, 5, nil, nil) | |
pop() | |
self:enemy_draw() | |
end | |
Wrapper = Class:extend() | |
Wrapper:implement(Enemy) | |
function Wrapper:new(x, y, hpm) | |
self.w = 16 | |
self.vs = {self.w, 0, -self.w/2, self.w/2, -self.w, 0, -self.w/2, -self.w/2} | |
self.lw = 2 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 150, 200*self.hpm) | |
self.r = math.pi/16 | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
end | |
function Wrapper:update(dt) | |
self:enemy_update(dt) | |
self.x = self.x + self.v*self.vm*math.cos(self.r)*dt | |
self.y = self.y + self.v*self.vm*math.sin(self.r)*dt | |
if self.x > x2 then | |
table.insert(effects, WrapEffect(self.x, self.y, self.w)) | |
self.x = x1 | |
table.insert(effects, WrapEffect(self.x, self.y, self.w)) | |
end | |
if self.x < x1 then | |
table.insert(effects, WrapEffect(self.x, self.y, self.w)) | |
self.x = x2 | |
table.insert(effects, WrapEffect(self.x, self.y, self.w)) | |
end | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Wrapper:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 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), self.lw, white) | |
pop() | |
self:enemy_draw() | |
end | |
BigRock = Class:extend() | |
BigRock:implement(Enemy) | |
function BigRock:new(x, y, hpm) | |
self.w = 32 | |
self.vs = {} | |
local r = 0 | |
for i = 1, 16 do | |
local w = rng:float(0.9, 1.1) | |
self.vs[2*(i-1)+1] = self.w*w*math.cos(r) | |
self.vs[2*i] = self.w*w*math.sin(r) | |
r = r + rng:float(math.pi/8 - math.pi/36, math.pi/8 + math.pi/36) | |
end | |
self.vr = rng:float(-2*math.pi, 2*math.pi) | |
self.lw = 3 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 50, 800*self.hpm) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
end | |
function BigRock:update(dt) | |
self:enemy_update(dt) | |
self.r = self.r + self.vr*dt | |
self.y = self.y + self.v*self.vm*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function BigRock:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 6 else self.lw = 3 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), self.lw, white) | |
pop() | |
self:enemy_draw() | |
end | |
Rock = Class:extend() | |
Rock:implement(Enemy) | |
function Rock:new(x, y, hpm) | |
self.w = 16 | |
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.lw = 2 | |
self.hpm = hpm or 1 | |
self:enemy_new(x, y, 50, 300*self.hpm) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
end | |
function Rock:update(dt) | |
self:enemy_update(dt) | |
self.r = self.r + self.vr*dt | |
self.y = self.y + self.v*self.vm*dt | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
self:collisions(dt) | |
end | |
function Rock:draw() | |
if self.y > gh + 64 then return end | |
if self.hit_flash then self.lw = 4 else self.lw = 2 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), self.lw, white) | |
pop() | |
self:enemy_draw() | |
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.avx, self.avy = 0, 0 | |
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 + self.avx)*dt | |
self.y = self.y + (self.vy + self.avy)*dt | |
self.r = math.atan2(self.vy + self.avy, self.vx + self.avx) | |
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 | |
RuneWord = Class:extend() | |
function RuneWord:new(x, y, w, h, word) | |
self.letters = {} | |
for i = 1, #word do table.insert(self.letters, word:sub(i, i)) end | |
self.rune_letters = {} | |
for i = 1, #self.letters do | |
timer:after((i-1)*0.08, function() | |
table.insert(self.rune_letters, RuneLetter(x + (i-1)*w, y, w, h, self.letters[i])) | |
end) | |
end | |
end | |
function RuneWord:update(dt) | |
for _, rl in ipairs(self.rune_letters) do rl:update(dt) end | |
end | |
function RuneWord:draw() | |
for _, rl in ipairs(self.rune_letters) do rl:draw() end | |
end | |
RuneLetter = Class:extend() | |
function RuneLetter:new(x, y, w, h, type) | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
self.type = type | |
self.scale_spring = Spring(1) | |
self.sx, self.sy = 1.5, 1.5 | |
self.fg_color = {0, 0, 0, 1} | |
self.bg_color = {1, 1, 1, 1} | |
self.visible = true | |
timer:everyi(0.05, function() self.visible = not self.visible end, 3, function() self.visible = true end) | |
timer:tween(0.15, self, {sx = 1, sy = 1}, linear, function() self.sx, self.sy = 1, 1 end) | |
timer:after(0.15, function() | |
self.bg_color = {0, 0, 0, 0} | |
self.fg_color = {1, 1, 1, 1} | |
end) | |
self:displace() | |
end | |
function RuneLetter:update(dt) | |
self.scale_spring:update(dt) | |
local mx, my = camera:get_mouse_position() | |
if mx > self.x - self.w/2 and mx < self.x + self.w/2 and my > self.y - self.h/2 and my < self.y + self.h/2 then | |
if not self.hot then | |
self.hot = true | |
self.scale_spring:pull(0.3) | |
self.fg_color = {0, 0, 0, 1} | |
self.bg_color = {1, 1, 1, 1} | |
timer:everyi(0.033, function() self.visible = not self.visible end, 3, function() self.visible = true end) | |
self:displace() | |
end | |
else | |
if self.hot then | |
self.hot = false | |
self.scale_spring:pull(0.15) | |
self.fg_color = {1, 1, 1, 1} | |
self.bg_color = {0, 0, 0, 0} | |
timer:everyi(0.033, function() self.visible = not self.visible end, 3, function() self.visible = true end) | |
end | |
end | |
end | |
function RuneLetter:draw() | |
if not self.visible then return end | |
push(self.x, self.y, 0, self.sx*self.scale_spring.x, self.sy*self.scale_spring.x) | |
rectf(self.x, self.y, self.w, self.h, nil, nil, self.bg_color) | |
draw_rune(self.x, self.y, self.w, self.h, self.type, 3, self.fg_color) | |
pop() | |
end | |
function RuneLetter:displace(m) | |
for i = 1, rng:int(4, 6) do | |
table.insert(displacements, DisplacementBlock(self.x, self.y + rng:float(-0.75*self.w, 0.75*self.w), rng:float(0.75*self.w, 2*self.w), rng:float(0.1*self.w, 0.25*self.w), 0.05*(m or 1), 0.25*(m or 1))) | |
end | |
end | |
TransitionRune = Class:extend() | |
function TransitionRune:new(x, y, w, h, type) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
self.type = type | |
self.scale_spring = Spring(1) | |
self.scale_spring:pull(0.5) | |
self.lw = 8 | |
self.timer:everyi({0.02, 0.07}, function() self.lw = rng:float(6, 10) end) | |
self.timer:everyi({0.04, 0.14}, function() | |
table.insert(displacements, DisplacementBlock(self.x, self.y + rng:float(-0.75*self.w, 0.75*self.w), rng:float(0.75*self.w, 2*self.w), rng:float(0.1*self.w, 0.25*self.w), 0.05, 0.25, rng:float(1, 4))) | |
end) | |
end | |
function TransitionRune:update(dt) | |
self.timer:update(dt) | |
self.scale_spring:update(dt) | |
end | |
function TransitionRune:draw() | |
push(self.x, self.y, 0, self.scale_spring.x, self.scale_spring.x) | |
draw_rune(self.x, self.y, self.w, self.h, self.type, self.lw, black) | |
pop() | |
end | |
Rune = Class:extend() | |
function Rune:new(x, y, w, h, type) | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
self.type = type | |
self.scale_spring = Spring(1) | |
end | |
function Rune:update(dt) | |
self.scale_spring:update(dt) | |
local mx, my = camera:get_mouse_position() | |
if mx > self.x - self.w/2 and mx < self.x + self.w/2 and my > self.y - self.h/2 and my < self.y + self.h/2 then | |
if not self.hot then | |
self.hot = true | |
self.scale_spring:pull(0.5) | |
self.rune_text = RuneText(x1 + 50, 50, self.type) | |
table.insert(ui, self.rune_text) | |
end | |
else | |
if self.hot then | |
self.hot = false | |
self.scale_spring:pull(0.2) | |
self.rune_text:die() | |
self.rune_text = nil | |
else | |
if self.rune_text then | |
self.rune_text:die() | |
self.rune_text = nil | |
end | |
end | |
end | |
end | |
function draw_rune(x, y, w, h, type, lw, color) | |
g.setLineWidth(lw or 2) | |
g.setColor(color or white) | |
local x1, y1 = x - w/2, y - h/2 | |
local x2, y2 = x + w/2, y + h/2 | |
if type == "double" then | |
g.line(x1 + w/3 + h/16, y1 + h/4, x1 + w/3 + h/16, y2 - h/4) | |
g.line(x2 - w/3 - h/16, y1 + h/4 + h/12, x2 - w/3 - h/16, y2 - h/4) | |
elseif type == "triple" then | |
g.line(x1 + w/4 + w/12, y1 + h/4, x1 + w/4 + w/12, y2 - h/4) | |
g.line(x, y - h/8, x, y + h/8) | |
g.line(x2 - w/4 - w/12, y1 + h/4 + h/16, x2 - w/4 - w/12, y2 - h/4 - h/16) | |
elseif type == "volley" then | |
g.line(x1 + w/3, y1 + h/4, x1 + w/3, y2 - h/4) | |
g.line(x, y1 + h/4, x, y2 - h/4) | |
g.line(x2 - w/3, y1 + h/4, x2 - w/3, y2 - h/4) | |
elseif type == "spread" then | |
g.line(x, y2 - h/4, x1 + w/4, y1 + h/3) | |
g.line(x, y2 - h/4, x, y1 + h/3) | |
g.line(x, y2 - h/4, x2 - w/4, y1 + h/3) | |
elseif type == "burst" then | |
g.line(x1 + w/3, y, x, y1 + h/4, x2 - w/3, y) | |
g.line(x1 + w/3 + w/12, y + h/6 - h/12 + h/24, x, y1 + h/4 + h/4 - h/12 + h/24, x2 - w/3 - w/12, y + h/6 - h/12 + h/24) | |
g.line(x1 + w/3 + w/12 + w/24, y + h/6 + h/12, x, y + h/12 + h/12, x2 - w/3 - w/12 - w/24, y + h/6 + h/12) | |
elseif type == "homing" then | |
g.line(x1 + w/3, y1 + h/4, x2 - w/3, y, x1 + w/3, y2 - h/4) | |
g.line(x1 + w/3, y1 + h/4 + h/6, x1 + w/3 + w/6, y1 + h/4) | |
elseif type == "speed" then | |
g.line(x - w/4, y, x + w/4, y) | |
g.line(x + w/4 - w/8, y + h/8, x + w/4, y, x + w/4 - w/8, y - h/8) | |
elseif type == "accel" then | |
g.line(x - w/4, y, x + w/4, y) | |
g.line(x + w/4 - w/8, y + h/8, x + w/4, y, x + w/4 - w/8, y - h/8) | |
g.line(x + w/4 - w/8 - w/8, y + h/8, x + w/4 - w/8, y, x + w/4 - w/8 - w/8, y - h/8) | |
elseif type == "decel" then | |
g.line(x - w/4, y, x + w/4, y) | |
g.line(x - w/4 + w/8, y + h/8, x - w/4, y, x - w/4 + w/8, y - h/8) | |
g.line(x - w/4 + w/8 + w/8, y + h/8, x - w/4 + w/8, y, x - w/4 + w/8 + w/8, y - h/8) | |
elseif type == "ricochet" then | |
g.line(x + w/10, y1 + h/4, x - w/8, y - h/12, x + w/8, y + h/12, x - w/10, y2 - h/4) | |
elseif type == "scatter" then | |
g.line(x, y2 - h/4, x, y - h/8) | |
g.line(x1 + w/4, y - h/8, x1 + w/4 + w/8, y1 + h/4, x, y - h/8, x2 - w/4 - w/8, y1 + h/4, x2 - w/4, y - h/8) | |
elseif type == "split" then | |
g.line(x, y1 + h/4, x, y2 - h/4) | |
g.line(x - w/6, y, x, y1 + h/4, x + w/6, y) | |
g.line(x - w/5, y1 + h/4, x + w/5, y1 + h/4) | |
elseif type == "chain" then | |
g.line(x + w/10, y1 + h/4, x - w/8, y, x + w/8, y, x - w/10, y2 - h/4) | |
elseif type == "pierce" then | |
g.line(x, y1 + h/4, x, y2 - h/4) | |
g.line(x - w/6, y, x, y1 + h/4, x + w/6, y) | |
elseif type == "fork" then | |
g.line(x, y2 - h/4, x, y) | |
g.line(x1 + w/3, y1 + h/4, x, y) | |
g.line(x2 - w/3, y1 + h/4, x, y) | |
elseif type == "cross" then | |
g.line(x1 + w/3, y, x2 - w/3, y) | |
g.line(x, y1 + h/3, x, y2 - h/3) | |
elseif type == "blast" then | |
g.line(x, y - h/12, x + w/12, y, x, y + h/12, x - w/12, y, x, y - h/12) | |
g.line(x1 + w/4, y, x1 + w/4 + w/10, y) | |
g.line(x2 - w/4, y, x2 - w/4 - w/10, y) | |
g.line(x, y1 + h/4, x, y1 + h/4 + h/10) | |
g.line(x, y2 - h/4, x, y2 - h/4 - h/10) | |
elseif type == "burn" then | |
g.line(x, y - h/10, x + w/10, y, x, y + h/10, x - w/10, y, x, y - h/10) | |
g.line(x - w/10, y - h/10, x - w/10, y - h/10 - h/12) | |
g.line(x + w/10, y - h/10, x + w/10, y - h/10 - h/12) | |
g.line(x, y - h/10 - h/12, x, y - h/5 - h/12) | |
elseif type == "finale" then | |
g.line(x, y1 + h/4, x, y2 - h/4) | |
g.line(x1 + w/3, y, x, y1 + h/4, x2 - w/3, y) | |
g.line(x1 + w/3 + w/12, y + h/6 - h/12 + h/24, x, y1 + h/4 + h/4 - h/12 + h/24, x2 - w/3 - w/12, y + h/6 - h/12 + h/24) | |
g.line(x1 + w/3 + w/12 + w/24, y + h/6 + h/12, x, y + h/12 + h/12, x2 - w/3 - w/12 - w/24, y + h/6 + h/12) | |
elseif type == "weaken" then | |
g.line(x1 + w/4, y1 + h/4, x, y, x1 + w/4, y2 - h/4) | |
g.line(x + w/8, y - h/8, x, y, x + w/8, y + h/8) | |
elseif type == "glitch" then | |
g.line(x1 + w/4, y - h/4, x + w/12, y - h/4) | |
g.line(x - w/8, y - h/8, x + w/4, y - h/8) | |
g.line(x - w/12, y + h/8, x + w/12, y + h/8) | |
g.line(x - w/24, y + h/4, x + w/12, y + h/4) | |
g.line(x + w/12, y - h/8, x + w/12, y + h/4) | |
elseif type == "slow" then | |
g.line(x1 + w/4, y + h/8, x - w/8, y - h/8, x + w/8, y + h/8, x + w/4, y - h/8) | |
elseif type == "haste" then | |
g.line(x1 + w/3, y1 + h/4, x2 - w/3, y, x1 + w/3, y2 - h/4) | |
g.line(x1 + w/3 - w/6 + w/8, y1 + h/4 + h/8, x2 - w/3 - w/3 + w/8, y, x1 + w/3 - w/6 + w/8, y2 - h/4 - h/8) | |
elseif type == "stun" then | |
g.line(x1 + w/3, y1 + h/3, x2 - w/3, y1 + h/3, x2 - w/3, y2 - h/3, x1 + w/3, y2 - h/3, x1 + w/3, y1 + h/3) | |
g.line(x2 - w/3, y1 + h/3, x1 + w/3, y2 - h/3) | |
elseif type == "sphere" then | |
g.line(x, y - h/8, x + w/8, y, x, y + h/8, x - w/8, y, x, y - h/8) | |
g.line(x, y - h/8, x, y - h/4, x + w/8, y - h/4) | |
g.line(x + w/8, y, x + w/4, y, x + w/4, y + h/8) | |
g.line(x, y + h/8, x, y + h/4, x - w/8, y + h/4) | |
g.line(x - w/8, y, x - w/4, y, x - w/4, y - h/8) | |
elseif type == "spawner" then | |
g.line(x, y1 + h/4, x, y2 - h/4) | |
g.line(x, y1 + h/4 + 1*h/10, x - w/8, y1 + h/4 + 1*h/10) | |
g.line(x, y1 + h/4 + 2*h/10, x + w/8, y1 + h/4 + 2*h/10) | |
g.line(x, y1 + h/4 + 3*h/10, x - w/8, y1 + h/4 + 3*h/10) | |
g.line(x, y1 + h/4 + 4*h/10, x + w/8, y1 + h/4 + 4*h/10) | |
elseif type == "new_run" then | |
g.line(x1 + w/4, y, x2 - w/4, y) | |
g.line(x2 - w/4 - w/4, y - h/4, x2 - w/4, y, x2 - w/4 - w/4, y + h/4) | |
elseif type == "options" then | |
g.line(x1 + w/4, y2 - h/4, x + w/8, y - h/8, x, y1 + h/4, x - w/8, y - h/8, x2 - w/4, y2 - h/4) | |
elseif type == "quit" then | |
g.line(x1 + w/4, y1 + h/4, x2 - w/4, y2 - h/4) | |
g.line(x1 + w/4, y2 - h/4, x2 - w/4, y1 + h/4) | |
elseif type == "R" then | |
g.line(x - w/4, y + h/4, x - w/4, y - h/4, x + w/4, y - h/8, x - w/4, y, x + w/4, y + h/4) | |
elseif type == "U" then | |
g.line(x - w/4, y - h/4, x - w/4, y + h/12, x + w/4, y + h/4, x + w/4, y - h/4) | |
elseif type == "N" then | |
g.line(x - w/4, y - h/4, x - w/4, y + h/4) | |
g.line(x + w/4, y - h/4, x + w/4, y + h/4) | |
g.line(x - w/4, y1 + h/4 + h/16, x + w/4, y2 - h/4 - h/16) | |
elseif type == "E" then | |
g.line(x + w/4, y, x - w/4, y - h/4, x - w/4, y + h/4, x, y + h/8) | |
g.line(x - w/4, y - h/8, x, y) | |
elseif type == "H" then | |
g.line(x - w/4, y - h/4, x - w/4, y + h/4) | |
g.line(x + w/4, y - h/4, x + w/4, y + h/4) | |
g.line(x - w/4, y - h/12, x + w/4, y + h/12) | |
elseif type == "A" then | |
g.line(x1 + w/4, y + h/4, x, y - h/4, x2 - w/4, y + h/4) | |
g.line(x - w/10, y, x + w/6, y + h/8) | |
elseif type == "C" then | |
g.line(x + w/4, y - h/4, x - w/4, y, x + w/4, y + h/4) | |
elseif type == "K" then | |
g.line(x + w/4, y - h/4, x - w/4, y, x + w/4, y + h/4) | |
g.line(x - w/4, y - h/4, x - w/4, y + h/4) | |
end | |
g.setLineWidth(1) | |
end | |
function Rune:draw() | |
push(self.x, self.y, 0, self.scale_spring.x, self.scale_spring.x) | |
draw_rune(self.x, self.y, self.w, self.h, self.type) | |
pop() | |
end | |
RuneText = Class:extend() | |
function RuneText:new(x, y, type) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.type = type | |
local sh = font_small:getHeight() | |
local h = font_medium:getHeight() | |
self.texts = {} | |
if self.type == "double" then | |
self.texts[1] = TextLine(self.x, self.y, "DOUBLE") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "2 projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "20% decreased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "100% decreased ammo spent") | |
elseif self.type == "triple" then | |
self.texts[1] = TextLine(self.x, self.y, "TRIPLE") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "3 projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "40% decreased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "100% decreased ammo spent") | |
elseif self.type == "volley" then | |
self.texts[1] = TextLine(self.x, self.y, "VOLLEY") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "3 projectiles in a wider area") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "50% decreased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "100% decreased ammo spent") | |
elseif self.type == "spread" then | |
self.texts[1] = TextLine(self.x, self.y, "SPREAD") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectiles at random angles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "25% increased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "300% decreased ammo spent") | |
elseif self.type == "burst" then | |
self.texts[1] = TextLine(self.x, self.y, "BURST") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "3 quick waves of projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "66% decreased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "100% decreased ammo spent") | |
elseif self.type == "homing" then | |
self.texts[1] = TextLine(self.x, self.y, "HOMING") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile seeks targets") | |
elseif self.type == "speed" then | |
self.texts[1] = TextLine(self.x, self.y, "SPEED") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile is faster") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "50% increased movement speed") | |
elseif self.type == "accel" then | |
self.texts[1] = TextLine(self.x, self.y, "ACCEL") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile accelerates") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "Starts at 0% speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "Accelerates to 150% speed") | |
elseif self.type == "decel" then | |
self.texts[1] = TextLine(self.x, self.y, "DECEL") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile decelerates") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "Starts at 150% speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "Decelerates to 25% speed") | |
elseif self.type == "ricochet" then | |
self.texts[1] = TextLine(self.x, self.y, "RICOCHET") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile ricochets twice") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Ricochet hit: only applies to walls", font_small) | |
elseif self.type == "scatter" then | |
self.texts[1] = TextLine(self.x, self.y, "SCATTER") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile ricochets and splits into secondary projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Ricochet hit: only applies to walls", font_small) | |
self.texts[4] = TextLine(self.x, self.y + sh + 2*h + 2*sh, "Secondary projectile: doesn't inherit passives", font_small) | |
elseif self.type == "split" then | |
self.texts[1] = TextLine(self.x, self.y, "SPLIT") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile splits into 2 projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Split hit: only applies to walls", font_small) | |
elseif self.type == "chain" then | |
self.texts[1] = TextLine(self.x, self.y, "CHAIN") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile seeks next target after hit") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Chain hit: only applies to enemies", font_small) | |
elseif self.type == "pierce" then | |
self.texts[1] = TextLine(self.x, self.y, "PIERCE") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile pierces target once") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Pierce hit: only applies to enemies", font_small) | |
elseif self.type == "fork" then | |
self.texts[1] = TextLine(self.x, self.y, "FORK") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile forks into 2 projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Fork hit: only applies to enemies", font_small) | |
elseif self.type == "cross" then | |
self.texts[1] = TextLine(self.x, self.y, "CROSS") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile splits into 4 projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Cross hit: only applies to enemies", font_small) | |
elseif self.type == "blast" then | |
self.texts[1] = TextLine(self.x, self.y, "BLAST") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectiles explode after build up") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "75% decreased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h + sh, "Blast meter hit: only builds from enemies", font_small) | |
self.texts[5] = TextLine(self.x, self.y + sh + 3*h + 2*sh, "Explosion hit: deals AoE damage", font_small) | |
elseif self.type == "burn" then | |
self.texts[1] = TextLine(self.x, self.y, "BURN") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Inflict nearby enemies with burn on kill") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Burn: damage over time", font_small) | |
elseif self.type == "finale" then | |
self.texts[1] = TextLine(self.x, self.y, "FINALE") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Every 5th projectile is final") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Final: pierces and deals extra damage", font_small) | |
elseif self.type == "weaken" then | |
self.texts[1] = TextLine(self.x, self.y, "WEAKEN") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile has 17% chance of inflicting weak") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Weak: enemy takes double damage for 10 seconds", font_small) | |
elseif self.type == "glitch" then | |
self.texts[1] = TextLine(self.x, self.y, "GLITCH") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile has 17% chance of inflicting glitch") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Glitch: enemy takes damage over 6 seconds", font_small) | |
elseif self.type == "slow" then | |
self.texts[1] = TextLine(self.x, self.y, "SLOW") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile has 17% chance of inflicting slow") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Slow: enemy has 100% decreased movement speed", font_small) | |
elseif self.type == "haste" then | |
self.texts[1] = TextLine(self.x, self.y, "HASTE") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile has 17% chance of inflicting haste") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Haste: enemy has 50% increased movement speed", font_small) | |
elseif self.type == "stun" then | |
self.texts[1] = TextLine(self.x, self.y, "STUN") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile has 17% chance of inflicting stun") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Stun: enemy can't move", font_small) | |
elseif self.type == "sphere" then | |
self.texts[1] = TextLine(self.x, self.y, "BALL LIGHTNING") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile pierces and discharges electricity") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h, "75% decreased attack speed") | |
self.texts[4] = TextLine(self.x, self.y + sh + 3*h, "300% increased ammo spent") | |
elseif self.type == "spawner" then | |
self.texts[1] = TextLine(self.x, self.y, "SPAWNER") | |
self.texts[2] = TextLine(self.x, self.y + sh + h, "Projectile spawns secondary projectiles") | |
self.texts[3] = TextLine(self.x, self.y + sh + 2*h + sh, "Secondary projectile: doesn't inherit passives", font_small) | |
elseif self.type == "new_run" then | |
self.texts[1] = TextLine(self.x, self.y, "NEW RUN") | |
elseif self.type == "options" then | |
self.texts[1] = TextLine(self.x, self.y, "OPTIONS") | |
elseif self.type == "quit" then | |
self.texts[1] = TextLine(self.x, self.y, "QUIT") | |
end | |
self.visible = true | |
self.timer:after(0.2, function() self.timer:everyi(0.05, function() self.visible = not self.visible end, 10, function() self.visible = true end) end) | |
end | |
function RuneText:update(dt) | |
self.timer:update(dt) | |
update_objects(self.texts, dt) | |
end | |
function RuneText:draw() | |
if not self.visible then return end | |
draw_objects(self.texts) | |
end | |
function RuneText:die() | |
self.timer:everyi(0.04, function() self.visible = not self.visible end, 5, function() self.dead = true end) | |
end | |
TextLine = Class:extend() | |
function TextLine:new(x, y, text, font, color, bg_color) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.font = font or font_medium | |
self.w = self.font:getWidth(text) | |
self.h = self.font:getHeight() | |
self.color = color or white | |
self.bg_color = bg_color or black | |
self.characters = {} | |
self.visuals = {} | |
for i = 1, #text do table.insert(self.characters, {c = text:sub(i, i), visible = false}); self.visuals[i] = 4 end | |
for i = 1, #self.characters do | |
self.timer:after((i-1)*0.02, function() | |
if rng:bool(25) then | |
self.visuals[i] = rng:int(1, 4) | |
if self.visuals[i-1] then self.visuals[i-1] = rng:int(1, 4) end | |
if self.visuals[i-2] then self.visuals[i-2] = rng:int(1, 4) end | |
self.timer:after(rng:float(0.07, 0.2), function() self.visuals[i] = 1 end) | |
else | |
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 | |
end | |
if rng:bool(50) then | |
local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ" | |
local r = rng:int(1, #random_characters) | |
local c = self.characters[i].c | |
self.characters[i].c = random_characters:sub(r, r) | |
self.characters[i].visible = true | |
self.timer:after(rng:float(0.07, 0.2), function() self.characters[i].c = c end) | |
else | |
self.characters[i].visible = true | |
end | |
end) | |
end | |
self.timer:after(0.02*#self.characters + 0.2, function() | |
self.visuals = {} | |
for i = 1, #self.characters do self.visuals[i] = 1 end | |
self.timer:every({0.4, 1}, function() | |
for i = 1, rng:int(2, 3) do table.insert(displacements, DisplacementBlock(self.x, self.y + rng:float(-0.75*self.h, 0.75*self.h), rng:float(0.75*self.w, 2*self.w), rng:float(0.25*self.h, 0.5*self.h), 0.05, 0.25)) end | |
local c = rng:int(1, #self.characters) | |
local v = rng:int(1, #self.visuals) | |
local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ" | |
local r = rng:int(1, #random_characters) | |
local pc = self.characters[c].c | |
self.characters[c].c = random_characters:sub(r, r) | |
self.timer:after(rng:float(0.07, 0.2), function() self.characters[c].c = pc; self.visuals[v] = 1 end) | |
self.visuals[v] = rng:int(1, 4) | |
end) | |
end) | |
--self.timer:after(2, function() self.dead = true end) | |
end | |
function TextLine:update(dt) | |
self.timer:update(dt) | |
end | |
function TextLine:draw() | |
local w, h = 0, 0 | |
local x, y = self.x, self.y | |
g.setFont(self.font) | |
g.setColor(self.color) | |
for i = 1, #self.characters do | |
if self.characters[i].visible then | |
local cw, ch = self.font:getWidth(self.characters[i].c), self.font:getHeight() | |
if self.visuals[i] == 1 then | |
g.setColor(self.color) | |
g.print(self.characters[i].c, x + w, y + h) | |
elseif self.visuals[i] == 2 then | |
g.setColor(self.color) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
g.setColor(self.bg_color) | |
g.print(self.characters[i].c, x + w, y + h) | |
elseif self.visuals[i] == 3 then | |
g.setColor(self.color) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
elseif self.visuals[i] == 4 then | |
g.setColor(self.bg_color) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
end | |
end | |
w = w + self.font:getWidth(self.characters[i].c) | |
end | |
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)) | |
table.insert(effects, ShootCircle(x2, y2, 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)) 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, m) | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
self.r = rng:float(-32*(m or 1), 32*(m or 1))/255 | |
self.color = {0.5 + self.r, 0.5 + self.r, 0.5 + self.r} | |
local n = rng:float(d1 or 0.05, d2 or 0.4) | |
timer:after(n, function() self.dead = true end) | |
if rng:bool(50) then timer:tween(n, self, {r = 0}, linear) end | |
end | |
function DisplacementBlock:update(dt) | |
self.color = {0.5 + self.r, 0.5 + self.r, 0.5 + self.r} | |
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) | |
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 | |
function PlayerHPUI:destroy() | |
self.dead = true | |
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 | |
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 | |
function PlayerAmmoUI:destroy() | |
self.dead = true | |
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 | |
PlayerResourceUI = Class:extend() | |
function PlayerResourceUI: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: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) | |
local oy = 28 | |
local w = 12 | |
self.vs = {self.x - w/2, self.y - w/2 + oy, self.x, self.y - w + oy, self.x + w/2, self.y - w/2 + oy, self.x + w/2, self.y + w/2 + oy, self.x, self.y + w + oy, self.x - w/2, self.y + w/2 + oy} | |
end | |
function PlayerResourceUI:update(dt) | |
self.timer:update(dt) | |
self.scale_spring:update(dt) | |
self.resource = player.resource | |
end | |
function PlayerResourceUI:draw() | |
push(self.x, self.y, 0, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) | |
draw_text(self.resource, self.x, self.y, 0, 1, 1, font_medium) | |
pop() | |
push(self.x, self.y + 28, 0, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) | |
polygonf(self.vs) | |
pop() | |
end | |
function PlayerResourceUI:jiggle() | |
self.scale_spring:pull(0.5) | |
end | |
function PlayerResourceUI:destroy() | |
self.dead = true | |
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))) | |
table.insert(ui_effects, ShootCircle(self.x, self.y + 28)) | |
table.insert(ui_effects, FadingShootCapsule(self.x, self.y + 28, rng:float(-math.pi/4, 0), rng:float(50, 150))) | |
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, self.h/2)) | |
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, d, m) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.sx, self.sy = 0.05, 0.05 | |
self.a = 1 | |
self.timer:tween(d or 0.5, self, {sx = 0.75*(m or 1), sy = 0.75*(m or 1), 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 | |
RectParticle = Class:extend() | |
function RectParticle:new(x, y, sm) | |
self.x, self.y = x, y | |
self.w = 0 | |
self.v = rng:float(100, 220) | |
self.r = rng:float(0, 2*math.pi) | |
timer:tween(rng:float(0.04, 0.06), self, {w = 16*(sm or 1)}, cubic_in_out, function() | |
timer:tween(rng:float(0.3, 0.4), self, {w = 0, v = 0}, linear, function() self.dead = true end) | |
end) | |
end | |
function RectParticle:update(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 RectParticle:draw() | |
rectf(self.x, self.y, self.w, self.w) | |
end | |
DustParticle = Class:extend() | |
function DustParticle:new(x, y, sm) | |
self.x, self.y = x, y | |
self.animation = Animation(1, get_animation_frames("smoke1"), "once") | |
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:tween(rng:float(0.04, 0.06), self, {sx = 0.7*(sm or 1), sy = 0.7*(sm or 1)}, 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() | |
draw_animation("smoke1", self.animation:get_current_frame(), self.x, self.y, self.rs, 3.5*self.sx, 3.5*self.sy) | |
end | |
AnimatedEffect = Class:extend() | |
function AnimatedEffect:new(x, y, name, delay, loop_mode, r, sx, sy, action, z, duration) | |
self.x, self.y = x, y | |
self.r, self.sx, self.sy = r or 0, sx or 1, sy or 1 | |
self.z = z or 0 | |
self.name = name | |
self.delay = delay | |
self.loop_mode = loop_mode or "once" | |
self.action = action | |
if self.loop_mode == "once" then | |
self.animation = Animation(delay, get_animation_frames(name), self.loop_mode, {[0] = function() | |
self.dead = true | |
if self.action then self.action() end | |
end}) | |
elseif self.loop_mode == "stay" then self.animation = Animation(delay, get_animation_frames(name), "once", {[0] = function() if self.action then self.action() end end}) | |
else self.animation = Animation(delay, get_animation_frames(name), self.loop_mode) end | |
if duration then timer:after(duration, function() self.dead = true end) 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 | |
WrapEffect = Class:extend() | |
function WrapEffect:new(x, y, r) | |
self.x, self.y = x, y | |
self.r = r | |
timer:tween(0.2, self, {r = 0}, linear, function() self.dead = true end) | |
for i = 1, 4 do table.insert(effects, EllipseParticle(x, y, rng:float(0, 2*math.pi), rng:float(200, 400))) end | |
end | |
function WrapEffect:update(dt) | |
end | |
function WrapEffect:draw() | |
circlef(self.x, self.y, self.r) | |
end | |
DeathRect = Class:extend() | |
function DeathRect:new(x, y, w, h) | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
timer:tween(0.26, self, {w = 0, h = 0}, cubic_in_out, function() self.dead = true end) | |
end | |
function DeathRect:update(dt) | |
end | |
function DeathRect:draw() | |
rectf(self.x, self.y, self.w, self.h) | |
end | |
DeathCircle = Class:extend() | |
function DeathCircle:new(x, y, r) | |
self.x, self.y = x, y | |
self.r = r | |
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) | |
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 | |
BlackRectangle = Class:extend() | |
function BlackRectangle:new(x, y, w, h) | |
self.x, self.y, self.w, self.h = x, y, w, h | |
end | |
function BlackRectangle:update(dt) | |
end | |
function BlackRectangle:draw() | |
rectf(self.x + self.w/2, self.y + self.h/2, self.w, self.h, nil, nil, black) | |
g.setColor(white) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment