master
a327ex 2021-04-02 23:53:12 -03:00
parent 32cb666cd3
commit 129d7f1a06
9 changed files with 176 additions and 15 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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