Sorcerer update 1/4

master
a327ex 2021-06-02 02:43:03 -03:00
parent aa598914ac
commit cea71538b2
10 changed files with 394 additions and 61 deletions

View File

@ -250,6 +250,7 @@ function Arena:on_enter(from, level, units, passives)
self.healer_level = class_levels.healer
self.psyker_level = class_levels.psyker
self.conjurer_level = class_levels.conjurer
self.sorcerer_level = class_levels.sorcerer
self.t:every(0.375, function()
local p = random:table(star_positions)
@ -732,6 +733,13 @@ function Arena:quit()
steam.userStats.storeStats()
end
if self.sorcerer_level >= 3 then
state.achievement_sorcerers_win = true
system.save_state()
steam.userStats.setAchievement('SORCERERS_WIN')
steam.userStats.storeStats()
end
local units = self.player:get_all_units()
local all_units_level_2 = true
for _, unit in ipairs(units) do

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Binary file not shown.

View File

@ -180,7 +180,8 @@ function Seeker:init(args)
elseif self.headbutter then
self.color = orange[0]:clone()
self.last_headbutt_time = 0
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 end, function()
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()
if self.headbutt_charging or self.headbutting then return end
self.headbutt_charging = true
self.t:tween(2, self.color, {r = fg[0].r, b = fg[0].b, g = fg[0].g}, math.cubic_in_out, function()
@ -191,19 +192,20 @@ 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.75, function()
self.t:after(0.5, 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)
self.buff_hp_m = 1.25 + (0.025*self.level) + (0.2*current_new_game_plus)
self:calculate_stats()
self.hp = self.max_hp
elseif self.shooter then
self.color = fg[0]:clone()
self.t:after({2, 4}, function()
local n = math.remap(current_new_game_plus, 0, 5, 1, 0.5)
self.t:after({2*n, 4*n}, function()
self.shooting = true
self.t:every({3, 5}, function()
for i = 1, 3 do
@ -434,7 +436,7 @@ function Seeker:hit(damage, projectile)
trigger:after(0.01, function()
local n = 8 + current_new_game_plus*2
for i = 1, n do
EnemyProjectile{group = main.current.main, x = self.x, y = self.y, color = blue[0], r = (i-1)*math.pi/(n/2), v = 150 + 5*self.level, dmg = 2*self.dmg}
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}
end
end)
end
@ -442,8 +444,8 @@ function Seeker:hit(damage, projectile)
if self.spawner then
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.35}
trigger:after(0.01, function()
for i = 1, random:int(3, 6) do
EnemyCritter{group = main.current.main, x = self.x, y = self.y, color = purple[0], r = random:float(0, 2*math.pi), v = 5 + 0.1*self.level, dmg = self.dmg, projectile = projectile}
for i = 1, random:int(5, 8) do
EnemyCritter{group = main.current.main, x = self.x, y = self.y, color = purple[0], r = random:float(0, 2*math.pi), v = 10 + 0.1*self.level, dmg = 2*self.dmg, projectile = projectile}
end
end)
end

132
main.lua
View File

@ -18,11 +18,13 @@ function init()
input:bind('enter', {'space', 'return', 'fleft', 'fdown', 'fright'})
local s = {tags = {sfx}}
illusion1 = Sound('Buff 5.ogg', s)
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)
frost1 = Sound('Frost Bolt 20.ogg', s)
arcane1 = Sound('Magical Impact 26.ogg', s)
pyro1 = Sound('Fire bolt 5.ogg', s)
pyro2 = Sound('Explosion Fireworks_01.ogg', s)
dot1 = Sound('Magical Swoosh 18.ogg', s)
@ -125,6 +127,7 @@ function init()
forcer = Image('forcer')
swarmer = Image('swarmer')
voider = Image('voider')
sorcerer = Image('sorcerer')
ouroboros_technique_r = Image('ouroboros_technique_r')
ouroboros_technique_l = Image('ouroboros_technique_l')
wall_echo = Image('wall_echo')
@ -184,6 +187,7 @@ function init()
['forcer'] = yellow[0],
['swarmer'] = orange[0],
['voider'] = purple[0],
['sorcerer'] = blue2[0],
}
class_color_strings = {
@ -200,6 +204,7 @@ function init()
['forcer'] = 'yellow',
['swarmer'] = 'orange',
['voider'] = 'purple',
['sorcerer'] = 'blue2',
}
character_names = {
@ -244,6 +249,13 @@ function init()
['priest'] = 'Priest',
['infestor'] = 'Infestor',
['flagellant'] = 'Flagellant',
['arcanist'] = 'Arcanist',
['illusionist'] = 'Illusionist',
['witch'] = 'Witch',
['silencer'] = 'Silencer',
['vulcanist'] = 'Vulcanist',
['warden'] = 'Warden',
['psychic'] = 'Psychic',
}
character_colors = {
@ -288,6 +300,13 @@ function init()
['priest'] = green[0],
['infestor'] = orange[0],
['flagellant'] = fg[0],
['arcanist'] = blue2[0],
['illusionist'] = blue2[0],
['witch'] = purple[0],
['silencer'] = blue2[0],
['vulcanist'] = red[0],
['warden'] = yellow[0],
['psychic'] = fg[0],
}
character_color_strings = {
@ -332,6 +351,13 @@ function init()
['priest'] = 'green',
['infestor'] = 'orange',
['flagellant'] = 'fg',
['arcanist'] = 'blue2',
['illusionist'] = 'blue2',
['witch'] = 'purple',
['silencer'] = 'blue2',
['vulcanist'] = 'red',
['warden'] = 'yellow',
['psychic'] = 'fg',
}
character_classes = {
@ -376,6 +402,13 @@ function init()
['priest'] = {'healer'},
['infestor'] = {'curser', 'swarmer'},
['flagellant'] = {'psyker', 'enchanter'},
['arcanist'] = {'sorcerer'},
['illusionist'] = {'sorcerer', 'conjurer'},
['witch'] = {'sorcerer', 'voider'},
['silencer'] = {'sorcerer', 'curser'},
['vulcanist'] = {'sorcerer', 'nuker'},
['warden'] = {'sorcerer', 'forcer'},
['psychic'] = {'sorcerer', 'psyker'},
}
character_class_strings = {
@ -420,6 +453,13 @@ function init()
['priest'] = '[green]Healer',
['infestor'] = '[purple]Curser, [orange]Swarmer',
['flagellant'] = '[fg]Psyker, [blue]Enchanter',
['arcanist'] = '[blue2]Sorcerer',
['illusionist'] = '[blue2]Sorcerer, [orange]Conjurer',
['witch'] = '[blue2]Sorcerer, [purple]Voider',
['silencer'] = '[blue2]Sorcerer, [purple]Curser',
['vulcanist'] = '[blue2]Sorcerer, [red]Nuker',
['warden'] = '[blue2]Sorcerer, [yellow]Forcer',
['psychic'] = '[blue2]Sorcerer, [fg]Psyker',
}
get_character_stat_string = function(character, level)
@ -468,7 +508,7 @@ function init()
['corruptor'] = function(lvl) return '[fg]spawn [yellow]3[fg] small critters if the corruptor kills an enemy' end,
['beastmaster'] = function(lvl) return '[fg]spawn [yellow]2[fg] small critters if the beastmaster crits' end,
['launcher'] = function(lvl) return '[fg]all nearby enemies are pushed after [yellow]4[fg] seconds, taking [yellow]' .. 2*get_character_stat('launcher', lvl, 'dmg') .. '[fg] damage on wall hit' end,
['jester'] = function(lvl) return "[fg]curses [yellow]5[fg] nearby enemies for [yellow]6[fg] seconds, they will explode into [yellow]3[fg] knives on death" end,
['jester'] = function(lvl) return "[fg]curses [yellow]4[fg] nearby enemies for [yellow]6[fg] seconds, they will explode into [yellow]3[fg] knives on death" end,
['assassin'] = function(lvl) return '[fg]throws a piercing knife that deals [yellow]' .. get_character_stat('assassin', lvl, 'dmg') .. '[fg] damage + [yellow]' ..
get_character_stat('assassin', lvl, 'dmg')/2 .. '[fg] damage per second' end,
['host'] = function(lvl) return '[fg]periodically spawn [yellow]1[fg] small critter' end,
@ -481,6 +521,13 @@ function init()
['priest'] = function(lvl) return '[fg]heals all allies for [yellow]20%[fg] their max HP' end,
['infestor'] = function(lvl) return '[fg]curses all nearby enemies for [yellow]6[fg] seconds, they will release [yellow]2[fg] critters on death' end,
['flagellant'] = function(lvl) return '[fg]deals [yellow]' .. 2*get_character_stat('flagellant', lvl, 'dmg') .. '[fg] damage to self and grants [yellow]+4%[fg] damage to all allies per cast' end,
['arcanist'] = function(lvl) return '[fg]launches a slow moving orb that launches projectiles, each dealing [yellow]' .. get_character_stat('arcanist', lvl, 'dmg') .. '[fg] damage' end,
['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]5[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') .. '[fg] damage' end,
}
character_effect_names = {
@ -525,6 +572,13 @@ function init()
['priest'] = '[green]Divine Intervention',
['infestor'] = '[orange]Infestation',
['flagellant'] = '[red]Zealotry',
['arcanist'] = '[blue2]Arcane Orb',
['illusionist'] = '[blue2]Mirror Image',
['witch'] = '[purple]Death Pool',
['silencer'] = '[blue2]Arcane Curse',
['vulcanist'] = '[red]Lava Burst',
['warden'] = '[yellow]Magnetic Field',
['psychic'] = '[fg]Mental Strike'
}
character_effect_names_gray = {
@ -569,6 +623,13 @@ function init()
['priest'] = '[light_bg]Divine Intervention',
['infestor'] = '[light_bg]Infestation',
['flagellant'] = '[light_bg]Zealotry',
['arcanist'] = '[light_bg]Arcane Orb',
['illusionist'] = '[light_bg]Mirror Image',
['witch'] = '[light_bg]Death Pool',
['silencer'] = '[light_bg]Arcane Curse',
['vulcanist'] = '[light_bg]Lava Burst',
['warden'] = '[light_bg]Magnetic Field',
['psychic'] = '[light_bg]Mental Strike'
}
character_effect_descriptions = {
@ -613,6 +674,13 @@ 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] increased 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 volcano spawn also deals [yellow]' .. get_character_stat('vulcanist', 3, 'dmg') .. 'AoE [fg] damage and [yellow]doubles[fg] the number of explosions' end,
['warden'] = function() return '[fg]creates the force field around [yellow]2[fg] additional random units' end,
['psychic'] = function() return '[fg]the attack can happen from any distance and deals [yellow]double[fg] damage' end,
}
character_effect_descriptions_gray = {
@ -657,6 +725,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% increased 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' 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 volcano spawn also deals ' .. get_character_stat('vulcanist', 3, 'dmg') .. 'AoE damage and doubles the number of explosions' end,
['warden'] = function() return '[light_bg]creates the force field around 2 additional random units' end,
['psychic'] = function() return '[light_bg]the attack can happen from any distance and deals double damage' end,
}
character_stats = {
@ -701,6 +776,13 @@ function init()
['priest'] = function(lvl) return get_character_stat_string('priest', lvl) end,
['infestor'] = function(lvl) return get_character_stat_string('infestor', lvl) end,
['flagellant'] = function(lvl) return get_character_stat_string('flagellant', lvl) end,
['arcanist'] = function(lvl) return get_character_stat_string('arcanist', lvl) end,
['illusionist'] = function(lvl) return get_character_stat_string('illusionist', lvl) end,
['witch'] = function(lvl) return get_character_stat_string('witch', lvl) end,
['silencer'] = function(lvl) return get_character_stat_string('silencer', lvl) end,
['vulcanist'] = function(lvl) return get_character_stat_string('vulcanist', lvl) end,
['warden'] = function(lvl) return get_character_stat_string('warden', lvl) end,
['psychic'] = function(lvl) return get_character_stat_string('psychic', lvl) end,
}
class_stat_multipliers = {
@ -717,14 +799,28 @@ function init()
['forcer'] = {hp = 1.25, dmg = 1.1, aspd = 0.9, area_dmg = 0.75, area_size = 0.75, def = 1.2, mvspd = 1},
['swarmer'] = {hp = 1.2, dmg = 1, aspd = 1.25, area_dmg = 1, area_size = 1, def = 0.75, mvspd = 0.5},
['voider'] = {hp = 0.75, dmg = 1.3, aspd = 1, area_dmg = 0.8, area_size = 0.75, def = 0.6, mvspd = 0.8},
['sorcerer'] = {hp = 0.8, dmg = 1.3, aspd = 1, area_dmg = 1.2, area_size = 1, def = 0.8, mvspd = 1},
['seeker'] = {hp = 0.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.3},
['mini_boss'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.3},
['enemy_critter'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.5},
['saboteur'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 1.4},
}
local ylb1 = function(lvl) return lvl >= 2 and 'fg' or (lvl >= 1 and 'yellow' or 'light_bg') end
local ylb2 = function(lvl) return (lvl >= 2 and 'yellow' or 'light_bg') end
local ylb1 = function(lvl)
if lvl == 3 then return 'fg'
elseif lvl == 2 then return 'fg'
elseif lvl == 1 then return 'yellow'
else return 'light_bg' end
end
local ylb2 = function(lvl)
if lvl == 3 then return 'fg'
elseif lvl == 2 then return 'yellow'
else return 'light_bg' end
end
local ylb3 = function(lvl)
if lvl == 3 then return 'yellow'
else return 'light_bg' end
end
class_descriptions = {
['ranger'] = function(lvl) return '[' .. ylb1(lvl) .. ']3[' .. ylb2(lvl) .. ']/6 [fg]- [' .. ylb1(lvl) .. ']8%[' .. ylb2(lvl) .. ']/16% [fg]chance to release a barrage on attack to allied rangers' end,
['warrior'] = function(lvl) return '[' .. ylb1(lvl) .. ']3[' .. ylb2(lvl) .. ']/6 [fg]- [' .. ylb1(lvl) .. ']+25[' .. ylb2(lvl) .. ']/+50 [fg]defense to allied warriors' end,
@ -739,6 +835,9 @@ function init()
['forcer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+25%[' .. ylb2(lvl) .. ']/+50% [fg]knockback force to all allies' end,
['swarmer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+1[' .. ylb2(lvl) .. ']/+3 [fg]hits to critters' end,
['voider'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+15%[' .. ylb2(lvl) .. ']/+25% [fg]damage over time to allied voiders' end,
['sorcerer'] = function(lvl)
return '[' .. ybl1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4[' .. ylb3(lvl) .. '/6 [fg]- sorcerers repeat their attacks once every [' .. ylb1(lvl) .. ']4/[' .. ylb2(lvl) .. ']3/[' .. ylb3(lvl) .. ']2[fg] attacks'
end,
}
tier_to_characters = {
@ -809,6 +908,7 @@ function init()
local forcers = 0
local swarmers = 0
local voiders = 0
local sorcerers = 0
for _, unit in ipairs(units) do
for _, unit_class in ipairs(character_classes[unit.character]) do
if unit_class == 'ranger' then rangers = rangers + 1 end
@ -824,10 +924,11 @@ function init()
if unit_class == 'forcer' then forcers = forcers + 1 end
if unit_class == 'swarmer' then swarmers = swarmers + 1 end
if unit_class == 'voider' then voiders = voiders + 1 end
if unit_class == 'sorcerer' then sorcerers = sorcerers + 1 end
end
end
return {ranger = rangers, warrior = warriors, healer = healers, mage = mages, nuker = nukers, conjurer = conjurers, rogue = rogues,
enchanter = enchanters, psyker = psykers, curser = cursers, forcer = forcers, swarmer = swarmers, voider = voiders}
enchanter = enchanters, psyker = psykers, curser = cursers, forcer = forcers, swarmer = swarmers, voider = voiders, sorcerer = sorcerers}
end
get_class_levels = function(units)
@ -841,6 +942,11 @@ function init()
if number_of_units >= 4 then return 2
elseif number_of_units >= 2 then return 1
else return 0 end
elseif class == 'sorcerer' then
if number_of_units >= 6 then return 3
elseif number_of_units >= 4 then return 2
elseif number_of_units >= 2 then return 1
else return 0 end
end
end
return {
@ -857,6 +963,7 @@ function init()
forcer = units_to_class_level(units_per_class.forcer, 'forcer'),
swarmer = units_to_class_level(units_per_class.swarmer, 'swarmer'),
voider = units_to_class_level(units_per_class.voider, 'voider'),
sorcerer = units_to_class_level(units_per_class.sorcerer, 'sorcerer'),
}
end
@ -1181,20 +1288,17 @@ function init()
if run.level and run.level > 0 then
main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
end
main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
--[[
main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', run.level or 0, run.units or {}, passives)
--[[
main:add(Arena'arena')
main:go_to('arena', 23, {
{character = 'wizard', level = 1},
{character = 'spellblade', level = 1},
{character = 'chronomancer', level = 1},
{character = 'lich', level = 1},
{character = 'psykino', level = 1},
}, passives)
]]--
main:add(Arena'arena')
main:go_to('arena', 16, {
{character = 'witch', level = 3},
}, passives)
trigger:every(2, function()
if debugging_memory then

View File

@ -262,6 +262,10 @@ function Unit:calculate_stats(first_run)
self.base_hp = 100*math.pow(2, self.level-1)
self.base_dmg = 10*math.pow(2, self.level-1)
self.base_mvspd = 75
elseif self:is(Illusion) then
self.base_hp = 100*math.pow(2, self.level-1)
self.base_dmg = 10*math.pow(2, self.level-1)
self.base_mvspd = 15
elseif self:is(EnemyCritter) or self:is(Critter) then
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}

View File

@ -73,6 +73,32 @@ function Player:init(args)
end
end, nil, nil, 'heal')
elseif self.character == 'arcanist' then
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()
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})
end
end, nil, nil, 'shoot')
elseif self.character == 'illusionist' 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()
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))
end
end, nil, nil, 'shoot')
self.t:every(8, function()
self.t:every(0.25, function()
SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y)
illusion1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
Illusion{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1, crit = (self.level == 3) and random:bool(50)}
end}
end, self.level == 3 and 2 or 1)
end)
elseif self.character == 'outlaw' then
self.attack_sensor = Circle(self.x, self.y, 96)
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()
@ -100,7 +126,7 @@ function Player:init(args)
elseif self.character == 'saboteur' then
self.t:every(8, function()
self.t:every(0.25, function()
SpawnEffect{group = main.current.effects, x = self.x, y = self.y, action = function(x, y)
SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y)
Saboteur{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1, crit = (self.level == 3) and random:bool(50)}
end}
end, 2)
@ -202,6 +228,11 @@ function Player:init(args)
end)
end
elseif self.character == 'witch' then
self.t:every(6, function()
self:dot_attack(24, {duration = random:float(12, 16), homing = true})
end, nil, nil, 'attack')
elseif self.character == 'barbarian' then
self.t:every(8, function()
self:attack(96, {stun = 4})
@ -267,7 +298,7 @@ function Player:init(args)
elseif self.character == 'jester' then
self.t:every({6, 10}, function()
buff1:play{pitch = random:float(0.9, 1.1), volume = 0.5}
local enemies = table.first(table.shuffle(main.current.main:get_objects_by_classes(main.current.enemies)), 5)
local enemies = table.first(table.shuffle(main.current.main:get_objects_by_classes(main.current.enemies)), self.level == 3 and 8 or 4)
for _, enemy in ipairs(enemies) do
if self:distance_to_object(enemy) < 128 then
enemy:curse('jester', 6*(self.hex_duration_m or 1), self.level == 3, self)
@ -1158,7 +1189,7 @@ function Player:shoot(r, mods)
dual_gunner2:play{pitch = random:float(0.95, 1.05), volume = 0.3}
elseif self.character == 'archer' or self.character == 'hunter' or self.character == 'barrager' or self.character == 'corruptor' then
archer1:play{pitch = random:float(0.95, 1.05), volume = 0.35}
elseif self.character == 'wizard' or self.character == 'lich' then
elseif self.character == 'wizard' or self.character == 'lich' or self.character == 'arcanist' or self.character == 'illusionist' then
wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.15}
elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'spellblade' or self.character == 'jester' or self.character == 'assassin' or self.character == 'beastmaster' then
_G[random:table{'scout1', 'scout2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.35}
@ -1173,6 +1204,10 @@ function Player:shoot(r, mods)
frost1:play{pitch = random:float(0.95, 1.05), volume = 0.3}
end
if self.character == 'arcanist' then
arcane1:play{pitch = random:float(0.95, 1.05), volume = 0.3}
end
if self.chance_to_barrage and random:bool(self.chance_to_barrage) then
self:barrage(r, 3)
end
@ -1205,7 +1240,8 @@ function Player:dot_attack(area, mods)
mods = mods or {}
camera:shake(2, 0.5)
self.hfx:use('shoot', 0.25)
local t = {group = main.current.effects, x = mods.x or self.x, y = mods.y or self.y, r = self.r, rs = self.area_size_m*(area or 64), color = self.color, dmg = self.area_dmg_m*self.dmg, character = self.character, level = self.level}
local t = {group = main.current.effects, x = mods.x or self.x, y = mods.y or self.y, r = self.r, rs = self.area_size_m*(area or 64), color = self.color, dmg = self.area_dmg_m*self.dmg,
character = self.character, level = self.level, parent = self}
DotArea(table.merge(t, mods))
dot1:play{pitch = random:float(0.9, 1.1), volume = 0.5}
@ -1233,6 +1269,7 @@ Projectile:implement(GameObject)
Projectile:implement(Physics)
function Projectile:init(args)
self:init_game_object(args)
self.hfx:add('hit', 1)
self:set_as_rectangle(10, 4, 'dynamic', 'projectile')
self.pierce = args.pierce or 0
self.chain = args.chain or 0
@ -1291,6 +1328,23 @@ function Projectile:init(args)
self.t:every(0.08, function()
HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color}
end)
elseif self.character == 'arcanist' then
self.dmg = 0.2*self.dmg
self.t:every(0.08, function() HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color, r = self.r + math.pi + random:float(-math.pi/6, math.pi/6), v = random:float(10, 25), parent = self} end)
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
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',
parent = self.parent, level = self.parent.level}
Projectile(table.merge(t, mods or {}))
end
end)
elseif self.character == 'witch' and self.level == 3 then
self.chain = 1
end
if self.parent.divine_machine_arrow and table.any(self.parent.classes, function(v) return v == 'ranger' end) then
@ -1381,7 +1435,12 @@ function Projectile:draw()
elseif self.character == 'lich' then
graphics.push(self.x, self.y, self.r, self.spring.x, self.spring.x)
graphics.circle(self.x, self.y, 4 + random:float(-1, 1), self.color)
graphics.circle(self.x, self.y, 3 + random:float(-1, 1), self.color)
graphics.pop()
elseif self.character == 'arcanist' then
graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x)
graphics.circle(self.x, self.y, 4, self.hfx.hit.f and fg[0] or self.color)
graphics.pop()
else
@ -1451,7 +1510,16 @@ function Projectile:on_collision_enter(other, contact)
if self.character == 'spellblade' then
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075}
end
elseif self.character == 'wizard' or self.character == 'lich' then
elseif self.character == 'illusionist_death' then
if self.ricochet <= 0 then
self:die(x, y, r, random:int(2, 3))
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075}
else
local r = Unit.bounce(self, nx, ny)
self.r = r
self.ricochet = self.ricochet - 1
end
elseif self.character == 'wizard' or self.character == 'lich' or self.character == 'arcanist' or self.character == 'arcanist_projectile' or self.character == 'illusionist' or self.character == 'witch' then
self:die(x, y, r, random:int(2, 3))
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075}
elseif self.character == 'cannoneer' then
@ -1511,8 +1579,10 @@ function Projectile:on_trigger_enter(other, contact)
if self.character == 'spellblade' then
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15}
end
elseif self.character == 'wizard' or self.character == 'lich' then
elseif self.character == 'wizard' or self.character == 'lich' or self.character == 'arcanist' or self.character == 'illusionist' or self.character == 'illusionist_death' or self.character == 'witch' then
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15}
elseif self.character == 'arcanist_projectile' then
magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075}
else
hit3:play{pitch = random:float(0.95, 1.05), volume = 0.35}
end
@ -1732,11 +1802,13 @@ end
DotArea = Object:extend()
DotArea:implement(GameObject)
DotArea:implement(Physics)
function DotArea:init(args)
self:init_game_object(args)
self.shape = Circle(self.x, self.y, self.rs)
self.closest_sensor = Circle(self.x, self.y, 128)
if self.character == 'plague_doctor' or self.character == 'pyromancer' then
if self.character == 'plague_doctor' or self.character == 'pyromancer' or self.character == 'witch' then
self.t:every(0.2, function()
local enemies = main.current.main:get_objects_in_shape(self.shape, main.current.enemies)
if #enemies > 0 then self.spring:pull(0.05, 200, 10) end
@ -1810,6 +1882,23 @@ function DotArea:init(args)
end, nil, nil, 'dot')
end
if self.character == 'witch' then
self.v = random:float(40, 80)
self.r = random:table{math.pi/4, 3*math.pi/4, -math.pi/4, -3*math.pi/4}
if self.level == 3 then
self.t:every(1, function()
local enemies = main.current.main:get_objects_in_shape(self.closest_sensor, main.current.enemies)
if enemies and #enemies > 0 then
wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.05}
local r = self:angle_to_object(enemies[1])
HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6}
local t = {group = main.current.main, x = self.x, y = self.y, v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, character = 'witch', parent = self.parent, level = self.parent.level}
Projectile(table.merge(t, mods or {}))
end
end)
end
end
self.color = fg[0]
self.color_transparent = Color(args.color.r, args.color.g, args.color.b, 0.08)
self.rs = 0
@ -1842,6 +1931,17 @@ function DotArea:update(dt)
self.shape:move_to(self.x, self.y)
end
end
if self.character == 'witch' then
self.x, self.y = self.x + self.v*math.cos(self.r)*dt, self.y + self.v*math.sin(self.r)*dt
if self.x >= main.current.x2 - self.shape.rs/2 or self.x <= main.current.x1 + self.shape.rs/2 then
self.r = math.pi - self.r
end
if self.y >= main.current.y2 - self.shape.rs/2 or self.y <= main.current.y1 + self.shape.rs/2 then
self.r = 2*math.pi - self.r
end
self.shape:move_to(self.x, self.y)
end
end
@ -2217,6 +2317,85 @@ end
Illusion = Object:extend()
Illusion:implement(GameObject)
Illusion:implement(Physics)
Illusion:implement(Unit)
function Illusion:init(args)
self:init_game_object(args)
self:init_unit()
self:set_as_rectangle(8, 8, 'dynamic', 'player')
self:set_restitution(0.5)
self.color = character_colors.illusionist
self.character = 'illusionist'
self.classes = {'sorcerer', 'conjurer'}
self:calculate_stats(true)
self:set_as_steerable(self.v, 2000, 4*math.pi, 4)
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()
local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies)
if closest_enemy then
wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.05}
local r = self:angle_to_object(closest_enemy)
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}
local t = {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), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, character = 'illusionist',
parent = self.parent, level = self.parent.level}
Projectile(table.merge(t, mods or {}))
end
end, nil, nil, 'shoot')
self.t:after(12*(self.parent.conjurer_buff_m or 1), function()
local n = n or random:int(3, 4)
for i = 1, n do HitParticle{group = main.current.effects, x = self.x, y = self.y, r = random:float(0, 2*math.pi), color = self.color} end
HitCircle{group = main.current.effects, x = self.x, y = self.y}:scale_down()
self.dead = true
if self.parent.level == 3 then
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.2}
for i = 1, 12 do
Projectile{group = main.current.main, x = self.x, y = self.y, color = self.color, r = (i-1)*math.pi/6, v = 200, dmg = self.parent.dmg, character = 'illusionist_death',
parent = self.parent, level = self.parent.level, pierce = 1, ricochet = 1}
end
end
end)
end
function Illusion:update(dt)
self:update_game_object(dt)
self:calculate_stats()
if not self.target then self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) end
if self.target and self.target.dead then self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) end
if not self.seek_f then return end
if not self.target then
self:seek_point(gw/2, gh/2)
self:wander(50, 200, 50)
self:rotate_towards_velocity(1)
self:steering_separate(32, {Illusion, Seeker, Player})
else
self:seek_point(self.target.x, self.target.y)
self:wander(50, 200, 50)
self:rotate_towards_velocity(1)
self:steering_separate(32, {Illusion, Seeker, Player})
end
self.r = self:get_angle()
self.attack_sensor:move_to(self.x, self.y)
end
function Illusion:draw()
graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x)
graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, self.hfx.hit.f and fg[0] or self.color)
graphics.pop()
end
Critter = Object:extend()
Critter:implement(GameObject)

View File

@ -12,6 +12,7 @@ function shared_init()
green = ColorRamp(Color'#8bbf40', 0.025),
red = ColorRamp(Color'#e91d39', 0.025),
purple = ColorRamp(Color'#8e559e', 0.025),
blue2 = ColorRamp(Color'#4778ba', 0.025),
}
for name, color in pairs(colors) do
_G[name] = color
@ -765,7 +766,11 @@ end
function HitParticle:draw()
graphics.push(self.x, self.y, self.r)
graphics.rectangle(self.x, self.y, self.w, self.h, 2, 2, self.color)
if self.parent and not self.parent.dead then
graphics.rectangle(self.x, self.y, self.w, self.h, 2, 2, self.parent.hfx.hit.f and fg[0] or self.color)
else
graphics.rectangle(self.x, self.y, self.w, self.h, 2, 2, self.color)
end
graphics.pop()
end

93
todo
View File

@ -1,37 +1,68 @@
Hide cursor during waves
Mouse follow control?
Item reroll for 15 gold
Option to have an arrow at the head of the snake - https://imgur.com/a/poXVsoN
Option to turn off camera movement
Headbutter damage should falloff faster after it strikes
https://i.imgur.com/QN0Ntq2.png
https://i.imgur.com/YfhqDYr.png
https://i.imgur.com/ps4OA7o.png
Remove level 3 units from rotation
Fix highlight colors and highlight reserve
Fix bug where quitting on level 1 jumps to level 2
Fix lock bug after death/win - https://i.imgur.com/iUyOtLk.png
Options menu from buy screen
Fix fullscreen with different resolutions that don't scale properly
Volume slider
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
Change cursers to trigger only near enemies
Rename tutorial to guide or manual
Add visuals for defensive ouroboros
Unlock automatically on shop enter
Bug fixes
Headbutter damage should falloff faster after it strikes
https://i.imgur.com/QN0Ntq2.png
https://i.imgur.com/YfhqDYr.png
https://i.imgur.com/ps4OA7o.png
https://i.imgur.com/j1LS3zt.png
https://i.imgur.com/XXJn4uW.png
Fix bug where quitting on level 1 jumps to level 2
Fix lock bug after death/win - https://i.imgur.com/iUyOtLk.png
Fix enemies still spawning after arena clear (this happens with the extra enemy spawns that were blocked earlier)
Fix death + win at the same time bug
Balance
Buff tanks, maybe add a simple forcer ability to them
Buff 24/25 HP again
Buff headbutter (+ trigger range)
New
Sorcerer = sorcerers repeat their attacks once every 4/3/2 attacks
* Arcanist (tier 1 sorcerer) - launches a slow piercing orb that launches other piercing projectiles, Lv.3 effect - 50% increased attack speed for the orb and 2 projectiles are released per cast
* Sorcerer + Conjurer = Illusionist - launches a projectile that deals X damage and creates copies that do the same, Lv.3 effect - doubles the number of copies created and they release additional projectiles on death
* Sorcerer + Voider = Witch - creates an area that ricochets around the arena and deals X damage over time, Lv.3 effect - the area periodically releases projectiles that chains once
Sorcerer + Curser = Silencer - curses 5 nearby enemies for 6 seconds, preventing them from using special attacks, Lv.3 effect - the curse also deals X damage over time
Sorcerer + Nuker = Vulcanist - creates a volcano that explodes the nearby area 5 times, Lv.3 effect - the volcano spawn also deals damage and doubles the number of explosions
Sorcerer + Forcer = Warden - creates a force field around a random unit that prevents enemies from entering, Lv.3 effect - creates the force field around 2 other random units
Sorcerer + Psyker = Psychic - creates a small area that deals X damage, Lv.3 effect - the attack can happen from any distance and repeats twice
Sorcerer = sorcerers repeat their attacks once every 3/2 attacks
Sorcerer + Conjurer = Illusionist - launches a projectile that deals X damage and creates copies that do the same, Lv.3 effect - doubles the number of copies created and they release additional projectiles on death
Sorcerer + Voider = Witch - creates an area that deals X damage over time and seeks nearby enemies, Lv.3 effect - the area periodically releases homing projectiles that pierce 2 times
Sorcerer + Curser = Linker - links 3 enemies together, they share damage taken, Lv.3 effect - link 6 enemies instead and damage shared is doubled
Sorcerer + Nuker = Vulcanist - creates a volcano that explodes the nearby area 5 times, Lv.3 effect - the volcano spawn also deals damage and doubles the number of explosions
Sorcerer + Forcer = Warden - creates a force field around a random unit that prevents enemies from entering, Lv.3 effect - creates the force field around 2 other random units
Sorcerer update patch notes
Decreased projectile speed for the exploder (blue enemy)
Changed shooters (white enemy) to scale with NG+ difficulty
Increased damage and movement speed for enemy swarmers (small purple enemies)
Added tooltips for "restart run" and "tutorial" buttons on the shop
Fixed text that was going outside the screen for the Priest and the Assassin
Fixed bug where Divine Punishment would continue triggering after death
Removed cooldown visuals on snake for units that don't have cooldowns (Squire, Chronomancer and Psykeeper)
Slightly decreased base game's difficulty
Fixed NG+ difficulty only being able to be lowered, now you can lower or increase it (up to your maximum acquired value)
Patch Notes:
Decreased projectile speed for the exploder (blue enemy)
Added tooltips for "restart run" and "tutorial" buttons on the shop
Fixed text that was going outside the screen for the Priest and the Assassin
Fixed bug where Divine Punishment would continue triggering after death
Removed cooldown visuals on snake for units that don't have cooldowns (Squire, Chronomancer and Psykeeper)
Slightly decreased base game's difficulty
Fixed NG+ difficulty only being able to be lowered, now you can lower or increase it (up to your maximum acquired value)
---
Future ideas:
Chaos related class = Invoker - shoots a projectile with random properties, Lv.3 effect - ???
Chaos related classes
Invoker - shoots a projectile with random properties, Lv.3 effect - ???
Trappers:
Triangler - drops a trap and the 3rd trap will trigger the area, dealing X AoE damage 2-3 times
Bench? - https://i.imgur.com/B1gNVKk.png
Balance option for when there are more sets - https://i.imgur.com/JMynwbL.png
Negative effect: colliding with yourself kills one of your units
Go through this later https://i.imgur.com/4t7NA32.png <- lots of good improvements
Remove level 3 units from rotation
Hide cursor during waves
Mouse follow control?
Roguelite update:
Slay the Spire-like node selection map (copy code from SHOOTRX repo as this is already implemented there)
Units die permanently when they die