Guest User

Biters nest on resources (but not on water)

a guest
Jul 30th, 2026
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 5.91 KB | Source Code | 0 0
  1. local math2d = require("__core__/lualib/math2d")
  2.  
  3. ---@type {[string]: BitersSpawnOnResources.ModData}
  4. local surfaces_data = {}
  5.  
  6. for _, data in pairs(prototypes.mod_data) do
  7.     if data.data_type == "biters-nest-on-resources" then
  8.         ---@type BitersSpawnOnResources.ModData
  9.         local surf = data.data
  10.         if surfaces_data[surf.surface] ~= nil then
  11.             error(
  12.                 "Surface `"
  13.                     .. surf.surface
  14.                     .. " is registered more than once in `biters-nest-on-resources` mod data category"
  15.             )
  16.         end
  17.         surfaces_data[surf.surface] = surf
  18.     end
  19. end
  20.  
  21. ---@generic T
  22. ---@param list T[]
  23. ---@return T?
  24. local function swap_remove_random(list)
  25.     if #list == 0 then
  26.         return nil
  27.     end
  28.     local idx = math.random(#list)
  29.  
  30.     local elem = list[idx]
  31.     list[idx] = list[#list]
  32.     table.remove(list, #list)
  33.     return elem
  34. end
  35.  
  36. --- @param surface LuaSurface
  37. --- @param defs BitersSpawnOnResources.ModData
  38. --- @param distance integer
  39. --- @param pos MapPosition.0
  40. local function try_spawn_entity_at(surface, defs, distance, pos)
  41.     --- @type {[number]: boolean}
  42.     local is_area_free = {}
  43.     local spawned = false
  44.  
  45.     for _, ent in ipairs(defs.entities) do
  46.         if
  47.             (ent.min_distance == nil or distance > ent.min_distance)
  48.             and (ent.max_distance == nil or distance < ent.max_distance)
  49.             and (ent.chance > 1 or math.random() < ent.chance)
  50.             and is_area_free[ent.clear_bb_size] ~= false
  51.         then
  52.             -- local ent_proto = prototypes.entity[ent.name]
  53.             local nearby_entities = surface.find_entities_filtered({
  54.                 area = math2d.bounding_box.create_from_centre(pos, ent.clear_bb_size * 2, ent.clear_bb_size * 2),
  55.             })
  56.  
  57.             local free = true
  58.             for _, colliding in pairs(nearby_entities) do
  59.                 if colliding.type ~= "resource" and not defs.allow_replacements[colliding.type] then
  60.                     free = false
  61.                     is_area_free[ent.clear_bb_size] = false
  62.                     break
  63.                 end
  64.             end
  65.            
  66.             local nearby_entities = surface.find_entities_filtered({
  67.                 area = math2d.bounding_box.create_from_centre(pos, ent.clear_bb_size * 2, ent.clear_bb_size * 2),
  68.             })
  69.  
  70.             if free then
  71.                 for _, colliding in pairs(nearby_entities) do
  72.                     if colliding.type ~= "resource" then
  73.                         colliding.destroy()
  74.                     end
  75.                 end
  76.                
  77.                 if surface.can_place_entity{
  78.                     name = ent.name,
  79.                     position = pos
  80.                 } then
  81.                     spawned = true
  82.                     surface.create_entity{
  83.                         name = ent.name,
  84.                         position = pos,
  85.                     }
  86.                 end
  87.             end
  88.         end
  89.     end
  90.    
  91.     return spawned
  92. end
  93.  
  94. ---@param pos MapPosition.0
  95. ---@param max_distance float
  96. ---@return Decorative
  97. local function create_decals(pos, max_distance)
  98.     local angle = math.random() * math.pi * 2
  99.     local distance = math.random() * max_distance
  100.     return {
  101.         name = distance < (max_distance / 3) and "enemy-decal" or "enemy-decal-transparent",
  102.         position = {
  103.             x = math.floor(pos.x + math.cos(angle) * distance + 0.5),
  104.             y = math.floor(pos.y + math.sin(angle) * distance + 0.5),
  105.         },
  106.         amount = 1,
  107.     }
  108. end
  109.  
  110. ---@param surface LuaSurface
  111. ---@param area BoundingBox
  112. local function process_area(surface, area)
  113.     local resources = surface.find_entities_filtered({
  114.         area = area,
  115.         type = "resource",
  116.     })
  117.  
  118.     local bb_center = math2d.bounding_box.get_centre(area) --[[@as MapPosition.0]]
  119.  
  120.     if #resources == 0 then
  121.         return
  122.     end
  123.  
  124.     local surface_data = surfaces_data[surface.name]
  125.     if surface_data == nil or #surface_data.entities == 0 or surface_data.density == 0 then
  126.         return
  127.     end
  128.  
  129.     -- find closest spawn point
  130.     local closest_spawn_pos = surface.map_gen_settings.starting_points[1] or { x = 0, y = 0 }
  131.     local closest_spawn_distance2 = math2d.position.distance_squared(closest_spawn_pos, bb_center)
  132.  
  133.     for _, spawn_pos in pairs(surface.map_gen_settings.starting_points) do
  134.         local dist2 = math2d.position.distance_squared(spawn_pos, bb_center)
  135.         if dist2 < closest_spawn_distance2 then
  136.             closest_spawn_distance2 = dist2
  137.             closest_spawn_pos = spawn_pos
  138.         end
  139.     end
  140.     local closest_spawn_distance = math.sqrt(closest_spawn_distance2)
  141.     local starting_area_distance = surface.map_gen_settings.starting_area * 120
  142.  
  143.     -- do not place enemies in starting area
  144.     if closest_spawn_distance < (starting_area_distance + 16) then
  145.         return
  146.     end
  147.  
  148.     local positions = {}
  149.  
  150.     ---@type {[string]: number}
  151.     local size_cache = {}
  152.     local attempts = #resources * surface_data.density
  153.  
  154.     for _, res in pairs(resources) do
  155.         local size = size_cache[res.name]
  156.         if size == nil then
  157.             local bb_xy = math2d.bounding_box.ensure_xy(res.bounding_box) --[[@as BoundingBox.0]]
  158.             local width = bb_xy.right_bottom.x - bb_xy.left_top.x
  159.             local height = bb_xy.right_bottom.y - bb_xy.left_top.y
  160.             size = math.sqrt(math.abs(width) * math.abs(height))
  161.             size_cache[res.name] = size
  162.         end
  163.  
  164.         if size < 0.5 then
  165.             positions[#positions + 1] = res.position
  166.         else
  167.             local square_size = size * 4
  168.             local amount = square_size * square_size * surface_data.density
  169.             attempts = attempts + amount
  170.             local res_pos = math2d.position.ensure_xy(res.position)
  171.             for _ = 1, amount do
  172.                 local x = res_pos.x + square_size / 2 * (math.random() * 2 - 1)
  173.                 local y = res_pos.y + square_size / 2 * (math.random() * 2 - 1)
  174.                 positions[#positions + 1] = { x = x, y = y }
  175.             end
  176.         end
  177.     end
  178.  
  179.     ---@type Decorative[]
  180.     local decals = {}
  181.  
  182.     for _ = 1, attempts do
  183.         local pos = swap_remove_random(positions)
  184.         if pos == nil then
  185.             break
  186.         end
  187.  
  188.         pos = math2d.position.ensure_xy(pos) --[[@as MapPosition.0]]
  189.  
  190.         local dist = math.sqrt((pos.x - closest_spawn_pos.x) ^ 2 + (pos.y - closest_spawn_pos.y) ^ 2)
  191.             + surface_data.distance_fuzz * (math.random() * 2 - 1)
  192.         if dist > starting_area_distance then
  193.  
  194.             if try_spawn_entity_at(surface, surface_data, dist, pos) then
  195.                 for i = 1, 2 do
  196.                     decals[#decals + 1] = create_decals(pos, 25)
  197.                 end
  198.             end
  199.         end
  200.     end
  201.  
  202.     surface.create_decoratives({
  203.         check_collision = true,
  204.         decoratives = decals,
  205.     })
  206. end
  207.  
  208. script.on_event(defines.events.on_chunk_generated, function(event)
  209.     process_area(event.surface, event.area)
  210. end)
  211.  
Tags: factorio
Advertisement
Add Comment
Please, Sign In to add comment