mirror of
https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git
synced 2025-02-07 13:07:58 +02:00
Have a workaround for demolishers that tracks them and destroys them if they are too close to the spawn. Can definitely be abused, but good enough for now. Fixed issue with joiners being teleported unexpectedly when rerolling a secondary spawn. Removed some old commented out code in utils. Tweak default danger radius to be smaller based on feedback. Fix fuild resources and sharing entities not moving when scaling spawn size. Added locale for welcome home ground text.
1578 lines
57 KiB
Lua
1578 lines
57 KiB
Lua
-- My general purpose utility functions and constants for factorio
|
|
-- Also contains some constants
|
|
|
|
require("lib/oarc_gui_utils")
|
|
require("mod-gui")
|
|
|
|
local util = require("util")
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Useful constants
|
|
--------------------------------------------------------------------------------
|
|
CHUNK_SIZE = 32
|
|
MAX_FORCES = 64
|
|
TICKS_PER_SECOND = 60
|
|
TICKS_PER_MINUTE = TICKS_PER_SECOND * 60
|
|
TICKS_PER_HOUR = TICKS_PER_MINUTE * 60
|
|
|
|
MAX_INT32_POS = 2147483647
|
|
MAX_INT32_NEG = -2147483648
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- --------------------------------------------------------------------------------
|
|
-- -- General Helper Functions
|
|
-- --------------------------------------------------------------------------------
|
|
|
|
-- -- Prints flying text.
|
|
-- -- Color is optional
|
|
-- function FlyingText(msg, pos, color, surface)
|
|
-- if color == nil then
|
|
-- surface.create_entity({ name = "flying-text", position = pos, text = msg })
|
|
-- else
|
|
-- surface.create_entity({ name = "flying-text", position = pos, text = msg, color = color })
|
|
-- end
|
|
-- end
|
|
|
|
-- Get a printable GPS string
|
|
---@param surface_name string
|
|
---@param position MapPosition
|
|
---@return string
|
|
function GetGPStext(surface_name, position)
|
|
return "[gps=" .. position.x .. "," .. position.y .. "," .. surface_name .. "]"
|
|
end
|
|
|
|
-- -- Requires having an on_tick handler.
|
|
-- function DisplaySpeechBubble(player, text, timeout_secs)
|
|
|
|
-- if (storage.oarc_speech_bubbles == nil) then
|
|
-- storage.oarc_speech_bubbles = {}
|
|
-- end
|
|
|
|
-- if (player and player.character) then
|
|
-- local sp = player.surface.create_entity{name = "compi-speech-bubble",
|
|
-- position = player.position,
|
|
-- text = text,
|
|
-- source = player.character}
|
|
-- table.insert(storage.oarc_speech_bubbles, {entity=sp,
|
|
-- timeout_tick=game.tick+(timeout_secs*TICKS_PER_SECOND)})
|
|
-- end
|
|
-- end
|
|
|
|
---Render some text on the ground. Visible to all players. Forever.
|
|
---@param surface LuaSurface
|
|
---@param position MapPosition
|
|
---@param scale number
|
|
---@param text string
|
|
---@param color Color
|
|
---@param alignment TextAlign?
|
|
---@return nil
|
|
function RenderPermanentGroundText(surface, position, scale, text, color, alignment)
|
|
rendering.draw_text { text = text,
|
|
surface = surface,
|
|
target = position,
|
|
color = color,
|
|
scale = scale,
|
|
--Allowed fonts: default-dialog-button default-game compilatron-message-font default-large default-large-semibold default-large-bold heading-1 compi
|
|
font = "compi",
|
|
alignment = alignment,
|
|
draw_on_ground = true }
|
|
end
|
|
|
|
---A standardized helper text that fades out over time
|
|
---@param text string|LocalisedString
|
|
---@param surface LuaSurface
|
|
---@param position MapPosition
|
|
---@param ttl number
|
|
---@param alignment TextAlign?
|
|
---@return nil
|
|
function TemporaryHelperText(text, surface, position, ttl, alignment)
|
|
local render_object = rendering.draw_text { text = text,
|
|
surface = surface,
|
|
target = position,
|
|
color = { 0.7, 0.7, 0.7, 0.7 },
|
|
scale = 1,
|
|
font = "compi",
|
|
time_to_live = ttl,
|
|
alignment = alignment,
|
|
draw_on_ground = false }
|
|
local rid = render_object.id
|
|
table.insert(storage.oarc_renders_fadeout, rid)
|
|
end
|
|
|
|
-- -- Every second, check a global table to see if we have any speech bubbles to kill.
|
|
-- function TimeoutSpeechBubblesOnTick()
|
|
-- if ((game.tick % (TICKS_PER_SECOND)) == 3) then
|
|
-- if (storage.oarc_speech_bubbles and (#storage.oarc_speech_bubbles > 0)) then
|
|
-- for k,sp in pairs(storage.oarc_speech_bubbles) do
|
|
-- if (game.tick > sp.timeout_tick) then
|
|
-- if (sp.entity ~= nil) and (sp.entity.valid) then
|
|
-- sp.entity.start_fading_out()
|
|
-- end
|
|
-- table.remove(storage.oarc_speech_bubbles, k)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
---Every tick, check a global table to see if we have any rendered thing that needs fading out.
|
|
---@return nil
|
|
function FadeoutRenderOnTick()
|
|
if (storage.oarc_renders_fadeout and (#storage.oarc_renders_fadeout > 0)) then
|
|
for k, rid in pairs(storage.oarc_renders_fadeout) do
|
|
local render_object = rendering.get_object_by_id(rid)
|
|
if (render_object and render_object.valid) then
|
|
local ttl = render_object.time_to_live
|
|
if ((ttl > 0) and (ttl < 200)) then
|
|
local color = render_object.color
|
|
if (color.a > 0.005) then
|
|
render_object.color = { r = color.r, g = color.g, b = color.b, a = color.a - 0.005 }
|
|
end
|
|
end
|
|
else
|
|
storage.oarc_renders_fadeout[k] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Broadcast messages to all connected players
|
|
---@param msg LocalisedString
|
|
---@return nil
|
|
function SendBroadcastMsg(msg)
|
|
for name, player in pairs(game.connected_players) do
|
|
player.print(msg)
|
|
end
|
|
end
|
|
|
|
---Send a message to a player, safely checks if they exist and are online.
|
|
---@param playerName string
|
|
---@param msg LocalisedString
|
|
---@return nil
|
|
function SendMsg(playerName, msg)
|
|
if ((game.players[playerName] ~= nil) and (game.players[playerName].connected)) then
|
|
game.players[playerName].print(msg)
|
|
end
|
|
end
|
|
|
|
---Checks if a string starts with another string
|
|
---@param string string The string to check
|
|
---@param start string The starting string to look for
|
|
function StringStartsWith(string, start)
|
|
return string:sub(1, #start) == start
|
|
end
|
|
|
|
---Checks if a surface is blacklisted based on the storage.ocfg settings
|
|
---@param surface_name string
|
|
---@return boolean --true if blacklisted
|
|
function IsSurfaceBlacklisted(surface_name)
|
|
if (storage.ocfg.surfaces_blacklist ~= nil) then
|
|
for _,name in pairs(storage.ocfg.surfaces_blacklist) do
|
|
if (name == surface_name) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
if (storage.ocfg.surfaces_blacklist_match ~= nil) then
|
|
for _,match in pairs(storage.ocfg.surfaces_blacklist_match) do
|
|
if (StringStartsWith(surface_name, match)) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- -- Simple way to write to a file. Always appends. Only server.
|
|
-- -- Has a global setting for enable/disable
|
|
-- function ServerWriteFile(filename, msg)
|
|
-- if (storage.ocfg.enable_server_write_files) then
|
|
-- game.write_file(filename, msg, true, 0)
|
|
-- end
|
|
-- end
|
|
|
|
---Useful for displaying game time in mins:secs format
|
|
---@param ticks number
|
|
---@return string
|
|
function FormatTime(ticks)
|
|
local seconds = ticks / 60
|
|
local minutes = math.floor((seconds)/60)
|
|
local seconds = math.floor(seconds - 60*minutes)
|
|
return string.format("%dm:%02ds", minutes, seconds)
|
|
end
|
|
|
|
---Useful for displaying game time in hrs:mins format
|
|
---@param ticks number
|
|
---@return string
|
|
function FormatTimeHoursSecs(ticks)
|
|
local seconds = ticks / 60
|
|
local total_minutes = math.floor((seconds)/60)
|
|
local hours = math.floor((total_minutes)/60)
|
|
local minutes = math.floor(total_minutes - 60*hours)
|
|
return string.format("%dh:%02dm", hours, minutes)
|
|
end
|
|
|
|
|
|
-- -- Simple math clamp
|
|
-- function clamp(val, min, max)
|
|
-- if (val > max) then
|
|
-- return max
|
|
-- elseif (val < min) then
|
|
-- return min
|
|
-- end
|
|
-- return val
|
|
-- end
|
|
-- function clampInt32(val)
|
|
-- return clamp(val, MAX_INT32_NEG, MAX_INT32_POS)
|
|
-- end
|
|
|
|
-- function MathRound(num)
|
|
-- return math.floor(num+0.5)
|
|
-- end
|
|
|
|
-- Fisher-Yares shuffle
|
|
-- https://stackoverflow.com/questions/35572435/how-do-you-do-the-fisher-yates-shuffle-in-lua
|
|
---@param T table
|
|
---@return table
|
|
function FYShuffle(T)
|
|
local tReturn = {}
|
|
for i = #T, 1, -1 do
|
|
local j = math.random(i)
|
|
T[i], T[j] = T[j], T[i]
|
|
table.insert(tReturn, T[i])
|
|
end
|
|
return tReturn
|
|
end
|
|
|
|
---Check if a table contains a value
|
|
---@param table table
|
|
---@param val any
|
|
---@return boolean
|
|
function TableContains(table, val)
|
|
for _, value in pairs(table) do
|
|
if value == val then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
---Get a key from a table given a value (if it exists)
|
|
---@param table table
|
|
---@param val any
|
|
---@return any
|
|
function GetTableKey(table, val)
|
|
for k, v in pairs(table) do
|
|
if v == val then
|
|
return k
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- ---Remove a value from a table
|
|
-- ---@param table table
|
|
-- ---@param val any
|
|
-- ---@return nil
|
|
-- function TableRemove(t, val)
|
|
-- for i = #t, 1, -1 do
|
|
-- if t[i] == val then
|
|
-- table.remove(t, i)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
function TableRemoveOneUsingPairs(t, val)
|
|
for k,v in pairs(t) do
|
|
if v == val then
|
|
table.remove(t, k)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
---Gets a random point within a circle of a given radius and center point.
|
|
---@param radius number
|
|
---@param center MapPosition
|
|
---@return MapPosition
|
|
function GetRandomPointWithinCircle(radius, center)
|
|
local angle = math.random() * 2 * math.pi
|
|
local distance = math.random() * radius
|
|
local x = center.x + distance * math.cos(angle)
|
|
local y = center.y + distance * math.sin(angle)
|
|
return {x=x, y=y}
|
|
end
|
|
|
|
-- -- Get a random KEY from a table.
|
|
-- function GetRandomKeyFromTable(t)
|
|
-- local keyset = {}
|
|
-- for k,v in pairs(t) do
|
|
-- table.insert(keyset, k)
|
|
-- end
|
|
-- return keyset[math.random(#keyset)]
|
|
-- end
|
|
|
|
-- -- A safer way to attempt to get the next key in a table. CHECK TABLE SIZE BEFORE CALLING THIS!
|
|
-- -- Ensures the key points to a valid entry before calling next. Otherwise it restarts.
|
|
-- -- If you get nil as a return, it means you hit the return.
|
|
-- function NextButChecksKeyIsValidFirst(table_in, key)
|
|
-- -- if (table_size(table_in) == 0) then you're fucked end
|
|
-- if ((not key) or (not table_in[key])) then
|
|
-- return next(table_in, nil)
|
|
-- else
|
|
-- return next(table_in, key)
|
|
-- end
|
|
-- end
|
|
|
|
-- -- Gets the next key, even if we have to start again.
|
|
-- function NextKeyInTableIncludingRestart(table_in, key)
|
|
-- local next_key = NextButChecksKeyIsValidFirst(table_in, key)
|
|
-- if (not next_key) then
|
|
-- return NextButChecksKeyIsValidFirst(table_in, next_key)
|
|
-- else
|
|
-- return next_key
|
|
-- end
|
|
-- end
|
|
|
|
-- function GetRandomValueFromTable(t)
|
|
-- return t[GetRandomKeyFromTable(t)]
|
|
-- end
|
|
|
|
-- -- Given a table of positions, returns key for closest to given pos.
|
|
-- function GetClosestPosFromTable(pos, pos_table)
|
|
|
|
-- local closest_dist = nil
|
|
-- local closest_key = nil
|
|
|
|
-- for k,p in pairs(pos_table) do
|
|
-- local new_dist = util.distance(pos, p)
|
|
-- if (closest_dist == nil) then
|
|
-- closest_dist = new_dist
|
|
-- closest_key = k
|
|
-- elseif (closest_dist > new_dist) then
|
|
-- closest_dist = new_dist
|
|
-- closest_key = k
|
|
-- end
|
|
-- end
|
|
|
|
-- if (closest_key == nil) then
|
|
-- log("GetClosestPosFromTable ERROR - None found?")
|
|
-- return nil
|
|
-- end
|
|
|
|
-- return pos_table[closest_key]
|
|
-- end
|
|
|
|
-- Chart area for a force
|
|
---@param force string|integer|LuaForce
|
|
---@param position MapPosition
|
|
---@param chunkDist number
|
|
---@param surface LuaSurface|string|integer
|
|
function ChartArea(force, position, chunkDist, surface)
|
|
force.chart(surface,
|
|
{ { position.x - (CHUNK_SIZE * chunkDist),
|
|
position.y - (CHUNK_SIZE * chunkDist) },
|
|
{ position.x + (CHUNK_SIZE * chunkDist),
|
|
position.y + (CHUNK_SIZE * chunkDist) } })
|
|
end
|
|
|
|
--- Better than util.insert_safe because we also check for 0 count items.
|
|
---@param entity LuaEntity|LuaPlayer
|
|
---@param item_dict table
|
|
---@return nil
|
|
function OarcsSaferInsert(entity, item_dict)
|
|
if not (entity and entity.valid and item_dict) then return end
|
|
local items = prototypes.item
|
|
local insert = entity.insert
|
|
for name, count in pairs(item_dict) do
|
|
if items[name] and count > 0 then
|
|
insert { name = name, count = count }
|
|
else
|
|
log("Item to insert not valid: " .. name)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Better than util.remove_safe because we also check for 0 count items.
|
|
---@param entity LuaEntity|LuaPlayer
|
|
---@param item_dict table
|
|
---@return nil
|
|
function OarcsSaferRemove(entity, item_dict)
|
|
if not (entity and entity.valid and item_dict) then return end
|
|
local items = prototypes.item
|
|
local remove = entity.remove_item
|
|
for name, count in pairs(item_dict) do
|
|
if items[name] and count > 0 then
|
|
remove { name = name, count = count }
|
|
else
|
|
log("Item to remove not valid: " .. name)
|
|
end
|
|
end
|
|
end
|
|
|
|
---Gives the player the respawn items if there are any
|
|
---@param player LuaPlayer
|
|
---@return nil
|
|
function GivePlayerRespawnItems(player)
|
|
local surface_name = player.character.surface.name
|
|
if (storage.ocfg.surfaces_config[surface_name] == nil) then
|
|
error("GivePlayerRespawnItems - Missing surface config! " .. surface_name)
|
|
return
|
|
end
|
|
|
|
local respawnItems = storage.ocfg.surfaces_config[surface_name].starting_items.player_respawn_items
|
|
|
|
OarcsSaferInsert(player, respawnItems)
|
|
end
|
|
|
|
---Gives the player the starter items if there are any
|
|
---@param player LuaPlayer
|
|
---@return nil
|
|
function GivePlayerStarterItems(player)
|
|
local surface_name = player.character.surface.name
|
|
if (storage.ocfg.surfaces_config[surface_name] == nil) then
|
|
error("GivePlayerStarterItems - Missing surface config! " .. surface_name)
|
|
return
|
|
end
|
|
|
|
local startItems = storage.ocfg.surfaces_config[surface_name].starting_items.player_start_items
|
|
|
|
OarcsSaferInsert(player, startItems)
|
|
end
|
|
|
|
---Half-heartedly attempts to remove starter items from the player. Probably more trouble than it's worth.
|
|
---@param player LuaPlayer
|
|
---@return nil
|
|
function RemovePlayerStarterItems(player)
|
|
if player == nil or player.character == nil then return end
|
|
local surface_name = player.character.surface.name
|
|
if (storage.ocfg.surfaces_config[surface_name]) ~= nil then
|
|
local startItems = storage.ocfg.surfaces_config[surface_name].starting_items.player_start_items
|
|
OarcsSaferRemove(player, startItems)
|
|
end
|
|
end
|
|
|
|
--- Delete all chunks on a surface
|
|
--- @param surface LuaSurface
|
|
--- @return nil
|
|
function DeleteAllChunks(surface)
|
|
for chunk in surface.get_chunks() do
|
|
surface.delete_chunk({chunk.x, chunk.y})
|
|
end
|
|
end
|
|
|
|
|
|
---Get position for buddy spawn (for buddy placement)
|
|
---@param position MapPosition
|
|
---@param surface_name string
|
|
---@param moat_enabled boolean
|
|
---@return MapPosition
|
|
function GetBuddySpawnPosition(position, surface_name, moat_enabled)
|
|
|
|
local spawn_config = storage.ocfg.surfaces_config[surface_name].spawn_config
|
|
|
|
local x_offset = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier * 2
|
|
x_offset = x_offset + storage.ocfg.spawn_general.moat_width_tiles
|
|
-- distance = distance + 5 -- EXTRA BUFFER?
|
|
|
|
-- Create that spawn in the global vars
|
|
local buddy_position = table.deepcopy(position)
|
|
-- The x_offset must be big enough to ensure the spawns DO NOT overlap!
|
|
buddy_position.x = buddy_position.x + x_offset
|
|
|
|
return buddy_position
|
|
end
|
|
|
|
-- -- Modular armor quick start
|
|
-- function GiveQuickStartModularArmor(player)
|
|
-- player.insert{name="modular-armor", count = 1}
|
|
|
|
-- if player and player.get_inventory(defines.inventory.character_armor) ~= nil and player.get_inventory(defines.inventory.character_armor)[1] ~= nil then
|
|
-- local p_armor = player.get_inventory(defines.inventory.character_armor)[1].grid
|
|
-- if p_armor ~= nil then
|
|
-- p_armor.put({name = "personal-roboport-equipment"})
|
|
-- p_armor.put({name = "battery-mk2-equipment"})
|
|
-- p_armor.put({name = "personal-roboport-equipment"})
|
|
-- for i=1,15 do
|
|
-- p_armor.put({name = "solar-panel-equipment"})
|
|
-- end
|
|
-- end
|
|
-- player.insert{name="construction-robot", count = 40}
|
|
-- end
|
|
-- end
|
|
|
|
-- -- Cheater's quick start
|
|
-- function GiveQuickStartPowerArmor(player)
|
|
-- player.insert{name="power-armor", count = 1}
|
|
|
|
-- if player and player.get_inventory(defines.inventory.character_armor) ~= nil and player.get_inventory(defines.inventory.character_armor)[1] ~= nil then
|
|
-- local p_armor = player.get_inventory(defines.inventory.character_armor)[1].grid
|
|
-- if p_armor ~= nil then
|
|
-- p_armor.put({name = "fusion-reactor-equipment"})
|
|
-- p_armor.put({name = "exoskeleton-equipment"})
|
|
-- p_armor.put({name = "battery-mk2-equipment"})
|
|
-- p_armor.put({name = "battery-mk2-equipment"})
|
|
-- p_armor.put({name = "personal-roboport-mk2-equipment"})
|
|
-- p_armor.put({name = "personal-roboport-mk2-equipment"})
|
|
-- p_armor.put({name = "personal-roboport-mk2-equipment"})
|
|
-- p_armor.put({name = "battery-mk2-equipment"})
|
|
-- for i=1,7 do
|
|
-- p_armor.put({name = "solar-panel-equipment"})
|
|
-- end
|
|
-- end
|
|
-- player.insert{name="construction-robot", count = 100}
|
|
-- player.insert{name="belt-immunity-equipment", count = 1}
|
|
-- end
|
|
-- end
|
|
|
|
-- TEST_KIT = {
|
|
-- {name="infinity-chest", count = 50},
|
|
-- {name="infinity-pipe", count = 50},
|
|
-- {name="electric-energy-interface", count = 50},
|
|
-- {name="express-loader", count = 50},
|
|
-- {name="express-transport-belt", count = 50},
|
|
-- }
|
|
|
|
-- function GiveTestKit(player)
|
|
-- for _,item in pairs(TEST_KIT) do
|
|
-- player.insert(item)
|
|
-- end
|
|
-- end
|
|
|
|
-- Safer teleport
|
|
---@param player LuaPlayer
|
|
---@param surface LuaSurface
|
|
---@param target_pos MapPosition
|
|
function SafeTeleport(player, surface, target_pos)
|
|
local safe_pos = surface.find_non_colliding_position("character", target_pos, CHUNK_SIZE, 1)
|
|
if (not safe_pos) then
|
|
player.teleport(target_pos, surface, true)
|
|
else
|
|
player.teleport(safe_pos, surface, true)
|
|
end
|
|
end
|
|
|
|
|
|
---Check if given position is in area bounding box
|
|
---@param point MapPosition
|
|
---@param area BoundingBox
|
|
---@return boolean
|
|
function CheckIfInArea(point, area)
|
|
if ((point.x >= area.left_top.x) and (point.x < area.right_bottom.x)) then
|
|
if ((point.y >= area.left_top.y) and (point.y < area.right_bottom.y)) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
---Configures the friend and cease fire relationships between all player forces.
|
|
---@param cease_fire boolean
|
|
---@param friends boolean
|
|
---@return nil
|
|
function ConfigurePlayerForceRelationships(cease_fire, friends)
|
|
local player_forces = {}
|
|
|
|
for name, force in pairs(game.forces) do
|
|
if name ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES_INCL_NEUTRAL, name) then
|
|
table.insert(player_forces, force)
|
|
end
|
|
end
|
|
|
|
for _, force1 in pairs(player_forces) do
|
|
for _, force2 in pairs(player_forces) do
|
|
if force1.name ~= force2.name then
|
|
force1.set_cease_fire(force2, cease_fire)
|
|
force1.set_friend(force2, friends)
|
|
|
|
force2.set_cease_fire(force1, cease_fire)
|
|
force2.set_friend(force1, friends)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ---Set all forces to ceasefire
|
|
-- ---@return nil
|
|
-- function SetCeaseFireBetweenAllPlayerForces()
|
|
-- for name, team in pairs(game.forces) do
|
|
-- if name ~= "neutral" and name ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES, name) then
|
|
-- for x, _ in pairs(game.forces) do
|
|
-- if x ~= "neutral" and x ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES, x) then
|
|
-- team.set_cease_fire(x, true)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
-- ---Set all forces to friendly
|
|
-- ---@return nil
|
|
-- function SetFriendlyBetweenAllPlayerForces()
|
|
-- for name, team in pairs(game.forces) do
|
|
-- if name ~= "neutral" and name ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES, name) then
|
|
-- for x, _ in pairs(game.forces) do
|
|
-- if x ~= "neutral" and x ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES, x) then
|
|
-- team.set_friend(x, true)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
|
|
---For each other player force, share a chat msg.
|
|
---@param player LuaPlayer
|
|
---@param msg LocalisedString
|
|
---@return nil
|
|
function ShareChatBetweenForces(player, msg)
|
|
for _,force in pairs(game.forces) do
|
|
if (force ~= nil) then
|
|
if ((force.name ~= "enemy") and
|
|
(force.name ~= "neutral") and
|
|
(force.name ~= "player") and
|
|
(force ~= player.force)) then
|
|
force.print(player.name..": "..msg)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- -- Merges force2 INTO force1 but keeps all research between both forces.
|
|
-- function MergeForcesKeepResearch(force1, force2)
|
|
-- for techName,luaTech in pairs(force2.technologies) do
|
|
-- if (luaTech.researched) then
|
|
-- force1.technologies[techName].researched = true
|
|
-- force1.technologies[techName].level = luaTech.level
|
|
-- end
|
|
-- end
|
|
-- game.merge_forces(force2, force1)
|
|
-- end
|
|
|
|
-- -- Undecorator
|
|
-- function RemoveDecorationsArea(surface, area)
|
|
-- surface.destroy_decoratives{area=area}
|
|
-- end
|
|
|
|
-- -- Remove fish
|
|
-- function RemoveFish(surface, area)
|
|
-- for _, entity in pairs(surface.find_entities_filtered{area = area, type="fish"}) do
|
|
-- entity.destroy()
|
|
-- end
|
|
-- end
|
|
|
|
-- -- Render a path
|
|
-- function RenderPath(path, ttl, players)
|
|
-- local last_pos = path[1].position
|
|
-- local color = {r = 1, g = 0, b = 0, a = 0.5}
|
|
|
|
-- for i,v in pairs(path) do
|
|
-- if (i ~= 1) then
|
|
|
|
-- color={r = 1/(1+(i%3)), g = 1/(1+(i%5)), b = 1/(1+(i%7)), a = 0.5}
|
|
-- rendering.draw_line{color=color,
|
|
-- width=2,
|
|
-- from=v.position,
|
|
-- to=last_pos,
|
|
-- surface=game.surfaces[GAME_SURFACE_NAME],
|
|
-- players=players,
|
|
-- time_to_live=ttl}
|
|
-- end
|
|
-- last_pos = v.position
|
|
-- end
|
|
-- end
|
|
|
|
---Get a random 1 or -1
|
|
---@return number
|
|
function RandomNegPos()
|
|
if (math.random(0,1) == 1) then
|
|
return 1
|
|
else
|
|
return -1
|
|
end
|
|
end
|
|
|
|
---Create a random direction vector to look in, returns normalized vector
|
|
---@return MapPosition
|
|
function GetRandomVector()
|
|
local randVec = {x=0,y=0}
|
|
while ((randVec.x == 0) and (randVec.y == 0)) do
|
|
randVec.x = math.random() * 2 - 1
|
|
randVec.y = math.random() * 2 - 1
|
|
end
|
|
-- Normalize the vector
|
|
local magnitude = math.sqrt((randVec.x^2) + (randVec.y^2))
|
|
randVec.x = randVec.x / magnitude
|
|
randVec.y = randVec.y / magnitude
|
|
return randVec
|
|
end
|
|
|
|
---Check for ungenerated chunks around a specific chunk +/- chunkDist in x and y directions
|
|
---@param chunkPos MapPosition
|
|
---@param chunkDist integer
|
|
---@param surface LuaSurface
|
|
---@return boolean
|
|
function IsChunkAreaUngenerated(chunkPos, chunkDist, surface)
|
|
for x=-chunkDist, chunkDist do
|
|
for y=-chunkDist, chunkDist do
|
|
local checkPos = {x=chunkPos.x+x,
|
|
y=chunkPos.y+y}
|
|
if (surface.is_chunk_generated(checkPos)) then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Clear out enemies around an area with a certain distance
|
|
---@param pos MapPosition
|
|
---@param safeDist number
|
|
---@param surface LuaSurface
|
|
function ClearNearbyEnemies(pos, safeDist, surface)
|
|
local safeArea = {
|
|
left_top =
|
|
{
|
|
x = pos.x - safeDist,
|
|
y = pos.y - safeDist
|
|
},
|
|
right_bottom =
|
|
{
|
|
x = pos.x + safeDist,
|
|
y = pos.y + safeDist
|
|
}
|
|
}
|
|
|
|
for _, entity in pairs(surface.find_entities_filtered { area = safeArea, force = "enemy" }) do
|
|
entity.destroy()
|
|
end
|
|
end
|
|
|
|
-- ---Function to find coordinates of ungenerated map area in a given direction starting from the center of the map
|
|
-- ---@param direction_vector MapPosition
|
|
-- ---@param surface LuaSurface
|
|
-- ---@return MapPosition
|
|
-- function FindMapEdge(direction_vector, surface)
|
|
-- local position = {x=0,y=0}
|
|
-- local chunk_position = {x=0,y=0}
|
|
|
|
-- -- Keep checking chunks in the direction of the vector
|
|
-- while(true) do
|
|
|
|
-- -- Set some absolute limits.
|
|
-- if ((math.abs(chunk_position.x) > 1000) or (math.abs(chunk_position.y) > 1000)) then
|
|
-- break
|
|
|
|
-- -- If chunk is already generated, keep looking
|
|
-- elseif (surface.is_chunk_generated(chunk_position)) then
|
|
-- chunk_position.x = chunk_position.x + direction_vector.x
|
|
-- chunk_position.y = chunk_position.y + direction_vector.y
|
|
|
|
-- -- Found a possible ungenerated area
|
|
-- else
|
|
|
|
-- chunk_position.x = chunk_position.x + direction_vector.x
|
|
-- chunk_position.y = chunk_position.y + direction_vector.y
|
|
|
|
-- -- Check there are no generated chunks in a 10x10 area.
|
|
-- if IsChunkAreaUngenerated(chunk_position, 10, surface) then
|
|
-- position.x = (chunk_position.x*CHUNK_SIZE) + (CHUNK_SIZE/2)
|
|
-- position.y = (chunk_position.y*CHUNK_SIZE) + (CHUNK_SIZE/2)
|
|
-- break
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
-- -- log("spawn: x=" .. position.x .. ", y=" .. position.y)
|
|
-- return position
|
|
-- end
|
|
|
|
|
|
---Pick a random direction, go at least the minimum distance, and start looking for ungenerated chunks
|
|
---We try a few times (hardcoded) and then try a different random direction if we fail (up to max_tries)
|
|
---@param surface_name string Surface name because we might need to force the creation of a new surface
|
|
---@param minimum_distance_chunks number Distance in chunks to start looking for ungenerated chunks
|
|
---@param max_tries integer Maximum number of tries to find a spawn point
|
|
---@return MapPosition
|
|
function FindUngeneratedCoordinates(surface_name, minimum_distance_chunks, max_tries)
|
|
|
|
local final_position = {x=0,y=0}
|
|
|
|
-- If surface is nil, it is probably a planet? Check and create if needed.
|
|
local surface = game.surfaces[surface_name]
|
|
if (surface == nil) then
|
|
if (game.planets[surface_name] == nil) then
|
|
error("ERROR! No surface or planet found for requested player spawn!")
|
|
return final_position
|
|
end
|
|
surface = game.planets[surface_name].create_surface()
|
|
if (surface == nil) then
|
|
error("ERROR! Failed to create planet surface for player spawn!")
|
|
return final_position
|
|
end
|
|
end
|
|
|
|
--- Get a random vector, figure out how many times to multiply it to get the minimum distance
|
|
local direction_vector = GetRandomVector()
|
|
local start_distance_tiles = minimum_distance_chunks * CHUNK_SIZE
|
|
|
|
local tries_remaining = max_tries - 1
|
|
|
|
-- Starting search position
|
|
local search_pos = {
|
|
x=direction_vector.x * start_distance_tiles,
|
|
y=direction_vector.y * start_distance_tiles
|
|
}
|
|
|
|
-- We check up to THIS many times, each jump moves out by minimum_distance_to_existing_chunks
|
|
local jumps_count = 3
|
|
|
|
local minimum_distance_to_existing_chunks = storage.ocfg.gameplay.minimum_distance_to_existing_chunks
|
|
|
|
-- Keep checking chunks in the direction of the vector, assumes this terminates...
|
|
while(true) do
|
|
|
|
local chunk_position = GetChunkPosFromTilePos(search_pos)
|
|
|
|
if (jumps_count <= 0) then
|
|
|
|
if (tries_remaining > 0) then
|
|
return FindUngeneratedCoordinates(surface_name, minimum_distance_chunks, tries_remaining)
|
|
else
|
|
log("WARNING - FindUngeneratedCoordinates - Hit max distance!")
|
|
break
|
|
end
|
|
|
|
-- If chunk is already generated, keep looking further out
|
|
elseif (surface.is_chunk_generated(chunk_position)) then
|
|
|
|
-- For debugging, ping the map
|
|
-- SendBroadcastMsg("GENERATED: " .. GetGPStext(surface.name, {x=chunk_position.x*32, y=chunk_position.y*32}))
|
|
|
|
-- Move out a bit more to give some space and then check the surrounding area
|
|
search_pos.x = search_pos.x + (direction_vector.x * CHUNK_SIZE * minimum_distance_to_existing_chunks)
|
|
search_pos.y = search_pos.y + (direction_vector.y * CHUNK_SIZE * minimum_distance_to_existing_chunks)
|
|
|
|
-- Found a possible ungenerated area
|
|
elseif IsChunkAreaUngenerated(chunk_position, minimum_distance_to_existing_chunks, surface) then
|
|
|
|
-- For debugging, ping the map
|
|
-- SendBroadcastMsg("SUCCESS: " .. GetGPStext(surface.name, {x=chunk_position.x*32, y=chunk_position.y*32}))
|
|
|
|
-- Place the spawn in the center of a chunk
|
|
final_position.x = (chunk_position.x * CHUNK_SIZE) + (CHUNK_SIZE/2)
|
|
final_position.y = (chunk_position.y * CHUNK_SIZE) + (CHUNK_SIZE/2)
|
|
break
|
|
|
|
-- The area around the chunk is not clear, keep looking
|
|
else
|
|
|
|
-- For debugging, ping the map
|
|
-- SendBroadcastMsg("NOT CLEAR: " .. GetGPStext(surface.name, {x=chunk_position.x*32, y=chunk_position.y*32}))
|
|
|
|
-- Move out a bit more to give some space and then check the surrounding area
|
|
search_pos.x = search_pos.x + (direction_vector.x * CHUNK_SIZE * minimum_distance_to_existing_chunks)
|
|
search_pos.y = search_pos.y + (direction_vector.y * CHUNK_SIZE * minimum_distance_to_existing_chunks)
|
|
end
|
|
|
|
jumps_count = jumps_count - 1
|
|
end
|
|
|
|
if (final_position.x == 0 and final_position.y == 0) then
|
|
log("WARNING! FindUngeneratedCoordinates - Failed to find a spawn point!")
|
|
end
|
|
|
|
return final_position
|
|
end
|
|
|
|
-- -- General purpose function for removing a particular recipe
|
|
-- function RemoveRecipe(force, recipeName)
|
|
-- local recipes = force.recipes
|
|
-- if recipes[recipeName] then
|
|
-- recipes[recipeName].enabled = false
|
|
-- end
|
|
-- end
|
|
|
|
-- -- General purpose function for adding a particular recipe
|
|
-- function AddRecipe(force, recipeName)
|
|
-- local recipes = force.recipes
|
|
-- if recipes[recipeName] then
|
|
-- recipes[recipeName].enabled = true
|
|
-- end
|
|
-- end
|
|
|
|
-- -- General command for disabling a tech.
|
|
-- function DisableTech(force, techName)
|
|
-- if force.technologies[techName] then
|
|
-- force.technologies[techName].enabled = false
|
|
-- force.technologies[techName].visible_when_disabled = true
|
|
-- end
|
|
-- end
|
|
|
|
-- -- General command for enabling a tech.
|
|
-- function EnableTech(force, techName)
|
|
-- if force.technologies[techName] then
|
|
-- force.technologies[techName].enabled = true
|
|
-- end
|
|
-- end
|
|
|
|
|
|
---Get a square area given a position and distance. Square length = 2x distance
|
|
---@param pos MapPosition
|
|
---@param dist number
|
|
---@return BoundingBox
|
|
function GetAreaAroundPos(pos, dist)
|
|
return {
|
|
left_top =
|
|
{
|
|
x = pos.x - dist,
|
|
y = pos.y - dist
|
|
},
|
|
right_bottom =
|
|
{
|
|
x = pos.x + dist,
|
|
y = pos.y + dist
|
|
}
|
|
}
|
|
end
|
|
|
|
---Gets chunk position of a tile.
|
|
---@param tile_pos TilePosition
|
|
---@return ChunkPosition
|
|
function GetChunkPosFromTilePos(tile_pos)
|
|
return {x=math.floor(tile_pos.x/32), y=math.floor(tile_pos.y/32)}
|
|
end
|
|
|
|
-- function GetCenterTilePosFromChunkPos(c_pos)
|
|
-- return {x=c_pos.x*32 + 16, y=c_pos.y*32 + 16}
|
|
-- end
|
|
|
|
-- -- Get the left_top
|
|
-- function GetChunkTopLeft(pos)
|
|
-- return {x=pos.x-(pos.x % 32), y=pos.y-(pos.y % 32)}
|
|
-- end
|
|
|
|
-- -- Get area given chunk
|
|
-- function GetAreaFromChunkPos(chunk_pos)
|
|
-- return {left_top={x=chunk_pos.x*32, y=chunk_pos.y*32},
|
|
-- right_bottom={x=chunk_pos.x*32+31, y=chunk_pos.y*32+31}}
|
|
-- end
|
|
|
|
-- Removes the entity type from the area given
|
|
-- function RemoveInArea(surface, area, type)
|
|
-- for key, entity in pairs(surface.find_entities_filtered{area=area, type= type}) do
|
|
-- if entity.valid and entity and entity.position then
|
|
-- entity.destroy()
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
---Removes the entity type from the area given. Only if it is within given distance from given position.
|
|
---@param surface LuaSurface
|
|
---@param area BoundingBox
|
|
---@param type string|string[]
|
|
---@param pos MapPosition
|
|
---@param dist number
|
|
---@return nil
|
|
function RemoveInCircle(surface, area, type, pos, dist)
|
|
for _, entity in pairs(surface.find_entities_filtered { area = area, type = type }) do
|
|
if entity.valid and entity and entity.position then
|
|
if ((pos.x - entity.position.x) ^ 2 + (pos.y - entity.position.y) ^ 2 < dist ^ 2) then
|
|
entity.destroy()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---Removes the entity type from the area given. Only if it is within given distance from given position.
|
|
---@param surface LuaSurface
|
|
---@param area BoundingBox
|
|
---@param type string|string[]
|
|
---@param pos MapPosition
|
|
---@param dist number
|
|
---@return nil
|
|
function RemoveInSquare(surface, area, type, pos, dist)
|
|
for _, entity in pairs(surface.find_entities_filtered { area = area, type = type }) do
|
|
if entity.valid and entity and entity.position then
|
|
local max_distance = math.max(math.abs(pos.x - entity.position.x), math.abs(pos.y - entity.position.y))
|
|
if (max_distance < dist) then
|
|
entity.destroy()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- -- For easy local testing of map gen settings. Just set what you want and uncomment usage in CreateGameSurface!
|
|
-- function SurfaceSettingsHelper(settings)
|
|
|
|
-- settings.terrain_segmentation = 4
|
|
-- settings.water = 3
|
|
-- settings.starting_area = 0
|
|
|
|
-- local r_freq = 1.20
|
|
-- local r_rich = 5.00
|
|
-- local r_size = 0.18
|
|
|
|
-- settings.autoplace_controls["coal"].frequency = r_freq
|
|
-- settings.autoplace_controls["coal"].richness = r_rich
|
|
-- settings.autoplace_controls["coal"].size = r_size
|
|
-- settings.autoplace_controls["copper-ore"].frequency = r_freq
|
|
-- settings.autoplace_controls["copper-ore"].richness = r_rich
|
|
-- settings.autoplace_controls["copper-ore"].size = r_size
|
|
-- settings.autoplace_controls["crude-oil"].frequency = r_freq
|
|
-- settings.autoplace_controls["crude-oil"].richness = r_rich
|
|
-- settings.autoplace_controls["crude-oil"].size = r_size
|
|
-- settings.autoplace_controls["iron-ore"].frequency = r_freq
|
|
-- settings.autoplace_controls["iron-ore"].richness = r_rich
|
|
-- settings.autoplace_controls["iron-ore"].size = r_size
|
|
-- settings.autoplace_controls["stone"].frequency = r_freq
|
|
-- settings.autoplace_controls["stone"].richness = r_rich
|
|
-- settings.autoplace_controls["stone"].size = r_size
|
|
-- settings.autoplace_controls["uranium-ore"].frequency = r_freq*0.5
|
|
-- settings.autoplace_controls["uranium-ore"].richness = r_rich
|
|
-- settings.autoplace_controls["uranium-ore"].size = r_size
|
|
|
|
-- settings.autoplace_controls["enemy-base"].frequency = 0.80
|
|
-- settings.autoplace_controls["enemy-base"].richness = 0.70
|
|
-- settings.autoplace_controls["enemy-base"].size = 0.70
|
|
|
|
-- settings.autoplace_controls["trees"].frequency = 1.00
|
|
-- settings.autoplace_controls["trees"].richness = 1.00
|
|
-- settings.autoplace_controls["trees"].size = 1.00
|
|
|
|
-- settings.cliff_settings.cliff_elevation_0 = 3
|
|
-- settings.cliff_settings.cliff_elevation_interval = 200
|
|
-- settings.cliff_settings.richness = 3
|
|
|
|
-- settings.property_expression_names["control-setting:aux:bias"] = "0.00"
|
|
-- settings.property_expression_names["control-setting:aux:frequency:multiplier"] = "5.00"
|
|
-- settings.property_expression_names["control-setting:moisture:bias"] = "0.40"
|
|
-- settings.property_expression_names["control-setting:moisture:frequency:multiplier"] = "50"
|
|
|
|
-- return settings
|
|
-- end
|
|
|
|
-- -- Create another surface so that we can modify map settings and not have a screwy nauvis map.
|
|
-- function CreateGameSurface()
|
|
|
|
-- if (GAME_SURFACE_NAME ~= "nauvis") then
|
|
|
|
-- -- Get starting surface settings.
|
|
-- local nauvis_settings = game.surfaces["nauvis"].map_gen_settings
|
|
|
|
-- if storage.ocfg.enable_vanilla_spawns then
|
|
-- nauvis_settings.starting_points = CreateVanillaSpawns(storage.ocfg.vanilla_spawn_count, storage.ocfg.vanilla_spawn_spacing)
|
|
|
|
-- -- ENFORCE ISLAND MAP GEN
|
|
-- if (storage.ocfg.silo_islands) then
|
|
-- nauvis_settings.property_expression_names.elevation = "0_17-island"
|
|
-- end
|
|
-- end
|
|
|
|
-- -- Enable this to test things out easily.
|
|
-- -- nauvis_settings = SurfaceSettingsHelper(nauvis_settings)
|
|
|
|
-- -- Create new game surface
|
|
-- local s = game.create_surface(GAME_SURFACE_NAME, nauvis_settings)
|
|
|
|
-- end
|
|
|
|
-- -- Add surface and safe areas
|
|
-- if storage.ocfg.enable_regrowth then
|
|
-- RegrowthMarkAreaSafeGivenChunkPos({x=0,y=0}, 4, true)
|
|
-- end
|
|
-- end
|
|
|
|
-- function CreateTileArrow(surface, pos, type)
|
|
|
|
-- tiles = {}
|
|
|
|
-- if (type == "LEFT") then
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+1, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+2, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+3, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x, pos.y+1}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x+1, pos.y+1}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x+2, pos.y+1}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x+3, pos.y+1}})
|
|
-- elseif (type == "RIGHT") then
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x+1, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x+2, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-right", position = {pos.x+3, pos.y}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x, pos.y+1}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+1, pos.y+1}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+2, pos.y+1}})
|
|
-- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+3, pos.y+1}})
|
|
-- end
|
|
|
|
-- surface.set_tiles(tiles, true)
|
|
-- end
|
|
|
|
-- -- Allowed colors: red, green, blue, orange, yellow, pink, purple, black, brown, cyan, acid
|
|
-- function CreateFixedColorTileArea(surface, area, color)
|
|
|
|
-- tiles = {}
|
|
|
|
-- for i=area.left_top.x,area.right_bottom.x do
|
|
-- for j=area.left_top.y,area.right_bottom.y do
|
|
-- table.insert(tiles, {name = color.."-refined-concrete", position = {i,j}})
|
|
-- end
|
|
-- end
|
|
|
|
-- surface.set_tiles(tiles, true)
|
|
-- end
|
|
|
|
-- -- Find closest player-owned entity
|
|
-- function FindClosestPlayerOwnedEntity(player, name, radius)
|
|
|
|
-- local entities = player.surface.find_entities_filtered{position=player.position,
|
|
-- radius=radius,
|
|
-- name=name,
|
|
-- force=player.force}
|
|
-- if (not entities or (#entities == 0)) then return nil end
|
|
|
|
-- return player.surface.get_closest(player.position, entities)
|
|
-- end
|
|
|
|
-- -- Add Long Reach to Character
|
|
-- function GivePlayerLongReach(player)
|
|
-- player.character.character_build_distance_bonus = BUILD_DIST_BONUS
|
|
-- player.character.character_reach_distance_bonus = REACH_DIST_BONUS
|
|
-- -- player.character.character_resource_reach_distance_bonus = RESOURCE_DIST_BONUS
|
|
-- end
|
|
|
|
-- -- General purpose cover an area in tiles.
|
|
-- function CoverAreaInTiles(surface, area, tile_name)
|
|
-- tiles = {}
|
|
-- for x = area.left_top.x,area.left_top.x+31 do
|
|
-- for y = area.left_top.y,area.left_top.y+31 do
|
|
-- table.insert(tiles, {name = tile_name, position = {x=x, y=y}})
|
|
-- end
|
|
-- end
|
|
-- surface.set_tiles(tiles, true)
|
|
-- end
|
|
|
|
-- --------------------------------------------------------------------------------
|
|
-- -- Anti-griefing Stuff & Gravestone (My own version)
|
|
-- --------------------------------------------------------------------------------
|
|
-- function AntiGriefing(force)
|
|
-- force.zoom_to_world_deconstruction_planner_enabled=false
|
|
-- SetForceGhostTimeToLive(force)
|
|
-- end
|
|
|
|
-- function SetForceGhostTimeToLive(force)
|
|
-- if storage.ocfg.ghost_ttl ~= 0 then
|
|
-- force.ghost_time_to_live = storage.ocfg.ghost_ttl+1
|
|
-- end
|
|
-- end
|
|
|
|
-- function SetItemBlueprintTimeToLive(event)
|
|
-- local type = event.created_entity.type
|
|
-- if type == "entity-ghost" or type == "tile-ghost" then
|
|
-- if storage.ocfg.ghost_ttl ~= 0 then
|
|
-- event.created_entity.time_to_live = storage.ocfg.ghost_ttl
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
-- --------------------------------------------------------------------------------
|
|
-- -- Resource patch and starting area generation
|
|
-- --------------------------------------------------------------------------------
|
|
|
|
---Circle spawn shape (handles land, trees and moat)
|
|
---@param surface LuaSurface
|
|
---@param centerPos MapPosition
|
|
---@param chunkArea BoundingBox
|
|
---@param tileRadius number
|
|
---@param fillTile string
|
|
---@param moat boolean
|
|
---@param bridge boolean
|
|
---@return nil
|
|
function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile, moat, bridge)
|
|
local tile_radius_sqr = tileRadius ^ 2
|
|
|
|
local moat_width = storage.ocfg.spawn_general.moat_width_tiles
|
|
local moat_radius_sqr = ((tileRadius + moat_width)^2)
|
|
|
|
local tree_width = storage.ocfg.spawn_general.tree_width_tiles
|
|
local tree_radius_sqr_inner = ((tileRadius - 1 - tree_width) ^ 2) -- 1 less to make sure trees are inside the spawn area
|
|
local tree_radius_sqr_outer = ((tileRadius - 1) ^ 2)
|
|
|
|
local surface_config = storage.ocfg.surfaces_config[surface.name]
|
|
local liquid_tile = surface_config.spawn_config.liquid_tile
|
|
local fish_enabled = (liquid_tile == "water")
|
|
|
|
local dirtTiles = {}
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
|
|
-- This ( X^2 + Y^2 ) is used to calculate if something is inside a circle area.
|
|
-- We avoid using sqrt for performance reasons.
|
|
local distSqr = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2)
|
|
|
|
-- Fill in all unexpected water (or force grass)
|
|
if (distSqr <= tile_radius_sqr) then
|
|
if (surface.get_tile(i, j).collides_with("water_tile") or
|
|
storage.ocfg.spawn_general.force_grass) then
|
|
table.insert(dirtTiles, { name = fillTile, position = { i, j } })
|
|
end
|
|
end
|
|
|
|
-- -- Create a tree ring
|
|
-- if ((distSqr < tree_radius_sqr_outer) and (distSqr > tree_radius_sqr_inner)) then
|
|
-- surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } })
|
|
-- end
|
|
|
|
-- Fill moat with water.
|
|
if (moat) then
|
|
if (bridge and ((j == centerPos.y - 1) or (j == centerPos.y) or (j == centerPos.y + 1))) then
|
|
-- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating
|
|
-- land connections if the spawn is on or near land.
|
|
elseif ((distSqr < moat_radius_sqr) and (distSqr > tile_radius_sqr)) then
|
|
table.insert(dirtTiles, { name = liquid_tile, position = { i, j } })
|
|
|
|
--5% chance of fish in water
|
|
if fish_enabled and (math.random(1,20) == 1) then
|
|
surface.create_entity({name="fish", position={i + 0.5, j + 0.5}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
surface.set_tiles(dirtTiles)
|
|
|
|
--Create trees (needs to be done after setting tiles!)
|
|
local tree_entity = surface_config.spawn_config.tree_entity
|
|
if (tree_entity == nil) then return end
|
|
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
local distSqr = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2)
|
|
if ((distSqr < tree_radius_sqr_outer) and (distSqr > tree_radius_sqr_inner)) then
|
|
local pos = surface.find_non_colliding_position(tree_entity, { i, j }, 2, 0.5)
|
|
if (pos ~= nil) then
|
|
surface.create_entity({ name = tree_entity, amount = 1, position = pos })
|
|
end
|
|
-- surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---` spawn shape (handles land, trees and moat) (Curtesy of jvmguy)
|
|
---@param surface LuaSurface
|
|
---@param centerPos MapPosition
|
|
---@param chunkArea BoundingBox
|
|
---@param tileRadius number
|
|
---@param fillTile string
|
|
---@param moat boolean
|
|
---@param bridge boolean
|
|
---@return nil
|
|
function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile, moat, bridge)
|
|
|
|
local moat_width = storage.ocfg.spawn_general.moat_width_tiles
|
|
local moat_width_outer = tileRadius + moat_width
|
|
|
|
local tree_width = storage.ocfg.spawn_general.tree_width_tiles
|
|
local tree_distance_inner = tileRadius - tree_width
|
|
|
|
local surface_config = storage.ocfg.surfaces_config[surface.name]
|
|
|
|
local dirtTiles = {}
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
|
|
local distVar1 = math.floor(math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j)))
|
|
local distVar2 = math.floor(math.abs(centerPos.x - i) + math.abs(centerPos.y - j))
|
|
local distVar = math.max(distVar1, distVar2 * 0.707);
|
|
|
|
-- Fill in all unexpected water (or force grass)
|
|
if (distVar <= tileRadius) then
|
|
if (surface.get_tile(i, j).collides_with("water_tile") or
|
|
storage.ocfg.spawn_general.force_grass) then
|
|
table.insert(dirtTiles, { name = fillTile, position = { i, j } })
|
|
end
|
|
end
|
|
|
|
-- -- Create a tree ring
|
|
-- if ((distVar < tileRadius) and (distVar >= tree_distance_inner)) then
|
|
-- surface.create_entity({ name = "tree-01", amount = 1, position = { i, j } })
|
|
-- end
|
|
|
|
-- Fill moat with water
|
|
if (moat) then
|
|
if (bridge and ((j == centerPos.y - 1) or (j == centerPos.y) or (j == centerPos.y + 1))) then
|
|
-- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating
|
|
-- land connections if the spawn is on or near land.
|
|
elseif ((distVar > tileRadius) and (distVar <= moat_width_outer)) then
|
|
table.insert(dirtTiles, { name = "water", position = { i, j } })
|
|
|
|
--5% chance of fish in water
|
|
if (math.random(1,20) == 1) then
|
|
surface.create_entity({name="fish", position={i + 0.5, j + 0.5}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
surface.set_tiles(dirtTiles)
|
|
|
|
|
|
--Create trees (needs to be done after setting tiles!)
|
|
local tree_entity = surface_config.spawn_config.tree_entity
|
|
if (tree_entity == nil) then return end
|
|
|
|
--Create trees (needs to be done after setting tiles!)
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
local distVar1 = math.floor(math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j)))
|
|
local distVar2 = math.floor(math.abs(centerPos.x - i) + math.abs(centerPos.y - j))
|
|
local distVar = math.max(distVar1, distVar2 * 0.707);
|
|
|
|
if ((distVar < tileRadius) and (distVar >= tree_distance_inner)) then
|
|
surface.create_entity({ name = "tree-01", amount = 1, position = { i, j } })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---Square spawn shape (handles land, trees and moat)
|
|
---@param surface LuaSurface
|
|
---@param centerPos MapPosition
|
|
---@param chunkArea BoundingBox
|
|
---@param tileRadius number
|
|
---@param fillTile string
|
|
---@param moat boolean
|
|
---@param bridge boolean
|
|
---@return nil
|
|
function CreateCropSquare(surface, centerPos, chunkArea, tileRadius, fillTile, moat, bridge)
|
|
|
|
local moat_width = storage.ocfg.spawn_general.moat_width_tiles
|
|
local moat_width_outer = tileRadius + moat_width
|
|
|
|
local tree_width = storage.ocfg.spawn_general.tree_width_tiles
|
|
local tree_distance_inner = tileRadius - tree_width
|
|
|
|
local surface_config = storage.ocfg.surfaces_config[surface.name]
|
|
|
|
local dirtTiles = {}
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
|
|
-- Max distance from center (either x or y)
|
|
local max_distance = math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j))
|
|
|
|
-- Fill in all unexpected water (or force grass)
|
|
if (max_distance <= tileRadius) then
|
|
if (surface.get_tile(i, j).collides_with("water_tile") or
|
|
storage.ocfg.spawn_general.force_grass) then
|
|
table.insert(dirtTiles, { name = fillTile, position = { i, j } })
|
|
end
|
|
end
|
|
|
|
-- -- Create a tree ring
|
|
-- if ((max_distance < tileRadius) and (max_distance >= tree_distance_inner)) then
|
|
-- surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } })
|
|
-- end
|
|
|
|
-- Fill moat with water
|
|
if (moat) then
|
|
if (bridge and ((j == centerPos.y - 1) or (j == centerPos.y) or (j == centerPos.y + 1))) then
|
|
-- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating
|
|
-- land connections if the spawn is on or near land.
|
|
elseif ((max_distance > tileRadius) and (max_distance <= moat_width_outer)) then
|
|
table.insert(dirtTiles, { name = "water", position = { i, j } })
|
|
|
|
--5% chance of fish in water
|
|
if (math.random(1,20) == 1) then
|
|
surface.create_entity({name="fish", position={i + 0.5, j + 0.5}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
surface.set_tiles(dirtTiles)
|
|
|
|
--Create trees (needs to be done after setting tiles!)
|
|
local tree_entity = surface_config.spawn_config.tree_entity
|
|
if (tree_entity == nil) then return end
|
|
|
|
--Create trees (needs to be done after setting tiles!)
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
local max_distance = math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j))
|
|
if ((max_distance < tileRadius) and (max_distance >= tree_distance_inner)) then
|
|
surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---Add a circle of water
|
|
---@param surface LuaSurface
|
|
---@param centerPos MapPosition
|
|
---@param chunkArea BoundingBox
|
|
---@param tileRadius number
|
|
---@param moatTile string
|
|
---@param bridge boolean
|
|
---@param shape SpawnShapeChoice
|
|
---@return nil
|
|
function CreateMoat(surface, centerPos, chunkArea, tileRadius, moatTile, bridge, shape)
|
|
local tileRadSqr = tileRadius ^ 2
|
|
|
|
local tiles = {}
|
|
for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do
|
|
for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do
|
|
if (bridge and ((j == centerPos.y - 1) or (j == centerPos.y) or (j == centerPos.y + 1))) then
|
|
-- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating
|
|
-- land connections if the spawn is on or near land.
|
|
else
|
|
-- This ( X^2 + Y^2 ) is used to calculate if something
|
|
-- is inside a circle area.
|
|
local distVar = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2)
|
|
|
|
-- Create a circle of water
|
|
if ((distVar < tileRadSqr + (1500 * storage.ocfg.spawn_general.moat_width_tiles)) and
|
|
(distVar > tileRadSqr)) then
|
|
table.insert(tiles, { name = moatTile, position = { i, j } })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
surface.set_tiles(tiles)
|
|
end
|
|
|
|
-- Create a horizontal line of tiles (typically used for water)
|
|
---@param surface LuaSurface
|
|
---@param leftPos TilePosition
|
|
---@param length integer
|
|
---@param tile_name string
|
|
---@return nil
|
|
function CreateTileStrip(surface, leftPos, length, tile_name)
|
|
local waterTiles = {}
|
|
for i = 0, length-1, 1 do
|
|
table.insert(waterTiles, { name = tile_name, position = { leftPos.x + i, leftPos.y } })
|
|
end
|
|
surface.set_tiles(waterTiles)
|
|
end
|
|
|
|
--- Function to generate a resource patch, of a certain size/amount at a pos.
|
|
---@param surface LuaSurface
|
|
---@param resourceName string
|
|
---@param diameter integer
|
|
---@param position TilePosition
|
|
---@param amount integer
|
|
function GenerateResourcePatch(surface, resourceName, diameter, position, amount)
|
|
local midPoint = math.floor(diameter / 2)
|
|
if (diameter == 0) then
|
|
return
|
|
end
|
|
|
|
-- Right now only 2 shapes are supported. Circle and Square.
|
|
local square_shape = (storage.ocfg.spawn_general.resources_shape == RESOURCES_SHAPE_CHOICE_SQUARE)
|
|
|
|
for y = -midPoint, midPoint do
|
|
for x = -midPoint, midPoint do
|
|
|
|
-- Either it's a square, or it's a circle so we check if it's inside the circle.
|
|
if (square_shape or ((x) ^ 2 + (y) ^ 2 < midPoint ^ 2)) then
|
|
surface.create_entity({
|
|
name = resourceName,
|
|
amount = amount,
|
|
position = { position.x + x, position.y + y }
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Function to generate a resource patch, of a certain size/amount at a pos.
|
|
---@param surface LuaSurface
|
|
---@param position MapPosition
|
|
---@return nil
|
|
function PlaceRandomEntities(surface, position)
|
|
local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config
|
|
local random_entities = spawn_config.random_entities
|
|
if (random_entities == nil) then return end
|
|
|
|
local tree_width = storage.ocfg.spawn_general.tree_width_tiles
|
|
local radius = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier - tree_width
|
|
|
|
--Iterate through the random entities and place them
|
|
for _, entry in pairs(random_entities) do
|
|
local entity_name = entry.name
|
|
|
|
for i = 1, entry.count do
|
|
|
|
local random_pos = GetRandomPointWithinCircle(radius, position)
|
|
local open_pos = surface.find_non_colliding_position(entity_name, random_pos, tree_width, 0.5)
|
|
|
|
if (open_pos ~= nil) then
|
|
surface.create_entity({
|
|
name = entity_name,
|
|
position = open_pos
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Randomly place lightning attractors specific for Fulgora. This should space them out so they don't overlap too much.
|
|
---@param surface LuaSurface
|
|
---@param position MapPosition
|
|
---@return nil
|
|
function PlaceFulgoranLightningAttractors(surface, position, count)
|
|
local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config
|
|
local radius = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier
|
|
|
|
-- HARDCODED FOR NOW
|
|
local ATTRACTOR_NAME = "fulgoran-ruin-attractor"
|
|
local ATTRACTOR_RADIUS = 20
|
|
|
|
--Iterate through and place them and use the largest available entity
|
|
for i = 1, count do
|
|
|
|
local random_pos = GetRandomPointWithinCircle(radius, position)
|
|
local open_pos = surface.find_non_colliding_position("crash-site-spaceship", random_pos, 1, 0.5)
|
|
|
|
if (open_pos ~= nil) then
|
|
surface.create_entity({
|
|
name = ATTRACTOR_NAME,
|
|
position = open_pos,
|
|
force = "player" -- Same as native game
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
-- --------------------------------------------------------------------------------
|
|
-- -- EVENT SPECIFIC FUNCTIONS
|
|
-- --------------------------------------------------------------------------------
|
|
|
|
-- -- Display messages to a user everytime they join
|
|
-- function PlayerJoinedMessages(event)
|
|
-- local player = game.players[event.player_index]
|
|
-- player.print(storage.ocfg.welcome_msg)
|
|
-- if (storage.oarc_announcements) then
|
|
-- player.print(storage.oarc_announcements)
|
|
-- end
|
|
-- end
|
|
|
|
-- -- Remove decor to save on file size
|
|
-- function UndecorateOnChunkGenerate(event)
|
|
-- local surface = event.surface
|
|
-- local chunkArea = event.area
|
|
-- RemoveDecorationsArea(surface, chunkArea)
|
|
-- -- If you care to, you can remove all fish with the Undecorator option here:
|
|
-- -- RemoveFish(surface, chunkArea)
|
|
-- end
|