mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2025-01-06 00:23:49 +02:00
1241 lines
32 KiB
Lua
1241 lines
32 KiB
Lua
-- track state of units by Gerkiz
|
|
local Event = require 'utils.event'
|
|
local Global = require 'utils.global'
|
|
local Task = require 'utils.task_token'
|
|
local Public = require 'modules.wave_defense.table'
|
|
local Difficulty = require 'modules.difficulty_vote_by_amount'
|
|
local Beams = require 'modules.render_beam'
|
|
|
|
local de = defines.events
|
|
local ev = Public.events
|
|
local random = math.random
|
|
local abs = math.abs
|
|
local floor = math.floor
|
|
local set_timeout_in_ticks = Task.set_timeout_in_ticks
|
|
|
|
local this = {
|
|
states = {},
|
|
settings = {
|
|
frenzy_length = 3600,
|
|
frenzy_burst_length = 160,
|
|
update_rate = 120,
|
|
enabled = true,
|
|
track_bosses_only = false,
|
|
wave_number = 0,
|
|
unit_limit = 300,
|
|
},
|
|
target_settings = {}
|
|
}
|
|
|
|
Public._esp = {}
|
|
|
|
Global.register(
|
|
this,
|
|
function (tbl)
|
|
this = tbl
|
|
for _, state in pairs(this.states) do
|
|
setmetatable(state, { __index = Public._esp })
|
|
end
|
|
end
|
|
)
|
|
|
|
local projectiles = {
|
|
'slowdown-capsule',
|
|
'defender-capsule',
|
|
'slowdown-capsule',
|
|
'destroyer-capsule',
|
|
'distractor-capsule',
|
|
'slowdown-capsule',
|
|
'rocket',
|
|
'slowdown-capsule',
|
|
'explosive-rocket',
|
|
'grenade',
|
|
'rocket',
|
|
'grenade'
|
|
}
|
|
|
|
local tiers = {
|
|
['small-biter'] = 'medium-biter',
|
|
['medium-biter'] = 'big-biter',
|
|
['big-biter'] = 'behemoth-biter',
|
|
['behemoth-biter'] = 'behemoth-biter',
|
|
['small-spitter'] = 'medium-spitter',
|
|
['medium-spitter'] = 'big-spitter',
|
|
['big-spitter'] = 'behemoth-spitter',
|
|
['behemoth-spitter'] = 'behemoth-spitter'
|
|
}
|
|
|
|
local tier_damage = {
|
|
['small-biter'] = { min = 25, max = 50 },
|
|
['medium-biter'] = { min = 50, max = 100 },
|
|
['big-biter'] = { min = 75, max = 150 },
|
|
['behemoth-biter'] = { min = 100, max = 200 },
|
|
['small-spitter'] = { min = 25, max = 50 },
|
|
['medium-spitter'] = { min = 50, max = 100 },
|
|
['big-spitter'] = { min = 75, max = 150 },
|
|
['behemoth-spitter'] = { min = 100, max = 200 }
|
|
}
|
|
|
|
local commands = {
|
|
['flee'] = 'attack',
|
|
['goto'] = 'attack_area',
|
|
['attack'] = 'flee',
|
|
['attack_area'] = 'goto',
|
|
}
|
|
|
|
local pf_flags = {
|
|
allow_destroy_friendly_entities = true,
|
|
allow_paths_through_own_entities = true,
|
|
cache = true,
|
|
prefer_straight_paths = false,
|
|
low_priority = false,
|
|
no_break = false,
|
|
}
|
|
|
|
--- A token to register tasks that entities are given
|
|
local work_token
|
|
work_token =
|
|
Task.register(
|
|
function (event)
|
|
if not event then
|
|
return
|
|
end
|
|
|
|
local self = Public.get_unit(event.unit_number)
|
|
local tick = game.tick
|
|
if not self then
|
|
return
|
|
end
|
|
|
|
if self:validate() then
|
|
self:work(tick)
|
|
self.command = commands[self.command]
|
|
set_timeout_in_ticks(self:get_update_rate(), work_token, event)
|
|
else
|
|
self:remove()
|
|
end
|
|
end
|
|
)
|
|
|
|
--- Restores a given entity to their original force
|
|
local restore_force_token =
|
|
Task.register(
|
|
function (event)
|
|
if not event then
|
|
return
|
|
end
|
|
local force_name = event.force_name
|
|
if not force_name then
|
|
return
|
|
end
|
|
|
|
local state = Public.get_unit(event.unit_number)
|
|
if state then
|
|
state:set_force()
|
|
state.frenzied = false
|
|
end
|
|
end
|
|
)
|
|
|
|
local function create_forces()
|
|
local aggressors = game.forces.aggressors
|
|
local aggressors_frenzy = game.forces.aggressors_frenzy
|
|
local enemy = game.forces.enemy
|
|
if not aggressors then
|
|
aggressors = game.create_force('aggressors')
|
|
end
|
|
if not aggressors_frenzy then
|
|
aggressors_frenzy = game.create_force('aggressors_frenzy')
|
|
end
|
|
|
|
aggressors.set_friend('aggressors_frenzy', true)
|
|
aggressors.set_cease_fire('aggressors_frenzy', true)
|
|
aggressors.set_friend('enemy', true)
|
|
aggressors.set_cease_fire('enemy', true)
|
|
|
|
aggressors_frenzy.set_friend('aggressors', true)
|
|
aggressors_frenzy.set_cease_fire('aggressors', true)
|
|
aggressors_frenzy.set_friend('enemy', true)
|
|
aggressors_frenzy.set_cease_fire('enemy', true)
|
|
|
|
enemy.set_friend('aggressors', true)
|
|
enemy.set_cease_fire('aggressors', true)
|
|
enemy.set_friend('aggressors_frenzy', true)
|
|
enemy.set_cease_fire('aggressors_frenzy', true)
|
|
end
|
|
|
|
local create_forces_token =
|
|
Task.register(
|
|
function ()
|
|
create_forces()
|
|
end
|
|
)
|
|
|
|
local function has_unit_limit_reached()
|
|
local uid = Public.get_count()
|
|
if uid >= (this.settings.unit_limit and this.settings.unit_limit) then
|
|
Public.debug_print('has_unit_limit_reached - Max units reached?')
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function is_closer(pos1, pos2, pos)
|
|
return ((pos1.x - pos.x) ^ 2 + (pos1.y - pos.y) ^ 2) < ((pos2.x - pos.x) ^ 2 + (pos2.y - pos.y) ^ 2)
|
|
end
|
|
|
|
local function shuffle_distance(tbl, position)
|
|
local size = #tbl
|
|
for i = size, 1, -1 do
|
|
local rand = random(size)
|
|
if is_closer(tbl[i].position, tbl[rand].position, position) and i > rand then
|
|
tbl[i], tbl[rand] = tbl[rand], tbl[i]
|
|
end
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
local function aoe_punch(entity, target, damage)
|
|
if not (target and target.valid) then
|
|
return
|
|
end
|
|
|
|
local base_vector = { target.position.x - entity.position.x, target.position.y - entity.position.y }
|
|
|
|
local vector = { base_vector[1], base_vector[2] }
|
|
vector[1] = vector[1] * 1000
|
|
vector[2] = vector[2] * 1000
|
|
|
|
if abs(vector[1]) > abs(vector[2]) then
|
|
local d = abs(vector[1])
|
|
if abs(vector[1]) > 0 then
|
|
vector[1] = vector[1] / d
|
|
end
|
|
if abs(vector[2]) > 0 then
|
|
vector[2] = vector[2] / d
|
|
end
|
|
else
|
|
local d = abs(vector[2])
|
|
if abs(vector[2]) > 0 then
|
|
vector[2] = vector[2] / d
|
|
end
|
|
if abs(vector[1]) > 0 and d > 0 then
|
|
vector[1] = vector[1] / d
|
|
end
|
|
end
|
|
|
|
vector[1] = vector[1] * 1.5
|
|
vector[2] = vector[2] * 1.5
|
|
|
|
local a = 0.20
|
|
|
|
local cs = entity.surface
|
|
local cp = entity.position
|
|
|
|
local valid_enemy_forces = Public.get('valid_enemy_forces')
|
|
|
|
for i = 1, 16, 1 do
|
|
for x = i * -1 * a, i * a, 1 do
|
|
for y = i * -1 * a, i * a, 1 do
|
|
local p = { cp.x + x + vector[1] * i, cp.y + y + vector[2] * i }
|
|
cs.create_trivial_smoke({ name = 'train-smoke', position = p })
|
|
for _, e in pairs(cs.find_entities({ { p[1] - a, p[2] - a }, { p[1] + a, p[2] + a } })) do
|
|
if e.valid then
|
|
if e.health then
|
|
if e.destructible and e.minable and not valid_enemy_forces[e.force.name] then
|
|
if e.force.index ~= entity.force.index then
|
|
if e.valid then
|
|
e.health = e.health - damage * 0.05
|
|
if e.health <= 0 then
|
|
e.die(e.force.name, entity)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function do_projectile(surface, name, _position, _force, target, max_range)
|
|
surface.create_entity(
|
|
{
|
|
name = name,
|
|
position = _position,
|
|
force = _force,
|
|
source = _position,
|
|
target = target or nil,
|
|
max_range = max_range or nil,
|
|
speed = 0.4,
|
|
fast_replace = true,
|
|
create_build_effect_smoke = false
|
|
}
|
|
)
|
|
return true
|
|
end
|
|
|
|
local function area_of_effect(entity, radius, callback, find_entities)
|
|
if not radius then
|
|
return
|
|
end
|
|
|
|
local function get_area(pos, dist)
|
|
local area = {
|
|
left_top = {
|
|
x = pos.x - dist,
|
|
y = pos.y - dist
|
|
},
|
|
right_bottom = {
|
|
x = pos.x + dist,
|
|
y = pos.y + dist
|
|
}
|
|
}
|
|
return area
|
|
end
|
|
|
|
local cs = entity.surface
|
|
local cp = entity.position
|
|
|
|
if radius and radius > 256 then
|
|
radius = 256
|
|
end
|
|
|
|
local area = get_area(cp, radius)
|
|
|
|
for x = area.left_top.x, area.right_bottom.x, 1 do
|
|
for y = area.left_top.y, area.right_bottom.y, 1 do
|
|
local d = floor((cp.x - x) ^ 2 + (cp.y - y) ^ 2)
|
|
if d < radius then
|
|
local p = { x = x, y = y }
|
|
if find_entities then
|
|
for _, e in pairs(cs.find_entities({ { p.x - 1, p.y - 1 }, { p.x + 1, p.y + 1 } })) do
|
|
if e and e.valid and e.name ~= 'character' and e.health and e.destructible then
|
|
callback(e, p)
|
|
end
|
|
end
|
|
else
|
|
callback(p)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function shoot_laser(surface, source, enemy)
|
|
local force = source.force
|
|
surface.create_entity { name = 'laser-beam', position = source.position, force = 'player', target = enemy, source = source, max_length = 32, duration = 60 }
|
|
enemy.damage(20 * (1 + force.get_ammo_damage_modifier('laser') + force.get_gun_speed_modifier('laser')), force, 'laser', source)
|
|
end
|
|
|
|
local function on_init()
|
|
this.states = {}
|
|
this.settings.spawned_units = 0
|
|
this.settings.frenzy_length = 3600
|
|
this.settings.frenzy_burst_length = 160
|
|
this.settings.update_rate = 120
|
|
this.target_settings = {}
|
|
this.settings.final_battle = false
|
|
create_forces()
|
|
Task.set_timeout_in_ticks(30, create_forces_token)
|
|
end
|
|
|
|
local function on_wave_created(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
|
|
local wave_number = event.wave_number
|
|
if not wave_number then
|
|
return
|
|
end
|
|
|
|
this.settings.wave_number = wave_number
|
|
|
|
if wave_number % 50 == 0 then
|
|
local state = Public.get_any()
|
|
if state then
|
|
local old = state:get_melee_speed()
|
|
state:set_attack_speed(old + 0.05)
|
|
end
|
|
elseif wave_number % 100 == 0 then
|
|
Public.enemy_weapon_damage('aggressors')
|
|
Public.enemy_weapon_damage('aggressors_frenzy')
|
|
end
|
|
end
|
|
|
|
local function on_unit_group_created(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
|
|
if not this.settings.enabled_ug then
|
|
return
|
|
end
|
|
|
|
local unit_group = event.unit_group
|
|
if not unit_group or not unit_group.valid then
|
|
return
|
|
end
|
|
|
|
for _, entity in pairs(unit_group.members) do
|
|
if not Public.get_unit(entity.unit_number) then
|
|
local data = {
|
|
entity = entity,
|
|
}
|
|
local state = Public.new(data)
|
|
if not state then
|
|
return
|
|
end
|
|
state:set_burst_frenzy()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_target_aquired(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
|
|
local target = event.target
|
|
if not target or not target.valid then
|
|
return
|
|
end
|
|
|
|
if this.target_settings.main_target and this.target_settings.main_target.valid then
|
|
if this.target_settings.main_target.unit_number ~= target.unit_number then
|
|
this.target_settings.main_target = event.target
|
|
end
|
|
else
|
|
this.target_settings.main_target = event.target
|
|
end
|
|
end
|
|
|
|
local function on_entity_created(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
|
|
local entity = event.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local state = Public.get_unit(entity.unit_number)
|
|
|
|
if not state then
|
|
local data = {
|
|
entity = entity
|
|
}
|
|
|
|
if this.settings.track_bosses_only then
|
|
if event.boss_unit then
|
|
state = Public.new(data)
|
|
if not state then
|
|
return
|
|
end
|
|
state:set_burst_frenzy()
|
|
state:set_boss()
|
|
end
|
|
else
|
|
state = Public.new(data)
|
|
if not state then
|
|
entity.destroy()
|
|
return
|
|
end
|
|
state:set_burst_frenzy()
|
|
if event.boss_unit then
|
|
state:set_boss()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_evolution_factor_changed(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
|
|
local evolution_factor = event.evolution_factor
|
|
if not evolution_factor then
|
|
return
|
|
end
|
|
|
|
local forces = game.forces
|
|
|
|
local surface_index = Public.get('surface_index')
|
|
|
|
if forces.aggressors.get_evolution_factor(surface_index) == 1 and evolution_factor == 1 then
|
|
return
|
|
end
|
|
|
|
forces.aggressors.set_evolution_factor(evolution_factor, surface_index)
|
|
forces.aggressors_frenzy.set_evolution_factor(evolution_factor, surface_index)
|
|
end
|
|
|
|
local function on_entity_died(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
|
|
local entity = event.entity
|
|
if not entity.valid then
|
|
return
|
|
end
|
|
|
|
local state = Public.get_unit(entity.unit_number)
|
|
if state then
|
|
state:remove(true)
|
|
end
|
|
end
|
|
|
|
local function on_entity_damaged(event)
|
|
if not this.settings.enabled then
|
|
return
|
|
end
|
|
local entity = event.entity
|
|
if not (entity and entity.valid) then
|
|
return
|
|
end
|
|
local state = Public.get_unit(entity.unit_number)
|
|
if not state then
|
|
return
|
|
end
|
|
|
|
local max = entity.max_health
|
|
|
|
if state.boss_unit then
|
|
state:spawn_children()
|
|
end
|
|
|
|
if random(1, 64) == 1 then
|
|
state:flee_command()
|
|
end
|
|
|
|
if state.boss_unit and entity.health <= max / 2 and state.teleported < 5 then
|
|
state.teleported = state.teleported + 1
|
|
if random(1, 4) == 1 then
|
|
state:switch_position()
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
---@param data table
|
|
---@return table|nil
|
|
function Public.new(data)
|
|
if has_unit_limit_reached() then
|
|
if data.entity and data.entity.valid then
|
|
data.entity.destroy()
|
|
end
|
|
return
|
|
end
|
|
|
|
local uid = Public.get_count()
|
|
local state = setmetatable({}, { __index = Public._esp })
|
|
local tick = game.tick
|
|
state.entity = data.entity
|
|
state.surface_id = state.entity.surface_index
|
|
state.force = game.forces.aggressors
|
|
state.unit_number = state.entity.unit_number
|
|
state.teleported = 0
|
|
state.uid = uid
|
|
state.command = 'goto'
|
|
state.last_command = 0
|
|
state.id = state.entity.unit_number
|
|
state.update_rate = this.settings.update_rate + (10 * state.uid)
|
|
state.ttl = data.ttl or tick + (5 * 3600) -- 5 minutes duration
|
|
state:check_unit_group()
|
|
state:validate()
|
|
|
|
set_timeout_in_ticks(state.update_rate, work_token, { unit_number = state.unit_number })
|
|
|
|
this.states[state.id] = state
|
|
|
|
return state
|
|
end
|
|
|
|
-- Adjusts the damage
|
|
function Public.enemy_weapon_damage(force)
|
|
if not force then
|
|
return
|
|
end
|
|
|
|
local e = game.forces[force]
|
|
|
|
local data = {
|
|
['artillery-shell'] = 0.05,
|
|
['biological'] = 0.06,
|
|
['beam'] = 0.08,
|
|
['bullet'] = 0.08,
|
|
['capsule'] = 0.08,
|
|
['electric'] = 0.08,
|
|
['flamethrower'] = 0.08,
|
|
['laser'] = 0.08,
|
|
['landmine'] = 0.08,
|
|
['melee'] = 0.08
|
|
}
|
|
|
|
for k, v in pairs(data) do
|
|
local new = Difficulty.get().value * v
|
|
|
|
local e_old = e.get_ammo_damage_modifier(k)
|
|
|
|
e.set_ammo_damage_modifier(k, new + e_old)
|
|
end
|
|
end
|
|
|
|
-- Gets a given unit
|
|
--- @param unit_number number
|
|
---@return table|nil
|
|
function Public.get_unit(unit_number)
|
|
return this.states[unit_number]
|
|
end
|
|
|
|
-- Gets a boss unit
|
|
---@return table|nil
|
|
function Public.get_boss_unit()
|
|
for _, state in pairs(this.states) do
|
|
if state and state.boss_unit then
|
|
return state
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Clears invalid units
|
|
---@return table|nil
|
|
function Public.check_states()
|
|
local c = 0
|
|
for _, state in pairs(this.states) do
|
|
if state then
|
|
if not (state.entity and state.entity.valid) then
|
|
state:remove()
|
|
else
|
|
c = c + 1
|
|
end
|
|
end
|
|
end
|
|
this.settings.spawned_units = c
|
|
end
|
|
|
|
-- Gets a boss unit
|
|
---@return integer
|
|
function Public.get_count()
|
|
local c = 1
|
|
for _, state in pairs(this.states) do
|
|
if state then
|
|
c = c + 1
|
|
end
|
|
end
|
|
|
|
this.settings.spawned_units = c
|
|
|
|
return c
|
|
end
|
|
|
|
-- Gets a first matched unit
|
|
---@return table|nil
|
|
function Public.get_any()
|
|
for _, state in pairs(this.states) do
|
|
if state then
|
|
return state
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Removes the given entity from tracking
|
|
function Public._esp:remove(raised)
|
|
if not raised and self.entity and self.entity.valid then
|
|
self.entity.destroy()
|
|
end
|
|
|
|
this.states[self.id] = nil
|
|
end
|
|
|
|
function Public._esp:get_update_rate()
|
|
return self.update_rate
|
|
end
|
|
|
|
-- Sets the entity force
|
|
function Public._esp:set_force()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
entity.force = game.forces.aggressors
|
|
self.force = entity.force
|
|
end
|
|
|
|
-- Grants the unit a frenzy attack speed
|
|
function Public._esp:set_frenzy()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
if self.frenzied then
|
|
return
|
|
end
|
|
|
|
self.frenzied = true
|
|
|
|
entity.force = game.forces.aggressors_frenzy
|
|
self.force = entity.force
|
|
end
|
|
|
|
-- Grants the unit a frenzy attack speed for a short time
|
|
function Public._esp:set_burst_frenzy()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
if self.frenzied then
|
|
return
|
|
end
|
|
|
|
self.frenzied = true
|
|
set_timeout_in_ticks(this.settings.frenzy_burst_length, restore_force_token, { force_name = self.force.name, unit_number = self.unit_number })
|
|
|
|
entity.force = game.forces.aggressors_frenzy
|
|
self.force = entity.force
|
|
end
|
|
|
|
-- Spawns biters that are one tier higher than the entity
|
|
function Public._esp:spawn_children()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
if has_unit_limit_reached() then
|
|
return
|
|
end
|
|
|
|
local tier = tiers[entity.name]
|
|
|
|
if not tier then
|
|
return
|
|
end
|
|
|
|
local max = entity.max_health
|
|
|
|
if entity.health <= max / 4 and not self.spawned_children then
|
|
self.spawned_children = true
|
|
Public.buried_biter(entity.surface, entity.position, 1, tier, entity.force.name)
|
|
end
|
|
end
|
|
|
|
--- Creates a beam.
|
|
function Public._esp:beam()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
Beams.new_beam_delayed(entity.surface, random(500, 3000))
|
|
end
|
|
|
|
--- Creates a laser.
|
|
function Public._esp:laser(boss)
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local limit = boss or 3
|
|
|
|
local surface = entity.surface
|
|
|
|
local enemies = surface.find_entities_filtered { radius = 10, limit = limit, force = 'player', position = entity.position }
|
|
if enemies == nil or #enemies == 0 then
|
|
return
|
|
end
|
|
for i = 1, #enemies, 1 do
|
|
local enemy = enemies[i]
|
|
if enemy and enemy.valid and enemy.health and enemy.health > 0 and enemy.destructible then
|
|
shoot_laser(surface, entity, enemy)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Creates spew.
|
|
function Public._esp:spew_damage()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local position = { entity.position.x + (-5 + random(0, 10)), entity.position.y + (-5 + random(0, 10)) }
|
|
|
|
entity.surface.create_entity({ name = 'acid-stream-spitter-medium', position = position, target = position, source = position })
|
|
end
|
|
|
|
--- Creates a projectile.
|
|
function Public._esp:fire_projectile()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local position = { entity.position.x + (-10 + random(0, 20)), entity.position.y + (-10 + random(0, 20)) }
|
|
|
|
entity.surface.create_entity(
|
|
{
|
|
name = projectiles[random(1, #projectiles)],
|
|
position = entity.position,
|
|
force = entity.force.name,
|
|
source = entity.position,
|
|
target = position,
|
|
max_range = 16,
|
|
speed = 0.01
|
|
}
|
|
)
|
|
end
|
|
|
|
--- Creates a aoe attack.
|
|
function Public._esp:aoe_attack()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local position = { x = entity.position.x + (-10 + random(0, 20)), y = entity.position.y + (-10 + random(0, 20)) }
|
|
|
|
local target = {
|
|
valid = true,
|
|
position = position
|
|
}
|
|
|
|
local damage = tier_damage[entity.name]
|
|
if not damage then
|
|
return
|
|
end
|
|
|
|
aoe_punch(entity, target, random(damage.min, damage.max))
|
|
end
|
|
|
|
--- Creates aoe attack.
|
|
function Public._esp:area_of_spit_attack(range)
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
area_of_effect(
|
|
entity,
|
|
range or 10,
|
|
function (p)
|
|
do_projectile(entity.surface, 'acid-stream-spitter-big', p, entity.force, p)
|
|
end,
|
|
false
|
|
)
|
|
end
|
|
|
|
function Public._esp:find_targets()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
if not self.last_searched then
|
|
self.last_searched = 0
|
|
end
|
|
|
|
if game.tick < self.last_searched then
|
|
return
|
|
end
|
|
|
|
self.last_searched = game.tick + 200
|
|
|
|
local unit_group_command_step_length = Public.get('unit_group_command_step_length')
|
|
local step_length = unit_group_command_step_length
|
|
|
|
local obstacles =
|
|
entity.surface.find_entities_filtered {
|
|
position = entity.position,
|
|
radius = step_length / 2,
|
|
type = { 'simple-entity', 'tree' },
|
|
limit = 50
|
|
}
|
|
if obstacles then
|
|
shuffle_distance(obstacles, entity.position)
|
|
self.commands = self.commands or {}
|
|
for ii = 1, #obstacles, 1 do
|
|
if obstacles[ii].valid then
|
|
self.commands[#self.commands + 1] = {
|
|
type = defines.command.attack,
|
|
target = obstacles[ii],
|
|
distraction = defines.distraction.by_anything
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Sets the attack speed for the given force
|
|
function Public._esp:set_attack_speed(speed)
|
|
if not speed then
|
|
speed = 1
|
|
end
|
|
|
|
self.force.set_gun_speed_modifier('melee', speed)
|
|
self.force.set_gun_speed_modifier('biological', speed * 2)
|
|
end
|
|
|
|
-- Gets the melee speed for the given force
|
|
---@return number
|
|
function Public._esp:get_melee_speed()
|
|
return self.force.get_gun_speed_modifier('melee')
|
|
end
|
|
|
|
--- Sets a new position near the LuaEntity.
|
|
---@return table|nil
|
|
function Public._esp:switch_position()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local position = { entity.position.x + (-5 + random(0, 15)), entity.position.y + (5 + random(0, 15)) }
|
|
|
|
local rand = entity.surface.find_non_colliding_position(entity.name, position, 0.2, 0.5)
|
|
if rand then
|
|
self.entity.teleport(rand)
|
|
end
|
|
|
|
return position
|
|
end
|
|
|
|
--- Validates if this unit has a unit_group.
|
|
---@return boolean|integer
|
|
function Public._esp:check_unit_group()
|
|
if not self:validate() then return false end
|
|
|
|
if not self.group or not self.group.valid then
|
|
local surface = self.surface_id and game.get_surface(self.surface_id) and game.get_surface(self.surface_id).valid and game.get_surface(self.surface_id)
|
|
local unit_group = surface.create_unit_group({ position = self.entity.position, force = self.force })
|
|
self.group = unit_group
|
|
end
|
|
|
|
if not self.group or not self.group.valid then
|
|
log('EnemyStates::CheckUnitGroup - Unit group is invalid')
|
|
return false
|
|
end
|
|
|
|
self.group.add_member(self.entity)
|
|
return true
|
|
end
|
|
|
|
--- Validates if a state is valid.
|
|
---@return boolean|integer
|
|
function Public._esp:validate()
|
|
if not self.id then
|
|
self:remove()
|
|
return false
|
|
end
|
|
if self.entity and self.entity.valid then
|
|
return true
|
|
else
|
|
self:remove()
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- Sets a unit as a boss unit
|
|
function Public._esp:set_boss()
|
|
local entity = self.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local tick = game.tick
|
|
|
|
self.boss_unit = true
|
|
|
|
self:set_ttl(game.tick + (20 * 3600))
|
|
|
|
if this.settings.final_battle then
|
|
self.go_havoc = true
|
|
self.proj_int = tick + 120
|
|
self.clear_go_havoc = tick + 3600
|
|
end
|
|
|
|
if this.settings.wave_number > 1499 then
|
|
if this.settings.wave_number % 100 == 0 then
|
|
self.go_havoc = true
|
|
self.proj_int = tick + 120
|
|
self.clear_go_havoc = tick + 3600
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Sets the time to live timer for the unit
|
|
function Public._esp:set_ttl(ttl)
|
|
if not ttl then
|
|
error('No ttl given')
|
|
end
|
|
|
|
self.ttl = ttl
|
|
end
|
|
|
|
function Public._esp:go_to_location_command()
|
|
local unit = this.target_settings.main_target
|
|
if not unit or not unit.valid then
|
|
return
|
|
end
|
|
|
|
if not self.group or not self.group.valid then
|
|
self:check_unit_group()
|
|
end
|
|
|
|
local group_commands = {}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.go_to_location,
|
|
pathfind_flags = pf_flags,
|
|
destination = unit.position,
|
|
distraction = defines.distraction.by_enemy
|
|
}
|
|
|
|
if not self.command_added then
|
|
self.command_added = true
|
|
|
|
self.group.set_command(
|
|
{
|
|
type = defines.command.compound,
|
|
structure_type = defines.compound_command.return_last,
|
|
commands = group_commands
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
function Public._esp:attack_command()
|
|
local unit = this.target_settings.main_target
|
|
if not unit or not unit.valid then
|
|
return
|
|
end
|
|
|
|
if not self.group or not self.group.valid then
|
|
self:check_unit_group()
|
|
end
|
|
|
|
local group_commands = {}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.go_to_location,
|
|
pathfind_flags = pf_flags,
|
|
destination = unit.position,
|
|
distraction = defines.distraction.by_enemy
|
|
}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.attack,
|
|
target = unit
|
|
}
|
|
|
|
self.group.set_command(
|
|
{
|
|
type = defines.command.compound,
|
|
structure_type = defines.compound_command.return_last,
|
|
commands = group_commands
|
|
}
|
|
)
|
|
end
|
|
|
|
function Public._esp:attack_area_command()
|
|
local unit = this.target_settings.main_target
|
|
if not unit or not unit.valid then
|
|
return
|
|
end
|
|
|
|
if not self.group or not self.group.valid then
|
|
self:check_unit_group()
|
|
end
|
|
|
|
local group_commands = {}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.go_to_location,
|
|
pathfind_flags = pf_flags,
|
|
destination = unit.position,
|
|
distraction = defines.distraction.by_enemy
|
|
}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.attack_area,
|
|
destination = { x = unit.position.x, y = unit.position.y },
|
|
radius = 30,
|
|
distraction = defines.distraction.by_anything
|
|
}
|
|
|
|
self.group.set_command(
|
|
{
|
|
type = defines.command.compound,
|
|
structure_type = defines.compound_command.return_last,
|
|
commands = group_commands
|
|
}
|
|
)
|
|
end
|
|
|
|
function Public._esp:flee_command()
|
|
local unit = this.target_settings.main_target
|
|
if not unit or not unit.valid then
|
|
return
|
|
end
|
|
|
|
if not self.group or not self.group.valid then
|
|
self:check_unit_group()
|
|
end
|
|
|
|
local group_commands = {}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.flee,
|
|
from = unit,
|
|
}
|
|
|
|
group_commands[#group_commands + 1] = {
|
|
type = defines.command.flee,
|
|
from = unit,
|
|
}
|
|
|
|
self.group.set_command(
|
|
{
|
|
type = defines.command.compound,
|
|
structure_type = defines.compound_command.return_last,
|
|
commands = group_commands
|
|
}
|
|
)
|
|
end
|
|
|
|
function Public._esp:work(tick)
|
|
if self.go_frenzy then
|
|
self:set_frenzy()
|
|
end
|
|
|
|
if self.last_command < tick then
|
|
if self.command == 'goto' then
|
|
self:go_to_location_command()
|
|
elseif self.command == 'attack_area' then
|
|
self:attack_area_command()
|
|
end
|
|
self.last_command = tick + 500
|
|
|
|
if this.target_settings.main_target and this.target_settings.main_target.valid and this.target_settings.main_target.name == 'character' then
|
|
Event.raise(Public.events.on_primary_target_missing)
|
|
end
|
|
end
|
|
|
|
if self.go_havoc and self.clear_go_havoc > tick then
|
|
if tick < self.proj_int then
|
|
self:fire_projectile()
|
|
self.proj_int = tick + 120
|
|
end
|
|
return
|
|
end
|
|
|
|
|
|
if self.boss_unit then
|
|
if random(1, 20) == 1 then
|
|
self:spew_damage()
|
|
elseif random(1, 30) == 1 then
|
|
self:set_burst_frenzy()
|
|
elseif random(1, 40) == 1 then
|
|
self:find_targets()
|
|
elseif random(1, 50) == 1 then
|
|
self:fire_projectile()
|
|
elseif random(1, 60) == 1 then
|
|
self:laser(6)
|
|
elseif random(1, 70) == 1 then
|
|
if this.settings.wave_number >= 200 then
|
|
self:area_of_spit_attack()
|
|
end
|
|
elseif random(1, 80) == 1 then
|
|
if this.settings.wave_number >= 200 then
|
|
self:aoe_attack()
|
|
end
|
|
end
|
|
if tick > self.ttl then
|
|
self:remove()
|
|
end
|
|
elseif tick < self.ttl then
|
|
if random(1, 30) == 1 then
|
|
self:find_targets()
|
|
elseif random(1, 40) == 1 then
|
|
self:spew_damage()
|
|
elseif random(1, 50) == 1 then
|
|
self:set_burst_frenzy()
|
|
elseif random(1, 60) == 1 then
|
|
self:laser()
|
|
end
|
|
else
|
|
self:remove()
|
|
end
|
|
end
|
|
|
|
Event.on_init(on_init)
|
|
Event.add(de.on_entity_died, on_entity_died)
|
|
Event.add(de.on_entity_damaged, on_entity_damaged)
|
|
Event.add(ev.on_wave_created, on_wave_created)
|
|
Event.add(ev.on_unit_group_created, on_unit_group_created)
|
|
Event.add(ev.on_entity_created, on_entity_created)
|
|
Event.add(ev.on_target_aquired, on_target_aquired)
|
|
Event.add(ev.on_evolution_factor_changed, on_evolution_factor_changed)
|
|
Event.add(ev.on_game_reset, on_init)
|
|
Event.on_nth_tick(100, Public.check_states)
|
|
|
|
--- This gets values from our table
|
|
-- @param key <string>
|
|
function Public.get_es(key)
|
|
if key then
|
|
return this[key]
|
|
else
|
|
return this
|
|
end
|
|
end
|
|
|
|
--- This sets values to our table
|
|
-- use with caution.
|
|
-- @param key <string>
|
|
-- @param value <string/boolean/int>
|
|
function Public.set_es(key, value)
|
|
if key and (value or value == false or value == 'nil') then
|
|
if value == 'nil' then
|
|
this[key] = nil
|
|
else
|
|
this[key] = value
|
|
end
|
|
return this[key]
|
|
elseif key then
|
|
return this[key]
|
|
else
|
|
return this
|
|
end
|
|
end
|
|
|
|
---@param value boolean
|
|
function Public.set_module_status(value)
|
|
on_init()
|
|
this.settings.enabled = value or false
|
|
end
|
|
|
|
---@param value boolean
|
|
function Public.set_track_bosses_only(value)
|
|
this.settings.track_bosses_only = value or false
|
|
end
|
|
|
|
---@param value integer|number
|
|
function Public.set_es_unit_limit(value)
|
|
this.settings.unit_limit = value or 300
|
|
end
|
|
|
|
Public.has_unit_limit_reached = has_unit_limit_reached
|
|
|
|
return Public
|