Day 45
parent
32cb666cd3
commit
129d7f1a06
|
@ -36,7 +36,7 @@ function Arena:on_enter(from, level, units)
|
|||
self.damage_dealt = 0
|
||||
self.damage_taken = 0
|
||||
self.main_slow_amount = 1
|
||||
self.enemies = {Seeker}
|
||||
self.enemies = {Seeker, EnemyCritter}
|
||||
self.color = self.color or fg[0]
|
||||
|
||||
-- Spawn solids and player
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -760,7 +760,7 @@ objects and the last thing I need is random bugs because a function or variable
|
|||
|
||||
Added a spawn marker so that it's easier for the player to tell where enemies are spawning and to give a chance to avoid unfair deaths. Slowly getting back into it now...
|
||||
|
||||
# Day 44 - 01/04/21
|
||||
# Day 43-44 - 31/03/21-01/04/21
|
||||
|
||||
* Added a spawn marker before enemies spawn to help with avoiding enemies spawning on top of the player
|
||||
* Prevent spawning of units that cost 3 or more gold on the first level
|
||||
|
@ -773,3 +773,10 @@ Added a spawn marker so that it's easier for the player to tell where enemies ar
|
|||
* green - Grant nearby enemies a speed boost on death
|
||||
* blue - Explode into projectiles on death
|
||||
* orange - Charge up and headbutt towards the player at increased speed and damage
|
||||
|
||||
# Day 45 - 02/04/21
|
||||
|
||||
* Added more enemy modifiers
|
||||
* yellow - Resistance to knockback and increased HP
|
||||
* white - Remain static and shoot projectiles
|
||||
* purple - Explodes into critters on death
|
||||
|
|
156
enemies.lua
156
enemies.lua
|
@ -13,12 +13,16 @@ function Seeker:init(args)
|
|||
self:calculate_stats(true)
|
||||
self:set_as_steerable(self.v, 2000, 4*math.pi, 4)
|
||||
|
||||
self.spawner = random:bool(25)
|
||||
|
||||
--[[
|
||||
if random:bool(35) then
|
||||
local n = random:int(1, 3)
|
||||
self.speed_booster = n == 1
|
||||
self.exploder = n == 2
|
||||
self.headbutter = n == 3
|
||||
end
|
||||
]]--
|
||||
|
||||
if self.speed_booster then
|
||||
self.color = green[0]:clone()
|
||||
|
@ -46,8 +50,25 @@ function Seeker:init(args)
|
|||
end)
|
||||
elseif self.tank then
|
||||
self.color = yellow[0]:clone()
|
||||
self.buff_hp_m = 1.5 + (0.05*self.level)
|
||||
self:calculate_stats()
|
||||
self.hp = self.max_hp
|
||||
elseif self.shooter then
|
||||
self.color = white[0]:clone()
|
||||
self.color = fg[0]:clone()
|
||||
self.t:after({2, 4}, function()
|
||||
self.shooting = true
|
||||
self.t:every({3, 5}, function()
|
||||
for i = 1, 3 do
|
||||
self.t:after((1 - self.level*0.01)*0.15*(i-1), function()
|
||||
shoot1:play{pitch = random:float(0.95, 1.05), volume = 0.1}
|
||||
self.hfx:use('hit', 0.25, 200, 10, 0.1)
|
||||
local r = self.r
|
||||
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}
|
||||
EnemyProjectile{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), color = fg[0], r = r, v = 150 + 5*self.level, dmg = 2*self.dmg}
|
||||
end)
|
||||
end
|
||||
end, nil, nil, 'shooter')
|
||||
end)
|
||||
elseif self.spawner then
|
||||
self.color = purple[0]:clone()
|
||||
end
|
||||
|
@ -71,6 +92,10 @@ function Seeker:update(dt)
|
|||
end
|
||||
self:calculate_stats()
|
||||
|
||||
if self.shooter then
|
||||
self.t:set_every_multiplier('shooter', (1 - self.level*0.02))
|
||||
end
|
||||
|
||||
if self.being_pushed then
|
||||
local v = math.length(self:get_velocity())
|
||||
if v < 25 then
|
||||
|
@ -80,7 +105,7 @@ function Seeker:update(dt)
|
|||
self:set_angular_damping(0)
|
||||
end
|
||||
else
|
||||
if self.headbutt_charging then
|
||||
if self.headbutt_charging or self.shooting then
|
||||
self:set_damping(10)
|
||||
self:rotate_towards_object(main.current.player, 0.5)
|
||||
elseif not self.headbutting then
|
||||
|
@ -135,7 +160,7 @@ function Seeker:on_collision_enter(other, contact)
|
|||
end
|
||||
|
||||
|
||||
function Seeker:hit(damage)
|
||||
function Seeker:hit(damage, projectile)
|
||||
if self.dead then return end
|
||||
self.hfx:use('hit', 0.25, 200, 10)
|
||||
self:show_hp()
|
||||
|
@ -170,11 +195,99 @@ function Seeker:hit(damage)
|
|||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if self.spawner then
|
||||
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.35}
|
||||
trigger:after(0.01, function()
|
||||
for i = 1, random:int(3, 6) do
|
||||
EnemyCritter{group = main.current.main, x = self.x, y = self.y, color = purple[0], r = random:float(0, 2*math.pi), v = 5 + 0.1*self.level, dmg = self.dmg, projectile = projectile}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Seeker:push(f, r)
|
||||
local n = 1
|
||||
if self.tank then n = 0.4 - 0.01*self.level end
|
||||
self.push_force = n*f
|
||||
self.being_pushed = true
|
||||
self.steering_enabled = false
|
||||
self:apply_impulse(n*f*math.cos(r), n*f*math.sin(r))
|
||||
self:apply_angular_impulse(random:table{random:float(-12*math.pi, -4*math.pi), random:float(4*math.pi, 12*math.pi)})
|
||||
self:set_damping(1.5*(1/n))
|
||||
self:set_angular_damping(1.5*(1/n))
|
||||
end
|
||||
|
||||
|
||||
function Seeker:speed_boost(duration)
|
||||
self.speed_boosting = love.timer.getTime()
|
||||
self.t:after(duration, function() self.speed_boosting = false end, 'speed_boost')
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
EnemyCritter = Object:extend()
|
||||
EnemyCritter:implement(GameObject)
|
||||
EnemyCritter:implement(Physics)
|
||||
EnemyCritter:implement(Unit)
|
||||
function EnemyCritter:init(args)
|
||||
self:init_game_object(args)
|
||||
self:init_unit()
|
||||
self:set_as_rectangle(7, 4, 'dynamic', 'enemy_projectile')
|
||||
self:set_restitution(0.5)
|
||||
|
||||
self.classes = {'enemy_critter'}
|
||||
self:calculate_stats(true)
|
||||
self:set_as_steerable(self.v, 400, math.pi, 1)
|
||||
self:push(args.v, args.r)
|
||||
self.invulnerable_to = args.projectile
|
||||
self.t:after(0.5, function() self.invulnerable_to = nil end)
|
||||
end
|
||||
|
||||
|
||||
function EnemyCritter:update(dt)
|
||||
self:update_game_object(dt)
|
||||
|
||||
if self.being_pushed then
|
||||
local v = math.length(self:get_velocity())
|
||||
if v < 50 then
|
||||
self.being_pushed = false
|
||||
self.steering_enabled = true
|
||||
self:set_damping(0)
|
||||
self:set_angular_damping(0)
|
||||
end
|
||||
else
|
||||
local player = main.current.player
|
||||
self:seek_point(player.x, player.y)
|
||||
self:wander(50, 200, 50)
|
||||
self:steering_separate(8, main.current.enemies)
|
||||
self:rotate_towards_velocity(1)
|
||||
end
|
||||
self.r = self:get_angle()
|
||||
end
|
||||
|
||||
|
||||
function EnemyCritter: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, 2, 2, self.hfx.hit.f and fg[0] or self.color)
|
||||
graphics.pop()
|
||||
end
|
||||
|
||||
|
||||
function EnemyCritter:hit(damage, projectile)
|
||||
if self.dead then return end
|
||||
if projectile == self.invulnerable_to then return end
|
||||
self.hfx:use('hit', 0.25, 200, 10)
|
||||
self.hp = self.hp - damage
|
||||
self:show_hp()
|
||||
if self.hp <= 0 then self:die() end
|
||||
end
|
||||
|
||||
|
||||
function EnemyCritter:push(f, r)
|
||||
self.push_force = f
|
||||
self.being_pushed = true
|
||||
self.steering_enabled = false
|
||||
|
@ -185,9 +298,40 @@ function Seeker:push(f, r)
|
|||
end
|
||||
|
||||
|
||||
function Seeker:speed_boost(duration)
|
||||
self.speed_boosting = love.timer.getTime()
|
||||
self.t:after(duration, function() self.speed_boosting = false end, 'speed_boost')
|
||||
function EnemyCritter:die(x, y, r, n)
|
||||
if self.dead then return end
|
||||
x = x or self.x
|
||||
y = y or self.y
|
||||
n = n or random:int(2, 3)
|
||||
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
|
||||
_G[random:table{'enemy_die1', 'enemy_die2'}]:play{pitch = random:float(0.9, 1.1), volume = 0.5}
|
||||
critter2:play{pitch = random:float(0.95, 1.05), volume = 0.2}
|
||||
end
|
||||
|
||||
|
||||
function EnemyCritter:on_collision_enter(other, contact)
|
||||
local x, y = contact:getPositions()
|
||||
local nx, ny = contact:getNormal()
|
||||
local r = 0
|
||||
if nx == 0 and ny == -1 then r = -math.pi/2
|
||||
elseif nx == 0 and ny == 1 then r = math.pi/2
|
||||
elseif nx == -1 and ny == 0 then r = math.pi
|
||||
else r = 0 end
|
||||
|
||||
if other:is(Wall) then
|
||||
self.hfx:use('hit', 0.15, 200, 10, 0.1)
|
||||
self:bounce(contact:getNormal())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function EnemyCritter:on_trigger_enter(other, contact)
|
||||
if other:is(Player) then
|
||||
self:die(self.x, self.y, nil, random:int(2, 3))
|
||||
other:hit(self.dmg)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
3
main.lua
3
main.lua
|
@ -25,6 +25,8 @@ function init()
|
|||
ui_transition1 = Sound('Wind Bolt 8.ogg', s)
|
||||
ui_transition2 = Sound('Wind Bolt 12.ogg', s)
|
||||
headbutt1 = Sound('Wind Bolt 14.ogg', s)
|
||||
critter1 = Sound('Critters eating 2.ogg', s)
|
||||
critter2 = Sound('Crickets Chirping 4.ogg', s)
|
||||
error1 = Sound('Error 2.ogg', s)
|
||||
coins1 = Sound('Coins 7.ogg', s)
|
||||
coins2 = Sound('Coins 8.ogg', s)
|
||||
|
@ -261,6 +263,7 @@ function init()
|
|||
['enchanter'] = {hp = 1.2, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1.2, mvspd = 1.2},
|
||||
['psy'] = {hp = 1.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 0.5, mvspd = 1},
|
||||
['seeker'] = {hp = 0.5, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.3},
|
||||
['enemy_critter'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 0.5},
|
||||
['saboteur'] = {hp = 1, dmg = 1, aspd = 1, area_dmg = 1, area_size = 1, def = 1, mvspd = 1.4},
|
||||
}
|
||||
|
||||
|
|
|
@ -236,6 +236,12 @@ function Unit:calculate_stats(first_run)
|
|||
self.base_hp = 100*math.pow(2, self.level-1)
|
||||
self.base_dmg = 10*math.pow(2, self.level-1)
|
||||
self.base_mvspd = 75
|
||||
elseif self:is(EnemyCritter) then
|
||||
local x = self.level
|
||||
local y = {0, 1, 4, 2, 3, 6, 3, 5, 9, 4, 6, 11, 7, 9, 15, 8, 10, 18, 9, 11, 21, 14, 15, 24, 25}
|
||||
self.base_hp = 25 + 30*y[x]
|
||||
self.base_dmg = 10 + 3*y[x]
|
||||
self.base_mvspd = 60 + 3*y[x]
|
||||
end
|
||||
self.base_aspd_m = 1
|
||||
self.base_area_dmg_m = 1
|
||||
|
|
|
@ -823,7 +823,7 @@ function Projectile:on_trigger_enter(other, contact)
|
|||
hit3:play{pitch = random:float(0.95, 1.05), volume = 0.35}
|
||||
end
|
||||
|
||||
other:hit(self.dmg)
|
||||
other:hit(self.dmg, self)
|
||||
|
||||
if self.character == 'hunter' and random:bool(40) then
|
||||
trigger:after(0.01, function()
|
||||
|
|
13
todo
13
todo
|
@ -11,9 +11,9 @@
|
|||
* green - Grant nearby enemies a speed boost on death
|
||||
* blue - Explode into projectiles on death
|
||||
* orange - Charge up and headbutt towards the player at increased speed and damage
|
||||
yellow - Resistance to knockback
|
||||
white - Remain static and shoot projectiles
|
||||
purple - Explodes into critters on death
|
||||
* yellow - Resistance to knockback and increased HP
|
||||
* white - Remain static and shoot projectiles
|
||||
* purple - Explodes into critters on death
|
||||
|
||||
6. Mini boss every 3rd level
|
||||
This is just a special enemy with more HP and ability to buff nearby enemies with modifiers, no additional AI or attack patterns
|
||||
|
@ -131,15 +131,16 @@ Boss ideas
|
|||
Pretends to be dead, grants speed buffs to enemies after death, especially if the round has gone on for too long which means the player is surviving with 1 unit
|
||||
Uses Psykino's tech to pull enemies together into a point and have that point move around and bounce on edges, thus having a ball of enemies moving around that occasionally explodes
|
||||
|
||||
Map modifiers
|
||||
Wall spikes: walls damage you when you hit it
|
||||
|
||||
Engine improvements for after SNKRX release
|
||||
Node refactor: described partly somewhere in the devlog
|
||||
Rewrite SNKRX: using this game as a target for the node refactor will yield good results for both the refactor and future SNKRX updates, if any
|
||||
on_hit:
|
||||
on_collision_enter/exit are automatically called and automatically call on_hit/on_leave for each object
|
||||
This enables the definition of on_hit on each object and the question of where the logic should stay is solved/dodged
|
||||
Spurred by Wall needing to have its own on_hit function to do something when the player hits it, without having to change player code for all Walls,
|
||||
Defining on_hit on the Wall creation call for that specific Wall, and thus that specific Wall will have this behavior while other walls won't
|
||||
https://i.imgur.com/asPdpnQ.png ?
|
||||
Not sure if I should go all the way with event systems like this or only have it work for specific cases, need to think about it more
|
||||
release tool:
|
||||
Build a command line tool that creates projects and builds them up for release automatically on Windows, Web and Steam
|
||||
All the steps for this are listed on the readme and everything about it that can be automated should
|
||||
|
|
Loading…
Reference in New Issue