diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93d43a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.lua \ No newline at end of file diff --git a/README.md b/README.md index 734ce69..68f62f2 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,24 @@ # FactorioScenarioMultiplayerSpawn A custom scenario for allowing separate spawn locations in multiplayer. Designed for Co-op and PvE. -0.17 is in a dev branch "dev_0.17". At this point it's basically stable. I will merge to master once 0.17 factorio is made stable. - -0.16 is stable but will not receive any further updates. - -## Instructions - -### STEP 1 - -Download the zip. - -Place it in your Factorio/scenarios/... folder. - -It should look something like this (for my windows steam install location): - -C:\Users\user\AppData\Roaming\Factorio\scenarios\FactorioScenarioMultiplayerSpawn\control.lua - - -### STEP 2 - -Go into config.lua and edit the strings to add your own server messages. - -Rename the "FactorioScenarioMultiplayerSpawn" folder to something shorter and more convenient (optional). - - -### STEP 3 - -#### OPTION 1 (Client Hosted) -Start a multiplayer game on your client like normal. - -#### OPTION 2 (Headless) -Generate a new map, use that save file to host if you want to. - -#### OPTION 3 (Headless) -Place the scenario code in the game's scenario folder, typically something like "..\Factorio\scenarios\FactorioScenarioMultiplayerSpawn\\.." - -Start a new game (generates a random map based on the config in config.lua) from the command line: -./factorio --start-server-load-scenario FactorioScenarioMultiplayerSpawn --server-settings my-server-settings.json - -If you want to RESUME from this method, use something like this: -./factorio --start-server-load-latest --server-settings my-server-settings.json - - -## Configuration - -Look in config.lua for some controls over the different modules. - -Not all configurations have been fully tested so modify at your own risk. - -If you want to change the RSO config, look for the rso_config and rso_resource_config files. - - -## TODO - -I need to update this with more details about the implementation and explain some of the configuration options. - -For now, just take a look at the source and it should be easy to understand I hope. +## Read the WIKI +The github wiki page will have the most up to date information. If you have questions, go there first, otherwise just ask. +## Stable Version (Factorio v0.17.69) +At this point master branch is basically stable, I'll help with bug fixes and minor features but I have no large changes planned. ## 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 for help as well: https://github.com/3RaGaming Praise be to Mylon - ## Random Notes - -While it is an option to disable RSO, I would not recommend doing that. I can't guarantee any bugs or issues as I focus mostly on testing with RSO enabled. - Feel free to submit bugs/fixes/requests/pulls/forks whatever you want. I do not plan on supporting PvP, but I will help anyone who wants to make it a configurable option. + +## Contact +discord.gg/trnpcen +oarcinae@gmail.com diff --git a/config.lua b/config.lua deleted file mode 100644 index 3a65933..0000000 --- a/config.lua +++ /dev/null @@ -1,405 +0,0 @@ --- config.lua --- Apr 2017 --- Configuration Options --- --- You should be able to leave most of the settings here as defaults. --- The only thing you definitely want to change are the welcome messages. - --------------------------------------------------------------------------------- --- Messages --- You will want to change some of these to be your own. --- Make sure SERVER_OWNER_IS_OARC = false --------------------------------------------------------------------------------- - --- This stuff is printed in the console. It's probably ignored most of the time. -WELCOME_MSG = "[INSERT SERVER OWNER MSG HERE!]" -GAME_MODE_MSG = "In the current game mode, a satellite must be launched from an existing far away rocket silo to win!" -MODULES_ENABLED = "Mods Enabled: Separate Spawns, RSO, Long-Reach, Autofill, Undecorator, Player List" - --- This stuff is shown in the welcome GUI. Make sure it's valid. -WELCOME_MSG_TITLE = "[INSERT SERVER OWNER MSG HERE!]" -SERVER_MSG = "Rules: Be polite. Ask before changing other players's stuff. Have fun!\n".. -"This server is running a custom scenario that changes spawn locations." - -SCENARIO_INFO_MSG = "Latest updates in this scenario version:\n".. -"0.16 experimental release. Tweaks to fix spawn issues / text / difficulty.\n".. -"This scenario gives you and/or your friends your own starting area.\n".. -"You can be on the main team or your own. All teams are friendly.\n".. -"If you leave in the first 15 minutes, your base and character will be deleted!" - -SPAWN_WARN_MSG = "Due to the way this scenario works, it may take some time for the land around your new spawn area to generate... Please wait for 10-20 seconds when you select your first spawn." - -CONTACT_MSG = "Contact: SteamID:Oarc | oarcinae@gmail.com | discord.gg/TPYxRrS" - --- This should be false for you, it's just a convenience for me. -SERVER_OWNER_IS_OARC = false - - --------------------------------------------------------------------------------- --- Module Enables --- These enables are not fully tested! For example, disabling separate spawns --- will probably break the frontier rocket silo mode --------------------------------------------------------------------------------- - --- Separate spawns --- This is the core of the mod. Probably not a good idea to disable it. -ENABLE_SEPARATE_SPAWNS = true - --- This allows 2 players to spawn next to each other in the wilderness, --- each with their own starting point. It adds more GUI selection options. -ENABLE_BUDDY_SPAWN = true - --- RSO soft-mod (included in the scenario) -ENABLE_RSO = true - --- Frontier style rocket silo mode -FRONTIER_ROCKET_SILO_MODE = true - --- Enable Undecorator --- Removes decorative items to reduce save file size. -ENABLE_UNDECORATOR = true - --- Enable Tags -ENABLE_TAGS = true - --- Enable Long Reach -ENABLE_LONGREACH = true - --- Enable Autofill -ENABLE_AUTOFILL = true - --- Enable Playerlist -ENABLE_PLAYER_LIST = true -PLAYER_LIST_OFFLINE_PLAYERS = true -- List offline players as well. - --- Enable Gravestone Chests -ENABLE_GRAVESTONE_ON_DEATH = false - --- Items dumped into chest when you leave. -ENABLE_GRAVESTONE_ON_LEAVING = false --- If anyone leaves within first X minutes, items get dumped into chest. -ENABLE_GRAVESTONE_ON_LEAVING_TIME_MINS = 15 - --- Enable quick start items -ENABLE_POWER_ARMOR_QUICK_START = false - --- Enable shared vision between teams (all teams are COOP regardless) -ENABLE_SHARED_TEAM_VISION = true - --- Enable map regrowth, see regrowth_map.lua for more info. --- I'm not a fan of this anymore, but it helps keep the map size down -ENABLE_REGROWTH = false - --- If you have regrowth enabled, this should also be enabled. --- It removes bases for players that join and leave the game quickly. --- This can also be used without enabling regrowth. -ENABLE_ABANDONED_BASE_REMOVAL = true - --------------------------------------------------------------------------------- --- Spawn Options --------------------------------------------------------------------------------- - ---------------------------------------- --- Starting Items ---------------------------------------- --- Items provided to the player the first time they join -PLAYER_SPAWN_START_ITEMS = { - {name="pistol", count=1}, - {name="firearm-magazine", count=100}, - {name="iron-plate", count=8}, - {name="burner-mining-drill", count = 1}, - {name="stone-furnace", count = 1}, - -- {name="iron-plate", count=20}, - -- {name="burner-mining-drill", count = 1}, - -- {name="stone-furnace", count = 1}, - -- {name="power-armor", count=1}, - -- {name="fusion-reactor-equipment", count=1}, - -- {name="battery-mk2-equipment", count=3}, - -- {name="exoskeleton-equipment", count=1}, - -- {name="personal-roboport-mk2-equipment", count=3}, - -- {name="solar-panel-equipment", count=7}, - -- {name="construction-robot", count=100}, - -- {name="repair-pack", count=100}, - -- {name="steel-axe", count=3}, -} - --- Items provided after EVERY respawn (disabled by default) -PLAYER_RESPAWN_START_ITEMS = { - -- {name="pistol", count=1}, - -- {name="firearm-magazine", count=100} -} - ---------------------------------------- --- Distance Options ---------------------------------------- - --- This is the radius, in chunks, that a spawn area is from any other generated --- chunks. It ensures the spawn area isn't too near generated/explored/existing --- area. The larger you make this, the further away players will spawn from --- generated map area (even if it is not visible on the map!). -CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS = 5 - --- Near Distance in chunks -NEAR_MIN_DIST = 0 -NEAR_MAX_DIST = 50 - --- Far Distance in chunks -FAR_MIN_DIST = 200 -FAR_MAX_DIST = 300 - ---------------------------------------- --- Resource & Spawn Circle Options ---------------------------------------- - --- Enable this to have a vanilla style starting spawn for all new spawns. --- This scenario normally gives you a fixed circle with resources. --- USE_VANILLA_STARTING_SPAWN = true --- TODO - Requires pre-allocating spawns... - --- Allow players to choose to spawn with a moat -SPAWN_MOAT_CHOICE_ENABLED = true --- If you change the spawn area size, you might have to adjust this as well -MOAT_SIZE_MODIFIER = 1 - --- THIS IS WHAT SETS THE SPAWN CIRCLE SIZE! --- Create a circle of land area for the spawn --- If you make this much bigger than a few chunks, good luck. -ENFORCE_LAND_AREA_TILE_DIST = CHUNK_SIZE*1.8 - --- Location of water strip (horizontal) -WATER_SPAWN_OFFSET_X = -4 -WATER_SPAWN_OFFSET_Y = -38 -WATER_SPAWN_LENGTH = 8 - --- Start resource amounts (per tile/oil spot) -START_IRON_AMOUNT = 1500 -START_COPPER_AMOUNT = 1500 -START_STONE_AMOUNT = 1000 -START_COAL_AMOUNT = 1500 -START_URANIUM_AMOUNT = 1000 -START_OIL_AMOUNT = 300000 - --- Start resource shape --- If this is true, it will be a circle --- If false, it will be a square -ENABLE_RESOURCE_SHAPE_CIRCLE = true - --- Start resource position and size --- Position is relative to player starting location -START_RESOURCE_STONE_POS_X = -27 -START_RESOURCE_STONE_POS_Y = -34 -START_RESOURCE_STONE_SIZE = 12 - -START_RESOURCE_COAL_POS_X = -27 -START_RESOURCE_COAL_POS_Y = -20 -START_RESOURCE_COAL_SIZE = 12 - -START_RESOURCE_COPPER_POS_X = -28 -START_RESOURCE_COPPER_POS_Y = -3 -START_RESOURCE_COPPER_SIZE = 14 - -START_RESOURCE_IRON_POS_X = -29 -START_RESOURCE_IRON_POS_Y = 16 -START_RESOURCE_IRON_SIZE = 16 - -START_RESOURCE_URANIUM_POS_X = 17 -START_RESOURCE_URANIUM_POS_Y = -34 -START_RESOURCE_URANIUM_SIZE = 0 -- Disabled by default. - --- Specify 2 oil spot locations for starting oil. -START_RESOURCE_OIL_NUM_PATCHES = 2 --- The first patch -START_RESOURCE_OIL_POS_X = -39 -START_RESOURCE_OIL_POS_Y = -2 --- How far each patch is offset from the others and in which direction --- Default (x=0, y=-4) means that patches spawn in a vertical row downwards. -START_RESOURCE_OIL_X_OFFSET = 0 -START_RESOURCE_OIL_Y_OFFSET = -4 - - --- Force the land area circle at the spawn to be fully grass -ENABLE_SPAWN_FORCE_GRASS = true - --- Set this to true for the spawn area to be surrounded by a circle of trees -SPAWN_TREE_CIRCLE_ENABLED = true - --- Set this to true for the spawn area to be surrounded by an octagon of trees --- I don't recommend using this with moatsm -SPAWN_TREE_OCTAGON_ENABLED = true - ---------------------------------------- --- Safe Spawn Area Options ---------------------------------------- - --- Safe area has no aliens --- +/- this in x and y direction -SAFE_AREA_TILE_DIST = CHUNK_SIZE*5 - --- Warning area has reduced aliens --- +/- this in x and y direction -WARNING_AREA_TILE_DIST = CHUNK_SIZE*10 - --- 1 : X (spawners alive : spawners destroyed) in this area -WARN_AREA_REDUCTION_RATIO = 10 - - ---------------------------------------- --- Other Forces/Teams Options ---------------------------------------- - --- Separate teams --- This allows you to join your own force/team. Everyone is still COOP/PvE, all --- teams are friendly and cease-fire. -ENABLE_SEPARATE_TEAMS = true - --- Main force is what default players join -MAIN_FORCE = "Main Force" - --- Enable if people can spawn at the main base --- THIS CURRENTLY IS BROKEN! YOU WILL NOT GET ANY RESOURCES IF YOU USE RSO! -ENABLE_DEFAULT_SPAWN = false -- DON'T USE THIS - --- Enable if people can allow others to join their base -ENABLE_SHARED_SPAWNS = true -MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN = 0 - --- Share local team chat with all teams --- This makes it so you don't have to use /s --- but it means you can't talk privately with your own team. -ENABLE_SHARED_TEAM_CHAT = true - ---------------------------------------- --- Special Action Cooldowns ---------------------------------------- -RESPAWN_COOLDOWN_IN_MINUTES = 15 -RESPAWN_COOLDOWN_TICKS = TICKS_PER_MINUTE * RESPAWN_COOLDOWN_IN_MINUTES - --- Require playes to be online for at least 5 minutes --- Else their character is removed and their spawn point is freed up for use -MIN_ONLINE_TIME_IN_MINUTES = 15 -MIN_ONLINE_TIME = TICKS_PER_MINUTE * MIN_ONLINE_TIME_IN_MINUTES - - --------------------------------------------------------------------------------- --- Alien Options --------------------------------------------------------------------------------- - --- Enable/Disable enemy expansion -ENEMY_EXPANSION = true - --- Divide the alien evolution factors by this number to reduce it (or multiply if < 1) -ENEMY_TIME_FACTOR_DISABLE = false -- Set this to true to disable time based evolution completely. -ENEMY_TIME_FACTOR_DIVISOR = 10 -ENEMY_POLLUTION_FACTOR_DISABLE = false -- Set this to true to disable pollution based evolution completely. -ENEMY_POLLUTION_FACTOR_DIVISOR = 10 -ENEMY_DESTROY_FACTOR_DISABLE = false -- Set this to true to disable spawner destruction based evolution completely. -ENEMY_DESTROY_FACTOR_DIVISOR = 1 - --------------------------------------------------------------------------------- --- Frontier Rocket Silo Options --------------------------------------------------------------------------------- - --- Number of silos found in the wild. --- These will spawn in a circle at given distance from the center of the map --- If you set this number too high, you'll have a lot of delay at the start of the game. -SILO_NUM_SPAWNS = 3 - --- How many chunks away from the center of the map should the silo be spawned -SILO_CHUNK_DISTANCE = 200 - --- If this is enabled, you get ONE silo at the location specified below. -SILO_FIXED_POSITION = false - --- If you want to set a fixed spawn location for a single silo -SILO_POSITION = {x = 0, y = 100} - --- Set this to false so that you have to search for the silo's. -ENABLE_SILO_VISION = true - --- Add beacons around the silo (Philip's modm) -ENABLE_SILO_BEACONS = false -ENABLE_SILO_RADAR = false - --------------------------------------------------------------------------------- --- Long Reach Options --------------------------------------------------------------------------------- - -BUILD_DIST_BONUS = 64 -REACH_DIST_BONUS = BUILD_DIST_BONUS -RESOURCE_DIST_BONUS = 2 - --------------------------------------------------------------------------------- --- Autofill Options --------------------------------------------------------------------------------- - -AUTOFILL_TURRET_AMMO_QUANTITY = 10 - --------------------------------------------------------------------------------- --- RSO Soft-Mod Configurations --- Configure these to tweak the RSO values. --------------------------------------------------------------------------------- --- CONFIGURE STUFF INSIDE rso_config.lua --- RSO resources can be very lucky/unlucky... --- don't complain if you can't find a resource. - - --------------------------------------------------------------------------------- --- MAP CONFIGURATION OPTIONS --- Configure these if you are running headless since there is no way to set --- resources otherwise. --------------------------------------------------------------------------------- - --- Set this to true if you are creating the scenario at the cmd line. -CMD_LINE_MAP_GEN = true - --- Adjust settings here to set your map stuff. --- "Sizes can be specified as none, very-low, low, normal, high, very-high" -global.clMapGen = {} -global.clMapGen.terrain_segmentation="normal" -global.clMapGen.water="normal" -global.clMapGen.starting_area="low" -global.clMapGen.peaceful_mode=false -global.clMapGen.seed=nil; --- These are my go to default vanilla settings, it's not RSO, but it's okay. -global.clMapGen.autoplace_controls = { - - -- Resources and enemies only matter if you are NOT using RSO. - ["coal"]={frequency="very-low", size= "low", richness= "high"}, - ["copper-ore"]={frequency= "very-low", size= "low", richness= "high"}, - ["crude-oil"]={frequency= "low", size= "low", richness= "high"}, - ["enemy-base"]={frequency= "low", size= "normal", richness= "normal"}, - ["iron-ore"]={frequency= "very-low", size= "low", richness= "high"}, - ["stone"]={frequency= "very-low", size= "low", richness= "high"}, - ["uranium-ore"]={frequency= "low", size= "low", richness= "high"}, - - ["desert"]={frequency= "low", size= "low", richness= "low"}, - ["dirt"]={frequency= "low", size= "low", richness= "low"}, - ["grass"]={frequency= "normal", size= "normal", richness= "normal"}, - ["sand"]={frequency= "low", size= "low", richness= "low"}, - ["trees"]={frequency= "normal", size= "normal", richness= "normal"} -} --- Cliff defaults are 10 and 10, set both to 0 to turn cliffs off I think? -global.clMapGen.cliff_settings={cliff_elevation_0=10, cliff_elevation_interval=10, name="cliff"} - --------------------------------------------------------------------------------- --- ANTI-Griefing stuff ( I don't personally maintain this as I don't care for it.) --- These things were added from other people's requests/changes and are disabled by default. --------------------------------------------------------------------------------- --- Enable this to disable some basic things like friendly fire, deconstructing from map view, etc. -ENABLE_ANTI_GRIEFING = false - --- Makes blueprint ghosts dissapear if they have been placed longer than this -GHOST_TIME_TO_LIVE = 0 * TICKS_PER_MINUTE -- set to 0 for infinite ghost life - -------------------------------------------------------------------------------- --- DEBUG / Custom stuff --------------------------------------------------------------------------------- -OARC_DIFFICULTY_CUSTOM = false - --- DEBUG prints for me -global.oarcDebugEnabled = false - --- These are my specific welcome messages that get used only if I am the user --- that creates the game. -WELCOME_MSG_OARC = "Welcome to Oarc's official server! Join the discord here: discord.gg/TPYxRrS" -WELCOME_MSG_TITLE_OARC = "Welcome to Oarc's Server!" diff --git a/control.lua b/control.lua index b243c20..3efa097 100644 --- a/control.lua +++ b/control.lua @@ -1,14 +1,13 @@ -- control.lua --- Apr 2017 +-- Mar 2019 -- Oarc's Separated Spawn Scenario --- +-- -- I wanted to create a scenario that allows you to spawn in separate locations -- From there, I ended up adding a bunch of other minor/major features --- +-- -- Credit: --- RSO mod to RSO author - Orzelek - I contacted him via the forum --- Tags - Taken from WOGs scenario +-- Tags - Taken from WOGs scenario -- Rocket Silo - Taken from Frontier as an idea -- -- Feel free to re-use anything you want. It would be nice to give me credit @@ -16,124 +15,99 @@ --- To keep the scenario more manageable I have done the following: +-- To keep the scenario more manageable (for myself) I have done the following: -- 1. Keep all event calls in control.lua (here) --- 2. Put all config options in config.lua --- 3. Put mods into their own files where possible (RSO has multiple) +-- 2. Put all config options in config.lua and provided an example-config.lua file too. +-- 3. Put other stuff into their own files where possible. +-- 4. Put all other files into lib folder +-- 5. Provided an examples folder for example/recommended map gen settings -- Generic Utility Includes -require("locale/oarc_utils") -require("locale/rso/rso_control") -require("locale/frontier_silo") -require("locale/tag") -require("locale/game_opts") +require("lib/oarc_utils") + +-- Other soft-mod type features. +require("lib/frontier_silo") +require("lib/tag") +require("lib/game_opts") +require("lib/player_list") +require("lib/rocket_launch") +require("lib/admin_commands") +require("lib/regrowth_map") -- For Philip. I currently do not use this and need to add proper support for -- commands like this in the future. -require("locale/temp/rgcommand") -require("locale/temp/helper_commands") +-- require("lib/rgcommand") +-- require("lib/helper_commands") -- Main Configuration File require("config") +-- Save all config settings to global table. +require("lib/oarc_global_cfg.lua") + -- Scenario Specific Includes -require("separate_spawns") -require("separate_spawns_guis") -require("regrowth_map") +require("lib/separate_spawns") +require("lib/separate_spawns_guis") --------------------------------------------------------------------------------- --- Rocket Launch Event Code --- Controls the "win condition" --------------------------------------------------------------------------------- -function RocketLaunchEvent(event) - local force = event.rocket.force - - if event.rocket.get_item_count("satellite") == 0 then - for index, player in pairs(force.players) do - player.print("You launched the rocket, but you didn't put a satellite inside.") - end - return - end +require("lib/oarc_gui_tabs") - if not global.satellite_sent then - global.satellite_sent = {} - end +-- Create a new surface so we can modify map settings at the start. +GAME_SURFACE_NAME="oarc" - if global.satellite_sent[force.name] then - global.satellite_sent[force.name] = global.satellite_sent[force.name] + 1 - else - game.set_game_state{game_finished=true, player_won=true, can_continue=true} - global.satellite_sent[force.name] = 1 - end - - for index, player in pairs(force.players) do - if player.gui.left.rocket_score then - player.gui.left.rocket_score.rocket_count.caption = tostring(global.satellite_sent[force.name]) - else - local frame = player.gui.left.add{name = "rocket_score", type = "frame", direction = "horizontal", caption="Score"} - frame.add{name="rocket_count_label", type = "label", caption={"", "Satellites launched", ":"}} - frame.add{name="rocket_count", type = "label", caption=tostring(global.satellite_sent[force.name])} - end - end -end +-- I'm reverting my decision to turn the regrowth thing into a mod. +remote.add_interface("oarc_regrowth", + {area_offlimits_chunkpos = MarkAreaSafeGivenChunkPos, + area_offlimits_tilepos = MarkAreaSafeGivenTilePos, + area_removal_tilepos = MarkAreaForRemoval, + trigger_immediate_cleanup = TriggerCleanup, + add_surface = RegrowthAddSurface}) + +commands.add_command("trigger-map-cleanup", + "Force immediate removal of all expired chunks (unused chunk removal mod)", + ForceRemoveChunksCmd) -------------------------------------------------------------------------------- -- ALL EVENT HANLDERS ARE HERE IN ONE PLACE! -------------------------------------------------------------------------------- - ---------------------------------------- --- On Init - only runs once the first +-- On Init - only runs once the first -- time the game starts ---------------------------------------- script.on_init(function(event) - -- CreateLobbySurface() -- Currently unused, but have plans for future. - - -- Configures the map settings for enemies - -- This controls evolution growth factors and enemy expansion settings. - ConfigureAlienStartingParams() + -- FIRST + InitOarcConfig() - -- Here I create the game surface. I do this so that I don't have to worry - -- about the game menu settings and I can now generate a map from the command - -- line more easily! - if ENABLE_RSO then - CreateGameSurface(RSO_MODE) - else - CreateGameSurface(VANILLA_MODE) - end - - if ENABLE_SEPARATE_SPAWNS then - InitSpawnGlobalsAndForces() - end - - if SILO_FIXED_POSITION then - SetFixedSiloPosition(SILO_POSITION) - else - SetRandomSiloPosition(SILO_NUM_SPAWNS) - end - - if FRONTIER_ROCKET_SILO_MODE then - GenerateRocketSiloAreas(game.surfaces[GAME_SURFACE_NAME]) - end - - SetServerWelcomeMessages() - - if ENABLE_REGROWTH or ENABLE_ABANDONED_BASE_REMOVAL then - OarcRegrowthInit() + -- Regrowth (always init so we can enable during play.) + RegrowthInit() + + -- Create new game surface + CreateGameSurface() + + -- MUST be before other stuff, but after surface creation. + InitSpawnGlobalsAndForces() + + -- Frontier Silo Area Generation + if (global.ocfg.frontier_rocket_silo) then + SpawnSilosAndGenerateSiloAreas() end + -- Everyone do the shuffle. Helps avoid always starting at the same location. + global.vanillaSpawns = FYShuffle(global.vanillaSpawns) + log("Vanilla spawns:") + log(serpent.block(global.vanillaSpawns)) end) ---------------------------------------- --- Freeplay rocket launch info --- Slightly modified for my purposes +-- Rocket launch event +-- Used for end game win conditions / unlocking late game stuff ---------------------------------------- script.on_event(defines.events.on_rocket_launched, function(event) - if FRONTIER_ROCKET_SILO_MODE then + if global.ocfg.frontier_rocket_silo then RocketLaunchEvent(event) end end) @@ -143,29 +117,21 @@ end) -- Chunk Generation ---------------------------------------- script.on_event(defines.events.on_chunk_generated, function(event) - if ENABLE_REGROWTH then - OarcRegrowthChunkGenerate(event.area.left_top) - end - if ENABLE_UNDECORATOR then + if global.ocfg.enable_regrowth then + RegrowthChunkGenerate(event) + end + if global.ocfg.enable_undecorator then UndecorateOnChunkGenerate(event) end - if ENABLE_RSO then - RSO_ChunkGenerated(event) - end - - if FRONTIER_ROCKET_SILO_MODE then + if global.ocfg.frontier_rocket_silo then GenerateRocketSiloChunk(event) end - if ENABLE_SEPARATE_SPAWNS and not USE_VANILLA_STARTING_SPAWN then - SeparateSpawnsGenerateChunk(event) - end + SeparateSpawnsGenerateChunk(event) - if not ENABLE_DEFAULT_SPAWN then - CreateHoldingPen(event.surface, event.area, 16, false) - end + CreateHoldingPen(event.surface, event.area, 16, 32) end) @@ -173,176 +139,176 @@ end) -- Gui Click ---------------------------------------- script.on_event(defines.events.on_gui_click, function(event) - if ENABLE_TAGS then + + -- Don't interfere with other mod related stuff. + if (event.element.get_mod() ~= nil) then return end + + if global.ocfg.enable_tags then TagGuiClick(event) end - if ENABLE_PLAYER_LIST then + if global.ocfg.enable_player_list then PlayerListGuiClick(event) end - if ENABLE_SEPARATE_SPAWNS then - WelcomeTextGuiClick(event) - SpawnOptsGuiClick(event) - SpawnCtrlGuiClick(event) - SharedSpwnOptsGuiClick(event) - BuddySpawnOptsGuiClick(event) - BuddySpawnWaitMenuClick(event) - BuddySpawnRequestMenuClick(event) - SharedSpawnJoinWaitMenuClick(event) - end + WelcomeTextGuiClick(event) + SpawnOptsGuiClick(event) + SpawnCtrlGuiClick(event) + SharedSpwnOptsGuiClick(event) + BuddySpawnOptsGuiClick(event) + BuddySpawnWaitMenuClick(event) + BuddySpawnRequestMenuClick(event) + SharedSpawnJoinWaitMenuClick(event) + + ClickOarcGuiButton(event) GameOptionsGuiClick(event) - end) script.on_event(defines.events.on_gui_checked_state_changed, function (event) - if ENABLE_SEPARATE_SPAWNS then - SpawnOptsRadioSelect(event) - SpawnCtrlGuiOptionsSelect(event) - end + SpawnOptsRadioSelect(event) + SpawnCtrlGuiOptionsSelect(event) end) +script.on_event(defines.events.on_gui_selected_tab_changed, function (event) + TabChangeOarcGui(event) +end) ---------------------------------------- -- Player Events ---------------------------------------- script.on_event(defines.events.on_player_joined_game, function(event) - - CreateGameOptionsGui(event) - PlayerJoinedMessages(event) - - if ENABLE_PLAYER_LIST then - CreatePlayerListGui(event) - end - - if ENABLE_TAGS then - CreateTagGui(event) - end - + ServerWriteFile("player_events", game.players[event.player_index].name .. " joined the game." .. "\n") end) script.on_event(defines.events.on_player_created, function(event) - + local player = game.players[event.player_index] + -- Move the player to the game surface immediately. - -- May change this to Lobby in the future. - game.players[event.player_index].teleport(game.forces[MAIN_FORCE].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) + player.teleport({x=0,y=0}, GAME_SURFACE_NAME) - if ENABLE_LONGREACH then - GivePlayerLongReach(game.players[event.player_index]) + if global.ocfg.enable_long_reach then + GivePlayerLongReach(player) end - if not ENABLE_SEPARATE_SPAWNS then - PlayerSpawnItems(event) - else - SeparateSpawnsPlayerCreated(event) - end + SeparateSpawnsPlayerCreated(event.player_index) + + InitOarcGuiTabs(player) end) script.on_event(defines.events.on_player_respawned, function(event) - if ENABLE_SEPARATE_SPAWNS then - SeparateSpawnsPlayerRespawned(event) - end - + SeparateSpawnsPlayerRespawned(event) + PlayerRespawnItems(event) - if ENABLE_LONGREACH then + if global.ocfg.enable_long_reach then GivePlayerLongReach(game.players[event.player_index]) end end) -script.on_event(defines.events.on_pre_player_died, function(event) - if ENABLE_GRAVESTONE_ON_DEATH then - DropGravestoneChests(game.players[event.player_index]) - end -end) - script.on_event(defines.events.on_player_left_game, function(event) - if ENABLE_GRAVESTONE_ON_LEAVING then - if (game.players[event.player_index].online_time < - ENABLE_GRAVESTONE_ON_LEAVING_TIME_MINS) then - DropGravestoneChests(game.players[event.player_index]) - end - end - - if ENABLE_SEPARATE_SPAWNS then - FindUnusedSpawns(event) - end + ServerWriteFile("player_events", game.players[event.player_index].name .. " left the game." .. "\n") + FindUnusedSpawns(game.players[event.player_index], true) end) +---------------------------------------- +-- On BUILD entity. Don't forget on_robot_built_entity too! +---------------------------------------- script.on_event(defines.events.on_built_entity, function(event) - if ENABLE_AUTOFILL then + if global.ocfg.enable_autofill then Autofill(event) end - if ENABLE_REGROWTH then - OarcRegrowthOffLimitsChunk(event.created_entity.position) + if global.ocfg.enable_regrowth then + remote.call("oarc_regrowth", + "area_offlimits_tilepos", + GAME_SURFACE_NAME, + event.created_entity.position, + 2) end if ENABLE_ANTI_GRIEFING then SetItemBlueprintTimeToLive(event) end + + if global.ocfg.frontier_rocket_silo then + BuildSiloAttempt(event) + end + end) ---------------------------------------- --- Shared vision, charts a small area around other players +-- On script_raised_built. This should help catch mods that +-- place items that don't count as player_built and robot_built. +-- Specifically FARL. +---------------------------------------- +script.on_event(defines.events.script_raised_built, function(event) + if global.ocfg.enable_regrowth then + remote.call("oarc_regrowth", + "area_offlimits_tilepos", + GAME_SURFACE_NAME, + event.entity.position, + 2) + end +end) + + +---------------------------------------- +-- On tick events. Stuff that needs to happen at regular intervals. +-- Delayed events, delayed spawns, ... ---------------------------------------- script.on_event(defines.events.on_tick, function(event) - if ENABLE_REGROWTH then - OarcRegrowthOnTick() + if global.ocfg.enable_regrowth then + RegrowthOnTick() + RegrowthForceRemovalOnTick() end - if ENABLE_ABANDONED_BASE_REMOVAL then - OarcRegrowthForceRemovalOnTick() - end + DelayedSpawnOnTick() - if ENABLE_SEPARATE_SPAWNS then - DelayedSpawnOnTick() - end - - if FRONTIER_ROCKET_SILO_MODE then - DelayedSiloCreationOnTick() + if global.ocfg.frontier_rocket_silo then + DelayedSiloCreationOnTick(game.surfaces[GAME_SURFACE_NAME]) end end) script.on_event(defines.events.on_sector_scanned, function (event) - if ENABLE_REGROWTH then - OarcRegrowthSectorScan(event) + if global.ocfg.enable_regrowth then + RegrowthSectorScan(event) end end) +-- script.on_event(defines.events.on_sector_scanned, function (event) + +-- end) ---------------------------------------- --- Refreshes regrowth timers around an active timer --- Refresh areas where stuff is built, and mark any chunks with player --- built stuff as permanent. +-- ---------------------------------------- -if ENABLE_REGROWTH then - - script.on_event(defines.events.on_robot_built_entity, function (event) - OarcRegrowthOffLimitsChunk(event.created_entity.position) - end) - - script.on_event(defines.events.on_player_mined_entity, function(event) - OarcRegrowthCheckChunkEmpty(event) - end) - - script.on_event(defines.events.on_robot_mined_entity, function(event) - OarcRegrowthCheckChunkEmpty(event) - end) - -end - +script.on_event(defines.events.on_robot_built_entity, function (event) + if global.ocfg.enable_regrowth then + remote.call("oarc_regrowth", + "area_offlimits_tilepos", + GAME_SURFACE_NAME, + event.created_entity.position, + 2) + end + if global.ocfg.frontier_rocket_silo then + BuildSiloAttempt(event) + end +end) ---------------------------------------- -- Shared chat, so you don't have to type /s +-- But you do lose your player colors across forces. ---------------------------------------- script.on_event(defines.events.on_console_chat, function(event) - if (ENABLE_SHARED_TEAM_CHAT) then + if (event.player_index) then + ServerWriteFile("server_chat", game.players[event.player_index].name .. ": " .. event.message .. "\n") + end + if (global.ocfg.enable_shared_chat) then if (event.player_index ~= nil) then ShareChatBetweenForces(game.players[event.player_index], event.message) end @@ -354,8 +320,43 @@ end) -- This is where you can permanently remove researched techs ---------------------------------------- script.on_event(defines.events.on_research_finished, function(event) - if FRONTIER_ROCKET_SILO_MODE then + + -- Never allows players to build rocket-silos in "frontier" mode. + if global.ocfg.frontier_rocket_silo and not global.ocfg.frontier_allow_build then RemoveRecipe(event.research.force, "rocket-silo") end + + if global.ocfg.lock_goodies_rocket_launch and + (not global.satellite_sent or not global.satellite_sent[event.research.force.name]) then + RemoveRecipe(event.research.force, "productivity-module-3") + RemoveRecipe(event.research.force, "speed-module-3") + end + + if global.ocfg.enable_loaders then + EnableLoaders(event) + end +end) + +---------------------------------------- +-- On Entity Spawned and On Biter Base Built +-- This is where I modify biter spawning based on location and other factors. +---------------------------------------- +script.on_event(defines.events.on_entity_spawned, function(event) + if (global.ocfg.modified_enemy_spawning) then + ModifyEnemySpawnsNearPlayerStartingAreas(event) + end +end) +script.on_event(defines.events.on_biter_base_built, function(event) + if (global.ocfg.modified_enemy_spawning) then + ModifyEnemySpawnsNearPlayerStartingAreas(event) + end +end) + +---------------------------------------- +-- On Corpse Timed Out +-- Save player's stuff so they don't lose it if they can't get to the corpse fast enough. +---------------------------------------- +script.on_event(defines.events.on_character_corpse_expired, function(event) + DropGravestoneChestFromCorpse(event.corpse) end) diff --git a/example-config.lua b/example-config.lua new file mode 100644 index 0000000..7d3af6c --- /dev/null +++ b/example-config.lua @@ -0,0 +1,423 @@ +-- example-config.lua (Rename this file to config.lua to use it) +-- Sep 24 2019 (updated on) +-- Configuration Options +-- +-- You should be able to leave most of the settings here as defaults. +-- The only thing you definitely want to change are the welcome messages. + +-------------------------------------------------------------------------------- +-- Messages +-- You will want to change some of these to be your own. +-------------------------------------------------------------------------------- + +-- This stuff is shown in the welcome GUI and Info panel. Make sure it's valid. +WELCOME_MSG_TITLE = "[INSERT SERVER OWNER MSG HERE test title!]" +WELCOME_MSG = "[INSERT SERVER OWNER MSG HERE test msg!]" -- Printed to player on join as well. +SERVER_MSG = "Rules: Be polite. Ask before changing other players's stuff. Have fun!\n".. +"This server is running a custom scenario that allows individual starting areas on the map." + +SCENARIO_INFO_MSG = "Latest updates in this scenario version:\n".. +"0.17 stable release. Regrowth back to softmod!\n".. +"This scenario gives you and/or your friends your own starting area.\n".. +"You can be on the main team or your own. All teams are friendly.\n".. +"If you leave in the first 15 minutes, your base and character will be deleted!" + +CONTACT_MSG = "Contact: SteamID:Oarc | oarcinae@gmail.com | discord.gg/trnpcen" + +-------------------------------------------------------------------------------- +-- Module Enables +-------------------------------------------------------------------------------- + +-- Enable this to have a vanilla style starting spawn. +-- This changes the experience pretty drastically. +-- If you enable this, you will NOT get the option to spawn using the "pre-fab" +-- fixed layout spawns. This is because the spawn types just don't balance well with +-- each other. +ENABLE_VANILLA_SPAWNS = false + +-- This allows 2 players to spawn next to each other in the wilderness, +-- each with their own starting point. It adds more GUI selection options. +ENABLE_BUDDY_SPAWN = true + +-- Frontier style rocket silo mode +-- This means you can't build silos, but some spawn out in the wild for you to use. +FRONTIER_ROCKET_SILO_MODE = true + +-- Silo Islands +-- This options is only valid when used with ENABLE_VANILLA_SPAWNS and FRONTIER_ROCKET_SILO_MODE! +-- This spreads out rocket silos on every OTHER island/vanilla spawn +SILO_ISLANDS_MODE = false + +-- Enable Undecorator +-- Removes decorative items to reduce save file size. +ENABLE_UNDECORATOR = true + +-- Enable Tags +ENABLE_TAGS = true + +-- Enable Long Reach +ENABLE_LONGREACH = true + +-- Enable Autofill +ENABLE_AUTOFILL = true + +-- Enable vanilla loaders +ENABLE_LOADERS = true + +-- Enable Playerlist +ENABLE_PLAYER_LIST = true +PLAYER_LIST_OFFLINE_PLAYERS = true -- List offline players as well. + +-- Enable shared vision between teams (all teams are COOP regardless) +ENABLE_SHARED_TEAM_VISION = true + +-- Cleans up unused chunks periodically. Helps keep map size down. +ENABLE_REGROWTH = true + +-- Only works if you have the Unused Chunk Removal mod installed. +ENABLE_ABANDONED_BASE_REMOVAL = true + +-- Enable the new 0.17 research queue by default for all forces. +ENABLE_RESEARCH_QUEUE = true + +-- Lock power armor mk2, atomic bombs and artillery until you launch a rocket. +-- Also lock speed/prod module-3s +LOCK_GOODIES_UNTIL_ROCKET_LAUNCH = false + +-------------------------------------------------------------------------------- +-- MAP CONFIGURATION OPTIONS +-- In past versions I had a way to config map settings here to be used for cmd +-- line launching, but now you should just be using --map-gen-settings and +-- --map-settings option since it works with --start-server-load-scenario +-- Read the README.md file for instructions. +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- Alien Options +-------------------------------------------------------------------------------- + +-- Adjust enemy spawning based on distance to spawns. All it does it make things +-- more balanced based on your distance and makes the game a little easier. +-- No behemoth worms everywhere just because you spawned far away. +-- If you're trying out the vanilla spawning, you might want to disable this. +OARC_MODIFIED_ENEMY_SPAWNING = true + +--------------------------------------- +-- Starting Items +--------------------------------------- +-- Items provided to the player the first time they join +PLAYER_SPAWN_START_ITEMS = { + {name="pistol", count=1}, + {name="firearm-magazine", count=200}, + {name="iron-plate", count=16}, + {name="burner-mining-drill", count = 2}, + {name="stone-furnace", count = 2}, + -- {name="iron-plate", count=20}, + -- {name="burner-mining-drill", count = 1}, + -- {name="stone-furnace", count = 1}, + -- {name="power-armor", count=1}, + -- {name="fusion-reactor-equipment", count=1}, + -- {name="battery-mk2-equipment", count=3}, + -- {name="exoskeleton-equipment", count=1}, + -- {name="personal-roboport-mk2-equipment", count=3}, + -- {name="solar-panel-equipment", count=7}, + -- {name="construction-robot", count=100}, + -- {name="repair-pack", count=100}, + -- {name="steel-axe", count=3}, +} + +-- Items provided after EVERY respawn (disabled by default) +PLAYER_RESPAWN_START_ITEMS = { + -- {name="pistol", count=1}, + -- {name="firearm-magazine", count=100} +} + +--------------------------------------- +-- Distance Options +--------------------------------------- + +-- This is the radius, in chunks, that a spawn area is from any other generated +-- chunks. It ensures the spawn area isn't too near generated/explored/existing +-- area. The larger you make this, the further away players will spawn from +-- generated map area (even if it is not visible on the map!). +CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS = 10 + +-- Near Distance in chunks +-- When a player selects "near" spawn, they will be in or as close to this range as possible. +NEAR_MIN_DIST = 0 +NEAR_MAX_DIST = 50 + +-- Far Distance in chunks +-- When a player selects "far" spawn, they will be at least this distance away. +FAR_MIN_DIST = 200 +FAR_MAX_DIST = 300 + +--------------------------------------- +-- Vanilla spawn point options +-- (only applicable if ENABLE_VANILLA_SPAWNS is enabled.) +--------------------------------------- + +-- Num total spawns pre-assigned (minimum number) +-- There is currently a bug in factorio that can cause desyncs if this number is much higher. +-- https://forums.factorio.com/viewtopic.php?f=7&t=68657 +-- Not sure you need that much anyways.... +-- Points are in an even grid layout. +VANILLA_SPAWN_COUNT = 60 + +-- Num tiles between each spawn. (I recommend at least 1000) +VANILLA_SPAWN_SPACING = 2000 + +--------------------------------------- +-- Resource & Spawn Circle Options +--------------------------------------- + +-- This is where you can modify what resources spawn, how much, where, etc. +-- Once you have a config you like, it's a good idea to save it for later use +-- so you don't lost it if you update the scenario. +OARC_CFG = { + + -- Misc spawn related config. + gen_settings = { + + -- THIS IS WHAT SETS THE SPAWN CIRCLE SIZE! + -- Create a circle of land area for the spawn + -- If you make this much bigger than a few chunks, good luck. + land_area_tiles = CHUNK_SIZE*1.8, + + -- Allow players to choose to spawn with a moat + moat_choice_enabled = true, + + -- If you change the spawn area size, you might have to adjust this as well + moat_size_modifier = 1, + + -- Start resource shape. true = circle, false = square. + resources_circle_shape = true, + + -- Force the land area circle at the spawn to be fully grass + force_grass = true, + + -- Spawn a circle/octagon of trees around the base outline. + tree_circle = true, + tree_octagon = false, + }, + + -- Safe Spawn Area Options + -- The default settings here are balanced for my recommended map gen settings (close to train world). + safe_area = + { + -- Safe area has no aliens + -- This is the radius in tiles of safe area. + safe_radius = CHUNK_SIZE*8, + + -- Warning area has significantly reduced aliens + -- This is the radius in tiles of warning area. + warn_radius = CHUNK_SIZE*16, + + -- 1 : X (spawners alive : spawners destroyed) in this area + warn_reduction = 20, + + -- Danger area has slightly reduce aliens + -- This is the radius in tiles of danger area. + danger_radius = CHUNK_SIZE*32, + + -- 1 : X (spawners alive : spawners destroyed) in this area + danger_reduction = 5, + }, + + -- Location of water strip (horizontal) + water = { + x_offset = -4, + y_offset = -48, + length = 8 + }, + + -- Handle placement of starting resources + resource_rand_pos_settings = + { + -- Autoplace resources (randomly in circle) + -- This will ignore the fixed x_offset/y_offset values in resource_tiles. + -- Only works for resource_tiles at the moment, not oil patches/water. + enabled = true, + -- Distance from center of spawn that resources are placed. + radius = 44, + -- At what angle (in radians) do resources start. + -- 0 means starts directly east. + -- Resources are placed clockwise from there. + angle_offset = 2.32, -- 2.32 is approx SSW. + -- At what andle do we place the last resource. + -- angle_offset and angle_final determine spacing and placement. + angle_final = 4.46 -- 4.46 is approx NNW. + }, + + -- Resource tiles + -- If you are running with mods like bobs/angels, you'll want to customize this. + resource_tiles = + { + ["iron-ore"] = + { + amount = 1800, + size = 16, + x_offset = -29, + y_offset = 16 + }, + ["copper-ore"] = + { + amount = 1500, + size = 14, + x_offset = -28, + y_offset = -3 + }, + ["stone"] = + { + amount = 1000, + size = 12, + x_offset = -27, + y_offset = -34 + }, + ["coal"] = + { + amount = 1500, + size = 12, + x_offset = -27, + y_offset = -20 + }--, + -- ["uranium-ore"] = + -- { + -- amount = 0, + -- size = 0, + -- x_offset = 17, + -- y_offset = -34 + -- } + + -- ####### Bobs + Angels ####### + -- DISABLE STARTING OIL PATCHES! + -- Coal = coal + -- Saphirite = angels-ore1 + -- Stiratite = angels-ore3 + -- Rubyte = angels-ore5 + -- Bobmonium = angels-ore6 + + -- ########## Bobs Ore ########## + -- Iron = iron-ore + -- Copper = copper-ore + -- Coal = coal + -- Stone = stone + -- Tin = tin-ore + -- Lead (Galena) = lead-ore + + -- See https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn/issues/11#issuecomment-479724909 + -- for full examples. + }, + + -- Special resource patches like oil + resource_patches = + { + ["crude-oil"] = + { + num_patches = 2, + amount = 900000, + x_offset_start = -3, + y_offset_start = 48, + x_offset_next = 6, + y_offset_next = 0 + } + }, +} + +--------------------------------------- +-- Other Forces/Teams Options +--------------------------------------- + +-- Separate teams +-- This allows you to join your own force/team. Everyone is still COOP/PvE, all +-- teams are friendly and cease-fire. +ENABLE_SEPARATE_TEAMS = true + +-- Main force is what default players join +MAIN_FORCE = "Main Force" + +-- Enable if players can allow others to join their base. +-- And specify how many including the host are allowed. +ENABLE_SHARED_SPAWNS = true +MAX_PLAYERS_AT_SHARED_SPAWN = 3 + +-- Share local team chat with all teams +-- This makes it so you don't have to use /s +-- But it also means you can't talk privately with your own team. +ENABLE_SHARED_TEAM_CHAT = true + +--------------------------------------- +-- Special Action Cooldowns +--------------------------------------- +RESPAWN_COOLDOWN_IN_MINUTES = 15 + +-- Require playes to be online for at least X minutes +-- Else their character is removed and their spawn point is freed up for use +MIN_ONLINE_TIME_IN_MINUTES = 15 + +-------------------------------------------------------------------------------- +-- Frontier Rocket Silo Options +-------------------------------------------------------------------------------- + +-- Number of silos found in the wild. +-- These will spawn in a circle at given distance from the center of the map +-- If you set this number too high, you'll have a lot of delay at the start of the game. +SILO_NUM_SPAWNS = 5 + +-- How many chunks away from the center of the map should the silo be spawned +SILO_CHUNK_DISTANCE = 200 + +-- If this is enabled, you get silos at the positions specified below. +-- (The other settings above are ignored in this case.) +SILO_FIXED_POSITION = false + +-- If you want to set fixed spawn locations for some silos. +SILO_POSITIONS = {{x = -1000, y = -1000}, + {x = -1000, y = 1000}, + {x = 1000, y = -1000}, + {x = 1000, y = 1000}} + +-- Set this to false so that you have to search for the silo's. +ENABLE_SILO_VISION = true + +-- Add beacons around the silo (Philip's mod) +ENABLE_SILO_BEACONS = false +ENABLE_SILO_RADAR = false + +-- Allow silos to be built by the player, but forces them to build in +-- the fixed locations. If this is false, silos are built and assigned +-- only to the main force. This can cause a problem for non main forces +-- when playing with LOCK_GOODIES_UNTIL_ROCKET_LAUNCH enabled. +ENABLE_SILO_PLAYER_BUILD = true + + +-------------------------------------------------------------------------------- +-- Long Reach Options +-------------------------------------------------------------------------------- +BUILD_DIST_BONUS = 64 +REACH_DIST_BONUS = BUILD_DIST_BONUS +RESOURCE_DIST_BONUS = 2 + +-------------------------------------------------------------------------------- +-- Autofill Options +-------------------------------------------------------------------------------- +AUTOFILL_TURRET_AMMO_QUANTITY = 10 + +-------------------------------------------------------------------------------- +-- ANTI-Griefing stuff ( I don't personally maintain this as I don't care for it.) +-- These things were added from other people's requests/changes and are disabled by default. +-------------------------------------------------------------------------------- +-- Enable this to disable deconstructing from map view, and setting a time limit +-- on ghost placements. +ENABLE_ANTI_GRIEFING = false + +-- Makes blueprint ghosts dissapear if they have been placed longer than this +-- ONLY has an effect if ENABLE_ANTI_GRIEFING is true! +GHOST_TIME_TO_LIVE = 10 * TICKS_PER_MINUTE + +-------------------------------------------------------------------------------- +-- This turns on writing chat and certain events to specific files so that I can +-- use that for discord integration. I suggest you leave this off unless you +-- know what you are doing. +-------------------------------------------------------------------------------- +ENABLE_SERVER_WRITE_FILES = false \ No newline at end of file diff --git a/example/example-map-gen-settings.json b/example/example-map-gen-settings.json new file mode 100644 index 0000000..13c1129 --- /dev/null +++ b/example/example-map-gen-settings.json @@ -0,0 +1,72 @@ +{ + "_terrain_segmentation_comment": "Inverse of map scale", + "terrain_segmentation": 1, + + "_water_comment": + [ + "Multiplier for water 'coverage' - higher increases the water level.", + "Water level = 10 * log2(this value)" + ], + "water": 1, + + "_comment_width+height": "Width and height of map, in tiles; 0 means infinite", + "width": 0, + "height": 0, + + "_starting_area_comment": "Multiplier for 'biter free zone radius'", + "starting_area": 1, + + "peaceful_mode": false, + + "autoplace_controls": + { + "coal" : {"frequency" : 0.20, "richness" : 0.50, "size" : 1.50}, + "copper-ore" : {"frequency" : 0.20, "richness" : 0.50, "size" : 1.50}, + "crude-oil" : {"frequency" : 0.20, "richness" : 0.50, "size" : 1.50}, + "enemy-base" : {"frequency" : 0.20, "richness" : 0.50, "size" : 0.50}, + "iron-ore" : {"frequency" : 0.20, "richness" : 0.50, "size" : 1.50}, + "stone" : {"frequency" : 0.20, "richness" : 0.50, "size" : 1.50}, + "trees" : {"frequency" : 0.50, "richness" : 1.00, "size" : 1.50}, + "uranium-ore" : {"frequency" : 0.20, "richness" : 0.50, "size" : 1.50} + }, + + "cliff_settings": + { + "_name_comment": "Name of the cliff prototype", + "name": "cliff", + + "_cliff_elevation_0_comment": "Elevation of first row of cliffs", + "cliff_elevation_0": 10, + + "_cliff_elevation_interval_comment": "Elevation difference between successive rows of cliffs", + "cliff_elevation_interval": 10, + + "_richness_comment": "Multiplier for cliff continuity; 0 will result in no cliffs, 10 will make all cliff rows completely solid", + "richness": 1 + }, + + "_property_expression_names_comment": + [ + "Overrides for property value generators", + "Elevation influences water and cliff placement.", + "Leave it blank to get 'normal' terrain.", + "Use '0_16-elevation' to reproduce terrain from 0.16.", + "Use '0_17-island' to get an island.", + "elevation'': ''0_17-island" + ], + "property_expression_names": + { + "control-setting:aux:bias": "0.00", + "control-setting:aux:frequency:multiplier": "1.00", + "control-setting:moisture:bias": "0.00", + "control-setting:moisture:frequency:multiplier": "1.00" + }, + + "starting_points": + [ + {"x": 0, "y": 0} + ], + + "_seed_comment": "Use null for a random seed, number for a specific seed.", + "seed": null +} \ No newline at end of file diff --git a/example/example-map-settings.json b/example/example-map-settings.json new file mode 100644 index 0000000..029d9fa --- /dev/null +++ b/example/example-map-settings.json @@ -0,0 +1,113 @@ +{ + "difficulty_settings": + { + "recipe_difficulty": 0, + "technology_difficulty": 0, + "technology_price_multiplier": 1 + }, + "pollution": + { + "enabled": true, + "_comment_min_to_diffuse_1": "these are values for 60 ticks (1 simulated second)", + "_comment_min_to_diffuse_2": "amount that is diffused to neighboring chunk", + "diffusion_ratio":0.02, + "min_to_diffuse":15, + "ageing":1, + "expected_max_per_chunk":7000, + "min_to_show_per_chunk":700, + "min_pollution_to_damage_trees": 3500, + "pollution_with_max_forest_damage": 10000, + "pollution_per_tree_damage": 2000, + "pollution_restored_per_tree_damage": 500, + "max_pollution_to_restore_trees": 1000 + }, + "enemy_evolution": + { + "enabled": true, + "time_factor": 0, + "destroy_factor": 0.001, + "pollution_factor": 0.000010 + }, + "enemy_expansion": + { + "enabled": true, + "min_base_spacing": 5, + "max_expansion_distance": 20, + "friendly_base_influence_radius": 2, + "enemy_building_influence_radius": 2, + "building_coefficient": 0.1, + "other_base_coefficient": 2.0, + "neighbouring_chunk_coefficient": 0.5, + "neighbouring_base_chunk_coefficient": 0.4, + "max_colliding_tiles_coefficient": 0.9, + "settler_group_min_size": 2, + "settler_group_max_size": 10, + "min_expansion_cooldown": 54000, + "max_expansion_cooldown": 216000 + }, + "unit_group": + { + "min_group_gathering_time": 3600, + "max_group_gathering_time": 14400, + "max_wait_time_for_late_members": 3600, + "max_group_radius": 30.0, + "min_group_radius": 5.0, + "max_member_speedup_when_behind": 1.4, + "max_member_slowdown_when_ahead": 0.6, + "max_group_slowdown_factor": 0.3, + "max_group_member_fallback_factor": 3, + "member_disown_distance": 10, + "tick_tolerance_when_member_arrives": 60, + "max_gathering_unit_groups": 30, + "max_unit_group_size": 20 + }, + "steering": + { + "default": + { + "radius": 1.2, + "separation_force": 0.005, + "separation_factor": 1.2, + "force_unit_fuzzy_goto_behavior": false + }, + "moving": + { + "radius": 3, + "separation_force": 0.01, + "separation_factor": 3, + "force_unit_fuzzy_goto_behavior": false + } + }, + "path_finder": + { + "fwd2bwd_ratio": 5, + "goal_pressure_ratio": 2, + "max_steps_worked_per_tick": 100, + "use_path_cache": true, + "short_cache_size": 5, + "long_cache_size": 25, + "short_cache_min_cacheable_distance": 10, + "short_cache_min_algo_steps_to_cache": 50, + "long_cache_min_cacheable_distance": 30, + "cache_max_connect_to_cache_steps_multiplier": 100, + "cache_accept_path_start_distance_ratio": 0.2, + "cache_accept_path_end_distance_ratio": 0.15, + "negative_cache_accept_path_start_distance_ratio": 0.3, + "negative_cache_accept_path_end_distance_ratio": 0.3, + "cache_path_start_distance_rating_multiplier": 10, + "cache_path_end_distance_rating_multiplier": 20, + "stale_enemy_with_same_destination_collision_penalty": 30, + "ignore_moving_enemy_collision_distance": 5, + "enemy_with_different_destination_collision_penalty": 30, + "general_entity_collision_penalty": 10, + "general_entity_subsequent_collision_penalty": 3, + "max_clients_to_accept_any_new_request": 10, + "max_clients_to_accept_short_new_request": 100, + "direct_distance_to_consider_short_request": 100, + "short_request_max_steps": 1000, + "short_request_ratio": 0.5, + "min_steps_to_check_path_find_termination": 2000, + "start_to_goal_cost_multiplier_to_terminate_path_find": 500.0 + }, + "max_failed_behavior_count": 3 +} diff --git a/lib/admin_commands.lua b/lib/admin_commands.lua new file mode 100644 index 0000000..a637d4f --- /dev/null +++ b/lib/admin_commands.lua @@ -0,0 +1,28 @@ +-- admin_commands.lua +-- May 2019 +-- +-- Yay, admin commands! + +require("lib/oarc_utils") + +-- Give yourself or another player, power armor +commands.add_command("powerstart", "give a start kit", function(command) + + local player = game.players[command.player_index] + local target = player + + if player ~= nil and player.admin then + if (command.parameter ~= nil) then + if game.players[command.parameter] ~= nil then + target = game.players[command.parameter] + else + target.print("Invalid player target. Double check the player name?") + return + end + end + + GiveQuickStartPowerArmor(target) + player.print("Gave a powerstart kit to " .. target.name) + target.print("You have been given a power armor starting kit!") + end +end) diff --git a/locale/frontier_silo.lua b/lib/frontier_silo.lua similarity index 55% rename from locale/frontier_silo.lua rename to lib/frontier_silo.lua index ccc12dc..90b958a 100644 --- a/locale/frontier_silo.lua +++ b/lib/frontier_silo.lua @@ -1,318 +1,408 @@ --- frontier_silo.lua --- Jan 2018 --- My take on frontier silos for my Oarc scenario - -require("config") -require("locale/oarc_utils") - --------------------------------------------------------------------------------- --- 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(num_silos) - if (global.siloPosition == nil) then - - global.siloPosition = {} - - random_angle_offset = math.random(0, math.pi * 2) - - for i=1,num_silos do - theta = ((math.pi * 2) / num_silos); - angle = (theta * i) + random_angle_offset; - - tx = (SILO_CHUNK_DISTANCE*CHUNK_SIZE * math.cos(angle)) - ty = (SILO_CHUNK_DISTANCE*CHUNK_SIZE * math.sin(angle)) - - table.insert(global.siloPosition, {x=math.floor(tx), y=math.floor(ty)}) - - log("Silo position: " .. tx .. ", " .. ty .. ", " .. angle) - end - end -end - --- Sets the global.siloPosition var to the set in the config file -function SetFixedSiloPosition(pos) - if (global.siloPosition == nil) then - global.siloPosition = {} - table.insert(global.siloPosition, SILO_POSITION) - end -end - --- Create a rocket silo at the specified positionmmmm --- Also makes sure tiles and entities are cleared if required. -local function CreateRocketSilo(surface, siloPosition, force) - - -- Delete any entities beneath the silo? - for _, entity in pairs(surface.find_entities_filtered{area = {{siloPosition.x-5, - siloPosition.y-6}, - {siloPosition.x+6, - siloPosition.y+6}}}) do - entity.destroy() - end - - -- Remove nearby enemies again - for _, entity in pairs(surface.find_entities_filtered{area = {{siloPosition.x-(CHUNK_SIZE*4), - siloPosition.y-(CHUNK_SIZE*4)}, - {siloPosition.x+(CHUNK_SIZE*4), - siloPosition.y+(CHUNK_SIZE*4)}}, force = "enemy"}) do - entity.destroy() - end - - -- Set tiles below the silo - tiles = {} - i = 1 - for dx = -5,5 do - for dy = -6,5 do - tiles[i] = {name = "concrete", position = {siloPosition.x+dx, siloPosition.y+dy}} - i=i+1 - end - end - surface.set_tiles(tiles, true) - - -- Create indestructible silo and assign to a force - local silo = surface.create_entity{name = "rocket-silo", position = {siloPosition.x+0.5, siloPosition.y}, force = force} - silo.destructible = false - silo.minable = false - - -- Make silo safe from being removed by regrowth - if ENABLE_REGROWTH then - OarcRegrowthOffLimits(siloPosition, 5) - end - - - if ENABLE_SILO_BEACONS then - PhilipsBeaconsAndShit(surface, siloPosition, game.forces[MAIN_FORCE]) - end - if ENABLE_SILO_RADAR then - PhilipsRadarAndShit(surface, siloPosition, game.forces[MAIN_FORCE]) - end - -end - --- Generates all rocket silos, should be called after the areas are generated --- Includes a crop circle -function GenerateAllSilos(surface) - - -- Create each silo in the list - for idx,siloPos in pairs(global.siloPosition) do - CreateRocketSilo(surface, siloPos, MAIN_FORCE) - end -end - --- Generate clean land and trees around silo area on chunk generate event -function GenerateRocketSiloChunk(event) - - -- Silo generation can take awhile depending on the number of silos. - if (game.tick < SILO_NUM_SPAWNS*10*TICKS_PER_SECOND) then - 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)} - - for idx,siloPos in pairs(global.siloPosition) do - local safeArea = {left_top= - {x=siloPos.x-(CHUNK_SIZE*4), - y=siloPos.y-(CHUNK_SIZE*4)}, - right_bottom= - {x=siloPos.x+(CHUNK_SIZE*4), - y=siloPos.y+(CHUNK_SIZE*4)}} - - - -- 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 - - -- Remove trees/resources inside the spawn area - RemoveInCircle(surface, chunkArea, "tree", siloPos, ENFORCE_LAND_AREA_TILE_DIST+5) - RemoveInCircle(surface, chunkArea, "resource", siloPos, ENFORCE_LAND_AREA_TILE_DIST+5) - RemoveInCircle(surface, chunkArea, "cliff", siloPos, ENFORCE_LAND_AREA_TILE_DIST+5) - RemoveDecorationsArea(surface, chunkArea) - - -- Create rocket silo - CreateCropOctagon(surface, siloPos, chunkArea, CHUNK_SIZE*2) - end - end - end -end - --- Generate chunks where we plan to place the rocket silos. -function GenerateRocketSiloAreas(surface) - for idx,siloPos in pairs(global.siloPosition) do - if (ENABLE_SILO_VISION) then - ChartRocketSiloAreas(surface, game.forces[MAIN_FORCE]) - end - surface.request_to_generate_chunks({siloPos.x, siloPos.y}, 3) - end -end - --- Chart chunks where we plan to place the rocket silos. -function ChartRocketSiloAreas(surface, force) - for idx,siloPos in pairs(global.siloPosition) do - force.chart(surface, {{siloPos.x-(CHUNK_SIZE*2), - siloPos.y-(CHUNK_SIZE*2)}, - {siloPos.x+(CHUNK_SIZE*2), - siloPos.y+(CHUNK_SIZE*2)}}) - end -end - -global.oarc_silos_generated = false -function DelayedSiloCreationOnTick(event) - - -- Delay the creation of the silos so we place them on already generated lands. - if (not global.oarc_silos_generated and (game.tick >= SILO_NUM_SPAWNS*10*TICKS_PER_SECOND)) then - DebugPrint("Frontier silos generated!") - global.oarc_silos_generated = true - GenerateAllSilos(game.surfaces[GAME_SURFACE_NAME]) - end - -end - - -function PhilipsBeaconsAndShit(surface, siloPos, force) - - -- Add Beacons - -- x = right, left; y = up, down - -- top 1 left 1 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y-9}, force = force} - beacon.destructible = false - beacon.minable = false - -- top 2 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-5, siloPos.y-9}, force = force} - beacon.destructible = false - beacon.minable = false - -- top 3 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-2, siloPos.y-9}, force = force} - beacon.destructible = false - beacon.minable = false - -- top 4 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+2, siloPos.y-9}, force = force} - beacon.destructible = false - beacon.minable = false - -- top 5 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+5, siloPos.y-9}, force = force} - beacon.destructible = false - beacon.minable = false - -- top 6 right 1 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y-9}, force = force} - beacon.destructible = false - beacon.minable = false - -- left 2 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-6, siloPos.y-6}, force = force} - beacon.destructible = false - beacon.minable = false - -- left 3 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-6, siloPos.y-3}, force = force} - beacon.destructible = false - beacon.minable = false - -- left 4 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-6, siloPos.y}, force = force} - beacon.destructible = false - beacon.minable = false - -- left 5 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-6, siloPos.y+3}, force = force} - beacon.destructible = false - beacon.minable = false - -- left 6 bottom 1 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y+6}, force = force} - beacon.destructible = false - beacon.minable = false - -- left 7 bottom 2 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-5, siloPos.y+6}, force = force} - beacon.destructible = false - beacon.minable = false - -- right 2 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+6, siloPos.y-6}, force = force} - beacon.destructible = false - beacon.minable = false - -- right 3 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+6, siloPos.y-3}, force = force} - beacon.destructible = false - beacon.minable = false - -- right 4 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+6, siloPos.y}, force = force} - beacon.destructible = false - beacon.minable = false - -- right 5 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+6, siloPos.y+3}, force = force} - beacon.destructible = false - beacon.minable = false - -- right 6 bottom 3 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+5, siloPos.y+6}, force = force} - beacon.destructible = false - beacon.minable = false - -- right 7 bottom 4 - local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y+6}, force = force} - beacon.destructible = false - beacon.minable = false - -- substations - -- top left - local substation = surface.create_entity{name = "substation", position = {siloPos.x-8, siloPos.y-6}, force = force} - substation.destructible = false - substation.minable = false - -- top right - local substation = surface.create_entity{name = "substation", position = {siloPos.x+9, siloPos.y-6}, force = force} - substation.destructible = false - substation.minable = false - -- bottom left - local substation = surface.create_entity{name = "substation", position = {siloPos.x-8, siloPos.y+4}, force = force} - substation.destructible = false - substation.minable = false - -- bottom right - local substation = surface.create_entity{name = "substation", position = {siloPos.x+9, siloPos.y+4}, force = force} - substation.destructible = false - substation.minable = false - - -- end adding beacons -end - -function PhilipsRadarAndShit(surface, siloPos, force) - - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-33, siloPos.y+3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-33, siloPos.y-3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-30, siloPos.y-6}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-27, siloPos.y-6}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-24, siloPos.y-6}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-24, siloPos.y-3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-24, siloPos.y}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-24, siloPos.y+3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-33, siloPos.y-6}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-30, siloPos.y+3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-27, siloPos.y+3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "radar", position = {siloPos.x-33, siloPos.y}, force = force} - radar.destructible = false - local substation = surface.create_entity{name = "substation", position = {siloPos.x-28, siloPos.y-1}, force = force} - substation.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-30, siloPos.y-1}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-30, siloPos.y-3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-30, siloPos.y+1}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-28, siloPos.y-3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-28, siloPos.y+1}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-26, siloPos.y-1}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-26, siloPos.y-3}, force = force} - radar.destructible = false - local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-26, siloPos.y+1}, force = force} - radar.destructible = false +-- frontier_silo.lua +-- Jan 2018 +-- My take on frontier silos for my Oarc scenario + +require("config") +require("lib/oarc_utils") + +-------------------------------------------------------------------------------- +-- Frontier style rocket silo stuff +-------------------------------------------------------------------------------- + + +function SpawnSilosAndGenerateSiloAreas() + + -- Special silo islands mode "boogaloo" + if (global.ocfg.silo_islands) then + + local num_spawns = #global.vanillaSpawns + local new_spawn_list = {} + + -- Pick out every OTHER vanilla spawn for the rocket silos. + for k,v in pairs(global.vanillaSpawns) do + if ((k <= num_spawns/2) and (k%2==1)) then + SetFixedSiloPosition({x=v.x,y=v.y}) + elseif ((k > num_spawns/2) and (k%2==0)) then + SetFixedSiloPosition({x=v.x,y=v.y}) + else + table.insert(new_spawn_list, v) + end + end + global.vanillaSpawns = new_spawn_list + + -- A set of fixed silo positions + elseif (global.ocfg.frontier_fixed_pos) then + for k,v in pairs(global.ocfg.frontier_pos_table) do + SetFixedSiloPosition(v) + end + + -- Random locations on a circle. + else + SetRandomSiloPosition(global.ocfg.frontier_silo_count) + + end + + -- Freezes the game at the start to generate all the chunks. + GenerateRocketSiloAreas(game.surfaces[GAME_SURFACE_NAME]) +end + +-- This creates a random silo position, stored to global.siloPosition +-- It uses the config setting global.ocfg.frontier_silo_distance and spawns the +-- silo somewhere on a circle edge with radius using that distance. +function SetRandomSiloPosition(num_silos) + if (global.siloPosition == nil) then + global.siloPosition = {} + end + + random_angle_offset = math.random(0, math.pi * 2) + + for i=1,num_silos do + theta = ((math.pi * 2) / num_silos); + angle = (theta * i) + random_angle_offset; + + tx = (global.ocfg.frontier_silo_distance*CHUNK_SIZE * math.cos(angle)) + ty = (global.ocfg.frontier_silo_distance*CHUNK_SIZE * math.sin(angle)) + + table.insert(global.siloPosition, {x=math.floor(tx), y=math.floor(ty)}) + + log("Silo position: " .. tx .. ", " .. ty .. ", " .. angle) + end +end + +-- Sets the global.siloPosition var to the set in the config file +function SetFixedSiloPosition(pos) + table.insert(global.siloPosition, pos) +end + +-- Create a rocket silo at the specified positionmmmm +-- Also makes sure tiles and entities are cleared if required. +local function CreateRocketSilo(surface, siloPosition, force) + + -- Delete any entities beneath the silo? + for _, entity in pairs(surface.find_entities_filtered{area = {{siloPosition.x-5, + siloPosition.y-6}, + {siloPosition.x+6, + siloPosition.y+6}}}) do + entity.destroy() + end + + -- Remove nearby enemies again + for _, entity in pairs(surface.find_entities_filtered{area = {{siloPosition.x-(CHUNK_SIZE*4), + siloPosition.y-(CHUNK_SIZE*4)}, + {siloPosition.x+(CHUNK_SIZE*4), + siloPosition.y+(CHUNK_SIZE*4)}}, force = "enemy"}) do + entity.destroy() + end + + -- Set tiles below the silo + tiles = {} + for dx = -10,10 do + for dy = -10,10 do + if (game.active_mods["oarc-restricted-build"]) then + table.insert(tiles, {name = global.ocfg.locked_build_area_tile, + position = {siloPosition.x+dx, siloPosition.y+dy}}) + else + if ((dx % 2 == 0) or (dx % 2 == 0)) then + table.insert(tiles, {name = "concrete", + position = {siloPosition.x+dx, siloPosition.y+dy}}) + else + table.insert(tiles, {name = "hazard-concrete-left", + position = {siloPosition.x+dx, siloPosition.y+dy}}) + end + end + end + end + surface.set_tiles(tiles, true) + + -- Create indestructible silo and assign to a force + if not global.ocfg.frontier_allow_build then + local silo = surface.create_entity{name = "rocket-silo", position = {siloPosition.x+0.5, siloPosition.y}, force = force} + silo.destructible = false + silo.minable = false + end + + -- TAG it on the main force at least. + game.forces[global.ocfg.main_force].add_chart_tag(game.surfaces[GAME_SURFACE_NAME], + {position=siloPosition, text="Rocket Silo", + icon={type="item",name="rocket-silo"}}) + + -- Make silo safe from being removed. + if global.ocfg.enable_regrowth then + remote.call("oarc_regrowth", + "area_offlimits_tilepos", + surface.index, + siloPosition, + 5) + end + + if ENABLE_SILO_BEACONS then + PhilipsBeacons(surface, siloPosition, game.forces[global.ocfg.main_force]) + end + if ENABLE_SILO_RADAR then + PhilipsRadar(surface, siloPosition, game.forces[global.ocfg.main_force]) + end + +end + +-- Generates all rocket silos, should be called after the areas are generated +-- Includes a crop circle +function GenerateAllSilos(surface) + + -- Create each silo in the list + for idx,siloPos in pairs(global.siloPosition) do + CreateRocketSilo(surface, siloPos, global.ocfg.main_force) + end +end + +-- Validates any attempt to build a silo. +-- Should be call in on_built_entity and on_robot_built_entity +function BuildSiloAttempt(event) + + -- Validation + if (event.created_entity == nil) then return end + + local e_name = event.created_entity.name + if (event.created_entity.name == "entity-ghost") then + e_name =event.created_entity.ghost_name + end + + if (e_name ~= "rocket-silo") then return end + + -- Check if it's in the right area. + local epos = event.created_entity.position + + for k,v in pairs(global.siloPosition) do + if (getDistance(epos, v) < 5) then + SendBroadcastMsg("Rocket silo has been built!") + return + end + end + + -- If we get here, means it wasn't in a valid position. Need to remove it. + if (event.created_entity.last_user ~= nil) then + FlyingText("Can't build silo here! Check the map!", epos, my_color_red, event.created_entity.surface) + if (event.created_entity.name == "entity-ghost") then + event.created_entity.destroy() + else + event.created_entity.last_user.mine_entity(event.created_entity, true) + end + else + log("ERROR! Rocket-silo had no valid last user?!?!") + end +end + +-- Generate clean land and trees around silo area on chunk generate event +function GenerateRocketSiloChunk(event) + + -- Silo generation can take awhile depending on the number of silos. + if (game.tick < #global.siloPosition*10*TICKS_PER_SECOND) then + 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)} + + for idx,siloPos in pairs(global.siloPosition) do + local safeArea = {left_top= + {x=siloPos.x-(CHUNK_SIZE*4), + y=siloPos.y-(CHUNK_SIZE*4)}, + right_bottom= + {x=siloPos.x+(CHUNK_SIZE*4), + y=siloPos.y+(CHUNK_SIZE*4)}} + + + -- 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 + + -- Remove trees/resources inside the spawn area + RemoveInCircle(surface, chunkArea, "tree", siloPos, global.ocfg.spawn_config.gen_settings.land_area_tiles+5) + RemoveInCircle(surface, chunkArea, "resource", siloPos, global.ocfg.spawn_config.gen_settings.land_area_tiles+5) + RemoveInCircle(surface, chunkArea, "cliff", siloPos, global.ocfg.spawn_config.gen_settings.land_area_tiles+5) + RemoveDecorationsArea(surface, chunkArea) + + -- Create rocket silo + CreateCropOctagon(surface, siloPos, chunkArea, CHUNK_SIZE*2, "grass-1") + end + end + end +end + +-- Generate chunks where we plan to place the rocket silos. +function GenerateRocketSiloAreas(surface) + for idx,siloPos in pairs(global.siloPosition) do + surface.request_to_generate_chunks({siloPos.x, siloPos.y}, 3) + end + if (global.ocfg.frontier_silo_vision) then + ChartRocketSiloAreas(surface, game.forces[global.ocfg.main_force]) + end +end + +-- Chart chunks where we plan to place the rocket silos. +function ChartRocketSiloAreas(surface, force) + for idx,siloPos in pairs(global.siloPosition) do + force.chart(surface, {{siloPos.x-(CHUNK_SIZE*2), + siloPos.y-(CHUNK_SIZE*2)}, + {siloPos.x+(CHUNK_SIZE*2), + siloPos.y+(CHUNK_SIZE*2)}}) + end +end + +global.oarc_silos_generated = false +function DelayedSiloCreationOnTick(surface) + + -- Delay the creation of the silos so we place them on already generated lands. + if (not global.oarc_silos_generated and (game.tick >= #global.siloPosition*10*TICKS_PER_SECOND)) then + log("Frontier silos generated!") + SendBroadcastMsg("Rocket silos are now available!") + global.oarc_silos_generated = true + GenerateAllSilos(surface) + end + +end + + +function PhilipsBeacons(surface, siloPos, force) + + -- Add Beacons + -- x = right, left; y = up, down + -- top 1 left 1 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y-8}, force = force} + beacon.destructible = false + beacon.minable = false + -- top 2 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-5, siloPos.y-8}, force = force} + beacon.destructible = false + beacon.minable = false + -- top 3 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-2, siloPos.y-8}, force = force} + beacon.destructible = false + beacon.minable = false + -- top 4 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+2, siloPos.y-8}, force = force} + beacon.destructible = false + beacon.minable = false + -- top 5 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+5, siloPos.y-8}, force = force} + beacon.destructible = false + beacon.minable = false + -- top 6 right 1 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y-8}, force = force} + beacon.destructible = false + beacon.minable = false + -- left 2 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y-5}, force = force} + beacon.destructible = false + beacon.minable = false + -- left 3 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y-2}, force = force} + beacon.destructible = false + beacon.minable = false + -- left 4 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y+2}, force = force} + beacon.destructible = false + beacon.minable = false + -- left 5 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y+5}, force = force} + beacon.destructible = false + beacon.minable = false + -- left 6 bottom 1 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-8, siloPos.y+8}, force = force} + beacon.destructible = false + beacon.minable = false + -- left 7 bottom 2 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x-5, siloPos.y+8}, force = force} + beacon.destructible = false + beacon.minable = false + -- right 2 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y-5}, force = force} + beacon.destructible = false + beacon.minable = false + -- right 3 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y-2}, force = force} + beacon.destructible = false + beacon.minable = false + -- right 4 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y+2}, force = force} + beacon.destructible = false + beacon.minable = false + -- right 5 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y+5}, force = force} + beacon.destructible = false + beacon.minable = false + -- right 6 bottom 3 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+5, siloPos.y+8}, force = force} + beacon.destructible = false + beacon.minable = false + -- right 7 bottom 4 + local beacon = surface.create_entity{name = "beacon", position = {siloPos.x+8, siloPos.y+8}, force = force} + beacon.destructible = false + beacon.minable = false + -- substations + -- top left + local substation = surface.create_entity{name = "substation", position = {siloPos.x-5, siloPos.y-5}, force = force} + substation.destructible = false + substation.minable = false + -- top right + local substation = surface.create_entity{name = "substation", position = {siloPos.x+6, siloPos.y-5}, force = force} + substation.destructible = false + substation.minable = false + -- bottom left + local substation = surface.create_entity{name = "substation", position = {siloPos.x-5, siloPos.y+6}, force = force} + substation.destructible = false + substation.minable = false + -- bottom right + local substation = surface.create_entity{name = "substation", position = {siloPos.x+6, siloPos.y+6}, force = force} + substation.destructible = false + substation.minable = false + + -- end adding beacons +end + +function PhilipsRadar(surface, siloPos, force) + + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-43, siloPos.y+3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-43, siloPos.y-3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-40, siloPos.y-6}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-37, siloPos.y-6}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-34, siloPos.y-6}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-34, siloPos.y-3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-34, siloPos.y}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-34, siloPos.y+3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-43, siloPos.y-6}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-40, siloPos.y+3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "solar-panel", position = {siloPos.x-37, siloPos.y+3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "radar", position = {siloPos.x-43, siloPos.y}, force = force} + radar.destructible = false + local substation = surface.create_entity{name = "substation", position = {siloPos.x-38, siloPos.y-1}, force = force} + substation.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-40, siloPos.y-1}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-40, siloPos.y-3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-40, siloPos.y+1}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-38, siloPos.y-3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-38, siloPos.y+1}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-36, siloPos.y-1}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-36, siloPos.y-3}, force = force} + radar.destructible = false + local radar = surface.create_entity{name = "accumulator", position = {siloPos.x-36, siloPos.y+1}, force = force} + radar.destructible = false end \ No newline at end of file diff --git a/lib/game_opts.lua b/lib/game_opts.lua new file mode 100644 index 0000000..3f5cf5e --- /dev/null +++ b/lib/game_opts.lua @@ -0,0 +1,144 @@ +-- game_opts.lua +-- Jan 2018 +-- Display current game options, maybe have some admin controls here + +-- Main Configuration File +require("config") +require("lib/oarc_utils") +require("lib/separate_spawns") + +function GameOptionsGuiClick(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 == "ban_player") then + local pIndex = event.element.parent.ban_players_dropdown.selected_index + + if (pIndex ~= 0) then + local banPlayer = event.element.parent.ban_players_dropdown.get_item(pIndex) + if (game.players[banPlayer]) then + game.ban_player(banPlayer, "Banned from admin panel.") + log("Banning " .. banPlayer) + end + end + end + + if (name == "restart_player") then + local pIndex = event.element.parent.ban_players_dropdown.selected_index + + if (pIndex ~= 0) then + local resetPlayer = event.element.parent.ban_players_dropdown.get_item(pIndex) + if (game.players[resetPlayer]) then + SeparateSpawnsPlayerCreated(resetPlayer) + log("Resetting " .. resetPlayer) + end + end + end +end + +-- Used by AddOarcGuiTab +function CreateGameOptionsTab(tab_container, player) + + -- General Server Info: + AddLabel(tab_container, "info_1", global.ocfg.welcome_msg, my_longer_label_style) + AddLabel(tab_container, "info_2", global.ocfg.server_rules, my_longer_label_style) + AddLabel(tab_container, "info_3", global.ocfg.server_contact, my_longer_label_style) + AddSpacerLine(tab_container) + + -- Enemy Settings: + local enemy_expansion_txt = "disabled" + if game.map_settings.enemy_expansion.enabled then enemy_expansion_txt = "enabled" end + + local enemy_text="Server Run Time: " .. formattime_hours_mins(game.tick) .. "\n" .. + "Current Evolution: " .. string.format("%.4f", game.forces["enemy"].evolution_factor) .. "\n" .. + "Enemy evolution time factor: " .. game.map_settings.enemy_evolution.time_factor .. "\n" .. + "Enemy evolution pollution factor: " .. game.map_settings.enemy_evolution.pollution_factor .. "\n" .. + "Enemy evolution destroy factor: " .. game.map_settings.enemy_evolution.destroy_factor .. "\n" .. + "Enemy expansion is " .. enemy_expansion_txt + + AddLabel(tab_container, "enemy_info", enemy_text, my_longer_label_style) + AddSpacerLine(tab_container) + + -- Game Mode: + AddLabel(tab_container, "core_mod_en", "Core game mode (separate spawns) is enabled.", my_longer_label_style) + + -- Soft Mods: + local soft_mods_string = "Oarc Core" + if (global.ocfg.enable_undecorator) then + soft_mods_string = soft_mods_string .. ", Undecorator" + end + if (global.ocfg.enable_tags) then + soft_mods_string = soft_mods_string .. ", Tags" + end + if (global.ocfg.enable_long_reach) then + soft_mods_string = soft_mods_string .. ", Long Reach" + end + if (global.ocfg.enable_autofill) then + soft_mods_string = soft_mods_string .. ", Auto Fill" + end + if (global.ocfg.enable_player_list) then + soft_mods_string = soft_mods_string .. ", Player List" + end + if (global.ocfg.enable_regrowth) then + soft_mods_string = soft_mods_string .. ", Regrowth" + end + + local game_info_str = "Soft Mods Enabled: " .. soft_mods_string + + -- Spawn options: + if (global.ocfg.enable_separate_teams) then + game_info_str = game_info_str.."\n".."You are allowed to spawn on your own team (have your own research tree). All teams are friendly!" + end + if (global.ocfg.enable_vanilla_spawns) then + game_info_str = game_info_str.."\n".."You are spawned in a default style starting area." + else + game_info_str = game_info_str.."\n".."You are spawned with a fix set of starting resources." + if (global.ocfg.enable_buddy_spawn) then + game_info_str = game_info_str.."\n".."You can chose to spawn alongside a buddy if you spawn together at the same time." + end + end + if (global.ocfg.enable_shared_spawns) then + game_info_str = game_info_str.."\n".."Spawn hosts may choose to share their spawn and allow other players to join them." + end + if (global.ocfg.enable_separate_teams and global.ocfg.enable_shared_team_vision) then + game_info_str = game_info_str.."\n".."Everyone (all teams) have shared vision." + end + if (global.ocfg.frontier_rocket_silo) then + game_info_str = game_info_str.."\n".."Silos are only placeable in certain areas on the map!" + end + if (global.ocfg.enable_regrowth) then + game_info_str = game_info_str.."\n".."Old parts of the map will slowly be deleted over time (chunks without any player buildings)." + end + if (ENABLE_POWER_ARMOR_QUICK_START) then + game_info_str = game_info_str.."\n".."Power armor quick start enabled." + end + if (global.ocfg.lock_goodies_rocket_launch) then + game_info_str = game_info_str.."\n".."Artillery/Nukes/ArmorMK2 tech and Prod/Speed 3 module recipes are locked until you launch a rocket!" + end + + + + AddLabel(tab_container, "game_info_label", game_info_str, my_longer_label_style) + + if (global.ocfg.enable_abandoned_base_removal) then + AddLabel(tab_container, "leave_warning_msg", "If you leave within " .. global.ocfg.minimum_online_time .. " minutes of joining, your base and character will be deleted.", my_longer_label_style) + tab_container.leave_warning_msg.style.font_color=my_color_red + end + + -- Ending Spacer + AddSpacerLine(tab_container) + + -- ADMIN CONTROLS + if (player.admin) then + player_list = {} + for _,player in pairs(game.connected_players) do + table.insert(player_list, player.name) + end + tab_container.add{name = "ban_players_dropdown", + type = "drop-down", + items = player_list} + tab_container.add{name="ban_player", type="button", caption="Ban Player"} + tab_container.add{name="restart_player", type="button", caption="Restart Player"} + end +end \ No newline at end of file diff --git a/locale/temp/helper_commands.lua b/lib/helper_commands.lua similarity index 98% rename from locale/temp/helper_commands.lua rename to lib/helper_commands.lua index fcfad06..fa3e8f0 100644 --- a/locale/temp/helper_commands.lua +++ b/lib/helper_commands.lua @@ -2,7 +2,7 @@ -- Jan 2018 -- None of this is my code. -require("locale/oarc_utils") +require("lib/oarc_utils") commands.add_command("run", "change player speed bonus", function(command) local player = game.players[command.player_index]; diff --git a/lib/oarc_global_cfg.lua b/lib/oarc_global_cfg.lua new file mode 100644 index 0000000..f67f9c1 --- /dev/null +++ b/lib/oarc_global_cfg.lua @@ -0,0 +1,211 @@ +-- oarc_global_cfg.lua +-- April 2019 +-- +-- Here is where we store/init config values to the global table. +-- Allows runtime modification of game settings if we want it. +-- Also allows supporting both MOD and SCENARIO versions. + +-- DON'T JUDGE ME + + +-- That's a LOT of settings. +function InitOarcConfig() + + global.ocfg = {} + + if (game.active_mods["clean-tutorial-grid"]) then + global.ocfg.locked_build_area_tile = "clean-tutorial-grid" + else + global.ocfg.locked_build_area_tile = "tutorial-grid" + end + + -- SCENARIO VERSION + if (not game.active_mods["oarc-mod"]) then + global.ocfg.welcome_title = WELCOME_MSG_TITLE + global.ocfg.welcome_msg = WELCOME_MSG + global.ocfg.server_rules = SERVER_MSG + global.ocfg.minimum_online_time = MIN_ONLINE_TIME_IN_MINUTES + global.ocfg.server_contact = CONTACT_MSG + global.ocfg.enable_vanilla_spawns = ENABLE_VANILLA_SPAWNS + global.ocfg.enable_buddy_spawn = ENABLE_BUDDY_SPAWN + global.ocfg.frontier_rocket_silo = FRONTIER_ROCKET_SILO_MODE + global.ocfg.silo_islands = SILO_ISLANDS_MODE + global.ocfg.enable_undecorator = ENABLE_UNDECORATOR + global.ocfg.enable_tags = ENABLE_TAGS + global.ocfg.enable_long_reach = ENABLE_LONGREACH + global.ocfg.enable_autofill = ENABLE_AUTOFILL + global.ocfg.enable_loaders = ENABLE_LOADERS + global.ocfg.enable_player_list = ENABLE_PLAYER_LIST + global.ocfg.list_offline_players = PLAYER_LIST_OFFLINE_PLAYERS + global.ocfg.enable_shared_team_vision = ENABLE_SHARED_TEAM_VISION + global.ocfg.enable_regrowth = ENABLE_REGROWTH + global.ocfg.enable_abandoned_base_removal = ENABLE_ABANDONED_BASE_REMOVAL + global.ocfg.enable_research_queue = ENABLE_RESEARCH_QUEUE + global.ocfg.lock_goodies_rocket_launch = LOCK_GOODIES_UNTIL_ROCKET_LAUNCH + + global.ocfg.modified_enemy_spawning = OARC_MODIFIED_ENEMY_SPAWNING + global.ocfg.near_dist_start = NEAR_MIN_DIST + global.ocfg.near_dist_end = NEAR_MAX_DIST + global.ocfg.far_dist_start = FAR_MIN_DIST + global.ocfg.far_dist_end = FAR_MAX_DIST + global.ocfg.vanilla_spawn_count = VANILLA_SPAWN_COUNT + global.ocfg.vanilla_spawn_spacing = VANILLA_SPAWN_SPACING + + global.ocfg.spawn_config = OARC_CFG + + global.ocfg.enable_separate_teams = ENABLE_SEPARATE_TEAMS + global.ocfg.main_force = MAIN_FORCE + global.ocfg.enable_shared_spawns = ENABLE_SHARED_SPAWNS + global.ocfg.max_players_shared_spawn = MAX_PLAYERS_AT_SHARED_SPAWN + global.ocfg.enable_shared_chat = ENABLE_SHARED_TEAM_CHAT + global.ocfg.respawn_cooldown_min = RESPAWN_COOLDOWN_IN_MINUTES + global.ocfg.frontier_silo_count = SILO_NUM_SPAWNS + global.ocfg.frontier_silo_distance = SILO_CHUNK_DISTANCE + global.ocfg.frontier_fixed_pos = SILO_FIXED_POSITION + global.ocfg.frontier_pos_table = SILO_POSITIONS + global.ocfg.frontier_silo_vision = ENABLE_SILO_VISION + global.ocfg.frontier_allow_build = ENABLE_SILO_PLAYER_BUILD + + global.ocfg.enable_server_write_files = ENABLE_SERVER_WRITE_FILES + + -- MOD VERSION + else + log("Oarc MOD! Version: " .. game.active_mods["oarc-mod"].version) + + global.ocfg.welcome_title = settings.global["oarc-welcome-title"].value + global.ocfg.welcome_msg = settings.global["oarc-welcome-msg"].value + global.ocfg.server_rules = settings.global["oarc-server-rules"].value + global.ocfg.minimum_online_time = settings.global["oarc-minimum-online-time"].value + global.ocfg.server_contact = settings.global["oarc-server-contact"].value + global.ocfg.enable_vanilla_spawns = settings.global["oarc-enable-vanilla-spawns"].value + global.ocfg.enable_buddy_spawn = settings.global["oarc-enable-buddy-spawn"].value + global.ocfg.frontier_rocket_silo = settings.global["oarc-frontier-rocket-silo"].value + global.ocfg.enable_undecorator = settings.global["oarc-enable-undecorator"].value + global.ocfg.enable_tags = settings.global["oarc-enable-tags"].value + global.ocfg.enable_long_reach = settings.global["oarc-enable-long-reach"].value + global.ocfg.enable_autofill = settings.global["oarc-enable-autofill"].value + global.ocfg.enable_loaders = false + global.ocfg.enable_player_list = settings.global["oarc-enable-player-list"].value + global.ocfg.list_offline_players = settings.global["oarc-list-offline-players"].value + global.ocfg.enable_shared_team_vision = settings.global["oarc-enable-shared-team-vision"].value + global.ocfg.enable_regrowth = settings.global["oarc-enable-regrowth"].value + global.ocfg.enable_research_queue = settings.global["oarc-enable-research-queue"].value + global.ocfg.lock_goodies_rocket_launch = false + global.ocfg.modified_enemy_spawning = settings.global["oarc-modified-enemy-spawning"].value + global.ocfg.near_dist_start = settings.global["oarc-near-dist-start"].value + global.ocfg.near_dist_end = settings.global["oarc-near-dist-end"].value + global.ocfg.far_dist_start = settings.global["oarc-far-dist-start"].value + global.ocfg.far_dist_end = settings.global["oarc-far-dist-end"].value + global.ocfg.vanilla_spawn_count = settings.global["oarc-vanilla-spawn-count"].value + global.ocfg.vanilla_spawn_spacing = settings.global["oarc-vanilla-spawn-spacing"].value + + global.ocfg.spawn_config = { + gen_settings = { + land_area_tiles = settings.global["oarc-enforce-land-area-tile-dist"].value, + moat_choice_enabled = settings.global["oarc-allow-moat-choice"].value, + moat_size_modifier = settings.global["oarc-moat-size-mod"].value, + resources_circle_shape = settings.global["oarc-resource-shape-circle"].value, + force_grass = settings.global["oarc-force-grass"].value, + tree_circle = settings.global["oarc-tree-circle"].value, + tree_octagon = settings.global["oarc-tree-octagon"].value, + }, + safe_area = + { + safe_radius = CHUNK_SIZE*10, + warn_radius = CHUNK_SIZE*20, + warn_reduction = 20, + danger_radius = CHUNK_SIZE*50, + danger_reduction = 5, + }, + water = { + x_offset = settings.global["oarc-water-x-offset"].value, + y_offset = settings.global["oarc-water-y-offset"].value, + length = settings.global["oarc-water-length"].value, + }, + resource_rand_pos_settings = + { + enabled = settings.global["oarc-resource-rand-pos-enabled"].value, + radius = settings.global["oarc-resource-rand-pos-radius"].value, + angle_offset = settings.global["oarc-resource-rand-pos-angle-offset"].value, + angle_final = settings.global["oarc-resource-rand-pos-angle-final"].value, + }, + resource_tiles = + { + [settings.global["oarc-resource-1-name"].value] = + { + amount = settings.global["oarc-resource-1-amount"].value, + size = settings.global["oarc-resource-1-size"].value, + x_offset = -29, + y_offset = 16 + }, + [settings.global["oarc-resource-2-name"].value] = + { + amount = settings.global["oarc-resource-2-amount"].value, + size = settings.global["oarc-resource-2-size"].value, + x_offset = -28, + y_offset = -3 + }, + [settings.global["oarc-resource-3-name"].value] = + { + amount = settings.global["oarc-resource-3-amount"].value, + size = settings.global["oarc-resource-3-size"].value, + x_offset = -27, + y_offset = -34 + }, + [settings.global["oarc-resource-4-name"].value] = + { + amount = settings.global["oarc-resource-4-amount"].value, + size = settings.global["oarc-resource-4-size"].value, + x_offset = -27, + y_offset = -20 + } + -- [settings.global["oarc-resource-5-name"].value] = + -- { + -- amount = settings.global["oarc-resource-5-amount"].value, + -- size = settings.global["oarc-resource-5-size"].value, + -- x_offset = -27, + -- y_offset = -20 + -- } + }, + resource_patches = + { + [settings.global["oarc-resource-patch-1-name"].value] = + { + num_patches = settings.global["oarc-resource-patch-1-count"].value, + amount = settings.global["oarc-resource-patch-1-amount"].value, + x_offset_start = 0, + y_offset_start = 48, + x_offset_next = 4, + y_offset_next = 0 + } + }, + } + + global.ocfg.enable_separate_teams = settings.global["oarc-enable-separate-teams"].value + global.ocfg.main_force = settings.global["oarc-main-force"].value + global.ocfg.enable_shared_spawns = settings.global["oarc-enable-shared-spawns"].value + global.ocfg.max_players_shared_spawn = settings.global["oarc-max-players-shared-spawn"].value + global.ocfg.enable_shared_chat = settings.global["oarc-enable-shared-chat"].value + global.ocfg.respawn_cooldown_min = settings.global["oarc-respawn-cooldown-min"].value + global.ocfg.frontier_silo_count = settings.global["oarc-frontier-silo-count"].value + global.ocfg.frontier_silo_distance = settings.global["oarc-frontier-silo-distance"].value + global.ocfg.frontier_fixed_pos = false + global.ocfg.frontier_pos_table = {{x = 0, y = 100}} + global.ocfg.frontier_silo_vision = settings.global["oarc-frontier-silo-vision"].value + global.ocfg.frontier_allow_build = true + end + + + ----------------------- + -- VALIDATION CHECKS -- + ----------------------- + + if (not global.ocfg.frontier_rocket_silo or not global.ocfg.enable_vanilla_spawns) then + global.ocfg.silo_islands = false + end + + if (global.ocfg.enable_vanilla_spawns) then + global.ocfg.enable_buddy_spawn = false + end + +end \ No newline at end of file diff --git a/lib/oarc_gui_tabs.lua b/lib/oarc_gui_tabs.lua new file mode 100644 index 0000000..75b87bc --- /dev/null +++ b/lib/oarc_gui_tabs.lua @@ -0,0 +1,246 @@ +-- oarc_gui_tabs.lua + +-------------------------------------------------------------------------------- +-- GUI Tab Handler +-------------------------------------------------------------------------------- + +-- NAME of the top level element (outer frame) +local OARC_GUI = "oarc_gui" + +-- LIST of all implemented tabs and their content Functions +OARC_GAME_OPTS_GUI_TAB_NAME = "Server Info" +OARC_SPAWN_CTRL_GUI_NAME = "Spawn Controls" +OARC_PLAYER_LIST_GUI_TAB_NAME = "Players" +OARC_TAGS_GUI_TAB_NAME = "Name Tags" +OARC_ROCKETS_GUI_TAB_NAME = "Rockets" + +local OARC_GUI_TAB_CONTENT_FUNCTIONS = {} +OARC_GUI_TAB_CONTENT_FUNCTIONS["Server Info"] = CreateGameOptionsTab +OARC_GUI_TAB_CONTENT_FUNCTIONS["Spawn Controls"] = CreateSpawnCtrlGuiTab +OARC_GUI_TAB_CONTENT_FUNCTIONS["Players"] = CreatePlayerListGuiTab +OARC_GUI_TAB_CONTENT_FUNCTIONS["Name Tags"] = CreateTagGuiTab +OARC_GUI_TAB_CONTENT_FUNCTIONS["Rockets"] = CreateRocketGuiTab + +function InitOarcGuiTabs(player) + CreateOarcGuiButton(player) + + -- Add general info tab + AddOarcGuiTab(player, OARC_GAME_OPTS_GUI_TAB_NAME) + SetOarcGuiTabEnabled(player, OARC_GAME_OPTS_GUI_TAB_NAME, true) + + -- Spawn control tab, disabled by default + AddOarcGuiTab(player, OARC_SPAWN_CTRL_GUI_NAME) + + -- If player list is enabled, create that + if global.ocfg.enable_player_list then + AddOarcGuiTab(player, OARC_PLAYER_LIST_GUI_TAB_NAME) + SetOarcGuiTabEnabled(player, OARC_PLAYER_LIST_GUI_TAB_NAME, true) + end + + -- Player tags + if global.ocfg.enable_tags then + AddOarcGuiTab(player, OARC_TAGS_GUI_TAB_NAME) + SetOarcGuiTabEnabled(player, OARC_TAGS_GUI_TAB_NAME, true) + end + + -- Rockets tab, only enable if one has been launched already + AddOarcGuiTab(player, OARC_ROCKETS_GUI_TAB_NAME) + if (global.satellite_sent) then + SetOarcGuiTabEnabled(player, OARC_ROCKETS_GUI_TAB_NAME, true) + end +end + +function CreateOarcGuiButton(player) + if (mod_gui.get_button_flow(player).oarc_button == nil) then + local b = mod_gui.get_button_flow(player).add{name="oarc_button", + type="sprite-button", + sprite="utility/expand_dots", + style=mod_gui.button_style} + b.style.padding=2 + b.style.width=20 + end +end + +function DoesOarcGuiExist(player) + return (mod_gui.get_frame_flow(player)[OARC_GUI] ~= nil) +end + +function ToggleOarcGuiVisible(player) + local of = mod_gui.get_frame_flow(player)[OARC_GUI] + if (of ~= nil) then + of.visible = not of.visible + end +end + +function GetOarcGuiTabsPane(player) + if (mod_gui.get_frame_flow(player)[OARC_GUI] == nil) then + return nil + else + return mod_gui.get_frame_flow(player)[OARC_GUI].oarc_if.oarc_tabs + end +end + +function ClickOarcGuiButton(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 ~= "oarc_button") then return end + if (not DoesOarcGuiExist(player)) then + CreateOarcGuiTabsPane(player) + else + ToggleOarcGuiVisible(player) + FakeTabChangeEventOarcGui(player) + end +end + +function TabChangeOarcGui(event) + if (event.element.name ~= "oarc_tabs") then return end + + local player = game.players[event.player_index] + local otabs = event.element + local selected_tab_name = otabs.tabs[otabs.selected_tab_index].tab.name + + -- Clear all tab contents + for i,t in pairs(otabs.tabs) do + t.content.clear() + end + + SetOarGuiTabContent(player, selected_tab_name) +end + +function FakeTabChangeEventOarcGui(player) + local event = {} + event.element = GetOarcGuiTabsPane(player) + event.player_index = player.index + TabChangeOarcGui(event) +end + +function CreateOarcGuiTabsPane(player) + + if (mod_gui.get_frame_flow(player)[OARC_GUI] == nil) then + + -- OUTER FRAME (TOP GUI ELEMENT) + local frame = mod_gui.get_frame_flow(player).add{ + type = 'frame', + name = OARC_GUI, + direction = "vertical"} + frame.style.padding = 5 + + -- INNER FRAME + local inside_frame = frame.add{ + type = "frame", + name = "oarc_if", + style = "inside_deep_frame", + direction = "vertical" + } + + -- SUB HEADING w/ LABEL + local subhead = inside_frame.add{ + type="frame", + name="sub_header", + style = "changelog_subheader_frame"} + AddLabel(subhead, "scen_info", "Scenario Info and Controls", "subheader_caption_label") + + -- TABBED PANE + local oarc_tabs = inside_frame.add{ + name="oarc_tabs", + type="tabbed-pane", + style="tabbed_pane"} + oarc_tabs.style.top_padding = 8 + end +end + +-- Function creates a new tab. +-- It adds whatever it wants to the provided scroll-pane. +-- content_function takes a content holder GUI and player +function AddOarcGuiTab(player, tab_name, content_function) + if (not DoesOarcGuiExist(player)) then + CreateOarcGuiTabsPane(player) + ToggleOarcGuiVisible(player) + end + + -- Get the tabbed pane + local otabs = GetOarcGuiTabsPane(player) + + -- Create new tab + local new_tab = otabs.add{ + type="tab", + name=tab_name, + caption=tab_name} + + -- Create inside frame for content + local tab_inside_frame = otabs.add{ + type="frame", + name=tab_name.."_if", + style = "inside_deep_frame", + direction="vertical"} + tab_inside_frame.style.left_margin = 10 + tab_inside_frame.style.right_margin = 10 + tab_inside_frame.style.top_margin = 4 + tab_inside_frame.style.bottom_margin = 4 + tab_inside_frame.style.padding = 5 + tab_inside_frame.style.horizontally_stretchable = true + -- tab_inside_frame.style.vertically_stretchable = true + -- tab_inside_frame.style.horizontally_squashable = true + -- tab_inside_frame.style.vertically_squashable = true + + -- Add the whole thing to the tab now. + otabs.add_tab(new_tab, tab_inside_frame) + + -- Disable all new tabs by default + new_tab.enabled = false + + -- If no other tabs are selected, select the first one. + if (otabs.selected_tab_index == nil) then + otabs.selected_tab_index = 1 + end + + -- if (global.oarc_gui_tab_funcs == nil) then + -- global.oarc_gui_tab_funcs = {} + -- end + + -- global.oarc_gui_tab_funcs[tab_name] = content_function +end + + +function SetOarGuiTabContent(player, tab_name) + if (not DoesOarcGuiExist(player)) then return end + + local otabs = GetOarcGuiTabsPane(player) + + for _,t in ipairs(otabs.tabs) do + if (t.tab.name == tab_name) then + t.content.clear() + OARC_GUI_TAB_CONTENT_FUNCTIONS[tab_name](t.content, player) + return + end + end +end + +function SetOarcGuiTabEnabled(player, tab_name, enable) + if (not DoesOarcGuiExist(player)) then return end + + local otabs = GetOarcGuiTabsPane(player) + + for _,t in ipairs(otabs.tabs) do + if (t.tab.name == tab_name) then + t.tab.enabled = enable + return + end + end +end + +function SwitchOarcGuiTab(player, tab_name) + if (not DoesOarcGuiExist(player)) then return end + + local otabs = GetOarcGuiTabsPane(player) + + for i,t in pairs(otabs.tabs) do + if (t.tab.name == tab_name) then + otabs.selected_tab_index = i + FakeTabChangeEventOarcGui(player) + return + end + end +end \ No newline at end of file diff --git a/lib/oarc_gui_utils.lua b/lib/oarc_gui_utils.lua new file mode 100644 index 0000000..da111d5 --- /dev/null +++ b/lib/oarc_gui_utils.lua @@ -0,0 +1,137 @@ +-- oarc_gui_utils.lua +-- Mar 2019 + +-- Generic GUI stuff goes here. + +-------------------------------------------------------------------------------- +-- GUI Styles +-------------------------------------------------------------------------------- + +my_fixed_width_style = { + minimal_width = 450, + maximal_width = 450 +} +my_label_style = { + -- minimal_width = 450, + -- maximal_width = 50, + single_line = false, + font_color = {r=1,g=1,b=1}, + top_padding = 0, + bottom_padding = 0 +} +my_label_header_style = { + single_line = false, + font = "heading-1", + font_color = {r=1,g=1,b=1}, + top_padding = 0, + bottom_padding = 0 +} +my_label_header_grey_style = { + single_line = false, + font = "heading-1", + font_color = {r=0.6,g=0.6,b=0.6}, + top_padding = 0, + bottom_padding = 0 +} +my_note_style = { + -- minimal_width = 450, + single_line = false, + font = "default-small-semibold", + font_color = {r=1,g=0.5,b=0.5}, + top_padding = 0, + bottom_padding = 0 +} +my_warning_style = { + -- minimal_width = 450, + -- maximal_width = 450, + single_line = false, + font_color = {r=1,g=0.1,b=0.1}, + top_padding = 0, + bottom_padding = 0 +} +my_spacer_style = { + minimal_height = 10, + top_padding = 0, + bottom_padding = 0 +} +my_small_button_style = { + font = "default-small-semibold" +} +my_player_list_fixed_width_style = { + minimal_width = 200, + maximal_width = 400, + maximal_height = 200 +} +my_player_list_admin_style = { + font = "default-semibold", + font_color = {r=1,g=0.5,b=0.5}, + minimal_width = 200, + top_padding = 0, + bottom_padding = 0, + single_line = false, +} +my_player_list_style = { + font = "default-semibold", + minimal_width = 200, + top_padding = 0, + bottom_padding = 0, + single_line = false, +} +my_player_list_offline_style = { + -- font = "default-semibold", + font_color = {r=0.5,g=0.5,b=0.5}, + minimal_width = 200, + top_padding = 0, + bottom_padding = 0, + single_line = false, +} +my_player_list_style_spacer = { + minimal_height = 20, +} +my_color_red = {r=1,g=0.1,b=0.1} + +my_longer_label_style = { + maximal_width = 600, + single_line = false, + font_color = {r=1,g=1,b=1}, + top_padding = 0, + bottom_padding = 0 +} +my_longer_warning_style = { + maximal_width = 600, + single_line = false, + font_color = {r=1,g=0.1,b=0.1}, + top_padding = 0, + bottom_padding = 0 +} + +-------------------------------------------------------------------------------- +-- GUI Functions +-------------------------------------------------------------------------------- + +-- Apply a style option to a GUI +function ApplyStyle (guiIn, styleIn) + for k,v in pairs(styleIn) do + guiIn.style[k]=v + end +end + +-- Shorter way to add a label with a style +function AddLabel(guiIn, name, message, style) + local g = guiIn.add{name = name, type = "label", + caption=message} + if (type(style) == "table") then + ApplyStyle(g, style) + else + g.style = style + end +end + +-- Shorter way to add a spacer +function AddSpacer(guiIn) + ApplyStyle(guiIn.add{type = "label", caption=" "}, my_spacer_style) +end + +function AddSpacerLine(guiIn) + ApplyStyle(guiIn.add{type = "line", direction="horizontal"}, my_spacer_style) +end \ No newline at end of file diff --git a/lib/oarc_utils.lua b/lib/oarc_utils.lua new file mode 100644 index 0000000..f35d52b --- /dev/null +++ b/lib/oarc_utils.lua @@ -0,0 +1,1064 @@ +-- oarc_utils.lua +-- Nov 2016 +-- +-- My general purpose utility functions for factorio +-- Also contains some constants and gui styles + +require("lib/oarc_gui_utils") +require("mod-gui") + +-------------------------------------------------------------------------------- +-- Useful constants +-------------------------------------------------------------------------------- +CHUNK_SIZE = 32 +MAX_FORCES = 64 +TICKS_PER_SECOND = 60 +TICKS_PER_MINUTE = TICKS_PER_SECOND * 60 +TICKS_PER_HOUR = TICKS_PER_MINUTE * 60 +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- General Helper Functions +-------------------------------------------------------------------------------- + +-- Prints flying text. +-- Color is optional +function FlyingText(msg, pos, color, surface) + if color == nil then + surface.create_entity({ name = "flying-text", position = pos, text = msg }) + else + surface.create_entity({ name = "flying-text", position = pos, text = msg, color = color }) + end +end + +-- Broadcast messages to all connected players +function SendBroadcastMsg(msg) + for name,player in pairs(game.connected_players) do + player.print(msg) + end +end + +-- Send a message to a player, safely checks if they exist and are online. +function SendMsg(playerName, msg) + if ((game.players[playerName] ~= nil) and (game.players[playerName].connected)) then + game.players[playerName].print(msg) + end +end + +-- Simple way to write to a file. Always appends. Only server. +-- Has a global setting for enable/disable +function ServerWriteFile(filename, msg) + if (global.ocfg.enable_server_write_files) then + game.write_file(filename, msg, true, 0) + end +end + +-- Useful for displaying game time in mins:secs format +function formattime(ticks) + local seconds = ticks / 60 + local minutes = math.floor((seconds)/60) + local seconds = math.floor(seconds - 60*minutes) + return string.format("%dm:%02ds", minutes, seconds) +end + +-- Useful for displaying game time in mins:secs format +function formattime_hours_mins(ticks) + local seconds = ticks / 60 + local minutes = math.floor((seconds)/60) + local hours = math.floor((minutes)/60) + local minutes = math.floor(minutes - 60*hours) + return string.format("%dh:%02dm", hours, minutes) +end + +-- Simple function to get total number of items in table +function TableLength(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +-- Fisher-Yares shuffle +-- https://stackoverflow.com/questions/35572435/how-do-you-do-the-fisher-yates-shuffle-in-lua +function FYShuffle(tInput) + local tReturn = {} + for i = #tInput, 1, -1 do + local j = math.random(i) + tInput[i], tInput[j] = tInput[j], tInput[i] + table.insert(tReturn, tInput[i]) + end + return tReturn +end + +-- Simple function to get distance between two positions. +function getDistance(posA, posB) + -- Get the length for each of the components x and y + local xDist = posB.x - posA.x + local yDist = posB.y - posA.y + + return math.sqrt( (xDist ^ 2) + (yDist ^ 2) ) +end + +-- Given a table of positions, returns key for closest to given pos. +function GetClosestPosFromTable(pos, pos_table) + + local closest_dist = nil + local closest_key = nil + + for k,p in pairs(pos_table) do + local new_dist = getDistance(pos, p) + if (closest_dist == nil) then + closest_dist = new_dist + closest_key = k + elseif (closest_dist > new_dist) then + closest_dist = new_dist + closest_key = k + end + end +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(defines.inventory.character_armor) ~= nil and player.get_inventory(defines.inventory.character_armor)[1] ~= nil then + local p_armor = player.get_inventory(defines.inventory.character_armor)[1].grid + if p_armor ~= nil then + p_armor.put({name = "fusion-reactor-equipment"}) + p_armor.put({name = "exoskeleton-equipment"}) + p_armor.put({name = "battery-mk2-equipment"}) + p_armor.put({name = "battery-mk2-equipment"}) + p_armor.put({name = "personal-roboport-mk2-equipment"}) + p_armor.put({name = "personal-roboport-mk2-equipment"}) + p_armor.put({name = "personal-roboport-mk2-equipment"}) + p_armor.put({name = "battery-mk2-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + p_armor.put({name = "solar-panel-equipment"}) + end + player.insert{name="construction-robot", count = 100} + player.insert{name="belt-immunity-equipment", count = 1} + end +end + +-- Create area given point and radius-distance +function GetAreaFromPointAndDistance(point, dist) + local area = {left_top= + {x=point.x-dist, + y=point.y-dist}, + right_bottom= + {x=point.x+dist, + y=point.y+dist}} + return area +end + +-- Check if given position is in area bounding box +function CheckIfInArea(point, area) + if ((point.x >= area.left_top.x) and (point.x < area.right_bottom.x)) then + if ((point.y >= area.left_top.y) and (point.y < area.right_bottom.y)) then + return true + end + end + return false +end + +-- Set all forces to ceasefire +function SetCeaseFireBetweenAllForces() + for name,team in pairs(game.forces) do + if name ~= "neutral" and name ~= "enemy" then + for x,y in pairs(game.forces) do + if x ~= "neutral" and x ~= "enemy" then + team.set_cease_fire(x,true) + end + end + end + end +end + +-- Set all forces to friendly +function SetFriendlyBetweenAllForces() + for name,team in pairs(game.forces) do + if name ~= "neutral" and name ~= "enemy" then + for x,y in pairs(game.forces) do + if x ~= "neutral" and x ~= "enemy" then + team.set_friend(x,true) + end + end + end + end +end + +-- For each other player force, share a chat msg. +function ShareChatBetweenForces(player, msg) + for _,force in pairs(game.forces) do + if (force ~= nil) then + if ((force.name ~= enemy) and + (force.name ~= neutral) and + (force.name ~= player) and + (force ~= player.force)) then + force.print(player.name..": "..msg) + end + end + end +end + +-- Merges force2 INTO force1 but keeps all research between both forces. +function MergeForcesKeepResearch(force1, force2) + for techName,luaTech in pairs(force2.technologies) do + if (luaTech.researched) then + force1.technologies[techName].researched = true + force1.technologies[techName].level = luaTech.level + end + end + game.merge_forces(force2, force1) +end + +-- Undecorator +function RemoveDecorationsArea(surface, area) + surface.destroy_decoratives{area=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 + +-- 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 + log("direction: x=" .. randVec.x .. ", y=" .. randVec.y) + return randVec +end + +-- Check for ungenerated chunks around a specific chunk +-- +/- chunkDist in x and y directions +function IsChunkAreaUngenerated(chunkPos, chunkDist, surface) + for x=-chunkDist, chunkDist do + for y=-chunkDist, chunkDist do + local checkPos = {x=chunkPos.x+x, + y=chunkPos.y+y} + if (surface.is_chunk_generated(checkPos)) then + return false + end + end + end + return true +end + +-- Clear out enemies around an area with a certain distance +function ClearNearbyEnemies(pos, safeDist, surface) + local safeArea = {left_top= + {x=pos.x-safeDist, + y=pos.y-safeDist}, + right_bottom= + {x=pos.x+safeDist, + y=pos.y+safeDist}} + + for _, entity in pairs(surface.find_entities_filtered{area = safeArea, force = "enemy"}) do + entity.destroy() + end +end + +-- Function to find coordinates of ungenerated map area in a given direction +-- starting from the center of the map +function FindMapEdge(directionVec, surface) + local position = {x=0,y=0} + local chunkPos = {x=0,y=0} + + -- Keep checking chunks in the direction of the vector + while(true) do + + -- Set some absolute limits. + if ((math.abs(chunkPos.x) > 1000) or (math.abs(chunkPos.y) > 1000)) then + break + + -- If chunk is already generated, keep looking + elseif (surface.is_chunk_generated(chunkPos)) then + chunkPos.x = chunkPos.x + directionVec.x + chunkPos.y = chunkPos.y + directionVec.y + + -- Found a possible ungenerated area + else + + chunkPos.x = chunkPos.x + directionVec.x + chunkPos.y = chunkPos.y + directionVec.y + + -- Check there are no generated chunks in a 10x10 area. + if IsChunkAreaUngenerated(chunkPos, 10, surface) then + position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) + position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) + break + end + end + end + + -- log("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 + log("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 + + log("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 + +-- General command for disabling a tech. +function DisableTech(force, techName) + if force.technologies[techName] then + force.technologies[techName].enabled = false + force.technologies[techName].visible_when_disabled = true + end +end + +-- General command for enabling a tech. +function EnableTech(force, techName) + if force.technologies[techName] then + force.technologies[techName].enabled = true + end +end + + +-- Get 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 + +-- Gets chunk position of a tile. +function GetChunkPosFromTilePos(tile_pos) + return {x=math.floor(tile_pos.x/32), y=math.floor(tile_pos.y/32)} +end + +-- Get the left_top +function GetChunkTopLeft(pos) + return {x=pos.x-(pos.x % 32), y=pos.y-(pos.y % 32)} +end + +-- Get area given chunk +function GetAreaFromChunkPos(chunk_pos) + return {left_top={x=chunk_pos.x*32, y=chunk_pos.y*32}, + right_bottom={x=chunk_pos.x*32+31, y=chunk_pos.y*32+31}} +end + +-- Removes the entity type from the area given +function RemoveInArea(surface, area, type) + for key, entity in pairs(surface.find_entities_filtered{area=area, type= type}) do + if entity.valid and entity and entity.position then + entity.destroy() + end + end +end + +-- Removes the entity type from the area given +-- Only if it is within given distance from given position. +function RemoveInCircle(surface, area, type, pos, dist) + for key, entity in pairs(surface.find_entities_filtered{area=area, type= type}) do + if entity.valid and entity and entity.position then + if ((pos.x - entity.position.x)^2 + (pos.y - entity.position.y)^2 < dist^2) then + entity.destroy() + end + end + end +end + +-- Create another surface so that we can modify map settings and not have a screwy nauvis map. +function CreateGameSurface() + + -- Get starting surface settings. + local nauvis_settings = game.surfaces["nauvis"].map_gen_settings + + if global.ocfg.enable_vanilla_spawns then + nauvis_settings.starting_points = CreateVanillaSpawns(global.ocfg.vanilla_spawn_count, global.ocfg.vanilla_spawn_spacing) + + -- ENFORCE ISLAND MAP GEN + if (global.ocfg.silo_islands) then + nauvis_settings.property_expression_names.elevation = "0_17-island" + end + end + + -- Create new game surface + local s = game.create_surface(GAME_SURFACE_NAME, nauvis_settings) + + -- Add surface and safe areas + if global.ocfg.enable_regrowth then + remote.call("oarc_regrowth", "add_surface", s.index) + remote.call("oarc_regrowth", "area_offlimits_chunkpos", s.index, {x=0,y=0}, 10) + end +end + +-------------------------------------------------------------------------------- +-- Functions for removing/modifying enemies +-------------------------------------------------------------------------------- + +-- 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 +end + +-- Downgrades worms in an area based on chance. +-- 100% small would mean all worms are changed to small. +function DowngradeWormsInArea(surface, area, small_percent, medium_percent, big_percent) + + local worm_types = {"small-worm-turret", "medium-worm-turret", "big-worm-turret", "behemoth-worm-turret"} + + for _, entity in pairs(surface.find_entities_filtered{area = area, name = worm_types}) do + + -- Roll a number between 0-100 + local rand_percent = math.random(0,100) + local worm_pos = entity.position + local worm_name = entity.name + + -- If number is less than small percent, change to small + if (rand_percent <= small_percent) then + if (not (worm_name == "small-worm-turret")) then + entity.destroy() + surface.create_entity{name = "small-worm-turret", position = worm_pos, force = game.forces.enemy} + end + + -- ELSE If number is less than medium percent, change to small + elseif (rand_percent <= medium_percent) then + if (not (worm_name == "medium-worm-turret")) then + entity.destroy() + surface.create_entity{name = "medium-worm-turret", position = worm_pos, force = game.forces.enemy} + end + + -- ELSE If number is less than big percent, change to small + elseif (rand_percent <= big_percent) then + if (not (worm_name == "big-worm-turret")) then + entity.destroy() + surface.create_entity{name = "big-worm-turret", position = worm_pos, force = game.forces.enemy} + end + + -- ELSE ignore it. + end + end +end + +function DowngradeWormsDistanceBasedOnChunkGenerate(event) + if (getDistance({x=0,y=0}, event.area.left_top) < (global.ocfg.near_dist_end*CHUNK_SIZE)) then + DowngradeWormsInArea(event.surface, event.area, 100, 100, 100) + elseif (getDistance({x=0,y=0}, event.area.left_top) < (global.ocfg.far_dist_start*CHUNK_SIZE)) then + DowngradeWormsInArea(event.surface, event.area, 50, 90, 100) + elseif (getDistance({x=0,y=0}, event.area.left_top) < (global.ocfg.far_dist_end*CHUNK_SIZE)) then + DowngradeWormsInArea(event.surface, event.area, 20, 80, 97) + else + DowngradeWormsInArea(event.surface, event.area, 0, 20, 90) + end +end + +-- A function to help me remove worms in an area. +-- Yeah kind of an unecessary wrapper, but makes my life easier to remember the worm types. +function RemoveWormsInArea(surface, area, small, medium, big, behemoth) + local worm_types = {} + + if (small) then + table.insert(worm_types, "small-worm-turret") + end + if (medium) then + table.insert(worm_types, "medium-worm-turret") + end + if (big) then + table.insert(worm_types, "big-worm-turret") + end + if (behemoth) then + table.insert(worm_types, "behemoth-worm-turret") + end + + -- Destroy + if (TableLength(worm_types) > 0) then + for _, entity in pairs(surface.find_entities_filtered{area = area, name = worm_types}) do + entity.destroy() + end + else + log("RemoveWormsInArea had empty worm_types list!") + end +end + +-- Add Long Reach to Character +function GivePlayerLongReach(player) + player.character.character_build_distance_bonus = BUILD_DIST_BONUS + player.character.character_reach_distance_bonus = REACH_DIST_BONUS + -- player.character.character_resource_reach_distance_bonus = RESOURCE_DIST_BONUS +end + +-- General purpose cover an area in tiles. +function CoverAreaInTiles(surface, area, tile_name) + tiles = {} + for x = area.left_top.x,area.left_top.x+31 do + for y = area.left_top.y,area.left_top.y+31 do + table.insert(tiles, {name = tile_name, position = {x=x, y=y}}) + end + end + surface.set_tiles(tiles, true) +end + +-------------------------------------------------------------------------------- +-- Anti-griefing Stuff & Gravestone (My own version) +-------------------------------------------------------------------------------- +function AntiGriefing(force) + force.zoom_to_world_deconstruction_planner_enabled=false + SetForceGhostTimeToLive(force) +end + +function SetForceGhostTimeToLive(force) + if GHOST_TIME_TO_LIVE ~= 0 then + force.ghost_time_to_live = GHOST_TIME_TO_LIVE+1 + end +end + +function SetItemBlueprintTimeToLive(event) + local type = event.created_entity.type + if type == "entity-ghost" or type == "tile-ghost" then + if GHOST_TIME_TO_LIVE ~= 0 then + event.created_entity.time_to_live = GHOST_TIME_TO_LIVE + end + end +end + +-------------------------------------------------------------------------------- +-- Gravestone soft mod. With my own modifications/improvements. +-------------------------------------------------------------------------------- +-- Return steel chest entity (or nil) +function DropEmptySteelChest(player) + local pos = player.surface.find_non_colliding_position("steel-chest", player.position, 15, 1) + if not pos then + return nil + end + local grave = player.surface.create_entity{name="steel-chest", position=pos, force="neutral"} + return grave +end + +function DropGravestoneChests(player) + + local grave + local count = 0 + + -- Make sure we save stuff we're holding in our hands. + player.clean_cursor() + + -- Loop through a players different inventories + -- Put it all into a chest. + -- If the chest is full, create a new chest. + for i, id in ipairs{ + defines.inventory.character_armor, + defines.inventory.character_main, + defines.inventory.character_guns, + defines.inventory.character_ammo, + defines.inventory.character_vehicle, + defines.inventory.character_trash} do + + local inv = player.get_inventory(id) + + -- No idea how inv can be nil sometimes...? + if (inv ~= nil) then + if ((#inv > 0) and not inv.is_empty()) then + for j = 1, #inv do + if inv[j].valid_for_read then + + -- Create a chest when counter is reset + if (count == 0) then + grave = DropEmptySteelChest(player) + if (grave == nil) then + -- player.print("Not able to place a chest nearby! Some items lost!") + return + end + grave_inv = grave.get_inventory(defines.inventory.chest) + end + count = count + 1 + + -- Copy the item stack into a chest slot. + grave_inv[count].set_stack(inv[j]) + + -- Reset counter when chest is full + if (count == #grave_inv) then + count = 0 + end + end + end + end + + -- Clear the player inventory so we don't have duplicate items lying around. + inv.clear() + end + end + + if (grave ~= nil) then + player.print("Successfully dropped your items into a chest! Go get them quick!") + end +end + +-- Dump player items into a chest after the body expires. +function DropGravestoneChestFromCorpse(corpse) + if ((corpse == nil) or (corpse.character_corpse_player_index == nil)) then return end + + local grave, grave_inv + local count = 0 + + local inv = corpse.get_inventory(defines.inventory.character_corpse) + + -- No idea how inv can be nil sometimes...? + if (inv ~= nil) then + if ((#inv > 0) and not inv.is_empty()) then + for j = 1, #inv do + if inv[j].valid_for_read then + + -- Create a chest when counter is reset + if (count == 0) then + grave = DropEmptySteelChest(corpse) + 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) and (game.players[corpse.character_corpse_player_index] ~= nil)then + game.players[corpse.character_corpse_player_index].print("Your corpse got eaten by biters! They kindly dropped your items into a chest! Go get them quick!") + end + +end + +-------------------------------------------------------------------------------- +-- Item/Inventory stuff (used in autofill) +-------------------------------------------------------------------------------- + +-- 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_main_inventory() + if (mainInv == nil) then return end + + -- 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_main_inventory() + if (mainInv == nil) then return end + + -- Attempt to transfer some fuel + if ((vehicle.name == "car") or (vehicle.name == "tank") or (vehicle.name == "locomotive")) then + TransferItemMultipleTypes(mainInv, vehicle, {"nuclear-fuel", "rocket-fuel", "solid-fuel", "coal", "wood"}, 50) + end + + -- Attempt to transfer some ammo + if ((vehicle.name == "car") or (vehicle.name == "tank")) then + TransferItemMultipleTypes(mainInv, vehicle, {"uranium-rounds-magazine", "piercing-rounds-magazine", "firearm-magazine"}, 100) + end + + -- Attempt to transfer some tank shells + if (vehicle.name == "tank") then + TransferItemMultipleTypes(mainInv, vehicle, {"explosive-uranium-cannon-shell", "uranium-cannon-shell", "explosive-cannon-shell", "cannon-shell"}, 100) + end +end + +-------------------------------------------------------------------------------- +-- Resource patch and starting area generation +-------------------------------------------------------------------------------- + +-- Enforce a circle of land, also adds trees in a ring around the area. +function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile) + + 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 + global.ocfg.spawn_config.gen_settings.force_grass or + (game.active_mods["oarc-restricted-build"])) then + table.insert(dirtTiles, {name = fillTile, position ={i,j}}) + end + end + + -- Create a circle of trees around the spawn point. + if ((distVar < tileRadSqr-200) and + (distVar > tileRadSqr-400)) then + surface.create_entity({name="tree-02", amount=1, position={i, j}}) + end + end + end + + surface.set_tiles(dirtTiles) +end + +-- COPIED FROM jvmguy! +-- Enforce a square of land, with a tree border +-- this is equivalent to the CreateCropCircle code +function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile) + + local dirtTiles = {} + for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do + for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do + + local distVar1 = math.floor(math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j))) + local distVar2 = math.floor(math.abs(centerPos.x - i) + math.abs(centerPos.y - j)) + local distVar = math.max(distVar1*1.1, distVar2 * 0.707*1.1); + + -- Fill in all unexpected water in a circle + if (distVar < tileRadius+2) then + if (surface.get_tile(i,j).collides_with("water-tile") or + global.ocfg.spawn_config.gen_settings.force_grass or + (game.active_mods["oarc-restricted-build"])) then + table.insert(dirtTiles, {name = fillTile, 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, fillTile) + + local tileRadSqr = tileRadius^2 + + local waterTiles = {} + for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do + for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do + + -- This ( X^2 + Y^2 ) is used to calculate if something + -- is inside a circle area. + local distVar = math.floor((centerPos.x - i)^2 + (centerPos.y - j)^2) + + -- Create a circle of water + if ((distVar < tileRadSqr+(1500*global.ocfg.spawn_config.gen_settings.moat_size_modifier)) and + (distVar > tileRadSqr)) then + table.insert(waterTiles, {name = "water", position ={i,j}}) + end + + -- Enforce land inside the edges of the circle to make sure it's + -- a clean transition + -- if ((distVar <= tileRadSqr) and + -- (distVar > tileRadSqr-10000)) then + -- table.insert(waterTiles, {name = fillTile, position ={i,j}}) + -- end + end + end + + surface.set_tiles(waterTiles) +end + +-- Create a horizontal line of water +function CreateWaterStrip(surface, leftPos, length) + local waterTiles = {} + for i=0,length,1 do + table.insert(waterTiles, {name = "water", position={leftPos.x+i,leftPos.y}}) + end + surface.set_tiles(waterTiles) +end + +-- Function to generate a resource patch, of a certain size/amount at a pos. +function GenerateResourcePatch(surface, resourceName, diameter, pos, amount) + local midPoint = math.floor(diameter/2) + if (diameter == 0) then + return + end + for y=-midPoint, midPoint do + for x=-midPoint, midPoint do + if (not global.ocfg.spawn_config.gen_settings.resources_circle_shape or ((x)^2 + (y)^2 < midPoint^2)) then + surface.create_entity({name=resourceName, amount=amount, + position={pos.x+x, pos.y+y}}) + end + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Holding pen for new players joining the map +-------------------------------------------------------------------------------- +function CreateWall(surface, pos) + local wall = surface.create_entity({name="stone-wall", position=pos, force=MAIN_TEAM}) + if wall then + wall.destructible = false + wall.minable = false + end +end + +function CreateHoldingPen(surface, chunkArea, sizeTiles, sizeMoat) + if (((chunkArea.left_top.x >= -(sizeTiles+sizeMoat+CHUNK_SIZE)) and (chunkArea.left_top.x <= (sizeTiles+sizeMoat+CHUNK_SIZE))) and + ((chunkArea.left_top.y >= -(sizeTiles+sizeMoat+CHUNK_SIZE)) and (chunkArea.left_top.y <= (sizeTiles+sizeMoat+CHUNK_SIZE)))) then + + -- Remove stuff + RemoveAliensInArea(surface, chunkArea) + RemoveInArea(surface, chunkArea, "tree") + RemoveInArea(surface, chunkArea, "resource") + RemoveInArea(surface, chunkArea, "cliff") + + -- This loop runs through each tile + local grassTiles = {} + local waterTiles = {} + for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do + for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do + + -- Are we within the moat area? + if ((i>-(sizeTiles+sizeMoat)) and (i<((sizeTiles+sizeMoat)-1)) and + (j>-(sizeTiles+sizeMoat)) and (j<((sizeTiles+sizeMoat)-1))) then + + -- Are we within the land area? Place land. + if ((i>-(sizeTiles)) and (i<((sizeTiles)-1)) and + (j>-(sizeTiles)) and (j<((sizeTiles)-1))) then + table.insert(grassTiles, {name = "grass-1", position ={i,j}}) + + -- Else, surround with water. + else + table.insert(waterTiles, {name = "water", position ={i,j}}) + end + end + end + end + surface.set_tiles(waterTiles) + surface.set_tiles(grassTiles) + end +end + +-------------------------------------------------------------------------------- +-- EVENT SPECIFIC FUNCTIONS +-------------------------------------------------------------------------------- + +-- Display messages to a user everytime they join +function PlayerJoinedMessages(event) + local player = game.players[event.player_index] + player.print(global.ocfg.welcome_msg) +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 + + -- Make sure player isn't dead? + if (player.character == nil) then return end + + 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 + +-- Map loaders to logistics tech for unlocks. +local loaders_technology_map = { + ['logistics'] = 'loader', + ['logistics-2'] = 'fast-loader', + ['logistics-3'] = 'express-loader' +} + +function EnableLoaders(event) + local research = event.research + local recipe = loaders_technology_map[research.name] + if recipe then + research.force.recipes[recipe].enabled = true + end +end diff --git a/lib/player_list.lua b/lib/player_list.lua new file mode 100644 index 0000000..504e5b3 --- /dev/null +++ b/lib/player_list.lua @@ -0,0 +1,46 @@ +-- oarc_player_list.lua +-- Mar 2019 + +-------------------------------------------------------------------------------- +-- Player List GUI - My own version +-------------------------------------------------------------------------------- +function CreatePlayerListGuiTab(tab_container, player) + local scrollFrame = tab_container.add{type="scroll-pane", + name="playerList-panel", + direction = "vertical"} + ApplyStyle(scrollFrame, my_player_list_fixed_width_style) + scrollFrame.horizontal_scroll_policy = "never" + + AddLabel(scrollFrame, "online_title_msg", "Online Players:", my_label_header_style) + for _,player in pairs(game.connected_players) do + local caption_str = player.name.." ["..player.force.name.."]".." ("..formattime_hours_mins(player.online_time)..")" + if (player.admin) then + AddLabel(scrollFrame, player.name.."_plist", caption_str, my_player_list_admin_style) + else + AddLabel(scrollFrame, player.name.."_plist", caption_str, my_player_list_style) + end + end + + -- List offline players + if (global.ocfg.list_offline_players) then + AddSpacerLine(scrollFrame) + AddLabel(scrollFrame, "offline_title_msg", "Offline Players:", my_label_header_grey_style) + for _,player in pairs(game.players) do + if (not player.connected) then + local caption_str = player.name.." ["..player.force.name.."]".." ("..formattime_hours_mins(player.online_time)..")" + local text = scrollFrame.add{type="label", caption=caption_str, name=player.name.."_plist"} + ApplyStyle(text, my_player_list_offline_style) + end + end + end +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 diff --git a/lib/regrowth_map.lua b/lib/regrowth_map.lua new file mode 100644 index 0000000..2c80f7d --- /dev/null +++ b/lib/regrowth_map.lua @@ -0,0 +1,382 @@ +-- regrowth_map.lua +-- Sep 2019 +-- REVERTED BACK TO SOFT MOD + +-- Code tracks all chunks generated and allows for deleting of inactive chunks. +-- +-- Basic rules of regrowth: +-- 1. Area around player is safe for quite a large distance. +-- 2. Chunks with pollution won't be deleted. +-- 3. Chunks with any player buildings won't be deleted. +-- 4. Anything within radar range won't be deleted, but radar MUST be active. +-- -- This works by refreshing all chunk timers within radar range using +-- the on_sector_scanned event. +-- 5. Chunks timeout after 1 hour-ish, configurable + +require("lib/oarc_utils") +require("config") + +REGROWTH_TIMEOUT_TICKS = TICKS_PER_HOUR + +-- Init globals and set player join area to be off limits. +function RegrowthInit() + if (global.rg == nil) then + global.rg = {} + global.rg.surfaces_index = 1 + global.rg.player_refresh_index = 1 + global.rg.force_removal_flag = -1000 + global.rg.active_surfaces = {} + end +end + +function TriggerCleanup() + global.rg.force_removal_flag = game.tick +end + +function ForceRemoveChunksCmd(cmd_table) + if (game.players[cmd_table.player_index].admin) then + TriggerCleanup() + end +end + +function RegrowthAddSurface(s_index) + RegrowthInit() + + if (global.rg[s_index] ~= nil) then + log("ERROR - Tried to add surface that was already added?") + return + end + + log("Oarc Regrowth - ADD SURFACE " .. game.surfaces[s_index].name) + + global.rg[s_index] = {} + table.insert(global.rg.active_surfaces, s_index) + + global.rg[s_index].map = {} + global.rg[s_index].removal_list = {} + global.rg[s_index].min_x = 0 + global.rg[s_index].max_x = 0 + global.rg[s_index].x_index = 0 + global.rg[s_index].min_y = 0 + global.rg[s_index].max_y = 0 + global.rg[s_index].y_index = 0 + + -- MarkAreaSafeGivenTilePos({x=0,y=0}, 10) +end + +-- Adds new chunks to the global table to track them. +-- This should always be called first in the chunk generate sequence +-- (Compared to other RSO & Oarc related functions...) +function RegrowthChunkGenerate(event) + + local s_index = event.surface.index + local c_pos = GetChunkPosFromTilePos(event.area.left_top) + + -- Surface must be "added" first. + if (global.rg[s_index] == nil) then return end + + -- If this is the first chunk in that row: + if (global.rg[s_index].map[c_pos.x] == nil) then + global.rg[s_index].map[c_pos.x] = {} + end + + -- Confirm the chunk doesn't already have a value set: + if (global.rg[s_index].map[c_pos.x][c_pos.y] == nil) then + global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick + end + + -- Store min/max values for x/y dimensions: + if (c_pos.x < global.rg[s_index].min_x) then + global.rg[s_index].min_x = c_pos.x + end + if (c_pos.x > global.rg[s_index].max_x) then + global.rg[s_index].max_x = c_pos.x + end + if (c_pos.y < global.rg[s_index].min_y) then + global.rg[s_index].min_y = c_pos.y + end + if (c_pos.y > global.rg[s_index].max_y) then + global.rg[s_index].max_y = c_pos.y + end +end + +-- Mark an area for "immediate" forced removal +function MarkAreaForRemoval(s_index, pos, chunk_radius) + local c_pos = GetChunkPosFromTilePos(pos) + for i=-chunk_radius,chunk_radius do + for k=-chunk_radius,chunk_radius do + local x = c_pos.x+i + local y = c_pos.y+k + + if (global.rg[s_index].map[x] == nil) then + global.rg[s_index].map[x] = {} + end + global.rg[s_index].map[x][y] = nil + table.insert(global.rg[s_index].removal_list, + {pos={x=x,y=y},force=true}) + end + end +end + +-- Marks a chunk containing a position that won't ever be deleted. +function MarkChunkSafe(s_index, c_pos) + if (global.rg[s_index].map[c_pos.x] == nil) then + global.rg[s_index].map[c_pos.x] = {} + end + global.rg[s_index].map[c_pos.x][c_pos.y] = -1 +end + +-- Marks a safe area around a TILE position that won't ever be deleted. +function MarkAreaSafeGivenTilePos(s_index, pos, chunk_radius) + if (global.rg[s_index] == nil) then return end + + local c_pos = GetChunkPosFromTilePos(pos) + MarkAreaSafeGivenChunkPos(s_index, c_pos, chunk_radius) +end + +-- Marks a safe area around a CHUNK position that won't ever be deleted. +function MarkAreaSafeGivenChunkPos(s_index, c_pos, chunk_radius) + if (global.rg[s_index] == nil) then return end + + for i=-chunk_radius,chunk_radius do + for j=-chunk_radius,chunk_radius do + MarkChunkSafe(s_index, {x=c_pos.x+i,y=c_pos.y+j}) + end + end +end + +-- Refreshes timers on a chunk containing position +function RefreshChunkTimer(s_index, pos, bonus_time) + local c_pos = GetChunkPosFromTilePos(pos) + + if (global.rg[s_index].map[c_pos.x] == nil) then + global.rg[s_index].map[c_pos.x] = {} + end + if (global.rg[s_index].map[c_pos.x][c_pos.y] ~= -1) then + global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick + bonus_time + end +end + +-- Forcefully refreshes timers on a chunk containing position +-- Will overwrite -1 flag. +-- function OarcRegrowthForceRefreshChunk(s_index, pos, bonus_time) +-- local c_pos = GetChunkPosFromTilePos(pos) + +-- if (global.rg[s_index].map[c_pos.x] == nil) then +-- global.rg[s_index].map[c_pos.x] = {} +-- end +-- global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick + bonus_time +-- end + + -- Refreshes timers on all chunks around a certain area +function RefreshArea(s_index, pos, chunk_radius, bonus_time) + local c_pos = GetChunkPosFromTilePos(pos) + + for i=-chunk_radius,chunk_radius do + for k=-chunk_radius,chunk_radius do + local x = c_pos.x+i + local y = c_pos.y+k + + if (global.rg[s_index].map[x] == nil) then + global.rg[s_index].map[x] = {} + end + if (global.rg[s_index].map[x][y] ~= -1) then + global.rg[s_index].map[x][y] = game.tick + bonus_time + end + end + end +end + +-- Refreshes timers on all chunks near an ACTIVE radar +function RegrowthSectorScan(event) + local s_index = event.radar.surface.index + if (global.rg[s_index] == nil) then return end + + RefreshArea(s_index, event.radar.position, 14, 0) + RefreshChunkTimer(s_index, event.chunk_position, 0) +end + +-- Refresh all chunks near a single player. Cyles through all connected players. +function RefreshPlayerArea() + global.rg.player_refresh_index = global.rg.player_refresh_index + 1 + if (global.rg.player_refresh_index > #game.connected_players) then + global.rg.player_refresh_index = 1 + end + if (game.connected_players[global.rg.player_refresh_index]) then + local player = game.connected_players[global.rg.player_refresh_index] + if (not player.character) then return end + + local s_index = player.character.surface.index + if (global.rg[s_index] == nil) then return end + + RefreshArea(s_index, player.position, 4, 0) + end +end + +-- Gets the next chunk the array map and checks to see if it has timed out. +-- Adds it to the removal list if it has. +function RegrowthSingleStepArray(s_index) + + -- Increment X and reset when we hit the end. + if (global.rg[s_index].x_index > global.rg[s_index].max_x) then + global.rg[s_index].x_index = global.rg[s_index].min_x + + -- Increment Y and reset when we hit the end. + if (global.rg[s_index].y_index > global.rg[s_index].max_y) then + global.rg[s_index].y_index = global.rg[s_index].min_y + -- log("Finished checking regrowth array. ".. + -- game.surfaces[s_index].name.." ".. + -- global.rg[s_index].min_x.." ".. + -- global.rg[s_index].max_x.." ".. + -- global.rg[s_index].min_y.." ".. + -- global.rg[s_index].max_y) + else + global.rg[s_index].y_index = global.rg[s_index].y_index + 1 + end + else + global.rg[s_index].x_index = global.rg[s_index].x_index + 1 + end + + local xidx = global.rg[s_index].x_index + local yidx = global.rg[s_index].y_index + + if (not xidx or not yidx) then + log("ERROR - xidx or yidx is nil?") + end + + -- Check row exists, otherwise make one. + if (global.rg[s_index].map[xidx] == nil) then + global.rg[s_index].map[xidx] = {} + end + + -- If the chunk has timed out, add it to the removal list + local c_timer = global.rg[s_index].map[xidx][yidx] + if ((c_timer ~= nil) and (c_timer ~= -1) and + ((c_timer + REGROWTH_TIMEOUT_TICKS) < game.tick)) then + + -- Check chunk actually exists + if (game.surfaces[s_index].is_chunk_generated({x=xidx, y=yidx})) then + table.insert(global.rg[s_index].removal_list, {pos={x=xidx, + y=yidx}, + force=false}) + global.rg[s_index].map[xidx][yidx] = nil + end + end +end + +-- Remove all chunks at same time to reduce impact to FPS/UPS +function OarcRegrowthRemoveAllChunks() + for _,s_index in pairs(global.rg.active_surfaces) do + print(k,v) + + while (#global.rg[s_index].removal_list > 0) do + local c_remove = table.remove(global.rg[s_index].removal_list) + local c_pos = c_remove.pos + local c_timer = global.rg[s_index].map[c_pos.x][c_pos.y] + + if (game.surfaces[s_index] == nil) then + log("Error! game.surfaces[name] is nil?? WTF?") + return + end + + -- Confirm chunk is still expired + if (c_timer == nil) then + + -- If it is FORCE removal, then remove it regardless of pollution. + if (c_remove.force) then + game.surfaces[s_index].delete_chunk(c_pos) + global.rg[s_index].map[c_pos.x][c_pos.y] = nil + + -- If it is a normal timeout removal, don't do it if there is pollution in the chunk. + elseif (game.surfaces[s_index].get_pollution({c_pos.x*32,c_pos.y*32}) > 0) then + global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick + + -- Else delete the chunk + else + game.surfaces[s_index].delete_chunk(c_pos) + global.rg[s_index].map[c_pos.x][c_pos.y] = nil + end + end + end + end +end + +-- This is the main work function, it checks a single chunk in the list +-- per tick. It works according to the rules listed in the header of this +-- file. +function RegrowthOnTick() + + if (#global.rg.active_surfaces == 0) then return end + + -- Every half a second, refresh all chunks near a single player + -- Cyles through all players. Tick is offset by 2 + if ((game.tick % (30)) == 2) then + RefreshPlayerArea() + end + + -- Iterate through the active surfaces. + if (global.rg.surfaces_index > #global.rg.active_surfaces) then + global.rg.surfaces_index = 1 + end + local s_index = global.rg.active_surfaces[global.rg.surfaces_index] + global.rg.surfaces_index = global.rg.surfaces_index+1 + + if (s_index == nil) then + log("ERROR - s_index = nil in OarcRegrowthOnTick?") + return + end + + -- Every tick, check a few points in the 2d array of one of the active surfaces + -- According to /measured-command this shouldn't take more + -- than 0.1ms on average + for i=1,20 do + RegrowthSingleStepArray(s_index) + end + + -- Allow enable/disable of auto cleanup, can change during runtime. + if (global.ocfg.enable_regrowth) then + + local interval_ticks = REGROWTH_TIMEOUT_TICKS + -- Send a broadcast warning before it happens. + if ((game.tick % interval_ticks) == interval_ticks-601) then + if (#global.rg[s_index].removal_list > 100) then + SendBroadcastMsg("Map cleanup in 10 seconds... Unused and old map chunks will be deleted!") + end + end + + -- Delete all listed chunks across all active surfaces + if ((game.tick % interval_ticks) == interval_ticks-1) then + if (#global.rg[s_index].removal_list > 100) then + OarcRegrowthRemoveAllChunks() + SendBroadcastMsg("Map cleanup done, sorry for your loss.") + end + end + end +end + +-- This function removes any chunks flagged but on demand. +-- Controlled by the global.rg.force_removal_flag +-- This function may be used outside of the normal regrowth modse. +function RegrowthForceRemovalOnTick() + -- Catch force remove flag + if (game.tick == global.rg.force_removal_flag+60) then + SendBroadcastMsg("Map cleanup (forced) in 10 seconds... Unused and old map chunks will be deleted!") + end + + if (game.tick == global.rg.force_removal_flag+660) then + OarcRegrowthRemoveAllChunks() + SendBroadcastMsg("Map cleanup done, sorry for your loss.") + 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 + +-- Gets chunk position of a tile. +function GetChunkPosFromTilePos(tile_pos) + return {x=math.floor(tile_pos.x/32), y=math.floor(tile_pos.y/32)} +end \ No newline at end of file diff --git a/locale/temp/rgcommand.lua b/lib/rgcommand.lua similarity index 100% rename from locale/temp/rgcommand.lua rename to lib/rgcommand.lua diff --git a/lib/rocket_launch.lua b/lib/rocket_launch.lua new file mode 100644 index 0000000..029559e --- /dev/null +++ b/lib/rocket_launch.lua @@ -0,0 +1,80 @@ +-- rocket_launch.lua +-- May 2019 + +-- This is meant to extract out any rocket launch related logic to support my oarc scenario designs. + +require("lib/oarc_utils") +require("config") + +-------------------------------------------------------------------------------- +-- Rocket Launch Event Code +-- Controls the "win condition" +-------------------------------------------------------------------------------- +function RocketLaunchEvent(event) + local force = event.rocket.force + + -- Notify players on force if rocket was launched without sat. + if event.rocket.get_item_count("satellite") == 0 then + for index, player in pairs(force.players) do + player.print("You launched the rocket, but you didn't put a satellite inside.") + end + return + end + + -- First ever sat launch + if not global.satellite_sent then + global.satellite_sent = {} + SendBroadcastMsg("Team " .. event.rocket.force.name .. " was the first to launch a rocket!") + ServerWriteFile("rocket_events", "Team " .. event.rocket.force.name .. " was the first to launch a rocket!" .. "\n") + + for name,player in pairs(game.players) do + SetOarcGuiTabEnabled(player, OARC_ROCKETS_GUI_TAB_NAME, true) + end + end + + -- Track additional satellites launched by this force + if global.satellite_sent[force.name] then + global.satellite_sent[force.name] = global.satellite_sent[force.name] + 1 + SendBroadcastMsg("Team " .. event.rocket.force.name .. " launched another rocket. Total " .. global.satellite_sent[force.name]) + ServerWriteFile("rocket_events", "Team " .. event.rocket.force.name .. " launched another rocket. Total " .. global.satellite_sent[force.name] .. "\n") + + -- First sat launch for this force. + else + -- game.set_game_state{game_finished=true, player_won=true, can_continue=true} + global.satellite_sent[force.name] = 1 + SendBroadcastMsg("Team " .. event.rocket.force.name .. " launched their first rocket!") + ServerWriteFile("rocket_events", "Team " .. event.rocket.force.name .. " launched their first rocket!" .. "\n") + + -- Unlock research + if global.ocfg.lock_goodies_rocket_launch then + EnableTech(force, "atomic-bomb") + EnableTech(force, "power-armor-mk2") + EnableTech(force, "artillery") + + if (force.technologies["speed-module-3"].researched) then + AddRecipe(force, "speed-module-3") + end + if (force.technologies["productivity-module-3"].researched) then + AddRecipe(force, "productivity-module-3") + end + end + end +end + +function CreateRocketGuiTab(tab_container, player) + -- local frame = tab_container.add{type="frame", name="rocket-panel", caption="Satellites Launched:", direction = "vertical"} + + AddLabel(tab_container, nil, "Satellites Launched:", my_label_header_style) + + if (global.satellite_sent == nil) then + AddLabel(tab_container, nil, "No launches yet.", my_label_style) + else + for force_name,sat_count in pairs(global.satellite_sent) do + AddLabel(tab_container, + "rc_"..force_name, + "Team " .. force_name .. ": " .. tostring(sat_count), + my_label_style) + end + end +end + diff --git a/lib/separate_spawns.lua b/lib/separate_spawns.lua new file mode 100644 index 0000000..2f71c51 --- /dev/null +++ b/lib/separate_spawns.lua @@ -0,0 +1,800 @@ +-- separate_spawns.lua +-- Nov 2016 +-- +-- Code that handles everything regarding giving each player a separate spawn +-- Includes the GUI stuff + +require("lib/oarc_utils") +require("config") + +-------------------------------------------------------------------------------- +-- EVENT RELATED FUNCTIONS +-------------------------------------------------------------------------------- + +-- When a new player is created, present the spawn options +-- Assign them to the main force so they can communicate with the team +-- without shouting. +function SeparateSpawnsPlayerCreated(player_index) + local player = game.players[player_index] + + -- Make sure spawn control tab is disabled + SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_GUI_NAME, false) + SwitchOarcGuiTab(player, OARC_GAME_OPTS_GUI_TAB_NAME) + + -- This checks if they have just joined the server. + -- No assigned force yet. + if (player.force.name ~= "player") then + FindUnusedSpawns(player, false) + end + + player.force = global.ocfg.main_force + DisplayWelcomeTextGui(player) +end + + +-- Check if the player has a different spawn point than the default one +-- Make sure to give the default starting items +function SeparateSpawnsPlayerRespawned(event) + local player = game.players[event.player_index] + SendPlayerToSpawn(player) +end + + +-- This is the main function that creates the spawn area +-- Provides resources, land and a safe zone +function SeparateSpawnsGenerateChunk(event) + local surface = event.surface + local chunkArea = event.area + + -- Modify enemies first. + if global.ocfg.modified_enemy_spawning then + DowngradeWormsDistanceBasedOnChunkGenerate(event) + end + + -- This handles chunk generation near player spawns + -- If it is near a player spawn, it does a few things like make the area + -- safe and provide a guaranteed area of land and water tiles. + SetupAndClearSpawnAreas(surface, chunkArea) +end + + +-- Call this if a player leaves the game or is reset +function FindUnusedSpawns(player, remove_player) + if not player then + log("ERROR - FindUnusedSpawns on NIL Player!") + return + end + + if (player.online_time < (global.ocfg.minimum_online_time * TICKS_PER_MINUTE)) then + + -- If this player is staying in the game, lets make sure we don't delete them + -- along with the map chunks being cleared. + player.teleport({x=0,y=0}, GAME_SURFACE_NAME) + + -- Clear out global variables for that player + if (global.playerSpawns[player.name] ~= nil) then + global.playerSpawns[player.name] = nil + end + + -- Remove them from the delayed spawn queue if they are in it + for i=#global.delayedSpawns,1,-1 do + delayedSpawn = global.delayedSpawns[i] + + if (player.name == delayedSpawn.playerName) then + if (delayedSpawn.vanilla) then + log("Returning a vanilla spawn back to available.") + table.insert(global.vanillaSpawns, {x=delayedSpawn.pos.x,y=delayedSpawn.pos.y}) + end + + table.remove(global.delayedSpawns, i) + log("Removing player from delayed spawn queue: " .. player.name) + end + end + + -- Transfer or remove a shared spawn if player is owner + if (global.sharedSpawns[player.name] ~= nil) then + + local teamMates = global.sharedSpawns[player.name].players + + if (#teamMates >= 1) then + local newOwnerName = table.remove(teamMates) + TransferOwnershipOfSharedSpawn(player.name, newOwnerName) + else + global.sharedSpawns[player.name] = nil + end + end + + -- If a uniqueSpawn was created for the player, mark it as unused. + if (global.uniqueSpawns[player.name] ~= nil) then + + local spawnPos = global.uniqueSpawns[player.name].pos + + -- Check if it was near someone else's base. + nearOtherSpawn = false + for spawnPlayerName,otherSpawnPos in pairs(global.uniqueSpawns) do + if ((spawnPlayerName ~= player.name) and (getDistance(spawnPos, otherSpawnPos.pos) < (global.ocfg.spawn_config.gen_settings.land_area_tiles*3))) then + log("Won't remove base as it's close to another spawn: " .. spawnPlayerName) + nearOtherSpawn = true + end + end + + -- Unused Chunk Removal mod (aka regrowth) + if (global.ocfg.enable_abandoned_base_removal and + (not nearOtherSpawn) and + global.ocfg.enable_regrowth) then + + if (global.uniqueSpawns[player.name].vanilla) then + log("Returning a vanilla spawn back to available.") + table.insert(global.vanillaSpawns, {x=spawnPos.x,y=spawnPos.y}) + end + + global.uniqueSpawns[player.name] = nil + + log("Removing base: " .. spawnPos.x .. "," .. spawnPos.y) + + remote.call("oarc_regrowth", + "area_removal_tilepos", + game.surfaces[GAME_SURFACE_NAME].index, + spawnPos, + CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS) + remote.call("oarc_regrowth", + "trigger_immediate_cleanup") + SendBroadcastMsg(player.name .. "'s base was marked for immediate clean up because they left within "..global.ocfg.minimum_online_time.." minutes of joining.") + + else + -- table.insert(global.unusedSpawns, global.uniqueSpawns[player.name]) -- Not used/implemented right now. + global.uniqueSpawns[player.name] = nil + SendBroadcastMsg(player.name .. " base was freed up because they left within "..global.ocfg.minimum_online_time.." minutes of joining.") + end + end + + -- remove that player's cooldown setting + if (global.playerCooldowns[player.name] ~= nil) then + global.playerCooldowns[player.name] = nil + end + + -- Remove from shared spawn player slots (need to search all) + for _,sharedSpawn in pairs(global.sharedSpawns) do + for key,playerName in pairs(sharedSpawn.players) do + if (player.name == playerName) then + sharedSpawn.players[key] = nil; + end + end + end + + -- Remove a force if this player created it and they are the only one on it + if ((#player.force.players <= 1) and (player.force.name ~= global.ocfg.main_force)) then + game.merge_forces(player.force, global.ocfg.main_force) + end + + -- Remove the character completely + if (remove_player) then + game.remove_offline_players({player}) + end + end +end + +-- Clear the spawn areas. +-- This should be run inside the chunk generate event and be given a list of all +-- unique spawn points. +-- This clears enemies in the immediate area, creates a slightly safe area around it, +-- It no LONGER generates the resources though as that is now handled in a delayed event! +function SetupAndClearSpawnAreas(surface, chunkArea) + for name,spawn in pairs(global.uniqueSpawns) do + + -- Create a bunch of useful area and position variables + local landArea = GetAreaAroundPos(spawn.pos, global.ocfg.spawn_config.gen_settings.land_area_tiles+CHUNK_SIZE) + local safeArea = GetAreaAroundPos(spawn.pos, global.ocfg.spawn_config.safe_area.safe_radius) + local warningArea = GetAreaAroundPos(spawn.pos, global.ocfg.spawn_config.safe_area.warn_radius) + local reducedArea = GetAreaAroundPos(spawn.pos, global.ocfg.spawn_config.safe_area.danger_radius) + local chunkAreaCenter = {x=chunkArea.left_top.x+(CHUNK_SIZE/2), + y=chunkArea.left_top.y+(CHUNK_SIZE/2)} + local spawnPosOffset = {x=spawn.pos.x+global.ocfg.spawn_config.gen_settings.land_area_tiles, + y=spawn.pos.y+global.ocfg.spawn_config.gen_settings.land_area_tiles} + + -- Make chunks near a spawn safe by removing enemies + if CheckIfInArea(chunkAreaCenter,safeArea) then + RemoveAliensInArea(surface, chunkArea) + + -- Create a warning area with heavily reduced enemies + elseif CheckIfInArea(chunkAreaCenter,warningArea) then + ReduceAliensInArea(surface, chunkArea, global.ocfg.spawn_config.safe_area.warn_reduction) + -- DowngradeWormsInArea(surface, chunkArea, 100, 100, 100) + RemoveWormsInArea(surface, chunkArea, false, true, true, true) -- remove all non-small worms. + + -- Create a third area with moderatly reduced enemies + elseif CheckIfInArea(chunkAreaCenter,reducedArea) then + ReduceAliensInArea(surface, chunkArea, global.ocfg.spawn_config.safe_area.danger_reduction) + -- DowngradeWormsInArea(surface, chunkArea, 50, 100, 100) + RemoveWormsInArea(surface, chunkArea, false, false, true, true) -- remove all huge/behemoth worms. + end + + if (not spawn.vanilla) then + -- 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, global.ocfg.spawn_config.gen_settings.land_area_tiles) + RemoveInCircle(surface, chunkArea, "resource", spawn.pos, global.ocfg.spawn_config.gen_settings.land_area_tiles+5) + RemoveInCircle(surface, chunkArea, "cliff", spawn.pos, global.ocfg.spawn_config.gen_settings.land_area_tiles+5) + RemoveDecorationsArea(surface, chunkArea) + + local fill_tile = "grass-1" + if (game.active_mods["oarc-restricted-build"]) then + fill_tile = global.ocfg.locked_build_area_tile + end + + if (global.ocfg.spawn_config.gen_settings.tree_circle) then + CreateCropCircle(surface, spawn.pos, chunkArea, global.ocfg.spawn_config.gen_settings.land_area_tiles, fill_tile) + end + if (global.ocfg.spawn_config.gen_settings.tree_octagon) then + CreateCropOctagon(surface, spawn.pos, chunkArea, global.ocfg.spawn_config.gen_settings.land_area_tiles, fill_tile) + end + if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled) then + if (spawn.moat) then + CreateMoat(surface, spawn.pos, chunkArea, global.ocfg.spawn_config.gen_settings.land_area_tiles, fill_tile) + end + end + end + end + end +end + +-- Same as GetClosestPosFromTable but specific to global.uniqueSpawns +function GetClosestUniqueSpawn(pos) + + local closest_dist = nil + local closest_key = nil + + for k,s in pairs(global.uniqueSpawns) do + local new_dist = getDistance(pos, s.pos) + if (closest_dist == nil) then + closest_dist = new_dist + closest_key = k + elseif (closest_dist > new_dist) then + closest_dist = new_dist + closest_key = k + end + end + + if (closest_key == nil) then + -- log("GetClosestUniqueSpawn ERROR - None found?") + return nil + end + + return global.uniqueSpawns[closest_key] +end + +-- I wrote this to ensure everyone gets safer spawns regardless of evolution level. +-- This is intended to downgrade any biters/spitters spawning near player bases. +-- I'm not sure the performance impact of this but I'm hoping it's not bad. +function ModifyEnemySpawnsNearPlayerStartingAreas(event) + + if (not event.entity or not (event.entity.force.name == "enemy") or not event.entity.position) then + log("ModifyBiterSpawns - Unexpected use.") + return + end + + local enemy_pos = event.entity.position + local surface = event.entity.surface + local enemy_name = event.entity.name + + local closest_spawn = GetClosestUniqueSpawn(enemy_pos) + + if (closest_spawn == nil) then + -- log("GetClosestUniqueSpawn ERROR - None found?") + return + end + + -- No enemies inside safe radius! + if (getDistance(enemy_pos, closest_spawn.pos) < global.ocfg.spawn_config.safe_area.safe_radius) then + event.entity.destroy() + + -- Warn distance is all SMALL only. + elseif (getDistance(enemy_pos, closest_spawn.pos) < global.ocfg.spawn_config.safe_area.warn_radius) then + if ((enemy_name == "big-biter") or (enemy_name == "behemoth-biter") or (enemy_name == "medium-biter")) then + event.entity.destroy() + surface.create_entity{name = "small-biter", position = enemy_pos, force = game.forces.enemy} + -- log("Downgraded biter close to spawn.") + elseif ((enemy_name == "big-spitter") or (enemy_name == "behemoth-spitter") or (enemy_name == "medium-spitter")) then + event.entity.destroy() + surface.create_entity{name = "small-spitter", position = enemy_pos, force = game.forces.enemy} + -- log("Downgraded spitter close to spawn.") + elseif ((enemy_name == "big-worm-turret") or (enemy_name == "behemoth-worm-turret") or (enemy_name == "medium-worm-turret")) then + event.entity.destroy() + surface.create_entity{name = "small-worm-turret", position = enemy_pos, force = game.forces.enemy} + -- log("Downgraded worm close to spawn.") + end + + -- Danger distance is MEDIUM max. + elseif (getDistance(enemy_pos, closest_spawn.pos) < global.ocfg.spawn_config.safe_area.danger_radius) then + if ((enemy_name == "big-biter") or (enemy_name == "behemoth-biter")) then + event.entity.destroy() + surface.create_entity{name = "medium-biter", position = enemy_pos, force = game.forces.enemy} + -- log("Downgraded biter further from spawn.") + elseif ((enemy_name == "big-spitter") or (enemy_name == "behemoth-spitter")) then + event.entity.destroy() + surface.create_entity{name = "medium-spitter", position = enemy_pos, force = game.forces.enemy} + -- log("Downgraded spitter further from spawn + elseif ((enemy_name == "big-worm-turret") or (enemy_name == "behemoth-worm-turret")) then + event.entity.destroy() + surface.create_entity{name = "medium-worm-turret", position = enemy_pos, force = game.forces.enemy} + -- log("Downgraded worm further from spawn.") + end + end +end + +-------------------------------------------------------------------------------- +-- NON-EVENT RELATED FUNCTIONS +-------------------------------------------------------------------------------- + +-- Generate the basic starter resource around a given location. +function GenerateStartingResources(surface, pos) + + local rand_settings = global.ocfg.spawn_config.resource_rand_pos_settings + + -- Generate all resource tile patches + if (not rand_settings.enabled) then + for t_name,t_data in pairs (global.ocfg.spawn_config.resource_tiles) do + local pos = {x=pos.x+t_data.x_offset, y=pos.y+t_data.y_offset} + GenerateResourcePatch(surface, t_name, t_data.size, pos, t_data.amount) + end + else + + -- Create list of resource tiles + local r_list = {} + for k,_ in pairs(global.ocfg.spawn_config.resource_tiles) do + if (k ~= "") then + table.insert(r_list, k) + end + end + local shuffled_list = FYShuffle(r_list) + + -- This places resources in a semi-circle + -- Tweak in config.lua + local angle_offset = rand_settings.angle_offset + local num_resources = TableLength(global.ocfg.spawn_config.resource_tiles) + local theta = ((rand_settings.angle_final - rand_settings.angle_offset) / num_resources); + local count = 0 + + for _,k_name in pairs (shuffled_list) do + local angle = (theta * count) + angle_offset; + + local tx = (rand_settings.radius * math.cos(angle)) + pos.x + local ty = (rand_settings.radius * math.sin(angle)) + pos.y + + local pos = {x=math.floor(tx), y=math.floor(ty)} + GenerateResourcePatch(surface, k_name, global.ocfg.spawn_config.resource_tiles[k_name].size, pos, global.ocfg.spawn_config.resource_tiles[k_name].amount) + count = count+1 + end + end + + -- Generate special resource patches (oil) + for p_name,p_data in pairs (global.ocfg.spawn_config.resource_patches) do + local oil_patch_x=pos.x+p_data.x_offset_start + local oil_patch_y=pos.y+p_data.y_offset_start + for i=1,p_data.num_patches do + surface.create_entity({name=p_name, amount=p_data.amount, + position={oil_patch_x, oil_patch_y}}) + oil_patch_x=oil_patch_x+p_data.x_offset_next + oil_patch_y=oil_patch_y+p_data.y_offset_next + end + end +end + +-- Add a spawn to the shared spawn global +-- Used for tracking which players are assigned to it, where it is and if +-- it is open for new players to join +function CreateNewSharedSpawn(player) + global.sharedSpawns[player.name] = {openAccess=true, + position=global.playerSpawns[player.name], + players={}} +end + +function TransferOwnershipOfSharedSpawn(prevOwnerName, newOwnerName) + -- Transfer the shared spawn global + global.sharedSpawns[newOwnerName] = global.sharedSpawns[prevOwnerName] + global.sharedSpawns[newOwnerName].openAccess = false + global.sharedSpawns[prevOwnerName] = nil + + -- Transfer the unique spawn global + global.uniqueSpawns[newOwnerName] = global.uniqueSpawns[prevOwnerName] + global.uniqueSpawns[prevOwnerName] = nil + + game.players[newOwnerName].print("You have been given ownership of this base!") +end + +-- Returns the number of players currently online at the shared spawn +function GetOnlinePlayersAtSharedSpawn(ownerName) + if (global.sharedSpawns[ownerName] ~= nil) then + + -- Does not count base owner + local count = 0 + + -- For each player in the shared spawn, check if online and add to count. + for _,player in pairs(game.connected_players) do + if (ownerName == player.name) then + count = count + 1 + end + + for _,playerName in pairs(global.sharedSpawns[ownerName].players) do + + if (playerName == player.name) then + count = count + 1 + end + end + end + + return count + else + return 0 + end +end + +-- Get the number of currently available shared spawns +-- This means the base owner has enabled access AND the number of online players +-- is below the threshold. +function GetNumberOfAvailableSharedSpawns() + local count = 0 + + for ownerName,sharedSpawn in pairs(global.sharedSpawns) do + if (sharedSpawn.openAccess and + (game.players[ownerName] ~= nil) and + game.players[ownerName].connected) then + if ((global.ocfg.max_players_shared_spawn == 0) or + (#global.sharedSpawns[ownerName].players < global.ocfg.max_players_shared_spawn)) then + count = count+1 + end + end + end + + return count +end + +-- Initializes the globals used to track the special spawn and player +-- status information +function InitSpawnGlobalsAndForces() + + -- This contains each player's spawn point. Literally where they will respawn. + -- There is a way in game to change this under one of the little menu features I added. + if (global.playerSpawns == nil) then + global.playerSpawns = {} + end + + -- This is the most important table. It is a list of all the unique spawn points. + -- This is what chunk generation checks against. + -- Each entry looks like this: {pos={x,y},moat=bool,vanilla=bool} + if (global.uniqueSpawns == nil) then + global.uniqueSpawns = {} + end + + -- List of available vanilla spawns + if (global.vanillaSpawns == nil) then + global.vanillaSpawns = {} + end + + -- This keeps a list of any player that has shared their base. + -- Each entry contains information about if it's open, spawn pos, and players in the group. + if (global.sharedSpawns == nil) then + global.sharedSpawns = {} + end + + -- This seems to be unused right now, but I had plans to re-use spawn points in the past. + -- if (global.unusedSpawns == nil) then + -- global.unusedSpawns = {} + -- end + + -- Each player has an option to change their respawn which has a cooldown when used. + -- Other similar abilities/functions that require cooldowns could be added here. + if (global.playerCooldowns == nil) then + global.playerCooldowns = {} + end + + -- List of players in the "waiting room" for a buddy spawn. + -- They show up in the list to select when doing a buddy spawn. + if (global.waitingBuddies == nil) then + global.waitingBuddies = {} + end + + -- Players who have made a spawn choice get put into this list while waiting. + -- An on_tick event checks when it expires and then places down the base resources, and teleports the player. + -- Go look at DelayedSpawnOnTick() for more info. + if (global.delayedSpawns == nil) then + global.delayedSpawns = {} + end + + -- This is what I use to communicate a buddy spawn request between the buddies. + -- This contains information of who is asking, and what options were selected. + if (global.buddySpawnOptions == nil) then + global.buddySpawnOptions = {} + end + + -- Silo info + if (global.siloPosition == nil) then + global.siloPosition = {} + end + + -- Name a new force to be the default force. + -- This is what any new player is assigned to when they join, even before they spawn. + local main_force = CreateForce(global.ocfg.main_force) + main_force.set_spawn_position({x=0,y=0}, GAME_SURFACE_NAME) +end + +function DoesPlayerHaveCustomSpawn(player) + for name,spawnPos in pairs(global.playerSpawns) do + if (player.name == name) then + return true + end + end + return false +end + +function ChangePlayerSpawn(player, pos) + global.playerSpawns[player.name] = pos + global.playerCooldowns[player.name] = {setRespawn=game.tick} +end + +function QueuePlayerForDelayedSpawn(playerName, spawn, moatEnabled, vanillaSpawn) + + -- If we get a valid spawn point, setup the area + if ((spawn.x ~= 0) or (spawn.y ~= 0)) then + global.uniqueSpawns[playerName] = {pos=spawn,moat=moatEnabled,vanilla=vanillaSpawn} + + local delay_spawn_seconds = 5*(math.ceil(global.ocfg.spawn_config.gen_settings.land_area_tiles/CHUNK_SIZE)) + + game.players[playerName].print("Generating your spawn now, please wait for at least " .. delay_spawn_seconds .. " seconds...") + game.players[playerName].surface.request_to_generate_chunks(spawn, 4) + delayedTick = game.tick + delay_spawn_seconds*TICKS_PER_SECOND + table.insert(global.delayedSpawns, {playerName=playerName, pos=spawn, moat=moatEnabled, vanilla=vanillaSpawn, delayedTick=delayedTick}) + + DisplayPleaseWaitForSpawnDialog(game.players[playerName], delay_spawn_seconds) + + else + log("THIS SHOULD NOT EVER HAPPEN! Spawn failed!") + SendBroadcastMsg("ERROR!! Failed to create spawn point for: " .. playerName) + end +end + + +-- Check a table to see if there are any players waiting to spawn +-- Check if we are past the delayed tick count +-- Spawn the players and remove them from the table. +function DelayedSpawnOnTick() + if ((game.tick % (30)) == 1) then + if ((global.delayedSpawns ~= nil) and (#global.delayedSpawns > 0)) then + for i=#global.delayedSpawns,1,-1 do + delayedSpawn = global.delayedSpawns[i] + + if (delayedSpawn.delayedTick < game.tick) then + -- TODO, add check here for if chunks around spawn are generated surface.is_chunk_generated(chunkPos) + if (game.players[delayedSpawn.playerName] ~= nil) then + SendPlayerToNewSpawnAndCreateIt(delayedSpawn) + end + table.remove(global.delayedSpawns, i) + end + end + end + end +end + +function SendPlayerToNewSpawnAndCreateIt(delayedSpawn) + + -- DOUBLE CHECK and make sure the area is super safe. + ClearNearbyEnemies(delayedSpawn.pos, global.ocfg.spawn_config.safe_area.safe_radius, game.surfaces[GAME_SURFACE_NAME]) + + if (not delayedSpawn.vanilla) then + + -- Create the spawn resources here + local water_data = global.ocfg.spawn_config.water + CreateWaterStrip(game.surfaces[GAME_SURFACE_NAME], + {x=delayedSpawn.pos.x+water_data.x_offset, y=delayedSpawn.pos.y+water_data.y_offset}, + water_data.length) + CreateWaterStrip(game.surfaces[GAME_SURFACE_NAME], + {x=delayedSpawn.pos.x+water_data.x_offset, y=delayedSpawn.pos.y+water_data.y_offset+1}, + water_data.length) + GenerateStartingResources(game.surfaces[GAME_SURFACE_NAME], delayedSpawn.pos) + + end + + -- Send the player to that position + local player = game.players[delayedSpawn.playerName] + player.teleport(delayedSpawn.pos, GAME_SURFACE_NAME) + GivePlayerStarterItems(game.players[delayedSpawn.playerName]) + + -- Chart the area. + ChartArea(player.force, delayedSpawn.pos, math.ceil(global.ocfg.spawn_config.gen_settings.land_area_tiles/CHUNK_SIZE), player.surface) + + if (player.gui.screen.wait_for_spawn_dialog ~= nil) then + player.gui.screen.wait_for_spawn_dialog.destroy() + end +end + +function SendPlayerToSpawn(player) + if (DoesPlayerHaveCustomSpawn(player)) then + player.teleport(global.playerSpawns[player.name], GAME_SURFACE_NAME) + else + player.teleport(game.forces[global.ocfg.main_force].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) + end +end + +function SendPlayerToRandomSpawn(player) + local numSpawns = TableLength(global.uniqueSpawns) + local rndSpawn = math.random(0,numSpawns) + local counter = 0 + + if (rndSpawn == 0) then + player.teleport(game.forces[global.ocfg.main_force].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) + else + counter = counter + 1 + for name,spawn in pairs(global.uniqueSpawns) do + if (counter == rndSpawn) then + player.teleport(spawn.pos) + break + end + counter = counter + 1 + end + end +end + +function CreateForce(force_name) + local newForce = nil + + -- Check if force already exists + if (game.forces[force_name] ~= nil) then + log("Force already exists!") + return game.forces[global.ocfg.main_force] + + -- Create a new force + elseif (TableLength(game.forces) < MAX_FORCES) then + newForce = game.create_force(force_name) + if global.ocfg.enable_shared_team_vision then + newForce.share_chart = true + end + if global.ocfg.enable_research_queue then + newForce.research_queue_enabled = true + end + -- Chart silo areas if necessary + if global.ocfg.frontier_rocket_silo and global.ocfg.frontier_silo_vision then + ChartRocketSiloAreas(game.surfaces[GAME_SURFACE_NAME], newForce) + end + SetCeaseFireBetweenAllForces() + SetFriendlyBetweenAllForces() + newForce.friendly_fire=false + if (ENABLE_ANTI_GRIEFING) then + AntiGriefing(newForce) + end + + if global.ocfg.lock_goodies_rocket_launch and not global.satellite_sent then + DisableTech(newForce, "atomic-bomb") + DisableTech(newForce, "power-armor-mk2") + DisableTech(newForce, "artillery") + end + else + log("TOO MANY FORCES!!! - CreateForce()") + end + + return newForce +end + +function CreatePlayerCustomForce(player) + + local newForce = CreateForce(player.name) + player.force = newForce + + if (newForce.name == player.name) then + SendBroadcastMsg(player.name.." has started their own team!") + else + player.print("Sorry, no new teams can be created. You were assigned to the default team instead.") + end + + return newForce +end + +-- Function to generate some map_gen_settings.starting_points +-- You should only use this at the start of the game really. +function CreateVanillaSpawns(count, spacing) + + local points = {} + + -- Get an ODD number from the square of the input count. + -- Always rounding up so we don't end up with less points that requested. + local sqrt_count = math.ceil(math.sqrt(count)) + if (sqrt_count % 2 == 0) then + sqrt_count = sqrt_count + 1 + end + + -- Need to know how much to offset the grid. + local sqrt_half = math.floor((sqrt_count-1)/2) + + if (sqrt_count < 1) then + log("CreateVanillaSpawns less than 1!!") + return + end + + if (global.vanillaSpawns == nil) then + global.vanillaSpawns = {} + end + + -- This should give me points centered around 0,0 I think. + for i=-sqrt_half,sqrt_half,1 do + for j=-sqrt_half,sqrt_half,1 do + if (i~=0 or j~=0) then -- EXCEPT don't put 0,0 + table.insert(points, {x=i*spacing,y=j*spacing}) + table.insert(global.vanillaSpawns, {x=i*spacing,y=j*spacing}) + end + end + end + + -- Do something with the return value. + return points +end + +-- Useful when combined with something like CreateVanillaSpawns +-- Where it helps ensure ALL chunks generated use new map_gen_settings. +function DeleteAllChunksExceptCenter(surface) + -- Delete the starting chunks that make it into the game before settings are changed. + for chunk in surface.get_chunks() do + -- Don't delete the chunk that might contain players lol. + -- This is really only a problem for launching AS the host. Not headless + if ((chunk.x ~= 0) and (chunk.y ~= 0)) then + surface.delete_chunk({chunk.x, chunk.y}) + end + end +end + +-- Find a vanilla spawn as close as possible to the given target_distance +function FindUnusedVanillaSpawn(surface, target_distance) + local best_key = nil + local best_distance = nil + + for k,v in pairs(global.vanillaSpawns) do + + -- Check if chunks nearby are not generated. + local chunk_pos = GetChunkPosFromTilePos(v) + if IsChunkAreaUngenerated(chunk_pos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS+15, surface) then + + -- Is this our first valid find? + if ((best_key == nil) or (best_distance == nil)) then + best_key = k + best_distance = math.abs(math.sqrt((v.x^2) + (v.y^2)) - target_distance) + + -- Check if it is closer to target_distance than previous option. + else + local new_distance = math.abs(math.sqrt((v.x^2) + (v.y^2)) - target_distance) + if (new_distance < best_distance) then + best_key = k + best_distance = new_distance + end + end + + -- If it's not a valid spawn anymore, let's remove it. + else + log("Removing vanilla spawn due to chunks generated: x=" .. v.x .. ",y=" .. v.y) + table.remove(global.vanillaSpawns, k) + end + end + + local spawn_pos = {x=0,y=0} + if ((best_key ~= nil) and (global.vanillaSpawns[best_key] ~= nil)) then + spawn_pos.x = global.vanillaSpawns[best_key].x + spawn_pos.y = global.vanillaSpawns[best_key].y + table.remove(global.vanillaSpawns, best_key) + end + log("Found unused vanilla spawn: x=" .. spawn_pos.x .. ",y=" .. spawn_pos.y) + return spawn_pos +end + + +function ValidateVanillaSpawns(surface) + for k,v in pairs(global.vanillaSpawns) do + + -- Check if chunks nearby are not generated. + local chunk_pos = GetChunkPosFromTilePos(v) + if not IsChunkAreaUngenerated(chunk_pos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS+15, surface) then + log("Removing vanilla spawn due to chunks generated: x=" .. v.x .. ",y=" .. v.y) + table.remove(global.vanillaSpawns, k) + end + end +end + diff --git a/separate_spawns_guis.lua b/lib/separate_spawns_guis.lua old mode 100755 new mode 100644 similarity index 52% rename from separate_spawns_guis.lua rename to lib/separate_spawns_guis.lua index 04b2dd6..6549cd2 --- a/separate_spawns_guis.lua +++ b/lib/separate_spawns_guis.lua @@ -1,1176 +1,1224 @@ --- separate_spawns_guis.lua --- Nov 2016 - --- I made a separate file for all the GUI related functions - -require("separate_spawns") - -local SPAWN_GUI_MAX_WIDTH = 500 -local SPAWN_GUI_MAX_HEIGHT = 1000 - --- Use this for testing shared spawns... --- local sharedSpawnExample1 = {openAccess=true, --- position={x=50,y=50}, --- players={"ABC", "DEF"}} --- local sharedSpawnExample2 = {openAccess=false, --- position={x=200,y=200}, --- players={"ABC", "DEF"}} --- local sharedSpawnExample3 = {openAccess=true, --- position={x=400,y=400}, --- players={"A", "B", "C", "D"}} --- global.sharedSpawns = {testName1=sharedSpawnExample1, --- testName2=sharedSpawnExample2, --- Oarc=sharedSpawnExample3} - - --- A display gui message --- Meant to be display the first time a player joins. -function DisplayWelcomeTextGui(player) - player.gui.center.add{name = "welcome_msg", - type = "frame", - direction = "vertical", - caption=global.welcome_msg_title} - local wGui = player.gui.center.welcome_msg - - wGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - wGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - -- Start with server message. - AddLabel(wGui, "server_msg_lbl1", SERVER_MSG, my_label_style) - AddLabel(wGui, "contact_info_msg_lbl1", CONTACT_MSG, my_label_style) - AddSpacer(wGui, "server_msg_spacer1") - - -- Informational message about the scenario - AddLabel(wGui, "scenario_info_msg_lbl1", SCENARIO_INFO_MSG, my_label_style) - AddSpacer(wGui, "scenario_info_msg_spacer1") - - -- Warning about spawn creation time - AddLabel(wGui, "spawn_time_msg_lbl1", SPAWN_WARN_MSG, my_warning_style) - wGui.add{name = "welcome_okay_btn", - type = "button", - caption="I Understand"} -end - - --- Handle the gui click of the welcome msg -function WelcomeTextGuiClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local buttonClicked = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (buttonClicked == "welcome_okay_btn") then - if (player.gui.center.welcome_msg ~= nil) then - player.gui.center.welcome_msg.destroy() - end - DisplaySpawnOptions(player) - end -end - - --- Display the spawn options and explanation -function DisplaySpawnOptions(player) - if (player == nil) then - DebugPrint("DisplaySpawnOptions with no valid player...") - return - end - - if (player.gui.center.spawn_opts ~= nil) then - DebugPrint("Tried to display spawn options when it was already displayed!") - return - end - player.gui.center.add{name = "spawn_opts", - type = "frame", - direction = "vertical", - caption="Spawn Options"} - local sGui = player.gui.center.spawn_opts - sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - - -- Warnings and explanations... - local warn_msg = "Click the INFO button in the top left to learn more about this scenario! This is your ONLY chance to choose a spawn option. Choose carefully..." - AddLabel(sGui, "warning_lbl1", warn_msg, my_warning_style) - AddLabel(sGui, "spawn_msg_lbl1", SPAWN_MSG1, my_label_style) - - -- Button and message about the regular vanilla spawn - if ENABLE_DEFAULT_SPAWN then - sGui.add{name = "default_spawn_btn", - type = "button", - caption="Vanilla Spawn"} - local normal_spawn_text = "This is the default spawn behavior of a vanilla game. You join the default team in the center of the map." - AddLabel(sGui, "normal_spawn_lbl1", normal_spawn_text, my_label_style) - AddSpacerLine(sGui, "normal_spawn_spacer") - end - - -- The main spawning options. Solo near and solo far. - -- If enable, you can also choose to be on your own team. - local soloSpawnFlow = sGui.add{name = "spawn_solo_flow", - type = "flow", - direction="vertical"} - - -- Radio buttons to pick your team. - if (ENABLE_SEPARATE_TEAMS) then - soloSpawnFlow.add{name = "isolated_spawn_main_team_radio", - type = "radiobutton", - caption="Join Main Team (shared research)", - state=false} - soloSpawnFlow.add{name = "isolated_spawn_new_team_radio", - type = "radiobutton", - caption="Create Your Own Team (own research tree)", - state=false} - end - - -- Allow players to spawn with a moat around their area. - if (SPAWN_MOAT_CHOICE_ENABLED) then - soloSpawnFlow.add{name = "isolated_spawn_moat_option_checkbox", - type = "checkbox", - caption="Surround your spawn with a moat", - state=false} - end - - -- Isolated spawn options. The core gameplay of this scenario. - local soloSpawnbuttons = soloSpawnFlow.add{name = "spawn_solo_flow", - type = "flow", - direction="horizontal"} - soloSpawnbuttons.add{name = "isolated_spawn_near", - type = "button", - caption="Solo Spawn (Near)"} - soloSpawnbuttons.add{name = "isolated_spawn_far", - type = "button", - caption="Solo Spawn (Far)"} - AddLabel(soloSpawnFlow, "isolated_spawn_lbl1", - "You are spawned in a new area, with some starting resources.", my_label_style) - AddSpacerLine(soloSpawnFlow, "isolated_spawn_spacer") - - - -- Spawn options to join another player's base. - if ENABLE_SHARED_SPAWNS then - local numAvailSpawns = GetNumberOfAvailableSharedSpawns() - if (numAvailSpawns > 0) then - sGui.add{name = "join_other_spawn", - type = "button", - caption="Join Someone (" .. numAvailSpawns .. " available)"} - local join_spawn_text = "You are spawned in someone else's base. This requires at least 1 person to have allowed access to their base. This choice is final and you will not be able to create your own spawn later." - AddLabel(sGui, "join_other_spawn_lbl1", join_spawn_text, my_label_style) - else - AddLabel(sGui, "join_other_spawn_lbl1", "There are currently no shared bases availble to spawn at.", my_label_style) - sGui.add{name = "join_other_spawn_check", - type = "button", - caption="Check Again"} - end - else - AddLabel(soloSpawnFlow, "join_other_spawn_lbl1", - "Shared spawns are disabled in this mode.", my_warning_style) - end - - -- New awesome buddy spawning system - if ENABLE_SHARED_SPAWNS and ENABLE_BUDDY_SPAWN then - AddSpacerLine(sGui, "buddy_spawn_msg_spacer") - sGui.add{name = "buddy_spawn", - type = "button", - caption="Buddy Spawn"} - AddLabel(sGui, "buddy_spawn_lbl1", - "The buddy system requires 2 players in this menu at the same time, you spawn beside each other, each with your own resources.", my_label_style) - end - - -- Some final notes - AddSpacerLine(sGui, "note_spacer1") - - if (MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN > 0) then - AddLabel(sGui, "buddy_spawn_lbl1", - "If you create your own spawn point you can allow up to " .. MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN-1 .. " other online players to join.", - my_note_style) - end - spawn_distance_notes="Near spawn is between " .. NEAR_MIN_DIST .. "-" .. NEAR_MAX_DIST .. " chunks away from the center of the map.\n".. - "Far spawn is between " .. FAR_MIN_DIST .. "-" .. FAR_MAX_DIST .. " chunks away from the center of the map.\n".. - "Solo spawns are dangerous! Expect a fight to reach other players." - AddLabel(sGui, "note_lbl1", spawn_distance_notes, my_note_style) -end - - --- This just updates the radio buttons when players click them. -function SpawnOptsRadioSelect(event) - if not (event and event.element and event.element.valid) then return end - local elemName = event.element.name - - - if (elemName == "isolated_spawn_main_team_radio") then - event.element.parent.isolated_spawn_new_team_radio.state=false - elseif (elemName == "isolated_spawn_new_team_radio") then - event.element.parent.isolated_spawn_main_team_radio.state=false - end - - if (elemName == "buddy_spawn_main_team_radio") then - event.element.parent.buddy_spawn_new_team_radio.state=false - event.element.parent.buddy_spawn_buddy_team_radio.state=false - elseif (elemName == "buddy_spawn_new_team_radio") then - event.element.parent.buddy_spawn_main_team_radio.state=false - event.element.parent.buddy_spawn_buddy_team_radio.state=false - elseif (elemName == "buddy_spawn_buddy_team_radio") then - event.element.parent.buddy_spawn_main_team_radio.state=false - event.element.parent.buddy_spawn_new_team_radio.state=false - end - -end - - --- Handle the gui click of the spawn options -function SpawnOptsGuiClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local elemName = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (player.gui.center.spawn_opts == nil) then - return -- Gui event unrelated to this gui. - end - - local joinMainTeamRadio, joinOwnTeamRadio, moatChoice = false - - -- Check if a valid button on the gui was pressed - -- and delete the GUI - if ((elemName == "default_spawn_btn") or - (elemName == "isolated_spawn_near") or - (elemName == "isolated_spawn_far") or - (elemName == "join_other_spawn") or - (elemName == "buddy_spawn") or - (elemName == "join_other_spawn_check")) then - - if (ENABLE_SEPARATE_TEAMS) then - joinMainTeamRadio = - player.gui.center.spawn_opts.spawn_solo_flow.isolated_spawn_main_team_radio.state - joinOwnTeamRadio = - player.gui.center.spawn_opts.spawn_solo_flow.isolated_spawn_new_team_radio.state - else - joinMainTeamRadio = true - joinOwnTeamRadio = false - end - if (SPAWN_MOAT_CHOICE_ENABLED) then - moatChoice = - player.gui.center.spawn_opts.spawn_solo_flow.isolated_spawn_moat_option_checkbox.state - end - player.gui.center.spawn_opts.destroy() - else - return -- Do nothing, no valid element item was clicked. - end - - if (elemName == "default_spawn_btn") then - GivePlayerStarterItems(player) - ChangePlayerSpawn(player, player.force.get_spawn_position(GAME_SURFACE_NAME)) - SendBroadcastMsg(player.name .. " is joining the main force!") - ChartArea(player.force, player.position, math.ceil(ENFORCE_LAND_AREA_TILE_DIST/CHUNK_SIZE), player.surface) - -- Create the button at the top left for setting respawn point and sharing base. - CreateSpawnCtrlGui(player) - - elseif ((elemName == "isolated_spawn_near") or (elemName == "isolated_spawn_far")) then - - -- Create a new spawn point - local newSpawn = {x=0,y=0} - - -- Create a new force for player if they choose that radio button - if ENABLE_SEPARATE_TEAMS and joinOwnTeamRadio then - local newForce = CreatePlayerCustomForce(player) - end - - -- Find coordinates of a good place to spawn - if (elemName == "isolated_spawn_far") then - newSpawn = FindUngeneratedCoordinates(FAR_MIN_DIST,FAR_MAX_DIST, player.surface) - elseif (elemName == "isolated_spawn_near") then - newSpawn = FindUngeneratedCoordinates(NEAR_MIN_DIST,NEAR_MAX_DIST, player.surface) - end - - -- If that fails, find a random map edge in a rand direction. - if ((newSpawn.x == 0) and (newSpawn.x == 0)) then - newSpawn = FindMapEdge(GetRandomVector(), player.surface) - DebugPrint("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y) - end - - -- Create that spawn in the global vars - ChangePlayerSpawn(player, newSpawn) - - -- Send the player there - QueuePlayerForDelayedSpawn(player.name, newSpawn, moatChoice) - if (elemName == "isolated_spawn_near") then - SendBroadcastMsg(player.name .. " is joining the game from a distance!") - elseif (elemName == "isolated_spawn_far") then - SendBroadcastMsg(player.name .. " is joining the game from a great distance!") - end - - -- Create the button at the top left for setting respawn point and sharing base. - CreateSpawnCtrlGui(player) - - player.print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!") - player.print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!!") - player.print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!!!") - - elseif (elemName == "join_other_spawn") then - DisplaySharedSpawnOptions(player) - - -- Provide a way to refresh the gui to check if people have shared their - -- bases. - elseif (elemName == "join_other_spawn_check") then - DisplaySpawnOptions(player) - - -- Hacky buddy spawn system - elseif (elemName == "buddy_spawn") then - table.insert(global.waitingBuddies, player.name) - SendBroadcastMsg(player.name .. " is looking for a buddy.") - - DisplayBuddySpawnOptions(player) - end -end - - --- Display the spawn options and explanation -function DisplaySharedSpawnOptions(player) - player.gui.center.add{name = "shared_spawn_opts", - type = "frame", - direction = "vertical", - caption="Available Bases to Join:"} - - local shGuiFrame = player.gui.center.shared_spawn_opts - local shGui = shGuiFrame.add{type="scroll-pane", name="spawns_scroll_pane", caption=""} - ApplyStyle(shGui, my_fixed_width_style) - shGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - shGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - shGui.horizontal_scroll_policy = "never" - - - for spawnName,sharedSpawn in pairs(global.sharedSpawns) do - if (sharedSpawn.openAccess and - (game.players[spawnName] ~= nil) and - game.players[spawnName].connected) then - local spotsRemaining = MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN - GetOnlinePlayersAtSharedSpawn(spawnName) - if (MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN == 0) then - shGui.add{type="button", caption=spawnName, name=spawnName} - elseif (spotsRemaining > 0) then - shGui.add{type="button", caption=spawnName .. " (" .. spotsRemaining .. " spots remaining)", name=spawnName} - end - if (shGui.spawnName ~= nil) then - -- AddSpacer(buddyGui, spawnName .. "spacer_lbl") - ApplyStyle(shGui[spawnName], my_small_button_style) - end - end - end - - - shGui.add{name = "shared_spawn_cancel", - type = "button", - caption="Cancel (Return to Previous Options)"} -end - --- Handle the gui click of the shared spawn options -function SharedSpwnOptsGuiClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local buttonClicked = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (event.element.parent) then - if (event.element.parent.name ~= "spawns_scroll_pane") then - return - end - end - - -- Check for cancel button, return to spawn options - if (buttonClicked == "shared_spawn_cancel") then - DisplaySpawnOptions(player) - if (player.gui.center.shared_spawn_opts ~= nil) then - player.gui.center.shared_spawn_opts.destroy() - end - - -- Else check for which spawn was selected - -- If a spawn is removed during this time, the button will not do anything - else - for spawnName,sharedSpawn in pairs(global.sharedSpawns) do - if ((buttonClicked == spawnName) and - (game.players[spawnName] ~= nil) and - (game.players[spawnName].connected)) then - - -- Add the player to that shared spawns join queue. - if (global.sharedSpawns[spawnName].joinQueue == nil) then - global.sharedSpawns[spawnName].joinQueue = {} - end - table.insert(global.sharedSpawns[spawnName].joinQueue, player.name) - - -- Clear the shared spawn options gui. - if (player.gui.center.shared_spawn_opts ~= nil) then - player.gui.center.shared_spawn_opts.destroy() - end - - -- Display wait menu with cancel button. - DisplaySharedSpawnJoinWaitMenu(player) - - -- Tell other player they are requesting a response. - game.players[spawnName].print(player.name .. " is requesting to join your base!") - break - end - end - end -end - -function DisplaySharedSpawnJoinWaitMenu(player) - - player.gui.center.add{name = "join_shared_spawn_wait_menu", - type = "frame", - direction = "vertical", - caption="Waiting for spawn owner to respond..."} - local sGui = player.gui.center.join_shared_spawn_wait_menu - sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - - -- Warnings and explanations... - AddLabel(sGui, "warning_lbl1", "You will spawn once the host selects yes...", my_warning_style) - sGui.add{name = "cancel_shared_spawn_wait_menu", - type = "button", - caption="Cancel (Return to starting spawn options)"} -end - --- Handle the gui click of the buddy wait menu -function SharedSpawnJoinWaitMenuClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local elemName = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (player.gui.center.join_shared_spawn_wait_menu == nil) then - return -- Gui event unrelated to this gui. - end - - -- Check if player is cancelling the request. - if (elemName == "cancel_shared_spawn_wait_menu") then - player.gui.center.join_shared_spawn_wait_menu.destroy() - DisplaySpawnOptions(player) - - -- Find and remove the player from the joinQueue they were in. - for spawnName,sharedSpawn in pairs(global.sharedSpawns) do - if (sharedSpawn.joinQueue ~= nil) then - for index,requestingPlayer in pairs(sharedSpawn.joinQueue) do - if (requestingPlayer == player.name) then - table.remove(global.sharedSpawns[spawnName].joinQueue, index) - game.players[spawnName].print(player.name .. " cancelled their request to join your spawn.") - return - end - end - end - end - - DebugPrint("ERROR! Failed to remove player from joinQueue!") - player.print("ERROR! Failed to remove player from joinQueue!") - end -end - - -function CreateSpawnCtrlGui(player) - if player and (player.gui.top.spwn_ctrls == nil) then - player.gui.top.add{name="spwn_ctrls", type="button", caption="Spawn Ctrl"} - end -end - - -local function IsSharedSpawnActive(player) - if ((global.sharedSpawns[player.name] == nil) or - (global.sharedSpawns[player.name].openAccess == false)) then - return false - else - return true - end -end - - --- Get a random warp point to go to -function GetRandomSpawnPoint() - local numSpawnPoints = TableLength(global.sharedSpawns) - if (numSpawnPoints > 0) then - local randSpawnNum = math.random(1,numSpawnPoints) - local counter = 1 - for _,sharedSpawn in pairs(global.sharedSpawns) do - if (randSpawnNum == counter) then - return sharedSpawn.position - end - counter = counter + 1 - end - end - - return {x=0,y=0} -end - - --- This is a toggle function, it either shows or hides the spawn controls -function ExpandSpawnCtrlGui(player, tick) - local spwnCtrlPanel = player.gui.left["spwn_ctrl_panel"] - if (spwnCtrlPanel) then - spwnCtrlPanel.destroy() - else - local spwnCtrlPanel = player.gui.left.add{type="frame", - name="spwn_ctrl_panel", caption="Spawn Controls:"} - local spwnCtrls = spwnCtrlPanel.add{type="scroll-pane", - name="spwn_ctrl_panel", caption=""} - ApplyStyle(spwnCtrls, my_fixed_width_style) - spwnCtrls.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - spwnCtrls.horizontal_scroll_policy = "never" - - if ENABLE_SHARED_SPAWNS then - if (global.uniqueSpawns[player.name] ~= nil) then - -- This checkbox allows people to join your base when they first - -- start the game. - spwnCtrls.add{type="checkbox", name="accessToggle", - caption="Allow others to join your base.", - state=IsSharedSpawnActive(player)} - spwnCtrls["accessToggle"].style.top_padding = 10 - spwnCtrls["accessToggle"].style.bottom_padding = 10 - ApplyStyle(spwnCtrls["accessToggle"], my_fixed_width_style) - end - end - - - -- Sets the player's custom spawn point to their current location - if ((tick - global.playerCooldowns[player.name].setRespawn) > RESPAWN_COOLDOWN_TICKS) then - spwnCtrls.add{type="button", name="setRespawnLocation", caption="Set New Respawn Location (1 hour cooldown)"} - spwnCtrls["setRespawnLocation"].style.font = "default-small-semibold" - - else - AddLabel(spwnCtrls, "respawn_cooldown_note1", "Set Respawn Cooldown Remaining: " .. formattime(RESPAWN_COOLDOWN_TICKS-(tick - global.playerCooldowns[player.name].setRespawn)), my_note_style) - end - AddLabel(spwnCtrls, "respawn_cooldown_note2", "This will set your respawn point to your current location.", my_note_style) - - -- Display a list of people in the join queue for your base. - if (ENABLE_SHARED_SPAWNS and IsSharedSpawnActive(player)) then - if ((global.sharedSpawns[player.name].joinQueue ~= nil) and - (#global.sharedSpawns[player.name].joinQueue > 0)) then - - - AddLabel(spwnCtrls, "drop_down_msg_lbl1", "Select a player from the join queue:", my_label_style) - spwnCtrls.add{name = "join_queue_dropdown", - type = "drop-down", - items = global.sharedSpawns[player.name].joinQueue} - spwnCtrls.add{name = "accept_player_request", - type = "button", - caption="Accept"} - spwnCtrls.add{name = "reject_player_request", - type = "button", - caption="Reject"} - else - AddLabel(spwnCtrls, "empty_join_queue_note1", "You have no players requesting to join you at this time.", my_note_style) - end - spwnCtrls.add{name = "join_queue_spacer", type = "label", - caption=" "} - end - end -end - - -function SpawnCtrlGuiOptionsSelect(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 not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - -- Handle changes to spawn sharing. - if (name == "accessToggle") then - if event.element.state then - if DoesPlayerHaveCustomSpawn(player) then - if (global.sharedSpawns[player.name] == nil) then - CreateNewSharedSpawn(player) - else - global.sharedSpawns[player.name].openAccess = true - end - - SendBroadcastMsg("New players can now join " .. player.name .. "'s base!") - end - else - if (global.sharedSpawns[player.name] ~= nil) then - global.sharedSpawns[player.name].openAccess = false - SendBroadcastMsg("New players can no longer join " .. player.name .. "'s base!") - end - end - end -end - -function SpawnCtrlGuiClick(event) - if not (event and event.element and event.element.valid) then return end - - local player = game.players[event.element.player_index] - local elemName = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (elemName == "spwn_ctrls") then - ExpandSpawnCtrlGui(player, event.tick) - end - - if (event.element.parent) then - if (event.element.parent.name ~= "spwn_ctrl_panel") then - return - end - end - - -- Sets a new respawn point and resets the cooldown. - if (elemName == "setRespawnLocation") then - if DoesPlayerHaveCustomSpawn(player) then - ChangePlayerSpawn(player, player.position) - ExpandSpawnCtrlGui(player, event.tick) - player.print("Re-spawn point updated!") - end - end - - -- Accept or reject pending player join requests to a shared base - if ((elemName == "accept_player_request") or (elemName == "reject_player_request")) then - - if ((event.element.parent.join_queue_dropdown == nil) or - (event.element.parent.join_queue_dropdown.selected_index == 0)) then - player.print("Selected player is no longer waiting to join!") - ExpandSpawnCtrlGui(player, event.tick) - return - end - - joinQueueIndex = event.element.parent.join_queue_dropdown.selected_index - joinQueuePlayerChoice = event.element.parent.join_queue_dropdown.get_item(joinQueueIndex) - - if ((game.players[joinQueuePlayerChoice] == nil) or - (not game.players[joinQueuePlayerChoice].connected)) then - player.print("Selected player is no longer waiting to join!") - ExpandSpawnCtrlGui(player, event.tick) - return - end - - if (elemName == "reject_player_request") then - player.print("You rejected " .. joinQueuePlayerChoice .. "'s request to join your base.") - SendMsg(joinQueuePlayerChoice, "Your request to join was rejected.") - ExpandSpawnCtrlGui(player, event.tick) - - -- Close the waiting players menu - if (game.players[joinQueuePlayerChoice].gui.center.join_shared_spawn_wait_menu) then - game.players[joinQueuePlayerChoice].gui.center.join_shared_spawn_wait_menu.destroy() - DisplaySpawnOptions(game.players[joinQueuePlayerChoice]) - end - - -- Find and remove the player from the joinQueue they were in. - for index,requestingPlayer in pairs(global.sharedSpawns[player.name].joinQueue) do - if (requestingPlayer == joinQueuePlayerChoice) then - table.remove(global.sharedSpawns[player.name].joinQueue, index) - return - end - end - - elseif (elemName == "accept_player_request") then - - -- Find and remove the player from the joinQueue they were in. - for index,requestingPlayer in pairs(global.sharedSpawns[player.name].joinQueue) do - if (requestingPlayer == joinQueuePlayerChoice) then - table.remove(global.sharedSpawns[player.name].joinQueue, index) - end - end - - -- If player exists, then do stuff. - if (game.players[joinQueuePlayerChoice]) then - -- Send an announcement - SendBroadcastMsg(joinQueuePlayerChoice .. " is joining " .. player.name .. "'s base!") - - -- Close the waiting players menu - if (game.players[joinQueuePlayerChoice].gui.center.join_shared_spawn_wait_menu) then - game.players[joinQueuePlayerChoice].gui.center.join_shared_spawn_wait_menu.destroy() - end - - -- Spawn the player - joiningPlayer = game.players[joinQueuePlayerChoice] - ChangePlayerSpawn(joiningPlayer, global.sharedSpawns[player.name].position) - SendPlayerToSpawn(joiningPlayer) - GivePlayerStarterItems(joiningPlayer) - table.insert(global.sharedSpawns[player.name].players, joiningPlayer.name) - joiningPlayer.force = game.players[player.name].force - - -- Create the button at the top left for setting respawn point and sharing base. - CreateSpawnCtrlGui(joiningPlayer) - ExpandSpawnCtrlGui(player, event.tick) - else - SendBroadcastMsg(joinQueuePlayerChoice .. " left the game. What an ass.") - end - end - end -end - --- Display the buddy spawn menu -function DisplayBuddySpawnOptions(player) - player.gui.center.add{name = "buddy_spawn_opts", - type = "frame", - direction = "vertical", - caption="Buddy Spawn Options"} - local buddyGui = player.gui.center.buddy_spawn_opts - buddyGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - buddyGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - - -- Warnings and explanations... - buddy_info_msg="To use this, make sure you and your buddy are in this menu at the same time. Only one of you must send the request. Select your buddy from the list (refresh if your buddy's name is not visible) and select your spawn options. Click one of the request buttons to send the request. The other buddy can then accept (or deny) the request. This will allow you both to spawn next to each other, each with your own spawn area. Once a buddy accepts a spawn request, it is final!" - AddLabel(buddyGui, "buddy_info_msg", buddy_info_msg, my_label_style) - AddSpacerLine(buddyGui, "buddy_info_spacer") - - buddyList = {} - for _,buddyName in pairs(global.waitingBuddies) do - if (buddyName ~= player.name) then - table.insert(buddyList, buddyName) - end - end - - AddLabel(buddyGui, "drop_down_msg_lbl1", "First, select a buddy from the waiting list. Then choose the spawn options and send your request:", my_label_style) - buddyGui.add{name = "waiting_buddies_dropdown", - type = "drop-down", - items = buddyList} - buddyGui.add{name = "refresh_buddy_list", - type = "button", - caption="Refresh Buddy List"} - -- AddSpacerLine(buddyGui, "waiting_buddies_spacer") - - -- The buddy spawning options. - local buddySpawnFlow = buddyGui.add{name = "spawn_buddy_flow", - type = "flow", - direction="vertical"} - - - -- Allow picking of teams - if (ENABLE_SEPARATE_TEAMS) then - buddySpawnFlow.add{name = "buddy_spawn_main_team_radio", - type = "radiobutton", - caption="Join Main Team (shared research)", - state=true} - buddySpawnFlow.add{name = "buddy_spawn_new_team_radio", - type = "radiobutton", - caption="Create Your Own Team (own research tree)", - state=false} - buddySpawnFlow.add{name = "buddy_spawn_buddy_team_radio", - type = "radiobutton", - caption="Create Your Own Buddy Team (buddy and you share research)", - state=false} - end - if (SPAWN_MOAT_CHOICE_ENABLED) then - buddySpawnFlow.add{name = "buddy_spawn_moat_option_checkbox", - type = "checkbox", - caption="Surround your spawn with a moat", - state=false} - end - - AddSpacer(buddyGui, "buddy_options_spacer") - buddySpawnFlow.add{name = "buddy_spawn_request_near", - type = "button", - caption="Request Buddy Spawn (Near)"} - buddySpawnFlow.add{name = "buddy_spawn_request_far", - type = "button", - caption="Request Buddy Spawn (Far)"} - AddSpacerLine(buddyGui, "buddy_spawn_spacer") - - - - buddyGui.add{name = "buddy_spawn_cancel", - type = "button", - caption="Cancel (Return to Previous Options)"} - - -- Some final notes - AddSpacerLine(buddyGui, "note_spacer1") - - if (MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN > 0) then - AddLabel(buddyGui, "buddy_spawn_lbl1", - "You can allow up to " .. MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN-1 .. " other online players to join.", - my_note_style) - end - spawn_distance_notes="Near spawn is between " .. NEAR_MIN_DIST .. "-" .. NEAR_MAX_DIST .. " chunks away from the center of the map.\n".. - "Far spawn is between " .. FAR_MIN_DIST .. "-" .. FAR_MAX_DIST .. " chunks away from the center of the map.\n".. - "Solo spawns are dangerous! Expect a fight to reach other players." - AddLabel(buddyGui, "note_lbl1", spawn_distance_notes, my_note_style) -end - - - --- Handle the gui click of the spawn options -function BuddySpawnOptsGuiClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local elemName = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (player.gui.center.buddy_spawn_opts == nil) then - return -- Gui event unrelated to this gui. - end - - -- Just refresh the buddy list dropdown values only. - if (elemName == "refresh_buddy_list") then - player.gui.center.buddy_spawn_opts.waiting_buddies_dropdown.clear_items() - - for _,buddyName in pairs(global.waitingBuddies) do - if (player.name ~= buddyName) then - player.gui.center.buddy_spawn_opts.waiting_buddies_dropdown.add_item(buddyName) - end - end - return - end - - -- Handle the cancel button to exit this menu - if (elemName == "buddy_spawn_cancel") then - player.gui.center.buddy_spawn_opts.destroy() - DisplaySpawnOptions(player) - - -- Remove them from the buddy list when they cancel - for i=#global.waitingBuddies,1,-1 do - name = global.waitingBuddies[i] - if (name == player.name) then - table.remove(global.waitingBuddies, i) - end - end - end - - local joinMainTeamRadio, joinOwnTeamRadio, joinBuddyTeamRadio, moatChoice = false - local buddyChoice = nil - - -- Handle the spawn request button clicks - if ((elemName == "buddy_spawn_request_near") or - (elemName == "buddy_spawn_request_far")) then - - buddySpawnGui = player.gui.center.buddy_spawn_opts - - dropDownIndex = buddySpawnGui.waiting_buddies_dropdown.selected_index - if (dropDownIndex > 0) then - buddyChoice = buddySpawnGui.waiting_buddies_dropdown.get_item(dropDownIndex) - else - player.print("You have not selected a valid buddy! Please try again.") - return - end - - buddyIsStillWaiting = false - for _,buddyName in pairs(global.waitingBuddies) do - if (buddyChoice == buddyName) then - if (game.players[buddyChoice]) then - buddyIsStillWaiting = true - end - break - end - end - if (not buddyIsStillWaiting) then - player.print("Selected buddy is no longer available! Please try again.") - player.gui.center.buddy_spawn_opts.destroy() - DisplayBuddySpawnOptions(player) - return - end - - if (ENABLE_SEPARATE_TEAMS) then - joinMainTeamRadio = buddySpawnGui.spawn_buddy_flow.buddy_spawn_main_team_radio.state - joinOwnTeamRadio = buddySpawnGui.spawn_buddy_flow.buddy_spawn_new_team_radio.state - joinBuddyTeamRadio = buddySpawnGui.spawn_buddy_flow.buddy_spawn_buddy_team_radio.state - else - joinMainTeamRadio = true - joinOwnTeamRadio = false - joinBuddyTeamRadio = false - end - if (SPAWN_MOAT_CHOICE_ENABLED) then - moatChoice = buddySpawnGui.spawn_buddy_flow.buddy_spawn_moat_option_checkbox.state - end - - -- Save the chosen spawn options somewhere for later use. - global.buddySpawnOptions[player.name] = {joinMainTeamRadio=joinMainTeamRadio, - joinOwnTeamRadio=joinOwnTeamRadio, - joinBuddyTeamRadio=joinBuddyTeamRadio, - moatChoice=moatChoice, - buddyChoice=buddyChoice, - distChoice=elemName} - - player.gui.center.buddy_spawn_opts.destroy() - - -- Display prompts to the players - DisplayBuddySpawnWaitMenu(player) - DisplayBuddySpawnRequestMenu(game.players[buddyChoice], player.name) - if (game.players[buddyChoice].gui.center.buddy_spawn_opts ~= nil) then - game.players[buddyChoice].gui.center.buddy_spawn_opts.destroy() - end - - -- Remove them from the buddy list while they make up their minds. - for i=#global.waitingBuddies,1,-1 do - name = global.waitingBuddies[i] - if ((name == player.name) or (name == buddyChoice)) then - table.remove(global.waitingBuddies, i) - end - end - - else - return -- Do nothing, no valid element item was clicked. - end -end - - -function DisplayBuddySpawnWaitMenu(player) - - player.gui.center.add{name = "buddy_wait_menu", - type = "frame", - direction = "vertical", - caption="Waiting for buddy to respond..."} - local sGui = player.gui.center.buddy_wait_menu - sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - - -- Warnings and explanations... - AddLabel(sGui, "warning_lbl1", "You will spawn once your buddy selects yes...", my_warning_style) - AddSpacer(sGui, "warning_spacer") - sGui.add{name = "cancel_buddy_wait_menu", - type = "button", - caption="Cancel (Return to starting spawn options)"} -end - --- Handle the gui click of the buddy wait menu -function BuddySpawnWaitMenuClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local elemName = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (player.gui.center.buddy_wait_menu == nil) then - return -- Gui event unrelated to this gui. - end - - -- Check if player is cancelling the request. - if (elemName == "cancel_buddy_wait_menu") then - player.gui.center.buddy_wait_menu.destroy() - DisplaySpawnOptions(player) - - buddy = game.players[global.buddySpawnOptions[player.name].buddyChoice] - - if (buddy.gui.center.buddy_request_menu ~= nil) then - buddy.gui.center.buddy_request_menu.destroy() - end - if (buddy.gui.center.buddy_spawn ~= nil) then - buddy.gui.center.buddy_spawn_opts.destroy() - end - DisplaySpawnOptions(buddy) - - buddy.print(player.name .. " cancelled their buddy request!") - end -end - -function DisplayBuddySpawnRequestMenu(player, requestingBuddyName) - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - player.gui.center.add{name = "buddy_request_menu", - type = "frame", - direction = "vertical", - caption="Buddy Request!"} - local sGui = player.gui.center.buddy_request_menu - sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - - -- Warnings and explanations... - AddLabel(sGui, "warning_lbl1", requestingBuddyName .. " is requesting a buddy spawn from you!", my_warning_style) - - teamText = "error!" - if (global.buddySpawnOptions[requestingBuddyName].joinMainTeamRadio) then - teamText = "the main team" - elseif (global.buddySpawnOptions[requestingBuddyName].joinOwnTeamRadio) then - teamText = "on separate teams" - elseif (global.buddySpawnOptions[requestingBuddyName].joinBuddyTeamRadio) then - teamText = "a buddy team" - end - - moatText = " " - if (global.buddySpawnOptions[requestingBuddyName].moatChoice) then - moatText = " surrounded by a moat " - end - - distText = "error!" - if (global.buddySpawnOptions[requestingBuddyName].distChoice == "buddy_spawn_request_near") then - distText = "near to the center of the map!" - elseif (global.buddySpawnOptions[requestingBuddyName].distChoice == "buddy_spawn_request_far") then - distText = "far from the center of the map!" - end - - - requestText = requestingBuddyName .. " would like to join " .. teamText .. " next to you" .. moatText .. distText - AddLabel(sGui, "note_lbl1", requestText, my_warning_style) - AddSpacer(sGui, "note_spacer1") - - - sGui.add{name = "accept_buddy_request", - type = "button", - caption="Accept"} - sGui.add{name = "decline_buddy_request", - type = "button", - caption="Decline"} -end - --- Handle the gui click of the buddy request menu -function BuddySpawnRequestMenuClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.player_index] - local elemName = event.element.name - - if not player then - DebugPrint("Another gui click happened with no valid player...") - return - end - - if (player.gui.center.buddy_request_menu == nil) then - return -- Gui event unrelated to this gui. - end - - -- Check if it's a button press and lookup the matching buddy info - if ((elemName == "accept_buddy_request") or (elemName == "decline_buddy_request")) then - - requesterName = nil - requesterOptions = {} - for name,opts in pairs(global.buddySpawnOptions) do - if (opts.buddyChoice == player.name) then - requesterName = name - requesterOptions = opts - end - end - - if (requesterName == nil) then - player.print("Error! Invalid buddy info...") - DebugPrint("Error! Invalid buddy info...") - - player.gui.center.buddy_request_menu.destroy() - DisplaySpawnOptions(player) - end - else - return -- Not a button click - end - - - -- Check if player is cancelling the request. - if (elemName == "accept_buddy_request") then - - if (game.players[requesterName].gui.center.buddy_wait_menu ~= nil) then - game.players[requesterName].gui.center.buddy_wait_menu.destroy() - end - if (player.gui.center.buddy_request_menu ~= nil) then - player.gui.center.buddy_request_menu.destroy() - end - - -- Create a new spawn point - local newSpawn = {x=0,y=0} - - -- Create a new force for each player if they chose that option - if requesterOptions.joinOwnTeamRadio then - local newForce = CreatePlayerCustomForce(player) - local buddyForce = CreatePlayerCustomForce(game.players[requesterName]) - - -- Create a new force for the combined players if they chose that option - elseif requesterOptions.joinBuddyTeamRadio then - local buddyForce = CreatePlayerCustomForce(game.players[requesterName]) - player.force = buddyForce - end - - -- Find coordinates of a good place to spawn - if (requesterOptions.distChoice == "buddy_spawn_request_far") then - newSpawn = FindUngeneratedCoordinates(FAR_MIN_DIST,FAR_MAX_DIST, player.surface) - elseif (requesterOptions.distChoice == "buddy_spawn_request_near") then - newSpawn = FindUngeneratedCoordinates(NEAR_MIN_DIST,NEAR_MAX_DIST, player.surface) - end - - -- If that fails, find a random map edge in a rand direction. - if ((newSpawn.x == 0) and (newSpawn.x == 0)) then - newSpawn = FindMapEdge(GetRandomVector(), player.surface) - DebugPrint("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y) - end - - -- Create that spawn in the global vars - if (requesterOptions.moatChoice) then - buddySpawn = {x=newSpawn.x+(ENFORCE_LAND_AREA_TILE_DIST*2)+10, y=newSpawn.y} - else - buddySpawn = {x=newSpawn.x+(ENFORCE_LAND_AREA_TILE_DIST*2), y=newSpawn.y} - end - ChangePlayerSpawn(player, newSpawn) - ChangePlayerSpawn(game.players[requesterName], buddySpawn) - - -- Send the player there - QueuePlayerForDelayedSpawn(player.name, newSpawn, requesterOptions.moatChoice) - QueuePlayerForDelayedSpawn(requesterName, buddySpawn, requesterOptions.moatChoice) - SendBroadcastMsg(requesterName .. " and " .. player.name .. " are joining the game together!") - - -- Create the button at the top left for setting respawn point and sharing base. - CreateSpawnCtrlGui(player) - CreateSpawnCtrlGui(game.players[requesterName]) - - player.print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!") - player.print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!!") - player.print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!!!") - game.players[requesterName].print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!") - game.players[requesterName].print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!!") - game.players[requesterName].print("PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!!!") - - - end - - - if (elemName == "decline_buddy_request") then - player.gui.center.buddy_request_menu.destroy() - DisplaySpawnOptions(player) - - requesterBuddy = game.players[requesterName] - - if (requesterBuddy.gui.center.buddy_wait_menu ~= nil) then - requesterBuddy.gui.center.buddy_wait_menu.destroy() - end - if (requesterBuddy.gui.center.buddy_spawn ~= nil) then - requesterBuddy.gui.center.buddy_spawn_opts.destroy() - end - DisplaySpawnOptions(requesterBuddy) - - requesterBuddy.print(player.name .. " declined your buddy request!") - end -end - - -function DisplayPleaseWaitForSpawnDialog(player, delay_seconds) - - player.gui.center.add{name = "wait_for_spawn_dialog", - type = "frame", - direction = "vertical", - caption="Please wait!"} - local pleaseWaitGui = player.gui.center.wait_for_spawn_dialog - pleaseWaitGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH - pleaseWaitGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT - - - -- Warnings and explanations... - local wait_warning_text = "Your spawn is being created now.\n".. - "You will be teleported there in "..delay_seconds.." seconds!\n".. - "Please standby..." - - AddLabel(pleaseWaitGui, "warning_lbl1", wait_warning_text, my_warning_style) +-- separate_spawns_guis.lua +-- Nov 2016 + +-- I made a separate file for all the GUI related functions + +require("lib/separate_spawns") + +local SPAWN_GUI_MAX_WIDTH = 500 +local SPAWN_GUI_MAX_HEIGHT = 1000 + +-- Use this for testing shared spawns... +-- local sharedSpawnExample1 = {openAccess=true, +-- position={x=50,y=50}, +-- players={"ABC", "DEF"}} +-- local sharedSpawnExample2 = {openAccess=false, +-- position={x=200,y=200}, +-- players={"ABC", "DEF"}} +-- local sharedSpawnExample3 = {openAccess=true, +-- position={x=400,y=400}, +-- players={"A", "B", "C", "D"}} +-- global.sharedSpawns = {testName1=sharedSpawnExample1, +-- testName2=sharedSpawnExample2, +-- Oarc=sharedSpawnExample3} + + +-- A display gui message +-- Meant to be display the first time a player joins. +function DisplayWelcomeTextGui(player) + if ((player.gui.screen["welcome_msg"] ~= nil) or + (player.gui.screen["spawn_opts"] ~= nil) or + (player.gui.screen["shared_spawn_opts"] ~= nil) or + (player.gui.screen["join_shared_spawn_wait_menu"] ~= nil) or + (player.gui.screen["buddy_spawn_opts"] ~= nil) or + (player.gui.screen["buddy_wait_menu"] ~= nil) or + (player.gui.screen["buddy_request_menu"] ~= nil) or + (player.gui.screen["wait_for_spawn_dialog"] ~= nil)) then + log("DisplayWelcomeTextGui called while some other dialog is already displayed!") + return false + end + + local wGui = player.gui.screen.add{name = "welcome_msg", + type = "frame", + direction = "vertical", + caption=global.ocfg.welcome_title} + wGui.auto_center=true + + wGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + wGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + + -- Start with server message. + AddLabel(wGui, "server_msg_lbl1", global.ocfg.server_rules, my_label_style) + AddLabel(wGui, "contact_info_msg_lbl1", global.ocfg.server_contact, my_label_style) + AddSpacer(wGui) + + -- Informational message about the scenario + AddLabel(wGui, "scenario_info_msg_lbl1", SCENARIO_INFO_MSG, my_label_style) + AddSpacer(wGui) + + -- Warning about spawn creation time + AddLabel(wGui, "spawn_time_msg_lbl1", {"oarc-spawn-time-warning-msg"}, my_warning_style) + + -- Confirm button + AddSpacerLine(wGui) + local button_flow = wGui.add{type = "flow"} + button_flow.style.horizontal_align = "right" + button_flow.style.horizontally_stretchable = true + button_flow.add{name = "welcome_okay_btn", + type = "button", + caption={"oarc-i-understand"}, + style = "confirm_button"} + + return true +end + + +-- Handle the gui click of the welcome msg +function WelcomeTextGuiClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local buttonClicked = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (buttonClicked == "welcome_okay_btn") then + if (player.gui.screen.welcome_msg ~= nil) then + player.gui.screen.welcome_msg.destroy() + end + DisplaySpawnOptions(player) + end +end + + +-- Display the spawn options and explanation +function DisplaySpawnOptions(player) + if (player == nil) then + log("DisplaySpawnOptions with no valid player...") + return + end + + if (player.gui.screen.spawn_opts ~= nil) then + log("Tried to display spawn options when it was already displayed!") + return + end + player.gui.screen.add{name = "spawn_opts", + type = "frame", + direction = "vertical", + caption={"oarc-spawn-options"}} + local sGui = player.gui.screen.spawn_opts + sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + sGui.auto_center=true + + -- Warnings and explanations... + local warn_msg = {"oarc-click-info-btn-help"} + AddLabel(sGui, "warning_lbl1", warn_msg, my_warning_style) + AddLabel(sGui, "spawn_msg_lbl1", SPAWN_MSG1, my_label_style) + + -- Button and message about the regular vanilla spawn + -- if ENABLE_DEFAULT_SPAWN then + -- sGui.add{name = "default_spawn_btn", + -- type = "button", + -- caption={"oarc-vanilla-spawn"}} + -- local normal_spawn_text = {"oarc-default-spawn-behavior"} + -- AddLabel(sGui, "normal_spawn_lbl1", normal_spawn_text, my_label_style) + -- -- AddSpacerLine(sGui, "normal_spawn_spacer") + -- end + + -- The main spawning options. Solo near and solo far. + -- If enable, you can also choose to be on your own team. + local soloSpawnFlow = sGui.add{name = "spawn_solo_flow", + type = "frame", + direction="vertical", + style = "bordered_frame"} + + -- Radio buttons to pick your team. + if (global.ocfg.enable_separate_teams) then + soloSpawnFlow.add{name = "isolated_spawn_main_team_radio", + type = "radiobutton", + caption={"oarc-join-main-team-radio"}, + state=true} + soloSpawnFlow.add{name = "isolated_spawn_new_team_radio", + type = "radiobutton", + caption={"oarc-create-own-team-radio"}, + state=false} + end + + -- OPTIONS frame + -- AddLabel(soloSpawnFlow, "options_spawn_lbl1", + -- "Additional spawn options can be selected here. Not all are compatible with each other.", my_label_style) + + -- Allow players to spawn with a moat around their area. + if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled and not global.ocfg.enable_vanilla_spawns) then + soloSpawnFlow.add{name = "isolated_spawn_moat_option_checkbox", + type = "checkbox", + caption={"oarc-moat-option"}, + state=false} + end + -- if (global.ocfg.enable_vanilla_spawns and (#global.vanillaSpawns > 0)) then + -- soloSpawnFlow.add{name = "isolated_spawn_vanilla_option_checkbox", + -- type = "checkbox", + -- caption="Use a pre-set vanilla spawn point. " .. #global.vanillaSpawns .. " available.", + -- state=false} + -- end + + -- Isolated spawn options. The core gameplay of this scenario. + local soloSpawnbuttons = soloSpawnFlow.add{name = "spawn_solo_flow", + type = "flow", + direction="horizontal"} + soloSpawnbuttons.style.horizontal_align = "center" + soloSpawnbuttons.style.horizontally_stretchable = true + soloSpawnbuttons.add{name = "isolated_spawn_near", + type = "button", + caption={"oarc-solo-spawn-near"}, + style = "confirm_button"} + soloSpawnbuttons.add{name = "isolated_spawn_far", + type = "button", + caption={"oarc-solo-spawn-far"}, + style = "confirm_button"} + + if (global.ocfg.enable_vanilla_spawns) then + AddLabel(soloSpawnFlow, "isolated_spawn_lbl1", + {"oarc-starting-area-vanilla"}, my_label_style) + AddLabel(soloSpawnFlow, "vanilla_spawn_lbl2", + {"oarc-vanilla-spawns-available", #global.vanillaSpawns}, my_label_style) + else + AddLabel(soloSpawnFlow, "isolated_spawn_lbl1", + {"oarc-starting-area-normal"}, my_label_style) + end + + -- Spawn options to join another player's base. + local sharedSpawnFrame = sGui.add{name = "spawn_shared_flow", + type = "frame", + direction="vertical", + style = "bordered_frame"} + if global.ocfg.enable_shared_spawns then + local numAvailSpawns = GetNumberOfAvailableSharedSpawns() + if (numAvailSpawns > 0) then + sharedSpawnFrame.add{name = "join_other_spawn", + type = "button", + caption={"oarc-join-someone-avail", numAvailSpawns}} + local join_spawn_text = {"oarc-join-someone-info"} + AddLabel(sharedSpawnFrame, "join_other_spawn_lbl1", join_spawn_text, my_label_style) + else + AddLabel(sharedSpawnFrame, "join_other_spawn_lbl1", {"oarc-no-shared-avail"}, my_label_style) + sharedSpawnFrame.add{name = "join_other_spawn_check", + type = "button", + caption={"oarc-join-check-again"}} + end + else + AddLabel(sharedSpawnFrame, "join_other_spawn_lbl1", + {"oarc-shared-spawn-disabled"}, my_warning_style) + end + + -- Awesome buddy spawning system + if (not global.ocfg.enable_vanilla_spawns) then + if global.ocfg.enable_shared_spawns and global.ocfg.enable_buddy_spawn then + local buddySpawnFrame = sGui.add{name = "spawn_buddy_flow", + type = "frame", + direction="vertical", + style = "bordered_frame"} + + -- AddSpacerLine(buddySpawnFrame, "buddy_spawn_msg_spacer") + buddySpawnFrame.add{name = "buddy_spawn", + type = "button", + caption={"oarc-buddy-spawn"}} + AddLabel(buddySpawnFrame, "buddy_spawn_lbl1", + {"oarc-buddy-spawn-info"} , my_label_style) + end + end + + -- Some final notes + if (global.ocfg.max_players_shared_spawn > 0) then + AddLabel(sGui, "max_players_lbl2", + {"oarc-max-players-shared-spawn", global.ocfg.max_players_shared_spawn-1}, + my_note_style) + end + local spawn_distance_notes={"oarc-spawn-dist-notes", global.ocfg.near_dist_start, global.ocfg.near_dist_end, global.ocfg.far_dist_start, global.ocfg.far_dist_end} + AddLabel(sGui, "note_lbl1", spawn_distance_notes, my_note_style) +end + + +-- This just updates the radio buttons/checkboxes when players click them. +function SpawnOptsRadioSelect(event) + if not (event and event.element and event.element.valid) then return end + local elemName = event.element.name + + if (elemName == "isolated_spawn_main_team_radio") then + event.element.parent.isolated_spawn_new_team_radio.state=false + elseif (elemName == "isolated_spawn_new_team_radio") then + event.element.parent.isolated_spawn_main_team_radio.state=false + end + + if (elemName == "buddy_spawn_main_team_radio") then + event.element.parent.buddy_spawn_new_team_radio.state=false + event.element.parent.buddy_spawn_buddy_team_radio.state=false + elseif (elemName == "buddy_spawn_new_team_radio") then + event.element.parent.buddy_spawn_main_team_radio.state=false + event.element.parent.buddy_spawn_buddy_team_radio.state=false + elseif (elemName == "buddy_spawn_buddy_team_radio") then + event.element.parent.buddy_spawn_main_team_radio.state=false + event.element.parent.buddy_spawn_new_team_radio.state=false + end +end + + +-- Handle the gui click of the spawn options +function SpawnOptsGuiClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local elemName = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (player.gui.screen.spawn_opts == nil) then + return -- Gui event unrelated to this gui. + end + + local pgcs = player.gui.screen.spawn_opts + + local joinMainTeamRadio, joinOwnTeamRadio, moatChoice, vanillaChoice = false + + -- Check if a valid button on the gui was pressed + -- and delete the GUI + if ((elemName == "default_spawn_btn") or + (elemName == "isolated_spawn_near") or + (elemName == "isolated_spawn_far") or + (elemName == "join_other_spawn") or + (elemName == "buddy_spawn") or + (elemName == "join_other_spawn_check")) then + + if (global.ocfg.enable_separate_teams) then + joinMainTeamRadio = + pgcs.spawn_solo_flow.isolated_spawn_main_team_radio.state + joinOwnTeamRadio = + pgcs.spawn_solo_flow.isolated_spawn_new_team_radio.state + else + joinMainTeamRadio = true + joinOwnTeamRadio = false + end + if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled and not global.ocfg.enable_vanilla_spawns and + (pgcs.spawn_solo_flow.isolated_spawn_moat_option_checkbox ~= nil)) then + moatChoice = pgcs.spawn_solo_flow.isolated_spawn_moat_option_checkbox.state + end + -- if (global.ocfg.enable_vanilla_spawns and + -- (pgcs.spawn_solo_flow.isolated_spawn_vanilla_option_checkbox ~= nil)) then + -- vanillaChoice = pgcs.spawn_solo_flow.isolated_spawn_vanilla_option_checkbox.state + -- end + pgcs.destroy() + else + return -- Do nothing, no valid element item was clicked. + end + + if (elemName == "default_spawn_btn") then + GivePlayerStarterItems(player) + ChangePlayerSpawn(player, player.force.get_spawn_position(GAME_SURFACE_NAME)) + SendBroadcastMsg({"oarc-player-is-joining-main-force", player.name}) + ChartArea(player.force, player.position, math.ceil(global.ocfg.spawn_config.gen_settings.land_area_tiles/CHUNK_SIZE), player.surface) + -- Unlock spawn control gui tab + SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_GUI_NAME, true) + + elseif ((elemName == "isolated_spawn_near") or (elemName == "isolated_spawn_far")) then + + -- Create a new spawn point + local newSpawn = {x=0,y=0} + + -- Create a new force for player if they choose that radio button + if global.ocfg.enable_separate_teams and joinOwnTeamRadio then + local newForce = CreatePlayerCustomForce(player) + end + + -- Find an unused vanilla spawn + -- if (vanillaChoice) then + if (global.ocfg.enable_vanilla_spawns) then + if (elemName == "isolated_spawn_far") then + newSpawn = FindUnusedVanillaSpawn(game.surfaces[GAME_SURFACE_NAME], + global.ocfg.far_dist_end*CHUNK_SIZE) + elseif (elemName == "isolated_spawn_near") then + newSpawn = FindUnusedVanillaSpawn(game.surfaces[GAME_SURFACE_NAME], + global.ocfg.near_dist_start*CHUNK_SIZE) + end + + -- Default OARC-type pre-set layout spawn. + else + -- Find coordinates of a good place to spawn + if (elemName == "isolated_spawn_far") then + newSpawn = FindUngeneratedCoordinates(global.ocfg.far_dist_start,global.ocfg.far_dist_end, player.surface) + elseif (elemName == "isolated_spawn_near") then + newSpawn = FindUngeneratedCoordinates(global.ocfg.near_dist_start,global.ocfg.near_dist_end, player.surface) + end + end + + -- If that fails, find a random map edge in a rand direction. + if ((newSpawn.x == 0) and (newSpawn.y == 0)) then + newSpawn = FindMapEdge(GetRandomVector(), player.surface) + log("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y) + end + + -- Create that player's spawn in the global vars + ChangePlayerSpawn(player, newSpawn) + + -- Send the player there + -- QueuePlayerForDelayedSpawn(player.name, newSpawn, moatChoice, vanillaChoice) + QueuePlayerForDelayedSpawn(player.name, newSpawn, moatChoice, global.ocfg.enable_vanilla_spawns) + if (elemName == "isolated_spawn_near") then + SendBroadcastMsg({"oarc-player-is-joining-near", player.name}) + elseif (elemName == "isolated_spawn_far") then + SendBroadcastMsg({"oarc-player-is-joining-far", player.name}) + end + + -- Unlock spawn control gui tab + SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_GUI_NAME, true) + + player.print({"oarc-please-wait"}) + player.print({"", {"oarc-please-wait"}, "!"}) + player.print({"", {"oarc-please-wait"}, "!!"}) + + elseif (elemName == "join_other_spawn") then + DisplaySharedSpawnOptions(player) + + -- Provide a way to refresh the gui to check if people have shared their + -- bases. + elseif (elemName == "join_other_spawn_check") then + DisplaySpawnOptions(player) + + -- Hacky buddy spawn system + elseif (elemName == "buddy_spawn") then + table.insert(global.waitingBuddies, player.name) + SendBroadcastMsg({"oarc-looking-for-buddy", player.name}) + + DisplayBuddySpawnOptions(player) + end +end + + +-- Display the spawn options and explanation +function DisplaySharedSpawnOptions(player) + player.gui.screen.add{name = "shared_spawn_opts", + type = "frame", + direction = "vertical", + caption={"oarc-avail-bases-join"}} + + local shGuiFrame = player.gui.screen.shared_spawn_opts + shGuiFrame.auto_center = true + local shGui = shGuiFrame.add{type="scroll-pane", name="spawns_scroll_pane", caption=""} + ApplyStyle(shGui, my_fixed_width_style) + shGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + shGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + shGui.horizontal_scroll_policy = "never" + + + for spawnName,sharedSpawn in pairs(global.sharedSpawns) do + if (sharedSpawn.openAccess and + (game.players[spawnName] ~= nil) and + game.players[spawnName].connected) then + local spotsRemaining = global.ocfg.max_players_shared_spawn - #global.sharedSpawns[spawnName].players + if (global.ocfg.max_players_shared_spawn == 0) then + shGui.add{type="button", caption=spawnName, name=spawnName} + elseif (spotsRemaining > 0) then + shGui.add{type="button", caption={"oarc-spawn-spots-remaining", spawnName, spotsRemaining}, name=spawnName} + end + if (shGui.spawnName ~= nil) then + -- AddSpacer(buddyGui, spawnName .. "spacer_lbl") + ApplyStyle(shGui[spawnName], my_small_button_style) + end + end + end + + + shGui.add{name = "shared_spawn_cancel", + type = "button", + caption={"oarc-cancel-return-to-previous"}, + style = "back_button"} +end + +-- Handle the gui click of the shared spawn options +function SharedSpwnOptsGuiClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local buttonClicked = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (event.element.parent) then + if (event.element.parent.name ~= "spawns_scroll_pane") then + return + end + end + + -- Check for cancel button, return to spawn options + if (buttonClicked == "shared_spawn_cancel") then + DisplaySpawnOptions(player) + if (player.gui.screen.shared_spawn_opts ~= nil) then + player.gui.screen.shared_spawn_opts.destroy() + end + + -- Else check for which spawn was selected + -- If a spawn is removed during this time, the button will not do anything + else + for spawnName,sharedSpawn in pairs(global.sharedSpawns) do + if ((buttonClicked == spawnName) and + (game.players[spawnName] ~= nil) and + (game.players[spawnName].connected)) then + + -- Add the player to that shared spawns join queue. + if (global.sharedSpawns[spawnName].joinQueue == nil) then + global.sharedSpawns[spawnName].joinQueue = {} + end + table.insert(global.sharedSpawns[spawnName].joinQueue, player.name) + + -- Clear the shared spawn options gui. + if (player.gui.screen.shared_spawn_opts ~= nil) then + player.gui.screen.shared_spawn_opts.destroy() + end + + -- Display wait menu with cancel button. + DisplaySharedSpawnJoinWaitMenu(player) + + -- Tell other player they are requesting a response. + game.players[spawnName].print({"oarc-player-requesting-join-you", player.name}) + break + end + end + end +end + +function DisplaySharedSpawnJoinWaitMenu(player) + + local sGui = player.gui.screen.add{name = "join_shared_spawn_wait_menu", + type = "frame", + direction = "vertical", + caption={"oarc-waiting-for-spawn-owner"}} + sGui.auto_center = true + sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + + + -- Warnings and explanations... + AddLabel(sGui, "warning_lbl1", {"oarc-you-will-spawn-once-host"}, my_warning_style) + sGui.add{name = "cancel_shared_spawn_wait_menu", + type = "button", + caption={"oarc-cancel-return-to-previous"}, + style = "back_button"} +end + +-- Handle the gui click of the buddy wait menu +function SharedSpawnJoinWaitMenuClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local elemName = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (player.gui.screen.join_shared_spawn_wait_menu == nil) then + return -- Gui event unrelated to this gui. + end + + -- Check if player is cancelling the request. + if (elemName == "cancel_shared_spawn_wait_menu") then + player.gui.screen.join_shared_spawn_wait_menu.destroy() + DisplaySpawnOptions(player) + + -- Find and remove the player from the joinQueue they were in. + for spawnName,sharedSpawn in pairs(global.sharedSpawns) do + if (sharedSpawn.joinQueue ~= nil) then + for index,requestingPlayer in pairs(sharedSpawn.joinQueue) do + if (requestingPlayer == player.name) then + table.remove(global.sharedSpawns[spawnName].joinQueue, index) + game.players[spawnName].print({"oarc-player-cancel-join-request", player.name}) + return + end + end + end + end + + log("ERROR! Failed to remove player from joinQueue!") + end +end + +local function IsSharedSpawnActive(player) + if ((global.sharedSpawns[player.name] == nil) or + (global.sharedSpawns[player.name].openAccess == false)) then + return false + else + return true + end +end + + +-- Get a random warp point to go to +function GetRandomSpawnPoint() + local numSpawnPoints = TableLength(global.sharedSpawns) + if (numSpawnPoints > 0) then + local randSpawnNum = math.random(1,numSpawnPoints) + local counter = 1 + for _,sharedSpawn in pairs(global.sharedSpawns) do + if (randSpawnNum == counter) then + return sharedSpawn.position + end + counter = counter + 1 + end + end + + return {x=0,y=0} +end + +-- This is a toggle function, it either shows or hides the spawn controls +function CreateSpawnCtrlGuiTab(tab_container, player) + local spwnCtrls = tab_container.add{ + type="scroll-pane", + name="spwn_ctrl_panel", + caption=""} + ApplyStyle(spwnCtrls, my_fixed_width_style) + spwnCtrls.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + spwnCtrls.horizontal_scroll_policy = "never" + + if global.ocfg.enable_shared_spawns then + if (global.uniqueSpawns[player.name] ~= nil) then + -- This checkbox allows people to join your base when they first + -- start the game. + spwnCtrls.add{type="checkbox", name="accessToggle", + caption={"oarc-spawn-allow-joiners"}, + state=IsSharedSpawnActive(player)} + ApplyStyle(spwnCtrls["accessToggle"], my_fixed_width_style) + end + end + + -- Sets the player's custom spawn point to their current location + if ((game.tick - global.playerCooldowns[player.name].setRespawn) > + (global.ocfg.respawn_cooldown_min * TICKS_PER_MINUTE)) then + spwnCtrls.add{type="button", name="setRespawnLocation", caption={"oarc-set-respawn-loc"}} + spwnCtrls["setRespawnLocation"].style.font = "default-small-semibold" + + else + AddLabel(spwnCtrls,"respawn_cooldown_note1", + {"oarc-set-respawn-loc-cooldown", formattime((global.ocfg.respawn_cooldown_min * TICKS_PER_MINUTE)-(game.tick - global.playerCooldowns[player.name].setRespawn))}, my_note_style) + end + AddLabel(spwnCtrls, "respawn_cooldown_note2", {"oarc-set-respawn-note"}, my_note_style) + + -- Display a list of people in the join queue for your base. + if (global.ocfg.enable_shared_spawns and IsSharedSpawnActive(player)) then + if ((global.sharedSpawns[player.name].joinQueue ~= nil) and + (#global.sharedSpawns[player.name].joinQueue > 0)) then + + + AddLabel(spwnCtrls, "drop_down_msg_lbl1", {"oarc-select-player-join-queue"}, my_label_style) + spwnCtrls.add{name = "join_queue_dropdown", + type = "drop-down", + items = global.sharedSpawns[player.name].joinQueue} + spwnCtrls.add{name = "accept_player_request", + type = "button", + caption={"oarc-accept"}} + spwnCtrls.add{name = "reject_player_request", + type = "button", + caption={"oarc-reject"}} + else + AddLabel(spwnCtrls, "empty_join_queue_note1", {"oarc-no-player-join-reqs"}, my_note_style) + end + spwnCtrls.add{name = "join_queue_spacer", type = "label", + caption=" "} + end +end + +function SpawnCtrlGuiOptionsSelect(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 not player then + log("Another gui click happened with no valid player...") + return + end + + -- Handle changes to spawn sharing. + if (name == "accessToggle") then + if event.element.state then + if DoesPlayerHaveCustomSpawn(player) then + if (global.sharedSpawns[player.name] == nil) then + CreateNewSharedSpawn(player) + else + global.sharedSpawns[player.name].openAccess = true + end + + SendBroadcastMsg({"oarc-start-shared-base", player.name}) + end + else + if (global.sharedSpawns[player.name] ~= nil) then + global.sharedSpawns[player.name].openAccess = false + SendBroadcastMsg({"oarc-stop-shared-base", player.name}) + end + end + FakeTabChangeEventOarcGui(player) + end +end + +function SpawnCtrlGuiClick(event) + if not (event and event.element and event.element.valid) then return end + + local player = game.players[event.element.player_index] + local elemName = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (event.element.parent) then + if (event.element.parent.name ~= "spwn_ctrl_panel") then + return + end + end + + -- Sets a new respawn point and resets the cooldown. + if (elemName == "setRespawnLocation") then + if DoesPlayerHaveCustomSpawn(player) then + ChangePlayerSpawn(player, player.position) + FakeTabChangeEventOarcGui(player) + player.print({"oarc-spawn-point-updated"}) + end + end + + -- Accept or reject pending player join requests to a shared base + if ((elemName == "accept_player_request") or (elemName == "reject_player_request")) then + + if ((event.element.parent.join_queue_dropdown == nil) or + (event.element.parent.join_queue_dropdown.selected_index == 0)) then + player.print({"oarc-selected-player-not-wait"}) + FakeTabChangeEventOarcGui(player) + return + end + + local joinQueueIndex = event.element.parent.join_queue_dropdown.selected_index + local joinQueuePlayerChoice = event.element.parent.join_queue_dropdown.get_item(joinQueueIndex) + + if ((game.players[joinQueuePlayerChoice] == nil) or + (not game.players[joinQueuePlayerChoice].connected)) then + player.print({"oarc-selected-player-not-wait"}) + FakeTabChangeEventOarcGui(player) + return + end + + if (elemName == "reject_player_request") then + player.print({"oarc-reject-joiner", joinQueuePlayerChoice}) + SendMsg(joinQueuePlayerChoice, {"oarc-your-request-rejected"}) + FakeTabChangeEventOarcGui(player) + + -- Close the waiting players menu + if (game.players[joinQueuePlayerChoice].gui.screen.join_shared_spawn_wait_menu) then + game.players[joinQueuePlayerChoice].gui.screen.join_shared_spawn_wait_menu.destroy() + DisplaySpawnOptions(game.players[joinQueuePlayerChoice]) + end + + -- Find and remove the player from the joinQueue they were in. + for index,requestingPlayer in pairs(global.sharedSpawns[player.name].joinQueue) do + if (requestingPlayer == joinQueuePlayerChoice) then + table.remove(global.sharedSpawns[player.name].joinQueue, index) + return + end + end + + elseif (elemName == "accept_player_request") then + + -- Find and remove the player from the joinQueue they were in. + for index,requestingPlayer in pairs(global.sharedSpawns[player.name].joinQueue) do + if (requestingPlayer == joinQueuePlayerChoice) then + table.remove(global.sharedSpawns[player.name].joinQueue, index) + end + end + + -- If player exists, then do stuff. + if (game.players[joinQueuePlayerChoice]) then + -- Send an announcement + SendBroadcastMsg({"oarc-player-joining-base", joinQueuePlayerChoice, player.name}) + + -- Close the waiting players menu + if (game.players[joinQueuePlayerChoice].gui.screen.join_shared_spawn_wait_menu) then + game.players[joinQueuePlayerChoice].gui.screen.join_shared_spawn_wait_menu.destroy() + end + + -- Spawn the player + local joiningPlayer = game.players[joinQueuePlayerChoice] + ChangePlayerSpawn(joiningPlayer, global.sharedSpawns[player.name].position) + SendPlayerToSpawn(joiningPlayer) + GivePlayerStarterItems(joiningPlayer) + table.insert(global.sharedSpawns[player.name].players, joiningPlayer.name) + joiningPlayer.force = game.players[player.name].force + + -- Unlock spawn control gui tab + SetOarcGuiTabEnabled(joiningPlayer, OARC_SPAWN_CTRL_GUI_NAME, true) + else + SendBroadcastMsg({"oarc-player-left-while-joining", joinQueuePlayerChoice}) + end + end + end +end + +-- Display the buddy spawn menu +function DisplayBuddySpawnOptions(player) + local buddyGui = player.gui.screen.add{name = "buddy_spawn_opts", + type = "frame", + direction = "vertical", + caption={"oarc-buddy-spawn-options"}} + buddyGui.auto_center = true + buddyGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + buddyGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + + + -- Warnings and explanations... + AddLabel(buddyGui, "buddy_info_msg", {"oarc-buddy-spawn-instructions"}, my_label_style) + AddSpacer(buddyGui) + + -- The buddy spawning options. + local buddySpawnFlow = buddyGui.add{name = "spawn_buddy_flow", + type = "frame", + direction="vertical", + style = "bordered_frame"} + + buddyList = {} + for _,buddyName in pairs(global.waitingBuddies) do + if (buddyName ~= player.name) then + table.insert(buddyList, buddyName) + end + end + + AddLabel(buddySpawnFlow, "drop_down_msg_lbl1", {"oarc-buddy-select-info"}, my_label_style) + buddySpawnFlow.add{name = "waiting_buddies_dropdown", + type = "drop-down", + items = buddyList} + buddySpawnFlow.add{name = "refresh_buddy_list", + type = "button", + caption={"oarc-buddy-refresh"}} + -- AddSpacerLine(buddySpawnFlow) + + -- Allow picking of teams + if (global.ocfg.enable_separate_teams) then + buddySpawnFlow.add{name = "buddy_spawn_main_team_radio", + type = "radiobutton", + caption={"oarc-join-main-team-radio"}, + state=true} + buddySpawnFlow.add{name = "buddy_spawn_new_team_radio", + type = "radiobutton", + caption={"oarc-create-own-team-radio"}, + state=false} + buddySpawnFlow.add{name = "buddy_spawn_buddy_team_radio", + type = "radiobutton", + caption={"oarc-create-buddy-team"}, + state=false} + end + if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled) then + buddySpawnFlow.add{name = "buddy_spawn_moat_option_checkbox", + type = "checkbox", + caption={"oarc-moat-option"}, + state=false} + end + + -- AddSpacerLine(buddySpawnFlow) + buddySpawnFlow.add{name = "buddy_spawn_request_near", + type = "button", + caption={"oarc-buddy-spawn-near"}, + style = "confirm_button"} + buddySpawnFlow.add{name = "buddy_spawn_request_far", + type = "button", + caption={"oarc-buddy-spawn-far"}, + style = "confirm_button"} + + AddSpacer(buddyGui) + buddyGui.add{name = "buddy_spawn_cancel", + type = "button", + caption={"oarc-cancel-return-to-previous"}, + style = "back_button"} + + -- Some final notes + AddSpacerLine(buddyGui) + if (global.ocfg.max_players_shared_spawn > 0) then + AddLabel(buddyGui, "buddy_max_players_lbl1", + {"oarc-max-players-shared-spawn", global.ocfg.max_players_shared_spawn-1}, + my_note_style) + end + local spawn_distance_notes={"oarc-spawn-dist-notes", global.ocfg.near_dist_start, global.ocfg.near_dist_end, global.ocfg.far_dist_start, global.ocfg.far_dist_end} + AddLabel(buddyGui, "note_lbl1", spawn_distance_notes, my_note_style) +end + + + +-- Handle the gui click of the spawn options +function BuddySpawnOptsGuiClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local elemName = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (player.gui.screen.buddy_spawn_opts == nil) then + return -- Gui event unrelated to this gui. + end + + local waiting_buddies_dropdown = player.gui.screen.buddy_spawn_opts.spawn_buddy_flow.waiting_buddies_dropdown + + -- Just refresh the buddy list dropdown values only. + if (elemName == "refresh_buddy_list") then + waiting_buddies_dropdown.clear_items() + + for _,buddyName in pairs(global.waitingBuddies) do + if (player.name ~= buddyName) then + waiting_buddies_dropdown.add_item(buddyName) + end + end + return + end + + -- Handle the cancel button to exit this menu + if (elemName == "buddy_spawn_cancel") then + player.gui.screen.buddy_spawn_opts.destroy() + DisplaySpawnOptions(player) + + -- Remove them from the buddy list when they cancel + for i=#global.waitingBuddies,1,-1 do + local name = global.waitingBuddies[i] + if (name == player.name) then + table.remove(global.waitingBuddies, i) + end + end + end + + local joinMainTeamRadio, joinOwnTeamRadio, joinBuddyTeamRadio, moatChoice = false + local buddyChoice = nil + + -- Handle the spawn request button clicks + if ((elemName == "buddy_spawn_request_near") or + (elemName == "buddy_spawn_request_far")) then + + local buddySpawnGui = player.gui.screen.buddy_spawn_opts.spawn_buddy_flow + + local dropDownIndex = buddySpawnGui.waiting_buddies_dropdown.selected_index + if ((dropDownIndex > 0) and (dropDownIndex <= #buddySpawnGui.waiting_buddies_dropdown.items)) then + buddyChoice = buddySpawnGui.waiting_buddies_dropdown.get_item(dropDownIndex) + else + player.print({"oarc-invalid-buddy"}) + return + end + + local buddyIsStillWaiting = false + for _,buddyName in pairs(global.waitingBuddies) do + if (buddyChoice == buddyName) then + if (game.players[buddyChoice]) then + buddyIsStillWaiting = true + end + break + end + end + if (not buddyIsStillWaiting) then + player.print({"oarc-buddy-not-avail"}) + player.gui.screen.buddy_spawn_opts.destroy() + DisplayBuddySpawnOptions(player) + return + end + + if (global.ocfg.enable_separate_teams) then + joinMainTeamRadio = buddySpawnGui.buddy_spawn_main_team_radio.state + joinOwnTeamRadio = buddySpawnGui.buddy_spawn_new_team_radio.state + joinBuddyTeamRadio = buddySpawnGui.buddy_spawn_buddy_team_radio.state + else + joinMainTeamRadio = true + joinOwnTeamRadio = false + joinBuddyTeamRadio = false + end + if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled) then + moatChoice = buddySpawnGui.buddy_spawn_moat_option_checkbox.state + end + + -- Save the chosen spawn options somewhere for later use. + global.buddySpawnOptions[player.name] = {joinMainTeamRadio=joinMainTeamRadio, + joinOwnTeamRadio=joinOwnTeamRadio, + joinBuddyTeamRadio=joinBuddyTeamRadio, + moatChoice=moatChoice, + buddyChoice=buddyChoice, + distChoice=elemName} + + player.gui.screen.buddy_spawn_opts.destroy() + + -- Display prompts to the players + DisplayBuddySpawnWaitMenu(player) + DisplayBuddySpawnRequestMenu(game.players[buddyChoice], player.name) + if (game.players[buddyChoice].gui.screen.buddy_spawn_opts ~= nil) then + game.players[buddyChoice].gui.screen.buddy_spawn_opts.destroy() + end + + -- Remove them from the buddy list while they make up their minds. + for i=#global.waitingBuddies,1,-1 do + name = global.waitingBuddies[i] + if ((name == player.name) or (name == buddyChoice)) then + table.remove(global.waitingBuddies, i) + end + end + + else + return -- Do nothing, no valid element item was clicked. + end +end + + +function DisplayBuddySpawnWaitMenu(player) + + local sGui = player.gui.screen.add{name = "buddy_wait_menu", + type = "frame", + direction = "vertical", + caption={"oarc-waiting-for-buddy"}} + sGui.auto_center = true + sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + + + -- Warnings and explanations... + AddLabel(sGui, "warning_lbl1", {"oarc-wait-buddy-select-yes"}, my_warning_style) + AddSpacer(sGui) + sGui.add{name = "cancel_buddy_wait_menu", + type = "button", + caption={"oarc-cancel-return-to-previous"}} +end + +-- Handle the gui click of the buddy wait menu +function BuddySpawnWaitMenuClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local elemName = event.element.name + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (player.gui.screen.buddy_wait_menu == nil) then + return -- Gui event unrelated to this gui. + end + + -- Check if player is cancelling the request. + if (elemName == "cancel_buddy_wait_menu") then + player.gui.screen.buddy_wait_menu.destroy() + DisplaySpawnOptions(player) + + local buddy = game.players[global.buddySpawnOptions[player.name].buddyChoice] + + if (buddy.gui.screen.buddy_request_menu ~= nil) then + buddy.gui.screen.buddy_request_menu.destroy() + end + if (buddy.gui.screen.buddy_spawn ~= nil) then + buddy.gui.screen.buddy_spawn_opts.destroy() + end + DisplaySpawnOptions(buddy) + + buddy.print({"oarc-buddy-cancel-request", player.name}) + end +end + +function DisplayBuddySpawnRequestMenu(player, requestingBuddyName) + + if not player then + log("Another gui click happened with no valid player...") + return + end + + local sGui = player.gui.screen.add{name = "buddy_request_menu", + type = "frame", + direction = "vertical", + caption="Buddy Request!"} + sGui.auto_center = true + sGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + sGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + + + -- Warnings and explanations... + AddLabel(sGui, "warning_lbl1", {"oarc-buddy-requesting-from-you", requestingBuddyName}, my_warning_style) + + local teamText = "error!" + if (global.buddySpawnOptions[requestingBuddyName].joinMainTeamRadio) then + teamText = {"oarc-buddy-txt-main-team"} + elseif (global.buddySpawnOptions[requestingBuddyName].joinOwnTeamRadio) then + teamText = {"oarc-buddy-txt-new-teams"} + elseif (global.buddySpawnOptions[requestingBuddyName].joinBuddyTeamRadio) then + teamText = {"oarc-buddy-txt-buddy-team"} + end + + local moatText = " " + if (global.buddySpawnOptions[requestingBuddyName].moatChoice) then + moatText = {"oarc-buddy-txt-moat"} + end + + local distText = "error!" + if (global.buddySpawnOptions[requestingBuddyName].distChoice == "buddy_spawn_request_near") then + distText = {"oarc-buddy-txt-near"} + elseif (global.buddySpawnOptions[requestingBuddyName].distChoice == "buddy_spawn_request_far") then + distText = {"oarc-buddy-txt-far"} + end + + + local requestText = {"", requestingBuddyName, {"oarc-buddy-txt-would-like"}, teamText, {"oarc-buddy-txt-next-to-you"}, moatText, distText} + AddLabel(sGui, "note_lbl1", requestText, my_warning_style) + AddSpacer(sGui) + + + sGui.add{name = "accept_buddy_request", + type = "button", + caption={"oarc-accept"}} + sGui.add{name = "decline_buddy_request", + type = "button", + caption={"oarc-reject"}} +end + +-- Handle the gui click of the buddy request menu +function BuddySpawnRequestMenuClick(event) + if not (event and event.element and event.element.valid) then return end + local player = game.players[event.player_index] + local elemName = event.element.name + local requesterName = nil + local requesterOptions = {} + + if not player then + log("Another gui click happened with no valid player...") + return + end + + if (player.gui.screen.buddy_request_menu == nil) then + return -- Gui event unrelated to this gui. + end + + + + -- Check if it's a button press and lookup the matching buddy info + if ((elemName == "accept_buddy_request") or (elemName == "decline_buddy_request")) then + for name,opts in pairs(global.buddySpawnOptions) do + if (opts.buddyChoice == player.name) then + requesterName = name + requesterOptions = opts + end + end + + if (requesterName == nil) then + player.print("Error! Invalid buddy info...") + log("Error! Invalid buddy info...") + + player.gui.screen.buddy_request_menu.destroy() + DisplaySpawnOptions(player) + end + else + return -- Not a button click + end + + -- Handle player accepted + if (elemName == "accept_buddy_request") then + + if (game.players[requesterName].gui.screen.buddy_wait_menu ~= nil) then + game.players[requesterName].gui.screen.buddy_wait_menu.destroy() + end + if (player.gui.screen.buddy_request_menu ~= nil) then + player.gui.screen.buddy_request_menu.destroy() + end + + -- Create a new spawn point + local newSpawn = {x=0,y=0} + + -- Create a new force for each player if they chose that option + if requesterOptions.joinOwnTeamRadio then + local newForce = CreatePlayerCustomForce(player) + local buddyForce = CreatePlayerCustomForce(game.players[requesterName]) + + -- Create a new force for the combined players if they chose that option + elseif requesterOptions.joinBuddyTeamRadio then + local buddyForce = CreatePlayerCustomForce(game.players[requesterName]) + player.force = buddyForce + end + + -- Find coordinates of a good place to spawn + if (requesterOptions.distChoice == "buddy_spawn_request_far") then + newSpawn = FindUngeneratedCoordinates(global.ocfg.far_dist_start,global.ocfg.far_dist_end, player.surface) + elseif (requesterOptions.distChoice == "buddy_spawn_request_near") then + newSpawn = FindUngeneratedCoordinates(global.ocfg.near_dist_start,global.ocfg.near_dist_end, player.surface) + end + + -- If that fails, find a random map edge in a rand direction. + if ((newSpawn.x == 0) and (newSpawn.x == 0)) then + newSpawn = FindMapEdge(GetRandomVector(), player.surface) + log("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y) + end + + -- Create that spawn in the global vars + local buddySpawn = {x=0,y=0} + if (requesterOptions.moatChoice) then + buddySpawn = {x=newSpawn.x+(global.ocfg.spawn_config.gen_settings.land_area_tiles*2)+10, y=newSpawn.y} + else + buddySpawn = {x=newSpawn.x+(global.ocfg.spawn_config.gen_settings.land_area_tiles*2), y=newSpawn.y} + end + ChangePlayerSpawn(player, newSpawn) + ChangePlayerSpawn(game.players[requesterName], buddySpawn) + + -- Send the player there + QueuePlayerForDelayedSpawn(player.name, newSpawn, requesterOptions.moatChoice, false) + QueuePlayerForDelayedSpawn(requesterName, buddySpawn, requesterOptions.moatChoice, false) + SendBroadcastMsg(requesterName .. " and " .. player.name .. " are joining the game together!") + + -- Unlock spawn control gui tab + SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_GUI_NAME, true) + SetOarcGuiTabEnabled(game.players[requesterName], OARC_SPAWN_CTRL_GUI_NAME, true) + + player.print({"oarc-please-wait"}) + player.print({"", {"oarc-please-wait"}, "!"}) + player.print({"", {"oarc-please-wait"}, "!!"}) + game.players[requesterName].print({"oarc-please-wait"}) + game.players[requesterName].print({"", {"oarc-please-wait"}, "!"}) + game.players[requesterName].print({"", {"oarc-please-wait"}, "!!"}) + + + end + + -- Check if player is cancelling the request. + if (elemName == "decline_buddy_request") then + player.gui.screen.buddy_request_menu.destroy() + DisplaySpawnOptions(player) + + local requesterBuddy = game.players[requesterName] + + if (requesterBuddy.gui.screen.buddy_wait_menu ~= nil) then + requesterBuddy.gui.screen.buddy_wait_menu.destroy() + end + if (requesterBuddy.gui.screen.buddy_spawn ~= nil) then + requesterBuddy.gui.screen.buddy_spawn_opts.destroy() + end + DisplaySpawnOptions(requesterBuddy) + + requesterBuddy.print({"oarc-buddy-declined", player.name}) + end +end + + +function DisplayPleaseWaitForSpawnDialog(player, delay_seconds) + + local pleaseWaitGui = player.gui.screen.add{name = "wait_for_spawn_dialog", + type = "frame", + direction = "vertical", + caption={"oarc-spawn-wait"}} + pleaseWaitGui.auto_center = true + pleaseWaitGui.style.maximal_width = SPAWN_GUI_MAX_WIDTH + pleaseWaitGui.style.maximal_height = SPAWN_GUI_MAX_HEIGHT + + + -- Warnings and explanations... + local wait_warning_text = {"oarc-wait-text", delay_seconds} + + AddLabel(pleaseWaitGui, "warning_lbl1", wait_warning_text, my_warning_style) end \ No newline at end of file diff --git a/lib/tag.lua b/lib/tag.lua new file mode 100644 index 0000000..0778396 --- /dev/null +++ b/lib/tag.lua @@ -0,0 +1,52 @@ +-- tag.lua +-- Apr 2017 +-- Allows adding play tags + +-- 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]"}} + +function CreateTagGuiTab(tab_container, player) + for i,role in ipairs(roles) do + tab_container.add{type="button", caption=role.display_name, name=role.display_name} + end + if (player.admin) then + tab_container.add{type="button", caption="[Admin]", name="admin"} + tab_container.add{type="button", caption="[Moderator]", name="moderator"} + end + tab_container.add{type="button", caption="Clear", name="clear_btn"} +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 == "clear_btn") then + player.tag = "" + return + end + + for i,role in ipairs(roles) do + if (name == role.display_name) then + player.tag = role.display_name + elseif (name == "admin") then + player.tag = "[Admin]" + elseif (name == "moderator") then + player.tag = "[Moderator]" + end + end +end diff --git a/lib/test_lib_file.lua b/lib/test_lib_file.lua new file mode 100644 index 0000000..7748f8a --- /dev/null +++ b/lib/test_lib_file.lua @@ -0,0 +1 @@ +-- test_lib_file.lua \ No newline at end of file diff --git a/locale/LICENSE b/locale/LICENSE deleted file mode 100644 index cb1cae2..0000000 --- a/locale/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -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/de/locale.cfg b/locale/de/locale.cfg new file mode 100644 index 0000000..d373d23 --- /dev/null +++ b/locale/de/locale.cfg @@ -0,0 +1,113 @@ +oarc-spawn-time-warning-msg=Aufgrund der Funktionsweise dieses Szenarios kann es einige Zeit dauern, bis das Land um deinen neuen Spawn herum generiert wurde. Bitte warte 10-20 Sekunden, nachdem du deinen Spawn ausgewählt hast. + +oarc-i-understand=Ich verstehe +oarc-spawn-options=Spawn Optionen + +oarc-click-info-btn-help=Klick oben links auf die Schaltfläche INFO, um mehr über dieses Szenario zu erfahren! Dies ist deine einzige Chance, eine Spawn-Option zu wählen. Wähle sorgfältig... + +oarc-vanilla-spawn=Vanilla Spawn +oarc-default-spawn-behavior=Dies ist das standardmäßige Spawn-Verhalten eines Vanilla-Spiels. Du trittst dem Standardteam in der Mitte der Karte bei. + +oarc-join-main-team-radio=Dem Standardteam beitreten (gemeinsame Forschung) +oarc-create-own-team-radio=Erstelle dein eigenes Team (eigene Forschung) + +oarc-moat-option=Umgib deinen Spawn mit einem Graben. + +oarc-solo-spawn-near=Allein spawnen (nah) +oarc-solo-spawn-far=Allein spawnen (fern) + +oarc-starting-area-vanilla=Du spawnst in deinem eigenen Startbereich (wie im Vanilla-Spiel). +oarc-vanilla-spawns-available=Es steht __1__ Vanilla-Spawn zur Verfügung. + +oarc-starting-area-normal=Du spawnst in einem neuen Gebiet mit voreingestellten Startressourcen. +oarc-join-someone-avail=Jemandem beitreten (__1__ verfügbar) +oarc-join-someone-info=Du wirst in der Basis von jemand anderem spawnen. Dies erfordert, dass mindestens eine Person Zugang zu seiner Basis gewährt hat. Diese Wahl ist endgültig und du wirst später nicht mehr in der Lage sein, deinen eigenen Spawn zu erstellen. + +oarc-no-shared-avail=Es gibt derzeit keine freigegebenen Basen, in denen man spawnen kann. +oarc-join-check-again=Erneut prüfen +oarc-shared-spawn-disabled=Gemeinsame Spawns sind in diesem Modus deaktiviert. + +oarc-buddy-spawn=Buddy Spawn +oarc-buddy-spawn-info=Das Buddy-System erfordert 2 Spieler in diesem Menü gleichzeitig, sie spawnen nebeneinander, jeder mit seinen eigenen Ressourcen. + +oarc-max-players-shared-spawn=Wenn du deinen eigenen Spawn-Punkt erstellst, kann bis zu __1__ anderer Online-Spieler teilnehmen. +oarc-spawn-dist-notes=Der nahe Spawn liegt zwischen __1__-__2__ Chunks von der Mitte der Karte entfernt.\nDer ferne Spawn liegt zwischen __3__-__4__ Chunks von der Mitte der Karte entfernt.\nAlleine Spawnen ist gefährlich! Du wirst kämpfen müssen, um andere Spieler erreichen zu können. + +oarc-player-is-joining-main-force=__1__ tritt dem Standardteam bei! +oarc-player-is-joining-near=__1__ nimmt aus der Ferne am Spiel teil! +oarc-player-is-joining-far=__1__ nimmt aus großer Entfernung am Spiel teil! + +oarc-please-wait=BITTE WARTE, BIS DEIN SPAWN-PUNKT GENERIERT IST! + +oarc-looking-for-buddy=__1__ sucht nach einem Buddy. + +oarc-avail-bases-join=Verfügbare Basen zum Beitreten: +oarc-spawn-spots-remaining=__1__ (__2__ verbleibende Plätze) +oarc-cancel-return-to-previous=Abbrechen (zurück zu den vorherigen Optionen) +oarc-player-requesting-join-you=__1__ bittet darum, deiner Basis beizutreten! +oarc-waiting-for-spawn-owner=Warte auf die Antwort des Spawn-Besitzers... + +oarc-you-will-spawn-once-host=Du wirst spawnen, sobald der Host ja gewählt hat... +oarc-player-cancel-join-request=__1__ hat die Anfrage zum Beitritt zu deinem Spawn abgebrochen. + +oarc-spawn-ctrl=Spawn-Einst. +oarc-spawn-controls=Spawn Einstellungen: +oarc-spawn-allow-joiners=Erlaube anderen, deiner Basis beizutreten. + +oarc-set-respawn-loc=Setze neuen Spawn-Punkt (1 Stunde Abklingzeit) +oarc-set-respawn-loc-cooldown=Set Respawn Cooldown Remaining: __1__ +oarc-set-respawn-note=Das setzt deinen Spawn-Punkt an deine aktuelle Position. + +oarc-select-player-join-queue=Wähle einen Spieler aus der Beitrittswarteschlange aus: + +oarc-accept=Akzeptieren +oarc-reject=Ablehnen + +oarc-no-player-join-reqs=Du hast keine Anfragen von Spielern, deiner Base beizutreten. + +oarc-start-shared-base=Neue Spieler können nun __1__'s Base beitreten! +oarc-stop-shared-base=Neue Spieler können nicht länger der Base von __1__ beitreten! + +oarc-spawn-point-updated=Re-Spawn Punkt aktualisiert! +oarc-selected-player-not-wait=Der ausgewählte Spieler wartet nicht mehr auf den Beitritt! +oarc-reject-joiner=Du lehnst __1__'s Anfrage, deiner Base beizutreten, ab. +oarc-your-request-rejected=Deine Anfrage zum Beitritt wurde abgelehnt. + +oarc-player-joining-base=__1__ trat der Base von __2__ bei! + +oarc-player-left-while-joining=__1__ verließ das Spiel. Was für ein Arsch. + +oarc-buddy-spawn-options=Buddy Spawn Optionen +oarc-buddy-spawn-instructions=Um dies zu nutzen, vergewissere dich, dass du und dein Buddy zur gleichen Zeit in diesem Menü sind. Nur einer von euch muss die Anfrage senden. Wähle deinen Buddy aus der Liste aus (aktualisiere, wenn der Name deines Buddys nicht sichtbar ist) und wähle deine Spawn-Optionen. Klicke auf eine der Schaltflächen für die Anforderung, um die Anforderung zu senden. Der andere Buddy kann dann die Anfrage annehmen (oder ablehnen). Dies ermöglicht es euch beiden, nebeneinander zu spawnen, jeder mit seinem eigenen Spawn-Bereich. Sobald ein Buddy eine Spawn-Anfrage annimmt, ist sie endgültig! + +oarc-buddy-select-info=Wähle zunächst einen Buddy aus der Warteliste aus. Wähle dann die Spawn-Optionen aus und sende deine Anfrage: + +oarc-buddy-refresh=Buddy-Liste aktualisieren +oarc-create-buddy-team=Erstelle dein eigenes Team (geteilte Entwicklung) + +oarc-buddy-spawn-near=Erfrage Buddy-Spawn (nah) +oarc-buddy-spawn-far=Erfrage Buddy-Spawn (fern) + +oarc-invalid-buddy=Du hast keinen gültigen Buddy ausgewählt, versuche es noch mal. +oarc-buddy-not-avail=Der ausgewählte Buddy ist nicht länger vefügbar, bitte versuche es erneut. + +oarc-waiting-for-buddy=Warte auf die Antwort vom Buddy... +oarc-wait-buddy-select-yes=Du wirst spawnen, sobald dein Buddy ja gewählt hat... + +oarc-buddy-cancel-request=__1__ brach die Buddy-Anfrage ab! + +oarc-buddy-requesting-from-you=__1__ fordert einen Buddy Spawn von dir an! + +oarc-buddy-txt-main-team=Das Standardteam +oarc-buddy-txt-new-teams=in getrennten Teams +oarc-buddy-txt-buddy-team=ein Buddy Team +oarc-buddy-txt-moat= umgeben von einem Graben +oarc-buddy-txt-near=in der Nähe der Mitte der Karte! +oarc-buddy-txt-far=weit weg von der Mitte der Karte! +oarc-buddy-txt-would-like= möchte teilnehmen +oarc-buddy-txt-next-to-you= neben dir + +oarc-buddy-declined=__1__ hat deine Buddy-Anfrage abgelehnt! + +oarc-spawn-wait=Bitte warte! +oarc-wait-text=Dein Spawn wird gerade erstellt. Du wirst in __1__ Sekunden dorthin teleportiert!\nHalte dich bereit... diff --git a/locale/en/locale.cfg b/locale/en/locale.cfg new file mode 100644 index 0000000..06b689c --- /dev/null +++ b/locale/en/locale.cfg @@ -0,0 +1,113 @@ +oarc-spawn-time-warning-msg=Due to the way this scenario works, it may take some time for the land around your new spawn area to generate... Please wait for 10-20 seconds when you select your first spawn. + +oarc-i-understand=I Understand +oarc-spawn-options=Spawn Options + +oarc-click-info-btn-help=Click the INFO button in the top left to learn more about this scenario! This is your ONLY chance to choose a spawn option. Choose carefully... + +oarc-vanilla-spawn=Vanilla Spawn +oarc-default-spawn-behavior=This is the default spawn behavior of a vanilla game. You join the default team in the center of the map. + +oarc-join-main-team-radio=Join Main Team (shared research) +oarc-create-own-team-radio=Create Your Own Team (own research tree) + +oarc-moat-option=Surround your spawn with a moat + +oarc-solo-spawn-near=Solo Spawn (Near) +oarc-solo-spawn-far=Solo Spawn (Far) + +oarc-starting-area-vanilla=You are spawned in your own starting area (vanilla style). +oarc-vanilla-spawns-available=There are __1__ vanilla spawns available. + +oarc-starting-area-normal=You are spawned in a new area, with pre-set starting resources. +oarc-join-someone-avail=Join Someone (__1__ available) +oarc-join-someone-info=You are spawned in someone else's base. This requires at least 1 person to have allowed access to their base. This choice is final and you will not be able to create your own spawn later. + +oarc-no-shared-avail=There are currently no shared bases availble to spawn at. +oarc-join-check-again=Check Again +oarc-shared-spawn-disabled=Shared spawns are disabled in this mode. + +oarc-buddy-spawn=Buddy Spawn +oarc-buddy-spawn-info=The buddy system requires 2 players in this menu at the same time, you spawn beside each other, each with your own resources. + +oarc-max-players-shared-spawn=If you create your own spawn point you can allow up to __1__ other online players to join. +oarc-spawn-dist-notes=Near spawn is between __1__-__2__ chunks away from the center of the map.\nFar spawn is between __3__-__4__ chunks away from the center of the map.\nSolo spawns are dangerous! Expect a fight to reach other players. + +oarc-player-is-joining-main-force=__1__ is joining the main force! +oarc-player-is-joining-near=__1__ is joining the game from a distance! +oarc-player-is-joining-far=__1__ is joining the game from a great distance! + +oarc-please-wait=PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED! + +oarc-looking-for-buddy=__1__ is looking for a buddy. + +oarc-avail-bases-join=Available Bases to Join: +oarc-spawn-spots-remaining=__1__ (__2__ spots remaining) +oarc-cancel-return-to-previous=Cancel (Return to Previous Options) +oarc-player-requesting-join-you=__1__ is requesting to join your base! +oarc-waiting-for-spawn-owner=Waiting for spawn owner to respond... + +oarc-you-will-spawn-once-host=You will spawn once the host selects yes... +oarc-player-cancel-join-request=__1__ cancelled their request to join your spawn. + +oarc-spawn-ctrl=Spawn Ctrl +oarc-spawn-controls=Spawn Controls: +oarc-spawn-allow-joiners=Allow others to join your base. + +oarc-set-respawn-loc=Set New Respawn Location (has a cooldown) +oarc-set-respawn-loc-cooldown=Set Respawn Cooldown Remaining: __1__ +oarc-set-respawn-note=This will set your respawn point to your current location. + +oarc-select-player-join-queue=Select a player from the join queue: + +oarc-accept=Accept +oarc-reject=Reject + +oarc-no-player-join-reqs=You have no players requesting to join you at this time. + +oarc-start-shared-base=New players can now join __1__'s base! +oarc-stop-shared-base=New players can no longer join __1__'s base! + +oarc-spawn-point-updated=Re-spawn point updated! +oarc-selected-player-not-wait=Selected player is no longer waiting to join! +oarc-reject-joiner=You rejected __1__'s request to join your base. +oarc-your-request-rejected=Your request to join was rejected. + +oarc-player-joining-base=__1__ is joining __2__'s base! + +oarc-player-left-while-joining=__1__ left the game. What an ass. + +oarc-buddy-spawn-options=Buddy Spawn Options +oarc-buddy-spawn-instructions=To use this, make sure you and your buddy are in this menu at the same time. Only one of you must send the request. Select your buddy from the list (refresh if your buddy's name is not visible) and select your spawn options. Click one of the request buttons to send the request. The other buddy can then accept (or deny) the request. This will allow you both to spawn next to each other, each with your own spawn area. Once a buddy accepts a spawn request, it is final! + +oarc-buddy-select-info=First, select a buddy from the waiting list. Then choose the spawn options and send your request: + +oarc-buddy-refresh=Refresh Buddy List +oarc-create-buddy-team=Create Your Own Buddy Team (buddy and you share research) + +oarc-buddy-spawn-near=Request Buddy Spawn (Near) +oarc-buddy-spawn-far=Request Buddy Spawn (Far) + +oarc-invalid-buddy=You have not selected a valid buddy! Please try again. +oarc-buddy-not-avail=Selected buddy is no longer available! Please try again. + +oarc-waiting-for-buddy=Waiting for buddy to respond... +oarc-wait-buddy-select-yes=You will spawn once your buddy selects yes... + +oarc-buddy-cancel-request=__1__ cancelled their buddy request! + +oarc-buddy-requesting-from-you=__1__ is requesting a buddy spawn from you! + +oarc-buddy-txt-main-team=the main team +oarc-buddy-txt-new-teams=on separate teams +oarc-buddy-txt-buddy-team=a buddy team +oarc-buddy-txt-moat= surrounded by a moat +oarc-buddy-txt-near=near to the center of the map! +oarc-buddy-txt-far=far from the center of the map! +oarc-buddy-txt-would-like= would like to join +oarc-buddy-txt-next-to-you= next to you + +oarc-buddy-declined=__1__ declined your buddy request! + +oarc-spawn-wait=Please wait! +oarc-wait-text=Your spawn is being created now.\nYou will be teleported there in __1__ seconds!\nPlease standby... \ No newline at end of file diff --git a/locale/fr/locale.cfg b/locale/fr/locale.cfg new file mode 100644 index 0000000..a044c87 --- /dev/null +++ b/locale/fr/locale.cfg @@ -0,0 +1,113 @@ +oarc-spawn-time-warning-msg=En raison de la manière de fonctionnement de ce scénario, la génération de votre base peut prendre un certain temps ... Veuillez patienter 10 à 20 secondes lorsque vous apparaissez. + +oarc-i-understand=Je comprends +oarc-spawn-options=Options d'apparition + +oarc-click-info-btn-help=Cliquez sur le bouton INFO en haut à gauche pour en savoir plus sur ce scénario! Ceci est votre SEULE chance de choisir une option de spawn. Choisissez soigneusement... + +oarc-vanilla-spawn=Spawn vanillia +oarc-default-spawn-behavior=C’est le comportement par défaut du spawn d’un jeu vanillia. Vous rejoignez l'équipe par défaut au centre de la carte. + +oarc-join-main-team-radio=Rejoindre l'équipe principale (recherche partagée) +oarc-create-own-team-radio=Créez votre propre équipe (propre arbre de recherche) + +oarc-moat-option=Entourez votre base d'eau (fossé) + +oarc-solo-spawn-near=Spawn solo (proche) +oarc-solo-spawn-far=Spawn solo (Loin) + +oarc-starting-area-vanilla=Vous êtes apparu dans votre propre base (style vanilla). +oarc-vanilla-spawns-available=Il y a __1__ une base vanilla disponible. + +oarc-starting-area-normal=Vous êtes apparu dans une nouvelle base, avec des ressources de départ prédéfinies. +oarc-join-someone-avail=Rejoindre quelqu'un (__1__ disponible) +oarc-join-someone-info=Vous rejoindrez la base de quelqu'un d'autre. Cela nécessite que au moins 1 personne autorise l'accès à leur base. Ce choix est définitif et vous ne pourrez pas créer votre propre zone plus tard. + +oarc-no-shared-avail=Il n’existe actuellement aucune base partagée disponible. +oarc-join-check-again=Vérifier à nouveau +oarc-shared-spawn-disabled=Les spawns partagés sont désactivés dans ce mode. + +oarc-buddy-spawn=Spawn avec un ami +oarc-buddy-spawn-info=Se système d'amis nécessite 2 joueurs dans ce menu en même temps, vous apparaissez côte à côte, chacun avec vos propres ressources. + +oarc-max-players-shared-spawn=Si vous créez votre propre point d'apparition, vous pouvez autoriser jusqu'à __1__ autres joueurs en ligne à rejoindre. +oarc-spawn-dist-notes=Le point d’apparition (proche) se situe entre __1__-__2__ morceaux du centre de la carte. \nLe point d’apparition (loin) se situe entre __3__-__4__ morceaux du centre de la carte. \nLes apparitions solo sont dangereuses! Attendez-vous à un combat pour atteindre d'autres joueurs. + +oarc-player-is-joining-main-force=__1__ rejoint l'équipe principale'! +oarc-player-is-joining-near=__1__ rejoint le jeu à distance! +oarc-player-is-joining-far=__1__ rejoint le jeu avec une grande distance! + +oarc-please-wait=VEUILLEZ ATTENDRE QUE VOTRE POINT D'APPARITION SOIT GÉNÉRÉ! + +oarc-looking-for-buddy=__1__ est à la recherche d'un copain. + +oarc-avail-bases-join=Bases disponibles à rejoindre: +oarc-spawn-spots-remaining=__1__ (__2__ places restantes) +oarc-cancel-return-to-previous=Annuler (retourner aux options précédentes) +oarc-player-requesting-join-you=__1__ demande à rejoindre votre base! +oarc-waiting-for-spawn-owner=Attendez que le propriétaire de la base réponde ... + +oarc-you-will-spawn-once-host=Vous apparaîtrez une fois que l'hôte aura choisi oui ... +oarc-player-cancel-join-request=__1__ a annulé sa demande pour rejoindre votre base. + +oarc-spawn-ctrl=Spawn Ctrl +oarc-spawn-controls=Contrôle d'apparition': +oarc-spawn-allow-joiners=Autoriser les autres à rejoindre votre base. + +oarc-set-respawn-loc=Définir le nouvel emplacement de réapparition (il y a une heure d'attente pour réutiliser ce boutton) +oarc-set-respawn-loc-cooldown=Définir le temps d'attente pour réapparaître restant: __1__ +oarc-set-respawn-note=Cela va définir votre point de réapparition à votre position actuelle. + +oarc-select-player-join-queue=Sélectionnez un joueur dans la file d'attente: + +oarc-accept=Accepter +oarc-reject=Rejeter + +oarc-no-player-join-reqs=Vous n'avez aucun joueur demandant à vous rejoindre pour le moment. + +oarc-start-shared-base=Les nouveaux joueurs peuvent maintenant rejoindre la base de __1__! +oarc-stop-shared-base=Les nouveaux joueurs ne peuvent plus rejoindre la base de __1__! + +oarc-spawn-point-updated=Point de réapparition mis à jour! +oarc-selected-player-not-wait=Le joueur sélectionné n'attend plus de rejoindre! +oarc-reject-joiner=Vous avez rejeté la demande de __1__ pour rejoindre votre base. +oarc-your-request-rejected=Votre demande d'adhésion a été rejetée. + +oarc-player-joining-base=__1__ à rejoint la base de __2__! + +oarc-player-left-while-joining=__1__ a quitté le match. Quel âne. + +oarc-buddy-spawn-options=Options d'apparition du menu (Spawn avec un ami) +oarc-buddy-spawn-instructions=Pour utiliser ceci, assurez-vous que votre ami et vous êtes dans ce menu en même temps. Un seul d'entre vous doit envoyer la demande. Sélectionnez votre ami dans la liste (actualisez-la si son nom n'est pas visible) et sélectionnez vos options d'apparition. Cliquez sur l'un des boutons de requête pour envoyer la requête. L'autre copain peut alors accepter (ou refuser) la demande. Cela vous permettra à tous les deux d'apparaître côte à côte, chacun avec votre propre base. Une fois que votre copain aura accepté une demande de spawn, c'est définitif! + +oarc-buddy-select-info=D'abord, sélectionnez un ami dans la liste d'attente. Puis choisissez les options de spawn et envoyez votre demande: + +oarc-buddy-refresh=Actualiser la liste d'amis +oarc-create-buddy-team=Créez votre propre équipe d'amis (vous et votre ami partagez la recherche) + +oarc-buddy-spawn-near=Demander la création de la base (proche) +oarc-buddy-spawn-far=Demander la création de la base (loin) + +oarc-invalid-buddy=Vous n'avez pas sélectionné de partenaire valide! Veuillez réessayer. +oarc-buddy-not-avail=Le copain sélectionné n'est plus disponible! Veuillez réessayer. + +oarc-waiting-for-buddy=Attendez que votre ami réponde ... +oarc-wait-buddy-select-yes=Vous apparaîtrez une fois que votre ami aura choisi oui ... + +oarc-buddy-cancel-request=__1__ a annulé sa demande! + +oarc-buddy-requesting-from-you=__1__ vous demande a appraître avec lui! + +oarc-buddy-txt-main-team=l'équipe principale +oarc-buddy-txt-new-teams=sur des équipes séparées +oarc-buddy-txt-buddy-team=une équipe de amis +oarc-buddy-txt-moat=entouré d'eau (fossé) +oarc-buddy-txt-near=proche du centre de la carte! +oarc-buddy-txt-far=loin du centre de la carte! +oarc-buddy-txt-would-like=aimerait rejoindre +oarc-buddy-txt-next-to-you=à côté de toi + +oarc-buddy-declined=__1__ a refusé votre demande d'ami! + +oarc-spawn-wait=Patienter s'il vous plaît! +oarc-wait-text=Votre spawn est en cours de création. \nVous serez téléporté dans __1__ secondes! \nVeuillez patienter ... diff --git a/locale/game_opts.lua b/locale/game_opts.lua deleted file mode 100644 index 05805ff..0000000 --- a/locale/game_opts.lua +++ /dev/null @@ -1,145 +0,0 @@ --- game_opts.lua --- Jan 2018 --- Display current game options, maybe have some admin controls here - --- Main Configuration File -require("config") -require("locale/oarc_utils") - -function CreateGameOptionsGui(event) - local player = game.players[event.player_index] - if player.gui.top.game_options == nil then - player.gui.top.add{name="game_options", type="button", caption="Info"} - end -end - -local function ExpandGameOptionsGui(player) - local frame = player.gui.left["game_options_panel"] - if (frame) then - frame.destroy() - else - local frame = player.gui.left.add{type="frame", - name="game_options_panel", - caption="Server Info:", - direction="vertical"} - - -- General Server Info: - AddLabel(frame, "info_1", global.welcome_msg, my_longer_label_style) - AddLabel(frame, "info_2", SERVER_MSG, my_longer_label_style) - AddSpacer(frame, "info_spacer1") - - -- Enemy Settings: - local enemy_expansion_txt = "disabled" - if game.map_settings.enemy_expansion.enabled then enemy_expansion_txt = "enabled" end - - local enemy_text="Server Run Time: " .. formattime_hours_mins(game.tick) .. "\n" .. - "Current Evolution: " .. string.format("%.4f", game.forces["enemy"].evolution_factor) .. "\n" .. - "Enemy evolution time factor: " .. game.map_settings.enemy_evolution.time_factor .. "\n" .. - "Enemy evolution pollution factor: " .. game.map_settings.enemy_evolution.pollution_factor .. "\n" .. - "Enemy evolution destroy factor: " .. game.map_settings.enemy_evolution.destroy_factor .. "\n" .. - "Enemy expansion is " .. enemy_expansion_txt - - AddLabel(frame, "enemy_info", enemy_text, my_longer_label_style) - AddSpacer(frame, "enemy_info_spacer1") - - -- Game Mode: - AddLabel(frame, "core_mod_en", "Core game mode (separate spawns) is enabled.", my_longer_label_style) - if (not ENABLE_SEPARATE_SPAWNS) then - frame.core_mod_en.caption="Core game mode (separate spawns) is DISABLED." - frame.core_mod_en.style.font_color=my_color_red - end - - -- Soft Mods: - local soft_mods_string = "Oarc Core" - if (not ENABLE_SEPARATE_SPAWNS) then - soft_mods_string = "Oarc Core [DISABLED!]" - end - if (ENABLE_RSO) then - soft_mods_string = soft_mods_string .. ", RSO" - end - if (ENABLE_UNDECORATOR) then - soft_mods_string = soft_mods_string .. ", Undecorator" - end - if (ENABLE_TAGS) then - soft_mods_string = soft_mods_string .. ", Tags" - end - if (ENABLE_LONGREACH) then - soft_mods_string = soft_mods_string .. ", Long Reach" - end - if (ENABLE_AUTOFILL) then - soft_mods_string = soft_mods_string .. ", Auto Fill" - end - if (ENABLE_PLAYER_LIST) then - soft_mods_string = soft_mods_string .. ", Player List" - end - - local game_info_str = "Soft Mods Enabled: " .. soft_mods_string - - -- Spawn options: - if (ENABLE_SEPARATE_TEAMS) then - game_info_str = game_info_str.."\n".."You are allowed to spawn on your own team (have your own research tree). All teams are friendly!" - end - if (ENABLE_BUDDY_SPAWN) then - game_info_str = game_info_str.."\n".."You can chose to spawn alongside a buddy if you spawn together at the same time." - end - if (ENABLE_SHARED_SPAWNS) then - game_info_str = game_info_str.."\n".."Spawn hosts may choose to share their spawn and allow other players to join them." - end - if (ENABLE_SEPARATE_TEAMS and ENABLE_SHARED_TEAM_VISION) then - game_info_str = game_info_str.."\n".."Everyone (all teams) have shared vision." - end - if (FRONTIER_ROCKET_SILO_MODE) then - game_info_str = game_info_str.."\n".."Silos are NOT craftable. There is at least one already located on the map." - end - if (ENABLE_REGROWTH) then - game_info_str = game_info_str.."\n".."Old parts of the map will slowly be deleted over time (chunks without any player buildings)." - end - if (ENABLE_POWER_ARMOR_QUICK_START) then - game_info_str = game_info_str.."\n".."Power armor quick start enabled." - end - - AddLabel(frame, "game_info_label", game_info_str, my_longer_label_style) - - if (ENABLE_ABANDONED_BASE_REMOVAL) then - AddLabel(frame, "leave_warning_msg", "If you leave within " .. MIN_ONLINE_TIME_IN_MINUTES .. " minutes of joining, your base and character will be deleted.", my_longer_label_style) - frame.leave_warning_msg.style.font_color=my_color_red - end - - -- Ending Spacer - AddSpacer(frame, "end_spacer") - - -- ADMIN CONTROLS - if (player.admin) then - player_list = {} - for _,player in pairs(game.connected_players) do - table.insert(player_list, player.name) - end - frame.add{name = "ban_players_dropdown", - type = "drop-down", - items = player_list} - frame.add{name="ban_player", type="button", caption="Ban Player"} - end - end -end - -function GameOptionsGuiClick(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 == "game_options") then - ExpandGameOptionsGui(player) - end - - if (name == "ban_player") then - banIndex = event.element.parent.ban_players_dropdown.selected_index - - if (banIndex ~= 0) then - banPlayer = event.element.parent.ban_players_dropdown.get_item(banIndex) - if (game.players[banPlayer]) then - game.ban_player(banPlayer, "Banned for griefing - Banned from admin panel.") - DebugPrint("Banning " .. banPlayer) - end - end - end -end diff --git a/locale/oarc_utils.lua b/locale/oarc_utils.lua deleted file mode 100644 index 3907df1..0000000 --- a/locale/oarc_utils.lua +++ /dev/null @@ -1,1319 +0,0 @@ --- oarc_utils.lua --- Nov 2016 --- --- My general purpose utility functions for factorio --- Also contains some constants and gui styles - - --------------------------------------------------------------------------------- --- Useful constants --------------------------------------------------------------------------------- -CHUNK_SIZE = 32 -MAX_FORCES = 64 -TICKS_PER_SECOND = 60 -TICKS_PER_MINUTE = TICKS_PER_SECOND * 60 -TICKS_PER_HOUR = TICKS_PER_MINUTE * 60 --------------------------------------------------------------------------------- - -GAME_SURFACE_NAME="Game" - --------------------------------------------------------------------------------- --- GUI Label Styles --------------------------------------------------------------------------------- -my_fixed_width_style = { - minimal_width = 450, - maximal_width = 450 -} -my_label_style = { - -- minimal_width = 450, - -- maximal_width = 50, - single_line = false, - font_color = {r=1,g=1,b=1}, - top_padding = 0, - bottom_padding = 0 -} -my_note_style = { - -- minimal_width = 450, - single_line = false, - font = "default-small-semibold", - font_color = {r=1,g=0.5,b=0.5}, - top_padding = 0, - bottom_padding = 0 -} -my_warning_style = { - -- minimal_width = 450, - -- maximal_width = 450, - single_line = false, - font_color = {r=1,g=0.1,b=0.1}, - top_padding = 0, - bottom_padding = 0 -} -my_spacer_style = { - minimal_height = 10, - font_color = {r=0,g=0,b=0}, - top_padding = 0, - bottom_padding = 0 -} -my_small_button_style = { - font = "default-small-semibold" -} -my_player_list_fixed_width_style = { - minimal_width = 200, - maximal_width = 400, - maximal_height = 200 -} -my_player_list_admin_style = { - font = "default-semibold", - font_color = {r=1,g=0.5,b=0.5}, - minimal_width = 200, - top_padding = 0, - bottom_padding = 0, - single_line = false, -} -my_player_list_style = { - font = "default-semibold", - minimal_width = 200, - top_padding = 0, - bottom_padding = 0, - single_line = false, -} -my_player_list_offline_style = { - -- font = "default-semibold", - font_color = {r=0.5,g=0.5,b=0.5}, - minimal_width = 200, - top_padding = 0, - bottom_padding = 0, - single_line = false, -} -my_player_list_style_spacer = { - minimal_height = 20, -} -my_color_red = {r=1,g=0.1,b=0.1} - -my_longer_label_style = { - maximal_width = 600, - single_line = false, - font_color = {r=1,g=1,b=1}, - top_padding = 0, - bottom_padding = 0 -} -my_longer_warning_style = { - maximal_width = 600, - single_line = false, - font_color = {r=1,g=0.1,b=0.1}, - top_padding = 0, - bottom_padding = 0 -} - --------------------------------------------------------------------------------- --- General Helper Functions --------------------------------------------------------------------------------- - --- Print debug only to me while testing. -function DebugPrint(msg) - if ((game.players["Oarc"] ~= nil) and (global.oarcDebugEnabled)) then - game.players["Oarc"].print("DEBUG: " .. msg) - end -end - --- Prints flying text. --- Color is optional -function FlyingText(msg, pos, color, surface) - if color == nil then - surface.create_entity({ name = "flying-text", position = pos, text = msg }) - else - surface.create_entity({ name = "flying-text", position = pos, text = msg, color = color }) - end -end - --- Broadcast messages to all connected players -function SendBroadcastMsg(msg) - for name,player in pairs(game.connected_players) do - player.print(msg) - end -end - --- Send a message to a player, safely checks if they exist and are online. -function SendMsg(playerName, msg) - if ((game.players[playerName] ~= nil) and (game.players[playerName].connected)) then - game.players[playerName].print(msg) - end -end - --- Special case for ensuring that if I create the server, my messages are --- used instead of the generic insert msg warning. -function SetServerWelcomeMessages() - if (SERVER_OWNER_IS_OARC) then - global.welcome_msg = WELCOME_MSG_OARC - global.welcome_msg_title = WELCOME_MSG_TITLE_OARC - else - global.welcome_msg = WELCOME_MSG - global.welcome_msg_title = WELCOME_MSG_TITLE - end -end - --- Useful for displaying game time in mins:secs format -function formattime(ticks) - local seconds = ticks / 60 - local minutes = math.floor((seconds)/60) - local seconds = math.floor(seconds - 60*minutes) - return string.format("%dm:%02ds", minutes, seconds) -end - --- Useful for displaying game time in mins:secs format -function formattime_hours_mins(ticks) - local seconds = ticks / 60 - local minutes = math.floor((seconds)/60) - local hours = math.floor((minutes)/60) - local minutes = math.floor(minutes - 60*hours) - return string.format("%dh:%02dm", hours, minutes) -end - --- Simple function to get total number of items in table -function TableLength(T) - local count = 0 - for _ in pairs(T) do count = count + 1 end - return count -end - --- Simple function to get distance between two positions. -function getDistance(posA, posB) - -- Get the length for each of the components x and y - local xDist = posB.x - posA.x - local yDist = posB.y - posA.y - - return math.sqrt( (xDist ^ 2) + (yDist ^ 2) ) -end - --- Chart area for a force -function ChartArea(force, position, chunkDist, surface) - force.chart(surface, - {{position.x-(CHUNK_SIZE*chunkDist), - position.y-(CHUNK_SIZE*chunkDist)}, - {position.x+(CHUNK_SIZE*chunkDist), - position.y+(CHUNK_SIZE*chunkDist)}}) -end - --- Give player these default items. -function GivePlayerItems(player) - for _,item in pairs(PLAYER_RESPAWN_START_ITEMS) do - player.insert(item) - end -end - --- Starter only items -function GivePlayerStarterItems(player) - for _,item in pairs(PLAYER_SPAWN_START_ITEMS) do - player.insert(item) - end - - if ENABLE_POWER_ARMOR_QUICK_START then - GiveQuickStartPowerArmor(player) - end -end - --- Cheater's quick start -function GiveQuickStartPowerArmor(player) - player.insert{name="power-armor", count = 1} - - if player and player.get_inventory(5) ~= nil and player.get_inventory(5)[1] ~= nil then - local p_armor = player.get_inventory(5)[1].grid --defines.inventory.player_armor = 5? - if p_armor ~= nil then - p_armor.put({name = "fusion-reactor-equipment"}) - p_armor.put({name = "exoskeleton-equipment"}) - p_armor.put({name = "battery-mk2-equipment"}) - p_armor.put({name = "battery-mk2-equipment"}) - p_armor.put({name = "personal-roboport-mk2-equipment"}) - p_armor.put({name = "personal-roboport-mk2-equipment"}) - p_armor.put({name = "personal-roboport-mk2-equipment"}) - p_armor.put({name = "battery-mk2-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - p_armor.put({name = "solar-panel-equipment"}) - end - player.insert{name="construction-robot", count = 100} - player.insert{name="belt-immunity-equipment", count = 1} - end -end - --- Create area given point and radius-distance -function GetAreaFromPointAndDistance(point, dist) - local area = {left_top= - {x=point.x-dist, - y=point.y-dist}, - right_bottom= - {x=point.x+dist, - y=point.y+dist}} - return area -end - --- Check if given position is in area bounding box -function CheckIfInArea(point, area) - if ((point.x >= area.left_top.x) and (point.x < area.right_bottom.x)) then - if ((point.y >= area.left_top.y) and (point.y < area.right_bottom.y)) then - return true - end - end - return false -end - --- Set all forces to ceasefire -function SetCeaseFireBetweenAllForces() - for name,team in pairs(game.forces) do - if name ~= "neutral" and name ~= "enemy" then - for x,y in pairs(game.forces) do - if x ~= "neutral" and x ~= "enemy" then - team.set_cease_fire(x,true) - end - end - end - end -end - --- Set all forces to friendly -function SetFriendlyBetweenAllForces() - for name,team in pairs(game.forces) do - if name ~= "neutral" and name ~= "enemy" then - for x,y in pairs(game.forces) do - if x ~= "neutral" and x ~= "enemy" then - team.set_friend(x,true) - end - end - end - end -end - --- For each other player force, share a chat msg. -function ShareChatBetweenForces(player, msg) - for _,force in pairs(game.forces) do - if (force ~= nil) then - if ((force.name ~= enemy) and - (force.name ~= neutral) and - (force.name ~= player) and - (force ~= player.force)) then - force.print(player.name..": "..msg) - end - end - end -end - --- Merges force2 INTO force1 but keeps all research between both forces. -function MergeForcesKeepResearch(force1, force2) - for techName,luaTech in pairs(force2.technologies) do - if (luaTech.researched) then - force1.technologies[techName].researched = true - force1.technologies[techName].level = luaTech.level - end - end - game.merge_forces(force2, force1) -end - --- Undecorator -function RemoveDecorationsArea(surface, area) - surface.destroy_decoratives(area) -end - --- Remove fish -function RemoveFish(surface, area) - for _, entity in pairs(surface.find_entities_filtered{area = area, type="fish"}) do - entity.destroy() - end -end - --- Apply a style option to a GUI -function ApplyStyle (guiIn, styleIn) - for k,v in pairs(styleIn) do - guiIn.style[k]=v - end -end - --- Shorter way to add a label with a style -function AddLabel(guiIn, name, message, style) - guiIn.add{name = name, type = "label", - caption=message} - ApplyStyle(guiIn[name], style) -end - --- Shorter way to add a spacer -function AddSpacer(guiIn, name) - guiIn.add{name = name, type = "label", - caption=" "} - ApplyStyle(guiIn[name], my_spacer_style) -end - --- Shorter way to add a spacer with a decorative line -function AddSpacerLine(guiIn, name) - guiIn.add{name = name, type = "label", - caption="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"} - ApplyStyle(guiIn[name], my_spacer_style) -end - --- Get a random 1 or -1 -function RandomNegPos() - if (math.random(0,1) == 1) then - return 1 - else - return -1 - end -end - --- Create a random direction vector to look in -function GetRandomVector() - local randVec = {x=0,y=0} - while ((randVec.x == 0) and (randVec.y == 0)) do - randVec.x = math.random(-3,3) - randVec.y = math.random(-3,3) - end - DebugPrint("direction: x=" .. randVec.x .. ", y=" .. randVec.y) - return randVec -end - --- Check for ungenerated chunks around a specific chunk --- +/- chunkDist in x and y directions -function IsChunkAreaUngenerated(chunkPos, chunkDist, surface) - for x=-chunkDist, chunkDist do - for y=-chunkDist, chunkDist do - local checkPos = {x=chunkPos.x+x, - y=chunkPos.y+y} - if (surface.is_chunk_generated(checkPos)) then - return false - end - end - end - return true -end - --- Clear out enemies around an area with a certain distance -function ClearNearbyEnemies(pos, safeDist, surface) - local safeArea = {left_top= - {x=pos.x-safeDist, - y=pos.y-safeDist}, - right_bottom= - {x=pos.x+safeDist, - y=pos.y+safeDist}} - - for _, entity in pairs(surface.find_entities_filtered{area = safeArea, force = "enemy"}) do - entity.destroy() - end -end - --- Function to find coordinates of ungenerated map area in a given direction --- starting from the center of the map -function FindMapEdge(directionVec, surface) - local position = {x=0,y=0} - local chunkPos = {x=0,y=0} - - -- Keep checking chunks in the direction of the vector - while(true) do - - -- Set some absolute limits. - if ((math.abs(chunkPos.x) > 1000) or (math.abs(chunkPos.y) > 1000)) then - break - - -- If chunk is already generated, keep looking - elseif (surface.is_chunk_generated(chunkPos)) then - chunkPos.x = chunkPos.x + directionVec.x - chunkPos.y = chunkPos.y + directionVec.y - - -- Found a possible ungenerated area - else - - chunkPos.x = chunkPos.x + directionVec.x - chunkPos.y = chunkPos.y + directionVec.y - - -- Check there are no generated chunks in a 10x10 area. - if IsChunkAreaUngenerated(chunkPos, 10, surface) then - position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) - position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) - break - end - end - end - - -- DebugPrint("spawn: x=" .. position.x .. ", y=" .. position.y) - return position -end - --- Find random coordinates within a given distance away --- maxTries is the recursion limit basically. -function FindUngeneratedCoordinates(minDistChunks, maxDistChunks, surface) - local position = {x=0,y=0} - local chunkPos = {x=0,y=0} - - local maxTries = 100 - local tryCounter = 0 - - local minDistSqr = minDistChunks^2 - local maxDistSqr = maxDistChunks^2 - - while(true) do - chunkPos.x = math.random(0,maxDistChunks) * RandomNegPos() - chunkPos.y = math.random(0,maxDistChunks) * RandomNegPos() - - local distSqrd = chunkPos.x^2 + chunkPos.y^2 - - -- Enforce a max number of tries - tryCounter = tryCounter + 1 - if (tryCounter > maxTries) then - DebugPrint("FindUngeneratedCoordinates - Max Tries Hit!") - break - - -- Check that the distance is within the min,max specified - elseif ((distSqrd < minDistSqr) or (distSqrd > maxDistSqr)) then - -- Keep searching! - - -- Check there are no generated chunks in a 10x10 area. - elseif IsChunkAreaUngenerated(chunkPos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS, surface) then - position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) - position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) - break -- SUCCESS - end - end - - DebugPrint("spawn: x=" .. position.x .. ", y=" .. position.y) - return position -end - --- General purpose function for removing a particular recipe -function RemoveRecipe(force, recipeName) - local recipes = force.recipes - if recipes[recipeName] then - recipes[recipeName].enabled = false - end -end - --- General purpose function for adding a particular recipe -function AddRecipe(force, recipeName) - local recipes = force.recipes - if recipes[recipeName] then - recipes[recipeName].enabled = true - end -end - --- Get an area given a position and distance. --- Square length = 2x distance -function GetAreaAroundPos(pos, dist) - - return {left_top= - {x=pos.x-dist, - y=pos.y-dist}, - right_bottom= - {x=pos.x+dist, - y=pos.y+dist}} -end - --- Removes the entity type from the area given -function RemoveInArea(surface, area, type) - for key, entity in pairs(surface.find_entities_filtered({area=area, type= type})) do - if entity.valid and entity and entity.position then - entity.destroy() - end - end -end - --- Removes the entity type from the area given --- Only if it is within given distance from given position. -function RemoveInCircle(surface, area, type, pos, dist) - for key, entity in pairs(surface.find_entities_filtered({area=area, type= type})) do - if entity.valid and entity and entity.position then - if ((pos.x - entity.position.x)^2 + (pos.y - entity.position.y)^2 < dist^2) then - entity.destroy() - end - end - end -end - --- Convenient way to remove aliens, just provide an area -function RemoveAliensInArea(surface, area) - for _, entity in pairs(surface.find_entities_filtered{area = area, force = "enemy"}) do - entity.destroy() - end -end - --- Make an area safer --- Reduction factor divides the enemy spawns by that number. 2 = half, 3 = third, etc... --- Also removes all big and huge worms in that area -function ReduceAliensInArea(surface, area, reductionFactor) - for _, entity in pairs(surface.find_entities_filtered{area = area, force = "enemy"}) do - if (math.random(0,reductionFactor) > 0) then - entity.destroy() - end - end - - -- Remove all big and huge worms - for _, entity in pairs(surface.find_entities_filtered{area = area, name = "medium-worm-turret"}) do - entity.destroy() - end - for _, entity in pairs(surface.find_entities_filtered{area = area, name = "big-worm-turret"}) do - entity.destroy() - end -end - - --- Adjust alien params -function ConfigureAlienStartingParams() - - -- These are the default values for reference: - -- "time_factor": 0.000004, - -- "destroy_factor": 0.002, - -- "pollution_factor": 0.000015 - - if ENEMY_TIME_FACTOR_DISABLE then - game.map_settings.enemy_evolution.time_factor = 0 - else - game.map_settings.enemy_evolution.time_factor=game.map_settings.enemy_evolution.time_factor / ENEMY_TIME_FACTOR_DIVISOR - end - - if ENEMY_POLLUTION_FACTOR_DISABLE then - game.map_settings.enemy_evolution.pollution_factor = 0 - else - game.map_settings.enemy_evolution.pollution_factor = game.map_settings.enemy_evolution.pollution_factor / ENEMY_POLLUTION_FACTOR_DIVISOR - end - - if ENEMY_DESTROY_FACTOR_DISABLE then - game.map_settings.enemy_evolution.destroy_factor = 0 - else - game.map_settings.enemy_evolution.destroy_factor = game.map_settings.enemy_evolution.destroy_factor / ENEMY_DESTROY_FACTOR_DIVISOR - end - - game.map_settings.enemy_expansion.enabled = ENEMY_EXPANSION - - if (OARC_DIFFICULTY_CUSTOM) then - - game.map_settings.pollution.diffusion_ratio = 0.08 - game.map_settings.pollution.ageing = 1 - - game.map_settings.enemy_expansion.max_expansion_distance = 20 - - game.map_settings.enemy_expansion.settler_group_min_size = 2 - game.map_settings.enemy_expansion.settler_group_max_size = 10 - - game.map_settings.enemy_expansion.min_expansion_cooldown = TICKS_PER_MINUTE*15 - game.map_settings.enemy_expansion.max_expansion_cooldown = TICKS_PER_MINUTE*60 - - game.map_settings.unit_group.min_group_gathering_time = TICKS_PER_MINUTE - game.map_settings.unit_group.max_group_gathering_time = 4 * TICKS_PER_MINUTE - game.map_settings.unit_group.max_wait_time_for_late_members = 1 * TICKS_PER_MINUTE - game.map_settings.unit_group.max_unit_group_size = 15 - - -- game.map_settings.pollution.enabled=true, - -- -- these are values for 60 ticks (1 simulated second) - -- -- - -- -- amount that is diffused to neighboring chunk - -- -- (possibly repeated for other directions as well) - -- game.map_settings.pollution.diffusion_ratio=0.02, - -- -- this much PUs must be on the chunk to start diffusing - -- game.map_settings.pollution.min_to_diffuse=15, - -- -- constant modifier a percentage of 1 - the pollution eaten by a chunks tiles - -- game.map_settings.pollution.ageing=1, - -- -- anything bigger than this is visualised as this value - -- game.map_settings.pollution.expected_max_per_chunk=7000, - -- -- anything lower than this (but > 0) is visualised as this value - -- game.map_settings.pollution.min_to_show_per_chunk=700, - -- game.map_settings.pollution.min_pollution_to_damage_trees = 3500, - -- game.map_settings.pollution.pollution_with_max_forest_damage = 10000, - -- game.map_settings.pollution.pollution_per_tree_damage = 2000, - -- game.map_settings.pollution.pollution_restored_per_tree_damage = 500, - -- game.map_settings.pollution.max_pollution_to_restore_trees = 1000 - - - -- game.map_settings.enemy_expansion.enabled = true, - -- -- Distance in chunks from the furthest base around. - -- -- This prevents expansions from reaching too far into the - -- -- player's territory - -- game.map_settings.enemy_expansion.max_expansion_distance = 7, - - -- game.map_settings.enemy_expansion.friendly_base_influence_radius = 2, - -- game.map_settings.enemy_expansion.enemy_building_influence_radius = 2, - - -- -- A candidate chunk's score is given as follows: - -- -- player = 0 - -- -- for neighbour in all chunks within enemy_building_influence_radius from chunk: - -- -- player += number of player buildings on neighbour - -- -- * building_coefficient - -- -- * neighbouring_chunk_coefficient^distance(chunk, neighbour) - -- -- - -- -- base = 0 - -- -- for neighbour in all chunk within friendly_base_influence_radius from chunk: - -- -- base += num of enemy bases on neighbour - -- -- * other_base_coefficient - -- -- * neighbouring_base_chunk_coefficient^distance(chunk, neighbour) - -- -- - -- -- score(chunk) = 1 / (1 + player + base) - -- -- - -- -- The iteration is over a square region centered around the chunk for which the calculation is done, - -- -- and includes the central chunk as well. distance is the Manhattan distance, and ^ signifies exponentiation. - -- game.map_settings.enemy_expansion.building_coefficient = 0.1, - -- game.map_settings.enemy_expansion.other_base_coefficient = 2.0, - -- game.map_settings.enemy_expansion.neighbouring_chunk_coefficient = 0.5, - -- game.map_settings.enemy_expansion.neighbouring_base_chunk_coefficient = 0.4; - - -- -- A chunk has to have at most this much percent unbuildable tiles for it to be considered a candidate. - -- -- This is to avoid chunks full of water to be marked as candidates. - -- game.map_settings.enemy_expansion.max_colliding_tiles_coefficient = 0.9, - - -- -- Size of the group that goes to build new base (in game this is multiplied by the - -- -- evolution factor). - -- game.map_settings.enemy_expansion.settler_group_min_size = 5, - -- game.map_settings.enemy_expansion.settler_group_max_size = 20, - - -- -- Ticks to expand to a single - -- -- position for a base is used. - -- -- - -- -- cooldown is calculated as follows: - -- -- cooldown = lerp(max_expansion_cooldown, min_expansion_cooldown, -e^2 + 2 * e), - -- -- where lerp is the linear interpolation function, and e is the current evolution factor. - -- game.map_settings.enemy_expansion.min_expansion_cooldown = 4 * 3600, - -- game.map_settings.enemy_expansion.max_expansion_cooldown = 60 * 3600 - - -- -- pollution triggered group waiting time is a random time between min and max gathering time - -- game.map_settings.unit_group.min_group_gathering_time = 3600, - -- game.map_settings.unit_group.max_group_gathering_time = 10 * 3600, - -- -- after the gathering is finished the group can still wait for late members, - -- -- but it doesn't accept new ones anymore - -- game.map_settings.unit_group.max_wait_time_for_late_members = 2 * 3600, - -- -- limits for group radius (calculated by number of numbers) - -- game.map_settings.unit_group.max_group_radius = 30.0, - -- game.map_settings.unit_group.min_group_radius = 5.0, - -- -- when a member falls behind the group he can speedup up till this much of his regular speed - -- game.map_settings.unit_group.max_member_speedup_when_behind = 1.4, - -- -- When a member gets ahead of its group, it will slow down to at most this factor of its speed - -- game.map_settings.unit_group.max_member_slowdown_when_ahead = 0.6, - -- -- When members of a group are behind, the entire group will slow down to at most this factor of its max speed - -- game.map_settings.unit_group.max_group_slowdown_factor = 0.3, - -- -- If a member falls behind more than this times the group radius, the group will slow down to max_group_slowdown_factor - -- game.map_settings.unit_group.max_group_member_fallback_factor = 3, - -- -- If a member falls behind more than this time the group radius, it will be removed from the group. - -- game.map_settings.unit_group.member_disown_distance = 10, - -- game.map_settings.unit_group.tick_tolerance_when_member_arrives = 60, - - -- -- Maximum number of automatically created unit groups gathering for attack at any time. - -- game.map_settings.unit_group.max_gathering_unit_groups = 30, - - -- -- Maximum size of an attack unit group. This only affects automatically-created unit groups; manual groups - -- -- created through the API are unaffected. - -- game.map_settings.unit_group.max_unit_group_size = 200 - - end -end - --- Add Long Reach to Character -function GivePlayerLongReach(player) - player.character.character_build_distance_bonus = BUILD_DIST_BONUS - player.character.character_reach_distance_bonus = REACH_DIST_BONUS - -- player.character.character_resource_reach_distance_bonus = RESOURCE_DIST_BONUS -end - --------------------------------------------------------------------------------- --- Player List GUI - My own version --------------------------------------------------------------------------------- -function CreatePlayerListGui(event) - local player = game.players[event.player_index] - if player.gui.top.playerList == nil then - player.gui.top.add{name="playerList", type="button", caption="Player List"} - end -end - -local function ExpandPlayerListGui(player) - local frame = player.gui.left["playerList-panel"] - if (frame) then - frame.destroy() - else - local frame = player.gui.left.add{type="frame", - name="playerList-panel", - caption="Online:"} - local scrollFrame = frame.add{type="scroll-pane", - name="playerList-panel", - direction = "vertical"} - ApplyStyle(scrollFrame, my_player_list_fixed_width_style) - scrollFrame.horizontal_scroll_policy = "never" - for _,player in pairs(game.connected_players) do - local caption_str = player.name.." ["..player.force.name.."]".." ("..formattime_hours_mins(player.online_time)..")" - if (player.admin) then - AddLabel(scrollFrame, player.name.."_plist", caption_str, my_player_list_admin_style) - else - AddLabel(scrollFrame, player.name.."_plist", caption_str, my_player_list_style) - end - end - - -- List offline players - if (PLAYER_LIST_OFFLINE_PLAYERS) then - AddLabel(scrollFrame, "offline_title_msg", "Offline Players:", my_label_style) - for _,player in pairs(game.players) do - if (not player.connected) then - local caption_str = player.name.." ["..player.force.name.."]".." ("..formattime_hours_mins(player.online_time)..")" - local text = scrollFrame.add{type="label", caption=caption_str, name=player.name.."_plist"} - ApplyStyle(text, my_player_list_offline_style) - end - end - end - local spacer = scrollFrame.add{type="label", caption=" ", name="plist_spacer_plist"} - ApplyStyle(spacer, my_player_list_style_spacer) - end -end - -function PlayerListGuiClick(event) - if not (event and event.element and event.element.valid) then return end - local player = game.players[event.element.player_index] - local name = event.element.name - - if (name == "playerList") then - ExpandPlayerListGui(player) - end -end - --------------------------------------------------------------------------------- --- Anti-griefing Stuff & Gravestone (My own version) --------------------------------------------------------------------------------- -function AntiGriefing(force) - force.zoom_to_world_deconstruction_planner_enabled=false - force.friendly_fire=false - SetForceGhostTimeToLive(force) -end - -function SetForceGhostTimeToLive(force) - if GHOST_TIME_TO_LIVE ~= 0 then - force.ghost_time_to_live = GHOST_TIME_TO_LIVE+1 - end -end - -function SetItemBlueprintTimeToLive(event) - local type = event.created_entity.type - if type == "entity-ghost" or type == "tile-ghost" then - if GHOST_TIME_TO_LIVE ~= 0 then - event.created_entity.time_to_live = GHOST_TIME_TO_LIVE - end - end -end - --------------------------------------------------------------------------------- --- Gravestone soft mod. With my own modifications/improvements. --------------------------------------------------------------------------------- --- Return steel chest entity (or nil) -function DropEmptySteelChest(player) - local pos = player.surface.find_non_colliding_position("steel-chest", player.position, 15, 1) - if not pos then - return nil - end - local grave = player.surface.create_entity{name="steel-chest", position=pos, force="neutral"} - return grave -end - -function DropGravestoneChests(player) - - local grave - local count = 0 - - -- Make sure we save stuff we're holding in our hands. - player.clean_cursor() - - -- Loop through a players different inventories - -- Put it all into a chest. - -- If the chest is full, create a new chest. - for i, id in ipairs{ - defines.inventory.player_armor, - defines.inventory.player_main, - defines.inventory.player_quickbar, - defines.inventory.player_guns, - defines.inventory.player_ammo, - defines.inventory.player_tools, - defines.inventory.player_trash} do - - local inv = player.get_inventory(id) - - if ((#inv > 0) and not inv.is_empty()) then - for j = 1, #inv do - if inv[j].valid_for_read then - - -- Create a chest when counter is reset - if (count == 0) then - grave = DropEmptySteelChest(player) - if (grave == nil) then - -- player.print("Not able to place a chest nearby! Some items lost!") - return - end - grave_inv = grave.get_inventory(defines.inventory.chest) - end - count = count + 1 - - -- Copy the item stack into a chest slot. - grave_inv[count].set_stack(inv[j]) - - -- Reset counter when chest is full - if (count == #grave_inv) then - count = 0 - end - end - end - end - - -- Clear the player inventory so we don't have duplicate items lying around. - inv.clear() - end - - if (grave ~= nil) then - player.print("Successfully dropped your items into a chest! Go get them quick!") - end -end - --------------------------------------------------------------------------------- --- Autofill Stuff --------------------------------------------------------------------------------- - --- Transfer Items Between Inventory --- Returns the number of items that were successfully transferred. --- Returns -1 if item not available. --- Returns -2 if can't place item into destInv (ERROR) -function TransferItems(srcInv, destEntity, itemStack) - -- Check if item is in srcInv - if (srcInv.get_item_count(itemStack.name) == 0) then - return -1 - end - - -- Check if can insert into destInv - if (not destEntity.can_insert(itemStack)) then - return -2 - end - - -- Insert items - local itemsRemoved = srcInv.remove(itemStack) - itemStack.count = itemsRemoved - return destEntity.insert(itemStack) -end - --- Attempts to transfer at least some of one type of item from an array of items. --- Use this to try transferring several items in order --- It returns once it successfully inserts at least some of one type. -function TransferItemMultipleTypes(srcInv, destEntity, itemNameArray, itemCount) - local ret = 0 - for _,itemName in pairs(itemNameArray) do - ret = TransferItems(srcInv, destEntity, {name=itemName, count=itemCount}) - if (ret > 0) then - return ret -- Return the value succesfully transferred - end - end - return ret -- Return the last error code -end - --- Autofills a turret with ammo -function AutofillTurret(player, turret) - local mainInv = player.get_inventory(defines.inventory.player_main) - - -- Attempt to transfer some ammo - local ret = TransferItemMultipleTypes(mainInv, turret, {"uranium-rounds-magazine", "piercing-rounds-magazine", "firearm-magazine"}, AUTOFILL_TURRET_AMMO_QUANTITY) - - -- Check the result and print the right text to inform the user what happened. - if (ret > 0) then - -- Inserted ammo successfully - -- FlyingText("Inserted ammo x" .. ret, turret.position, my_color_red, player.surface) - elseif (ret == -1) then - FlyingText("Out of ammo!", turret.position, my_color_red, player.surface) - elseif (ret == -2) then - FlyingText("Autofill ERROR! - Report this bug!", turret.position, my_color_red, player.surface) - end -end - --- Autofills a vehicle with fuel, bullets and shells where applicable -function AutoFillVehicle(player, vehicle) - local mainInv = player.get_inventory(defines.inventory.player_main) - - -- Attempt to transfer some fuel - if ((vehicle.name == "car") or (vehicle.name == "tank") or (vehicle.name == "locomotive")) then - TransferItemMultipleTypes(mainInv, vehicle, {"nuclear-fuel", "rocket-fuel", "solid-fuel", "coal", "raw-wood"}, 50) - end - - -- Attempt to transfer some ammo - if ((vehicle.name == "car") or (vehicle.name == "tank")) then - TransferItemMultipleTypes(mainInv, vehicle, {"uranium-rounds-magazine", "piercing-rounds-magazine", "firearm-magazine"}, 100) - end - - -- Attempt to transfer some tank shells - if (vehicle.name == "tank") then - TransferItemMultipleTypes(mainInv, vehicle, {"explosive-uranium-cannon-shell", "uranium-cannon-shell", "explosive-cannon-shell", "cannon-shell"}, 100) - end -end - --------------------------------------------------------------------------------- --- Resource patch and starting area generation --------------------------------------------------------------------------------- - --- Enforce a circle of land, also adds trees in a ring around the area. -function CreateCropCircle(surface, centerPos, chunkArea, tileRadius) - - local tileRadSqr = tileRadius^2 - - local dirtTiles = {} - for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do - for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do - - -- This ( X^2 + Y^2 ) is used to calculate if something - -- is inside a circle area. - local distVar = math.floor((centerPos.x - i)^2 + (centerPos.y - j)^2) - - -- Fill in all unexpected water in a circle - if (distVar < tileRadSqr) then - if (surface.get_tile(i,j).collides_with("water-tile") or ENABLE_SPAWN_FORCE_GRASS) then - table.insert(dirtTiles, {name = "grass-1", position ={i,j}}) - end - end - - -- Create a circle of trees around the spawn point. - if ((distVar < tileRadSqr-200) and - (distVar > tileRadSqr-400)) then - surface.create_entity({name="tree-02", amount=1, position={i, j}}) - end - end - end - - surface.set_tiles(dirtTiles) -end - --- COPIED FROM jvmguy! --- Enforce a square of land, with a tree border --- this is equivalent to the CreateCropCircle code -function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius) - - local dirtTiles = {} - for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do - for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do - - local distVar1 = math.floor(math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j))) - local distVar2 = math.floor(math.abs(centerPos.x - i) + math.abs(centerPos.y - j)) - local distVar = math.max(distVar1*1.1, distVar2 * 0.707*1.1); - - -- Fill in all unexpected water in a circle - if (distVar < tileRadius+2) then - if (surface.get_tile(i,j).collides_with("water-tile") or ENABLE_SPAWN_FORCE_GRASS) then - table.insert(dirtTiles, {name = "grass-1", position ={i,j}}) - end - end - - -- Create a tree ring - if ((distVar < tileRadius) and - (distVar > tileRadius-2)) then - surface.create_entity({name="tree-01", amount=1, position={i, j}}) - end - end - end - surface.set_tiles(dirtTiles) -end - --- Add a circle of water -function CreateMoat(surface, centerPos, chunkArea, tileRadius) - - local tileRadSqr = tileRadius^2 - - local waterTiles = {} - for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do - for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do - - -- This ( X^2 + Y^2 ) is used to calculate if something - -- is inside a circle area. - local distVar = math.floor((centerPos.x - i)^2 + (centerPos.y - j)^2) - - -- Create a circle of water - if ((distVar < tileRadSqr+(1500*MOAT_SIZE_MODIFIER)) and - (distVar > tileRadSqr)) then - table.insert(waterTiles, {name = "water", position ={i,j}}) - end - - -- Enforce land inside the edges of the circle to make sure it's - -- a clean transition - if ((distVar <= tileRadSqr) and - (distVar > tileRadSqr-10000)) then - table.insert(waterTiles, {name = "grass-1", position ={i,j}}) - end - end - end - - surface.set_tiles(waterTiles) -end - --- Create a horizontal line of water -function CreateWaterStrip(surface, leftPos, length) - local waterTiles = {} - for i=0,length,1 do - table.insert(waterTiles, {name = "water", position={leftPos.x+i,leftPos.y}}) - end - surface.set_tiles(waterTiles) -end - --- Function to generate a resource patch, of a certain size/amount at a pos. -function GenerateResourcePatch(surface, resourceName, diameter, pos, amount) - local midPoint = math.floor(diameter/2) - if (diameter == 0) then - return - end - for y=0, diameter do - for x=0, diameter do - if (not ENABLE_RESOURCE_SHAPE_CIRCLE or ((x-midPoint)^2 + (y-midPoint)^2 < midPoint^2)) then - surface.create_entity({name=resourceName, amount=amount, - position={pos.x+x, pos.y+y}}) - end - end - end -end - - - --- Generate the basic starter resource around a given location. -function GenerateStartingResources(surface, pos) - -- Generate stone - local stonePos = {x=pos.x+START_RESOURCE_STONE_POS_X, - y=pos.y+START_RESOURCE_STONE_POS_Y} - - -- Generate coal - local coalPos = {x=pos.x+START_RESOURCE_COAL_POS_X, - y=pos.y+START_RESOURCE_COAL_POS_Y} - - -- Generate copper ore - local copperOrePos = {x=pos.x+START_RESOURCE_COPPER_POS_X, - y=pos.y+START_RESOURCE_COPPER_POS_Y} - - -- Generate iron ore - local ironOrePos = {x=pos.x+START_RESOURCE_IRON_POS_X, - y=pos.y+START_RESOURCE_IRON_POS_Y} - - -- Generate uranium - local uraniumOrePos = {x=pos.x+START_RESOURCE_URANIUM_POS_X, - y=pos.y+START_RESOURCE_URANIUM_POS_Y} - - -- Tree generation is taken care of in chunk generation - - -- Generate oil patches - oil_patch_x=pos.x+START_RESOURCE_OIL_POS_X - oil_patch_y=pos.y+START_RESOURCE_OIL_POS_Y - for i=1,START_RESOURCE_OIL_NUM_PATCHES do - surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT, - position={oil_patch_x, oil_patch_y}}) - oil_patch_x=oil_patch_x+START_RESOURCE_OIL_X_OFFSET - oil_patch_y=oil_patch_y+START_RESOURCE_OIL_Y_OFFSET - end - - -- Generate Stone - GenerateResourcePatch(surface, "stone", START_RESOURCE_STONE_SIZE, stonePos, START_STONE_AMOUNT) - - -- Generate Coal - GenerateResourcePatch(surface, "coal", START_RESOURCE_COAL_SIZE, coalPos, START_COAL_AMOUNT) - - -- Generate Copper - GenerateResourcePatch(surface, "copper-ore", START_RESOURCE_COPPER_SIZE, copperOrePos, START_COPPER_AMOUNT) - - -- Generate Iron - GenerateResourcePatch(surface, "iron-ore", START_RESOURCE_IRON_SIZE, ironOrePos, START_IRON_AMOUNT) - - -- Generate Uranium - GenerateResourcePatch(surface, "uranium-ore", START_RESOURCE_URANIUM_SIZE, uraniumOrePos, START_URANIUM_AMOUNT) -end - - - --- Clear the spawn areas. --- This should be run inside the chunk generate event and be given a list of all --- unique spawn points. --- This clears enemies in the immediate area, creates a slightly safe area around it, --- It no LONGER generates the resources though as that is now handled in a delayed event! -function SetupAndClearSpawnAreas(surface, chunkArea, spawnPointTable) - for name,spawn in pairs(spawnPointTable) do - - -- Create a bunch of useful area and position variables - local landArea = GetAreaAroundPos(spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+CHUNK_SIZE) - local safeArea = GetAreaAroundPos(spawn.pos, SAFE_AREA_TILE_DIST) - local warningArea = GetAreaAroundPos(spawn.pos, WARNING_AREA_TILE_DIST) - local chunkAreaCenter = {x=chunkArea.left_top.x+(CHUNK_SIZE/2), - y=chunkArea.left_top.y+(CHUNK_SIZE/2)} - local spawnPosOffset = {x=spawn.pos.x+ENFORCE_LAND_AREA_TILE_DIST, - y=spawn.pos.y+ENFORCE_LAND_AREA_TILE_DIST} - - -- Make chunks near a spawn safe by removing enemies - if CheckIfInArea(chunkAreaCenter,safeArea) then - RemoveAliensInArea(surface, chunkArea) - - -- Create a warning area with reduced enemies - elseif CheckIfInArea(chunkAreaCenter,warningArea) then - ReduceAliensInArea(surface, chunkArea, WARN_AREA_REDUCTION_RATIO) - end - - -- If the chunk is within the main land area, then clear trees/resources - -- and create the land spawn areas (guaranteed land with a circle of trees) - if CheckIfInArea(chunkAreaCenter,landArea) then - - -- Remove trees/resources inside the spawn area - RemoveInCircle(surface, chunkArea, "tree", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST) - RemoveInCircle(surface, chunkArea, "resource", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+5) - RemoveInCircle(surface, chunkArea, "cliff", spawn.pos, ENFORCE_LAND_AREA_TILE_DIST+5) - RemoveDecorationsArea(surface, chunkArea) - - if (SPAWN_TREE_CIRCLE_ENABLED) then - CreateCropCircle(surface, spawn.pos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST) - end - if (SPAWN_TREE_OCTAGON_ENABLED) then - CreateCropOctagon(surface, spawn.pos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST) - end - if (SPAWN_MOAT_CHOICE_ENABLED) then - if (spawn.moat) then - CreateMoat(surface, spawn.pos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST) - end - end - end - - -- Provide starting resources - -- This is run on the bottom, right chunk of the spawn area which should be - -- generated last, so it should work everytime. - -- if CheckIfInArea(spawnPosOffset,chunkArea) then - -- CreateWaterStrip(surface, - -- {x=spawn.pos.x+WATER_SPAWN_OFFSET_X, y=spawn.pos.y+WATER_SPAWN_OFFSET_Y}, - -- WATER_SPAWN_LENGTH) - -- CreateWaterStrip(surface, - -- {x=spawn.pos.x+WATER_SPAWN_OFFSET_X, y=spawn.pos.y+WATER_SPAWN_OFFSET_Y+1}, - -- WATER_SPAWN_LENGTH) - -- GenerateStartingResources(surface, spawn.pos) - -- end - end -end - --------------------------------------------------------------------------------- --- Surface Generation Functions --------------------------------------------------------------------------------- - -RSO_MODE = 1 -VANILLA_MODE = 2 - -function CreateGameSurface(mode) - local mapSettings = game.surfaces["nauvis"].map_gen_settings - - if CMD_LINE_MAP_GEN then - mapSettings.terrain_segmentation = global.clMapGen.terrain_segmentation - mapSettings.water = global.clMapGen.water - mapSettings.starting_area = global.clMapGen.starting_area - mapSettings.peaceful_mode = global.clMapGen.peaceful_mode - mapSettings.seed = global.clMapGen.seed - mapSettings.autoplace_controls = global.clMapGen.autoplace_controls - mapSettings.cliff_settings = global.clMapGen.cliff_settings - end - - -- To use RSO resources, we have to disable vanilla ore generation - if (mode == RSO_MODE) then - mapSettings.autoplace_controls["coal"].size="none" - mapSettings.autoplace_controls["copper-ore"].size="none" - mapSettings.autoplace_controls["iron-ore"].size="none" - mapSettings.autoplace_controls["stone"].size="none" - mapSettings.autoplace_controls["uranium-ore"].size="none" - mapSettings.autoplace_controls["crude-oil"].size="none" - mapSettings.autoplace_controls["enemy-base"].size="none" - end - - local surface = game.create_surface(GAME_SURFACE_NAME,mapSettings) - surface.set_tiles({{name = "out-of-map",position = {1,1}}}) -end - - --------------------------------------------------------------------------------- --- Holding pen for new players joining the map --------------------------------------------------------------------------------- -function CreateWall(surface, pos) - local wall = surface.create_entity({name="stone-wall", position=pos, force=MAIN_TEAM}) - if wall then - wall.destructible = false - wall.minable = false - end -end - -function CreateHoldingPen(surface, chunkArea, sizeTiles, walls) - if (((chunkArea.left_top.x == -32) or (chunkArea.left_top.x == 0)) and - ((chunkArea.left_top.y == -32) or (chunkArea.left_top.y == 0))) then - - -- Remove stuff - RemoveAliensInArea(surface, chunkArea) - RemoveInArea(surface, chunkArea, "tree") - RemoveInArea(surface, chunkArea, "resource") - RemoveInArea(surface, chunkArea, "cliff") - - -- This loop runs through each tile - local grassTiles = {} - local waterTiles = {} - for i=chunkArea.left_top.x,chunkArea.right_bottom.x,1 do - for j=chunkArea.left_top.y,chunkArea.right_bottom.y,1 do - - if ((i>-sizeTiles) and (i<(sizeTiles-1)) and (j>-sizeTiles) and (j<(sizeTiles-1))) then - - -- Fill all area with grass only - table.insert(grassTiles, {name = "grass-1", position ={i,j}}) - - -- Create the spawn box walls - if (j<(sizeTiles-1) and j>-sizeTiles) then - - -- Create horizontal sides of center spawn box - if (((j>-sizeTiles and j<-(sizeTiles-4)) or (j<(sizeTiles-1) and j>(sizeTiles-5))) and (i<(sizeTiles-1) and i>-sizeTiles)) then - if walls then - CreateWall(surface, {i,j}) - else - table.insert(waterTiles, {name = "water", position ={i,j}}) - end - end - - -- Create vertical sides of center spawn box - if ((i>-sizeTiles and i<-(sizeTiles-4)) or (i<(sizeTiles-1) and i>(sizeTiles-5))) then - if walls then - CreateWall(surface, {i,j}) - else - table.insert(waterTiles, {name = "water", position ={i,j}}) - end - end - - end - end - end - end - surface.set_tiles(grassTiles) - surface.set_tiles(waterTiles) - end -end - --------------------------------------------------------------------------------- --- EVENT SPECIFIC FUNCTIONS --------------------------------------------------------------------------------- - --- Display messages to a user everytime they join -function PlayerJoinedMessages(event) - local player = game.players[event.player_index] - player.print(global.welcome_msg) - player.print(GAME_MODE_MSG) - player.print(MODULES_ENABLED) -end - --- Remove decor to save on file size -function UndecorateOnChunkGenerate(event) - local surface = event.surface - local chunkArea = event.area - RemoveDecorationsArea(surface, chunkArea) - RemoveFish(surface, chunkArea) -end - --- Give player items on respawn --- Intended to be the default behavior when not using separate spawns -function PlayerRespawnItems(event) - GivePlayerItems(game.players[event.player_index]) -end - -function PlayerSpawnItems(event) - GivePlayerStarterItems(game.players[event.player_index]) -end - --- Autofill softmod -function Autofill(event) - local player = game.players[event.player_index] - local eventEntity = event.created_entity - - if (eventEntity.name == "gun-turret") then - AutofillTurret(player, eventEntity) - end - - if ((eventEntity.name == "car") or (eventEntity.name == "tank") or (eventEntity.name == "locomotive")) then - AutoFillVehicle(player, eventEntity) - end -end diff --git a/locale/rso/metaball.lua b/locale/rso/metaball.lua deleted file mode 100644 index 407111b..0000000 --- a/locale/rso/metaball.lua +++ /dev/null @@ -1,104 +0,0 @@ ---[[-- -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/prototype_utils.lua b/locale/rso/prototype_utils.lua deleted file mode 100644 index e4566a2..0000000 --- a/locale/rso/prototype_utils.lua +++ /dev/null @@ -1,49 +0,0 @@ -require("locale/rso/rso_config") - -function add_peak(ent, peak) - if ent and ent.autoplace then - ent.autoplace.peaks[#ent.autoplace.peaks+1] = peak - end -end - -function change_ocataves(autoplace, octaves) - for k,v in pairs(autoplace.peaks) do - if v.noise_layer then - v.noise_octaves_difference = (v.noise_octaves_difference or 0) + octaves - end - end -end - -function generate_basic_peaks(noise_layer) - return { - { - influence = 0.1 - }, - { - influence = 0.67, - noise_layer = noise_layer, - noise_octaves_difference = -2.7, - noise_persistence = 0.3 - } - } - -end - -function resetRichness(ent) - if ent and ent.autoplace then - ent.autoplace.richness_multiplier = 0 - ent.autoplace.richness_base = 0 - end -end - -function removeAutoplace(ent) - if ent then - ent.autoplace = nil - end -end - -function removePeaks(ent) - if ent and ent.autoplace then - ent.autoplace.peaks = {} - end -end diff --git a/locale/rso/rso_config.lua b/locale/rso/rso_config.lua deleted file mode 100644 index f11a495..0000000 --- a/locale/rso/rso_config.lua +++ /dev/null @@ -1,57 +0,0 @@ -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 = 1 -- starting area in regions, safe from random nonsense - -absolute_resource_chance = 0.40 -- 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= 0.8 -- exponent for richness distance factor calculation -fluid_richness_distance_factor = 0.8 -- exponent for richness distance factor calculation for fluids -size_distance_factor=0.3 -- 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 - -- -useResourceCollisionDetection = true -- enables avoidace calculations to reduce ores overlaping of each other -resourceCollisionDetectionRatio = 0.999 -- threshold to exit placement early -resourceCollisionDetectionRatioFallback = 0.75 -- 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 - -remove_trees = false -reveal_spawn_resources = false diff --git a/locale/rso/rso_control.lua b/locale/rso/rso_control.lua deleted file mode 100644 index 5c9afc6..0000000 --- a/locale/rso/rso_control.lua +++ /dev/null @@ -1,1565 +0,0 @@ -require("config") -require("util") - -require("locale/rso/rso_config") -require("locale/rso/rso_resource_config") -require("locale/oarc_utils") - -local MB=require("locale/rso/metaball") - --- 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 - --- cached setting value --- local region_size = settings.global["rso-region-size"].value - --- 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 = 32771 - -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 distance = util.distance -local invalidResources = {} - --- 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 32 bits - return floor(n) % 0xffffffff -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 rng_for_reg_pos(pos) - local x = pos.x - local y = pos.y - - local rng = game.create_random_generator() - - if x < 0 then - x = abs(x) * 111 - end - if y < 0 then - y = abs(y) * 111 - end - - rng.re_seed(normalize(x * 65536)) - local valX = rng(65536) - rng.re_seed(normalize(y * 32768)) - local valY = rng(65536) - - local seed = normalize( valX * valY * global.seed ) - rng.re_seed( seed ) - - debug("Generator for " .. pos.x .. "," .. pos.y .. " created with seed " .. seed .. " x:" .. valX .. " y:" .. valY) - - return rng -end - -local function rng_restricted_angle(restrictions, rng) - local value = rng() - local x_scale, y_scale - local deformX = rng() * 2 - 1 - local deformY = rng() * 2 - 1 - - if restrictions=='xy' then - y_scale=1.0 + deformY*0.5 - x_scale=1.0 + deformX*0.5 - angle = value*pi*2 - elseif restrictions=='x' then - y_scale=1.0 + deformY*0.6 - x_scale=1.0 + deformX*0.6 - angle = value*pi/2 - pi/4 - elseif restrictions=='y' then - y_scale=1.0 + deformY*0.6 - x_scale=1.0 + deformX*0.6 - angle = value*pi/2 + pi/2 - else - y_scale=1.0 + deformY*0.3 - x_scale=1.0 + deformX*0.3 - angle = value*pi*2 - end - - return angle, x_scale, y_scale -end - -local function vary_by_percentage(x, p, rng) - return x + (0.5 - rng())*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 - for _, entity in pairs(surface.find_entities_filtered{area = bb, type="simple-entity"}) 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, rng) - local offset_x=rng(region_size) - 1 - local offset_y=rng(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 --- log("Random chunk "..r_x..","..r_y.." coords "..c_x..","..c_y.." offset "..offset_x..","..offset_y) - 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, rng) - -- somewhat bruteforce and unoptimized - local x_dir = rng(-2,2) - local y_dir = rng(-2,2) - - 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 - 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 - - return ocx, ocy -end - -local function isInStartingArea( tileX, tileY ) - - for idx, pos in pairs( global.startingAreas ) do - - local adjustedX = ( tileX - pos.x ) / REGION_TILE_SIZE - local adjustedY = ( tileY - pos.y ) / REGION_TILE_SIZE - if ((adjustedX * adjustedX + adjustedY * adjustedY) <= global.starting_area_size * global.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, rng) - -- 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 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 radiusMax = radius * 3 -- arbitrary multiplier - needs to be big enough to not cut any metaballs - updateRect(outside, x, y, radiusMax) - updateRect(inside, x, y, radius) - end - - local function roundRect( rect ) - rect.xmin = round( rect.xmin ) - rect.xmax = round( rect.xmax ) - rect.ymin = round( rect.ymin ) - rect.ymax = round( rect.ymax ) - end - - local function generate_p_ball(rng) - local angle, x_scale, y_scale, x, y, b_radius, shape - angle, x_scale, y_scale=rng_restricted_angle(restrictions, rng) - local dev = radius / 2 + rng() * radius / 4--math.min(CHUNK_SIZE/3, radius*1.5) - local dev_x, dev_y = pos.x, pos.y - x = rng(-dev, dev)+dev_x - y = rng(-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 + rng() * radius / 4) -- * (P_BALL_SIZE_FACTOR^#p_balls) - else - b_radius = ( radius / 4 + rng() * radius / 2) -- * (P_BALL_SIZE_FACTOR^#p_balls) - end - - - if #p_balls > 0 then - local tempRect = table.deepcopy(inside) - updateRect(tempRect, x, y, 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) - -- 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[rng(1,#meta_shapes)] - local radiusText = "" --- log("Index rolled "..index.." shapes amount "..#meta_shapes) - if shape.type == "MetaDonut" then - local inRadius = b_radius / 4 + b_radius / 2 * rng() - 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, rng) - local angle, x_scale, y_scale, x, y, b_radius, shape - angle, x_scale, y_scale=rng_restricted_angle('xy', rng) - if p_balls[i] then - local new_angle = p_balls[i].angle + pi*rng(0,2) + (rng()-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 + (rng()-0.5)*pi*2/3 - else - x = rng(-radius, radius)+pos.x - y = rng(-radius, radius)+pos.y - end - - if p_balls[i] then - b_radius = (p_balls[i].radius / 4 + rng() * p_balls[i].radius / 2) -- * (N_BALL_SIZE_FACTOR^#n_balls) - else - b_radius = (radius / 4 + rng() * radius / 2) -- * (N_BALL_SIZE_FACTOR^#n_balls) - end - - if b_radius < 1 then - b_radius = 1 - end - - shape = meta_shapes[rng(1,#meta_shapes)] --- log("Index rolled "..index.." shapes amount "..#meta_shapes) - local radiusText = "" - if shape.type == "MetaDonut" then - local inRadius = b_radius / 4 + b_radius / 2 * rng() - 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 pairs(p_balls) do - p_force = p_force + ball:force(x,y) - end - for _,ball in pairs(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 = global.config[rname].min_amount or min_amount - if restrictions == 'xy' then - -- we have full 4 chunks - --radius = radius * 1.5 - --richness = richness * 2 / 3 - --min_amount = min_amount / 3 - 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(rng) - end - - for i=1,rng(1, #p_balls) do - generate_n_ball(i, rng) - end - ---* local _a = {} - local _total = 0 - local oreLocations = {} - local forceTotal = 0 - - roundRect( outside ) - -- fill the map --- for y=pos.y-CHUNK_SIZE*2, pos.y+CHUNK_SIZE*2-1 do - for y=outside.ymin, outside.ymax do ---* local _b = {} ---* _a[#_a+1] = _b --- for x=pos.x-CHUNK_SIZE*2, pos.x+CHUNK_SIZE*2-1 do - for x=outside.xmin, outside.xmax do --- if surface.get_tile(x,y).valid then - force = calculate_force(x, y) - if force > 0 then - --debug("@ "..x..","..y.." force: "..force.." amount: "..amount) - -- if not surface.get_tile(x,y).collides_with("water-tile") and surface.can_place_entity{name = rname, position = {x,y}} then ---* _b[#_b+1] = '#' - oreLocations[#oreLocations + 1] = {x = x, y = y, force = force, valid = false} - forceTotal = forceTotal + force --- 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 = game.find_entities_filtered{area = {{x-2.75, y-2.75}, {x+2.75, y+2.75}}, name=rname} --- if entities and #entities > 0 then ---* _b[#_b+1] = 'O' --- _total = _total + amount --- for k, ent in pairs(entities) do --- ent.amount = ent.amount + floor(amount/#entities) --- end --- else --- _b[#_b+1] = '.' --- end - -- else - -- _b[#_b+1] = 'c' - -- end ---* else ---* _b[#_b+1] = '<' - end ---* else ---* _b[#_b+1] = 'x' --- end - end - end - - local validCount, resOffsetX, resOffsetY, ratio - - local bestRatio = 0 - local bestRatioIndex = -1 - local checkedLocations = {} - local needRecheck = false - - for index, locationOffset in pairs(locationOrder) do - validCount = 0 - resOffsetX = locationOffset.x * CHUNK_SIZE - resOffsetY = locationOffset.y * CHUNK_SIZE - - for _, location in pairs(oreLocations) do - - local newX = location.x + resOffsetX - local newY = location.y + resOffsetY - location.valid = false --- debug("Checking ".. newX .. "," .. newY .."("..location.x..","..location.y..")") - if surface.can_place_entity{name = rname, position = {x = newX,y = newY}} then - location.valid = true - validCount = validCount + 1 --- debug("Passed") - else --- debug("Failed ".. newX .. "," .. newY .."("..location.x..","..location.y..")") - end - end - - ratio = 0 - - if validCount > 0 then - ratio = validCount / #oreLocations - end - - debug("Valid ratio "..ratio.." for index "..index.." offsets "..resOffsetX..","..resOffsetY ) - - if not useResourceCollisionDetection then - break - end - - if ratio > bestRatio then - bestRatio = ratio - bestRatioIndex = index - end - if ratio > resourceCollisionDetectionRatio then - break - end - end - - if bestRatioIndex ~= index then - needRecheck = true - end - - if resourceCollisionFieldSkip and bestRatio < resourceCollisionDetectionRatioFallback then - validCount = 0 - -- debug("Cancelled with best ratio "..bestRatio.." for index "..bestRatioIndex ) - else - resOffsetX = locationOrder[bestRatioIndex].x * CHUNK_SIZE - resOffsetY = locationOrder[bestRatioIndex].y * CHUNK_SIZE - validCount = floor( bestRatio * #oreLocations ) - -- debug("Selected ratio "..bestRatio.." for index "..bestRatioIndex.." offsets "..resOffsetX..","..resOffsetY ) - end - - if validCount > 0 then - local removeTrees = remove_trees - local revealResources = reveal_spawn_resources - local revealChunks = {} - revealChunks.minX = 1e9 - revealChunks.minY = 1e9 - revealChunks.maxX = -1e9 - revealChunks.maxY = -1e9 - - 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 = rng(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 = rng(3000, 4000) - end --- elseif forceFactor > 25000 then -- limit size of one resource pile --- forceFactor = rgen:random(20000, 25000) --- 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 --- if game.players[1] then --- game.players[1].print("Spawning "..rname.." total amount "..(approxDepositSize + validCount * min_amount)*richnessMultiplier) --- end - - local spawnName = rname - - for _,location in pairs(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 amount > 0 then - local spawnX = location.x + resOffsetX - local spawnY = location.y + resOffsetY - - if not needRecheck or surface.can_place_entity{name = spawnName, position = {spawnX,spawnY}} then - surface.create_entity{name = spawnName, - position = {spawnX, spawnY}, - force = game.forces.neutral, - amount = amount} - end - - if removeTrees then - remove_trees(surface, spawnX, spawnY, 1, 1) - end - - revealChunks.minX = math.min( revealChunks.minX, spawnX ) - revealChunks.minY = math.min( revealChunks.minY, spawnY ) - revealChunks.maxX = math.max( revealChunks.maxX, spawnX ) - revealChunks.maxY = math.max( revealChunks.maxY, spawnY ) - end - end - end - - if revealResources then - for _,force in pairs( game.forces )do - force.chart( surface, {{x = revealChunks.minX, y = revealChunks.minY}, {x = revealChunks.maxX, y = revealChunks.maxY}}) - 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, rng) - restrictions = restrictions or '' - -- debug("Entering spawn_resource_liquid "..rname.." "..pos.x..","..pos.y.." "..size.." "..richness.." "..tostring(startingArea).." "..restrictions) - local _total = 0 - local max_radius = rng() * CHUNK_SIZE / 2 + CHUNK_SIZE - --[[ - if restrictions == 'xy' then - -- we have full 4 chunks - max_radius = floor(max_radius*1.5) - size = floor(size*1.2) - end - ]]-- - -- don't reduce amount of liquids - they are already infinite - -- size = modify_resource_size(size) - - richness = ( 0.75 + rng() / 2 ) * richness * size - - resourceEntity = game.entity_prototypes[rname] - - local total_share = 0 - local avg_share = 1/size - local angle = rng()*pi*2 - local saved = 0 - local removeTrees = remove_trees - local richnessMultiplier = global_richness_mult - local spawnCount = 0 - - if startingArea then - richnessMultiplier = starting_richness_mult - end - - while total_share < 1 do - local new_share = vary_by_percentage(avg_share, 0.25, rng) - 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 - - --if amount >= game.entity_prototypes[rname].minimum then - if amount >= global.config[rname].minimum_amount then - saved = 0 - - local spawned = false - local x, y - - for try=1,15 do - local dist = rng()*(max_radius - max_radius*0.1) * try / 15 - angle = angle + pi/4 + rng()*pi/2 - x = pos.x + cos(angle)*dist - y = 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 = rng(4)} - if removeTrees then - remove_trees(surface, x, y, 4, 4) - end - end - - if spawnCount == 0 then - debug("Switched center location to "..x..","..y) - pos.x = x - pos.y = y - end - - spawnCount = spawnCount + 1 - spawned = true - break - end - end - - if not startingArea and not spawned 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 - 5, y - 5}, {x + 5, y + 5}}, 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 - else - saved = amount - end - end - -- debug("Total amount: ".._total.." in "..spawnCount.." fields") - -- 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 = rng(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 = 20 - local distancePerBatch = 0.5 - - initSpawnerTable() - - local useBobEntity = global.useBobEntity - local spawner_probability_edge = global.spawner_probability_edge - local spawnerName = nil - - for i=1,size do - for attempt = 1, maxAttemptCount do - local max_d = floor(CHUNK_SIZE * (0.5 + (attempt/5) * distancePerBatch)) - local s_x = x + rng(0, floor(max_d - r_config.clear_range[1])) - max_d/2 + r_config.clear_range[1] - local s_y = y + rng(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]) - - if spawner_probability_edge > 0 then - - bigSpawnerChance = rng() - - if rng() < 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 + 1 - -- 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 - -- game.players[1].print("Entity "..spawnerName.." spawn failed") - break; - else - if attempt == maxAttemptCount then - -- debug(spawnerName.." @ "..s_x..","..s_y.." `spawn") - end - end - else - game.players[1].print("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 rng() < sub_spawn_prob then - for i=1,(rng(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 * (r_distance^v.allotment_distance_factor) - 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 = rng(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((attempt/5) * CHUNK_SIZE * distancePerBatch) - s_x = x + rng(max_d) - max_d/2 - s_y = y + rng(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.disableStartingArea then return end - 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 global.starting_area_size < 0.1 then return end -- skip spawning if starting area is to small - - local position = global.startingAreas[index] - local configIndexed = global.configIndexed - - rng = rng_for_reg_pos( position ) - local status = true - for _,v in pairs(configIndexed) do - if v.starting then - local prob = rng() -- 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 = 20 - local min_threshold = 0 - - if v.type == "resource-ore" then - min_threshold = v.starting.richness * rng(5, 10) -- lets make sure that there is at least 5-10 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 = rng() * pi * 2 - local dist = rng(20) + 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, null, rng) - elseif v.type == "resource-liquid" then - total = total + spawn_resource_liquid(surface, v.name, pos, v.starting.size, v.starting.richness, true, null, rng) - end - radius = radius + 20 - end - if total < min_threshold then - status = false - end - end - end - end - - global.startingAreas[index].spawned = true - --l:dump('logs/start_'..global.seed..'.log') -end - -local function modifyMinMax(value, mod) - value.min = round( value.min * mod ) - value.max = round( value.max * mod ) -end - -local function build_config_data(surface) - local mapGenSettings = nil - - if not ignoreMapGenSettings then - mapGenSettings = surface.map_gen_settings - end - local autoPlaceSettings = nil - if mapGenSettings then - autoPlaceSettings = mapGenSettings.autoplace_controls - end - - local configIndexed = {} - local config = global.config - - -- build additional indexed array to the associative array - for res_name, resConf in pairs(config) do - if resConf.valid then -- only add valid resources - res_conf = table.deepcopy(resConf) - 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] - - 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 - else - -- log("Null allotment mod for "..res_name.." value "..settingsForResource.frequency) - end - - if sizeMod ~= nil and sizeMod == 0 then - addResource = false - -- log("Null size mod for "..res_name.." value "..settingsForResource.size) - 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 - else - -- log("Null richness mod for "..res_name.." value "..settingsForResource.richness) - end - if allotmentMod and richnessMod and sizeMod then - debug(res_name .. " allotment mod " .. allotmentMod .. " size mod " .. sizeMod .. " richness mod " .. richnessMod ) - end - end - - if addResource then - 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 - configIndexed[#configIndexed + 1] = res_conf - end - end - end - - table.sort(configIndexed, function(a, b) return a.name < b.name end) - - local pr=0 - local maxAllotment = 0 - for index,v in pairs(configIndexed) 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 = maxAllotment, max = maxAllotment + v.allotment} - maxAllotment = maxAllotment + v.allotment - end - end - - global.maxAllotment = maxAllotment - global.starting_area_size = starting_area_size - - if mapGenSettings and mapGenSettings.starting_area then - local multiplier = startingAreaMultiplier[mapGenSettings.starting_area] - if multiplier ~= nil then - global.starting_area_size = starting_area_size * multiplier - debug("Starting area "..global.starting_area_size) - end - end - - global.configIndexed = configIndexed -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 - global.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! - - local prototypes = game.entity_prototypes - local config = global.config - - for resourceName, resourceConfig in pairs(config) do - if prototypes[resourceName] or resourceConfig.type == "entity" then - resourceConfig.valid = true - else - if prototypes[resourceName] and prototypes[resourceName].autoplace ~= nil then - -- 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 - 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, c_y ) then - return false - end - - local configIndexed = global.configIndexed - - -- Reroll regions every region_size^2 chunk generation calls. - global.regrow_rso = false - if (ENABLE_REGROWTH) then - if (global.chunk_regrow.rso_region_roll_counter > (region_size*region_size/2)) then - global.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 global.regions[r_x] and global.regions[r_x][r_y] then - -- r_data = global.regions[r_x][r_y] - -- end - - -- if (not (global.regions[r_x] and global.regions[r_x][r_y]) or regrow_rso) then - - if (global.regions[r_x] and global.regions[r_x][r_y] and not global.regrow_rso) then - r_data = global.regions[r_x][r_y] - else - --if this chunk is the first in its region to be generated - 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] - -- if (global.regrow_rso and not deterministic) then - -- rng = rng_for_reg_pos{x=r_x*math.random(-1,1),y=r_y*math.random(-1,1)} - -- else - rng = rng_for_reg_pos{x=r_x,y=r_y} - -- end - - - local rollCount = math.ceil(#configIndexed / 10) - 1 -- 0 based counter is more convenient here - rollCount = math.min(rollCount, 3) - - local resourceSetting = absolute_resource_chance - - local maxAllotment = global.maxAllotment - - -- if in some way there are no valid ores present just bail or it will die on rng - if maxAllotment == 0 then - return false - end - - - for rollNumber = 0,rollCount do - - local resourceChance = resourceSetting - rollNumber * 0.1 - --absolute chance to spawn resource - local abct = rng() - -- debug("Rolling resource "..abct.." against "..resourceChance.." roll "..rollNumber) - if abct <= resourceChance then - local res_type=rng(1, maxAllotment) - for index,v in pairs(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 roll="..res_type.." @ "..r_x..","..r_y) - local num_spawns = 0 - if v.spawns_per_region.min == v.spawns_per_region.max then - num_spawns = v.spawns_per_region.min - else - num_spawns = rng(v.spawns_per_region.min, v.spawns_per_region.max) - end - local last_spawn_coords = {} - local along_ - for i=1,num_spawns do - local c_x, c_y = find_random_chunk(r_x, r_y, rng) - - -- even if initial chunk is outside region might overlap with starting area - need to recheck here if rolled coords are outside - if not isInStartingArea( c_x, c_y ) then - 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 = rng() - for index,vv in pairs(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 - end - -- roll multiple resources in same region - local deep=0 - if #last_spawn_coords > 0 then - while v.multi_resource_chance and rng() <= v.multi_resource_chance*(multi_resource_chance_diminish^deep) do - deep = deep + 1 - local multiAllotmentMax = 0 - for index,sub_res in pairs(v.multi_resource) do multiAllotmentMax = multiAllotmentMax + sub_res.allotment end - - local res_type = 1 -- with allotment of 1 we don't need to roll rng and rng will complain when it's range is 0 - if multiAllotmentMax > 1 then - res_type = rng(1, multiAllotmentMax) - end - 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[rng(1, #last_spawn_coords)] - local c_x, c_y = find_random_neighbour_chunk(last_coords[1], last_coords[2], rng) -- in same region 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 - end - break - end - end - - end - end - -- roll for absolute_probability - this rolls the enemies - - for index,v in pairs(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 = rng() - if abs_roll 10 then - global.startingAreas[1].spawned = true - end - end - - global.seed = surface.map_gen_settings.seed - - global.config = loadResourceConfig() - checkConfigForInvalidResources() - build_config_data(surface) - - -- checkForBobEnemies() - calculate_spawner_ratio() - -end - -function RSO_init() - if not initDone then - - local surface = game.surfaces[GAME_SURFACE_NAME] - - updateConfig() - - 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 deleted file mode 100644 index 196ee74..0000000 --- a/locale/rso/rso_resource_config.lua +++ /dev/null @@ -1,171 +0,0 @@ - -local function fillVanillaConfig() - - config["iron-ore"] = { - type="resource-ore", - - -- general spawn params - allotment=90, -- how common resource is - spawns_per_region={min=1, max=1}, --number of chunks - richness=20000, -- 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=80, - spawns_per_region={min=1, max=1}, - richness=20000, - 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=16000, - 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=12000, - 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=1}, - richness={min=400000, max=1000000}, -- richness per resource spawn - size={min=3, max=7}, - - starting={richness=400000, 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=0.3, - 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 deleted file mode 100644 index b47c53a..0000000 --- a/locale/tag.lua +++ /dev/null @@ -1,61 +0,0 @@ --- tag.lua --- Apr 2017 --- Allows adding play tags - -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 diff --git a/regrowth_map.lua b/regrowth_map.lua deleted file mode 100644 index 4be72b7..0000000 --- a/regrowth_map.lua +++ /dev/null @@ -1,349 +0,0 @@ --- regrowth_map.lua --- July 2017 --- --- Code tracks all chunks generated and allows for deleting inactive chunks --- Relies on some changes to RSO to provide random resource locations the next --- time the land is regenerated. -- (THIS IS CURRENTLY NOT WORKING IN 0.16, --- resources always how up in the same spot!) --- --- Basic rules of regrowth: --- 1. Area around player is safe for quite a large distance. --- 2. Rocket silo won't be deleted. - PERMANENT --- 3. Chunks with pollution won't be deleted. --- 4. Chunks with railways won't be deleted. --- 5. Anything within radar range won't be deleted, but radar MUST be active. --- -- This works by refreshing all chunk timers within radar range using --- the on_sector_scanned event. --- 6. Chunks timeout after 1 hour-ish, configurable --- 7. For now, oarc spawns are deletion safe as well, but only immediate area. - --- Generic Utility Includes -require("locale/oarc_utils") - - --- Default timeout of generated chunks -REGROWTH_TIMEOUT_TICKS = TICKS_PER_HOUR - --- We can't delete chunks regularly without causing lag. --- So we should save them up to delete them. -REGROWTH_CLEANING_INTERVAL_TICKS = REGROWTH_TIMEOUT_TICKS - --- Not used right now. --- It takes a radar 7 hours and 20 minutes to scan it's whole area completely --- So I will bump the refresh time of blocks up by 8 hours --- RADAR_COMPLETE_SCAN_TICKS = TICKS_PER_HOUR*8 --- Additional bonus time for certain things: --- REFRESH_BONUS_RADAR = RADAR_COMPLETE_SCAN_TICKS - - --- Init globals and set player join area to be off limits. -function OarcRegrowthInit() - global.chunk_regrow = {} - global.chunk_regrow.map = {} - global.chunk_regrow.removal_list = {} - global.chunk_regrow.rso_region_roll_counter = 0 - global.chunk_regrow.player_refresh_index = 1 - global.chunk_regrow.min_x = 0 - global.chunk_regrow.max_x = 0 - global.chunk_regrow.x_index = 0 - global.chunk_regrow.min_y = 0 - global.chunk_regrow.max_y = 0 - global.chunk_regrow.y_index = 0 - global.chunk_regrow.force_removal_flag = -1000 - - OarcRegrowthOffLimits({x=0,y=0}, 10) -end - -function GetChunkTopLeft(pos) - return {x=pos.x-(pos.x % 32), y=pos.y-(pos.y % 32)} -end - -function GetChunkCoordsFromPos(pos) - return {x=math.floor(pos.x/32), y=math.floor(pos.y/32)} -end - --- This complicated function checks that if a chunk -function CheckChunkEmpty(pos) - chunkPos = GetChunkCoordsFromPos(pos) - search_top_left = {x=chunkPos.x*32, y=chunkPos.y*32} - search_area = {search_top_left, {x=search_top_left.x+32,y=search_top_left.y+32}} - total = 0 - for f,_ in pairs(game.forces) do - if f ~= "neutral" and f ~= "enemy" then - entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area = search_area, force=f} - total = total + #entities - if (#entities > 0) then - - for _,e in pairs(entities) do - if ((e.type == "player") or - (e.type == "car") or - (e.type == "logistic-robot") or - (e.type == "construction-robot")) then - total = total - 1 - end - end - end - end - end - - -- A destroyed entity is still found during the event check. - return (total == 1) -end - --- game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area = {game.player.position, {game.player.position.x+32, game.player.position-`32}}, type= "resource"} - --- If an entity is mined or destroyed, then check if the chunk --- is empty. If it's empty, reset the refresh timer. -function OarcRegrowthCheckChunkEmpty(event) - if ((event.entity.force ~= nil) and (event.entity.force ~= "neutral") and (event.entity.force ~= "enemy")) then - if CheckChunkEmpty(event.entity.position) then - DebugPrint("Resetting chunk timer."..event.entity.position.x.." "..event.entity.position.y) - OarcRegrowthForceRefreshChunk(event.entity.position, 0) - end - end -end - --- Adds new chunks to the global table to track them. --- This should always be called first in the chunk generate sequence --- (Compared to other RSO & Oarc related functions...) -function OarcRegrowthChunkGenerate(pos) - - c_pos = GetChunkCoordsFromPos(pos) - - -- If this is the first chunk in that row: - if (global.chunk_regrow.map[c_pos.x] == nil) then - global.chunk_regrow.map[c_pos.x] = {} - end - - -- Confirm the chunk doesn't already have a value set: - if (global.chunk_regrow.map[c_pos.x][c_pos.y] == nil) then - global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick - end - - -- Store min/max values for x/y dimensions: - if (c_pos.x < global.chunk_regrow.min_x) then - global.chunk_regrow.min_x = c_pos.x - end - if (c_pos.x > global.chunk_regrow.max_x) then - global.chunk_regrow.max_x = c_pos.x - end - if (c_pos.y < global.chunk_regrow.min_y) then - global.chunk_regrow.min_y = c_pos.y - end - if (c_pos.y > global.chunk_regrow.max_y) then - global.chunk_regrow.max_y = c_pos.y - end -end - --- Mark an area for immediate forced removal -function OarcRegrowthMarkForRemoval(pos, chunk_radius) - local c_pos = GetChunkCoordsFromPos(pos) - for i=-chunk_radius,chunk_radius do - for k=-chunk_radius,chunk_radius do - local x = c_pos.x+i - local y = c_pos.y+k - - if (global.chunk_regrow.map[x] == nil) then - global.chunk_regrow.map[x] = {} - end - global.chunk_regrow.map[x][y] = nil - table.insert(global.chunk_regrow.removal_list, {x=x,y=y}) - end - end -end - --- Marks a chunk a position that won't ever be deleted. -function OarcRegrowthOffLimitsChunk(pos) - local c_pos = GetChunkCoordsFromPos(pos) - - if (global.chunk_regrow.map[c_pos.x] == nil) then - global.chunk_regrow.map[c_pos.y] = {} - end - global.chunk_regrow.map[c_pos.x][c_pos.y] = -1 -end - - --- Marks a safe area around a position that won't ever be deleted. -function OarcRegrowthOffLimits(pos, chunk_radius) - local c_pos = GetChunkCoordsFromPos(pos) - for i=-chunk_radius,chunk_radius do - for k=-chunk_radius,chunk_radius do - local x = c_pos.x+i - local y = c_pos.y+k - - if (global.chunk_regrow.map[x] == nil) then - global.chunk_regrow.map[x] = {} - end - global.chunk_regrow.map[x][y] = -1 - end - end -end - --- Refreshes timers on a chunk containing position -function OarcRegrowthRefreshChunk(pos, bonus_time) - local c_pos = GetChunkCoordsFromPos(pos) - - if (global.chunk_regrow.map[c_pos.x] == nil) then - global.chunk_regrow.map[c_pos.y] = {} - end - if (global.chunk_regrow.map[c_pos.x][c_pos.y] ~= -1) then - global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick + bonus_time - end -end - --- Forcefully refreshes timers on a chunk containing position --- Will overwrite -1 flag. -function OarcRegrowthForceRefreshChunk(pos, bonus_time) - local c_pos = GetChunkCoordsFromPos(pos) - - if (global.chunk_regrow.map[c_pos.x] == nil) then - global.chunk_regrow.map[c_pos.y] = {} - end - global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick + bonus_time -end - - -- Refreshes timers on all chunks around a certain area -function OarcRegrowthRefreshArea(pos, chunk_radius, bonus_time) - local c_pos = GetChunkCoordsFromPos(pos) - - for i=-chunk_radius,chunk_radius do - for k=-chunk_radius,chunk_radius do - local x = c_pos.x+i - local y = c_pos.y+k - - if (global.chunk_regrow.map[x] == nil) then - global.chunk_regrow.map[x] = {} - end - if (global.chunk_regrow.map[x][y] ~= -1) then - global.chunk_regrow.map[x][y] = game.tick + bonus_time - end - end - end -end - --- Refreshes timers on all chunks near an ACTIVE radar -function OarcRegrowthSectorScan(event) - OarcRegrowthRefreshArea(event.radar.position, 14, 0) - OarcRegrowthRefreshChunk(event.chunk_position, 0) -end - --- Refresh all chunks near a single player. Cyles through all connected players. -function OarcRegrowthRefreshPlayerArea() - global.chunk_regrow.player_refresh_index = global.chunk_regrow.player_refresh_index + 1 - if (global.chunk_regrow.player_refresh_index > #game.connected_players) then - global.chunk_regrow.player_refresh_index = 1 - end - if (game.connected_players[global.chunk_regrow.player_refresh_index]) then - OarcRegrowthRefreshArea(game.connected_players[global.chunk_regrow.player_refresh_index].position, 4, 0) - end -end - --- Check each chunk in the 2d array for a timeout value -function OarcRegrowthCheckArray() - - -- Increment X - if (global.chunk_regrow.x_index > global.chunk_regrow.max_x) then - global.chunk_regrow.x_index = global.chunk_regrow.min_x - - -- Increment Y - if (global.chunk_regrow.y_index > global.chunk_regrow.max_y) then - global.chunk_regrow.y_index = global.chunk_regrow.min_y - DebugPrint("Finished checking regrowth array."..global.chunk_regrow.min_x.." "..global.chunk_regrow.max_x.." "..global.chunk_regrow.min_y.." "..global.chunk_regrow.max_y) - else - global.chunk_regrow.y_index = global.chunk_regrow.y_index + 1 - end - else - global.chunk_regrow.x_index = global.chunk_regrow.x_index + 1 - end - - -- Check row exists, otherwise make one. - if (global.chunk_regrow.map[global.chunk_regrow.x_index] == nil) then - global.chunk_regrow.map[global.chunk_regrow.x_index] = {} - end - - -- If the chunk has timed out, add it to the removal list - local c_timer = global.chunk_regrow.map[global.chunk_regrow.x_index][global.chunk_regrow.y_index] - if ((c_timer ~= nil) and (c_timer ~= -1) and ((c_timer+REGROWTH_TIMEOUT_TICKS) < game.tick)) then - - -- Check chunk actually exists - if (game.surfaces[GAME_SURFACE_NAME].is_chunk_generated({x=(global.chunk_regrow.x_index), - y=(global.chunk_regrow.y_index)})) then - table.insert(global.chunk_regrow.removal_list, {x=global.chunk_regrow.x_index, - y=global.chunk_regrow.y_index}) - global.chunk_regrow.map[global.chunk_regrow.x_index][global.chunk_regrow.y_index] = nil - end - end -end - --- Remove all chunks at same time to reduce impact to FPS/UPS -function OarcRegrowthRemoveAllChunks() - while (#global.chunk_regrow.removal_list > 0) do - local c_pos = table.remove(global.chunk_regrow.removal_list) - local c_timer = global.chunk_regrow.map[c_pos.x][c_pos.y] - - -- Confirm chunk is still expired - if (c_timer == nil) then - - -- Check for pollution - if (game.surfaces[GAME_SURFACE_NAME].get_pollution({c_pos.x*32,c_pos.y*32}) > 0) then - global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick - - -- Else delete the chunk - else - game.surfaces[GAME_SURFACE_NAME].delete_chunk(c_pos) - global.chunk_regrow.map[c_pos.x][c_pos.y] = nil - end - else - - -- DebugPrint("Chunk no longer expired?") - end - end -end - --- This is the main work function, it checks a single chunk in the list --- per tick. It works according to the rules listed in the header of this --- file. -function OarcRegrowthOnTick() - - -- Every half a second, refresh all chunks near a single player - -- Cyles through all players. Tick is offset by 2 - if ((game.tick % (30)) == 2) then - OarcRegrowthRefreshPlayerArea() - end - - -- Every tick, check a few points in the 2d array - -- According to /measured-command this shouldn't take more - -- than 0.1ms on average - for i=1,20 do - OarcRegrowthCheckArray() - end - - -- Send a broadcast warning before it happens. - if ((game.tick % REGROWTH_CLEANING_INTERVAL_TICKS) == REGROWTH_CLEANING_INTERVAL_TICKS-601) then - if (#global.chunk_regrow.removal_list > 100) then - SendBroadcastMsg("Map cleanup in 10 seconds, if you don't want to lose what you found drop a powered radar on it!") - end - end - - -- Delete all listed chunks - if ((game.tick % REGROWTH_CLEANING_INTERVAL_TICKS) == REGROWTH_CLEANING_INTERVAL_TICKS-1) then - if (#global.chunk_regrow.removal_list > 100) then - OarcRegrowthRemoveAllChunks() - SendBroadcastMsg("Map cleanup done, sorry for your loss.") - end - end -end - --- This function removes any chunks flagged but on demand. --- Controlled by the global.chunk_regrow.force_removal_flag --- This function may be used outside of the normal regrowth modse. -function OarcRegrowthForceRemovalOnTick() - -- Catch force remove flag - if (game.tick == global.chunk_regrow.force_removal_flag+60) then - SendBroadcastMsg("Map cleanup in 10 seconds, if you don't want to lose what you found drop a powered radar on it!") - end - if (game.tick == global.chunk_regrow.force_removal_flag+660) then - OarcRegrowthRemoveAllChunks() - SendBroadcastMsg("Map cleanup done, sorry for your loss.") - end -end \ No newline at end of file diff --git a/separate_spawns.lua b/separate_spawns.lua deleted file mode 100644 index a9c0cf8..0000000 --- a/separate_spawns.lua +++ /dev/null @@ -1,392 +0,0 @@ --- separate_spawns.lua --- Nov 2016 --- --- Code that handles everything regarding giving each player a separate spawn --- Includes the GUI stuff - - --------------------------------------------------------------------------------- --- EVENT RELATED FUNCTIONS --------------------------------------------------------------------------------- - --- When a new player is created, present the spawn options --- Assign them to the main force so they can communicate with the team --- without shouting. -function SeparateSpawnsPlayerCreated(event) - local player = game.players[event.player_index] - player.force = MAIN_FORCE - DisplayWelcomeTextGui(player) -end - - --- Check if the player has a different spawn point than the default one --- Make sure to give the default starting items -function SeparateSpawnsPlayerRespawned(event) - local player = game.players[event.player_index] - SendPlayerToSpawn(player) -end - - --- This is the main function that creates the spawn area --- Provides resources, land and a safe zone -function SeparateSpawnsGenerateChunk(event) - local surface = event.surface - local chunkArea = event.area - - -- This handles chunk generation near player spawns - -- If it is near a player spawn, it does a few things like make the area - -- safe and provide a guaranteed area of land and water tiles. - SetupAndClearSpawnAreas(surface, chunkArea, global.uniqueSpawns) -end - - --- Call this if a player leaves the game --- Still seems to have a bug. -function FindUnusedSpawns(event) - local player = game.players[event.player_index] - if (player.online_time < MIN_ONLINE_TIME) then - - DropGravestoneChests(player) - - -- Clear out global variables for that player - if (global.playerSpawns[player.name] ~= nil) then - global.playerSpawns[player.name] = nil - end - - -- Remove them from the delayer spawn queue if they are in it - for i=#global.delayedSpawns,1,-1 do - delayedSpawn = global.delayedSpawns[i] - - if (player.name == delayedSpawn.playerName) then - table.remove(global.delayedSpawns, i) - DebugPrint("Removing player from delayed spawn queue: " .. player.name) - end - end - - -- Transfer or remove a shared spawn if player is owner - if (global.sharedSpawns[player.name] ~= nil) then - - local teamMates = global.sharedSpawns[player.name].players - - if (#teamMates >= 1) then - local newOwnerName = table.remove(teamMates) - TransferOwnershipOfSharedSpawn(player.name, newOwnerName) - else - global.sharedSpawns[player.name] = nil - end - end - - -- If a uniqueSpawn was created for the player, mark it as unused. - if (global.uniqueSpawns[player.name] ~= nil) then - - local spawnPos = global.uniqueSpawns[player.name].pos - - -- Check if it was near someone else's base. - nearOtherSpawn = false - for spawnPlayerName,otherSpawnPos in pairs(global.uniqueSpawns) do - if ((spawnPlayerName ~= player.name) and (getDistance(spawnPos, otherSpawnPos.pos) < (ENFORCE_LAND_AREA_TILE_DIST*3))) then - DebugPrint("Won't remove base as it's close to another spawn: " .. spawnPlayerName) - nearOtherSpawn = true - end - end - - if (ENABLE_ABANDONED_BASE_REMOVAL and not nearOtherSpawn) then - global.uniqueSpawns[player.name] = nil - - SendBroadcastMsg(player.name .. "'s base was marked for immediate clean up because they left within "..MIN_ONLINE_TIME_IN_MINUTES.." minutes of joining.") - OarcRegrowthMarkForRemoval(spawnPos, 10) - global.chunk_regrow.force_removal_flag = game.tick - else - table.insert(global.unusedSpawns, global.uniqueSpawns[player.name]) - global.uniqueSpawns[player.name] = nil - SendBroadcastMsg(player.name .. " base was freed up because they left within "..MIN_ONLINE_TIME_IN_MINUTES.." minutes of joining.") - end - end - - -- remove that player's cooldown setting - if (global.playerCooldowns[player.name] ~= nil) then - global.playerCooldowns[player.name] = nil - end - - -- Remove from shared spawn player slots (need to search all) - for _,sharedSpawn in pairs(global.sharedSpawns) do - for key,playerName in pairs(sharedSpawn.players) do - if (player.name == playerName) then - sharedSpawn.players[key] = nil; - end - end - end - - -- Remove a force if this player created it and they are the only one on it - if ((#player.force.players <= 1) and (player.force.name ~= MAIN_FORCE)) then - game.merge_forces(player.force, MAIN_FORCE) - end - - -- Remove the character completely - game.remove_offline_players({player}) - end -end - - --------------------------------------------------------------------------------- --- NON-EVENT RELATED FUNCTIONS --------------------------------------------------------------------------------- - --- Add a spawn to the shared spawn global --- Used for tracking which players are assigned to it, where it is and if --- it is open for new players to join -function CreateNewSharedSpawn(player) - global.sharedSpawns[player.name] = {openAccess=true, - position=global.playerSpawns[player.name], - players={}} -end - -function TransferOwnershipOfSharedSpawn(prevOwnerName, newOwnerName) - -- Transfer the shared spawn global - global.sharedSpawns[newOwnerName] = global.sharedSpawns[prevOwnerName] - global.sharedSpawns[newOwnerName].openAccess = false - global.sharedSpawns[prevOwnerName] = nil - - -- Transfer the unique spawn global - global.uniqueSpawns[newOwnerName] = global.uniqueSpawns[prevOwnerName] - global.uniqueSpawns[prevOwnerName] = nil - - game.players[newOwnerName].print("You have been given ownership of this base!") -end - --- Returns the number of players currently online at the shared spawn -function GetOnlinePlayersAtSharedSpawn(ownerName) - if (global.sharedSpawns[ownerName] ~= nil) then - - -- Does not count base owner - local count = 0 - - -- For each player in the shared spawn, check if online and add to count. - for _,player in pairs(game.connected_players) do - if (ownerName == player.name) then - count = count + 1 - end - - for _,playerName in pairs(global.sharedSpawns[ownerName].players) do - - if (playerName == player.name) then - count = count + 1 - end - end - end - - return count - else - return 0 - end -end - --- Get the number of currently available shared spawns --- This means the base owner has enabled access AND the number of online players --- is below the threshold. -function GetNumberOfAvailableSharedSpawns() - local count = 0 - - for ownerName,sharedSpawn in pairs(global.sharedSpawns) do - if (sharedSpawn.openAccess and - (game.players[ownerName] ~= nil) and - game.players[ownerName].connected) then - if ((MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN == 0) or - (GetOnlinePlayersAtSharedSpawn(ownerName) < MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN)) then - count = count+1 - end - end - end - - return count -end - - --- Initializes the globals used to track the special spawn and player --- status information -function InitSpawnGlobalsAndForces() - -- Containes an array of all player spawns - -- A secondary array tracks whether the character will respawn there. - if (global.playerSpawns == nil) then - global.playerSpawns = {} - end - if (global.uniqueSpawns == nil) then - global.uniqueSpawns = {} - end - if (global.sharedSpawns == nil) then - global.sharedSpawns = {} - end - if (global.unusedSpawns == nil) then - global.unusedSpawns = {} - end - if (global.playerCooldowns == nil) then - global.playerCooldowns = {} - end - if (global.waitingBuddies == nil) then - global.waitingBuddies = {} - end - if (global.delayedSpawns == nil) then - global.delayedSpawns = {} - end - if (global.buddySpawnOptions == nil) then - global.buddySpawnOptions = {} - end - - game.create_force(MAIN_FORCE) - game.forces[MAIN_FORCE].set_spawn_position(game.forces["player"].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) - - if ENABLE_SHARED_TEAM_VISION then - game.forces[MAIN_FORCE].share_chart = true - end - - SetCeaseFireBetweenAllForces() - SetFriendlyBetweenAllForces() - if (ENABLE_ANTI_GRIEFING) then - AntiGriefing(game.forces[MAIN_FORCE]) - end -end - - -function DoesPlayerHaveCustomSpawn(player) - for name,spawnPos in pairs(global.playerSpawns) do - if (player.name == name) then - return true - end - end - return false -end - -function ChangePlayerSpawn(player, pos) - global.playerSpawns[player.name] = pos - global.playerCooldowns[player.name] = {setRespawn=game.tick} -end - -function QueuePlayerForDelayedSpawn(playerName, spawn, moatEnabled) - - -- If we get a valid spawn point, setup the area - if ((spawn.x ~= 0) and (spawn.y ~= 0)) then - global.uniqueSpawns[playerName] = {pos=spawn,moat=moatEnabled} - - local delay_spawn_seconds = 5*(math.ceil(ENFORCE_LAND_AREA_TILE_DIST/CHUNK_SIZE)) - - game.players[playerName].print("Generating your spawn now, please wait a few for " .. delay_spawn_seconds .. " seconds...") - game.players[playerName].surface.request_to_generate_chunks(spawn, 4) - delayedTick = game.tick + delay_spawn_seconds*TICKS_PER_SECOND - table.insert(global.delayedSpawns, {playerName=playerName, spawn=spawn, moatEnabled=moatEnabled, delayedTick=delayedTick}) - - DisplayPleaseWaitForSpawnDialog(game.players[playerName], delay_spawn_seconds) - - else - DebugPrint("THIS SHOULD NOT EVER HAPPEN! Spawn failed!") - SendBroadcastMsg("ERROR!! Failed to create spawn point for: " .. playerName) - end -end - - --- Check a table to see if there are any players waiting to spawn --- Check if we are past the delayed tick count --- Spawn the players and remove them from the table. -function DelayedSpawnOnTick() - if ((game.tick % (30)) == 1) then - if ((global.delayedSpawns ~= nil) and (#global.delayedSpawns > 0)) then - for i=#global.delayedSpawns,1,-1 do - delayedSpawn = global.delayedSpawns[i] - - if (delayedSpawn.delayedTick < game.tick) then - -- TODO, add check here for if chunks around spawn are generated surface.is_chunk_generated(chunkPos) - if (game.players[delayedSpawn.playerName] ~= nil) then - SendPlayerToNewSpawnAndCreateIt(delayedSpawn.playerName, delayedSpawn.spawn, delayedSpawn.moatEnabled) - end - table.remove(global.delayedSpawns, i) - end - end - end - end -end - -function SendPlayerToNewSpawnAndCreateIt(playerName, spawn, moatEnabled) - - -- Make sure the area is super safe. - ClearNearbyEnemies(spawn, SAFE_AREA_TILE_DIST, game.surfaces[GAME_SURFACE_NAME]) - - -- Create the spawn resources here - CreateWaterStrip(game.surfaces[GAME_SURFACE_NAME], - {x=spawn.x+WATER_SPAWN_OFFSET_X, y=spawn.y+WATER_SPAWN_OFFSET_Y}, - WATER_SPAWN_LENGTH) - CreateWaterStrip(game.surfaces[GAME_SURFACE_NAME], - {x=spawn.x+WATER_SPAWN_OFFSET_X, y=spawn.y+WATER_SPAWN_OFFSET_Y+1}, - WATER_SPAWN_LENGTH) - GenerateStartingResources(game.surfaces[GAME_SURFACE_NAME], spawn) - - -- Send the player to that position - game.players[playerName].teleport(spawn, GAME_SURFACE_NAME) - GivePlayerStarterItems(game.players[playerName]) - - -- Chart the area. - ChartArea(game.players[playerName].force, game.players[playerName].position, math.ceil(ENFORCE_LAND_AREA_TILE_DIST/CHUNK_SIZE), game.players[playerName].surface) - - if (game.players[playerName].gui.center.wait_for_spawn_dialog ~= nil) then - game.players[playerName].gui.center.wait_for_spawn_dialog.destroy() - end -end - -function SendPlayerToSpawn(player) - if (DoesPlayerHaveCustomSpawn(player)) then - player.teleport(global.playerSpawns[player.name], GAME_SURFACE_NAME) - else - player.teleport(game.forces[MAIN_FORCE].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) - end -end - -function SendPlayerToRandomSpawn(player) - local numSpawns = TableLength(global.uniqueSpawns) - local rndSpawn = math.random(0,numSpawns) - local counter = 0 - - if (rndSpawn == 0) then - player.teleport(game.forces[MAIN_FORCE].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) - else - counter = counter + 1 - for name,spawn in pairs(global.uniqueSpawns) do - if (counter == rndSpawn) then - player.teleport(spawn.pos) - break - end - counter = counter + 1 - end - end -end - -function CreatePlayerCustomForce(player) - local newForce = nil - - -- Check if force already exists - if (game.forces[player.name] ~= nil) then - DebugPrint("Force already exists!") - player.force = game.forces[player.name] - return game.forces[player.name] - - -- Create a new force using the player's name - elseif (TableLength(game.forces) < MAX_FORCES) then - newForce = game.create_force(player.name) - if ENABLE_SHARED_TEAM_VISION then - newForce.share_chart = true - end - -- Chart silo areas if necessary - if FRONTIER_ROCKET_SILO_MODE and ENABLE_SILO_VISION then - ChartRocketSiloAreas(game.surfaces[GAME_SURFACE_NAME], newForce) - end - player.force = newForce - SetCeaseFireBetweenAllForces() - SetFriendlyBetweenAllForces() - if (ENABLE_ANTI_GRIEFING) then - AntiGriefing(newForce) - end - SendBroadcastMsg(player.name.." has started their own team!") - else - player.force = MAIN_FORCE - player.print("Sorry, no new teams can be created. You were assigned to the default team instead.") - end - - return newForce -end