diff --git a/arena.lua b/arena.lua index 910ad12..6e75dc4 100644 --- a/arena.lua +++ b/arena.lua @@ -330,10 +330,6 @@ function Arena:on_enter(from, level, loop, units, passives, shop_level, shop_xp, self.t:after(1.125 + math.floor(n/4)*0.25, function() self.spawning_enemies = false end, 'spawning_enemies') self.enemy_spawns_prevented = 0 end) - - self.level = 25*(self.loop+1) - self.won = true - self:quit() end @@ -392,7 +388,9 @@ function Arena:update(dt) 'defensive_stance', 'offensive_stance', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'seeping', 'deceleration', 'annihilation', 'malediction', 'hextouch', 'whispers_of_doom', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'hive', 'baneling_burst', 'blunt_arrow', 'explosive_arrow', 'divine_machine_arrow', 'chronomancy', 'awakening', 'divine_punishment', 'assassination', 'flying_daggers', 'ultimatum', 'magnify', 'echo_barrage', 'unleash', 'reinforce', 'payback', 'enchanted', 'freezing_field', 'burning_field', 'gravity_field', 'magnetism', - 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'summon_instability', + 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'construct_instability', + 'intimidation', 'vulnerability', 'temporal_chains', 'ceremonial_dagger', 'homing_barrage', 'critical_strike', 'noxious_strike', 'infesting_strike', 'burning_strike', 'lucky_strike', 'healing_strike', 'stunning_strike', + 'silencing_strike', 'culling_strike', 'lightning_strike', 'psycholeak', 'divine_blessing', } max_units = math.clamp(7 + current_new_game_plus + self.loop, 7, 12) main:add(BuyScreen'buy_screen') @@ -838,7 +836,9 @@ function Arena:die() 'defensive_stance', 'offensive_stance', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'seeping', 'deceleration', 'annihilation', 'malediction', 'hextouch', 'whispers_of_doom', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'hive', 'baneling_burst', 'blunt_arrow', 'explosive_arrow', 'divine_machine_arrow', 'chronomancy', 'awakening', 'divine_punishment', 'assassination', 'flying_daggers', 'ultimatum', 'magnify', 'echo_barrage', 'unleash', 'reinforce', 'payback', 'enchanted', 'freezing_field', 'burning_field', 'gravity_field', 'magnetism', - 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'summon_instability', + 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'construct_instability', + 'intimidation', 'vulnerability', 'temporal_chains', 'ceremonial_dagger', 'homing_barrage', 'critical_strike', 'noxious_strike', 'infesting_strike', 'burning_strike', 'lucky_strike', 'healing_strike', 'stunning_strike', + 'silencing_strike', 'culling_strike', 'lightning_strike', 'psycholeak', 'divine_blessing', } max_units = math.clamp(7 + current_new_game_plus + self.loop, 7, 12) main:add(BuyScreen'buy_screen') @@ -908,6 +908,8 @@ function Arena:create_credits() open_url(b, 'https://freesound.org/people/benzix2/sounds/467951/') end} Button{group = self.credits, x = 204, y = 160, button_text = 'lord', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b) open_url(b, 'https://store.steampowered.com/developer/T_TGames') end} + Button{group = self.credits, x = 254, y = 160, button_text = 'InspectorJ', fg_color = 'yellowm5', bg_color = 'yellow', credits_button = true, action = function(b) + open_url(b, 'https://freesound.org/people/InspectorJ/sounds/458586/') end} Text2{group = self.credits, x = 70, y = 190, lines = {{text = '[red]playtesters: ', font = pixul_font}}} Button{group = self.credits, x = 130, y = 190, button_text = 'Jofer', fg_color = 'redm5', bg_color = 'red', credits_button = true, action = function(b) open_url(b, 'https://twitter.com/JofersGames') end} @@ -1108,7 +1110,7 @@ function Arena:spawn_n_enemies(p, j, n, pass) spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15} if not pass then check_circle:move_to(x, y) - local objects = self.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Player, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = self.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Player, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) if #objects > 0 then self.enemy_spawns_prevented = self.enemy_spawns_prevented + 1; return end end diff --git a/assets/images/burning_strike.png b/assets/images/burning_strike.png new file mode 100644 index 0000000..bab44d7 Binary files /dev/null and b/assets/images/burning_strike.png differ diff --git a/assets/images/ceremonial_dagger.png b/assets/images/ceremonial_dagger.png new file mode 100644 index 0000000..229c152 Binary files /dev/null and b/assets/images/ceremonial_dagger.png differ diff --git a/assets/images/summon_instability.png b/assets/images/construct_instability.png similarity index 100% rename from assets/images/summon_instability.png rename to assets/images/construct_instability.png diff --git a/assets/images/critical_strike.png b/assets/images/critical_strike.png new file mode 100644 index 0000000..f9b0c47 Binary files /dev/null and b/assets/images/critical_strike.png differ diff --git a/assets/images/culling_strike.png b/assets/images/culling_strike.png new file mode 100644 index 0000000..41407c0 Binary files /dev/null and b/assets/images/culling_strike.png differ diff --git a/assets/images/divine_blessing.png b/assets/images/divine_blessing.png new file mode 100644 index 0000000..eeab219 Binary files /dev/null and b/assets/images/divine_blessing.png differ diff --git a/assets/images/healing_strike.png b/assets/images/healing_strike.png new file mode 100644 index 0000000..a6519b7 Binary files /dev/null and b/assets/images/healing_strike.png differ diff --git a/assets/images/homing_barrage.png b/assets/images/homing_barrage.png new file mode 100644 index 0000000..ec9d0e4 Binary files /dev/null and b/assets/images/homing_barrage.png differ diff --git a/assets/images/infesting_strike.png b/assets/images/infesting_strike.png new file mode 100644 index 0000000..3e5aae8 Binary files /dev/null and b/assets/images/infesting_strike.png differ diff --git a/assets/images/kinetic_strike.png b/assets/images/kinetic_strike.png new file mode 100644 index 0000000..2defdbc Binary files /dev/null and b/assets/images/kinetic_strike.png differ diff --git a/assets/images/lightning_strike.png b/assets/images/lightning_strike.png new file mode 100644 index 0000000..8717176 Binary files /dev/null and b/assets/images/lightning_strike.png differ diff --git a/assets/images/lucky_strike.png b/assets/images/lucky_strike.png new file mode 100644 index 0000000..8c9e819 Binary files /dev/null and b/assets/images/lucky_strike.png differ diff --git a/assets/images/noxious_strike.png b/assets/images/noxious_strike.png new file mode 100644 index 0000000..b56077d Binary files /dev/null and b/assets/images/noxious_strike.png differ diff --git a/assets/images/psycholeak.png b/assets/images/psycholeak.png new file mode 100644 index 0000000..966d71b Binary files /dev/null and b/assets/images/psycholeak.png differ diff --git a/assets/images/silencing_strike.png b/assets/images/silencing_strike.png new file mode 100644 index 0000000..6a6baf3 Binary files /dev/null and b/assets/images/silencing_strike.png differ diff --git a/assets/images/stunning_strike.png b/assets/images/stunning_strike.png new file mode 100644 index 0000000..b7d0358 Binary files /dev/null and b/assets/images/stunning_strike.png differ diff --git a/assets/images/warping_shots.png b/assets/images/warping_shots.png new file mode 100644 index 0000000..86ac272 Binary files /dev/null and b/assets/images/warping_shots.png differ diff --git a/assets/sounds/458586__inspectorj__ui-mechanical-notification-01-fx.ogg b/assets/sounds/458586__inspectorj__ui-mechanical-notification-01-fx.ogg new file mode 100644 index 0000000..0a50a64 Binary files /dev/null and b/assets/sounds/458586__inspectorj__ui-mechanical-notification-01-fx.ogg differ diff --git a/assets/sounds/Explosion Grenade_04.ogg b/assets/sounds/Explosion Grenade_04.ogg new file mode 100644 index 0000000..9dfb121 Binary files /dev/null and b/assets/sounds/Explosion Grenade_04.ogg differ diff --git a/buy_screen.lua b/buy_screen.lua index 8481fb3..f6e3244 100644 --- a/buy_screen.lua +++ b/buy_screen.lua @@ -144,7 +144,9 @@ function BuyScreen:on_enter(from, level, loop, units, passives, shop_level, shop 'defensive_stance', 'offensive_stance', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'seeping', 'deceleration', 'annihilation', 'malediction', 'hextouch', 'whispers_of_doom', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'hive', 'baneling_burst', 'blunt_arrow', 'explosive_arrow', 'divine_machine_arrow', 'chronomancy', 'awakening', 'divine_punishment', 'assassination', 'flying_daggers', 'ultimatum', 'magnify', 'echo_barrage', 'unleash', 'reinforce', 'payback', 'enchanted', 'freezing_field', 'burning_field', 'gravity_field', 'magnetism', - 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'summon_instability', + 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'construct_instability', + 'intimidation', 'vulnerability', 'temporal_chains', 'ceremonial_dagger', 'homing_barrage', 'critical_strike', 'noxious_strike', 'infesting_strike', 'burning_strike', 'lucky_strike', 'healing_strike', 'stunning_strike', + 'silencing_strike', 'culling_strike', 'lightning_strike', 'psycholeak', 'divine_blessing', } max_units = math.clamp(7 + current_new_game_plus + self.loop, 7, 12) main:add(BuyScreen'buy_screen') @@ -547,7 +549,9 @@ function RestartButton:update(dt) 'defensive_stance', 'offensive_stance', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'seeping', 'deceleration', 'annihilation', 'malediction', 'hextouch', 'whispers_of_doom', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'hive', 'baneling_burst', 'blunt_arrow', 'explosive_arrow', 'divine_machine_arrow', 'chronomancy', 'awakening', 'divine_punishment', 'assassination', 'flying_daggers', 'ultimatum', 'magnify', 'echo_barrage', 'unleash', 'reinforce', 'payback', 'enchanted', 'freezing_field', 'burning_field', 'gravity_field', 'magnetism', - 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'summon_instability', + 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'construct_instability', + 'intimidation', 'vulnerability', 'temporal_chains', 'ceremonial_dagger', 'homing_barrage', 'critical_strike', 'noxious_strike', 'infesting_strike', 'burning_strike', 'lucky_strike', 'healing_strike', 'stunning_strike', + 'silencing_strike', 'culling_strike', 'lightning_strike', 'psycholeak', 'divine_blessing', } system.save_state() main:add(BuyScreen'buy_screen') diff --git a/enemies.lua b/enemies.lua index dbf89c0..d165f13 100644 --- a/enemies.lua +++ b/enemies.lua @@ -214,6 +214,7 @@ function Seeker:init(args) self.hp = self.max_hp local n = math.remap(current_new_game_plus, 0, 5, 1, 0.75) self.t:every({3*n, 5*n}, function() + if self.silenced or self.barbarian_stunned then return end local enemy = self:get_closest_object_in_shape(Circle(self.x, self.y, 64), main.current.enemies) if enemy then wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.5} @@ -247,17 +248,17 @@ function Seeker:init(args) local player = main.current.player if player and player.intimidation and not self.boss and not self.tank then - self.buff_hp_m = 0.8 + self.buff_hp_m = (player.intimidation == 1 and 0.9) or (player.intimidation == 2 and 0.8) or (player.intimidation == 3 and 0.7) self:calculate_stats() self.hp = self.max_hp end if player and player.vulnerability then - self.vulnerable = true + self.vulnerable = (player.vulnerability == 1 and 1.1) or (player.vulnerability == 2 and 1.2) or (player.vulnerability == 3 and 1.3) end if player and player.temporal_chains then - self.temporal_chains_mvspd_m = 0.8 + self.temporal_chains_mvspd_m = (player.temporal_chains and 0.9) or (player.temporal_chains and 0.8) or (player.temporal_chains and 0.7) end self.usurer_count = 0 @@ -348,9 +349,9 @@ end function Seeker:draw() graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x) if self.boss then - graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 4, 4, self.hfx.hit.f and fg[0] or self.color) + graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 4, 4, self.hfx.hit.f and fg[0] or (self.silenced and bg[10]) or self.color) else - graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, self.hfx.hit.f and fg[0] or self.color) + graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, self.hfx.hit.f and fg[0] or (self.silenced and bg[10]) or self.color) end graphics.pop() @@ -384,7 +385,7 @@ function Seeker:on_collision_enter(other, contact) if main.current.player.heavy_impact then if self.being_pushed then - self:hit(self.push_force) + self:hit(self.push_force, nil, nil, true) end end @@ -413,8 +414,8 @@ function Seeker:on_collision_enter(other, contact) elseif table.any(main.current.enemies, function(v) return other:is(v) end) then if self.being_pushed and math.length(self:get_velocity()) > 60 then - other:hit(math.floor(self.push_force/4)) - self:hit(math.floor(self.push_force/2)) + other:hit(math.floor(self.push_force/4), nil, nil, true) + self:hit(math.floor(self.push_force/2), nil, nil, true) other:push(math.floor(self.push_force/2), other:angle_to_object(self)) HitCircle{group = main.current.effects, x = x, y = y, rs = 6, color = fg[0], duration = 0.1} for i = 1, 2 do HitParticle{group = main.current.effects, x = x, y = y, color = self.color} end @@ -431,26 +432,79 @@ function Seeker:on_collision_enter(other, contact) elseif other:is(Turret) then self.headbutting = false _G[random:table{'player_hit1', 'player_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.35} - self:hit(0) + self:hit(0, nil, nil, true) self:push(random:float(2.5, 7), other:angle_to_object(self)) end end -function Seeker:hit(damage, projectile, dot) +function Seeker:hit(damage, projectile, dot, from_enemy) 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 = math.max(self:calculate_damage(damage)*(self.stun_dmg_m or 1), 0) - if self.vulnerable then actual_damage = actual_damage*1.2 end + + local crit = 1 + if main.current.player.critical_strike and not dot and not from_enemy then + if random:bool((main.current.player.critical_strike == 1 and 5) or (main.current.player.critical_strike == 2 and 10) or (main.current.player.critical_strike == 3 and 15)) then + crit = 2 + camera:shake(2.5, 0.25) + rogue_crit1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + rogue_crit2:play{pitch = random:float(0.95, 1.05), volume = 0.15} + for i = 1, 2 do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color, v = random:float(100, 400)} end + HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 12, color = fg[0], duration = 0.3}:scale_down():change_color(0.5, self.color) + end + end + + if main.current.player.kinetic_strike and not dot and not from_enemy then + if random:bool((main.current.player.kinetic_strike == 1 and 10) or (main.current.player.kinetic_strike == 2 and 20) or (main.current.player.kinetic_strike == 3 and 30)) then + local units = main.current.player:get_all_units() + local cx, cy = 0, 0 + for _, unit in ipairs(units) do + cx = cx + unit.x + cy = cy + unit.y + end + cx = cx/#units + cy = cy/#units + self:push(random:float(30, 60), projectile and projectile.r or self:angle_from_point(cx, cy)) + end + end + + if main.current.player.stunning_strike and not dot and not from_enemy then + if random:bool((main.current.player.stunning_strike == 1 and 8) or (main.current.player.stunning_strike == 2 and 16) or (main.current.player.stunning_strike == 3 and 24)) then + self:slow(0.1, 2) + self.barbarian_stunned = true + self.t:after(2, function() self.barbarian_stunned = false end) + end + end + + if main.current.player.silencing_strike and not dot and not from_enemy then + if random:bool((main.current.player.silencing_strike == 1 and 8) or (main.current.player.silencing_strike == 2 and 16) or (main.current.player.silencing_strike == 3 and 24)) then + self.silenced = true + self.t:after(4, function() self.silenced = false end) + end + end + + local actual_damage = math.max(self:calculate_damage(damage)*(self.stun_dmg_m or 1)*crit, 0) + if self.vulnerable then actual_damage = actual_damage*self.vulnerable end self.hp = self.hp - actual_damage if self.hp > self.max_hp then self.hp = self.max_hp end main.current.damage_dealt = main.current.damage_dealt + actual_damage + if main.current.player.noxious_strike and not dot and not from_enemy then + if random:bool((main.current.player.noxious_strike == 1 and 8) or (main.current.player.noxious_strike == 2 and 16) or (main.current.player.noxious_strike == 3 and 24)) then + self:apply_dot(0.6*actual_damage*(main.current.player.dot_dmg_m or 1)*(main.current.chronomancer_dot or 1), 3) + end + end + + if main.current.player.burning_strike and not dot and not from_enemy then + if random:bool(15) then + self:apply_dot(0.6*actual_damage*(main.current.player.dot_dmg_m or 1)*(main.current.chronomancer_dot or 1), 3, red[0]) + end + end + if dot then self.seeping_def_m = (main.current.player.seeping == 1 and 0.85) or (main.current.player.seeping == 2 and 0.75) or (main.current.player.seeping == 3 and 0.65) or 1 self.t:after(1, function() @@ -472,6 +526,12 @@ function Seeker:hit(damage, projectile, dot) end) end + if self.boss and main.current.player.culling_strike then + if self.hp <= self.max_hp*((main.current.player.culling_strike == 1 and 0.1) or (main.current.player.culling_strike == 2 and 0.2) or (main.current.player.culling_strike == 3 and 0.3)) then + self.hp = 0 + 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 @@ -578,6 +638,54 @@ function Seeker:hit(damage, projectile, dot) dmg = self.bane_ref.area_dmg_m*self.bane_ref.dmg*(self.bane_ref.dot_dmg_m or 1), void_rift = true, duration = 1} end) end + + if main.current.player.ceremonial_dagger and not from_enemy then + trigger:after(0.01, function() + if tostring(self.x) == tostring(0/0) or tostring(self.y) == tostring(0/0) then return end + _G[random:table{'scout1', 'scout2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.35} + HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6} + local r = random:float(0, 2*math.pi) + local t = {group = main.current.main, x = self.x + 8*math.cos(r), y = self.y + 8*math.sin(r), v = 250, r = r, color = red[0], dmg = self.max_hp, + homing = true, character = 'jester', parent = main.current.player} + Projectile(table.merge(t, mods or {})) + end) + end + + if main.current.player.homing_barrage and not from_enemy then + trigger:after(0.01, function() + if random:bool((main.current.player.homing_barrage == 1 and 8) or (main.current.player.homing_barrage == 2 and 16) or (main.current.player.homing_barrage == 3 and 24)) then + local target = main.current.player:get_closest_object_in_shape(Circle(main.current.player.x, main.current.player.y, 128), main.current.enemies) + main.current.player:barrage(target and main.current.player:angle_to_object(target) or main.current.player.r, 4, nil, nil, nil, true) + end + end) + end + + if main.current.player.infesting_strike and not from_enemy then + trigger:after(0.01, function() + if random:bool((main.current.player.infesting_strike == 1 and 10) or (main.current.player.infesting_strike == 2 and 20) or (main.current.player.infesting_strike == 3 and 30)) then + critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + 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 = 5, dmg = main.current.player.dmg, parent = main.current.player} + end + end + end) + end + + if main.current.player.lucky_strike and not from_enemy then + if random:bool(8) then + trigger:after(0.01, function() + Gold{group = main.current.main, x = self.x, y = self.y} + end) + end + end + + if main.current.player.healing_strike and not from_enemy then + if random:bool(8) then + trigger:after(0.01, function() + HealingOrb{group = main.current.main, x = self.x, y = self.y} + end) + end + end end end @@ -668,13 +776,13 @@ function Seeker:curse(curse, duration, arg1, arg2, arg3) end -function Seeker:apply_dot(dmg, duration) +function Seeker:apply_dot(dmg, duration, color) self.t:every(0.25, function() hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2} self:hit(dmg/4, nil, true) HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = fg[0], duration = 0.1} for i = 1, 1 do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color} end - for i = 1, 1 do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = purple[0]} end + for i = 1, 1 do HitParticle{group = main.current.effects, x = self.x, y = self.y, color = color or purple[0]} end end, math.floor(duration/0.2)) end @@ -833,7 +941,7 @@ end function EnemyCritter:on_trigger_enter(other, contact) if other:is(Player) then self:die(self.x, self.y, nil, random:int(2, 3)) - other:hit(self.dmg) + other:hit(self.dmg, nil, nil, true) end end @@ -978,7 +1086,7 @@ function EnemyProjectile:on_trigger_enter(other, contact) elseif other:is(Seeker) or other:is(EnemyCritter) then if self.source == 'shooter' then self:die(self.x, self.y, nil, random:int(2, 3)) - other:hit(0.5*self.dmg) + other:hit(0.5*self.dmg, nil, nil, true) end end end diff --git a/main.lua b/main.lua index 74e6d8e..2ae2448 100644 --- a/main.lua +++ b/main.lua @@ -21,6 +21,8 @@ function init() input:bind('enter', {'space', 'return', 'fleft', 'fdown', 'fright'}) local s = {tags = {sfx}} + artificer1 = Sound('458586__inspectorj__ui-mechanical-notification-01-fx.ogg', s) + explosion1 = Sound('Explosion Grenade_04.ogg', s) mine1 = Sound('Weapon Swap 2.ogg', s) level_up1 = Sound('Buff 4.ogg', s) unlock1 = Sound('Unlock 3.ogg', s) @@ -213,7 +215,26 @@ function init() psychosense = Image('psychosense') rearm = Image('rearm') taunt = Image('taunt') - summon_instability = Image('summon_instability') + construct_instability = Image('construct_instability') + intimidation = Image('intimidation') + vulnerability = Image('vulnerability') + temporal_chains = Image('temporal_chains') + ceremonial_dagger = Image('ceremonial_dagger') + homing_barrage = Image('homing_barrage') + critical_strike = Image('critical_strike') + noxious_strike = Image('noxious_strike') + infesting_strike = Image('infesting_strike') + kinetic_strike = Image('kinetic_strike') + burning_strike = Image('burning_strike') + lucky_strike = Image('lucky_strike') + healing_strike = Image('healing_strike') + stunning_strike = Image('stunning_strike') + silencing_strike = Image('silencing_strike') + warping_shots = Image('warping_shots') + culling_strike = Image('culling_strike') + lightning_strike = Image('lightning_strike') + psycholeak = Image('psycholeak') + divine_blessing = Image('divine_blessing') class_colors = { ['warrior'] = yellow[0], @@ -265,12 +286,14 @@ function init() ['blade'] = 'Blade', ['elementor'] = 'Elementor', ['saboteur'] = 'Saboteur', + ['bomber'] = 'Bomber', ['stormweaver'] = 'Stormweaver', ['sage'] = 'Sage', ['squire'] = 'Squire', ['cannoneer'] = 'Cannoneer', ['dual_gunner'] = 'Dual Gunner', ['hunter'] = 'Hunter', + ['sentry'] = 'Sentry', ['chronomancer'] = 'Chronomancer', ['spellblade'] = 'Spellblade', ['psykeeper'] = 'Psykeeper', @@ -298,6 +321,7 @@ function init() ['flagellant'] = 'Flagellant', ['arcanist'] = 'Arcanist', ['illusionist'] = 'Illusionist', + ['artificer'] = 'Artificer', ['witch'] = 'Witch', ['silencer'] = 'Silencer', ['vulcanist'] = 'Vulcanist', @@ -322,12 +346,14 @@ function init() ['blade'] = yellow[0], ['elementor'] = blue[0], ['saboteur'] = orange[0], + ['bomber'] = orange[0], ['stormweaver'] = blue[0], ['sage'] = purple[0], ['squire'] = yellow[0], ['cannoneer'] = orange[0], ['dual_gunner'] = green[0], ['hunter'] = green[0], + ['sentry'] = green[0], ['chronomancer'] = blue[0], ['spellblade'] = blue[0], ['psykeeper'] = fg[0], @@ -355,6 +381,7 @@ function init() ['flagellant'] = fg[0], ['arcanist'] = blue2[0], ['illusionist'] = blue2[0], + ['artificer'] = blue2[0], ['witch'] = purple[0], ['silencer'] = blue2[0], ['vulcanist'] = red[0], @@ -379,12 +406,14 @@ function init() ['blade'] = 'yellow', ['elementor'] = 'blue', ['saboteur'] = 'orange', + ['bomber'] = 'orange', ['stormweaver'] = 'blue', ['sage'] = 'purple', ['squire'] = 'yellow', ['cannoneer'] = 'orange', ['dual_gunner'] = 'green', ['hunter'] = 'green', + ['sentry'] = 'green', ['chronomancer'] = 'blue', ['spellblade'] = 'blue', ['psykeeper'] = 'fg', @@ -412,6 +441,7 @@ function init() ['flagellant'] = 'fg', ['arcanist'] = 'blue2', ['illusionist'] = 'blue2', + ['artificer'] = 'blue2', ['witch'] = 'purple', ['silencer'] = 'blue2', ['vulcanist'] = 'red', @@ -435,13 +465,15 @@ function init() ['outlaw'] = {'warrior', 'rogue'}, ['blade'] = {'warrior', 'nuker'}, ['elementor'] = {'mage', 'nuker'}, - ['saboteur'] = {'rogue', 'conjurer', 'nuker'}, + -- ['saboteur'] = {'rogue', 'conjurer', 'nuker'}, + ['bomber'] = {'nuker', 'conjurer'}, ['stormweaver'] = {'enchanter'}, ['sage'] = {'nuker', 'forcer'}, ['squire'] = {'warrior', 'enchanter'}, ['cannoneer'] = {'ranger', 'nuker'}, ['dual_gunner'] = {'ranger', 'rogue'}, - ['hunter'] = {'ranger', 'conjurer', 'forcer'}, + -- ['hunter'] = {'ranger', 'conjurer', 'forcer'}, + ['sentry'] = {'ranger', 'conjurer'}, ['chronomancer'] = {'mage', 'enchanter'}, ['spellblade'] = {'mage', 'rogue'}, ['psykeeper'] = {'healer', 'psyker'}, @@ -468,7 +500,8 @@ function init() ['infestor'] = {'curser', 'swarmer'}, ['flagellant'] = {'psyker', 'enchanter'}, ['arcanist'] = {'sorcerer'}, - ['illusionist'] = {'sorcerer', 'conjurer'}, + -- ['illusionist'] = {'sorcerer', 'conjurer'}, + ['artificer'] = {'sorcerer', 'conjurer'}, ['witch'] = {'sorcerer', 'voider'}, ['silencer'] = {'sorcerer', 'curser'}, ['vulcanist'] = {'sorcerer', 'nuker'}, @@ -492,17 +525,19 @@ function init() ['outlaw'] = '[yellow]Warrior, [red]Rogue', ['blade'] = '[yellow]Warrior, [red]Nuker', ['elementor'] = '[blue]Mage, [red]Nuker', - ['saboteur'] = '[red]Rogue, [orange]Conjurer, [red]Nuker', + -- ['saboteur'] = '[red]Rogue, [orange]Conjurer, [red]Nuker', + ['bomber'] = '[red]Nuker, [orange]Builder', ['stormweaver'] = '[blue]Enchanter', ['sage'] = '[red]Nuker, [yellow]Forcer', ['squire'] = '[yellow]Warrior, [blue]Enchanter', ['cannoneer'] = '[green]Ranger, [red]Nuker', ['dual_gunner'] = '[green]Ranger, [red]Rogue', - ['hunter'] = '[green]Ranger, [orange]Conjurer, [yellow]Forcer', + -- ['hunter'] = '[green]Ranger, [orange]Conjurer, [yellow]Forcer', + ['sentry'] = '[green]Ranger, [orange]Builder', ['chronomancer'] = '[blue]Mage, Enchanter', ['spellblade'] = '[blue]Mage, [red]Rogue', ['psykeeper'] = '[green]Healer, [fg]Psyker', - ['engineer'] = '[orange]Conjurer', + ['engineer'] = '[orange]Builder', ['plague_doctor'] = '[red]Nuker, [purple]Voider', ['barbarian'] = '[purple]Curser, [yellow]Warrior', ['juggernaut'] = '[yellow]Forcer, Warrior', @@ -515,7 +550,7 @@ function init() ['jester'] = '[purple]Curser, [red]Rogue', ['assassin'] = '[red]Rogue, [purple]Voider', ['host'] = '[orange]Swarmer', - ['carver'] = '[orange]Conjurer, [green]Healer', + ['carver'] = '[orange]Builder, [green]Healer', ['bane'] = '[purple]Curser, Voider', ['psykino'] = '[blue]Mage, [fg]Psyker, [yellow]Forcer', ['barrager'] = '[green]Ranger, [yellow]Forcer', @@ -525,7 +560,8 @@ function init() ['infestor'] = '[purple]Curser, [orange]Swarmer', ['flagellant'] = '[fg]Psyker, [blue]Enchanter', ['arcanist'] = '[blue2]Sorcerer', - ['illusionist'] = '[blue2]Sorcerer, [orange]Conjurer', + -- ['illusionist'] = '[blue2]Sorcerer, [orange]Conjurer', + ['artificer'] = '[blue2]Sorcerer, [orange]Builder', ['witch'] = '[blue2]Sorcerer, [purple]Voider', ['silencer'] = '[blue2]Sorcerer, [purple]Curser', ['vulcanist'] = '[blue2]Sorcerer, [red]Nuker', @@ -566,12 +602,14 @@ function init() ['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[fg] saboteurs to seek targets and deal [yellow]' .. get_character_stat('saboteur', lvl, 'dmg') .. ' AoE[fg] damage' end, + ['bomber'] = function(lvl) return '[fg]plants a bomb, when it explodes it deals [yellow]' .. 2*get_character_stat('bomber', lvl, 'dmg') .. ' AoE[fg] damage' end, ['stormweaver'] = function(lvl) return '[fg]infuses projectiles with chain lightning that deals [yellow]20%[fg] damage to [yellow]2[fg] enemies' end, ['sage'] = function(lvl) return '[fg]shoots a slow projectile that draws enemies in' end, ['squire'] = function(lvl) return '[yellow]+20%[fg] damage and defense to all allies' end, ['cannoneer'] = function(lvl) return '[fg]shoots a projectile that deals [yellow]' .. 2*get_character_stat('cannoneer', lvl, 'dmg') .. ' AoE[fg] damage' end, ['dual_gunner'] = function(lvl) return '[fg]shoots two parallel projectiles, each dealing [yellow]' .. get_character_stat('dual_gunner', lvl, 'dmg') .. '[fg] damage' 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, + ['sentry'] = function(lvl) return '[fg]spawns a rotating turret that shoots [yellow]4[fg] projectiles, each dealing [yellow]' .. get_character_stat('sentry', lvl, 'dmg') .. '[fg] damage' 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]creates [yellow]1[fg] healing orb every time the psykeeper takes [yellow]20%[fg] of its max HP in damage' end, @@ -589,7 +627,7 @@ function init() ['assassin'] = function(lvl) return '[fg]throws a piercing knife that deals [yellow]' .. get_character_stat('assassin', lvl, 'dmg') .. '[fg] damage + [yellow]' .. get_character_stat('assassin', lvl, 'dmg')/2 .. '[fg] damage per second' end, ['host'] = function(lvl) return '[fg]periodically spawn [yellow]1[fg] small critter' end, - ['carver'] = function(lvl) return '[fg]carves a statue that periodically heals [yellow]1[fg] unit for [yellow]20%[fg] max HP if in range' end, + ['carver'] = function(lvl) return '[fg]carves a statue that creates [yellow]1[fg] healing orb every [yellow]6[fg] seconds' end, ['bane'] = function(lvl) return '[fg]curses [yellow]6[fg] nearby enemies for [yellow]6[fg] seconds, they will create small void rifts on death' end, ['psykino'] = function(lvl) return '[fg]pulls enemies together for [yellow]2[fg] seconds' end, ['barrager'] = function(lvl) return '[fg]shoots a barrage of [yellow]3[fg] arrows, each dealing [yellow]' .. get_character_stat('barrager', lvl, 'dmg') .. '[fg] damage and pushing enemies' end, @@ -600,6 +638,7 @@ function init() ['flagellant'] = function(lvl) return '[fg]deals [yellow]' .. 2*get_character_stat('flagellant', lvl, 'dmg') .. '[fg] damage to self and grants [yellow]+4%[fg] damage to all allies per cast' end, ['arcanist'] = function(lvl) return '[fg]launches a slow moving orb that launches projectiles, each dealing [yellow]' .. get_character_stat('arcanist', lvl, 'dmg') .. '[fg] damage' end, ['illusionist'] = function(lvl) return '[fg]launches a projectile that deals [yellow]' .. get_character_stat('illusionist', lvl, 'dmg') .. '[fg] damage and creates copies that do the same' end, + ['artificer'] = function(lvl) return '[fg]spawns an automaton that shoots a projectile that deals [yellow]' .. get_character_stat('artificer', lvl, 'dmg') .. '[fg] damage' end, ['witch'] = function(lvl) return '[fg]creates an area that ricochets and deals [yellow]' .. get_character_stat('witch', lvl, 'dmg') .. '[fg] damage per second' end, ['silencer'] = function(lvl) return '[fg]curses [yellow]5[fg] nearby enemies for [yellow]6[fg] seconds, preventing them from using special attacks' end, ['vulcanist'] = function(lvl) return '[fg]creates a volcano that explodes the nearby area [yellow]4[fg] times, dealing [yellow]' .. get_character_stat('vulcanist', lvl, 'dmg') .. ' AoE [fg]damage' end, @@ -619,17 +658,19 @@ function init() ['magician'] = '[blue]Ethereal Form', ['archer'] = '[green]Bounce Shot', ['scout'] = '[red]Dagger Resonance', - ['cleric'] = '[green]Mass Heal ', + ['cleric'] = '[green]Mass Heal', ['outlaw'] = '[red]Flying Daggers', ['blade'] = '[yellow]Blade Resonance', ['elementor'] = '[blue]Windfield', ['saboteur'] = '[orange]Demoman', + ['bomber'] = '[orange]Demoman', ['stormweaver'] = '[blue]Wide Lightning', ['sage'] = '[purple]Dimension Compression', ['squire'] = '[yellow]Shiny Gear', ['cannoneer'] = '[orange]Cannon Barrage', ['dual_gunner'] = '[green]Gun Kata', ['hunter'] = '[green]Feral Pack', + ['sentry'] = '[green]Sentry Barrage', ['chronomancer'] = '[blue]Quicken', ['spellblade'] = '[blue]Spiralism', ['psykeeper'] = '[fg]Crucio', @@ -657,6 +698,7 @@ function init() ['flagellant'] = '[red]Zealotry', ['arcanist'] = '[blue2]Arcane Orb', ['illusionist'] = '[blue2]Mirror Image', + ['artificer'] = '[blue2]Spell Formula Efficiency', ['witch'] = '[purple]Death Pool', ['silencer'] = '[blue2]Arcane Curse', ['vulcanist'] = '[red]Lava Burst', @@ -681,12 +723,14 @@ function init() ['blade'] = '[light_bg]Blade Resonance', ['elementor'] = '[light_bg]Windfield', ['saboteur'] = '[light_bg]Demoman', + ['bomber'] = '[light_bg]Demoman', ['stormweaver'] = '[light_bg]Wide Lightning', ['sage'] = '[light_bg]Dimension Compression', ['squire'] = '[light_bg]Shiny Gear', ['cannoneer'] = '[light_bg]Cannon Barrage', ['dual_gunner'] = '[light_bg]Gun Kata', ['hunter'] = '[light_bg]Feral Pack', + ['sentry'] = '[light_bg]Sentry Barrage', ['chronomancer'] = '[light_bg]Quicken', ['spellblade'] = '[light_bg]Spiralism', ['psykeeper'] = '[light_bg]Crucio', @@ -714,6 +758,7 @@ function init() ['flagellant'] = '[light_bg]Zealotry', ['arcanist'] = '[light_bg]Arcane Orb', ['illusionist'] = '[light_bg]Mirror Image', + ['artificer'] = '[light_bg]Spell Formula Efficiency', ['witch'] = '[light_bg]Death Pool', ['silencer'] = '[light_bg]Arcane Curse', ['vulcanist'] = '[light_bg]Lava Burst', @@ -738,12 +783,14 @@ function init() ['blade'] = function() return '[fg]deal additional [yellow]' .. math.round(get_character_stat('blade', 3, 'dmg')/3, 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, + ['bomber'] = function() return '[yellow]+100%[fg] bomb area and damage' end, ['stormweaver'] = function() return "[fg]chain lightning's trigger area of effect and number of units hit is [yellow]doubled" end, ['sage'] = function() return '[fg]when the projectile expires deal [yellow]' .. 3*get_character_stat('sage', 3, 'dmg') .. '[fg] damage to all enemies under its influence' end, ['squire'] = function() return '[yellow]+30%[fg] damage, attack speed, movement speed and defense to all allies' end, ['cannoneer'] = function() return '[fg]showers the hit area in [yellow]7[fg] additional cannon shots that deal [yellow]' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. '[fg] AoE damage' end, ['dual_gunner'] = function() return '[fg]every 5th attack shoot in rapid succession for [yellow]2[fg] seconds' end, ['hunter'] = function() return '[fg]summons [yellow]3[fg] pets and the pets ricochet off walls once' end, + ['sentry'] = function() return '[yellow]+50%[fg] sentry attack speed and the projectiles ricochet [yellow]twice[fg]' end, ['chronomancer'] = function() return '[fg]enemies take damage over time [yellow]50%[fg] faster' end, ['spellblade'] = function() return '[fg]faster projectile speed and tighter turns' end, ['psykeeper'] = function() return '[fg]deal [yellow]double[fg] the damage taken by the psykeeper to all enemies' end, @@ -760,7 +807,7 @@ function init() ['jester'] = function() return '[fg]all knives seek enemies and pierce [yellow]2[fg] times' end, ['assassin'] = function() return '[fg]poison inflicted from crits deals [yellow]8x[fg] damage' end, ['host'] = function() return '[fg][yellow]+100%[fg] critter spawn rate and spawn [yellow]2[fg] critters instead' end, - ['carver'] = function() return '[fg]carves a tree that heals [yellow]twice[fg] as fast, in a bigger area, and heals [yellow]2[fg] units instead' end, + ['carver'] = function() return '[fg]carves a tree that creates healing orbs [yellow]twice[fg] as fast' end, ['bane'] = function() return "[yellow]100%[fg] increased area for bane's void rifts" end, ['psykino'] = function() return '[fg]enemies take [yellow]' .. 4*get_character_stat('psykino', 3, 'dmg') .. '[fg] damage and are pushed away when the area expires' end, ['barrager'] = function() return '[fg]every 3rd attack the barrage shoots [yellow]15[fg] projectiles and they push harder' end, @@ -771,6 +818,7 @@ function init() ['flagellant'] = function() return '[yellow]2X[fg] flagellant max HP and grants [yellow]+12%[fg] damage to all allies per cast instead' end, ['arcanist'] = function() return '[yellow]+50%[fg] attack speed for the orb and [yellow]2[fg] projectiles are released per cast' end, ['illusionist'] = function() return '[yellow]doubles[fg] the number of copies created and they release [yellow]12[fg] projectiles on death' end, + ['artificer'] = function() return '[fg]automatons shoot and move 50% faster and release [yellow]12[fg] projectiles on death' end, ['witch'] = function() return '[fg]the area releases projectiles, each dealing [yellow]' .. get_character_stat('witch', 3, 'dmg') .. '[fg] damage and chaining once' end, ['silencer'] = function() return '[fg]the curse also deals [yellow]' .. get_character_stat('silencer', 3, 'dmg') .. '[fg] damage per second' end, ['vulcanist'] = function() return '[fg]the number and speed of explosions is [yellow]doubled[fg]' end, @@ -795,12 +843,14 @@ function init() ['blade'] = function() return '[light_bg]deal additional ' .. math.round(get_character_stat('blade', 3, 'dmg')/2, 2) .. ' damage per enemy hit' end, ['elementor'] = function() return '[light_bg]slows enemies by 60% for 6 seconds on hit' end, ['saboteur'] = function() return '[light_bg]the explosion has 50% chance to crit, increasing in size and dealing 2x damage' end, + ['bomber'] = function() return '[light_bg]+100% bomb area and damage' end, ['stormweaver'] = function() return "[light_bg]chain lightning's trigger area of effect and number of units hit is doubled" end, ['sage'] = function() return '[light_bg]when the projectile expires deal ' .. 3*get_character_stat('sage', 3, 'dmg') .. ' damage to all enemies under its influence' end, ['squire'] = function() return '[light_bg]+30% damage, attack speed, movement speed and defense to all allies' end, ['cannoneer'] = function() return '[light_bg]showers the hit area in 7 additional cannon shots that deal ' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. ' AoE damage' end, ['dual_gunner'] = function() return '[light_bg]every 5th attack shoot in rapid succession for 2 seconds' end, ['hunter'] = function() return '[light_bg]summons 3 pets and the pets ricochet off walls once' end, + ['sentry'] = function() return '[light_bg]+50% attack speed and the projectiles ricochet twice' end, ['chronomancer'] = function() return '[light_bg]enemies take damage over time 50% faster' end, ['spellblade'] = function() return '[light_bg]faster projectile speed and tighter turns' end, ['psykeeper'] = function() return '[light_bg]deal double the damage taken by the psykeeper to all enemies' end, @@ -828,6 +878,7 @@ function init() ['flagellant'] = function() return '[light_bg]2X flagellant max HP and grants +12% damage to all allies per cast instead' end, ['arcanist'] = function() return '[light_bg]+50% attack speed for the orb and 2 projectiles are released per cast' end, ['illusionist'] = function() return '[light_bg]doubles the number of copies created and they release 12 projectiles on death' end, + ['artificer'] = function() return '[light_bg]automatons shoot and move 50% faster and release 12 projectiles on death' end, ['witch'] = function() return '[light_bg]the area periodically releases projectiles, each dealing ' .. get_character_stat('witch', 3, 'dmg') .. ' damage and chaining once' end, ['silencer'] = function() return '[light_bg]the curse also deals ' .. get_character_stat('silencer', 3, 'dmg') .. ' damage per second' end, ['vulcanist'] = function() return '[light_bg]the number and speed of explosions is doubled' end, @@ -852,12 +903,14 @@ function init() ['blade'] = function(lvl) return get_character_stat_string('blade', lvl) end, ['elementor'] = function(lvl) return get_character_stat_string('elementor', lvl) end, ['saboteur'] = function(lvl) return get_character_stat_string('saboteur', lvl) end, + ['bomber'] = function(lvl) return get_character_stat_string('bomber', lvl) end, ['stormweaver'] = function(lvl) return get_character_stat_string('stormweaver', lvl) end, ['sage'] = function(lvl) return get_character_stat_string('sage', lvl) end, ['squire'] = function(lvl) return get_character_stat_string('squire', lvl) end, ['cannoneer'] = function(lvl) return get_character_stat_string('cannoneer', lvl) end, ['dual_gunner'] = function(lvl) return get_character_stat_string('dual_gunner', lvl) end, ['hunter'] = function(lvl) return get_character_stat_string('hunter', lvl) end, + ['sentry'] = function(lvl) return get_character_stat_string('sentry', lvl) end, ['chronomancer'] = function(lvl) return get_character_stat_string('chronomancer', lvl) end, ['spellblade'] = function(lvl) return get_character_stat_string('spellblade', lvl) end, ['psykeeper'] = function(lvl) return get_character_stat_string('psykeeper', lvl) end, @@ -885,6 +938,7 @@ function init() ['flagellant'] = function(lvl) return get_character_stat_string('flagellant', lvl) end, ['arcanist'] = function(lvl) return get_character_stat_string('arcanist', lvl) end, ['illusionist'] = function(lvl) return get_character_stat_string('illusionist', lvl) end, + ['artificer'] = function(lvl) return get_character_stat_string('artificer', lvl) end, ['witch'] = function(lvl) return get_character_stat_string('witch', lvl) end, ['silencer'] = function(lvl) return get_character_stat_string('silencer', lvl) end, ['vulcanist'] = function(lvl) return get_character_stat_string('vulcanist', lvl) end, @@ -943,7 +997,7 @@ function init() ['healer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[light_bg]/[' .. ylb2(lvl) .. ']4 [fg]- [' .. ylb1(lvl) .. ']+8%[light_bg]/[' .. ylb2(lvl) .. ']+16% [fg] chance for enemies to drop healing orbs on death' end, ['enchanter'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[light_bg]/[' .. ylb2(lvl) .. ']4 [fg]- [' .. ylb1(lvl) .. ']+15%[light_bg]/[' .. ylb2(lvl) .. ']+25% [fg]damage to all allies' end, ['nuker'] = function(lvl) return '[' .. ylb1(lvl) .. ']3[light_bg]/[' .. ylb2(lvl) .. ']6 [fg]- [' .. ylb1(lvl) .. ']+15%[light_bg]/[' .. ylb2(lvl) .. ']+25% [fg]area damage and size to allied nukers' end, - ['conjurer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[light_bg]/[' .. ylb2(lvl) .. ']4 [fg]- [' .. ylb1(lvl) .. ']+25%[light_bg]/[' .. ylb2(lvl) .. ']+50% [fg]summon damage and duration' end, + ['conjurer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[light_bg]/[' .. ylb2(lvl) .. ']4 [fg]- [' .. ylb1(lvl) .. ']+25%[light_bg]/[' .. ylb2(lvl) .. ']+50% [fg]construct damage and duration' end, ['psyker'] = function(lvl) return '[fg]create a piercing, damaging orb around each psyker' end, ['curser'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[light_bg]/[' .. ylb2(lvl) .. ']4 [fg]- [' .. ylb1(lvl) .. ']+1[light_bg]/[' .. ylb2(lvl) .. ']+3 [fg]max curse targets to allied cursers' end, ['forcer'] = function(lvl) return '[' .. ylb1(lvl) .. ']2[light_bg]/[' .. ylb2(lvl) .. ']4 [fg]- [' .. ylb1(lvl) .. ']+25%[light_bg]/[' .. ylb2(lvl) .. ']+50% [fg]knockback force to all allies' end, @@ -959,8 +1013,8 @@ function init() tier_to_characters = { [1] = {'vagrant', 'swordsman', 'magician', 'archer', 'scout', 'cleric', 'arcanist', 'merchant'}, - [2] = {'wizard', 'saboteur', 'sage', 'squire', 'dual_gunner', 'hunter', 'chronomancer', 'barbarian', 'cryomancer', 'beastmaster', 'jester', 'carver', 'psychic', 'witch', 'silencer', 'outlaw', 'miner'}, - [3] = {'elementor', 'stormweaver', 'spellblade', 'psykeeper', 'engineer', 'juggernaut', 'pyromancer', 'host', 'assassin', 'bane', 'barrager', 'infestor', 'flagellant', 'illusionist', 'usurer', 'gambler'}, + [2] = {'wizard', 'bomber', 'sage', 'squire', 'dual_gunner', 'sentry', 'chronomancer', 'barbarian', 'cryomancer', 'beastmaster', 'jester', 'carver', 'psychic', 'witch', 'silencer', 'outlaw', 'miner'}, + [3] = {'elementor', 'stormweaver', 'spellblade', 'psykeeper', 'engineer', 'juggernaut', 'pyromancer', 'host', 'assassin', 'bane', 'barrager', 'infestor', 'flagellant', 'artificer', 'usurer', 'gambler'}, [4] = {'priest', 'highlander', 'psykino', 'fairy', 'blade', 'plague_doctor', 'cannoneer', 'vulcanist', 'warden', 'corruptor', 'thief'}, } @@ -977,14 +1031,16 @@ function init() ['outlaw'] = 2, ['blade'] = 4, ['elementor'] = 3, - ['saboteur'] = 2, + -- ['saboteur'] = 2, + ['bomber'] = 2, ['wizard'] = 2, ['stormweaver'] = 3, ['sage'] = 2, ['squire'] = 2, ['cannoneer'] = 4, ['dual_gunner'] = 2, - ['hunter'] = 2, + -- ['hunter'] = 2, + ['sentry'] = 2, ['chronomancer'] = 2, ['spellblade'] = 3, ['psykeeper'] = 3, @@ -1011,7 +1067,8 @@ function init() ['infestor'] = 3, ['flagellant'] = 3, ['arcanist'] = 1, - ['illusionist'] = 3, + -- ['illusionist'] = 3, + ['artificer'] = 3, ['witch'] = 2, ['silencer'] = 2, ['vulcanist'] = 4, @@ -1026,7 +1083,7 @@ function init() launches_projectiles = function(character) local classes = {'vagrant', 'archer', 'scout', 'outlaw', 'blade', 'wizard', 'cannoneer', 'dual_gunner', 'hunter', 'spellblade', 'engineer', 'corruptor', 'beastmaster', 'jester', 'assassin', 'barrager', - 'arcanist', 'illusionist', 'miner', 'thief'} + 'arcanist', 'illusionist', 'artificer', 'miner', 'thief', 'sentry'} return table.any(classes, function(v) return v == character end) end @@ -1202,7 +1259,26 @@ function init() ['psychosense'] = 'Psychosense', ['rearm'] = 'Rearm', ['taunt'] = 'Taunt', - ['summon_instability'] = 'Summon Instability', + ['construct_instability'] = 'Construct Instability', + ['intimidation'] = 'Intimidation', + ['vulnerability'] = 'Vulnerability', + ['temporal_chains'] = 'Temporal Chains', + ['ceremonial_dagger'] = 'Ceremonial Dagger', + ['homing_barrage'] = 'Homing Barrage', + ['critical_strike'] = 'Critical Strike', + ['noxious_strike'] = 'Noxious Strike', + ['infesting_strike'] = 'Infesting Strike', + ['kinetic_strike'] = 'Kinetic Strike', + ['burning_strike'] = 'Burning Strike', + ['lucky_strike'] = 'Lucky Strike', + ['healing_strike'] = 'Healing Strike', + ['stunning_strike'] = 'Stunning Strike', + ['silencing_strike'] = 'Silencing Strike', + ['warping_shots'] = 'Warping Shots', + ['culling_strike'] = 'Culling Strike', + ['lightning_strike'] = 'Lightning Strike', + ['psycholeak'] = 'Psycholeak', + ['divine_blessing'] = 'Divine Blessing', } passive_descriptions = { @@ -1266,9 +1342,28 @@ function init() ['orbitism'] = '[yellow]+33/66/99%[fg] orb movement speed', ['psyker_orbs'] = '[yellow]+1/2/3[fg] psyker orbs', ['psychosense'] = '[yellow]+33/66/99%[fg] orb range', - ['rearm'] = '[fg]summons repeat their attacks once', - ['taunt'] = '[yellow]10/20/30%[fg] chance for summons to taunt nearby enemies on attack', - ['summon_instability'] = '[fg]summons explode when disappearing, dealing [yellow]100/150/200%[fg] damage', + ['rearm'] = '[fg]constructs repeat their attacks once', + ['taunt'] = '[yellow]10/20/30%[fg] chance for constructs to taunt nearby enemies on attack', + ['construct_instability'] = '[fg]constructs explode when disappearing, dealing [yellow]100/150/200%[fg] damage', + ['intimidation'] = '[fg]enemies spawn with [yellow]-10/20/30%[fg] max HP', + ['vulnerability'] = '[fg]enemies take [yellow]+10/20/30%[fg] damage', + ['temporal_chains'] = '[fg]enemies are [yellow]10/20/30%[fg] slower', + ['ceremonial_dagger'] = '[fg]killing an enemy fires a homing dagger', + ['homing_barrage'] = '[yellow]8/16/24%[fg] chance to release a homing barrage on enemy kill', + ['critical_strike'] = '[yellow]5/10/15%[fg] chance for attacks to critically strike, dealing [yellow]2x[fg] damage', + ['noxious_strike'] = '[yellow]8/16/24%[fg] chance for attacks to poison, dealing [yellow]20%[fg] dps for [yellow]3[fg] seconds', + ['infesting_strike'] = '[yellow]10/20/30%[fg] chance for attacks to spawn [yellow]2[fg] critters on kill', + ['kinetic_strike'] = '[yellow]10/20/30%[fg] chance for attacks to push enemies away with high force', + ['burning_strike'] = '[yellow]15%[fg] chance for attacks to burn, dealing [yellow]20%[fg] dps for [yellow]3[fg] seconds', + ['lucky_strike'] = '[yellow]8%[fg] chance for attacks to cause enemies to drop gold on death', + ['healing_strike'] = '[yellow]8%[fg] chance for attacks to spawn a healing orb on kill', + ['stunning_strike'] = '[yellow]8/16/24%[fg] chance for attacks to stun for [yellow]2[fg] seconds', + ['silencing_strike'] = '[yellow]8/16/24%[fg] chance for attacks to silence for [yellow]2[fg] seconds on hit', + ['warping_shots'] = 'projectiles ignore wall collisions and warp around the screen [yellow]1/2/3[fg] times', + ['culling_strike'] = '[fg]instantly kill elites below [yellow]10/20/30%[fg] max HP', + ['lightning_strike'] = '[yellow]5/10/15%[fg] chance for projectiles to create chain lightning, dealing [yellow]60/80/100%[fg] damage', + ['psycholeak'] = '[fg]position [yellow]1[fg] generates [yellow]1[fg] psyker orb every [yellow]10[fg] seconds', + ['divine_blessing'] = '[fg]generate [yellow]1[fg] healing orb every [yellow]8[fg] seconds', } local ts = function(lvl, a, b, c) return '[' .. ylb1(lvl) .. ']' .. tostring(a) .. '[light_bg]/[' .. ylb2(lvl) .. ']' .. tostring(b) .. '[light_bg]/[' .. ylb3(lvl) .. ']' .. tostring(c) .. '[fg]' end @@ -1333,9 +1428,28 @@ function init() ['orbitism'] = function(lvl) return ts(lvl, '+33%', '66%', '99%') .. ' orb movement speed' end, ['psyker_orbs'] = function(lvl) return ts(lvl, '+1', '2', '3') .. ' psyker orbs' end, ['psychosense'] = function(lvl) return ts(lvl, '+33%', '66%', '99%') .. ' orb range' end, - ['rearm'] = function(lvl) return '[fg]summons repeat their attacks once' end, - ['taunt'] = function(lvl) return ts(lvl, '10%', '20%', '30%') .. ' chance for summons to taunt nearby enemies on attack' end, - ['summon_instability'] = function(lvl) return '[fg]summons explode when disappearing, dealing [yellow]100/150/200%[fg] damage' end, + ['rearm'] = function(lvl) return '[fg]constructs repeat their attacks once' end, + ['taunt'] = function(lvl) return ts(lvl, '10%', '20%', '30%') .. ' chance for constructs to taunt nearby enemies on attack' end, + ['construct_instability'] = function(lvl) return '[fg]constructs explode when disappearing, dealing ' .. ts(lvl, '100', '150', '200%') .. ' damage' end, + ['intimidation'] = function(lvl) return '[fg]enemies spawn with ' .. ts(lvl, '-10', '20', '30%') .. ' max HP' end, + ['vulnerability'] = function(lvl) return '[fg]enemies take ' .. ts(lvl, '+10', '20', '30%').. ' damage' end, + ['temporal_chains'] = function(lvl) return '[fg]enemies are ' .. ts(lvl, '10', '20', '30%').. ' slower' end, + ['ceremonial_dagger'] = function(lvl) return '[fg]killing an enemy fires a homing dagger' end, + ['homing_barrage'] = function(lvl) return ts(lvl, '8', '16', '24%') .. ' chance to release a homing barrage on enemy kill' end, + ['critical_strike'] = function(lvl) return ts(lvl, '5', '10', '15%') .. ' chance for attacks to critically strike, dealing [yellow]2x[fg] damage' end, + ['noxious_strike'] = function(lvl) return ts(lvl, '8', '16', '24%') .. ' chance for attacks to poison, dealing [yellow]20%[fg] dps for [yellow]3[fg] seconds' end, + ['infesting_strike'] = function(lvl) return ts(lvl, '10', '20', '30%') .. ' chance for attacks to spawn [yellow]2[fg] critters on kill' end, + ['kinetic_strike'] = function(lvl) return ts(lvl, '10', '20', '30%') .. ' chance for attacks to push enemies away with high force' end, + ['burning_strike'] = function(lvl) return '[yellow]15%[fg] chance for attacks to burn, dealing [yellow]20%[fg] dps for [yellow]3[fg] seconds' end, + ['lucky_strike'] = function(lvl) return '[yellow]8%[fg] chance for attacks to cause enemies to drop gold on death' end, + ['healing_strike'] = function(lvl) return '[yellow]8%[fg] chance for attacks to spawn a healing orb on kill' end, + ['stunning_strike'] = function(lvl) return ts(lvl, '8', '16', '24%') .. ' chance for attacks to stun for [yellow]2[fg] seconds' end, + ['silencing_strike'] = function(lvl) return ts(lvl, '8', '16', '24%') .. ' chance for attacks to silence for [yellow]2[fg] seconds on hit' end, + ['warping_shots'] = function(lvl) return 'projectiles ignore wall collisions and warp around the screen ' .. ts(lvl, '1', '2', '3') .. ' times' end, + ['culling_strike'] = function(lvl) return '[fg]instantly kill elites below ' .. ts(lvl, '10', '20', '30%') .. ' max HP' end, + ['lightning_strike'] = function(lvl) return ts(lvl, '5', '10', '15%') .. ' chance for projectiles to create chain lightning, dealing ' .. ts(lvl, '60', '80', '100%') .. ' damage' end, + ['psycholeak'] = function(lvl) return '[fg]position [yellow]1[fg] generates [yellow]1[fg] psyker orb every [yellow]10[fg] seconds' end, + ['divine_blessing'] = function(lvl) return '[fg]generate [yellow]1[fg] healing orb every [yellow]8[fg] seconds' end, } level_to_tier_weights = { @@ -1571,7 +1685,7 @@ function init() unlevellable_items = { 'speed_3', 'damage_4', 'shoot_5', 'death_6', 'lasting_7', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'annihilation', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'divine_punishment', 'unleash', 'freezing_field', 'burning_field', 'gravity_field', - 'magnetism', 'insurance', 'dividends', 'haste', 'rearm', + 'magnetism', 'insurance', 'dividends', 'haste', 'rearm', 'ceremonial_dagger', 'burning_strike', 'lucky_strike', 'healing_strike', 'psycholeak', 'divine_blessing' } steam.userStats.requestCurrentStats() @@ -1584,8 +1698,10 @@ function init() main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5} main = Main() + --[[ main:add(MainMenu'mainmenu') main:go_to('mainmenu') + ]]-- --[[ main:add(BuyScreen'buy_screen') @@ -1593,33 +1709,25 @@ function init() -- main:go_to('buy_screen', 7, run.units or {}, {'unleash'}) ]]-- - --[[ gold = 10 run_passive_pool = { 'centipede', 'ouroboros_technique_r', 'ouroboros_technique_l', 'amplify', 'resonance', 'ballista', 'call_of_the_void', 'crucio', 'speed_3', 'damage_4', 'shoot_5', 'death_6', 'lasting_7', 'defensive_stance', 'offensive_stance', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'seeping', 'deceleration', 'annihilation', 'malediction', 'hextouch', 'whispers_of_doom', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'hive', 'baneling_burst', 'blunt_arrow', 'explosive_arrow', 'divine_machine_arrow', 'chronomancy', 'awakening', 'divine_punishment', 'assassination', 'flying_daggers', 'ultimatum', 'magnify', 'echo_barrage', 'unleash', 'reinforce', 'payback', 'enchanted', 'freezing_field', 'burning_field', 'gravity_field', 'magnetism', - 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'summon_instability', + 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'construct_instability', + 'intimidation', 'vulnerability', 'temporal_chains', 'ceremonial_dagger', 'homing_barrage', 'critical_strike', 'noxious_strike', 'infesting_strike', 'burning_strike', 'lucky_strike', 'healing_strike', 'stunning_strike', + 'silencing_strike', 'culling_strike', 'lightning_strike', 'psycholeak', 'divine_blessing', } main:add(Arena'arena') - main:go_to('arena', 1, { - {character = 'vagrant', level = 3}, + main:go_to('arena', 2, 0, { + {character = 'carver', level = 3}, -- {character = 'carver', level = 2}, - {character = 'dual_gunner', level = 3}, -- {character = 'saboteur', level = 2}, -- {character = 'hunter', level = 2}, }, { - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, - {passive = 'summon_instability', level = 3}, + {passive = 'rearm', level = 1}, }) - ]]-- --[[ main:add(Media'media') @@ -1789,7 +1897,7 @@ function open_options(self) 'defensive_stance', 'offensive_stance', 'kinetic_bomb', 'porcupine_technique', 'last_stand', 'seeping', 'deceleration', 'annihilation', 'malediction', 'hextouch', 'whispers_of_doom', 'tremor', 'heavy_impact', 'fracture', 'meat_shield', 'hive', 'baneling_burst', 'blunt_arrow', 'explosive_arrow', 'divine_machine_arrow', 'chronomancy', 'awakening', 'divine_punishment', 'assassination', 'flying_daggers', 'ultimatum', 'magnify', 'echo_barrage', 'unleash', 'reinforce', 'payback', 'enchanted', 'freezing_field', 'burning_field', 'gravity_field', 'magnetism', - 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'summon_instability', + 'insurance', 'dividends', 'berserking', 'unwavering_stance', 'unrelenting_stance', 'blessing', 'haste', 'divine_barrage', 'orbitism', 'psyker_orbs', 'psychosense', 'rearm', 'taunt', 'construct_instability', } max_units = 7 + current_new_game_plus main:add(BuyScreen'buy_screen') diff --git a/objects.lua b/objects.lua index e21d217..57d4f93 100644 --- a/objects.lua +++ b/objects.lua @@ -310,7 +310,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(Illusion) then + elseif self:is(Automaton) then self.base_hp = 100*math.pow(2, self.level-1) self.base_dmg = 10*math.pow(2, self.level-1) self.base_mvspd = 15 diff --git a/player.lua b/player.lua index 78f8c3c..f27bf0d 100644 --- a/player.lua +++ b/player.lua @@ -141,14 +141,14 @@ function Player:init(args) if self.level == 3 then for i = 1, 4 do local check_circle = Circle(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16), 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) while #objects > 0 do check_circle:move_to(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16)) - objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) end SpawnEffect{group = main.current.effects, x = check_circle.x, y = check_circle.y, color = green[0], action = function(x, y) local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end @@ -156,14 +156,14 @@ function Player:init(args) end else local check_circle = Circle(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16), 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) while #objects > 0 do check_circle:move_to(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16)) - objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) end SpawnEffect{group = main.current.effects, x = check_circle.x, y = check_circle.y, color = green[0], action = function(x, y) local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Automaton, Bomb, Volcano, Saboteur, Pet, Turret}) if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end @@ -205,49 +205,31 @@ function Player:init(args) end end, nil, nil, 'shoot') - elseif self.character == 'illusionist' then + elseif self.character == 'artificer' then self.sorcerer_count = 0 self.attack_sensor = Circle(self.x, self.y, 96) - self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() - local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) - if closest_enemy then - self:shoot(self:angle_to_object(closest_enemy)) - if main.current.sorcerer_level > 0 then - self.sorcerer_count = self.sorcerer_count + 1 - if self.sorcerer_count >= ((main.current.sorcerer_level == 3 and 2) or (main.current.sorcerer_level == 2 and 3) or (main.current.sorcerer_level == 1 and 4)) then - self:sorcerer_repeat() - self.sorcerer_count = 0 - self.t:after(0.25, function() - self:shoot(self:angle_to_object(closest_enemy)) - end) - end + self.t:every(6, function() + SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y) + artificer1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + local check_circle = Circle(self.x, self.y, 2) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Pet, Turret, Sentry, Bomb}) + if #objects == 0 then Automaton{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1} end + end} + if main.current.sorcerer_level > 0 then + self.sorcerer_count = self.sorcerer_count + 1 + if self.sorcerer_count >= ((main.current.sorcerer_level == 3 and 2) or (main.current.sorcerer_level == 2 and 3) or (main.current.sorcerer_level == 1 and 4)) then + self:sorcerer_repeat() + self.sorcerer_count = 0 + self.t:after(0.25, function() + SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y) + artificer1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + local check_circle = Circle(self.x, self.y, 2) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Pet, Turret, Sentry, Bomb}) + if #objects == 0 then Automaton{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1} end + end} + end) end end - end, nil, nil, 'shoot') - self.t:every(8, function() - self.t:every(0.25, function() - SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y) - illusion1:play{pitch = random:float(0.95, 1.05), volume = 0.5} - local check_circle = Circle(self.x, self.y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) - if #objects == 0 then Illusion{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1} end - end} - if main.current.sorcerer_level > 0 then - self.sorcerer_count = self.sorcerer_count + 1 - if self.sorcerer_count >= ((main.current.sorcerer_level == 3 and 2) or (main.current.sorcerer_level == 2 and 3) or (main.current.sorcerer_level == 1 and 4)) then - self:sorcerer_repeat() - self.sorcerer_count = 0 - self.t:after(0.25, function() - SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y) - illusion1:play{pitch = random:float(0.95, 1.05), volume = 0.5} - local check_circle = Circle(self.x, self.y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) - if #objects == 0 then Illusion{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1} end - end} - end) - end - end - end, self.level == 3 and 2 or 1) end) elseif self.character == 'outlaw' then @@ -316,6 +298,13 @@ function Player:init(args) end, 2) end, nil, nil, 'spawn') + elseif self.character == 'bomber' then + self.t:every(8, function() + SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = self.color, action = function(x, y) + Bomb{group = main.current.main, x = x, y = y, parent = self, level = self.level, conjurer_buff_m = self.conjurer_buff_m or 1} + end} + end, nil, nil, 'spawn') + elseif self.character == 'stormweaver' then self.t:every(8, function() stormweaver1:play{pitch = random:float(0.95, 1.05), volume = 0.5} @@ -363,7 +352,7 @@ function Player:init(args) x, y = x/2, y/2 main.current.t:every_immediate(0.1, function() local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Player, Seeker, EnemyCritter, Critter, Illusion, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Player, Seeker, EnemyCritter, Critter, Saboteur, Pet, Turret, Sentry, Bomb}) if #objects == 0 then Volcano{group = main.current.main, x = x, y = y, color = self.color, parent = self, rs = 24, level = self.level} main.current.t:cancel('volcano_spawn') @@ -619,7 +608,12 @@ function Player:init(args) elseif self.character == 'carver' then self.t:every(16, function() - Tree{group = main.current.main, x = self.x, y = self.y, color = self.color, parent = self, rs = self.area_size_m*(self.level == 3 and 128 or 64), level = self.level} + Tree{group = main.current.main, x = self.x, y = self.y, color = self.color, parent = self, level = self.level} + end, nil, nil, 'spawn') + + elseif self.character == 'sentry' then + self.t:every(7, function() + Sentry{group = main.current.main, x = self.x, y = self.y, color = self.color, parent = self, level = self.level} end, nil, nil, 'spawn') elseif self.character == 'bane' then @@ -697,14 +691,14 @@ function Player:init(args) buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5} for i = 1, 2 do local check_circle = Circle(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16), 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry}) while #objects > 0 do check_circle:move_to(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16)) - objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Pet, Turret, Sentry, Bomb}) end SpawnEffect{group = main.current.effects, x = check_circle.x, y = check_circle.y, color = green[0], action = function(x, y) local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry}) if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end @@ -720,14 +714,14 @@ function Player:init(args) heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5} buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5} local check_circle = Circle(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16), 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry}) while #objects > 0 do check_circle:move_to(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16)) - objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry}) end SpawnEffect{group = main.current.effects, x = check_circle.x, y = check_circle.y, color = green[0], action = function(x, y) local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Pet, Turret, Sentry, Bomb}) if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end @@ -812,7 +806,7 @@ function Player:init(args) for i = 1, 5 do SpawnEffect{group = main.current.effects, x = x, y = y, color = green[0], action = function(x, y) local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry, Automaton}) if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end end} end @@ -1090,6 +1084,24 @@ function Player:init(args) end) end + if self.leader and self.psycholeak then + main.current.t:every(10, function() + local unit = main.current.player + Projectile{group = main.current.main, x = unit.x + 24*math.cos(unit.r), y = unit.y + 24*math.sin(unit.r), color = fg[0], v = 200, dmg = unit.dmg, character = 'psyker', parent = unit} + end) + end + + if self.leader and self.divine_blessing then + main.current.t:every(8, function() + local x, y = random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16) + SpawnEffect{group = main.current.effects, x = x, y = y, color = green[0], action = function(x, y) + local check_circle = Circle(x, y, 2) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry, Automaton}) + if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end + end} + end) + end + self.mouse_control_v_buffer = {} if main.current:is(MainMenu) then @@ -1516,14 +1528,14 @@ function Player:hit(damage, from_undead) if self.stored_heal > (0.2*self.max_hp) then self.stored_heal = 0 local check_circle = Circle(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16), 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry, Automaton}) while #objects > 0 do check_circle:move_to(random:float(main.current.x1 + 16, main.current.x2 - 16), random:float(main.current.y1 + 16, main.current.y2 - 16)) - objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Volcano, Saboteur, Bomb, Pet, Turret, Sentry, Automaton}) end SpawnEffect{group = main.current.effects, x = check_circle.x, y = check_circle.y, color = green[0], action = function(x, y) local check_circle = Circle(x, y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter, Critter, Sentry, Volcano, Saboteur, Bomb, Pet, Turret, Automaton}) if #objects == 0 then HealingOrb{group = main.current.main, x = x, y = y} end @@ -1817,7 +1829,7 @@ function Player:shoot(r, mods) Projectile(table.merge(t, mods or {})) end - if self.character == 'vagrant' or self.character == 'illusionist' then + if self.character == 'vagrant' or self.character == 'artificer' then shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.2} elseif self.character == 'dual_gunner' then dual_gunner1:play{pitch = random:float(0.95, 1.05), volume = 0.3} @@ -1886,7 +1898,7 @@ function Player:dot_attack(area, mods) end -function Player:barrage(r, n, pierce, ricochet, shoot_5) +function Player:barrage(r, n, pierce, ricochet, shoot_5, homing) n = n or 8 for i = 1, n do self.t:after((i-1)*0.075, function() @@ -1894,7 +1906,7 @@ function Player:barrage(r, n, pierce, ricochet, shoot_5) else archer1:play{pitch = random:float(0.95, 1.05), volume = 0.35} end 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', level = self.level, pierce = pierce or 0, ricochet = ricochet or 0, shoot_5 = shoot_5} + parent = self, character = 'barrage', level = self.level, pierce = pierce or 0, ricochet = ricochet or 0, shoot_5 = shoot_5, homing = homing} Projectile(table.merge(t, mods or {})) end) end @@ -1909,7 +1921,7 @@ Projectile:implement(Physics) function Projectile:init(args) self:init_game_object(args) self.hfx:add('hit', 1) - self:set_as_rectangle(10, 4, 'dynamic', 'projectile') + self:set_as_rectangle(10, 4, 'dynamic', main.current.player.warping_shots and 'true_ghost' or 'projectile') self.pierce = args.pierce or 0 self.chain = args.chain or 0 self.ricochet = args.ricochet or 0 @@ -1986,7 +1998,7 @@ function Projectile:init(args) local t = {group = main.current.main, x = self.x + 8*math.cos(r), y = self.y + 8*math.sin(r), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, pierce = 2, character = 'arcanist_projectile', parent = self.parent, level = self.parent.level} local check_circle = Circle(t.x, t.y, 2) - local objects = main.current.main:get_objects_in_shape(check_circle, {Player, Seeker, EnemyCritter, Critter, Illusion, Volcano, Saboteur, Pet, Turret}) + local objects = main.current.main:get_objects_in_shape(check_circle, {Player, Seeker, EnemyCritter, Critter, Sentry, Volcano, Saboteur, Bomb, Pet, Turret, Automaton}) if #objects == 0 then Projectile(table.merge(t, mods or {})) end end end) @@ -2162,7 +2174,7 @@ function Projectile:on_collision_enter(other, contact) else r = 0 end if other:is(Wall) then - if self.character == 'archer' or self.character == 'hunter' or self.character == 'barrage' or self.character == 'barrager' then + if self.character == 'archer' or self.character == 'hunter' or self.character == 'barrage' or self.character == 'barrager' or self.character == 'sentry' then 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} @@ -2183,7 +2195,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 == 'illusionist_death' then + elseif self.character == 'artificer_death' then if self.ricochet <= 0 then self:die(x, y, r, random:int(2, 3)) magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075} @@ -2249,7 +2261,7 @@ function Projectile:on_trigger_enter(other, contact) if self.character == 'archer' or self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'hunter' or self.character == 'spellblade' or self.character == 'engineer' or self.character == 'jester' or self.character == 'assassin' or self.character == 'barrager' or self.character == 'beastmaster' or self.character == 'witch' or self.character == 'miner' or self.character == 'thief' or - self.character == 'psyker' then + self.character == 'psyker' or self.character == 'sentry' then hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} if self.character == 'spellblade' or self.character == 'psyker' then magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15} @@ -2312,6 +2324,26 @@ function Projectile:on_trigger_enter(other, contact) end end + if self.parent and self.parent.lightning_strike then + if random:bool((self.parent.lightning_strike == 1 and 5) or (self.parent.lightning_strike == 2 and 10) or (self.parent.lightning_strike == 3 and 15)) then + local src = other + for j = 1, 3 do + main.current.t:after((j-1)*0.1, function() + _G[random:table{'spark1', 'spark2', 'spark3'}]:play{pitch = random:float(0.9, 1.1), volume = 0.3} + for i = 1, 3 do + table.insert(self.infused_enemies_hit, src) + local dst = src:get_random_object_in_shape(Circle(src.x, src.y, 64), main.current.enemies, self.infused_enemies_hit) + if dst then + dst:hit(0.33*((self.parent.lightning_strike == 1 and 0.6) or (self.parent.lightning_strike == 2 and 0.8) or (self.parent.lightning_strike == 3 and 1))*self.dmg*(self.distance_dmg_m or 1)) + LightningLine{group = main.current.effects, src = src, dst = dst} + src = dst + end + end + end) + end + end + end + if self.crit then camera:shake(5, 0.25) rogue_crit1:play{pitch = random:float(0.95, 1.05), volume = 0.5} @@ -2388,7 +2420,7 @@ function Area:init(args) 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' or self.character == 'pyromancer' then + elseif self.character == 'saboteur' or self.character == 'pyromancer' or self.character == 'bomber' 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 @@ -2729,7 +2761,7 @@ function Tree:init(args) self:set_restitution(0.5) self.hfx:add('hit', 1) self.color = orange[0] - self.heal_sensor = Circle(self.x, self.y, args.rs) + self.heal_sensor = Circle(self.x, self.y, 48) self.vr = 0 self.dvr = random:float(-math.pi/4, math.pi/4) @@ -2743,6 +2775,39 @@ function Tree:init(args) self.t:tween(0.05, self, {rs = args.rs}, math.cubic_in_out, function() self.spring:pull(0.15) end) self.t:after(0.2, function() self.color = args.color end) + self.t:every(self.parent.level == 3 and 3 or 6, function() + self.hfx:use('hit', 0.2) + HealingOrb{group = main.current.main, x = self.x, y = self.y} + if self.parent.taunt and random:bool((self.parent.taunt == 1 and 10) or (self.parent.taunt == 2 and 20) or (self.parent.taunt == 3 and 30)) then + local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 96), main.current.enemies) + + if #enemies > 0 then + for _, enemy in ipairs(enemies) do + enemy.taunted = self + enemy.t:after(4, function() enemy.taunted = false end, 'taunt') + end + end + end + + if self.parent.rearm then + self.t:after(0.25, function() + self.hfx:use('hit', 0.2) + HealingOrb{group = main.current.main, x = self.x, y = self.y} + + if self.parent.taunt and random:bool((self.parent.taunt == 1 and 10) or (self.parent.taunt == 2 and 20) or (self.parent.taunt == 3 and 30)) then + local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 96), main.current.enemies) + if #enemies > 0 then + for _, enemy in ipairs(enemies) do + enemy.taunted = self + enemy.t:after(4, function() enemy.taunted = false end, 'taunt') + end + end + end + end) + end + end) + + --[[ self.t:cooldown(3.33/(self.level == 3 and 2 or 1), function() return #self:get_objects_in_shape(self.heal_sensor, {Player}) > 0 end, function() local n = n or random:int(3, 4) for i = 1, n do HitParticle{group = main.current.effects, x = self.x, y = self.y, r = random:float(0, 2*math.pi), color = self.color} end @@ -2815,14 +2880,15 @@ function Tree:init(args) end end end) + ]]-- - self.t:after(10*(self.parent.conjurer_buff_m or 1), function() + self.t:after(12*(self.parent.conjurer_buff_m or 1), function() self.t:every_immediate(0.05, function() self.hidden = not self.hidden end, 7, function() self.dead = true - if self.parent.summon_instability then + if self.parent.construct_instability then camera:shake(2, 0.5) - local n = (self.parent.summon_instability == 1 and 1) or (self.parent.summon_instability == 2 and 1.5) or (self.parent.summon_instability == 3 and 2) or 1 + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*48, color = self.color, dmg = n*self.parent.dmg*self.parent.area_dmg_m, parent = self.parent} _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end @@ -2974,6 +3040,97 @@ end +Sentry = Object:extend() +Sentry:implement(GameObject) +Sentry:implement(Physics) +function Sentry:init(args) + self:init_game_object(args) + self:set_as_rectangle(6, 6, 'static', 'player') + self:set_restitution(0.5) + self.hfx:add('hit', 1) + + self.t:after(15*(self.parent.conjurer_buff_m or 1), function() + local n = n or random:int(3, 4) + for i = 1, n do HitParticle{group = main.current.effects, x = self.x, y = self.y, r = random:float(0, 2*math.pi), color = self.color} end + HitCircle{group = main.current.effects, x = self.x, y = self.y}:scale_down() + self.dead = true + + if self.parent.construct_instability then + camera:shake(2, 0.5) + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*48, color = self.color, dmg = n*self.parent.dmg*self.parent.area_dmg_m, parent = self.parent} + _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} + end + end) + + self.t:every({2.75, 3.5}, function() + self.hfx:use('hit', 0.25, 200, 10) + local r = self.r + for i = 1, 4 do + 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 = 200, r = r, color = self.color, + dmg = self.parent.dmg*(self.parent.conjurer_buff_m or 1), character = 'sentry', parent = self.parent} + Projectile(table.merge(t, mods or {})) + r = r + math.pi/2 + end + + if self.parent.taunt and random:bool((self.parent.taunt == 1 and 10) or (self.parent.taunt == 2 and 20) or (self.parent.taunt == 3 and 30)) then + local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 96), main.current.enemies) + if #enemies > 0 then + for _, enemy in ipairs(enemies) do + enemy.taunted = self + enemy.t:after(4, function() enemy.taunted = false end, 'taunt') + end + end + end + + if self.parent.rearm then + self.t:after(0.25, function() + self.hfx:use('hit', 0.25, 200, 10) + local r = self.r + for i = 1, 4 do + 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 = 200, r = r, color = self.color, + dmg = self.parent.dmg*(self.parent.conjurer_buff_m or 1), character = 'sentry', parent = self.parent} + Projectile(table.merge(t, mods or {})) + r = r + math.pi/2 + end + + if self.parent.taunt and random:bool((self.parent.taunt == 1 and 10) or (self.parent.taunt == 2 and 20) or (self.parent.taunt == 3 and 30)) then + local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 96), main.current.enemies) + if #enemies > 0 then + for _, enemy in ipairs(enemies) do + enemy.taunted = self + enemy.t:after(4, function() enemy.taunted = false end, 'taunt') + end + end + end + end) + end + end) +end + + +function Sentry:update(dt) + self:update_game_object(dt) + self.r = self.r + math.pi*dt + self:set_angle(self.r) +end + + +function Sentry:draw() + if self.hidden then return end + graphics.push(self.x, self.y, self.r, self.spring.x, self.spring.x) + graphics.rectangle(self.x, self.y, 2*self.shape.w, 4, 2, 2, self.hfx.hit.f and fg[0] or self.color) + graphics.rectangle(self.x, self.y, 4, 2*self.shape.h, 2, 2, self.hfx.hit.f and fg[0] or self.color) + graphics.pop() +end + + + + Turret = Object:extend() Turret:implement(GameObject) Turret:implement(Physics) @@ -3038,9 +3195,9 @@ function Turret:init(args) HitCircle{group = main.current.effects, x = self.x, y = self.y}:scale_down() self.dead = true - if self.parent.summon_instability then + if self.parent.construct_instability then camera:shake(2, 0.5) - local n = (self.parent.summon_instability == 1 and 1) or (self.parent.summon_instability == 2 and 1.5) or (self.parent.summon_instability == 3 and 2) or 1 + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*48, color = self.color, dmg = n*self.parent.dmg*self.parent.area_dmg_m, parent = self.parent} _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end @@ -3135,9 +3292,9 @@ function Pet:on_collision_enter(other, contact) else self.dead = true - if self.parent.summon_instability then + if self.parent.construct_instability then camera:shake(2, 0.5) - local n = (self.parent.summon_instability == 1 and 1) or (self.parent.summon_instability == 2 and 1.5) or (self.parent.summon_instability == 3 and 2) or 1 + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*48, color = self.color, dmg = n*self.parent.dmg*self.parent.area_dmg_m, parent = self.parent} _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end @@ -3173,6 +3330,74 @@ end +Bomb = Object:extend() +Bomb:implement(GameObject) +Bomb:implement(Physics) +function Bomb:init(args) + self:init_game_object(args) + self:set_as_rectangle(8, 8, 'static', 'player') + self:set_restitution(0.5) + self.hfx:add('hit', 1) + + mine1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.color = orange[0] + self.dmg = 2*get_character_stat('bomber', self.level, 'dmg') + self.t:after(8, function() self:explode() end) +end + + +function Bomb:update(dt) + self:update_game_object(dt) +end + + +function Bomb:draw() + graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x) + graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, self.hfx.hit.f and fg[0] or self.color) + graphics.pop() +end + + +function Bomb:explode() + camera:shake(4, 0.5) + local t = {group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*64*(self.level == 3 and 2 or 1), color = self.color, + dmg = self.parent.area_dmg_m*self.dmg*(self.parent.conjurer_buff_m or 1)*(self.level == 3 and 2 or 1), character = self.character, parent = self.parent} + Area(table.merge(t, mods or {})) + if not self.parent.construct_instability and not self.parent.rearm then self.dead = true end + _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} + _G[random:table{'saboteur_hit1', 'saboteur_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} + explosion1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + + self.t:after(0.25, function() + if self.parent.construct_instability then + camera:shake(2, 0.5) + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r + random:float(-math.pi/16, math.pi/16), w = self.parent.area_size_m*48*(self.level == 3 and 2 or 1), color = self.color, + dmg = n*self.parent.dmg*self.parent.area_dmg_m*(self.level == 3 and 2 or 1), parent = self.parent} + _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.dead = true + end + + if self.parent.rearm then + camera:shake(2, 0.5) + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r + random:float(-math.pi/16, math.pi/16), w = self.parent.area_size_m*48*(self.level == 3 and 2 or 1), color = self.color, + dmg = n*self.parent.dmg*self.parent.area_dmg_m*(self.level == 3 and 2 or 1), parent = self.parent} + _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.dead = true + end + end) +end + + +function Bomb:on_collision_enter(other, contact) + if table.any(main.current.enemies, function(v) return other:is(v) end) then + self:explode() + end +end + + + Saboteur = Object:extend() Saboteur:implement(GameObject) @@ -3232,10 +3457,10 @@ function Saboteur:on_collision_enter(other, contact) dmg = (self.crit and 2 or 1)*self.area_dmg_m*self.actual_dmg*(self.conjurer_buff_m or 1), character = self.character, parent = self.parent} Area(table.merge(t, mods or {})) - if self.parent.summon_instability then + if self.parent.construct_instability then self.t:after(0.25, function() camera:shake(2, 0.5) - local n = (self.parent.summon_instability == 1 and 1) or (self.parent.summon_instability == 2 and 1.5) or (self.parent.summon_instability == 3 and 2) or 1 + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*48, color = self.color, dmg = n*self.parent.dmg*self.parent.area_dmg_m, parent = self.parent} _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} self.dead = true @@ -3248,18 +3473,18 @@ end -Illusion = Object:extend() -Illusion:implement(GameObject) -Illusion:implement(Physics) -Illusion:implement(Unit) -function Illusion:init(args) +Automaton = Object:extend() +Automaton:implement(GameObject) +Automaton:implement(Physics) +Automaton:implement(Unit) +function Automaton:init(args) self:init_game_object(args) self:init_unit() self:set_as_rectangle(8, 8, 'dynamic', 'player') self:set_restitution(0.5) - self.color = character_colors.illusionist - self.character = 'illusionist' + self.color = character_colors.artificer + self.character = 'artificer' self.classes = {'sorcerer', 'conjurer'} self:calculate_stats(true) self:set_as_steerable(self.v, 2000, 4*math.pi, 4) @@ -3268,10 +3493,12 @@ function Illusion: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 - wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.05} + turret1:play{pitch = random:float(0.95, 1.05), volume = 0.10} + turret2:play{pitch = random:float(0.95, 1.05), volume = 0.10} + wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.10} local r = self:angle_to_object(closest_enemy) HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6} - local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, character = 'illusionist', + local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, character = 'artificer', parent = self.parent, level = self.parent.level} Projectile(table.merge(t, mods or {})) end @@ -3280,10 +3507,12 @@ function Illusion:init(args) self.t:after(0.25, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then - wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.05} + turret1:play{pitch = random:float(0.95, 1.05), volume = 0.10} + turret2:play{pitch = random:float(0.95, 1.05), volume = 0.10} + wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.10} local r = self:angle_to_object(closest_enemy) HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6} - local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, character = 'illusionist', + local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.parent.color, dmg = self.parent.dmg, character = 'artificer', parent = self.parent, level = self.parent.level} Projectile(table.merge(t, mods or {})) end @@ -3301,7 +3530,7 @@ function Illusion:init(args) end end, nil, nil, 'shoot') - self.t:after(12*(self.parent.conjurer_buff_m or 1), function() + self.t:after(18*(self.parent.conjurer_buff_m or 1), function() local n = n or random:int(3, 4) for i = 1, n do HitParticle{group = main.current.effects, x = self.x, y = self.y, r = random:float(0, 2*math.pi), color = self.color} end HitCircle{group = main.current.effects, x = self.x, y = self.y}:scale_down() @@ -3310,14 +3539,14 @@ function Illusion:init(args) if self.parent.level == 3 then shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.2} for i = 1, 12 do - Projectile{group = main.current.main, x = self.x, y = self.y, color = self.color, r = (i-1)*math.pi/6, v = 200, dmg = self.parent.dmg, character = 'illusionist_death', + Projectile{group = main.current.main, x = self.x, y = self.y, color = self.color, r = (i-1)*math.pi/6, v = 200, dmg = self.parent.dmg, character = 'artificer_death', parent = self.parent, level = self.parent.level, pierce = 1, ricochet = 1} end end - if self.parent.summon_instability then + if self.parent.construct_instability then camera:shake(2, 0.5) - local n = (self.parent.summon_instability == 1 and 1) or (self.parent.summon_instability == 2 and 1.5) or (self.parent.summon_instability == 3 and 2) or 1 + local n = (self.parent.construct_instability == 1 and 1) or (self.parent.construct_instability == 2 and 1.5) or (self.parent.construct_instability == 3 and 2) or 1 Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*48, color = self.color, dmg = n*self.parent.dmg*self.parent.area_dmg_m, parent = self.parent} _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end @@ -3325,7 +3554,7 @@ function Illusion:init(args) end -function Illusion:update(dt) +function Automaton:update(dt) self:update_game_object(dt) self:calculate_stats() @@ -3346,11 +3575,12 @@ function Illusion:update(dt) end self.r = self:get_angle() + self.t:set_every_multiplier('shoot', self.parent.level == 3 and 0.75 or 1) self.attack_sensor:move_to(self.x, self.y) end -function Illusion:draw() +function Automaton:draw() graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x) graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, self.hfx.hit.f and fg[0] or self.color) graphics.pop() diff --git a/todo b/todo index fe5f6e6..bba6999 100644 --- a/todo +++ b/todo @@ -1,24 +1,24 @@ Loop Update New items: General: - Intimidation - enemies spawn with -10/20/30% max HP - Vulnerability - enemies take +10/20/30% damage - Ceremonial Dagger - killing an enemy fires a homing dagger - Homing Barrage - X/Y/Z% chance to release a homing barrage on enemy kill - Critical Strike - X/Y/Z% chance for attacks to critically strike, dealing 2x damage - Noxious Strike - X/Y/Z% chance for attacks to poison enemies, dealing 20% damage per second for 3 seconds - Infesting Strike - X/Y/Z% chance for attacks to spawn 2 critters on kill - Kinetic Strike - X/Y/Z% chance for attacks to push enemies away with high force - Burning Strike - X% chance for attacks to ignite, dealing 20% dps for 3 seconds, ignited enemies ignite nearby enemies on death - Lucky Strike - X% chance for attacks to cause enemies to drop gold on death - Healing Strike - X% chance for attacks to spawn a healing orb on kill - Stunning Strike - X/Y/Z% chance for attacks to stun for 2 seconds - Silencing Strike - X/Y/Z% chance for attacks to silence on hit - Warping Shots - X/Y/Z% chance for projectiles to ignore wall collisions and warp around the screen 1/2/3 times - Culling Strike - instantly kill elites below 10/20/30% max HP - Lightning Strike - X/Y/Z% chance for attacks to create chain lightning, dealing 60/80/100% damage and hitting 2/3/4 enemies - Psycholeak - Position X generates 1 additional orb every Y seconds - Divine Blessing - generate 1 healing orb every X seconds + * Intimidation - enemies spawn with -10/20/30% max HP + * Vulnerability - enemies take +10/20/30% damage + * Temporal Chains - enemies are 10/20/30% slower + * Ceremonial Dagger - killing an enemy fires a homing dagger + * Homing Barrage - 8/16/24% chance to release a homing barrage on enemy kill + * Critical Strike - 5/10/15% chance for attacks to critically strike, dealing 2x damage + * Noxious Strike - 8/16/24% chance for attacks to poison enemies, dealing 20% dps for 3 seconds + * Infesting Strike - 10/20/30% chance for attacks to spawn 2 critters on kill + * Kinetic Strike - 10/20/30% chance for attacks to push enemies away with high force + * Burning Strike - 15% chance for attacks to ignite, dealing 20% dps for 3 seconds + * Lucky Strike - 8% chance for attacks to cause enemies to drop gold on death + * Healing Strike - 8% chance for attacks to spawn a healing orb on kill + * Stunning Strike - 8/16/24% chance for attacks to stun for 2 seconds + * Silencing Strike - 8/16/24% chance for attacks to silence for 4 seconds on hit + * Culling Strike - instantly kill elites below 10/20/30% max HP + * Lightning Strike - 5/10/15% chance for projectiles to create chain lightning, dealing 60/80/100% damage + * Psycholeak - Position 1 generates 1 psyker orb every 10 seconds + * Divine Blessing - generate 1 healing orb every 8 seconds Psyker Additional orbs will prefer to be assigned to psykers, if there are no psykers then they will be assigned randomly Orb speed scales with the unit's attack speed @@ -35,12 +35,15 @@ Loop Update Increases amount of orbs dropped with only 1 or 2 healers Decreases amount of orbs dropped on full build Healing orbs are slightly attracted to the snake + Warriors + Increase sensor range + Set juggernaut and barbarian's attacks on :cooldown calls Builders - Carver (builder, healer) - every 12 seconds, creates a tree that drops 3 healing orbs, Lv.3 effect: drops 6 healing orbs instead - Engineer (builder) - unchanged - Illusionist -> Golemancer (builder, sorcerer): spawns 1 golem every 6 seconds, it shoots 2 rocks that deal X damage each, Lv.3 effect: golem rocks stun enemies for 2 seconds - Saboteur -> Bomber (builder, nuker): plants a static bomb, when it explodes it deals 2X AoE damage, Lv.3 effect: 100% increased bomb area and damage - Hunter (builder, ranger, forcer): shoots a projectile that has 20% chance to spawn a pet, Lv.3 effect: spawns 3 pets instead + * Carver (builder, healer) - creates a tree that creates 1 healing orb every 6 seconds, Lv.3 effect: healing orbs are created twice as fast + * Engineer (builder): unchanged + * Illusionist -> Artificer (builder, sorcerer): unchanged + * Saboteur -> Bomber (builder, nuker): plants a bomb, when it explodes it deals 2X AoE damage, Lv.3 effect: +100% bomb area and damage + * Hunter -> Sentry (builder, ranger): spawns a turret that shoots projectiles, each dealing X damage, Lv.3 effect: +50% attack speed and the projectiles ricochet twice Looping * Change end screen * Loop button on end screen @@ -53,14 +56,14 @@ Loop Update Add option for mouse cursor to always be visible Show cooldown on elite attack Add visuals divine intervention, fairy buff - warrior attack range - barbarian jugg attack Party + items on death screen + * Silenced units are now gray * Items on end screen * Items on passive screen * Selling items * Decreased sound effect volume for shoot 5 Bug fixes + Fix stars * Fixed a series of crashes that happened sometimes right before changing from the arena back to the shop * Fixed merchant not giving interest or free reroll if it died in combat * Fixed shoot 5 and death 6 not working if a unit placed before it on the snake died