From 9d3f7773421384c8a1d68c336a9051b0d4352dae Mon Sep 17 00:00:00 2001 From: a327ex Date: Sun, 21 Feb 2021 02:47:59 -0300 Subject: [PATCH] Day 4 --- arena.lua | 256 +++++++++++++++++++++++++++++++++++++++++++++++++--- devlog.md | 24 +++++ objects.lua | 17 +++- player.lua | 84 +++++++++++++++-- shared.lua | 1 + todo | 16 ++-- 6 files changed, 369 insertions(+), 29 deletions(-) diff --git a/arena.lua b/arena.lua index 6e5cfb7..587464a 100644 --- a/arena.lua +++ b/arena.lua @@ -7,7 +7,11 @@ function Arena:init(name) end -function Arena:on_enter(from) +function Arena:on_enter(from, level) + self.hfx:add('condition1', 1) + self.hfx:add('condition2', 1) + self.level = level or 1 + self.main = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'}) self.effects = Group() self.ui = Group():no_camera() @@ -34,13 +38,15 @@ function Arena:on_enter(from) {x = gw/2, y = gh/2, r = random:float(0, 2*math.pi)} } self.spawn_offsets = {{x = -12, y = -12}, {x = 12, y = -12}, {x = 12, y = 12}, {x = -12, y = 12}, {x = 0, y = 0}} + self.last_spawn_enemy_time = love.timer.getTime() Wall{group = self.main, vertices = math.to_rectangle_vertices(-40, -40, self.x1, gh + 40), color = bg[-1]} Wall{group = self.main, vertices = math.to_rectangle_vertices(self.x2, -40, gw + 40, gh + 40), color = bg[-1]} Wall{group = self.main, vertices = math.to_rectangle_vertices(self.x1, -40, self.x2, self.y1), color = bg[-1]} Wall{group = self.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 = 'vagrant'} - self.player:add_follower(Player{group = self.main, character = 'swordsman'}) + + self.player = Player{group = self.main, x = gw/2, y = gh/2, leader = true, character = 'scout'} + --self.player:add_follower(Player{group = self.main, character = 'scout'}) --[[ self.player:add_follower(Player{group = self.main, character = 'vagrant'}) self.player:add_follower(Player{group = self.main, character = 'vagrant'}) @@ -48,6 +54,98 @@ function Arena:on_enter(from) self.player:add_follower(Player{group = self.main, character = 'vagrant'}) self.player:add_follower(Player{group = self.main, character = 'vagrant'}) ]]-- + + self.win_condition = random:table{'time', 'enemy_kill', 'wave'} + if self.win_condition == 'wave' then + self.level_to_max_waves = { + 2, 2, random:int(2, 3), + 3, 3, 3, random:int(3, 4), + 4, 4, 4, 4, random:int(4, 5), + 5, 5, 5, 5, 5, random:int(5, 6), + 6, 7, 8, 9, 9, 10 + } + self.max_waves = self.level_to_max_waves[self.level] + self.start_time = 3 + self.t:after(1, function() + self.t:every(1, function() + self.start_time = self.start_time - 1 + self.hfx:use('condition1', 0.25, 200, 10) + end, 3, function() + camera:shake(4, 0.25) + SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48} + self.wave = 0 + self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() + self.wave = self.wave + 1 + 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}} + self:spawn_n_enemies(spawn_points[spawn_type], nil, 8 + (self.wave-1)*2) + end) + end, self.max_waves, function() self.can_quit = true end) + end) + end) + + elseif self.win_condition == 'enemy_kill' then + self.level_to_enemies_to_kill = { + 16, 16, random:int(16, 18), + 18, 18, 18, random:int(18, 20), + 20, 20, 20, 20, random:int(20, 22), + 22, 22, 22, 22, 22, random:int(22, 24), + 24, 26, 28, 30, 30, 32 + } + self.enemies_killed = 0 + self.enemies_to_kill = self.level_to_enemies_to_kill[self.level] + self.enemy_spawn_delay = 8 + self.start_time = 3 + self.t:after(1, function() + self.t:every(1, function() + self.start_time = self.start_time - 1 + self.hfx:use('condition1', 0.25, 200, 10) + end, 3, function() + camera:shake(4, 0.25) + SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48} + self:spawn_distributed_enemies() + self.t:every(2, function() + if love.timer.getTime() - self.last_spawn_enemy_time >= self.enemy_spawn_delay then + self:spawn_distributed_enemies() + end + end, nil, nil, 'spawn_enemies') + end) + end) + + elseif self.win_condition == 'time' then + self.level_to_time_left = { + 20, 20, random:int(20, 25), + 25, 25, 25, random:int(25, 30), + 30, 30, 30, 30, random:int(30, 35), + 35, 35, 35, 35, 35, random:int(35, 40), + 40, 45, 50, 55, 55, 60 + } + self.time_left = self.level_to_time_left[self.level] + self.start_time = 3 + self.t:after(1, function() + self.t:every(1, function() + self.start_time = self.start_time - 1 + self.hfx:use('condition1', 0.25, 200, 10) + end, 3, function() + camera:shake(4, 0.25) + SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48} + self.t:every(1, function() + self.time_left = self.time_left - 1 + self.hfx:use('condition1', 0.25, 200, 10) + self.hfx:pull('condition2', 0.0625) + end, self.time_left, function() self.can_quit = true end) + + self.t:every_immediate(2, function() + if #self.main:get_objects_by_classes(self.enemies) <= 0 or love.timer.getTime() - self.last_spawn_enemy_time >= 8 then + self:spawn_distributed_enemies() + end + end, self.time_left/2) + end) + end) + end end @@ -57,8 +155,24 @@ function Arena:update(dt) self.effects:update(dt*slow_amount) self.ui:update(dt*slow_amount) - if input.k.pressed then - self:spawn_enemy(4) + if self.win_condition == 'enemy_kill' then + if self.can_quit then + self.t:after(2, function() + -- PostArenaScreen{group = self.ui, x = gw/2, y = gh/2} + end) + end + + else + if self.can_quit and #self.main:get_objects_by_classes(self.enemies) <= 0 then + self.can_quit = false + self.t:after(2, function() + if #self.main:get_objects_by_classes(self.enemies) > 0 then + self.can_quit = true + else + -- PostArenaScreen{group = self.ui, x = gw/2, y = gh/2} + end + end) + end end end @@ -67,16 +181,132 @@ function Arena:draw() self.main:draw() self.effects:draw() self.ui:draw() + + camera:attach() + if self.start_time and self.start_time > 0 then + graphics.push(gw/2, gh/2 - 48, 0, self.hfx.condition1.x, self.hfx.condition1.x) + graphics.print_centered(tostring(self.start_time), fat_font, gw/2, gh/2 - 48, 0, 1, 1, nil, nil, self.hfx.condition1.f and fg[0] or red[0]) + 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() + end + end + end + camera:detach() end -function Arena:spawn_enemy(n) - n = n or 1 - local p = table.random(self.spawn_points) - for i = 1, n do - self.t:after((i-1)*0.1, function() - local o = table.random(self.spawn_offsets) - SpawnEffect{group = self.effects, x = p.x + o.x, y = p.y + o.y, action = function(x, y) Seeker{group = self.main, x = x, y = y, character = 'seeker'} end} - end) +function Arena:enemy_killed() + if self.win_condition == 'enemy_kill' then + self.enemies_killed = self.enemies_killed + 1 + self.hfx:use('condition1', 0.25, 200, 10) + self.hfx:pull('condition2', 0.0625) + self.enemy_spawn_delay = self.enemy_spawn_delay*0.95 + if self.enemies_killed >= self.enemies_to_kill then + self.can_quit = true + self.t:cancel'spawn_enemies' + end end end + + +function Arena:spawn_distributed_enemies() + local t = {'4', '4+4', '4+4+4', '2x4', '3x4', '4x2'} + local spawn_type = t[random:weighted_pick(40, 20, 5, 15, 5, 15)] + local spawn_points = table.copy(self.spawn_points) + if spawn_type == '4' then + self:spawn_n_enemies(random:table_remove(spawn_points)) + elseif spawn_type == '4+4' then + local p = random:table_remove(spawn_points) + self:spawn_n_enemies(p) + self.t:after(2, function() self:spawn_n_enemies(p) end) + elseif spawn_type == '4+4+4' then + local p = random:table_remove(spawn_points) + self:spawn_n_enemies(p) + self.t:after(1, function() + self:spawn_n_enemies(p) + self.t:after(1, function() + self:spawn_n_enemies(p) + end) + end) + elseif spawn_type == '2x4' then + self:spawn_n_enemies(random:table_remove(spawn_points), 1) + self:spawn_n_enemies(random:table_remove(spawn_points), 2) + elseif spawn_type == '3x4' then + self:spawn_n_enemies(random:table_remove(spawn_points), 1) + self:spawn_n_enemies(random:table_remove(spawn_points), 2) + self:spawn_n_enemies(random:table_remove(spawn_points), 3) + elseif spawn_type == '4x2' then + self:spawn_n_enemies(random:table_remove(spawn_points), 1, 2) + self:spawn_n_enemies(random:table_remove(spawn_points), 2, 2) + self:spawn_n_enemies(random:table_remove(spawn_points), 3, 2) + self:spawn_n_enemies(random:table_remove(spawn_points), 4, 2) + end +end + + +function Arena:spawn_n_enemies(p, j, n) + j = j or 1 + n = n or 4 + self.last_spawn_enemy_time = love.timer.getTime() + self.t:every(0.1, function() + local o = self.spawn_offsets[(self.t:get_every_iteration('spawn_enemies_' .. j) % 5) + 1] + SpawnEffect{group = self.effects, x = p.x + o.x, y = p.y + o.y, action = function(x, y) + if self.level == 1 then + Seeker{group = self.main, x = x, y = y, character = 'seeker'} + elseif self.level == 2 then + Seeker{group = self.main, x = x, y = y, character = 'seeker'} + elseif self.level == 3 then + elseif self.level == 4 then + elseif self.level == 5 then + elseif self.level == 6 then + elseif self.level == 7 then + elseif self.level == 8 then + elseif self.level == 9 then + elseif self.level == 10 then + elseif self.level == 11 then + elseif self.level == 12 then + elseif self.level == 13 then + elseif self.level == 14 then + elseif self.level == 15 then + elseif self.level == 16 then + elseif self.level == 17 then + elseif self.level == 18 then + elseif self.level == 19 then + elseif self.level == 20 then + elseif self.level == 21 then + elseif self.level == 22 then + elseif self.level == 23 then + elseif self.level == 24 then + elseif self.level == 25 then + end + end} + end, n, nil, 'spawn_enemies_' .. j) +end diff --git a/devlog.md b/devlog.md index bea9b42..83ac525 100644 --- a/devlog.md +++ b/devlog.md @@ -66,3 +66,27 @@ And the stats are: HP, damage and defense are flat stats, whereas area damage, area of effect and attack speed are multipliers. This is because each character/attack has its own attack speed/area and trying to generalize that too much wouldn't work well. For tomorrow I'll just try to finish the rest of the todo, which is add more characters, port enemy spawning logic from SHOOTRX and add sounds. + +# Day 4 - 20/02/21 + +Ported over enemy spawning logic and added all characters. The characters currently are: + +* Vagrant: shoots an ethereal projectile at any nearby enemy that deals physical and magical damage, medium range +* Scout: throws a knife that chains 3 times at any nearby enemy, small range +* Cleric: heals every unit when any one drops below 50% HP +* Swordsman: deals physical damage in an area around the unit, small range +* Archer: shoots an arrow that pierces at any nearby enemy, very long range +* Wizard: shoots a projectile at any nearby enemy and deals AoE magical damage on contact, small range, AoE has very small range + +The classes are: + +* Ranger: yellow, buff attack speed +* Warrior: orange, buff attack damage +* Healer: green, buff healing effectiveness +* Mage: blue, debuff enemy defense +* Void: purple, buff area damage and size +* Builder: orange, buffs construct damage, attack speed and duration +* Rogue: red, chance to crit dealing 4x damage + +I'm not sure what I should focus on next. I know that there's sounds left to add, but after that I should probably start doing the actual game progression, but for that I think need a bunch more characters. +I should probably try to think of a cast of maybe 15-20 characters (however that number should be as low as possible) that fills up the current classes to their multiple levels. Levels possible are: 1, 2, 3, 2/4, 2/4/6 and 3/6. diff --git a/objects.lua b/objects.lua index e1ef867..7c064e4 100644 --- a/objects.lua +++ b/objects.lua @@ -46,6 +46,10 @@ function Unit:hit(damage) 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 HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 12}:scale_down(0.3):change_color(0.5, self.color) + + if table.any(main.current.enemies, function(v) return self:is(v) end) then + main.current:enemy_killed() + end end end @@ -93,6 +97,7 @@ function Unit:calculate_stats(first_run) elseif class == 'mage' then self.class_hp_m = self.class_hp_m*0.6 elseif class == 'healer' then self.class_hp_m = self.class_hp_m*1.1 elseif class == 'void' then self.class_hp_m = self.class_hp_m*0.9 + elseif class == 'rogue' then self.class_hp_m = self.class_hp_m*0.8 elseif class == 'seeker' then self.class_hp_m = self.class_hp_m*0.5 end end self.max_hp = (self.base_hp + self.class_hp_a + self.buff_hp_a)*self.class_hp_m*self.buff_hp_m @@ -101,27 +106,31 @@ function Unit:calculate_stats(first_run) for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_dmg_m = self.class_dmg_m*1.1 elseif class == 'ranger' then self.class_dmg_m = self.class_dmg_m*1.2 + elseif class == 'rogue' then self.class_dmg_m = self.class_dmg_m*1.2 elseif class == 'mage' then self.class_dmg_m = self.class_dmg_m*1.4 end end self.dmg = (self.base_dmg + self.class_dmg_a + self.buff_dmg_a)*self.class_dmg_m*self.buff_dmg_m for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_aspd_m = self.class_aspd_m*0.9 - elseif class == 'ranger' then self.class_aspd_m = self.class_aspd_m*1.4 + elseif class == 'ranger' then self.class_aspd_m = self.class_aspd_m*1.5 elseif class == 'healer' then self.class_aspd_m = self.class_aspd_m*0.5 + elseif class == 'rogue' then self.class_aspd_m = self.class_aspd_m*1.1 elseif class == 'void' then self.class_aspd_m = self.class_aspd_m*0.75 end end self.aspd_m = 1/(self.base_aspd_m*self.class_aspd_m*self.buff_aspd_m) for _, class in ipairs(self.classes) do if class == 'mage' then self.class_area_dmg_m = self.class_area_dmg_m*1.25 - elseif class == 'void' then self.class_area_dmg_m = self.class_area_m*1.5 end + elseif class == 'void' then self.class_area_dmg_m = self.class_area_dmg_m*1.5 + elseif class == 'rogue' then self.class_area_dmg_m = self.class_area_dmg_m*0.6 end end self.area_dmg_m = self.base_area_dmg_m*self.class_area_dmg_m*self.buff_area_dmg_m for _, class in ipairs(self.classes) do if class == 'mage' then self.class_area_size_m = self.class_area_size_m*1.2 - elseif class == 'void' then self.class_area_size_m = self.class_area_m*1.3 end + elseif class == 'void' then self.class_area_size_m = self.class_area_size_m*1.3 + elseif class == 'rogue' then self.class_area_size_m = self.class_area_size_m*0.6 end end self.area_size_m = self.base_area_size_m*self.class_area_size_m*self.buff_area_size_m @@ -129,6 +138,7 @@ function Unit:calculate_stats(first_run) if class == 'warrior' then self.class_def_m = self.class_def_m*1.25 elseif class == 'ranger' then self.class_def_m = self.class_def_m*1.1 elseif class == 'mage' then self.class_def_m = self.class_def_m*0.8 + elseif class == 'rogue' then self.class_def_m = self.class_def_m*0.8 elseif class == 'healer' then self.class_def_m = self.class_def_m*1.2 end end self.def = (self.base_def + self.class_def_a + self.buff_def_a)*self.class_def_m*self.buff_def_m @@ -136,6 +146,7 @@ function Unit:calculate_stats(first_run) for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_mvspd_m = self.class_mvspd_m*0.9 elseif class == 'ranger' then self.class_mvspd_m = self.class_mvspd_m*1.2 + elseif class == 'rogue' then self.class_mvspd_m = self.class_mvspd_m*1.4 elseif class == 'seeker' then self.class_mvspd_m = self.class_mvspd_m*0.3 end end self.v = (self.base_mvspd + self.class_mvspd_a + self.buff_mvspd_a)*self.class_mvspd_m*self.buff_mvspd_m diff --git a/player.lua b/player.lua index 528b010..cb7577e 100644 --- a/player.lua +++ b/player.lua @@ -7,7 +7,7 @@ function Player:init(args) self:init_unit() if self.character == 'vagrant' then - self.color = blue[0] + self.color = fg[0] self:set_as_rectangle(9, 9, 'dynamic', 'player') self.visual_shape = 'rectangle' self.classes = {'ranger', 'warrior', 'mage'} @@ -33,6 +33,48 @@ function Player:init(args) self:attack() end end, nil, nil, 'attack') + + elseif self.character == 'wizard' then + self.color = blue[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'mage'} + + self.attack_sensor = Circle(self.x, self.y, 64) + self.t:every(2, 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}) + end + end, nil, nil, 'shoot') + + elseif self.character == 'archer' then + self.color = yellow[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'ranger'} + + self.attack_sensor = Circle(self.x, self.y, 160) + self.t:every(2, 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}) + end + end, nil, nil, 'shoot') + + elseif self.character == 'scout' then + self.color = red[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'rogue'} + + self.attack_sensor = Circle(self.x, self.y, 64) + self.t:every(2, 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}) + end + end, nil, nil, 'shoot') end self:calculate_stats(true) @@ -51,6 +93,8 @@ function Player:update(dt) self:update_game_object(dt) self:calculate_stats() + if self.character == 'archer' then print(self.aspd_m) end + self.attack_sensor:move_to(self.x, self.y) self.t:set_every_multiplier('shoot', self.aspd_m) self.t:set_every_multiplier('attack', self.aspd_m) @@ -109,6 +153,7 @@ 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 @@ -138,18 +183,20 @@ function Player:add_follower(unit) end -function Player:shoot(r) +function Player:shoot(r, mods) camera:spring_shake(2, r) self.hfx:use('shoot', 0.25) 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} - Projectile{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} + 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} + Projectile(table.merge(t, mods or {})) end -function Player:attack() +function Player:attack(mods) camera:shake(2, 0.5) self.hfx:use('shoot', 0.25) - Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.area_size_m*64, color = self.color, dmg = self.area_dmg_m*self.dmg} + local t = {group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.area_size_m*64, color = self.color, dmg = self.area_dmg_m*self.dmg} + Area(table.merge(t, mods or {})) end @@ -161,6 +208,9 @@ Projectile:implement(Physics) function Projectile:init(args) self:init_game_object(args) self:set_as_rectangle(10, 4, 'dynamic', 'projectile') + self.pierce = args.pierce or 0 + self.chain = args.chain or 0 + self.chain_enemies_hit = {} end @@ -187,6 +237,10 @@ function Projectile:die(x, y, r, n) 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 + + if self.wizard then + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.wizard.area_size_m*32, color = self.color, dmg = self.wizard.area_dmg_m*self.dmg} + end end @@ -207,7 +261,25 @@ end function Projectile:on_trigger_enter(other, contact) if table.any(main.current.enemies, function(v) return other:is(v) end) then - self:die(self.x, self.y, nil, random:int(2, 3)) + if self.pierce <= 0 and self.chain <= 0 then + self:die(self.x, self.y, nil, random:int(2, 3)) + else + if self.pierce > 0 then + self.pierce = self.pierce - 1 + end + if self.chain > 0 then + self.chain = self.chain - 1 + table.insert(self.chain_enemies_hit, other) + local object = self:get_random_object_in_shape(Circle(self.x, self.y, 160), main.current.enemies, self.chain_enemies_hit) + if object then + self.r = self:angle_to_object(object) + self.v = self.v*1.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 + end other:hit(self.dmg) end end diff --git a/shared.lua b/shared.lua index 3ad55a1..70d27ff 100644 --- a/shared.lua +++ b/shared.lua @@ -7,6 +7,7 @@ function shared_init() fg_alt = ColorRamp(Color'#b0a89f', 0.025), yellow = ColorRamp(Color'#facf00', 0.025), orange = ColorRamp(Color'#f07021', 0.025), + yellow_orange = ColorRamp(Color(245, 160, 16), 0.025), blue = ColorRamp(Color'#019bd6', 0.025), green = ColorRamp(Color'#8bbf40', 0.025), red = ColorRamp(Color'#e91d39', 0.025), diff --git a/todo b/todo index 13c7736..751e480 100644 --- a/todo +++ b/todo @@ -1,16 +1,18 @@ Vagrant: shoots an ethereal projectile at any nearby enemy that deals physical and magical damage, medium range -Scout: throws a knife at any nearby enemy that deals physical damage and chains, small range +Scout: throws a knife that chains 3 times at any nearby enemy, small range Cleric: heals every unit when any one drops below 50% HP Swordsman: deals physical damage in an area around the unit, small range -Archer: shoots an arrow at any nearby enemy in front of the unit, very long range -Wizard: shoots a projectile at any nearby enemy and deals AoE magical damage on contact, small range -Engineer: drops a turret that shoots secondary projectiles very fast, long range +Archer: shoots an arrow that pierces at any nearby enemy, very long range +Wizard: shoots a projectile at any nearby enemy and deals AoE magical damage on contact, small range, AoE has very small range +Engineer: drops a turret that shoots secondary projectiles very fast, medium range Ranger: yellow, buff attack speed Warrior: orange, buff attack damage Healer: green, buff healing effectiveness Mage: blue, debuff enemy defense Void: purple, buff area damage and size +Builder: orange, buffs construct damage, attack speed and duration +Rogue: red, chance to crit dealing 4x damage HP Damage @@ -22,7 +24,7 @@ Defense -> if defense >= 0 then dmg_m = 100/(100+defense) else dmg_m = 2-100/(10 * HP bar should be drawn on top of all player units * Projectiles * Areas -* Stats: attack speed, damage -One or a few of the characters -Port over enemy spawn logic from SHOOTRX +* Stats: attack speed, damage, area +* One or a few of the characters +* Port over enemy spawn logic from SHOOTRX Sounds