master
a327ex 2021-02-23 00:47:57 -03:00
parent 2ff3cb9445
commit ec0af70374
7 changed files with 299 additions and 38 deletions

View File

@ -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

Binary file not shown.

View File

@ -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.

View File

@ -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')

View File

@ -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

View File

@ -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

75
todo
View File

@ -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