Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local math2d = require("__core__/lualib/math2d")
- ---@type {[string]: BitersSpawnOnResources.ModData}
- local surfaces_data = {}
- for _, data in pairs(prototypes.mod_data) do
- if data.data_type == "biters-nest-on-resources" then
- ---@type BitersSpawnOnResources.ModData
- local surf = data.data
- if surfaces_data[surf.surface] ~= nil then
- error(
- "Surface `"
- .. surf.surface
- .. " is registered more than once in `biters-nest-on-resources` mod data category"
- )
- end
- surfaces_data[surf.surface] = surf
- end
- end
- ---@generic T
- ---@param list T[]
- ---@return T?
- local function swap_remove_random(list)
- if #list == 0 then
- return nil
- end
- local idx = math.random(#list)
- local elem = list[idx]
- list[idx] = list[#list]
- table.remove(list, #list)
- return elem
- end
- --- @param surface LuaSurface
- --- @param defs BitersSpawnOnResources.ModData
- --- @param distance integer
- --- @param pos MapPosition.0
- local function try_spawn_entity_at(surface, defs, distance, pos)
- --- @type {[number]: boolean}
- local is_area_free = {}
- local spawned = false
- for _, ent in ipairs(defs.entities) do
- if
- (ent.min_distance == nil or distance > ent.min_distance)
- and (ent.max_distance == nil or distance < ent.max_distance)
- and (ent.chance > 1 or math.random() < ent.chance)
- and is_area_free[ent.clear_bb_size] ~= false
- then
- -- local ent_proto = prototypes.entity[ent.name]
- local nearby_entities = surface.find_entities_filtered({
- area = math2d.bounding_box.create_from_centre(pos, ent.clear_bb_size * 2, ent.clear_bb_size * 2),
- })
- local free = true
- for _, colliding in pairs(nearby_entities) do
- if colliding.type ~= "resource" and not defs.allow_replacements[colliding.type] then
- free = false
- is_area_free[ent.clear_bb_size] = false
- break
- end
- end
- local nearby_entities = surface.find_entities_filtered({
- area = math2d.bounding_box.create_from_centre(pos, ent.clear_bb_size * 2, ent.clear_bb_size * 2),
- })
- if free then
- for _, colliding in pairs(nearby_entities) do
- if colliding.type ~= "resource" then
- colliding.destroy()
- end
- end
- if surface.can_place_entity{
- name = ent.name,
- position = pos
- } then
- spawned = true
- surface.create_entity{
- name = ent.name,
- position = pos,
- }
- end
- end
- end
- end
- return spawned
- end
- ---@param pos MapPosition.0
- ---@param max_distance float
- ---@return Decorative
- local function create_decals(pos, max_distance)
- local angle = math.random() * math.pi * 2
- local distance = math.random() * max_distance
- return {
- name = distance < (max_distance / 3) and "enemy-decal" or "enemy-decal-transparent",
- position = {
- x = math.floor(pos.x + math.cos(angle) * distance + 0.5),
- y = math.floor(pos.y + math.sin(angle) * distance + 0.5),
- },
- amount = 1,
- }
- end
- ---@param surface LuaSurface
- ---@param area BoundingBox
- local function process_area(surface, area)
- local resources = surface.find_entities_filtered({
- area = area,
- type = "resource",
- })
- local bb_center = math2d.bounding_box.get_centre(area) --[[@as MapPosition.0]]
- if #resources == 0 then
- return
- end
- local surface_data = surfaces_data[surface.name]
- if surface_data == nil or #surface_data.entities == 0 or surface_data.density == 0 then
- return
- end
- -- find closest spawn point
- local closest_spawn_pos = surface.map_gen_settings.starting_points[1] or { x = 0, y = 0 }
- local closest_spawn_distance2 = math2d.position.distance_squared(closest_spawn_pos, bb_center)
- for _, spawn_pos in pairs(surface.map_gen_settings.starting_points) do
- local dist2 = math2d.position.distance_squared(spawn_pos, bb_center)
- if dist2 < closest_spawn_distance2 then
- closest_spawn_distance2 = dist2
- closest_spawn_pos = spawn_pos
- end
- end
- local closest_spawn_distance = math.sqrt(closest_spawn_distance2)
- local starting_area_distance = surface.map_gen_settings.starting_area * 120
- -- do not place enemies in starting area
- if closest_spawn_distance < (starting_area_distance + 16) then
- return
- end
- local positions = {}
- ---@type {[string]: number}
- local size_cache = {}
- local attempts = #resources * surface_data.density
- for _, res in pairs(resources) do
- local size = size_cache[res.name]
- if size == nil then
- local bb_xy = math2d.bounding_box.ensure_xy(res.bounding_box) --[[@as BoundingBox.0]]
- local width = bb_xy.right_bottom.x - bb_xy.left_top.x
- local height = bb_xy.right_bottom.y - bb_xy.left_top.y
- size = math.sqrt(math.abs(width) * math.abs(height))
- size_cache[res.name] = size
- end
- if size < 0.5 then
- positions[#positions + 1] = res.position
- else
- local square_size = size * 4
- local amount = square_size * square_size * surface_data.density
- attempts = attempts + amount
- local res_pos = math2d.position.ensure_xy(res.position)
- for _ = 1, amount do
- local x = res_pos.x + square_size / 2 * (math.random() * 2 - 1)
- local y = res_pos.y + square_size / 2 * (math.random() * 2 - 1)
- positions[#positions + 1] = { x = x, y = y }
- end
- end
- end
- ---@type Decorative[]
- local decals = {}
- for _ = 1, attempts do
- local pos = swap_remove_random(positions)
- if pos == nil then
- break
- end
- pos = math2d.position.ensure_xy(pos) --[[@as MapPosition.0]]
- local dist = math.sqrt((pos.x - closest_spawn_pos.x) ^ 2 + (pos.y - closest_spawn_pos.y) ^ 2)
- + surface_data.distance_fuzz * (math.random() * 2 - 1)
- if dist > starting_area_distance then
- if try_spawn_entity_at(surface, surface_data, dist, pos) then
- for i = 1, 2 do
- decals[#decals + 1] = create_decals(pos, 25)
- end
- end
- end
- end
- surface.create_decoratives({
- check_collision = true,
- decoratives = decals,
- })
- end
- script.on_event(defines.events.on_chunk_generated, function(event)
- process_area(event.surface, event.area)
- end)
Advertisement
Add Comment
Please, Sign In to add comment