From 2276925f34c6157a7c422d9f4bc10ef9ead5ac7a Mon Sep 17 00:00:00 2001 From: a327ex Date: Sat, 5 Jun 2021 03:11:23 -0300 Subject: [PATCH] Sorcerer update 4/4 --- arena.lua | 110 +++++++++++++++++++++++++++++++++---------------- buy_screen.lua | 90 ++++++++++++++++++++++++++++------------ enemies.lua | 44 +++++++++++++------- main.lua | 63 ++++++++++++++++------------ media.lua | 13 +++--- objects.lua | 6 +-- player.lua | 43 ++++++++++++------- todo | 46 +++++++++------------ 8 files changed, 260 insertions(+), 155 deletions(-) diff --git a/arena.lua b/arena.lua index 929dbb0..6d88d67 100644 --- a/arena.lua +++ b/arena.lua @@ -327,6 +327,7 @@ function Arena:update(dt) main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5} end + if self.shop_text then self.shop_text:update(dt) end -- print(self.enemy_spawns_prevented) if input.escape.pressed and not self.transitioning and not self.in_credits and not self.choosing_passives then @@ -338,7 +339,7 @@ function Arena:update(dt) self.paused_t2 = Text2{group = self.ui, x = gw/2, y = gh/2 - 92, lines = {{text = '[bg10]turn left turn right', font = pixul_font, alignment = 'center'}}} self.ng_t = Text2{group = self.ui, x = gw/2 + 63, y = gh - 50, lines = {{text = '[bg10]current: ' .. current_new_game_plus, font = pixul_font, alignment = 'center'}}} - self.resume_button = Button{group = self.ui, x = gw/2, y = gh - 200, force_update = true, button_text = 'resume game (esc)', fg_color = 'bg10', bg_color = 'bg', action = function(b) + self.resume_button = Button{group = self.ui, x = gw/2, y = gh - 225, 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 @@ -357,14 +358,16 @@ function Arena:update(dt) 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 if self.screen_shake_button then self.screen_shake_button.dead = true; self.screen_shake_button = nil end + if self.screen_movement_button then self.screen_movement_button.dead = true; self.screen_movement_button = nil end if self.cooldown_snake_button then self.cooldown_snake_button.dead = true; self.cooldown_snake_button = nil end + if self.arrow_snake_button then self.arrow_snake_button.dead = true; self.arrow_snake_button = nil end if self.ng_plus_plus_button then self.ng_plus_plus_button.dead = true; self.ng_plus_plus_button = nil end if self.ng_plus_minus_button then self.ng_plus_minus_button.dead = true; self.ng_plus_minus_button = nil end system.save_state() end, 'pause') end} - self.restart_button = Button{group = self.ui, x = gw/2, y = gh - 175, force_update = true, button_text = 'restart run (r)', fg_color = 'bg10', bg_color = 'bg', action = function(b) + self.restart_button = Button{group = self.ui, x = gw/2, y = gh - 200, 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} @@ -389,7 +392,7 @@ function Arena:update(dt) 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 - 150, force_update = true, button_text = 'toggle sfx (n)', fg_color = 'bg10', bg_color = 'bg', action = function(b) + self.sfx_button = Button{group = self.ui, x = gw/2, y = gh - 175, 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 @@ -401,7 +404,7 @@ function Arena:update(dt) end end} - self.music_button = Button{group = self.ui, x = gw/2, y = gh - 125, force_update = true, button_text = 'toggle music (m)', fg_color = 'bg10', bg_color = 'bg', action = function(b) + self.music_button = Button{group = self.ui, x = gw/2, y = gh - 150, 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 @@ -413,7 +416,7 @@ function Arena:update(dt) end end} - self.video_button_1 = Button{group = self.ui, x = gw/2 - 86, y = gh - 100, force_update = true, button_text = 'window size-', fg_color = 'bg10', bg_color = 'bg', action = function() + self.video_button_1 = Button{group = self.ui, x = gw/2 - 86, y = gh - 125, force_update = true, button_text = 'window size-', fg_color = 'bg10', bg_color = 'bg', action = function() ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} sx, sy = sx - 1, sy - 1 love.window.setMode(480*sx, 270*sy) @@ -421,7 +424,7 @@ function Arena:update(dt) state.fullscreen = false end} - self.video_button_2 = Button{group = self.ui, x = gw/2, y = gh - 100, force_update = true, button_text = 'window size+', fg_color = 'bg10', bg_color = 'bg', action = function() + self.video_button_2 = Button{group = self.ui, x = gw/2, y = gh - 125, force_update = true, button_text = 'window size+', fg_color = 'bg10', bg_color = 'bg', action = function() ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} sx, sy = sx + 1, sy + 1 love.window.setMode(480*sx, 270*sy) @@ -429,7 +432,7 @@ function Arena:update(dt) state.fullscreen = false end} - self.video_button_3 = Button{group = self.ui, x = gw/2 + 79, y = gh - 100, force_update = true, button_text = 'fullscreen', fg_color = 'bg10', bg_color = 'bg', action = function() + self.video_button_3 = Button{group = self.ui, x = gw/2 + 79, y = gh - 125, force_update = true, button_text = 'fullscreen', fg_color = 'bg10', bg_color = 'bg', action = function() ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} local _, _, flags = love.window.getMode() local window_width, window_height = love.window.getDesktopDimensions(flags.display) @@ -439,20 +442,38 @@ function Arena:update(dt) state.fullscreen = true end} - self.screen_shake_button = Button{group = self.ui, x = gw/2 - 57, y = gh - 75, w = 110, force_update = true, button_text = '[bg10]screen shake: ' .. tostring(state.no_screen_shake and 'no' or 'yes'), + self.screen_shake_button = Button{group = self.ui, x = gw/2 - 57, y = gh - 100, w = 110, force_update = true, button_text = '[bg10]screen shake: ' .. tostring(state.no_screen_shake and 'no' or 'yes'), fg_color = 'bg10', bg_color = 'bg', action = function(b) ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} state.no_screen_shake = not state.no_screen_shake b:set_text('screen shake: ' .. tostring(state.no_screen_shake and 'no' or 'yes')) end} - self.cooldown_snake_button = Button{group = self.ui, x = gw/2 + 75, y = gh - 75, w = 145, force_update = true, button_text = '[bg10]cooldowns on snake: ' .. tostring(state.cooldown_snake and 'yes' or 'no'), + self.cooldown_snake_button = Button{group = self.ui, x = gw/2 + 75, y = gh - 100, w = 145, force_update = true, button_text = '[bg10]cooldowns on snake: ' .. tostring(state.cooldown_snake and 'yes' or 'no'), fg_color = 'bg10', bg_color = 'bg', action = function(b) ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} state.cooldown_snake = not state.cooldown_snake b:set_text('cooldowns on snake: ' .. tostring(state.cooldown_snake and 'yes' or 'no')) end} + self.arrow_snake_button = Button{group = self.ui, x = gw/2 + 65, y = gh - 75, w = 125, force_update = true, button_text = '[bg10]arrow on snake: ' .. tostring(state.arrow_snake and 'yes' or 'no'), + fg_color = 'bg10', bg_color = 'bg', action = function(b) + ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + state.arrow_snake = not state.arrow_snake + b:set_text('arrow on snake: ' .. tostring(state.arrow_snake and 'yes' or 'no')) + end} + + self.screen_movement_button = Button{group = self.ui, x = gw/2 - 69, y = gh - 75, w = 135, force_update = true, button_text = '[bg10]screen movement: ' .. tostring(state.no_screen_movement and 'no' or 'yes'), + fg_color = 'bg10', bg_color = 'bg', action = function(b) + ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + state.no_screen_movement = not state.no_screen_movement + if state.no_screen_movement then + camera.x, camera.y = gw/2, gh/2 + camera.r = 0 + end + b:set_text('screen movement: ' .. tostring(state.no_screen_movement and 'no' or 'yes')) + end} + self.ng_plus_minus_button = Button{group = self.ui, x = gw/2 - 58, y = gh - 50, force_update = true, button_text = 'NG+ down', fg_color = 'bg10', bg_color = 'bg', action = function(b) ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5} b.spring:pull(0.2, 200, 10) @@ -495,7 +516,9 @@ function Arena:update(dt) 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.screen_shake_button then self.screen_shake_button.dead = true; self.screen_shake_button = nil end + if self.screen_movement_button then self.screen_movement_button.dead = true; self.screen_movement_button = nil end if self.cooldown_snake_button then self.cooldown_snake_button.dead = true; self.cooldown_snake_button = nil end + if self.arrow_snake_button then self.arrow_snake_button.dead = true; self.arrow_snake_button = nil end if self.quit_button then self.quit_button.dead = true; self.quit_button = nil end if self.ng_plus_plus_button then self.ng_plus_plus_button.dead = true; self.ng_plus_plus_button = nil end if self.ng_plus_minus_button then self.ng_plus_minus_button.dead = true; self.ng_plus_minus_button = nil end @@ -787,32 +810,9 @@ function Arena:quit() self.arena_clear_text.dead = true trigger:tween(1, _G, {slow_amount = 0}, math.linear, function() slow_amount = 0 end, 'slow_amount') 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_1 = random:table_remove(run_passive_pool_by_tiers[tier_1]) - local passive_2 = random:table_remove(run_passive_pool_by_tiers[tier_2]) - local passive_3 = random:table_remove(run_passive_pool_by_tiers[tier_3]) - if passive_1 then - 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, card_i = 1, tier = tier_1, arena = self, passive = passive_1, force_update = true}) - end - if passive_2 then - 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, card_i = 2, tier = tier_2, arena = self, passive = passive_2, force_update = true}) - end - if passive_3 then - 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, card_i = 3, tier = tier_3, arena = self, passive = passive_3, force_update = true}) - end - self.passive_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 65, lines = {{text = '[fg, wavy]choose one', font = fat_font, alignment = 'center'}}} - if not passive_1 and not passive_2 and not passive_3 then - self:transition() - end + self:set_passives() + RerollButton{group = main.current.ui, x = 40, y = 40, parent = self, force_update = true} + self.shop_text = Text({{text = '[wavy_mid, fg]gold: [yellow]' .. gold, font = pixul_font, alignment = 'center'}}, global_text_tags) else self:transition() end @@ -821,6 +821,44 @@ function Arena:quit() end +function Arena:set_passives(from_reroll) + if from_reroll then + self:restore_passives_to_pool(0) + self.cards[1].dead = true + self.cards[2].dead = true + self.cards[3].dead = true + self.cards = {} + 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_1 = random:table_remove(run_passive_pool_by_tiers[tier_1]) + local passive_2 = random:table_remove(run_passive_pool_by_tiers[tier_2]) + local passive_3 = random:table_remove(run_passive_pool_by_tiers[tier_3]) + if passive_1 then + 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, card_i = 1, tier = tier_1, arena = self, passive = passive_1, force_update = true}) + end + if passive_2 then + 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, card_i = 2, tier = tier_2, arena = self, passive = passive_2, force_update = true}) + end + if passive_3 then + 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, card_i = 3, tier = tier_3, arena = self, passive = passive_3, force_update = true}) + end + self.passive_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 65, lines = {{text = '[fg, wavy]choose one', font = fat_font, alignment = 'center'}}} + if not passive_1 and not passive_2 and not passive_3 then + self:transition() + end +end + + function Arena:restore_passives_to_pool(j) for i = 1, 3 do if i ~= j then @@ -881,6 +919,8 @@ function Arena:draw() 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.shop_text then self.shop_text:draw(40, 20) end + 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 diff --git a/buy_screen.lua b/buy_screen.lua index f1d0a61..d338998 100644 --- a/buy_screen.lua +++ b/buy_screen.lua @@ -773,9 +773,14 @@ RerollButton = Object:extend() RerollButton:implement(GameObject) function RerollButton:init(args) self:init_game_object(args) - self.shape = Rectangle(self.x, self.y, 54, 16) self.interact_with_mouse = true - self.text = Text({{text = '[bg10]reroll: [yellow]2', font = pixul_font, alignment = 'center'}}, global_text_tags) + if self.parent:is(BuyScreen) then + self.shape = Rectangle(self.x, self.y, 54, 16) + self.text = Text({{text = '[bg10]reroll: [yellow]2', font = pixul_font, alignment = 'center'}}, global_text_tags) + elseif self.parent:is(Arena) then + self.shape = Rectangle(self.x, self.y, 60, 16) + self.text = Text({{text = '[bg10]reroll: [yellow]15', font = pixul_font, alignment = 'center'}}, global_text_tags) + end end @@ -783,26 +788,49 @@ function RerollButton:update(dt) self:update_game_object(dt) if self.selected and input.m1.pressed then - if gold < 2 then - self.spring:pull(0.2, 200, 10) - self.selected = true - error1:play{pitch = random:float(0.95, 1.05), volume = 0.5} - if not self.info_text then - self.info_text = InfoText{group = main.current.ui} - self.info_text:activate({ - {text = '[fg]not enough gold', 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 + if gold < 2 then + self.spring:pull(0.2, 200, 10) + self.selected = true + error1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + if not self.info_text then + self.info_text = InfoText{group = main.current.ui} + self.info_text:activate({ + {text = '[fg]not enough gold', 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 + end + self.t:after(2, function() self.info_text:deactivate(); self.info_text.dead = true; self.info_text = nil end, 'info_text') + else + ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.parent:set_cards(random:int(1, 25), true) + self.selected = true + self.spring:pull(0.2, 200, 10) + gold = gold - 2 + self.parent.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}} + system.save_run(self.parent.level == 1 and 0 or self.parent.level, gold, self.parent.units, passives, run_passive_pool_by_tiers, locked_state) + end + elseif self.parent:is(Arena) then + if gold < 15 then + self.spring:pull(0.2, 200, 10) + self.selected = true + error1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + if not self.info_text then + self.info_text = InfoText{group = main.current.ui, force_update = true} + self.info_text:activate({ + {text = '[fg]not enough gold', 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 + end + self.t:after(2, function() self.info_text:deactivate(); self.info_text.dead = true; self.info_text = nil end, 'info_text') + else + ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.parent:set_passives(true) + self.selected = true + self.spring:pull(0.2, 200, 10) + gold = gold - 15 + self.parent.shop_text:set_text{{text = '[fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}} end - self.t:after(2, function() self.info_text:deactivate(); self.info_text.dead = true; self.info_text = nil end, 'info_text') - else - ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5} - self.parent:set_cards(random:int(1, 25), true) - self.selected = true - self.spring:pull(0.2, 200, 10) - gold = gold - 2 - self.parent.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}} - system.save_run(self.parent.level == 1 and 0 or self.parent.level, gold, self.parent.units, passives, run_passive_pool_by_tiers, locked_state) end end end @@ -820,13 +848,21 @@ function RerollButton: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]reroll: 2', font = pixul_font, alignment = 'center'}} + if self.parent:is(BuyScreen) then + self.text:set_text{{text = '[fgm5]reroll: 2', font = pixul_font, alignment = 'center'}} + elseif self.parent:is(Arena) then + self.text:set_text{{text = '[fgm5]reroll: 15', font = pixul_font, alignment = 'center'}} + end self.spring:pull(0.2, 200, 10) end function RerollButton:on_mouse_exit() - self.text:set_text{{text = '[bg10]reroll: [yellow]2', font = pixul_font, alignment = 'center'}} + if self.parent:is(BuyScreen) then + self.text:set_text{{text = '[bg10]reroll: [yellow]2', font = pixul_font, alignment = 'center'}} + elseif self.parent:is(Arena) then + self.text:set_text{{text = '[fgm5]reroll: [yellow]15', font = pixul_font, alignment = 'center'}} + end self.selected = false end @@ -1106,7 +1142,6 @@ function PassiveCard:update(dt) if self.selected and input.m1.pressed and self.arena.choosing_passives then self.arena.choosing_passives = false - table.insert(passives, self.passive) table.insert(self.arena.passives, self.passive) self.arena:restore_passives_to_pool(self.card_i) trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function() @@ -1324,7 +1359,6 @@ function CharacterIcon:init(args) self:init_game_object(args) self.shape = Rectangle(self.x, self.y, 40, 20) self.interact_with_mouse = true - print(character_color_strings[self.character]) 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 @@ -1480,7 +1514,11 @@ function ClassIcon:draw() local next_n if self.parent:is(ShopCard) then next_n = n+1 - if next_n > j then next_n = nil end + if k then + if next_n > k then next_n = nil end + else + if next_n > j then next_n = nil end + end if table.any(self.units, function(v) return v.character == self.character end) then next_n = nil end end diff --git a/enemies.lua b/enemies.lua index c37ba0d..baf7a4a 100644 --- a/enemies.lua +++ b/enemies.lua @@ -23,7 +23,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.05 + current_new_game_plus*0.2) + enemy:speed_boost(3 + self.level*0.025 + current_new_game_plus*0.1) end end end) @@ -85,7 +85,7 @@ function Seeker:init(args) enemy:hit(10000) critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} critter3:play{pitch = random:float(0.95, 1.05), volume = 0.6} - for i = 1, random:int(4, 6) do EnemyCritter{group = main.current.main, x = enemy.x, y = enemy.y, color = purple[0], r = random:float(0, 2*math.pi), v = 5 + 0.1*enemy.level, dmg = enemy.dmg} end + for i = 1, random:int(4, 6) do EnemyCritter{group = main.current.main, x = enemy.x, y = enemy.y, color = purple[0], r = random:float(0, 2*math.pi), v = 8 + 0.1*enemy.level, dmg = 2*enemy.dmg} end end end) @@ -100,7 +100,7 @@ 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 + current_new_game_plus*2 + local n = math.floor(8 + current_new_game_plus*1.5) 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 = 120 + 5*enemy.level, dmg = (1 + 0.1*current_new_game_plus)*enemy.dmg} end end end) @@ -119,7 +119,7 @@ function Seeker:init(args) enemy:hit(10000) shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4} local n = 8 + current_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*current_new_game_plus)*enemy.dmg} end + for i = 1, n do EnemyProjectile{group = main.current.main, x = enemy.x, y = enemy.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 125 + 5*enemy.level, dmg = (1 + 0.2*current_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}) @@ -130,7 +130,7 @@ function Seeker:init(args) enemy:hit(10000) critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} critter3:play{pitch = random:float(0.95, 1.05), volume = 0.6} - for i = 1, random:int(4, 6) do EnemyCritter{group = main.current.main, x = enemy.x, y = enemy.y, color = purple[0], r = random:float(0, 2*math.pi), v = 5 + 0.1*enemy.level, dmg = enemy.dmg} end + for i = 1, random:int(4, 6) do EnemyCritter{group = main.current.main, x = enemy.x, y = enemy.y, color = purple[0], r = random:float(0, 2*math.pi), v = 8 + 0.1*enemy.level, dmg = 2*enemy.dmg} end end elseif attack == 'force' then local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 64), {Seeker}) @@ -150,7 +150,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.05 + current_new_game_plus*0.2) + enemy:speed_boost(3 + self.level*0.025 + current_new_game_plus*0.1) end end end @@ -186,7 +186,7 @@ function Seeker:init(args) self.color = orange[0]:clone() self.last_headbutt_time = 0 local n = math.remap(current_new_game_plus, 0, 5, 1, 0.5) - self.t:every(function() return math.distance(self.x, self.y, main.current.player.x, main.current.player.y) < 64 and love.timer.getTime() - self.last_headbutt_time > 10*n end, function() + self.t:every(function() return math.distance(self.x, self.y, main.current.player.x, main.current.player.y) < 76 and love.timer.getTime() - self.last_headbutt_time > 10*n end, function() if self.silenced then return end if self.headbutt_charging or self.headbutting then return end self.headbutt_charging = true @@ -198,16 +198,24 @@ function Seeker:init(args) self.last_headbutt_time = love.timer.getTime() self:set_damping(0) self:apply_steering_impulse(300, self:angle_to_object(main.current.player), 0.75) - self.t:after(0.5, function() + self.t:after(0.75, function() self.headbutting = false end) end) end) elseif self.tank then self.color = yellow[0]:clone() - self.buff_hp_m = 1.25 + (0.025*self.level) + (0.2*current_new_game_plus) + self.buff_hp_m = 1.25 + (0.1*self.level) + (0.4*current_new_game_plus) self:calculate_stats() self.hp = self.max_hp + local n = math.remap(current_new_game_plus, 0, 5, 1, 0.75) + self.t:every({3*n, 5*n}, function() + local enemy = self:get_closest_object_in_shape(Circle(self.x, self.y, 128), main.current.enemies) + if enemy then + wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + enemy:push(random:float(40, 80), enemy:angle_to_object(main.current.player), true) + end + end) elseif self.shooter then self.color = fg[0]:clone() local n = math.remap(current_new_game_plus, 0, 5, 1, 0.5) @@ -221,7 +229,7 @@ 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 + 8*current_new_game_plus, + EnemyProjectile{group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), color = fg[0], r = r, v = 140 + 5*self.level + 4*current_new_game_plus, dmg = (current_new_game_plus*0.1 + 1)*self.dmg} end) end @@ -257,8 +265,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.05*self.level + current_new_game_plus*0.2), 1, 0.5) - self.speed_boosting_mvspd_m = (3 + 0.05*self.level + 0.2*current_new_game_plus)*n + local n = math.remap(love.timer.getTime() - self.speed_boosting, 0, (3 + 0.025*self.level + current_new_game_plus*0.1), 1, 0.5) + self.speed_boosting_mvspd_m = (3 + 0.025*self.level + 0.1*current_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) @@ -269,7 +277,7 @@ function Seeker:update(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.buff_mvspd_m = (self.speed_boosting_mvspd_m or 1)*(self.slow_mvspd_m or 1)*(self.temporal_chains_mvspd_m or 1)*(self.tank and 0.35 or 1) self:calculate_stats() @@ -368,6 +376,10 @@ function Seeker:on_collision_enter(other, contact) end end + if self.headbutter and self.headbutting then + self.headbutting = false + end + elseif table.any(main.current.enemies, function(v) return other:is(v) end) then if self.being_pushed and math.length(self:get_velocity()) > 60 then other:hit(math.floor(self.push_force/4)) @@ -382,9 +394,11 @@ function Seeker:on_collision_enter(other, contact) HitCircle{group = main.current.effects, x = x, y = y, rs = 6, color = fg[0], duration = 0.1} for i = 1, 2 do HitParticle{group = main.current.effects, x = x, y = y, color = self.color} end hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} + if other:is(Seeker) then self.headbutting = false end end elseif other:is(Turret) then + self.headbutting = false _G[random:table{'player_hit1', 'player_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.35} self:hit(0) self:push(random:float(2.5, 7), other:angle_to_object(self)) @@ -443,9 +457,9 @@ function Seeker:hit(damage, projectile) if self.silenced then return end shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.4} trigger:after(0.01, function() - local n = 8 + current_new_game_plus*2 + local n = math.floor(8 + current_new_game_plus*1.5) 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 = 120 + 5*self.level, dmg = 2*self.dmg} + EnemyProjectile{group = main.current.main, x = self.x, y = self.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 120 + 5*self.level, dmg = 1.5*self.dmg} end end) end diff --git a/main.lua b/main.lua index 03ad1ff..5a8abcc 100644 --- a/main.lua +++ b/main.lua @@ -29,7 +29,7 @@ function init() thunder1 = Sound('399656__bajko__sfx-thunder-blast.ogg', s) flagellant1 = Sound('Whipping Horse 3.ogg', s) bard2 = Sound('376532__womb-affliction__flute-trill.ogg', s) - bard1 = Sound('Magical Impact 12.ogg', s) + arcane2 = Sound('Magical Impact 12.ogg', s) frost1 = Sound('Frost Bolt 20.ogg', s) arcane1 = Sound('Magical Impact 26.ogg', s) pyro1 = Sound('Fire bolt 5.ogg', s) @@ -383,7 +383,7 @@ function init() ['elementor'] = {'mage', 'nuker'}, ['saboteur'] = {'rogue', 'conjurer', 'nuker'}, ['stormweaver'] = {'enchanter'}, - ['sage'] = {'forcer', 'nuker'}, + ['sage'] = {'nuker', 'forcer'}, ['squire'] = {'warrior', 'enchanter'}, ['cannoneer'] = {'ranger', 'nuker'}, ['dual_gunner'] = {'ranger', 'rogue'}, @@ -435,7 +435,7 @@ function init() ['elementor'] = '[blue]Mage, [red]Nuker', ['saboteur'] = '[red]Rogue, [orange]Conjurer, [red]Nuker', ['stormweaver'] = '[blue]Enchanter', - ['sage'] = '[red]Nuker', + ['sage'] = '[red]Nuker, [yellow]Forcer', ['squire'] = '[yellow]Warrior, [blue]Enchanter', ['cannoneer'] = '[green]Ranger, [red]Nuker', ['dual_gunner'] = '[green]Ranger, [red]Rogue', @@ -494,7 +494,7 @@ function init() ['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')*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, - ['magician'] = function(lvl) return '[fg]creates a small area that deals [yellow]' .. get_character_stat('magician', lvl, 'dmg') .. 'AoE[fg] damage' end, + ['magician'] = function(lvl) return '[fg]creates a small area that deals [yellow]' .. get_character_stat('magician', 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, ['cleric'] = function(lvl) return '[fg]heals a unit for [yellow]20%[fg] of its max hp when it drops below [yellow]50%[fg] max hp' end, @@ -538,16 +538,16 @@ function init() ['illusionist'] = function(lvl) return '[fg]launches a projectile that deals [yellow]' .. get_character_stat('illusionist', lvl, 'dmg') .. '[fg] damage and creates copies that do the same' end, ['witch'] = function(lvl) return '[fg]creates an area that ricochets around the arena and deals [yellow]' .. get_character_stat('witch', lvl, 'dmg') .. '[fg] damage per second' end, ['silencer'] = function(lvl) return '[fg]curses [yellow]5[fg] nearby enemies for [yellow]6[fg] seconds, preventing them from using special attacks' end, - ['vulcanist'] = function(lvl) return '[fg]creates a volcano that explodes the nearby area [yellow]4[fg] times, dealing [yellow]' .. get_character_stat('vulcanist', lvl, 'dmg') .. 'AoE [fg]damage' end, + ['vulcanist'] = function(lvl) return '[fg]creates a volcano that explodes the nearby area [yellow]4[fg] times, dealing [yellow]' .. get_character_stat('vulcanist', lvl, 'dmg') .. ' AoE [fg]damage' end, ['warden'] = function(lvl) return '[fg]creates a force field around a random unit that prevents enemies from entering' end, - ['psychic'] = function(lvl) return '[fg]creates a small area that deals [yellow]' .. get_character_stat('psychic', lvl, 'dmg') .. 'AoE[fg] damage' end, + ['psychic'] = function(lvl) return '[fg]creates a small area that deals [yellow]' .. get_character_stat('psychic', lvl, 'dmg') .. ' AoE[fg] damage' end, } character_effect_names = { ['vagrant'] = '[fg]Champion', ['swordsman'] = '[yellow]Cleave', ['wizard'] = '[blue]Magic Missile', - ['magician'] = '[blue]Teleportation', + ['magician'] = '[blue]Ethereal Form', ['archer'] = '[green]Bounce Shot', ['scout'] = '[red]Dagger Resonance', ['cleric'] = '[green]Mass Heal ', @@ -599,7 +599,7 @@ function init() ['vagrant'] = '[light_bg]Champion', ['swordsman'] = '[light_bg]Cleave', ['wizard'] = '[light_bg]Magic Missile', - ['magician'] = '[light_bg]Teleportation', + ['magician'] = '[light_bg]Ethereal Form', ['archer'] = '[light_bg]Bounce Shot', ['scout'] = '[light_bg]Dagger Resonance', ['cleric'] = '[light_bg]Mass Heal ', @@ -651,7 +651,7 @@ function init() ['vagrant'] = function() return '[yellow]+10%[fg] damage and [yellow]+10%[fg] attack speed per active set' end, ['swordsman'] = function() return "[fg]the swordsman's damage is [yellow]doubled" end, ['wizard'] = function() return '[fg]the projectile chains [yellow]2[fg] times' end, - ['magician'] = function() return '[fg]the magician becomes invulnerable for [yellow]6[fg] seconds' end, + ['magician'] = function() return '[fg]the magician becomes invulnerable for [yellow]6[fg] seconds but also cannot attack' end, ['archer'] = function() return '[fg]the arrow ricochets off walls [yellow]3[fg] times' end, ['scout'] = function() return '[yellow]+25%[fg] damage per chain and [yellow]+3[fg] chains' end, ['cleric'] = function() return '[fg]heals all units' end, @@ -690,20 +690,20 @@ function init() ['priest'] = function() return '[fg]picks [yellow]3[fg] units at random and grants them a buff that prevents death once' end, ['infestor'] = function() return '[fg][yellow]triples[fg] the number of critters released' end, ['flagellant'] = function() return '[fg]deals [yellow]' .. 2*get_character_stat('flagellant', 3, 'dmg') .. '[fg] damage to all allies and grants [yellow]+12%[fg] damage to all allies per cast' end, - ['arcanist'] = function() return '[yellow]+100%[fg] attack speed for the orb and [yellow]2[fg] projectiles are released per cast' end, + ['arcanist'] = function() return '[yellow]+50%[fg] attack speed for the orb and [yellow]2[fg] projectiles are released per cast' end, ['illusionist'] = function() return '[yellow]doubles[fg] the number of copies created and they release [yellow]12[fg] projectiles on death that pierce and ricochet once' end, ['witch'] = function() return '[fg]the area periodically releases projectiles, each dealing [yellow]' .. get_character_stat('witch', 3, 'dmg') .. '[fg] damage and chaining once' end, ['silencer'] = function() return '[fg]the curse also deals [yellow]' .. get_character_stat('silencer', 3, 'dmg') .. '[fg] damage per second' end, ['vulcanist'] = function() return '[fg]the number and speed of explosions is [yellow]doubled[fg]' end, - ['warden'] = function() return '[fg]creates the force field around [yellow]2[fg] units, and they are always the [yellow]head[fg] and [yellow]tail[fg] of the snake' end, - ['psychic'] = function() return '[fg]the attack can happen from any distance and deals [yellow]double[fg] damage' end, + ['warden'] = function() return '[fg]creates the force field around [yellow]2[fg] units' end, + ['psychic'] = function() return '[fg]the attack can happen from any distance and repeats once' end, } character_effect_descriptions_gray = { ['vagrant'] = function() return '[light_bg]+10% damage and +10% attack speed per active set' end, ['swordsman'] = function() return "[light_bg]the swordsman's damage is doubled" end, ['wizard'] = function() return '[light_bg]the projectile chains 3 times' end, - ['magician'] = function() return '[light_bg]the magician becomes invulnerable for 6 seconds' end, + ['magician'] = function() return '[light_bg]the magician becomes invulnerable for 6 seconds but also cannot attack' end, ['archer'] = function() return '[light_bg]the arrow ricochets off walls 3 times' end, ['scout'] = function() return '[light_bg]+25% damage per chain and +3 chains' end, ['cleric'] = function() return '[light_bg]heals all units' end, @@ -742,13 +742,13 @@ function init() ['priest'] = function() return '[light_bg]picks 3 units at random and grants them a buff that prevents death once' 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, - ['arcanist'] = function() return '[light_bg]+100% attack speed for the orb and 2 projectiles are released per cast' end, + ['arcanist'] = function() return '[light_bg]+50% attack speed for the orb and 2 projectiles are released per cast' end, ['illusionist'] = function() return '[light_bg]doubles the number of copies created and they release 12 projectiles on death that pierce and ricochet once' end, ['witch'] = function() return '[light_bg]the area periodically releases projectiles, each dealing ' .. get_character_stat('witch', 3, 'dmg') .. ' damage and chaining once' end, ['silencer'] = function() return '[light_bg]the curse also deals ' .. get_character_stat('silencer', 3, 'dmg') .. ' damage per second' end, ['vulcanist'] = function() return '[light_bg]the number and speed of explosions is doubled' end, - ['warden'] = function() return '[light_bg]creates the force field around 2 units, and they are always the head and tail of the snake' end, - ['psychic'] = function() return '[light_bg]the attack can happen from any distance and deals double damage' end, + ['warden'] = function() return '[light_bg]creates the force field around 2 units' end, + ['psychic'] = function() return '[light_bg]the attack can happen from any distance and repeats once' end, } character_stats = { @@ -1196,12 +1196,12 @@ function init() [4] = {3, 5}, [5] = {4, 7}, [6] = {6, 10}, - [7] = {6, 7}, - [8] = {7, 8}, - [9] = {10, 16}, - [10] = {10, 12}, - [11] = {12, 14}, - [12] = {18, 20}, + [7] = {10, 14}, + [8] = {12, 16}, + [9] = {14, 18}, + [10] = {10, 14}, + [11] = {12, 16}, + [12] = {20, 24}, [13] = {12, 16}, [14] = {14, 18}, [15] = {16, 20}, @@ -1234,13 +1234,13 @@ function init() [3] = {10}, [4] = {4, 4}, [5] = {4, 3, 2}, - [6] = {14}, + [6] = {12}, [7] = {5, 3, 2}, [8] = {6, 3, 3, 3}, - [9] = {18}, + [9] = {14}, [10] = {8, 4}, [11] = {8, 6, 2}, - [12] = {18}, + [12] = {16}, [13] = {8, 8}, [14] = {12, 6}, [15] = {18}, @@ -1319,14 +1319,21 @@ function init() main:add(BuyScreen'buy_screen') main:go_to('buy_screen', run.level or 0, run.units or {}, passives) + -- main:go_to('buy_screen', 2, run.units or {}, passives) --[[ main:add(Arena'arena') - main:go_to('arena', 14, { - {character = 'magician', level = 3}, + main:go_to('arena', 1, { + {character = 'dual_gunner', level = 1}, + {character = 'scout', level = 1}, }, 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 @@ -1336,6 +1343,8 @@ function init() print() end end) + + print(table.tostring(love.graphics.getSupported())) end diff --git a/media.lua b/media.lua index 9767cb2..4e16a8d 100644 --- a/media.lua +++ b/media.lua @@ -11,7 +11,11 @@ function Media:on_enter(from) self.effects = Group() self.ui = Group() - graphics.set_background_color(fg[0]) + graphics.set_background_color(blue2[0]) + Text2{group = self.ui, x = gw/2, y = gh/2, lines = { + {text = '[fg]SNKRX', font = fat_font, alignment = 'center', height_offset = -15}, + {text = '[fg]sorcerer update', font = pixul_font, alignment = 'center'}, + }} end @@ -27,10 +31,3 @@ function Media:draw() self.effects:draw() self.ui:draw() 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 -]]-- diff --git a/objects.lua b/objects.lua index e3e32ed..43464a7 100644 --- a/objects.lua +++ b/objects.lua @@ -232,7 +232,7 @@ function Unit:calculate_stats(first_run) if current_new_game_plus == 0 then if self.boss then local x = self.level - local y = {0, 0, 3, 0, 0, 6, 0, 0, 9, 0, 0, 12, 0, 0, 18, 0, 0, 24, 0, 0, 32, 0, 0, 40, 48} + local y = {0, 0, 3, 0, 0, 6, 0, 0, 9, 0, 0, 12, 0, 0, 18, 0, 0, 40, 0, 0, 32, 0, 0, 64, 96} self.base_hp = 100 + (current_new_game_plus*5) + (90 + current_new_game_plus*10)*y[x] self.base_dmg = (12 + current_new_game_plus*2) + (2 + current_new_game_plus)*y[x] self.base_mvspd = 35 + 1.5*y[x] @@ -246,7 +246,7 @@ function Unit:calculate_stats(first_run) else if self.boss then local x = self.level - local y = {0, 0, 3, 0, 0, 6, 0, 0, 9, 0, 0, 12, 0, 0, 18, 0, 0, 24, 0, 0, 32, 0, 0, 40, 48} + local y = {0, 0, 3, 0, 0, 6, 0, 0, 9, 0, 0, 12, 0, 0, 18, 0, 0, 40, 0, 0, 32, 0, 0, 64, 96} self.base_hp = 100 + (current_new_game_plus*5) + (90 + current_new_game_plus*10)*y[x] self.base_dmg = (12 + current_new_game_plus*2) + (2 + current_new_game_plus)*y[x] self.base_mvspd = 35 + 1.5*y[x] @@ -254,7 +254,7 @@ function Unit:calculate_stats(first_run) 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 = 22 + (current_new_game_plus*3) + (15 + current_new_game_plus*2.7)*y[x] - self.base_dmg = (4 + current_new_game_plus*1.25) + (2 + current_new_game_plus*1.2)*y[x] + self.base_dmg = (4 + current_new_game_plus*1.15) + (2 + current_new_game_plus*1)*y[x] self.base_mvspd = 70 + 3*y[x] end end diff --git a/player.lua b/player.lua index 8f8760c..a604244 100644 --- a/player.lua +++ b/player.lua @@ -41,6 +41,7 @@ function Player:init(args) elseif self.character == 'magician' then self.attack_sensor = Circle(self.x, self.y, 96) 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() + if self.magician_invulnerable then return end local enemy = self:get_random_object_in_shape(self.attack_sensor, main.current.enemies) if enemy then self:attack(32, {x = enemy.x, y = enemy.y}) @@ -91,7 +92,7 @@ function Player:init(args) elseif self.character == 'arcanist' then self.sorcerer_count = 0 self.attack_sensor = Circle(self.x, self.y, 128) - self.t:cooldown(3, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + self.t:cooldown(4, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() 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), {pierce = 1000, v = 40}) @@ -838,7 +839,6 @@ function Player:init(args) local unit_1 = random:table_remove(units) local unit_2 = random:table_remove(units) local unit_3 = random:table_remove(units) - print(unit_1, unit_2, unit_3) if unit_1 then unit_1.t:every(2, function() unit_1:hit(0.05*unit_1.max_hp) end) end if unit_2 then unit_2.t:every(2, function() unit_2:hit(0.05*unit_2.max_hp) end) end if unit_3 then unit_3.t:every(2, function() unit_3:hit(0.05*unit_3.max_hp) end) end @@ -889,6 +889,7 @@ function Player:update(dt) if class_levels.forcer >= 1 then number_of_active_sets = number_of_active_sets + 1 end if class_levels.swarmer >= 1 then number_of_active_sets = number_of_active_sets + 1 end if class_levels.voider >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.sorcerer >= 1 then number_of_active_sets = number_of_active_sets + 1 end self.vagrant_dmg_m = 1 + 0.1*number_of_active_sets self.vagrant_aspd_m = 1 + 0.1*number_of_active_sets end @@ -953,6 +954,7 @@ function Player:update(dt) if class_levels.forcer >= 1 then number_of_active_sets = number_of_active_sets + 1 end if class_levels.swarmer >= 1 then number_of_active_sets = number_of_active_sets + 1 end if class_levels.voider >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.sorcerer >= 1 then number_of_active_sets = number_of_active_sets + 1 end if main.current.psyker_level == 2 then self.psyker_dmg_m = 1 + 0.2*number_of_active_sets self.psyker_aspd_m = 1 + 0.2*number_of_active_sets @@ -1021,21 +1023,24 @@ function Player:update(dt) for _, unit in ipairs(units) do total_v = total_v + unit.max_v end - total_v = total_v/#units + total_v = math.floor(total_v/#units) + self.total_v = total_v self:set_velocity(total_v*math.cos(self.r), total_v*math.sin(self.r)) if not main.current.won and not main.current.choosing_passives then - local vx, vy = self:get_velocity() - local hd = math.remap(math.abs(self.x - gw/2), 0, 192, 1, 0) - local vd = math.remap(math.abs(self.y - gh/2), 0, 108, 1, 0) - camera.x = camera.x + math.remap(vx, -100, 100, -24*hd, 24*hd)*dt - camera.y = camera.y + math.remap(vy, -100, 100, -8*vd, 8*vd)*dt - if input.move_right.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, math.pi/256) - elseif input.move_left.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, -math.pi/256) - elseif input.move_down.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, math.pi/256) - elseif input.move_up.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, -math.pi/256) - else camera.r = math.lerp_angle_dt(0.005, dt, camera.r, 0) end + if not state.no_screen_movement then + local vx, vy = self:get_velocity() + local hd = math.remap(math.abs(self.x - gw/2), 0, 192, 1, 0) + local vd = math.remap(math.abs(self.y - gh/2), 0, 108, 1, 0) + camera.x = camera.x + math.remap(vx, -100, 100, -24*hd, 24*hd)*dt + camera.y = camera.y + math.remap(vy, -100, 100, -8*vd, 8*vd)*dt + if input.move_right.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, math.pi/256) + elseif input.move_left.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, -math.pi/256) + elseif input.move_down.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, math.pi/256) + elseif input.move_up.down then camera.r = math.lerp_angle_dt(0.01, dt, camera.r, -math.pi/256) + else camera.r = math.lerp_angle_dt(0.005, dt, camera.r, 0) end + end end self:set_angle(self.r) @@ -1049,7 +1054,7 @@ function Player:update(dt) local distance_to_previous = math.distance(previous.x, previous.y, point.x, point.y) distance_sum = distance_sum + distance_to_previous if distance_sum >= target_distance then - p = point + p = self.parent.previous_positions[i-1] break end previous = point @@ -1079,6 +1084,12 @@ function Player:draw() else graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, (self.hfx.hit.f or self.hfx.shoot.f) and fg[0] or self.color) end + + if self.leader and state.arrow_snake then + local x, y = self.x + 0.9*self.shape.w, self.y + graphics.line(x + 3, y, x, y - 3, character_colors[self.character], 1) + graphics.line(x + 3, y, x, y + 3, character_colors[self.character], 1) + end end graphics.pop() end @@ -1148,7 +1159,7 @@ function Player:hit(damage) self.hfx:use('hit', 0.25, 200, 10) self:show_hp() - local actual_damage = self:calculate_damage(damage) + local actual_damage = math.max(self:calculate_damage(damage), 0) self.hp = self.hp - actual_damage _G[random:table{'player_hit1', 'player_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} camera:shake(4, 0.5) @@ -1221,6 +1232,7 @@ function Player:hit(damage) self.hp = 1 end else + self.dead = true if self.leader then self:recalculate_followers() else self.parent:recalculate_followers() end end @@ -1546,6 +1558,7 @@ function Projectile:init(args) self.t:every(self.parent.level == 3 and 0.54 or 0.8, function() local enemies = table.head(self:get_objects_in_shape(Circle(self.x, self.y, 128), main.current.enemies), self.level == 3 and 2 or 1) for _, enemy in ipairs(enemies) do + arcane2:play{pitch = random:float(0.7, 1.3), volume = 0.15} self.hfx:use('hit', 0.5) local r = self:angle_to_object(enemy) local t = {group = main.current.main, x = self.x + 8*math.cos(r), y = self.y + 8*math.sin(r), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, pierce = 1000, character = 'arcanist_projectile', diff --git a/todo b/todo index 55f91b0..cb89687 100644 --- a/todo +++ b/todo @@ -1,23 +1,3 @@ -Sorcerer update - Options - Option to have an arrow at the head of the snake - https://imgur.com/a/poXVsoN - Option to turn off camera movement - Options menu from buy screen - Volume slider - Fix fullscreen with different resolutions that don't scale properly - QoL - Item reroll for 15 gold - Fix highlight colors and highlight reserve - Rename tutorial to guide or manual - Add visuals for defensive ouroboros - Unlock automatically on shop enter - Balance - Buff tanks, maybe add a simple forcer ability to them - Buff 24/25 HP again - Buff headbutter (+ trigger range) - Bugs - Fix leader (snake head) distance being off by 1 or 2 pixels on certain snake speeds - Sorcerer update patch notes Summary: This update adds 1 new class with 8 new units, implements more QoL features, further balance changes and lots of bug fixes. Added sorcerer class: sorcerers repeat their attacks once every 4/3/2 attacks @@ -56,15 +36,21 @@ Summary: This update adds 1 new class with 8 new units, implements more QoL feat Fixed a crash whenever the Fairy would try to select a unit that just died this or last frame Fixed a crash that would happen when trying to restart the game through the menu on the passive selection screen Fixed a bug where you could die and win at the same time - Fixed a crash triggered by infested enemies on death rarely + Fixed a crash triggered rarely by infested enemies on death Fixed a bug where quitting on level 1 would automatically jump to level 2 - Fixed a bug where lock state would carry over after a run death/win/restart - Fixed a bug where items from a previous restarted run would remain after the 1st round - + Fixed a bug where the shop's lock state would carry over after a run death/win/restart + Fixed a bug where items from a previous restarted run would remain after the 1st round of a new run + Fixed a bug where the leader's (snake head) distance was off by 1 or 2 pixels on certain snake speeds + Added item reroll for 15 gold + Buffed tank (yellow enemy) and added a special attack + Buffed boss HP for levels 18, 24 and 25 (again) + Added an option to have an arrow at the head of the snake + Added an option to turn off camera movement + Slightly decreased enemy damage across all difficulties --- -Future ideas: +Future updates: Chaos related classes Invoker - shoots a projectile with random properties, Lv.3 effect - ??? Trappers: @@ -83,7 +69,15 @@ Hide cursor during waves Mouse follow control? Visuals for divine intervertion, fairy buff Fix enemies still spawning after arena clear (this happens with the extra enemy spawns that were blocked earlier) +Fix highlight colors and highlight reserve +Add visuals for defensive ouroboros +Options menu from buy screen +Volume slider +Fix fullscreen with different resolutions that don't scale properly Roguelite update: -Slay the Spire-like node selection map (copy code from SHOOTRX repo as this is already implemented there) +Hades-like no map system, this is more elegant and requires way less work than something like Isaac or StS Units die permanently when they die +Units can have items attached to them like in Underlords +Unit item ideas: + This unit's projectiles pierce/chain/fork/seek/split/stun/etc