Created
January 31, 2020 14:55
-
-
Save a327ex/5be6cd49d2b75b8d983b2986ca936f1c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function init() | |
white = {1, 1, 1, 1} | |
black = {0, 0, 0, 1} | |
red = {1.0, 0.1, 0.2, 1} | |
combine = g.newShader("combine.frag") | |
aesthetic = g.newShader("aesthetic.frag") | |
aesthetic_canvas = g.newCanvas(gw, gh) | |
displacement_canvas = g.newCanvas(gw, gh) | |
game_canvas = g.newCanvas(gw, gh) | |
shockwave = g.newImage("res/shockwave_displacement.png") | |
new_animation("hit1", 96, 47) | |
new_animation("smoke1", 50, 50, {1}) | |
new_animation("firehit1", 200, 200) | |
new_animation("firehit2", 200, 200) | |
new_animation("firehit3", 200, 200) | |
new_animation("radial1", 200, 200) | |
new_animation("radial2", 200, 200) | |
new_animation("fire1", 192, 108, {1, 3, 5, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19}) | |
new_animation("disappear1", 100, 100) | |
new_animation("disappear2", 100, 100) | |
new_animation("transition1", 480, 270) | |
new_animation("transition2", 480, 270) | |
new_animation("transition3", 480, 270) | |
new_animation("transition4", 480, 270) | |
displacements = {} | |
projectiles = {} | |
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) | |
timer:after(3, function() | |
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)) | |
main_menu() | |
end) | |
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 player then player:update(dt) end | |
update_objects(displacements, dt) | |
update_objects(projectiles, dt) | |
update_objects(enemies, 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(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 | |
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:update(dt) | |
self.scale_spring:update(dt) | |
self.attack_spring:update(dt) | |
self.reload_spring:update(dt) | |
self.s = self.scale_spring.x*self.attack_spring.x*self.reload_spring.x | |
self.r = angle_to_mouse(self.x, self.y) | |
-- double, triple and volley change shooting_positions | |
self.shooting_positions = {} | |
local s = self.s*1.5*self.rs | |
if self.mods.volley then | |
if self.mods.double and self.mods.triple then | |
local ofs = {-64, -56, -48, -40, -32, -16, -8, 0, 8, 16, 32, 40, 48, 56, 64} | |
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end | |
elseif self.mods.double and not self.mods.triple then | |
local ofs = {-28, -20, -4, 4, 20, 28} | |
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end | |
elseif self.mods.triple and not self.mods.double then | |
local ofs = {-32, -24, -16, -8, 0, 8, 16, 24, 32} | |
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end | |
else | |
local ofs = {-24, 0, 24} | |
for i = 1, #ofs do table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r) + ofs[i]*math.cos(self.r + math.pi/2), y = self.y + s*math.sin(self.r) + ofs[i]*math.sin(self.r + math.pi/2)}) end | |
end | |
elseif self.mods.triple then | |
if self.mods.double then | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/4), y = self.y + s*math.sin(self.r - math.pi/4)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/8), y = self.y + s*math.sin(self.r - math.pi/8)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r), y = self.y + s*math.sin(self.r)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/8), y = self.y + s*math.sin(self.r + math.pi/8)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/4), y = self.y + s*math.sin(self.r + math.pi/4)}) | |
else | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/8), y = self.y + s*math.sin(self.r - math.pi/8)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r), y = self.y + s*math.sin(self.r)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/8), y = self.y + s*math.sin(self.r + math.pi/8)}) | |
end | |
elseif self.mods.double then | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r - math.pi/18), y = self.y + s*math.sin(self.r - math.pi/18)}) | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r + math.pi/18), y = self.y + s*math.sin(self.r + math.pi/18)}) | |
else | |
table.insert(self.shooting_positions, {x = self.x + s*math.cos(self.r), y = self.y + s*math.sin(self.r)}) | |
end | |
local cdm = 1 | |
if self.mods.double then cdm = cdm*1.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 _, sp in ipairs(self.shooting_positions) do | |
if self:spend_ammo(am) then | |
self:shoot(sp.x, sp.y) | |
end | |
end | |
end) | |
end | |
else | |
for _, sp in ipairs(self.shooting_positions) do | |
if self:spend_ammo(am) then | |
self:shoot(sp.x, sp.y) | |
end | |
end | |
end | |
end | |
self.attack_pulse_timer = self.attack_pulse_timer + dt | |
if self.attack_pulse_timer > 4*self.attack_cd*cdm then | |
self.attack_pulse_timer = 0 | |
self.attack_spring:pull(0.25) | |
end | |
end | |
if not self.blasting then | |
self.blast_timer = self.blast_timer + dt | |
end | |
local r = math.deg(self.r) | |
local mx = camera:get_mouse_position() | |
local rd = 1 | |
if mx > self.x then rd = -1 else rd = 1 end | |
if mouse_pressed(2) and self.can_shoot then | |
self.reloading = true | |
self.reload_spring:pull(0.4) | |
self.reload_text = ReloadText(self.x + rd*62, self.y - 14, self.reload_cd, rd*math.pi/32) | |
table.insert(effects, self.reload_text) | |
end | |
if mouse_released(2) 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() | |
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 get_closest_unhit_enemy(enemies_hit, x, y) | |
local min_d, min_i = 1000000, 0 | |
for i, enemy in ipairs(enemies) do | |
if not any(enemies_hit, enemy.id) then | |
local d = distance(x, y, enemy.x, enemy.y) | |
if d < min_d then | |
min_d = d | |
min_i = i | |
end | |
end | |
end | |
return enemies[min_i] | |
end | |
function get_close_enemies(x, y, d) | |
local out = {} | |
for _, e in ipairs(enemies) do | |
if distance(e.x, e.y, x, y) < d then | |
table.insert(out, e) | |
end | |
end | |
return out | |
end | |
Projectile = Class:extend() | |
function Projectile:new(x, y, v, r, mods, enemies_hit) | |
self.timer = Timer() | |
self.mods = mods | |
self.x, self.y = x, y | |
self.v, self.r = v, r | |
if self.mods.sphere then self.v = v/8 end | |
self.vx, self.vy = self.v*math.cos(self.r), self.v*math.sin(self.r) | |
self.sx, self.sy = 1, 1 | |
self.w, self.h = 16, 4 | |
if self.mods.blasting then self.w, self.h = 24, 6 end | |
if self.mods.final then self.w, self.h = 24, 6 end | |
self.shape = HC.rectangle(self.x - self.w/2, self.y - self.h/2, self.w, self.h) | |
self.shape.parent = self | |
self.enemies_hit = enemies_hit or {} | |
self.damage = rng:int(40, 60) | |
if self.mods.blasting then self.damage = 4*self.damage end | |
if self.mods.final then self.damage = 3*self.damage end | |
self.accel_vm = 1 | |
self.decel_vm = 1 | |
self.speed_vm = 1 | |
self.scatter_vm = 1 | |
if self.mods.accel then self.accel_vm = 0; self.timer:tween(0.5, self, {accel_vm = 1.5}, linear) end | |
if self.mods.decel then self.decel_vm = 1.5; self.timer:tween(0.5, self, {decel_vm = 0.25}, linear) end | |
if self.mods.speed then self.speed_vm = 1.5 end | |
if self.mods.ricochet then self.ricochet_amount = mods.ricochet_amount or player.ricochet_amount end | |
if self.mods.scatter then self.scatter_amount = mods.scatter_amount or player.scatter_amount end | |
if self.mods.chain then self.chain_amount = mods.chain_amount or player.chain_amount end | |
if self.mods.pierce then self.pierce_amount = mods.pierce_amount or player.pierce_amount end | |
if self.mods.sphere then self.sphere_cd = 0.3; self.sphere_timer = 0; self.sphere_range = 128 end | |
if self.mods.spawner then | |
self.spawner_timer = 0 | |
self.spawner_cd = rng:float(0.1, 0.3) | |
if self.mods.sphere then self.spawner_cd = self.spawner_cd*8 end | |
if self.mods.accel then self.spawner_cd = self.spawner_cd*0.75 end | |
if self.mods.decel then self.spawner_cd = self.spawner_cd*2 end | |
if self.mods.speed then self.spawner_cd = self.spawner_cd*0.5 end | |
end | |
end | |
function Projectile:update(dt) | |
self.timer:update(dt) | |
local vx, vy = 0, 0 | |
if self.mods.homing then | |
local target = get_closest_unhit_enemy(self.enemies_hit, self.x, self.y) | |
if target then | |
local phx, phy = normalize(self.vx, self.vy) | |
local r = math.atan2(target.y - self.y, target.x - self.x) | |
local tthx, tthy = normalize(math.cos(r), math.sin(r)) | |
local fhx, fhy = normalize(phx + 0.1*tthx, phy + 0.1*tthy) | |
self.homing_vx, self.homing_vy = self.v*fhx, self.v*fhy | |
else self.homing_vx, self.homing_vy = nil, nil end | |
self.vx = (self.homing_vx or self.v*math.cos(self.r))*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
self.vy = (self.homing_vy or self.v*math.sin(self.r))*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
else | |
self.vx = self.v*math.cos(self.r)*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
self.vy = self.v*math.sin(self.r)*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
end | |
self.x = self.x + self.vx*dt | |
self.y = self.y + self.vy*dt | |
self.r = math.atan2(self.vy, self.vx) | |
if self.x < x1 then self:wall(0, x1, nil, math.pi - self.r); self.vx = -self.vx; self.x = x1 + 2; self.r = math.pi - self.r end | |
if self.x > x2 then self:wall(math.pi, x2, nil, math.pi - self.r); self.vx = -self.vx; self.x = x2 - 2; self.r = math.pi - self.r end | |
if self.y < 0 then self:wall(math.pi/2, nil, nil, 2*math.pi - self.r); self.vy = -self.vy; self.y = 2; self.r = 2*math.pi - self.r end | |
if self.y > gh then self:wall(-math.pi/2, nil, nil, 2*math.pi - self.r); self.vy = -self.vy; self.y = gh - 2; self.r = 2*math.pi - self.r end | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
if self.mods.sphere then | |
self.sphere_timer = self.sphere_timer + dt | |
if self.sphere_timer > self.sphere_cd then | |
self.sphere_timer = 0 | |
local targets = get_close_enemies(self.x, self.y, self.sphere_range) | |
if targets then | |
local target = rng:table(targets) | |
if target then | |
target:hit(self.damage) | |
table.insert(effects, LightningLine(self.x, self.y, target.x, target.y + target.v/10)) | |
table.insert(effects, HitEffect(target.x, target.y + target.v/10)) | |
for i = 1, 2 do table.insert(effects, ExplosionParticle(target.x, target.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end | |
end | |
end | |
end | |
end | |
if self.mods.spawner then | |
self.spawner_timer = self.spawner_timer + dt | |
if self.spawner_timer > self.spawner_cd then | |
self.spawner_timer = 0 | |
table.insert(projectiles, SecondaryProjectile(self.x, self.y, self.v, self.r - math.pi/2)) | |
table.insert(projectiles, SecondaryProjectile(self.x, self.y, self.v, self.r + math.pi/2)) | |
for i = 1, 4 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(50, 200))) end | |
table.insert(effects, ShootCircle(self.x, self.y)) | |
end | |
end | |
end | |
function Projectile:draw() | |
local s = 1 | |
if self.mods.blasting or self.mods.sphere then s = rng:float(1, 1.1) end | |
push(self.x, self.y, self.r, s*self.sx, s*self.sy) | |
if self.mods.sphere then | |
circlef(self.x, self.y, self.w/4 + rng:float(-1, 1)) | |
circle(self.x, self.y, self.sphere_range, 2, {1, 1, 1, 0.04}) | |
else rectf(self.x, self.y, self.w, self.h, nil, nil, white) end | |
pop() | |
end | |
function Projectile:wall(r, x, y, rr) | |
if (self.mods.ricochet and self.ricochet_amount > 0) or (self.mods.scatter and self.scatter_amount > 0) then | |
if self.mods.ricochet then self.ricochet_amount = self.ricochet_amount - 1 end | |
if self.mods.scatter then self.scatter_amount = self.scatter_amount - 1; self.scatter_vm = self.scatter_vm*1.5 end | |
for i = 1, 2 do table.insert(effects, EllipseParticle(x or self.x, y or self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end | |
table.insert(effects, ShootCircle(x or self.x, y or self.y)) | |
else | |
self.dead = true | |
for i = 1, 2 do table.insert(effects, EllipseParticle(x or self.x, y or self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end | |
table.insert(effects, ShootCircle(x or self.x, y or self.y)) | |
if self.mods.scatter then | |
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
for i = 1, rng:int(2, 8) do | |
table.insert(projectiles, SecondaryProjectile(self.x, self.y, 1.25*v, rr + rng:float(-math.pi/4, math.pi/4))) | |
end | |
end | |
if self.mods.split then | |
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
local mods = copy(self.mods) | |
mods.split = false | |
table.insert(projectiles, Projectile(self.x, self.y, v, r + math.pi/4, mods)) | |
table.insert(projectiles, Projectile(self.x, self.y, v, r - math.pi/4, mods)) | |
end | |
end | |
end | |
function Projectile:hit(enemy) | |
if any(self.enemies_hit, enemy.id) then return end | |
if self.mods.chain or self.mods.pierce or self.mods.sphere then | |
table.insert(self.enemies_hit, enemy.id) | |
if self.mods.chain then | |
self.chain_amount = self.chain_amount - 1 | |
table.insert(effects, CircleEffect2(enemy.x, enemy.y, enemy.w)) | |
for i = 1, 2 do table.insert(effects, ExplosionParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end | |
local target = get_closest_unhit_enemy(self.enemies_hit, self.x, self.y) | |
if target then self.r = math.atan2((target.y + 0.5*target.v) - self.y, target.x - self.x) | |
else self:die() end | |
if self.chain_amount < 0 then self:die() end | |
elseif self.mods.pierce then | |
self.pierce_amount = self.pierce_amount - 1 | |
table.insert(effects, CircleEffect2(enemy.x, enemy.y, enemy.w)) | |
for i = 1, 2 do table.insert(effects, ExplosionParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end | |
if self.pierce_amount < 0 then self:die() end | |
elseif self.mods.sphere then | |
table.insert(effects, CircleEffect2(enemy.x, enemy.y, enemy.w)) | |
for i = 1, 2 do table.insert(effects, ExplosionParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 300))) end | |
end | |
else self:die() end | |
if self.mods.fork then | |
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
local mods = copy(self.mods) | |
mods.fork = false | |
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - math.pi/4, mods, {enemy.id})) | |
table.insert(projectiles, Projectile(self.x, self.y, v, self.r + math.pi/4, mods, {enemy.id})) | |
end | |
if self.mods.cross then | |
local v = self.v*self.accel_vm*self.decel_vm*self.speed_vm*self.scatter_vm | |
local mods = copy(self.mods) | |
mods.cross = false | |
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - math.pi/2, mods, {enemy.id})) | |
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - 2*math.pi/2, mods, {enemy.id})) | |
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - 3*math.pi/2, mods, {enemy.id})) | |
table.insert(projectiles, Projectile(self.x, self.y, v, self.r - 4*math.pi/2, mods, {enemy.id})) | |
end | |
if self.mods.blast then | |
if not player.blasting and player.blast_timer > player.blast_cd then | |
player.blast_meter = player.blast_meter + 5 | |
if player.blast_meter > 50 then | |
player.blasting = true | |
player.blast_meter = 50 | |
table.insert(effects, InfoText(player.x, player.y - 36, "BLAST!", 0.6)) | |
end | |
end | |
if self.mods.blasting then | |
table.insert(effects, ExplosionCircle(enemy.x, enemy.y)) | |
table.insert(displacements, Shockwave(enemy.x, enemy.y)) | |
for i = 1, rng:int(8, 12) do table.insert(effects, ExplosionParticle(enemy.x, enemy.y, rng:float(0, 2*math.pi), rng:float(300, 600))) end | |
local targets = get_close_enemies(enemy.x, enemy.y, 64) | |
for i, t in ipairs(targets) do | |
timer:after((i-1)*0.05, function() | |
t:hit(2*self.damage) | |
table.insert(effects, ExplosionCircle(enemy.x, enemy.y, 0.5)) | |
for i = 1, rng:int(4, 6) do table.insert(effects, ExplosionParticle(enemy.x, enemy.y, rng:float(0, 2*math.pi), rng:float(200, 400))) end | |
end) | |
end | |
end | |
end | |
if self.mods.weaken then if rng:bool(17) then enemy:weaken() end end | |
if self.mods.glitch then if rng:bool(17) then enemy:glitch() end end | |
if self.mods.slow then if rng:bool(17) then enemy:slow() end end | |
if self.mods.haste then if rng:bool(17) then enemy:hasten() end end | |
if self.mods.stun then if rng:bool(17) then enemy:stun() end end | |
return true | |
end | |
function Projectile:die() | |
self.dead = true | |
for i = 1, 2 do table.insert(effects, EllipseParticle(x or self.x, y or self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end | |
table.insert(effects, ShootCircle(x or self.x, y or self.y)) | |
end | |
function Projectile:die2() | |
self.dead = true | |
for i = 1, 2 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 200))) end | |
table.insert(effects, ShootCircle(self.x, self.y)) | |
end | |
Enemy = Class:extend() | |
function Enemy:enemy_new(v, hp) | |
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 | |
table.insert(effects, DeathCircle(self.x, self.y, 2*self.w, white)) | |
table.insert(effects, CircleEffect(self.x, self.y, self.w, white)) | |
player:hit(1) | |
flash(1, black) | |
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) | |
if self.dead then return end | |
self.sx, self.sy = 1.35, 1.35 | |
self.hit_flash = true | |
self.timer:tween(0.1, self, {sx = 1, sy = 1}, linear, function() self.hit_flash = false end, "hit") | |
if self.weak then damage = 2*damage end | |
self.hp = self.hp - damage | |
if self.hp <= 0 then | |
self.dead = true | |
director:killed_enemy() | |
player:change_resource(math.ceil(rng:int(2, 4)*self.hpm)) | |
if self.stun_effect then self.stun_effect:die() end | |
table.insert(effects, DeathCircle(self.x, self.y, 2*self.w)) | |
for i = 1, 4 do table.insert(effects, DustParticle(self.x, self.y, white)) end | |
if projectile then | |
if projectile.mods.burn then | |
table.insert(effects, AnimatedEffect(self.x + 6, self.y - 6, "radial1", 0.01, nil, nil)) | |
local targets = get_close_enemies(self.x, self.y, 104) | |
for i, t in ipairs(targets) do | |
timer:after((i-1)*0.05, function() | |
t:burn() | |
end) | |
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 - 3, "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) | |
for j = 1, 3 do | |
self.timer:after((j-1)*(0.3/3), function() | |
table.insert(displacements, DisplacementBlock(self.x + rng:float(-self.w, self.w), self.y + rng:float(-self.w, self.w), rng:float(self.w/2, 2*self.w), rng:float(self.w/2, 2*self.w), 0.15, 0.3)) | |
table.insert(effects, Block(self.x + rng:float(-self.w, self.w), self.y + rng:float(-self.w, self.w), rng:float(self.w/2, 1.5*self.w), rng:float(self.w/2, 1.5*self.w), 0.15, 0.3)) | |
end) | |
end | |
end, 20, nil, "glitche") | |
self.timer:after(6, function() self.glitched = false end, "glitcha") | |
end | |
function 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 | |
table.insert(effects, AnimatedEffect(self.x, self.y + 4, rng:table({"disappear1", "disappear2"}), 0.02, nil, math.pi/2, 1, 1)) | |
end, 15, function() self.slowed = false end, "slowe") | |
end | |
function 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 | |
table.insert(effects, AnimatedEffect(self.x, self.y + 4, rng:table({"disappear1", "disappear2"}), 0.02, nil, 0, 1, 1)) | |
end, 15, function() self.haste = false end, "hastee") | |
end | |
function 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 | |
Rock = Class:extend() | |
Rock:implement(Enemy) | |
function Rock:new(x, y, hpm) | |
self.id = rng:uid() | |
self.x, self.y = x, y | |
self.w = 16 | |
self.r = 0 | |
self.sx, self.sy = 1, 1 | |
self.d = 1 | |
self.lw = 2 | |
self.vs = {} | |
local r = 0 | |
for i = 1, 8 do | |
local w = rng:float(0.95, 1.05) | |
self.vs[2*(i-1)+1] = self.w*w*math.cos(r) | |
self.vs[2*i] = self.w*w*math.sin(r) | |
r = r + math.pi/4 | |
end | |
self.vr = rng:float(-4*math.pi, 4*math.pi) | |
self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) | |
self.shape.parent = self | |
self.hpm = hpm or 1 | |
self:enemy_new(50, 250*self.hpm) | |
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.sx, self.sy = 1, 1 | |
self.w, self.h = 9, 3 | |
self.shape = HC.rectangle(self.x - self.w/2, self.y - self.h/2, self.w, self.h) | |
self.shape.parent = self | |
self.damage = rng:int(10, 20) | |
end | |
function SecondaryProjectile:update(dt) | |
self.x = self.x + self.vx*dt | |
self.y = self.y + self.vy*dt | |
self.r = math.atan2(self.vy, self.vx) | |
if self.x < x1 then self:die(x1, self.y) end | |
if self.x > x2 then self:die(x2, self.y) end | |
if self.y < 0 then self:die(self.x, 0) end | |
if self.y > gh then self:die(self.x, gh) end | |
self.shape:moveTo(self.x, self.y) | |
self.shape:setRotation(self.r) | |
end | |
function SecondaryProjectile:draw() | |
push(self.x, self.y, self.r, self.sx, self.sy) | |
rectf(self.x, self.y, self.w, self.h, nil, nil, white) | |
pop() | |
end | |
function SecondaryProjectile:die(x, y) | |
self.dead = true | |
table.insert(effects, ShootCircle(x or self.x, y or self.y, 8)) | |
end | |
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)) | |
self.lw = 3 | |
self.generations = 4 | |
self.max_offset = 12 | |
self:generate() | |
self.timer:after(0.1, function() self.dead = true end) | |
end | |
function LightningLine:update(dt) | |
self.timer:update(dt) | |
end | |
function LightningLine:generate() | |
local offset_amount = self.max_offset | |
local lines = self.lines | |
for j = 1, self.generations do | |
for i = #lines, 1, -1 do | |
local x1, y1 = lines[i].x1, lines[i].y1 | |
local x2, y2 = lines[i].x2, lines[i].y2 | |
table.remove(lines, i) | |
local x, y = (x1 + x2)/2, (y1 + y2)/2 | |
local px, py = perpendicular(normalize(x2 - x1, y2 - y1)) | |
x = x + px*rng:float(-offset_amount, offset_amount) | |
y = y + py*rng:float(-offset_amount, offset_amount) | |
table.insert(lines, {x1 = x1, y1 = y1, x2 = x, y2 = y}) | |
table.insert(lines, {x1 = x, y1 = y, x2 = x2, y2 = y2}) | |
end | |
offset_amount = offset_amount/2 | |
end | |
end | |
function LightningLine:draw() | |
for i, line in ipairs(self.lines) do | |
g.setLineWidth(self.lw) | |
g.line(line.x1, line.y1, line.x2, line.y2) | |
end | |
g.setLineWidth(1) | |
g.setColor(1, 1, 1, 1) | |
end | |
StunEffect = Class:extend() | |
function StunEffect:new(x, y, enemy) | |
self.x, self.y = x, y | |
self.sx, self.sy = 4, 4 | |
self.a = 0 | |
self.vs = enemy.vs | |
self.enemy = enemy | |
self.scale_spring = Spring(1) | |
timer:tween(0.2, self, {a = 1, sx = 1, sy = 1}, linear, function() | |
self.sx, self.sy = 1, 1 | |
self.a = 1 | |
self.scale_spring:pull(-0.5) | |
for i = 1, 2 do table.insert(effects, DustParticle(self.x, self.y, white)) end | |
end) | |
end | |
function StunEffect:update(dt) | |
self.scale_spring:update(dt) | |
self.r = self.enemy.r | |
end | |
function StunEffect:draw() | |
push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) | |
polygon(to_polygon(self.x, self.y, self.vs), 3, {1, 1, 1, self.a}) | |
pop() | |
end | |
function StunEffect:die() | |
table.insert(effects, CircleEffect(self.x, self.y, self.enemy.w)) | |
for i = 1, 6 do table.insert(effects, EllipseParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(150, 300))) end | |
timer:tween(0.2, self, {a = 0, sx = 4, sy = 4}, linear, function() self.dead = true end) | |
end | |
Block = Class:extend() | |
function Block:new(x, y, w, h, d1, d2) | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
timer:after({d1 or 0.05, d2 or 0.4}, function() self.dead = true end) | |
end | |
function Block:update(dt) | |
end | |
function Block:draw() | |
g.setColor(1, 1, 1, 1) | |
g.rectangle("fill", self.x - self.w/2, self.y - self.h/2, self.w, self.h) | |
end | |
DisplacementBlock = Class:extend() | |
function DisplacementBlock:new(x, y, w, h, d1, d2, 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) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.sx, self.sy = 0.05, 0.05 | |
self.a = 1 | |
self.timer:tween(0.5, self, {sx = 0.75, sy = 0.75, a = 0}, linear, function() self.dead = true end) | |
end | |
function Shockwave:update(dt) | |
self.timer:update(dt) | |
end | |
function Shockwave:draw() | |
g.setColor(1, 1, 1, self.a) | |
g.draw(shockwave, self.x, self.y, 0, self.sx, self.sy, shockwave:getWidth()/2, shockwave:getHeight()/2) | |
g.setColor(1, 1, 1, 1) | |
end | |
InfoText = Class:extend() | |
function InfoText:new(x, y, text, duration, r, glitch) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.r = r or 0 | |
self.sx, self.sy = 1, 1 | |
self.characters = {} | |
self.visuals = {} | |
for i = 1, #text do table.insert(self.visuals, 1) end | |
self.visible = true | |
self.font = font_medium | |
self.w, self.h = self.font:getWidth(text), self.font:getHeight() | |
self.scale_spring = Spring(1) | |
self.scale_spring:pull(0.15) | |
self.t = 0 | |
local characters = {} | |
for i = 1, #text do table.insert(characters, text:sub(i, i)) end | |
for i = 1, #characters do | |
self.timer:after((i-1)*(0.15/6), function() | |
self.visuals[i] = 3 | |
if self.visuals[i-1] then self.visuals[i-1] = 2 end | |
if self.visuals[i-2] then self.visuals[i-2] = 1 end | |
table.insert(self.characters, characters[i]) | |
end) | |
end | |
self.timer:after(0.15, function() | |
self.visuals = {1, 1, 1, 1, 1, 1} | |
self.timer:every(0.05, function() self.visible = not self.visible end, math.floor((duration - 0.15)/0.05)) | |
self.timer:after((duration - 0.15), function() self.visible = true; self.dead = true end) | |
self.timer:every(0.035, function() | |
local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ" | |
for i, c in ipairs(self.characters) do | |
if rng:bool(10) then | |
local r = rng:int(1, #random_characters) | |
self.characters[i] = random_characters:sub(r, r) | |
end | |
if rng:bool(10) then self.visuals[i] = rng:int(1, 4) end | |
end | |
end, math.floor((duration - 0.15)/0.035)) | |
end) | |
end | |
function InfoText:update(dt) | |
self.timer:update(dt) | |
self.scale_spring:update(dt) | |
end | |
function InfoText:draw() | |
if not self.visible then return end | |
local w, h = 0, 0 | |
local x, y = self.x - self.w/2, self.y - self.h/2 | |
g.setFont(self.font) | |
push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) | |
for i = 1, #self.characters do | |
local cw, ch = self.font:getWidth(self.characters[i]), self.font:getHeight() | |
if self.visuals[i] == 1 then | |
g.setColor(white) | |
g.print(self.characters[i], x + w, y + h) | |
elseif self.visuals[i] == 2 then | |
g.setColor(white) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
g.setColor(black) | |
g.print(self.characters[i], x + w, y + h) | |
elseif self.visuals[i] == 3 then | |
g.setColor(white) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
elseif self.visuals[i] == 4 then | |
g.setColor(black) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
end | |
w = w + self.font:getWidth(self.characters[i]) | |
end | |
g.setColor(white) | |
g.rectangle("fill", x, y - 5, self.w*self.t, 3) | |
pop() | |
end | |
ReloadText = Class:extend() | |
function ReloadText:new(x, y, duration, r) | |
self.timer = Timer() | |
self.x, self.y = x, y | |
self.r = r | |
self.sx, self.sy = 1, 1 | |
self.characters = {} | |
self.visuals = {1, 1, 1, 1, 1, 1} | |
self.visible = true | |
self.font = font_medium | |
self.w, self.h = self.font:getWidth("RELOAD"), self.font:getHeight() | |
self.scale_spring = Spring(1) | |
self.scale_spring:pull(0.15) | |
self.t = 0 | |
local characters = {"R", "E", "L", "O", "A", "D"} | |
for i = 1, 6 do | |
self.timer:after((i-1)*(0.15/6), function() | |
self.visuals[i] = 3 | |
if self.visuals[i-1] then self.visuals[i-1] = 2 end | |
if self.visuals[i-2] then self.visuals[i-2] = 1 end | |
table.insert(self.characters, characters[i]) | |
end) | |
end | |
self.timer:after(0.15, function() | |
self.visuals = {1, 1, 1, 1, 1, 1} | |
self.timer:every(0.05, function() self.visible = not self.visible end, math.floor((duration - 0.15)/0.05)) | |
self.timer:after((duration - 0.15), function() self.visible = true end) | |
self.timer:every(0.035, function() | |
local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ" | |
for i, c in ipairs(self.characters) do | |
if rng:bool(10) then | |
local r = rng:int(1, #random_characters) | |
self.characters[i] = random_characters:sub(r, r) | |
end | |
if rng:bool(10) then self.visuals[i] = rng:int(1, 4) end | |
end | |
end, math.floor((duration - 0.15)/0.035)) | |
end) | |
end | |
function ReloadText:update(dt) | |
self.timer:update(dt) | |
self.scale_spring:update(dt) | |
end | |
function ReloadText:draw() | |
if not self.visible then return end | |
local w, h = 0, 0 | |
local x, y = self.x - self.w/2, self.y - self.h/2 | |
g.setFont(self.font) | |
push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) | |
for i = 1, #self.characters do | |
local cw, ch = self.font:getWidth(self.characters[i]), self.font:getHeight() | |
if self.visuals[i] == 1 then | |
g.setColor(white) | |
g.print(self.characters[i], x + w, y + h) | |
elseif self.visuals[i] == 2 then | |
g.setColor(white) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
g.setColor(black) | |
g.print(self.characters[i], x + w, y + h) | |
elseif self.visuals[i] == 3 then | |
g.setColor(white) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
elseif self.visuals[i] == 4 then | |
g.setColor(black) | |
rectf(x + w + cw/2, y + h + ch/2, cw, ch) | |
end | |
w = w + self.font:getWidth(self.characters[i]) | |
end | |
g.setColor(white) | |
g.rectangle("fill", x, y - 5, self.w*self.t, 3) | |
pop() | |
end | |
function ReloadText:die() | |
self.dead = true | |
end | |
RefreshEffect = Class:extend() | |
function RefreshEffect:new(x, y, w, h) | |
self.x, self.y = x, y | |
self.w, self.h = w, h | |
self.oy = h/3 | |
timer:tween(0.15, self, {h = 0}, linear, function() self.dead = true end) | |
end | |
function RefreshEffect:update(dt) | |
end | |
function RefreshEffect:draw() | |
g.rectangle("fill", self.x - self.w/2, self.y - self.oy, self.w, self.h) | |
end | |
DamageNumber = Class:extend() | |
function DamageNumber:new(x, y, vx, vy, t) | |
self.x, self.y = x, y | |
self.sx, self.sy = 1.00, 1.00 | |
self.vx, self.vy = vx, vy | |
self.t = t | |
self.scale_spring = Spring(1) | |
self.scale_spring:pull(0.25) | |
self.r = 0 | |
-- if vx > 0 then self.vr = rng:float(2*math.pi, 4*math.pi) else self.vr = rng:float(-4*math.pi, -2*math.pi) end | |
timer:after(0.25, function() | |
timer:tween(0.05, self, {sx = 0, sy = 0}, linear, function() self.dead = true end) | |
end) | |
end | |
function DamageNumber:update(dt) | |
self.scale_spring:update(dt) | |
self.vy = self.vy + 400*dt | |
self.x = self.x + self.vx*dt | |
self.y = self.y + self.vy*dt | |
-- self.r = self.r + self.vr*dt | |
end | |
function DamageNumber:draw() | |
push(self.x, self.y, self.r, 1.25*self.scale_spring.x*self.sx, 1.25*self.scale_spring.x*self.sy) | |
draw_text(self.t, self.x, self.y, 0, 1, 1, font_small) | |
pop() | |
end | |
HitEffect = Class:extend() | |
function HitEffect:new(x, y) | |
self.x, self.y = x, y | |
self.r = rng:float(0, 2*math.pi) | |
self.animation = Animation(0.025, get_animation_frames("hit1"), "once", {[0] = function() self.dead = true end}) | |
self.sx, self.sy = 1.2, 1.2 | |
timer:tween(0.025*get_animation_frames("hit1"), self, {sx = 1, sy = 1}, linear) | |
end | |
function HitEffect:update(dt) | |
self.animation:update(dt) | |
end | |
function HitEffect:draw() | |
draw_animation("hit1", self.animation:get_current_frame(), self.x, self.y, self.r, 1.35*self.sx, 1.35*self.sy) | |
end | |
ChargeParticle = Class:extend() | |
function ChargeParticle:new(x, y, rs) | |
local r = rng:float(0, 2*math.pi) | |
local d = rng:float(0.75*rs, 1.25*rs) | |
self.x, self.y = x + d*math.cos(r), y + d*math.sin(r) | |
self.rs = rng:float(6, 10) | |
timer:tween(rng:float(0.5, 1.5), self, {x = x, y = y}, linear, function() self.dead = true end) | |
end | |
function ChargeParticle:update(dt) | |
end | |
function ChargeParticle:draw() | |
circlef(self.x, self.y, self.rs, white) | |
end | |
BurnParticle = Class:extend() | |
function BurnParticle:new(x, y) | |
self.x, self.y = x, y | |
self.animation = Animation(1, get_animation_frames("smoke1"), "once") | |
self.sx, self.sy = 0, 0 | |
self.r = rng:float(0, 2*math.pi) | |
self.v = rng:float(50, 150) | |
self.rs = 0 | |
self.vr = rng:float(0, 2*math.pi) | |
timer:tween(rng:float(0.04, 0.06), self, {sx = 0.5, sy = 0.5}, cubic_in_out, function() | |
timer:tween(rng:float(0.2, 0.4), self, {sx = 0, sy = 0}, linear, function() self.dead = true end) | |
end) | |
end | |
function BurnParticle:update(dt) | |
self.animation:update(dt) | |
self.x = self.x + self.v*math.cos(self.r)*dt | |
self.y = self.y + self.v*math.sin(self.r)*dt | |
self.rs = self.rs + self.vr*dt | |
end | |
function BurnParticle:draw() | |
draw_animation("smoke1", self.animation:get_current_frame(), self.x, self.y, self.rs, 3*self.sx, 3*self.sy) | |
end | |
DustParticle = Class:extend() | |
function DustParticle:new(x, y, color) | |
self.x, self.y = x, y | |
self.animation = Animation(1, get_animation_frames("smoke1"), "once") | |
self.color = color or red | |
self.sx, self.sy = 0, 0 | |
self.v = rng:float(100, 220) | |
self.r = rng:float(0, 2*math.pi) | |
self.rs = 0 | |
self.vr = rng:float(0, 2*math.pi) | |
timer:after(0.1, function() self.color = color or white end) | |
timer:tween(rng:float(0.04, 0.06), self, {sx = 0.7, sy = 0.7}, cubic_in_out, function() | |
timer:tween(rng:float(0.3, 0.4), self, {sx = 0, sy = 0, v = 0}, linear, function() self.dead = true end) | |
end) | |
end | |
function DustParticle:update(dt) | |
self.animation:update(dt) | |
self.rs = self.rs + self.vr*dt | |
self.x, self.y = self.x + self.v*math.cos(self.r)*dt, self.y + self.v*math.sin(self.r)*dt | |
end | |
function DustParticle:draw() | |
g.setShader(combine) | |
g.setColor(self.color) | |
draw_animation("smoke1", self.animation:get_current_frame(), self.x, self.y, self.rs, 3.5*self.sx, 3.5*self.sy) | |
g.setShader() | |
g.setColor(white) | |
end | |
AnimatedEffect = Class:extend() | |
function AnimatedEffect:new(x, y, name, delay, loop_mode, r, sx, sy, 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 | |
DeathCircle = Class:extend() | |
function DeathCircle:new(x, y, r, color) | |
self.x, self.y = x, y | |
self.r = r | |
self.color = color or white | |
timer:tween(0.26, self, {r = 0}, cubic_in_out, function() self.dead = true end) | |
end | |
function DeathCircle:update(dt) | |
end | |
function DeathCircle:draw() | |
circlef(self.x, self.y, self.r, self.color) | |
end | |
DeathParticle = Class:extend() | |
function DeathParticle:new(x, y, r, v, color) | |
self.x, self.y = x, y | |
self.r = r | |
self.v = v | |
self.w, self.h = 14, 4.5 | |
self.color = color or white | |
timer:tween({0.2, 0.4}, self, {v = 0}, linear, function() self.dead = true end) | |
end | |
function DeathParticle:update(dt) | |
self.x = self.x + self.v*math.cos(self.r)*dt | |
self.y = self.y + (self.v*math.sin(self.r) + 0)*dt | |
self.w = remap(self.v, 0, 400, 0, 14) | |
self.h = remap(self.v, 0, 400, 0, 4.5) | |
end | |
function DeathParticle:draw() | |
push(self.x, self.y, math.atan2(self.v*math.sin(self.r) + 0, self.v*math.cos(self.r))) | |
rectf(self.x, self.y, self.w, self.h, nil, nil, self.color) | |
pop() | |
end | |
ShootCircle = Class:extend() | |
function ShootCircle:new(x, y, rs) | |
self.x, self.y = x, y | |
self.rs = rs or 12 | |
timer:tween(0.1, self, {rs = 0}, linear, function() self.dead = true end) | |
end | |
function ShootCircle:update(dt) | |
end | |
function ShootCircle:draw() | |
circlef(self.x, self.y, self.rs) | |
end | |
ShootCapsule = Class:extend() | |
function ShootCapsule:new(x, y, r, v) | |
self.x, self.y = x, y | |
self.vx, self.vy = v*math.cos(r), v*math.sin(r) | |
self.w, self.h = 6, 3 | |
self.r = 0 | |
self.vr = rng:float(-4*math.pi, 4*math.pi) | |
end | |
function ShootCapsule:update(dt) | |
self.vy = self.vy + 600*dt | |
self.x = self.x + self.vx*dt | |
self.y = self.y + self.vy*dt | |
self.r = self.r + self.vr*dt | |
if self.y > gh or self.y < 0 or self.x > gw or self.x < 0 then | |
self.dead = true | |
table.insert(ui_effects, ShootCircle(self.x, self.y)) | |
end | |
end | |
function ShootCapsule:draw() | |
push(self.x, self.y, self.r) | |
rectf(self.x, self.y, self.w, self.h) | |
pop() | |
end | |
FadingShootCapsule = Class:extend() | |
function FadingShootCapsule:new(x, y, r, v) | |
self.x, self.y = x, y | |
self.vx, self.vy = v*math.cos(r), v*math.sin(r) | |
self.w, self.h = 6, 3 | |
self.r = 0 | |
self.vr = rng:float(-4*math.pi, 4*math.pi) | |
self.color = {1, 1, 1, 1} | |
timer:after(0.1, function() | |
timer:tween({0.2, 0.5}, self.color, {[4] = 0}, linear, function() self.dead = true end) | |
end) | |
end | |
function FadingShootCapsule:update(dt) | |
self.vy = self.vy + 600*dt | |
self.x = self.x + self.vx*dt | |
self.y = self.y + self.vy*dt | |
self.r = self.r + self.vr*dt | |
if self.y > gh or self.y < 0 or self.x > gw or self.x < 0 then | |
self.dead = true | |
table.insert(ui_effects, ShootCircle(self.x, self.y)) | |
end | |
end | |
function FadingShootCapsule:draw() | |
push(self.x, self.y, self.r) | |
rectf(self.x, self.y, self.w, self.h, nil, nil, self.color) | |
pop() | |
end | |
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