diff --git a/arena.lua b/arena.lua index f1302a1..46c677d 100644 --- a/arena.lua +++ b/arena.lua @@ -50,12 +50,14 @@ 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 = 'swordsman'} + self.player = Player{group = self.main, x = gw/2, y = gh/2, leader = true, character = 'elementor'} + --[[ self.player:add_follower(Player{group = self.main, character = 'archer'}) self.player:add_follower(Player{group = self.main, character = 'vagrant'}) self.player:add_follower(Player{group = self.main, character = 'cleric'}) self.player:add_follower(Player{group = self.main, character = 'scout'}) self.player:add_follower(Player{group = self.main, character = 'wizard'}) + ]]-- self.win_condition = random:table{'time', 'enemy_kill', 'wave'} if self.win_condition == 'wave' then diff --git a/assets/sounds/Wind Bolt 18.ogg b/assets/sounds/Wind Bolt 18.ogg new file mode 100644 index 0000000..22a08ab Binary files /dev/null and b/assets/sounds/Wind Bolt 18.ogg differ diff --git a/devlog.md b/devlog.md index c2d706e..ff436f1 100644 --- a/devlog.md +++ b/devlog.md @@ -97,3 +97,101 @@ Sounds done for everything. Surprising to me how much sounds added to the game a I should probably make it a habit to add sounds earlier rather than later from now on. Tomorrow I should probably ideaguy the full set of characters and classes that I'll need so that the game is playable and start on implementing those additional characters as well as some class bonuses. + +# Day 6 - 22/02/21 + +Ideaguyed the entire roster for the demo and implemented a few of them. + +### Classes + +| Class | Color | Set Effect | +| --- | --- | --- | +| Ranger | yellow | chance to release a barrage | +| Warrior | orange | increased defense | +| Healer | green | increased healing effectiveness | +| Mage | blue | decreased enemy defense | +| Nuker | purple | increased area damage and size | +| Conjurer | orange | increased construct damage and duration | +| Rogue | red | chance to crit dealing 4x damage | +| Enchanter | pink | increased damage to all allies | +| Psy | white | returns damage taken based on number of active psy units | + +### Characters + +| Character | Description | Trigger Range | Effect Range | +| --- | --- | --- | --- | +| Vagrant | shoots a projectile | medium | nil | +| Scout | throws a knife that chains 3 times | small | nil | +| Cleric | heals every unit when any one drops below 50% HP | nil | nil | +| Swordsman | deals physical damage in an area around the unit | small | medium | +| Archer | shoots an arrow that pierces | very long | nil | +| Wizard | shoots a projectile that deals AoE damage | long | very small | +| Outlaw | throws a fan of 5 knives | medium | nil | +| Blade | shoots multiple blades that deal AoE damage on contact | small | small | +| Elementor | deals massive AoE damage to a random target | long | medium | +| Ninja | creates clones that roam and shoot shurikens | nil | very small | +| Linker | links nearby enemies together making them share damage taken | medium | small | +| Sage | shoots a slow projectile that draws enemies in | medium | medium | +| Squire | improves damage and defense for adjacent units as well as healing them periodically | nil | nil | +| Cannoneer | shoots a projectile that deals massive AoE damage | long | medium | +| Dual Gunner | shoots two parallel projectiles | medium | nil | +| Hunter | shoots an arrow with a chance to summon a pet | long | small | +| Chronomancer | dramatically improves attack speed for adjacent units | nil | nil | +| Spellblade | knives orbit you and hoam towards nearby enemies | small | small | +| Psykeeper | all damage taken is stored and distributed as healing | nil | nil | +| Gambler | drops a sentry that uses random attacks | nil | medium | + +### Character Classes + +| Character | Classes | +| --- | --- | +| Vagrant | warrior, ranger, psy | +| Scout | rogue | +| Cleric | healer | +| Swordsman | warrior | +| Archer | ranger | +| Wizard | mage | +| Outlaw | rogue, warrior | +| Blade | warrior, nuker | +| Elementor | mage, nuker | +| Ninja | rogue, conjurer | +| Linker | enchanter, nuker | +| Sage | mage, nuker | +| Squire | warrior, healer, enchanter | +| Cannoneer | ranger, nuker | +| Dual | unner [ranger, rogue | +| Hunter | ranger, conjurer | +| Chronomancer | mage, enchanter | +| Spellblade | mage, rogue | +| Psykeeper | healer, psy | +| Gambler | conjurer | + +### Class Numbers + +| Class | Set Levels | Total Units | +| --- | --- | --- | +| Ranger | 2, 4 | 5 | +| Warrior | 2, 4 | 5 | +| Healer | 3 | 3 | +| Mage | 2, 4 | 5 | +| Nuker | 2, 4 | 5 | +| Conjurer | 2 | 3 | +| Rogue | 2, 4 | 5 | +| Enchanter | 3 | 3 | +| Psy | n | 2 | + +### Class Stat Multipliers + +| Class | HP | DMG | ASPD | Area DMG | Area Size | DEF | MVSPD | +| --- | --- | --- | --- | --- | --- | --- | --- | +| Warrior | 1.4 | 1.1 | 0.9 | 1.0 | 1.0 | 1.25 | 0.9 | +| Ranger | 1.0 | 1.2 | 1.5 | 1.0 | 1.0 | 0.9 | 1.2 | +| Healer | 1.2 | 1.0 | 0.5 | 1.0 | 1.0 | 1.2 | 1.0 | +| Mage | 0.6 | 1.4 | 1.0 | 1.25 | 1.25 | 0.75 | 1.0 | +| Rogue | 0.8 | 1.3 | 1.1 | 0.6 | 0.6 | 0.8 | 1.4 | +| Nuker | 0.9 | 1.4 | 0.75 | 1.5 | 1.3 | 1.0 | 1.0 | +| Conjurer | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | +| Enchanter | 1.2 | 1.0 | 1.0 | 1.0 | 1.0 | 1.2 | 1.2 | +| Psy | 1.5 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 1.0 | + +I've implemented up to Elementor today and ATM in the process of doing Ninja, but today seems like a particularly low energy day so I'm just going to play some games instead. diff --git a/main.lua b/main.lua index 05b63b9..8d3411d 100644 --- a/main.lua +++ b/main.lua @@ -34,6 +34,7 @@ function init() magic_hit1 = Sound('Shadow Punch 1.ogg', s) magic_die1 = Sound('Magical Impact 27.ogg', s) knife_hit_wall1 = Sound('Shield Impacts Sword 1.ogg', s) + blade_hit1 = Sound('Sword impact (Flesh) 2.ogg', s) player_hit1 = Sound('Body Fall 2.ogg', s) player_hit2 = Sound('Body Fall 18.ogg', s) player_hit_wall1 = Sound('Wood Heavy 5.ogg', s) @@ -41,6 +42,7 @@ function init() heal1 = Sound('Buff 3.ogg', s) spawn1 = Sound('Buff 13.ogg', s) alert1 = Sound('Alert sounds 3.ogg', s) + elementor1 = Sound('Wind Bolt 18.ogg', s) main = Main() main:add(Arena'arena') diff --git a/objects.lua b/objects.lua index 8d53d0e..f4645db 100644 --- a/objects.lua +++ b/objects.lua @@ -110,7 +110,7 @@ function Unit:calculate_stats(first_run) self.base_aspd_m = 1 self.base_area_dmg_m = 1 self.base_area_size_m = 1 - self.base_def = 0 + self.base_def = 25 self.base_mvspd = 75 self.class_hp_a = 0 self.class_dmg_a = 0 @@ -138,9 +138,11 @@ function Unit:calculate_stats(first_run) for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_hp_m = self.class_hp_m*1.4 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 == 'healer' then self.class_hp_m = self.class_hp_m*1.2 + elseif class == 'nuker' 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 == 'enchanter' then self.class_hp_m = self.class_hp_m*1.2 + elseif class == 'psy' then self.class_hp_m = self.class_hp_m*1.5 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 @@ -149,7 +151,7 @@ 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 == 'rogue' then self.class_dmg_m = self.class_dmg_m*1.3 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 @@ -159,29 +161,31 @@ function Unit:calculate_stats(first_run) 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 + elseif class == 'nuker' 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_dmg_m*1.5 + elseif class == 'nuker' 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_size_m*1.3 + elseif class == 'nuker' 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 for _, class in ipairs(self.classes) do 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 == 'ranger' then self.class_def_m = self.class_def_m*0.9 + elseif class == 'mage' then self.class_def_m = self.class_def_m*0.75 elseif class == 'rogue' then self.class_def_m = self.class_def_m*0.8 + elseif class == 'enchanter' then self.class_def_m = self.class_def_m*1.2 + elseif class == 'psy' then self.class_def_m = self.class_def_m*0.5 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 @@ -190,6 +194,7 @@ function Unit:calculate_stats(first_run) 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 == 'enchanter' then self.class_mvspd_m = self.class_mvspd_m*1.2 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 474b971..1fe3543 100644 --- a/player.lua +++ b/player.lua @@ -10,7 +10,7 @@ function Player:init(args) self.color = fg[0] self:set_as_rectangle(9, 9, 'dynamic', 'player') self.visual_shape = 'rectangle' - self.classes = {'ranger', 'warrior', 'mage'} + self.classes = {'ranger', 'warrior', 'psy'} self.attack_sensor = Circle(self.x, self.y, 96) self.t:every(2, function() @@ -94,6 +94,62 @@ function Player:init(args) heal1:play{pitch = random:float(0.95, 1.05), volume = 0.5} end end) + + elseif self.character == 'outlaw' then + self.color = red[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'warrior', 'rogue'} + + self.attack_sensor = Circle(self.x, self.y, 96) + self.t:every(3, 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 == 'blade' then + self.color = orange[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + 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 + end, nil, nil, 'shoot') + + elseif self.character == 'elementor' then + self.color = blue[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, 128) + self.t:every(12, 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}) + end + end, nil, nil, 'attack') + + elseif self.character == 'ninja' then + self.color = red[0] + self:set_as_rectangle(9, 9, 'dynamic', 'player') + self.visual_shape = 'rectangle' + self.classes = {'rogue', 'conjurer'} + + self.t:every(8, function() + self.t:every(0.25, function() + SpawnEffect{group = self.effects, x = self.x, y = self.y, action = function(x, y) + NinjaClone{group = self.main, x = x, y = y, parent = self} + end} + end, 3) + end) end self:calculate_stats(true) @@ -247,9 +303,30 @@ end 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} - 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} - Projectile(table.merge(t, mods or {})) + + if self.character == 'outlaw' 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} + r = r - 2*math.pi/8 + for i = 1, 5 do + local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.color, dmg = self.dmg, character = self.character, parent = self} + 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 + for _, enemy in ipairs(enemies) do + local r = self:angle_to_object(enemy) + HitCircle{group = main.current.effects, x = self.x + 0.8*self.shape.w*math.cos(r), y = self.y + 0.8*self.shape.w*math.sin(r), rs = 6} + local t = {group = main.current.main, x = self.x + 1.6*self.shape.w*math.cos(r), y = self.y + 1.6*self.shape.w*math.sin(r), v = 250, r = r, color = self.color, dmg = self.dmg, character = self.character, parent = self} + Projectile(table.merge(t, mods or {})) + end + end + 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 shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.3} @@ -257,7 +334,7 @@ function Player:shoot(r, mods) 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' then + elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' then _G[random:table{'scout1', 'scout2'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5} end end @@ -266,11 +343,13 @@ end function Player:attack(area, mods) camera:shake(2, 0.5) self.hfx:use('shoot', 0.25) - local t = {group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.area_size_m*(area or 64), color = self.color, dmg = self.area_dmg_m*self.dmg, character = self.character} + local t = {group = main.current.effects, x = mods.x or self.x, y = mods.y or self.y, r = self.r, w = self.area_size_m*(area or 64), color = self.color, dmg = self.area_dmg_m*self.dmg, character = self.character} Area(table.merge(t, mods or {})) if self.character == 'swordsman' then _G[random:table{'swordsman1', 'swordsman2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.75} + elseif self.character == 'elementor' then + elementor1:play{pitch = random:float(0.9, 1.1), volume = 0.5} end end @@ -314,7 +393,9 @@ function Projectile:die(x, y, r, n) self.dead = true if self.character == 'wizard' then - Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.wizard.area_size_m*32, color = self.color, dmg = self.wizard.area_dmg_m*self.dmg, character = self.character} + Area{group = main.current.effects, x = self.x, y = self.y, r = self.r, w = self.parent.area_size_m*32, color = self.color, dmg = self.parent.area_dmg_m*self.dmg, character = self.character} + 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} end end @@ -333,7 +414,7 @@ function Projectile:on_collision_enter(other, contact) 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' then + elseif self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' 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) @@ -373,7 +454,7 @@ 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' then + if self.character == 'archer' or self.character == 'scout' or self.character == 'outlaw' or self.character == 'blade' then hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} elseif self.character == 'wizard' then magic_area1:play{pitch = random:float(0.95, 1.05), volume = 0.15} @@ -398,10 +479,13 @@ function Area:init(args) HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1} for i = 1, 2 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = self.color} end for i = 1, 2 do HitParticle{group = main.current.effects, x = enemy.x, y = enemy.y, color = enemy.color} end - if self.character == 'wizard' then + if self.character == 'wizard' or self.character == 'elementor' then magic_hit1:play{pitch = random:float(0.95, 1.05), volume = 0.5} elseif self.character == 'swordsman' then hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} + elseif self.character == 'blade' then + blade_hit1:play{pitch = random:float(0.9, 1.1), volume = 0.35} + hit2:play{pitch = random:float(0.95, 1.05), volume = 0.2} end end @@ -436,3 +520,34 @@ function Area:draw() graphics.rectangle((x1+x2)/2, (y1+y2)/2, x2-x1, y2-y1, nil, nil, self.color_transparent) graphics.pop() end + + + + +NinjaClone = Object:extend() +NinjaClone:implement(GameObject) +NinjaClone:implement(Physics) +function NinjaClone:init(args) + self:init_game_object(args) + self:init_unit() + self:set_as_rectangle(8, 8, 'dynamic', 'enemy') + self:set_restitution(0.5) + + self.color = red[0] + self.classes = {'ninja_clone'} + self:calculate_stats(true) + self:set_as_steerable(self.v, 2000, 4*math.pi, 4) +end + + +function NinjaClone:update(dt) + self:update_game_object(dt) + self:calculate_stats() +end + + +function NinjaClone: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 diff --git a/todo b/todo index b0c8ee5..6fba923 100644 --- a/todo +++ b/todo @@ -1,19 +1,66 @@ -Vagrant: shoots an ethereal projectile at any nearby enemy that deals physical and magical damage, medium range +Vagrant: shoots a projectile at any nearby enemy, 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 a medium area around the unit, small range +Swordsman: deals physical damage in a medium area around the unit, small range, AoE has medium 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, long range, AoE has very small range +Wizard: shoots a projectile at any nearby enemy and deals AoE damage on contact, long range, AoE has very small range +Outlaw: throws a leque of knives at nearby enemies, medium range +Blade: shoots multiple blades at nearby enemies, each dealing AoE damage on contact, small range, AoE has small range +Elementor: deals massive AoE damage to a random target, long range, AoE has medium range -Engineer: drops a turret that shoots secondary projectiles very fast, medium range +Ninja: creates clones that roam and shoot projectiles at nearby enemies, very small range +Linker: links nearby enemies together making them share damage taken, medium range +Sage: shoots a slow projectile that draws enemies in, medium range, AoE has medium 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 +Psykeeper: all damage taken is stored and distributed as healing +Gambler: drops a sentry that uses random attacks, 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 +Ranger: yellow, chance to release a barrage +Warrior: orange, increased defense +Healer: green, increased healing effectiveness +Mage: blue, decreased enemy defense +Nuker: purple, increased area damage and size +Conjurer: orange, increased construct damage and duration Rogue: red, chance to crit dealing 4x damage +Enchanter: pink, increased damage to all allies +Psy: white, returns damage taken based on number of active psy units + +Vagrant [warrior, ranger, psy] +Scout [rogue] +Cleric [healer] +Swordsman [warrior] +Archer [ranger] +Wizard [mage] +Outlaw [rogue, warrior] +Blade [warrior, nuker] +Elementor [mage, nuker] + +Ninja [rogue, conjurer] +Linker [enchanter, nuker] +Sage [mage, nuker] +Squire [warrior, healer, enchanter] +Cannoneer [ranger, nuker] +Dual Gunner [ranger, rogue] +Hunter [ranger, conjurer] +Chronomancer [mage, enchanter] +Spellblade [mage, rogue] +Psykeeper [healer, psy] +Gambler [conjurer] + +Ranger [2, 4] (5) +Warrior [2, 4] (5) +Healer [3] +Mage [2, 4] (5) +Nuker [2, 4] (5) +Conjurer [2] (3) +Rogue [2, 4] (5) +Enchanter [3] (3) +Psy [n] (2) HP Damage @@ -21,11 +68,3 @@ Area damage Area of effect Attack speed Defense -> if defense >= 0 then dmg_m = 100/(100+defense) else dmg_m = 2-100/(100-defense) - -* HP bar should be drawn on top of all player units -* Projectiles -* Areas -* Stats: attack speed, damage, area -* One or a few of the characters -* Port over enemy spawn logic from SHOOTRX -* Sounds