2173 lines
66 KiB
Lua
2173 lines
66 KiB
Lua
require "config"
|
|
require "util"
|
|
|
|
require "resourceconfigs.mainconfig"
|
|
require "libs.straight_world"
|
|
require "libs.straight_world_platforms"
|
|
|
|
local MB=require "libs/metaball"
|
|
|
|
local logger = require 'libs/logger'
|
|
local l = logger.new_logger()
|
|
|
|
-- math shortcuts
|
|
local floor = math.floor
|
|
local abs = math.abs
|
|
local cos = math.cos
|
|
local sin = math.sin
|
|
local pi = math.pi
|
|
local max = math.max
|
|
|
|
local function round(value)
|
|
return math.floor(value + 0.5)
|
|
end
|
|
|
|
local function debug(str)
|
|
if debug_enabled then
|
|
l:log(str)
|
|
end
|
|
end
|
|
|
|
function tableLength(tableToCount)
|
|
local count = 0
|
|
for _ in pairs(tableToCount) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
-- cached setting value
|
|
local region_size = settings.global["rso-region-size"].value
|
|
|
|
-- constants
|
|
local CHUNK_SIZE = 32
|
|
local REGION_TILE_SIZE = CHUNK_SIZE*region_size
|
|
local MIN_BALL_DISTANCE = CHUNK_SIZE/6
|
|
local P_BALL_SIZE_FACTOR = 0.7
|
|
local N_BALL_SIZE_FACTOR = 0.95
|
|
local NEGATIVE_MODIFICATOR = 9973
|
|
|
|
local meta_shapes = nil
|
|
|
|
if settings.global["rso-use-donuts"].value then
|
|
meta_shapes = {MB.MetaEllipse, MB.MetaSquare, MB.MetaDonut}
|
|
else
|
|
meta_shapes = {MB.MetaEllipse, MB.MetaSquare}
|
|
end
|
|
|
|
-- local globals
|
|
local distance = util.distance
|
|
local invalidResources = {}
|
|
|
|
--[[ HELPER METHODS ]]--
|
|
|
|
local function normalize(n) -- keep numbers at 32 bits
|
|
return floor(n) % 0xffffffff
|
|
end
|
|
|
|
local function bearing(origin, dest)
|
|
-- finds relative angle
|
|
local xd = dest.x - origin.x
|
|
local yd = dest.y - origin.y
|
|
return math.atan2(xd, yd);
|
|
end
|
|
|
|
local function str2num(s)
|
|
local num = 0
|
|
for i=1,s:len() do
|
|
num=num + (s:byte(i) - 33)*i
|
|
end
|
|
return num
|
|
end
|
|
|
|
--local function rng_for_pos(pos)
|
|
-- local num = 0
|
|
-- local x = pos.x
|
|
-- local y = pos.y
|
|
--
|
|
-- if x == 0 then x = 0.5 end
|
|
-- if y == 0 then y = 0.5 end
|
|
-- if x < 0 then
|
|
-- x = abs(x) + NEGATIVE_MODIFICATOR
|
|
-- end
|
|
-- if y < 0 then
|
|
-- y = abs(y) + NEGATIVE_MODIFICATOR
|
|
-- end
|
|
--
|
|
-- return rng* drand.lcg(y, 'mvc'):random(0)*drand.lcg(x, 'nr'):random(0)
|
|
--end
|
|
|
|
local function rng_for_reg_pos(surfaceIndex, pos)
|
|
local x = pos.x
|
|
local y = pos.y
|
|
|
|
local rng = game.create_random_generator()
|
|
|
|
if x == 0 then x = 0.5 end
|
|
if y == 0 then y = 0.5 end
|
|
|
|
if x < 0 then
|
|
x = abs(x) + NEGATIVE_MODIFICATOR
|
|
end
|
|
if y < 0 then
|
|
y = abs(y) + NEGATIVE_MODIFICATOR
|
|
end
|
|
|
|
rng.re_seed(normalize(x * 65536))
|
|
local valX = rng(65536)
|
|
rng.re_seed(normalize(y * 32768))
|
|
local valY = rng(32768)
|
|
|
|
local mapSeed = global.surfaces[surfaceIndex].seed
|
|
if global.mapSeedOverride then
|
|
mapSeed = global.mapSeedOverride
|
|
end
|
|
local seed = normalize( valX * valY * mapSeed )
|
|
rng.re_seed( seed )
|
|
|
|
debug("Generator for " .. pos.x .. "," .. pos.y .. " created with seed " .. seed .. " x:" .. valX .. " y:" .. valY)
|
|
|
|
return rng
|
|
end
|
|
|
|
local function rng_restricted_angle(restrictions, rng)
|
|
local value = rng()
|
|
local x_scale, y_scale, angle
|
|
local deformX = rng() * 2 - 1
|
|
local deformY = rng() * 2 - 1
|
|
|
|
if restrictions=='xy' then
|
|
y_scale=1.0 + deformY*0.5
|
|
x_scale=1.0 + deformX*0.5
|
|
angle = value*pi*2
|
|
elseif restrictions=='x' then
|
|
y_scale=1.0 + deformY*0.6
|
|
x_scale=1.0 + deformX*0.6
|
|
angle = value*pi/2 - pi/4
|
|
elseif restrictions=='y' then
|
|
y_scale=1.0 + deformY*0.6
|
|
x_scale=1.0 + deformX*0.6
|
|
angle = value*pi/2 + pi/2
|
|
else
|
|
y_scale=1.0 + deformY*0.3
|
|
x_scale=1.0 + deformX*0.3
|
|
angle = value*pi*2
|
|
end
|
|
|
|
return angle, x_scale, y_scale
|
|
end
|
|
|
|
local function vary_by_percentage(x, p, rng)
|
|
return x + (0.5 - rng())*2*x*p
|
|
end
|
|
|
|
|
|
local function remove_trees(surface, x, y, x_size, y_size )
|
|
local bb = {{x - x_size, y - y_size}, {x + x_size, y + y_size}}
|
|
|
|
for _, entity in pairs(surface.find_entities_filtered{area = bb, type={"tree", "simple-entity"}}) do
|
|
if entity.valid then
|
|
entity.destroy()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function remove_trees_based_on_bb(surface, x, y, boundingBox)
|
|
local bb = {{x + boundingBox.left_top.x, y + boundingBox.left_top.y}, {x + boundingBox.right_bottom.x, y + boundingBox.right_bottom.x}}
|
|
|
|
for _, entity in pairs(surface.find_entities_filtered{area = bb, type={"tree", "simple-entity"}}) do
|
|
if entity.valid then
|
|
entity.destroy()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function find_intersection(surface, x, y)
|
|
-- try to get position in between of valid chunks by probing map
|
|
-- this may breaks determinism of generation, but so far it returned on first if
|
|
local gt = surface.get_tile
|
|
local restriction = ''
|
|
if gt(x + CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x + CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid then
|
|
restriction = 'xy'
|
|
elseif gt(x + CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid and gt(x + CHUNK_SIZE*2, y).valid and gt(x, y + CHUNK_SIZE*2).valid then
|
|
x=x + CHUNK_SIZE/2
|
|
y=y + CHUNK_SIZE/2
|
|
restriction = 'xy'
|
|
elseif gt(x + CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x + CHUNK_SIZE*2, y).valid and gt(x, y - CHUNK_SIZE*2).valid then
|
|
x=x + CHUNK_SIZE/2
|
|
y=y - CHUNK_SIZE/2
|
|
restriction = 'xy'
|
|
elseif gt(x - CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y).valid and gt(x, y + CHUNK_SIZE*2).valid then
|
|
x=x - CHUNK_SIZE/2
|
|
y=y + CHUNK_SIZE/2
|
|
restriction = 'xy'
|
|
elseif gt(x - CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y).valid and gt(x, y - CHUNK_SIZE*2).valid then
|
|
x=x - CHUNK_SIZE/2
|
|
y=y - CHUNK_SIZE/2
|
|
restriction = 'xy'
|
|
elseif gt(x + CHUNK_SIZE*2, y).valid then
|
|
x=x + CHUNK_SIZE/2
|
|
restriction = 'x'
|
|
elseif gt(x - CHUNK_SIZE*2, y).valid then
|
|
x=x - CHUNK_SIZE/2
|
|
restriction = 'x'
|
|
elseif gt(x, y + CHUNK_SIZE*2).valid then
|
|
y=y + CHUNK_SIZE/2
|
|
restriction = 'y'
|
|
elseif gt(x, y - CHUNK_SIZE*2).valid then
|
|
y=y - CHUNK_SIZE/2
|
|
restriction = 'y'
|
|
end
|
|
return x, y, restriction
|
|
end
|
|
|
|
local function find_random_chunk(r_x, r_y, rng)
|
|
local offset_x=rng(region_size) - 1
|
|
local offset_y=rng(region_size) - 1
|
|
local c_x=r_x*REGION_TILE_SIZE + offset_x*CHUNK_SIZE
|
|
local c_y=r_y*REGION_TILE_SIZE + offset_y*CHUNK_SIZE
|
|
-- debug("Random chunk "..r_x..","..r_y.." coords "..c_x..","..c_y.." offset "..offset_x..","..offset_y)
|
|
return c_x, c_y
|
|
end
|
|
|
|
local function is_same_region(c_x1, c_y1, c_x2, c_y2)
|
|
if not (floor(c_x1/REGION_TILE_SIZE) == floor(c_x2/REGION_TILE_SIZE)) then
|
|
return false
|
|
end
|
|
if not (floor(c_y1/REGION_TILE_SIZE) == floor(c_y2/REGION_TILE_SIZE)) then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function find_random_neighbour_chunk(ocx, ocy, rng)
|
|
-- somewhat bruteforce and unoptimized
|
|
local x_dir = rng(-2,2)
|
|
local y_dir = rng(-2,2)
|
|
|
|
local ncx = ocx + x_dir * CHUNK_SIZE
|
|
local ncy = ocy + y_dir * CHUNK_SIZE
|
|
if is_same_region(ncx, ncy, ocx, ocy) then
|
|
return ncx, ncy
|
|
end
|
|
|
|
ncx = ocx - x_dir * CHUNK_SIZE
|
|
ncy = ocy - y_dir * CHUNK_SIZE
|
|
if is_same_region(ncx, ncy, ocx, ocy) then
|
|
return ncx, ncy
|
|
end
|
|
|
|
ncx = ocx - x_dir * CHUNK_SIZE
|
|
ncy = ocy + y_dir * CHUNK_SIZE
|
|
if is_same_region(ncx, ncy, ocx, ocy) then
|
|
return ncx, ncy
|
|
end
|
|
|
|
ncx = ocx + x_dir * CHUNK_SIZE
|
|
ncy = ocy - y_dir * CHUNK_SIZE
|
|
if is_same_region(ncx, ncy, ocx, ocy) then
|
|
return ncx, ncy
|
|
end
|
|
|
|
return ocx, ocy
|
|
end
|
|
|
|
local function isInStartingArea( surfaceIndex, tileX, tileY )
|
|
|
|
local surfaceData = global.surfaces[surfaceIndex]
|
|
|
|
if surfaceData and surfaceData.startingAreas then
|
|
for idx, data in pairs( surfaceData.startingAreas ) do
|
|
|
|
local adjustedX = ( tileX - data.x ) / REGION_TILE_SIZE
|
|
local adjustedY = ( tileY - data.y ) / REGION_TILE_SIZE
|
|
if ((adjustedX * adjustedX + adjustedY * adjustedY) <= surfaceData.starting_area_size * surfaceData.starting_area_size) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function distanceFromStartingAreas( surfaceData, regionX, regionY )
|
|
|
|
local minDistance = nil
|
|
|
|
for idx, data in pairs( surfaceData.startingAreas ) do
|
|
local dist = distance({x = data.x / REGION_TILE_SIZE, y = data.y / REGION_TILE_SIZE},{x = regionX, y = regionY})
|
|
if minDistance ~= nil then
|
|
minDistance = math.min(minDistance, dist)
|
|
else
|
|
minDistance = dist
|
|
end
|
|
end
|
|
|
|
if minDistance == nil then
|
|
minDistance = distance({x = 0, y = 0},{x = regionX, y = regionY})
|
|
end
|
|
|
|
return minDistance
|
|
end
|
|
|
|
|
|
-- modifies the resource size - only used in endless_resource_mode
|
|
local function modify_resource_size(resourceName, resourceSize, startingArea)
|
|
|
|
if not startingArea then
|
|
resourceSize = math.ceil(resourceSize * settings.global["rso-global-size-mult"].value)
|
|
end
|
|
|
|
local resourceEntity = game.entity_prototypes[resourceName]
|
|
if resourceEntity and resourceEntity.infinite_resource then
|
|
|
|
newResourceSize = resourceSize * endless_resource_mode_sizeModifier
|
|
|
|
-- make sure it's still an integer
|
|
newResourceSize = math.ceil(newResourceSize)
|
|
-- make sure it's not 0
|
|
if newResourceSize == 0 then newResourceSize = 1 end
|
|
return newResourceSize
|
|
else
|
|
return resourceSize
|
|
end
|
|
end
|
|
|
|
--[[ SPAWN METHODS ]]--
|
|
|
|
local locationOrder =
|
|
{
|
|
{ x = 0, y = 0 },
|
|
{ x = -1, y = 0 },
|
|
{ x = 1, y = 0 },
|
|
{ x = 0, y = -1 },
|
|
{ x = 0, y = 1 },
|
|
{ x = -1, y = -1 },
|
|
{ x = 1, y = -1 },
|
|
{ x = -1, y = 1 },
|
|
{ x = 1, y = 1 }
|
|
}
|
|
|
|
--[[ entity-field ]]--
|
|
local function spawn_resource_ore(surface, rname, pos, size, richness, startingArea, restrictions, rng)
|
|
-- blob generator, centered at pos, size controls blob diameter
|
|
restrictions = restrictions or ''
|
|
debug("Entering spawn_resource_ore "..rname.." at:"..pos.x..","..pos.y.." size:"..size.." richness:"..richness.." isStart:"..tostring(startingArea).." restrictions:"..restrictions)
|
|
|
|
size = modify_resource_size(rname, size, startingArea)
|
|
local radius = size / 2 -- to radius
|
|
|
|
local p_balls={}
|
|
local n_balls={}
|
|
local MIN_BALL_DISTANCE = math.min(MIN_BALL_DISTANCE, radius/2)
|
|
|
|
local maxPradius = 0
|
|
local outside = { xmin = 1e10, xmax = -1e10, ymin = 1e10, ymax = -1e10 }
|
|
local inside = { xmin = 1e10, xmax = -1e10, ymin = 1e10, ymax = -1e10 }
|
|
|
|
local function updateRect(rect, x, y, radius)
|
|
rect.xmin = math.min(rect.xmin, x - radius)
|
|
rect.xmax = math.max(rect.xmax, x + radius)
|
|
rect.ymin = math.min(rect.ymin, y - radius)
|
|
rect.ymax = math.max(rect.ymax, y + radius)
|
|
end
|
|
|
|
local function updateRects(x, y, radius, scaleX, scaleY)
|
|
local radiusMax = radius * 3 -- arbitrary multiplier - needs to be big enough to not cut any metaballs
|
|
updateRect(outside, x, y, radiusMax)
|
|
updateRect(inside, x, y, radius)
|
|
end
|
|
|
|
local function roundRect( rect )
|
|
rect.xmin = round( rect.xmin )
|
|
rect.xmax = round( rect.xmax )
|
|
rect.ymin = round( rect.ymin )
|
|
rect.ymax = round( rect.ymax )
|
|
end
|
|
|
|
local function generate_p_ball(rng)
|
|
local angle, x_scale, y_scale, x, y, b_radius, shape
|
|
angle, x_scale, y_scale=rng_restricted_angle(restrictions, rng)
|
|
local dev = radius / 2 + rng() * radius / 4--math.min(CHUNK_SIZE/3, radius*1.5)
|
|
local dev_x, dev_y = pos.x, pos.y
|
|
x = rng(-dev, dev)+dev_x
|
|
y = rng(-dev, dev)+dev_y
|
|
if p_balls[#p_balls] and distance(p_balls[#p_balls], {x=x, y=y}) < MIN_BALL_DISTANCE then
|
|
local new_angle = bearing(p_balls[#p_balls], {x=x, y=y})
|
|
debug("Move ball old xy @ "..x..","..y)
|
|
x=(cos(new_angle)*MIN_BALL_DISTANCE) + x
|
|
y=(sin(new_angle)*MIN_BALL_DISTANCE) + y
|
|
debug("Move ball new xy @ "..x..","..y)
|
|
end
|
|
|
|
if #p_balls == 0 then
|
|
b_radius = ( 3 * radius / 4 + rng() * radius / 4) -- * (P_BALL_SIZE_FACTOR^#p_balls)
|
|
else
|
|
b_radius = ( radius / 4 + rng() * radius / 2) -- * (P_BALL_SIZE_FACTOR^#p_balls)
|
|
end
|
|
|
|
|
|
if #p_balls > 0 then
|
|
local tempRect = table.deepcopy(inside)
|
|
updateRect(tempRect, x, y, b_radius, x_scale, y_scale)
|
|
local rectSize = math.max(tempRect.xmax - tempRect.xmin, tempRect.ymax - tempRect.ymin)
|
|
local targetSize = size * 1.25
|
|
debug("Rect size "..rectSize.." targetSize "..targetSize)
|
|
if rectSize > targetSize then
|
|
local widthLeft = (targetSize - (inside.xmax - inside.xmin))
|
|
local heightLeft = (targetSize - (inside.ymax - inside.ymin))
|
|
local widthMod = math.min(x - inside.xmin, inside.xmax - x)
|
|
local heightMod = math.min(y - inside.ymin, inside.ymax - y)
|
|
local radiusBackup = b_radius
|
|
b_radius = math.min(widthLeft + widthMod, heightLeft + heightMod)
|
|
debug("Reduced ball radius from "..radiusBackup.." to "..b_radius.." widthLeft:"..widthLeft.." heightLeft:"..heightLeft.." widthMod:"..widthMod.." heightMod:"..heightMod)
|
|
end
|
|
end
|
|
|
|
if b_radius < 2 and #p_balls == 0 then
|
|
b_radius = 2
|
|
end
|
|
|
|
if b_radius > 0 then
|
|
|
|
maxPradius = math.max(maxPradius, b_radius)
|
|
shape = meta_shapes[rng(1,#meta_shapes)]
|
|
local radiusText = ""
|
|
-- log("Index rolled "..index.." shapes amount "..#meta_shapes)
|
|
if shape.type == "MetaDonut" then
|
|
local inRadius = b_radius / 4 + b_radius / 2 * rng()
|
|
radiusText = " inRadius:"..inRadius
|
|
p_balls[#p_balls+1] = shape:new(x, y, b_radius, inRadius, angle, x_scale, y_scale, 1.1)
|
|
else
|
|
p_balls[#p_balls+1] = shape:new(x, y, b_radius, angle, x_scale, y_scale, 1.1)
|
|
end
|
|
updateRects(x, y, b_radius, x_scale, y_scale)
|
|
|
|
debug("P+Ball "..shape.type.." @ "..x..","..y.." radius: "..b_radius..radiusText.." angle: "..math.deg(angle).." scale: "..x_scale..", "..y_scale)
|
|
end
|
|
end
|
|
|
|
local function generate_n_ball(i, rng)
|
|
local angle, x_scale, y_scale, x, y, b_radius, shape
|
|
angle, x_scale, y_scale=rng_restricted_angle('xy', rng)
|
|
if p_balls[i] then
|
|
local new_angle = p_balls[i].angle + pi*rng(0,2) + (rng()-0.5)*pi/2
|
|
local dist = p_balls[i].radius
|
|
x=(cos(new_angle)*dist) + p_balls[i].x
|
|
y=(sin(new_angle)*dist) + p_balls[i].y
|
|
angle = p_balls[i].angle + pi/2 + (rng()-0.5)*pi*2/3
|
|
else
|
|
x = rng(-radius, radius)+pos.x
|
|
y = rng(-radius, radius)+pos.y
|
|
end
|
|
|
|
if p_balls[i] then
|
|
b_radius = (p_balls[i].radius / 4 + rng() * p_balls[i].radius / 2) -- * (N_BALL_SIZE_FACTOR^#n_balls)
|
|
else
|
|
b_radius = (radius / 4 + rng() * radius / 2) -- * (N_BALL_SIZE_FACTOR^#n_balls)
|
|
end
|
|
|
|
if b_radius < 1 then
|
|
b_radius = 1
|
|
end
|
|
|
|
shape = meta_shapes[rng(1,#meta_shapes)]
|
|
-- log("Index rolled "..index.." shapes amount "..#meta_shapes)
|
|
local radiusText = ""
|
|
if shape.type == "MetaDonut" then
|
|
local inRadius = b_radius / 4 + b_radius / 2 * rng()
|
|
radiusText = " inRadius:"..inRadius
|
|
n_balls[#n_balls+1] = shape:new(x, y, b_radius, inRadius, angle, x_scale, y_scale, 1.15)
|
|
else
|
|
n_balls[#n_balls+1] = shape:new(x, y, b_radius, angle, x_scale, y_scale, 1.15)
|
|
end
|
|
-- updateRects(x, y, b_radius, x_scale, y_scale) -- should not be needed here - only positive ball can generate ore
|
|
debug("N-Ball "..shape.type.." @ "..x..","..y.." radius: "..b_radius..radiusText.." angle: "..math.deg(angle).." scale: "..x_scale..", "..y_scale)
|
|
end
|
|
|
|
local function calculate_force(x,y)
|
|
local p_force = 0
|
|
local n_force = 0
|
|
for _,ball in pairs(p_balls) do
|
|
p_force = p_force + ball:force(x,y)
|
|
end
|
|
for _,ball in pairs(n_balls) do
|
|
n_force = n_force + ball:force(x,y)
|
|
end
|
|
local totalForce = 0
|
|
if p_force > n_force then
|
|
totalForce = 1 - 1/(p_force - n_force)
|
|
end
|
|
--debug("Force at "..x..","..y.." p:"..p_force.." n:"..n_force.." result:"..totalForce)
|
|
--return (1 - 1/p_force) - n_force
|
|
return totalForce
|
|
end
|
|
|
|
local max_p_balls = 2
|
|
local min_amount = global.surfaces[surface.index].config[rname].min_amount or min_amount
|
|
if restrictions == 'xy' then
|
|
-- we have full 4 chunks
|
|
--radius = radius * 1.5
|
|
--richness = richness * 2 / 3
|
|
--min_amount = min_amount / 3
|
|
max_p_balls = 3
|
|
end
|
|
|
|
radius = math.min(radius, 2*CHUNK_SIZE)
|
|
|
|
local force
|
|
-- generate blobs
|
|
for i=1,max_p_balls do
|
|
generate_p_ball(rng)
|
|
end
|
|
|
|
for i=1,rng(1, #p_balls) do
|
|
generate_n_ball(i, rng)
|
|
end
|
|
|
|
local _total = 0
|
|
local oreLocations = {}
|
|
local forceTotal = 0
|
|
|
|
roundRect( outside )
|
|
|
|
for y=outside.ymin, outside.ymax do
|
|
for x=outside.xmin, outside.xmax do
|
|
force = calculate_force(x, y)
|
|
if force > 0 then
|
|
oreLocations[#oreLocations + 1] = {x = x, y = y, force = force}
|
|
forceTotal = forceTotal + force
|
|
end
|
|
end
|
|
end
|
|
|
|
local validCount, resOffsetX, resOffsetY, ratio
|
|
|
|
local bestRatio = 0
|
|
local bestRatioIndex = -1
|
|
local checkedLocations = {}
|
|
|
|
for index, locationOffset in pairs(locationOrder) do
|
|
validCount = 0
|
|
resOffsetX = locationOffset.x * CHUNK_SIZE
|
|
resOffsetY = locationOffset.y * CHUNK_SIZE
|
|
|
|
for _, location in pairs(oreLocations) do
|
|
|
|
local newX = location.x + resOffsetX
|
|
local newY = location.y + resOffsetY
|
|
|
|
if not checkedLocations[newX] then
|
|
checkedLocations[newX] = {}
|
|
end
|
|
if checkedLocations[newX][newY] == nil then
|
|
checkedLocations[newX][newY] = surface.can_place_entity{name = rname, position = {x = newX,y = newY}}
|
|
end
|
|
|
|
if checkedLocations[newX][newY] then
|
|
validCount = validCount + 1
|
|
end
|
|
end
|
|
|
|
ratio = 0
|
|
|
|
if validCount > 0 then
|
|
ratio = validCount / #oreLocations
|
|
end
|
|
|
|
debug("Valid ratio "..ratio.." for index "..index.." offsets "..resOffsetX..","..resOffsetY )
|
|
|
|
if not useResourceCollisionDetection then
|
|
break
|
|
end
|
|
|
|
if ratio > bestRatio then
|
|
bestRatio = ratio
|
|
bestRatioIndex = index
|
|
end
|
|
if ratio > resourceCollisionDetectionRatio then
|
|
break
|
|
end
|
|
end
|
|
|
|
if resourceCollisionFieldSkip and bestRatio < resourceCollisionDetectionRatioFallback then
|
|
validCount = 0
|
|
debug("Cancelled with best ratio "..bestRatio.." for index "..bestRatioIndex )
|
|
else
|
|
resOffsetX = locationOrder[bestRatioIndex].x * CHUNK_SIZE
|
|
resOffsetY = locationOrder[bestRatioIndex].y * CHUNK_SIZE
|
|
validCount = floor( bestRatio * #oreLocations )
|
|
debug("Selected ratio "..bestRatio.." for index "..bestRatioIndex.." offsets "..resOffsetX..","..resOffsetY )
|
|
end
|
|
|
|
if validCount > 0 then
|
|
local removeTrees = settings.global["rso-remove-trees"].value
|
|
local revealResources = settings.global["rso-reveal-spawned-resources"].value
|
|
local revealChunks = {}
|
|
revealChunks.minX = 1e9
|
|
revealChunks.minY = 1e9
|
|
revealChunks.maxX = -1e9
|
|
revealChunks.maxY = -1e9
|
|
|
|
local rectSize = ((inside.xmax - inside.xmin) + (inside.ymax - inside.ymin)) / 2
|
|
|
|
local sizeMultiplier = rectSize ^ 0.6
|
|
local minSize = richness * 5 * sizeMultiplier
|
|
local maxSize = richness * 10 * sizeMultiplier
|
|
local approxDepositSize = rng(minSize, maxSize)
|
|
|
|
approxDepositSize = approxDepositSize - validCount * min_amount
|
|
|
|
if approxDepositSize < 0 then
|
|
approxDepositSize = 100 * validCount
|
|
end
|
|
|
|
local forceFactor = approxDepositSize / forceTotal
|
|
|
|
-- don't create very dense resources in starting area - another field will be generated
|
|
if startingArea and forceFactor > 4000 then
|
|
forceFactor = rng(3000, 4000)
|
|
end
|
|
-- elseif forceFactor > 25000 then -- limit size of one resource pile
|
|
-- forceFactor = rgen:random(20000, 25000)
|
|
-- end
|
|
|
|
debug( "Force total:"..forceTotal.." sizeMin:"..minSize.." sizeMax:"..maxSize.." factor:"..forceFactor.." location#:"..validCount.." rectSize:"..rectSize.." sizeMultiplier:"..sizeMultiplier)
|
|
local richnessMultiplier = settings.global["rso-global-richness-mult"].value
|
|
|
|
if startingArea then
|
|
richnessMultiplier = settings.global["rso-starting-richness-mult"].value
|
|
end
|
|
-- if game.players[1] then
|
|
-- game.players[1].print("Spawning "..rname.." total amount "..(approxDepositSize + validCount * min_amount)*richnessMultiplier)
|
|
-- end
|
|
|
|
-- infinite ore handling for Angels Ores mod
|
|
local infiniteOrePresent = false
|
|
local infiniteOreName = "infinite-"..rname
|
|
local minimumInfiniteOreAmount = nil
|
|
local spawnName = rname
|
|
|
|
if game.entity_prototypes[infiniteOreName] then
|
|
infiniteOrePresent = true
|
|
minimumInfiniteOreAmount = game.entity_prototypes[infiniteOreName].minimum_resource_amount
|
|
end
|
|
|
|
if startingArea and not settings.global["rso-infinite-ores-in-start-area"].value then
|
|
infiniteOrePresent = false
|
|
end
|
|
|
|
local infiniteResourceSpawnThreshold = settings.global["rso-infinite-ore-threshold"].value
|
|
|
|
for _,location in pairs(oreLocations) do
|
|
|
|
local spawnX = location.x + resOffsetX
|
|
local spawnY = location.y + resOffsetY
|
|
|
|
if checkedLocations[spawnX][spawnY] then
|
|
|
|
local amount = floor(( forceFactor * location.force + min_amount ) * richnessMultiplier)
|
|
|
|
if amount > 1e9 then
|
|
amount = 1e9
|
|
end
|
|
|
|
_total = _total + amount
|
|
|
|
spawnName = rname
|
|
if infiniteOrePresent and location.force > infiniteResourceSpawnThreshold then
|
|
spawnName = infiniteOreName
|
|
if minimumInfiniteOreAmount and amount < minimumInfiniteOreAmount then
|
|
amount = minimumInfiniteOreAmount
|
|
end
|
|
-- debug("Infinite spawn: "..location.force)
|
|
end
|
|
|
|
if amount > 0 then
|
|
surface.create_entity{name = spawnName,
|
|
position = {spawnX, spawnY},
|
|
force = game.forces.neutral,
|
|
amount = amount,
|
|
raise_built = true}
|
|
|
|
if removeTrees then
|
|
remove_trees(surface, spawnX, spawnY, 1, 1)
|
|
end
|
|
|
|
revealChunks.minX = math.min( revealChunks.minX, spawnX )
|
|
revealChunks.minY = math.min( revealChunks.minY, spawnY )
|
|
revealChunks.maxX = math.max( revealChunks.maxX, spawnX )
|
|
revealChunks.maxY = math.max( revealChunks.maxY, spawnY )
|
|
end
|
|
end
|
|
end
|
|
|
|
if revealResources then
|
|
for _,force in pairs( game.forces )do
|
|
force.chart( surface, {{x = revealChunks.minX, y = revealChunks.minY}, {x = revealChunks.maxX, y = revealChunks.maxY}})
|
|
end
|
|
end
|
|
|
|
-- special handling for homeworld - sand resource has no graphics and is simply marked as sand tiles
|
|
if rname == "sand-source" then
|
|
local tileTable = {}
|
|
for _,location in pairs(oreLocations) do
|
|
if location.valid then
|
|
table.insert(tileTable,{ name = "sand-1", position = {location.x + resOffsetX,location.y + resOffsetY}})
|
|
end
|
|
end
|
|
if #tileTable > 1 then
|
|
surface.set_tiles(tileTable)
|
|
end
|
|
end
|
|
end
|
|
|
|
if debug_enabled then
|
|
-- debug("Search time "..scanProfiler.." spawn time "..placeProfiler)
|
|
debug("Total amount: ".._total)
|
|
--* for _,v in pairs(_a) do
|
|
--output a nice ASCII map
|
|
--* debug(table.concat(v))
|
|
--* end
|
|
debug("Leaving spawn_resource_ore")
|
|
end
|
|
return _total
|
|
end
|
|
|
|
--[[ entity-liquid ]]--
|
|
local function spawn_resource_liquid(surface, rname, pos, size, richness, startingArea, restrictions, rng)
|
|
restrictions = restrictions or ''
|
|
debug("Entering spawn_resource_liquid "..rname.." "..pos.x..","..pos.y.." "..size.." "..richness.." "..tostring(startingArea).." "..restrictions)
|
|
local _total = 0
|
|
local max_radius = rng() * CHUNK_SIZE / 2 + CHUNK_SIZE
|
|
--[[
|
|
if restrictions == 'xy' then
|
|
-- we have full 4 chunks
|
|
max_radius = floor(max_radius*1.5)
|
|
size = floor(size*1.2)
|
|
end
|
|
]]--
|
|
-- don't reduce amount of liquids - they are already infinite
|
|
-- size = modify_resource_size(size)
|
|
|
|
richness = ( 0.75 + rng() / 2 ) * richness * size
|
|
|
|
local resourceEntity = game.entity_prototypes[rname]
|
|
|
|
local total_share = 0
|
|
local avg_share = 1/size
|
|
local angle = rng()*pi*2
|
|
local saved = 0
|
|
local removeTrees = settings.global["rso-remove-trees"].value
|
|
local richnessMultiplier = settings.global["rso-global-richness-mult"].value
|
|
local spawnCount = 0
|
|
|
|
if startingArea then
|
|
richnessMultiplier = settings.global["rso-starting-richness-mult"].value
|
|
end
|
|
|
|
while total_share < 1 do
|
|
local new_share = vary_by_percentage(avg_share, 0.25, rng)
|
|
if size == 1 then
|
|
new_share = 1
|
|
end
|
|
|
|
if new_share + total_share > 1 then
|
|
new_share = 1 - total_share
|
|
end
|
|
total_share = new_share + total_share
|
|
if new_share < avg_share/10 then
|
|
-- too small
|
|
break
|
|
end
|
|
local amount = floor(richness*new_share) + saved
|
|
|
|
--if amount >= game.entity_prototypes[rname].minimum then
|
|
if amount >= global.surfaces[surface.index].config[rname].minimum_amount then
|
|
saved = 0
|
|
|
|
local spawned = false
|
|
local x, y
|
|
|
|
for try=1,15 do
|
|
local dist = rng()*(max_radius - max_radius*0.1) * try / 15
|
|
angle = angle + pi/4 + rng()*pi/2
|
|
x = pos.x + cos(angle)*dist
|
|
y = pos.y + sin(angle)*dist
|
|
if surface.can_place_entity{name = rname, position = {x,y}} then
|
|
debug("@ "..x..","..y.." amount: "..amount.." new_share: "..new_share.." try: "..try)
|
|
amount = floor(amount * richnessMultiplier)
|
|
|
|
if amount > 1e9 then
|
|
amount = 1e9
|
|
end
|
|
|
|
_total = _total + amount
|
|
|
|
if amount > 0 then
|
|
surface.create_entity{name = rname,
|
|
position = {x,y},
|
|
force = game.forces.neutral,
|
|
amount = amount,
|
|
direction = rng(4),
|
|
raise_built = true}
|
|
if removeTrees then
|
|
remove_trees(surface, x, y, 4, 4)
|
|
end
|
|
end
|
|
|
|
if spawnCount == 0 then
|
|
debug("Switched center location to "..x..","..y)
|
|
pos.x = x
|
|
pos.y = y
|
|
end
|
|
|
|
spawnCount = spawnCount + 1
|
|
spawned = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if not startingArea and not spawned then -- we don't want to make ultra rich nodes in starting area - failing to make them will add second spawn in different location
|
|
local entities = surface.find_entities_filtered{area = {{x - 5, y - 5}, {x + 5, y + 5}}, name=rname}
|
|
if entities and #entities > 0 then
|
|
for k, ent in pairs(entities) do
|
|
local newAmount = ent.amount + floor(amount/#entities)
|
|
if newAmount > 1e9 then
|
|
newAmount = 1e9
|
|
end
|
|
|
|
ent.amount = newAmount
|
|
_total = _total + newAmount
|
|
end
|
|
break
|
|
end
|
|
end
|
|
else
|
|
saved = amount
|
|
debug("Not placed "..rname.." amount "..amount.." minimum "..global.surfaces[surface.index].config[rname].minimum_amount.. " total share "..total_share.." placed till now ".._total)
|
|
end
|
|
end
|
|
debug("Total amount: ".._total.." in "..spawnCount.." fields")
|
|
debug("Leaving spawn_resource_liquid")
|
|
return _total
|
|
end
|
|
|
|
local spawnerTable = nil
|
|
|
|
local function initSpawnerTable()
|
|
if spawnerTable == nil then
|
|
spawnerTable = {}
|
|
for _, prototype in pairs(game.entity_prototypes) do
|
|
if prototype.type == "unit-spawner" then
|
|
spawnerTable[prototype.name] = prototype
|
|
end
|
|
end
|
|
-- log("Spawner table "..serpent.block(spawnerTable))
|
|
end
|
|
end
|
|
|
|
local function computeAllotments(entityTable, regionDistance)
|
|
|
|
local allotment_max = 0
|
|
|
|
if not entityTable then
|
|
return allotment_max
|
|
end
|
|
|
|
for k,v in pairs(entityTable) do
|
|
if v then
|
|
if not v.min_distance or regionDistance > v.min_distance then
|
|
local allotment = v.allotment
|
|
if v.allotment_distance_factor then
|
|
local dist_factor = regionDistance^v.allotment_distance_factor
|
|
if v.max_probability_distance_factor then
|
|
dist_factor = max(dist_factor, v.max_probability_distance_factor)
|
|
end
|
|
allotment = allotment * dist_factor
|
|
end
|
|
v.allotment_range ={min = allotment_max, max = allotment_max + allotment}
|
|
allotment_max = allotment_max + allotment
|
|
else
|
|
v.allotment_range = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
return allotment_max
|
|
end
|
|
|
|
local function spawn_entity_helper(surface, prototype, x, y, config)
|
|
position = {x, y}
|
|
local collides = surface.entity_prototype_collides(prototype, position, true)
|
|
if collides then
|
|
remove_trees_based_on_bb(surface, x, y, prototype.map_generator_bounding_box)
|
|
end
|
|
|
|
collides = surface.entity_prototype_collides(prototype, position, true)
|
|
|
|
if not collides then
|
|
surface.create_entity{name=prototype.name, position={x, y}, force=game.forces[config.force], spawn_decorations=true, raise_built = true}
|
|
end
|
|
|
|
return not collides
|
|
end
|
|
|
|
local function spawn_entity(surface, ent, r_config, x, y, rng)
|
|
if not settings.global["rso-biter-generation"].value then return end
|
|
local size = rng(r_config.size.min, r_config.size.max)
|
|
|
|
local _total = 0
|
|
local r_distance = distanceFromStartingAreas(global.surfaces[surface.index], x/REGION_TILE_SIZE, y/REGION_TILE_SIZE)
|
|
local surfaceData = global.surfaces[surface.index]
|
|
|
|
if surfaceData and surfaceData.starting_area_size then
|
|
r_distance = r_distance - surfaceData.starting_area_size
|
|
if r_distance < 0.5 then
|
|
r_distance = 0.5
|
|
end
|
|
end
|
|
|
|
local distanceMultiplier = math.min(r_distance^r_config.size_per_region_factor, 5)
|
|
if r_config.size_per_region_factor then
|
|
size = size*distanceMultiplier
|
|
end
|
|
|
|
size = size * settings.global["rso-enemy-base-size"].value
|
|
|
|
debug("Entering spawn_entity "..ent.." "..x..","..y.." size:"..size.." dist:"..r_distance)
|
|
|
|
local maxAttemptCount = 16
|
|
local distancePerBatch = 0.25
|
|
|
|
initSpawnerTable()
|
|
|
|
local spawnerName = nil
|
|
local spawnString = ""
|
|
local failCount = 0
|
|
|
|
for i=1,size do
|
|
if failCount > 5 and failCount / (i - 1) > 0.75 then
|
|
break
|
|
end
|
|
|
|
local baseSpawned = false
|
|
local baseX, baseY
|
|
|
|
for attempt = 1, maxAttemptCount do
|
|
local max_d = floor(CHUNK_SIZE * (0.5 + (attempt/4) * distancePerBatch))
|
|
baseX = x + rng(0, floor(max_d)) - max_d/2
|
|
baseY = y + rng(0, floor(max_d)) - max_d/2
|
|
|
|
if surface.get_tile(baseX, baseY).valid then
|
|
|
|
spawnerName = nil
|
|
local allotment_max = computeAllotments(r_config.bases, r_distance)
|
|
|
|
if allotment_max == 0 then
|
|
log("No enemy base definition found")
|
|
return
|
|
end
|
|
|
|
local base_type = rng(0, allotment_max)
|
|
for base_name,v in pairs(r_config.bases) do
|
|
if v and v.allotment_range and base_type >= v.allotment_range.min and base_type <= v.allotment_range.max then
|
|
spawnerName = base_name
|
|
break
|
|
end
|
|
end
|
|
|
|
if spawnerName and spawnerTable[spawnerName] then
|
|
if spawn_entity_helper(surface, spawnerTable[spawnerName], baseX, baseY, r_config) then
|
|
_total = _total + 1
|
|
debug(spawnerName.." @ "..baseX..","..baseY.." placed on "..attempt.." attempt")
|
|
spawnString = spawnString.."+"
|
|
baseSpawned = true
|
|
break;
|
|
else
|
|
if attempt == maxAttemptCount then
|
|
debug(spawnerName.." @ "..baseX..","..baseY.." failed to spawn")
|
|
failCount = failCount + 1
|
|
spawnString = spawnString.."-"
|
|
end
|
|
end
|
|
else
|
|
game.players[1].print("Entity "..spawnerName.." doesn't exist")
|
|
end
|
|
end
|
|
end
|
|
|
|
if r_config.sub_spawn_probability and baseSpawned then
|
|
local sub_spawn_prob = r_config.sub_spawn_probability*math.min(r_config.sub_spawn_max_distance_factor, r_config.sub_spawn_distance_factor^r_distance)
|
|
if rng() < sub_spawn_prob then
|
|
|
|
local allotment_max = computeAllotments(r_config.sub_spawns, r_distance)
|
|
|
|
for i=1,(rng(r_config.sub_spawn_size.min, r_config.sub_spawn_size.max)*distanceMultiplier) do
|
|
|
|
local sub_type = rng(0, allotment_max)
|
|
for sub_spawn,v in pairs(r_config.sub_spawns) do
|
|
if v.allotment_range and sub_type >= v.allotment_range.min and sub_type <= v.allotment_range.max then
|
|
local turretPrototype = game.entity_prototypes[sub_spawn]
|
|
if turretPrototype then
|
|
for attempt = 1, maxAttemptCount do
|
|
local max_d = floor((attempt/4) * CHUNK_SIZE * distancePerBatch)
|
|
local s_x = baseX + rng(max_d) - max_d/2
|
|
local s_y = baseY + rng(max_d) - max_d/2
|
|
remove_trees(surface, s_x, s_y, v.clear_range[1], v.clear_range[2])
|
|
if spawn_entity_helper(surface, turretPrototype, s_x, s_y, r_config) then
|
|
debug("Rolled subspawn "..sub_spawn.." @ "..s_x..","..s_x.." after "..attempt.." attempts")
|
|
break;
|
|
else
|
|
if attempt == maxAttemptCount then
|
|
debug("Rolling subspawn "..sub_spawn.." @ "..s_x..","..s_x.." failed")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
debug("Total entity amount: ".._total.." fails "..failCount.." size "..size.." spawn map "..spawnString)
|
|
end
|
|
|
|
--[[ EVENT/INIT METHODS ]]--
|
|
|
|
local function spawn_starting_resources( surface, index )
|
|
|
|
local surfaceData = global.surfaces[surface.index]
|
|
if surfaceData.startingAreas[index].spawned then return end
|
|
|
|
-- skip spawning if starting area is to small or starting areas are disabled
|
|
if global.disableStartingArea or surfaceData.starting_area_size < 0.1 then
|
|
surfaceData.startingAreas[index].spawned = true
|
|
return
|
|
end
|
|
|
|
local position = surfaceData.startingAreas[index]
|
|
local config = surfaceData.config
|
|
|
|
-- generate chunks for starting area - it shouldn't matter at 0,0 but it's needed if it has been moved
|
|
local areaRadius = surfaceData.starting_area_size * REGION_TILE_SIZE
|
|
surface.request_to_generate_chunks(position, math.ceil(areaRadius/CHUNK_SIZE))
|
|
surface.force_generate_chunk_requests()
|
|
|
|
local rng = rng_for_reg_pos( surface.index, position )
|
|
local status = true
|
|
for resName,resConfig in pairs(config) do
|
|
if resConfig.starting and resConfig.valid and resConfig.allotment > 0 then
|
|
local prob = rng() -- probability that this resource is spawned
|
|
debug("starting resource probability rolled "..prob)
|
|
if resConfig.starting.probability > 0 and prob <= resConfig.starting.probability then
|
|
local total = 0
|
|
local radius = 50
|
|
local maxRadius = 301
|
|
maxRadius = maxRadius * surface.map_gen_settings.starting_area
|
|
local min_threshold = 0
|
|
local richness = resConfig.starting.richness
|
|
|
|
if resConfig.type == "resource-ore" then
|
|
min_threshold = richness * rng(5, 10) -- lets make sure that there is at least 5-10 times starting richness ore at start
|
|
elseif resConfig.type == "resource-liquid" then
|
|
min_threshold = richness * 0.5 * resConfig.starting.size
|
|
end
|
|
|
|
while (radius < maxRadius) and (total < min_threshold) do
|
|
|
|
radius = radius + 25
|
|
|
|
local angle = rng() * pi * 2
|
|
local dist = radius / 2 + rng(radius / 2)
|
|
-- debug("Starting offset "..dist.." at "..angle)
|
|
local pos = { x = floor(cos(angle) * dist) + position.x, y = floor(sin(angle) * dist) + position.y }
|
|
if resConfig.type == "resource-ore" then
|
|
total = total + spawn_resource_ore(surface, resName, pos, resConfig.starting.size, richness, true, null, rng)
|
|
elseif resConfig.type == "resource-liquid" then
|
|
total = total + spawn_resource_liquid(surface, resName, pos, resConfig.starting.size, richness, true, null, rng)
|
|
end
|
|
|
|
if richness == resConfig.starting.richness and total > 0 then
|
|
richness = resConfig.starting.richness / 4
|
|
end
|
|
end
|
|
if total < min_threshold then
|
|
status = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
surfaceData.startingAreas[index].spawned = true
|
|
end
|
|
|
|
local function modifyMinMax(value, mod)
|
|
value.min = round( value.min * mod )
|
|
value.max = round( value.max * mod )
|
|
end
|
|
|
|
local function build_config_data(surface)
|
|
local mapGenSettings = nil
|
|
|
|
if not ignoreMapGenSettings then
|
|
mapGenSettings = surface.map_gen_settings
|
|
end
|
|
local autoPlaceSettings = nil
|
|
if mapGenSettings then
|
|
autoPlaceSettings = mapGenSettings.autoplace_controls
|
|
end
|
|
|
|
local configIndexed = {}
|
|
local surfaceData = global.surfaces[surface.index]
|
|
local config = surfaceData.config
|
|
|
|
debug("Building config for " .. surface.name .. " index " .. surface.index)
|
|
-- build additional indexed array to the associative array
|
|
for res_name, resConf in pairs(config) do
|
|
if resConf.valid then -- only add valid resources
|
|
|
|
local settingsForResource = nil
|
|
local isEntity = (resConf.type == "entity")
|
|
local addResource = true
|
|
|
|
local autoplaceName = res_name
|
|
|
|
if resConf.autoplace_name then
|
|
autoplaceName = resConf.autoplace_name
|
|
end
|
|
|
|
if autoPlaceSettings then
|
|
settingsForResource = autoPlaceSettings[autoplaceName]
|
|
end
|
|
|
|
if settingsForResource then
|
|
local allotmentMod = settingsForResource.frequency
|
|
local sizeMod = settingsForResource.size
|
|
local richnessMod = settingsForResource.richness
|
|
|
|
if allotmentMod then
|
|
if isEntity then
|
|
resConf.absolute_probability = resConf.absolute_probability * allotmentMod
|
|
debug("Entity chance modified to "..resConf.absolute_probability)
|
|
else
|
|
resConf.allotment = round( resConf.allotment * allotmentMod )
|
|
end
|
|
else
|
|
log("Null allotment mod for "..res_name.." value "..settingsForResource.frequency)
|
|
end
|
|
|
|
if sizeMod ~= nil and sizeMod == 0 then
|
|
addResource = false
|
|
--log("Null size mod for "..res_name.." value "..settingsForResource.size)
|
|
end
|
|
|
|
if sizeMod ~= nil then
|
|
modifyMinMax(resConf.size, sizeMod)
|
|
|
|
if resConf.starting then
|
|
if sizeMod == 0 then
|
|
resConf.starting = nil
|
|
else
|
|
resConf.starting.size = round( resConf.starting.size * sizeMod )
|
|
end
|
|
end
|
|
|
|
if isEntity then
|
|
if resConf.sub_spawn_size then
|
|
modifyMinMax(resConf.sub_spawn_size, sizeMod)
|
|
end
|
|
modifyMinMax(resConf.spawns_per_region, sizeMod)
|
|
end
|
|
end
|
|
|
|
if richnessMod then
|
|
if resConf.type == "resource-ore" then
|
|
resConf.richness = round( resConf.richness * richnessMod )
|
|
elseif resConf.type == "resource-liquid" then
|
|
modifyMinMax(resConf.richness, richnessMod)
|
|
end
|
|
|
|
if resConf.starting then
|
|
resConf.starting.richness = round( resConf.starting.richness * richnessMod )
|
|
end
|
|
else
|
|
log("Null richness mod for "..res_name.." value "..settingsForResource.richness)
|
|
end
|
|
|
|
if allotmentMod and richnessMod and sizeMod then
|
|
debug(res_name .. " allotment mod " .. allotmentMod .. " size mod " .. sizeMod .. " richness mod " .. richnessMod )
|
|
end
|
|
end
|
|
|
|
if not settings.global["rso-oil-in-start-area"].value and resConf.type == "resource-liquid" then
|
|
resConf.starting = nil
|
|
end
|
|
|
|
if not settings.global["rso-ore-in-start-area"].value and resConf.type == "resource-ore" then
|
|
resConf.starting = nil
|
|
end
|
|
|
|
if addResource then
|
|
-- this should be a limited table most likely not full config copy
|
|
local res_conf = table.deepcopy(resConf)
|
|
res_conf.name = res_name
|
|
|
|
if res_conf.multi_resource and settings.global["rso-multi-resource-active"].value then
|
|
local new_list = {}
|
|
for sub_res_name, allotment in pairs(res_conf.multi_resource) do
|
|
if config[sub_res_name] and config[sub_res_name].valid then
|
|
new_list[#new_list+1] = {name = sub_res_name, allotment = allotment}
|
|
end
|
|
end
|
|
table.sort(new_list, function(a, b) return a.name < b.name end)
|
|
res_conf.multi_resource = new_list
|
|
else
|
|
res_conf.multi_resource_chance = nil
|
|
end
|
|
configIndexed[#configIndexed + 1] = res_conf
|
|
end
|
|
end
|
|
end
|
|
|
|
table.sort(configIndexed, function(a, b) return a.name < b.name end)
|
|
|
|
local pr=0
|
|
local maxAllotment = 0
|
|
for index,v in pairs(configIndexed) do
|
|
if v.along_resource_probability then
|
|
v.along_resource_probability_range={min=pr, max=pr+v.along_resource_probability}
|
|
pr = pr + v.along_resource_probability
|
|
end
|
|
if v.allotment and v.allotment > 0 then
|
|
v.allotment_range={min = maxAllotment, max = maxAllotment + v.allotment}
|
|
maxAllotment = maxAllotment + v.allotment
|
|
end
|
|
end
|
|
|
|
surfaceData.maxAllotment = maxAllotment
|
|
surfaceData.starting_area_size = starting_area_size
|
|
|
|
if mapGenSettings and mapGenSettings.starting_area then
|
|
surfaceData.starting_area_size = starting_area_size * mapGenSettings.starting_area
|
|
debug("Starting area "..surfaceData.starting_area_size.." for surface "..surface.index)
|
|
end
|
|
|
|
surfaceData.configIndexed = configIndexed
|
|
end
|
|
|
|
local function checkConfigForInvalidResources(surfaceIndex)
|
|
--make sure that every resource in the config is actually available.
|
|
--call this function, before the auxiliary config is prebuilt!
|
|
|
|
local prototypes = game.entity_prototypes
|
|
local config = global.surfaces[surfaceIndex].config
|
|
|
|
for resourceName, resourceConfig in pairs(config) do
|
|
if prototypes[resourceName] or resourceConfig.type == "entity" then
|
|
resourceConfig.valid = true
|
|
else
|
|
-- resource was in config, but it doesn't exist in game files anymore - mark it invalid
|
|
resourceConfig.valid = false
|
|
|
|
--table.insert(invalidResources, "Resource not available: " .. resourceName)
|
|
debug("Resource not available: " .. resourceName)
|
|
log("Resource not available: " .. resourceName)
|
|
end
|
|
|
|
if resourceConfig.valid and resourceConfig.type ~= "entity" then
|
|
if prototypes[resourceName].autoplace_specification == nil then
|
|
resourceConfig.valid = false
|
|
debug("Resource "..resourceName.." invalidated - autoplace not present")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function checkForBobEnemies()
|
|
if game.entity_prototypes["bob-biter-spawner"] and game.entity_prototypes["bob-spitter-spawner"] then
|
|
global.useBobEntity = true
|
|
else
|
|
global.useBobEntity = false
|
|
end
|
|
end
|
|
|
|
local function roll_region(surface, c_x, c_y)
|
|
--in what region is this chunk?
|
|
local r_x=floor(c_x/REGION_TILE_SIZE)
|
|
local r_y=floor(c_y/REGION_TILE_SIZE)
|
|
local r_data = nil
|
|
--don't spawn stuff in starting area
|
|
if isInStartingArea( surface.index, c_x, c_y ) then
|
|
return false
|
|
end
|
|
|
|
local surfaceData = global.surfaces[surface.index]
|
|
local configIndexed = surfaceData.configIndexed
|
|
local regions = surfaceData.regions
|
|
|
|
if regions[r_x] and regions[r_x][r_y] then
|
|
r_data = regions[r_x][r_y]
|
|
else
|
|
--if this chunk is the first in its region to be generated
|
|
if not regions[r_x] then regions[r_x] = {} end
|
|
regions[r_x][r_y]={}
|
|
r_data = regions[r_x][r_y]
|
|
local rng = rng_for_reg_pos(surface.index, {x=r_x,y=r_y})
|
|
|
|
local rollCount = math.ceil(#configIndexed / 10) - 1 -- 0 based counter is more convenient here
|
|
rollCount = math.min(rollCount, 3)
|
|
|
|
local resourceSetting = settings.global["rso-resource-chance"].value
|
|
|
|
local maxAllotment = surfaceData.maxAllotment
|
|
|
|
-- rolle ores only if they are present (it will fail if allotment is 0)
|
|
if maxAllotment > 0 then
|
|
for rollNumber = 0,rollCount do
|
|
|
|
local resourceChance = resourceSetting - rollNumber * 0.1
|
|
--absolute chance to spawn resource
|
|
local abct = rng()
|
|
debug("Rolling resource "..abct.." against "..resourceChance.." roll "..rollNumber)
|
|
if abct <= resourceChance then
|
|
local res_type=rng(1, maxAllotment)
|
|
for index,v in pairs(configIndexed) do
|
|
if v.allotment_range and ((res_type >= v.allotment_range.min) and (res_type <= v.allotment_range.max)) then
|
|
debug("Rolled primary resource "..v.name.." with roll="..res_type.." @ "..r_x..","..r_y)
|
|
local num_spawns = 0
|
|
if v.spawns_per_region.min == v.spawns_per_region.max then
|
|
num_spawns = v.spawns_per_region.min
|
|
else
|
|
num_spawns = rng(v.spawns_per_region.min, v.spawns_per_region.max)
|
|
end
|
|
local last_spawn_coords = {}
|
|
local along_
|
|
for i=1,num_spawns do
|
|
local c_x, c_y = find_random_chunk(r_x, r_y, rng)
|
|
|
|
-- even if initial chunk is outside region might overlap with starting area - need to recheck here if rolled coords are outside
|
|
if not isInStartingArea( surface.index, c_x, c_y ) then
|
|
if not r_data[c_x] then r_data[c_x] = {} end
|
|
if not r_data[c_x][c_y] then r_data[c_x][c_y] = {} end
|
|
local c_data = r_data[c_x][c_y]
|
|
c_data[#c_data+1]={v.name, rollNumber}
|
|
last_spawn_coords[#last_spawn_coords+1] = {c_x, c_y}
|
|
debug("Rolled primary chunk "..v.name.." @ "..c_x..","..c_y.." reg: "..r_x..","..r_y.." actual reg: "..floor(c_x/REGION_TILE_SIZE)..","..floor(c_y/REGION_TILE_SIZE))
|
|
-- Along resource spawn, only once
|
|
if i == 1 then
|
|
local am_roll = rng()
|
|
for index,vv in pairs(configIndexed) do
|
|
if vv.along_resource_probability_range and am_roll >= vv.along_resource_probability_range.min and am_roll <= vv.along_resource_probability_range.max then
|
|
c_data = r_data[c_x][c_y]
|
|
c_data[#c_data+1]={vv.name, rollNumber}
|
|
debug("Rolled along "..vv.name.." @ "..c_x.."."..c_y.." reg: "..r_x..","..r_y)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- roll multiple resources in same region
|
|
local deep=0
|
|
if #last_spawn_coords > 0 then
|
|
while v.multi_resource_chance and rng() <= v.multi_resource_chance*(multi_resource_chance_diminish^deep) do
|
|
debug("Multi roll chance "..v.multi_resource_chance.." with diminish chance "..v.multi_resource_chance*(multi_resource_chance_diminish^deep))
|
|
deep = deep + 1
|
|
local multiAllotmentMax = 0
|
|
for index,sub_res in pairs(v.multi_resource) do multiAllotmentMax = multiAllotmentMax + sub_res.allotment end
|
|
|
|
local res_type = 1 -- with allotment of 1 we don't need to roll rng and rng will complain when it's range is 0
|
|
if multiAllotmentMax > 1 then
|
|
res_type = rng(1, multiAllotmentMax)
|
|
end
|
|
local min=0
|
|
for _, sub_res in pairs(v.multi_resource) do
|
|
if (res_type >= min) and (res_type <= sub_res.allotment + min) then
|
|
local last_coords = last_spawn_coords[rng(1, #last_spawn_coords)]
|
|
local c_x, c_y = find_random_neighbour_chunk(last_coords[1], last_coords[2], rng) -- in same region as primary resource chunk
|
|
if not r_data[c_x] then r_data[c_x] = {} end
|
|
if not r_data[c_x][c_y] then r_data[c_x][c_y] = {} end
|
|
local c_data = r_data[c_x][c_y]
|
|
c_data[#c_data+1]={sub_res.name, deep}
|
|
debug("Rolled multiple "..sub_res.name..":"..deep.." with res_type="..res_type.." @ "..c_x..","..c_y.." reg: "..r_x..","..r_y)
|
|
break
|
|
else
|
|
min = min + sub_res.allotment
|
|
end
|
|
end
|
|
end
|
|
end
|
|
break
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
-- roll for absolute_probability - this rolls the enemies
|
|
for index,v in pairs(configIndexed) do
|
|
if v.absolute_probability then
|
|
local prob_factor = 1
|
|
if v.probability_distance_factor then
|
|
prob_factor = math.min(v.max_probability_distance_factor, v.probability_distance_factor^distance({x=0,y=0},{x=r_x,y=r_y}))
|
|
end
|
|
local abs_roll = rng()
|
|
if abs_roll<v.absolute_probability*prob_factor then
|
|
local num_spawns=rng(v.spawns_per_region.min, v.spawns_per_region.max)
|
|
for i=1,num_spawns do
|
|
local c_x, c_y = find_random_chunk(r_x, r_y, rng)
|
|
if not isInStartingArea( surface.index, c_x, c_y ) then
|
|
if not r_data[c_x] then r_data[c_x] = {} end
|
|
if not r_data[c_x][c_y] then r_data[c_x][c_y] = {} end
|
|
local c_data = r_data[c_x][c_y]
|
|
c_data[#c_data+1] = {v.name, 1}
|
|
debug("Rolled absolute "..v.name.." with rt="..abs_roll.." @ "..c_x..","..c_y.." reg: "..r_x..","..r_y)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local function roll_chunk(surface, c_x, c_y)
|
|
--handle spawn in chunks
|
|
local r_x=floor(c_x/REGION_TILE_SIZE)
|
|
local r_y=floor(c_y/REGION_TILE_SIZE)
|
|
local r_data = nil
|
|
|
|
debug("Entering roll chunk "..c_x..","..c_y.." reg: "..r_x..","..r_y)
|
|
|
|
--don't spawn stuff in starting area
|
|
if isInStartingArea( surface.index, c_x, c_y ) then
|
|
return false
|
|
end
|
|
|
|
local surfaceData = global.surfaces[surface.index]
|
|
|
|
local c_center_x=c_x + CHUNK_SIZE/2
|
|
local c_center_y=c_y + CHUNK_SIZE/2
|
|
if not (surfaceData.regions[r_x] and surfaceData.regions[r_x][r_y]) then
|
|
return
|
|
end
|
|
|
|
r_data = surfaceData.regions[r_x][r_y]
|
|
if not (r_data[c_x] and r_data[c_x][c_y]) then
|
|
return
|
|
end
|
|
|
|
-- log("Region data for "..r_x..","..r_y.." for pos "..c_x..","..c_y)
|
|
-- log(serpent.dump(r_data))
|
|
|
|
if r_data[c_x] and r_data[c_x][c_y] then
|
|
local rng = rng_for_reg_pos(surface.index, {x=c_center_x,y=c_center_y})
|
|
|
|
debug("Stumbled upon "..c_x..","..c_y.." reg: "..r_x..","..r_y)
|
|
local resource_list = r_data[c_x][c_y]
|
|
local richness_distance_factor = settings.global["rso-distance-exponent"].value
|
|
local fluid_richness_distance_factor = settings.global["rso-fluid-distance-exponent"].value
|
|
local size_distance_factor = settings.global["rso-size-exponent"].value
|
|
--for resource, deep in pairs(r_data[c_x][c_y]) do
|
|
-- resource_list[#resource_list+1] = {resource, deep}
|
|
--end
|
|
table.sort(resource_list, function(res1, res2) return res1[2] < res2[2] end)
|
|
|
|
local function calculateFactor(distance, exponent)
|
|
if distance < 1 and exponent < 1 then
|
|
return distance
|
|
end
|
|
return distance^exponent
|
|
end
|
|
|
|
for _, res_con in pairs(resource_list) do
|
|
local resource = res_con[1]
|
|
local deep = res_con[2]
|
|
local r_config = surfaceData.config[resource]
|
|
if r_config and r_config.valid then
|
|
local dist = distanceFromStartingAreas(surfaceData, r_x, r_y)
|
|
local sizeFactor = calculateFactor(dist, size_distance_factor)
|
|
if r_config.type=="resource-ore" then
|
|
local richFactor = calculateFactor(dist, richness_distance_factor)
|
|
debug("Resource "..resource.." distance "..dist.." factors (size, richness) "..sizeFactor..","..richFactor)
|
|
local size=rng(r_config.size.min, r_config.size.max) * (multi_resource_size_factor^deep) * sizeFactor
|
|
local richness = r_config.richness * richFactor * (multi_resource_richness_factor^deep)
|
|
local restriction = ''
|
|
debug("Center @ "..c_center_x..","..c_center_y)
|
|
c_center_x, c_center_y, restriction = find_intersection(surface, c_center_x, c_center_y)
|
|
debug("New Center @ "..c_center_x..","..c_center_y)
|
|
|
|
-- local richness_max = 20000
|
|
-- local size_max = 30
|
|
-- if (richness_max > 0 and richness > richness_max) then
|
|
-- richness = math.min(richness, richness_max)
|
|
-- end
|
|
-- if (size_max > 0 and size > size_max) then
|
|
-- size = math.min(size, size_max)
|
|
-- end
|
|
|
|
spawn_resource_ore(surface, resource, {x=c_center_x,y=c_center_y}, size, richness, false, restriction, rng)
|
|
elseif r_config.type=="resource-liquid" then
|
|
local richFactor = 0;
|
|
if r_config.useOreScaling then
|
|
richFactor = calculateFactor(dist, richness_distance_factor)
|
|
else
|
|
richFactor = calculateFactor(dist, fluid_richness_distance_factor)
|
|
end
|
|
debug("Resource "..resource.." distance "..dist.." factors (size, richness) "..sizeFactor..","..richFactor)
|
|
local size=rng(r_config.size.min, r_config.size.max) * (multi_resource_size_factor^deep) * sizeFactor
|
|
if r_config.size.min == 1 and r_config.size.max == 1 then
|
|
size = 1
|
|
end
|
|
local richness=rng(r_config.richness.min, r_config.richness.max) * richFactor * (multi_resource_richness_factor^deep)
|
|
local restriction = ''
|
|
c_center_x, c_center_y, restriction = find_intersection(surface, c_center_x, c_center_y)
|
|
spawn_resource_liquid(surface, resource, {x=c_center_x,y=c_center_y}, size, richness, false, restriction, rng)
|
|
elseif r_config.type=="entity" and not global.skipEnemies then
|
|
spawn_entity(surface, resource, r_config, c_center_x, c_center_y, rng)
|
|
end
|
|
else
|
|
debug("Resource access failed for " .. resource)
|
|
game.players[1].print("Resource access failed for " .. resource)
|
|
end
|
|
end
|
|
r_data[c_x][c_y] = nil
|
|
|
|
if tableLength(r_data[c_x]) == 0 then
|
|
r_data[c_x] = nil
|
|
end
|
|
|
|
--l:dump()
|
|
end
|
|
end
|
|
|
|
local function clear_chunk(surface, tileX, tileY, ent_list)
|
|
|
|
local _count = 0
|
|
for _, obj in pairs(surface.find_entities_filtered{area = {{tileX, tileY}, {tileX + CHUNK_SIZE, tileY + CHUNK_SIZE}}, name=ent_list}) do
|
|
if obj.valid then
|
|
obj.destroy()
|
|
_count = _count + 1
|
|
end
|
|
end
|
|
|
|
if not global.skipEnemies then
|
|
-- remove biters
|
|
for _, obj in pairs(surface.find_entities_filtered{area = {{tileX, tileY}, {tileX + CHUNK_SIZE, tileY + CHUNK_SIZE}}, type="unit"}) do
|
|
-- and (string.find(obj.name, "-biter", -6) or string.find(obj.name, "-spitter", -8))
|
|
if obj.valid and obj.force.name == "enemy" then
|
|
obj.destroy()
|
|
_count = _count + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
if _count > 0 then debug("Destroyed - ".._count) end
|
|
end
|
|
|
|
local function prepareEntityList(surfaceIndex)
|
|
local entityList = {}
|
|
local configIndexed = global.surfaces[surfaceIndex].configIndexed
|
|
|
|
for _,v in pairs(configIndexed) do
|
|
entityList[#entityList + 1] = v.name
|
|
local infiniteOreName = "infinite-".. v.name
|
|
|
|
if game.entity_prototypes[infiniteOreName] then
|
|
entityList[#entityList + 1] = infiniteOreName
|
|
end
|
|
|
|
if not global.skipEnemies then
|
|
if v.bases then
|
|
for base, config in pairs(v.bases) do
|
|
entityList[#entityList + 1] = base
|
|
end
|
|
end
|
|
|
|
if v.sub_spawns then
|
|
for ent,vv in pairs(v.sub_spawns) do
|
|
entityList[#entityList + 1] = ent
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return entityList
|
|
end
|
|
|
|
local function regenerateSurface(surface, clearOnly)
|
|
|
|
local surfaceData = global.surfaces[surface.index]
|
|
surfaceData.regions = {}
|
|
|
|
local entityList = prepareEntityList(surface.index)
|
|
|
|
for chunk in surface.get_chunks() do
|
|
local tileX = chunk.x * CHUNK_SIZE
|
|
local tileY = chunk.y * CHUNK_SIZE
|
|
|
|
if not isInStartingArea( surface.index, tileX, tileY ) then
|
|
clear_chunk(surface, tileX, tileY, entityList)
|
|
end
|
|
end
|
|
|
|
if not clearOnly then
|
|
for chunk in surface.get_chunks() do
|
|
|
|
local tileX = chunk.x * CHUNK_SIZE
|
|
local tileY = chunk.y * CHUNK_SIZE
|
|
|
|
if not isInStartingArea( surface.index, tileX, tileY ) then
|
|
roll_region(surface, tileX, tileY)
|
|
roll_chunk(surface, tileX, tileY)
|
|
|
|
if useStraightWorldMod then
|
|
straightWorld(surface, {x = tileX, y = tileY}, {x = tileX + CHUNK_SIZE, y = tileY + CHUNK_SIZE})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if clearOnly then
|
|
log("Ore clear done")
|
|
else
|
|
log("Ore regeneration done")
|
|
end
|
|
end
|
|
|
|
local function regenerate_everything(clearOnly, noEnemies)
|
|
global.skipEnemies = noEnemies
|
|
|
|
for index, surfaceData in pairs(global.surfaces) do
|
|
local surface = game.surfaces[index]
|
|
regenerateSurface(surface, clearOnly)
|
|
end
|
|
|
|
global.skipEnemies = nil
|
|
end
|
|
|
|
local function clearStartingArea( surface, pos )
|
|
|
|
local startingAreaTilesSize = math.ceil( global.surfaces[surface.index].starting_area_size * REGION_TILE_SIZE )
|
|
|
|
local chunkPosX = math.floor( pos.x/CHUNK_SIZE ) * CHUNK_SIZE
|
|
local chunkPosY = math.floor( pos.y/CHUNK_SIZE ) * CHUNK_SIZE
|
|
local entityList = prepareEntityList(surface.index)
|
|
|
|
for posX = chunkPosX - startingAreaTilesSize, chunkPosX + startingAreaTilesSize, CHUNK_SIZE do
|
|
for posY = chunkPosY - startingAreaTilesSize, chunkPosY + startingAreaTilesSize, CHUNK_SIZE do
|
|
clear_chunk(surface, posX, posY, entityList)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function extendRect(leftTop, bottomRight)
|
|
leftTop.x = leftTop.x - CHUNK_SIZE / 2
|
|
leftTop.y = leftTop.y - CHUNK_SIZE / 2
|
|
bottomRight.x = bottomRight.x + CHUNK_SIZE
|
|
bottomRight.x = bottomRight.x + CHUNK_SIZE
|
|
|
|
return leftTop, bottomRight
|
|
end
|
|
|
|
local function printResourceProbability(player)
|
|
-- prints the probability of each resource - how likely it is to be spawned in percent
|
|
-- this ignores the multi resource chance
|
|
local surfaceData = global.surfaces[player.surface.index]
|
|
local maxAllotment = surfaceData.maxAllotment
|
|
player.print("Max allotment"..string.format("%.1f",maxAllotment))
|
|
debug("Max allotment"..string.format("%.1f",maxAllotment))
|
|
local sanityCheckAllotment = 0
|
|
for index,v in pairs(surfaceData.configIndexed) do
|
|
if v.type ~= "entity" then -- ignore enemies - they don't have allotment set
|
|
if v.allotment then
|
|
local resProbability = (v.allotment/maxAllotment) * 100
|
|
sanityCheckAllotment = sanityCheckAllotment + v.allotment
|
|
player.print("Resource: "..v.name.." Prob: "..string.format("%.1f",resProbability))
|
|
debug("Resource: "..v.name.." Prob: "..string.format("%.1f",resProbability))
|
|
else
|
|
player.print("Resource: "..v.name.." Allotment not set")
|
|
debug("Resource: "..v.name.." Allotment not set")
|
|
end
|
|
end
|
|
end
|
|
|
|
player.print("SanityCheck Allotment: "..string.format("%.1f", sanityCheckAllotment))
|
|
debug("SanityCheck Allotment: "..string.format("%.1f", sanityCheckAllotment))
|
|
end
|
|
|
|
local IgnoredResources =
|
|
{
|
|
["deep_oil"] = true,
|
|
["bi-ground-steam"] = true,
|
|
["bi-ground-sulfuric-acid"] = true,
|
|
["fossil-roots"] = true,
|
|
-- ["termal"] = true,
|
|
["termal2"] = true,
|
|
["tibGrowthNode"] = true,
|
|
["natural-gas-1"] = true,
|
|
["natural-gas-2"] = true,
|
|
["natural-gas-3"] = true,
|
|
["natural-gas-4"] = true
|
|
}
|
|
|
|
local function IsIgnoreResource(ResourcePrototype)
|
|
if string.find( ResourcePrototype.name, "underground-" ) ~= nil then
|
|
return true
|
|
end
|
|
if string.find( ResourcePrototype.name, "infinite-" ) ~= nil then
|
|
return true
|
|
end
|
|
-- Cargo ships mod - it takes care of spawning it by itself but leaves it's autoplace in it so needs to be ignored
|
|
if IgnoredResources[ResourcePrototype.name] then
|
|
return true
|
|
end
|
|
if ResourcePrototype.autoplace_specification == nil then
|
|
return true
|
|
end
|
|
if ResourcePrototype.autoplace_specification and ResourcePrototype.autoplace_specification.tile_restriction then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function checkForUnusedResources(player)
|
|
-- find all resources and check if we have it in our config
|
|
-- if not, tell the user that this resource won't be spawned (with RSO)
|
|
local surfaceData = global.surfaces[player.surface.index]
|
|
|
|
for prototypeName, prototype in pairs(game.entity_prototypes) do
|
|
if prototype.type == "resource" then
|
|
if not surfaceData.config[prototypeName] then
|
|
if IsIgnoreResource(prototype) then -- ignore resources which are not autoplace
|
|
debug("Resource not configured but ignored (non-autoplace): "..prototypeName)
|
|
else
|
|
player.print("The resource "..prototypeName.." is not configured in RSO. It won't be spawned!")
|
|
debug("Resource not configured: "..prototypeName)
|
|
end
|
|
else
|
|
-- these are the configured ones
|
|
if IsIgnoreResource(prototype) then
|
|
debug("Configured resource (but it is in ignore list - will be used!): " .. prototypeName)
|
|
else
|
|
debug("Configured resource: " .. prototypeName)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function printInvalidResources(player)
|
|
-- prints all invalid resources which were found when the config was processed.
|
|
for _, message in pairs(invalidResources) do
|
|
player.print(message)
|
|
end
|
|
end
|
|
|
|
local function loadAndPrepareConfig(surface)
|
|
global.surfaces[surface.index].config = loadResourceConfig()
|
|
checkConfigForInvalidResources(surface.index)
|
|
build_config_data(surface)
|
|
end
|
|
|
|
local function updateSurfaceConfig(surface, justCreated, surfaceData)
|
|
|
|
if not surfaceData then
|
|
global.surfaces[surface.index] = global.surfaces[surface.index] or {}
|
|
surfaceData = global.surfaces[surface.index]
|
|
end
|
|
|
|
surfaceData.seed = surface.map_gen_settings.seed
|
|
|
|
if not surfaceData.regions then
|
|
surfaceData.regions = {}
|
|
end
|
|
|
|
if not surfaceData.startingAreas then
|
|
surfaceData.startingAreas = {}
|
|
|
|
local startingPoints = surface.map_gen_settings.starting_points
|
|
if startingPoints and surface.index == game.surfaces['nauvis'].index then
|
|
for _, startingPoint in pairs(startingPoints) do
|
|
local startX = 0
|
|
local startY = 0
|
|
|
|
startX = startingPoint.x
|
|
startY = startingPoint.y
|
|
|
|
table.insert( surfaceData.startingAreas, { x = startX, y = startY, spawned = false } )
|
|
|
|
if not justCreated then
|
|
surfaceData.startingAreas[1].spawned = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
loadAndPrepareConfig(surface)
|
|
|
|
if config_log_enabled then
|
|
log("***** Config for surface "..surface.index)
|
|
log(serpent.block(global.surfaces[surface.index].config))
|
|
log(serpent.block(global.surfaces[surface.index].configIndexed))
|
|
end
|
|
end
|
|
|
|
local function initConfig()
|
|
if not global.surfaces then
|
|
global.surfaces = {}
|
|
|
|
local nauvisSurfaceData = global.surfaces[game.surfaces['nauvis'].index]
|
|
|
|
if not nauvisSurfaceData then
|
|
nauvisSurfaceData = {}
|
|
|
|
local justCreated = true
|
|
|
|
if global.regions then
|
|
nauvisSurfaceData.regions = global.regions
|
|
global.regions = nil
|
|
justCreated = false
|
|
else
|
|
if game.tick > 10 then
|
|
justCreated = false
|
|
end
|
|
end
|
|
|
|
global.surfaces[game.surfaces['nauvis'].index] = nauvisSurfaceData
|
|
|
|
updateSurfaceConfig(game.surfaces['nauvis'], justCreated)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function updateConfig()
|
|
|
|
initConfig()
|
|
|
|
for surfaceIndex, surfaceData in pairs(global.surfaces) do
|
|
local surface = game.surfaces[surfaceIndex]
|
|
if surface then
|
|
updateSurfaceConfig(surface, false, surfaceData)
|
|
else
|
|
game.surfaces[surfaceIndex] = nil
|
|
end
|
|
end
|
|
|
|
for _,surface in pairs(game.surfaces) do
|
|
if not global.surfaces[surface.index] then
|
|
updateSurfaceConfig(surface, false)
|
|
end
|
|
end
|
|
|
|
checkForBobEnemies()
|
|
log("RSO: Updated resource configurations")
|
|
end
|
|
|
|
local function localGenerateChunk( event )
|
|
--changes by xiaoHong - ignore surfaces interface - 11/29/2015
|
|
if global.ignoreSurfaceNames and global.ignoreSurfaceNames[event.surface.name] then
|
|
return
|
|
end
|
|
|
|
local c_x = event.area.left_top.x
|
|
local c_y = event.area.left_top.y
|
|
|
|
roll_region(event.surface, c_x, c_y)
|
|
roll_chunk(event.surface, c_x, c_y)
|
|
|
|
if useStraightWorldMod then
|
|
straightWorld(event.surface, event.area.left_top, event.area.right_bottom)
|
|
elseif game.active_mods["building-platform"] and useStraightWorldPlatforms then
|
|
straightWorldPlatforms(event.surface, event.area.left_top, event.area.right_bottom)
|
|
end
|
|
|
|
end
|
|
|
|
local function init()
|
|
|
|
updateConfig()
|
|
|
|
spawn_starting_resources(game.surfaces['nauvis'], 1 )
|
|
|
|
if not global.disableEventHandler then
|
|
script.on_event(defines.events.on_chunk_generated, localGenerateChunk)
|
|
end
|
|
end
|
|
|
|
script.on_init(init)
|
|
script.on_load(function()
|
|
if not global.disableEventHandler then
|
|
script.on_event(defines.events.on_chunk_generated, localGenerateChunk)
|
|
end
|
|
end)
|
|
|
|
script.on_configuration_changed(updateConfig)
|
|
|
|
script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
|
|
if event.setting == "rso-region-size" then
|
|
game.players[event.player_index].print("Warning: Region size changed. Dynamic size changes are not supported - recommend regenerating of resources to use new region size")
|
|
region_size = settings.global["rso-region-size"].value
|
|
REGION_TILE_SIZE = CHUNK_SIZE*region_size
|
|
elseif event.setting == "rso-enemy-chance" then
|
|
for surfaceIndex, surfaceData in pairs(global.surfaces) do
|
|
for index,v in pairs(surfaceData.configIndexed) do
|
|
if v.absolute_probability then
|
|
v.absolute_probability = settings.global["rso-enemy-chance"].value
|
|
end
|
|
end
|
|
end
|
|
game.players[event.player_index].print("Enemy spawn chance update - it will be applied to new regions.")
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
script.on_event(defines.events.on_player_created, function(event)
|
|
|
|
local player = game.players[event.player_index]
|
|
|
|
if not global.surfaces[player.surface.index] then
|
|
updateSurfaceConfig(player.surface, false)
|
|
end
|
|
|
|
checkForUnusedResources(player)
|
|
printInvalidResources(player)
|
|
|
|
if debug_enabled then
|
|
|
|
printResourceProbability(player)
|
|
|
|
if useBobEntity then
|
|
player.print("RSO: BobEnemies found")
|
|
end
|
|
|
|
if debug_items_enabled then
|
|
player.character.insert{name = "coal", count = 1000}
|
|
player.character.insert{name = "raw-wood", count = 100}
|
|
player.character.insert{name = "car", count = 1}
|
|
player.character.insert{name = "car", count = 1}
|
|
player.character.insert{name = "car", count = 1}
|
|
|
|
if game.item_prototypes["resource-monitor"] then
|
|
player.character.insert{name = "resource-monitor", count = 1}
|
|
end
|
|
end
|
|
end
|
|
|
|
l:dump()
|
|
end)
|
|
|
|
script.on_event(defines.events.on_surface_created, function(event)
|
|
|
|
local surface = game.surfaces[event.surface_index]
|
|
|
|
if global.ignoreSurfaceNames and global.ignoreSurfaceNames[surface.name] then
|
|
return
|
|
end
|
|
|
|
initConfig()
|
|
|
|
updateSurfaceConfig(surface, true)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_surface_deleted, function(event)
|
|
global.surfaces[event.surface_index] = nil
|
|
end)
|
|
|
|
script.on_event(defines.events.on_surface_cleared, function(event)
|
|
if global.surfaces[event.surface_index] then
|
|
global.surfaces[event.surface_index].regions = {}
|
|
end
|
|
end)
|
|
|
|
function regenerateCommand(parameters)
|
|
local skipEnemies = false
|
|
|
|
if parameters and parameters.parameter then
|
|
if parameters.parameter == "noenemies" then
|
|
skipEnemies = true
|
|
end
|
|
end
|
|
|
|
regenerate_everything(false, skipEnemies)
|
|
end
|
|
|
|
function clearCommand(parameters)
|
|
local skipEnemies = false
|
|
|
|
if parameters and parameters.parameter then
|
|
if parameters.parameter == "noenemies" then
|
|
skipEnemies = true
|
|
end
|
|
end
|
|
|
|
regenerate_everything(true, skipEnemies)
|
|
end
|
|
|
|
function seedCommand(parameters)
|
|
|
|
global.mapSeedOverride = nil
|
|
|
|
if parameters and parameters.parameter then
|
|
global.mapSeedOverride = tonumber(parameters.parameter)
|
|
end
|
|
end
|
|
|
|
|
|
commands.add_command("rso-regenerate", "", regenerateCommand)
|
|
commands.add_command("rso-clear", "", clearCommand)
|
|
commands.add_command("rso-override-seed", "", seedCommand)
|
|
|
|
|
|
remote.add_interface("RSO", {
|
|
-- remote.call("RSO", "regenerate", true/false, surface)
|
|
regenerate = function(new_seed, surface)
|
|
regenerateCommand(nil)
|
|
end,
|
|
|
|
clear = function()
|
|
clearCommand(nil)
|
|
end,
|
|
|
|
--changes by xiaoHong - ignore surfaces interface - 11/29/2015
|
|
-- remote.call("RSO", "ignoreSurface", "name-of-surface")
|
|
ignoreSurface = function(surfaceName)
|
|
if type(surfaceName) ~= "string" then
|
|
game.players[1].print("RSO ignoreSurface interface: surfaceName should be a string")
|
|
end
|
|
if debug_enabled then
|
|
game.players[1].print("RSO ignoring surface " .. surfaceName .. " for generation")
|
|
end
|
|
global.ignoreSurfaceNames = global.ignoreSurfaceNames or {}
|
|
global.ignoreSurfaceNames[surfaceName] = true
|
|
end,
|
|
|
|
addStartLocation = function(pos, player)
|
|
local outputPlayer = nil
|
|
|
|
if game.player then
|
|
outputPlayer = game.player
|
|
end
|
|
|
|
if player then
|
|
outputPlayer = player
|
|
end
|
|
|
|
if not ( pos and pos.x and pos.y ) then
|
|
log("Invalid parameters for new start location - please use following format: {x=0, y=0}")
|
|
return
|
|
end
|
|
|
|
local surface = nil
|
|
if outputPlayer then
|
|
surface = outputPlayer.surface
|
|
else
|
|
surface = game.surfaces['nauvis']
|
|
end
|
|
|
|
local surfaceData = global.surfaces[surface.index]
|
|
|
|
local radius = surfaceData.starting_area_size * REGION_TILE_SIZE
|
|
|
|
for idx, startingPos in pairs( surfaceData.startingAreas ) do
|
|
if distance( startingPos, pos ) < 2 * radius then
|
|
log("Creation of starting area on "..surface.name.." failed - to close to starting area at "..startingPos.x..","..startingPos.y)
|
|
return
|
|
end
|
|
end
|
|
|
|
log("Creating new starting area on "..surface.name.." at "..pos.x..","..pos.y)
|
|
|
|
clearStartingArea( surface, pos )
|
|
|
|
pos.spawned = false;
|
|
|
|
table.insert( surfaceData.startingAreas, pos )
|
|
|
|
spawn_starting_resources( surface, #surfaceData.startingAreas )
|
|
|
|
-- if outputPlayer then
|
|
-- outputPlayer.force.chart(outputPlayer.surface, {{x = pos.x - radius, y = pos.y - radius}, {x = pos.x + radius, y = pos.y + radius}})
|
|
-- end
|
|
end,
|
|
|
|
saveLog = function()
|
|
--debug(serpent.block(global.surfaces[1]))
|
|
l:dump()
|
|
end,
|
|
|
|
regenConfig = function()
|
|
for index, surfaceData in pairs(global.surfaces) do
|
|
loadAndPrepareConfig(game.surfaces[index])
|
|
end
|
|
end,
|
|
|
|
disableChunkHandler = function()
|
|
if not global.disableEventHandler then
|
|
script.on_event(defines.events.on_chunk_generated, nil)
|
|
end
|
|
global.disableEventHandler = true
|
|
end,
|
|
|
|
disableStartingArea = function()
|
|
global.disableStartingArea = true
|
|
end,
|
|
|
|
generateChunk = function(event)
|
|
localGenerateChunk(event)
|
|
end,
|
|
|
|
resetGeneration = function(surface, ignoreStartingAreas)
|
|
if surface == nil then
|
|
return
|
|
end
|
|
|
|
local surfaceData = global.surfaces[surface.index]
|
|
|
|
updateSurfaceConfig(surface, false, surfaceData)
|
|
|
|
surfaceData.regions = {}
|
|
|
|
if not ignoreStartingAreas then
|
|
for index, startingArea in pairs(surfaceData.startingAreas) do
|
|
startingArea.spawned = false
|
|
spawn_starting_resources(surface, index)
|
|
end
|
|
end
|
|
end,
|
|
|
|
runTest = function(areaSizeX, areaSizeY)
|
|
game.player.character = god
|
|
local sizeX = areaSizeX or 2000
|
|
local sizeY = areaSizeY or 2000
|
|
game.forces.player.chart(game.player.surface, {left_top = {x = -sizeX, y = -sizeY},
|
|
right_bottom = {x = sizeX, y = sizeY}})
|
|
game.speed = 2
|
|
game.player.cheat_mode = true
|
|
|
|
if game.player.force.technologies["resource-monitoring"] then
|
|
game.player.force.technologies["resource-monitoring"].researched = true
|
|
end
|
|
end,
|
|
|
|
isInStartingArea = function(surfaceIndex, tileX, tileY)
|
|
return isInStartingArea(surfaceIndex, tileX, tileY)
|
|
end,
|
|
})
|
|
|
|
--Time for the debug code. If any (not global.) globals are written to at this point, an error will be thrown.
|
|
--eg, x = 2 will throw an error because it's not global.x or local x (by Mylon to check for global variables that might cause desyncs)
|
|
--setmetatable(_G, {
|
|
-- __newindex = function(_, n, v)
|
|
-- log("Desync warning: attempt to write to undeclared var " .. n)
|
|
-- game.print("Attempt to write to undeclared var " .. n)
|
|
-- global[n] = v;
|
|
-- end,
|
|
-- __index = function(_, n)
|
|
-- return global[n];
|
|
-- end
|
|
--})
|