-- oarc_utils.lua -- Nov 2016 -- -- My general purpose utility functions for factorio -- Also contains some constants and gui styles -------------------------------------------------------------------------------- -- 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 -------------------------------------------------------------------------------- GAME_SURFACE_NAME="Game" -------------------------------------------------------------------------------- -- GUI Label Styles -------------------------------------------------------------------------------- my_fixed_width_style = { minimal_width = 450, maximal_width = 450 } my_label_style = { -- minimal_width = 450, -- maximal_width = 50, single_line = false, font_color = {r=1,g=1,b=1}, top_padding = 0, bottom_padding = 0 } my_note_style = { -- minimal_width = 450, single_line = false, font = "default-small-semibold", font_color = {r=1,g=0.5,b=0.5}, top_padding = 0, bottom_padding = 0 } my_warning_style = { -- minimal_width = 450, -- maximal_width = 450, single_line = false, font_color = {r=1,g=0.1,b=0.1}, top_padding = 0, bottom_padding = 0 } my_spacer_style = { minimal_height = 10, font_color = {r=0,g=0,b=0}, top_padding = 0, bottom_padding = 0 } my_small_button_style = { font = "default-small-semibold" } my_player_list_fixed_width_style = { minimal_width = 200, maximal_width = 400, maximal_height = 200 } my_player_list_admin_style = { font = "default-semibold", font_color = {r=1,g=0.5,b=0.5}, minimal_width = 200, top_padding = 0, bottom_padding = 0, single_line = false, } my_player_list_style = { font = "default-semibold", minimal_width = 200, top_padding = 0, bottom_padding = 0, single_line = false, } my_player_list_offline_style = { -- font = "default-semibold", font_color = {r=0.5,g=0.5,b=0.5}, minimal_width = 200, top_padding = 0, bottom_padding = 0, single_line = false, } my_player_list_style_spacer = { minimal_height = 20, } my_color_red = {r=1,g=0.1,b=0.1} my_longer_label_style = { maximal_width = 600, single_line = false, font_color = {r=1,g=1,b=1}, top_padding = 0, bottom_padding = 0 } my_longer_warning_style = { maximal_width = 600, single_line = false, font_color = {r=1,g=0.1,b=0.1}, top_padding = 0, bottom_padding = 0 } -------------------------------------------------------------------------------- -- General Helper Functions -------------------------------------------------------------------------------- -- Print debug only to me while testing. function DebugPrint(msg) if ((game.players["Oarc"] ~= nil) and (global.oarcDebugEnabled)) then game.players["Oarc"].print("DEBUG: " .. msg) end end -- 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 -- Broadcast messages to all connected players 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. function SendMsg(playerName, msg) if ((game.players[playerName] ~= nil) and (game.players[playerName].connected)) then game.players[playerName].print(msg) end end -- Special case for ensuring that if I create the server, my messages are -- used instead of the generic insert msg warning. function SetServerWelcomeMessages() if (SERVER_OWNER_IS_OARC) then global.welcome_msg = WELCOME_MSG_OARC global.welcome_msg_title = WELCOME_MSG_TITLE_OARC else global.welcome_msg = WELCOME_MSG global.welcome_msg_title = WELCOME_MSG_TITLE end end -- Useful for displaying game time in mins:secs format 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 mins:secs format function formattime_hours_mins(ticks) local seconds = ticks / 60 local minutes = math.floor((seconds)/60) local hours = math.floor((minutes)/60) local minutes = math.floor(minutes - 60*hours) return string.format("%dh:%02dm", hours, minutes) end -- Simple function to get total number of items in table function TableLength(T) local count = 0 for _ in pairs(T) do count = count + 1 end return count end -- Simple function to get distance between two positions. function getDistance(posA, posB) -- Get the length for each of the components x and y local xDist = posB.x - posA.x local yDist = posB.y - posA.y return math.sqrt( (xDist ^ 2) + (yDist ^ 2) ) end -- Chart area for a force 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 -- Give player these default items. function GivePlayerItems(player) for _,item in pairs(PLAYER_RESPAWN_START_ITEMS) do player.insert(item) end end -- Starter only items function GivePlayerStarterItems(player) for _,item in pairs(PLAYER_SPAWN_START_ITEMS) do player.insert(item) end if ENABLE_POWER_ARMOR_QUICK_START then GiveQuickStartPowerArmor(player) end end -- Cheater's quick start function GiveQuickStartPowerArmor(player) player.insert{name="power-armor", count = 1} if player and player.get_inventory(5) ~= nil and player.get_inventory(5)[1] ~= nil then local p_armor = player.get_inventory(5)[1].grid --defines.inventory.player_armor = 5? 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"}) p_armor.put({name = "solar-panel-equipment"}) p_armor.put({name = "solar-panel-equipment"}) p_armor.put({name = "solar-panel-equipment"}) p_armor.put({name = "solar-panel-equipment"}) p_armor.put({name = "solar-panel-equipment"}) p_armor.put({name = "solar-panel-equipment"}) p_armor.put({name = "solar-panel-equipment"}) end player.insert{name="construction-robot", count = 100} player.insert{name="belt-immunity-equipment", count = 1} end end -- Create area given point and radius-distance function GetAreaFromPointAndDistance(point, dist) local area = {left_top= {x=point.x-dist, y=point.y-dist}, right_bottom= {x=point.x+dist, y=point.y+dist}} return area end -- Check if given position is in area bounding box 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 -- Set all forces to ceasefire function SetCeaseFireBetweenAllForces() for name,team in pairs(game.forces) do if name ~= "neutral" and name ~= "enemy" then for x,y in pairs(game.forces) do if x ~= "neutral" and x ~= "enemy" then team.set_cease_fire(x,true) end end end end end -- Set all forces to friendly function SetFriendlyBetweenAllForces() for name,team in pairs(game.forces) do if name ~= "neutral" and name ~= "enemy" then for x,y in pairs(game.forces) do if x ~= "neutral" and x ~= "enemy" then team.set_friend(x,true) end end end end end -- For each other player force, share a chat msg. 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) end -- Remove fish function RemoveFish(surface, area) for _, entity in pairs(surface.find_entities_filtered{area = area, type="fish"}) do entity.destroy() end end -- Apply a style option to a GUI function ApplyStyle (guiIn, styleIn) for k,v in pairs(styleIn) do guiIn.style[k]=v end end -- Shorter way to add a label with a style function AddLabel(guiIn, name, message, style) guiIn.add{name = name, type = "label", caption=message} ApplyStyle(guiIn[name], style) end -- Shorter way to add a spacer function AddSpacer(guiIn, name) guiIn.add{name = name, type = "label", caption=" "} ApplyStyle(guiIn[name], my_spacer_style) end -- Shorter way to add a spacer with a decorative line function AddSpacerLine(guiIn, name) guiIn.add{name = name, type = "label", caption="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"} ApplyStyle(guiIn[name], my_spacer_style) end -- Get a random 1 or -1 function RandomNegPos() if (math.random(0,1) == 1) then return 1 else return -1 end end -- Create a random direction vector to look in function GetRandomVector() local randVec = {x=0,y=0} while ((randVec.x == 0) and (randVec.y == 0)) do randVec.x = math.random(-3,3) randVec.y = math.random(-3,3) end DebugPrint("direction: x=" .. randVec.x .. ", y=" .. randVec.y) return randVec end -- Check for ungenerated chunks around a specific chunk -- +/- chunkDist in x and y directions 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 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 function FindMapEdge(directionVec, surface) local position = {x=0,y=0} local chunkPos = {x=0,y=0} -- Keep checking chunks in the direction of the vector while(true) do -- Set some absolute limits. if ((math.abs(chunkPos.x) > 1000) or (math.abs(chunkPos.y) > 1000)) then break -- If chunk is already generated, keep looking elseif (surface.is_chunk_generated(chunkPos)) then chunkPos.x = chunkPos.x + directionVec.x chunkPos.y = chunkPos.y + directionVec.y -- Found a possible ungenerated area else chunkPos.x = chunkPos.x + directionVec.x chunkPos.y = chunkPos.y + directionVec.y -- Check there are no generated chunks in a 10x10 area. if IsChunkAreaUngenerated(chunkPos, 10, surface) then position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) break end end end -- DebugPrint("spawn: x=" .. position.x .. ", y=" .. position.y) return position end -- Find random coordinates within a given distance away -- maxTries is the recursion limit basically. function FindUngeneratedCoordinates(minDistChunks, maxDistChunks, surface) local position = {x=0,y=0} local chunkPos = {x=0,y=0} local maxTries = 100 local tryCounter = 0 local minDistSqr = minDistChunks^2 local maxDistSqr = maxDistChunks^2 while(true) do chunkPos.x = math.random(0,maxDistChunks) * RandomNegPos() chunkPos.y = math.random(0,maxDistChunks) * RandomNegPos() local distSqrd = chunkPos.x^2 + chunkPos.y^2 -- Enforce a max number of tries tryCounter = tryCounter + 1 if (tryCounter > maxTries) then DebugPrint("FindUngeneratedCoordinates - Max Tries Hit!") break -- Check that the distance is within the min,max specified elseif ((distSqrd < minDistSqr) or (distSqrd > maxDistSqr)) then -- Keep searching! -- Check there are no generated chunks in a 10x10 area. elseif IsChunkAreaUngenerated(chunkPos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS, surface) then position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) break -- SUCCESS end end DebugPrint("spawn: x=" .. position.x .. ", y=" .. position.y) return 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 -- Get an area given a position and distance. -- Square length = 2x distance 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 -- 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. function RemoveInCircle(surface, area, type, pos, dist) for key, 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 -- Convenient way to remove aliens, just provide an area function RemoveAliensInArea(surface, area) for _, entity in pairs(surface.find_entities_filtered{area = area, force = "enemy"}) do entity.destroy() end end -- Make an area safer -- Reduction factor divides the enemy spawns by that number. 2 = half, 3 = third, etc... -- Also removes all big and huge worms in that area function ReduceAliensInArea(surface, area, reductionFactor) for _, entity in pairs(surface.find_entities_filtered{area = area, force = "enemy"}) do if (math.random(0,reductionFactor) > 0) then entity.destroy() end end -- Remove all big and huge worms for _, entity in pairs(surface.find_entities_filtered{area = area, name = "medium-worm-turret"}) do entity.destroy() end for _, entity in pairs(surface.find_entities_filtered{area = area, name = "big-worm-turret"}) do entity.destroy() end end -- Adjust alien params function ConfigureAlienStartingParams() -- These are the default values for reference: -- "time_factor": 0.000004, -- "destroy_factor": 0.002, -- "pollution_factor": 0.000015 if ENEMY_TIME_FACTOR_DISABLE then game.map_settings.enemy_evolution.time_factor = 0 else game.map_settings.enemy_evolution.time_factor=game.map_settings.enemy_evolution.time_factor / ENEMY_TIME_FACTOR_DIVISOR end if ENEMY_POLLUTION_FACTOR_DISABLE then game.map_settings.enemy_evolution.pollution_factor = 0 else game.map_settings.enemy_evolution.pollution_factor = game.map_settings.enemy_evolution.pollution_factor / ENEMY_POLLUTION_FACTOR_DIVISOR end if ENEMY_DESTROY_FACTOR_DISABLE then game.map_settings.enemy_evolution.destroy_factor = 0 else game.map_settings.enemy_evolution.destroy_factor = game.map_settings.enemy_evolution.destroy_factor / ENEMY_DESTROY_FACTOR_DIVISOR end game.map_settings.enemy_expansion.enabled = ENEMY_EXPANSION if (OARC_DIFFICULTY_CUSTOM) then game.map_settings.pollution.diffusion_ratio = 0.08 game.map_settings.pollution.ageing = 1 game.map_settings.enemy_expansion.max_expansion_distance = 20 game.map_settings.enemy_expansion.settler_group_min_size = 2 game.map_settings.enemy_expansion.settler_group_max_size = 10 game.map_settings.enemy_expansion.min_expansion_cooldown = TICKS_PER_MINUTE*15 game.map_settings.enemy_expansion.max_expansion_cooldown = TICKS_PER_MINUTE*60 game.map_settings.unit_group.min_group_gathering_time = TICKS_PER_MINUTE game.map_settings.unit_group.max_group_gathering_time = 4 * TICKS_PER_MINUTE game.map_settings.unit_group.max_wait_time_for_late_members = 1 * TICKS_PER_MINUTE game.map_settings.unit_group.max_unit_group_size = 15 -- game.map_settings.pollution.enabled=true, -- -- these are values for 60 ticks (1 simulated second) -- -- -- -- amount that is diffused to neighboring chunk -- -- (possibly repeated for other directions as well) -- game.map_settings.pollution.diffusion_ratio=0.02, -- -- this much PUs must be on the chunk to start diffusing -- game.map_settings.pollution.min_to_diffuse=15, -- -- constant modifier a percentage of 1 - the pollution eaten by a chunks tiles -- game.map_settings.pollution.ageing=1, -- -- anything bigger than this is visualised as this value -- game.map_settings.pollution.expected_max_per_chunk=7000, -- -- anything lower than this (but > 0) is visualised as this value -- game.map_settings.pollution.min_to_show_per_chunk=700, -- game.map_settings.pollution.min_pollution_to_damage_trees = 3500, -- game.map_settings.pollution.pollution_with_max_forest_damage = 10000, -- game.map_settings.pollution.pollution_per_tree_damage = 2000, -- game.map_settings.pollution.pollution_restored_per_tree_damage = 500, -- game.map_settings.pollution.max_pollution_to_restore_trees = 1000 -- game.map_settings.enemy_expansion.enabled = true, -- -- Distance in chunks from the furthest base around. -- -- This prevents expansions from reaching too far into the -- -- player's territory -- game.map_settings.enemy_expansion.max_expansion_distance = 7, -- game.map_settings.enemy_expansion.friendly_base_influence_radius = 2, -- game.map_settings.enemy_expansion.enemy_building_influence_radius = 2, -- -- A candidate chunk's score is given as follows: -- -- player = 0 -- -- for neighbour in all chunks within enemy_building_influence_radius from chunk: -- -- player += number of player buildings on neighbour -- -- * building_coefficient -- -- * neighbouring_chunk_coefficient^distance(chunk, neighbour) -- -- -- -- base = 0 -- -- for neighbour in all chunk within friendly_base_influence_radius from chunk: -- -- base += num of enemy bases on neighbour -- -- * other_base_coefficient -- -- * neighbouring_base_chunk_coefficient^distance(chunk, neighbour) -- -- -- -- score(chunk) = 1 / (1 + player + base) -- -- -- -- The iteration is over a square region centered around the chunk for which the calculation is done, -- -- and includes the central chunk as well. distance is the Manhattan distance, and ^ signifies exponentiation. -- game.map_settings.enemy_expansion.building_coefficient = 0.1, -- game.map_settings.enemy_expansion.other_base_coefficient = 2.0, -- game.map_settings.enemy_expansion.neighbouring_chunk_coefficient = 0.5, -- game.map_settings.enemy_expansion.neighbouring_base_chunk_coefficient = 0.4; -- -- A chunk has to have at most this much percent unbuildable tiles for it to be considered a candidate. -- -- This is to avoid chunks full of water to be marked as candidates. -- game.map_settings.enemy_expansion.max_colliding_tiles_coefficient = 0.9, -- -- Size of the group that goes to build new base (in game this is multiplied by the -- -- evolution factor). -- game.map_settings.enemy_expansion.settler_group_min_size = 5, -- game.map_settings.enemy_expansion.settler_group_max_size = 20, -- -- Ticks to expand to a single -- -- position for a base is used. -- -- -- -- cooldown is calculated as follows: -- -- cooldown = lerp(max_expansion_cooldown, min_expansion_cooldown, -e^2 + 2 * e), -- -- where lerp is the linear interpolation function, and e is the current evolution factor. -- game.map_settings.enemy_expansion.min_expansion_cooldown = 4 * 3600, -- game.map_settings.enemy_expansion.max_expansion_cooldown = 60 * 3600 -- -- pollution triggered group waiting time is a random time between min and max gathering time -- game.map_settings.unit_group.min_group_gathering_time = 3600, -- game.map_settings.unit_group.max_group_gathering_time = 10 * 3600, -- -- after the gathering is finished the group can still wait for late members, -- -- but it doesn't accept new ones anymore -- game.map_settings.unit_group.max_wait_time_for_late_members = 2 * 3600, -- -- limits for group radius (calculated by number of numbers) -- game.map_settings.unit_group.max_group_radius = 30.0, -- game.map_settings.unit_group.min_group_radius = 5.0, -- -- when a member falls behind the group he can speedup up till this much of his regular speed -- game.map_settings.unit_group.max_member_speedup_when_behind = 1.4, -- -- When a member gets ahead of its group, it will slow down to at most this factor of its speed -- game.map_settings.unit_group.max_member_slowdown_when_ahead = 0.6, -- -- When members of a group are behind, the entire group will slow down to at most this factor of its max speed -- game.map_settings.unit_group.max_group_slowdown_factor = 0.3, -- -- If a member falls behind more than this times the group radius, the group will slow down to max_group_slowdown_factor -- game.map_settings.unit_group.max_group_member_fallback_factor = 3, -- -- If a member falls behind more than this time the group radius, it will be removed from the group. -- game.map_settings.unit_group.member_disown_distance = 10, -- game.map_settings.unit_group.tick_tolerance_when_member_arrives = 60, -- -- Maximum number of automatically created unit groups gathering for attack at any time. -- game.map_settings.unit_group.max_gathering_unit_groups = 30, -- -- Maximum size of an attack unit group. This only affects automatically-created unit groups; manual groups -- -- created through the API are unaffected. -- game.map_settings.unit_group.max_unit_group_size = 200 end 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 -------------------------------------------------------------------------------- -- Player List GUI - My own version -------------------------------------------------------------------------------- function CreatePlayerListGui(event) local player = game.players[event.player_index] if player.gui.top.playerList == nil then player.gui.top.add{name="playerList", type="button", caption="Player List"} end end local function ExpandPlayerListGui(player) local frame = player.gui.left["playerList-panel"] if (frame) then frame.destroy() else local frame = player.gui.left.add{type="frame", name="playerList-panel", caption="Online:"} local scrollFrame = frame.add{type="scroll-pane", name="playerList-panel", direction = "vertical"} ApplyStyle(scrollFrame, my_player_list_fixed_width_style) scrollFrame.horizontal_scroll_policy = "never" for _,player in pairs(game.connected_players) do local caption_str = player.name.." ["..player.force.name.."]".." ("..formattime_hours_mins(player.online_time)..")" if (player.admin) then AddLabel(scrollFrame, player.name.."_plist", caption_str, my_player_list_admin_style) else AddLabel(scrollFrame, player.name.."_plist", caption_str, my_player_list_style) end end -- List offline players if (PLAYER_LIST_OFFLINE_PLAYERS) then AddLabel(scrollFrame, "offline_title_msg", "Offline Players:", my_label_style) for _,player in pairs(game.players) do if (not player.connected) then local caption_str = player.name.." ["..player.force.name.."]".." ("..formattime_hours_mins(player.online_time)..")" local text = scrollFrame.add{type="label", caption=caption_str, name=player.name.."_plist"} ApplyStyle(text, my_player_list_offline_style) end end end local spacer = scrollFrame.add{type="label", caption=" ", name="plist_spacer_plist"} ApplyStyle(spacer, my_player_list_style_spacer) end end function PlayerListGuiClick(event) if not (event and event.element and event.element.valid) then return end local player = game.players[event.element.player_index] local name = event.element.name if (name == "playerList") then ExpandPlayerListGui(player) end end -------------------------------------------------------------------------------- -- Anti-griefing Stuff & Gravestone (My own version) -------------------------------------------------------------------------------- function AntiGriefing(force) force.zoom_to_world_deconstruction_planner_enabled=false force.friendly_fire=false SetForceGhostTimeToLive(force) end function SetForceGhostTimeToLive(force) if GHOST_TIME_TO_LIVE ~= 0 then force.ghost_time_to_live = GHOST_TIME_TO_LIVE+1 end end function SetItemBlueprintTimeToLive(event) local type = event.created_entity.type if type == "entity-ghost" or type == "tile-ghost" then if GHOST_TIME_TO_LIVE ~= 0 then event.created_entity.time_to_live = GHOST_TIME_TO_LIVE end end end -------------------------------------------------------------------------------- -- Gravestone soft mod. With my own modifications/improvements. -------------------------------------------------------------------------------- -- Return steel chest entity (or nil) function DropEmptySteelChest(player) local pos = player.surface.find_non_colliding_position("steel-chest", player.position, 15, 1) if not pos then return nil end local grave = player.surface.create_entity{name="steel-chest", position=pos, force="neutral"} return grave end function DropGravestoneChests(player) local grave local count = 0 -- Make sure we save stuff we're holding in our hands. player.clean_cursor() -- Loop through a players different inventories -- Put it all into a chest. -- If the chest is full, create a new chest. for i, id in ipairs{ defines.inventory.player_armor, defines.inventory.player_main, defines.inventory.player_quickbar, defines.inventory.player_guns, defines.inventory.player_ammo, defines.inventory.player_tools, defines.inventory.player_trash} do local inv = player.get_inventory(id) if ((#inv > 0) and not inv.is_empty()) then for j = 1, #inv do if inv[j].valid_for_read then -- Create a chest when counter is reset if (count == 0) then grave = DropEmptySteelChest(player) if (grave == nil) then -- player.print("Not able to place a chest nearby! Some items lost!") return end grave_inv = grave.get_inventory(defines.inventory.chest) end count = count + 1 -- Copy the item stack into a chest slot. grave_inv[count].set_stack(inv[j]) -- Reset counter when chest is full if (count == #grave_inv) then count = 0 end end end end -- Clear the player inventory so we don't have duplicate items lying around. inv.clear() end if (grave ~= nil) then player.print("Successfully dropped your items into a chest! Go get them quick!") end end -------------------------------------------------------------------------------- -- Autofill Stuff -------------------------------------------------------------------------------- -- Transfer Items Between Inventory -- Returns the number of items that were successfully transferred. -- Returns -1 if item not available. -- Returns -2 if can't place item into destInv (ERROR) function TransferItems(srcInv, destEntity, itemStack) -- Check if item is in srcInv if (srcInv.get_item_count(itemStack.name) == 0) then return -1 end -- Check if can insert into destInv if (not destEntity.can_insert(itemStack)) then return -2 end -- Insert items local itemsRemoved = srcInv.remove(itemStack) itemStack.count = itemsRemoved return destEntity.insert(itemStack) end -- Attempts to transfer at least some of one type of item from an array of items. -- Use this to try transferring several items in order -- It returns once it successfully inserts at least some of one type. function TransferItemMultipleTypes(srcInv, destEntity, itemNameArray, itemCount) local ret = 0 for _,itemName in pairs(itemNameArray) do ret = TransferItems(srcInv, destEntity, {name=itemName, count=itemCount}) if (ret > 0) then return ret -- Return the value succesfully transferred end end return ret -- Return the last error code end -- Autofills a turret with ammo function AutofillTurret(player, turret) local mainInv = player.get_inventory(defines.inventory.player_main) -- Attempt to transfer some ammo local ret = TransferItemMultipleTypes(mainInv, turret, {"uranium-rounds-magazine", "piercing-rounds-magazine", "firearm-magazine"}, AUTOFILL_TURRET_AMMO_QUANTITY) -- Check the result and print the right text to inform the user what happened. if (ret > 0) then -- Inserted ammo successfully -- FlyingText("Inserted ammo x" .. ret, turret.position, my_color_red, player.surface) elseif (ret == -1) then FlyingText("Out of ammo!", turret.position, my_color_red, player.surface) elseif (ret == -2) then FlyingText("Autofill ERROR! - Report this bug!", turret.position, my_color_red, player.surface) end end -- Autofills a vehicle with fuel, bullets and shells where applicable function AutoFillVehicle(player, vehicle) local mainInv = player.get_inventory(defines.inventory.player_main) -- Attempt to transfer some fuel if ((vehicle.name == "car") or (vehicle.name == "tank") or (vehicle.name == "locomotive")) then TransferItemMultipleTypes(mainInv, vehicle, {"nuclear-fuel", "rocket-fuel", "solid-fuel", "coal", "raw-wood"}, 50) end -- Attempt to transfer some ammo if ((vehicle.name == "car") or (vehicle.name == "tank")) then TransferItemMultipleTypes(mainInv, vehicle, {"uranium-rounds-magazine", "piercing-rounds-magazine", "firearm-magazine"}, 100) end -- Attempt to transfer some tank shells if (vehicle.name == "tank") then TransferItemMultipleTypes(mainInv, vehicle, {"explosive-uranium-cannon-shell", "uranium-cannon-shell", "explosive-cannon-shell", "cannon-shell"}, 100) end end -------------------------------------------------------------------------------- -- Resource patch and starting area generation -------------------------------------------------------------------------------- -- Enforce a circle of land, also adds trees in a ring around the area. function CreateCropCircle(surface, centerPos, chunkArea, tileRadius) local tileRadSqr = tileRadius^2 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. local distVar = math.floor((centerPos.x - i)^2 + (centerPos.y - j)^2) -- Fill in all unexpected water in a circle if (distVar < tileRadSqr) then if (surface.get_tile(i,j).collides_with("water-tile") or ENABLE_SPAWN_FORCE_GRASS) then table.insert(dirtTiles, {name = "grass-1", position ={i,j}}) end end -- Create a circle of trees around the spawn point. if ((distVar < tileRadSqr-200) and (distVar > tileRadSqr-400)) then surface.create_entity({name="tree-02", amount=1, position={i, j}}) end end end surface.set_tiles(dirtTiles) end -- COPIED FROM jvmguy! -- Enforce a square of land, with a tree border -- this is equivalent to the CreateCropCircle code function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius) 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*1.1, distVar2 * 0.707*1.1); -- Fill in all unexpected water in a circle if (distVar < tileRadius+2) then if (surface.get_tile(i,j).collides_with("water-tile") or ENABLE_SPAWN_FORCE_GRASS) then table.insert(dirtTiles, {name = "grass-1", position ={i,j}}) end end -- Create a tree ring if ((distVar < tileRadius) and (distVar > tileRadius-2)) then surface.create_entity({name="tree-01", amount=1, position={i, j}}) end end end surface.set_tiles(dirtTiles) end -- Add a circle of water function CreateMoat(surface, centerPos, chunkArea, tileRadius) local tileRadSqr = tileRadius^2 local waterTiles = {} 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. local distVar = math.floor((centerPos.x - i)^2 + (centerPos.y - j)^2) -- Create a circle of water if ((distVar < tileRadSqr+(1500*MOAT_SIZE_MODIFIER)) and (distVar > tileRadSqr)) then table.insert(waterTiles, {name = "water", position ={i,j}}) end -- Enforce land inside the edges of the circle to make sure it's -- a clean transition if ((distVar <= tileRadSqr) and (distVar > tileRadSqr-10000)) then table.insert(waterTiles, {name = "grass-1", position ={i,j}}) end end end surface.set_tiles(waterTiles) end -- Create a horizontal line of water function CreateWaterStrip(surface, leftPos, length) local waterTiles = {} for i=0,length,1 do table.insert(waterTiles, {name = "water", 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. function GenerateResourcePatch(surface, resourceName, diameter, pos, amount) local midPoint = math.floor(diameter/2) if (diameter == 0) then return end for y=0, diameter do for x=0, diameter do if (not ENABLE_RESOURCE_SHAPE_CIRCLE or ((x-midPoint)^2 + (y-midPoint)^2 < midPoint^2)) then surface.create_entity({name=resourceName, amount=amount, position={pos.x+x, pos.y+y}}) end end end end -- Generate the basic starter resource around a given location. function GenerateStartingResources(surface, pos) -- Generate stone local stonePos = {x=pos.x+START_RESOURCE_STONE_POS_X, y=pos.y+START_RESOURCE_STONE_POS_Y} -- Generate coal local coalPos = {x=pos.x+START_RESOURCE_COAL_POS_X, y=pos.y+START_RESOURCE_COAL_POS_Y} -- Generate copper ore local copperOrePos = {x=pos.x+START_RESOURCE_COPPER_POS_X, y=pos.y+START_RESOURCE_COPPER_POS_Y} -- Generate iron ore local ironOrePos = {x=pos.x+START_RESOURCE_IRON_POS_X, y=pos.y+START_RESOURCE_IRON_POS_Y} -- Generate uranium local uraniumOrePos = {x=pos.x+START_RESOURCE_URANIUM_POS_X, y=pos.y+START_RESOURCE_URANIUM_POS_Y} -- Tree generation is taken care of in chunk generation -- Generate oil patches oil_patch_x=pos.x+START_RESOURCE_OIL_POS_X oil_patch_y=pos.y+START_RESOURCE_OIL_POS_Y for i=1,START_RESOURCE_OIL_NUM_PATCHES do surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT, position={oil_patch_x, oil_patch_y}}) oil_patch_x=oil_patch_x+START_RESOURCE_OIL_X_OFFSET oil_patch_y=oil_patch_y+START_RESOURCE_OIL_Y_OFFSET end -- Generate Stone GenerateResourcePatch(surface, "stone", START_RESOURCE_STONE_SIZE, stonePos, START_STONE_AMOUNT) -- Generate Coal GenerateResourcePatch(surface, "coal", START_RESOURCE_COAL_SIZE, coalPos, START_COAL_AMOUNT) -- Generate Copper GenerateResourcePatch(surface, "copper-ore", START_RESOURCE_COPPER_SIZE, copperOrePos, START_COPPER_AMOUNT) -- Generate Iron GenerateResourcePatch(surface, "iron-ore", START_RESOURCE_IRON_SIZE, ironOrePos, START_IRON_AMOUNT) -- Generate Uranium GenerateResourcePatch(surface, "uranium-ore", START_RESOURCE_URANIUM_SIZE, uraniumOrePos, START_URANIUM_AMOUNT) end -- Clear the spawn areas. -- This should be run inside the chunk generate event and be given a list of all -- unique spawn points. -- This clears enemies in the immediate area, creates a slightly safe area around it, -- It no LONGER generates the resources though as that is now handled in a delayed event! function SetupAndClearSpawnAreas(surface, chunkArea, spawnPointTable) for name,spawn in pairs(spawnPointTable) do -- Create a bunch of useful area and position variables local landArea = GetAreaAroundPos(spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+CHUNK_SIZE) local safeArea = GetAreaAroundPos(spawn.pos, SAFE_AREA_TILE_DIST) local warningArea = GetAreaAroundPos(spawn.pos, WARNING_AREA_TILE_DIST) local chunkAreaCenter = {x=chunkArea.left_top.x+(CHUNK_SIZE/2), y=chunkArea.left_top.y+(CHUNK_SIZE/2)} local spawnPosOffset = {x=spawn.pos.x+ENFORCE_LAND_AREA_TILE_DIST, y=spawn.pos.y+ENFORCE_LAND_AREA_TILE_DIST} -- Make chunks near a spawn safe by removing enemies if CheckIfInArea(chunkAreaCenter,safeArea) then RemoveAliensInArea(surface, chunkArea) -- Create a warning area with reduced enemies elseif CheckIfInArea(chunkAreaCenter,warningArea) then ReduceAliensInArea(surface, chunkArea, WARN_AREA_REDUCTION_RATIO) end -- If the chunk is within the main land area, then clear trees/resources -- and create the land spawn areas (guaranteed land with a circle of trees) if CheckIfInArea(chunkAreaCenter,landArea) then -- Remove trees/resources inside the spawn area RemoveInCircle(surface, chunkArea, "tree", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST) RemoveInCircle(surface, chunkArea, "resource", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+5) RemoveInCircle(surface, chunkArea, "cliff", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+5) RemoveDecorationsArea(surface, chunkArea) if (SPAWN_TREE_CIRCLE_ENABLED) then CreateCropCircle(surface, spawn.pos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST) end if (SPAWN_TREE_OCTAGON_ENABLED) then CreateCropOctagon(surface, spawn.pos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST) end if (SPAWN_MOAT_CHOICE_ENABLED) then if (spawn.moat) then CreateMoat(surface, spawn.pos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST) end end end -- Provide starting resources -- This is run on the bottom, right chunk of the spawn area which should be -- generated last, so it should work everytime. -- if CheckIfInArea(spawnPosOffset,chunkArea) then -- CreateWaterStrip(surface, -- {x=spawn.pos.x+WATER_SPAWN_OFFSET_X, y=spawn.pos.y+WATER_SPAWN_OFFSET_Y}, -- WATER_SPAWN_LENGTH) -- CreateWaterStrip(surface, -- {x=spawn.pos.x+WATER_SPAWN_OFFSET_X, y=spawn.pos.y+WATER_SPAWN_OFFSET_Y+1}, -- WATER_SPAWN_LENGTH) -- GenerateStartingResources(surface, spawn.pos) -- end end end -------------------------------------------------------------------------------- -- Surface Generation Functions -------------------------------------------------------------------------------- RSO_MODE = 1 VANILLA_MODE = 2 function CreateGameSurface(mode) local mapSettings = game.surfaces["nauvis"].map_gen_settings if CMD_LINE_MAP_GEN then mapSettings.terrain_segmentation = global.clMapGen.terrain_segmentation mapSettings.water = global.clMapGen.water mapSettings.starting_area = global.clMapGen.starting_area mapSettings.peaceful_mode = global.clMapGen.peaceful_mode mapSettings.seed = global.clMapGen.seed mapSettings.autoplace_controls = global.clMapGen.autoplace_controls mapSettings.cliff_settings = global.clMapGen.cliff_settings end -- To use RSO resources, we have to disable vanilla ore generation if (mode == RSO_MODE) then mapSettings.autoplace_controls["coal"].size="none" mapSettings.autoplace_controls["copper-ore"].size="none" mapSettings.autoplace_controls["iron-ore"].size="none" mapSettings.autoplace_controls["stone"].size="none" mapSettings.autoplace_controls["uranium-ore"].size="none" mapSettings.autoplace_controls["crude-oil"].size="none" mapSettings.autoplace_controls["enemy-base"].size="none" end local surface = game.create_surface(GAME_SURFACE_NAME,mapSettings) surface.set_tiles({{name = "out-of-map",position = {1,1}}}) end -------------------------------------------------------------------------------- -- Holding pen for new players joining the map -------------------------------------------------------------------------------- function CreateWall(surface, pos) local wall = surface.create_entity({name="stone-wall", position=pos, force=MAIN_TEAM}) if wall then wall.destructible = false wall.minable = false end end function CreateHoldingPen(surface, chunkArea, sizeTiles, walls) if (((chunkArea.left_top.x == -32) or (chunkArea.left_top.x == 0)) and ((chunkArea.left_top.y == -32) or (chunkArea.left_top.y == 0))) then -- Remove stuff RemoveAliensInArea(surface, chunkArea) RemoveInArea(surface, chunkArea, "tree") RemoveInArea(surface, chunkArea, "resource") RemoveInArea(surface, chunkArea, "cliff") -- This loop runs through each tile local grassTiles = {} local waterTiles = {} 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 ((i>-sizeTiles) and (i<(sizeTiles-1)) and (j>-sizeTiles) and (j<(sizeTiles-1))) then -- Fill all area with grass only table.insert(grassTiles, {name = "grass-1", position ={i,j}}) -- Create the spawn box walls if (j<(sizeTiles-1) and j>-sizeTiles) then -- Create horizontal sides of center spawn box if (((j>-sizeTiles and j<-(sizeTiles-4)) or (j<(sizeTiles-1) and j>(sizeTiles-5))) and (i<(sizeTiles-1) and i>-sizeTiles)) then if walls then CreateWall(surface, {i,j}) else table.insert(waterTiles, {name = "water", position ={i,j}}) end end -- Create vertical sides of center spawn box if ((i>-sizeTiles and i<-(sizeTiles-4)) or (i<(sizeTiles-1) and i>(sizeTiles-5))) then if walls then CreateWall(surface, {i,j}) else table.insert(waterTiles, {name = "water", position ={i,j}}) end end end end end end surface.set_tiles(grassTiles) surface.set_tiles(waterTiles) 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(global.welcome_msg) player.print(GAME_MODE_MSG) player.print(MODULES_ENABLED) end -- Remove decor to save on file size function UndecorateOnChunkGenerate(event) local surface = event.surface local chunkArea = event.area RemoveDecorationsArea(surface, chunkArea) RemoveFish(surface, chunkArea) end -- Give player items on respawn -- Intended to be the default behavior when not using separate spawns function PlayerRespawnItems(event) GivePlayerItems(game.players[event.player_index]) end function PlayerSpawnItems(event) GivePlayerStarterItems(game.players[event.player_index]) end -- Autofill softmod function Autofill(event) local player = game.players[event.player_index] local eventEntity = event.created_entity if (eventEntity.name == "gun-turret") then AutofillTurret(player, eventEntity) end if ((eventEntity.name == "car") or (eventEntity.name == "tank") or (eventEntity.name == "locomotive")) then AutoFillVehicle(player, eventEntity) end end