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.passives = passives
trigger:tween(2, main_song_instance, {volume = 0.5, pitch = 1}, math.linear)
steam.friends.setRichPresence('steam_display', '#StatusFull')
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.effects = Group()
self.ui = Group()
self.credits = Group()
self.main:disable_collision_between('player', 'player')
self.main:disable_collision_between('player', '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.color = self.color or fg[0]
self.level = 25
self.can_quit = true
-- Spawn solids and player
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
@ -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, action = function(x, y)
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}
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:pull('condition2', 0.0625)
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_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]
@ -114,7 +125,7 @@ function Arena:on_enter(from, level, units, passives)
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.can_quit = true
self:quit()
if self.level == 6 then
state.achievement_speed_booster = true
system.save_state()
@ -172,7 +183,7 @@ function Arena:on_enter(from, level, units, passives)
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
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
if self.wave > self.max_waves then return end
self.hfx:use('condition1', 0.25, 200, 10)
@ -186,6 +197,8 @@ function Arena:on_enter(from, level, units, passives)
end)
end
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_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]
@ -195,12 +208,12 @@ function Arena:on_enter(from, level, units, passives)
end)
end, self.max_waves+1)
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)
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, 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'}}}
end
end
@ -236,28 +249,143 @@ function Arena:on_enter(from, level, units, passives)
self.psyker_level = class_levels.psyker
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
function Arena:update(dt)
if input.k.pressed then
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}
Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = 'randomizer'}
end}
if main_song_instance:isStopped() then
main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
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
trigger:tween(0.25, _G, {slow_amount = 0}, math.linear, function()
slow_amount = 0
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_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_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.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'}}}
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'}}}
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 = '[bg10]turn left turn right', font = pixul_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)
trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function()
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')
else
trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function()
@ -265,21 +393,21 @@ function Arena:update(dt)
self.paused = false
self.paused_t1.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_t2 = nil
self.paused_t3 = nil
self.paused_t4 = nil
self.paused_t5 = nil
self.paused_t6 = 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
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
self.transitioning = true
ui_transition2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
@ -289,7 +417,7 @@ function Arena:update(dt)
slow_amount = 1
gold = 2
passives = {}
cascade_instance:stop()
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'},
@ -301,189 +429,214 @@ function Arena:update(dt)
main:go_to('buy_screen', 0, {}, passives)
end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)}
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
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.main:update(dt*slow_amount*self.main_slow_amount)
self.post_main:update(dt*slow_amount)
self.effects: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
state.achievement_new_game_5 = true
system.save_state()
steam.userStats.setAchievement('NEW_GAME_5')
steam.userStats.storeStats()
self.win_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 30, force_update = true, lines = {
{text = "[fg]now you've really beaten the game!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{text = "[fg]thanks a lot for playing it and completing it entirely!", font = pixul_font, alignment = 'center', height_multiplier = 1.24},
{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)
if new_game_plus == 10 then
state.achievement_game_complete = true
system.save_state()
steam.userStats.setAchievement('GAME_COMPLETE')
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
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
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')
if new_game_plus == 5 then
state.achievement_new_game_5 = true
system.save_state()
steam.userStats.setAchievement('GAME_COMPLETE')
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
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
@ -533,10 +686,12 @@ function Arena:draw()
end
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
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
@ -548,15 +703,93 @@ function Arena:die()
{text = '[wavy_mid, cbyc]you died...', font = fat_font, alignment = 'center', height_multiplier = 1.25},
}}
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]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
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()
self.transitioning = true
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)
if self.died then return end
if self.arena_clear_text then return end
if self.quitting then return end
j = j or 1
n = n or 4
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.effects:destroy()
self.ui:destroy()
self.t:destroy()
self.main = nil
self.effects = nil
self.ui = nil
@ -18,10 +19,18 @@ function BuyScreen:on_exit()
self.party_text = nil
self.sets_text = nil
self.items_text = nil
self.ng_text = nil
self.characters = nil
self.sets = nil
self.cards = 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
@ -32,7 +41,7 @@ function BuyScreen:on_enter(from, level, units, passives)
camera.x, camera.y = gw/2, gh/2
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.first_screen = true
end
@ -43,6 +52,7 @@ function BuyScreen:on_enter(from, level, units, passives)
self.main = Group()
self.effects = Group()
self.ui = Group()
self.tutorial = Group()
self:set_cards()
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)
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}
-- WishlistButton{group = self.main, x = gw - 147, y = gh - 20, parent = self}
GoButton{group = self.main, x = gw - 90, 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
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)
cascade_instance.pitch = 1
self.main:update(dt*slow_amount)
self.effects:update(dt*slow_amount)
self.ui:update(dt*slow_amount)
if not self.in_tutorial then
self.main: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.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
if self.in_tutorial and input.escape.pressed then
self:quit_tutorial()
end
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
@ -88,40 +159,58 @@ function BuyScreen:draw()
if new_game_plus > 0 then
self.ng_text:draw(240, 20)
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
function BuyScreen:buy(character, i)
local bought
if table.any(self.units, function(v) return v.character == character end) and gold >= character_tiers[character] then
gold = gold - character_tiers[character]
self.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}}
for _, unit in ipairs(self.units) do
if unit.character == character then
if unit.level == 1 then
unit.reserve[1] = unit.reserve[1] + 1
if unit.reserve[1] > 1 then
unit.reserve[1] = 0
unit.level = 2
unit.spawn_effect = true
end
elseif unit.level == 2 then
unit.reserve[1] = unit.reserve[1] + 1
if unit.reserve[1] > 2 then
if unit.reserve[2] == 1 then
unit.reserve[2] = 0
if table.any(self.units, function(v) return v.character == character and v.level == 3 end) then
if not self.info_text then
self.info_text = InfoText{group = main.current.ui}
self.info_text:activate({
{text = "[fg]this unit has already reached max level", font = pixul_font, alignment = 'center'},
}, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw - 140, gh - 20
end
self.t:after(2, function() self.info_text:deactivate(); self.info_text.dead = true; self.info_text = nil end, 'info_text')
else
gold = gold - character_tiers[character]
self.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}}
for _, unit in ipairs(self.units) do
if unit.character == character then
if unit.level == 1 then
unit.reserve[1] = unit.reserve[1] + 1
if unit.reserve[1] > 1 then
unit.reserve[1] = 0
unit.level = 3
unit.level = 2
unit.spawn_effect = true
else
unit.reserve[2] = unit.reserve[2] + 1
unit.reserve[1] = 0
end
elseif unit.level == 2 then
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
bought = true
end
bought = true
else
if #self.units >= max_units then
if not self.info_text then
@ -217,6 +306,7 @@ end
function SteamFollowButton:update(dt)
self:update_game_object(dt)
if main.current.in_credits then return end
if self.selected and input.m1.pressed then
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
@ -237,6 +327,7 @@ end
function SteamFollowButton:on_mouse_enter()
if main.current.in_credits then return end
love.mouse.setCursor(love.mouse.getSystemCursor'hand')
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}
@ -247,6 +338,7 @@ end
function SteamFollowButton:on_mouse_exit()
if main.current.in_credits then return end
love.mouse.setCursor()
self.text:set_text{{text = '[greenm5]follow me on steam!', font = pixul_font, alignment = 'center'}}
self.selected = false
@ -327,6 +419,7 @@ end
function RestartButton:update(dt)
if main.current.in_credits then return end
self:update_game_object(dt)
if self.selected and input.m1.pressed then
@ -338,7 +431,7 @@ function RestartButton:update(dt)
slow_amount = 1
gold = 2
passives = {}
cascade_instance:stop()
main_song_instance:stop()
new_game_plus = new_game_plus + 1
state.new_game_plus = new_game_plus
system.save_state()
@ -358,6 +451,7 @@ end
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}
pop2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
self.selected = true
@ -367,6 +461,7 @@ end
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.selected = false
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:implement(GameObject)
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()
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)
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
@ -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:implement(GameObject)
function CharacterPart:init(args)
@ -571,6 +765,7 @@ function CharacterPart:on_mouse_enter()
}, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw/2, gh/2 + 10
--[[
if self.parent:is(BuyScreen) then
for _, set in ipairs(self.parent.sets) do
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
@ -600,6 +796,7 @@ function CharacterPart:on_mouse_exit()
end
self.info_text = nil
--[[
if self.parent:is(BuyScreen) then
for _, set in ipairs(self.parent.sets) do
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
@ -619,6 +817,7 @@ function CharacterPart:die()
self.info_text = nil
end
--[[
if self.selected and self.parent:is(BuyScreen) then
for _, set in ipairs(self.parent.sets) do
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
@ -876,7 +1076,7 @@ function CharacterIcon:init(args)
self:init_game_object(args)
self.shape = Rectangle(self.x, self.y, 40, 20)
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
@ -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: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+.
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}
for _, enemy in ipairs(enemies) do
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)
@ -96,14 +96,14 @@ function Seeker:init(args)
LightningLine{group = main.current.effects, src = self, dst = enemy, color = blue[0]}
enemy:hit(10000)
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4}
local n = 8 + new_game_plus
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
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 = (1 + 0.2*new_game_plus)*enemy.dmg} end
end
end)
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(2, function()
self.t:every(6, function()
local attack = random:table{'explode', 'swarm', 'force', 'speed_boost'}
if attack == 'explode' then
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]}
enemy:hit(10000)
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4}
local n = 8 + new_game_plus
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
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 = (1 + 0.2*new_game_plus)*enemy.dmg} end
end
elseif attack == 'swarm' then
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}
for _, enemy in ipairs(enemies) do
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
@ -198,7 +198,7 @@ function Seeker:init(args)
end)
elseif self.tank then
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.hp = self.max_hp
elseif self.shooter then
@ -212,12 +212,12 @@ function Seeker:init(args)
self.hfx:use('hit', 0.25, 200, 10, 0.1)
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}
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,
dmg = (new_game_plus*0.1 + 1.5)*self.dmg}
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.2 + 1)*self.dmg}
end)
end
end, nil, nil, 'shooter')
end)
end)
elseif self.spawner then
self.color = purple[0]:clone()
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.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)
self.speed_boosting_mvspd_m = (3 + 0.1*self.level + 0.1*new_game_plus)*n
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.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
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)
@ -432,7 +432,7 @@ function Seeker:hit(damage, projectile)
if self.exploder then
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4}
trigger:after(0.01, function()
local n = 8 + new_game_plus
local n = 8 + new_game_plus*2
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}
end
@ -468,7 +468,7 @@ function Seeker:hit(damage, projectile)
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
trigger:after(0.01, function()
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
@ -503,7 +503,7 @@ function Seeker:slow(amount, duration)
end
function Seeker:curse(curse, duration, arg1, arg2)
function Seeker: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
@ -520,6 +520,7 @@ function Seeker:curse(curse, duration, arg1, arg2)
end
end
buff1:play{pitch = random:float(0.65, 0.75), volume = 0.25}
if curse == 'launcher' then
self.t:after(duration*curse_m, function()
self.launcher_push = arg1
@ -534,6 +535,7 @@ function Seeker:curse(curse, duration, arg1, arg2)
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
@ -558,6 +560,7 @@ EnemyCritter:implement(Physics)
EnemyCritter:implement(Unit)
function EnemyCritter:init(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:set_as_rectangle(7, 4, 'dynamic', 'enemy_projectile')
self:set_restitution(0.5)
@ -574,6 +577,11 @@ end
function EnemyCritter:update(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
local v = math.length(self:get_velocity())
if v < 50 then
@ -665,6 +673,61 @@ function EnemyCritter:speed_boost(duration)
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()
@ -672,6 +735,7 @@ EnemyProjectile:implement(GameObject)
EnemyProjectile:implement(Physics)
function EnemyProjectile:init(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')
end

View File

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

View File

@ -47,6 +47,7 @@ function engine_run(config)
if not web then
love.filesystem.setIdentity(config.game_name)
steam.init()
system.load_state()
local _, _, flags = love.window.getMode()
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)
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)
else
@ -72,6 +78,7 @@ function engine_run(config)
ww, wh = 960, 540
end
love.window.setIcon(love.image.newImageData('assets/images/icon.png'))
love.graphics.setBackgroundColor(0, 0, 0, 1)
love.graphics.setColor(1, 1, 1, 1)
love.joystick.loadGamepadMappings("engine/gamecontrollerdb.txt")

View File

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

110
main.lua
View File

@ -11,8 +11,8 @@ require 'media'
function init()
shared_init()
input:bind('move_left', {'a', 'left', 'dpleft'})
input:bind('move_right', {'d', 'right', 'dpright'})
input:bind('move_left', {'a', 'left', 'dpleft', 'm1'})
input:bind('move_right', {'d', 'right', 'dpright', 'm2'})
input:bind('move_up', {'w', 'up', 'dpup'})
input:bind('move_down', {'s', 'down', 'dpdown'})
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)
rogue_crit1 = Sound('Dagger Stab (Flesh) 4.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')
exploder_elite = Image('exploder_elite')
@ -162,6 +168,7 @@ function init()
hive = Image('hive')
void_rift = Image('void_rift')
star = Image('star')
arrow = Image('arrow')
class_colors = {
['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,
['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,
['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,
['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,
@ -462,7 +469,7 @@ function init()
['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,
['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,
['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,
@ -489,7 +496,7 @@ function init()
['saboteur'] = '[orange]Demoman',
['stormweaver'] = '[blue]Wide Lightning',
['sage'] = '[purple]Dimension Compression',
['squire'] = '[yellow]Repair',
['squire'] = '[yellow]Shiny Gear',
['cannoneer'] = '[orange]Cannon Barrage',
['dual_gunner'] = '[green]Gun Kata',
['hunter'] = '[green]Feral Pack',
@ -533,7 +540,7 @@ function init()
['saboteur'] = '[light_bg]Demoman',
['stormweaver'] = '[light_bg]Wide Lightning',
['sage'] = '[light_bg]Dimension Compression',
['squire'] = '[light_bg]Repair',
['squire'] = '[light_bg]Shiny Gear',
['cannoneer'] = '[light_bg]Cannon Barrage',
['dual_gunner'] = '[light_bg]Gun Kata',
['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,
['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,
['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,
['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,
@ -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,
['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,
['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,
['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,
@ -648,7 +655,7 @@ function init()
['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,
['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,
}
@ -951,7 +958,7 @@ function init()
['magnify'] = '[yellow]+25%[fg] area size',
['concentrated_fire'] = '[yellow]-50%[fg] area size and [yellow]+100%[fg] area damage',
['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',
['blessing'] = '[yellow]+20%[fg] healing effectiveness',
['hex_master'] = '[yellow]+25%[fg] curse duration',
@ -1031,20 +1038,20 @@ function init()
[9] = {50, 30, 15, 5},
[10] = {50, 30, 15, 5},
[11] = {45, 30, 20, 5},
[12] = {45, 30, 20, 5},
[13] = {40, 30, 20, 10},
[14] = {40, 30, 20, 10},
[15] = {35, 35, 20, 10},
[16] = {30, 40, 20, 10},
[17] = {20, 40, 25, 15},
[18] = {20, 40, 25, 15},
[19] = {15, 40, 30, 15},
[20] = {10, 40, 30, 20},
[21] = {5, 40, 35, 20},
[22] = {5, 35, 35, 25},
[23] = {5, 35, 35, 25},
[24] = {0, 30, 40, 30},
[25] = {0, 25, 40, 35},
[12] = {40, 30, 20, 10},
[13] = {35, 30, 25, 10},
[14] = {30, 30, 25, 15},
[15] = {25, 30, 30, 15},
[16] = {25, 25, 30, 20},
[17] = {20, 25, 35, 20},
[18] = {15, 25, 35, 25},
[19] = {10, 25, 40, 25},
[20] = {5, 25, 40, 30},
[21] = {0, 25, 40, 35},
[22] = {0, 20, 40, 40},
[23] = {0, 20, 35, 45},
[24] = {0, 10, 30, 60},
[25] = {0, 0, 0, 100},
}
level_to_gold_gained = {
@ -1159,48 +1166,81 @@ function init()
}
gold = 2
passives = {}
steam.userStats.requestCurrentStats()
system.load_state()
new_game_plus = state.new_game_plus or 0
steam.userStats.requestCurrentStats()
max_units = 7 + math.floor(new_game_plus/2)
max_units = 7 + new_game_plus
main = Main()
-- main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
main:add(BuyScreen'buy_screen')
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: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
function 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 sfx.volume == 0.5 then
sfx.volume = 0
state.volume_muted = true
elseif sfx.volume == 0 then
sfx.volume = 0.5
state.volume_muted = false
end
end
if input.m.pressed then
if music.volume == 0.5 then
state.music_muted = true
music.volume = 0
elseif music.volume == 0 then
music.volume = 0.5
state.music_muted = false
end
end
if input.k.pressed then
steam.userStats.setAchievement('ASCENSION_1')
steam.userStats.storeStats()
end
if input.l.pressed then
if input.f12.pressed then
steam.userStats.resetAllStats(true)
steam.userStats.storeStats()
end

View File

@ -11,7 +11,6 @@ function Media:on_enter(from)
self.effects = Group()
self.ui = Group()
self.mode = 'achievements'
graphics.set_background_color(fg[0])
end
@ -27,8 +26,11 @@ function Media:draw()
self.main:draw()
self.effects: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
--[[
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
local line = table.remove(self.lines, min_i)
table.insert(self.points, line.x1)
table.insert(self.points, line.y1)
if line then
table.insert(self.points, line.x1)
table.insert(self.points, line.y1)
end
end
end
@ -230,14 +232,14 @@ function Unit:calculate_stats(first_run)
if self.boss then
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}
self.base_hp = 100 + (new_game_plus*5) + (100 + new_game_plus*5)*y[x]
self.base_dmg = 50 + 15*y[x]
self.base_hp = 100 + (new_game_plus*10) + (90 + new_game_plus*14)*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]
else
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}
self.base_hp = 25 + (new_game_plus*2) + (25 + new_game_plus*2)*y[x]
self.base_dmg = 10 + 3*y[x]
self.base_hp = 22 + (new_game_plus*3) + (17 + new_game_plus*2.6)*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]
end
elseif self:is(Saboteur) then

View File

@ -204,7 +204,7 @@ function Player:init(args)
elseif self.character == 'juggernaut' then
self.t:every(8, function()
self:attack(64, {juggernaut_push = true})
self:attack(96, {juggernaut_push = true})
end, nil, nil, 'attack')
elseif self.character == 'lich' then
@ -326,7 +326,7 @@ function Player:init(args)
end, nil, nil, 'shoot')
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()
if self.level == 3 then
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)
for _, enemy in ipairs(enemies) do
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}
LightningLine{group = main.current.effects, src = self, dst = enemy, color = orange[0]}
end
@ -495,8 +495,10 @@ function Player:init(args)
end
end
local mage = random:table(mages)
mage.awakening_aspd_m = 2
mage.awakening_dmg_m = 2
if mage then
mage.awakening_aspd_m = 2
mage.awakening_dmg_m = 2
end
end)
end
@ -552,19 +554,21 @@ function Player:init(args)
end
if self.reinforce then
local units = self:get_all_units()
local any_enchanter = false
for _, unit in ipairs(units) do
if table.any(unit.classes, function(v) return v == 'enchanter' end) then
any_enchanter = true
break
main.current.t:after(0.1, function()
local units = self:get_all_units()
local any_enchanter = false
for _, unit in ipairs(units) do
if table.any(unit.classes, function(v) return v == 'enchanter' end) then
any_enchanter = true
break
end
end
end
if any_enchanter then
self.reinforce_dmg_m = 1.1
self.reinforce_def_m = 1.1
self.reinforce_aspd_m = 1.1
end
if any_enchanter then
self.reinforce_dmg_m = 1.1
self.reinforce_def_m = 1.1
self.reinforce_aspd_m = 1.1
end
end)
end
if self.payback then
@ -602,8 +606,14 @@ function Player:update(dt)
if self.character == 'squire' then
local all_units = self:get_all_units()
for _, unit in ipairs(all_units) do
unit.squire_dmg_m = 1.15
unit.squire_def_m = 1.15
unit.squire_dmg_m = 1.2
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
elseif self.character == 'chronomancer' then
local all_units = self:get_all_units()
@ -733,12 +743,12 @@ function Player:update(dt)
end
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_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_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()
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
end
_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)
knife_hit_wall1:play{pitch = random:float(0.9, 1.1), volume = 0.2}
local r = Unit.bounce(self, nx, ny)
@ -1489,7 +1499,7 @@ function Projectile:on_trigger_enter(other, contact)
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
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}
if self.character == 'spellblade' then
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
if self.character == 'elementor' then
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
enemy:slow(0.4, 6)
end
elseif self.character == 'swordsman' then
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
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
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
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)
else
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
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
@ -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 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
@ -1705,7 +1715,7 @@ function DotArea:init(args)
enemy.pyrod = self
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}
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
@ -1723,7 +1733,7 @@ function DotArea:init(args)
if self.level == 3 then
enemy:slow(0.4, 4)
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}
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
@ -1757,7 +1767,7 @@ function DotArea:init(args)
if #enemies > 0 then self.spring:pull(0.05, 200, 10) end
for _, enemy in ipairs(enemies) do
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}
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
@ -2281,6 +2291,7 @@ end
function Critter:on_trigger_enter(other, contact)
if other:is(Seeker) then
critter2:play{pitch = random:float(0.65, 0.85), volume = 0.1}
self:hit(1)
other:hit(self.dmg, self)
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)
end
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_gradient = GradientImage('vertical', Color(128, 128, 128, 0), Color(0, 0, 0, 0.3))
@ -30,7 +31,10 @@ function shared_init()
sfx = SoundTag()
sfx.volume = 0.5
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)
pixul_font = Font('PixulBrush', 8)
@ -40,13 +44,9 @@ function shared_init()
shadow_shader = Shader(nil, 'shadow.frag')
star_canvas = Canvas(gw, gh, {stencil = true})
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, 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
@ -87,7 +87,7 @@ function shared_draw(draw_action)
end)
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)
end
@ -111,6 +111,7 @@ function Star:update(dt)
self.x = self.x + self.v*math.cos(-math.pi/4)
self.y = self.y + self.v*math.sin(-math.pi/4)
self.vr = self.vr + self.dvr*dt
if self.x > gw + 64 then self.dead = true end
end
@ -505,6 +506,10 @@ global_text_tags = {
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},
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_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},

28
todo
View File

@ -1,24 +1,4 @@
General balance
Trailers
3-4 pure gameplay playthroughs showcasing different builds
1 normal 30-40s trailer
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)
Lich buff
Highlander buff
Concentrated Fire change to "chance to create secondary AoEs on AoE hit" (like Cannoneer's Lv.3)
Change Cannoneer's Lv.3 to 7 repeats, and each repeat should be slightly slower