Week 12-13

master
a327ex 2021-05-17 05:05:48 -03:00
parent 9ad62548e3
commit bc2991a38b
40 changed files with 1008 additions and 367 deletions

640
arena.lua
View File

@ -14,6 +14,8 @@ function Arena:on_enter(from, level, units, passives)
self.units = units self.units = units
self.passives = passives self.passives = passives
trigger:tween(2, main_song_instance, {volume = 0.5, pitch = 1}, math.linear)
steam.friends.setRichPresence('steam_display', '#StatusFull') steam.friends.setRichPresence('steam_display', '#StatusFull')
steam.friends.setRichPresence('text', 'Arena - Level ' .. self.level) steam.friends.setRichPresence('text', 'Arena - Level ' .. self.level)
@ -22,6 +24,7 @@ function Arena:on_enter(from, level, units, passives)
self.post_main = Group() self.post_main = Group()
self.effects = Group() self.effects = Group()
self.ui = Group() self.ui = Group()
self.credits = Group()
self.main:disable_collision_between('player', 'player') self.main:disable_collision_between('player', 'player')
self.main:disable_collision_between('player', 'projectile') self.main:disable_collision_between('player', 'projectile')
self.main:disable_collision_between('player', 'enemy_projectile') self.main:disable_collision_between('player', 'enemy_projectile')
@ -40,9 +43,6 @@ function Arena:on_enter(from, level, units, passives)
self.enemies = {Seeker, EnemyCritter} self.enemies = {Seeker, EnemyCritter}
self.color = self.color or fg[0] self.color = self.color or fg[0]
self.level = 25
self.can_quit = true
-- Spawn solids and player -- Spawn solids and player
self.x1, self.y1 = gw/2 - 0.8*gw/2, gh/2 - 0.8*gh/2 self.x1, self.y1 = gw/2 - 0.8*gw/2, gh/2 - 0.8*gh/2
self.x2, self.y2 = gw/2 + 0.8*gw/2, gh/2 + 0.8*gh/2 self.x2, self.y2 = gw/2 + 0.8*gw/2, gh/2 + 0.8*gh/2
@ -99,12 +99,23 @@ function Arena:on_enter(from, level, units, passives)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48} SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
SpawnEffect{group = self.effects, x = gw/2, y = gh/2, action = function(x, y) SpawnEffect{group = self.effects, x = gw/2, y = gh/2, action = function(x, y)
spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15} spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15}
self.boss = Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = level_to_boss[self.level]} SpawnMarker{group = self.effects, x = x, y = y}
self.t:after(0.75, function()
self.boss = Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = level_to_boss[self.level]}
end)
end} end}
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.t:every(function()
if self.boss and not self.boss.dead then
return (#self.main:get_objects_by_classes(self.enemies) <= 1) and not self.spawning_enemies
elseif self.boss and self.boss.dead then
return (#self.main:get_objects_by_classes(self.enemies) <= 0) and not self.spawning_enemies
end
end, function()
self.hfx:use('condition1', 0.25, 200, 10) self.hfx:use('condition1', 0.25, 200, 10)
self.hfx:pull('condition2', 0.0625) self.hfx:pull('condition2', 0.0625)
self.t:after(0.5, function() self.t:after(0.5, function()
self.spawning_enemies = true
self.t:after((8 + math.floor(self.level/2))*0.1 + 0.5 + 0.75, function() self.spawning_enemies = false end, 'spawning_enemies')
local spawn_type = random:table{'left', 'middle', 'right'} local spawn_type = random:table{'left', 'middle', 'right'}
local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}} local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}}
local p = spawn_points[spawn_type] local p = spawn_points[spawn_type]
@ -114,7 +125,7 @@ function Arena:on_enter(from, level, units, passives)
end) end)
end) end)
self.t:every(function() return self.start_time <= 0 and (self.boss and self.boss.dead) and #self.main:get_objects_by_classes(self.enemies) <= 0 and not self.spawning_enemies and not self.quitting end, function() self.t:every(function() return self.start_time <= 0 and (self.boss and self.boss.dead) and #self.main:get_objects_by_classes(self.enemies) <= 0 and not self.spawning_enemies and not self.quitting end, function()
self.can_quit = true self:quit()
if self.level == 6 then if self.level == 6 then
state.achievement_speed_booster = true state.achievement_speed_booster = true
system.save_state() system.save_state()
@ -172,7 +183,7 @@ function Arena:on_enter(from, level, units, passives)
alert1:play{pitch = 1.2, volume = 0.5} alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25) camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48} SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and not self.spawning_enemies end, function()
self.wave = self.wave + 1 self.wave = self.wave + 1
if self.wave > self.max_waves then return end if self.wave > self.max_waves then return end
self.hfx:use('condition1', 0.25, 200, 10) self.hfx:use('condition1', 0.25, 200, 10)
@ -186,6 +197,8 @@ function Arena:on_enter(from, level, units, passives)
end) end)
end end
else else
self.spawning_enemies = true
self.t:after((8 + (self.wave-1)*2)*0.1 + 0.5 + 0.75, function() self.spawning_enemies = false end, 'spawning_enemies')
local spawn_type = random:table{'left', 'middle', 'right'} local spawn_type = random:table{'left', 'middle', 'right'}
local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}} local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}}
local p = spawn_points[spawn_type] local p = spawn_points[spawn_type]
@ -195,12 +208,12 @@ function Arena:on_enter(from, level, units, passives)
end) end)
end, self.max_waves+1) end, self.max_waves+1)
end) end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.wave > self.max_waves end, function() self.can_quit = true end) self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.wave > self.max_waves and not self.quitting and not self.spawning_enemies end, function() self:quit() end)
end) end)
if self.level == 18 and self.trailer then if self.level == 20 and self.trailer then
Text2{group = self.ui, x = gw/2, y = gh/2 - 24, lines = {{text = '[fg, wavy]SNKRX', font = fat_font, alignment = 'center'}}} Text2{group = self.ui, x = gw/2, y = gh/2 - 24, lines = {{text = '[fg, wavy]SNKRX', font = fat_font, alignment = 'center'}}}
Text2{group = self.ui, x = gw/2, y = gh/2, sx = 0.5, sy = 0.5, lines = {{text = '[fg, wavy_mid]wishlist now!', font = fat_font, alignment = 'center'}}} Text2{group = self.ui, x = gw/2, y = gh/2, sx = 0.5, sy = 0.5, lines = {{text = '[fg, wavy_mid]play now!', font = fat_font, alignment = 'center'}}}
Text2{group = self.ui, x = gw/2, y = gh/2 + 24, sx = 0.5, sy = 0.5, lines = {{text = '[light_bg, wavy_mid]music: kubbi - ember', font = fat_font, alignment = 'center'}}} Text2{group = self.ui, x = gw/2, y = gh/2 + 24, sx = 0.5, sy = 0.5, lines = {{text = '[light_bg, wavy_mid]music: kubbi - ember', font = fat_font, alignment = 'center'}}}
end end
end end
@ -236,28 +249,143 @@ function Arena:on_enter(from, level, units, passives)
self.psyker_level = class_levels.psyker self.psyker_level = class_levels.psyker
self.conjurer_level = class_levels.conjurer self.conjurer_level = class_levels.conjurer
self.t:every(0.375, function()
local p = random:table(star_positions)
Star{group = star_group, x = p.x, y = p.y}
end)
end
function Arena:on_exit()
self.floor:destroy()
self.main:destroy()
self.post_main:destroy()
self.effects:destroy()
self.ui:destroy()
self.credits:destroy()
self.t:destroy()
self.floor = nil
self.main = nil
self.post_main = nil
self.effects = nil
self.ui = nil
self.credits = nil
self.units = nil
self.passives = nil
self.player = nil
self.t = nil
self.springs = nil
self.flashes = nil
self.hfx = nil
end end
function Arena:update(dt) function Arena:update(dt)
if input.k.pressed then if main_song_instance:isStopped() then
SpawnEffect{group = self.effects, x = gw/2, y = gh/2, action = function(x, y) main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15}
Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = 'randomizer'}
end}
end end
if input.escape.pressed and not self.transitioning then if input.escape.pressed and not self.transitioning and not self.in_credits then
if not self.paused then if not self.paused then
trigger:tween(0.25, _G, {slow_amount = 0}, math.linear, function() trigger:tween(0.25, _G, {slow_amount = 0}, math.linear, function()
slow_amount = 0 slow_amount = 0
self.paused = true self.paused = true
self.paused_t1 = Text2{group = self.ui, x = gw/2, y = gh/2 - 68, sx = 0.6, sy = 0.6, lines = {{text = '[fg]<- or a -> or d', font = fat_font, alignment = 'center'}}} self.paused_t1 = Text2{group = self.ui, x = gw/2, y = gh/2 - 68, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]<-, a or m1 ->, d or m2', font = fat_font, alignment = 'center'}}}
self.paused_t2 = Text2{group = self.ui, x = gw/2, y = gh/2 - 52, lines = {{text = '[fg]turn left turn right', font = pixul_font, alignment = 'center'}}} self.paused_t2 = Text2{group = self.ui, x = gw/2, y = gh/2 - 52, lines = {{text = '[bg10]turn left turn right', font = pixul_font, alignment = 'center'}}}
self.paused_t3 = Text2{group = self.ui, x = gw/2, y = gh/2 - 22, sx = 0.6, sy = 0.6, lines = {{text = '[fg]n - mute sfx', font = fat_font, alignment = 'center'}}}
self.paused_t4 = Text2{group = self.ui, x = gw/2, y = gh/2 + 0, sx = 0.6, sy = 0.6, lines = {{text = '[fg]m - mute music', font = fat_font, alignment = 'center'}}} self.resume_button = Button{group = self.ui, x = gw/2, y = gh - 160, force_update = true, button_text = 'resume game (esc)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
self.paused_t5 = Text2{group = self.ui, x = gw/2, y = gh/2 + 22, sx = 0.6, sy = 0.6, lines = {{text = '[fg]esc - resume game', font = fat_font, alignment = 'center'}}} trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function()
self.paused_t6 = Text2{group = self.ui, x = gw/2, y = gh/2 + 44, sx = 0.6, sy = 0.6, lines = {{text = '[fg]r - restart run', font = fat_font, alignment = 'center'}}} slow_amount = 1
self.paused = false
self.paused_t1.dead = true
self.paused_t2.dead = true
self.paused_t1 = nil
self.paused_t2 = nil
if self.resume_button then self.resume_button.dead = true; self.resume_button = nil end
if self.restart_button then self.restart_button.dead = true; self.restart_button = nil end
if self.sfx_button then self.sfx_button.dead = true; self.sfx_button = nil end
if self.music_button then self.music_button.dead = true; self.music_button = nil end
if self.video_button_1 then self.video_button_1.dead = true; self.video_button_1 = nil end
if self.video_button_2 then self.video_button_2.dead = true; self.video_button_2 = nil end
if self.video_button_3 then self.video_button_3.dead = true; self.video_button_3 = nil end
if self.quit_button then self.quit_button.dead = true; self.quit_button = nil end
end, 'pause')
end}
self.restart_button = Button{group = self.ui, x = gw/2, y = gh - 135, force_update = true, button_text = 'restart run (r)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
self.transitioning = true
ui_transition2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
TransitionEffect{group = main.transitions, x = gw/2, y = gh/2, color = fg[0], transition_action = function()
slow_amount = 1
gold = 2
passives = {}
main_song_instance:stop()
run_passive_pool_by_tiers = {
[1] = { 'wall_echo', 'wall_rider', 'centipede', 'temporal_chains', 'amplify', 'amplify_x', 'ballista', 'ballista_x', 'blunt_arrow', 'berserking', 'unwavering_stance', 'assassination', 'unleash', 'blessing',
'hex_master', 'force_push', 'spawning_pool'},
[2] = {'ouroboros_technique_r', 'ouroboros_technique_l', 'intimidation', 'vulnerability', 'resonance', 'point_blank', 'longshot', 'explosive_arrow', 'chronomancy', 'awakening', 'ultimatum', 'concentrated_fire',
'reinforce', 'payback', 'whispers_of_doom', 'heavy_impact', 'immolation', 'call_of_the_void'},
[3] = {'divine_machine_arrow', 'divine_punishment', 'flying_daggers', 'crucio', 'hive', 'void_rift'},
}
main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', 0, {}, passives)
end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)}
end}
self.sfx_button = Button{group = self.ui, x = gw/2, y = gh - 110, force_update = true, button_text = 'toggle sfx (n)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
b.spring:pull(0.2, 200, 10)
b.selected = true
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
if sfx.volume == 0.5 then
sfx.volume = 0
elseif sfx.volume == 0 then
sfx.volume = 0.5
end
end}
self.music_button = Button{group = self.ui, x = gw/2, y = gh - 85, force_update = true, button_text = 'toggle music (m)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
b.spring:pull(0.2, 200, 10)
b.selected = true
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
if music.volume == 0.5 then
music.volume = 0
elseif music.volume == 0 then
music.volume = 0.5
end
end}
self.video_button_1 = Button{group = self.ui, x = gw/2 - 86, y = gh - 60, force_update = true, button_text = 'window size-', fg_color = 'bg10', bg_color = 'bg', action = function()
sx, sy = sx - 1, sy - 1
love.window.setMode(480*sx, 270*sy)
state.sx, state.sy = sx, sy
state.fullscreen = false
end}
self.video_button_2 = Button{group = self.ui, x = gw/2, y = gh - 60, force_update = true, button_text = 'window size+', fg_color = 'bg10', bg_color = 'bg', action = function()
sx, sy = sx + 1, sy + 1
love.window.setMode(480*sx, 270*sy)
state.sx, state.sy = sx, sy
state.fullscreen = false
end}
self.video_button_3 = Button{group = self.ui, x = gw/2 + 79, y = gh - 60, force_update = true, button_text = 'fullscreen', fg_color = 'bg10', bg_color = 'bg', action = function()
local _, _, flags = love.window.getMode()
local window_width, window_height = love.window.getDesktopDimensions(flags.display)
sx, sy = window_width/480, window_height/270
ww, wh = window_width, window_height
love.window.setMode(window_width, window_height, {fullscreen = true})
state.fullscreen = true
end}
self.quit_button = Button{group = self.ui, x = gw/2, y = gh - 35, force_update = true, button_text = 'quit', fg_color = 'bg10', bg_color = 'bg', action = function()
system.save_state()
steam.shutdown()
love.event.quit()
end}
end, 'pause') end, 'pause')
else else
trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function() trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function()
@ -265,21 +393,21 @@ function Arena:update(dt)
self.paused = false self.paused = false
self.paused_t1.dead = true self.paused_t1.dead = true
self.paused_t2.dead = true self.paused_t2.dead = true
self.paused_t3.dead = true
self.paused_t4.dead = true
self.paused_t5.dead = true
self.paused_t6.dead = true
self.paused_t1 = nil self.paused_t1 = nil
self.paused_t2 = nil self.paused_t2 = nil
self.paused_t3 = nil if self.resume_button then self.resume_button.dead = true; self.resume_button = nil end
self.paused_t4 = nil if self.restart_button then self.restart_button.dead = true; self.restart_button = nil end
self.paused_t5 = nil if self.sfx_button then self.sfx_button.dead = true; self.sfx_button = nil end
self.paused_t6 = nil if self.music_button then self.music_button.dead = true; self.music_button = nil end
if self.video_button_1 then self.video_button_1.dead = true; self.video_button_1 = nil end
if self.video_button_2 then self.video_button_2.dead = true; self.video_button_2 = nil end
if self.video_button_3 then self.video_button_3.dead = true; self.video_button_3 = nil end
if self.quit_button then self.quit_button.dead = true; self.quit_button = nil end
end, 'pause') end, 'pause')
end end
end end
if self.paused or self.died and not self.transitioning then if self.paused or self.died or self.won and not self.transitioning then
if input.r.pressed then if input.r.pressed then
self.transitioning = true self.transitioning = true
ui_transition2:play{pitch = random:float(0.95, 1.05), volume = 0.5} ui_transition2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
@ -289,7 +417,7 @@ function Arena:update(dt)
slow_amount = 1 slow_amount = 1
gold = 2 gold = 2
passives = {} passives = {}
cascade_instance:stop() main_song_instance:stop()
run_passive_pool_by_tiers = { run_passive_pool_by_tiers = {
[1] = { 'wall_echo', 'wall_rider', 'centipede', 'temporal_chains', 'amplify', 'amplify_x', 'ballista', 'ballista_x', 'blunt_arrow', 'berserking', 'unwavering_stance', 'assassination', 'unleash', 'blessing', [1] = { 'wall_echo', 'wall_rider', 'centipede', 'temporal_chains', 'amplify', 'amplify_x', 'ballista', 'ballista_x', 'blunt_arrow', 'berserking', 'unwavering_stance', 'assassination', 'unleash', 'blessing',
'hex_master', 'force_push', 'spawning_pool'}, 'hex_master', 'force_push', 'spawning_pool'},
@ -301,189 +429,214 @@ function Arena:update(dt)
main:go_to('buy_screen', 0, {}, passives) main:go_to('buy_screen', 0, {}, passives)
end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)} end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)}
end end
if input.escape.pressed then
self.in_credits = false
if self.credits_button then self.credits_button:on_mouse_exit() end
for _, object in ipairs(self.credits.objects) do
object.dead = true
end
self.credits:update(0)
end
end end
self:update_game_object(dt*slow_amount) self:update_game_object(dt*slow_amount)
cascade_instance.pitch = math.clamp(slow_amount*self.main_slow_amount, 0.05, 1) main_song_instance.pitch = math.clamp(slow_amount*self.main_slow_amount, 0.05, 1)
star_group:update(dt*slow_amount)
self.floor:update(dt*slow_amount) self.floor:update(dt*slow_amount)
self.main:update(dt*slow_amount*self.main_slow_amount) self.main:update(dt*slow_amount*self.main_slow_amount)
self.post_main:update(dt*slow_amount) self.post_main:update(dt*slow_amount)
self.effects:update(dt*slow_amount) self.effects:update(dt*slow_amount)
self.ui:update(dt*slow_amount) self.ui:update(dt*slow_amount)
self.credits:update(dt)
end
if self.can_quit and #self.main:get_objects_by_classes(self.enemies) <= 0 and not self.transitioning then
self.can_quit = false
self.quitting = true
if self.level == 25 then
if not self.win_text and not self.win_text2 then
self.won = true
trigger:tween(1, _G, {slow_amount = 0}, math.linear, function() slow_amount = 0 end)
trigger:tween(4, camera, {x = gw/2, y = gh/2, r = 0}, math.linear, function() camera.x, camera.y, camera.r = gw/2, gh/2, 0 end)
self.win_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 66, force_update = true, lines = {{text = '[wavy_mid, cbyc2]congratulations!', font = fat_font, alignment = 'center'}}}
trigger:after(2.5, function()
if new_game_plus == 10 then
else
self.win_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 20, force_update = true, lines = {
{text = "[fg]you've beaten the game!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]i made this game in 3 months as a dev challenge", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]and i'm happy with how it turned out!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]if you liked it too and want to play more games like this:", font = pixul_font, alignment = 'center', height_multiplier = 4},
{text = "[fg]i will release more games this year, so stay tuned!", font = pixul_font, alignment = 'center', height_multiplier = 1.4},
{text = "[wavy_mid, yellow]thanks for playing!", font = pixul_font, alignment = 'center'},
}}
SteamFollowButton{group = self.ui, x = gw/2, y = gh/2 + 34, force_update = true}
RestartButton{group = self.ui, x = gw - 40, y = gh - 20, force_update = true}
trigger:after(12, function()
self.try_ng_text = Text2{group = self.ui, x = gw - 140, y = gh - 20, force_update = true, lines = {
{text = '[cbyc3, wavy_mid]try a harder difficulty:', font = pixul_font},
}}
end)
end
end)
if new_game_plus == 1 then
state.achievement_new_game_1 = true
system.save_state()
steam.userStats.setAchievement('NEW_GAME_1')
steam.userStats.storeStats()
end
function Arena:quit()
self.quitting = true
if self.level == 25 then
if not self.win_text and not self.win_text2 then
self.won = true
trigger:tween(1, _G, {slow_amount = 0}, math.linear, function() slow_amount = 0 end)
trigger:tween(4, camera, {x = gw/2, y = gh/2, r = 0}, math.linear, function() camera.x, camera.y, camera.r = gw/2, gh/2, 0 end)
self.win_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 66, force_update = true, lines = {{text = '[wavy_mid, cbyc2]congratulations!', font = fat_font, alignment = 'center'}}}
trigger:after(2.5, function()
if new_game_plus == 5 then if new_game_plus == 5 then
state.achievement_new_game_5 = true self.win_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 30, force_update = true, lines = {
system.save_state() {text = "[fg]now you've really beaten the game!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
steam.userStats.setAchievement('NEW_GAME_5') {text = "[fg]thanks a lot for playing it and completing it entirely!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
steam.userStats.storeStats() {text = "[fg]this game was inspired by:", font = pixul_font, alignment = 'center', height_multiplier = 4},
{text = "[fg]so check those games out, they're fun!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]and to get more games like this in the future:", font = pixul_font, alignment = 'center', height_multiplier = 4},
{text = "[wavy_mid, yellow]thanks for playing!", font = pixul_font, alignment = 'center'},
}}
SteamFollowButton{group = self.ui, x = gw/2, y = gh/2 + 78, force_update = true}
Button{group = self.ui, x = gw - 40, y = gh - 44, force_update = true, button_text = 'credits', fg_color = 'bg10', bg_color = 'bg', action = function() self:create_credits() end}
Button{group = self.ui, x = gw - 32, y = gh - 20, force_update = true, button_text = 'quit', fg_color = 'bg10', bg_color = 'bg', action = function() love.event.quit() end}
local open_url = function(b, url)
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
b.spring:pull(0.2, 200, 10)
b.selected = true
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
system.open_url(url)
end
Button{group = self.ui, x = gw/2 - 50, y = gh/2 + 12, force_update = true, button_text = 'nimble quest', fg_color = 'bluem5', bg_color = 'blue', action = function(b) open_url(b, 'https://store.steampowered.com/app/259780/Nimble_Quest/') end}
Button{group = self.ui, x = gw/2 + 50, y = gh/2 + 12, force_update = true, button_text = 'dota underlords', fg_color = 'bluem5', bg_color = 'blue', action = function(b) open_url(b, 'https://store.steampowered.com/app/1046930/Dota_Underlords/') end}
else
self.win_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 20, force_update = true, lines = {
{text = "[fg]you've beaten the game!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]i made this game in 3 months as a dev challenge", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]and i'm happy with how it turned out!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]if you liked it too and want to play more games like this:", font = pixul_font, alignment = 'center', height_multiplier = 4},
{text = "[fg]i will release more games this year, so stay tuned!", font = pixul_font, alignment = 'center', height_multiplier = 1.4},
{text = "[wavy_mid, yellow]thanks for playing!", font = pixul_font, alignment = 'center'},
}}
SteamFollowButton{group = self.ui, x = gw/2, y = gh/2 + 34, force_update = true}
RestartButton{group = self.ui, x = gw - 40, y = gh - 20, force_update = true}
trigger:after(12, function()
self.try_ng_text = Text2{group = self.ui, x = gw - 220, y = gh - 20, force_update = true, lines = {
{text = '[cbyc3]try a harder difficulty with +1 max snake size:', font = pixul_font},
}}
end)
self.credits_button = Button{group = self.ui, x = gw - 40, y = gh - 44, force_update = true, button_text = 'credits', fg_color = 'bg10', bg_color = 'bg', action = function()
self:create_credits()
end}
end end
end)
if new_game_plus == 10 then if new_game_plus == 1 then
state.achievement_game_complete = true state.achievement_new_game_1 = true
system.save_state() system.save_state()
steam.userStats.setAchievement('GAME_COMPLETE') steam.userStats.setAchievement('NEW_GAME_1')
steam.userStats.storeStats() steam.userStats.storeStats()
end
if self.ranger_level >= 2 then
state.achievement_rangers_win = true
system.save_state()
steam.userStats.setAchievement('RANGERS_WIN')
steam.userStats.storeStats()
end
if self.warrior_level >= 2 then
state.achievement_warriors_win = true
system.save_state()
steam.userStats.setAchievement('WARRIORS_WIN')
steam.userStats.storeStats()
end
if self.mage_level >= 2 then
state.achievement_mages_win = true
system.save_state()
steam.userStats.setAchievement('MAGES_WIN')
steam.userStats.storeStats()
end
if self.rogue_level >= 2 then
state.achievement_rogues_win = true
system.save_state()
steam.userStats.setAchievement('ROGUES_WIN')
steam.userStats.storeStats()
end
if self.healer_level >= 2 then
state.achievement_healers_win = true
system.save_state()
steam.userStats.setAchievement('HEALERS_WIN')
steam.userStats.storeStats()
end
if self.enchanter_level >= 2 then
state.achievement_enchanters_win = true
system.save_state()
steam.userStats.setAchievement('ENCHANTERS_WIN')
steam.userStats.storeStats()
end
if self.nuker_level >= 2 then
state.achievement_nukers_win = true
system.save_state()
steam.userStats.setAchievement('NUKERS_WIN')
steam.userStats.storeStats()
end
if self.conjurer_level >= 2 then
state.achievement_conjurers_win = true
system.save_state()
steam.userStats.setAchievement('CONJURERS_WIN')
steam.userStats.storeStats()
end
if self.psyker_level >= 2 then
state.achievement_psykers_win = true
system.save_state()
steam.userStats.setAchievement('PSYKERS_WIN')
steam.userStats.storeStats()
end
if self.curser_level >= 2 then
state.achievement_cursers_win = true
system.save_state()
steam.userStats.setAchievement('CURSERS_WIN')
steam.userStats.storeStats()
end
if self.forcer_level >= 2 then
state.achievement_forcers_win = true
system.save_state()
steam.userStats.setAchievement('FORCERS_WIN')
steam.userStats.storeStats()
end
if self.swarmer_level >= 2 then
state.achievement_swarmers_win = true
system.save_state()
steam.userStats.setAchievement('SWARMERS_WIN')
steam.userStats.storeStats()
end
if self.voider_level >= 2 then
state.achievement_voiders_win = true
system.save_state()
steam.userStats.setAchievement('VOIDERS_WIN')
steam.userStats.storeStats()
end
end end
else if new_game_plus == 5 then
if not self.arena_clear_text then self.arena_clear_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 48, lines = {{text = '[wavy_mid, cbyc]arena clear!', font = fat_font, alignment = 'center'}}} end state.achievement_new_game_5 = true
self.t:after(3, function() system.save_state()
if self.level % 3 == 0 then steam.userStats.setAchievement('GAME_COMPLETE')
self.arena_clear_text.dead = true steam.userStats.storeStats()
trigger:tween(1, _G, {slow_amount = 0}, math.linear, function() slow_amount = 0 end) end
trigger:tween(4, camera, {x = gw/2, y = gh/2, r = 0}, math.linear, function() camera.x, camera.y, camera.r = gw/2, gh/2, 0 end)
local card_w, card_h = 100, 100 if self.ranger_level >= 2 then
local w = 3*card_w + 2*20 state.achievement_rangers_win = true
self.choosing_passives = true system.save_state()
self.cards = {} steam.userStats.setAchievement('RANGERS_WIN')
local tier_1 = random:weighted_pick(unpack(level_to_passive_tier_weights[level or self.level])) steam.userStats.storeStats()
local tier_2 = random:weighted_pick(unpack(level_to_passive_tier_weights[level or self.level])) end
local tier_3 = random:weighted_pick(unpack(level_to_passive_tier_weights[level or self.level]))
local passive_pool_copy = table.copy(run_passive_pool_by_tiers) if self.warrior_level >= 2 then
local passive_1 = random:table_remove(passive_pool_copy[tier_1]) state.achievement_warriors_win = true
local passive_2 = random:table_remove(passive_pool_copy[tier_2]) system.save_state()
local passive_3 = random:table_remove(passive_pool_copy[tier_3]) steam.userStats.setAchievement('WARRIORS_WIN')
table.insert(self.cards, PassiveCard{group = main.current.ui, x = gw/2 - w/2 + 0*(card_w + 20) + card_w/2, y = gh/2 - 6, w = card_w, h = card_h, arena = self, passive = passive_1, force_update = true}) steam.userStats.storeStats()
table.insert(self.cards, PassiveCard{group = main.current.ui, x = gw/2 - w/2 + 1*(card_w + 20) + card_w/2, y = gh/2 - 6, w = card_w, h = card_h, arena = self, passive = passive_2, force_update = true}) end
table.insert(self.cards, PassiveCard{group = main.current.ui, x = gw/2 - w/2 + 2*(card_w + 20) + card_w/2, y = gh/2 - 6, w = card_w, h = card_h, arena = self, passive = passive_3, force_update = true})
self.passive_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 65, lines = {{text = '[fg, wavy]choose one', font = fat_font, alignment = 'center'}}} if self.mage_level >= 2 then
else state.achievement_mages_win = true
self:transition() system.save_state()
end steam.userStats.setAchievement('MAGES_WIN')
end, 'transition') steam.userStats.storeStats()
end
if self.rogue_level >= 2 then
state.achievement_rogues_win = true
system.save_state()
steam.userStats.setAchievement('ROGUES_WIN')
steam.userStats.storeStats()
end
if self.healer_level >= 2 then
state.achievement_healers_win = true
system.save_state()
steam.userStats.setAchievement('HEALERS_WIN')
steam.userStats.storeStats()
end
if self.enchanter_level >= 2 then
state.achievement_enchanters_win = true
system.save_state()
steam.userStats.setAchievement('ENCHANTERS_WIN')
steam.userStats.storeStats()
end
if self.nuker_level >= 2 then
state.achievement_nukers_win = true
system.save_state()
steam.userStats.setAchievement('NUKERS_WIN')
steam.userStats.storeStats()
end
if self.conjurer_level >= 2 then
state.achievement_conjurers_win = true
system.save_state()
steam.userStats.setAchievement('CONJURERS_WIN')
steam.userStats.storeStats()
end
if self.psyker_level >= 2 then
state.achievement_psykers_win = true
system.save_state()
steam.userStats.setAchievement('PSYKERS_WIN')
steam.userStats.storeStats()
end
if self.curser_level >= 2 then
state.achievement_cursers_win = true
system.save_state()
steam.userStats.setAchievement('CURSERS_WIN')
steam.userStats.storeStats()
end
if self.forcer_level >= 2 then
state.achievement_forcers_win = true
system.save_state()
steam.userStats.setAchievement('FORCERS_WIN')
steam.userStats.storeStats()
end
if self.swarmer_level >= 2 then
state.achievement_swarmers_win = true
system.save_state()
steam.userStats.setAchievement('SWARMERS_WIN')
steam.userStats.storeStats()
end
if self.voider_level >= 2 then
state.achievement_voiders_win = true
system.save_state()
steam.userStats.setAchievement('VOIDERS_WIN')
steam.userStats.storeStats()
end
end end
else
if not self.arena_clear_text then self.arena_clear_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 48, lines = {{text = '[wavy_mid, cbyc]arena clear!', font = fat_font, alignment = 'center'}}} end
self.t:after(3, function()
if self.level % 3 == 0 then
self.arena_clear_text.dead = true
trigger:tween(1, _G, {slow_amount = 0}, math.linear, function() slow_amount = 0 end)
trigger:tween(4, camera, {x = gw/2, y = gh/2, r = 0}, math.linear, function() camera.x, camera.y, camera.r = gw/2, gh/2, 0 end)
local card_w, card_h = 100, 100
local w = 3*card_w + 2*20
self.choosing_passives = true
self.cards = {}
local tier_1 = random:weighted_pick(unpack(level_to_passive_tier_weights[level or self.level]))
local tier_2 = random:weighted_pick(unpack(level_to_passive_tier_weights[level or self.level]))
local tier_3 = random:weighted_pick(unpack(level_to_passive_tier_weights[level or self.level]))
local passive_pool_copy = table.copy(run_passive_pool_by_tiers)
local passive_1 = random:table_remove(passive_pool_copy[tier_1])
local passive_2 = random:table_remove(passive_pool_copy[tier_2])
local passive_3 = random:table_remove(passive_pool_copy[tier_3])
table.insert(self.cards, PassiveCard{group = main.current.ui, x = gw/2 - w/2 + 0*(card_w + 20) + card_w/2, y = gh/2 - 6, w = card_w, h = card_h, arena = self, passive = passive_1, force_update = true})
table.insert(self.cards, PassiveCard{group = main.current.ui, x = gw/2 - w/2 + 1*(card_w + 20) + card_w/2, y = gh/2 - 6, w = card_w, h = card_h, arena = self, passive = passive_2, force_update = true})
table.insert(self.cards, PassiveCard{group = main.current.ui, x = gw/2 - w/2 + 2*(card_w + 20) + card_w/2, y = gh/2 - 6, w = card_w, h = card_h, arena = self, passive = passive_3, force_update = true})
self.passive_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 65, lines = {{text = '[fg, wavy]choose one', font = fat_font, alignment = 'center'}}}
else
self:transition()
end
end, 'transition')
end end
end end
@ -533,10 +686,12 @@ function Arena:draw()
end end
camera:detach() camera:detach()
if self.level == 18 and self.trailer then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end if self.level == 20 and self.trailer then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
if self.choosing_passives or self.won or self.paused or self.died then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end if self.choosing_passives or self.won or self.paused or self.died then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
self.ui:draw() self.ui:draw()
if self.in_credits then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent_2) end
self.credits:draw()
end end
@ -548,15 +703,93 @@ function Arena:die()
{text = '[wavy_mid, cbyc]you died...', font = fat_font, alignment = 'center', height_multiplier = 1.25}, {text = '[wavy_mid, cbyc]you died...', font = fat_font, alignment = 'center', height_multiplier = 1.25},
}} }}
self.t:after(2, function() self.t:after(2, function()
self.death_info_text = Text2{group = self.ui, x = gw/2, y = gh/2 + 24, sx = 0.7, sy = 0.7, lines = { self.death_info_text = Text2{group = self.ui, x = gw/2, y = gh/2, sx = 0.7, sy = 0.7, lines = {
{text = '[wavy_mid, fg]level reached: [wavy_mid, yellow]' .. self.level, font = fat_font, alignment = 'center'}, {text = '[wavy_mid, fg]level reached: [wavy_mid, yellow]' .. self.level, font = fat_font, alignment = 'center'},
{text = '[wavy_mid, fg]r - start new run', font = fat_font, alignment = 'center'},
}} }}
self.restart_button = Button{group = self.ui, x = gw/2, y = gh/2 + 24, force_update = true, button_text = 'restart run (r)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
self.transitioning = true
ui_transition2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
TransitionEffect{group = main.transitions, x = gw/2, y = gh/2, color = fg[0], transition_action = function()
slow_amount = 1
gold = 2
passives = {}
main_song_instance:stop()
run_passive_pool_by_tiers = {
[1] = { 'wall_echo', 'wall_rider', 'centipede', 'temporal_chains', 'amplify', 'amplify_x', 'ballista', 'ballista_x', 'blunt_arrow', 'berserking', 'unwavering_stance', 'assassination', 'unleash', 'blessing',
'hex_master', 'force_push', 'spawning_pool'},
[2] = {'ouroboros_technique_r', 'ouroboros_technique_l', 'intimidation', 'vulnerability', 'resonance', 'point_blank', 'longshot', 'explosive_arrow', 'chronomancy', 'awakening', 'ultimatum', 'concentrated_fire',
'reinforce', 'payback', 'whispers_of_doom', 'heavy_impact', 'immolation', 'call_of_the_void'},
[3] = {'divine_machine_arrow', 'divine_punishment', 'flying_daggers', 'crucio', 'hive', 'void_rift'},
}
main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', 0, {}, passives)
end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)}
end}
end) end)
end end
end end
function Arena:create_credits()
local open_url = function(b, url)
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
b.spring:pull(0.2, 200, 10)
b.selected = true
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
system.open_url(url)
end
self.in_credits = true
Text2{group = self.credits, x = 60, y = 20, lines = {{text = '[bg10]main dev: ', font = pixul_font}}}
Button{group = self.credits, x = 117, y = 20, button_text = 'a327ex', fg_color = 'bg10', bg_color = 'bg', credits_button = true, action = function(b) open_url(b, 'https://store.steampowered.com/dev/a327ex/') end}
Text2{group = self.credits, x = 60, y = 50, lines = {{text = '[blue]code: ', font = pixul_font}}}
Button{group = self.credits, x = 102, y = 50, button_text = 'love2d', fg_color = 'bluem5', bg_color = 'blue', credits_button = true, action = function(b) open_url(b, 'https://love2d.org') end}
Button{group = self.credits, x = 159, y = 50, button_text = 'bakpakin', fg_color = 'bluem5', bg_color = 'blue', credits_button = true, action = function(b) open_url(b, 'https://github.com/bakpakin/binser') end}
Button{group = self.credits, x = 226, y = 50, button_text = 'davisdude', fg_color = 'bluem5', bg_color = 'blue', credits_button = true, action = function(b) open_url(b, 'https://github.com/davisdude/mlib') end}
Button{group = self.credits, x = 295, y = 50, button_text = 'tesselode', fg_color = 'bluem5', bg_color = 'blue', credits_button = true, action = function(b) open_url(b, 'https://github.com/tesselode/ripple') end}
Text2{group = self.credits, x = 60, y = 80, lines = {{text = '[green]music: ', font = pixul_font}}}
Button{group = self.credits, x = 100, y = 80, button_text = 'kubbi', fg_color = 'greenm5', bg_color = 'green', credits_button = true, action = function(b) open_url(b, 'https://kubbimusic.com/album/ember') end}
Text2{group = self.credits, x = 60, y = 110, lines = {{text = '[yellow]sounds: ', font = pixul_font}}}
Button{group = self.credits, x = 135, y = 110, button_text = 'sidearm studios', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://sidearm-studios.itch.io/ultimate-sound-fx-bundle') end}
Button{group = self.credits, x = 217, y = 110, button_text = 'justinbw', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://freesound.org/people/JustinBW/sounds/80921/') end}
Button{group = self.credits, x = 279, y = 110, button_text = 'jcallison', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://freesound.org/people/jcallison/sounds/258269/') end}
Button{group = self.credits, x = 342, y = 110, button_text = 'hybrid_v', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://freesound.org/people/Hybrid_V/sounds/321215/') end}
Button{group = self.credits, x = 427, y = 110, button_text = 'womb_affliction', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://freesound.org/people/womb_affliction/sounds/376532/') end}
Button{group = self.credits, x = 106, y = 130, button_text = 'bajko', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://freesound.org/people/bajko/sounds/399656/') end}
Button{group = self.credits, x = 157, y = 130, button_text = 'benzix2', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://freesound.org/people/benzix2/sounds/467951/') end}
Button{group = self.credits, x = 204, y = 130, button_text = 'lord', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b)
open_url(b, 'https://store.steampowered.com/developer/T_TGames') end}
Text2{group = self.credits, x = 70, y = 160, lines = {{text = '[red]playtesters: ', font = pixul_font}}}
Button{group = self.credits, x = 130, y = 160, button_text = 'Jofer', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/JofersGames') end}
Button{group = self.credits, x = 172, y = 160, button_text = 'ekun', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/ekunenuke') end}
Button{group = self.credits, x = 224, y = 160, button_text = 'cvisy_GN', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/cvisy_GN') end}
Button{group = self.credits, x = 292, y = 160, button_text = 'Blue Fairy', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/blue9fairy') end}
Button{group = self.credits, x = 362, y = 160, button_text = 'Phil Blank', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/PhilBlankGames') end}
Button{group = self.credits, x = 440, y = 160, button_text = 'DefineDoddy', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/DefineDoddy') end}
Button{group = self.credits, x = 140, y = 180, button_text = 'Ge0force', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/Ge0forceBE') end}
Button{group = self.credits, x = 193, y = 180, button_text = 'Vlad', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/thecryru') end}
Button{group = self.credits, x = 223, y = 180, button_text = 'F', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b)
open_url(b, 'https://twitter.com/notyps') end}
end
function Arena:transition() function Arena:transition()
self.transitioning = true self.transitioning = true
local gold_gained = random:int(level_to_gold_gained[self.level][1], level_to_gold_gained[self.level][2]) local gold_gained = random:int(level_to_gold_gained[self.level][1], level_to_gold_gained[self.level][2])
@ -689,6 +922,9 @@ end
function Arena:spawn_n_enemies(p, j, n) function Arena:spawn_n_enemies(p, j, n)
if self.died then return end if self.died then return end
if self.arena_clear_text then return end
if self.quitting then return end
j = j or 1 j = j or 1
n = n or 4 n = n or 4
self.last_spawn_enemy_time = love.timer.getTime() self.last_spawn_enemy_time = love.timer.getTime()

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -11,6 +11,7 @@ function BuyScreen:on_exit()
self.main:destroy() self.main:destroy()
self.effects:destroy() self.effects:destroy()
self.ui:destroy() self.ui:destroy()
self.t:destroy()
self.main = nil self.main = nil
self.effects = nil self.effects = nil
self.ui = nil self.ui = nil
@ -18,10 +19,18 @@ function BuyScreen:on_exit()
self.party_text = nil self.party_text = nil
self.sets_text = nil self.sets_text = nil
self.items_text = nil self.items_text = nil
self.ng_text = nil
self.characters = nil self.characters = nil
self.sets = nil self.sets = nil
self.cards = nil self.cards = nil
self.info_text = nil self.info_text = nil
self.units = nil
self.passives = nil
self.player = nil
self.t = nil
self.springs = nil
self.flashes = nil
self.hfx = nil
end end
@ -32,7 +41,7 @@ function BuyScreen:on_enter(from, level, units, passives)
camera.x, camera.y = gw/2, gh/2 camera.x, camera.y = gw/2, gh/2
if self.level == 0 then if self.level == 0 then
cascade_instance = cascade:play{volume = 0.5, loop = true} main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
self.level = 1 self.level = 1
self.first_screen = true self.first_screen = true
end end
@ -43,6 +52,7 @@ function BuyScreen:on_enter(from, level, units, passives)
self.main = Group() self.main = Group()
self.effects = Group() self.effects = Group()
self.ui = Group() self.ui = Group()
self.tutorial = Group()
self:set_cards() self:set_cards()
self:set_party_and_sets() self:set_party_and_sets()
@ -55,24 +65,85 @@ function BuyScreen:on_enter(from, level, units, passives)
self.ng_text = Text({{text = '[fg]NG+' .. new_game_plus, font = pixul_font, alignment = 'center'}}, global_text_tags) self.ng_text = Text({{text = '[fg]NG+' .. new_game_plus, font = pixul_font, alignment = 'center'}}, global_text_tags)
if not self.first_screen then RerollButton{group = self.main, x = 150, y = 18, parent = self} end if not self.first_screen then RerollButton{group = self.main, x = 150, y = 18, parent = self} end
GoButton{group = self.main, x = gw - 100, y = gh - 20, parent = self} GoButton{group = self.main, x = gw - 90, y = gh - 20, parent = self}
-- WishlistButton{group = self.main, x = gw - 147, y = gh - 20, parent = self} self.tutorial_button = Button{group = self.main, x = gw/2 + 142, y = 18, button_text = '?', fg_color = 'yellowm5', bg_color = 'yellow', action = function()
self.in_tutorial = true
self.title_text = Text2{group = self.tutorial, x = gw/2, y = 35, lines = {{text = '[fg]WELCOME TO SNKRX!', font = fat_font, alignment = 'center'}}}
self.tutorial_text = Text2{group = self.tutorial, x = 228, y = 160, lines = {
{text = '[fg]You control a snake of multiple heroes that auto-attack nearby enemies.', font = pixul_font, height_multiplier = 1.2},
{text = '[fg]You can steer the snake left or right by pressing [yellow]A/D[fg] or [yellow]left/right arrows[fg].', font = pixul_font, height_multiplier = 2.2},
{text = '[fg]Combine the same heroes to level them up:', font = pixul_font, height_multiplier = 1.2},
{text = '[fg]At [yellow]Lv.3[fg] heroes unlock special effects.', font = pixul_font, height_multiplier = 2.2},
{text = '[fg]Hire heroes of the same classes to unlock class passives:', font = pixul_font, height_multiplier = 1.2},
{text = '[fg]Each hero can have between [yellow]1 to 3[fg] classes.', font = pixul_font, height_multiplier = 2.2},
{text = '[fg]You gain [yellow]1 interest per 5 gold[fg], up to a maximum of 5.', font = pixul_font, height_multiplier = 1.2},
{text = "[fg]This means that saving above [yellow]25 gold[fg] doesn't yield more interest.", font = pixul_font, height_multiplier = 2.2},
{text = "[yellow, wavy_mid]Good luck!", font = pixul_font, height_multiplier = 2.2, alignment = 'center'},
}}
self.tutorial_cards = {}
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 34, y = gh/2 - 30, character = 'swordsman', level = 1})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 54, y = gh/2 - 30, character = 'swordsman', level = 1})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 74, y = gh/2 - 30, character = 'swordsman', level = 1})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 34, y = gh/2 - 10, character = 'swordsman', level = 2})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 54, y = gh/2 - 10, character = 'swordsman', level = 2})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 74, y = gh/2 - 10, character = 'swordsman', level = 2})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 114, y = gh/2 - 30, character = 'swordsman', level = 2})
table.insert(self.tutorial_cards, TutorialCharacterPart{group = self.tutorial, x = gw/2 + 114, y = gh/2 - 10, character = 'swordsman', level = 3})
table.insert(self.tutorial_cards, TutorialClassIcon{group = self.tutorial, x = gw/2 + 114, y = gh/2 + 18, class = 'warrior', units = {}})
table.insert(self.tutorial_cards, TutorialClassIcon{group = self.tutorial, x = gw/2 + 134, y = gh/2 + 18, class = 'warrior', units = {{character = 'swordsman'}, {character = 'barbarian'}, {character = 'juggernaut'}}})
table.insert(self.tutorial_cards, TutorialClassIcon{group = self.tutorial, x = gw/2 + 154, y = gh/2 + 18, class = 'warrior', units = {{character = 'swordsman'}, {character = 'barbarian'}, {character = 'juggernaut'},
{character = 'vagrant'}, {character = 'outlaw'}, {character = 'blade'}}
})
self.close_button = Button{group = self.tutorial, x = gw - 20, y = 20, button_text = 'x', bg_color = 'bg', fg_color = 'bg10', action = function()
trigger:after(0.01, function()
self:quit_tutorial()
end)
end}
end}
trigger:tween(1, main_song_instance, {volume = 0.2}, math.linear)
end end
function BuyScreen:update(dt) function BuyScreen:update(dt)
if main_song_instance:isStopped() then
main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.2}
end
self:update_game_object(dt*slow_amount) self:update_game_object(dt*slow_amount)
cascade_instance.pitch = 1
self.main:update(dt*slow_amount) if not self.in_tutorial then
self.effects:update(dt*slow_amount) self.main:update(dt*slow_amount)
self.ui:update(dt*slow_amount) self.effects:update(dt*slow_amount)
self.ui:update(dt*slow_amount)
if self.shop_text then self.shop_text:update(dt) end
if self.sets_text then self.sets_text:update(dt) end
if self.party_text then self.party_text:update(dt) end
if self.items_text then self.items_text:update(dt) end
if self.ng_text then self.ng_text:update(dt) end
else
self.tutorial:update(dt*slow_amount)
end
if self.shop_text then self.shop_text:update(dt) end if self.in_tutorial and input.escape.pressed then
if self.sets_text then self.sets_text:update(dt) end self:quit_tutorial()
if self.party_text then self.party_text:update(dt) end end
if self.items_text then self.items_text:update(dt) end end
if self.ng_text then self.ng_text:update(dt) end
function BuyScreen:quit_tutorial()
self.in_tutorial = false
self.tutorial_text.dead = true
self.tutorial_text = nil
self.title_text.dead = true
self.title_text = nil
for _, t in ipairs(self.tutorial_cards) do t.dead = true end
self.close_button.dead = true
self.close_button = nil
self.tutorial_cards = {}
self.tutorial:update(0)
end end
@ -88,40 +159,58 @@ function BuyScreen:draw()
if new_game_plus > 0 then if new_game_plus > 0 then
self.ng_text:draw(240, 20) self.ng_text:draw(240, 20)
end end
if self.in_tutorial then
graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent_2)
arrow:draw(gw/2 + 93, gh/2 - 30, 0, 0.4, 0.35)
arrow:draw(gw/2 + 93, gh/2 - 10, 0, 0.4, 0.35)
end
self.tutorial:draw()
end end
function BuyScreen:buy(character, i) function BuyScreen:buy(character, i)
local bought local bought
if table.any(self.units, function(v) return v.character == character end) and gold >= character_tiers[character] then if table.any(self.units, function(v) return v.character == character end) and gold >= character_tiers[character] then
gold = gold - character_tiers[character] if table.any(self.units, function(v) return v.character == character and v.level == 3 end) then
self.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}} if not self.info_text then
for _, unit in ipairs(self.units) do self.info_text = InfoText{group = main.current.ui}
if unit.character == character then self.info_text:activate({
if unit.level == 1 then {text = "[fg]this unit has already reached max level", font = pixul_font, alignment = 'center'},
unit.reserve[1] = unit.reserve[1] + 1 }, nil, nil, nil, nil, 16, 4, nil, 2)
if unit.reserve[1] > 1 then self.info_text.x, self.info_text.y = gw - 140, gh - 20
unit.reserve[1] = 0 end
unit.level = 2 self.t:after(2, function() self.info_text:deactivate(); self.info_text.dead = true; self.info_text = nil end, 'info_text')
unit.spawn_effect = true else
end gold = gold - character_tiers[character]
elseif unit.level == 2 then self.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}}
unit.reserve[1] = unit.reserve[1] + 1 for _, unit in ipairs(self.units) do
if unit.reserve[1] > 2 then if unit.character == character then
if unit.reserve[2] == 1 then if unit.level == 1 then
unit.reserve[2] = 0 unit.reserve[1] = unit.reserve[1] + 1
if unit.reserve[1] > 1 then
unit.reserve[1] = 0 unit.reserve[1] = 0
unit.level = 3 unit.level = 2
unit.spawn_effect = true unit.spawn_effect = true
else end
unit.reserve[2] = unit.reserve[2] + 1 elseif unit.level == 2 then
unit.reserve[1] = 0 unit.reserve[1] = unit.reserve[1] + 1
if unit.reserve[1] > 2 then
if unit.reserve[2] == 1 then
unit.reserve[2] = 0
unit.reserve[1] = 0
unit.level = 3
unit.spawn_effect = true
else
unit.reserve[2] = unit.reserve[2] + 1
unit.reserve[1] = 0
end
end end
end end
end end
end end
bought = true
end end
bought = true
else else
if #self.units >= max_units then if #self.units >= max_units then
if not self.info_text then if not self.info_text then
@ -217,6 +306,7 @@ end
function SteamFollowButton:update(dt) function SteamFollowButton:update(dt)
self:update_game_object(dt) self:update_game_object(dt)
if main.current.in_credits then return end
if self.selected and input.m1.pressed then if self.selected and input.m1.pressed then
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5} ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
@ -237,6 +327,7 @@ end
function SteamFollowButton:on_mouse_enter() function SteamFollowButton:on_mouse_enter()
if main.current.in_credits then return end
love.mouse.setCursor(love.mouse.getSystemCursor'hand') love.mouse.setCursor(love.mouse.getSystemCursor'hand')
ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5} ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5}
pop2:play{pitch = random:float(0.95, 1.05), volume = 0.5} pop2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
@ -247,6 +338,7 @@ end
function SteamFollowButton:on_mouse_exit() function SteamFollowButton:on_mouse_exit()
if main.current.in_credits then return end
love.mouse.setCursor() love.mouse.setCursor()
self.text:set_text{{text = '[greenm5]follow me on steam!', font = pixul_font, alignment = 'center'}} self.text:set_text{{text = '[greenm5]follow me on steam!', font = pixul_font, alignment = 'center'}}
self.selected = false self.selected = false
@ -327,6 +419,7 @@ end
function RestartButton:update(dt) function RestartButton:update(dt)
if main.current.in_credits then return end
self:update_game_object(dt) self:update_game_object(dt)
if self.selected and input.m1.pressed then if self.selected and input.m1.pressed then
@ -338,7 +431,7 @@ function RestartButton:update(dt)
slow_amount = 1 slow_amount = 1
gold = 2 gold = 2
passives = {} passives = {}
cascade_instance:stop() main_song_instance:stop()
new_game_plus = new_game_plus + 1 new_game_plus = new_game_plus + 1
state.new_game_plus = new_game_plus state.new_game_plus = new_game_plus
system.save_state() system.save_state()
@ -358,6 +451,7 @@ end
function RestartButton:on_mouse_enter() function RestartButton:on_mouse_enter()
if main.current.in_credits then return end
ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5} ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5}
pop2:play{pitch = random:float(0.95, 1.05), volume = 0.5} pop2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
self.selected = true self.selected = true
@ -367,6 +461,7 @@ end
function RestartButton:on_mouse_exit() function RestartButton:on_mouse_exit()
if main.current.in_credits then return end
self.text:set_text{{text = '[bg10]NG+' .. tostring(new_game_plus+1), font = pixul_font, alignment = 'center'}} self.text:set_text{{text = '[bg10]NG+' .. tostring(new_game_plus+1), font = pixul_font, alignment = 'center'}}
self.selected = false self.selected = false
end end
@ -374,6 +469,53 @@ end
Button = Object:extend()
Button:implement(GameObject)
function Button:init(args)
self:init_game_object(args)
self.shape = Rectangle(self.x, self.y, pixul_font:get_text_width(self.button_text) + 8, pixul_font.h + 4)
self.interact_with_mouse = true
self.text = Text({{text = '[' .. self.fg_color .. ']' .. self.button_text, font = pixul_font, alignment = 'center'}}, global_text_tags)
end
function Button:update(dt)
self:update_game_object(dt)
if main.current.in_credits and not self.credits_button then return end
if self.selected and input.m1.pressed then
self:action()
end
end
function Button:draw()
graphics.push(self.x, self.y, 0, self.spring.x, self.spring.y)
graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 4, 4, self.selected and fg[0] or _G[self.bg_color][0])
self.text:draw(self.x, self.y + 1, 0, 1, 1)
graphics.pop()
end
function Button:on_mouse_enter()
if main.current.in_credits and not self.credits_button then return end
ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5}
pop2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
self.selected = true
self.text:set_text{{text = '[fgm5]' .. self.button_text, font = pixul_font, alignment = 'center'}}
self.spring:pull(0.2, 200, 10)
end
function Button:on_mouse_exit()
if main.current.in_credits and not self.credits_button then return end
self.text:set_text{{text = '[' .. self.fg_color .. ']' .. self.button_text, font = pixul_font, alignment = 'center'}}
self.selected = false
end
GoButton = Object:extend() GoButton = Object:extend()
GoButton:implement(GameObject) GoButton:implement(GameObject)
function GoButton:init(args) function GoButton:init(args)
@ -409,7 +551,7 @@ function GoButton:update(dt)
TransitionEffect{group = main.transitions, x = self.x, y = self.y, color = character_colors[random:table(self.parent.units).character], transition_action = function() TransitionEffect{group = main.transitions, x = self.x, y = self.y, color = character_colors[random:table(self.parent.units).character], transition_action = function()
main:add(Arena'arena') main:add(Arena'arena')
main:go_to('arena', ((self.parent.first_screen and 1) or (self.parent.level + 1)), self.parent.units, self.parent.passives) main:go_to('arena', ((self.parent.first_screen and 1) or (self.parent.level + 1)), self.parent.units, self.parent.passives)
end, text = Text({{text = '[wavy, bg]level ' .. ((self.parent.first_screen and 1) or (self.parent.level + 1)), font = pixul_font, alignment = 'center'}}, global_text_tags)} end, text = Text({{text = '[wavy, bg]level ' .. ((self.parent.first_screen and 1) or (self.parent.level + 1)) .. '/25', font = pixul_font, alignment = 'center'}}, global_text_tags)}
end end
end end
end end
@ -502,6 +644,58 @@ end
TutorialCharacterPart = Object:extend()
TutorialCharacterPart:implement(GameObject)
function TutorialCharacterPart:init(args)
self:init_game_object(args)
self.shape = Rectangle(self.x, self.y, self.sx*20, self.sy*20)
self.interact_with_mouse = true
self.spring:pull(0.2, 200, 10)
end
function TutorialCharacterPart:update(dt)
self:update_game_object(dt)
end
function TutorialCharacterPart:draw()
graphics.push(self.x, self.y, 0, self.sx*self.spring.x, self.sy*self.spring.x)
graphics.rectangle(self.x, self.y, 14, 14, 3, 3, self.highlighted and fg[0] or character_colors[self.character])
graphics.print_centered(self.level, pixul_font, self.x + 0.5, self.y + 2, 0, 1, 1, 0, 0, self.highlighted and fg[-5] or _G[character_color_strings[self.character]][-5])
graphics.pop()
end
function TutorialCharacterPart:on_mouse_enter()
ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5}
self.selected = true
self.spring:pull(0.2, 200, 10)
self.info_text = InfoText{group = main.current.tutorial}
self.info_text:activate({
{text = '[' .. character_color_strings[self.character] .. ']' .. self.character:capitalize() .. '[fg] - [yellow]Lv.' .. self.level,
font = pixul_font, alignment = 'center', height_multiplier = 1.25},
{text = '[fg]Classes: ' .. character_class_strings[self.character], font = pixul_font, alignment = 'center', height_multiplier = 1.25},
{text = character_descriptions[self.character](self.level), font = pixul_font, alignment = 'center', height_multiplier = 2},
{text = '[' .. (self.level == 3 and 'yellow' or 'light_bg') .. ']Lv.3 [' .. (self.level == 3 and 'fg' or 'light_bg') .. ']Effect - ' ..
(self.level == 3 and character_effect_names[self.character] or character_effect_names_gray[self.character]), font = pixul_font, alignment = 'center', height_multiplier = 1.25},
{text = (self.level == 3 and character_effect_descriptions[self.character]() or character_effect_descriptions_gray[self.character]()), font = pixul_font, alignment = 'center'},
}, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw/2, gh/2 + gh/4 - 12
end
function TutorialCharacterPart:on_mouse_exit()
self.selected = false
if self.info_text then
self.info_text:deactivate()
self.info_text.dead = true
end
self.info_text = nil
end
CharacterPart = Object:extend() CharacterPart = Object:extend()
CharacterPart:implement(GameObject) CharacterPart:implement(GameObject)
function CharacterPart:init(args) function CharacterPart:init(args)
@ -571,6 +765,7 @@ function CharacterPart:on_mouse_enter()
}, nil, nil, nil, nil, 16, 4, nil, 2) }, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw/2, gh/2 + 10 self.info_text.x, self.info_text.y = gw/2, gh/2 + 10
--[[
if self.parent:is(BuyScreen) then if self.parent:is(BuyScreen) then
for _, set in ipairs(self.parent.sets) do for _, set in ipairs(self.parent.sets) do
if table.any(character_classes[self.character], function(v) return v == set.class end) then if table.any(character_classes[self.character], function(v) return v == set.class end) then
@ -578,6 +773,7 @@ function CharacterPart:on_mouse_enter()
end end
end end
end end
]]--
end end
@ -600,6 +796,7 @@ function CharacterPart:on_mouse_exit()
end end
self.info_text = nil self.info_text = nil
--[[
if self.parent:is(BuyScreen) then if self.parent:is(BuyScreen) then
for _, set in ipairs(self.parent.sets) do for _, set in ipairs(self.parent.sets) do
if table.any(character_classes[self.character], function(v) return v == set.class end) then if table.any(character_classes[self.character], function(v) return v == set.class end) then
@ -607,6 +804,7 @@ function CharacterPart:on_mouse_exit()
end end
end end
end end
]]--
end end
@ -619,6 +817,7 @@ function CharacterPart:die()
self.info_text = nil self.info_text = nil
end end
--[[
if self.selected and self.parent:is(BuyScreen) then if self.selected and self.parent:is(BuyScreen) then
for _, set in ipairs(self.parent.sets) do for _, set in ipairs(self.parent.sets) do
if table.any(character_classes[self.character], function(v) return v == set.class end) then if table.any(character_classes[self.character], function(v) return v == set.class end) then
@ -626,6 +825,7 @@ function CharacterPart:die()
end end
end end
end end
]]--
end end
@ -876,7 +1076,7 @@ function CharacterIcon:init(args)
self:init_game_object(args) self:init_game_object(args)
self.shape = Rectangle(self.x, self.y, 40, 20) self.shape = Rectangle(self.x, self.y, 40, 20)
self.interact_with_mouse = true self.interact_with_mouse = true
self.character_text = Text({{text = '[' .. character_color_strings[self.character] .. ']' .. self.character, font = pixul_font, alignment = 'center'}}, global_text_tags) self.character_text = Text({{text = '[' .. character_color_strings[self.character] .. ']' .. string.lower(character_names[self.character]), font = pixul_font, alignment = 'center'}}, global_text_tags)
end end
@ -930,6 +1130,84 @@ end
TutorialClassIcon = Object:extend()
TutorialClassIcon:implement(GameObject)
function TutorialClassIcon:init(args)
self:init_game_object(args)
self.shape = Rectangle(self.x, self.y + 11, 20, 40)
self.interact_with_mouse = true
self.spring:pull(0.2, 200, 10)
end
function TutorialClassIcon:update(dt)
self:update_game_object(dt)
end
function TutorialClassIcon:draw()
graphics.push(self.x, self.y, 0, self.sx*self.spring.x, self.sy*self.spring.x)
local i, j, n = class_set_numbers[self.class](self.units)
graphics.rectangle(self.x, self.y, 16, 24, 4, 4, self.highlighted and fg[0] or ((n >= i) and class_colors[self.class] or bg[3]))
_G[self.class]:draw(self.x, self.y, 0, 0.3, 0.3, 0, 0, self.highlighted and fg[-5] or ((n >= i) and _G[class_color_strings[self.class]][-5] or bg[10]))
graphics.rectangle(self.x, self.y + 26, 16, 16, 3, 3, self.highlighted and fg[0] or bg[3])
if i == 2 then
if self.highlighted then
graphics.line(self.x - 3, self.y + 20, self.x - 3, self.y + 25, (n >= 1) and fg[-5] or fg[-10], 3)
graphics.line(self.x - 3, self.y + 27, self.x - 3, self.y + 32, (n >= 2) and fg[-5] or fg[-10], 3)
graphics.line(self.x + 4, self.y + 20, self.x + 4, self.y + 25, (n >= 3) and fg[-5] or fg[-10], 3)
graphics.line(self.x + 4, self.y + 27, self.x + 4, self.y + 32, (n >= 4) and fg[-5] or fg[-10], 3)
else
graphics.line(self.x - 3, self.y + 20, self.x - 3, self.y + 25, (n >= 1) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x - 3, self.y + 27, self.x - 3, self.y + 32, (n >= 2) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 20, self.x + 4, self.y + 25, (n >= 3) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 27, self.x + 4, self.y + 32, (n >= 4) and class_colors[self.class] or bg[10], 3)
end
elseif i == 3 then
if self.highlighted then
graphics.line(self.x - 3, self.y + 19, self.x - 3, self.y + 22, (n >= 1) and fg[-5] or fg[-10], 3)
graphics.line(self.x - 3, self.y + 24, self.x - 3, self.y + 27, (n >= 2) and fg[-5] or fg[-10], 3)
graphics.line(self.x - 3, self.y + 29, self.x - 3, self.y + 32, (n >= 3) and fg[-5] or fg[-10], 3)
graphics.line(self.x + 4, self.y + 19, self.x + 4, self.y + 22, (n >= 4) and fg[-5] or fg[-10], 3)
graphics.line(self.x + 4, self.y + 24, self.x + 4, self.y + 27, (n >= 5) and fg[-5] or fg[-10], 3)
graphics.line(self.x + 4, self.y + 29, self.x + 4, self.y + 32, (n >= 6) and fg[-5] or fg[-10], 3)
else
graphics.line(self.x - 3, self.y + 19, self.x - 3, self.y + 22, (n >= 1) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x - 3, self.y + 24, self.x - 3, self.y + 27, (n >= 2) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x - 3, self.y + 29, self.x - 3, self.y + 32, (n >= 3) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 19, self.x + 4, self.y + 22, (n >= 4) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 24, self.x + 4, self.y + 27, (n >= 5) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 29, self.x + 4, self.y + 32, (n >= 6) and class_colors[self.class] or bg[10], 3)
end
end
graphics.pop()
end
function TutorialClassIcon:on_mouse_enter()
ui_hover1:play{pitch = random:float(1.3, 1.5), volume = 0.5}
self.spring:pull(0.2, 200, 10)
local i, j, owned = class_set_numbers[self.class](self.units)
self.info_text = InfoText{group = main.current.tutorial}
self.info_text:activate({
{text = '[' .. class_color_strings[self.class] .. ']' .. self.class:capitalize() .. '[fg] - owned: [yellow]' .. owned, font = pixul_font, alignment = 'center', height_multiplier = 1.25},
{text = class_descriptions[self.class]((owned >= j and 2) or (owned >= i and 1) or 0), font = pixul_font, alignment = 'center'},
}, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw/2 - 25, gh/2 + 25
end
function TutorialClassIcon:on_mouse_exit()
if self.info_text then
self.info_text:deactivate()
self.info_text.dead = true
end
self.info_text = nil
end
ClassIcon = Object:extend() ClassIcon = Object:extend()
ClassIcon:implement(GameObject) ClassIcon:implement(GameObject)

View File

@ -1082,3 +1082,8 @@ Once that's done I'll do credits screen, NG+10 (game complete) screen, and find
Spending a lot of time balancing the game. The end game is pretty hectic and I need to make sure that it's reachable at NG+~7+. Spending a lot of time balancing the game. The end game is pretty hectic and I need to make sure that it's reachable at NG+~7+.
Basically just playing the game a lot, changing things that are too strong weak, then playing more and seeing if it's better. Basically just playing the game a lot, changing things that are too strong weak, then playing more and seeing if it's better.
# Week 12-13 - 03-17/05/21
These 2 last weeks have been a lot of playtesting and just taking care of details. But now it's finally over.
I finally finished this game and I'm fairly happy with how it turned out. I'll write more about it in a separate blog post, I don't really have the energy to write much now.

View File

@ -22,7 +22,7 @@ function Seeker:init(args)
HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = green[0], duration = 0.1} HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = green[0], duration = 0.1}
for _, enemy in ipairs(enemies) do for _, enemy in ipairs(enemies) do
LightningLine{group = main.current.effects, src = self, dst = enemy, color = green[0]} LightningLine{group = main.current.effects, src = self, dst = enemy, color = green[0]}
enemy:speed_boost(3 + self.level*0.1 + new_game_plus*0.1) enemy:speed_boost(3 + self.level*0.05 + new_game_plus*0.2)
end end
end end
end) end)
@ -96,14 +96,14 @@ function Seeker:init(args)
LightningLine{group = main.current.effects, src = self, dst = enemy, color = blue[0]} LightningLine{group = main.current.effects, src = self, dst = enemy, color = blue[0]}
enemy:hit(10000) enemy:hit(10000)
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4} shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4}
local n = 8 + new_game_plus local n = 8 + new_game_plus*2
for i = 1, n do EnemyProjectile{group = main.current.main, x = enemy.x, y = enemy.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*enemy.level, dmg = 2*enemy.dmg} end for i = 1, n do EnemyProjectile{group = main.current.main, x = enemy.x, y = enemy.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*enemy.level, dmg = (1 + 0.2*new_game_plus)*enemy.dmg} end
end end
end) end)
elseif self.boss == 'randomizer' then elseif self.boss == 'randomizer' then
self.t:every_immediate(0.07, function() self.color = _G[random:table{'green', 'purple', 'yellow', 'blue'}][0]:clone() end) self.t:every_immediate(0.07, function() self.color = _G[random:table{'green', 'purple', 'yellow', 'blue'}][0]:clone() end)
self.t:every(2, function() self.t:every(6, function()
local attack = random:table{'explode', 'swarm', 'force', 'speed_boost'} local attack = random:table{'explode', 'swarm', 'force', 'speed_boost'}
if attack == 'explode' then if attack == 'explode' then
local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 128), {Seeker}) local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 128), {Seeker})
@ -113,8 +113,8 @@ function Seeker:init(args)
LightningLine{group = main.current.effects, src = self, dst = enemy, color = blue[0]} LightningLine{group = main.current.effects, src = self, dst = enemy, color = blue[0]}
enemy:hit(10000) enemy:hit(10000)
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4} shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4}
local n = 8 + new_game_plus local n = 8 + new_game_plus*2
for i = 1, n do EnemyProjectile{group = main.current.main, x = enemy.x, y = enemy.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*enemy.level, dmg = 2*enemy.dmg} end for i = 1, n do EnemyProjectile{group = main.current.main, x = enemy.x, y = enemy.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*enemy.level, dmg = (1 + 0.2*new_game_plus)*enemy.dmg} end
end end
elseif attack == 'swarm' then elseif attack == 'swarm' then
local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 128), {Seeker}) local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 128), {Seeker})
@ -145,7 +145,7 @@ function Seeker:init(args)
HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = green[0], duration = 0.1} HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = green[0], duration = 0.1}
for _, enemy in ipairs(enemies) do for _, enemy in ipairs(enemies) do
LightningLine{group = main.current.effects, src = self, dst = enemy, color = green[0]} LightningLine{group = main.current.effects, src = self, dst = enemy, color = green[0]}
enemy:speed_boost(3 + self.level*0.1 + new_game_plus*0.1) enemy:speed_boost(3 + self.level*0.05 + new_game_plus*0.2)
end end
end end
end end
@ -198,7 +198,7 @@ function Seeker:init(args)
end) end)
elseif self.tank then elseif self.tank then
self.color = yellow[0]:clone() self.color = yellow[0]:clone()
self.buff_hp_m = 1.5 + (0.05*self.level) self.buff_hp_m = 1.25 + (0.025*self.level)
self:calculate_stats() self:calculate_stats()
self.hp = self.max_hp self.hp = self.max_hp
elseif self.shooter then elseif self.shooter then
@ -212,12 +212,12 @@ function Seeker:init(args)
self.hfx:use('hit', 0.25, 200, 10, 0.1) self.hfx:use('hit', 0.25, 200, 10, 0.1)
local r = self.r local r = self.r
HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6} HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6}
EnemyProjectile{group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), color = fg[0], r = r, v = 150 + 5*self.level + 5*new_game_plus, EnemyProjectile{group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), color = fg[0], r = r, v = 150 + 5*self.level + 10*new_game_plus,
dmg = (new_game_plus*0.1 + 1.5)*self.dmg} dmg = (new_game_plus*0.2 + 1)*self.dmg}
end) end)
end end
end, nil, nil, 'shooter') end, nil, nil, 'shooter')
end) end)
elseif self.spawner then elseif self.spawner then
self.color = purple[0]:clone() self.color = purple[0]:clone()
end end
@ -248,8 +248,8 @@ function Seeker:update(dt)
if self.headbutt_charging or self.headbutting then self.buff_def_m = 3 end if self.headbutt_charging or self.headbutting then self.buff_def_m = 3 end
if self.speed_boosting then if self.speed_boosting then
local n = math.remap(love.timer.getTime() - self.speed_boosting, 0, (3 + 0.1*self.level + new_game_plus*0.1), 1, 0.5) local n = math.remap(love.timer.getTime() - self.speed_boosting, 0, (3 + 0.05*self.level + new_game_plus*0.2), 1, 0.5)
self.speed_boosting_mvspd_m = (3 + 0.1*self.level + 0.1*new_game_plus)*n self.speed_boosting_mvspd_m = (3 + 0.05*self.level + 0.2*new_game_plus)*n
if not self.speed_booster and not self.exploder and not self.headbutter and not self.tank and not self.shooter and not self.spawner then if not self.speed_booster and not self.exploder and not self.headbutter and not self.tank and not self.shooter and not self.spawner then
self.color.r = math.remap(n, 1, 0.5, green[0].r, red[0].r) self.color.r = math.remap(n, 1, 0.5, green[0].r, red[0].r)
self.color.g = math.remap(n, 1, 0.5, green[0].g, red[0].g) self.color.g = math.remap(n, 1, 0.5, green[0].g, red[0].g)
@ -432,7 +432,7 @@ function Seeker:hit(damage, projectile)
if self.exploder then if self.exploder then
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4} shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4}
trigger:after(0.01, function() trigger:after(0.01, function()
local n = 8 + new_game_plus local n = 8 + new_game_plus*2
for i = 1, n do for i = 1, n do
EnemyProjectile{group = main.current.main, x = self.x, y = self.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*self.level, dmg = 2*self.dmg} EnemyProjectile{group = main.current.main, x = self.x, y = self.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*self.level, dmg = 2*self.dmg}
end end
@ -468,7 +468,7 @@ function Seeker:hit(damage, projectile)
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
trigger:after(0.01, function() trigger:after(0.01, function()
for i = 1, self.infested do for i = 1, self.infested do
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = self.infested_dmg, parent = projectile.parent} Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = self.infested_dmg, parent = self.infested_ref}
end end
end) end)
end end
@ -503,7 +503,7 @@ function Seeker:slow(amount, duration)
end end
function Seeker:curse(curse, duration, arg1, arg2) function Seeker:curse(curse, duration, arg1, arg2, arg3)
local curse_m = 1 local curse_m = 1
if main.current.curser_level == 2 then curse_m = 1.5 if main.current.curser_level == 2 then curse_m = 1.5
elseif main.current.curser_level == 1 then curse_m = 1.25 elseif main.current.curser_level == 1 then curse_m = 1.25
@ -520,6 +520,7 @@ function Seeker:curse(curse, duration, arg1, arg2)
end end
end end
buff1:play{pitch = random:float(0.65, 0.75), volume = 0.25}
if curse == 'launcher' then if curse == 'launcher' then
self.t:after(duration*curse_m, function() self.t:after(duration*curse_m, function()
self.launcher_push = arg1 self.launcher_push = arg1
@ -534,6 +535,7 @@ function Seeker:curse(curse, duration, arg1, arg2)
elseif curse == 'infestor' then elseif curse == 'infestor' then
self.infested = arg1 self.infested = arg1
self.infested_dmg = arg2 self.infested_dmg = arg2
self.infested_ref = arg3
self.t:after(duration*curse_m, function() self.infested = false end, 'infestor_curse') self.t:after(duration*curse_m, function() self.infested = false end, 'infestor_curse')
end end
end end
@ -558,6 +560,7 @@ EnemyCritter:implement(Physics)
EnemyCritter:implement(Unit) EnemyCritter:implement(Unit)
function EnemyCritter:init(args) function EnemyCritter:init(args)
self:init_game_object(args) self:init_game_object(args)
if tostring(self.x) == tostring(0/0) or tostring(self.y) == tostring(0/0) then self.dead = true; return end
self:init_unit() self:init_unit()
self:set_as_rectangle(7, 4, 'dynamic', 'enemy_projectile') self:set_as_rectangle(7, 4, 'dynamic', 'enemy_projectile')
self:set_restitution(0.5) self:set_restitution(0.5)
@ -574,6 +577,11 @@ end
function EnemyCritter:update(dt) function EnemyCritter:update(dt)
self:update_game_object(dt) self:update_game_object(dt)
if self.slowed then self.slow_mvspd_m = self.slowed
else self.slow_mvspd_m = 1 end
self.buff_mvspd_m = (self.speed_boosting_mvspd_m or 1)*(self.slow_mvspd_m or 1)*(self.temporal_chains_mvspd_m or 1)
self:calculate_stats()
if self.being_pushed then if self.being_pushed then
local v = math.length(self:get_velocity()) local v = math.length(self:get_velocity())
if v < 50 then if v < 50 then
@ -665,6 +673,61 @@ function EnemyCritter:speed_boost(duration)
end end
function EnemyCritter:slow(amount, duration)
self.slowed = amount
self.t:after(duration, function() self.slowed = false end, 'slow')
end
function EnemyCritter:curse(curse, duration, arg1, arg2, arg3)
local curse_m = 1
if main.current.curser_level == 2 then curse_m = 1.5
elseif main.current.curser_level == 1 then curse_m = 1.25
else curse_m = 1 end
if main.current.player.whispers_of_doom then
if not self.doom then self.doom = 0 end
self.doom = self.doom + 1
if self.doom == 4 then
self.doom = 0
self:hit(200)
buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
end
end
if curse == 'launcher' then
self.t:after(duration*curse_m, function()
self.launcher_push = arg1
self.launcher = arg2
self:push(random:float(50, 75)*self.launcher.knockback_m, random:table{0, math.pi, math.pi/2, -math.pi/2})
end, 'launcher_curse')
elseif curse == 'bard' then
self.bard_cursed = true
elseif curse == 'bane' then
self.baned = true
self.t:after(duration*curse_m, function() self.baned = false end, 'bane_curse')
elseif curse == 'infestor' then
self.infested = arg1
self.infested_dmg = arg2
self.infested_ref = arg3
self.t:after(duration*curse_m, function() self.infested = false end, 'infestor_curse')
end
end
function EnemyCritter:apply_dot(dmg, duration)
self.t:every(0.25, function()
hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2}
self:hit(dmg/4)
HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 1 do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color} end
for i = 1, 1 do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = purple[0]} end
end, math.floor(duration/0.2))
end
EnemyProjectile = Object:extend() EnemyProjectile = Object:extend()
@ -672,6 +735,7 @@ EnemyProjectile:implement(GameObject)
EnemyProjectile:implement(Physics) EnemyProjectile:implement(Physics)
function EnemyProjectile:init(args) function EnemyProjectile:init(args)
self:init_game_object(args) self:init_game_object(args)
if tostring(self.x) == tostring(0/0) or tostring(self.y) == tostring(0/0) then self.dead = true; return end
self:set_as_rectangle(10, 4, 'dynamic', 'enemy_projectile') self:set_as_rectangle(10, 4, 'dynamic', 'enemy_projectile')
end end

View File

@ -180,6 +180,11 @@ function Trigger:resolve_delay(delay)
end end
function Trigger:destroy()
self.triggers = nil
end
function Trigger:update(dt) function Trigger:update(dt)
for tag, trigger in pairs(self.triggers) do for tag, trigger in pairs(self.triggers) do
if trigger.timer then if trigger.timer then

View File

@ -47,6 +47,7 @@ function engine_run(config)
if not web then if not web then
love.filesystem.setIdentity(config.game_name) love.filesystem.setIdentity(config.game_name)
steam.init() steam.init()
system.load_state()
local _, _, flags = love.window.getMode() local _, _, flags = love.window.getMode()
local window_width, window_height = love.window.getDesktopDimensions(flags.display) local window_width, window_height = love.window.getDesktopDimensions(flags.display)
@ -63,7 +64,12 @@ function engine_run(config)
sx, sy = window_width/(config.game_width or 480), window_height/(config.game_height or 270) sx, sy = window_width/(config.game_width or 480), window_height/(config.game_height or 270)
ww, wh = window_width, window_height ww, wh = window_width, window_height
love.window.setMode(window_width, window_height, {fullscreen = config.fullscreen, vsync = config.vsync, msaa = msaa or 0, display = config.display}) if state.sx and state.sy then
sx, sy = state.sx, state.sy
love.window.setMode(state.sx*gw, state.sy*gh, {fullscreen = state.fullscreen, vsync = config.vsync, msaa = msaa or 0, display = config.display})
else
love.window.setMode(window_width, window_height, {fullscreen = config.fullscreen, vsync = config.vsync, msaa = msaa or 0, display = config.display})
end
love.window.setTitle(config.game_name) love.window.setTitle(config.game_name)
else else
@ -72,6 +78,7 @@ function engine_run(config)
ww, wh = 960, 540 ww, wh = 960, 540
end end
love.window.setIcon(love.image.newImageData('assets/images/icon.png'))
love.graphics.setBackgroundColor(0, 0, 0, 1) love.graphics.setBackgroundColor(0, 0, 0, 1)
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.joystick.loadGamepadMappings("engine/gamecontrollerdb.txt") love.joystick.loadGamepadMappings("engine/gamecontrollerdb.txt")

View File

@ -35,7 +35,8 @@ function system.count_all(f)
seen[t] = true seen[t] = true
for k, v in pairs(t) do for k, v in pairs(t) do
if type(v) == "table" then count_table(v) if type(v) == "table" then count_table(v)
elseif type(v) == "userdata" then f(v) end elseif type(v) == "userdata" then f(v)
end
end end
end end
count_table(_G) count_table(_G)
@ -45,8 +46,13 @@ end
function system.type_count() function system.type_count()
local counts = {} local counts = {}
local enumerate = function(o) local enumerate = function(o)
local t = system.type_name(o) if type(o) == 'function' then
counts[t] = (counts[t] or 0) + 1 local upvalues = {}
else
local t = system.type_name(o)
counts[t] = (counts[t] or 0) + 1
end
end end
system.count_all(enumerate) system.count_all(enumerate)
return counts return counts

110
main.lua
View File

@ -11,8 +11,8 @@ require 'media'
function init() function init()
shared_init() shared_init()
input:bind('move_left', {'a', 'left', 'dpleft'}) input:bind('move_left', {'a', 'left', 'dpleft', 'm1'})
input:bind('move_right', {'d', 'right', 'dpright'}) input:bind('move_right', {'d', 'right', 'dpright', 'm2'})
input:bind('move_up', {'w', 'up', 'dpup'}) input:bind('move_up', {'w', 'up', 'dpup'})
input:bind('move_down', {'s', 'down', 'dpdown'}) input:bind('move_down', {'s', 'down', 'dpdown'})
input:bind('enter', {'space', 'return', 'fleft', 'fdown', 'fright'}) input:bind('enter', {'space', 'return', 'fleft', 'fdown', 'fright'})
@ -99,7 +99,13 @@ function init()
turret_deploy = Sound('321215__hybrid-v__sci-fi-weapons-deploy.ogg', s) turret_deploy = Sound('321215__hybrid-v__sci-fi-weapons-deploy.ogg', s)
rogue_crit1 = Sound('Dagger Stab (Flesh) 4.ogg', s) rogue_crit1 = Sound('Dagger Stab (Flesh) 4.ogg', s)
rogue_crit2 = Sound('Sword hits another sword 6.ogg', s) rogue_crit2 = Sound('Sword hits another sword 6.ogg', s)
cascade = Sound('Kubbi - Ember - 04 Cascade.ogg', {tags = {music}})
song1 = Sound('Kubbi - Ember - 01 Pathfinder.ogg', {tags = {music}})
song2 = Sound('Kubbi - Ember - 02 Ember.ogg', {tags = {music}})
song3 = Sound('Kubbi - Ember - 03 Firelight.ogg', {tags = {music}})
song4 = Sound('Kubbi - Ember - 04 Cascade.ogg', {tags = {music}})
song5 = Sound('Kubbi - Ember - 05 Compass.ogg', {tags = {music}})
death_song = Sound('Kubbi - Ember - 09 Formed by Glaciers.ogg', {tags = {music}})
speed_booster_elite = Image('speed_booster_elite') speed_booster_elite = Image('speed_booster_elite')
exploder_elite = Image('exploder_elite') exploder_elite = Image('exploder_elite')
@ -162,6 +168,7 @@ function init()
hive = Image('hive') hive = Image('hive')
void_rift = Image('void_rift') void_rift = Image('void_rift')
star = Image('star') star = Image('star')
arrow = Image('arrow')
class_colors = { class_colors = {
['warrior'] = yellow[0], ['warrior'] = yellow[0],
@ -444,7 +451,7 @@ function init()
['saboteur'] = function(lvl) return '[fg]calls [yellow]2[fg] saboteurs to seek targets and deal [yellow]' .. get_character_stat('saboteur', lvl, 'dmg') .. ' AoE[fg] damage' end, ['saboteur'] = function(lvl) return '[fg]calls [yellow]2[fg] saboteurs to seek targets and deal [yellow]' .. get_character_stat('saboteur', lvl, 'dmg') .. ' AoE[fg] damage' end,
['stormweaver'] = function(lvl) return '[fg]infuses projectiles with chain lightning that deals [yellow]20%[fg] damage to [yellow]2[fg] enemies' end, ['stormweaver'] = function(lvl) return '[fg]infuses projectiles with chain lightning that deals [yellow]20%[fg] damage to [yellow]2[fg] enemies' end,
['sage'] = function(lvl) return '[fg]shoots a slow projectile that draws enemies in' end, ['sage'] = function(lvl) return '[fg]shoots a slow projectile that draws enemies in' end,
['squire'] = function(lvl) return '[yellow]+15%[fg] damage and defense to all allies' end, ['squire'] = function(lvl) return '[yellow]+20%[fg] damage and defense to all allies' end,
['cannoneer'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. 2*get_character_stat('cannoneer', lvl, 'dmg') .. ' AoE[fg] damage' end, ['cannoneer'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. 2*get_character_stat('cannoneer', lvl, 'dmg') .. ' AoE[fg] damage' end,
['dual_gunner'] = function(lvl) return '[fg]shoots two parallel projectiles, each dealing [yellow]' .. get_character_stat('dual_gunner', lvl, 'dmg') .. '[fg] damage' end, ['dual_gunner'] = function(lvl) return '[fg]shoots two parallel projectiles, each dealing [yellow]' .. get_character_stat('dual_gunner', lvl, 'dmg') .. '[fg] damage' end,
['hunter'] = function(lvl) return '[fg]shoots an arrow that deals [yellow]' .. get_character_stat('hunter', lvl, 'dmg') .. '[fg] damage and has a [yellow]20%[fg] chance to summon a pet' end, ['hunter'] = function(lvl) return '[fg]shoots an arrow that deals [yellow]' .. get_character_stat('hunter', lvl, 'dmg') .. '[fg] damage and has a [yellow]20%[fg] chance to summon a pet' end,
@ -462,7 +469,7 @@ function init()
['beastmaster'] = function(lvl) return '[fg]spawn [yellow]2[fg] small critters if the beastmaster crits' end, ['beastmaster'] = function(lvl) return '[fg]spawn [yellow]2[fg] small critters if the beastmaster crits' end,
['launcher'] = function(lvl) return '[fg]nearby enemies are pushed after [yellow]4[fg] seconds, taking [yellow]' .. 2*get_character_stat('launcher', lvl, 'dmg') .. '[fg] damage on wall hit' end, ['launcher'] = function(lvl) return '[fg]nearby enemies are pushed after [yellow]4[fg] seconds, taking [yellow]' .. 2*get_character_stat('launcher', lvl, 'dmg') .. '[fg] damage on wall hit' end,
['bard'] = function(lvl) return "[fg]throws a knife that deals [yellow]" .. get_character_stat('bard', lvl, 'dmg') .. "[fg] damage and inflicts enemies hit with the bard's curse" end, ['bard'] = function(lvl) return "[fg]throws a knife that deals [yellow]" .. get_character_stat('bard', lvl, 'dmg') .. "[fg] damage and inflicts enemies hit with the bard's curse" end,
['assassin'] = function(lvl) return '[fg]throws a piercing knife that deals [yellow]' .. get_character_stat('assassin', lvl, 'dmg') .. '[fg] damage and inflicts poison that deals [yellow]' .. ['assassin'] = function(lvl) return '[fg]throws a piercing knife that deals [yellow]' .. get_character_stat('assassin', lvl, 'dmg') .. '[fg] damage + [yellow]' ..
get_character_stat('assassin', lvl, 'dmg')/2 .. '[fg] damage per second for [yellow]3[fg] seconds' end, get_character_stat('assassin', lvl, 'dmg')/2 .. '[fg] damage per second for [yellow]3[fg] seconds' end,
['host'] = function(lvl) return '[fg]periodically spawn [yellow]1[fg] small critter' end, ['host'] = function(lvl) return '[fg]periodically spawn [yellow]1[fg] small critter' end,
['carver'] = function(lvl) return '[fg]carves a statue that periodically heals [yellow]1[fg] unit for [yellow]20%[fg] max HP if in range' end, ['carver'] = function(lvl) return '[fg]carves a statue that periodically heals [yellow]1[fg] unit for [yellow]20%[fg] max HP if in range' end,
@ -489,7 +496,7 @@ function init()
['saboteur'] = '[orange]Demoman', ['saboteur'] = '[orange]Demoman',
['stormweaver'] = '[blue]Wide Lightning', ['stormweaver'] = '[blue]Wide Lightning',
['sage'] = '[purple]Dimension Compression', ['sage'] = '[purple]Dimension Compression',
['squire'] = '[yellow]Repair', ['squire'] = '[yellow]Shiny Gear',
['cannoneer'] = '[orange]Cannon Barrage', ['cannoneer'] = '[orange]Cannon Barrage',
['dual_gunner'] = '[green]Gun Kata', ['dual_gunner'] = '[green]Gun Kata',
['hunter'] = '[green]Feral Pack', ['hunter'] = '[green]Feral Pack',
@ -533,7 +540,7 @@ function init()
['saboteur'] = '[light_bg]Demoman', ['saboteur'] = '[light_bg]Demoman',
['stormweaver'] = '[light_bg]Wide Lightning', ['stormweaver'] = '[light_bg]Wide Lightning',
['sage'] = '[light_bg]Dimension Compression', ['sage'] = '[light_bg]Dimension Compression',
['squire'] = '[light_bg]Repair', ['squire'] = '[light_bg]Shiny Gear',
['cannoneer'] = '[light_bg]Cannon Barrage', ['cannoneer'] = '[light_bg]Cannon Barrage',
['dual_gunner'] = '[light_bg]Gun Kata', ['dual_gunner'] = '[light_bg]Gun Kata',
['hunter'] = '[light_bg]Feral Pack', ['hunter'] = '[light_bg]Feral Pack',
@ -577,7 +584,7 @@ function init()
['saboteur'] = function() return '[fg]the explosion has [yellow]50%[fg] chance to crit, increasing in size and dealing [yellow]2x[fg] damage' end, ['saboteur'] = function() return '[fg]the explosion has [yellow]50%[fg] chance to crit, increasing in size and dealing [yellow]2x[fg] damage' end,
['stormweaver'] = function() return "[fg]chain lightning's trigger area of effect and number of units hit is [yellow]doubled" end, ['stormweaver'] = function() return "[fg]chain lightning's trigger area of effect and number of units hit is [yellow]doubled" end,
['sage'] = function() return '[fg]when the projectile expires deal [yellow]' .. 3*get_character_stat('sage', 3, 'dmg') .. '[fg] damage to all enemies under its influence' end, ['sage'] = function() return '[fg]when the projectile expires deal [yellow]' .. 3*get_character_stat('sage', 3, 'dmg') .. '[fg] damage to all enemies under its influence' end,
['squire'] = function() return '[fg]you can reroll your item choices once, these opportunities stack if unused' end, ['squire'] = function() return '[yellow]+30%[fg] damage, attack speed, movement speed and defense to all allies' end,
['cannoneer'] = function() return '[fg]showers the hit area in [yellow]5[fg] additional cannon shots that deal [yellow]' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. '[fg] AoE damage' end, ['cannoneer'] = function() return '[fg]showers the hit area in [yellow]5[fg] additional cannon shots that deal [yellow]' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. '[fg] AoE damage' end,
['dual_gunner'] = function() return '[fg]every 5th attack shoot in rapid succession for [yellow]2[fg] seconds' end, ['dual_gunner'] = function() return '[fg]every 5th attack shoot in rapid succession for [yellow]2[fg] seconds' end,
['hunter'] = function() return '[fg]summons [yellow]3[fg] pets and the pets ricochet off walls once' end, ['hunter'] = function() return '[fg]summons [yellow]3[fg] pets and the pets ricochet off walls once' end,
@ -621,7 +628,7 @@ function init()
['saboteur'] = function() return '[light_bg]the explosion has 50% chance to crit, increasing in size and dealing 2x damage' end, ['saboteur'] = function() return '[light_bg]the explosion has 50% chance to crit, increasing in size and dealing 2x damage' end,
['stormweaver'] = function() return "[light_bg]chain lightning's trigger area of effect and number of units hit is doubled" end, ['stormweaver'] = function() return "[light_bg]chain lightning's trigger area of effect and number of units hit is doubled" end,
['sage'] = function() return '[light_bg]when the projectile expires deal ' .. 3*get_character_stat('sage', 3, 'dmg') .. ' damage to all enemies under its influence' end, ['sage'] = function() return '[light_bg]when the projectile expires deal ' .. 3*get_character_stat('sage', 3, 'dmg') .. ' damage to all enemies under its influence' end,
['squire'] = function() return '[light_bg]you can reroll your item choices once, these opportunities stack if unused' end, ['squire'] = function() return '[light_bg]+30% damage, attack speed, movement speed and defense to all allies' end,
['cannoneer'] = function() return '[light_bg]showers the hit area in 5 additional cannon shots that deal ' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. ' AoE damage' end, ['cannoneer'] = function() return '[light_bg]showers the hit area in 5 additional cannon shots that deal ' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. ' AoE damage' end,
['dual_gunner'] = function() return '[light_bg]every 5th attack shoot in rapid succession for 2 seconds' end, ['dual_gunner'] = function() return '[light_bg]every 5th attack shoot in rapid succession for 2 seconds' end,
['hunter'] = function() return '[light_bg]summons 3 pets and the pets ricochet off walls once' end, ['hunter'] = function() return '[light_bg]summons 3 pets and the pets ricochet off walls once' end,
@ -648,7 +655,7 @@ function init()
['highlander'] = function() return '[light_bg]quickly repeats the attack 3 times' end, ['highlander'] = function() return '[light_bg]quickly repeats the attack 3 times' end,
['fairy'] = function() return '[light_bg]heals 2 units instead and grants them an additional 100% attack speed' end, ['fairy'] = function() return '[light_bg]heals 2 units instead and grants them an additional 100% attack speed' end,
['priest'] = function() return '[light_bg]at the start of the round pick 3 units at random and grants them a buff that prevents death once' end, ['priest'] = function() return '[light_bg]at the start of the round pick 3 units at random and grants them a buff that prevents death once' end,
['infestor'] = function() return '[light_bg][yellow]triples the number of critters released' end, ['infestor'] = function() return '[light_bg]triples the number of critters released' end,
['flagellant'] = function() return '[light_bg]deals ' .. 2*get_character_stat('flagellant', 3, 'dmg') .. ' damage to all allies and grants +12% damage to all allies per cast' end, ['flagellant'] = function() return '[light_bg]deals ' .. 2*get_character_stat('flagellant', 3, 'dmg') .. ' damage to all allies and grants +12% damage to all allies per cast' end,
} }
@ -951,7 +958,7 @@ function init()
['magnify'] = '[yellow]+25%[fg] area size', ['magnify'] = '[yellow]+25%[fg] area size',
['concentrated_fire'] = '[yellow]-50%[fg] area size and [yellow]+100%[fg] area damage', ['concentrated_fire'] = '[yellow]-50%[fg] area size and [yellow]+100%[fg] area damage',
['unleash'] = '[yellow]+2%[fg] area size and damage per second', ['unleash'] = '[yellow]+2%[fg] area size and damage per second',
['reinforce'] = '[yellow]+10%[fg] damage, defense and attack speed to all allies if you have at least one enchanter', ['reinforce'] = '[yellow]+10%[fg] damage, defense and attack speed to all allies with at least one enchanter',
['payback'] = '[yellow]+5%[fg] damage to all allies whenever an enchanter is hit', ['payback'] = '[yellow]+5%[fg] damage to all allies whenever an enchanter is hit',
['blessing'] = '[yellow]+20%[fg] healing effectiveness', ['blessing'] = '[yellow]+20%[fg] healing effectiveness',
['hex_master'] = '[yellow]+25%[fg] curse duration', ['hex_master'] = '[yellow]+25%[fg] curse duration',
@ -1031,20 +1038,20 @@ function init()
[9] = {50, 30, 15, 5}, [9] = {50, 30, 15, 5},
[10] = {50, 30, 15, 5}, [10] = {50, 30, 15, 5},
[11] = {45, 30, 20, 5}, [11] = {45, 30, 20, 5},
[12] = {45, 30, 20, 5}, [12] = {40, 30, 20, 10},
[13] = {40, 30, 20, 10}, [13] = {35, 30, 25, 10},
[14] = {40, 30, 20, 10}, [14] = {30, 30, 25, 15},
[15] = {35, 35, 20, 10}, [15] = {25, 30, 30, 15},
[16] = {30, 40, 20, 10}, [16] = {25, 25, 30, 20},
[17] = {20, 40, 25, 15}, [17] = {20, 25, 35, 20},
[18] = {20, 40, 25, 15}, [18] = {15, 25, 35, 25},
[19] = {15, 40, 30, 15}, [19] = {10, 25, 40, 25},
[20] = {10, 40, 30, 20}, [20] = {5, 25, 40, 30},
[21] = {5, 40, 35, 20}, [21] = {0, 25, 40, 35},
[22] = {5, 35, 35, 25}, [22] = {0, 20, 40, 40},
[23] = {5, 35, 35, 25}, [23] = {0, 20, 35, 45},
[24] = {0, 30, 40, 30}, [24] = {0, 10, 30, 60},
[25] = {0, 25, 40, 35}, [25] = {0, 0, 0, 100},
} }
level_to_gold_gained = { level_to_gold_gained = {
@ -1159,48 +1166,81 @@ function init()
} }
gold = 2 gold = 2
passives = {} passives = {}
steam.userStats.requestCurrentStats()
system.load_state() system.load_state()
new_game_plus = state.new_game_plus or 0 new_game_plus = state.new_game_plus or 0
steam.userStats.requestCurrentStats() max_units = 7 + new_game_plus
max_units = 7 + math.floor(new_game_plus/2)
main = Main() main = Main()
-- main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
main:add(BuyScreen'buy_screen') main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', 0, {}, passives) main:go_to('buy_screen', 0, {}, passives)
--[[ --[[
main:add(Arena'arena')
main:go_to('arena', 20, {
{character = 'vagrant', level = 3},
{character = 'spellblade', level = 3},
{character = 'assassin', level = 3},
{character = 'scout', level = 3},
{character = 'engineer', level = 3},
{character = 'swordsman', level = 3},
{character = 'archer', level = 3},
}, passives)
main:add(Media'media') main:add(Media'media')
main:go_to('media') main:go_to('media')
]]-- ]]--
trigger:every(2, function()
if debugging_memory then
for k, v in pairs(system.type_count()) do
print(k, v)
end
print("-- " .. math.round(tonumber(collectgarbage("count"))/1024, 3) .. "MB --")
print()
end
end)
end end
function update(dt) function update(dt)
main:update(dt) main:update(dt)
star_group:update(dt)
--[[
if input.b.pressed then
-- debugging_memory = not debugging_memory
for k, v in pairs(system.type_count()) do
print(k, v)
end
print("-- " .. math.round(tonumber(collectgarbage("count"))/1024, 3) .. "MB --")
print()
end
]]--
if input.n.pressed then if input.n.pressed then
if sfx.volume == 0.5 then if sfx.volume == 0.5 then
sfx.volume = 0 sfx.volume = 0
state.volume_muted = true
elseif sfx.volume == 0 then elseif sfx.volume == 0 then
sfx.volume = 0.5 sfx.volume = 0.5
state.volume_muted = false
end end
end end
if input.m.pressed then if input.m.pressed then
if music.volume == 0.5 then if music.volume == 0.5 then
state.music_muted = true
music.volume = 0 music.volume = 0
elseif music.volume == 0 then elseif music.volume == 0 then
music.volume = 0.5 music.volume = 0.5
state.music_muted = false
end end
end end
if input.k.pressed then if input.f12.pressed then
steam.userStats.setAchievement('ASCENSION_1')
steam.userStats.storeStats()
end
if input.l.pressed then
steam.userStats.resetAllStats(true) steam.userStats.resetAllStats(true)
steam.userStats.storeStats() steam.userStats.storeStats()
end end

View File

@ -11,7 +11,6 @@ function Media:on_enter(from)
self.effects = Group() self.effects = Group()
self.ui = Group() self.ui = Group()
self.mode = 'achievements'
graphics.set_background_color(fg[0]) graphics.set_background_color(fg[0])
end end
@ -27,8 +26,11 @@ function Media:draw()
self.main:draw() self.main:draw()
self.effects:draw() self.effects:draw()
self.ui:draw() self.ui:draw()
if self.mode == 'achievements' then
graphics.print_centered('GG', fat_font, 32, 32, 0, 1, 1, 0, 0, fg[-5])
end
end end
--[[
build your party: hire heroes, rank them up and defeat endless waves of enemies
make synergies: combine heroes of the same class to unlock unique class passives
find passive items: further enhance your party with powerful passive items
create your build: explore the possibilities and combinations to create your own unique build
]]--

View File

@ -97,8 +97,10 @@ function LightningLine:generate()
end end
end end
local line = table.remove(self.lines, min_i) local line = table.remove(self.lines, min_i)
table.insert(self.points, line.x1) if line then
table.insert(self.points, line.y1) table.insert(self.points, line.x1)
table.insert(self.points, line.y1)
end
end end
end end
@ -230,14 +232,14 @@ function Unit:calculate_stats(first_run)
if self.boss then if self.boss then
local x = self.level local x = self.level
local y = {0, 0, 3, 0, 0, 6, 0, 0, 9, 0, 0, 12, 0, 0, 15, 0, 0, 18, 0, 0, 21, 0, 0, 24, 25} local y = {0, 0, 3, 0, 0, 6, 0, 0, 9, 0, 0, 12, 0, 0, 15, 0, 0, 18, 0, 0, 21, 0, 0, 24, 25}
self.base_hp = 100 + (new_game_plus*5) + (100 + new_game_plus*5)*y[x] self.base_hp = 100 + (new_game_plus*10) + (90 + new_game_plus*14)*y[x]
self.base_dmg = 50 + 15*y[x] self.base_dmg = (25 + new_game_plus*10) + (7 + new_game_plus*4)*y[x]
self.base_mvspd = 35 + 1.5*y[x] self.base_mvspd = 35 + 1.5*y[x]
else else
local x = self.level local x = self.level
local y = {0, 1, 3, 3, 4, 6, 5, 6, 9, 7, 8, 12, 10, 11, 15, 12, 13, 18, 16, 17, 21, 17, 20, 24, 25} local y = {0, 1, 3, 3, 4, 6, 5, 6, 9, 7, 8, 12, 10, 11, 15, 12, 13, 18, 16, 17, 21, 17, 20, 24, 25}
self.base_hp = 25 + (new_game_plus*2) + (25 + new_game_plus*2)*y[x] self.base_hp = 22 + (new_game_plus*3) + (17 + new_game_plus*2.6)*y[x]
self.base_dmg = 10 + 3*y[x] self.base_dmg = (4 + new_game_plus*1.5) + (2.2 + new_game_plus)*y[x]
self.base_mvspd = 70 + 3*y[x] self.base_mvspd = 70 + 3*y[x]
end end
elseif self:is(Saboteur) then elseif self:is(Saboteur) then

View File

@ -204,7 +204,7 @@ function Player:init(args)
elseif self.character == 'juggernaut' then elseif self.character == 'juggernaut' then
self.t:every(8, function() self.t:every(8, function()
self:attack(64, {juggernaut_push = true}) self:attack(96, {juggernaut_push = true})
end, nil, nil, 'attack') end, nil, nil, 'attack')
elseif self.character == 'lich' then elseif self.character == 'lich' then
@ -326,7 +326,7 @@ function Player:init(args)
end, nil, nil, 'shoot') end, nil, nil, 'shoot')
elseif self.character == 'highlander' then elseif self.character == 'highlander' then
self.attack_sensor = Circle(self.x, self.y, 64) self.attack_sensor = Circle(self.x, self.y, 32)
self.t:cooldown(4, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() self.t:cooldown(4, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function()
if self.level == 3 then if self.level == 3 then
self.t:every(0.25, function() self.t:every(0.25, function()
@ -385,7 +385,7 @@ function Player:init(args)
local enemies = main.current.main:get_objects_by_classes(main.current.enemies) local enemies = main.current.main:get_objects_by_classes(main.current.enemies)
for _, enemy in ipairs(enemies) do for _, enemy in ipairs(enemies) do
if self:distance_to_object(enemy) < 128 then if self:distance_to_object(enemy) < 128 then
enemy:curse('infestor', 6*(self.hex_duration_m or 1), (self.level == 3 and 6 or 2), self.dmg) enemy:curse('infestor', 6*(self.hex_duration_m or 1), (self.level == 3 and 6 or 2), self.dmg, self)
HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = orange[0], duration = 0.1} HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = orange[0], duration = 0.1}
LightningLine{group = main.current.effects, src = self, dst = enemy, color = orange[0]} LightningLine{group = main.current.effects, src = self, dst = enemy, color = orange[0]}
end end
@ -495,8 +495,10 @@ function Player:init(args)
end end
end end
local mage = random:table(mages) local mage = random:table(mages)
mage.awakening_aspd_m = 2 if mage then
mage.awakening_dmg_m = 2 mage.awakening_aspd_m = 2
mage.awakening_dmg_m = 2
end
end) end)
end end
@ -552,19 +554,21 @@ function Player:init(args)
end end
if self.reinforce then if self.reinforce then
local units = self:get_all_units() main.current.t:after(0.1, function()
local any_enchanter = false local units = self:get_all_units()
for _, unit in ipairs(units) do local any_enchanter = false
if table.any(unit.classes, function(v) return v == 'enchanter' end) then for _, unit in ipairs(units) do
any_enchanter = true if table.any(unit.classes, function(v) return v == 'enchanter' end) then
break any_enchanter = true
break
end
end end
end if any_enchanter then
if any_enchanter then self.reinforce_dmg_m = 1.1
self.reinforce_dmg_m = 1.1 self.reinforce_def_m = 1.1
self.reinforce_def_m = 1.1 self.reinforce_aspd_m = 1.1
self.reinforce_aspd_m = 1.1 end
end end)
end end
if self.payback then if self.payback then
@ -602,8 +606,14 @@ function Player:update(dt)
if self.character == 'squire' then if self.character == 'squire' then
local all_units = self:get_all_units() local all_units = self:get_all_units()
for _, unit in ipairs(all_units) do for _, unit in ipairs(all_units) do
unit.squire_dmg_m = 1.15 unit.squire_dmg_m = 1.2
unit.squire_def_m = 1.15 unit.squire_def_m = 1.2
if self.level == 3 then
unit.squire_dmg_m = 1.5
unit.squire_def_m = 1.5
unit.squire_aspd_m = 1.3
unit.squire_mvspd_m = 1.3
end
end end
elseif self.character == 'chronomancer' then elseif self.character == 'chronomancer' then
local all_units = self:get_all_units() local all_units = self:get_all_units()
@ -733,12 +743,12 @@ function Player:update(dt)
end end
self.buff_def_a = (self.warrior_def_a or 0) self.buff_def_a = (self.warrior_def_a or 0)
self.buff_aspd_m = (self.chronomancer_aspd_m or 1)*(self.vagrant_aspd_m or 1)*(self.outlaw_aspd_m or 1)*(self.fairy_aspd_m or 1)*(self.psyker_aspd_m or 1)*(self.chronomancy_aspd_m or 1)*(self.awakening_aspd_m or 1)*(self.berserking_aspd_m or 1)*(self.reinforce_aspd_m or 1) self.buff_aspd_m = (self.chronomancer_aspd_m or 1)*(self.vagrant_aspd_m or 1)*(self.outlaw_aspd_m or 1)*(self.fairy_aspd_m or 1)*(self.psyker_aspd_m or 1)*(self.chronomancy_aspd_m or 1)*(self.awakening_aspd_m or 1)*(self.berserking_aspd_m or 1)*(self.reinforce_aspd_m or 1)*(self.squire_aspd_m or 1)
self.buff_dmg_m = (self.squire_dmg_m or 1)*(self.vagrant_dmg_m or 1)*(self.enchanter_dmg_m or 1)*(self.swordsman_dmg_m or 1)*(self.flagellant_dmg_m or 1)*(self.psyker_dmg_m or 1)*(self.ballista_dmg_m or 1)*(self.ballista_x_dmg_m or 1)*(self.awakening_dmg_m or 1)*(self.reinforce_dmg_m or 1)*(self.payback_dmg_m or 1)*(self.immolation_dmg_m or 1) self.buff_dmg_m = (self.squire_dmg_m or 1)*(self.vagrant_dmg_m or 1)*(self.enchanter_dmg_m or 1)*(self.swordsman_dmg_m or 1)*(self.flagellant_dmg_m or 1)*(self.psyker_dmg_m or 1)*(self.ballista_dmg_m or 1)*(self.ballista_x_dmg_m or 1)*(self.awakening_dmg_m or 1)*(self.reinforce_dmg_m or 1)*(self.payback_dmg_m or 1)*(self.immolation_dmg_m or 1)
self.buff_def_m = (self.squire_def_m or 1)*(self.ouroboros_def_m or 1)*(self.unwavering_stance_def_m or 1)*(self.reinforce_def_m or 1) self.buff_def_m = (self.squire_def_m or 1)*(self.ouroboros_def_m or 1)*(self.unwavering_stance_def_m or 1)*(self.reinforce_def_m or 1)
self.buff_area_size_m = (self.nuker_area_size_m or 1)*(self.magnify_area_size_m or 1)*(self.concentrated_fire_area_size_m or 1)*(self.unleash_area_size_m or 1) self.buff_area_size_m = (self.nuker_area_size_m or 1)*(self.magnify_area_size_m or 1)*(self.concentrated_fire_area_size_m or 1)*(self.unleash_area_size_m or 1)
self.buff_area_dmg_m = (self.nuker_area_dmg_m or 1)*(self.amplify_area_dmg_m or 1)*(self.amplify_x_area_dmg_m or 1)*(self.concentrated_fire_area_dmg_m or 1)*(self.unleash_area_dmg_m or 1) self.buff_area_dmg_m = (self.nuker_area_dmg_m or 1)*(self.amplify_area_dmg_m or 1)*(self.amplify_x_area_dmg_m or 1)*(self.concentrated_fire_area_dmg_m or 1)*(self.unleash_area_dmg_m or 1)
self.buff_mvspd_m = (self.wall_rider_mvspd_m or 1)*(self.centipede_mvspd_m or 1) self.buff_mvspd_m = (self.wall_rider_mvspd_m or 1)*(self.centipede_mvspd_m or 1)*(self.squire_mvspd_m or 1)
self:calculate_stats() self:calculate_stats()
if self.attack_sensor then self.attack_sensor:move_to(self.x, self.y) end if self.attack_sensor then self.attack_sensor:move_to(self.x, self.y) end
@ -1424,7 +1434,7 @@ function Projectile:on_collision_enter(other, contact)
self.ricochet = self.ricochet - 1 self.ricochet = self.ricochet - 1
end end
_G[random:table{'arrow_hit_wall1', 'arrow_hit_wall2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.2} _G[random:table{'arrow_hit_wall1', 'arrow_hit_wall2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.2}
elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'spellblade' or self.character == 'bard' then elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'spellblade' or self.character == 'bard' or self.character == 'beastmaster' then
self:die(x, y, r, 0) self:die(x, y, r, 0)
knife_hit_wall1:play{pitch = random:float(0.9, 1.1), volume = 0.2} knife_hit_wall1:play{pitch = random:float(0.9, 1.1), volume = 0.2}
local r = Unit.bounce(self, nx, ny) local r = Unit.bounce(self, nx, ny)
@ -1489,7 +1499,7 @@ function Projectile:on_trigger_enter(other, contact)
end end
if self.character == 'archer' or self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'hunter' or self.character == 'spellblade' or self.character == 'engineer' or if self.character == 'archer' or self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'hunter' or self.character == 'spellblade' or self.character == 'engineer' or
self.character == 'bard' or self.character == 'assassin' or self.character == 'barrager' then self.character == 'bard' or self.character == 'assassin' or self.character == 'barrager' or self.character == 'beastmaster' then
hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35}
if self.character == 'spellblade' then if self.character == 'spellblade' then
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15} magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15}
@ -1595,25 +1605,25 @@ function Area:init(args)
local resonance_dmg = 0 local resonance_dmg = 0
if self.character == 'elementor' then if self.character == 'elementor' then
if self.parent.resonance then resonance_dmg = 2*self.dmg*0.05*#enemies end if self.parent.resonance then resonance_dmg = 2*self.dmg*0.05*#enemies end
enemy:hit(2*self.dmg + resonance_dmg) enemy:hit(2*self.dmg + resonance_dmg, self)
if self.level == 3 then if self.level == 3 then
enemy:slow(0.4, 6) enemy:slow(0.4, 6)
end end
elseif self.character == 'swordsman' then elseif self.character == 'swordsman' then
if self.parent.resonance then resonance_dmg = (self.dmg + self.dmg*0.15*#enemies)*0.05*#enemies end if self.parent.resonance then resonance_dmg = (self.dmg + self.dmg*0.15*#enemies)*0.05*#enemies end
enemy:hit(self.dmg + self.dmg*0.15*#enemies + resonance_dmg) enemy:hit(self.dmg + self.dmg*0.15*#enemies + resonance_dmg, self)
elseif self.character == 'blade' and self.level == 3 then elseif self.character == 'blade' and self.level == 3 then
if self.parent.resonance then resonance_dmg = (self.dmg + self.dmg*0.33*#enemies)*0.05*#enemies end if self.parent.resonance then resonance_dmg = (self.dmg + self.dmg*0.33*#enemies)*0.05*#enemies end
enemy:hit(self.dmg + self.dmg*0.33*#enemies + resonance_dmg) enemy:hit(self.dmg + self.dmg*0.33*#enemies + resonance_dmg, self)
elseif self.character == 'highlander' then elseif self.character == 'highlander' then
if self.parent.resonance then resonance_dmg = 6*self.dmg*0.05*#enemies end if self.parent.resonance then resonance_dmg = 6*self.dmg*0.05*#enemies end
enemy:hit(6*self.dmg + resonance_dmg) enemy:hit(6*self.dmg + resonance_dmg, self)
elseif self.character == 'launcher' then elseif self.character == 'launcher' then
if self.parent.resonance then resonance_dmg = (self.level == 3 and 6*self.dmg*0.05*#enemies or 2*self.dmg*0.05*#enemies) end if self.parent.resonance then resonance_dmg = (self.level == 3 and 6*self.dmg*0.05*#enemies or 2*self.dmg*0.05*#enemies) end
enemy:curse('launcher', 4*(self.hex_duration_m or 1), (self.level == 3 and 6*self.dmg or 2*self.dmg) + resonance_dmg, self.parent) enemy:curse('launcher', 4*(self.hex_duration_m or 1), (self.level == 3 and 6*self.dmg or 2*self.dmg) + resonance_dmg, self.parent)
else else
if self.parent.resonance then resonance_dmg = self.dmg*0.05*#enemies end if self.parent.resonance then resonance_dmg = self.dmg*0.05*#enemies end
enemy:hit(self.dmg + resonance_dmg) enemy:hit(self.dmg + resonance_dmg, self)
end end
HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end
@ -1647,7 +1657,7 @@ function Area:init(args)
if self.parent.void_rift and table.any(self.parent.classes, function(v) return v == 'mage' or v == 'nuker' or v == 'voider' end) then if self.parent.void_rift and table.any(self.parent.classes, function(v) return v == 'mage' or v == 'nuker' or v == 'voider' end) then
if random:bool(20) then if random:bool(20) then
DotArea{group = main.current.effects, x = self.x, y = self.y, rs = self.parent.area_size_m*24, color = self.color, dmg = self.parent.area_dmg_m*self.dmg*(self.parent.dot_dmg_m or 1), void_rift = true, duration = 1} DotArea{group = main.current.effects, x = self.x, y = self.y, rs = self.parent.area_size_m*24, color = self.color, dmg = self.parent.area_dmg_m*self.dmg*(self.parent.dot_dmg_m or 1), void_rift = true, duration = 1, parent = self.parent}
end end
end end
@ -1705,7 +1715,7 @@ function DotArea:init(args)
enemy.pyrod = self enemy.pyrod = self
end end
end end
enemy:hit((self.dot_dmg_m or 1)*self.dmg/5) enemy:hit((self.dot_dmg_m or 1)*self.dmg/5, self)
HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end
@ -1723,7 +1733,7 @@ function DotArea:init(args)
if self.level == 3 then if self.level == 3 then
enemy:slow(0.4, 4) enemy:slow(0.4, 4)
end end
enemy:hit((self.dot_dmg_m or 1)*2*self.dmg) enemy:hit((self.dot_dmg_m or 1)*2*self.dmg, self)
HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end
@ -1757,7 +1767,7 @@ function DotArea:init(args)
if #enemies > 0 then self.spring:pull(0.05, 200, 10) end if #enemies > 0 then self.spring:pull(0.05, 200, 10) end
for _, enemy in ipairs(enemies) do for _, enemy in ipairs(enemies) do
hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2} hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2}
enemy:hit((self.dot_dmg_m or 1)*self.dmg/5) enemy:hit((self.dot_dmg_m or 1)*self.dmg/5, self)
HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end
for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end
@ -2281,6 +2291,7 @@ end
function Critter:on_trigger_enter(other, contact) function Critter:on_trigger_enter(other, contact)
if other:is(Seeker) then if other:is(Seeker) then
critter2:play{pitch = random:float(0.65, 0.85), volume = 0.1}
self:hit(1) self:hit(1)
other:hit(self.dmg, self) other:hit(self.dmg, self)
end end

View File

@ -19,6 +19,7 @@ function shared_init()
_G[name .. '_transparent_weak'] = Color(color[0].r, color[0].g, color[0].b, 0.25) _G[name .. '_transparent_weak'] = Color(color[0].r, color[0].g, color[0].b, 0.25)
end end
modal_transparent = Color(0.1, 0.1, 0.1, 0.6) modal_transparent = Color(0.1, 0.1, 0.1, 0.6)
modal_transparent_2 = Color(0.1, 0.1, 0.1, 0.9)
bg_off = Color(46, 46, 46) bg_off = Color(46, 46, 46)
bg_gradient = GradientImage('vertical', Color(128, 128, 128, 0), Color(0, 0, 0, 0.3)) bg_gradient = GradientImage('vertical', Color(128, 128, 128, 0), Color(0, 0, 0, 0.3))
@ -30,7 +31,10 @@ function shared_init()
sfx = SoundTag() sfx = SoundTag()
sfx.volume = 0.5 sfx.volume = 0.5
music = SoundTag() music = SoundTag()
music.volume = 0 music.volume = 0.5
if state.volume_muted then sfx.volume = 0 end
if state.music_muted then music.volume = 0 end
fat_font = Font('FatPixelFont', 8) fat_font = Font('FatPixelFont', 8)
pixul_font = Font('PixulBrush', 8) pixul_font = Font('PixulBrush', 8)
@ -40,13 +44,9 @@ function shared_init()
shadow_shader = Shader(nil, 'shadow.frag') shadow_shader = Shader(nil, 'shadow.frag')
star_canvas = Canvas(gw, gh, {stencil = true}) star_canvas = Canvas(gw, gh, {stencil = true})
star_group = Group() star_group = Group()
local star_positions = {} star_positions = {}
for i = -30, gh + 30, 15 do table.insert(star_positions, {x = -40, y = i}) end for i = -30, gh + 30, 15 do table.insert(star_positions, {x = -40, y = i}) end
for i = -30, gw, 15 do table.insert(star_positions, {x = i, y = gh + 40}) end for i = -30, gw, 15 do table.insert(star_positions, {x = i, y = gh + 40}) end
trigger:every(0.375, function()
local p = random:table(star_positions)
Star{group = star_group, x = p.x, y = p.y}
end)
end end
@ -87,7 +87,7 @@ function shared_draw(draw_action)
end) end)
background_canvas:draw(0, 0, 0, sx, sy) background_canvas:draw(0, 0, 0, sx, sy)
shadow_canvas:draw(6, 6, 0, sx, sy) shadow_canvas:draw(1.5*sx, 1.5*sy, 0, sx, sy)
main_canvas:draw(0, 0, 0, sx, sy) main_canvas:draw(0, 0, 0, sx, sy)
end end
@ -111,6 +111,7 @@ function Star:update(dt)
self.x = self.x + self.v*math.cos(-math.pi/4) self.x = self.x + self.v*math.cos(-math.pi/4)
self.y = self.y + self.v*math.sin(-math.pi/4) self.y = self.y + self.v*math.sin(-math.pi/4)
self.vr = self.vr + self.dvr*dt self.vr = self.vr + self.dvr*dt
if self.x > gw + 64 then self.dead = true end
end end
@ -505,6 +506,10 @@ global_text_tags = {
green5 = TextTag{draw = function(c, i, text) graphics.set_color(green[5]) end}, green5 = TextTag{draw = function(c, i, text) graphics.set_color(green[5]) end},
blue5 = TextTag{draw = function(c, i, text) graphics.set_color(blue[5]) end}, blue5 = TextTag{draw = function(c, i, text) graphics.set_color(blue[5]) end},
bluem5 = TextTag{draw = function(c, i, text) graphics.set_color(blue[-5]) end}, bluem5 = TextTag{draw = function(c, i, text) graphics.set_color(blue[-5]) end},
redm5 = TextTag{draw = function(c, i, text) graphics.set_color(red[-5]) end},
orangem5 = TextTag{draw = function(c, i, text) graphics.set_color(orange[-5]) end},
purplem5 = TextTag{draw = function(c, i, text) graphics.set_color(purple[-5]) end},
yellowm5 = TextTag{draw = function(c, i, text) graphics.set_color(yellow[-5]) end},
wavy = TextTag{update = function(c, dt, i, text) c.oy = 2*math.sin(4*time + i) end}, wavy = TextTag{update = function(c, dt, i, text) c.oy = 2*math.sin(4*time + i) end},
wavy_mid = TextTag{update = function(c, dt, i, text) c.oy = 0.75*math.sin(3*time + i) end}, wavy_mid = TextTag{update = function(c, dt, i, text) c.oy = 0.75*math.sin(3*time + i) end},
wavy_mid2 = TextTag{update = function(c, dt, i, text) c.oy = 0.5*math.sin(3*time + i) end}, wavy_mid2 = TextTag{update = function(c, dt, i, text) c.oy = 0.5*math.sin(3*time + i) end},

28
todo
View File

@ -1,24 +1,4 @@
General balance Lich buff
Trailers Highlander buff
3-4 pure gameplay playthroughs showcasing different builds Concentrated Fire change to "chance to create secondary AoEs on AoE hit" (like Cannoneer's Lv.3)
1 normal 30-40s trailer Change Cannoneer's Lv.3 to 7 repeats, and each repeat should be slightly slower
Misc
Squire's Lv.3 effect
NG+1-10 (difficulty ramps up faster and goes higher than normal at the end, the player also gains more gold per round, and on NG+1 and NG+5 the maximum number of units is increased by 1 (10->11->12)
NG+10 end screen
New music
Credits
Level X/25 text
Engine improvements for after SNKRX release
on_hit:
on_collision_enter/exit are automatically called and automatically call on_hit/on_leave for each object
This enables the definition of on_hit on each object and the question of where the logic should stay is solved/dodged
Spurred by Wall needing to have its own on_hit function to do something when the player hits it, without having to change player code for all Walls,
Defining on_hit on the Wall creation call for that specific Wall, and thus that specific Wall will have this behavior while other walls won't
https://i.imgur.com/asPdpnQ.png ?
Not sure if I should go all the way with event systems like this or only have it work for specific cases, need to think about it more
Rewrite
DPS list - needs every damage event to be logged with source, destination and damage values being detached from intermediary objects (areas, projectiles, etc)