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