Week 12-13
640
arena.lua
|
@ -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()
|
||||
|
|
After Width: | Height: | Size: 333 B |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 180 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 175 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 317 KiB |
After Width: | Height: | Size: 311 KiB |
After Width: | Height: | Size: 175 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 9.0 KiB |
350
buy_screen.lua
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
96
enemies.lua
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
12
media.lua
|
@ -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
|
||||
]]--
|
||||
|
|
14
objects.lua
|
@ -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
|
||||
|
|
75
player.lua
|
@ -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
|
||||
|
|
19
shared.lua
|
@ -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
|
@ -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
|
||||
|
|