master
a327ex 2021-02-21 02:47:59 -03:00
parent 8f8910fec9
commit 9d3f777342
6 changed files with 369 additions and 29 deletions

256
arena.lua
View File

@ -7,7 +7,11 @@ function Arena:init(name)
end 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.main = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'})
self.effects = Group() self.effects = Group()
self.ui = Group():no_camera() 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)} {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.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(-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.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, -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]} 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'})
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.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 end
@ -57,8 +155,24 @@ function Arena:update(dt)
self.effects:update(dt*slow_amount) self.effects:update(dt*slow_amount)
self.ui:update(dt*slow_amount) self.ui:update(dt*slow_amount)
if input.k.pressed then if self.win_condition == 'enemy_kill' then
self:spawn_enemy(4) 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
end end
@ -67,16 +181,132 @@ function Arena:draw()
self.main:draw() self.main:draw()
self.effects:draw() self.effects:draw()
self.ui: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 end
function Arena:spawn_enemy(n) function Arena:enemy_killed()
n = n or 1 if self.win_condition == 'enemy_kill' then
local p = table.random(self.spawn_points) self.enemies_killed = self.enemies_killed + 1
for i = 1, n do self.hfx:use('condition1', 0.25, 200, 10)
self.t:after((i-1)*0.1, function() self.hfx:pull('condition2', 0.0625)
local o = table.random(self.spawn_offsets) self.enemy_spawn_delay = self.enemy_spawn_delay*0.95
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} if self.enemies_killed >= self.enemies_to_kill then
end) self.can_quit = true
self.t:cancel'spawn_enemies'
end
end 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

View File

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

View File

@ -46,6 +46,10 @@ function Unit:hit(damage)
self.dead = true 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 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) 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
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 == '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 == '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 == '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 elseif class == 'seeker' then self.class_hp_m = self.class_hp_m*0.5 end
end end
self.max_hp = (self.base_hp + self.class_hp_a + self.buff_hp_a)*self.class_hp_m*self.buff_hp_m 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 for _, class in ipairs(self.classes) do
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.2
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 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
for _, class in ipairs(self.classes) do for _, class in ipairs(self.classes) do
if class == 'warrior' then self.class_aspd_m = self.class_aspd_m*0.9 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 == '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 == 'void' then self.class_aspd_m = self.class_aspd_m*0.75 end
end end
self.aspd_m = 1/(self.base_aspd_m*self.class_aspd_m*self.buff_aspd_m) self.aspd_m = 1/(self.base_aspd_m*self.class_aspd_m*self.buff_aspd_m)
for _, class in ipairs(self.classes) do for _, class in ipairs(self.classes) do
if class == 'mage' then self.class_area_dmg_m = self.class_area_dmg_m*1.25 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 end
self.area_dmg_m = self.base_area_dmg_m*self.class_area_dmg_m*self.buff_area_dmg_m 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 for _, class in ipairs(self.classes) do
if class == 'mage' then self.class_area_size_m = self.class_area_size_m*1.2 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 end
self.area_size_m = self.base_area_size_m*self.class_area_size_m*self.buff_area_size_m 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 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 == '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 == '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 elseif class == 'healer' then self.class_def_m = self.class_def_m*1.2 end
end end
self.def = (self.base_def + self.class_def_a + self.buff_def_a)*self.class_def_m*self.buff_def_m 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 for _, class in ipairs(self.classes) do
if class == 'warrior' then self.class_mvspd_m = self.class_mvspd_m*0.9 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 == '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 elseif class == 'seeker' then self.class_mvspd_m = self.class_mvspd_m*0.3 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

View File

@ -7,7 +7,7 @@ function Player:init(args)
self:init_unit() self:init_unit()
if self.character == 'vagrant' then if self.character == 'vagrant' then
self.color = blue[0] self.color = fg[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 = {'ranger', 'warrior', 'mage'} self.classes = {'ranger', 'warrior', 'mage'}
@ -33,6 +33,48 @@ function Player:init(args)
self:attack() self:attack()
end end
end, nil, nil, 'attack') 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 end
self:calculate_stats(true) self:calculate_stats(true)
@ -51,6 +93,8 @@ function Player:update(dt)
self:update_game_object(dt) self:update_game_object(dt)
self:calculate_stats() self:calculate_stats()
if self.character == 'archer' then print(self.aspd_m) end
self.attack_sensor:move_to(self.x, self.y) self.attack_sensor:move_to(self.x, self.y)
self.t:set_every_multiplier('shoot', self.aspd_m) self.t:set_every_multiplier('shoot', self.aspd_m)
self.t:set_every_multiplier('attack', 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) 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 end
graphics.pop() graphics.pop()
-- self.attack_sensor:draw(self.color, 2)
end end
@ -138,18 +183,20 @@ function Player:add_follower(unit)
end end
function Player:shoot(r) function Player:shoot(r, mods)
camera:spring_shake(2, r) camera:spring_shake(2, r)
self.hfx:use('shoot', 0.25) 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} 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 end
function Player:attack() function Player:attack(mods)
camera:shake(2, 0.5) camera:shake(2, 0.5)
self.hfx:use('shoot', 0.25) 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 end
@ -161,6 +208,9 @@ Projectile:implement(Physics)
function Projectile:init(args) function Projectile:init(args)
self:init_game_object(args) self:init_game_object(args)
self:set_as_rectangle(10, 4, 'dynamic', 'projectile') 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 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 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() HitCircle{group = main.current.effects, x = x, y = y}:scale_down()
self.dead = true 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 end
@ -207,7 +261,25 @@ end
function Projectile:on_trigger_enter(other, contact) function Projectile:on_trigger_enter(other, contact)
if table.any(main.current.enemies, function(v) return other:is(v) end) then 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) other:hit(self.dmg)
end end
end end

View File

@ -7,6 +7,7 @@ function shared_init()
fg_alt = ColorRamp(Color'#b0a89f', 0.025), fg_alt = ColorRamp(Color'#b0a89f', 0.025),
yellow = ColorRamp(Color'#facf00', 0.025), yellow = ColorRamp(Color'#facf00', 0.025),
orange = ColorRamp(Color'#f07021', 0.025), orange = ColorRamp(Color'#f07021', 0.025),
yellow_orange = ColorRamp(Color(245, 160, 16), 0.025),
blue = ColorRamp(Color'#019bd6', 0.025), blue = ColorRamp(Color'#019bd6', 0.025),
green = ColorRamp(Color'#8bbf40', 0.025), green = ColorRamp(Color'#8bbf40', 0.025),
red = ColorRamp(Color'#e91d39', 0.025), red = ColorRamp(Color'#e91d39', 0.025),

16
todo
View File

@ -1,16 +1,18 @@
Vagrant: shoots an ethereal projectile at any nearby enemy that deals physical and magical damage, medium range 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 Cleric: heals every unit when any one drops below 50% HP
Swordsman: deals physical damage in an area around the unit, small range 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 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 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, long range Engineer: drops a turret that shoots secondary projectiles very fast, medium range
Ranger: yellow, buff attack speed Ranger: yellow, buff attack speed
Warrior: orange, buff attack damage Warrior: orange, buff attack damage
Healer: green, buff healing effectiveness Healer: green, buff healing effectiveness
Mage: blue, debuff enemy defense Mage: blue, debuff enemy defense
Void: purple, buff area damage and size 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 HP
Damage 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 * HP bar should be drawn on top of all player units
* Projectiles * Projectiles
* Areas * Areas
* Stats: attack speed, damage * Stats: attack speed, damage, area
One or a few of the characters * One or a few of the characters
Port over enemy spawn logic from SHOOTRX * Port over enemy spawn logic from SHOOTRX
Sounds Sounds