diff --git a/arena.lua b/arena.lua index 38ae28d..22ac5b1 100644 --- a/arena.lua +++ b/arena.lua @@ -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 diff --git a/assets/images/cluster_elite.png b/assets/images/cluster_elite.png new file mode 100644 index 0000000..ff88e92 Binary files /dev/null and b/assets/images/cluster_elite.png differ diff --git a/assets/images/exploder_elite.png b/assets/images/exploder_elite.png new file mode 100644 index 0000000..a13ddc8 Binary files /dev/null and b/assets/images/exploder_elite.png differ diff --git a/assets/images/forcer_elite.png b/assets/images/forcer_elite.png new file mode 100644 index 0000000..c282e60 Binary files /dev/null and b/assets/images/forcer_elite.png differ diff --git a/assets/images/speed_booster_elite.png b/assets/images/speed_booster_elite.png new file mode 100644 index 0000000..da3f664 Binary files /dev/null and b/assets/images/speed_booster_elite.png differ diff --git a/assets/images/swarmer_elite.png b/assets/images/swarmer_elite.png new file mode 100644 index 0000000..1d2f1d8 Binary files /dev/null and b/assets/images/swarmer_elite.png differ diff --git a/assets/media/achievement_cluster.png b/assets/media/achievement_cluster.png new file mode 100644 index 0000000..bd86294 Binary files /dev/null and b/assets/media/achievement_cluster.png differ diff --git a/assets/media/achievement_cluster_gray.png b/assets/media/achievement_cluster_gray.png new file mode 100644 index 0000000..1c42ed7 Binary files /dev/null and b/assets/media/achievement_cluster_gray.png differ diff --git a/assets/media/achievement_exploder.png b/assets/media/achievement_exploder.png new file mode 100644 index 0000000..0a18391 Binary files /dev/null and b/assets/media/achievement_exploder.png differ diff --git a/assets/media/achievement_exploder_gray.png b/assets/media/achievement_exploder_gray.png new file mode 100644 index 0000000..ca37dbe Binary files /dev/null and b/assets/media/achievement_exploder_gray.png differ diff --git a/assets/media/achievement_forcer.png b/assets/media/achievement_forcer.png new file mode 100644 index 0000000..b7e51ca Binary files /dev/null and b/assets/media/achievement_forcer.png differ diff --git a/assets/media/achievement_forcer_gray.png b/assets/media/achievement_forcer_gray.png new file mode 100644 index 0000000..d65ee61 Binary files /dev/null and b/assets/media/achievement_forcer_gray.png differ diff --git a/assets/media/achievement_game_complete.png b/assets/media/achievement_game_complete.png index cd8333d..955075f 100644 Binary files a/assets/media/achievement_game_complete.png and b/assets/media/achievement_game_complete.png differ diff --git a/assets/media/achievement_game_complete_gray.png b/assets/media/achievement_game_complete_gray.png index 9be4fc9..b05a6d9 100644 Binary files a/assets/media/achievement_game_complete_gray.png and b/assets/media/achievement_game_complete_gray.png differ diff --git a/assets/media/achievement_new_game_1.png b/assets/media/achievement_new_game_1.png new file mode 100644 index 0000000..eb2313f Binary files /dev/null and b/assets/media/achievement_new_game_1.png differ diff --git a/assets/media/achievement_new_game_1_gray.png b/assets/media/achievement_new_game_1_gray.png new file mode 100644 index 0000000..03cafff Binary files /dev/null and b/assets/media/achievement_new_game_1_gray.png differ diff --git a/assets/media/achievement_new_game_5.png b/assets/media/achievement_new_game_5.png new file mode 100644 index 0000000..a3bb71e Binary files /dev/null and b/assets/media/achievement_new_game_5.png differ diff --git a/assets/media/achievement_new_game_5_gray.png b/assets/media/achievement_new_game_5_gray.png new file mode 100644 index 0000000..bc1d26d Binary files /dev/null and b/assets/media/achievement_new_game_5_gray.png differ diff --git a/assets/media/achievement_speed_booster.png b/assets/media/achievement_speed_booster.png new file mode 100644 index 0000000..387def4 Binary files /dev/null and b/assets/media/achievement_speed_booster.png differ diff --git a/assets/media/achievement_speed_booster_gray.png b/assets/media/achievement_speed_booster_gray.png new file mode 100644 index 0000000..9cc26d1 Binary files /dev/null and b/assets/media/achievement_speed_booster_gray.png differ diff --git a/assets/media/achievement_swarmer.png b/assets/media/achievement_swarmer.png new file mode 100644 index 0000000..884c130 Binary files /dev/null and b/assets/media/achievement_swarmer.png differ diff --git a/assets/media/achievement_swarmer_gray.png b/assets/media/achievement_swarmer_gray.png new file mode 100644 index 0000000..00fa33b Binary files /dev/null and b/assets/media/achievement_swarmer_gray.png differ diff --git a/assets/media/event_cover.png b/assets/media/event_cover.png index 96a1961..8a409d7 100644 Binary files a/assets/media/event_cover.png and b/assets/media/event_cover.png differ diff --git a/assets/media/library_capsule.png b/assets/media/library_capsule.png index c32fb6b..f9e3a11 100644 Binary files a/assets/media/library_capsule.png and b/assets/media/library_capsule.png differ diff --git a/assets/media/main_capsule.png b/assets/media/main_capsule.png index e027963..b488a52 100644 Binary files a/assets/media/main_capsule.png and b/assets/media/main_capsule.png differ diff --git a/buy_screen.lua b/buy_screen.lua index a4908b2..6a528fe 100644 --- a/buy_screen.lua +++ b/buy_screen.lua @@ -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 diff --git a/devlog.md b/devlog.md index 77551b7..b307584 100644 --- a/devlog.md +++ b/devlog.md @@ -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. diff --git a/enemies.lua b/enemies.lua index cab0ea7..df5df88 100644 --- a/enemies.lua +++ b/enemies.lua @@ -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 diff --git a/engine/game/group.lua b/engine/game/group.lua index 038f0c4..cfe002c 100644 --- a/engine/game/group.lua +++ b/engine/game/group.lua @@ -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 diff --git a/engine/game/trigger.lua b/engine/game/trigger.lua index 2e8bb8c..d3b9b21 100644 --- a/engine/game/trigger.lua +++ b/engine/game/trigger.lua @@ -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]) diff --git a/main.lua b/main.lua index 0b47bca..2fb2efe 100644 --- a/main.lua +++ b/main.lua @@ -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 diff --git a/media.lua b/media.lua index cc4c722..4c5fb81 100644 --- a/media.lua +++ b/media.lua @@ -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 diff --git a/player.lua b/player.lua index 57c3d86..3a563e8 100644 --- a/player.lua +++ b/player.lua @@ -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 diff --git a/shared.lua b/shared.lua index c27c72d..b0d0560 100644 --- a/shared.lua +++ b/shared.lua @@ -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() diff --git a/todo b/todo index c88b736..7a6aff2 100644 --- a/todo +++ b/todo @@ -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)