mirror of
https://github.com/Refactorio/RedMew.git
synced 2024-12-14 10:13:13 +02:00
527 lines
16 KiB
Lua
527 lines
16 KiB
Lua
local Event = require 'utils.event'
|
|
local Task = require 'utils.task'
|
|
local Token = require 'utils.token'
|
|
local Global = require 'utils.global'
|
|
local math = require 'utils.math'
|
|
local table = require 'utils.table'
|
|
|
|
local random = math.random
|
|
local set_timeout_in_ticks = Task.set_timeout_in_ticks
|
|
local ceil = math.ceil
|
|
local draw_arc = rendering.draw_arc
|
|
local fast_remove = table.fast_remove
|
|
|
|
local tau = 2 * math.pi
|
|
local start_angle = -tau / 4
|
|
local update_rate = 4 -- ticks between updates
|
|
local time_to_live = update_rate + 1
|
|
local pole_respawn_time = 60 * 60
|
|
|
|
local no_coin_entity = {}
|
|
|
|
Global.register(
|
|
{no_coin_entity = no_coin_entity},
|
|
function(tbl)
|
|
no_coin_entity = tbl.no_coin_entity
|
|
end
|
|
)
|
|
|
|
local entity_drop_amount = {
|
|
--[[['small-biter'] = {low = -62, high = 1},
|
|
['small-spitter'] = {low = -62, high = 1},
|
|
['medium-biter'] = {low = -14, high = 1},
|
|
['medium-spitter'] = {low = -14, high = 1},
|
|
['big-biter'] = {low = -2, high = 1},
|
|
['big-spitter'] = {low = -2, high = 1},
|
|
['behemoth-biter'] = {low = 1, high = 1},
|
|
['behemoth-spitter'] = {low = 1, high = 1}, ]]
|
|
['biter-spawner'] = {low = 8, high = 24},
|
|
['spitter-spawner'] = {low = 8, high = 24},
|
|
['small-worm-turret'] = {low = 3, high = 10},
|
|
['medium-worm-turret'] = {low = 8, high = 24},
|
|
['big-worm-turret'] = {low = 15, high = 30},
|
|
['behemoth-worm-turret'] = {low = 25, high = 45}
|
|
}
|
|
|
|
local spill_items =
|
|
Token.register(
|
|
function(data)
|
|
local stack = {name = 'coin', count = data.count}
|
|
data.surface.spill_item_stack(data.position, stack, true)
|
|
end
|
|
)
|
|
|
|
local entity_spawn_map = {
|
|
['medium-biter'] = {name = 'small-worm-turret', count = 1, chance = 0.2},
|
|
['big-biter'] = {name = 'medium-worm-turret', count = 1, chance = 0.2},
|
|
['behemoth-biter'] = {name = 'big-worm-turret', count = 1, chance = 0.2},
|
|
['medium-spitter'] = {name = 'small-worm-turret', count = 1, chance = 0.2},
|
|
['big-spitter'] = {name = 'medium-worm-turret', count = 1, chance = 0.2},
|
|
['behemoth-spitter'] = {name = 'big-worm-turret', count = 1, chance = 0.2},
|
|
['biter-spawner'] = {type = 'biter', count = 5, chance = 1},
|
|
['spitter-spawner'] = {type = 'spitter', count = 5, chance = 1},
|
|
['behemoth-worm-turret'] = {
|
|
type = 'compound',
|
|
spawns = {
|
|
{name = 'behemoth-spitter', count = 2},
|
|
{name = 'behemoth-biter', count = 2}
|
|
},
|
|
chance = 1
|
|
},
|
|
['stone-furnace'] = {type = 'cause', count = 2, chance = 1},
|
|
['steel-furnace'] = {type = 'cause', count = 2, chance = 1},
|
|
['electric-furnace'] = {type = 'cause', count = 4, chance = 1},
|
|
['assembling-machine-1'] = {type = 'cause', count = 4, chance = 1},
|
|
['assembling-machine-2'] = {type = 'cause', count = 4, chance = 1},
|
|
['assembling-machine-3'] = {type = 'cause', count = 4, chance = 1},
|
|
['chemical-plant'] = {type = 'cause', count = 4, chance = 1},
|
|
['centrifuge'] = {type = 'cause', count = 6, chance = 1},
|
|
['pumpjack'] = {type = 'cause', count = 6, chance = 1},
|
|
['storage-tank'] = {type = 'cause', count = 4, chance = 1},
|
|
['oil-refinery'] = {type = 'cause', count = 8, chance = 1},
|
|
['offshore-pump'] = {type = 'cause', count = 2, chance = 1},
|
|
['boiler'] = {type = 'cause', count = 2, chance = 1},
|
|
['heat-exchanger'] = {type = 'cause', count = 4, chance = 1},
|
|
['steam-engine'] = {type = 'cause', count = 6, chance = 1},
|
|
['steam-turbine'] = {type = 'cause', count = 10, chance = 1},
|
|
['nuclear-reactor'] = {type = 'cause', count = 20, chance = 1},
|
|
['rocket-silo'] = {type = 'cause', count = 40, chance = 1},
|
|
['train-stop'] = {type = 'cause', count = 2, chance = 1},
|
|
['burner-mining-drill'] = {type = 'cause', count = 2, chance = 1},
|
|
['electric-mining-drill'] = {type = 'cause', count = 4, chance = 1},
|
|
['lab'] = {type = 'cause', count = 6, chance = 1},
|
|
['solar-panel'] = {type = 'cause', count = 4, chance = 1},
|
|
['accumulator'] = {type = 'cause', count = 2, chance = 1},
|
|
['beacon'] = {type = 'cause', count = 6, chance = 1},
|
|
['radar'] = {type = 'cause', count = 4, chance = 1}
|
|
}
|
|
|
|
local unit_levels = {
|
|
biter = {'small-biter', 'medium-biter', 'big-biter', 'behemoth-biter'},
|
|
spitter = {
|
|
'small-spitter',
|
|
'medium-spitter',
|
|
'big-spitter',
|
|
'behemoth-spitter'
|
|
}
|
|
}
|
|
|
|
local worms = {
|
|
['small-worm-turret'] = true,
|
|
['medium-worm-turret'] = true,
|
|
['big-worm-turret'] = true,
|
|
['behemoth-worm-turret'] = true
|
|
}
|
|
|
|
local allowed_cause_source = {
|
|
['small-biter'] = true,
|
|
['medium-biter'] = true,
|
|
['big-biter'] = true,
|
|
['behemoth-biter'] = true,
|
|
['small-spitter'] = true,
|
|
['medium-spitter'] = true,
|
|
['big-spitter'] = true,
|
|
['behemoth-spitter'] = true
|
|
}
|
|
|
|
local turret_evolution_factor = {
|
|
['gun-turret'] = 0.001,
|
|
['laser-turret'] = 0.002,
|
|
['flamethrower-turret'] = 0.0015,
|
|
['artillery-turret'] = 0.004
|
|
}
|
|
|
|
local spawn_worm =
|
|
Token.register(
|
|
function(data)
|
|
local surface = data.surface
|
|
local name = data.name
|
|
local position = data.position
|
|
|
|
local p = surface.find_non_colliding_position(name, position, 8, 1)
|
|
|
|
if p then
|
|
local entity = surface.create_entity({name = data.name, position = data.position})
|
|
no_coin_entity[entity.unit_number] = true
|
|
end
|
|
end
|
|
)
|
|
|
|
local function get_level()
|
|
local ef = game.forces.enemy.evolution_factor
|
|
if ef == 0 then
|
|
return 1
|
|
else
|
|
return ceil(ef * 4)
|
|
end
|
|
end
|
|
|
|
local spawn_units =
|
|
Token.register(
|
|
function(data)
|
|
local surface = data.surface
|
|
local name = data.name
|
|
local position = data.position
|
|
for _ = 1, data.count do
|
|
local p = surface.find_non_colliding_position(name, position, 8, 1)
|
|
if p then
|
|
surface.create_entity {name = name, position = p}
|
|
end
|
|
end
|
|
end
|
|
)
|
|
|
|
local spawn_player =
|
|
Token.register(
|
|
function(player)
|
|
if player and player.valid then
|
|
player.ticks_to_respawn = 3600
|
|
end
|
|
end
|
|
)
|
|
|
|
local function has_valid_turret(turrets)
|
|
for i = #turrets, 1, -1 do
|
|
local turret = turrets[i]
|
|
if turret.valid then
|
|
return true
|
|
else
|
|
fast_remove(turrets, i)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local pole_callback
|
|
pole_callback =
|
|
Token.register(
|
|
function(data)
|
|
if not has_valid_turret(data.turrets) then
|
|
return
|
|
end
|
|
|
|
local tick = data.tick
|
|
local now = game.tick
|
|
|
|
if now >= tick then
|
|
data.surface.create_entity(
|
|
{
|
|
name = data.name,
|
|
force = 'enemy',
|
|
position = data.position
|
|
}
|
|
)
|
|
return
|
|
end
|
|
|
|
local fraction = ((now - tick) / pole_respawn_time) + 1
|
|
|
|
draw_arc(
|
|
{
|
|
color = {1 - fraction, fraction, 0},
|
|
max_radius = 0.5,
|
|
min_radius = 0.4,
|
|
start_angle = start_angle,
|
|
angle = fraction * tau,
|
|
target = data.position,
|
|
surface = data.surface,
|
|
time_to_live = time_to_live
|
|
}
|
|
)
|
|
|
|
set_timeout_in_ticks(update_rate, pole_callback, data)
|
|
end
|
|
)
|
|
|
|
local filter = {area = nil, name = 'laser-turret', force = 'enemy'}
|
|
|
|
local function do_pole(entity)
|
|
if entity.type ~= 'electric-pole' then
|
|
return
|
|
end
|
|
|
|
local supply_area_distance = entity.prototype.supply_area_distance
|
|
if not supply_area_distance then
|
|
return
|
|
end
|
|
|
|
local surface = entity.surface
|
|
local position = entity.position
|
|
local x, y = position.x, position.y
|
|
local d = supply_area_distance / 2
|
|
filter.area = {{x - d, y - d}, {x + d, y + d}}
|
|
|
|
local turrets = surface.find_entities_filtered(filter)
|
|
|
|
if #turrets == 0 then
|
|
return
|
|
end
|
|
|
|
set_timeout_in_ticks(
|
|
update_rate,
|
|
pole_callback,
|
|
{
|
|
name = entity.name,
|
|
position = position,
|
|
surface = surface,
|
|
tick = game.tick + pole_respawn_time,
|
|
turrets = turrets
|
|
}
|
|
)
|
|
end
|
|
|
|
local function do_evolution(entity_name, entity_force)
|
|
local factor = turret_evolution_factor[entity_name]
|
|
if factor then
|
|
local old = entity_force.evolution_factor
|
|
local new = old + (1 - old) * factor
|
|
entity_force.evolution_factor = math.min(new, 1)
|
|
end
|
|
end
|
|
|
|
local bot_spawn_whitelist = {
|
|
['gun-turret'] = true,
|
|
['laser-turret'] = true,
|
|
['flamethrower-turret'] = true,
|
|
['artillery-turret'] = true
|
|
}
|
|
|
|
local bot_cause_whitelist = {
|
|
['character'] = true,
|
|
['artillery-turret'] = true,
|
|
['artillery-wagon'] = true,
|
|
['spidertron'] = true
|
|
}
|
|
|
|
local function do_bot_spawn(entity_name, entity, event)
|
|
|
|
local cause = event.cause
|
|
local entity_force = entity.force
|
|
local ef = entity_force.evolution_factor
|
|
local create_entity = entity.surface.create_entity
|
|
|
|
if ef <= 0.2 then
|
|
return
|
|
end
|
|
|
|
local spawn_entity = {
|
|
position = entity.position,
|
|
target = cause,
|
|
force = entity_force
|
|
}
|
|
|
|
if not bot_spawn_whitelist[entity_name] then
|
|
if cause then
|
|
return
|
|
end
|
|
end
|
|
|
|
if not cause then
|
|
-- If we reach here then the player might have picked up the artillery turret before the projectile hit the entity and killed it.
|
|
-- We therefore won't know the location of the artillery turret/wagon that killed the turret, so let's punish them and send even more bots, straight at spawn?
|
|
for i = 1, 60 do
|
|
spawn_entity.name = 'destroyer-capsule'
|
|
spawn_entity.speed = 0.2
|
|
spawn_entity.target = {0,0}
|
|
create_entity(spawn_entity)
|
|
end
|
|
return
|
|
end
|
|
|
|
if not bot_cause_whitelist[cause.name] then
|
|
return
|
|
end
|
|
|
|
local repeat_cycle = 1 -- The number of times a squad of robots are spawned default must be 1
|
|
if ef > .95 then
|
|
repeat_cycle = 2
|
|
end
|
|
|
|
if cause.name ~= 'character' then
|
|
if (entity_name == 'artillery-turret') or (entity_name == 'artillery-wagon') then
|
|
repeat_cycle = 15
|
|
else
|
|
repeat_cycle = 4
|
|
end
|
|
for i = 1, repeat_cycle do
|
|
if (cause.name == 'artillery-turret') or (cause.name == 'artillery-wagon') then
|
|
spawn_entity.target = cause.position -- Overwrite target. Artillery turrets/wagons don't move so send them to entity position. Stops players from picking up the arty and the bots stopping dead.
|
|
spawn_entity.speed = 0.2
|
|
|
|
spawn_entity.name = 'defender-capsule' -- use 'defender-capsule' (projectile) not 'defender' (entity) since a projectile can target a position but a capsule entity must have another entity as target
|
|
create_entity(spawn_entity)
|
|
create_entity(spawn_entity)
|
|
|
|
spawn_entity.name = 'destroyer-capsule'
|
|
create_entity(spawn_entity)
|
|
create_entity(spawn_entity)
|
|
else
|
|
-- projectiles don't have AI so won't track/follow a player
|
|
-- if the cause wasn't artillery turret/wagon then spawn a capsule entity not projectile
|
|
spawn_entity.name = 'defender' -- use defender-capsule (projectile) not defender (entity) since a projectile can target a position but a capsule entity must have another entity as target
|
|
create_entity(spawn_entity)
|
|
create_entity(spawn_entity)
|
|
|
|
spawn_entity.name = 'destroyer'
|
|
create_entity(spawn_entity)
|
|
create_entity(spawn_entity)
|
|
end
|
|
end
|
|
elseif entity_name == 'gun-turret' then
|
|
for i = 1, repeat_cycle do
|
|
spawn_entity.name = 'defender'
|
|
create_entity(spawn_entity)
|
|
create_entity(spawn_entity)
|
|
|
|
spawn_entity.name = 'destroyer'
|
|
create_entity(spawn_entity)
|
|
end
|
|
elseif entity_name == 'laser-turret' then
|
|
for i = 1, repeat_cycle do
|
|
spawn_entity.name = 'defender'
|
|
create_entity(spawn_entity)
|
|
|
|
spawn_entity.name = 'destroyer'
|
|
create_entity(spawn_entity)
|
|
create_entity(spawn_entity)
|
|
end
|
|
else
|
|
for i = 1, repeat_cycle do
|
|
spawn_entity.name = 'distractor-capsule'
|
|
spawn_entity.speed = 0
|
|
create_entity(spawn_entity)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function do_coin_drop(entity_name, entity)
|
|
local position = entity.position
|
|
local bounds = entity_drop_amount[entity_name]
|
|
if not bounds then
|
|
return
|
|
end
|
|
|
|
local unit_number = entity.unit_number
|
|
if no_coin_entity[unit_number] then
|
|
no_coin_entity[unit_number] = nil
|
|
return
|
|
end
|
|
|
|
local count = random(bounds.low, bounds.high)
|
|
if count > 0 then
|
|
set_timeout_in_ticks(
|
|
1,
|
|
spill_items,
|
|
{
|
|
count = count,
|
|
surface = entity.surface,
|
|
position = position
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
local function do_spawn_entity(entity_name, entity, event)
|
|
local spawn = entity_spawn_map[entity_name]
|
|
if not spawn then
|
|
return
|
|
end
|
|
|
|
local chance = spawn.chance
|
|
if chance ~= 1 and random() > chance then
|
|
return
|
|
end
|
|
|
|
local name = spawn.name
|
|
if name == nil then
|
|
local type = spawn.type
|
|
if type == 'cause' then
|
|
local cause = event.cause
|
|
if not cause then
|
|
return
|
|
end
|
|
name = cause.name
|
|
if not allowed_cause_source[cause.name] then
|
|
return
|
|
end
|
|
elseif type == 'compound' then
|
|
local spawns = spawn.spawns
|
|
spawn = spawns[random(#spawns)]
|
|
name = spawn.name
|
|
else
|
|
name = unit_levels[type][get_level()]
|
|
end
|
|
end
|
|
|
|
local position = entity.position
|
|
if worms[name] then
|
|
set_timeout_in_ticks(
|
|
5,
|
|
spawn_worm,
|
|
{
|
|
surface = entity.surface,
|
|
name = name,
|
|
position = position
|
|
}
|
|
)
|
|
else
|
|
set_timeout_in_ticks(
|
|
5,
|
|
spawn_units,
|
|
{
|
|
surface = entity.surface,
|
|
name = name,
|
|
position = position,
|
|
count = spawn.count
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
Event.add(
|
|
defines.events.on_entity_died,
|
|
function(event)
|
|
local entity = event.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local entity_force = entity.force
|
|
local entity_name = entity.name
|
|
|
|
if entity_force.name == 'enemy' then
|
|
do_pole(entity)
|
|
do_evolution(entity_name, entity_force)
|
|
do_coin_drop(entity_name, entity)
|
|
do_bot_spawn(entity_name, entity, event)
|
|
end
|
|
|
|
do_spawn_entity(entity_name, entity, event)
|
|
end
|
|
)
|
|
|
|
Event.add(
|
|
defines.events.on_player_died,
|
|
function(event)
|
|
local player = game.get_player(event.player_index)
|
|
set_timeout_in_ticks(1, spawn_player, player)
|
|
end
|
|
)
|
|
|
|
Event.add(
|
|
defines.events.on_combat_robot_expired,
|
|
function(event)
|
|
local entity = event.robot
|
|
local position = entity.position
|
|
local owner = event.owner
|
|
if owner == nil or not owner.valid then
|
|
return
|
|
end
|
|
if entity.force.name == 'enemy' and owner.name == "artillery-wagon" then
|
|
-- only create a grenade entity if an artillery wagon (the event owner) killed the target that spawned the combabt robot
|
|
entity.surface.create_entity{name = "cluster-grenade", position=position, target=position, speed=1}
|
|
end
|
|
end
|
|
)
|