diff --git a/arena.lua b/arena.lua index e2730df..8ef0e4b 100644 --- a/arena.lua +++ b/arena.lua @@ -73,7 +73,37 @@ function Arena:on_enter(from, level, units) self.level_1000_text = Text2{group = self.ui, x = gw/2, y = gh/2, lines = {{text = '[fg, wavy_mid]SNKRX', font = fat_font, alignment = 'center'}}} -- self.level_1000_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 64, lines = {{text = '[fg, wavy_mid]SNKRX', font = pixul_font, alignment = 'center'}}} -- Wall{group = self.main, vertices = math.to_rectangle_vertices(gw/2 - 0.45*self.level_1000_text.w, gh/2 - 0.3*self.level_1000_text.h, gw/2 + 0.45*self.level_1000_text.w, gh/2 - 3), snkrx = true, color = bg[-1]} - + + elseif self.level == 6 or self.level == 12 or self.level == 18 or self.level == 24 or self.level == 25 then + self.boss_level = true + self.start_time = 3 + self.t:after(1, function() + self.t:every(1, function() + if self.start_time > 1 then alert1:play{volume = 0.5} end + self.start_time = self.start_time - 1 + self.hfx:use('condition1', 0.25, 200, 10) + end, 3, function() + alert1:play{pitch = 1.2, volume = 0.5} + camera:shake(4, 0.25) + SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48} + SpawnEffect{group = self.effects, x = gw/2, y = gh/2, action = function(x, y) + spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15} + self.boss = Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = boss_by_level[self.level]} + end} + self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 1 end, function() + self.hfx:use('condition1', 0.25, 200, 10) + self.hfx:pull('condition2', 0.0625) + self.t:after(0.5, function() + local spawn_type = random:table{'left', 'middle', 'right'} + local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}} + local p = spawn_points[spawn_type] + SpawnMarker{group = self.effects, x = p.x, y = p.y} + self.t:after(0.75, function() self:spawn_n_enemies(p, nil, 8 + math.floor(self.level/2)) end) + end) + end) + end) + self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.can_quit = true end) + end) else -- Set win condition and enemy spawns self.win_condition = random:table{'time', 'enemy_kill', 'wave'} @@ -254,6 +284,13 @@ end function Arena:update(dt) + if input.k.pressed then + SpawnEffect{group = self.effects, x = gw/2, y = gh/2, action = function(x, y) + spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15} + Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = 'forcer'} + end} + end + if input.escape.pressed and not self.transitioning then if not self.paused then trigger:tween(0.25, _G, {slow_amount = 0}, math.linear, function() @@ -320,11 +357,6 @@ function Arena:update(dt) self:update_game_object(dt*slow_amount) -- cascade_instance.pitch = math.clamp(slow_amount*self.main_slow_amount, 0.05, 1) - if input.k.pressed then - local enemies = self.main:get_objects_by_classes(self.enemies) - for _, enemy in ipairs(enemies) do enemy:hit(1000000000000) end - end - if self.enchanter_level == 1 then self.enchanter_dmg_m = 1.25 else self.enchanter_dmg_m = 1 end @@ -413,33 +445,41 @@ function Arena:draw() graphics.pop() end - if self.win_condition then - if self.win_condition == 'time' then - if self.start_time <= 0 then - graphics.push(self.x2 - 66, self.y1 - 9, 0, self.hfx.condition2.x, self.hfx.condition2.x) - graphics.print_centered('time left:', fat_font, self.x2 - 66, self.y1 - 9, 0, 0.6, 0.6, nil, nil, fg[0]) - graphics.pop() - graphics.push(self.x2 - 18 + fat_font:get_text_width(tostring(self.time_left))/2, self.y1 - 8, 0, self.hfx.condition1.x, self.hfx.condition1.x) - graphics.print(tostring(self.time_left), fat_font, self.x2 - 18, self.y1 - 8, 0, 0.75, 0.75, nil, fat_font.h/2, self.hfx.condition1.f and fg[0] or yellow[0]) - graphics.pop() - end - elseif self.win_condition == 'wave' then - if self.start_time <= 0 then - graphics.push(self.x2 - 50, self.y1 - 10, 0, self.hfx.condition2.x, self.hfx.condition2.x) - graphics.print_centered('wave:', fat_font, self.x2 - 50, self.y1 - 10, 0, 0.6, 0.6, nil, nil, fg[0]) - graphics.pop() - graphics.push(self.x2 - 25 + fat_font:get_text_width(self.wave .. '/' .. self.max_waves)/2, self.y1 - 8, 0, self.hfx.condition1.x, self.hfx.condition1.x) - graphics.print(self.wave .. '/' .. self.max_waves, fat_font, self.x2 - 25, self.y1 - 8, 0, 0.75, 0.75, nil, fat_font.h/2, self.hfx.condition1.f and fg[0] or yellow[0]) - graphics.pop() - end - elseif self.win_condition == 'enemy_kill' then - if self.start_time <= 0 then - graphics.push(self.x2 - 106, self.y1 - 10, 0, self.hfx.condition2.x, self.hfx.condition2.x) - graphics.print_centered('enemies killed:', fat_font, self.x2 - 106, self.y1 - 10, 0, 0.6, 0.6, nil, nil, fg[0]) - graphics.pop() - graphics.push(self.x2 - 41 + fat_font:get_text_width(self.enemies_killed .. '/' .. self.enemies_to_kill)/2, self.y1 - 8, 0, self.hfx.condition1.x, self.hfx.condition1.x) - graphics.print(self.enemies_killed .. '/' .. self.enemies_to_kill, fat_font, self.x2 - 41, self.y1 - 8, 0, 0.75, 0.75, nil, fat_font.h/2, self.hfx.condition1.f and fg[0] or yellow[0]) - graphics.pop() + if self.boss_level then + if self.start_time <= 0 then + graphics.push(self.x2 - 106, self.y1 - 10, 0, self.hfx.condition2.x, self.hfx.condition2.x) + graphics.print_centered('kill the elite', fat_font, self.x2 - 106, self.y1 - 10, 0, 0.6, 0.6, nil, nil, fg[0]) + graphics.pop() + end + else + if self.win_condition then + if self.win_condition == 'time' then + if self.start_time <= 0 then + graphics.push(self.x2 - 66, self.y1 - 9, 0, self.hfx.condition2.x, self.hfx.condition2.x) + graphics.print_centered('time left:', fat_font, self.x2 - 66, self.y1 - 9, 0, 0.6, 0.6, nil, nil, fg[0]) + graphics.pop() + graphics.push(self.x2 - 18 + fat_font:get_text_width(tostring(self.time_left))/2, self.y1 - 8, 0, self.hfx.condition1.x, self.hfx.condition1.x) + graphics.print(tostring(self.time_left), fat_font, self.x2 - 18, self.y1 - 8, 0, 0.75, 0.75, nil, fat_font.h/2, self.hfx.condition1.f and fg[0] or yellow[0]) + graphics.pop() + end + elseif self.win_condition == 'wave' then + if self.start_time <= 0 then + graphics.push(self.x2 - 50, self.y1 - 10, 0, self.hfx.condition2.x, self.hfx.condition2.x) + graphics.print_centered('wave:', fat_font, self.x2 - 50, self.y1 - 10, 0, 0.6, 0.6, nil, nil, fg[0]) + graphics.pop() + graphics.push(self.x2 - 25 + fat_font:get_text_width(self.wave .. '/' .. self.max_waves)/2, self.y1 - 8, 0, self.hfx.condition1.x, self.hfx.condition1.x) + graphics.print(self.wave .. '/' .. self.max_waves, fat_font, self.x2 - 25, self.y1 - 8, 0, 0.75, 0.75, nil, fat_font.h/2, self.hfx.condition1.f and fg[0] or yellow[0]) + graphics.pop() + end + elseif self.win_condition == 'enemy_kill' then + if self.start_time <= 0 then + graphics.push(self.x2 - 106, self.y1 - 10, 0, self.hfx.condition2.x, self.hfx.condition2.x) + graphics.print_centered('enemies killed:', fat_font, self.x2 - 106, self.y1 - 10, 0, 0.6, 0.6, nil, nil, fg[0]) + graphics.pop() + graphics.push(self.x2 - 41 + fat_font:get_text_width(self.enemies_killed .. '/' .. self.enemies_to_kill)/2, self.y1 - 8, 0, self.hfx.condition1.x, self.hfx.condition1.x) + graphics.print(self.enemies_killed .. '/' .. self.enemies_to_kill, fat_font, self.x2 - 41, self.y1 - 8, 0, 0.75, 0.75, nil, fat_font.h/2, self.hfx.condition1.f and fg[0] or yellow[0]) + graphics.pop() + end end end end diff --git a/assets/sounds/Magical Impact 18.ogg b/assets/sounds/Magical Impact 18.ogg new file mode 100644 index 0000000..72257b8 Binary files /dev/null and b/assets/sounds/Magical Impact 18.ogg differ diff --git a/devlog.md b/devlog.md index 64f8eff..5fe3672 100644 --- a/devlog.md +++ b/devlog.md @@ -780,3 +780,9 @@ Added a spawn marker so that it's easier for the player to tell where enemies ar * yellow - Resistance to knockback and increased HP * white - Remain static and shoot projectiles * purple - Explodes into critters on death + +# Day 46 - 03/04/21 + +* Adding mini bosses + * Speed Booster - grants speed boost to nearby enemies + * Forcer - pulls enemies together into a point and pushes them out diff --git a/enemies.lua b/enemies.lua index 35e62ca..88ec68e 100644 --- a/enemies.lua +++ b/enemies.lua @@ -5,15 +5,80 @@ Seeker:implement(Unit) function Seeker:init(args) self:init_game_object(args) self:init_unit() - self:set_as_rectangle(14, 6, 'dynamic', 'enemy') - self:set_restitution(0.5) - self.color = red[0]:clone() - self.classes = {'seeker'} - self:calculate_stats(true) - self:set_as_steerable(self.v, 2000, 4*math.pi, 4) + if self.boss then + self:set_as_rectangle(18, 7, 'dynamic', 'enemy') + self:set_restitution(0.5) + self.classes = {'mini_boss'} + self:calculate_stats(true) + self:set_as_steerable(self.v, 1000, 2*math.pi, 2) - self.spawner = random:bool(25) + if self.boss == 'speed_booster' then + self.color = green[0]:clone() + self.t:every(5, function() + local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 128), main.current.enemies) + if #enemies > 0 then + buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = green[0], duration = 0.1} + for _, enemy in ipairs(enemies) do + LightningLine{group = main.current.effects, src = self, dst = enemy, color = green[0]} + enemy:speed_boost(3 + self.level*0.1) + end + end + end) + + elseif self.boss == 'forcer' then + self.color = yellow[0]:clone() + self.t:every(6, function() + local enemies = main.current.main:get_objects_by_classes(main.current.enemies) + local x, y = 0, 0 + if #enemies > 0 then + for _, enemy in ipairs(enemies) do + x = x + enemy.x + y = y + enemy.y + end + x = x/#enemies + y = y/#enemies + else + x, y = player.x, player.y + end + self.px, self.py = x + random:float(-16, 16), y + random:float(-16, 16) + self.pull_sensor = Circle(self.px, self.py, 160) + self.prs = 0 + self.t:tween(0.05, self, {prs = 4}, math.cubic_in_out, function() self.spring:pull(0.15) end) + self.t:after(2 - 0.05*7, function() + self.t:every_immediate(0.05, function() self.phidden = not self.phidden end, 7) + end) + self.color_transparent = Color(yellow[0].r, yellow[0].g, yellow[0].b, 0.08) + self.t:every(0.08, function() HitParticle{group = main.current.effects, x = self.px, y = self.py, color = yellow[0]} end, math.floor(2/0.08)) + self.vr = 0 + self.dvr = random:float(-math.pi/4, math.pi/4) + + force1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + self.t:during(2, function(dt) + 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(enemy:distance_to_point(self.px, self.py), 0, 160, 400, 200), enemy:angle_to_point(self.px, self.py)) + end + self.vr = self.vr + self.dvr*dt + end, function() + wizard1:play{pitch = random:float(0.95, 1.05), volume = 0.5} + local enemies = self:get_objects_in_shape(self.pull_sensor, main.current.enemies) + for _, enemy in ipairs(enemies) do + enemy:push(random:float(40, 80), enemy:angle_to_object(main.current.player), true) + end + self.px, self.py = nil, nil + end) + end) + end + else + self:set_as_rectangle(14, 6, 'dynamic', 'enemy') + self:set_restitution(0.5) + self.color = red[0]:clone() + self.classes = {'seeker'} + self:calculate_stats(true) + self:set_as_steerable(self.v, 2000, 4*math.pi, 4) + end --[[ if random:bool(35) then @@ -99,6 +164,7 @@ function Seeker:update(dt) if self.being_pushed then local v = math.length(self:get_velocity()) if v < 25 then + if self.push_invulnerable then self.push_invulnerable = false end self.being_pushed = false self.steering_enabled = true self:set_damping(0) @@ -110,8 +176,25 @@ function Seeker:update(dt) self:rotate_towards_object(main.current.player, 0.5) elseif not self.headbutting then local player = main.current.player - self:seek_point(player.x, player.y) - self:wander(50, 100, 20) + if self.boss then + local enemies = main.current.main:get_objects_by_classes(main.current.enemies) + local x, y = 0, 0 + if #enemies > 0 then + for _, enemy in ipairs(enemies) do + x = x + enemy.x + y = y + enemy.y + end + x = x/#enemies + y = y/#enemies + else + x, y = player.x, player.y + end + self:seek_point(x, y) + self:wander(10, 250, 3) + else + self:seek_point(player.x, player.y) + self:wander(50, 100, 20) + end self:steering_separate(16, main.current.enemies) self:rotate_towards_velocity(0.5) end @@ -124,8 +207,22 @@ end function Seeker: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) + 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) + 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) + end graphics.pop() + + if self.px and self.py then + if self.phidden then return end + graphics.push(self.px, self.py, self.vr, self.spring.x, self.spring.x) + graphics.circle(self.px, self.py, self.prs + random:float(-1, 1), yellow[0]) + graphics.circle(self.px, self.py, 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.px, self.py, 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, yellow[0], lw) end + graphics.pop() + end end @@ -163,6 +260,7 @@ end function Seeker:hit(damage, projectile) if self.dead then return end self.hfx:use('hit', 0.25, 200, 10) + if self.push_invulnerable then return end self:show_hp() local actual_damage = self:calculate_damage(damage) @@ -208,9 +306,11 @@ function Seeker:hit(damage, projectile) end -function Seeker:push(f, r) +function Seeker:push(f, r, push_invulnerable) local n = 1 if self.tank then n = 0.4 - 0.01*self.level end + if self.boss then n = 0.1 end + self.push_invulnerable = push_invulnerable self.push_force = n*f self.being_pushed = true self.steering_enabled = false diff --git a/engine/game/trigger.lua b/engine/game/trigger.lua index 33417ae..477a2c9 100644 --- a/engine/game/trigger.lua +++ b/engine/game/trigger.lua @@ -231,8 +231,9 @@ function Trigger:update(dt) trigger.last_condition = condition elseif trigger.type == "during" then - trigger.action() + trigger.action(dt) if trigger.timer > trigger.delay then + trigger.after() self.triggers[tag] = nil end diff --git a/main.lua b/main.lua index 6d5a06a..257550a 100644 --- a/main.lua +++ b/main.lua @@ -27,6 +27,7 @@ function init() headbutt1 = Sound('Wind Bolt 14.ogg', s) critter1 = Sound('Critters eating 2.ogg', s) critter2 = Sound('Crickets Chirping 4.ogg', s) + force1 = Sound('Magical Impact 18.ogg', s) error1 = Sound('Error 2.ogg', s) coins1 = Sound('Coins 7.ogg', s) coins2 = Sound('Coins 8.ogg', s) @@ -263,6 +264,7 @@ function init() ['enchanter'] = {hp = 1.2, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1.2, mvspd = 1.2}, ['psy'] = {hp = 1.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 0.5, mvspd = 1}, ['seeker'] = {hp = 0.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.3}, + ['mini_boss'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.3}, ['enemy_critter'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.5}, ['saboteur'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 1.4}, } @@ -414,6 +416,14 @@ function init() [25] = {100, 100}, } + boss_by_level = { + [6] = 'speed_booster', + [12] = 'exploder', + [18] = 'swarmer', + [24] = 'bouncer', + [25] = 'orbitter', + } + gold = 2 main = Main() @@ -453,13 +463,6 @@ function update(dt) music.volume = 0.5 end end - - if input.k.pressed then - print(steam.user.getPlayerSteamLevel()) - print(steam.user.getSteamID()) - print(steam.friends.getFriendPersonaName(steam.user.getSteamID())) - print(steam.utils.getAppID()) - end end diff --git a/objects.lua b/objects.lua index a9da518..e1867f0 100644 --- a/objects.lua +++ b/objects.lua @@ -227,11 +227,19 @@ function Unit:calculate_stats(first_run) self.base_dmg = 10*math.pow(2, self.level-1) self.base_mvspd = 75 elseif self:is(Seeker) then - local x = self.level - local y = {0, 1, 4, 2, 3, 6, 3, 5, 9, 4, 6, 11, 7, 9, 15, 8, 10, 18, 9, 11, 21, 14, 15, 24, 25} - self.base_hp = 50 + 55*y[x] - self.base_dmg = 10 + 3*y[x] - self.base_mvspd = 70 + 3*y[x] + if self.boss then + local x = self.level + local y = {0, 1, 4, 2, 3, 6, 3, 5, 9, 4, 6, 11, 7, 9, 15, 8, 10, 18, 9, 11, 21, 14, 15, 24, 25} + self.base_hp = 250 + 275*y[x] + self.base_dmg = 50 + 15*y[x] + self.base_mvspd = 35 + 1.5*y[x] + else + local x = self.level + local y = {0, 1, 4, 2, 3, 6, 3, 5, 9, 4, 6, 11, 7, 9, 15, 8, 10, 18, 9, 11, 21, 14, 15, 24, 25} + self.base_hp = 50 + 55*y[x] + self.base_dmg = 10 + 3*y[x] + self.base_mvspd = 70 + 3*y[x] + end elseif self:is(Saboteur) then self.base_hp = 100*math.pow(2, self.level-1) self.base_dmg = 10*math.pow(2, self.level-1) diff --git a/todo b/todo index b6ec0bf..aea028e 100644 --- a/todo +++ b/todo @@ -7,7 +7,7 @@ * Psykeeper: stores damage taken by all allies up to 50% its max HP and redistributes it as healing * Squire: +5%/10%/15% damage and defense to all allies -7. Enemy modifiers +* 7. Enemy modifiers * green - Grant nearby enemies a speed boost on death * blue - Explode into projectiles on death * orange - Charge up and headbutt towards the player at increased speed and damage @@ -16,8 +16,11 @@ * purple - Explodes into critters on death 6. Mini boss every 3rd level - This is just a special enemy with more HP and ability to buff nearby enemies with modifiers, no additional AI or attack patterns - ... aiming for ~5 different modifier combos that the boss uses + * Speed Booster - grants speed boost to nearby enemies + Swarmer - explodes enemies into a swarm of critters + Exploder - explodes enemies into projectiles + * Forcer - pulls enemies together into a point and pushes them out + Orbitter - spawns shooters that orbit the boss 8. Additional characters and classes 9. Lv.3 effects for every character