local Public = {} local table_size = table.size local table_insert = table.insert local math_random = math.random local math_rad = math.rad local math_sin = math.sin local math_cos = math.cos local math_floor = math.floor local Table = require 'modules.scrap_towny_ffa.table' local Enemy = require 'modules.scrap_towny_ffa.enemy' local Building = require 'modules.scrap_towny_ffa.building' -- don't spawn if town is within this range local spawn_point_town_buffer = 256 -- clear enemies within this distance from spawn local spawn_point_safety = 16 -- incremental spawn distance from existing town -- this is how much each attempt is incremented by for checking a pollution free area local spawn_point_incremental_distance = 16 local function force_load(position, surface, radius) --log("is_chunk_generated = " .. tostring(surface.is_chunk_generated(position))) surface.request_to_generate_chunks(position, radius) --log("force load position = {" .. position.x .. "," .. position.y .. "}") surface.force_generate_chunk_requests() end -- gets an area (might not be even amount) local function get_area(position, w, h) local x1 = math_floor(w / 2) local x2 = w - x1 local y1 = math_floor(h / 2) local y2 = h - y1 return {{position.x - x1, position.y - y1}, {position.x + x2, position.y + y2}} end local function clear_spawn(position, surface, w, h) --log("clear_spawn {" .. position.x .. "," .. position.y .. "}") local area = get_area(position, w, h) for _, e in pairs(surface.find_entities_filtered({area = area})) do if e.type ~= 'character' then e.destroy() end end end -- does the position have any pollution local function has_pollution(position, surface) local result = surface.get_pollution(position) > 0.0 --log("has_pollution = " .. tostring(result)) return result end -- is the position already used local function in_use(position) local ffatable = Table.get_table() local result = false for _, v in pairs(ffatable.spawn_point) do if v == position then result = true end end --log("in_use = " .. tostring(result)) return result end -- is the position empty local function is_empty(position, surface) local chunk_position = {} chunk_position.x = math_floor(position.x / 32) chunk_position.y = math_floor(position.y / 32) if not surface.is_chunk_generated(chunk_position) then -- force load the chunk surface.request_to_generate_chunks(position, 0) surface.force_generate_chunk_requests() end local entity_radius = 3 local tile_radius = 2 local entities = surface.find_entities_filtered({position = position, radius = entity_radius}) --log("entities = " .. #entities) if #entities > 0 then return false end local tiles = surface.count_tiles_filtered({position = position, radius = tile_radius, collision_mask = 'water-tile'}) --log("water-tiles = " .. tiles) if tiles > 0 then return false end local result = surface.can_place_entity({name = 'character', position = position}) --log("is_empty = " .. tostring(result)) return result end -- finds a valid spawn point that is not near a town and not in a polluted area local function find_valid_spawn_point(force_name, surface) local ffatable = Table.get_table() -- check center of map first if valid local position = {x = 0, y = 0} --log("testing {" .. position.x .. "," .. position.y .. "}") force_load(position, surface, 1) -- is the point near any buildings if in_use(position) == false then if Building.near_another_town(force_name, position, surface, spawn_point_town_buffer) == false then -- force load the position if is_empty(position, surface) == true then --log("found valid spawn point at {" .. position.x .. "," .. position.y .. "}") return position end end end -- otherwise find a nearby town local keyset = {} for town_name, _ in pairs(ffatable.town_centers) do table_insert(keyset, town_name) end local count = table_size(keyset) if count > 0 then local town_name = keyset[math_random(1, count)] local town_center = ffatable.town_centers[town_name] if town_center ~= nil then position = town_center.market.position end --log("town center is {" .. position.x .. "," .. position.y .. "}") end -- and start checking around it for a suitable spawn position local tries = 0 local radius = spawn_point_town_buffer local angle while (tries < 100) do -- 8 attempts each position for _ = 1, 8 do -- position on the circle radius angle = math_random(0, 360) local t = math_rad(angle) local x = math_floor(position.x + math_cos(t) * radius) local y = math_floor(position.y + math_sin(t) * radius) local target = {x = x, y = y} --log("testing {" .. target.x .. "," .. target.y .. "}") force_load(position, surface, 1) if in_use(target) == false then if has_pollution(target, surface) == false then if Building.near_another_town(force_name, target, surface, spawn_point_town_buffer) == false then if is_empty(target, surface) == true then --log("found valid spawn point at {" .. target.x .. "," .. target.y .. "}") position = target return position end end end end end -- near a town, increment the radius and select another angle radius = radius + math_random(1, spawn_point_incremental_distance) tries = tries + 1 end return {x = 0, y = 0} end function Public.get_new_spawn_point(player, surface) local ffatable = Table.get_table() -- get a new spawn point local position = {0, 0} if player ~= nil then local force = player.force if force ~= nil then local force_name = force.name position = find_valid_spawn_point(force_name, surface) end end -- should never be invalid or blocked ffatable.spawn_point[player.name] = position --log("player " .. player.name .. " assigned new spawn point at {" .. position.x .. "," .. position.y .. "}") return position end -- gets a new or existing spawn point for the player function Public.get_spawn_point(player, surface) local ffatable = Table.get_table() local position = ffatable.spawn_point[player.name] -- if there is a spawn point and less than three strikes if position ~= nil and ffatable.strikes[player.name] < 3 then -- check that the spawn point is not blocked if surface.can_place_entity({name = 'character', position = position}) then --log("player " .. player.name .. "using existing spawn point at {" .. position.x .. "," .. position.y .. "}") return position else position = surface.find_non_colliding_position('character', position, 0, 0.25) return position end end -- otherwise get a new spawn point return Public.get_new_spawn_point(player, surface) end function Public.clear_spawn_point(position, surface) Enemy.clear_worms(position, surface, spawn_point_safety) -- behemoth worms can attack from a range of 48, clear first time only Enemy.clear_enemies(position, surface, spawn_point_safety) -- behemoth worms can attack from a range of 48 clear_spawn(position, surface, 7, 9) end return Public