From fe1bdbf61a8da873ade21267cafabd356e72a748 Mon Sep 17 00:00:00 2001 From: Oarcinae Date: Sat, 5 Aug 2017 09:47:46 -0400 Subject: [PATCH] Adding locale back in to main source, instead of a submodule. --- .gitmodules | 3 - locale | 1 - locale/LICENSE | 21 + locale/README.md | 18 + locale/frontier_silo.lua | 79 ++ locale/oarc_utils.lua | 1105 ++++++++++++++++++++++ locale/rso/drand.lua | 269 ++++++ locale/rso/metaball.lua | 104 ++ locale/rso/rso_config.lua | 55 ++ locale/rso/rso_control.lua | 1405 ++++++++++++++++++++++++++++ locale/rso/rso_resource_config.lua | 171 ++++ locale/tag.lua | 60 ++ 12 files changed, 3287 insertions(+), 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 locale create mode 100644 locale/LICENSE create mode 100644 locale/README.md create mode 100644 locale/frontier_silo.lua create mode 100644 locale/oarc_utils.lua create mode 100644 locale/rso/drand.lua create mode 100644 locale/rso/metaball.lua create mode 100644 locale/rso/rso_config.lua create mode 100644 locale/rso/rso_control.lua create mode 100644 locale/rso/rso_resource_config.lua create mode 100644 locale/tag.lua diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f698b4c..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "oarc_utils"] - path = locale - url = https://github.com/Oarcinae/FactorioUtils diff --git a/locale b/locale deleted file mode 160000 index a788fcc..0000000 --- a/locale +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a788fcc4ac079eee1c52b88cf79722b09bf97a96 diff --git a/locale/LICENSE b/locale/LICENSE new file mode 100644 index 0000000..cb1cae2 --- /dev/null +++ b/locale/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Oarcinae + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/locale/README.md b/locale/README.md new file mode 100644 index 0000000..c9901b7 --- /dev/null +++ b/locale/README.md @@ -0,0 +1,18 @@ +# FactorioUtils +My softmod utilities for factorio + +## Instructions +Include this repo inside a folder named "locale". This is the only folder that can be included in a scenario. + +## Note +This is meant to be used with my own scenarios. It's hacky but convenient for myself. + +## Credit + +RSO is not my own creation. It was done by Orzelek. I requested permission to include it in my scenario. + +https://mods.factorio.com/mods/orzelek/rso-mod + +Several other portions of the code (tags, frontier style rocket silo) have also been adapted from other scenario code. + +Credit to 3Ra as well: https://github.com/3RaGaming/3Ra-Enhanced-Vanilla \ No newline at end of file diff --git a/locale/frontier_silo.lua b/locale/frontier_silo.lua new file mode 100644 index 0000000..e214cac --- /dev/null +++ b/locale/frontier_silo.lua @@ -0,0 +1,79 @@ +-- frontier_silo.lua +-- Nov 2016 + +require("config") +require("locale/oarc_utils") + +-- Create a rocket silo +local function CreateRocketSilo(surface, chunkArea, force) + if CheckIfInArea(global.siloPosition, chunkArea) then + + -- Delete any entities beneat the silo? + for _, entity in pairs(surface.find_entities_filtered{area = {{global.siloPosition.x-5, global.siloPosition.y-6},{global.siloPosition.x+6, global.siloPosition.y+6}}}) do + entity.destroy() + end + + -- Set tiles below the silo + local tiles = {} + local i = 1 + for dx = -6,6 do + for dy = -7,6 do + tiles[i] = {name = "grass", position = {global.siloPosition.x+dx, global.siloPosition.y+dy}} + i=i+1 + end + end + surface.set_tiles(tiles, false) + tiles = {} + i = 1 + for dx = -5,5 do + for dy = -6,5 do + tiles[i] = {name = "concrete", position = {global.siloPosition.x+dx, global.siloPosition.y+dy}} + i=i+1 + end + end + surface.set_tiles(tiles, true) + + -- Create silo and assign to a force + local silo = surface.create_entity{name = "rocket-silo", position = {global.siloPosition.x+0.5, global.siloPosition.y}, force = force} + silo.destructible = false + silo.minable = false + + -- Make silo safe from being removed. + if ENABLE_REGROWTH then + OarcRegrowthOffLimits(global.siloPosition, 5) + end + end +end + +-- Generates the rocket silo during chunk generation event +-- Includes a crop circle +function GenerateRocketSiloChunk(event) + local surface = event.surface + local chunkArea = event.area + + local chunkAreaCenter = {x=chunkArea.left_top.x+(CHUNK_SIZE/2), + y=chunkArea.left_top.y+(CHUNK_SIZE/2)} + local safeArea = {left_top= + {x=global.siloPosition.x-150, + y=global.siloPosition.y-150}, + right_bottom= + {x=global.siloPosition.x+150, + y=global.siloPosition.y+150}} + + + -- Clear enemies directly next to the rocket + if CheckIfInArea(chunkAreaCenter,safeArea) then + for _, entity in pairs(surface.find_entities_filtered{area = chunkArea, force = "enemy"}) do + entity.destroy() + end + end + + -- Create rocket silo + CreateRocketSilo(surface, chunkArea, MAIN_FORCE) + CreateCropCircle(surface, global.siloPosition, chunkArea, 40) + +end + +function ChartRocketSiloArea(force, surface) + force.chart(surface, {{global.siloPosition.x-(CHUNK_SIZE*2), global.siloPosition.y-(CHUNK_SIZE*2)}, {global.siloPosition.x+(CHUNK_SIZE*2), global.siloPosition.y+(CHUNK_SIZE*2)}}) +end \ No newline at end of file diff --git a/locale/oarc_utils.lua b/locale/oarc_utils.lua new file mode 100644 index 0000000..03898a4 --- /dev/null +++ b/locale/oarc_utils.lua @@ -0,0 +1,1105 @@ +-- 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 +-------------------------------------------------------------------------------- + +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 = 200, + 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 + +-- 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 +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 text = scrollFrame.add{type="label", caption="("..formattime(player.online_time)..") "..player.name, 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, {"raw-wood", "coal", "solid-fuel", "rocket-fuel"}, 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-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" } + } + 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 diff --git a/locale/rso/drand.lua b/locale/rso/drand.lua new file mode 100644 index 0000000..60db0ad --- /dev/null +++ b/locale/rso/drand.lua @@ -0,0 +1,269 @@ +--[[------------------------------------ +RandomLua v0.3.1 +Pure Lua Pseudo-Random Numbers Generator +Under the MIT license. +copyright(c) 2011 linux-man +--]]------------------------------------ + +local _M = {} +local mod = math.fmod +local floor = math.floor +local abs = math.abs + +local function normalize(n) --keep numbers at (positive) 32 bits + return n % 0x80000000 +end + +local function bit_and(a, b) + local r = 0 + local m = 0 + for m = 0, 31 do + if (a % 2 == 1) and (b % 2 == 1) then r = r + 2^m end + if a % 2 ~= 0 then a = a - 1 end + if b % 2 ~= 0 then b = b - 1 end + a = a / 2 b = b / 2 + end + return normalize(r) +end + +local function bit_or(a, b) + local r = 0 + local m = 0 + for m = 0, 31 do + if (a % 2 == 1) or (b % 2 == 1) then r = r + 2^m end + if a % 2 ~= 0 then a = a - 1 end + if b % 2 ~= 0 then b = b - 1 end + a = a / 2 b = b / 2 + end + return normalize(r) +end + +local function bit_xor(a, b) + local r = 0 + local m = 0 + for m = 0, 31 do + if a % 2 ~= b % 2 then r = r + 2^m end + if a % 2 ~= 0 then a = a - 1 end + if b % 2 ~= 0 then b = b - 1 end + a = a / 2 b = b / 2 + end + return normalize(r) +end + +local function seed() + --return normalize(tonumber(tostring(os.time()):reverse())) + return normalize(os.time()) +end + +--Mersenne twister +mersenne_twister = {} +mersenne_twister.__index = mersenne_twister + +function mersenne_twister:randomseed(s) + if not s then s = seed() end + self.mt[0] = normalize(s) + for i = 1, 623 do + self.mt[i] = normalize(0x6c078965 * bit_xor(self.mt[i-1], floor(self.mt[i-1] / 0x40000000)) + i) + end +end + +function mersenne_twister:random(a, b) + local y + if self.index == 0 then + for i = 0, 623 do + --y = bit_or(floor(self.mt[i] / 0x80000000) * 0x80000000, self.mt[(i + 1) % 624] % 0x80000000) + y = self.mt[(i + 1) % 624] % 0x80000000 + self.mt[i] = bit_xor(self.mt[(i + 397) % 624], floor(y / 2)) + if y % 2 ~= 0 then self.mt[i] = bit_xor(self.mt[i], 0x9908b0df) end + end + end + y = self.mt[self.index] + y = bit_xor(y, floor(y / 0x800)) + y = bit_xor(y, bit_and(normalize(y * 0x80), 0x9d2c5680)) + y = bit_xor(y, bit_and(normalize(y * 0x8000), 0xefc60000)) + y = bit_xor(y, floor(y / 0x40000)) + self.index = (self.index + 1) % 624 + if not a then return y / 0x80000000 + elseif not b then + if a == 0 then return y + else return 1 + (y % a) + end + else + return a + (y % (b - a + 1)) + end +end + +function _M.twister(s) + local temp = {} + setmetatable(temp, mersenne_twister) + temp.mt = {} + temp.index = 0 + temp:randomseed(s) + return temp +end + +--Linear Congruential Generator +linear_congruential_generator = {} +linear_congruential_generator.__index = linear_congruential_generator + +function linear_congruential_generator:random(a, b) + local y = (self.a * self.x + self.c) % self.m + self.x = y + if not a then return y / 0x10000 + elseif not b then + if a == 0 then return y + else return 1 + (y % a) end + else + return a + (y % (b - a + 1)) + end +end + +function linear_congruential_generator:randomseed(s) + if not s then s = seed() end + self.x = normalize(s) +end + +function _M.lcg(s, r) + local temp = {} + setmetatable(temp, linear_congruential_generator) + temp.a, temp.c, temp.m = 1103515245, 12345, 0x10000 --from Ansi C + if r then + if r == 'nr' then temp.a, temp.c, temp.m = 1664525, 1013904223, 0x10000 --from Numerical Recipes. + elseif r == 'mvc' then temp.a, temp.c, temp.m = 214013, 2531011, 0x10000 end--from MVC + end + temp:randomseed(s) + return temp +end + +-- Multiply-with-carry +multiply_with_carry = {} +multiply_with_carry.__index = multiply_with_carry + +function multiply_with_carry:random(a, b) + local m = self.m + local t = self.a * self.x + self.c + local y = t % m + self.x = y + self.c = floor(t / m) + if not a then return y / 0x10000 + elseif not b then + if a == 0 then return y + else return 1 + (y % a) end + else + local diff = 0 + if a == b then return a end + if a < 0 then + diff = abs(a) + a = a + diff + b = b + diff + end + return a + (y % (b - a + 1)) - diff + end +end + +function multiply_with_carry:randomseed(s) + if not s then s = seed() end + self.c = self.ic + self.x = normalize(s) +end + +function _M.mwc(s, r) + local temp = {} + setmetatable(temp, multiply_with_carry) + temp.a, temp.c, temp.m = 1103515245, 12345, 0x10000 --from Ansi C + if r then + if r == 'nr' then temp.a, temp.c, temp.m = 1664525, 1013904223, 0x10000 --from Numerical Recipes. + elseif r == 'mvc' then temp.a, temp.c, temp.m = 214013, 2531011, 0x10000 end--from MVC + end + temp.ic = temp.c + temp:randomseed(s) + return temp +end + +function _M.mwvc(s) + return _M.mwc(s, 'mvc') +end + +local B = 0x10000 + +-- rough adaptation of Knuth float generator +function _M.krandom( seedobj, fVal1, fVal2 ) + local ma = seedobj.ma + local seed = seedobj.seed + local mj, mk + if seed < 0 or not ma then + ma = {} + seedobj.ma = ma + mj = normalize( seed ) + mj = mod( mj, B ) + ma[55] = mj + mk = 1 + for i = 1, 54 do + local ii = mod( 21 * i, 55 ) + ma[ii] = mk + mk = mj - mk + if mk < 0 then mk = mk + B end + mj = ma[ii] + end + for k = 1, 4 do + for i = 1, 55 do + ma[i] = ma[i] - ma[ 1 + mod( i + 30, 55) ] + if ma[i] < 0 then ma[i] = ma[i] + B end + end + end + seedobj.inext = 0 + seedobj.inextp = 31 + seedobj.seed = 1 + end -- if + local inext = seedobj.inext + local inextp = seedobj.inextp + inext = inext + 1 + if inext == 56 then inext = 1 end + seedobj.inext = inext + inextp = inextp + 1 + if inextp == 56 then inextp = 1 end + seedobj.inextp = inextp + mj = ma[ inext ] - ma[ inextp ] + if mj < 0 then mj = mj + B end + ma[ inext ] = mj + local temp_rand = mj / B + if fVal2 then + return floor( fVal1 + 0.5 + temp_rand * ( fVal2 - fVal1 ) ) + elseif fVal1 then + return floor( temp_rand * fVal1 ) + 1 + else + return temp_rand + end +end + +-- Sys rand +sys_rand = {} +sys_rand.__index = sys_rand +function sys_rand:random(a, b) + local diff = 0 + if a and b and a == b then math.random(); return a end + if a and b then + if a < 0 then + diff = abs(a) + a = a + diff + b = b + diff + end + return math.random(a, b) - diff + end + if a and a == 0 then return floor(math.random() * 0x10000) end + if a then return math.random(a) end + return math.random() +end + +function sys_rand:randomseed(s) + -- ignore + return +end + +function _M.sys_rand(s) + local temp = {} + setmetatable(temp, sys_rand) + return temp +end + +return _M \ No newline at end of file diff --git a/locale/rso/metaball.lua b/locale/rso/metaball.lua new file mode 100644 index 0000000..09b207c --- /dev/null +++ b/locale/rso/metaball.lua @@ -0,0 +1,104 @@ +--[[-- +Metaball implementation for LUA by Dark +For bruteforce usage, nor efficient nor fast + +Force scales to from inf to 1 at R +--]]-- +local _M = {} +local sqrt = math.sqrt +local cos = math.cos +local sin = math.sin +local abs = math.abs +local zero_value = 0x80000000 + +--Classic ball +local MetaBall = {x=0, y=0, radius=0, goo=1, type="MetaBall"} +MetaBall.__index = MetaBall +_M.MetaBall=MetaBall + +function MetaBall:new(x, y, radius, goo) + goo = goo or 1 + return setmetatable({x=x, y=y, radius=radius, goo=goo}, MetaBall) +end + +function MetaBall:force(x, y) + --Calculate force at point x y + local force = sqrt( (x - self.x)^2 + (y - self.y)^2 ) + if force == 0 then return zero_value end + return (self.radius / force)^self.goo +end + +--Ellipse +local MetaEllipse = {x=0, y=0, radius=0, angle=0, x_scale=1, y_scale=1, type="MetaEllipse"} +MetaEllipse.__index = MetaEllipse +_M.MetaEllipse=MetaEllipse + +function MetaEllipse:new(x, y, radius, angle, x_scale, y_scale, goo) + angle = angle or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + goo = goo or 1 + cosa = cos(angle) + sina = sin(angle) + return setmetatable({x=x, y=y, radius=radius, angle=angle, x_scale=x_scale, y_scale=y_scale, goo=goo, cosa=cosa, sina=sina}, MetaEllipse) +end + +function MetaEllipse:force(x, y) + --Calculate force at point x y + local force = sqrt( (( (x - self.x)*self.cosa + (y - self.y)*self.sina )^2)/(self.x_scale) + + (( (y - self.y)*self.cosa - (x - self.x)*self.sina )^2)/(self.y_scale) ) + if force == 0 then return zero_value end + return (self.radius / force)^self.goo +end + +--SquareBalls +local MetaSquare = {x=0, y=0, radius=0, angle=0, x_scale=1, y_scale=1, type="MetaSquare"} +MetaSquare.__index = MetaSquare +_M.MetaSquare=MetaSquare + +function MetaSquare:new(x, y, radius, angle, x_scale, y_scale, goo) + angle = angle or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + goo = goo or 1 + cosa = cos(angle) + sina = sin(angle) + return setmetatable({x=x, y=y, radius=radius, angle=angle, x_scale=x_scale, y_scale=y_scale, goo=goo, cosa=cosa, sina=sina}, MetaSquare) +end + +function MetaSquare:force(x, y) + --Calculate force at point x y + local force = ( abs( (x - self.x)*self.cosa + (y - self.y)*self.sina )/self.x_scale + + abs( (y - self.y)*self.cosa - (x - self.x)*self.sina )/self.y_scale ) + if force == 0 then return zero_value end + return (self.radius / force)^self.goo +end + +--Donuts +local MetaDonut = {x=0, y=0, radius=0, angle=0, x_scale=1, y_scale=1, type="MetaDonut"} +MetaDonut.__index = MetaDonut +_M.MetaDonut=MetaDonut + +function MetaDonut:new(x, y, out_r, int_r, angle, x_scale, y_scale, goo) + angle = angle or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + goo = goo or 1 + cosa = cos(angle) + sina = sin(angle) + if int_r >= out_r then error("int_r >= out_r ("..int_r.." > "..out_r); return; end + local radius = out_r--(out_r - int_r)*0.5 + local radius2 = int_r--(radius2 + radius)*0.5 + return setmetatable({x=x, y=y, radius=radius, radius2=radius2, x_scale=x_scale, y_scale=y_scale, goo=goo, cosa=cosa, sina=sina}, MetaDonut) +end + +function MetaDonut:force(x, y) + --Calculate force at point x y + local force = abs(self.radius - sqrt( (( (x - self.x)*self.cosa + (y - self.y)*self.sina )^2)/(self.x_scale) + + (( (y - self.y)*self.cosa - (x - self.x)*self.sina )^2)/(self.y_scale) )) + if force == 0 then return zero_value end + return (self.radius2 / force)^self.goo + +end + +return _M \ No newline at end of file diff --git a/locale/rso/rso_config.lua b/locale/rso/rso_config.lua new file mode 100644 index 0000000..21e87a0 --- /dev/null +++ b/locale/rso/rso_config.lua @@ -0,0 +1,55 @@ +debug_enabled = false + +region_size = 10 -- alternative mean to control how further away resources would be, default - 256 tiles or 8 chunks + -- each region is region_size*region_size chunks + -- each chunk is 32*32 tiles + +use_donut_shapes = false -- setting this to false will remove donuts from possible resource layouts + +starting_area_size = 0 -- starting area in regions, safe from random nonsense + +absolute_resource_chance = 0.50 -- chance to spawn an resource in a region +starting_richness_mult = 1 -- multiply starting area richness for resources +global_richness_mult = 1 -- multiply richness for all resources except starting area +global_size_mult = 1 -- multiply size for all ores, doesn't affect starting area + +absolute_enemy_chance = 3 -- chance to spawn enemies per sector (can be more then one base if spawned) +enemy_base_size_multiplier = 1 -- all base sizes will be multiplied by this - larger number means bigger bases + +multi_resource_active = false -- global switch for multi resource chances +multi_resource_richness_factor = 0.60 -- any additional resource is multiplied by this value times resources-1 +multi_resource_size_factor = 0.90 +multi_resource_chance_diminish = 0.6 -- diminishing effect factor on multi_resource_chance + +min_amount=250 -- default value for minimum amount of resource in single pile + +richness_distance_factor= 1 -- exponent for richness distance factor calculation +fluid_richness_distance_factor = 0.8 -- exponent for richness distance factor calculation for fluids +size_distance_factor=0.15 -- exponent for size distance factor calculation + +deterministic = true -- set to false to use system for all decisions math.random + +-- mode is no longer used by generation process - it autodetects endless resources +-- endless_resource_mode = false -- if true, the size of each resource is modified by the following modifier. Use with the endless resources mod. +endless_resource_mode_sizeModifier = 0.80 + +-- This setting isn't used anywhere in the soft mod version of RSO -- OARC +-- Just set it from Oarc's config.lua (look for ENEMY_EXPANSION) +-- disableEnemyExpansion = false -- allows for disabling of in-game biter base building + +use_RSO_biter_spawning = true -- enables spawning of biters controlled by RSO mod - less enemies around with more space between bases +use_vanilla_biter_spawning = false -- enables using of vanilla spawning + +biter_ratio_segment=3 --the ratio components determining how many biters to spitters will be spawned +spitter_ratio_segment=1 --eg. 1 and 1 -> equal number of biters and spitters, 10 and 1 -> 10 times as many biters to spitters + +useEnemiesInPeaceMod = false -- additional override for peace mod detection - when set to true it will spawn enemies normally, needs to have enemies enabled in peace mod + +-- Always leave this setting to true in this soft mod scenario version! -- OARC +ignoreMapGenSettings = true -- stops the default behaviour of reading map gen settings + +fluidResourcesFactor = 20 -- temporary factor for calculation of resource %-ages for fluids + -- +useResourceCollisionDetection = true -- enables avoidace calculations to reduce ores overlaping of each other +resourceCollisionDetectionRatio = 0.8 -- at least this much of ore field needs to be placable to spawn it +resourceCollisionFieldSkip = true -- determines if ore field should be skipped completely if placement based on ratio failed diff --git a/locale/rso/rso_control.lua b/locale/rso/rso_control.lua new file mode 100644 index 0000000..382e3d0 --- /dev/null +++ b/locale/rso/rso_control.lua @@ -0,0 +1,1405 @@ +require("locale/rso/rso_config") +require("util") +require("locale/rso/rso_resource_config") +require("locale/oarc_utils") +require("config") + +local MB=require("locale/rso/metaball") +local drand = require("locale/rso/drand") +local rng = drand.mwvc +if not deterministic then rng = drand.sys_rand end + +-- math shortcuts +local floor = math.floor +local abs = math.abs +local cos = math.cos +local sin = math.sin +local pi = math.pi +local max = math.max + +local function round(value) + return math.floor(value + 0.5) +end + +local function debug(str) + if debug_enabled then + game.players[1].print(str) + end +end + +-- constants +local CHUNK_SIZE = 32 +local REGION_TILE_SIZE = CHUNK_SIZE*region_size +local MIN_BALL_DISTANCE = CHUNK_SIZE/6 +local P_BALL_SIZE_FACTOR = 0.7 +local N_BALL_SIZE_FACTOR = 0.95 +local NEGATIVE_MODIFICATOR = 123456 + +local meta_shapes = nil + +if use_donut_shapes then + meta_shapes = {MB.MetaEllipse, MB.MetaSquare, MB.MetaDonut} +else + meta_shapes = {MB.MetaEllipse, MB.MetaSquare} +end + +-- local globals +local index_is_built = false +local max_allotment = 0 +local rgen = nil +local distance = util.distance +local spawner_probability_edge = 0 -- below this value a biter spawner, above/equal this value a spitter spawner +local invalidResources = {} +local config = nil +local configIndexed = nil + +-- map gen settings mapping + +local startingAreaMultiplier = +{ + none = 0, + ["very-low"] = 0.25, + low = 0.5, + normal = 1, + high = 1.5, + ["very-high"] = 2, +} + +local frequencyAllotmentMultiplier = +{ + ["very-low"] = 0.5, + low = 0.75, + normal = 1, + high = 1.5, + ["very-high"] = 2, +} + +local sizeMultiplier = +{ + none = 0, + ["very-low"] = 0.5, + low = 0.75, + normal = 1, + high = 1.25, + ["very-high"] = 1.5, +} + +local richnessMultiplier = +{ + ["very-low"] = 0.125, + low = 0.25, + normal = 1, + high = 2, + ["very-high"] = 4, +} + +local entityFrequencyMultiplier = +{ + ["very-low"] = 0.25, + low = 0.5, + normal = 1, + high = 2, + ["very-high"] = 4, +} + +local entitySizeMultiplier = +{ + none = 0, + ["very-low"] = 0.5, + low = 0.75, + normal = 1, + high = 2, + ["very-high"] = 4, +} + +--[[ HELPER METHODS ]]-- + +local function normalize(n) --keep numbers at (positive) 32 bits + return floor(n) % 0x80000000 +end + +local function bearing(origin, dest) + -- finds relative angle + local xd = dest.x - origin.x + local yd = dest.y - origin.y + return math.atan2(xd, yd); +end + +local function str2num(s) + local num = 0 + for i=1,s:len() do + num=num + (s:byte(i) - 33)*i + end + return num +end + +local function mult_for_pos(pos) + local num = 0 + local x = pos.x + local y = pos.y + + if x == 0 then x = 0.5 end + if y == 0 then y = 0.5 end + if x < 0 then + x = abs(x) + NEGATIVE_MODIFICATOR + end + if y < 0 then + y = abs(y) + NEGATIVE_MODIFICATOR + end + + return drand.lcg(y, 'mvc'):random(0)*drand.lcg(x, 'nr'):random(0) +end + +local function rng_for_reg_pos(pos) + local rgen = rng(normalize(global.seed*mult_for_pos(pos))) + rgen:random() + rgen:random() + rgen:random() + return rgen +end + +local function rng_restricted_angle(restrictions) + local rng = rgen:random() + local x_scale, y_scale + local deformX = rgen:random() * 2 - 1 + local deformY = rgen:random() * 2 - 1 + + if restrictions=='xy' then + y_scale=1.0 + deformY*0.5 + x_scale=1.0 + deformX*0.5 + angle = rng*pi*2 + elseif restrictions=='x' then + y_scale=1.0 + deformY*0.6 + x_scale=1.0 + deformX*0.6 + angle = rng*pi/2 - pi/4 + elseif restrictions=='y' then + y_scale=1.0 + deformY*0.6 + x_scale=1.0 + deformX*0.6 + angle = rng*pi/2 + pi/2 + else + y_scale=1.0 + deformY*0.3 + x_scale=1.0 + deformX*0.3 + angle = rng*pi*2 + end + + return angle, x_scale, y_scale +end + +local function vary_by_percentage(x, p) + return x + (0.5 - rgen:random())*2*x*p +end + + +local function remove_trees(surface, x, y, x_size, y_size ) + local bb={{x - x_size, y - y_size}, {x + x_size, y + y_size}} + for _, entity in pairs(surface.find_entities_filtered{area = bb, type="tree"}) do + if entity.valid then + entity.destroy() + end + end +end + +local function removeDecorations(surface, x, y, width, height ) + local bb={{x, y}, {x + width, y + height}} + for _, entity in pairs(surface.find_entities_filtered{area = bb, type="decorative"}) do + if entity.valid then + entity.destroy() + end + end +end + +local function find_intersection(surface, x, y) + -- try to get position in between of valid chunks by probing map + -- this may breaks determinism of generation, but so far it returned on first if + local gt = surface.get_tile + local restriction = '' + if gt(x + CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x + CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid then + restriction = 'xy' + elseif gt(x + CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid and gt(x + CHUNK_SIZE*2, y).valid and gt(x, y + CHUNK_SIZE*2).valid then + x=x + CHUNK_SIZE/2 + y=y + CHUNK_SIZE/2 + restriction = 'xy' + elseif gt(x + CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x + CHUNK_SIZE*2, y).valid and gt(x, y - CHUNK_SIZE*2).valid then + x=x + CHUNK_SIZE/2 + y=y - CHUNK_SIZE/2 + restriction = 'xy' + elseif gt(x - CHUNK_SIZE*2, y + CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y).valid and gt(x, y + CHUNK_SIZE*2).valid then + x=x - CHUNK_SIZE/2 + y=y + CHUNK_SIZE/2 + restriction = 'xy' + elseif gt(x - CHUNK_SIZE*2, y - CHUNK_SIZE*2).valid and gt(x - CHUNK_SIZE*2, y).valid and gt(x, y - CHUNK_SIZE*2).valid then + x=x - CHUNK_SIZE/2 + y=y - CHUNK_SIZE/2 + restriction = 'xy' + elseif gt(x + CHUNK_SIZE*2, y).valid then + x=x + CHUNK_SIZE/2 + restriction = 'x' + elseif gt(x - CHUNK_SIZE*2, y).valid then + x=x - CHUNK_SIZE/2 + restriction = 'x' + elseif gt(x, y + CHUNK_SIZE*2).valid then + y=y + CHUNK_SIZE/2 + restriction = 'y' + elseif gt(x, y - CHUNK_SIZE*2).valid then + y=y - CHUNK_SIZE/2 + restriction = 'y' + end + return x, y, restriction +end + +local function find_random_chunk(r_x, r_y) + local offset_x=rgen:random(region_size)-1 + local offset_y=rgen:random(region_size)-1 + local c_x=r_x*REGION_TILE_SIZE + offset_x*CHUNK_SIZE + local c_y=r_y*REGION_TILE_SIZE + offset_y*CHUNK_SIZE + return c_x, c_y +end + +local function is_same_region(c_x1, c_y1, c_x2, c_y2) + if not floor(c_x1/REGION_TILE_SIZE) == floor(c_x2/REGION_TILE_SIZE) then + return false + end + if not floor(c_y1/REGION_TILE_SIZE) == floor(c_y2/REGION_TILE_SIZE) then + return false + end + return true +end + +local function find_random_neighbour_chunk(ocx, ocy) + -- somewhat bruteforce and unoptimized + local x_dir = rgen:random(-1,1) + local y_dir = rgen:random(-1,1) + local ncx = ocx + x_dir*CHUNK_SIZE + local ncy = ocy + y_dir*CHUNK_SIZE + if is_same_region(ncx, ncy, ocx, ocy) then + return ncx, ncy + end + + ncx = ocx - x_dir*CHUNK_SIZE + ncy = ocy - y_dir*CHUNK_SIZE + if is_same_region(ncx, ncy, ocx, ocy) then + return ncx, ncy + end + + ncx = ocx - x_dir*CHUNK_SIZE + if is_same_region(ncx, ocy, ocx, ocy) then + return ncx, ocy + end + + ncy = ocy - y_dir*CHUNK_SIZE + if is_same_region(ocx, ncy, ocx, ocy) then + return ocx, ncy + end + + return ocx, ocy +end + +local function isInStartingArea( regionX, regionY ) + + for idx, pos in pairs( global.startingAreas ) do + + local adjustedX = regionX - pos.x / REGION_TILE_SIZE + local adjustedY = regionY - pos.y / REGION_TILE_SIZE + if ((adjustedX * adjustedX + adjustedY * adjustedY) <= starting_area_size * starting_area_size) then + return true + end + end + + return false +end + +-- OARC SPECIFIC FUNCTION -- +-- Checks if a point is near a spawn area +local function isNearOarcSpawn(pointPos) + + if (global.uniqueSpawns) and (ENFORCE_LAND_AREA_TILE_DIST) then + for name,spawn in pairs(global.uniqueSpawns) do + local clearArea = GetAreaFromPointAndDistance(spawn.pos, + (ENFORCE_LAND_AREA_TILE_DIST+2*CHUNK_SIZE)) + if (CheckIfInArea(pointPos,clearArea)) then + return true + end + end + end + + return false +end + +-- modifies the resource size - only used in endless_resource_mode +local function modify_resource_size(resourceName, resourceSize, startingArea) + + if not startingArea then + resourceSize = math.ceil(resourceSize * global_size_mult) + end + + resourceEntity = game.entity_prototypes[resourceName] + if resourceEntity and resourceEntity.infinite_resource then + + newResourceSize = resourceSize * endless_resource_mode_sizeModifier + + -- make sure it's still an integer + newResourceSize = math.ceil(newResourceSize) + -- make sure it's not 0 + if newResourceSize == 0 then newResourceSize = 1 end + return newResourceSize + else + return resourceSize + end +end + +--[[ SPAWN METHODS ]]-- + +local locationOrder = +{ + { x = 0, y = 0 }, + { x = -1, y = 0 }, + { x = 1, y = 0 }, + { x = 0, y = -1 }, + { x = 0, y = 1 }, + { x = -1, y = -1 }, + { x = 1, y = -1 }, + { x = -1, y = 1 }, + { x = 1, y = 1 } +} + +--[[ entity-field ]]-- +local function spawn_resource_ore(surface, rname, pos, size, richness, startingArea, restrictions) + -- blob generator, centered at pos, size controls blob diameter + restrictions = restrictions or '' + debug("Entering spawn_resource_ore "..rname.." at:"..pos.x..","..pos.y.." size:"..size.." richness:"..richness.." isStart:"..tostring(startingArea).." restrictions:"..restrictions) + + size = modify_resource_size(rname, size, startingArea) + local radius = size / 2 -- to radius + + local p_balls={} + local n_balls={} + local MIN_BALL_DISTANCE = math.min(MIN_BALL_DISTANCE, radius/2) + + local maxPradius = 0 + local outside = { xmin = 1e10, xmax = -1e10, ymin = 1e10, ymax = -1e10 } + local inside = { xmin = 1e10, xmax = -1e10, ymin = 1e10, ymax = -1e10 } + + local function adjustRadius(radius, scaleX, scaleY, up) + return radius + end + + local function updateRect(rect, x, y, radius) + rect.xmin = math.min(rect.xmin, x - radius) + rect.xmax = math.max(rect.xmax, x + radius) + rect.ymin = math.min(rect.ymin, y - radius) + rect.ymax = math.max(rect.ymax, y + radius) + end + + local function updateRects(x, y, radius, scaleX, scaleY) + local adjustedRadius = adjustRadius(radius, scaleX, scaleY, true) + local radiusMax = adjustedRadius * 3 -- arbitrary multiplier - needs to be big enough to not cut any metaballs + updateRect(outside, x, y, radiusMax) + updateRect(inside, x, y, adjustedRadius) + end + + local function generate_p_ball() + local angle, x_scale, y_scale, x, y, b_radius, shape + angle, x_scale, y_scale=rng_restricted_angle(restrictions) + local dev = radius / 2 + rgen:random() * radius / 4--math.min(CHUNK_SIZE/3, radius*1.5) + local dev_x, dev_y = pos.x, pos.y + x = rgen:random(-dev, dev)+dev_x + y = rgen:random(-dev, dev)+dev_y + if p_balls[#p_balls] and distance(p_balls[#p_balls], {x=x, y=y}) < MIN_BALL_DISTANCE then + local new_angle = bearing(p_balls[#p_balls], {x=x, y=y}) + debug("Move ball old xy @ "..x..","..y) + x=(cos(new_angle)*MIN_BALL_DISTANCE) + x + y=(sin(new_angle)*MIN_BALL_DISTANCE) + y + debug("Move ball new xy @ "..x..","..y) + end + + if #p_balls == 0 then + b_radius = ( 3 * radius / 4 + rgen:random() * radius / 4) -- * (P_BALL_SIZE_FACTOR^#p_balls) + else + b_radius = ( radius / 4 + rgen:random() * radius / 2) -- * (P_BALL_SIZE_FACTOR^#p_balls) + end + + + if #p_balls > 0 then + local tempRect = table.deepcopy(inside) + updateRect(tempRect, x, y, adjustRadius(b_radius, x_scale, y_scale)) + local rectSize = math.max(tempRect.xmax - tempRect.xmin, tempRect.ymax - tempRect.ymin) + local targetSize = size * 1.25 + debug("Rect size "..rectSize.." targetSize "..targetSize) + if rectSize > targetSize then + local widthLeft = (targetSize - (inside.xmax - inside.xmin)) + local heightLeft = (targetSize - (inside.ymax - inside.ymin)) + local widthMod = math.min(x - inside.xmin, inside.xmax - x) + local heightMod = math.min(y - inside.ymin, inside.ymax - y) + local radiusBackup = b_radius + b_radius = math.min(widthLeft + widthMod, heightLeft + heightMod) + b_radius = adjustRadius(b_radius, x_scale, y_scale, false) + debug("Reduced ball radius from "..radiusBackup.." to "..b_radius.." widthLeft:"..widthLeft.." heightLeft:"..heightLeft.." widthMod:"..widthMod.." heightMod:"..heightMod) + end + end + + if b_radius < 2 and #p_balls == 0 then + b_radius = 2 + end + + if b_radius > 0 then + + maxPradius = math.max(maxPradius, b_radius) + shape = meta_shapes[rgen:random(1,#meta_shapes)] + local radiusText = "" + if shape.type == "MetaDonut" then + local inRadius = b_radius / 4 + b_radius / 2 * rgen:random() + radiusText = " inRadius:"..inRadius + p_balls[#p_balls+1] = shape:new(x, y, b_radius, inRadius, angle, x_scale, y_scale, 1.1) + else + p_balls[#p_balls+1] = shape:new(x, y, b_radius, angle, x_scale, y_scale, 1.1) + end + updateRects(x, y, b_radius, x_scale, y_scale) + + debug("P+Ball "..shape.type.." @ "..x..","..y.." radius: "..b_radius..radiusText.." angle: "..math.deg(angle).." scale: "..x_scale..", "..y_scale) + end + end + + local function generate_n_ball(i) + local angle, x_scale, y_scale, x, y, b_radius, shape + angle, x_scale, y_scale=rng_restricted_angle('xy') + if p_balls[i] then + local new_angle = p_balls[i].angle + pi*rgen:random(0,1) + (rgen:random()-0.5)*pi/2 + local dist = p_balls[i].radius + x=(cos(new_angle)*dist) + p_balls[i].x + y=(sin(new_angle)*dist) + p_balls[i].y + angle = p_balls[i].angle + pi/2 + (rgen:random()-0.5)*pi*2/3 + else + x = rgen:random(-radius, radius)+pos.x + y = rgen:random(-radius, radius)+pos.y + end + + if p_balls[i] then + b_radius = (p_balls[i].radius / 4 + rgen:random() * p_balls[i].radius / 2) -- * (N_BALL_SIZE_FACTOR^#n_balls) + else + b_radius = (radius / 4 + rgen:random() * radius / 2) -- * (N_BALL_SIZE_FACTOR^#n_balls) + end + + if b_radius < 1 then + b_radius = 1 + end + + shape = meta_shapes[rgen:random(1,#meta_shapes)] + local radiusText = "" + if shape.type == "MetaDonut" then + local inRadius = b_radius / 4 + b_radius / 2 * rgen:random() + radiusText = " inRadius:"..inRadius + n_balls[#n_balls+1] = shape:new(x, y, b_radius, inRadius, angle, x_scale, y_scale, 1.15) + else + n_balls[#n_balls+1] = shape:new(x, y, b_radius, angle, x_scale, y_scale, 1.15) + end + -- updateRects(x, y, b_radius, x_scale, y_scale) -- should not be needed here - only positive ball can generate ore + debug("N-Ball "..shape.type.." @ "..x..","..y.." radius: "..b_radius..radiusText.." angle: "..math.deg(angle).." scale: "..x_scale..", "..y_scale) + end + + local function calculate_force(x,y) + local p_force = 0 + local n_force = 0 + for _,ball in ipairs(p_balls) do + p_force = p_force + ball:force(x,y) + end + for _,ball in ipairs(n_balls) do + n_force = n_force + ball:force(x,y) + end + local totalForce = 0 + if p_force > n_force then + totalForce = 1 - 1/(p_force - n_force) + end + --debug("Force at "..x..","..y.." p:"..p_force.." n:"..n_force.." result:"..totalForce) + --return (1 - 1/p_force) - n_force + return totalForce + end + + local max_p_balls = 2 + local min_amount = config[rname].min_amount or min_amount + if restrictions == 'xy' then + max_p_balls = 3 + end + + radius = math.min(radius, 2*CHUNK_SIZE) + + local force + -- generate blobs + for i=1,max_p_balls do + generate_p_ball() + end + + for i=1,rgen:random(1, #p_balls) do + generate_n_ball(i) + end + + + local _total = 0 + local oreLocations = {} + local forceTotal = 0 + + -- fill the map + for y=outside.ymin, outside.ymax do + + for x=outside.xmin, outside.xmax do + force = calculate_force(x, y) + if force > 0 then + oreLocations[#oreLocations + 1] = {x = x, y = y, force = force, valid = false} + forceTotal = forceTotal + force + end + end + end + + local validCount, resOffsetX, resOffsetY, ratio + + for _,locationOffset in ipairs(locationOrder) do + validCount = 0 + resOffsetX = locationOffset.x * CHUNK_SIZE + resOffsetY = locationOffset.y * CHUNK_SIZE + + for _, location in ipairs(oreLocations) do + + local newX = location.x + resOffsetX + local newY = location.y + resOffsetY + location.valid = false + if surface.can_place_entity{name = rname, position = {x = newX,y = newY}} then + location.valid = true + validCount = validCount + 1 + end + end + + ratio = 0 + + if validCount > 0 then + ratio = validCount / #oreLocations + end + + debug("Valid ratio ".. ratio) + + if not useResourceCollisionDetection then + break + end + + if ratio > resourceCollisionDetectionRatio then + break + elseif resourceCollisionFieldSkip then -- in case no valid ratio was found we skip the field completely + validCount = 0 + end + end + + if validCount > 0 then + local rectSize = ((inside.xmax - inside.xmin) + (inside.ymax - inside.ymin)) / 2 + + local sizeMultiplier = rectSize ^ 0.6 + local minSize = richness * 5 * sizeMultiplier + local maxSize = richness * 10 * sizeMultiplier + local approxDepositSize = rgen:random(minSize, maxSize) + + approxDepositSize = approxDepositSize - validCount * min_amount + + if approxDepositSize < 0 then + approxDepositSize = 100 * validCount + end + + local forceFactor = approxDepositSize / forceTotal + + -- don't create very dense resources in starting area - another field will be generated + if startingArea and forceFactor > 4000 then + forceFactor = rgen:random(3000, 4000) + end + + debug( "Force total:"..forceTotal.." sizeMin:"..minSize.." sizeMax:"..maxSize.." factor:"..forceFactor.." location#:"..validCount.." rectSize:"..rectSize.." sizeMultiplier:"..sizeMultiplier) + local richnessMultiplier = global_richness_mult + + if startingArea then + richnessMultiplier = starting_richness_mult + end + + -- infinite ore handling for Angels Ores mod + local infiniteOrePresent = false + local infiniteOreName = "infinite-"..rname + local minimumInfiniteOreAmount = nil + local spawnName = rname + + if game.entity_prototypes[infiniteOreName] then + infiniteOrePresent = true + minimumInfiniteOreAmount = game.entity_prototypes[infiniteOreName].minimum_resource_amount + end + + if startingArea and not infiniteResourceInStartArea then + infiniteOrePresent = false + end + + for _,location in ipairs(oreLocations) do + if location.valid then + + local amount = floor(( forceFactor * location.force + min_amount ) * richnessMultiplier) + + if amount > 1e9 then + amount = 1e9 + end + + _total = _total + amount + + spawnName = rname + if infiniteOrePresent and location.force > infiniteResourceSpawnThreshold then + spawnName = infiniteOreName + if minimumInfiniteOreAmount and amount < minimumInfiniteOreAmount then + amount = minimumInfiniteOreAmount + end + end + + if amount > 0 then + surface.create_entity{name = spawnName, + position = {location.x + resOffsetX,location.y + resOffsetY}, + force = game.forces.neutral, + amount = amount} + end + end + end + + end + + if debug_enabled then + debug("Total amount: ".._total) + debug("Leaving spawn_resource_ore") + end + return _total +end + +--[[ entity-liquid ]]-- +local function spawn_resource_liquid(surface, rname, pos, size, richness, startingArea, restrictions) + restrictions = restrictions or '' + debug("Entering spawn_resource_liquid "..rname.." "..pos.x..","..pos.y.." "..size.." "..richness.." "..tostring(startingArea).." "..restrictions) + local _total = 0 + local max_radius = rgen:random()*CHUNK_SIZE/2 + CHUNK_SIZE + + richness = ( 0.75 + rgen:random() / 2 ) * richness * size + + resourceEntity = game.entity_prototypes[rname] + + + local total_share = 0 + local avg_share = 1/size + local angle = rgen:random()*pi*2 + local saved = 0 + while total_share < 1 do + local new_share = vary_by_percentage(avg_share, 0.25) + if new_share + total_share > 1 then + new_share = 1 - total_share + end + total_share = new_share + total_share + if new_share < avg_share/10 then + -- too small + break + end + local amount = floor(richness*new_share) + saved + + local richnessMultiplier = global_richness_mult + + if startingArea then + richnessMultiplier = starting_richness_mult + end + + --if amount >= game.entity_prototypes[rname].minimum then + if amount >= config[rname].minimum_amount then + saved = 0 + for try=1,5 do + local dist = rgen:random()*(max_radius - max_radius*0.1) + angle = angle + pi/4 + rgen:random()*pi/2 + local x, y = pos.x + cos(angle)*dist, pos.y + sin(angle)*dist + if surface.can_place_entity{name = rname, position = {x,y}} then + debug("@ "..x..","..y.." amount: "..amount.." new_share: "..new_share.." try: "..try) + amount = floor(amount * richnessMultiplier) + + if amount > 1e9 then + amount = 1e9 + end + + _total = _total + amount + + if amount > 0 then + surface.create_entity{name = rname, + position = {x,y}, + force = game.forces.neutral, + amount = amount, + direction = rgen:random(4)} + end + break + elseif not startingArea then -- we don't want to make ultra rich nodes in starting area - failing to make them will add second spawn in different location + entities = surface.find_entities_filtered{area = {{x-2.75, y-2.75}, {x+2.75, y+2.75}}, name=rname} + if entities and #entities > 0 then + _total = _total + amount + for k, ent in pairs(entities) do + ent.amount = ent.amount + floor(amount/#entities) + end + break + end + end + end + else + saved = amount + end + end + debug("Total amount: ".._total) + debug("Leaving spawn_resource_liquid") + return _total +end + +local spawnerTable = nil + +local function initSpawnerTable() + if spawnerTable == nil then + spawnerTable = {} + spawnerTable["biter-spawner"] = game.entity_prototypes["biter-spawner"] ~= nil + spawnerTable["bob-biter-spawner"] = game.entity_prototypes["bob-biter-spawner"] ~= nil + spawnerTable["spitter-spawner"] = game.entity_prototypes["spitter-spawner"] ~= nil + spawnerTable["bob-spitter-spawner"] = game.entity_prototypes["bob-spitter-spawner"] ~= nil + end +end + +local function spawn_entity(surface, ent, r_config, x, y) + if not use_RSO_biter_spawning then return end + local size=rgen:random(r_config.size.min, r_config.size.max) + + local _total = 0 + local r_distance = distance({x=0,y=0},{x=x/REGION_TILE_SIZE,y=y/REGION_TILE_SIZE}) + + local distanceMultiplier = math.min(r_distance^r_config.size_per_region_factor, 5) + if r_config.size_per_region_factor then + size = size*distanceMultiplier + end + + size = size * enemy_base_size_multiplier + + debug("Entering spawn_entity "..ent.." "..x..","..y.." "..size) + + local maxAttemptCount = 5 + local distancePerAttempt = 0.2 + + initSpawnerTable() + + for i=1,size do + for attempt = 1, maxAttemptCount do + local richness=r_config.richness*(r_distance^richness_distance_factor) + local max_d = floor(CHUNK_SIZE*(0.5 + distancePerAttempt*attempt)) + local s_x = x + rgen:random(0, floor(max_d - r_config.clear_range[1])) - max_d/2 + r_config.clear_range[1] + local s_y = y + rgen:random(0, floor(max_d - r_config.clear_range[2])) - max_d/2 + r_config.clear_range[2] + + if surface.get_tile(s_x, s_y).valid then + + remove_trees(surface, s_x, s_y, r_config.clear_range[1], r_config.clear_range[2]) + + local spawnerName = nil + + if spawner_probability_edge > 0 then + + bigSpawnerChance = rgen:random() + + if rgen:random() < spawner_probability_edge then + if ( useBobEntity and bigSpawnerChance > 0.75 ) then + spawnerName = "bob-biter-spawner" + else + spawnerName = "biter-spawner" + end + else + if ( useBobEntity and bigSpawnerChance > 0.75 ) then + spawnerName = "bob-spitter-spawner" + else + spawnerName = "spitter-spawner" + end + end + end + + if spawnerName and spawnerTable[spawnerName] then + if surface.can_place_entity{name=spawnerName, position={s_x, s_y}} then + _total = _total + richness + debug(spawnerName.." @ "..s_x..","..s_y.." placed on "..attempt.." attempt") + + surface.create_entity{name=spawnerName, position={s_x, s_y}, force=game.forces[r_config.force], amount=floor(richness)}--, direction=rgen:random(4) + -- else + -- debug("Entity "..spawnerName.." spawn failed") + break; + else + if attempt == maxAttemptCount then + debug(spawnerName.." @ "..s_x..","..s_y.." failed to spawn") + end + end + else + debug("Entity "..spawnerName.." doesn't exist") + end + end + end + + if r_config.sub_spawn_probability then + local sub_spawn_prob = r_config.sub_spawn_probability*math.min(r_config.sub_spawn_max_distance_factor, r_config.sub_spawn_distance_factor^r_distance) + if rgen:random() < sub_spawn_prob then + for i=1,(rgen:random(r_config.sub_spawn_size.min, r_config.sub_spawn_size.max)*distanceMultiplier) do + local allotment_max = 0 + -- build table + for k,v in pairs(r_config.sub_spawns) do + if not v.min_distance or r_distance > v.min_distance then + local allotment = v.allotment + if v.allotment_distance_factor then + allotment = allotment * (v.allotment_distance_factor^r_distance) + end + v.allotment_range ={min = allotment_max, max = allotment_max + allotment} + allotment_max = allotment_max + allotment + else + v.allotment_range = nil + end + end + local sub_type = rgen:random(0, allotment_max) + for sub_spawn,v in pairs(r_config.sub_spawns) do + if v.allotment_range and sub_type >= v.allotment_range.min and sub_type <= v.allotment_range.max then + for attempt = 1, maxAttemptCount do + local max_d = floor(CHUNK_SIZE*distancePerAttempt*attempt) + s_x = x + rgen:random(max_d) - max_d/2 + s_y = y + rgen:random(max_d) - max_d/2 + remove_trees(surface, s_x, s_y, v.clear_range[1], v.clear_range[2]) + if surface.can_place_entity{name=sub_spawn, position={s_x, s_y}} then + surface.create_entity{name=sub_spawn, position={s_x, s_y}, force=game.forces[r_config.force]}--, direction=rgen:random(4) + debug("Rolled subspawn "..sub_spawn.." @ "..s_x..","..s_x.." after "..attempt.." attempts") + break; + else + if attempt == maxAttemptCount then + debug("Rolling subspawn "..sub_spawn.." @ "..s_x..","..s_x.." failed") + end + end + end + break + end + end + end + end + end + end + debug("Total amount: ".._total) + debug("Leaving spawn_entity") +end + +--[[ EVENT/INIT METHODS ]]-- + +local function spawn_starting_resources( surface, index ) + + if global.startingAreas[index].spawned then return end + if surface.map_gen_settings.starting_area == "none" and not ignoreMapGenSettings then return end -- starting area disabled by map gen + if starting_area_size < 0.1 then return end -- skip spawning if starting area is to small + + local position = global.startingAreas[index] + + rgen = rng_for_reg_pos( position ) + local status = true + for index,v in ipairs(configIndexed) do + if v.starting then + local prob = rgen:random() -- probability that this resource is spawned + debug("starting resource probability rolled "..prob) + if v.starting.probability > 0 and prob <= v.starting.probability then + local total = 0 + local radius = 25 + local min_threshold = 0 + + if v.type == "resource-ore" then + min_threshold = v.starting.richness * rgen:random(5, 10) -- lets make sure that there is at least 10-15 times starting richness ore at start + elseif v.type == "resource-liquid" then + min_threshold = v.starting.richness * 0.5 * v.starting.size + end + + while (radius < 200) and (total < min_threshold) do + local angle = rgen:random() * pi * 2 + local dist = rgen:random() * 30 + radius * 2 + local pos = { x = floor(cos(angle) * dist) + position.x, y = floor(sin(angle) * dist) + position.y } + if v.type == "resource-ore" then + total = total + spawn_resource_ore(surface, v.name, pos, v.starting.size, v.starting.richness, true) + elseif v.type == "resource-liquid" then + total = total + spawn_resource_liquid(surface, v.name, pos, v.starting.size, v.starting.richness, true) + end + radius = radius + 10 + end + if total < min_threshold then + status = false + end + end + end + end + + global.startingAreas[index].spawned = true +end + +local function modifyMinMax(value, mod) + value.min = round( value.min * mod ) + value.max = round( value.max * mod ) +end + +local function prebuild_config_data(surface) + if index_is_built then return false end + + local mapGenSettings = nil + + if not ignoreMapGenSettings then + mapGenSettings = surface.map_gen_settings + end + local autoPlaceSettings = nil + if mapGenSettings then + autoPlaceSettings = mapGenSettings.autoplace_controls + end + + configIndexed = {} + -- build additional indexed array to the associative array + for res_name, res_conf in pairs(config) do + if res_conf.valid then -- only add valid resources + res_conf.name = res_name + + local settingsForResource = nil + local isEntity = (res_conf.type == "entity") + local addResource = true + + local autoplaceName = res_name + + if res_conf.autoplace_name then + autoplaceName = res_conf.autoplace_name + end + + if autoPlaceSettings then + settingsForResource = autoPlaceSettings[autoplaceName] + end + + if settingsForResource then + local allotmentMod = nil + local sizeMod = nil + if isEntity then + allotmentMod = entityFrequencyMultiplier[settingsForResource.frequency] + sizeMod = entitySizeMultiplier[settingsForResource.size] + else + allotmentMod =frequencyAllotmentMultiplier[settingsForResource.frequency] + sizeMod = sizeMultiplier[settingsForResource.size] + end + + local richnessMod = richnessMultiplier[settingsForResource.richness] + + + debug(res_name .. " allotment mod " .. allotmentMod .. " size mod " .. sizeMod .. " richness mod " .. richnessMod ) + + + if allotmentMod then + if isEntity then + res_conf.absolute_probability = res_conf.absolute_probability * allotmentMod + debug("Entity chance modified to "..res_conf.absolute_probability) + else + res_conf.allotment = round( res_conf.allotment * allotmentMod ) + end + end + + if sizeMod ~= nil and sizeMod == 0 then + addResource = false + end + + if sizeMod then + modifyMinMax(res_conf.size, sizeMod) + + if res_conf.starting then + res_conf.starting.size = round( res_conf.starting.size * sizeMod ) + end + + if isEntity then + if res_conf.sub_spawn_size then + modifyMinMax(res_conf.sub_spawn_size, sizeMod) + end + modifyMinMax(res_conf.spawns_per_region, sizeMod) + end + end + + if richnessMod then + if type == "resource-ore" then + res_conf.richness = round( res_conf.richness * richnessMod ) + elseif type == "resource-liquid" then + modifyMinMax(res_conf.richness, richnessMod) + end + + if res_conf.starting then + res_conf.starting.richness = round( res_conf.starting.richness * richnessMod ) + end + end + end + + if addResource then + configIndexed[#configIndexed + 1] = res_conf + if res_conf.multi_resource and multi_resource_active then + local new_list = {} + for sub_res_name, allotment in pairs(res_conf.multi_resource) do + if config[sub_res_name] and config[sub_res_name].valid then + new_list[#new_list+1] = {name = sub_res_name, allotment = allotment} + end + end + table.sort(new_list, function(a, b) return a.name < b.name end) + res_conf.multi_resource = new_list + else + res_conf.multi_resource_chance = nil + end + end + end + end + + table.sort(configIndexed, function(a, b) return a.name < b.name end) + + local pr=0 + for index,v in pairs(config) do + if v.along_resource_probability then + v.along_resource_probability_range={min=pr, max=pr+v.along_resource_probability} + pr=pr+v.along_resource_probability + end + if v.allotment and v.allotment > 0 then + v.allotment_range={min=max_allotment, max=max_allotment+v.allotment} + max_allotment=max_allotment+v.allotment + end + end + + if mapGenSettings and mapGenSettings.starting_area then + local multiplier = startingAreaMultiplier[mapGenSettings.starting_area] + if multiplier ~= nil then + starting_area_size = starting_area_size * multiplier + debug("Starting area "..starting_area_size) + end + end + + index_is_built = true +end + +-- set up the probabilty segments from which to roll between for biter and spitter spawners +local function calculate_spawner_ratio() + if (biter_ratio_segment ~= 0 and spitter_ratio_segment ~= 0) and biter_ratio_segment >= 0 and spitter_ratio_segment >= 0 then + spawner_probability_edge=biter_ratio_segment/(biter_ratio_segment+spitter_ratio_segment) -- normalize to between 0 and 1 + end +end + +local function checkConfigForInvalidResources() + --make sure that every resource in the config is actually available. + --call this function, before the auxiliary config is prebuilt! + if index_is_built then return end + + local prototypes = game.entity_prototypes + + for resourceName, resourceConfig in pairs(config) do + if prototypes[resourceName] or resourceConfig.type == "entity" then + resourceConfig.valid = true + else + -- resource was in config, but it doesn't exist in game files anymore - mark it invalid + resourceConfig.valid = false + + table.insert(invalidResources, "Resource not available: " .. resourceName) + debug("Resource not available: " .. resourceName) + end + + if resourceConfig.valid and resourceConfig.type ~= "entity" then + if prototypes[resourceName].autoplace_specification == nil then + resourceConfig.valid = false + debug("Resource "..resourceName.." invalidated - autoplace not present") + end + end + end +end + +local function roll_region(c_x, c_y) + --in what region is this chunk? + local r_x=floor(c_x/REGION_TILE_SIZE) + local r_y=floor(c_y/REGION_TILE_SIZE) + local r_data = nil + + --don't spawn stuff in starting area + if isInStartingArea( c_x/REGION_TILE_SIZE, c_y/REGION_TILE_SIZE ) then + return false + end + + local regrow_rso = false + if (ENABLE_REGROWTH) then + if (global.chunk_regrow.rso_region_roll_counter > (region_size*region_size/2)) then + regrow_rso = true + global.chunk_regrow.rso_region_roll_counter = 0 + else + global.chunk_regrow.rso_region_roll_counter = global.chunk_regrow.rso_region_roll_counter + 1 + end + end + + --if this chunk is the first in its region to be generated + -- or we're hitting a lucky regrowth reroll... + if (not (global.regions[r_x] and global.regions[r_x][r_y]) or regrow_rso) then + + if not global.regions[r_x] then global.regions[r_x] = {} end + global.regions[r_x][r_y]={} + r_data = global.regions[r_x][r_y] + rgen = rng_for_reg_pos{x=r_x,y=r_y} + + local rollCount = math.ceil(#configIndexed / 10) - 1 -- 0 based counter is more convenient here + rollCount = math.min(rollCount, 3) + + for rollNumber = 0,rollCount do + + local resourceChance = absolute_resource_chance - rollNumber * 0.1 + --absolute chance to spawn resource + local abct = rgen:random() + debug("Rolling resource "..abct.." against "..resourceChance.." roll "..rollNumber) + if abct <= resourceChance then + local res_type=rgen:random(1, max_allotment) + for index,v in ipairs(configIndexed) do + if v.allotment_range and ((res_type >= v.allotment_range.min) and (res_type <= v.allotment_range.max)) then + debug("Rolled primary resource "..v.name.." with res_type="..res_type.." @ "..r_x..","..r_y) + local num_spawns=rgen:random(v.spawns_per_region.min, v.spawns_per_region.max) + local last_spawn_coords = {} + local along_ + for i=1,num_spawns do + local c_x, c_y = find_random_chunk(r_x, r_y) + if not r_data[c_x] then r_data[c_x] = {} end + if not r_data[c_x][c_y] then r_data[c_x][c_y] = {} end + local c_data = r_data[c_x][c_y] + c_data[#c_data+1]={v.name, rollNumber} + last_spawn_coords[#last_spawn_coords+1] = {c_x, c_y} + debug("Rolled primary chunk "..v.name.." @ "..c_x.."."..c_y.." reg: "..r_x..","..r_y) + -- Along resource spawn, only once + if i == 1 then + local am_roll = rgen:random() + for index,vv in ipairs(configIndexed) do + if vv.along_resource_probability_range and am_roll >= vv.along_resource_probability_range.min and am_roll <= vv.along_resource_probability_range.max then + c_data = r_data[c_x][c_y] + c_data[#c_data+1]={vv.name, rollNumber} + debug("Rolled along "..vv.name.." @ "..c_x.."."..c_y.." reg: "..r_x..","..r_y) + end + end + end + end + -- roll multiple resources in same region + local deep=0 + while v.multi_resource_chance and rgen:random() <= v.multi_resource_chance*(multi_resource_chance_diminish^deep) do + deep = deep + 1 + local max_allotment = 0 + for index,sub_res in pairs(v.multi_resource) do max_allotment=max_allotment+sub_res.allotment end + + local res_type=rgen:random(1, max_allotment) + local min=0 + for _, sub_res in pairs(v.multi_resource) do + if (res_type >= min) and (res_type <= sub_res.allotment + min) then + local last_coords = last_spawn_coords[rgen:random(1, #last_spawn_coords)] + local c_x, c_y = find_random_neighbour_chunk(last_coords[1], last_coords[2]) -- in same as primary resource chunk + if not r_data[c_x] then r_data[c_x] = {} end + if not r_data[c_x][c_y] then r_data[c_x][c_y] = {} end + local c_data = r_data[c_x][c_y] + c_data[#c_data+1]={sub_res.name, deep} + debug("Rolled multiple "..sub_res.name..":"..deep.." with res_type="..res_type.." @ "..c_x.."."..c_y.." reg: "..r_x.."."..r_y) + break + else + min = min + sub_res.allotment + end + end + end + break + end + end + + end + end + -- roll for absolute_probability - this rolls the enemies + + for index,v in ipairs(configIndexed) do + if v.absolute_probability then + local prob_factor = 1 + if v.probability_distance_factor then + prob_factor = math.min(v.max_probability_distance_factor, v.probability_distance_factor^distance({x=0,y=0},{x=r_x,y=r_y})) + end + local abs_roll = rgen:random() + if abs_roll 10 then + global.startingAreas[1].spawned = true + end + end + + calculate_spawner_ratio() + spawn_starting_resources(surface, 1 ) + + initDone = true + + if surface.map_gen_settings.autoplace_controls["iron-ore"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA iron-ore GEN IS NOT DISABLED!") + end + if surface.map_gen_settings.autoplace_controls["copper-ore"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA copper-ore GEN IS NOT DISABLED!") + end + if surface.map_gen_settings.autoplace_controls["uranium-ore"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA uranium-ore GEN IS NOT DISABLED!") + end + if surface.map_gen_settings.autoplace_controls["crude-oil"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA crude-oil GEN IS NOT DISABLED!") + end + if surface.map_gen_settings.autoplace_controls["enemy-base"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA enemy-base GEN IS NOT DISABLED!") + end + if surface.map_gen_settings.autoplace_controls["stone"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA stone GEN IS NOT DISABLED!") + end + if surface.map_gen_settings.autoplace_controls["coal"].size ~= "none" then + game.players[1].print("RSO WARNING - VANILLA coal GEN IS NOT DISABLED!") + end + end + +end + +function RSO_ChunkGenerated(event) + local c_x = event.area.left_top.x + local c_y = event.area.left_top.y + + RSO_init() + + roll_region(c_x, c_y) + roll_chunk(event.surface, c_x, c_y) +end \ No newline at end of file diff --git a/locale/rso/rso_resource_config.lua b/locale/rso/rso_resource_config.lua new file mode 100644 index 0000000..d465415 --- /dev/null +++ b/locale/rso/rso_resource_config.lua @@ -0,0 +1,171 @@ + +local function fillVanillaConfig() + + config["iron-ore"] = { + type="resource-ore", + + -- general spawn params + allotment=100, -- how common resource is + spawns_per_region={min=1, max=1}, --number of chunks + richness=10000, -- resource_ore has only one richness value - resource-liquid has min/max + + size={min=15, max=25}, -- rough radius of area, too high value can produce square shaped areas + min_amount=350, + + -- resource provided at starting location + -- probability: 1 = 100% chance to be in starting area + -- 0 = resource is not in starting area + starting={richness=8000, size=25, probability=1}, + + multi_resource_chance=0.20, -- absolute value + multi_resource={ + ["iron-ore"] = 2, -- ["resource_name"] = allotment + ['copper-ore'] = 4, + ["coal"] = 4, + ["stone"] = 4, + } + } + + config["copper-ore"] = { + type="resource-ore", + + allotment=90, + spawns_per_region={min=1, max=1}, + richness=10000, + size={min=15, max=25}, + min_amount=350, + + starting={richness=6000, size=25, probability=1}, + + multi_resource_chance=0.20, + multi_resource={ + ["iron-ore"] = 4, + ['copper-ore'] = 2, + ["coal"] = 4, + ["stone"] = 4, + } + } + + config["coal"] = { + type="resource-ore", + + allotment=80, + + spawns_per_region={min=1, max=1}, + size={min=15, max=25}, + richness=8000, + min_amount=350, + + starting={richness=6000, size=20, probability=1}, + + multi_resource_chance=0.30, + multi_resource={ + ["crude-oil"] = 1, + ["iron-ore"] = 3, + ['copper-ore'] = 3, + } + } + + config["stone"] = { + type="resource-ore", + + allotment=60, + spawns_per_region={min=1, max=1}, + richness=6000, + size={min=15, max=20}, + min_amount=250, + + starting={richness=5000, size=16, probability=1}, + + multi_resource_chance=0.30, + multi_resource={ + ["coal"] = 4, + ["iron-ore"] = 3, + ['copper-ore'] = 3, + } + } + + config["uranium-ore"] = { + type="resource-ore", + + allotment=50, + spawns_per_region={min=1, max=1}, + richness=10000, + size={min=15, max=20}, + min_amount=500, + + starting={richness=2000, size=10, probability=1}, + } + + config["crude-oil"] = { + type="resource-liquid", + minimum_amount=10000, + allotment=70, + spawns_per_region={min=1, max=2}, + richness={min=100000, max=200000}, -- richness per resource spawn + size={min=3, max=7}, + + starting={richness=200000, size=2, probability=1}, + + multi_resource_chance=0.20, + multi_resource={ + ["coal"] = 4, + } + } +end + +local function fillEnemies() + + config["enemy-base"] = { + type="entity", + force="enemy", + clear_range = {6, 6}, + + spawns_per_region={min=2,max=4}, + size={min=2,max=4}, + size_per_region_factor=1, + richness=3, + + absolute_probability=absolute_enemy_chance, -- chance to spawn in region + probability_distance_factor=1.1, -- relative increase per region + max_probability_distance_factor=3, -- absolute value + + along_resource_probability=0.20, -- chance to spawn in resource chunk anyway, absolute value. Can happen once per resource. + + sub_spawn_probability=0.1, -- chance for this entity to spawn anything from sub_spawns table, absolute value + sub_spawn_size={min=1, max=2}, -- in same chunk + sub_spawn_distance_factor=1.01, + sub_spawn_max_distance_factor=1.5, + sub_spawns={ + ["small-worm-turret"]={ + min_distance=0, + allotment=200, + allotment_distance_factor=0.99, + clear_range = {2, 2}, + }, + ["medium-worm-turret"]={ + min_distance=10, + allotment=100, + allotment_distance_factor=1.01, + clear_range = {2, 2}, + }, + ["big-worm-turret"]={ + min_distance=20, + allotment=100, + allotment_distance_factor=1.015, + clear_range = {2, 2}, + } + } + } + +end + +function loadResourceConfig() + + config={} + + fillVanillaConfig() + fillEnemies() + + return config +end \ No newline at end of file diff --git a/locale/tag.lua b/locale/tag.lua new file mode 100644 index 0000000..204c2a2 --- /dev/null +++ b/locale/tag.lua @@ -0,0 +1,60 @@ +-- control.lua +-- Apr 2017 + +function CreateTagGui(event) + local player = game.players[event.player_index] + if player.gui.top.tag == nil then + player.gui.top.add{name="tag", type="button", caption="Tag"} + end +end + +-- Tag list +local roles = { + {display_name = "[Solo]"}, + {display_name = "[Mining]"}, + {display_name = "[Power]"}, + {display_name = "[Oil]"}, + {display_name = "[Smelt]"}, + {display_name = "[Rail]"}, + {display_name = "[Defense]"}, + {display_name = "[Circuits]"}, + {display_name = "[Science!]"}, + {display_name = "[Logistics]"}, + {display_name = "[Misc]"}, + {display_name = "[Aliens]"}, + {display_name = "[Rocket]"}, + {display_name = "[AFK]"}, + {display_name = "Clear"}} + +local function ExpandTagGui(player) + local frame = player.gui.left["tag-panel"] + if (frame) then + frame.destroy() + else + local frame = player.gui.left.add{type="frame", name="tag-panel", caption="What are you doing:"} + for _, role in pairs(roles) do + frame.add{type="button", caption=role.display_name, name=role.display_name} + end + end +end + +function TagGuiClick(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 == "tag") then + ExpandTagGui(player) + end + + if (name == "Clear") then + player.tag = "" + return + end + + for _, role in pairs(roles) do + if (name == role.display_name) then + player.tag = role.display_name + end + end +end