1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2024-12-16 10:19:27 +02:00
RedMew/map_gen/maps/crash_site/outpost_builder.lua
Jayefuu d3aa8f18e0 Added configurable pollution to outposts
My plan is to make a harder crash site version where getting free resources from outposts creates pollution.

My intention is to balance it so it's still better to get items from outposts, but this commit is intended to give me a way to take saved games and test different amounts of pollution to check for balance. I don't intend to test this on live for a while but want to test it offline, this will give me the ability to turn it on half way through a game.

Need to consider how it doesn't take into account what the item is so it will be better to get free end-game items and probably limit how much iron we let into base later in the game. Might also create interesting meta where we are careful about what resources we take from outposts depending upon the pollution making setting.
2021-02-02 18:26:17 +00:00

1953 lines
51 KiB
Lua

--local Random = require 'map_gen.shared.random'
local Command = require 'utils.command'
local Ranks = require 'resources.ranks'
local Token = require 'utils.token'
local Global = require 'utils.global'
local Event = require 'utils.event'
local Task = require 'utils.task'
local Retailer = require 'features.retailer'
local Donator = require 'features.donator'
local RS = require 'map_gen.shared.redmew_surface'
local Server = require 'features.server'
local CrashSiteToast = require 'map_gen.maps.crash_site.crash_site_toast'
local ScoreTracker = require 'utils.score_tracker'
local change_for_player = ScoreTracker.change_for_player
local coins_earned_name = 'coins-earned'
local table = require 'utils.table'
--local next = next
local pairs = pairs
local concat = table.concat
local floor = math.floor
local format = string.format
local tostring = tostring
local draw_text = rendering.draw_text
local render_mode_game = defines.render_mode.game
local b = require 'map_gen.shared.builders'
local direction_bit_mask = 0xc0000000
local section_bit_mask = 0x30000000
local level_bit_mask = 0x0fffffff
--local not_level_bit_mask = 0xf0000000
local direction_bit_shift = 30
local section_bit_shift = 28
--local section_straight = 0
--local section_outer_corner = 1
--local section_inner_corner = 2
local wall_north_straight = 0x00000001
local wall_east_straight = 0x40000001
local wall_south_straight = 0x80000001
local wall_west_straight = 0xc0000001
local wall_north_outer = 0x10000001
local wall_east_outer = 0x50000001
local wall_south_outer = 0x90000001
local wall_west_outer = 0xd0000001
local wall_north_inner = 0x20000001
local wall_east_inner = 0x60000001
local wall_south_inner = 0xa0000001
local wall_west_inner = 0xe0000001
local default_part_size = 6
local magic_crafters_per_tick = 3
local magic_fluid_crafters_per_tick = 8
local refill_turrets = {index = 1}
local power_sources = {}
local turret_to_outpost = {}
local magic_crafters = {index = 1}
local magic_fluid_crafters = {index = 1}
local outposts = {}
local artillery_outposts = {index = 1}
local outpost_count = 0
local pollution_multiplier = 0
Global.register(
{
refil_turrets = refill_turrets,
power_sources = power_sources,
turret_to_outpost = turret_to_outpost,
magic_crafters = magic_crafters,
magic_fluid_crafters = magic_fluid_crafters,
outposts = outposts,
artillery_outposts = artillery_outposts,
pollution_multiplier = pollution_multiplier
},
function(tbl)
refill_turrets = tbl.refil_turrets
power_sources = tbl.power_sources
turret_to_outpost = tbl.turret_to_outpost
magic_crafters = tbl.magic_crafters
magic_fluid_crafters = tbl.magic_fluid_crafters
outposts = tbl.outposts
artillery_outposts = tbl.artillery_outposts
pollution_multiplier = tbl.pollution_multiplier
end
)
--[[ local function get_direction(part)
local dir = bit32.band(part, direction_bit_mask)
return bit32.rshift(dir, direction_bit_shift - 1)
end ]]
local function get_4_way_direction(part)
local dir = bit32.band(part, direction_bit_mask)
return bit32.rshift(dir, direction_bit_shift)
end
local function get_section(part)
local sec = bit32.band(part, section_bit_mask)
return bit32.rshift(sec, section_bit_shift)
end
local function get_level(part)
return bit32.band(part, level_bit_mask)
end
--[[ local function set_level(part, level)
local not_level = bit32.band(part)
return not_level + level
end
local function set_block(tbl, x, y, value)
tbl[(y - 1) * tbl.size + x] = value
end
local function get_block(tbl, x, y)
local size = tbl.size
if x < 1 or x > size or y < 1 or y > size then
return 0
end
return tbl[(y - 1) * size + x] or 0
end ]]
local function fast_remove(tbl, index)
local count = #tbl
if index > count then
return
elseif index < count then
tbl[index] = tbl[count]
end
tbl[count] = nil
end
local Public = {}
Public.__index = Public
Public.empty_template = {}
function Public.new(random)
local obj = {random = random}
return setmetatable(obj, Public)
end
local function do_walls(self, blocks, outpost_variance, outpost_min_step)
local size = blocks.size
local random = self.random
local max_variance = size - outpost_variance + 1
local variance_step = outpost_variance + outpost_min_step
local x = random:next_int(1, outpost_variance)
local y = random:next_int(1, outpost_variance)
local start_x, start_y = x, y
local i = (y - 1) * size + x
local pv = -1
-- top
while x < size do
local tx = x + random:next_int(outpost_min_step, variance_step)
tx = math.min(tx, size)
if pv == 0 then
blocks[i] = wall_north_straight
elseif pv == -1 then
blocks[i] = wall_north_outer
else
blocks[i] = wall_east_inner
end
x = x + 1
i = i + 1
while x < tx do
blocks[i] = wall_north_straight
x = x + 1
i = i + 1
end
if x < size - outpost_min_step then
local ty = random:next_int(1, outpost_variance)
if y == ty then
pv = 0
elseif y < ty then
pv = 1
blocks[i] = wall_east_outer
y = y + 1
i = i + size
while y < ty do
blocks[i] = wall_east_straight
y = y + 1
i = i + size
end
else
pv = -1
blocks[i] = wall_north_inner
y = y - 1
i = i - size
while y > ty do
blocks[i] = wall_west_straight
y = y - 1
i = i - size
end
end
else
pv = 0
end
end
pv = 1
-- right
while y < size do
local ty = y + random:next_int(outpost_min_step, variance_step)
ty = math.min(ty, size)
if pv == 0 then
blocks[i] = wall_east_straight
elseif pv == -1 then
blocks[i] = wall_south_inner
else
blocks[i] = wall_east_outer
end
y = y + 1
i = i + size
while y < ty do
blocks[i] = wall_east_straight
y = y + 1
i = i + size
end
if y < size - outpost_min_step then
local tx = random:next_int(max_variance, size)
if x == tx then
pv = 0
elseif x < tx then
pv = 1
blocks[i] = wall_east_inner
x = x + 1
i = i + 1
while x < tx do
blocks[i] = wall_north_straight
x = x + 1
i = i + 1
end
else
pv = -1
blocks[i] = wall_south_outer
x = x - 1
i = i - 1
while x > tx do
blocks[i] = wall_south_straight
x = x - 1
i = i - 1
end
end
else
pv = 0
end
end
pv = 1
-- bottom
while x > 1 do
local tx = x - random:next_int(outpost_min_step, variance_step)
tx = math.max(tx, 1)
if pv == 0 then
blocks[i] = wall_south_straight
elseif pv == -1 then
blocks[i] = wall_west_inner
else
blocks[i] = wall_south_outer
end
x = x - 1
i = i - 1
while x > tx do
blocks[i] = wall_south_straight
x = x - 1
i = i - 1
end
if x > outpost_min_step + 1 then
local ty = random:next_int(max_variance, size)
if y == ty then
pv = 0
elseif y < ty then
pv = 1
blocks[i] = wall_south_inner
y = y + 1
i = i + size
while y < ty do
blocks[i] = wall_east_straight
y = y + 1
i = i + size
end
else
pv = -1
blocks[i] = wall_west_outer
y = y - 1
i = i - size
while y > ty do
blocks[i] = wall_west_straight
y = y - 1
i = i - size
end
end
else
pv = 0
end
end
pv = -1
-- left
local bottom_left_y = y
while y > start_y + variance_step do
local ty = y - random:next_int(outpost_min_step, variance_step)
ty = math.max(ty, start_y)
if pv == 0 then
blocks[i] = wall_west_straight
elseif pv == -1 then
blocks[i] = wall_west_outer
else
blocks[i] = wall_north_inner
end
y = y - 1
i = i - size
while y > ty do
blocks[i] = wall_west_straight
y = y - 1
i = i - size
end
if y > start_y + variance_step + outpost_min_step then
local tx = random:next_int(1, outpost_variance)
if x == tx then
pv = 0
elseif x < tx then
pv = 1
--blocks[i] = wall_west_outer
blocks[i] = wall_north_outer
x = x + 1
i = i + 1
while x < tx do
blocks[i] = wall_north_straight
x = x + 1
i = i + 1
end
else
pv = -1
blocks[i] = wall_west_inner
x = x - 1
i = i - 1
while x > tx do
blocks[i] = wall_south_straight
x = x - 1
i = i - 1
end
end
else
pv = 0
end
end
-- final connection
if y == bottom_left_y then
blocks[i] = wall_west_outer
y = y - 1
i = i - size
while y > bottom_left_y - outpost_min_step do
blocks[i] = wall_west_straight
y = y - 1
i = i - size
end
end
if x == start_x then
pv = 0
elseif x < start_x then
pv = 1
blocks[i] = wall_north_outer
x = x + 1
i = i + 1
while x < start_x do
blocks[i] = wall_north_straight
x = x + 1
i = i + 1
end
else
pv = -1
blocks[i] = wall_west_inner
x = x - 1
i = i - 1
while x > start_x do
blocks[i] = wall_south_straight
x = x - 1
i = i - 1
end
end
if pv == 0 then
blocks[i] = wall_west_straight
elseif pv == -1 then
blocks[i] = wall_west_outer
else
blocks[i] = wall_north_inner
end
y = y - 1
i = i - size
while y > start_y do
blocks[i] = wall_west_straight
y = y - 1
i = i - size
end
end
local function fill(blocks)
local size = blocks.size
local anti_set = {size = size}
local anti_stack = {}
local y_offset = (size - 1) * size
for x = 1, size do
if blocks[x] == nil then
anti_stack[#anti_stack + 1] = {x = x, y = 1}
end
if blocks[x + y_offset] == nil then
anti_stack[#anti_stack + 1] = {x = x, y = size}
end
end
for y = 2, size do
y_offset = (y - 1) * size
if blocks[y_offset + 1] == nil then
anti_stack[#anti_stack + 1] = {x = 1, y = y}
end
if blocks[y_offset + size] == nil then
anti_stack[#anti_stack + 1] = {x = size, y = y}
end
end
while #anti_stack > 0 do
local point = table.remove(anti_stack)
local x, y = point.x, point.y
local offset = (y - 1) * size + x
anti_set[offset] = true
if x > 1 then
local x2 = x - 1
local offset2 = offset - 1
if not anti_set[offset2] and not blocks[offset2] then
anti_stack[#anti_stack + 1] = {x = x2, y = y}
end
end
if x < size then
local x2 = x + 1
local offset2 = offset + 1
if not anti_set[offset2] and not blocks[offset2] then
anti_stack[#anti_stack + 1] = {x = x2, y = y}
end
end
if y > 1 then
local y2 = y - 1
local offset2 = offset - size
if not anti_set[offset2] and not blocks[offset2] then
anti_stack[#anti_stack + 1] = {x = x, y = y2}
end
end
if y < size then
local y2 = y + 1
local offset2 = offset + size
if not anti_set[offset2] and not blocks[offset2] then
anti_stack[#anti_stack + 1] = {x = x, y = y2}
end
end
end
for y = 1, size do
local offset = (y - 1) * size
for x = 1, size do
local i = offset + x
if not anti_set[i] and not blocks[i] then
blocks[i] = 2
end
end
end
end
local function do_levels(blocks, max_level)
local size = blocks.size
local level = 2
while level < max_level do
local next_level = level + 1
for y = 1, size do
local offset = (y - 1) * size
for x = 1, size do
local i = offset + x
if get_level(blocks[i] or 0) >= level then
local count = 0
if x > 1 and get_level(blocks[i - 1] or 0) >= level then
count = count + 1
end
if x < size and get_level(blocks[i + 1] or 0) >= level then
count = count + 1
end
if y > 1 and get_level(blocks[i - size] or 0) >= level then
count = count + 1
end
if y < size and get_level(blocks[i + size] or 0) >= level then
count = count + 1
end
if count == 4 then
blocks[i] = next_level
end
end
end
end
level = level + 1
end
local levels = {}
blocks.levels = levels
for i = 1, max_level do
levels[i] = {}
end
for y = 1, size do
local offset = (y - 1) * size
for x = 1, size do
local i = offset + x
local block = blocks[i]
if block then
local l = get_level(block)
local lvl = levels[l]
lvl[#lvl + 1] = i
end
end
end
end
local function get_template(random, templates, templates_count, counts)
local template
if templates_count == 0 then
return nil
elseif templates_count == 1 then
template = templates[1]
else
local ti = random:next_int(1, templates_count)
template = templates[ti]
end
if template == Public.empty_template then
return nil
end
local count = counts[template] or 0
local max_count = template.max_count
while count == max_count do
template = template.fallback
if template == nil then
return nil
end
count = counts[template] or 0
max_count = template.max_count
end
counts[template] = count + 1
return template
end
local function make_blocks(self, blocks, template)
local random = self.random
local counts = {}
local levels = blocks.levels
local wall_level = levels[1]
local walls = template.walls
local wall_template_count = #walls
while #wall_level > 0 do
local index = random:next_int(1, #wall_level)
local i = wall_level[index]
fast_remove(wall_level, index)
local block = get_template(random, walls, wall_template_count, counts)
if block == Public.empty_template then
blocks[i] = nil
else
local block_data = blocks[i]
local section = get_section(block_data)
local dir = get_4_way_direction(block_data)
local new_block = block[section + 1][dir + 1]
blocks[i] = new_block
end
end
local bases = template.bases
for l = 2, #levels do
local level = levels[l]
local base_templates = bases[l - 1]
if base_templates then
local base_template_count = #base_templates
while #level > 0 do
local index = random:next_int(1, #level)
local i = level[index]
fast_remove(level, index)
blocks[i] = get_template(random, base_templates, base_template_count, counts)
end
else
for index = 1, #level do
local i = level[index]
blocks[i] = nil
end
end
end
end
local remove_entity_types = {'tree', 'simple-entity'}
local function to_shape(blocks, part_size, on_init)
part_size = part_size or default_part_size
local inv_part_size = 1 / part_size
local size = blocks.size
local t_size = size * part_size
local half_t_size = t_size * 0.5
local outpost_id = outpost_count + 1
outpost_count = outpost_id
if on_init then
outposts[outpost_id] = {
outpost_id = outpost_id,
magic_crafters = {},
--magic_fluid_crafters = {},
market = nil,
turret_count = 0,
top_left = {nil, nil},
bottom_right = {nil, nil},
level = 1,
upgrade_rate = nil,
upgrade_base_cost = nil,
upgrade_cost_base = nil,
artillery_area = nil,
artillery_turrets = nil,
last_fire_tick = nil
}
end
local function shape(x, y, world)
x, y = floor(x + half_t_size), floor(y + half_t_size)
if x < 0 or y < 0 or x >= t_size or y >= t_size then
return false
end
local x2, y2 = floor(x * inv_part_size), floor(y * inv_part_size)
local template = blocks[y2 * size + x2 + 1]
if not template then
return false
end
local wx, wy = world.x, world.y
local entities =
world.surface.find_entities_filtered {
area = {{wx, wy}, {wx + 1, wy + 1}},
type = remove_entity_types
}
for i = 1, #entities do
local e = entities[i]
e.destroy()
end
local x3, y3 = x - x2 * part_size, y - y2 * part_size
local i = y3 * part_size + x3 + 1
local entry = template[i]
if not entry then
return false
end
local entity = entry.entity
local tile = entry.tile or true
if entity then
local data
local callback = entity.callback
if callback then
data = {outpost_id = outpost_id}
local cd = template[callback]
callback = cd.callback
data.callback_data = cd.data
end
return {
tile = tile,
entities = {
{
name = entity.name,
direction = entity.direction,
force = entity.force or template.force,
callback = callback,
data = data,
always_place = true
}
}
}
end
return tile
end
return b.change_map_gen_collision_hidden_tile(shape, 'water-tile', 'grass-1')
end
Public.to_shape = to_shape
function Public:do_outpost(template, on_init)
local settings = template.settings
local blocks = {size = settings.blocks}
do_walls(self, blocks, settings.variance, settings.min_step)
fill(blocks)
do_levels(blocks, settings.max_level)
make_blocks(self, blocks, template)
return to_shape(blocks, settings.part_size, on_init)
end
local function change_direction(entity, new_dir)
local copy = {}
for k, v in pairs(entity) do
copy[k] = v
end
copy.direction = new_dir
return copy
end
function Public.make_1_way(data)
data.__index = data
return data
end
local function set_tile(tbl, index, tile)
local entry = tbl[index]
if entry then
entry.tile = tile
else
tbl[index] = {tile = tile}
end
end
local function set_entity(tbl, index, entity)
local entry = tbl[index]
if entry then
entry.entity = entity
else
tbl[index] = {entity = entity}
end
end
function Public.make_4_way(data)
local part_size = data.part_size or default_part_size
local inv_part_size = 1 / part_size
local props = {}
local north = {}
local east = {}
local south = {}
local west = {}
local res = {north, east, south, west}
for i, entry in pairs(data) do
if type(i) == 'string' then
props[i] = entry
else
local y = math.ceil(i * inv_part_size)
local x = i - (y - 1) * part_size
local x2 = part_size - y + 1
local y2 = x
local x3 = part_size - x + 1
local y3 = part_size - y + 1
local x4 = y
local y4 = part_size - x + 1
local i2 = (y2 - 1) * part_size + x2
local i3 = (y3 - 1) * part_size + x3
local i4 = (y4 - 1) * part_size + x4
local tile = entry.tile
if tile then
set_tile(north, i, tile)
set_tile(east, i2, tile)
set_tile(south, i3, tile)
set_tile(west, i4, tile)
end
local entity = entry.entity
if entity then
local offset = entity.offset
if offset == 3 then
i = i + part_size + 1
i2 = i2 + part_size
i4 = i4 + 1
elseif offset == 1 then
i = i + 1
i2 = i2 + part_size
elseif offset == 2 then
i = i + part_size
i4 = i4 + 1
end
local dir = entity.direction or 0
set_entity(north, i, entity)
set_entity(east, i2, change_direction(entity, (dir + 2) % 8))
set_entity(south, i3, change_direction(entity, (dir + 4) % 8))
set_entity(west, i4, change_direction(entity, (dir + 6) % 8))
end
end
end
north.__index = north
east.__index = east
south.__index = south
west.__index = west
for k, v in pairs(props) do
north[k] = v
east[k] = v
south[k] = v
west[k] = v
end
return res
end
function Public.make_walls(data)
data.__index = data
return data
end
local function shallow_copy(tbl)
local copy = {}
for k, v in pairs(tbl) do
copy[k] = v
end
return copy
end
function Public.extend_1_way(data, tbl)
return setmetatable(shallow_copy(tbl), data)
end
function Public.extend_4_way(data, tbl)
return {
setmetatable(shallow_copy(tbl), data[1]),
setmetatable(shallow_copy(tbl), data[2]),
setmetatable(shallow_copy(tbl), data[3]),
setmetatable(shallow_copy(tbl), data[4])
}
end
function Public.extend_walls(data, tbl)
local copy = shallow_copy(tbl)
local base = {
Public.extend_4_way(data[1], copy),
Public.extend_4_way(data[2], copy),
Public.extend_4_way(data[3], copy)
}
base.__index = base
return setmetatable(copy, base)
end
local function update_market_upgrade_description(outpost_data)
local outpost_id = outpost_data.outpost_id
local upgrade_rate = outpost_data.upgrade_rate
local upgrade_base_cost = outpost_data.upgrade_base_cost
local upgrade_cost_base = outpost_data.upgrade_cost_base
local level = outpost_data.level
local base_outputs = outpost_data.base_outputs
local outpost_magic_crafters = outpost_data.magic_crafters
local prototype = Retailer.get_items(outpost_id)['upgrade']
prototype.price = upgrade_base_cost * #outpost_magic_crafters * upgrade_cost_base ^ (level - 1)
prototype.name_label = 'Upgrade Outpost to level ' .. tostring(level + 1)
local tooltip_str = {''}
local mapview_str = {''}
local count = 2
for k, v in pairs(base_outputs) do
local base_rate = v * 60
local upgrade_per_level = base_rate * upgrade_rate
local current_rate = base_rate + (level - 1) * upgrade_per_level
local next_rate = current_rate + upgrade_per_level
local name = game.item_prototypes[k]
if name then
tooltip_str[count] = concat {'[item=', k, ']'}
mapview_str[count] = name.localised_name
else
name = game.fluid_prototypes[k]
if name then
tooltip_str[count] = concat {'[fluid=', k, ']'}
mapview_str[count] = name.localised_name
else
tooltip_str[count] = k
mapview_str[count] = k
end
end
count = count + 1
local str = concat {': ', format('%.2f', current_rate), ' -> ', format('%.2f / sec', next_rate)}
tooltip_str[count] = str
tooltip_str[count + 1] = '\n'
mapview_str[count] = str
mapview_str[count + 1] = ', '
count = count + 2
end
tooltip_str[count - 1] = nil
mapview_str[count - 1] = nil
prototype.description = tooltip_str
prototype.disabled = false
prototype.mapview_description = mapview_str
Retailer.set_item(outpost_id, prototype)
end
local function do_outpost_upgrade(event)
if event.item.type ~= 'upgrade' then
return
end
local outpost_id = event.group_name
local outpost_data = outposts[outpost_id]
local outpost_magic_crafters = outpost_data.magic_crafters
local upgrade_rate = outpost_data.upgrade_rate
local level = outpost_data.level + 1
outpost_data.level = level
local player_name = event.player.name
local outpost_name = Retailer.get_market_group_label(outpost_id)
local message = concat {player_name, ' has upgraded ', outpost_name, ' to level ', level}
CrashSiteToast.do_outpost_toast(outpost_data.market, message)
Server.to_discord_bold(concat {'*** ', message, ' ***'})
for i = 1, #outpost_magic_crafters do
local crafter = outpost_magic_crafters[i]
local rate = crafter.rate
local upgrade_amount = upgrade_rate * crafter.base_rate
crafter.rate = rate + upgrade_amount
end
update_market_upgrade_description(outpost_data)
end
local function activate_market_upgrade(outpost_data)
local outpost_magic_crafters = outpost_data.magic_crafters
if #outpost_magic_crafters == 0 then
local outpost_id = outpost_data.outpost_id
local prototype = Retailer.get_items(outpost_id)['upgrade']
prototype.disabled_reason = 'No machines to upgrade.'
Retailer.set_item(outpost_id, prototype)
return
end
local base_outputs = {}
for i = 1, #outpost_magic_crafters do
local crafter = outpost_magic_crafters[i]
local item = crafter.item
local count = base_outputs[item] or 0
count = count + crafter.rate
base_outputs[item] = count
end
outpost_data.base_outputs = base_outputs
update_market_upgrade_description(outpost_data)
end
function Public.activate_market_upgrade(outpost_id)
local outpost_data = outposts[outpost_id]
activate_market_upgrade(outpost_data)
end
local function do_capture_outpost(outpost_data)
local area = {top_left = outpost_data.top_left, bottom_right = outpost_data.bottom_right}
local walls = RS.get_surface().find_entities_filtered {area = area, force = 'enemy', name = 'stone-wall'}
for i = 1, #walls do
walls[i].force = 'player'
end
local outpost_id = outpost_data.outpost_id
local name = Retailer.get_market_group_label(outpost_id)
if name == 'Market' then
return
end
local donators = Donator.get_donators_table()
if next(donators) then
local donator = table.get_random_dictionary_entry(donators, true)
if donator then
name = concat({donator, "'s ", name})
Retailer.set_market_group_label(outpost_id, name)
end
end
local message = 'Outpost captured: ' .. name
CrashSiteToast.do_outpost_toast(outpost_data.market, message)
Server.to_discord_bold(concat {'*** ', message, ' ***'})
activate_market_upgrade(outpost_data)
end
local function do_refill_turrets()
local index = refill_turrets.index
if index > #refill_turrets then
refill_turrets.index = 1
return
end
local turret_data = refill_turrets[index]
local turret = turret_data.turret
if not turret.valid then
fast_remove(refill_turrets, index)
return
end
refill_turrets.index = index + 1
local data = turret_data.data
if data.liquid then
turret.fluidbox[1] = data
elseif data then
turret.insert(data)
end
end
local artillery_target_entities = {
'character',
'tank',
'car',
'locomotive',
'cargo-wagon',
'fluid-wagon',
'artillery-wagon',
'spidertron'
}
local artillery_target_callback =
Token.register(
function(data)
local position = data.position
local entity = data.entity
if not entity.valid then
return
end
local tx, ty = position.x, position.y
local pos = entity.position
local x, y = pos.x, pos.y
local dx, dy = tx - x, ty - y
local d = dx * dx + dy * dy
if d >= 1024 then -- 32 ^ 2
entity.surface.create_entity {
name = 'artillery-projectile',
position = position,
target = entity,
speed = 1.5
}
if entity.name == "spidertron" then
-- fire a rocket as well as artillery-projectile since artillery-projectile doesn't damage spidertrons
-- Balance note: 1 rocket per artillery kills spidertron shield fast but takes ~ 1 minute to kill the spidertron
entity.surface.create_entity {
name = 'rocket',
position = position,
target = entity,
speed = 1.5
}
end
end
end
)
local function do_artillery_turrets_targets()
local index = artillery_outposts.index
if index > #artillery_outposts then
artillery_outposts.index = 1
return
end
artillery_outposts.index = index + 1
local outpost = artillery_outposts[index]
local now = game.tick
if now - outpost.last_fire_tick < 480 then
return
end
local turrets = outpost.artillery_turrets
for i = #turrets, 1, -1 do
local turret = turrets[i]
if not turret.valid then
fast_remove(turrets, i)
end
end
local count = #turrets
if count == 0 then
fast_remove(artillery_outposts, index)
return
end
outpost.last_fire_tick = now
local turret = turrets[1]
local area = outpost.artillery_area
local surface = turret.surface
local entities = surface.find_entities_filtered {area = area, name = artillery_target_entities}
if #entities == 0 then
return
end
local position = turret.position
for i = 1, count do
local entity = entities[math.random(#entities)]
if entity and entity.valid then
local data = {position = position, entity = entity}
Task.set_timeout_in_ticks(i * 60, artillery_target_callback, data)
end
end
end
local function set_pollution_multiplier(args, player)
game.print(args.multiplier)
local multipliers = {}
for m in string.gmatch(args.multiplier, "%-?%d+") do -- Assuming the surface name isn't a valid number.
table.insert(multipliers, tonumber(m))
end
if not multipliers or #multipliers ~= 1 then
game.player.print("Fail")
return
end
local old_multiplier = pollution_multiplier
pollution_multiplier = multipliers[1]
for _, p in pairs(game.players) do
if p.admin then
p.print(player.name..' changed magic crafter pollution multiplier from '..old_multiplier..' to '..pollution_multiplier)
end
end
end
local function get_pollution_multiplier(player)
game.player.print('Current pollution multiplier is: '..pollution_multiplier)
end
local function do_magic_crafters()
local limit = #magic_crafters
if limit == 0 then
return
end
local index = magic_crafters.index
for i = 1, magic_crafters_per_tick do
if index > limit then
index = 1
end
local data = magic_crafters[index]
local entity = data.entity
if not entity.valid then
fast_remove(magic_crafters, index)
limit = limit - 1
if limit == 0 then
return
end
else
index = index + 1
local tick = game.tick
local last_tick = data.last_tick
local rate = data.rate
local count = (tick - last_tick) * rate
local fcount = floor(count)
if fcount > 0 then
if entity.get_output_inventory().can_insert(data.item) then -- No pollution once full. Taking items out of crafters makes pollution
local pollution_amount = pollution_multiplier * 0.01
entity.surface.pollute(entity.position, pollution_amount)
entity.get_output_inventory().insert {name = data.item, count = fcount}
end
data.last_tick = tick - (count - fcount) / rate
end
end
end
magic_crafters.index = index
end
local function do_magic_fluid_crafters()
local limit = #magic_fluid_crafters
if limit == 0 then
return
end
local index = magic_fluid_crafters.index
for i = 1, magic_fluid_crafters_per_tick do
if index > limit then
index = 1
end
local data = magic_fluid_crafters[index]
local entity = data.entity
if not entity.valid then
fast_remove(magic_fluid_crafters, index)
limit = limit - 1
if limit == 0 then
return
end
else
index = index + 1
local tick = game.tick
local last_tick = data.last_tick
local rate = data.rate
local count = (tick - last_tick) * rate
local fcount = floor(count)
if fcount > 0 then
local fluidbox_index = data.fluidbox_index
local fb = entity.fluidbox
local fb_data = fb[fluidbox_index] or {name = data.item, amount = 0}
fb_data.amount = fb_data.amount + fcount
fb[fluidbox_index] = fb_data
data.last_tick = tick - (count - fcount) / rate
end
end
end
magic_fluid_crafters.index = index
end
local function tick()
do_refill_turrets()
do_artillery_turrets_targets()
do_magic_crafters()
do_magic_fluid_crafters()
end
Public.refill_turret_callback =
Token.register(
function(turret, data)
local outpost_id = data.outpost_id
refill_turrets[#refill_turrets + 1] = {turret = turret, data = data.callback_data}
turret_to_outpost[turret.unit_number] = outpost_id
local outpost_data = outposts[outpost_id]
outpost_data.turret_count = outpost_data.turret_count + 1
end
)
Public.refill_liquid_turret_callback =
Token.register(
function(turret, data)
local callback_data = data.callback_data
callback_data.liquid = true
local outpost_id = data.outpost_id
refill_turrets[#refill_turrets + 1] = {turret = turret, data = callback_data}
turret_to_outpost[turret.unit_number] = outpost_id
local outpost_data = outposts[outpost_id]
outpost_data.turret_count = outpost_data.turret_count + 1
end
)
Public.refill_artillery_turret_callback =
Token.register(
function(turret, data)
local outpost_id = data.outpost_id
refill_turrets[#refill_turrets + 1] = {turret = turret, data = data.callback_data}
turret_to_outpost[turret.unit_number] = outpost_id
local outpost_data = outposts[outpost_id]
outpost_data.turret_count = outpost_data.turret_count + 1
local artillery_turrets = outpost_data.artillery_turrets
if not artillery_turrets then
artillery_turrets = {}
outpost_data.artillery_turrets = artillery_turrets
local pos = turret.position
local x, y = pos.x, pos.y
outpost_data.artillery_area = {{x - 112, y - 112}, {x + 112, y + 112}}
outpost_data.last_fire_tick = 0
artillery_outposts[#artillery_outposts + 1] = outpost_data
end
artillery_turrets[#artillery_turrets + 1] = turret
end
)
Public.power_source_callback =
Token.register(
function(turret, data)
local outpost_id = data.outpost_id
local callback_data = data.callback_data
local power_source =
turret.surface.create_entity {name = 'hidden-electric-energy-interface', position = turret.position}
power_source.electric_buffer_size = callback_data.buffer_size
power_source.power_production = callback_data.power_production
power_source.destructible = false
power_sources[turret.unit_number] = {entity = power_source}
turret_to_outpost[turret.unit_number] = outpost_id
local outpost_data = outposts[outpost_id]
outpost_data.turret_count = outpost_data.turret_count + 1
end
)
Public.worm_turret_callback =
Token.register(
function(turret, data)
local outpost_id = data.outpost_id
turret_to_outpost[turret.unit_number] = outpost_id
local outpost_data = outposts[outpost_id]
outpost_data.turret_count = outpost_data.turret_count + 1
end
)
local function add_magic_crafter_output(entity, output, distance, outpost_id)
local rate = output.min_rate + output.distance_factor * distance
local fluidbox_index = output.fluidbox_index
local data = {
entity = entity,
last_tick = game.tick,
base_rate = rate,
rate = rate,
item = output.item,
fluidbox_index = fluidbox_index
}
if fluidbox_index then
magic_fluid_crafters[#magic_fluid_crafters + 1] = data
else
magic_crafters[#magic_crafters + 1] = data
end
local outpost_magic_crafters = outposts[outpost_id].magic_crafters
outpost_magic_crafters[#outpost_magic_crafters + 1] = data
end
local set_inactive_token =
Token.register(
function(entity)
if entity.valid then
entity.active = false
end
end
)
Public.magic_item_crafting_callback =
Token.register(
function(entity, data)
local outpost_id = data.outpost_id
local callback_data = data.callback_data
entity.minable = false
entity.destructible = false
entity.operable = false
local recipe = callback_data.recipe
if recipe then
entity.set_recipe(recipe)
else
local furance_item = callback_data.furance_item
if furance_item then
local inv = entity.get_inventory(2) -- defines.inventory.furnace_source
inv.insert(furance_item)
end
end
local p = entity.position
local x, y = p.x, p.y
local distance = math.sqrt(x * x + y * y)
local output = callback_data.output
if #output == 0 then
add_magic_crafter_output(entity, output, distance, outpost_id)
else
for i = 1, #output do
local o = output[i]
add_magic_crafter_output(entity, o, distance, outpost_id)
end
end
if not callback_data.keep_active then
Task.set_timeout_in_ticks(2, set_inactive_token, entity) -- causes problems with refineries.
end
end
)
Public.magic_item_crafting_callback_weighted =
Token.register(
function(entity, data)
local outpost_id = data.outpost_id
local callback_data = data.callback_data
entity.minable = false
entity.destructible = false
entity.operable = false
local weights = callback_data.weights
local loot = callback_data.loot
local i = math.random() * weights.total
local index = table.binary_search(weights, i)
if (index < 0) then
index = bit32.bnot(index)
end
local stack = loot[index].stack
if not stack then
return
end
local recipe = stack.recipe
if recipe then
entity.set_recipe(recipe)
else
local furance_item = stack.furance_item
if furance_item then
local inv = entity.get_inventory(2) -- defines.inventory.furnace_source
inv.insert(furance_item)
end
end
local p = entity.position
local x, y = p.x, p.y
local distance = math.sqrt(x * x + y * y)
local output = stack.output
if #output == 0 then
add_magic_crafter_output(entity, output, distance, outpost_id)
else
for o_i = 1, #output do
local o = output[o_i]
add_magic_crafter_output(entity, o, distance, outpost_id)
end
end
if not callback_data.keep_active then
Task.set_timeout_in_ticks(2, set_inactive_token, entity) -- causes problems with refineries.
end
end
)
Public.wall_callback =
Token.register(
function(entity, data)
if not entity.valid then
return
end
local position = entity.position
local px, py = position.x, position.y
local outpost_id = data.outpost_id
local outpost_data = outposts[outpost_id]
local top_left = outpost_data.top_left
local bottom_right = outpost_data.bottom_right
local tx, ty = top_left.x, top_left.y
local bx, by = bottom_right.x, bottom_right.y
if not tx or px < tx then
top_left.x = px
end
if not ty or py < ty then
top_left.y = py
end
if not bx or px > bx then
bottom_right.x = px
end
if not by or py > by then
bottom_right.y = py
end
end
)
Public.deactivate_callback =
Token.register(
function(entity)
entity.active = false
entity.operable = false
entity.destructible = false
end
)
Public.scenario_chest_callback = Token.register(function(chest)
if not chest or not chest.valid then
return
end
chest.destructible = false
chest.minable = false
end)
local function turret_died(event)
local entity = event.entity
if not entity or not entity.valid then
return
end
local number = entity.unit_number
if not number then
return
end
local ps_data = power_sources[number]
if ps_data then
power_sources[number] = nil
local ps_entity = ps_data.entity
if ps_entity and ps_entity.valid then
ps_entity.destroy()
end
end
local outpost_id = turret_to_outpost[number]
if outpost_id then
local outpost_data = outposts[outpost_id]
local turret_count = outpost_data.turret_count - 1
outpost_data.turret_count = turret_count
if turret_count == 0 then
do_capture_outpost(outpost_data)
end
end
end
Public.market_set_items_callback =
Token.register(
function(entity, data)
if not entity.valid then
return
end
local callback_data = data.callback_data
entity.destructible = false
local market_id = data.outpost_id
local outpost_data = outposts[market_id]
local upgrade_base_cost = callback_data.upgrade_base_cost or 0
outpost_data.market = entity
outpost_data.upgrade_rate = callback_data.upgrade_rate
outpost_data.upgrade_base_cost = upgrade_base_cost
outpost_data.upgrade_cost_base = callback_data.upgrade_cost_base
Retailer.add_market(market_id, entity)
Retailer.set_market_group_label(market_id, callback_data.market_name)
Retailer.set_item(
market_id,
{
name = 'upgrade',
type = 'upgrade',
name_label = 'Upgrade Outpost',
sprite = 'item-group/production',
price = 0,
stack_limit = 1,
disabled = true,
disabled_reason = 'Outpost must be captured first.',
description = 'Increases output.'
}
)
local p = entity.position
local x, y = p.x, p.y
local d = math.sqrt(x * x + y * y)
for i = 1, #callback_data do
local item = callback_data[i]
local price = item.price
local df = item.distance_factor
if df then
local min_price = item.min_price or 1
price = item.price - d * df
price = math.max(price, min_price)
end
Retailer.set_item(
market_id,
{
name = item.name,
type = item.type,
price = price,
stack_limit = item.stack_limit,
name_label = item.name_label,
sprite = item.sprite,
disabled = item.disabled,
disabled_reason = item.disabled_reason,
description = item.description}
)
end
end
)
Public.firearm_magazine_ammo = {name = 'firearm-magazine', count = 200}
Public.piercing_rounds_magazine_ammo = {name = 'piercing-rounds-magazine', count = 200}
Public.uranium_rounds_magazine_ammo = {name = 'uranium-rounds-magazine', count = 200}
Public.artillery_shell_ammo = {name = 'artillery-shell', count = 15}
Public.light_oil_ammo = {name = 'light-oil', amount = 100}
Public.laser_turrent_power_source = {buffer_size = 2400000, power_production = 40000}
function Public.prepare_weighted_loot(loot)
local total = 0
local weights = {}
for i = 1, #loot do
local v = loot[i]
total = total + v.weight
weights[#weights + 1] = total
end
weights.total = total
return weights
end
function Public.do_random_loot(entity, weights, loot)
if not entity.valid then
return
end
entity.operable = false
--entity.destructible = false
local i = math.random() * weights.total
local index = table.binary_search(weights, i)
if (index < 0) then
index = bit32.bnot(index)
end
local stack = loot[index].stack
if not stack then
return
end
local df = stack.distance_factor
local count
if df then
local p = entity.position
local x, y = p.x, p.y
local d = math.sqrt(x * x + y * y)
count = stack.count + d * df
else
count = stack.count
end
entity.insert {name = stack.name, count = count}
end
function Public.do_random_fluid_loot(entity, weights, loot)
if not entity.valid then
return
end
entity.operable = false
entity.destructible = false
local i = math.random() * weights.total
local index = table.binary_search(weights, i)
if (index < 0) then
index = bit32.bnot(index)
end
local stack = loot[index].stack
if not stack then
return
end
local df = stack.distance_factor
local count
if df then
local p = entity.position
local x, y = p.x, p.y
local d = math.sqrt(x * x + y * y)
count = stack.count + d * df
else
count = stack.count
end
entity.fluidbox[1] = {name = stack.name, amount = count}
end
function Public.do_factory_loot(entity, weights, loot)
if not entity.valid then
return
end
entity.operable = false
entity.destructible = false
local i = math.random() * weights.total
local index = table.binary_search(weights, i)
if (index < 0) then
index = bit32.bnot(index)
end
local stack = loot[index].stack
if not stack then
return
end
local df = stack.distance_factor
local count
if df then
local p = entity.position
local x, y = p.x, p.y
local d = math.sqrt(x * x + y * y)
count = stack.count + d * df
else
count = stack.count
end
local name = stack.name
entity.set_recipe(name)
entity.get_output_inventory().insert {name = name, count = count}
end
local function coin_mined(event)
local stack = event.item_stack
if stack.name == 'coin' then
change_for_player(event.player_index, coins_earned_name, stack.count)
end
end
local function market_selected(event)
local player = game.get_player(event.player_index)
if not player or not player.valid then
return
end
if player.render_mode == render_mode_game then
return
end
local selected = player.selected
if not selected or not selected.valid or selected.name ~= 'market' then
return
end
local group = Retailer.get_market_group_name(selected)
local prototype = Retailer.get_items(group)['upgrade']
if prototype.disabled then
return
end
local args = {
text = nil,
target = selected,
target_offset = nil,
alignment = 'center',
surface = selected.surface,
color = {1, 1, 1},
players = {player},
scale = 1.5,
scale_with_zoom = true,
time_to_live = 180
}
args.text = prototype.name_label
args.target_offset = {0, -6.5}
draw_text(args)
args.text = 'Price: ' .. prototype.price
args.target_offset = {0, -5}
draw_text(args)
args.text = prototype.mapview_description
args.target_offset = {0, -3.5}
draw_text(args)
end
Event.add(defines.events.on_tick, tick)
Event.add(defines.events.on_entity_died, turret_died)
Event.on_init(
function()
game.forces.neutral.recipes['steel-plate'].enabled = true
end
)
Event.add(defines.events.on_player_mined_item, coin_mined)
Event.add(Retailer.events.on_market_purchase, do_outpost_upgrade)
Event.add(defines.events.on_selected_entity_changed, market_selected)
Command.add(
'set_pollution_multiplier',
{
description = {'command_description.set_pollution_multiplier'},
arguments = {'multiplier'},
required_rank = Ranks.admin,
capture_excess_arguments = true,
allowed_by_server = true
},
set_pollution_multiplier
)
Command.add(
'get_pollution_multiplier',
{
description = {'command_description.get_pollution_multiplier'},
required_rank = Ranks.admin,
capture_excess_arguments = true,
allowed_by_server = false
},
get_pollution_multiplier
)
return Public