Day 69-72

master
a327ex 2021-04-29 20:38:36 -03:00
parent 8970ce92bc
commit e60c843848
35 changed files with 464 additions and 104 deletions

244
arena.lua
View File

@ -65,12 +65,18 @@ function Arena:on_enter(from, level, units, passives)
for i, unit in ipairs(units) do
if i == 1 then
self.player = Player{group = self.main, x = gw/2, y = gh/2 + 16, leader = true, character = unit.character, level = unit.level, passives = self.passives}
self.player = Player{group = self.main, x = gw/2, y = gh/2 + 16, leader = true, character = unit.character, level = unit.level, passives = self.passives, ii = i}
else
self.player:add_follower(Player{group = self.main, character = unit.character, level = unit.level, passives = self.passives})
self.player:add_follower(Player{group = self.main, character = unit.character, level = unit.level, passives = self.passives, ii = i})
end
end
local units = self.player:get_all_units()
for _, unit in ipairs(units) do
local chp = CharacterHP{group = self.effects, x = self.x1 + 8 + (unit.ii-1)*22, y = self.y2 + 14, parent = unit}
unit.character_hp = chp
end
if self.level == 1000 then
self.level_1000_text = Text2{group = self.ui, x = gw/2, y = gh/2, lines = {{text = '[fg, wavy_mid]SNKRX', font = fat_font, alignment = 'center'}}}
-- self.level_1000_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 64, lines = {{text = '[fg, wavy_mid]SNKRX', font = pixul_font, alignment = 'center'}}}
@ -104,7 +110,35 @@ function Arena:on_enter(from, level, units, passives)
end)
end)
end)
self.t:every(function() return self.start_time <= 0 and (self.boss and self.boss.dead) and #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.can_quit = true 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 end, function()
self.can_quit = true
if self.level == 6 then
state.achievement_speed_booster = true
system.save_state()
steam.userStats.setAchievement('SPEED_BOOSTER')
steam.userStats.storeStats()
elseif self.level == 12 then
state.achievement_exploder = true
system.save_state()
steam.userStats.setAchievement('EXPLODER')
steam.userStats.storeStats()
elseif self.level == 18 then
state.achievement_swarmer = true
system.save_state()
steam.userStats.setAchievement('SWARMER')
steam.userStats.storeStats()
elseif self.level == 24 then
state.achievement_forcer = true
system.save_state()
steam.userStats.setAchievement('FORCER')
steam.userStats.storeStats()
elseif self.level == 25 then
state.achievement_cluster = true
system.save_state()
steam.userStats.setAchievement('CLUSTER')
steam.userStats.storeStats()
end
end)
end)
else
-- Set win condition and enemy spawns
@ -174,13 +208,13 @@ function Arena:on_enter(from, level, units, passives)
end, self.time_left)
self.t:every_immediate(2, function()
if #self.main:get_objects_by_classes(self.enemies) <= 0 or love.timer.getTime() - self.last_spawn_enemy_time >= 8 and not self.transitioning then
if #self.main:get_objects_by_classes(self.enemies) <= 0 or love.timer.getTime() - self.last_spawn_enemy_time >= 8 and not self.transitioning and not self.can_quit then
self:spawn_distributed_enemies()
end
end, self.time_left/2)
end)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.time_left <= 0 end, function() self.can_quit = true end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.time_left <= 0 and not self.can_quit end, function() self.can_quit = true end)
end
if self.level == 18 and self.trailer then
@ -314,17 +348,140 @@ function Arena:update(dt)
camera.x, camera.y = gw/2, gh/2
self.win_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 66, lines = {{text = '[wavy_mid, cbyc2]congratulations!', font = fat_font, alignment = 'center'}}}
self.t:after(2.5, function()
self.win_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 20, lines = {
{text = "[fg]you've beaten the game!", font = pixul_font, alignment = 'center', height_multiplier = 1.2},
{text = "[fg]i made this game in 2 months as a dev challenge", font = pixul_font, alignment = 'center', height_multiplier = 1.2},
{text = "[fg]and i'm happy with how it turned out!", font = pixul_font, alignment = 'center', height_multiplier = 1.2},
{text = "[fg]if you liked it too and want to play more games like this:", font = pixul_font, alignment = 'center', height_multiplier = 5},
{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 + 37}
RestartButton{group = self.ui, x = gw - 40, y = gh - 20}
if new_game_plus == 10 then
else
self.win_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 20, lines = {
{text = "[fg]you've beaten the game!", font = pixul_font, alignment = 'center', height_multiplier = 1.2},
{text = "[fg]i made this game in 3 months as a dev challenge", font = pixul_font, alignment = 'center', height_multiplier = 1.2},
{text = "[fg]and i'm happy with how it turned out!", font = pixul_font, alignment = 'center', height_multiplier = 1.2},
{text = "[fg]if you liked it too and want to play more games like this:", font = pixul_font, alignment = 'center', height_multiplier = 5},
{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 + 37}
RestartButton{group = self.ui, x = gw - 40, y = gh - 20}
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
if new_game_plus == 5 then
state.achievement_new_game_5 = true
system.save_state()
steam.userStats.setAchievement('NEW_GAME_5')
steam.userStats.storeStats()
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
--[[
state.achievement_speed_booster = true
system.save_state()
steam.userStats.setAchievement('SPEED_BOOSTER')
steam.userStats.storeStats()
]]--
end
else
@ -359,9 +516,6 @@ function Arena:draw()
self.main:draw()
self.post_main:draw()
self.effects:draw()
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.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()
graphics.draw_with_mask(function()
star_canvas:draw(0, 0, 0, 1, 1)
@ -408,6 +562,11 @@ function Arena:draw()
end
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.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()
end
@ -550,3 +709,52 @@ function Arena:spawn_n_enemies(p, j, n)
end}
end, n, nil, 'spawn_enemies_' .. j)
end
CharacterHP = Object:extend()
CharacterHP:implement(GameObject)
function CharacterHP:init(args)
self:init_game_object(args)
self.hfx:add('hit', 1)
self.cooldown_ratio = 0
end
function CharacterHP:update(dt)
self:update_game_object(dt)
local t, d = self.parent.t:get_timer_and_delay'shoot'
if t and d then
local m = self.parent.t:get_every_multiplier'shoot'
self.cooldown_ratio = math.min(t/(d*m), 1)
end
local t, d = self.parent.t:get_timer_and_delay'attack'
if t and d then
local m = self.parent.t:get_every_multiplier'attack'
self.cooldown_ratio = math.min(t/(d*m), 1)
end
local t, d = self.parent.t:get_timer_and_delay'heal'
if t and d then self.cooldown_ratio = math.min(t/d, 1) end
local t, d = self.parent.t:get_timer_and_delay'buff'
if t and d then self.cooldown_ratio = math.min(t/d, 1) end
local t, d = self.parent.t:get_timer_and_delay'spawn'
if t and d then self.cooldown_ratio = math.min(t/d, 1) end
end
function CharacterHP:draw()
graphics.push(self.x, self.y, 0, self.hfx.hit.x, self.hfx.hit.x)
graphics.rectangle(self.x, self.y - 2, 14, 4, 2, 2, self.parent.dead and bg[5] or (self.hfx.hit.f and fg[0] or _G[character_color_strings[self.parent.character]][-2]), 2)
if self.parent.hp > 0 then
graphics.rectangle2(self.x - 7, self.y - 4, 14*(self.parent.hp/self.parent.max_hp), 4, nil, nil, self.parent.dead and bg[5] or (self.hfx.hit.f and fg[0] or _G[character_color_strings[self.parent.character]][-2]))
end
if not self.parent.dead then
graphics.line(self.x - 8, self.y + 5, self.x - 8 + 15.5*self.cooldown_ratio, self.y + 5, self.hfx.hit.f and fg[0] or _G[character_color_strings[self.parent.character]][-2], 2)
end
graphics.pop()
end
function CharacterHP:change_hp()
self.hfx:use('hit', 0.5)
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -317,7 +317,7 @@ function RestartButton:init(args)
self:init_game_object(args)
self.shape = Rectangle(self.x, self.y, pixul_font:get_text_width('restart') + 4, pixul_font.h + 4)
self.interact_with_mouse = true
self.text = Text({{text = '[bg10]restart', font = pixul_font, alignment = 'center'}}, global_text_tags)
self.text = Text({{text = '[bg10]NG+' .. tostring(new_game_plus+1), font = pixul_font, alignment = 'center'}}, global_text_tags)
end
@ -334,6 +334,9 @@ function RestartButton:update(dt)
gold = 2
passives = {}
cascade_instance:stop()
new_game_plus = new_game_plus + 1
state.new_game_plus = new_game_plus
system.save_state()
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)}
@ -353,13 +356,13 @@ function RestartButton:on_mouse_enter()
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]restart', font = pixul_font, alignment = 'center'}}
self.text:set_text{{text = '[fgm5]NG+' .. tostring(new_game_plus+1), font = pixul_font, alignment = 'center'}}
self.spring:pull(0.2, 200, 10)
end
function RestartButton:on_mouse_exit()
self.text:set_text{{text = '[bg10]restart', font = pixul_font, alignment = 'center'}}
self.text:set_text{{text = '[bg10]NG+' .. tostring(new_game_plus+1), font = pixul_font, alignment = 'center'}}
self.selected = false
end
@ -541,8 +544,8 @@ end
function CharacterPart: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, character_colors[self.character])
graphics.print_centered(self.level, pixul_font, self.x + 0.5, self.y + 2, 0, 1, 1, 0, 0, _G[character_color_strings[self.character]][-5])
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
@ -562,15 +565,23 @@ function CharacterPart:on_mouse_enter()
{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 + 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
set:highlight()
end
end
end
end
function CharacterPart:get_sale_price()
local total = 0
total = total + ((self.level == 1 and 1) or (self.level == 2 and 4) or (self.level == 3 and 8))
total = total + ((self.level == 1 and character_tiers[self.character]) or (self.level == 2 and 2*character_tiers[self.character]) or (self.level == 3 and 6*character_tiers[self.character]))
if self.reserve then
if self.reserve[2] then total = total + self.reserve[2]*4 end
if self.reserve[1] then total = total + self.reserve[1] end
if self.reserve[2] then total = total + self.reserve[2]*character_tiers[self.character]*2 end
if self.reserve[1] then total = total + self.reserve[1]*character_tiers[self.character] end
end
return total
end
@ -578,9 +589,19 @@ end
function CharacterPart:on_mouse_exit()
self.selected = false
self.info_text:deactivate()
self.info_text.dead = true
if self.info_text then
self.info_text:deactivate()
self.info_text.dead = true
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
set:unhighlight()
end
end
end
end
@ -592,6 +613,26 @@ function CharacterPart:die()
self.info_text.dead = true
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], set.class) then
set:unhighlight()
end
end
end
end
function CharacterPart:highlight()
self.highlighted = true
self.spring:pull(0.2, 200, 10)
end
function CharacterPart:unhighlight()
self.highlighted = false
self.spring:pull(0.05, 200, 10)
end
@ -863,8 +904,10 @@ end
function CharacterIcon:on_mouse_exit()
self.info_text:deactivate()
self.info_text.dead = true
if self.info_text then
self.info_text:deactivate()
self.info_text.dead = true
end
self.info_text = nil
end
@ -908,14 +951,21 @@ function ClassIcon:draw()
if table.any(self.units, function(v) return v.character == self.character end) then next_n = nil end
end
graphics.rectangle(self.x, self.y, 16, 24, 4, 4, (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, (n >= i) and _G[class_color_strings[self.class]][-5] or bg[10])
graphics.rectangle(self.x, self.y + 26, 16, 16, 3, 3, bg[3])
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
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)
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
if next_n then
if next_n == 1 then
graphics.line(self.x - 3, self.y + 20, self.x - 3, self.y + 25, self.flash and class_colors[self.class] or bg[10], 3)
@ -928,12 +978,21 @@ function ClassIcon:draw()
end
end
elseif i == 3 then
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)
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
if next_n then
if next_n == 1 then
graphics.line(self.x - 3, self.y + 19, self.x - 3, self.y + 22, self.flash and class_colors[self.class] or bg[10], 3)
@ -964,13 +1023,31 @@ function ClassIcon:on_mouse_enter()
{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, gh/2 + 10
if not self.parent:is(ShopCard) then
for _, character in ipairs(self.parent.characters) do
if table.any(character_classes[character.character], function(v) return v == self.class end) then
character:highlight()
end
end
end
end
function ClassIcon:on_mouse_exit()
self.info_text:deactivate()
self.info_text.dead = true
if self.info_text then
self.info_text:deactivate()
self.info_text.dead = true
end
self.info_text = nil
if not self.parent:is(ShopCard) then
for _, character in ipairs(self.parent.characters) do
if table.any(character_classes[character.character], function(v) return v == self.class end) then
character:unhighlight()
end
end
end
end
@ -983,4 +1060,24 @@ function ClassIcon:die(dont_spawn_effect)
self.info_text.dead = true
self.info_text = nil
end
if self.selected and not self.parent:is(ShopCard) then
for _, character in ipairs(self.parent.characters) do
if table.any(character.classes, function(v) return v == self.class end) then
character:highlight()
end
end
end
end
function ClassIcon:highlight()
self.highlighted = true
self.spring:pull(0.2, 200, 10)
end
function ClassIcon:unhighlight()
self.highlighted = false
self.spring:pull(0.05, 200, 10)
end

View File

@ -1060,3 +1060,20 @@ Added an end game screen and started work on making elites spawn throughout the
Elites now spawn at appropriate rates. Testing out the game a little with this and it seems a lot better than before, but I need to balance a few of the elite units more (headbutter and spawner seem weak).
Also went through a bunch of smaller bug fixes and changes as I play the game more and missing details start popping up. I should leave proper balancing for later and start work on final UI improvements tomorrow.
# Day 69-72 - 26-29/04/21
Everything is done except these:
* General balance
* Trailers
* 3-4 pure gameplay playthroughs showcasing different builds
* 1 normal 30-40s trailer
* Misc
* 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
Tomorrow I'll start balancing the game out, which should lead to a bunch of bug fixes and small changes, and after that's done I'll get NG+1-10 working.
Once that's done I'll do credits screen, NG+10 (game complete) screen, and find new music for the game. I want something more ambient generally and less upbeat.

View File

@ -171,8 +171,10 @@ function Seeker:init(args)
if self.speed_booster then
self.color = green[0]:clone()
self.area_sensor = Circle(self.x, self.y, 128)
self.t:after({16, 32}, function() self:hit(10000) end)
elseif self.exploder then
self.color = blue[0]:clone()
self.t:after({16, 32}, function() self:hit(10000) end)
elseif self.headbutter then
self.color = orange[0]:clone()
self.last_headbutt_time = 0
@ -285,7 +287,7 @@ function Seeker:update(dt)
if self.boss then
local enemies = main.current.main:get_objects_by_classes(main.current.enemies)
local x, y = 0, 0
if #enemies > 0 then
if #enemies > 1 then
for _, enemy in ipairs(enemies) do
x = x + enemy.x
y = y + enemy.y

View File

@ -63,9 +63,11 @@ function Group:update(dt)
self.cells = {}
for _, object in ipairs(self.objects) do
local cx, cy = math.floor(object.x/self.cell_size), math.floor(object.y/self.cell_size)
if tostring(cx) == tostring(0/0) or tostring(cy) == tostring(0/0) then goto continue end
if not self.cells[cx] then self.cells[cx] = {} end
if not self.cells[cx][cy] then self.cells[cx][cy] = {} end
table.insert(self.cells[cx][cy], object)
::continue::
end
for i = #self.objects, 1, -1 do

View File

@ -151,13 +151,26 @@ function Trigger:set_every_multiplier(tag, multiplier)
end
function Trigger:get_every_multiplier(tag)
if not self.triggers[tag] then return end
return self.triggers[tag].multiplier
end
-- Returns the elapsed time of a given trigger as a number between 0 and 1.
-- Useful if you need to know where you currently are in the duration of a during call.
function Trigger:get_during_elapsed_time(tag)
if not self.triggers[tag] then return end
return self.triggers[tag].timer/self.triggers[tag].delay
end
function Trigger:get_timer_and_delay(tag)
if not self.triggers[tag] then return end
return self.triggers[tag].timer, self.triggers[tag].delay
end
function Trigger:resolve_delay(delay)
if type(delay) == "table" then
return random:float(delay[1], delay[2])

View File

@ -101,6 +101,11 @@ function init()
rogue_crit2 = Sound('Sword hits another sword 6.ogg', s)
cascade = Sound('Kubbi - Ember - 04 Cascade.ogg', {tags = {music}})
speed_booster_elite = Image('speed_booster_elite')
exploder_elite = Image('exploder_elite')
swarmer_elite = Image('swarmer_elite')
forcer_elite = Image('forcer_elite')
cluster_elite = Image('cluster_elite')
warrior = Image('warrior')
ranger = Image('ranger')
healer = Image('healer')
@ -428,7 +433,7 @@ function init()
character_descriptions = {
['vagrant'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. get_character_stat('vagrant', lvl, 'dmg') .. '[fg] damage' end,
['swordsman'] = function(lvl) return '[fg]deals [yellow]' .. get_character_stat('swordsman', lvl, 'dmg') .. '[fg] damage in an area, deals extra [yellow]' ..
math.round(get_character_stat('swordsman', lvl, 'dmg')/3, 2) .. '[fg] damage per unit hit' end,
math.round(get_character_stat('swordsman', lvl, 'dmg')*0.15, 2) .. '[fg] damage per unit hit' end,
['wizard'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. get_character_stat('wizard', lvl, 'dmg') .. ' AoE[fg] damage' end,
['archer'] = function(lvl) return '[fg]shoots an arrow that deals [yellow]' .. get_character_stat('archer', lvl, 'dmg') .. '[fg] damage and pierces' end,
['scout'] = function(lvl) return '[fg]throws a knife that deals [yellow]' .. get_character_stat('scout', lvl, 'dmg') .. '[fg] damage and chains [yellow]3[fg] times' end,
@ -567,7 +572,7 @@ function init()
['scout'] = function() return '[yellow]+25%[fg] damage per chain and [yellow]+3[fg] chains' end,
['cleric'] = function() return '[fg]heals all units' end,
['outlaw'] = function() return "[yellow]+50%[fg] outlaw attack speed and his knives seek enemies" end,
['blade'] = function() return '[fg]deal additional [yellow]' .. get_character_stat('blade', 3, 'dmg')/2 .. '[fg] damage per enemy hit' end,
['blade'] = function() return '[fg]deal additional [yellow]' .. get_character_stat('blade', 3, 'dmg')/3 .. '[fg] damage per enemy hit' end,
['elementor'] = function() return '[fg]slows enemies by [yellow]60%[fg] for [yellow]6[fg] seconds on hit' end,
['saboteur'] = function() return '[fg]the explosion has [yellow]50%[fg] chance to crit, increasing in size and dealing [yellow]2x[fg] damage' end,
['stormweaver'] = function() return "[fg]chain lightning's trigger area of effect and number of units hit is [yellow]doubled" end,
@ -1148,14 +1153,15 @@ function init()
gold = 2
passives = {}
system.load_state()
new_game_plus = state.new_game_plus or 0
steam.userStats.requestCurrentStats()
main = Main()
main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', 0, {}, passives)
--[[
--main:add(BuyScreen'buy_screen')
--main:go_to('buy_screen', 0, {}, passives)
main:add(Media'media')
main:go_to('media')
]]--
end
@ -1178,6 +1184,16 @@ function update(dt)
music.volume = 0.5
end
end
if input.k.pressed then
steam.userStats.setAchievement('ASCENSION_1')
steam.userStats.storeStats()
end
if input.l.pressed then
steam.userStats.resetAllStats(true)
steam.userStats.storeStats()
end
end
@ -1191,7 +1207,7 @@ end
function love.run()
return engine_run({
game_name = 'SNKRX',
window_width = 480*3,
window_height = 270*3,
window_width = 480*4,
window_height = 270*4,
})
end

View File

@ -12,7 +12,7 @@ function Media:on_enter(from)
self.ui = Group()
self.mode = 'achievements'
graphics.set_background_color(purple[0])
graphics.set_background_color(fg[0])
end
@ -29,6 +29,6 @@ function Media:draw()
self.ui:draw()
if self.mode == 'achievements' then
graphics.print_centered('Lv2', fat_font, 32, 32, 0, 1, 1, 0, 0, purple[-5])
graphics.print_centered('GG', fat_font, 32, 32, 0, 1, 1, 0, 0, fg[-5])
end
end

View File

@ -12,6 +12,7 @@ function Player:init(args)
self:set_as_rectangle(9, 9, 'dynamic', 'player')
self.visual_shape = 'rectangle'
self.classes = character_classes[self.character]
self.damage_dealt = 0
if self.character == 'vagrant' then
self.attack_sensor = Circle(self.x, self.y, 96)
@ -70,7 +71,7 @@ function Player:init(args)
end
heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
end
end)
end, nil, nil, 'heal')
elseif self.character == 'outlaw' then
self.attack_sensor = Circle(self.x, self.y, 96)
@ -103,7 +104,7 @@ function Player:init(args)
Saboteur{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1, crit = (self.level == 3) and random:bool(50)}
end}
end, 2)
end)
end, nil, nil, 'spawn')
elseif self.character == 'stormweaver' then
self.t:every(8, function()
@ -112,7 +113,7 @@ function Player:init(args)
for _, unit in ipairs(units) do
unit:chain_infuse(4)
end
end)
end, nil, nil, 'buff')
elseif self.character == 'sage' then
self.attack_sensor = Circle(self.x, self.y, 96)
@ -121,7 +122,7 @@ function Player:init(args)
if closest_enemy then
self:shoot(self:angle_to_object(closest_enemy))
end
end)
end, nil, nil, 'shoot')
elseif self.character == 'cannoneer' then
self.attack_sensor = Circle(self.x, self.y, 128)
@ -183,12 +184,12 @@ function Player:init(args)
turret:upgrade()
end
end
end)
end, nil, nil, 'spawn')
elseif self.character == 'plague_doctor' then
self.t:every(5, function()
self:dot_attack(24, {duration = 12})
end)
end, nil, nil, 'attack')
if self.level == 3 then
self.t:after(0.01, function()
@ -199,12 +200,12 @@ function Player:init(args)
elseif self.character == 'barbarian' then
self.t:every(8, function()
self:attack(96, {stun = 4})
end)
end, nil, nil, 'attack')
elseif self.character == 'juggernaut' then
self.t:every(8, function()
self:attack(64, {juggernaut_push = true})
end)
end, nil, nil, 'attack')
elseif self.character == 'lich' then
self.attack_sensor = Circle(self.x, self.y, 128)
@ -230,7 +231,7 @@ function Player:init(args)
self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function()
local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies)
if closest_enemy then
self:shoot(self:angle_to_object(closest_enemy), {spawn_critters_on_kill = 3, spawn_critters_on_hit = (self.level == 3 and 3 or 0)})
self:shoot(self:angle_to_object(closest_enemy), {spawn_critters_on_kill = 3, spawn_critters_on_hit = (self.level == 3 and 3 or nil)})
end
end, nil, nil, 'shoot')
@ -246,7 +247,7 @@ function Player:init(args)
elseif self.character == 'launcher' then
self.t:every(8, function()
self:attack(128)
end)
end, nil, nil, 'attack')
elseif self.character == 'bard' then
self.bard_counter = 0
@ -274,24 +275,24 @@ function Player:init(args)
for i = 1, 2 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.dmg, parent = self}
end
end)
end, nil, nil, 'spawn')
else
self.t:every(2, function()
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.35}
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.dmg, parent = self}
end)
end, nil, nil, 'spawn')
end
elseif self.character == 'carver' then
self.t:every(16, function()
Tree{group = main.current.main, x = self.x, y = self.y, color = self.color, parent = self, rs = self.area_size_m*(self.level == 3 and 128 or 64), level = self.level}
end)
end, nil, nil, 'spawn')
elseif self.character == 'bane' then
self.t:every(12, function()
self.dot_area = DotArea{group = main.current.effects, x = self.x, y = self.y, rs = self.area_size_m*128, color = self.color, dmg = self.area_dmg_m*(self.level == 3 and self.dmg or 0),
character = self.character, level = self.level, parent = self, duration = 8}
end)
end, nil, nil, 'spawn')
elseif self.character == 'psykino' then
self.t:every(4, function()
@ -299,7 +300,7 @@ function Player:init(args)
if center_enemy then
ForceArea{group = main.current.effects, x = center_enemy.x, y = center_enemy.y, rs = self.area_size_m*64, color = self.color, character = self.character, level = self.level, parent = self}
end
end)
end, nil, nil, 'attack')
elseif self.character == 'barrager' then
self.barrager_counter = 0
@ -322,7 +323,7 @@ function Player:init(args)
end)
end
end
end)
end, nil, nil, 'shoot')
elseif self.character == 'highlander' then
self.attack_sensor = Circle(self.x, self.y, 64)
@ -358,7 +359,7 @@ function Player:init(args)
heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
end
end)
end, nil, nil, 'heal')
elseif self.character == 'priest' then
if self.level == 3 then
@ -377,7 +378,7 @@ function Player:init(args)
local all_units = self:get_all_units()
for _, unit in ipairs(all_units) do unit:heal(0.2*unit.max_hp*(self.heal_effect_m or 1)) end
heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
end)
end, nil, nil, 'heal')
elseif self.character == 'infestor' then
self.t:every(8, function()
@ -389,7 +390,7 @@ function Player:init(args)
LightningLine{group = main.current.effects, src = self, dst = enemy, color = orange[0]}
end
end
end)
end, nil, nil, 'attack')
elseif self.character == 'flagellant' then
self.t:every(8, function()
@ -422,7 +423,7 @@ function Player:init(args)
unit.flagellant_dmg_m = unit.flagellant_dmg_m + 0.04
end
end
end)
end, nil, nil, 'buff')
end
self:calculate_stats(true)
@ -542,6 +543,9 @@ function Player:init(args)
self.t:every(1, function()
self.unleash_area_dmg_m = self.unleash_area_dmg_m + 0.02
self.unleash_area_size_m = self.unleash_area_size_m + 0.02
if self.dot_area then
self.dot_area:scale(self.unleash_area_size_m)
end
end)
end
@ -707,7 +711,7 @@ function Player:update(dt)
elseif main.current.voider_level == 1 then self.dot_dmg_m = 1.15
else self.dot_dmg_m = 1 end
end
if self.call_of_the_void then self.dot_dmg_m = self.dot_dmg_m*1.25 end
if self.call_of_the_void then self.dot_dmg_m = (self.dot_dmg_m or 1)*1.25 end
if self.ouroboros_technique_l and self.leader then
local units = self:get_all_units()
@ -925,6 +929,8 @@ function Player:hit(damage)
end
end
self.character_hp:change_hp()
if self.hp <= 0 then
if self.divined then
self:heal(self.max_hp)
@ -961,6 +967,8 @@ function Player:heal(amount)
self:show_heal(1.5)
self.hp = self.hp + amount
if self.hp > self.max_hp then self.hp = self.max_hp end
self.character_hp:change_hp()
end
@ -1590,11 +1598,11 @@ function Area:init(args)
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)
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)
elseif self.character == 'blade' and self.level == 3 then
if self.parent.resonance then resonance_dmg = (self.dmg + self.dmg*0.5*#enemies)*0.05*#enemies end
enemy:hit(self.dmg + self.dmg*0.5*#enemies + resonance_dmg)
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)
@ -1802,6 +1810,11 @@ function DotArea:draw()
end
function DotArea:scale(v)
self.shape = Circle(self.x, self.y, (v or 1)*self.rs)
end
ForceArea = Object:extend()
@ -2146,7 +2159,7 @@ function Saboteur:on_collision_enter(other, contact)
if table.any(main.current.enemies, function(v) return other:is(v) end) then
camera:shake(4, 0.5)
local t = {group = main.current.effects, x = self.x, y = self.y, r = self.r, w = (self.crit and 1.5 or 1)*self.area_size_m*64, color = self.color,
dmg = (self.crit and 2 or 1)*self.area_dmg_m*self.actual_dmg*(self.conjurer_buff_m or 1), character = self.character}
dmg = (self.crit and 2 or 1)*self.area_dmg_m*self.actual_dmg*(self.conjurer_buff_m or 1), character = self.character, parent = self.parent}
Area(table.merge(t, mods or {}))
self.dead = true
end

View File

@ -13,9 +13,12 @@ function shared_init()
red = ColorRamp(Color'#e91d39', 0.025),
purple = ColorRamp(Color'#8e559e', 0.025),
}
for name, color in pairs(colors) do _G[name] = color end
for name, color in pairs(colors) do
_G[name] = color
_G[name .. '_transparent'] = Color(color[0].r, color[0].g, color[0].b, 0.5)
_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)
fg_transparent = Color(fg[0].r, fg[0].g, fg[0].b, 0.5)
bg_off = Color(47, 47, 47)
bg_gradient = GradientImage('vertical', Color(128, 128, 128, 0), Color(0, 0, 0, 0.3))
@ -48,6 +51,7 @@ end
function shared_draw(draw_action)
--[[
star_canvas:draw_to(function()
star_group:draw()
end)
@ -70,6 +74,7 @@ function shared_draw(draw_action)
bg_gradient:draw(gw/2, gh/2, 480, 270)
camera:detach()
end)
]]--
main_canvas:draw_to(function()
draw_action()

29
todo
View File

@ -1,28 +1,12 @@
Selling should keep units value unless it's already merged into a higher level
Boss stands still for 1 second before enemies spawn
General balance
* Enemies should have a chance to be spawned with a modifier as levels increase
* Every 3rd level should be wave only
* Remove "enemies killed" mode
Balance playthroughs (record all balance playthroughs as they can be used for trailers; do this after graphical improvements done)
UI improvements
Hover class highlight
DPS list (right)
HP list (bottom)
Item list (left)
Graphical improvements
Further graphical improvements if there's time
Trailers
3-4 pure gameplay playthroughs showcasing different builds
1 normal 30-40s trailer
Misc
* Better pause screen
* End screen
Ascension mode (difficulty ramps up faster and goes higher than normal at the end, the player also gains more gold, and on ascension 1 and 5 the maximum number of units is increased by 1 (10->11->12)
Music for bosses and shop
Achievements
Come up with a few new ones as I play the game more and balance the numbers
Implement all the ones I already uploaded to Steam
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
Engine improvements for after SNKRX release
@ -33,3 +17,6 @@ Engine improvements for after SNKRX release
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)