diff --git a/arena.lua b/arena.lua index 76fbc2b..42ca561 100644 --- a/arena.lua +++ b/arena.lua @@ -235,13 +235,13 @@ function Arena:on_enter(from, level, units) table.insert(units, self.player) for _, f in ipairs(self.player.followers) do table.insert(units, f) end - local class_levels = get_class_levels(get_number_of_units_per_class(units)) + local class_levels = get_class_levels(units) self.ranger_level = class_levels.ranger self.warrior_level = class_levels.warrior self.mage_level = class_levels.mage self.rogue_level = class_levels.rogue self.nuker_level = class_levels.nuker - self.trapper_level = class_levels.trapper + self.curser_level = class_levels.curser self.forcer_level = class_levels.forcer self.swarmer_level = class_levels.swarmer self.voider_level = class_levels.voider diff --git a/assets/images/trapper.png b/assets/images/curser.png similarity index 100% rename from assets/images/trapper.png rename to assets/images/curser.png diff --git a/assets/sounds/Explosion Fireworks_01.ogg b/assets/sounds/Explosion Fireworks_01.ogg new file mode 100644 index 0000000..9bff10e Binary files /dev/null and b/assets/sounds/Explosion Fireworks_01.ogg differ diff --git a/assets/sounds/Fire bolt 5.ogg b/assets/sounds/Fire bolt 5.ogg new file mode 100644 index 0000000..d73a70f Binary files /dev/null and b/assets/sounds/Fire bolt 5.ogg differ diff --git a/assets/sounds/Frost Bolt 20.ogg b/assets/sounds/Frost Bolt 20.ogg new file mode 100644 index 0000000..1ca3289 Binary files /dev/null and b/assets/sounds/Frost Bolt 20.ogg differ diff --git a/devlog.md b/devlog.md index 5ecfbe2..0069376 100644 --- a/devlog.md +++ b/devlog.md @@ -863,3 +863,27 @@ Either way, 21 out of 40 characters 100% done is still good. | Psykeeper | Crucio | also redistributes damage taken as damage to all enemies at double value | | Engineer | Upgrade | every 3rd sentry dropped upgrade all sentries with +100% damage and attack speed | | Plague Doctor | Black Death Steam | nearby enemies take an additional X damage per second | + +# Day 54 - 11/04/21 + +Implemented more 7 units fully. Today was slower because these were all new units that also were the first units for the new 4 classes, so I had to do some more work to get it going. The rest should go faster tomorrow. + +| Character | Classes | Description | +| --- | --- | --- | +| Barbarian | curser, warrior | deals X AoE damage and stuns enemies hit for 4 seconds | +| Juggernaut | forcer, warrior | deals X AoE damage and pushes enemies away with a strong force | +| Lich | mage | launches a slow projectilt that jumps 7 times, dealing 2X damage per hit | +| Cryomancer | mage, voider | nearby enemies take X damage per second | +| Pyromancer | mage, nuker, voider | nearby enemies take X damage per second | +| Corruptor | ranger, swarmer | spawn 3 small critters if the corruptor kills an enemy | +| Beastmaster | rogue, swarmer | spawn 2 small critters if the beastmaster crits | + +| Character | Lv.3 Effect Name | Lv.3 Effect Description | +| --- | --- | --- | +| Barbarian | Seism | stunned enemies also take +100% damage | +| Juggernaut | Brutal Impact | enemies pushed by the juggernaut take 4X damage if they hit a wall | +| Lich | Chain Frost | chain frost slows enemies hit by 80% for 2 seconds and chains +7 times | +| Cryomancer | Frostbite | enemies are also slowed by 60% while in the area | +| Pyromancer | Ignite | enemies killed by the pyromancer explode, dealing X AoE damage | +| Corruptor | Corruption | spawn 3 small critters if the corruptor hits an enemy | +| Beastmaster | Call of the Wild | spawn 2 small critters if the beastmaster gets hit | diff --git a/enemies.lua b/enemies.lua index 3730178..8bac598 100644 --- a/enemies.lua +++ b/enemies.lua @@ -243,6 +243,8 @@ function Seeker:update(dt) self:calculate_stats() + self.stun_dmg_m = (self.barbarian_stunned and 2 or 1) + if self.shooter then self.t:set_every_multiplier('shooter', (1 - self.level*0.02)) end @@ -253,6 +255,7 @@ function Seeker:update(dt) if self.push_invulnerable then self.push_invulnerable = false end self.being_pushed = false self.steering_enabled = true + self.juggernaut_push = false self:set_damping(0) self:set_angular_damping(0) end @@ -318,6 +321,10 @@ function Seeker:on_collision_enter(other, contact) if other:is(Wall) then self.hfx:use('hit', 0.15, 200, 10, 0.1) self:bounce(contact:getNormal()) + if self.juggernaut_push then + self:hit(self.juggernaut_push) + hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} + 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 @@ -344,14 +351,26 @@ end function Seeker:hit(damage, projectile) + local pyrod = self.pyrod + self.pyrod = false if self.dead then return end self.hfx:use('hit', 0.25, 200, 10) if self.push_invulnerable then return end self:show_hp() - local actual_damage = self:calculate_damage(damage) + local actual_damage = self:calculate_damage(damage)*self.stun_dmg_m self.hp = self.hp - actual_damage main.current.damage_dealt = main.current.damage_dealt + actual_damage + + if projectile and projectile.spawn_critters_on_hit then + critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + trigger:after(0.01, function() + for i = 1, projectile.spawn_critters_on_hit do + Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = projectile.parent.dmg} + end + end) + end + if self.hp <= 0 then self.dead = true for i = 1, random:int(4, 6) do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color} end @@ -388,6 +407,22 @@ function Seeker:hit(damage, projectile) end end) end + + if pyrod then + trigger:after(0.01, function() + Area{group = main.current.main, x = self.x, y = self.y, color = red[0], w = 32*pyrod.parent.area_size_m, r = random:float(0, 2*math.pi), dmg = pyrod.parent.area_dmg_m*pyrod.dmg, + character = pyrod.character, level = pyrod.level, parent = pyrod.parent} + end) + end + + if projectile and projectile.spawn_critters_on_kill then + trigger:after(0.01, function() + critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + for i = 1, projectile.spawn_critters_on_kill do + Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 5, dmg = projectile.parent.dmg} + end + end) + end end end diff --git a/main.lua b/main.lua index fc8c6e7..dc1632e 100644 --- a/main.lua +++ b/main.lua @@ -19,6 +19,9 @@ function init() music.volume = 0 local s = {tags = {sfx}} + frost1 = Sound('Frost Bolt 20.ogg', s) + pyro1 = Sound('Fire bolt 5.ogg', s) + pyro2 = Sound('Explosion Fireworks_01.ogg', s) dot1 = Sound('Magical Swoosh 18.ogg', s) gun_kata1 = Sound('Pistol Shot_07.ogg', s) gun_kata2 = Sound('Pistol Shot_08.ogg', s) @@ -104,7 +107,7 @@ function init() conjurer = Image('conjurer') enchanter = Image('enchanter') psyker = Image('psyker') - trapper = Image('trapper') + curser = Image('curser') forcer = Image('forcer') swarmer = Image('swarmer') voider = Image('voider') @@ -119,9 +122,9 @@ function init() ['rogue'] = red[0], ['enchanter'] = blue[0], ['psyker'] = fg[0], - ['trapper'] = orange[0], + ['curser'] = purple[0], ['forcer'] = yellow[0], - ['swarmer'] = purple[0], + ['swarmer'] = orange[0], ['voider'] = purple[0], } @@ -135,9 +138,9 @@ function init() ['rogue'] = 'red', ['enchanter'] = 'blue', ['psyker'] = 'fg', - ['trapper'] = 'orange', + ['curser'] = 'purple', ['forcer'] = 'yellow', - ['swarmer'] = 'purple', + ['swarmer'] = 'orange', ['voider'] = 'purple', } @@ -163,25 +166,25 @@ function init() ['psykeeper'] = fg[0], ['engineer'] = orange[0], ['plague_doctor'] = purple[0], - ['fisherman'] = yellow[0], + ['barbarian'] = yellow[0], ['juggernaut'] = yellow[0], ['lich'] = blue[0], ['cryomancer'] = blue[0], ['pyromancer'] = red[0], - ['corruptor'] = purple[0], + ['corruptor'] = orange[0], ['beastmaster'] = red[0], ['launcher'] = orange[0], - ['spiker'] = orange[0], + ['bard'] = red[0], ['assassin'] = purple[0], - ['host'] = purple[0], + ['host'] = orange[0], ['carver'] = green[0], ['bane'] = purple[0], ['psykino'] = fg[0], ['arbalester'] = green[0], - ['barbarian'] = yellow[0], + ['highlander'] = yellow[0], ['sapper'] = blue[0], ['priest'] = green[0], - ['burrower'] = orange[0], + ['infestor'] = orange[0], ['flagellant'] = fg[0], } @@ -207,25 +210,25 @@ function init() ['psykeeper'] = 'fg', ['engineer'] = 'orange', ['plague_doctor'] = 'purple', - ['fisherman'] = 'yellow', + ['barbarian'] = 'yellow', ['juggernaut'] = 'yellow', ['lich'] = 'blue', ['cryomancer'] = 'blue', ['pyromancer'] = 'red', - ['corruptor'] = 'purple', + ['corruptor'] = 'orange', ['beastmaster'] = 'red', ['launcher'] = 'orange', - ['spiker'] = 'orange', + ['bard'] = 'red', ['assassin'] = 'purple', - ['host'] = 'purple', + ['host'] = 'orange', ['carver'] = 'green', ['bane'] = 'purple', ['psykino'] = 'fg', ['arbalester'] = 'green', - ['barbarian'] = 'yellow', + ['highlander'] = 'yellow', ['sapper'] = 'blue', ['priest'] = 'green', - ['burrower'] = 'orange', + ['infestor'] = 'orange', ['flagellant'] = 'fg', } @@ -251,25 +254,25 @@ function init() ['psykeeper'] = {'healer', 'psyker'}, ['engineer'] = {'conjurer'}, ['plague_doctor'] = {'nuker', 'voider'}, - ['fisherman'] = {'trapper', 'warrior'}, + ['barbarian'] = {'curser', 'warrior'}, ['juggernaut'] = {'forcer', 'warrior'}, ['lich'] = {'mage'}, ['cryomancer'] = {'mage', 'voider'}, ['pyromancer'] = {'mage', 'nuker', 'voider'}, ['corruptor'] = {'ranger', 'swarmer'}, ['beastmaster'] = {'rogue', 'swarmer'}, - ['launcher'] = {'trapper', 'forcer'}, - ['spiker'] = {'trapper', 'rogue'}, + ['launcher'] = {'curser', 'forcer'}, + ['bard'] = {'curser', 'rogue'}, ['assassin'] = {'rogue', 'voider'}, ['host'] = {'conjurer', 'swarmer'}, - ['carver'] = {'conjurer', 'healer'}, - ['bane'] = {'swarmer', 'voider'}, + ['carver'] = {'conjurer', 'curser', 'healer'}, + ['bane'] = {'curser', 'voider'}, ['psykino'] = {'mage', 'psyker', 'forcer'}, ['arbalester'] = {'ranger', 'forcer'}, - ['barbarian'] = {'warrior'}, - ['sapper'] = {'trapper', 'enchanter', 'healer'}, + ['highlander'] = {'warrior'}, + ['sapper'] = {'enchanter', 'voider', 'healer'}, ['priest'] = {'healer'}, - ['burrower'] = {'trapper', 'swarmer'}, + ['infestor'] = {'curser', 'swarmer'}, ['flagellant'] = {'psyker', 'enchanter'}, } @@ -295,25 +298,25 @@ function init() ['psykeeper'] = '[green]Healer, [fg]Psyker', ['engineer'] = '[orange]Conjurer', ['plague_doctor'] = '[red]Nuker, [purple]Voider', - ['fisherman'] = '[orange]Trapper, [yellow]Warrior', + ['barbarian'] = '[purple]Curser, [yellow]Warrior', ['juggernaut'] = '[yellow]Forcer, Warrior', ['lich'] = '[blue]Mage', ['cryomancer'] = '[blue]Mage, [purple]Voider', ['pyromancer'] = '[blue]Mage, [red]Nuker, [purple]Voider', - ['corruptor'] = '[green]Ranger, [purple]Swarmer', - ['beastmaster'] = '[red]Rogue, [purple]Swarmer', - ['launcher'] = '[orange]Trapper, [yellow]Forcer', - ['spiker'] = '[orange]Trapper, [red]Rogue', + ['corruptor'] = '[green]Ranger, [orange]Swarmer', + ['beastmaster'] = '[red]Rogue, [orange]Swarmer', + ['launcher'] = '[purple]Curser, [yellow]Forcer', + ['bard'] = '[purple]Curser, [red]Rogue', ['assassin'] = '[red]Rogue, [purple]Voider', - ['host'] = '[orange]Conjurer, [purple]Swarmer', - ['carver'] = '[orange]Conjurer, [green]Healer', - ['bane'] = '[purple]Swarmer, Voider', + ['host'] = '[orange]Conjurer, [orange]Swarmer', + ['carver'] = '[orange]Conjurer, [purple]Curser, [green]Healer', + ['bane'] = '[purple]Curser, Voider', ['psykino'] = '[blue]Mage, [fg]Psyker, [yellow]Forcer', ['arbalester'] = '[green]Ranger, [yellow]Forcer', - ['barbarian'] = '[yellow]Warrior', - ['sapper'] = '[orange]Trapper, [blue]Enchanter, [green]Healer', + ['highlander'] = '[yellow]Warrior', + ['sapper'] = '[blue]Enchanter, [purple]Voider, [green]Healer', ['priest'] = '[green]Healer', - ['burrower'] = '[orange]Trapper, [purple]Swarmer', + ['infestor'] = '[purple]Curser, [orange]Swarmer', ['flagellant'] = '[fg]Psyker, [blue]Enchanter', } @@ -355,27 +358,26 @@ function init() ['psykeeper'] = function(lvl) return '[fg]all damage taken is stored up to [yellow]50%[fg] max HP and distributed as healing to all allies' end, ['engineer'] = function(lvl) return '[fg]drops sentries that shoot bursts of projectiles, each dealing [yellow]' .. get_character_stat('engineer', lvl, 'dmg') .. '[fg] damage' end, ['plague_doctor'] = function(lvl) return '[fg]creates an area that deals [yellow]' .. get_character_stat('plague_doctor', lvl, 'dmg') .. '[fg] damage per second' end, - ['fisherman'] = function(lvl) return '[fg]throws a net that entangles enemies and prevents them from moving for [yellow]4[fg] seconds' end, - ['juggernaut'] = function(lvl) return '[fg]creates a small area that deals [yellow]' .. get_character_stat('juggernaut', lvl, 'dmg') .. '[fg] damage and pushes enemies away with a strong force' end, - ['lich'] = function(lvl) return '[fg]launches a chain frost that jumps [yellow]7[fg] times, dealing [yellow]' .. - get_character_stat('lich', lvl, 'dmg') .. '[fg] damage and slowing enemies by [yellow]50%[fg] for [yellow]2[fg] seconds on hit' end, - ['cryomancer'] = function(lvl) return '[fg]nearby enemies take [yellow]' .. get_character_stat('cryomancer', lvl, 'dmg') .. '[fg] damage per second and have [yellow]25%[fg] decreased movement speed' end, - ['pyromancer'] = function(lvl) return '[fg]nearby enemies take [yellow]' .. get_character_stat('pyromancer', lvl, 'dmg') .. '[fg] damage per second and deal [yellow]25%[fg] decreased damage' end, + ['barbarian'] = function(lvl) return '[fg]deals [yellow]' .. get_character_stat('barbarian', lvl, 'dmg') .. '[fg] AoE damage and stuns enemies hit for [yellow]4[fg] seconds' end, + ['juggernaut'] = function(lvl) return '[fg]deals [yellow]' .. get_character_stat('juggernaut', lvl, 'dmg') .. '[fg] AoE damage and pushes enemies away with a strong force' end, + ['lich'] = function(lvl) return '[fg]launches a slow projectile that jumps [yellow]7[fg] times, dealing [yellow]' .. 2*get_character_stat('lich', lvl, 'dmg') .. '[fg] damage per hit' end, + ['cryomancer'] = function(lvl) return '[fg]nearby enemies take [yellow]' .. get_character_stat('cryomancer', lvl, 'dmg') .. '[fg] damage per second' end, + ['pyromancer'] = function(lvl) return '[fg]nearby enemies take [yellow]' .. get_character_stat('pyromancer', lvl, 'dmg') .. '[fg] damage per second' end, ['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]creates a trap that launches enemies that trigger it' end, - ['spiker'] = function(lvl) return '[fg]creates a trap that crits when triggered, dealing [yellow]' .. 4*get_character_stat('spiker', lvl, 'dmg') .. '[fg] damage' end, + ['launcher'] = function(lvl) return '[fg]nearby enemies are afflicted with a kinetic curse that triggers after [yellow]4[fg] seconds' end, + ['bard'] = function(lvl) return "[fg]shoots a projectile that inflicts enemies hit with the bard's curse" end, ['assassin'] = function(lvl) return '[fg]throws a piercing knife that deals [yellow]' .. get_character_stat('assassin', lvl, 'dmg') .. '[fg] damage and inflicts poison that deals [yellow]' .. get_character_stat('assassin', lvl, 'dmg')/2 .. '[fg] damage per second for [yellow]4[fg] seconds' end, ['host'] = function(lvl) return '[fg]creates [yellow]2[fg] overlords that periodically spawn small critters' end, ['carver'] = function(lvl) return '[fg]carves a statue that periodically heals for [yellow]20%[fg] max HP in an area around it' end, - ['bane'] = function(lvl) return '[fg]spawn a small critter that explodes and deals [yellow]' .. get_character_stat('bane', lvl, 'dmg') .. '[fg] damage per second in an area' end, + ['bane'] = function(lvl) return '[fg]creates a large area that curses enemies to take [yellow]50%[fg] increased damage over time' end, ['psykino'] = function(lvl) return '[fg]quickly pulls enemies together and then release them with a force' end, ['arbalester'] = function(lvl) return '[fg]launches a massive arrow that deals [yellow]' .. get_character_stat('arbalester', lvl, 'dmg') .. '[fg] damage and pushes enemies back, ignoring knockback resistances' end, - ['barbarian'] = function(lvl) return '[fg]creates a small area that deals [yellow]' .. 4*get_character_stat('barbarian', lvl, 'dmg') .. '[fg] damage and stuns for [yellow]2[fg] seconds' end, - ['sapper'] = function(lvl) return '[fg]creates a trap that steals [yellow]10%[fg] enemy HP and grants you [yellow]+25%[fg] movement speed' end, + ['highlander'] = function(lvl) return '[fg]deals [yellow]' .. 6*get_character_stat('highlander', lvl, 'dmg') .. '[fg] AoE damage' end, + ['sapper'] = function(lvl) return '[fg]periodically steals [yellow]5%[fg] max HP per second from nearby enemies and gain 25% increased movement speed' end, ['priest'] = function(lvl) return '[fg]heals all allies for [yellow]20%[fg] their max HP' end, - ['burrower'] = function(lvl) return '[fg]creates a trap that contains [yellow]6[fg] small critters' end, + ['infestor'] = function(lvl) return '[fg]curses enemies in an area for [yellow]6[fg] seconds, they will release multiple critters on death' end, ['flagellant'] = function(lvl) return '[fg]deals damage to self and grants [yellow]+4%[fg] damage to all allies per cast' end, } @@ -401,25 +403,25 @@ function init() ['psykeeper'] = '[fg]Crucio', ['engineer'] = '[orange]Upgrade', ['plague_doctor'] = '[purple]Black Death Steam', - ['fisherman'] = '[yellow]Electric Net', + ['barbarian'] = '[yellow]Seism', ['juggernaut'] = '[yellow]Brutal Impact', - ['lich'] = '[blue]Piercing Frost', + ['lich'] = '[blue]Chain Frost', ['cryomancer'] = '[blue]Frostbite', ['pyromancer'] = '[red]Ignite', - ['corruptor'] = '[purple]Infestation', + ['corruptor'] = '[orange]Corruption', ['beastmaster'] = '[red]Call of the Wild', ['launcher'] = '[orange]Kineticism', - ['spiker'] = '[orange]Caltrops', + ['bard'] = "[orange]The Bard's Song", ['assassin'] = '[purple]Toxic Delivery', - ['host'] = '[purple]Invasion', + ['host'] = '[orange]Invasion', ['carver'] = '[green]World Tree', - ['bane'] = '[purple]Baneling Swarm', + ['bane'] = '[purple]Nightmare', ['psykino'] = '[fg]Magnetic Force', ['arbalester'] = '[green]Ballista Sinitra', - ['barbarian'] = '[yellow]Berserk', - ['sapper'] = '[blue]Chain Reaction', + ['highlander'] = '[yellow]Crosscut', + ['sapper'] = '[blue]Enduring Sap', ['priest'] = '[green]Divine Intervention', - ['burrower'] = '[orange]Zergling Rush', + ['infestor'] = '[orange]Infestation', ['flagellant'] = '[red]Zealotry', } @@ -450,7 +452,7 @@ function init() ['lich'] = '[light_bg]Piercing Frost', ['cryomancer'] = '[light_bg]Frostbite', ['pyromancer'] = '[light_bg]Ignite', - ['corruptor'] = '[light_bg]Infestation', + ['corruptor'] = '[light_bg]Corruption', ['beastmaster'] = '[light_bg]Call of the Wild', ['launcher'] = '[light_bg]Kineticism', ['spiker'] = '[light_bg]Caltrops', @@ -463,7 +465,7 @@ function init() ['barbarian'] = '[light_bg]Berserk', ['sapper'] = '[light_bg]Chain Reaction', ['priest'] = '[light_bg]Divine Intervention', - ['burrower'] = '[light_bg]Zergling Rush', + ['infestor'] = '[light_bg]Infestation', ['flagellant'] = '[light_bg]Zealotry', } @@ -489,26 +491,26 @@ function init() ['psykeeper'] = function() return '[fg]also redistributes damage taken as damage to all enemies at [yellow]double[fg] value' end, ['engineer'] = function() return '[fg]every 3rd sentry dropped upgrade all sentries with [yellow]+100%[fg] damage and attack speed' end, ['plague_doctor'] = function() return '[fg]nearby enemies take an additional [yellow]' .. get_character_stat('plague_doctor', 3, 'dmg') .. '[fg] damage per second' end, - ['fisherman'] = function() return '[fg]enemies caught take [yellow]' .. get_character_stat('fisherman', 3, 'dmg')/4 .. '[fg] damage per second' end, - ['juggernaut'] = function() return '[fg]enemies pushed away by the juggernaut are instantly killed if they hit a wall' end, - ['lich'] = function() return '[fg]chain frost decreases enemy defenses by [yellow]30[fg] for [yellow]4[fg] seconds' end, - ['cryomancer'] = function() return '[fg]enemies killed by the cryomancer freeze nearby enemies, frozen enemies take increased damage and do not move' end, + ['barbarian'] = function() return '[fg]stunned enemies also take [yellow]100%[fg] increased damage' end, + ['juggernaut'] = function() return '[fg]enemies pushed by the juggernaut take [yellow]' .. 4*get_character_stat('juggernaut', 3, 'dmg') .. '[fg] damage if they hit a wall' end, + ['lich'] = function() return '[fg]chain frost slows enemies hit by [yellow]80%[fg] for [yellow]2[fg] seconds and chains [yellow]+7[fg] times' end, + ['cryomancer'] = function() return '[fg]enemies are also slowed by [yellow]60%[fg] while in the area' end, ['pyromancer'] = function() return '[fg]enemies killed by the pyromancer explode, dealing [yellow]' .. get_character_stat('pyromancer', 3, 'dmg') .. '[fg] AoE damage' end, ['corruptor'] = function() return '[fg]spawn [yellow]3[fg] small critters if the corruptor hits an enemy' end, ['beastmaster'] = function() return '[fg]spawn [yellow]2[fg] small critters if the beastmaster gets hit' end, ['launcher'] = function() return '[fg]enemies launched that hit other enemies push those enemies at double the force they were pushed' end, - ['spiker'] = function() return '[fg]slows enemies hit by [yellow]50%[fg] for [yellow]2[fg] seconds and deals [yellow]' .. get_character_stat('spiker', 3, 'dmg') .. '[fg] damage per second' end, + ['bard'] = function() return '[fg] every 5th attack consume the curse to deal [yellow]' .. 3*get_character_stat('bard', 3, 'dmg') .. '[fg] damage to affected enemies' end, ['assassin'] = function() return '[fg]poison inflicted from crits deals [yellow]8x[fg] damage' end, ['host'] = function() return '[fg][yellow]+50%[fg] critter spawn rate' end, ['carver'] = function() return '[fg]carves a tree that heals in a bigger area and removes all buffs from enemies' end, - ['bane'] = function() return '[fg]spawn [yellow]4[fg] banelings' end, + ['bane'] = function() return '[fg]the area also deals [yellow]' .. get_character_stat('bane', 3, 'dmg') .. '[fg] damage per second and slows enemies by [yellow]50%[fg]' end, ['psykino'] = function() return '[fg]enemies pulled together are forced to collide with each other multiple times' end, ['arbalester'] = function() return '[fg]enemies hit by the arrow have defense decreased by [yellow]100[fg] for [yellow]4[fg] seconds' end, - ['barbarian'] = function() return '[fg][yellow]+50%[fg] attack speed' end, - ['sapper'] = function() return '[fg]when a sapper trap is triggered other nearby traps are also triggered' end, + ['crosscut'] = function() return '[fg]two crosscutting areas of +100% size are created instead' end, + ['sapper'] = function() return '[fg]sapped enemies permanently take [yellow]' .. get_character_stat('sapper', 3, 'dmg') .. '[fg] damage per second' end, ['priest'] = function() return '[fg]at the start of the round pick [yellow]3[fg] units at random and grants them a buff that prevents death once' end, - ['burrower'] = function() return '[fg][yellow]triples[fg] the number of critters released' end, - ['flagellant'] = function() return '[fg]deals damage to all allies instead and grants [yellow]+10%[fg] damage to all allies per cast' end, + ['infestor'] = function() return '[fg][yellow]triples[fg] the number of critters released' end, + ['flagellant'] = function() return '[fg]deals damage to all allies instead and grants stacking [yellow]+10%[fg] damage to all allies per cast' end, } character_stats = { @@ -533,7 +535,7 @@ function init() ['psykeeper'] = function(lvl) return get_character_stat_string('psykeeper', lvl) end, ['engineer'] = function(lvl) return get_character_stat_string('engineer', lvl) end, ['plague_doctor'] = function(lvl) return get_character_stat_string('plague_doctor', lvl) end, - ['fisherman'] = function(lvl) return get_character_stat_string('fisherman', lvl) end, + ['barbarian'] = function(lvl) return get_character_stat_string('barbarian', lvl) end, ['juggernaut'] = function(lvl) return get_character_stat_string('juggernaut', lvl) end, ['lich'] = function(lvl) return get_character_stat_string('lich', lvl) end, ['cryomancer'] = function(lvl) return get_character_stat_string('cryomancer', lvl) end, @@ -541,17 +543,17 @@ function init() ['corruptor'] = function(lvl) return get_character_stat_string('corruptor', lvl) end, ['beastmaster'] = function(lvl) return get_character_stat_string('beastmaster', lvl) end, ['launcher'] = function(lvl) return get_character_stat_string('launcher', lvl) end, - ['spiker'] = function(lvl) return get_character_stat_string('spiker', lvl) end, + ['bard'] = function(lvl) return get_character_stat_string('bard', lvl) end, ['assassin'] = function(lvl) return get_character_stat_string('assassin', lvl) end, ['host'] = function(lvl) return get_character_stat_string('host', lvl) end, ['carver'] = function(lvl) return get_character_stat_string('carver', lvl) end, ['bane'] = function(lvl) return get_character_stat_string('bane', lvl) end, ['psykino'] = function(lvl) return get_character_stat_string('psykino', lvl) end, ['arbalester'] = function(lvl) return get_character_stat_string('arbalester', lvl) end, - ['barbarian'] = function(lvl) return get_character_stat_string('barbarian', lvl) end, + ['highlander'] = function(lvl) return get_character_stat_string('highlander', lvl) end, ['sapper'] = function(lvl) return get_character_stat_string('sapper', lvl) end, ['priest'] = function(lvl) return get_character_stat_string('priest', lvl) end, - ['burrower'] = function(lvl) return get_character_stat_string('burrower', lvl) end, + ['infestor'] = function(lvl) return get_character_stat_string('infestor', lvl) end, ['flagellant'] = function(lvl) return get_character_stat_string('flagellant', lvl) end, } @@ -565,7 +567,7 @@ function init() ['nuker'] = {hp = 0.9, dmg = 1, aspd = 0.75, area_dmg = 1.5, area_size = 1.5, def = 1, mvspd = 1}, ['conjurer'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 1}, ['psyker'] = {hp = 1.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 0.5, mvspd = 1}, - ['trapper'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 0.75, mvspd = 1}, + ['curser'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 0.75, mvspd = 1}, ['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}, @@ -587,7 +589,7 @@ function init() ['nuker'] = function(lvl) return '[' .. ylb1(lvl) .. ']3[' .. ylb2(lvl) .. ']/6 [fg]- [' .. ylb1(lvl) .. ']+15%[' .. ylb2(lvl) .. ']/+25% [fg]area damage and size to allied nukers' end, ['conjurer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+25%[' .. ylb2(lvl) .. ']/+50% [fg]summon damage and duration' end, ['psyker'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+5%[' .. ylb2(lvl) .. ']/+10% [fg]damage and health per active set to allied psykers' end, - ['trapper'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+1[' .. ylb2(lvl) .. ']/+2 [fg]extra traps released' end, + ['curser'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[' .. ylb2(lvl) .. ']/4 [fg]- [' .. ylb1(lvl) .. ']+25%[' .. ylb2(lvl) .. ']/+50% [fg]curse effectiveness and duration' end, ['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, @@ -595,9 +597,9 @@ function init() tier_to_characters = { [1] = {'vagrant', 'swordsman', 'wizard', 'archer', 'scout', 'cleric'}, - [2] = {'saboteur', 'sage', 'squire', 'dual_gunner', 'hunter', 'chronomancer', 'fisherman', 'cryomancer', 'beastmaster', 'launcher', 'spiker', 'carver'}, - [3] = {'outlaw', 'elementor', 'stormweaver', 'spellblade', 'psykeeper', 'engineer', 'juggernaut', 'pyromancer', 'corruptor', 'assassin', 'bane', 'arbalester', 'burrower', 'flagellant'}, - [4] = {'priest', 'barbarian', 'psykino', 'lich', 'host', 'sapper', 'blade', 'plague_doctor', 'cannoneer'}, + [2] = {'saboteur', 'sage', 'squire', 'dual_gunner', 'hunter', 'chronomancer', 'barbarian', 'cryomancer', 'beastmaster', 'launcher', 'bard', 'carver'}, + [3] = {'outlaw', 'elementor', 'stormweaver', 'spellblade', 'psykeeper', 'engineer', 'juggernaut', 'pyromancer', 'corruptor', 'assassin', 'bane', 'arbalester', 'infestor', 'flagellant'}, + [4] = {'priest', 'highlander', 'psykino', 'lich', 'host', 'sapper', 'blade', 'plague_doctor', 'cannoneer'}, } non_attacking_characters = {'cleric', 'stormweaver', 'squire', 'chronomancer', 'sage'} @@ -624,7 +626,7 @@ function init() ['psykeeper'] = 3, ['engineer'] = 3, ['plague_doctor'] = 4, - ['fisherman'] = 2, + ['barbarian'] = 2, ['juggernaut'] = 3, ['lich'] = 4, ['cryomancer'] = 2, @@ -632,17 +634,17 @@ function init() ['corruptor'] = 3, ['beastmaster'] = 2, ['launcher'] = 2, - ['spiker'] = 2, + ['bard'] = 2, ['assassin'] = 3, ['host'] = 4, ['carver'] = 2, ['bane'] = 3, ['psykino'] = 4, ['arbalester'] = 3, - ['barbarian'] = 4, + ['highlander'] = 4, ['sapper'] = 4, ['priest'] = 4, - ['burrower'] = 3, + ['infestor'] = 3, ['flagellant'] = 3, } @@ -656,7 +658,7 @@ function init() local rogues = 0 local enchanters = 0 local psykers = 0 - local trappers = 0 + local cursers = 0 local forcers = 0 local swarmers = 0 local voiders = 0 @@ -671,14 +673,14 @@ function init() if unit_class == 'rogue' then rogues = rogues + 1 end if unit_class == 'enchanter' then enchanters = enchanters + 1 end if unit_class == 'psyker' then psykers = psykers + 1 end - if unit_class == 'trapper' then trappers = trappers + 1 end + if unit_class == 'curser' then cursers = cursers + 1 end 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 end end return {ranger = rangers, warrior = warriors, healer = healers, mage = mages, nuker = nukers, conjurer = conjurers, rogue = rogues, - enchanter = enchanters, psyker = psykers, trapper = trappers, forcer = forcers, swarmer = swarmers, voider = voiders} + enchanter = enchanters, psyker = psykers, curser = cursers, forcer = forcers, swarmer = swarmers, voider = voiders} end get_class_levels = function(units) @@ -688,7 +690,7 @@ function init() if number_of_units >= 6 then return 2 elseif number_of_units >= 3 then return 1 else return 0 end - elseif class == 'healer' or class == 'conjurer' or class == 'enchanter' or class == 'psyker' or class == 'trapper' or class == 'forcer' or class == 'swarmer' or class == 'voider' then + elseif class == 'healer' or class == 'conjurer' or class == 'enchanter' or class == 'psyker' or class == 'curser' or class == 'forcer' or class == 'swarmer' or class == 'voider' then if number_of_units >= 4 then return 2 elseif number_of_units >= 2 then return 1 else return 0 end @@ -704,7 +706,7 @@ function init() conjurer = units_to_class_level(units_per_class.conjurer, 'conjurer'), enchanter = units_to_class_level(units_per_class.enchanter, 'enchanter'), psyker = units_to_class_level(units_per_class.psyker, 'psyker'), - trapper = units_to_class_level(units_per_class.trapper, 'trapper'), + curser = units_to_class_level(units_per_class.curser, 'curser'), 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'), @@ -729,7 +731,7 @@ function init() ['conjurer'] = function(units) return 2, 4, get_number_of_units_per_class(units).conjurer end, ['enchanter'] = function(units) return 2, 4, get_number_of_units_per_class(units).enchanter end, ['psyker'] = function(units) return 2, 4, get_number_of_units_per_class(units).psyker end, - ['trapper'] = function(units) return 2, 4, get_number_of_units_per_class(units).trapper end, + ['curser'] = function(units) return 2, 4, get_number_of_units_per_class(units).curser end, ['forcer'] = function(units) return 2, 4, get_number_of_units_per_class(units).forcer end, ['swarmer'] = function(units) return 2, 4, get_number_of_units_per_class(units).swarmer end, ['voider'] = function(units) return 2, 4, get_number_of_units_per_class(units).voider end, @@ -804,7 +806,9 @@ function init() main = Main() main:add(BuyScreen'buy_screen') main:go_to('buy_screen', 22, { - {character = 'fisherman', level = 3}, + {character = 'beastmaster', level = 3}, + {character = 'scout', level = 3}, + {character = 'outlaw', level = 3}, }) --[[ main:add(Arena'arena') diff --git a/objects.lua b/objects.lua index e1867f0..da73d81 100644 --- a/objects.lua +++ b/objects.lua @@ -244,7 +244,7 @@ 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(EnemyCritter) then + elseif self:is(EnemyCritter) or self:is(Critter) then local x = self.level local y = {0, 1, 4, 2, 3, 6, 3, 5, 9, 4, 6, 11, 7, 9, 15, 8, 10, 18, 9, 11, 21, 14, 15, 24, 25} self.base_hp = 25 + 30*y[x] diff --git a/player.lua b/player.lua index 9d4ee3e..a81286d 100644 --- a/player.lua +++ b/player.lua @@ -294,15 +294,87 @@ function Player:init(args) end) end - elseif self.character == 'fisherman' then - self.color = character_colors.fisherman + elseif self.character == 'barbarian' then + self.color = character_colors.barbarian self:set_as_rectangle(9, 9, 'dynamic', 'player') self.visual_shape = 'rectangle' - self.classes = character_classes.fisherman + self.classes = character_classes.barbarian - self.t:every(10, function() - Trap{group = main.current.main, x = self.x, y = self.y, color = self.color, v = 50, r = random:float(0, 2*math.pi), dmg = self.dmg, character = self.character, level = self.level, parent = self} + self.t:every(8, function() + self:attack(96, {stun = 4}) end) + + elseif self.character == 'juggernaut' then + self.color = character_colors.juggernaut + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.juggernaut + + self.t:every(8, function() + self:attack(64, {juggernaut_push = true}) + end) + + elseif self.character == 'lich' then + self.color = character_colors.lich + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.lich + + self.attack_sensor = Circle(self.x, self.y, 128) + 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), {chain = (self.level == 3 and 14 or 7), v = 70}) + end + end, nil, nil, 'shoot') + + elseif self.character == 'cryomancer' then + self.color = character_colors.cryomancer + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.cryomancer + + self.t:after(0.01, function() + self.dot_area = DotArea{group = main.current.effects, x = self.x, y = self.y, rs = self.area_size_m*48, color = self.color, dmg = self.area_dmg_m*self.dmg, character = self.character, level = self.level, parent = self} + end) + + elseif self.character == 'pyromancer' then + self.color = character_colors.pyromancer + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.pyromancer + + self.t:after(0.01, function() + self.dot_area = DotArea{group = main.current.effects, x = self.x, y = self.y, rs = self.area_size_m*48, color = self.color, dmg = self.area_dmg_m*self.dmg, character = self.character, level = self.level, parent = self} + end) + + elseif self.character == 'corruptor' then + self.color = character_colors.corruptor + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.corruptor + + self.attack_sensor = Circle(self.x, self.y, 160) + self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:shoot(self:angle_to_object(closest_enemy), {spawn_critters_on_kill = 3, spawn_critters_on_hit = (self.level == 3 and 3 or 0)}) + end + end, nil, nil, 'shoot') + + elseif self.character == 'beastmaster' then + self.color = character_colors.beastmaster + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.beastmaster + + self.attack_sensor = Circle(self.x, self.y, 160) + self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:shoot(self:angle_to_object(closest_enemy), {spawn_critters_on_crit = 2}) + end + end, nil, nil, 'shoot') end self:calculate_stats(true) @@ -345,7 +417,7 @@ function Player:update(dt) if class_levels.conjurer >= 1 then number_of_active_sets = number_of_active_sets + 1 end if class_levels.enchanter >= 1 then number_of_active_sets = number_of_active_sets + 1 end if class_levels.psyker >= 1 then number_of_active_sets = number_of_active_sets + 1 end - if class_levels.trapper >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.curser >= 1 then number_of_active_sets = number_of_active_sets + 1 end 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 @@ -522,6 +594,15 @@ function Player:hit(damage) camera:shake(4, 0.5) main.current.damage_taken = main.current.damage_taken + actual_damage + if self.character == 'beastmaster' and self.level == 3 then + critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + trigger:after(0.01, function() + for i = 1, 2 do + Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 20, dmg = self.dmg} + end + end) + end + local psykeeper = self:get_unit'psykeeper' if psykeeper then psykeeper.stored_heal = psykeeper.stored_heal + actual_damage @@ -557,6 +638,8 @@ function Player:hit(damage) if self.leader then self:recalculate_followers() else self.parent:recalculate_followers() end end + + if self.dot_area then self.dot_area.dead = true; self.dot_area = nil end end end @@ -638,6 +721,16 @@ function Player:shoot(r, mods) local dmg_m = 1 local crit = false if self.chance_to_crit and random:bool(self.chance_to_crit) then dmg_m = 4; crit = true end + print(crit, mods.spawn_critters_on_crit, self.chance_to_crit) + + if crit and mods.spawn_critters_on_crit then + critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + trigger:after(0.01, function() + for i = 1, mods.spawn_critters_on_crit do + Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = self.dmg} + end + end) + end if self.character == 'outlaw' then 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} @@ -707,7 +800,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' then archer1:play{pitch = random:float(0.95, 1.05), volume = 0.35} - elseif self.character == 'wizard' then + elseif self.character == 'wizard' or self.character == 'lich' 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' then _G[random:table{'scout1', 'scout2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.35} @@ -718,6 +811,10 @@ function Player:shoot(r, mods) _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end + if self.character == 'lich' then + frost1: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, 4) end @@ -728,14 +825,19 @@ function Player: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, w = 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, w = 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} Area(table.merge(t, mods)) - if self.character == 'swordsman' then + if self.character == 'swordsman' or self.character == 'barbarian' or self.character == 'juggernaut' then _G[random:table{'swordsman1', 'swordsman2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.75} elseif self.character == 'elementor' then elementor1:play{pitch = random:float(0.9, 1.1), volume = 0.5} end + + if self.character == 'juggernaut' then + elementor1:play{pitch = random:float(0.9, 1.1), volume = 0.5} + end end @@ -823,6 +925,12 @@ function Projectile:init(args) self.t:tween(12.25, self, {orbit_vr = 0}, math.linear) end) end + + elseif self.character == 'lich' then + self.spring:pull(0.15) + self.t:every(0.08, function() + HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color} + end) end if self.homing then @@ -881,6 +989,11 @@ function Projectile:draw() for i = 1, 4 do graphics.arc('open', self.x, self.y, self.pull_sensor.rs, (i-1)*math.pi/2 + math.pi/4 - math.pi/8, (i-1)*math.pi/2 + math.pi/4 + math.pi/8, self.color, lw) end graphics.pop() + 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.pop() + else graphics.push(self.x, self.y, self.r + (self.orbit_r or 0)) graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 2, 2, self.color) @@ -943,7 +1056,7 @@ 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' then + elseif self.character == 'wizard' or self.character == 'lich' 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 @@ -973,10 +1086,17 @@ function Projectile:on_trigger_enter(other, contact) if self.chain > 0 then self.chain = self.chain - 1 table.insert(self.chain_enemies_hit, other) - local object = self:get_random_object_in_shape(Circle(self.x, self.y, 160), main.current.enemies, self.chain_enemies_hit) + local object = self:get_random_object_in_shape(Circle(self.x, self.y, 48), main.current.enemies, self.chain_enemies_hit) if object then self.r = self:angle_to_object(object) - self.v = self.v*1.25 + if self.character == 'lich' then + self.v = self.v*1.1 + if self.level == 3 then + object:slow(0.2, 2) + end + else + self.v = self.v*1.25 + end if self.level == 3 and self.character == 'scout' then self.dmg = self.dmg*1.25 end @@ -992,7 +1112,7 @@ 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' then + elseif self.character == 'wizard' or self.character == 'lich' then magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15} else hit3:play{pitch = random:float(0.95, 1.05), volume = 0.35} @@ -1063,7 +1183,6 @@ function Area:init(args) self:init_game_object(args) self.shape = Rectangle(self.x, self.y, 1.5*self.w, 1.5*self.w, self.r) local enemies = main.current.main:get_objects_in_shape(self.shape, main.current.enemies) - if self.character == 'saboteur' then print(self.dmg) end for _, enemy in ipairs(enemies) do if self.character == 'elementor' then enemy:hit(2*self.dmg) @@ -1082,16 +1201,29 @@ function Area:init(args) for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end if self.character == 'wizard' or self.character == 'elementor' then magic_hit1:play{pitch = random:float(0.95, 1.05), volume = 0.5} - elseif self.character == 'swordsman' then + elseif self.character == 'swordsman' or self.character == 'barbarian' or self.character == 'juggernaut' then hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} elseif self.character == 'blade' then blade_hit1:play{pitch = random:float(0.9, 1.1), volume = 0.35} hit2:play{pitch = random:float(0.95, 1.05), volume = 0.2} - elseif self.character == 'saboteur' then + elseif self.character == 'saboteur' or self.character == 'pyromancer' then + if self.character == 'pyromancer' then pyro2:play{pitch = random:float(0.95, 1.05), volume = 0.4} end _G[random:table{'saboteur_hit1', 'saboteur_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.2} elseif self.character == 'cannoneer' then _G[random:table{'saboteur_hit1', 'saboteur_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.075} end + + if self.stun then + enemy:slow(0.1, self.stun) + enemy.barbarian_stunned = true + enemy.t:after(self.stun, function() enemy.barbarian_stunned = false end) + end + + if self.juggernaut_push then + local r = self.parent:angle_to_object(enemy) + enemy:push(random:float(75, 100), r) + enemy.juggernaut_push = 3*self.dmg + end end self.color = fg[0] @@ -1136,16 +1268,40 @@ function DotArea:init(args) self:init_game_object(args) self.shape = Circle(self.x, self.y, self.rs) - self.t:every(0.2, function() - local enemies = main.current.main:get_objects_in_shape(self.shape, main.current.enemies) - for _, enemy in ipairs(enemies) do - hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2} - enemy:hit(self.dmg/5) - HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} - for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end - for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end - end - end, nil, nil, 'dot') + if self.character == 'plague_doctor' or self.character == 'pyromancer' 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 + for _, enemy in ipairs(enemies) do + hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2} + if self.character == 'pyromancer' then + pyro1:play{pitch = random:float(1.5, 1.8), volume = 0.1} + enemy.pyrod = self + end + enemy:hit(self.dmg/5) + HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} + for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end + for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end + end + end, nil, nil, 'dot') + elseif self.character == 'cryomancer' then + self.t:every(2, function() + local enemies = main.current.main:get_objects_in_shape(self.shape, main.current.enemies) + if #enemies > 0 then + self.spring:pull(0.15, 200, 10) + frost1:play{pitch = random:float(0.8, 1.2), volume = 0.4} + end + for _, enemy in ipairs(enemies) do + if self.level == 3 then + enemy:slow(0.4, 4) + end + enemy:hit(2*self.dmg) + HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} + for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end + for i = 1, 1 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end + end + end, nil, nil, 'dot') + end self.color = fg[0] self.color_transparent = Color(args.color.r, args.color.g, args.color.b, 0.08) @@ -1170,7 +1326,7 @@ function DotArea:update(dt) self.vr = self.vr + self.dvr*dt if self.parent then - if self.character == 'plague_doctor' and self.level == 3 then + if (self.character == 'plague_doctor' and self.level == 3) or self.character == 'cryomancer' or self.character == 'pyromancer' then self.x, self.y = self.parent.x, self.parent.y self.shape:move_to(self.x, self.y) end @@ -1192,49 +1348,6 @@ end ---[[ -Trap = Object:extend() -Trap:implement(GameObject) -Trap:implement(Physics) -function Trap:init(args) - self:init_game_object(args) - - self.shape = Rectangle(self.x, self.y, 6, 6) - self.spring:pull(0.15, 200, 20) - self.vr = 0 - self.dvr = random:float(-2*math.pi, 2*math.pi) - self.t:tween(1, self, {v = 0, dvr = 0}, math.linear) -end - - -function Trap:update(dt) - self:update_game_object(dt) - self:move_along_angle(self.v, self.r) - self.vr = self.vr + self.dvr*dt - self.shape:move_to(self.x, self.y) - - local enemies = self:get_objects_in_shape(self.shape, main.current.enemies) - if #enemies > 0 then - self.dead = true - local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 24), main.current.enemies) - for _, enemy in ipairs(enemies) do - enemy:slow(0.1, 4) - end - end -end - - -function Trap:draw() - graphics.push(self.x, self.y, self.r + self.vr, self.spring.x, self.spring.x) - graphics.line(self.x - 6, self.y, self.x + 6, self.y, self.color, 3) - graphics.line(self.x, self.y - 6, self.x, self.y + 6, self.color, 3) - graphics.pop() -end -]]-- - - - - Turret = Object:extend() Turret:implement(GameObject) Turret:implement(Physics) @@ -1413,13 +1526,16 @@ function Saboteur:update(dt) self:calculate_stats() if not self.target then self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) end - if not self.target then return end - if self.target.dead then self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) end - if not self.target then return end - - self:seek_point(self.target.x, self.target.y) - self:rotate_towards_velocity(0.5) - self.r = self:get_angle() + 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.target then + self:seek_point(gw/2, gh/2) + self:rotate_towards_velocity(0.5) + self.r = self:get_angle() + else + self:seek_point(self.target.x, self.target.y) + self:rotate_towards_velocity(0.5) + self.r = self:get_angle() + end end @@ -1439,3 +1555,121 @@ function Saboteur:on_collision_enter(other, contact) self.dead = true end end + + + + +Critter = Object:extend() +Critter:implement(GameObject) +Critter:implement(Physics) +Critter:implement(Unit) +function Critter:init(args) + self:init_game_object(args) + self:init_unit() + self:set_as_rectangle(7, 4, 'dynamic', 'player') + self:set_restitution(0.5) + + self.classes = {'enemy_critter'} + self.color = orange[0] + self:calculate_stats(true) + self:set_as_steerable(self.v, 400, math.pi, 1) + self:push(args.v, args.r) + self.invulnerable = true + self.t:after(0.5, function() self.invulnerable = false end) + + self.hp = 1 +end + + +function Critter:update(dt) + self:update_game_object(dt) + + if self.being_pushed then + local v = math.length(self:get_velocity()) + if v < 50 then + self.being_pushed = false + self.steering_enabled = true + self:set_damping(0) + self:set_angular_damping(0) + end + else + 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.target then + self:seek_point(gw/2, gh/2) + self:wander(50, 200, 50) + self:rotate_towards_velocity(1) + self:steering_separate(8, {Critter}) + else + self:seek_point(self.target.x, self.target.y) + self:wander(50, 200, 50) + self:rotate_towards_velocity(1) + self:steering_separate(8, {Critter}) + end + end + self.r = self:get_angle() +end + + +function Critter: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, 2, 2, self.hfx.hit.f and fg[0] or self.color) + graphics.pop() +end + + +function Critter:hit(damage) + if self.dead or self.invulnerable then return end + self.hfx:use('hit', 0.25, 200, 10) + self.hp = self.hp - damage + -- self:show_hp() + if self.hp <= 0 then self:die() end +end + + +function Critter:push(f, r) + self.push_force = f + self.being_pushed = true + self.steering_enabled = false + self:apply_impulse(f*math.cos(r), f*math.sin(r)) + self:apply_angular_impulse(random:table{random:float(-12*math.pi, -4*math.pi), random:float(4*math.pi, 12*math.pi)}) + self:set_damping(1.5) + self:set_angular_damping(1.5) +end + + +function Critter:die(x, y, r, n) + if self.dead then return end + x = x or self.x + y = y or self.y + n = n or random:int(2, 3) + for i = 1, n do HitParticle{group = main.current.effects, x = x, y = y, r = random:float(0, 2*math.pi), color = self.color} end + HitCircle{group = main.current.effects, x = x, y = y}:scale_down() + self.dead = true + _G[random:table{'enemy_die1', 'enemy_die2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.5} + critter2:play{pitch = random:float(0.95, 1.05), volume = 0.2} +end + + +function Critter:on_collision_enter(other, contact) + local x, y = contact:getPositions() + local nx, ny = contact:getNormal() + local r = 0 + if nx == 0 and ny == -1 then r = -math.pi/2 + elseif nx == 0 and ny == 1 then r = math.pi/2 + elseif nx == -1 and ny == 0 then r = math.pi + else r = 0 end + + if other:is(Wall) then + self.hfx:use('hit', 0.15, 200, 10, 0.1) + self:bounce(contact:getNormal()) + end +end + + +function Critter:on_trigger_enter(other, contact) + if other:is(Seeker) then + self:hit(1) + other:hit(self.dmg) + end +end diff --git a/todo b/todo index 9bcee6a..b1773b0 100644 --- a/todo +++ b/todo @@ -35,7 +35,7 @@ Enchanter: increased damage Conjurer: increased summon damage and duration Psyker: increased damage and health based on number of active sets - Trapper: release extra traps + Curser: increased curse effect and duration Forcer: increased knockback force Swarmer: increased critter health Voider: increased damage over time @@ -56,30 +56,30 @@ * Cannoneer [ranger, nuker]: shoots a projectile that deals AoE damage - Lv.3: Cannon Barrage - showers the hit area in additional cannon shots that deal AoE damage * Dual Gunner [ranger, rogue]: shoots two parallel projectiles - Lv.3: Gun Kata - every 5th attack shoots projectiles in a rapid succession for a duration, targetting all nearby enemies * Hunter [ranger, conjurer]: shoots an arrow that summons a pet - Lv.3: Feral Pack - summons 3 pets - (Apply arena.chronomancer_dot when damage over time is implemented) * Chronomancer [mage, enchanter]: increased attack speed to all allies - Lv.3: Quicken - enemies take DoT faster + * (Apply arena.chronomancer_dot when damage over time is implemented) * Chronomancer [mage, enchanter]: increased attack speed to all allies - Lv.3: Quicken - enemies take DoT faster * Spellblade [mage, rogue]: throws knives that spiral outwards and pierce - Lv.3: Spiralism - faster projectile speed and tighter turns * Psykeeper [healer, psyker]: stores damage taken by all allies and redistributes it as healing - Lv.3: Crucio - also redistributes it as damage to all enemies * Engineer [conjurer]: drops sentries that shoot bursts of projectils - Lv.3: Upgrade - every 3rd sentry dropped, upgrade all sentries temporarily, giving increased damage and attack speed * Plague Doctor [nuker, voider]: creates an area that deals DoT - Lv.3: Pandemic - inflicts enemies with a contagion that deals additional DoT, if they die from it it passes to a nearby enemy - Fisherman [trapper, warrior]: throws a net that entangles enemies and prevents them from moving - Lv.3: Electric Net - enemies caught take DoT - Juggernaut [forcer, warrior]: creates a small area that deals AoE damage and pushes enemies away - Lv.3: Brutal Impact - enemies pushed away are instantly killed if they hit a wall - Lich [mage]: launches a chain frost that chains 7 times, dealing damage and slowing enemies it hits - Lv.3: Piercing Frost - chain frost ignores enemy defenses - Cryomancer [mage, voider]: nearby enemies take damage over time and have decreased movement speed - Lv.3: Frostbite - enemies killed by the cryomancer freeze nearby enemies, frozen enemies can't move and take increased damage - Pyromancer [mage, nuker, voider]: nearby enemies take damage over time and deal decreased damage - Lv.3: Ignite - enemies killed by the pyromancer explode, dealing AoE damage - Corruptor [ranger, swarmer]: spawn 3 small critters if the corruptor kills an enemy - Lv.3: Infestation - spawn 3 small critters if the corruptor hits an enemy - Beastmaster [rogue, swarmer]: spawn 2 small critters if the beastmaster crits - Lv.3: Call of the Wild - spawn 2 small critters if the beastmaster gets hit - Launcher [trapper, forcer]: creates a trap that launches enemies that trigger it - Lv.3: Kineticism - enemies launched that hit other enemies transfer their kinetic energy - Spiker [trapper, rogue]: creates a trap that crits when triggered - Lv.3: Caltrops - slows enemies hit and deals DoT + * Barbarian [curser, warrior]: deals AoE damage and stuns enemies hit for 4 seconds - Lv.3: Seism - stunned enemies also take 100% increased damage + * Juggernaut [forcer, warrior]: creates a small area that deals AoE damage and pushes enemies away - Lv.3: Brutal Impact - enemies pushed away are instantly killed if they hit a wall + * Lich [mage]: launches a chain frost that chains 7 times, dealing damage and slowing enemies it hits - Lv.3: Piercing Frost - chain frost ignores enemy defenses + * Cryomancer [mage, voider]: nearby enemies take damage over time and have decreased movement speed - Lv.3: Frostbite - enemies killed by the cryomancer freeze nearby enemies, frozen enemies can't move and take increased damage + * Pyromancer [mage, nuker, voider]: nearby enemies take damage over time and deal decreased damage - Lv.3: Ignite - enemies killed by the pyromancer explode, dealing AoE damage + * Corruptor [ranger, swarmer]: spawn 3 small critters if the corruptor kills an enemy - Lv.3: Corruption - spawn 3 small critters if the corruptor hits an enemy + * Beastmaster [rogue, swarmer]: spawn 2 small critters if the beastmaster crits - Lv.3: Call of the Wild - spawn 2 small critters if the beastmaster gets hit + Launcher [curser, forcer]: curses nearby enemies with a kinetic curse that triggers after 4 seconds - Lv.3: Kineticism - enemies launched that hit other enemies transfer their kinetic energy at double value + Bard [curser, rogue]: shoots a projectile that inflicts enemies hit with the bard's curse - Lv.3: The Bard's Song - every 5th attack consume the curse to deal massive damage to enemies affected Assassin [rogue, voider]: throws a piercing knife that inflicts poison - Lv.3: Toxic Delivery - poison inflicted from crits deals more damage Host [conjurer, swarmer]: creates overlords that periodically spawn small critters - Lv.3: Invasion - increased critter spawn rate - Carver [conjurer, healer]: carves a statue that periodically heals in an area - Lv.3: World Tree - carves a tree that heals in a bigger area and removes all buffs from enemies - Bane [swarmer, voider]: spawn a small critter periodically that explodes and deals DoT - Lv.3: Baneling Swarm - spawn 4 banelings instead + Carver [conjurer, curser, healer]: carves a statue that periodically heals in an area - Lv.3: World Tree - carves a tree that heals in a bigger area and removes all buffs from enemies + Bane [curser, voider]: creates a large area that curses enemies to take increased DoT - Lv.3: Nightmare - the area also deals DoT and slows enemies Psykino [mage, psyker, forcer]: quickly pulls enemies together and then releases them with a force - Lv.3: Magnetic Force - enemies pulled together are forced to collide with each other before being released Arbalester [ranger, forcer]: launches a massive arrow that pushes enemies back, ignoring knockback resistances - Lv.3: Ballista Sinistra - enemies hit by the arrow have massively decreased defense - Pirate [warrior, forcer]: launches a hook that captures nearby enemies and pulls them towards you - Lv.3: Jolly Roger - place a flag that grants gold based on number of enemies killed under its effect - Sapper [trapper, enchanter, healer]: creates a trap that steals health from enemies that trigger it and grants increased movement speed - Lv.3: when a sapper trap is triggered other nearby traps are triggered + Highlander [warrior]: creates a small area that deals massive damage - Lv.3: Crosscut - two crosscutting areas of larger size are created instead + Sapper [enchanter, voider, healer]: periodically steals health from nearby enemies and gain increased movement speed - Lv.3: Enduring Sap - sapped enemies permanently take damage over time, even outside the sapper's area of effect Priest [healer]: heals all units periodically - Lv.3: Divine Intervention - at the start of the round pick 3 units at random and grants them a buff that prevents them from dying once - Burrower [trapper, swarmer]: creates a trap that contains 6 small critters which are released when triggered - Lv.3: Zergling Rush - triples the number of critters released + Infestor [curser, swarmer]: curses enemies in an area for 6 seconds, they will release multiple critters on death - Lv.3: Infestation - triples the number of critters released Flagellant [psyker, enchanter]: periodically deals damage to self and grants a damage buff to all allies - Lv.3: Zealotry - deals damage to all allies instead and also grants a massive damage buff Sets Ranger = 8/8 @@ -93,8 +93,8 @@ Psyker = 4/4 Healer = 5/5 Enchanter = 5/5 - Trapper = 5/5 - Swarmer = 5/5 + Curser = 6/6 + Swarmer = 4/4 10. Items Ouroboros Technique R: rotating around yourself to the right makes every unit periodically release projectiles