Sorcerer update 4/4

master
a327ex 2021-06-05 03:11:23 -03:00
parent 521ce0cca5
commit 2276925f34
8 changed files with 260 additions and 155 deletions

View File

@ -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,6 +810,26 @@ 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)
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
end, 'transition')
end
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
@ -813,11 +856,6 @@ function Arena:quit()
if not passive_1 and not passive_2 and not passive_3 then
self:transition()
end
else
self:transition()
end
end, 'transition')
end
end
@ -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

View File

@ -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
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,6 +788,7 @@ function RerollButton:update(dt)
self:update_game_object(dt)
if self.selected and input.m1.pressed then
if self.parent:is(BuyScreen) then
if gold < 2 then
self.spring:pull(0.2, 200, 10)
self.selected = true
@ -804,6 +810,28 @@ function RerollButton:update(dt)
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
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
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()
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 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

View File

@ -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

View File

@ -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',
@ -547,7 +547,7 @@ function init()
['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

View File

@ -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
]]--

View File

@ -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

View File

@ -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,11 +1023,13 @@ 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
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)
@ -1037,6 +1041,7 @@ function Player:update(dt)
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',

46
todo
View File

@ -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