diff --git a/arena.lua b/arena.lua index fa0b459..76fbc2b 100644 --- a/arena.lua +++ b/arena.lua @@ -235,51 +235,20 @@ 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 rangers = 0 - local warriors = 0 - local healers = 0 - local mages = 0 - local nukers = 0 - local conjurers = 0 - local rogues = 0 - local enchanters = 0 - local psys = 0 - for _, unit in ipairs(units) do - for _, unit_class in ipairs(unit.classes) do - if unit_class == 'ranger' then rangers = rangers + 1 end - if unit_class == 'warrior' then warriors = warriors + 1 end - if unit_class == 'healer' then healers = healers + 1 end - if unit_class == 'mage' then mages = mages + 1 end - if unit_class == 'nuker' then nukers = nukers + 1 end - if unit_class == 'conjurer' then conjurers = conjurers + 1 end - if unit_class == 'rogue' then rogues = rogues + 1 end - if unit_class == 'enchanter' then enchanters = enchanters + 1 end - if unit_class == 'psy' then psys = psys + 1 end - end - end - - self.ranger_level = 0 - if rangers >= 2 then self.ranger_level = 1 end - if rangers >= 4 then self.ranger_level = 2 end - self.warrior_level = 0 - if warriors >= 2 then self.warrior_level = 1 end - if warriors >= 4 then self.warrior_level = 2 end - self.healer_level = 0 - if healers >= 3 then self.healer_level = 1 end - self.mage_level = 0 - if mages >= 2 then self.mage_level = 1 end - if mages >= 4 then self.mage_level = 2 end - self.nuke_level = 0 - if nukers >= 2 then self.nuke_level = 1 end - if nukers >= 4 then self.nuke_level = 2 end - self.conjurer_level = 0 - if conjurers >= 2 then self.conjurer_level = 1 end - self.rogue_level = 0 - if rogues >= 2 then self.rogue_level = 1 end - if rogues >= 4 then self.rogue_level = 2 end - self.enchanter_level = 0 - if enchanters >= 3 then self.enchanter_level = 1 end - self.psy_level = psys + local class_levels = get_class_levels(get_number_of_units_per_class(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.forcer_level = class_levels.forcer + self.swarmer_level = class_levels.swarmer + self.voider_level = class_levels.voider + self.enchanter_level = class_levels.enchanter + self.healer_level = class_levels.healer + self.psyker_level = class_levels.psyker + self.conjurer_level = class_levels.conjurer end diff --git a/buy_screen.lua b/buy_screen.lua index 3ac66f7..d8be260 100644 --- a/buy_screen.lua +++ b/buy_screen.lua @@ -49,7 +49,7 @@ function BuyScreen:on_enter(from, level, units) self.shop_text = Text({{text = '[wavy_mid, fg]shop [fg]- gold: [yellow]' .. gold, font = pixul_font, alignment = 'center'}}, global_text_tags) self.party_text = Text({{text = '[wavy_mid, fg]party', font = pixul_font, alignment = 'center'}}, global_text_tags) - self.sets_text = Text({{text = '[wavy_mid, fg]sets', font = pixul_font, alignment = 'center'}}, global_text_tags) + self.sets_text = Text({{text = '[wavy_mid, fg]classes', font = pixul_font, alignment = 'center'}}, global_text_tags) self.items_text = Text({{text = '[wavy_mid, fg]items', font = pixul_font, alignment = 'center'}}, global_text_tags) self.under_text = Text2{group = self.main, x = 140, y = gh - 55, r = -math.pi/48, lines = { {text = '[light_bg]under', font = fat_font, alignment = 'center'}, @@ -64,7 +64,7 @@ end function BuyScreen:update(dt) self:update_game_object(dt*slow_amount) - cascade_instance.pitch = 1 + -- cascade_instance.pitch = 1 self.main:update(dt*slow_amount) self.effects:update(dt*slow_amount) @@ -442,7 +442,11 @@ function CharacterPart:on_mouse_enter() self.info_text:activate({ {text = '[' .. character_color_strings[self.character] .. ']' .. self.character:capitalize() .. '[fg] - [yellow]Lv.' .. self.level .. '[fg] - sells for [yellow]' .. self:get_sale_price(), font = pixul_font, alignment = 'center', height_multiplier = 1.25}, - {text = character_descriptions[self.character](self.level), font = pixul_font, alignment = 'center'}, + {text = '[fg]Classes: ' .. character_class_strings[self.character], font = pixul_font, alignment = 'center', height_multiplier = 1.25}, + {text = character_descriptions[self.character](self.level), font = pixul_font, alignment = 'center', height_multiplier = 2}, + {text = '[' .. (self.level == 3 and 'yellow' or 'light_bg') .. ']Lv.3 [' .. (self.level == 3 and 'fg' or 'light_bg') .. ']Effect - ' .. + (self.level == 3 and character_effect_names[self.character] or character_effect_names_gray[self.character]), font = pixul_font, alignment = 'center', height_multiplier = 1.25}, + {text = character_effect_descriptions[self.character](), font = pixul_font, alignment = 'center'}, }, nil, nil, nil, nil, 16, 4, nil, 2) self.info_text.x, self.info_text.y = gw/2, gh/2 + 10 end @@ -613,7 +617,9 @@ function CharacterIcon:on_mouse_enter() self.info_text = InfoText{group = main.current.ui} self.info_text:activate({ {text = '[' .. character_color_strings[self.character] .. ']' .. self.character:capitalize() .. '[fg] - cost: [yellow]' .. self.parent.cost, font = pixul_font, alignment = 'center', height_multiplier = 1.25}, - {text = character_descriptions[self.character](get_character_stat(self.character, 1, 'dmg')), font = pixul_font, alignment = 'center'}, + {text = '[fg]Classes: ' .. character_class_strings[self.character], font = pixul_font, alignment = 'center', height_multiplier = 1.25}, + {text = character_descriptions[self.character](1), font = pixul_font, alignment = 'center'}, + -- {text = character_stats[self.character](1), font = pixul_font, alignment = 'center'}, }, nil, nil, nil, nil, 16, 4, nil, 2) self.info_text.x, self.info_text.y = gw/2, gh/2 + 10 end @@ -685,9 +691,13 @@ function ClassIcon:draw() end end elseif i == 3 then - graphics.line(self.x - 4, self.y + 22, self.x - 4, self.y + 30, (n >= 1) and class_colors[self.class] or bg[10], 2) - graphics.line(self.x, self.y + 22, self.x, self.y + 30, (n >= 2) and class_colors[self.class] or bg[10], 2) - graphics.line(self.x + 4, self.y + 22, self.x + 4, self.y + 30, (n >= 3) and class_colors[self.class] or bg[10], 2) + graphics.line(self.x - 3, self.y + 19, self.x - 3, self.y + 22, (n >= 1) and class_colors[self.class] or bg[10], 3) + graphics.line(self.x - 3, self.y + 24, self.x - 3, self.y + 27, (n >= 2) and class_colors[self.class] or bg[10], 3) + graphics.line(self.x - 3, self.y + 29, self.x - 3, self.y + 32, (n >= 3) and class_colors[self.class] or bg[10], 3) + graphics.line(self.x + 4, self.y + 19, self.x + 4, self.y + 22, (n >= 4) and class_colors[self.class] or bg[10], 3) + graphics.line(self.x + 4, self.y + 24, self.x + 4, self.y + 27, (n >= 5) and class_colors[self.class] or bg[10], 3) + graphics.line(self.x + 4, self.y + 29, self.x + 4, self.y + 32, (n >= 6) and class_colors[self.class] or bg[10], 3) + --[[ if next_n then if next_n == 1 then graphics.line(self.x - 4, self.y + 22, self.x - 4, self.y + 30, self.flash and class_colors[self.class] or bg[10], 2) @@ -697,16 +707,7 @@ function ClassIcon:draw() graphics.line(self.x + 4, self.y + 22, self.x + 4, self.y + 30, self.flash and class_colors[self.class] or bg[10], 2) end end - elseif i == 1 then - graphics.line(self.x - 3, self.y + 22, self.x - 3, self.y + 30, (n >= 1) and class_colors[self.class] or bg[10], 3) - graphics.line(self.x + 4, self.y + 22, self.x + 4, self.y + 30, (n >= 2) and class_colors[self.class] or bg[10], 3) - if next_n then - if next_n == 1 then - graphics.line(self.x - 3, self.y + 22, self.x - 3, self.y + 30, (n >= 1) and class_colors[self.class] or bg[10], 3) - elseif next_n == 2 then - graphics.line(self.x + 4, self.y + 22, self.x + 4, self.y + 30, (n >= 2) and class_colors[self.class] or bg[10], 3) - end - end + ]]-- end graphics.pop() end diff --git a/devlog.md b/devlog.md index 3fd8242..9d8d8d1 100644 --- a/devlog.md +++ b/devlog.md @@ -796,3 +796,37 @@ Nothing. Updated all tables with text descriptions as well as stats and overall gameplay numbers for all classes and characters. Tomorrow I start implementing the remaining 4 classes and 20 characters as well as revising the existing ones. + +# Day 50-51 - 07-08/04/21 + +Nothing... Finding the energy to work on this has been getting harder. I'm sure I'll get to it eventually but for now I've been spending time with some more fun projects. + +# Day 52 - 09/04/21 + +Lots of improvements and fixes to lots of different things that needed improving and fixing. I also started on the implementation of characters and Lv.3 effects. Today I got 10 out of 40 characters done completely: + +| Character | Classes | Description | +| --- | --- | --- | +| Vagrant | psyker, ranger, warrior | shoots a projectile that deals X damage | +| Swordsman | warrior | deals X AoE damage in an area, deals extra X/3 damage per unit hit | +| Wizard | mage | shoots a projectile that deals X AoE damage | +| Archer | ranger | shoots an arrow that deals X damage and pierces | +| Scout | rogue | throws a knife that deals X damage and chains 3 times | +| Cleric | healer | heals a unit for 20% of its max HP when it drops below 50% max HP | +| Outlaw | warrior, rogue | throws a fan of 5 knives, each dealing X damage | +| Blade | warrior, nuker | throws multiple blades that deal X AoE damage | +| Elementor | mage, nuker | deals X AoE damage in a large area centered on a random target | +| Saboteur | rogue, conjurer, nuker | calls 2 saboteurs to seek targets and deal X AoE damage | + +| Character | Lv.3 Effect Name | Lv.3 Effect Description | +| --- | --- | --- | +| Vagrant | Champion | +10% damage and +5% attack speed per active set | +| Swordsman | Cleave | the swordsman's damage is doubled | +| Wizard | Magic Missile | the projectile chains 5 times | +| Archer | Bounce Shot | the arrow ricochets off walls 3 times | +| Scout | Dagger Resonance | +25% damage per chain and +3 chains | +| Cleric | Mass Heal | heals all units | +| Outlaw | Flying Daggers | +50% outlaw attack speed and his knives seek enemies | +| Blade | Blade Resonance | deal additional X/2 damage per enemy hit | +| Elementor | Windfield | slows enemies by 60% for 6 seconds on hit | +| Saboteur | Demoman | the explosion has 50% chance to crit, increasing in size and dealing 2X damage | diff --git a/enemies.lua b/enemies.lua index 04c8ae9..3730178 100644 --- a/enemies.lua +++ b/enemies.lua @@ -225,15 +225,22 @@ function Seeker:update(dt) if main.current.mage_level == 2 then self.buff_def_a = -30 elseif main.current.mage_level == 1 then self.buff_def_a = -15 else self.buff_def_a = 0 end + if self.speed_boosting then local n = math.remap(love.timer.getTime() - self.speed_boosting, 0, 3, 1, 0.5) - self.buff_mvspd_m = (3 + 0.1*self.level)*n + self.speed_boosting_mvspd_m = (3 + 0.1*self.level)*n if not self.speed_booster and not self.exploder and not self.headbutter and not self.tank and not self.shooter and not self.spawner then self.color.r = math.remap(n, 1, 0.5, green[0].r, red[0].r) self.color.g = math.remap(n, 1, 0.5, green[0].g, red[0].g) self.color.b = math.remap(n, 1, 0.5, green[0].b, red[0].b) end - end + else self.speed_boosting_mvspd_m = 1 end + + if self.slowed then self.slow_mvspd_m = self.slowed + else self.slow_mvspd_m = 1 end + + self.buff_mvspd_m = (self.speed_boosting_mvspd_m or 1)*(self.slow_mvspd_m or 1) + self:calculate_stats() if self.shooter then @@ -406,6 +413,12 @@ function Seeker:speed_boost(duration) end +function Seeker:slow(amount, duration) + self.slowed = amount + self.t:after(duration, function() self.slowed = false end, 'slow') +end + + EnemyCritter = Object:extend() diff --git a/main.lua b/main.lua index f8838f8..098fe4c 100644 --- a/main.lua +++ b/main.lua @@ -268,29 +268,88 @@ function init() ['flagellant'] = {'psyker', 'enchanter'}, } + character_class_strings = { + ['vagrant'] = '[fg]Psyker, [green]Ranger, [yellow]Warrior', + ['swordsman'] = '[yellow]Warrior', + ['wizard'] = '[blue]Mage', + ['archer'] = '[green]Ranger', + ['scout'] = '[red]Rogue', + ['cleric'] = '[green]Healer', + ['outlaw'] = '[yellow]Warrior, [red]Rogue', + ['blade'] = '[yellow]Warrior, [red]Nuker', + ['elementor'] = '[blue]Mage, [red]Nuker', + ['saboteur'] = '[red]Rogue, [orange]Conjurer, [red]Nuker', + ['stormweaver'] = '[blue]Enchanter', + ['sage'] = '[red]Nuker', + ['squire'] = '[yellow]Warrior, [blue]Enchanter', + ['cannoneer'] = '[green]Ranger, [red]Nuker', + ['dual_gunner'] = '[green]Ranger, [red]Rogue', + ['hunter'] = '[green]Ranger, [orange]Conjurer, [yellow]Forcer', + ['chronomancer'] = '[blue]Mage, Enchanter', + ['spellblade'] = '[blue]Mage, [red]Rogue', + ['psykeeper'] = '[green]Healer, [fg]Psyker', + ['engineer'] = '[orange]Conjurer', + ['plague_doctor'] = '[red]Nuker, [purple]Voider', + ['fisherman'] = '[orange]Trapper, [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', + ['assassin'] = '[red]Rogue, [purple]Voider', + ['host'] = '[orange]Conjurer, [purple]Swarmer', + ['carver'] = '[orange]Conjurer, [green]Healer', + ['bane'] = '[purple]Swarmer, Voider', + ['psykino'] = '[blue]Mage, [fg]Psyker, [yellow]Forcer', + ['arbalester'] = '[green]Ranger, [yellow]Forcer', + ['barbarian'] = '[yellow]Warrior', + ['sapper'] = '[orange]Trapper, [blue]Enchanter, [green]Healer', + ['priest'] = '[green]Healer', + ['burrower'] = '[orange]Trapper, [purple]Swarmer', + ['flagellant'] = '[fg]Psyker, [blue]Enchanter', + } + + get_character_stat_string = function(character, level) + local group = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'}) + local mock = Player{group = group, leader = true, character = character, level = level, follower_index = 1} + mock:update(0) + return '[red]HP: [red]' .. mock.max_hp .. '[fg], [red]DMG: [red]' .. mock.dmg .. '[fg], [green]ASPD: [green]' .. math.round(mock.aspd_m, 2) .. 'x[fg], [blue]AREA: [blue]' .. + math.round(mock.area_dmg_m*mock.area_size_m, 2) .. 'x[fg], [yellow]DEF: [yellow]' .. math.round(mock.def, 2) .. '[fg], [green]MVSPD: [green]' .. math.round(mock.v, 2) .. '[fg]' + end + + get_character_stat = function(character, level, stat) + local group = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'}) + local mock = Player{group = group, leader = true, character = character, level = level, follower_index = 1} + mock:update(0) + return math.round(mock[stat], 2) + end + character_descriptions = { ['vagrant'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. get_character_stat('vagrant', lvl, 'dmg') .. '[fg] damage' end, - ['swordsman'] = function(lvl) return '[fg]deals [yellow]' .. get_character_stat('swordsman', lvl, 'dmg') .. '[fg] damage in an area around the unit, deals extra [yellow]' .. + ['swordsman'] = function(lvl) return '[fg]deals [yellow]' .. get_character_stat('swordsman', lvl, 'dmg') .. '[fg] damage in an area, deals extra [yellow]' .. math.round(get_character_stat('swordsman', lvl, 'dmg')/3, 2) .. '[fg] damage per unit hit' end, ['wizard'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. get_character_stat('wizard', lvl, 'dmg') .. ' AoE[fg] damage' end, ['archer'] = function(lvl) return '[fg]shoots an arrow that deals [yellow]' .. get_character_stat('archer', lvl, 'dmg') .. '[fg] damage and pierces' end, ['scout'] = function(lvl) return '[fg]throws a knife that deals [yellow]' .. get_character_stat('scout', lvl, 'dmg') .. '[fg] damage and chains [yellow]3[fg] times' end, ['cleric'] = function(lvl) return '[fg]heals a unit for [yellow]20%[fg] of its max hp when it drops below [yellow]50%[fg] max hp' end, - ['outlaw'] = function(lvl) return '[fg]throws a fan of [yellow]5[] knives, each dealing [yellow]' .. get_character_stat('outlaw', lvl, 'dmg') .. '[fg] damage' end, + ['outlaw'] = function(lvl) return '[fg]throws a fan of [yellow]5[fg] knives, each dealing [yellow]' .. get_character_stat('outlaw', lvl, 'dmg') .. '[fg] damage' end, ['blade'] = function(lvl) return '[fg]throws multiple blades that deal [yellow]' .. get_character_stat('blade', lvl, 'dmg') .. ' AoE[fg] damage' end, ['elementor'] = function(lvl) return '[fg]deals [yellow]' .. get_character_stat('elementor', lvl, 'dmg') .. ' AoE[fg] damage in a large area centered on a random target' end, - ['saboteur'] = function(lvl) return '[fg]calls [yellow]2[] saboteurs to seek targets and deal [yellow]' .. get_character_stat('saboteur', lvl, 'dmg') .. ' AoE[fg] damage' end, + ['saboteur'] = function(lvl) return '[fg]calls [yellow]2[fg] saboteurs to seek targets and deal [yellow]' .. get_character_stat('saboteur', lvl, 'dmg') .. ' AoE[fg] damage' end, ['stormweaver'] = function(lvl) return '[fg]infuses all allied projectiles with chain lightning that deals [yellow]+20%[fg] damage on hit' end, ['sage'] = function(lvl) return '[fg]shoots a slow projectile that draws enemies in' end, - ['squire'] = function(lvl) return '[yellow]+10% damage and defense to all allies' end, + ['squire'] = function(lvl) return '[yellow]+10%[fg] damage and defense to all allies' end, ['cannoneer'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. get_character_stat('cannoneer', lvl, 'dmg') .. ' AoE[fg] damage' end, ['dual_gunner'] = function(lvl) return '[fg]shoots two parallel projectiles' end, ['hunter'] = function(lvl) return '[fg]shoots an arrow that deals [yellow]' .. get_character_stat('hunter', lvl, 'dmg') .. '[fg] damage and has a [yellow]20%[fg] chance to summon a pet' end, - ['chronomancer'] = function(lvl) return '[yellow]+20% attack speed[fg] to all allies' end, + ['chronomancer'] = function(lvl) return '[yellow]+20%[fg] attack speed to all allies' end, ['spellblade'] = function(lvl) return '[fg]throws knives that deal [yellow]' .. get_character_stat('spellblade', lvl, 'dmg') .. '[fg] damage, pierce and spiral outwards' end, ['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, 'dot_dmg') .. '[fg] damage per second' 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]2[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]' .. @@ -302,33 +361,150 @@ function init() ['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, ['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, 'dot_dmg') .. '[fg] damage per second for [yellow]2[fg] seconds' end, + 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, 'dot_dmg') .. '[fg] damage per second in an area' 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, ['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, ['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, - ['flagellant'] = function(lvl) return '[fg]deals damage to self and grants +20% damage to all allies' end, + ['flagellant'] = function(lvl) return '[fg]deals damage to self and grants [yellow]+4%[fg] damage to all allies per cast' end, } - get_character_stat_string = function(character, level) - local group = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'}) - local mock = Player{group = group, leader = true, character = character, level = level, follower_index = 1} - mock:update(0) - return '[red]HP: [red]' .. mock.max_hp .. '[fg], [red]DMG: [red]' .. mock.dmg .. '[fg], [purple]DOT DMG: [purple]' .. mock.dot_dmg .. '[fg], [green]ASPD: [green]' .. math.round(mock.aspd_m, 2) .. 'x[fg], [blue]AREA: [blue]' .. - math.round(mock.area_dmg_m*mock.area_size_m, 2) .. 'x[fg], [yellow]DEF: [yellow]' .. math.round(mock.def, 2) .. '[fg], [green]MVSPD: [green]' .. math.round(mock.v, 2) .. '[fg]' - end + character_effect_names = { + ['vagrant'] = '[fg]Champion', + ['swordsman'] = '[yellow]Cleave', + ['wizard'] = '[blue]Magic Missile', + ['archer'] = '[green]Bounce Shot', + ['scout'] = '[red]Dagger Resonance', + ['cleric'] = '[green]Mass Heal ', + ['outlaw'] = '[red]Flying Daggers', + ['blade'] = '[yellow]Blade Resonance', + ['elementor'] = '[blue]Windfield', + ['saboteur'] = '[orange]Demoman', + ['stormweaver'] = '[blue]Lightning Spire', + ['sage'] = '[purple]Dimension Compression', + ['squire'] = '[yellow]Repair', + ['cannoneer'] = '[orange]Cannon Barrage', + ['dual_gunner'] = '[green]Gun Kata', + ['hunter'] = '[green]Feral Pack', + ['chronomancer'] = '[blue]Quicken', + ['spellblade'] = '[blue]Spiralism', + ['psykeeper'] = '[fg]Crucio', + ['engineer'] = '[orange]Upgrade', + ['plague_doctor'] = '[purple]Pandemic', + ['fisherman'] = '[yellow]Electric Net', + ['juggernaut'] = '[yellow]Brutal Impact', + ['lich'] = '[blue]Piercing Frost', + ['cryomancer'] = '[blue]Frostbite', + ['pyromancer'] = '[red]Ignite', + ['corruptor'] = '[purple]Infestation', + ['beastmaster'] = '[red]Call of the Wild', + ['launcher'] = '[orange]Kineticism', + ['spiker'] = '[orange]Caltrops', + ['assassin'] = '[purple]Toxic Delivery', + ['host'] = '[purple]Invasion', + ['carver'] = '[green]World Tree', + ['bane'] = '[purple]Baneling Swarm', + ['psykino'] = '[fg]Magnetic Force', + ['arbalester'] = '[green]Ballista Sinitra', + ['barbarian'] = '[yellow]Berserk', + ['sapper'] = '[blue]Chain Reaction', + ['priest'] = '[green]Divine Intervention', + ['burrower'] = '[orange]Zergling Rush', + ['flagellant'] = '[red]Zealotry', + } - get_character_stat = function(character, level, stat) - local group = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'}) - local mock = Player{group = group, leader = true, character = character, level = level, follower_index = 1} - mock:update(0) - return math.round(mock[stat], 2) - end + character_effect_names_gray = { + ['vagrant'] = '[light_bg]Champion', + ['swordsman'] = '[light_bg]Cleave', + ['wizard'] = '[light_bg]Magic Missile', + ['archer'] = '[light_bg]Bounce Shot', + ['scout'] = '[light_bg]Replica', + ['cleric'] = '[light_bg]Mass Heal ', + ['outlaw'] = '[light_bg]Fatal Roulette', + ['blade'] = '[light_bg]Blade Resonance', + ['elementor'] = '[light_bg]Windfield', + ['saboteur'] = '[light_bg]Chain Reaction', + ['stormweaver'] = '[light_bg]Lightning Spire', + ['sage'] = '[light_bg]Dimension Compression', + ['squire'] = '[light_bg]Repair', + ['cannoneer'] = '[light_bg]Cannon Barrage', + ['dual_gunner'] = '[light_bg]Gun Kata', + ['hunter'] = '[light_bg]Feral Pack', + ['chronomancer'] = '[light_bg]Quicken', + ['spellblade'] = '[light_bg]Spiralism', + ['psykeeper'] = '[light_bg]Crucio', + ['engineer'] = '[light_bg]Upgrade', + ['plague_doctor'] = '[light_bg]Pandemic', + ['fisherman'] = '[light_bg]Electric Net', + ['juggernaut'] = '[light_bg]Brutal Impact', + ['lich'] = '[light_bg]Piercing Frost', + ['cryomancer'] = '[light_bg]Frostbite', + ['pyromancer'] = '[light_bg]Ignite', + ['corruptor'] = '[light_bg]Infestation', + ['beastmaster'] = '[light_bg]Call of the Wild', + ['launcher'] = '[light_bg]Kineticism', + ['spiker'] = '[light_bg]Caltrops', + ['assassin'] = '[light_bg]Toxic Delivery', + ['host'] = '[light_bg]Invasion', + ['carver'] = '[light_bg]World Tree', + ['bane'] = '[light_bg]Baneling Swarm', + ['psykino'] = '[light_bg]Magnetic Force', + ['arbalester'] = '[light_bg]Ballista Sinitra', + ['barbarian'] = '[light_bg]Berserk', + ['sapper'] = '[light_bg]Chain Reaction', + ['priest'] = '[light_bg]Divine Intervention', + ['burrower'] = '[light_bg]Zergling Rush', + ['flagellant'] = '[light_bg]Zealotry', + } + + character_effect_descriptions = { + ['vagrant'] = function() return '[yellow]+10%[fg] damage and [yellow]+5%[fg] attack speed per active set' end, + ['swordsman'] = function() return "[fg]the swordsman's damage is [yellow]doubled" end, + ['wizard'] = function() return '[fg]the projectile chains [yellow]5[fg] times' end, + ['archer'] = function() return '[fg]the arrow ricochets off walls [yellow]3[fg] times' end, + ['scout'] = function() return '[yellow]+25%[fg] damage per chain and [yellow]+3[fg] chains' end, + ['cleric'] = function() return '[fg]heals all units' end, + ['outlaw'] = function() return "[yellow]+50%[fg] outlaw attack speed and his knives seek enemies" end, + ['blade'] = function() return '[fg]deal additional [yellow]' .. get_character_stat('blade', 3, 'dmg')/2 .. '[fg] damage per enemy hit' end, + ['elementor'] = function() return '[fg]slows enemies by [yellow]60%[fg] for [yellow]6[fg] seconds on hit' end, + ['saboteur'] = function() return '[fg]the explosion has [yellow]50%[fg] chance to crit, increasing in size and dealing [yellow]2x[fg] damage' end, + ['stormweaver'] = function() return '[fg]cast a spire of lightning periodically' end, + ['sage'] = function() return '[fg]when the projectile expires deal [yellow]' .. get_character_stat('sage', 3, 'dmg') .. '[fg] to all enemies under its influence' end, + ['squire'] = function() return '[fg]you can reroll your item choices once, these opportunities stack if unused' end, + ['cannoneer'] = function() return '[fg]showers the area in additional cannon shots that deal [yellow]' .. get_character_stat('cannoneer', 3, 'dmg') .. '[fg] AoE damage' end, + ['dual_gunner'] = function() return '[fg]every 5th attack shoots projectiles in rapid succession targetting all nearby enemies for [yellow]2[fg] seconds' end, + ['hunter'] = function() return '[fg]summons 3 pets' end, + ['chronomancer'] = function() return '[fg]enemies take damave over time [yellow]50%[fg] faster' end, + ['spellblade'] = function() return '[fg]faster projectile speed and tighter turns' end, + ['psykeeper'] = function() return '[fg]also redistributes damage taken as damage to all enemies' end, + ['engineer'] = function() return '[fg]every 3rd sentry dropped upgrade all sentries, granting them [yellow]+100%[fg] damage and attack speed' end, + ['plague_doctor'] = function() return '[fg]inflicts enemies with a contagion that deals additional [yellow]' .. get_character_stat('plague_doctor', 3, 'dmg') .. '[fg] damage per second and spreads to nearby enemies' 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, + ['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, + ['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, + ['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, + ['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, + } character_stats = { ['vagrant'] = function(lvl) return get_character_stat_string('vagrant', lvl) end, @@ -375,19 +551,19 @@ function init() } class_stat_multipliers = { - ['ranger'] = {hp = 1, dmg = 1.2, aspd = 1.5, area_dmg = 1, area_size = 1, dot_dmg = 1.1, def = 0.9, mvspd = 1.2}, - ['warrior'] = {hp = 1.4, dmg = 1.1, aspd = 0.9, area_dmg = 1, area_size = 1, dot_dmg = 1, def = 1.25, mvspd = 0.9}, - ['mage'] = {hp = 0.6, dmg = 1.4, aspd = 1, area_dmg = 1.25, area_size = 1.2, dot_dmg = 1.25, def = 0.75, mvspd = 1}, - ['rogue'] = {hp = 0.8, dmg = 1.3, aspd = 1.1, area_dmg = 0.6, area_size = 0.6, dot_dmg = 1.4, def = 0.8, mvspd = 1.4}, - ['healer'] = {hp = 1.2, dmg = 1, aspd = 0.5, area_dmg = 1, area_size = 1, dot_dmg = 1, def = 1.2, mvspd = 1}, - ['enchanter'] = {hp = 1.2, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, dot_dmg = 1, def = 1.2, mvspd = 1.2}, - ['nuker'] = {hp = 0.9, dmg = 1, aspd = 0.75, area_dmg = 1.5, area_size = 1.5, dot_dmg = 0.75, def = 1, mvspd = 1}, - ['conjurer'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, dot_dmg = 1, def = 1, mvspd = 1}, - ['psyker'] = {hp = 1.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, dot_dmg = 1, def = 0.5, mvspd = 1}, - ['trapper'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, dot_dmg = 1.1, def = 0.75, mvspd = 1}, - ['forcer'] = {hp = 1.25, dmg = 1.1, aspd = 0.9, area_dmg = 0.75, area_size = 0.75, dot_dmg = 1, def = 1.2, mvspd = 1}, - ['swarmer'] = {hp = 1.2, dmg = 1, aspd = 1.25, area_dmg = 1, area_size = 1, dot_dmg = 1, def = 0.75, mvspd = 0.5}, - ['voider'] = {hp = 0.75, dmg = 1.3, aspd = 1, area_dmg = 0.8, area_size = 0.75, dot_dmg = 2, def = 0.6, mvspd = 0.8}, + ['ranger'] = {hp = 1, dmg = 1.2, aspd = 1.5, area_dmg = 1, area_size = 1, def = 0.9, mvspd = 1.2}, + ['warrior'] = {hp = 1.4, dmg = 1.1, aspd = 0.9, area_dmg = 1, area_size = 1, def = 1.25, mvspd = 0.9}, + ['mage'] = {hp = 0.6, dmg = 1.4, aspd = 1, area_dmg = 1.25, area_size = 1.2, def = 0.75, mvspd = 1}, + ['rogue'] = {hp = 0.8, dmg = 1.3, aspd = 1.1, area_dmg = 0.6, area_size = 0.6, def = 0.8, mvspd = 1.4}, + ['healer'] = {hp = 1.2, dmg = 1, aspd = 0.5, area_dmg = 1, area_size = 1, def = 1.2, mvspd = 1}, + ['enchanter'] = {hp = 1.2, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1.2, mvspd = 1.2}, + ['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}, + ['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}, ['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}, @@ -414,9 +590,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', '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'}, } non_attacking_characters = {'cleric', 'stormweaver', 'squire', 'chronomancer', 'sage'} @@ -500,6 +676,36 @@ function init() enchanter = enchanters, psyker = psykers, trapper = trappers, forcer = forcers, swarmer = swarmers, voider = voiders} end + get_class_levels = function(units) + local units_per_class = get_number_of_units_per_class(units) + local units_to_class_level = function(number_of_units, class) + if class == 'ranger' or class == 'warrior' or class == 'mage' or class == 'nuker' or class == 'rogue' then + 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 + if number_of_units >= 4 then return 2 + elseif number_of_units >= 2 then return 1 + else return 0 end + end + end + return { + ranger = units_to_class_level(units_per_class.ranger, 'ranger'), + warrior = units_to_class_level(units_per_class.warrior, 'warrior'), + mage = units_to_class_level(units_per_class.mage, 'mage'), + nuker = units_to_class_level(units_per_class.nuker, 'nuker'), + rogue = units_to_class_level(units_per_class.rogue, 'rogue'), + healer = units_to_class_level(units_per_class.healer, 'healer'), + 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'), + 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'), + } + end + get_classes = function(units) local classes = {} for _, unit in ipairs(units) do @@ -580,7 +786,7 @@ function init() [25] = {100, 100}, } - boss_by_level = { + level_to_boss = { [6] = 'speed_booster', [12] = 'exploder', [18] = 'swarmer', @@ -592,7 +798,9 @@ function init() main = Main() main:add(BuyScreen'buy_screen') - main:go_to('buy_screen', 0, {}) + main:go_to('buy_screen', 15, { + {character = 'saboteur', level = 3}, + }) --[[ main:add(Arena'arena') main:go_to('arena', 18, { diff --git a/player.lua b/player.lua index 5d0de0d..a841dc6 100644 --- a/player.lua +++ b/player.lua @@ -41,7 +41,7 @@ function Player:init(args) self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then - self:shoot(self:angle_to_object(closest_enemy)) + self:shoot(self:angle_to_object(closest_enemy), {chain = (self.level == 3 and 5 or 0)}) end end, nil, nil, 'shoot') @@ -55,7 +55,7 @@ function Player:init(args) self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then - self:shoot(self:angle_to_object(closest_enemy), {pierce = 1000}) + self:shoot(self:angle_to_object(closest_enemy), {pierce = 1000, ricochet = (self.level == 3 and 3 or 0)}) end end, nil, nil, 'shoot') @@ -69,7 +69,7 @@ function Player:init(args) self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then - self:shoot(self:angle_to_object(closest_enemy), {chain = 3}) + self:shoot(self:angle_to_object(closest_enemy), {chain = (self.level == 3 and 6 or 3)}) end end, nil, nil, 'shoot') @@ -82,9 +82,15 @@ function Player:init(args) self.last_heal_time = love.timer.getTime() self.t:every(2, function() local all_units = self:get_all_units() - if table.any(all_units, function(v) return v.hp <= 0.5*v.max_hp end) and love.timer.getTime() - self.last_heal_time > 6 then + local unit_index = table.contains(all_units, function(v) return v.hp <= 0.5*v.max_hp end) + if unit_index and love.timer.getTime() - self.last_heal_time > 6 then + local unit = all_units[unit_index] self.last_heal_time = love.timer.getTime() - for _, unit in ipairs(all_units) do unit:heal(0.1*unit.max_hp*(self.heal_effect_m or 1)) end + if self.level == 3 then + for _, unit in ipairs(all_units) do unit:heal(0.2*unit.max_hp*(self.heal_effect_m or 1)) end + else + unit:heal(0.2*unit.max_hp*(self.heal_effect_m or 1)) + end heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5} end end) @@ -99,7 +105,7 @@ function Player:init(args) 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)) + self:shoot(self:angle_to_object(closest_enemy), {homing = (self.level == 3)}) end end, nil, nil, 'shoot') @@ -137,7 +143,7 @@ function Player:init(args) 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) - Saboteur{group = main.current.main, x = x, y = y, parent = self, conjurer_buff_m = self.conjurer_buff_m or 1} + Saboteur{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1, crit = (self.level == 3) and random:bool(50)} end} end, 2) end) @@ -257,6 +263,19 @@ function Player:init(args) end) end + --[[ + elseif self.character == 'plague_doctor' then + self.color = character_colors.plague_doctor + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = character_classes.plague_doctor + + self.t:every(5, function() + self:attack(64) + end, nil, nil, 'attack') + end + ]]-- + self:calculate_stats(true) if self.leader then @@ -286,6 +305,33 @@ function Player:update(dt) end end + if self.character == 'vagrant' and self.level == 3 then + local class_levels = get_class_levels(self:get_all_units()) + local number_of_active_sets = 0 + if class_levels.ranger >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.warrior >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.mage >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.rogue >= 1 then number_of_active_sets = number_of_active_sets + 1 end + if class_levels.healer >= 1 then number_of_active_sets = number_of_active_sets + 1 end + 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.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 + self.vagrant_dmg_m = 1 + 0.1*number_of_active_sets + self.vagrant_aspd_m = 1 + 0.05*number_of_active_sets + end + + if self.character == 'swordsman' and self.level == 3 then + self.swordsman_dmg_m = 2 + end + + if self.character == 'outlaw' and self.level == 3 then + self.outlaw_aspd_m = 1.5 + end + if table.any(self.classes, function(v) return v == 'ranger' end) then if main.current.ranger_level == 2 then self.chance_to_barrage = 20 elseif main.current.ranger_level == 1 then self.chance_to_barrage = 10 @@ -326,8 +372,8 @@ function Player:update(dt) end self.buff_def_a = (self.warrior_def_a or 0) - self.buff_aspd_m = (self.chronomancer_aspd_m or 1) - self.buff_dmg_m = (self.squire_dmg_m or 1)*(main.current.enchanter_dmg_m or 1) + self.buff_aspd_m = (self.chronomancer_aspd_m or 1)*(self.vagrant_aspd_m or 1)*(self.outlaw_aspd_m or 1) + self.buff_dmg_m = (self.squire_dmg_m or 1)*(self.vagrant_dmg_m or 1)*(main.current.enchanter_dmg_m or 1)*(self.swordsman_dmg_m or 1) self.buff_def_m = (self.squire_def_m or 1) self.buff_area_size_m = (self.nuker_area_size_m or 1) self.buff_area_dmg_m = (self.nuker_area_dmg_m or 1) @@ -559,7 +605,7 @@ function Player:shoot(r, mods) r = r - 2*math.pi/8 for i = 1, 5 do 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.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, - parent = self} + parent = self, level = self.level} Projectile(table.merge(t, mods or {})) r = r + math.pi/8 end @@ -571,30 +617,31 @@ function Player:shoot(r, mods) local r = self:angle_to_object(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.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, - parent = self} + parent = self, level = self.level} Projectile(table.merge(t, mods or {})) end end elseif self.character == 'sage' 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} - 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 = 25, r = r, color = self.color, dmg = self.dmg, pierce = 1000, character = 'sage', parent = self} + 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 = 25, r = r, color = self.color, dmg = self.dmg, pierce = 1000, character = 'sage', + parent = self, level = self.level} Projectile(table.merge(t, mods or {})) elseif self.character == 'dual_gunner' then HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r) + 4*math.cos(r - math.pi/2), y = self.y + 0.8*self.shape.w*math.sin(r) + 4*math.sin(r - math.pi/2), rs = 6} HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r) + 4*math.cos(r + math.pi/2), y = self.y + 0.8*self.shape.w*math.sin(r) + 4*math.sin(r + math.pi/2), rs = 6} local t1 = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r) + 4*math.cos(r - math.pi/2) , y = self.y + 1.6*self.shape.w*math.sin(r) + 4*math.sin(r - math.pi/2), - v = 250, r = r, color = self.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, parent = self} + v = 250, r = r, color = self.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, parent = self, level = self.level} local t2 = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r) + 4*math.cos(r + math.pi/2) , y = self.y + 1.6*self.shape.w*math.sin(r) + 4*math.sin(r + math.pi/2), - v = 250, r = r, color = self.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, parent = self} + v = 250, r = r, color = self.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, parent = self, level = self.level} Projectile(table.merge(t1, mods or {})) Projectile(table.merge(t2, mods or {})) else 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.color, dmg = self.dmg*dmg_m, crit = crit, character = self.character, - parent = self} + parent = self, level = self.level} Projectile(table.merge(t, mods or {})) end @@ -623,7 +670,7 @@ 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} + 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} Area(table.merge(t, mods)) if self.character == 'swordsman' then @@ -641,7 +688,7 @@ function Player:barrage(r, n) archer1:play{pitch = random:float(0.95, 1.05), volume = 0.35} 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 + random:float(-math.pi/16, math.pi/16), color = self.color, dmg = self.dmg, - parent = self, character = 'barrage'} + parent = self, character = 'barrage', level = self.level} Projectile(table.merge(t, mods or {})) end) end @@ -658,6 +705,7 @@ function Projectile:init(args) self:set_as_rectangle(10, 4, 'dynamic', 'projectile') self.pierce = args.pierce or 0 self.chain = args.chain or 0 + self.ricochet = args.ricochet or 0 self.chain_enemies_hit = {} self.infused_enemies_hit = {} @@ -685,6 +733,14 @@ function Projectile:init(args) self.t:tween(12.25, self, {orbit_vr = 0}, math.linear) end) end + + if self.homing then + self.homing = false + self.t:after(0.1, function() + self.homing = true + self.closest_sensor = Circle(self.x, self.y, 64) + end) + end end @@ -695,8 +751,22 @@ function Projectile:update(dt) self.orbit_r = self.orbit_r + self.orbit_vr*dt end - self:set_angle(self.r) - self:move_along_angle(self.v, self.r + (self.orbit_r or 0)) + if self.homing then + self.closest_sensor:move_to(self.x, self.y) + local target = self:get_closest_object_in_shape(self.closest_sensor, main.current.enemies) + if target then + self:rotate_towards_object(target, 0.1) + self.r = self:get_angle() + self:move_along_angle(self.v, self.r + (self.orbit_r or 0)) + else + self:set_angle(self.r) + self:move_along_angle(self.v, self.r + (self.orbit_r or 0)) + end + else + self:set_angle(self.r) + self:move_along_angle(self.v, self.r + (self.orbit_r or 0)) + end + if self.character == 'sage' then self.pull_sensor:move_to(self.x, self.y) @@ -738,11 +808,11 @@ function Projectile:die(x, y, r, n) self.dead = true if self.character == 'wizard' then - Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*32, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*32, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character, level = self.level} elseif self.character == 'blade' then - Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*64, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*64, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character, level = self.level} elseif self.character == 'cannoneer' then - Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*96, color = self.color, dmg = 2*self.parent.area_dmg_m*self.dmg, character = self.character} + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*96, color = self.color, dmg = 2*self.parent.area_dmg_m*self.dmg, character = self.character, level = self.level} end end @@ -758,9 +828,15 @@ function Projectile:on_collision_enter(other, contact) if other:is(Wall) then if self.character == 'archer' or self.character == 'hunter' or self.character == 'barrage' then - self:die(x, y, r, 0) + if self.ricochet <= 0 then + self:die(x, y, r, 0) + WallArrow{group = main.current.main, x = x, y = y, r = self.r, color = self.color} + else + local r = Unit.bounce(self, nx, ny) + self.r = r + self.ricochet = self.ricochet - 1 + end _G[random:table{'arrow_hit_wall1', 'arrow_hit_wall2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.2} - WallArrow{group = main.current.main, x = x, y = y, r = self.r, color = self.color} elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'spellblade' then self:die(x, y, r, 0) knife_hit_wall1:play{pitch = random:float(0.9, 1.1), volume = 0.2} @@ -805,6 +881,9 @@ function Projectile:on_trigger_enter(other, contact) if object then self.r = self:angle_to_object(object) self.v = self.v*1.25 + if self.level == 3 and self.character == 'scout' then + self.dmg = self.dmg*1.25 + end end end HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = fg[0], duration = 0.1} @@ -825,6 +904,10 @@ function Projectile:on_trigger_enter(other, contact) other:hit(self.dmg, self) + if self.character == 'wizard' and self.level == 3 then + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*32, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} + end + if self.character == 'hunter' and random:bool(40) then trigger:after(0.01, function() SpawnEffect{group = main.current.effects, x = self.parent.x, y = self.parent.y, color = orange[0], action = function(x, y) @@ -877,9 +960,17 @@ 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) + if self.level == 3 then + enemy:slow(0.4, 6) + end + elseif self.character == 'swordsman' then + enemy:hit(self.dmg + self.dmg*0.33*#enemies) + elseif self.character == 'blade' and self.level == 3 then + enemy:hit(self.dmg + self.dmg*0.5*#enemies) else enemy:hit(self.dmg) end @@ -1081,6 +1172,8 @@ function Saboteur:init(args) _G[random:table{'saboteur1', 'saboteur2', 'saboteur3'}]:play{pitch = random:float(0.8, 1.2), volume = 0.2} self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) + + self.actual_dmg = get_character_stat('saboteur', self.level, 'dmg') end @@ -1109,7 +1202,8 @@ end function Saboteur:on_collision_enter(other, contact) if table.any(main.current.enemies, function(v) return other:is(v) end) then camera:shake(4, 0.5) - local t = {group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.area_size_m*64, color = self.color, dmg = self.area_dmg_m*self.dmg*(self.conjurer_buff_m or 1), character = self.character} + local t = {group = main.current.effects, x = self.x, y = self.y, r = self.r, w = (self.crit and 1.5 or 1)*self.area_size_m*64, color = self.color, + dmg = (self.crit and 2 or 1)*self.area_dmg_m*self.actual_dmg*(self.conjurer_buff_m or 1), character = self.character} Area(table.merge(t, mods or {})) self.dead = true end diff --git a/todo b/todo index 98443ed..ef47812 100644 --- a/todo +++ b/todo @@ -40,16 +40,16 @@ Swarmer: increased critter health Voider: increased damage over time Characters - Vagrant [psyker, ranger, warrior]: shoots a projectile - Lv.3: Champion - gains increased damage and attack speed based on number of active sets - Swordsman [warrior]: deals AoE damage, deals extra damage for each unit hit - Lv.3: Cleave - damage is doubled - Wizard [mage]: shoots a projectile that deals AoE damage - Lv.3: Magic Missile - the projectile chains 5 times, each dealing AoE damage on impact - Archer [ranger]: shoots an arrow that pierces - Lv.3: Bounce Shot - the arrow ricochets on walls 3 times - Scout [rogue]: throws a knife that chains 3 times - Lv.3: Replica - each chain grants +15% damage and the last chain splits - Cleric [healer]: heals a unit when its health drops below half HP - Lv.3: Mass Heal - heals all units instead of one - Outlaw [warrior, rogue]: throws a fan of 5 knives - Lv.3: Fatal Roulette - every 3rd attack throw a nova of 15 knives instead - Blade [warrior, nuker]: throws multiple blades that deal AoE damage - Lv.3: Blade Resonance - deal additional damage based on number of enemies hit - Elementor [mage, nuker]: deals AoE damage to a random target in a large area - Lv.3: Windfield - slows enemies hit - Saboteur [rogue, conjurer, nuker]: calls saboteurs to seek targets and deal AoE damage - Lv.3: Chain Reaction - should an enemy die from a saboteur explosion, it also explodes + * Vagrant [psyker, ranger, warrior]: shoots a projectile - Lv.3: Champion - gains increased damage and attack speed based on number of active sets + * Swordsman [warrior]: deals AoE damage, deals extra damage for each unit hit - Lv.3: Cleave - damage is doubled + * Wizard [mage]: shoots a projectile that deals AoE damage - Lv.3: Magic Missile - the projectile chains 5 times, each dealing AoE damage on impact + * Archer [ranger]: shoots an arrow that pierces - Lv.3: Bounce Shot - the arrow ricochets on walls 3 times + * Scout [rogue]: throws a knife that chains 3 times - Lv.3: Replica - each chain grants +15% damage and the last chain splits + * Cleric [healer]: heals a unit when its health drops below half HP - Lv.3: Mass Heal - heals all units instead of one + * Outlaw [warrior, rogue]: throws a fan of 5 knives - Lv.3: Fatal Roulette - every 3rd attack throw a nova of 15 knives instead + * Blade [warrior, nuker]: throws multiple blades that deal AoE damage - Lv.3: Blade Resonance - deal additional damage based on number of enemies hit + * Elementor [mage, nuker]: deals AoE damage to a random target in a large area - Lv.3: Windfield - slows enemies hit + * Saboteur [rogue, conjurer, nuker]: calls saboteurs to seek targets and deal AoE damage - Lv.3: Chain Reaction - should an enemy die from a saboteur explosion, it also explodes Stormweaver [enchanter]: infuses all allied projectiles with chain lightning that deals extra damage - Lv.3: Lightning Spire - cast a spire of lightning periodically Sage [nuker]: shoots a slow moving projectile that pulls enemies in - Lv.3: Dimension Compression - when the projectile expires deal massive damage to all enemies under its influence Squire [warrior, enchanter]: increased damage and defense to all allies - Lv.3: Repair - you can reroll your item choice once every 3 levels, these opportunities stack if unused