diff --git a/arena.lua b/arena.lua index b45a493..d09840c 100644 --- a/arena.lua +++ b/arena.lua @@ -50,13 +50,16 @@ function Arena:on_enter(from, level) WallCover{group = self.post_main, vertices = math.to_rectangle_vertices(self.x1, -40, self.x2, self.y1), color = bg[-1]} WallCover{group = self.post_main, vertices = math.to_rectangle_vertices(self.x1, self.y2, self.x2, gh + 40), color = bg[-1]} - self.player = Player{group = self.main, x = gw/2, y = gh/2, leader = true, character = 'stormweaver'} - self.player:add_follower(Player{group = self.main, character = 'vagrant'}) - self.player:add_follower(Player{group = self.main, character = 'archer'}) - self.player:add_follower(Player{group = self.main, character = 'wizard'}) - self.player:add_follower(Player{group = self.main, character = 'scout'}) + self.player = Player{group = self.main, x = gw/2, y = gh/2, leader = true, character = 'engineer'} --[[ + self.player:add_follower(Player{group = self.main, character = 'sage'}) + self.player:add_follower(Player{group = self.main, character = 'archer'}) + self.player:add_follower(Player{group = self.main, character = 'spellblade'}) + self.player:add_follower(Player{group = self.main, character = 'hunter'}) self.player:add_follower(Player{group = self.main, character = 'cleric'}) + self.player:add_follower(Player{group = self.main, character = 'wizard'}) + self.player:add_follower(Player{group = self.main, character = 'squire'}) + self.player:add_follower(Player{group = self.main, character = 'scout'}) self.player:add_follower(Player{group = self.main, character = 'swordsman'}) self.player:add_follower(Player{group = self.main, character = 'scout'}) self.player:add_follower(Player{group = self.main, character = 'wizard'}) diff --git a/assets/sounds/321215__hybrid-v__sci-fi-weapons-deploy.ogg b/assets/sounds/321215__hybrid-v__sci-fi-weapons-deploy.ogg new file mode 100644 index 0000000..2ce365d Binary files /dev/null and b/assets/sounds/321215__hybrid-v__sci-fi-weapons-deploy.ogg differ diff --git a/assets/sounds/Cannon impact sounds (Hitting ship) 4.ogg b/assets/sounds/Cannon impact sounds (Hitting ship) 4.ogg new file mode 100644 index 0000000..52e8363 Binary files /dev/null and b/assets/sounds/Cannon impact sounds (Hitting ship) 4.ogg differ diff --git a/assets/sounds/Cannon shots 1.ogg b/assets/sounds/Cannon shots 1.ogg new file mode 100644 index 0000000..27255a6 Binary files /dev/null and b/assets/sounds/Cannon shots 1.ogg differ diff --git a/assets/sounds/Cannon shots 7.ogg b/assets/sounds/Cannon shots 7.ogg new file mode 100644 index 0000000..50c9565 Binary files /dev/null and b/assets/sounds/Cannon shots 7.ogg differ diff --git a/assets/sounds/Concrete 6.ogg b/assets/sounds/Concrete 6.ogg new file mode 100644 index 0000000..1a1d63f Binary files /dev/null and b/assets/sounds/Concrete 6.ogg differ diff --git a/assets/sounds/Concrete 7.ogg b/assets/sounds/Concrete 7.ogg new file mode 100644 index 0000000..9b6c5d4 Binary files /dev/null and b/assets/sounds/Concrete 7.ogg differ diff --git a/assets/sounds/Sci Fi Machine Gun 7.ogg b/assets/sounds/Sci Fi Machine Gun 7.ogg new file mode 100644 index 0000000..47cb619 Binary files /dev/null and b/assets/sounds/Sci Fi Machine Gun 7.ogg differ diff --git a/assets/sounds/Sniper Shot_09.ogg b/assets/sounds/Sniper Shot_09.ogg new file mode 100644 index 0000000..8bde1e7 Binary files /dev/null and b/assets/sounds/Sniper Shot_09.ogg differ diff --git a/assets/sounds/Wolf barks 5.ogg b/assets/sounds/Wolf barks 5.ogg new file mode 100644 index 0000000..25c6073 Binary files /dev/null and b/assets/sounds/Wolf barks 5.ogg differ diff --git a/devlog.md b/devlog.md index 21c548a..fa9f422 100644 --- a/devlog.md +++ b/devlog.md @@ -201,6 +201,19 @@ I've implemented up to Elementor today and ATM in the process of doing Ninja, bu Not a lot done today... My sleep schedule is fucked up and I've been unable to focus properly. I managed to get 2 characters done though and also changed their definitions a bit: Ninja -> Saboteur: calls on other saboteurs to seek targets and explode on contact, AoE has small range + Ninja -> Saboteur: rogue, conjurer, nuker + Linker -> Stormweaver: infuses all projetile attacks with chain lightning, medium range + Linker -> Stormweaver: enchanter, ~~nuker~~ + +# Day 8-9 - 24-25/02/21 + +Finished all characters finally. My sleep is so fucked these two days blended together seamlessly. It's so fucking hot and I'm so tired. God damn I fucking hate the summer so fucking much. I hope I can sleep properly today. +Definition changes for one character: Spellblade - knives slowly spiral outwards. + +Tomorrow I'll probably do some UI work so the player can buy new characters as he goes from arena to arena, or work on the game's progression in terms of enemy HP and DMG. These are fundamentally the only two things missing +and I have a essentially 1 week to do them, which should be more than enough. + +Note: remember to attribute https://freesound.org/people/Hybrid_V/sounds/321215/ for turret_deploy sound in credits. diff --git a/enemies.lua b/enemies.lua index a4b545e..df1829d 100644 --- a/enemies.lua +++ b/enemies.lua @@ -31,7 +31,7 @@ function Seeker:update(dt) local player = main.current.player self:seek_point(player.x, player.y) self:wander(50, 100, 20) - self:separate(16, main.current.enemies) + self:steering_separate(16, main.current.enemies) self:rotate_towards_velocity(0.5) end self.r = self:get_angle() @@ -61,6 +61,11 @@ function Seeker:on_collision_enter(other, contact) for i = 1, 2 do HitParticle{group = main.current.effects, x = x, y = y, color = self.color} end hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} end + + elseif other:is(Turret) then + _G[random:table{'player_hit1', 'player_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.35} + self:hit(0) + self:push(random:float(2.5, 7), other:angle_to_object(self)) end end diff --git a/engine/game/steering.lua b/engine/game/steering.lua index a65efe0..a4f341e 100644 --- a/engine/game/steering.lua +++ b/engine/game/steering.lua @@ -8,6 +8,9 @@ function Physics:set_as_steerable(max_v, max_f, max_turn_rate, turn_multiplier) self.steering_enabled = true self.heading = Vector() self.side = Vector() + self.steering_force = Vector() + self.applied_force = Vector() + self.applied_impulse = Vector() self.mass = 1 self.max_v = max_v or 100 self.max_f = max_f or 2000 @@ -32,29 +35,33 @@ end function Physics:steering_update(dt) if self.steerable and self.steering_enabled then local steering_force = self:calculate_steering_force(dt):div(self.mass) - self:apply_force(steering_force.x, steering_force.y) + local applied_force = self:calculate_applied_force(dt):div(self.mass) + local applied_impulse = self:calculate_applied_impulse(dt):div(self.mass) + self:apply_force(steering_force.x + applied_force.x, steering_force.y + applied_force.y) local vx, vy = self:get_velocity() local v = Vector(vx, vy):truncate(self.max_v) - self:set_velocity(v.x, v.y) + self:set_velocity(v.x + applied_impulse.x, v.y + applied_impulse.y) if v:length_squared() > 0.00001 then self.heading = v:clone():normalize() self.side = self.heading:perpendicular() end + self.apply_force_f:set(0, 0) + self.apply_impulse_f:set(0, 0) end end function Physics:calculate_steering_force(dt) - local steering_force = Vector(0, 0) - if self.seeking then steering_force:add(self.seek_f) end - if self.fleeing then steering_force:add(self.flee_f) end - if self.pursuing then steering_force:add(self.pursuit_f) end - if self.evading then steering_force:add(self.evade_f) end - if self.wandering then steering_force:add(self.wander_f) end - if self.path_following then steering_force:add(self.path_follow_f) end - if self.separating then steering_force:add(self.separation_f) end - if self.aligning then steering_force:add(self.alignment_f) end - if self.cohesing then steering_force:add(self.cohesion_f) end + self.steering_force:set(0, 0) + if self.seeking then self.steering_force:add(self.seek_f) end + if self.fleeing then self.steering_force:add(self.flee_f) end + if self.pursuing then self.steering_force:add(self.pursuit_f) end + if self.evading then self.steering_force:add(self.evade_f) end + if self.wandering then self.steering_force:add(self.wander_f) end + if self.path_following then self.steering_force:add(self.path_follow_f) end + if self.separating then self.steering_force:add(self.separation_f) end + if self.aligning then self.steering_force:add(self.alignment_f) end + if self.cohesing then self.steering_force:add(self.cohesion_f) end self.seeking = false self.fleeing = false self.pursuing = false @@ -64,7 +71,55 @@ function Physics:calculate_steering_force(dt) self.separating = false self.aligning = false self.cohesing = false - return steering_force:truncate(self.max_f) + return self.steering_force:truncate(self.max_f) +end + + +function Physics:calculate_applied_force(dt) + self.applied_force:set(0, 0) + if self.applying_force then self.applied_force:add(self.apply_force_f) end + return self.applied_force +end + + +function Physics:calculate_applied_impulse(dt) + self.applied_impulse:set(0, 0) + if self.applying_impulse then self.applied_impulse:add(self.apply_impulse_f) end + return self.applied_impulse +end + + +-- Applies force f to the object at the given angle r for duration s +-- This plays along with steering behaviors, whereas the apply_force function simply applies it directly to the body and doesn't work when steering behaviors are enabled +-- self:apply_steering_force(100, math.pi/4) +function Physics:apply_steering_force(f, r, s) + self.applying_force = true + self.apply_force_f:set(f*math.cos(r), f*math.sin(r)) + if s then + self.t:after((s or 0.01)/2, function() + self.t:tween((s or 0.01)/2, self.apply_force_f, {x = 0, y = 0}, math.linear, function() + self.applying_force = false + self.apply_force_f:set(0, 0) + end, 'apply_steering_force_2') + end, 'apply_steering_force_1') + end +end + + +-- Applies impulse f to the object at the given angle r for duration s +-- This plays along with steering behaviors, whereas the apply_impulse function simply applies it directly to the body and doesn't work when steering behaviors are enabled +-- self:apply_steering_impulse(100, math.pi/4, 0.5) +function Physics:apply_steering_impulse(f, r, s) + self.applying_impulse = true + self.apply_impulse_f:set(f*math.cos(r), f*math.sin(r)) + if s then + self.t:after((s or 0.01)/2, function() + self.t:tween((s or 0.01)/2, self.apply_impulse_f, {x = 0, y = 0}, math.linear, function() + self.applying_impulse = false + self.apply_impulse_f:set(0, 0) + end, 'apply_steering_impulse_2') + end, 'apply_steering_impulse_1') + end end diff --git a/engine/game/trigger.lua b/engine/game/trigger.lua index 2bf8e69..33417ae 100644 --- a/engine/game/trigger.lua +++ b/engine/game/trigger.lua @@ -31,6 +31,20 @@ function Trigger:after(delay, action, tag) end +-- Calls the action every delay seconds if the condition is true. +-- If the condition isn't true when delay seconds are up then it waits and only performs the action and resets the timer when that happens. +-- If times is passed in then it only calls action for that amount of times. +-- If after is passed in then it is called after the last time action is called. +-- If tag is passed in then any other trigger actions with the same tag are automatically cancelled. +-- trigger:cooldown(2, function() return #self:get_objects_in_shape(self.attack_sensor, enemies) > 0 end, function() self:attack() end) -> only attacks when 2 seconds have passed and there are more than 0 enemies around +function Trigger:cooldown(delay, condition, action, times, after, tag) + local times = times or 0 + local after = after or function() end + local tag = tag or random:uid() + self.triggers[tag] = {type = "cooldown", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), condition = condition, action = action, times = times, max_times = times, after = after, multiplier = 1} +end + + -- Calls the action every delay seconds. -- Or calls the action once every time the condition becomes true. -- If times is passed in then it only calls action for that amount of times. @@ -162,6 +176,20 @@ function Trigger:update(dt) if trigger.type == "run" then trigger.action() + elseif trigger.type == "cooldown" then + if trigger.timer > trigger.delay*trigger.multiplier and trigger.condition() then + trigger.action() + trigger.timer = 0 + trigger.delay = self:resolve_delay(trigger.unresolved_delay) + if trigger.times > 0 then + trigger.times = trigger.times - 1 + if trigger.times <= 0 then + trigger.after() + self.triggers[tag] = nil + end + end + end + elseif trigger.type == "after" then if trigger.timer > trigger.delay then trigger.action() diff --git a/engine/graphics/graphics.lua b/engine/graphics/graphics.lua index 640c071..915a738 100644 --- a/engine/graphics/graphics.lua +++ b/engine/graphics/graphics.lua @@ -148,6 +148,14 @@ function graphics.circle(x, y, r, color, line_width) end +-- Draws an arc of radius r from angle r1 to angle r2 centered on x, y. +-- If color is passed in then the arc will be filled with that color (color is Color object) +-- If line_width is passed in then the arc will not be filled and will instead be drawn as a set of lines of the given width. +function graphics.arc(arctype, x, y, r, r1, r2, color, line_width) + graphics.shape("arc", color, line_width, arctype, x, y, r, r1, r2) +end + + -- Draws a polygon with the given points. -- If color is passed in then the polygon will be filled with that color (color is Color object) -- If line_width is passed in then the polygon will not be filled and will instead be drawn as a set of lines of the given width. diff --git a/main.lua b/main.lua index 501f193..15b7dca 100644 --- a/main.lua +++ b/main.lua @@ -52,6 +52,15 @@ function init() spark2 = Sound('Spark 2.ogg', s) spark3 = Sound('Spark 3.ogg', s) stormweaver1 = Sound('Buff 8.ogg', s) + cannoneer1 = Sound('Cannon shots 1.ogg', s) + cannoneer2 = Sound('Cannon shots 7.ogg', s) + cannon_hit_wall1 = Sound('Cannon impact sounds (Hitting ship) 4.ogg', s) + pet1 = Sound('Wolf barks 5.ogg', s) + turret1 = Sound('Sci Fi Machine Gun 7.ogg', s) + turret2 = Sound('Sniper Shot_09.ogg', s) + turret_hit_wall1 = Sound('Concrete 6.ogg', s) + turret_hit_wall2 = Sound('Concrete 7.ogg', s) + turret_deploy = Sound('321215__hybrid-v__sci-fi-weapons-deploy.ogg', s) main = Main() main:add(Arena'arena') diff --git a/objects.lua b/objects.lua index 7117c63..a7f1004 100644 --- a/objects.lua +++ b/objects.lua @@ -135,8 +135,7 @@ function Unit:init_unit() self.hfx:add('hit', 1) self.hfx:add('shoot', 1) self.hp_bar = HPBar{group = main.current.effects, parent = self} - self.heal_bar = HealBar{group = main.current.effects, parent = self} - self.infused_bar = InfusedBar{group = main.current.effects, parent = self} + self.effect_bar = EffectBar{group = main.current.effects, parent = self} end @@ -156,19 +155,36 @@ end function Unit:show_hp(n) self.hp_bar.hidden = false + self.hp_bar.color = red[0] self.t:after(n or 2, function() self.hp_bar.hidden = true end, 'hp_bar') end function Unit:show_heal(n) - self.heal_bar.hidden = false - self.t:after(n or 4, function() self.heal_bar.hidden = true end, 'heal_bar') + self.effect_bar.hidden = false + self.effect_bar.color = green[0] + self.t:after(n or 4, function() self.effect_bar.hidden = true end, 'effect_bar') end function Unit:show_infused(n) - self.infused_bar.hidden = false - self.t:after(n, function() self.infused_bar.hidden = true end, 'infused_bar') + self.effect_bar.hidden = false + self.effect_bar.color = blue[0] + self.t:after(n or 4, function() self.effect_bar.hidden = true end, 'effect_bar') +end + + +function Unit:show_squire(n) + self.effect_bar.hidden = false + self.effect_bar.color = purple[0] + self.t:after(n or 4, function() self.effect_bar.hidden = false end, 'effect_bar') +end + + +function Unit:show_chronomancer(n) + self.effect_bar.hidden = false + self.effect_bar.color = purple[0] + self.t:after(n or 2, function() self.effect_bar.hidden = false end, 'effect_bar') end @@ -198,17 +214,19 @@ function Unit:calculate_stats(first_run) self.class_area_size_m = 1 self.class_def_m = 1 self.class_mvspd_m = 1 - self.buff_hp_a = 0 - self.buff_dmg_a = 0 - self.buff_def_a = 0 - self.buff_mvspd_a = 0 - self.buff_hp_m = 1 - self.buff_dmg_m = 1 - self.buff_aspd_m = 1 - self.buff_area_dmg_m = 1 - self.buff_area_size_m = 1 - self.buff_def_m = 1 - self.buff_mvspd_m = 1 + if first_run then + self.buff_hp_a = 0 + self.buff_dmg_a = 0 + self.buff_def_a = 0 + self.buff_mvspd_a = 0 + self.buff_hp_m = 1 + self.buff_dmg_m = 1 + self.buff_aspd_m = 1 + self.buff_area_dmg_m = 1 + self.buff_area_size_m = 1 + self.buff_def_m = 1 + self.buff_mvspd_m = 1 + end for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_hp_m = self.class_hp_m*1.4 @@ -280,59 +298,30 @@ end -InfusedBar = Object:extend() -InfusedBar:implement(GameObject) -InfusedBar:implement(Parent) -function InfusedBar:init(args) +EffectBar = Object:extend() +EffectBar:implement(GameObject) +EffectBar:implement(Parent) +function EffectBar:init(args) self:init_game_object(args) self.hidden = true - self.color = blue[0] - self.color_transparent = Color(self.color.r, self.color.g, self.color.b, 0.2) + self.color = fg[0] end -function InfusedBar:update(dt) +function EffectBar:update(dt) self:update_game_object(dt) self:follow_parent_exclusively() end -function InfusedBar:draw() +function EffectBar:draw() if self.hidden then return end + --[[ local p = self.parent - graphics.push(p.x, p.y, 0, p.hfx.hit.x, p.hfx.hit.x) - graphics.rectangle(p.x, p.y, 1.25*p.shape.w, 1.25*p.shape.h, 2, 2, self.color_transparent) - graphics.rectangle(p.x, p.y, 1.25*p.shape.w, 1.25*p.shape.h, 2, 2, self.color, 1) - graphics.pop() -end - - - - -HealBar = Object:extend() -HealBar:implement(GameObject) -HealBar:implement(Parent) -function HealBar:init(args) - self:init_game_object(args) - self.hidden = true - self.color = green[0] - self.color_transparent = Color(self.color.r, self.color.g, self.color.b, 0.2) -end - - -function HealBar:update(dt) - self:update_game_object(dt) - self:follow_parent_exclusively() -end - - -function HealBar:draw() - if self.hidden then return end - local p = self.parent - graphics.push(p.x, p.y, 0, p.hfx.hit.x, p.hfx.hit.x) - graphics.rectangle(p.x, p.y, 1.25*p.shape.w, 1.25*p.shape.h, 2, 2, self.color_transparent) - graphics.rectangle(p.x, p.y, 1.25*p.shape.w, 1.25*p.shape.h, 2, 2, self.color, 1) + graphics.push(p.x, p.y, p.r, p.hfx.hit.x, p.hfx.hit.x) + graphics.rectangle(p.x, p.y, 3, 3, 1, 1, self.color) graphics.pop() + ]]-- end diff --git a/player.lua b/player.lua index ac53477..f291028 100644 --- a/player.lua +++ b/player.lua @@ -13,7 +13,7 @@ function Player:init(args) self.classes = {'ranger', 'warrior', 'psy'} self.attack_sensor = Circle(self.x, self.y, 96) - self.t:every(2, function() + 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)) @@ -27,11 +27,8 @@ function Player:init(args) self.classes = {'warrior'} self.attack_sensor = Circle(self.x, self.y, 64) - self.t:every(3, function() - local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies) - if enemies and #enemies > 0 then - self:attack(96) - end + self.t:cooldown(3, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + self:attack(96) end, nil, nil, 'attack') elseif self.character == 'wizard' then @@ -41,10 +38,10 @@ function Player:init(args) self.classes = {'mage'} self.attack_sensor = Circle(self.x, self.y, 128) - self.t:every(2, function() + 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), {wizard = self}) + self:shoot(self:angle_to_object(closest_enemy)) end end, nil, nil, 'shoot') @@ -55,7 +52,7 @@ function Player:init(args) self.classes = {'ranger'} self.attack_sensor = Circle(self.x, self.y, 160) - self.t:every(2, function() + self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then self:shoot(self:angle_to_object(closest_enemy), {pierce = 1000}) @@ -69,7 +66,7 @@ function Player:init(args) self.classes = {'rogue'} self.attack_sensor = Circle(self.x, self.y, 64) - self.t:every(2, function() + self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then self:shoot(self:angle_to_object(closest_enemy), {chain = 3}) @@ -102,7 +99,7 @@ function Player:init(args) self.classes = {'warrior', 'rogue'} self.attack_sensor = Circle(self.x, self.y, 96) - self.t:every(3, function() + self.t:cooldown(3, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) if closest_enemy then self:shoot(self:angle_to_object(closest_enemy)) @@ -116,11 +113,8 @@ function Player:init(args) self.classes = {'warrior', 'rogue'} self.attack_sensor = Circle(self.x, self.y, 64) - self.t:every(4, function() - local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies) - if enemies and #enemies > 0 then - self:shoot() - end + self.t:cooldown(4, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + self:shoot() end, nil, nil, 'shoot') elseif self.character == 'elementor' then @@ -130,7 +124,7 @@ function Player:init(args) self.classes = {'mage', 'nuker'} self.attack_sensor = Circle(self.x, self.y, 128) - self.t:every(12, function() + self.t:cooldown(12, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() local enemy = self:get_random_object_in_shape(self.attack_sensor, main.current.enemies) if enemy then self:attack(128, {x = enemy.x, y = enemy.y}) @@ -157,7 +151,6 @@ function Player:init(args) self.visual_shape = 'rectangle' self.classes = {'enchanter'} - self.attack_sensor = Circle(self.x, self.y, 96) self.t:every(8, function() stormweaver1:play{pitch = random:float(0.95, 1.05), volume = 0.5} local followers @@ -169,6 +162,142 @@ function Player:init(args) end end end) + + elseif self.character == 'sage' then + self.color = purple[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'mage', 'nuker'} + + self.attack_sensor = Circle(self.x, self.y, 96) + self.t:cooldown(12, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:shoot(self:angle_to_object(closest_enemy)) + end + end) + + elseif self.character == 'squire' then + self.color = green[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'warrior', 'healer', 'enchanter'} + + self.t:every(8, function() + self.applying_buff = true + local followers + local leader = (self.leader and self) or self.parent + if self.leader then followers = self.followers else followers = self.parent.followers end + local next_character = followers[self.follower_index + 1] + local previous_character = followers[self.follower_index - 1] + if next_character then next_character:squire_buff(8) end + if previous_character then previous_character:squire_buff(8) end + self.t:after(8, function() self.applying_buff = false end, 'squire_buff_apply') + heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + if next_character then next_character:heal(0.1*next_character.max_hp) end + if previous_character then previous_character:heal(0.1*previous_character.max_hp) end + end) + + elseif self.character == 'cannoneer' then + self.color = yellow[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'ranger', 'nuker'} + + self.attack_sensor = Circle(self.x, self.y, 128) + self.t:cooldown(6, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:shoot(self:angle_to_object(closest_enemy)) + end + end, nil, nil, 'shoot') + + elseif self.character == 'dual_gunner' then + self.color = yellow[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'ranger', 'rogue'} + + self.attack_sensor = Circle(self.x, self.y, 96) + self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:shoot(self:angle_to_object(closest_enemy)) + end + end, nil, nil, 'shoot') + + elseif self.character == 'hunter' then + self.color = orange[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'ranger', 'rogue'} + + self.attack_sensor = Circle(self.x, self.y, 160) + self.t:cooldown(2, function() local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies); return enemies and #enemies > 0 end, function() + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:shoot(self:angle_to_object(closest_enemy)) + end + end, nil, nil, 'shoot') + + elseif self.character == 'chronomancer' then + self.color = purple[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'mage', 'enchanter'} + + self.t:every(2, function() + local followers + local leader = (self.leader and self) or self.parent + if self.leader then followers = self.followers else followers = self.parent.followers end + local next_character = followers[self.follower_index + 1] + local previous_character = followers[self.follower_index - 1] + if next_character then next_character:chronomancer_buff(2) end + if previous_character then previous_character:chronomancer_buff(2) end + end) + + elseif self.character == 'spellblade' then + self.color = blue[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'mage', 'rogue'} + + self.t:every(2, function() + self:shoot(random:float(0, 2*math.pi)) + end, nil, nil, 'shoot') + + elseif self.character == 'psykeeper' then + self.color = fg[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'healer', 'psy'} + + self.psykeeper_heal = 0 + self.t:every(8, function() + local followers + local leader = (self.leader and self) or self.parent + if self.leader then followers = self.followers else followers = self.parent.followers end + + if self.psykeeper_heal > 0 then + local heal_amount = math.floor(self.psykeeper_heal/(#followers+1)) + if self.leader then self:heal(heal_amount) else self.parent:heal(heal_amount) end + for _, f in ipairs(followers) do f:heal(heal_amount) end + heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.psykeeper_heal = 0 + end + end) + + elseif self.character == 'engineer' then + self.color = orange[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'conjurer'} + + self.t:every(8, function() + SpawnEffect{group = main.current.effects, x = self.x, y = self.y, color = orange[0], action = function(x, y) + Turret{group = main.current.main, x = x, y = y, parent = self} + end} + end) end self:calculate_stats(true) @@ -184,13 +313,55 @@ end function Player:update(dt) + if self.attack_sensor then self.enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies) end self:update_game_object(dt) + + if self.character == 'squire' then + local followers + local leader = (self.leader and self) or self.parent + if self.leader then followers = self.followers else followers = self.parent.followers end + local next_character = followers[self.follower_index + 1] + local previous_character = followers[self.follower_index - 1] + if self.applying_buff then + if next_character then + next_character.squire_dmg_a = 10 + next_character.squire_def_a = 25 + end + if previous_character then + previous_character.squire_dmg_a = 10 + previous_character.squire_def_a = 25 + end + else + if next_character then + next_character.squire_dmg_a = 0 + next_character.squire_def_a = 0 + end + if previous_character then + previous_character.squire_dmg_a = 0 + previous_character.squire_def_a = 0 + end + end + + elseif self.character == 'chronomancer' then + local followers + local leader = (self.leader and self) or self.parent + if self.leader then followers = self.followers else followers = self.parent.followers end + local next_character = followers[self.follower_index + 1] + local previous_character = followers[self.follower_index - 1] + if next_character then next_character.chronomancer_aspd_m = 1.25 end + if previous_character then previous_character.chronomancer_aspd_m = 1.25 end + end + + self.buff_dmg_a = self.squire_dmg_a or 0 + self.buff_def_a = self.squire_def_a or 0 + self.buff_aspd_m = self.chronomancer_aspd_m or 1 self:calculate_stats() if self.attack_sensor then self.attack_sensor:move_to(self.x, self.y) end self.t:set_every_multiplier('shoot', self.aspd_m) self.t:set_every_multiplier('attack', self.aspd_m) + if self.leader then if input.move_left.down then self.r = self.r - 1.66*math.pi*dt end if input.move_right.down then self.r = self.r + 1.66*math.pi*dt end @@ -210,7 +381,7 @@ function Player:update(dt) self:set_angle(self.r) else - local target_distance = 10.6*self.follower_index + local target_distance = 10.4*self.follower_index local distance_sum = 0 local p local previous = self.parent @@ -246,7 +417,6 @@ function Player:draw() graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 3, 3, (self.hfx.hit.f or self.hfx.shoot.f) and fg[0] or self.color) end graphics.pop() - -- self.attack_sensor:draw(self.color, 2) end @@ -292,6 +462,8 @@ function Player:hit(damage) _G[random:table{'player_hit1', 'player_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} camera:shake(4, 0.5) + if self.character == 'psykeeper' then self.psykeeper_heal = self.psykeeper_heal + actual_damage end + if self.hp <= 0 then slow(0.25, 1) self.dead = true @@ -319,6 +491,16 @@ function Player:chain_infuse(duration) end +function Player:squire_buff(duration) + self:show_squire(duration or 2) +end + + +function Player:chronomancer_buff(duration) + self:show_chronomancer(duration or 2) +end + + function Player:add_follower(unit) table.insert(self.followers, unit) unit.parent = self @@ -338,6 +520,7 @@ function Player:shoot(r, mods) Projectile(table.merge(t, mods or {})) r = r + math.pi/8 end + elseif self.character == 'blade' then local enemies = self:get_objects_in_shape(self.attack_sensor, main.current.enemies) if enemies and #enemies > 0 then @@ -348,20 +531,41 @@ function Player:shoot(r, mods) Projectile(table.merge(t, mods or {})) end end + + elseif self.character == 'sage' then + HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6} + local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 25, r = r, color = self.color, dmg = self.dmg, pierce = 1000, character = 'sage', parent = self} + Projectile(table.merge(t, mods or {})) + + elseif self.character == 'dual_gunner' then + HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r) + 4*math.cos(r - math.pi/2), y = self.y + 0.8*self.shape.w*math.sin(r) + 4*math.sin(r - math.pi/2), rs = 6} + HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r) + 4*math.cos(r + math.pi/2), y = self.y + 0.8*self.shape.w*math.sin(r) + 4*math.sin(r + math.pi/2), rs = 6} + local t1 = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r) + 4*math.cos(r - math.pi/2) , y = self.y + 1.6*self.shape.w*math.sin(r) + 4*math.sin(r - math.pi/2), + v = 250, r = r, color = self.color, dmg = self.dmg, character = self.character, parent = self} + local t2 = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r) + 4*math.cos(r + math.pi/2) , y = self.y + 1.6*self.shape.w*math.sin(r) + 4*math.sin(r + math.pi/2), + v = 250, r = r, color = self.color, dmg = self.dmg, character = self.character, parent = self} + Projectile(table.merge(t1, mods or {})) + Projectile(table.merge(t2, mods or {})) + else HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6} local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.color, dmg = self.dmg, character = self.character, parent = self} Projectile(table.merge(t, mods or {})) end - if self.character == 'vagrant' then + if self.character == 'vagrant' or self.character == 'dual_gunner' then shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.3} - elseif self.character == 'archer' then + elseif self.character == 'archer' or self.character == 'hunter' then archer1:play{pitch = random:float(0.95, 1.05), volume = 0.5} elseif self.character == 'wizard' then wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.15} - elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' then + elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'spellblade' then _G[random:table{'scout1', 'scout2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} + if self.character == 'spellblade' then + wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.15} + end + elseif self.character == 'cannoneer' then + _G[random:table{'cannoneer1', 'cannoneer2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end end @@ -393,21 +597,71 @@ function Projectile:init(args) self.chain = args.chain or 0 self.chain_enemies_hit = {} self.infused_enemies_hit = {} + + if self.character == 'sage' then + self.dmg = 0 + self.pull_sensor = Circle(self.x, self.y, 64*self.parent.area_size_m) + self.rs = 0 + self.t:tween(0.05, self, {rs = self.shape.w/2.5}, math.cubic_in_out, function() self.spring:pull(0.15) end) + self.t:after(4, function() + self.t:every_immediate(0.05, function() self.hidden = not self.hidden end, 7, function() self:die() end) + end) + + self.color_transparent = Color(args.color.r, args.color.g, args.color.b, 0.08) + self.t:every(0.08, function() + HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color} + end) + self.vr = 0 + self.dvr = random:float(-math.pi/4, math.pi/4) + + elseif self.character == 'spellblade' then + self.pierce = 1000 + self.orbit_r = 0 + self.orbit_vr = 8*math.pi + self.t:tween(6.25, self, {orbit_vr = math.pi}, math.expo_out, function() + self.t:tween(12.25, self, {orbit_vr = 0}, math.linear) + end) + end end function Projectile:update(dt) self:update_game_object(dt) + if self.character == 'spellblade' then + self.orbit_r = self.orbit_r + self.orbit_vr*dt + end + self:set_angle(self.r) - self:move_along_angle(self.v, self.r) + self:move_along_angle(self.v, self.r + (self.orbit_r or 0)) + + if self.character == 'sage' then + self.pull_sensor:move_to(self.x, self.y) + local enemies = self:get_objects_in_shape(self.pull_sensor, main.current.enemies) + for _, enemy in ipairs(enemies) do + enemy:apply_steering_force(math.remap(self:distance_to_object(enemy), 0, 100, 250, 50), enemy:angle_to_object(self)) + end + self.vr = self.vr + self.dvr*dt + end end function Projectile:draw() - graphics.push(self.x, self.y, self.r) - graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 2, 2, self.color) - graphics.pop() + if self.character == 'sage' then + if self.hidden then return end + + graphics.push(self.x, self.y, self.r + self.vr, self.spring.x, self.spring.x) + graphics.circle(self.x, self.y, self.rs + random:float(-1, 1), self.color) + graphics.circle(self.x, self.y, self.pull_sensor.rs, self.color_transparent) + local lw = math.remap(self.pull_sensor.rs, 32, 256, 2, 4) + for i = 1, 4 do graphics.arc('open', self.x, self.y, self.pull_sensor.rs, (i-1)*math.pi/2 + math.pi/4 - math.pi/8, (i-1)*math.pi/2 + math.pi/4 + math.pi/8, self.color, lw) end + graphics.pop() + + else + graphics.push(self.x, self.y, self.r + (self.orbit_r or 0)) + graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 2, 2, self.color) + graphics.pop() + end end @@ -424,6 +678,8 @@ function Projectile:die(x, y, r, n) Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*32, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} elseif self.character == 'blade' then Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*64, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} + elseif self.character == 'cannoneer' then + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*96, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} end end @@ -438,20 +694,29 @@ function Projectile:on_collision_enter(other, contact) else r = 0 end if other:is(Wall) then - if self.character == 'archer' then + if self.character == 'archer' or self.character == 'hunter' then self:die(x, y, r, 0) _G[random:table{'arrow_hit_wall1', 'arrow_hit_wall2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.2} WallArrow{group = main.current.main, x = x, y = y, r = self.r, color = self.color} - elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' then + elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' or self.character == 'spellblade' then self:die(x, y, r, 0) knife_hit_wall1:play{pitch = random:float(0.9, 1.1), volume = 0.2} local r = Unit.bounce(self, nx, ny) trigger:after(0.01, function() WallKnife{group = main.current.main, x = x, y = y, r = r, v = self.v*0.1, color = self.color} end) + if self.character == 'spellblade' then + magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075} + end elseif self.character == 'wizard' then self:die(x, y, r, random:int(2, 3)) magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.075} + elseif self.character == 'cannoneer' then + self:die(x, y, r, random:int(2, 3)) + cannon_hit_wall1:play{pitch = random:float(0.95, 1.05), volume = 0.1} + elseif self.character == 'engineer' then + self:die(x, y, r, random:int(2, 3)) + _G[random:table{'turret_hit_wall1', 'turret_hit_wall2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.2} else self:die(x, y, r, random:int(2, 3)) proj_hit_wall1:play{pitch = random:float(0.9, 1.1), volume = 0.2} @@ -461,6 +726,8 @@ end function Projectile:on_trigger_enter(other, contact) + if self.character == 'sage' then return end + if table.any(main.current.enemies, function(v) return other:is(v) end) then if self.pierce <= 0 and self.chain <= 0 then self:die(self.x, self.y, nil, random:int(2, 3)) @@ -482,9 +749,11 @@ function Projectile:on_trigger_enter(other, contact) HitParticle{group = main.current.effects, x = self.x, y = self.y, color = other.color} end - - if self.character == 'archer' or self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' then + 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' then hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} + if self.character == 'spellblade' then + magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15} + end elseif self.character == 'wizard' then magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15} else @@ -493,6 +762,14 @@ function Projectile:on_trigger_enter(other, contact) other:hit(self.dmg) + if self.character == 'hunter' and random:bool(40) then + trigger:after(0.01, function() + SpawnEffect{group = main.current.effects, x = self.parent.x, y = self.parent.y, color = orange[0], action = function(x, y) + Pet{group = main.current.main, x = x, y = y, r = self.parent:angle_to_object(other), v = 150, parent = self.parent} + end} + end) + end + if self.parent.chain_infused then local src = other for i = 1, 2 do @@ -532,6 +809,8 @@ function Area:init(args) hit2:play{pitch = random:float(0.95, 1.05), volume = 0.2} elseif self.character == 'saboteur' then _G[random:table{'saboteur_hit1', 'saboteur_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.2} + elseif self.character == 'cannoneer' then + _G[random:table{'saboteur_hit1', 'saboteur_hit2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.075} end end @@ -559,10 +838,11 @@ function Area:draw() local w10 = self.w/10 local x1, y1 = self.x - w, self.y - w local x2, y2 = self.x + w, self.y + w - graphics.polyline(self.color, 2, x1, y1 + w10, x1, y1, x1 + w10, y1) - graphics.polyline(self.color, 2, x2 - w10, y1, x2, y1, x2, y1 + w10) - graphics.polyline(self.color, 2, x2 - w10, y2, x2, y2, x2, y2 - w10) - graphics.polyline(self.color, 2, x1, y2 - w10, x1, y2, x1 + w10, y2) + local lw = math.remap(w, 32, 256, 2, 4) + graphics.polyline(self.color, lw, x1, y1 + w10, x1, y1, x1 + w10, y1) + graphics.polyline(self.color, lw, x2 - w10, y1, x2, y1, x2, y1 + w10) + graphics.polyline(self.color, lw, x2 - w10, y2, x2, y2, x2, y2 - w10) + graphics.polyline(self.color, lw, x1, y2 - w10, x1, y2, x1 + w10, y2) graphics.rectangle((x1+x2)/2, (y1+y2)/2, x2-x1, y2-y1, nil, nil, self.color_transparent) graphics.pop() end @@ -570,6 +850,133 @@ end +Turret = Object:extend() +Turret:implement(GameObject) +Turret:implement(Physics) +function Turret:init(args) + self:init_game_object(args) + self:set_as_rectangle(14, 6, 'static', 'player') + self:set_restitution(0.5) + self.hfx:add('hit', 1) + self.color = orange[0] + self.attack_sensor = Circle(self.x, self.y, 96) + turret_deploy:play{pitch = 1.2, volume = 0.2} + + self.t:every({3.5, 4.5}, function() + self.t:every({0.1, 0.2}, function() + self.hfx:use('hit', 0.25, 200, 10) + HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(self.r), y = self.y + 0.8*self.shape.w*math.sin(self.r), rs = 6} + local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(self.r), y = self.y + 1.6*self.shape.w*math.sin(self.r), v = 200, r = self.r, color = self.color, dmg = self.parent.dmg, + character = self.parent.character, parent = self.parent} + Projectile(table.merge(t, mods or {})) + turret1:play{pitch = random:float(0.95, 1.05), volume = 0.35} + turret2:play{pitch = random:float(0.95, 1.05), volume = 0.35} + end, 3) + end) + + self.t:after(24, 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 + end) +end + + +function Turret:update(dt) + self:update_game_object(dt) + + local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies) + if closest_enemy then + self:rotate_towards_object(closest_enemy, 0.2) + self.r = self:get_angle() + end +end + + +function Turret: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 + + + + +Pet = Object:extend() +Pet:implement(GameObject) +Pet:implement(Physics) +function Pet:init(args) + self:init_game_object(args) + self:set_as_rectangle(8, 8, 'dynamic', 'projectile') + self:set_restitution(0.5) + self.hfx:add('hit', 1) + self.color = orange[0] + self.pierce = 6 + pet1:play{pitch = random:float(0.95, 1.05), volume = 0.35} +end + + +function Pet:update(dt) + self:update_game_object(dt) + + self:set_angle(self.r) + self:move_along_angle(self.v, self.r) +end + + +function Pet: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 Pet:on_collision_enter(other, contact) + local x, y = contact:getPositions() + local nx, ny = contact:getNormal() + local r = 0 + if nx == 0 and ny == -1 then r = -math.pi/2 + elseif nx == 0 and ny == 1 then r = math.pi/2 + elseif nx == -1 and ny == 0 then r = math.pi + else r = 0 end + + if other:is(Wall) then + local n = n or random:int(3, 4) + for i = 1, n do HitParticle{group = main.current.effects, x = x, y = y, r = random:float(0, 2*math.pi), color = self.color} end + HitCircle{group = main.current.effects, x = x, y = y}:scale_down() + self.dead = true + hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} + end +end + + +function Pet:on_trigger_enter(other) + if table.any(main.current.enemies, function(v) return other:is(v) end) then + if self.pierce <= 0 then + camera:shake(2, 0.5) + other:hit(self.parent.dmg) + other:push(35, self:angle_to_object(other)) + self.dead = true + local n = random:int(3, 4) + for i = 1, n do HitParticle{group = main.current.effects, x = x, y = y, r = random:float(0, 2*math.pi), color = self.color} end + HitCircle{group = main.current.effects, x = x, y = y}:scale_down() + else + camera:shake(2, 0.5) + other:hit(self.parent.dmg) + other:push(35, self:angle_to_object(other)) + self.pierce = self.pierce - 1 + end + self.hfx:use('hit', 0.25) + HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = fg[0], duration = 0.1} + HitParticle{group = main.current.effects, x = self.x, y = self.y, color = self.color} + HitParticle{group = main.current.effects, x = self.x, y = self.y, color = other.color} + end +end + + + + Saboteur = Object:extend() Saboteur:implement(GameObject) Saboteur:implement(Physics) diff --git a/todo b/todo index 4b56aca..6983700 100644 --- a/todo +++ b/todo @@ -9,15 +9,15 @@ Blade: shoots multiple blades at nearby enemies, each dealing AoE damage on cont Elementor: deals massive AoE damage to a random target, long range, AoE has medium range Saboteur: calls on other saboteurs to seek targets and explode on contact, AoE has small range Stormweaver: infuses all projectile attacks with chain lightning, small range -Sage: shoots a slow projectile that draws enemies in, medium range, AoE has medium range +Sage: shoots a slow projectile that draws enemies in, medium range, AoE has small range Squire: improves damage and defense for adjacent units, as well as healing them periodically Cannoneer: shoots a projectile at any nearby enemy and deals massive AoE damage on contact, long range, AoE has medium range Dual Gunner: shoots two parallel projectiles at any nearby enemy, medium range Hunter: shoots an arrow at any nearby enemy with a chance to summon a pet that will trample through enemies knocking them away, arrow has long range, pet has small range Chronomancer: dramatically improves attack speed for adjacent units -Spellblade: knives orbit you and hoam towards nearby enemies, small range +Spellblade: knives spiral outwards and pierce enemies Psykeeper: all damage taken is stored and distributed as healing -Gambler: drops a sentry that uses random attacks, medium range +Engineer: drops a sentry that uses random attacks, medium range Ranger: yellow, chance to release a barrage Warrior: orange, increased defense @@ -39,8 +39,7 @@ Outlaw [rogue, warrior] Blade [warrior, nuker] Elementor [mage, nuker] Saboteur [rogue, conjurer, nuker] - -Linker [enchanter] +Stormweaver [enchanter] Sage [mage, nuker] Squire [warrior, healer, enchanter] Cannoneer [ranger, nuker] @@ -49,7 +48,8 @@ Hunter [ranger, conjurer] Chronomancer [mage, enchanter] Spellblade [mage, rogue] Psykeeper [healer, psy] -Gambler [conjurer] + +Engineer [conjurer] Ranger [2, 4] (5) Warrior [2, 4] (5)