-- 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 = 450, maximal_height = 10, font_color = {r=1,g=1,b=1}, top_padding = 0, bottom_padding = 0 } my_note_style = { minimal_width = 450, maximal_height = 10, 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, maximal_height = 10, font_color = {r=1,g=0.1,b=0.1}, top_padding = 0, bottom_padding = 0 } my_spacer_style = { minimal_width = 450, maximal_width = 450, minimal_height = 20, maximal_height = 20, 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, maximal_height = 15 } my_player_list_style = { font = "default-semibold", minimal_width = 200, top_padding = 0, bottom_padding = 0, maximal_height = 15 } my_player_list_style_spacer = { maximal_height = 15 } my_color_red = {r=1,g=0.1,b=0.1} -------------------------------------------------------------------------------- -- 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 -- 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 -- 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} 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 -- 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 -- 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(player, safeDist) local safeArea = {left_top= {x=player.position.x-safeDist, y=player.position.y-safeDist}, right_bottom= {x=player.position.x+safeDist, y=player.position.y+safeDist}} for _, entity in pairs(player.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 entity.destroy() 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 ((pos.x - entity.position.x)^2 + (pos.y - entity.position.y)^2 < dist^2) then entity.destroy() 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() game.map_settings.enemy_evolution.time_factor=0 game.map_settings.enemy_evolution.destroy_factor = game.map_settings.enemy_evolution.destroy_factor / ENEMY_DESTROY_FACTOR_DIVISOR game.map_settings.enemy_evolution.pollution_factor = game.map_settings.enemy_evolution.pollution_factor / ENEMY_POLLUTION_FACTOR_DIVISOR game.map_settings.enemy_expansion.enabled = ENEMY_EXPANSION game.map_settings.enemy_expansion.min_base_spacing = 10 game.map_settings.enemy_expansion.max_expansion_distance = 10 game.map_settings.enemy_expansion.settler_group_min_size = 5 game.map_settings.enemy_expansion.settler_group_max_size = 20 -- game.map_settings.enemy_expansion.friendly_base_influence_radius = 4 -- game.map_settings.enemy_expansion.enemy_building_influence_radius = 4 game.map_settings.enemy_expansion.min_expansion_cooldown = TICKS_PER_MINUTE*30 game.map_settings.enemy_expansion.max_expansion_cooldown = TICKS_PER_MINUTE*120 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)..")" local text = scrollFrame.add{type="label", caption=caption_str, name=player.name.."_plist"} if (player.admin) then ApplyStyle(text, my_player_list_admin_style) else ApplyStyle(text, my_player_list_style) 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 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 (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 -------------------------------------------------------------------------------- -- Frontier style rocket silo stuff -------------------------------------------------------------------------------- -- This creates a random silo position, stored to global.siloPosition -- It uses the config setting SILO_CHUNK_DISTANCE and spawns the silo somewhere -- on a circle edge with radius using that distance. function SetRandomSiloPosition() if (global.siloPosition == nil) then -- Get an X,Y on a circle far away. local distX = math.random(0,SILO_CHUNK_DISTANCE_X) local distY = RandomNegPos() * math.floor(math.sqrt(SILO_CHUNK_DISTANCE_X^2 - distX^2)) local distX = RandomNegPos() * distX -- Set those values. local siloX = distX*CHUNK_SIZE + CHUNK_SIZE/2 local siloY = distY*CHUNK_SIZE + CHUNK_SIZE/2 global.siloPosition = {x = siloX, y = siloY} end end -- Sets the global.siloPosition var to the set in the config file function SetFixedSiloPosition() if (global.siloPosition == nil) then global.siloPosition = SILO_POSITION 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, {"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", position ={i,j}}) end end -- Create a circle of trees around the spawn point. if ((distVar < tileRadSqr-200) and (distVar > tileRadSqr-300)) then surface.create_entity({name="tree-01", 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, distVar2 * 0.707); -- 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", 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+400) and (distVar > tileRadSqr-500)) 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-500) and (distVar > tileRadSqr-1000)) then table.insert(waterTiles, {name = "grass", 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) 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 2 oil patches surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT, position={pos.x+START_RESOURCE_OIL_A_POS_X, pos.y+START_RESOURCE_OIL_A_POS_Y}}) surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT, position={pos.x+START_RESOURCE_OIL_B_POS_X, pos.y+START_RESOURCE_OIL_B_POS_Y}}) -- 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 -- Create 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, -- And spawns the basic resources as well function CreateSpawnAreas(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+5) RemoveInCircle(surface, chunkArea, "resource", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+5) 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+10) 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) GenerateStartingResources(surface, spawn.pos) end end end -------------------------------------------------------------------------------- -- Surface Generation Functions -------------------------------------------------------------------------------- -- To use RSO resources, we have to disable vanilla ore generation local surface_autoplace_none = { ["coal"]={ size="none" }, ["copper-ore"]={ size="none" }, ["iron-ore"]={ size="none" }, ["stone"]={ size="none" }, ["uranium-ore"]={ size="none" }, ["crude-oil"]={ size="none" }, ["enemy-base"]={ size="none" } } RSO_MODE = 1 VANILLA_MODE = 2 function CreateLobbySurface() local surface = game.create_surface("Lobby",{width = 1, height = 1}) surface.set_tiles({{name = "out-of-map",position = {1,1}}}) end function CreateGameSurface(mode) local mapSettings = game.surfaces["nauvis"].map_gen_settings if (mode == RSO_MODE) then mapSettings.terrain_segmentation=MAP_SETTINGS_RSO_TERRAIN_SEGMENTATION mapSettings.water=MAP_SETTINGS_RSO_WATER mapSettings.starting_area=MAP_SETTINGS_RSO_STARTING_AREA mapSettings.peaceful_mode=MAP_SETTINGS_RSO_PEACEFUL mapSettings.seed=math.random(999999999); mapSettings.autoplace_controls = { ["coal"]={ size="none" }, ["copper-ore"]={ size="none" }, ["iron-ore"]={ size="none" }, ["stone"]={ size="none" }, ["uranium-ore"]={ size="none" }, ["crude-oil"]={ size="none" }, ["enemy-base"]={ size="none" } } else mapSettings.terrain_segmentation="normal" mapSettings.water="normal" mapSettings.starting_area="normal" mapSettings.peaceful_mode=false mapSettings.seed=math.random(999999999); mapSettings.autoplace_controls = { ["coal"]={frequency="very-low", size= "normal", richness= "very-high"}, ["copper-ore"]={frequency= "very-low", size= "normal", richness= "very-high"}, ["crude-oil"]={frequency= "very-low", size= "normal", richness= "very-high"}, ["enemy-base"]={frequency= "very-low", size= "very-low", richness= "very-low"}, ["iron-ore"]={frequency= "very-low", size= "normal", richness= "very-high"}, ["stone"]={frequency= "very-low", size= "normal", richness= "very-high"}, ["uranium-ore"]={frequency= "very-low", size= "normal", richness= "very-high"} } 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) 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") -- 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 -- Fill all area with grass only table.insert(grassTiles, {name = "grass", position ={i,j}}) -- Create the spawn box walls if (j<15 and j>-16) then -- Create horizontal sides of center spawn box if (((j>-16 and j<-12) or (j<15 and j>11)) and (i<15 and i>-16)) then -- CreateWall(surface, {i,j}) table.insert(waterTiles, {name = "water", position ={i,j}}) end -- Create vertical sides of center spawn box if ((i>-16 and i<-12) or (i<15 and i>11)) then -- CreateWall(surface, {i,j}) table.insert(waterTiles, {name = "water", position ={i,j}}) end end end end surface.set_tiles(grassTiles) surface.set_tiles(waterTiles) end end function CreateHoldingPenGenerateChunk(event) CreateHoldingPen(event.surface, event.area) 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