master
a327ex 2021-04-25 22:17:31 -03:00
parent 5ee9cdf2ec
commit 8970ce92bc
10 changed files with 127 additions and 47 deletions

View File

@ -104,11 +104,12 @@ function Arena:on_enter(from, level, units, passives)
end)
end)
end)
self.t:every(function() return self.start_time <= 0 and #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.can_quit = true end)
self.t:every(function() return self.start_time <= 0 and (self.boss and self.boss.dead) and #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.can_quit = true end)
end)
else
-- Set win condition and enemy spawns
self.win_condition = random:table{'time', 'enemy_kill', 'wave'}
self.win_condition = random:table{'time', 'wave'}
if self.level % 3 == 0 then self.win_condition = 'wave' end
if self.level == 18 and self.trailer then self.win_condition = 'wave' end
if self.win_condition == 'wave' then
self.level_to_max_waves = {
@ -235,13 +236,12 @@ function Arena:update(dt)
trigger:tween(0.25, _G, {slow_amount = 0}, math.linear, function()
slow_amount = 0
self.paused = true
self.paused_t1 = Text2{group = self.ui, x = gw/2, y = gh/2 - 68, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]<- or a -> or d', font = fat_font, alignment = 'center'}}}
self.paused_t2 = Text2{group = self.ui, x = gw/2, y = gh/2 - 52, lines = {{text = '[bg10]turn left turn right', font = pixul_font, alignment = 'center'}}}
self.paused_t3 = Text2{group = self.ui, x = gw/2, y = gh/2 - 22, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]n - mute sfx', font = fat_font, alignment = 'center'}}}
self.paused_t4 = Text2{group = self.ui, x = gw/2, y = gh/2 + 0, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]m - mute music', font = fat_font, alignment = 'center'}}}
self.paused_t5 = Text2{group = self.ui, x = gw/2, y = gh/2 + 22, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]esc - resume game', font = fat_font, alignment = 'center'}}}
self.paused_t6 = Text2{group = self.ui, x = gw/2, y = gh/2 + 44, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]r - restart run', font = fat_font, alignment = 'center'}}}
self.paused_t7 = Text2{group = self.ui, x = gw/2, y = gh/2 + 68, sx = 0.6, sy = 0.6, lines = {{text = '[bg10]w - wishlist on steam', font = fat_font, alignment = 'center'}}}
self.paused_t1 = Text2{group = self.ui, x = gw/2, y = gh/2 - 68, sx = 0.6, sy = 0.6, lines = {{text = '[fg]<- or a -> or d', font = fat_font, alignment = 'center'}}}
self.paused_t2 = Text2{group = self.ui, x = gw/2, y = gh/2 - 52, lines = {{text = '[fg]turn left turn right', font = pixul_font, alignment = 'center'}}}
self.paused_t3 = Text2{group = self.ui, x = gw/2, y = gh/2 - 22, sx = 0.6, sy = 0.6, lines = {{text = '[fg]n - mute sfx', font = fat_font, alignment = 'center'}}}
self.paused_t4 = Text2{group = self.ui, x = gw/2, y = gh/2 + 0, sx = 0.6, sy = 0.6, lines = {{text = '[fg]m - mute music', font = fat_font, alignment = 'center'}}}
self.paused_t5 = Text2{group = self.ui, x = gw/2, y = gh/2 + 22, sx = 0.6, sy = 0.6, lines = {{text = '[fg]esc - resume game', font = fat_font, alignment = 'center'}}}
self.paused_t6 = Text2{group = self.ui, x = gw/2, y = gh/2 + 44, sx = 0.6, sy = 0.6, lines = {{text = '[fg]r - restart run', font = fat_font, alignment = 'center'}}}
end, 'pause')
else
trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function()
@ -253,14 +253,12 @@ function Arena:update(dt)
self.paused_t4.dead = true
self.paused_t5.dead = true
self.paused_t6.dead = true
self.paused_t7.dead = true
self.paused_t1 = nil
self.paused_t2 = nil
self.paused_t3 = nil
self.paused_t4 = nil
self.paused_t5 = nil
self.paused_t6 = nil
self.paused_t7 = nil
end, 'pause')
end
end
@ -362,7 +360,7 @@ function Arena:draw()
self.post_main:draw()
self.effects:draw()
if self.level == 18 and self.trailer then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
if self.choosing_passives or self.won or self.paused then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
if self.choosing_passives or self.won or self.paused or self.died then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
self.ui:draw()
graphics.draw_with_mask(function()
@ -422,8 +420,8 @@ function Arena:die()
}}
self.t:after(2, function()
self.death_info_text = Text2{group = self.ui, x = gw/2, y = gh/2 + 24, sx = 0.7, sy = 0.7, lines = {
{text = '[wavy_mid, light_bg]level reached: [wavy_mid, yellow]' .. self.level, font = fat_font, alignment = 'center'},
{text = '[wavy_mid, light_bg]r - start new run', font = fat_font, alignment = 'center'},
{text = '[wavy_mid, fg]level reached: [wavy_mid, yellow]' .. self.level, font = fat_font, alignment = 'center'},
{text = '[wavy_mid, fg]r - start new run', font = fat_font, alignment = 'center'},
}}
end)
end
@ -542,7 +540,13 @@ function Arena:spawn_n_enemies(p, j, n)
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)
spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15}
Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level}
if random:bool(table.reduce(level_to_elite_spawn_weights[self.level], function(memo, v) return memo + v end)) then
local elite_type = level_to_elite_spawn_types[self.level][random:weighted_pick(unpack(level_to_elite_spawn_weights[self.level]))]
Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level,
speed_booster = elite_type == 'speed_booster', exploder = elite_type == 'exploder', shooter = elite_type == 'shooter', headbutter = elite_type == 'headbutter', tank = elite_type == 'tank', spawner = elite_type == 'spawner'}
else
Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level}
end
end}
end, n, nil, 'spawn_enemies_' .. j)
end

View File

@ -559,7 +559,7 @@ function CharacterPart:on_mouse_enter()
{text = character_descriptions[self.character](self.level), font = pixul_font, alignment = 'center', height_multiplier = 2},
{text = '[' .. (self.level == 3 and 'yellow' or 'light_bg') .. ']Lv.3 [' .. (self.level == 3 and 'fg' or 'light_bg') .. ']Effect - ' ..
(self.level == 3 and character_effect_names[self.character] or character_effect_names_gray[self.character]), font = pixul_font, alignment = 'center', height_multiplier = 1.25},
{text = character_effect_descriptions[self.character](), font = pixul_font, alignment = 'center'},
{text = (self.level == 3 and character_effect_descriptions[self.character]() or character_effect_descriptions_gray[self.character]()), font = pixul_font, alignment = 'center'},
}, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw/2, gh/2 + 10
end
@ -934,17 +934,21 @@ function ClassIcon:draw()
graphics.line(self.x + 4, self.y + 19, self.x + 4, self.y + 22, (n >= 4) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 24, self.x + 4, self.y + 27, (n >= 5) and class_colors[self.class] or bg[10], 3)
graphics.line(self.x + 4, self.y + 29, self.x + 4, self.y + 32, (n >= 6) and class_colors[self.class] or bg[10], 3)
--[[
if next_n then
if next_n == 1 then
graphics.line(self.x - 4, self.y + 22, self.x - 4, self.y + 30, self.flash and class_colors[self.class] or bg[10], 2)
graphics.line(self.x - 3, self.y + 19, self.x - 3, self.y + 22, self.flash and class_colors[self.class] or bg[10], 3)
elseif next_n == 2 then
graphics.line(self.x, self.y + 22, self.x, self.y + 30, self.flash and class_colors[self.class] or bg[10], 2)
graphics.line(self.x - 3, self.y + 24, self.x - 3, self.y + 27, self.flash and class_colors[self.class] or bg[10], 3)
elseif next_n == 3 then
graphics.line(self.x + 4, self.y + 22, self.x + 4, self.y + 30, self.flash and class_colors[self.class] or bg[10], 2)
graphics.line(self.x - 3, self.y + 29, self.x - 3, self.y + 32, self.flash and class_colors[self.class] or bg[10], 3)
elseif next_n == 4 then
graphics.line(self.x + 4, self.y + 19, self.x + 4, self.y + 22, self.flash and class_colors[self.class] or bg[10], 3)
elseif next_n == 5 then
graphics.line(self.x + 4, self.y + 24, self.x + 4, self.y + 27, self.flash and class_colors[self.class] or bg[10], 3)
elseif next_n == 6 then
graphics.line(self.x + 4, self.y + 29, self.x + 4, self.y + 32, self.flash and class_colors[self.class] or bg[10], 3)
end
end
]]--
end
graphics.pop()
end

View File

@ -1055,3 +1055,8 @@ I also made some small graphical improvements to the game that I think will help
# Day 67 - 24/04/21
Added an end game screen and started work on making elites spawn throughout the levels.
# Day 68 - 25/04/21
Elites now spawn at appropriate rates. Testing out the game a little with this and it seems a lot better than before, but I need to balance a few of the elite units more (headbutter and spawner seem weak).
Also went through a bunch of smaller bug fixes and changes as I play the game more and missing details start popping up. I should leave proper balancing for later and start work on final UI improvements tomorrow.

View File

@ -15,8 +15,8 @@ function Seeker:init(args)
if self.boss == 'speed_booster' then
self.color = green[0]:clone()
self.t:every(5, function()
local enemies = self:get_objects_in_shape(Circle(self.x, self.y, 128), main.current.enemies)
self.t:every(8, function()
local enemies = table.head(self:get_objects_in_shape(Circle(self.x, self.y, 128), main.current.enemies), 4)
if #enemies > 0 then
buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
HitCircle{group = main.current.effects, x = self.x, y = self.y, rs = 6, color = green[0], duration = 0.1}
@ -647,6 +647,12 @@ function EnemyCritter:on_trigger_enter(other, contact)
end
function EnemyCritter:speed_boost(duration)
self.speed_boosting = love.timer.getTime()
self.t:after(duration, function() self.speed_boosting = false end, 'speed_boost')
end
EnemyProjectile = Object:extend()

View File

@ -92,13 +92,17 @@ end
-- t = {4, 3, 2, 1}
-- table.head(t) -> 4
-- table.head(t, 2) -> {4, 3}
-- If n is defined then it always returns a table, even with only 1 value.
function table.head(t, n)
local out = {}
for i = 1, (n or 1) do
table.push(out, t[i])
end
if #out == 1 then return out[1]
else return out end
if n then return out
else
if #out == 1 then return out[1]
else return out end
end
end
@ -106,13 +110,17 @@ end
-- t = {5, 4, 3, 2, 1}
-- table.tail(t) -> 1
-- table.tail(t, 2) -> {2, 1}
-- If n is defined then it always returns a table, even with only 1 value.
function table.tail(t, n)
local out = {}
for i = #t-(n or #t-1)+1, #t do
table.push(out, t[i])
end
if #out == 1 then return out[1]
else return out end
if n then return out
else
if #out == 1 then return out[1]
else return out end
end
end
@ -255,8 +263,14 @@ end
-- For those cases the third argument comes in handy and can be used to set the initial value of memo directly.
function table.reduce(t, f, dv, ...)
local memo = dv or t[1]
for i = 1, #t do
memo = f(memo, t[i], i, ...)
if dv then
for i = 1, #t do
memo = f(memo, t[i], i, ...)
end
else
for i = 2, #t do
memo = f(memo, t[i], i, ...)
end
end
return memo
end

View File

@ -20,8 +20,8 @@ end
-- Returns a random real number between the range.
-- random:bool(0, 1) -> returns a random number between 0 and 1, like 0.432
-- random:bool(-100, 45.2) -> returns a random number between -100 and 45.2, like -99.7
-- random:float(0, 1) -> returns a random number between 0 and 1, like 0.432
-- random:float(-100, 45.2) -> returns a random number between -100 and 45.2, like -99.7
function Random:float(min, max)
min = min or 0
max = max or 1
@ -46,7 +46,7 @@ end
-- Returns a random value of the table and also removes it.
-- a = {7, 6, 5, 4}
-- random:table(a) -> returns either 7, 6, 5 or 4 randomly and removes it from the table as well
-- random:table_remove(a) -> returns either 7, 6, 5 or 4 randomly and removes it from the table as well
function Random:table_remove(t)
return table.remove(t, self.generator:random(1, #t))
end

View File

@ -11,11 +11,11 @@ require 'media'
function init()
shared_init()
input:bind('move_left', {'a', 'left'})
input:bind('move_right', {'d', 'right'})
input:bind('move_up', {'w', 'up'})
input:bind('move_down', {'s', 'down'})
input:bind('enter', {'space', 'return'})
input:bind('move_left', {'a', 'left', 'dpleft'})
input:bind('move_right', {'d', 'right', 'dpright'})
input:bind('move_up', {'w', 'up', 'dpup'})
input:bind('move_down', {'s', 'down', 'dpdown'})
input:bind('enter', {'space', 'return', 'fleft', 'fdown', 'fright'})
local s = {tags = {sfx}}
thunder1 = Sound('399656__bajko__sfx-thunder-blast.ogg', s)
@ -603,6 +603,50 @@ function init()
['flagellant'] = function() return '[fg]deals [yellow]' .. 2*get_character_stat('flagellant', 3, 'dmg') .. '[fg] damage to all allies and grants [yellow]+12%[fg] damage to all allies per cast' end,
}
character_effect_descriptions_gray = {
['vagrant'] = function() return '[light_bg]+10% damage and +5% attack speed per active set' end,
['swordsman'] = function() return "[light_bg]the swordsman's damage is doubled" end,
['wizard'] = function() return '[light_bg]the projectile chains 5 times' end,
['archer'] = function() return '[light_bg]the arrow ricochets off walls 3 times' end,
['scout'] = function() return '[light_bg]+25% damage per chain and +3 chains' end,
['cleric'] = function() return '[light_bg]heals all units' end,
['outlaw'] = function() return "[light_bg]+50% outlaw attack speed and his knives seek enemies" end,
['blade'] = function() return '[light_bg]deal additional ' .. get_character_stat('blade', 3, 'dmg')/2 .. ' damage per enemy hit' end,
['elementor'] = function() return '[light_bg]slows enemies by 60% for 6 seconds on hit' end,
['saboteur'] = function() return '[light_bg]the explosion has 50% chance to crit, increasing in size and dealing 2x damage' end,
['stormweaver'] = function() return "[light_bg]chain lightning's trigger area of effect and number of units hit is doubled" end,
['sage'] = function() return '[light_bg]when the projectile expires deal ' .. 3*get_character_stat('sage', 3, 'dmg') .. ' damage to all enemies under its influence' end,
['squire'] = function() return '[light_bg]you can reroll your item choices once, these opportunities stack if unused' end,
['cannoneer'] = function() return '[light_bg]showers the hit area in 5 additional cannon shots that deal ' .. get_character_stat('cannoneer', 3, 'dmg')/2 .. ' AoE damage' end,
['dual_gunner'] = function() return '[light_bg]every 5th attack shoot in rapid succession for 2 seconds' end,
['hunter'] = function() return '[light_bg]summons 3 pets and the pets ricochet off walls once' end,
['chronomancer'] = function() return '[light_bg]enemies take damave over time 50% faster' end,
['spellblade'] = function() return '[light_bg]faster projectile speed and tighter turns' end,
['psykeeper'] = function() return '[light_bg]also redistributes damage taken as damage to all enemies at double value' end,
['engineer'] = function() return '[light_bg]every 3rd sentry dropped upgrade all sentries with +100% damage and attack speed' end,
['plague_doctor'] = function() return '[light_bg]nearby enemies take an additional ' .. get_character_stat('plague_doctor', 3, 'dmg') .. ' damage per second' end,
['barbarian'] = function() return '[light_bg]stunned enemies also take 100% increased damage' end,
['juggernaut'] = function() return '[light_bg]enemies pushed by the juggernaut take ' .. 4*get_character_stat('juggernaut', 3, 'dmg') .. ' damage if they hit a wall' end,
['lich'] = function() return '[light_bg]chain frost slows enemies hit by 80% for 2 seconds and chains +7 times' end,
['cryomancer'] = function() return '[light_bg]enemies are also slowed by 60% while in the area' end,
['pyromancer'] = function() return '[light_bg]enemies killed by the pyromancer explode, dealing ' .. get_character_stat('pyromancer', 3, 'dmg') .. ' AoE damage' end,
['corruptor'] = function() return '[light_bg]spawn 3 small critters if the corruptor hits an enemy' end,
['beastmaster'] = function() return '[light_bg]spawn 2 small critters if the beastmaster gets hit' end,
['launcher'] = function() return '[light_bg]enemies launched take 300% more damage when they hit walls' end,
['bard'] = function() return '[light_bg]every 8th attack consume the curse to deal ' .. 4*get_character_stat('bard', 3, 'dmg') .. ' damage to affected enemies' end,
['assassin'] = function() return '[light_bg]poison inflicted from crits deals 8x damage' end,
['host'] = function() return '[light_bg]+100% critter spawn rate and spawn 2 critters instead' end,
['carver'] = function() return '[light_bg]carves a tree that heals twice as fast, in a bigger area, and heals 2 units instead' end,
['bane'] = function() return '[light_bg]the area also deals ' .. get_character_stat('bane', 3, 'dmg') .. ' damage per second and slows enemies by 50%' end,
['psykino'] = function() return '[light_bg]enemies take ' .. 4*get_character_stat('psykino', 3, 'dmg') .. ' damage and are pushed away when the area expires' end,
['barrager'] = function() return '[light_bg]every 3rd attack the barrage shoots 15 projectiles and they push harder' end,
['highlander'] = function() return '[light_bg]quickly repeats the attack 3 times' end,
['fairy'] = function() return '[light_bg]heals 2 units instead and grants them an additional 100% attack speed' end,
['priest'] = function() return '[light_bg]at the start of the round pick 3 units at random and grants them a buff that prevents death once' end,
['infestor'] = function() return '[light_bg][yellow]triples the number of critters released' end,
['flagellant'] = function() return '[light_bg]deals ' .. 2*get_character_stat('flagellant', 3, 'dmg') .. ' damage to all allies and grants +12% damage to all allies per cast' end,
}
character_stats = {
['vagrant'] = function(lvl) return get_character_stat_string('vagrant', lvl) end,
['swordsman'] = function(lvl) return get_character_stat_string('swordsman', lvl) end,
@ -692,7 +736,7 @@ function init()
[4] = {'priest', 'highlander', 'psykino', 'lich', 'host', 'fairy', 'blade', 'plague_doctor', 'cannoneer'},
}
non_attacking_characters = {'cleric', 'stormweaver', 'squire', 'chronomancer', 'sage'}
non_attacking_characters = {'cleric', 'stormweaver', 'squire', 'chronomancer', 'sage', 'psykeeper', 'bane', 'carver', 'fairy', 'priest', 'flagellant'}
character_tiers = {
['vagrant'] = 1,

View File

@ -230,7 +230,7 @@ function Unit:calculate_stats(first_run)
if self.boss 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 = 250 + 275*y[x]
self.base_hp = 150 + 175*y[x]
self.base_dmg = 50 + 15*y[x]
self.base_mvspd = 35 + 1.5*y[x]
else

View File

@ -173,7 +173,7 @@ function Player:init(args)
Turret{group = main.current.main, x = x, y = y, parent = self}
end}
self.turret_counter = self.turret_counter + 1
if self.turret_counter == 3 then
if self.turret_counter == 3 and self.level == 3 then
self.turret_counter = 0
local turrets = main.current.main:get_objects_by_class(Turret)
buff1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
@ -308,7 +308,7 @@ function Player:init(args)
local closest_enemy = self:get_closest_object_in_shape(self.attack_sensor, main.current.enemies)
local r = self:angle_to_object(closest_enemy)
self.barrager_counter = self.barrager_counter + 1
if self.barrager_counter == 3 then
if self.barrager_counter == 3 and self.level == 3 then
self.barrage_counter = 0
for i = 1, 15 do
self.t:after((i-1)*0.05, function()
@ -1088,7 +1088,7 @@ function Player:shoot(r, mods)
Projectile(table.merge(t1, mods or {}))
Projectile(table.merge(t2, mods or {}))
self.dg_counter = self.dg_counter + 1
if self.dg_counter == 5 then
if self.dg_counter == 5 and self.level == 3 then
self.dg_counter = 0
self.t:every(0.1, function()
local random_enemy = self:get_random_object_in_shape(self.attack_sensor, main.current.enemies)
@ -1518,7 +1518,7 @@ function Projectile:on_trigger_enter(other, contact)
end
if self.character == 'assassin' then
other:apply_dot((self.crit and 4*self.dmg or self.dmg/2)*(self.dot_dmg_m or 1)*(main.current.chronomancer_dot), 3)
other:apply_dot((self.crit and 4*self.dmg or self.dmg/2)*(self.dot_dmg_m or 1)*(main.current.chronomancer_dot or 1), 3)
end
if self.parent.chain_infused then

11
todo
View File

@ -1,6 +1,9 @@
Selling should keep units value unless it's already merged into a higher level
Boss stands still for 1 second before enemies spawn
General balance
Enemies should have a chance to be spawned with a modifier as levels increase
Every 3rd level should be wave only
* Enemies should have a chance to be spawned with a modifier as levels increase
* Every 3rd level should be wave only
* Remove "enemies killed" mode
Balance playthroughs (record all balance playthroughs as they can be used for trailers; do this after graphical improvements done)
UI improvements
@ -13,9 +16,9 @@ Graphical improvements
Trailers
3-4 pure gameplay playthroughs showcasing different builds
Misc
Better pause screen
* Better pause screen
* End screen
Ascension mode (difficulty ramps up faster and goes higher than normal at the end)
Ascension mode (difficulty ramps up faster and goes higher than normal at the end, the player also gains more gold, and on ascension 1 and 5 the maximum number of units is increased by 1 (10->11->12)
Music for bosses and shop
Achievements
Come up with a few new ones as I play the game more and balance the numbers