master
a327ex 2021-02-24 02:47:22 -03:00
parent ec0af70374
commit 57e7b3cab7
16 changed files with 235 additions and 37 deletions

View File

@ -50,13 +50,18 @@ 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, -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]} 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 = 'elementor'} 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 = 'archer'})
self.player:add_follower(Player{group = self.main, character = 'vagrant'}) 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:add_follower(Player{group = self.main, character = 'cleric'}) self.player:add_follower(Player{group = self.main, character = 'cleric'})
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 = 'scout'})
self.player:add_follower(Player{group = self.main, character = 'wizard'}) self.player:add_follower(Player{group = self.main, character = 'wizard'})
self.player:add_follower(Player{group = self.main, character = 'blade'})
self.player:add_follower(Player{group = self.main, character = 'elementor'})
]]-- ]]--
self.win_condition = random:table{'time', 'enemy_kill', 'wave'} self.win_condition = random:table{'time', 'enemy_kill', 'wave'}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -120,26 +120,26 @@ Ideaguyed the entire roster for the demo and implemented a few of them.
| Character | Description | Trigger Range | Effect Range | | Character | Description | Trigger Range | Effect Range |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Vagrant | shoots a projectile | medium | nil | | Vagrant | shoots a projectile | medium | |
| Scout | throws a knife that chains 3 times | small | nil | | Scout | throws a knife that chains 3 times | small | |
| Cleric | heals every unit when any one drops below 50% HP | nil | nil | | Cleric | heals every unit when any one drops below 50% HP | | |
| Swordsman | deals physical damage in an area around the unit | small | medium | | Swordsman | deals physical damage in an area around the unit | small | medium |
| Archer | shoots an arrow that pierces | very long | nil | | Archer | shoots an arrow that pierces | very long | |
| Wizard | shoots a projectile that deals AoE damage | long | very small | | Wizard | shoots a projectile that deals AoE damage | long | very small |
| Outlaw | throws a fan of 5 knives | medium | nil | | Outlaw | throws a fan of 5 knives | medium | |
| Blade | shoots multiple blades that deal AoE damage on contact | small | small | | Blade | shoots multiple blades that deal AoE damage on contact | small | small |
| Elementor | deals massive AoE damage to a random target | long | medium | | Elementor | deals massive AoE damage to a random target | long | medium |
| Ninja | creates clones that roam and shoot shurikens | nil | very small | | Ninja | creates clones that roam and shoot shurikens | | very small |
| Linker | links nearby enemies together making them share damage taken | medium | small | | Linker | links nearby enemies together making them share damage taken | medium | small |
| Sage | shoots a slow projectile that draws enemies in | medium | medium | | 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 | | Squire | improves damage and defense for adjacent units as well as healing them periodically | | |
| Cannoneer | shoots a projectile that deals massive AoE damage | long | medium | | Cannoneer | shoots a projectile that deals massive AoE damage | long | medium |
| Dual Gunner | shoots two parallel projectiles | medium | nil | | Dual Gunner | shoots two parallel projectiles | medium | |
| Hunter | shoots an arrow with a chance to summon a pet | long | small | | Hunter | shoots an arrow with a chance to summon a pet | long | small |
| Chronomancer | dramatically improves attack speed for adjacent units | nil | nil | | Chronomancer | dramatically improves attack speed for adjacent units | | |
| Spellblade | knives orbit you and hoam towards nearby enemies | small | small | | Spellblade | knives orbit you and hoam towards nearby enemies | small | small |
| Psykeeper | all damage taken is stored and distributed as healing | nil | nil | | Psykeeper | all damage taken is stored and distributed as healing | | |
| Gambler | drops a sentry that uses random attacks | nil | medium | | Gambler | drops a sentry that uses random attacks | | medium |
### Character Classes ### Character Classes
@ -195,3 +195,12 @@ Ideaguyed the entire roster for the demo and implemented a few of them.
| Psy | 1.5 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 1.0 | | 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. 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.
# Day 7 - 23/02/21
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~~

View File

@ -41,8 +41,17 @@ function init()
pop1 = Sound('Pop sounds 10.ogg', s) pop1 = Sound('Pop sounds 10.ogg', s)
heal1 = Sound('Buff 3.ogg', s) heal1 = Sound('Buff 3.ogg', s)
spawn1 = Sound('Buff 13.ogg', s) spawn1 = Sound('Buff 13.ogg', s)
alert1 = Sound('Alert sounds 3.ogg', s) alert1 = Sound('Click.ogg', s)
elementor1 = Sound('Wind Bolt 18.ogg', s) elementor1 = Sound('Wind Bolt 18.ogg', s)
saboteur_hit1 = Sound('Explosion Flesh_01.ogg', s)
saboteur_hit2 = Sound('Explosion Flesh_02.ogg', s)
saboteur1 = Sound('Male Jump 1.ogg', s)
saboteur2 = Sound('Male Jump 2.ogg', s)
saboteur3 = Sound('Male Jump 3.ogg', s)
spark1 = Sound('Spark 1.ogg', s)
spark2 = Sound('Spark 2.ogg', s)
spark3 = Sound('Spark 3.ogg', s)
stormweaver1 = Sound('Buff 8.ogg', s)
main = Main() main = Main()
main:add(Arena'arena') main:add(Arena'arena')

View File

@ -1,3 +1,71 @@
LightningLine = Object:extend()
LightningLine:implement(GameObject)
function LightningLine:init(args)
self:init_game_object(args)
self.lines = {}
table.insert(self.lines, {x1 = self.src.x, y1 = self.src.y, x2 = self.dst.x, y2 = self.dst.y})
self.w = 3
self.generations = 3
self.max_offset = 8
self:generate()
self.t:tween(0.1, self, {w = 1}, math.linear, function() self.dead = true end)
self.color = blue[0]
HitCircle{group = main.current.effects, x = self.src.x, y = self.src.y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 2 do HitParticle{group = main.current.effects, x = self.src.x, y = self.src.y, color = blue[0]} end
HitCircle{group = main.current.effects, x = self.dst.x, y = self.dst.y, rs = 6, color = fg[0], duration = 0.1}
HitParticle{group = main.current.effects, x = self.dst.x, y = self.dst.y, color = blue[0]}
end
function LightningLine:update(dt)
self:update_game_object(dt)
end
function LightningLine:draw()
graphics.polyline(self.color, self.w, unpack(self.points))
end
function LightningLine:generate()
local offset_amount = self.max_offset
local lines = self.lines
for j = 1, self.generations do
for i = #self.lines, 1, -1 do
local x1, y1 = self.lines[i].x1, self.lines[i].y1
local x2, y2 = self.lines[i].x2, self.lines[i].y2
table.remove(self.lines, i)
local x, y = (x1+x2)/2, (y1+y2)/2
local p = Vector(x2-x1, y2-y1):normalize():perpendicular()
x = x + p.x*random:float(-offset_amount, offset_amount)
y = y + p.y*random:float(-offset_amount, offset_amount)
table.insert(self.lines, {x1 = x1, y1 = y1, x2 = x, y2 = y})
table.insert(self.lines, {x1 = x, y1 = y, x2 = x2, y2 = y2})
end
offset_amount = offset_amount/2
end
self.points = {}
while #self.lines > 0 do
local min_d, min_i = 1000000, 0
for i, line in ipairs(self.lines) do
local d = math.distance(self.src.x, self.src.y, line.x1, line.y1)
if d < min_d then
min_d = d
min_i = i
end
end
local line = table.remove(self.lines, min_i)
table.insert(self.points, line.x1)
table.insert(self.points, line.y1)
end
end
WallKnife = Object:extend() WallKnife = Object:extend()
WallKnife:implement(GameObject) WallKnife:implement(GameObject)
WallKnife:implement(Physics) WallKnife:implement(Physics)
@ -68,6 +136,7 @@ function Unit:init_unit()
self.hfx:add('shoot', 1) self.hfx:add('shoot', 1)
self.hp_bar = HPBar{group = main.current.effects, parent = self} self.hp_bar = HPBar{group = main.current.effects, parent = self}
self.heal_bar = HealBar{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}
end end
@ -97,6 +166,12 @@ function Unit:show_heal(n)
end end
function Unit:show_infused(n)
self.infused_bar.hidden = false
self.t:after(n, function() self.infused_bar.hidden = true end, 'infused_bar')
end
function Unit:calculate_damage(dmg) function Unit:calculate_damage(dmg)
if self.def >= 0 then dmg = dmg*(100/(100+self.def)) if self.def >= 0 then dmg = dmg*(100/(100+self.def))
else dmg = dmg*(2 - 100/(100+self.def)) end else dmg = dmg*(2 - 100/(100+self.def)) end
@ -152,7 +227,8 @@ function Unit:calculate_stats(first_run)
if class == 'warrior' then self.class_dmg_m = self.class_dmg_m*1.1 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 == '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.3 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 elseif class == 'mage' then self.class_dmg_m = self.class_dmg_m*1.4
elseif class == 'ninja_clone' then self.class_dmg_m = self.class_dmg_m*1.5 end
end end
self.dmg = (self.base_dmg + self.class_dmg_a + self.buff_dmg_a)*self.class_dmg_m*self.buff_dmg_m self.dmg = (self.base_dmg + self.class_dmg_a + self.buff_dmg_a)*self.class_dmg_m*self.buff_dmg_m
@ -195,7 +271,8 @@ function Unit:calculate_stats(first_run)
elseif class == 'ranger' then self.class_mvspd_m = self.class_mvspd_m*1.2 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 == '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 == '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 elseif class == 'seeker' then self.class_mvspd_m = self.class_mvspd_m*0.3
elseif class == 'saboteur' then self.class_mvspd_m = self.class_mvspd_m*1.4 end
end end
self.v = (self.base_mvspd + self.class_mvspd_a + self.buff_mvspd_a)*self.class_mvspd_m*self.buff_mvspd_m self.v = (self.base_mvspd + self.class_mvspd_a + self.buff_mvspd_a)*self.class_mvspd_m*self.buff_mvspd_m
end end
@ -203,6 +280,35 @@ end
InfusedBar = Object:extend()
InfusedBar:implement(GameObject)
InfusedBar:implement(Parent)
function InfusedBar: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)
end
function InfusedBar:update(dt)
self:update_game_object(dt)
self:follow_parent_exclusively()
end
function InfusedBar: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 = Object:extend()
HealBar:implement(GameObject) HealBar:implement(GameObject)
HealBar:implement(Parent) HealBar:implement(Parent)

View File

@ -137,18 +137,37 @@ function Player:init(args)
end end
end, nil, nil, 'attack') end, nil, nil, 'attack')
elseif self.character == 'ninja' then elseif self.character == 'saboteur' then
self.color = red[0] self.color = red[0]
self:set_as_rectangle(9, 9, 'dynamic', 'player') self:set_as_rectangle(9, 9, 'dynamic', 'player')
self.visual_shape = 'rectangle' self.visual_shape = 'rectangle'
self.classes = {'rogue', 'conjurer'} self.classes = {'rogue', 'conjurer', 'nuker'}
self.t:every(8, function() self.t:every(8, function()
self.t:every(0.25, function() self.t:every(0.25, function()
SpawnEffect{group = self.effects, x = self.x, y = self.y, action = function(x, y) SpawnEffect{group = main.current.effects, x = self.x, y = self.y, action = function(x, y)
NinjaClone{group = self.main, x = x, y = y, parent = self} Saboteur{group = main.current.main, x = x, y = y, parent = self}
end} end}
end, 3) end, 4)
end)
elseif self.character == 'stormweaver' then
self.color = blue[0]
self:set_as_rectangle(9, 9, 'dynamic', 'player')
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
local leader = (self.leader and self) or self.parent
if self.leader then followers = self.followers else followers = self.parent.followers end
for _, f in ipairs(followers) do
if f.character ~= 'swordsman' and f.character ~= 'cleric' and f.character ~= 'elementor' and f.character ~= 'saboteur' then
f:chain_infuse(4)
end
end
end) end)
end end
self:calculate_stats(true) self:calculate_stats(true)
@ -244,7 +263,7 @@ function Player:on_collision_enter(other, contact)
pop1:play{pitch = r, volume = 0.2} pop1:play{pitch = r, volume = 0.2}
for i, f in ipairs(self.followers) do for i, f in ipairs(self.followers) do
trigger:after(i*0.002*self.v, function() trigger:after(i*(10.6/self.v), function()
f.hfx:use('hit', 0.5, 200, 10, 0.1) f.hfx:use('hit', 0.5, 200, 10, 0.1)
player_hit_wall1:play{pitch = r + 0.025*i, volume = 0.1} player_hit_wall1:play{pitch = r + 0.025*i, volume = 0.1}
pop1:play{pitch = r + 0.05*i, volume = 0.2} pop1:play{pitch = r + 0.05*i, volume = 0.2}
@ -293,6 +312,13 @@ function Player:heal(amount)
end end
function Player:chain_infuse(duration)
self.chain_infused = true
self:show_infused(duration or 2)
self.t:after(duration or 2, function() self.chain_infused = false end, 'chain_infuse')
end
function Player:add_follower(unit) function Player:add_follower(unit)
table.insert(self.followers, unit) table.insert(self.followers, unit)
unit.parent = self unit.parent = self
@ -341,10 +367,11 @@ end
function Player:attack(area, mods) function Player:attack(area, mods)
mods = mods or {}
camera:shake(2, 0.5) camera:shake(2, 0.5)
self.hfx:use('shoot', 0.25) self.hfx:use('shoot', 0.25)
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} 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 {})) Area(table.merge(t, mods))
if self.character == 'swordsman' then if self.character == 'swordsman' then
_G[random:table{'swordsman1', 'swordsman2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.75} _G[random:table{'swordsman1', 'swordsman2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.75}
@ -365,6 +392,7 @@ function Projectile:init(args)
self.pierce = args.pierce or 0 self.pierce = args.pierce or 0
self.chain = args.chain or 0 self.chain = args.chain or 0
self.chain_enemies_hit = {} self.chain_enemies_hit = {}
self.infused_enemies_hit = {}
end end
@ -454,6 +482,7 @@ function Projectile:on_trigger_enter(other, contact)
HitParticle{group = main.current.effects, x = self.x, y = self.y, color = other.color} HitParticle{group = main.current.effects, x = self.x, y = self.y, color = other.color}
end 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' then
hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35} hit2:play{pitch = random:float(0.95, 1.05), volume = 0.35}
elseif self.character == 'wizard' then elseif self.character == 'wizard' then
@ -461,7 +490,22 @@ function Projectile:on_trigger_enter(other, contact)
else else
hit3:play{pitch = random:float(0.95, 1.05), volume = 0.35} hit3:play{pitch = random:float(0.95, 1.05), volume = 0.35}
end end
other:hit(self.dmg) other:hit(self.dmg)
if self.parent.chain_infused then
local src = other
for i = 1, 2 do
_G[random:table{'spark1', 'spark2', 'spark3'}]:play{pitch = random:float(0.9, 1.1), volume = 0.3}
table.insert(self.infused_enemies_hit, src)
local dst = src:get_random_object_in_shape(Circle(src.x, src.y, 64), main.current.enemies, self.infused_enemies_hit)
if dst then
dst:hit(self.dmg/(i+1))
LightningLine{group = main.current.effects, src = src, dst = dst}
src = dst
end
end
end
end end
end end
@ -486,6 +530,8 @@ function Area:init(args)
elseif self.character == 'blade' then elseif self.character == 'blade' then
blade_hit1:play{pitch = random:float(0.9, 1.1), volume = 0.35} 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} 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}
end end
end end
@ -524,30 +570,54 @@ end
NinjaClone = Object:extend() Saboteur = Object:extend()
NinjaClone:implement(GameObject) Saboteur:implement(GameObject)
NinjaClone:implement(Physics) Saboteur:implement(Physics)
function NinjaClone:init(args) Saboteur:implement(Unit)
function Saboteur:init(args)
self:init_game_object(args) self:init_game_object(args)
self:init_unit() self:init_unit()
self:set_as_rectangle(8, 8, 'dynamic', 'enemy') self:set_as_rectangle(8, 8, 'dynamic', 'player')
self:set_restitution(0.5) self:set_restitution(0.5)
self.color = red[0] self.color = red[0]
self.classes = {'ninja_clone'} self.character = 'saboteur'
self.classes = {'saboteur', 'rogue', 'nuker'}
self:calculate_stats(true) self:calculate_stats(true)
self:set_as_steerable(self.v, 2000, 4*math.pi, 4) self:set_as_steerable(self.v, 2000, 4*math.pi, 4)
_G[random:table{'saboteur1', 'saboteur2', 'saboteur3'}]:play{pitch = random:float(0.8, 1.2), volume = 0.2}
self.target = random:table(self.group:get_objects_by_classes(main.current.enemies))
end end
function NinjaClone:update(dt) function Saboteur:update(dt)
self:update_game_object(dt) self:update_game_object(dt)
self:calculate_stats() self:calculate_stats()
if not self.target then self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) end
if not self.target then return end
if self.target.dead then self.target = random:table(self.group:get_objects_by_classes(main.current.enemies)) end
if not self.target then return end
self:seek_point(self.target.x, self.target.y)
self:rotate_towards_velocity(0.5)
self.r = self:get_angle()
end end
function NinjaClone:draw() function Saboteur:draw()
graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x) 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.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() graphics.pop()
end end
function Saboteur:on_collision_enter(other, contact)
if table.any(main.current.enemies, function(v) return other:is(v) end) then
camera:shake(4, 0.5)
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, character = self.character}
Area(table.merge(t, mods or {}))
self.dead = true
end
end

9
todo
View File

@ -7,9 +7,8 @@ Wizard: shoots a projectile at any nearby enemy and deals AoE damage on contact,
Outlaw: throws a leque of knives at nearby enemies, medium 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 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 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
Ninja: creates clones that roam and shoot projectiles at nearby enemies, very small range Stormweaver: infuses all projectile attacks with chain lightning, 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 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 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 Cannoneer: shoots a projectile at any nearby enemy and deals massive AoE damage on contact, long range, AoE has medium range
@ -39,9 +38,9 @@ Wizard [mage]
Outlaw [rogue, warrior] Outlaw [rogue, warrior]
Blade [warrior, nuker] Blade [warrior, nuker]
Elementor [mage, nuker] Elementor [mage, nuker]
Saboteur [rogue, conjurer, nuker]
Ninja [rogue, conjurer] Linker [enchanter]
Linker [enchanter, nuker]
Sage [mage, nuker] Sage [mage, nuker]
Squire [warrior, healer, enchanter] Squire [warrior, healer, enchanter]
Cannoneer [ranger, nuker] Cannoneer [ranger, nuker]