1
0
mirror of https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git synced 2024-12-04 09:43:00 +02:00

Merge pull request from Oarcinae/space_age_v2_mod

Version 2.0 Mod Release
This commit is contained in:
Oarcinae 2024-09-25 13:15:05 -04:00 committed by GitHub
commit d8504739b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 8405 additions and 8301 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
config.lua
releases

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016 Oarcinae
Copyright (c) 2024 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

View File

@ -1,22 +1,26 @@
# FactorioScenarioMultiplayerSpawn
A custom scenario for allowing separate spawn locations in multiplayer. Designed for Co-op and PvE.
# OARC Multiplayer Spawn
A Factorio mod for allowing separate spawn locations in multiplayer. Designed for Co-op and PvE.
## WORK IN PROGRESS
This is the currently in development branch for Factorio V2.0. It is not yet complete. If you want a stable version, please use the latest release.
## 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.
## Versions
Check releases for stable versions. 0.17 and 0.16 have final stable versions that will no longer be maintained.
The latest master is for 1.0.0+
Check releases for stable versions.
2.0.X WILL BE the currently in work version that will support Factorio V2.0 with and without the Space Age expansion.
1.1.X is the latest stable version for V1.0 that I plan to do minimal maintenance on.
0.17 and 0.16 have final stable versions that will no longer be maintained.
## Status
At this time I have no more planned features and will only be in support and maintenance mode.
Currently working on adding support for V2.0 Factorio.
Will be removing many of the soft-mod features and other feature creep things for the first version.
Waiting on Space Age release so I can start working on support for that.
## Credit
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
Thanks to https://github.com/vfinn (JustGoFly) and many others for their assistance!
https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn/graphs/contributors
## Random Notes
Feel free to submit bugs/fixes/requests/pulls/forks whatever you want.
@ -24,5 +28,5 @@ 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
https://discord.gg/trnpcen
oarcinae@gmail.com

22
changelog.txt Normal file
View File

@ -0,0 +1,22 @@
---------------------------------------------------------------------------------------------------
Version: 2.0.0
Date: 2024-09-25
Major Features:
- Initial release of a formal mod (non-scenario) version. This is essentially a rewrite of the original scenario in mod form.
- Added support for spawning on different surfaces in preparation for space age support.
- Updated all GUIs to be more in line with style recommendations and to be more user friendly.
- New preview GUI while spawn is being generated and instant transition to the new spawn when ready.
- Exposed most of the mod settings in the custom in game mod GUI. This provides a nicer interface than the native mod settings allows.
- New holding pen surface with restricted permissions for players to spawn into before being moved to their primary spawn.
- Regrowth and world eater features can safely be toggled on/off at any time and supports multiple surfaces.
Bugfixes:
- Fixed an issue where active radars would block spawn areas from being cleaned up if a player left within the removal window.
- Fixed an issue where regrowth would sometimes delete chunks with vehicles, robots or spidertron in them.
- Added several mods as hidden dependencies to avoid fatal errors on startup due to incompatible load order issues.
Optimizations:
- New implementation of shared electricity should stop any possibility of desync with the old method as well as improve performance. (Uses cross-surface power connections.)
- New implementation of shared items should also improve performance. (Uses linked-chest.)
- Some changes to regrowth to improve performance (reducing unnecessary refresh areas).
Info:
- Several of the soft-mod features have been removed (including the coin shop).
- Space age support will not be available until some time after it is released. I will need time to test and implement it. For now, this is provided as a feature for experimentation. You can test "secondary" spawns by enabling that feature in the settings, it is currently disabled by default.

View File

@ -1,14 +0,0 @@
Compat = Compat or {}
function Compat.handle_factoriomaps()
if remote.interfaces.factoriomaps then
script.on_event(remote.call("factoriomaps", "get_start_capture_event_id"), function()
print("Starting factoriomaps-oarc integration script")
remote.call("factoriomaps", "surface_set_default", "oarc")
end)
end
end

View File

@ -1,376 +1,219 @@
-- control.lua
-- Mar 2019
-- Sep 2024
-- ____ _____ _____
-- / __ \ /\ | __ \ / ____|
-- | | | | / \ | |__) | |
-- | | | |/ /\ \ | _ /| |
-- | |__| / ____ \| | \ \| |____
-- \____/_/ \_\_| \_\\_____|
-- 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:
-- 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
-- if you can.
-- Oarc's Separated Spawn MOD V2
-- I decided to rewrite my old scenario due to the coming changes in Factorio V2.0 and its new Space Age Expansion.
-- Change Overview:
-- Support the scenario "as a mod" ONLY. Scenario merely provides a way to overwrite settings on_init.
-- Removed a lot of unnecessary feature bloat.
-- Move text to locale files where possible.
-- Major Features:
-- Core feature allows for a safe, separate spawn area for each player.
-- Players can choose to spawn with friends (buddy spawn) or join other bases.
-- Offline protection from enemy attacks.
-- Chunk cleanup to keep save file size down.
-- Sharing of electricity and items between players.
-- 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 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("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/config")
require("lib/config_parser")
require("lib/regrowth_map")
require("lib/shared_chests")
require("lib/notepad")
require("lib/map_features")
require("lib/oarc_buy")
require("lib/auto_decon_miners")
-- For Philip. I currently do not use this and need to add proper support for
-- commands like this in the future.
-- 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("lib/holding_pen")
require("lib/separate_spawns")
require("lib/separate_spawns_guis")
require("lib/oarc_enemies")
require("lib/oarc_gui_tabs")
require("lib/offline_protection")
require("lib/scaled_enemies")
require("lib/sharing")
-- compatibility with mods
require("compat/factoriomaps")
-- TODO: Possibly remove this later?
require("lib/oarc_tests")
-- Create a new surface so we can modify map settings at the start.
GAME_SURFACE_NAME="oarc"
commands.add_command("trigger-map-cleanup",
"Force immediate removal of all expired chunks (unused chunk removal mod)",
RegrowthForceRemoveChunksCmd)
--------------------------------------------------------------------------------
-- ALL EVENT HANLDERS ARE HERE IN ONE PLACE!
-- On Init - Only runs once the first time the game starts
--------------------------------------------------------------------------------
----------------------------------------
-- On Init - only runs once the first
-- time the game starts
----------------------------------------
script.on_init(function(event)
-- FIRST
InitOarcConfig()
-- Regrowth (always init so we can enable during play.)
ValidateAndLoadConfig()
RegrowthInit()
-- Create new game surface
CreateGameSurface()
-- MUST be before other stuff, but after surface creation.
InitSpawnGlobalsAndForces()
CreateHoldingPenSurface() -- Must be after init spawn globals?
-- Frontier Silo Area Generation
if (global.ocfg.frontier_rocket_silo and not global.ocfg.enable_magic_factories) then
SpawnSilosAndGenerateSiloAreas()
-- Useful for debugging and if players choose not to use the provided empty scenario.
if remote.interfaces["freeplay"] then
log("Freeplay interface detected. Disabling various freeplay features now!")
remote.call("freeplay", "set_skip_intro", true)
remote.call("freeplay", "set_disable_crashsite", true)
remote.call("freeplay", "set_created_items", {})
remote.call("freeplay", "set_respawn_items", {})
end
-- Everyone do the shuffle. Helps avoid always starting at the same location.
-- Needs to be done after the silo spawning.
if (global.ocfg.enable_vanilla_spawns) then
global.vanillaSpawns = FYShuffle(global.vanillaSpawns)
log("Vanilla spawns:")
log(serpent.block(global.vanillaSpawns))
-- If there are any players that already exist, init them now.
for _,player in pairs(game.players) do
SeparateSpawnsInitPlayer(player.index)
end
Compat.handle_factoriomaps()
if (global.ocfg.enable_coin_shop and global.ocfg.enable_chest_sharing) then
SharedChestInitItems()
end
if (global.ocfg.enable_coin_shop and global.ocfg.enable_magic_factories) then
MagicFactoriesInit()
end
OarcMapFeatureInitGlobalCounters()
OarcAutoDeconOnInit()
-- Display starting point text as a display of dominance.
RenderPermanentGroundText(game.surfaces[GAME_SURFACE_NAME], {x=-29,y=-30}, 40, "OARC", {0.9, 0.7, 0.3, 0.8})
end)
script.on_load(function()
Compat.handle_factoriomaps()
end)
--------------------------------------------------------------------------------
-- On Configuration Changed - Only runs when the mod configuration changes
--------------------------------------------------------------------------------
-- oarc_new_spawn_created = script.generate_event_name()
-- script.on_configuration_changed(function(data)
-- -- Regenerate event ID:
-- end)
----------------------------------------
-- Rocket launch event
-- Used for end game win conditions / unlocking late game stuff
----------------------------------------
script.on_event(defines.events.on_rocket_launched, function(event)
RocketLaunchEvent(event)
end)
----------------------------------------
-- Chunk Generation
----------------------------------------
script.on_event(defines.events.on_chunk_generated, function(event)
if (event.surface.name ~= GAME_SURFACE_NAME) then return end
if global.ocfg.enable_regrowth then
RegrowthChunkGenerate(event)
end
if global.ocfg.enable_undecorator then
UndecorateOnChunkGenerate(event)
end
SeparateSpawnsGenerateChunk(event)
CreateHoldingPen(event.surface, event.area)
end)
----------------------------------------
-- Gui Click
----------------------------------------
script.on_event(defines.events.on_gui_click, function(event)
-- 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
WelcomeTextGuiClick(event)
SpawnOptsGuiClick(event)
SpawnCtrlGuiClick(event)
SharedSpwnOptsGuiClick(event)
BuddySpawnOptsGuiClick(event)
BuddySpawnWaitMenuClick(event)
BuddySpawnRequestMenuClick(event)
SharedSpawnJoinWaitMenuClick(event)
ClickOarcGuiButton(event)
if global.ocfg.enable_coin_shop then
ClickOarcStoreButton(event)
end
GameOptionsGuiClick(event)
end)
script.on_event(defines.events.on_gui_checked_state_changed, function (event)
SpawnOptsRadioSelect(event)
SpawnCtrlGuiOptionsSelect(event)
end)
script.on_event(defines.events.on_gui_selected_tab_changed, function (event)
TabChangeOarcGui(event)
if global.ocfg.enable_coin_shop then
TabChangeOarcStore(event)
end
script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
if (not StringStartsWith(event.setting, "oarc-mod")) then return end
RuntimeModSettingChanged(event)
end)
----------------------------------------
-- Player Events
----------------------------------------
script.on_event(defines.events.on_player_joined_game, function(event)
PlayerJoinedMessages(event)
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.
player.teleport({x=0,y=0}, GAME_SURFACE_NAME)
if global.ocfg.enable_long_reach then
GivePlayerLongReach(player)
end
SeparateSpawnsPlayerCreated(event.player_index, true)
InitOarcGuiTabs(player)
if global.ocfg.enable_coin_shop then
InitOarcStoreGuiTabs(player)
end
SeparateSpawnsInitPlayer(event.player_index)
end)
script.on_event(defines.events.on_player_respawned, function(event)
SeparateSpawnsPlayerRespawned(event)
PlayerRespawnItems(event)
if global.ocfg.enable_long_reach then
GivePlayerLongReach(game.players[event.player_index])
end
end)
script.on_event(defines.events.on_player_left_game, function(event)
ServerWriteFile("player_events", game.players[event.player_index].name .. " left the game." .. "\n")
local player = game.players[event.player_index]
-- If players leave early, say goodbye.
if (player and (player.online_time < (global.ocfg.minimum_online_time * TICKS_PER_MINUTE))) then
log("Player left early: " .. player.name)
SendBroadcastMsg(player.name .. "'s base was marked for immediate clean up because they left within "..global.ocfg.minimum_online_time.." minutes of joining.")
RemoveOrResetPlayer(player, true, true, true, true)
end
SeparateSpawnsPlayerLeft(event)
end)
-- script.on_event(defines.events.on_player_removed, function(event)
-- Player is already deleted when this is called.
-- end)
script.on_event(defines.events.on_player_changed_surface, function(event)
SeparateSpawnsPlayerChangedSurface(event)
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 (global.ocfg.gameplay.enable_shared_team_chat) then
if (event.player_index ~= nil) then
ShareChatBetweenForces(game.players[event.player_index], event.message)
end
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 global.ocfg.enable_regrowth then
RegrowthOnTick()
RegrowthForceRemovalOnTick()
end
DelayedSpawnOnTick()
if global.ocfg.enable_chest_sharing then
SharedChestsOnTick()
end
if (global.ocfg.enable_chest_sharing and global.ocfg.enable_magic_factories) then
MagicFactoriesOnTick()
end
TimeoutSpeechBubblesOnTick()
FadeoutRenderOnTick()
if global.ocfg.enable_miner_decon then
OarcAutoDeconOnTick()
if global.ocfg.regrowth.enable_regrowth then
RegrowthOnTick()
end
RegrowthForceRemovalOnTick() -- Allows for abandoned base cleanup without regrowth enabled.
if global.ocfg.gameplay.modified_enemy_spawning then
RestrictEnemyEvolutionOnTick()
end
end)
----------------------------------------
-- Chunk Generation
----------------------------------------
script.on_event(defines.events.on_chunk_generated, function(event)
if global.ocfg.regrowth.enable_regrowth then
RegrowthChunkGenerate(event)
end
CreateHoldingPenChunks(event)
SeparateSpawnsGenerateChunk(event)
if global.ocfg.gameplay.modified_enemy_spawning then
DowngradeWormsDistanceBasedOnChunkGenerate(event)
DowngradeAndReduceEnemiesOnChunkGenerate(event)
end
end)
----------------------------------------
-- Radar Scanning
----------------------------------------
script.on_event(defines.events.on_sector_scanned, function (event)
if global.ocfg.enable_regrowth then
if global.ocfg.regrowth.enable_regrowth then
RegrowthSectorScan(event)
end
end)
----------------------------------------
-- Surface Generation
----------------------------------------
-- This is not called when the default surface "nauvis" is created as it will always exist!
script.on_event(defines.events.on_surface_created, function(event)
log("Surface created: " .. game.surfaces[event.surface_index].name)
SeparateSpawnsSurfaceCreated(event)
RegrowthSurfaceCreated(event)
end)
script.on_event(defines.events.on_pre_surface_deleted, function(event)
log("Surface deleted: " .. game.surfaces[event.surface_index].name)
SeparateSpawnsSurfaceDeleted(event)
RegrowthSurfaceDeleted(event)
end)
----------------------------------------
-- Various on "built" events
----------------------------------------
script.on_event(defines.events.on_built_entity, function(event)
if global.ocfg.enable_autofill then
Autofill(event)
if global.ocfg.regrowth.enable_regrowth then
RegrowthMarkAreaSafeGivenTilePos(event.created_entity.surface.name, event.created_entity.position, 2, false)
end
if global.ocfg.enable_regrowth then
if (event.created_entity.surface.name ~= GAME_SURFACE_NAME) then return end
RegrowthMarkAreaSafeGivenTilePos(event.created_entity.position, 2, false)
end
if global.ocfg.enable_anti_grief then
SetItemBlueprintTimeToLive(event)
end
if global.ocfg.frontier_rocket_silo then
BuildSiloAttempt(event)
end
-- For tracking spidertrons...
RegrowthOnBuiltEntity(event)
-- if global.ocfg.enable_anti_grief then
-- SetItemBlueprintTimeToLive(event)
-- end
end)
script.on_event(defines.events.on_robot_built_entity, function (event)
if global.ocfg.enable_regrowth then
if (event.created_entity.surface.name ~= GAME_SURFACE_NAME) then return end
RegrowthMarkAreaSafeGivenTilePos(event.created_entity.position, 2, false)
end
if global.ocfg.frontier_rocket_silo then
BuildSiloAttempt(event)
if global.ocfg.regrowth.enable_regrowth then
RegrowthMarkAreaSafeGivenTilePos(event.created_entity.surface.name, event.created_entity.position, 2, false)
end
end)
script.on_event(defines.events.on_player_built_tile, function (event)
if global.ocfg.enable_regrowth then
if (game.surfaces[event.surface_index].name ~= GAME_SURFACE_NAME) then return end
for k,v in pairs(event.tiles) do
RegrowthMarkAreaSafeGivenTilePos(v.position, 2, false)
if global.ocfg.regrowth.enable_regrowth then
for _,v in pairs(event.tiles) do
RegrowthMarkAreaSafeGivenTilePos(game.surfaces[event.surface_index].name, v.position, 2, false)
end
end
end)
--If a player gets in or out of a vehicle, mark the area as safe so we don't delete the vehicle by accident.
--Only world eater will clean up these chunks over time if it is enabled.
script.on_event(defines.events.on_player_driving_changed_state, function (event)
if global.ocfg.regrowth.enable_regrowth then
RegrowthMarkAreaSafeGivenTilePos(event.entity.surface.name, event.entity.position, 1, false)
end
end)
----------------------------------------
-- 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
if (event.entity.surface.name ~= GAME_SURFACE_NAME) then return end
RegrowthMarkAreaSafeGivenTilePos(event.entity.position, 2, false)
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 (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
end
end)
----------------------------------------
-- On Research Finished
-- This is where you can permanently remove researched techs
----------------------------------------
script.on_event(defines.events.on_research_finished, function(event)
-- 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.ocore.satellite_sent or not global.ocore.satellite_sent[event.research.force.name]) then
for _,v in ipairs(LOCKED_RECIPES) do
RemoveRecipe(event.research.force, v.r)
end
if global.ocfg.regrowth.enable_regrowth then
RegrowthMarkAreaSafeGivenTilePos(event.entity.surface.name, event.entity.position, 2, false)
end
end)
@ -379,13 +222,15 @@ end)
-- 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
-- if (global.ocfg.gameplay.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)
if (global.ocfg.gameplay.modified_enemy_spawning) then
-- ModifyEnemySpawnsNearPlayerStartingAreas(event)
ChangeEnemySpawnersToOtherForceOnBuilt(event)
end
end)
@ -394,58 +239,83 @@ end)
-- This is where I remove biter waves on offline players
----------------------------------------
script.on_event(defines.events.on_unit_group_finished_gathering, function(event)
if (global.ocfg.enable_offline_protect) then
OarcModifyEnemyGroup(event.group)
if (global.ocfg.gameplay.enable_offline_protection) then
OarcModifyEnemyGroup(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.
-- Gui Events
----------------------------------------
script.on_event(defines.events.on_character_corpse_expired, function(event)
DropGravestoneChestFromCorpse(event.corpse)
script.on_event(defines.events.on_gui_click, function(event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
SeparateSpawnsGuiClick(event)
ClickOarcGuiButton(event)
ServerInfoGuiClick(event)
SpawnCtrlGuiClick(event)
SettingsControlsTabGuiClick(event)
SettingsSurfaceControlsTabGuiClick(event)
end)
--- Called when LuaGuiElement checked state is changed (related to checkboxes and radio buttons).
script.on_event(defines.events.on_gui_checked_state_changed, function (event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
----------------------------------------
-- On Gui Text Change
-- For capturing text entry.
----------------------------------------
script.on_event(defines.events.on_gui_text_changed, function(event)
NotepadOnGuiTextChange(event)
SeparateSpawnsGuiCheckedStateChanged(event)
SpawnCtrlGuiOptionsSelect(event)
end)
script.on_event(defines.events.on_gui_selected_tab_changed, function (event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
OarcGuiSelectedTabChanged(event)
end)
----------------------------------------
-- On Gui Closed
-- For capturing player escaping custom GUI so we can close it using ESC key.
----------------------------------------
script.on_event(defines.events.on_gui_closed, function(event)
OarcGuiOnGuiClosedEvent(event)
if global.ocfg.enable_coin_shop then
OarcStoreOnGuiClosedEvent(event)
end
OarcGuiClosed(event)
end)
--- For sliders and other value changing elements.
script.on_event(defines.events.on_gui_value_changed, function(event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
SeparateSpawnsGuiValueChanged(event)
SettingsControlsTabGuiValueChanged(event)
end)
--- For dropdowns and listboxes.
script.on_event(defines.events.on_gui_selection_state_changed, function(event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
SeparateSpawnsGuiSelectionStateChanged(event)
SettingsControlsTabGuiSelectionStateChanged(event)
end)
script.on_event(defines.events.on_gui_text_changed, function(event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
SettingsControlsTabGuiTextChanged(event)
end)
script.on_event(defines.events.on_gui_confirmed, function(event)
if not event.element.valid then return end -- Should we ever react to invalid GUI elements?
SettingsControlsTabGuiTextconfirmed(event)
end)
----------------------------------------
-- On enemies killed
-- For coin generation and stuff
-- Remote Interface
----------------------------------------
script.on_event(defines.events.on_post_entity_died, function(event)
if (game.surfaces[event.surface_index].name ~= GAME_SURFACE_NAME) then return end
if global.ocfg.enable_coin_shop then
CoinsFromEnemiesOnPostEntityDied(event)
end
end,
{{filter="type", type = "unit"}, {filter="type", type = "unit-spawner"}, {filter="type", type = "turret"}})
local oarc_mod_interface =
{
get_mod_settings = function()
return OCFG
end
}
----------------------------------------
-- Scripted auto decon for miners...
----------------------------------------
script.on_event(defines.events.on_resource_depleted, function(event)
if global.ocfg.enable_miner_decon then
OarcAutoDeconOnResourceDepleted(event)
end
end)
remote.add_interface("oarc_mod", oarc_mod_interface)

27
data.lua Normal file
View File

@ -0,0 +1,27 @@
-- I created a few custom entities for making it clear that the shared power poles and chests are special.
local oarc_linked_chest=table.deepcopy(data.raw["container"]["wooden-chest"])
oarc_linked_chest.type="linked-container"
oarc_linked_chest.name="oarc-linked-chest"
oarc_linked_chest.inventory_type="with_filters_and_bar"
oarc_linked_chest.inventory_size=settings.startup["oarc-mod-linked-chest-size"].value --[[@as integer]]
oarc_linked_chest.picture.layers[1].filename = "__oarc-mod__/graphics/oarc-linked-chest.png"
oarc_linked_chest.picture.layers[1].hr_version.filename = "__oarc-mod__/graphics/hr-oarc-linked-chest.png"
local oarc_linked_power=table.deepcopy(data.raw["electric-pole"]["small-electric-pole"])
oarc_linked_power.name="oarc-linked-power"
oarc_linked_power.pictures.layers[1].filename = "__oarc-mod__/graphics/oarc-electric-pole.png"
oarc_linked_power.pictures.layers[1].hr_version.filename = "__oarc-mod__/graphics/hr-oarc-electric-pole.png"
data:extend({
{
type = "sprite",
name = "oarc-mod-sprite-40",
filename = "__oarc-mod__/icon_40x40.png",
width = 40,
height = 40
},
oarc_linked_chest, oarc_linked_power
})

141
devplan.txt Normal file
View File

@ -0,0 +1,141 @@
ACTIVE ITEMS:
------------------------------------------------------------------------------------------------------------------------
BACKLOG:
Not specific:
Minor:
- If dead when resetting spawn... possibly delay the opening of the welcome GUI or block spawning until character is spawned?
- Expose old enemy scaling as an option? And/or remove unnecessary checks/logs
- Refresh players in admin controls when dropdown is clicked
- Add a setting for forcing primary spawns to only be on default surface maybe?
Performance:
- User on_nth_tick for any tick % (modulo) operations.
- SeparateSpawnsGenerateChunk should only search for closest spawn once and pass to sub functions!
- Rework world eater to use less find_entities_filtered
Major:
- Space Age Support (TBD)
------------------------------------------------------SPACE AGE---------------------------------------------------------
- Map Settings changes?
- Pollution changes (regrowth)?
- Enemy changes?
- Landing pad locations per FORCE limited to 1?
- Surface names for space ships?
- Spawner health tied to evolution?
- Respawn position is surface specific? Each surface needs a separate respawn point? Default respawn behavior?
- Confirm launch into scenario works (V2.0 fix supposedly) -- https://forums.factorio.com/110708
- Radar quality affects regrowth safe range?
- Update electric pole connections for shared power if things change in V2.0
------------------------------------------------------------------------------------------------------------------------
Other Ideas, Not Committed:
- Add option to spawn on existing chunks (look for chunks with any entities in them, or use regrowth logic)
- Add option for spawn pen to be on a specified surface (not sure this serves any real purpose)
- Change "search for ungenerated spawn point" to be a "roll" button that the player can re-roll? Maybe it shows on the map in an icon where they might go first?
- Make players join a "holding_pen" force instead of the main force? Might cause issues with chat and vision?
- Separate chest sharing and electricity ()
- Consider scrapping any overlapping mod settings from the config. ONLY keep the part that can't easily be done in the mod settings menu... NOT SURE about this.
- Change enable_shared_team_vision to allow players to change this per player (like BNO)
- Change enable_friendly_fire to be per team?
- Allow players to spawn "near" an existing player (by request)
- Allow players to restart at anytime via GUI button (configurable setting by admin)
- Change regrowth to be list of surfaces indexed by surface name?
- Figure out how to reset player inventory on player reset to avoid extra items? (save and load items?)
- Work on space ex support?
- Profile regrowth and try to improve performance ?
- Possibly adjust easy/medium evo factors on new player joined?
- Convert regrowth to a proper mod
- Create shared electricty with a LIMITED output transfer rate using a custom accumulator?
- Cleanup offline protection (Lots of commented out code)
- Custom tips and tricks??
--------------------------------------------------------DONE------------------------------------------------------------
- First setup the mod settings and lua global settings.
- Test out removing the creation of a custom surface and ensure map settings can be set as needed. Possibly create a separate surface, or just a separate area away from the origin, for starting players.
- Start copying in the core pieces required for the primary spawning mechanic.
- Document config and mod settings using Lua annotation as a custom class
- Document global ocore as a custom class (with subclasses/types as needed)
- Add multiple surfaces to options/settings [As a single boolean.]
- Convert scenario to a mod.
- Add multiple surfaces to Spawn GUI (Drop down selection?)
- Configurable welcome/server messages in mod settings.
- Check and update all functions using surfaces to clearly use either the LuaSurface obj OR a string name.
- Create server settings admin GUI tab
- Figure out how to define custom lua table/data structs to make syntax/linting work?
- Setup multiplayer testing using multiple instances and some batch files.
- Change Near/Far buttons to radio selection w/ text explanation and have a single Spawn button.
- Refactor the spawn menu GUI (don't destroy the menu unless we need to, refresh only the elements we need to, save data to a global using tags?)
- Remove separate buddy spawn menu?
- Remove the shared spawn separate GUI window?
- Redo DisplayBuddySpawnRequestMenu and DisplayBuddySpawnWaitMenu and move their events to the new event handlers
- Refresh the spawn controls GUI when player accepts/rejects
- Redo FindUngeneratedCoordinates to pick a random direction, and use the distance to get a starting point, instead of multiple tries.
- Fix all GUI styling (buttons outside of content)
- Force enable_world_eater to require enable_regrowth
- Add warning for modifying surface settings after gameplay has started
- Tooltips for GUI elements in spawn menu options!
- Check all settings to see which CAN'T be changed during runtime. Possibly move these to startup.
- FIGURE OUT CHUNK NOT IN MAP REGROWTH ISSUE!
- Add validation for default starting surface name
- Make server info headings consistent / Add heading for spawn controls (change button?)
- Confirm regrowth should add new chunks when found (not only on chunk generate?)
- Make disable main team setting work (hide radio button)
- Support run time toggling of enable_shared_team_vision
- Support run time toggling of enable_friendly_fire
- Watch for chunks being generated to be able to move the player to their spawn as soon as it is done with the last chunk.
- If this is their first spawn, give them items. Otherwise don't give new items and don't clear items either?
- Change enable_spawning_on_other_surfaces to a start up setting?
- Test out space expansion
- Compare GUI mod button icons to space ex (looks better??)
- Create surface blacklist setting.
- Enable/Disable and show information about shared spawn based on shared spawn dropdown interactions.
- Ensure updates to regrowth surfaces don't cause errors (update indexes)
- Confirm regrowth does or does not delete chunks with robots in them?
- Show the surface name in shared spawn join GUI
- Show the surface name in the share spawn controls tab
- Add rich text map location in spawn controls tab for current home location
- Move sharedspawns data under unique spawns
- "uniqueSpawns" should have a "primary" flag and be indexed by surface FIRST
- Make respawn locations first be indexed by player, then surface
- Offline protection re-implement!
- Resolve regrowth issue with radars and confirm that when we mark chunks for removal, they can be refreshed still. (trace logic!)
- Move "buddy" info to unique_spawns as well.
- Fix search vector to use more variable vector'ing, always normalize vector, and then ensure the other reliant functions work still.
- Add regrowth settings GUI tab? Not sure how the other settings fit in with a dedicated regrowth tab? Need to be able to enable/disable other surfaces during runtime?
- Test multiple enemy forces to provide a way to scale evolution locally (need to continually set the evo factor back)
- Change main_force_name to a startup setting! New players in spawn area should stay on default "player" force to avoid mod conflicts on player init?
- In spawn controls, add a note if spawn is full (and maybe disable the shared spawn checkbox?)
- Add rich text map location links for new spawns (print to chat)
- Add in square bases again
- TEST resizing spawn areas and moat sizes!
- Create a function to create secondary uniqueSpawns for the same player
- Expose primary AND secondary spawn info in spawn controls GUI tab
- Shared items (proper)
- Shared electricity (proper) (with configurable settings)
- Add a show current respawn location button (same as show spawn location)
- Expose some settings for adjusting easy and medium enemy evolution values
- Add FAQ for enemy modifications
- Add FAQ for item and energy sharing
- FIX GetNextPlayerIndex!
- Lots of localizations!
- List all TO-DOs in code here.
- Remove testing surfaces before release!
- Change default surface selection to be the default surface if multiple are enabled
- Test and make sure scenario settings overrides get written back to mod settings to avoid any out of sync settings.
- Test teleporting to other surfaces
- Test on_player_changed_surface
- Run the profiler
- Pull out general spawn config from surfaces config
- Redo resource placement to be simpler (and make a linear layout for square base)
- Default to selecting SELF in admin controls player dropdown?
- Add refresh chunks around spidertrons based on their vision

View File

@ -1,473 +0,0 @@
-- example-config.lua (Rename this file to config.lua to use it)
-- May 26 2020 (updated on)
-- Configuration Options
--
-- You should be safe to leave most of the settings here as defaults if you want.
-- 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"..
"Item & energy sharing system! No attacks on your base while you are offline!\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:Oarc#8695"
DISCORD_INV = "discord.gg/trnpcen"
------------------------------------------------------------------------------------------------------------------------
-- Module Enables
-- Each of the following things enable special features. These can't be changed once the game starts.
------------------------------------------------------------------------------------------------------------------------
-- 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.
-- if ENABLE_MAGIC_FACTORIES=false, you will find a few special areas to launch rockets from.
-- If ENABLE_MAGIC_FACTORIES=true, you must buy a silo at one of the special chunks.
FRONTIER_ROCKET_SILO_MODE = true
-- Enable Undecorator. Removes decorative items to reduce save file size.
ENABLE_UNDECORATOR = true
-- Enable Tags (Players can add a name-tag to explain what type of role they are doing if they want.)
ENABLE_TAGS = true
-- Enable Long Reach
ENABLE_LONGREACH = true
-- Enable Autofill (My autofill is very simplistic, if you are using a similar mod disable this!)
ENABLE_AUTOFILL = true
-- Enable auto decon of miners (My miner decon is very simplistic, if you are using a similar mod disable this!)
ENABLE_MINER_AUTODECON = 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
-- This removes player bases when they leave shortly after joining. Only works if you have regrowth enabled!
ENABLE_ABANDONED_BASE_REMOVAL = true
-- Enable the research queue by default for all forces.
ENABLE_RESEARCH_QUEUE = true
-- This enables coin drops from enemies and a shop (GUI) to buy stuff from.
ENABLE_COIN_SHOP = false
-- Enable item & energy sharing system.
ENABLE_ITEM_AND_ENERGY_SHARING = false -- REQUIRES ENABLE_COIN_SHOP=true!
-- Enable magic chunks around the map that let you buy powerful factories that smelt/assemble/process very very quickly.
ENABLE_MAGIC_FACTORIES = false -- REQUIRES ENABLE_COIN_SHOP=true!
-- This inhibits enemy attacks on bases where all players are offline.
-- Not 100% guaranteed.
ENABLE_OFFLINE_PROTECTION = true
-- This allows you to set the tech price multiplier for the game, but
-- have it only affect the main force. We just pad all non-main forces lab prod bonus.
-- This has no effect unless the tech multiplier is more than 1!
ENABLE_FORCE_LAB_PROD_BONUS = true
-- Lock various recipes and technologies behind a rocket launch.
-- Each team/force must launch their own rocket to unlock this!
LOCK_GOODIES_UNTIL_ROCKET_LAUNCH = true
LOCKED_TECHNOLOGIES = {
{t="atomic-bomb"},{t="power-armor-mk2"},{t="artillery"},{t="spidertron"}
}
LOCKED_RECIPES = {
{r="productivity-module-3"},{r="speed-module-3"}
}
-- Give cheaty items on start.
ENABLE_POWER_ARMOR_QUICK_START = false
ENABLE_MODULAR_ARMOR_QUICK_START = 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.
------------------------------------------------------------------------------------------------------------------------
-- This scales resources so that even if you spawn "far away" from the center
-- of the map, resources near to your spawn point scale so you aren't
-- surrounded by 100M patches or something. This is useful depending on what
-- map gen settings you pick.
SCALE_RESOURCES_AROUND_SPAWNS = true
------------------------------------------------------------------------------------------------------------------------
-- 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 = {
["pistol"]=1,
["firearm-magazine"]=200,
["iron-plate"]=100,
["burner-mining-drill"] = 4,
["stone-furnace"] = 4,
["coal"] = 50,
["stone"] = 50,
["coin"] = 2500, -- Don't give coins unless you have shared chests enabled.
}
-- Items provided after EVERY respawn (disabled by default)
PLAYER_RESPAWN_START_ITEMS = {
-- ["pistol"]=1,
-- ["firearm-magazine"]=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 = 50
NEAR_MAX_DIST = 100
-- 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
------------------------------------------------------------------------------------------------------------------------
-- 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*2,
-- Allow players to choose to spawn with a moat
moat_choice_enabled = true,
-- If there is a moat, this attempts to connect to land to avoid "turtling"
moat_bridging = 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,
-- Add a crashed ship like a vanilla game (create_crash_site)
-- Resources go in the ship itself. (5 slots)
-- Wreakage is distributed in small pieces. (I recommend only 1 item type.)
crashed_ship = true,
crashed_ship_resources = {
["electronic-circuit"] = 200,
["iron-gear-wheel"] = 100,
["copper-cable"] = 200,
-- ["spidertron"] = 1,
["steel-plate"] = 100
},
crashed_ship_wreakage = {
["iron-plate"] = 100
},
},
-- 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*6,
-- Warning area has significantly reduced aliens
-- This is the radius in tiles of warning area.
warn_radius = CHUNK_SIZE*12,
-- 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 = 45,
-- 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 = 1500,
size = 18,
x_offset = -29,
y_offset = 16
},
["copper-ore"] =
{
amount = 1200,
size = 18,
x_offset = -28,
y_offset = -3
},
["stone"] =
{
amount = 1200,
size = 16,
x_offset = -27,
y_offset = -34
},
["coal"] =
{
amount = 1200,
size = 16,
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
-- I like keeping this off... set to true if you want to shoot your own chests
-- and stuff.
ENABLE_FRIENDLY_FIRE = false
------------------------------------------------------------------------------------------------------------------------
-- EXPERIMENTAL FEATURES
-- The following things are not recommended unless you really know what you are doing and are okay with crashes and
-- editing lua code.
------------------------------------------------------------------------------------------------------------------------
-- 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
-- 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
-- Vanilla spawn point options (only applicable if ENABLE_VANILLA_SPAWNS is enabled.)
-- Num total spawns pre-assigned (minimum number)
-- 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
-- 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
-- This is part of regrowth, and if both are enabled, any chunks which aren't active and have no entities will
-- eventually be deleted over time. DO NOT USE THIS WITH MODS!
ENABLE_WORLD_EATER = false

View File

@ -10,14 +10,16 @@
"water": 1,
"_comment_width+height": "Width and height of map, in tiles; 0 means infinite",
"width": 0,
"height": 0,
"width": 1,
"height": 1,
"_starting_area_comment": "Multiplier for 'biter free zone radius'",
"starting_area": 1,
"peaceful_mode": false,
"default_enable_all_autoplace_controls": true,
"autoplace_controls":
{
"coal" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20},

View File

@ -3,7 +3,7 @@
{
"recipe_difficulty": 0,
"technology_difficulty": 0,
"technology_price_multiplier": 3,
"technology_price_multiplier": 1,
"research_queue_setting": "always"
},
"pollution":
@ -113,7 +113,8 @@
"min_steps_to_check_path_find_termination": 2000,
"start_to_goal_cost_multiplier_to_terminate_path_find": 500.0,
"overload_levels": [0, 100, 500],
"overload_multipliers": [2, 3, 4]
"overload_multipliers": [2, 3, 4],
"negative_path_cache_delay_interval": 20
},
"max_failed_behavior_count": 3
}

Binary file not shown.

After

(image error) Size: 56 KiB

Binary file not shown.

After

(image error) Size: 22 KiB

Binary file not shown.

After

(image error) Size: 23 KiB

Binary file not shown.

After

(image error) Size: 14 KiB

BIN
icon_40x40.png Normal file

Binary file not shown.

After

(image error) Size: 13 KiB

19
info.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "oarc-mod",
"version": "2.0.0",
"factorio_version": "1.1",
"title": "Oarc Multiplayer Spawn",
"author": "Oarcinae",
"contact": "Mod Discussion Page, oarcinae@gmail.com, Discord:oarc",
"homepage": "https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn",
"description": "[[Description is in locale file!]]",
"dependencies": [
"base >= 1.1.0",
"(?) dangOreus" ,
"(?) Milestones",
"(?) space-exploration",
"(?) alien-biomes",
"(?) sonaxaton-research-queue",
"(?) helmod"
]
}

View File

@ -1,153 +0,0 @@
-- admin_commands.lua
-- May 2019
--
-- Yay, admin commands!
require("lib/oarc_utils")
-- name :: string: Name of the command.
-- tick :: uint: Tick the command was used.
-- player_index :: uint (optional): The player who used the command. It will be missing if run from the server console.
-- parameter :: string (optional): The parameter passed after the command, separated from the command by 1 space.
-- Give yourself or another player, power armor
commands.add_command("give-power-armor-kit", "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)
commands.add_command("give-test-kit", "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
GiveTestKit(target)
player.print("Gave a test kit to " .. target.name)
target.print("You have been given a test kit!")
end
end)
commands.add_command("load-quickbar", "Pre-load quickbar shortcuts", function(command)
local p = game.players[command.player_index]
-- 1st Row
p.set_quick_bar_slot(1, "transport-belt");
p.set_quick_bar_slot(2, "small-electric-pole");
p.set_quick_bar_slot(3, "inserter");
p.set_quick_bar_slot(4, "underground-belt");
p.set_quick_bar_slot(5, "splitter");
p.set_quick_bar_slot(6, "coal");
p.set_quick_bar_slot(7, "repair-pack");
p.set_quick_bar_slot(8, "gun-turret");
p.set_quick_bar_slot(9, "stone-wall");
p.set_quick_bar_slot(10, "radar");
-- 2nd Row
p.set_quick_bar_slot(11, "stone-furnace");
p.set_quick_bar_slot(12, "wooden-chest");
p.set_quick_bar_slot(13, "steel-chest");
p.set_quick_bar_slot(14, "assembling-machine-1");
p.set_quick_bar_slot(15, "assembling-machine-2");
p.set_quick_bar_slot(16, nil);
p.set_quick_bar_slot(17, nil);
p.set_quick_bar_slot(18, nil);
p.set_quick_bar_slot(19, nil);
p.set_quick_bar_slot(20, nil);
-- 3rd Row
p.set_quick_bar_slot(21, "electric-mining-drill");
p.set_quick_bar_slot(22, "fast-inserter");
p.set_quick_bar_slot(23, "long-handed-inserter");
p.set_quick_bar_slot(24, "medium-electric-pole");
p.set_quick_bar_slot(25, "big-electric-pole");
p.set_quick_bar_slot(26, "stack-inserter");
p.set_quick_bar_slot(27, nil);
p.set_quick_bar_slot(28, nil);
p.set_quick_bar_slot(29, nil);
p.set_quick_bar_slot(30, nil);
-- 4th Row
p.set_quick_bar_slot(31, "fast-transport-belt");
p.set_quick_bar_slot(32, "medium-electric-pole");
p.set_quick_bar_slot(33, "fast-inserter");
p.set_quick_bar_slot(34, "fast-underground-belt");
p.set_quick_bar_slot(35, "fast-splitter");
p.set_quick_bar_slot(36, "stone-wall");
p.set_quick_bar_slot(37, "repair-pack");
p.set_quick_bar_slot(38, "gun-turret");
p.set_quick_bar_slot(39, "laser-turret");
p.set_quick_bar_slot(40, "radar");
-- 5th Row
p.set_quick_bar_slot(41, "train-stop");
p.set_quick_bar_slot(42, "rail-signal");
p.set_quick_bar_slot(43, "rail-chain-signal");
p.set_quick_bar_slot(44, "rail");
p.set_quick_bar_slot(45, "big-electric-pole");
p.set_quick_bar_slot(46, "locomotive");
p.set_quick_bar_slot(47, "cargo-wagon");
p.set_quick_bar_slot(48, "fluid-wagon");
p.set_quick_bar_slot(49, "pump");
p.set_quick_bar_slot(50, "storage-tank");
-- 6th Row
p.set_quick_bar_slot(51, "oil-refinery");
p.set_quick_bar_slot(52, "chemical-plant");
p.set_quick_bar_slot(53, "storage-tank");
p.set_quick_bar_slot(54, "pump");
p.set_quick_bar_slot(55, nil);
p.set_quick_bar_slot(56, "pipe");
p.set_quick_bar_slot(57, "pipe-to-ground");
p.set_quick_bar_slot(58, "assembling-machine-2");
p.set_quick_bar_slot(59, "pump");
p.set_quick_bar_slot(60, nil);
-- 7th Row
p.set_quick_bar_slot(61, "roboport");
p.set_quick_bar_slot(62, "logistic-chest-storage");
p.set_quick_bar_slot(63, "logistic-chest-passive-provider");
p.set_quick_bar_slot(64, "logistic-chest-requester");
p.set_quick_bar_slot(65, "logistic-chest-buffer");
p.set_quick_bar_slot(66, "logistic-chest-active-provider");
p.set_quick_bar_slot(67, "logistic-robot");
p.set_quick_bar_slot(68, "construction-robot");
p.set_quick_bar_slot(69, nil);
p.set_quick_bar_slot(70, nil);
end)

View File

@ -1,43 +0,0 @@
-- auto_decon_miners.lua
-- May 2020
-- My shitty softmod version which is buggy
function OarcAutoDeconOnInit(event)
if (not global.oarc_decon_miners) then
global.oarc_decon_miners = {}
end
end
function OarcAutoDeconOnTick()
if (global.oarc_decon_miners and (#global.oarc_decon_miners > 0)) then
for i,miner in pairs(global.oarc_decon_miners) do
if ((not miner) or (not miner.valid)) then
table.remove(global.oarc_decon_miners, i)
else
if (#miner.surface.find_entities_filtered{area = {{miner.position.x-3, miner.position.y-3},
{miner.position.x+3, miner.position.y+3}},
type = "resource", limit = 1} == 0) then
miner.order_deconstruction(miner.force)
end
table.remove(global.oarc_decon_miners, i)
end
end
end
end
function OarcAutoDeconOnResourceDepleted(event)
if (not global.oarc_decon_miners) then
global.oarc_decon_miners = {}
end
if (event.entity and event.entity.position and event.entity.surface) then
local nearby_miners = event.entity.surface.find_entities_filtered{area = {{event.entity.position.x-1, event.entity.position.y-1},
{event.entity.position.x+1, event.entity.position.y+1}},
name = {"burner-mining-drill", "electric-mining-drill"}}
for i,v in pairs(nearby_miners) do
table.insert(global.oarc_decon_miners, v)
end
end
end

519
lib/config.lua Normal file
View File

@ -0,0 +1,519 @@
--[[
__ __ ___ ___ __ ___ ___ _ _ ___ _ _ ___
| \/ |/ _ \| \ \ \ / /_\ | _ \ \| |_ _| \| |/ __|
| |\/| | (_) | |) | \ \/\/ / _ \| / .` || || .` | (_ |
|_| |_|\___/|___/ \_/\_/_/ \_\_|_\_|\_|___|_|\_|\___|
DO NOT EDIT THIS FILE!
DO NOT EDIT THIS FILE!
DO NOT EDIT THIS FILE!
If you want to customize settings at init, edit the control.lua file in the scenarios/OARC folder and load that scenario!
]]
-- More settings are available here than are provided in the mod settings menu.
-- Additionally, many settings are exposed in the game itself and can be changed in game.
-- For convenience you, you can edit the control.lua file in the scenarios/OARC folder to override settings as well.
-- That method is useful for headless server hosting where it's hard to configure mod settings.
---@alias SpawnShapeChoice "circle" | "octagon" | "square"
SPAWN_SHAPE_CHOICE_CIRCLE = "circle"
SPAWN_SHAPE_CHOICE_OCTAGON = "octagon"
SPAWN_SHAPE_CHOICE_SQUARE = "square"
---@alias SpawnResourcesShapeChoice "circle" | "square"
RESOURCES_SHAPE_CHOICE_CIRCLE = "circle"
RESOURCES_SHAPE_CHOICE_SQUARE = "square"
---@type OarcConfigStartingItems
NAUVIS_STARTER_ITEMS =
{
player_start_items = {
["pistol"]=1,
["firearm-magazine"]=200,
["iron-plate"]=100,
["burner-mining-drill"] = 4,
["stone-furnace"] = 4,
["coal"] = 50,
["stone"] = 50,
},
player_respawn_items = {
-- ["pistol"]=1,
-- ["firearm-magazine"]=100,
},
crashed_ship = true,
crashed_ship_resources = {
["electronic-circuit"] = 200,
["iron-gear-wheel"] = 100,
["copper-cable"] = 200,
["steel-plate"] = 100
},
crashed_ship_wreakage = {
["iron-plate"] = 100 -- I don't recommend more than 1 item type here!
},
}
---@type OarcConfigSpawn
NAUVIS_SPAWN_CONFIG =
{
-- 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*6,
-- Warning area has significantly reduced aliens
-- This is the radius in tiles of warning area.
warn_radius = CHUNK_SIZE*12,
-- 1 : X (spawners alive : spawners destroyed) in this area
warn_reduction = 20,
-- Danger area has slightly reduced 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 within the spawn area (2 horizontal rows)
-- The offset is from the TOP (NORTH) of the spawn area.
water = {
x_offset = -4,
y_offset = 10,
length = 8,
},
-- Location of shared power pole within the spawn area (if enabled)
-- The offset is from the RIGHT (WEST) of the spawn area.
shared_power_pole_position = {
x_offset=-10,
y_offset=0
},
-- Location of shared chest within the spawn area (if enabled)
-- The offset is from the RIGHT (WEST) of the spawn area.
shared_chest_position = {
x_offset=-10,
y_offset=1
},
-- Solid resource tiles
-- If you are running with mods that add or change resources, you'll want to customize this.
-- Offsets only are applicable if auto placement is disabled. Offsets are from CENTER of spawn area.
solid_resources = {
["iron-ore"] = {
amount = 1500,
size = 21,
x_offset = -29,
y_offset = 16
},
["copper-ore"] = {
amount = 1200,
size = 21,
x_offset = -28,
y_offset = -3
},
["stone"] = {
amount = 1200,
size = 21,
x_offset = -27,
y_offset = -34
},
["coal"] = {
amount = 1200,
size = 21,
x_offset = -27,
y_offset = -20
}
},
-- Fluid resource patches like oil
-- If you are running with mods that add or change resources, you'll want to customize this.
-- The offset is from the BOTTOM (SOUTH) of the spawn area.
fluid_resources =
{
["crude-oil"] =
{
num_patches = 2,
amount = 900000,
-- Starting position offset (relative to bottom/south of spawn area)
x_offset_start = -3,
y_offset_start = -10,
-- Additional position offsets for each new oil patch (relative to previous oil patch)
x_offset_next = 6,
y_offset_next = 0
}
},
}
---@type OarcConfigSurface
NAUVIS_SURFACE_CONFIG =
{
starting_items = NAUVIS_STARTER_ITEMS,
spawn_config = NAUVIS_SPAWN_CONFIG
}
---@type OarcConfig
OCFG = {
-- Server Info - This stuff is shown in the welcome GUI and Info panel.
---@type OarcConfigServerInfo
server_info = {
welcome_msg_title = "Insert Server Title Here!",
welcome_msg = "Insert Server Welcome Message Here!",
discord_invite = "Insert Discord Invite Here!"
},
-- General gameplay related settings that I didn't want to expose in the mod settings since these should
-- basically always be enabled unless you're making serious changes.
gameplay = {
-- Default setting for enabling spawning on other surfaces other than the default_surface.
-- This is a STARTUP setting, so it can't be changed in game!!
-- This is a STARTUP setting, so it can't be changed in game!!
default_allow_spawning_on_other_surfaces = true,
-- The name of the main force.
-- This is a STARTUP setting, so it can't be changed in game!!
-- This is a STARTUP setting, so it can't be changed in game!!
main_force_name = "Main Force",
-- At least one of these must be enabled! (enable_main_team and enable_separate_teams)
-- Otherwise we default to enable_main_team = true
-- Allow all players to join a primary force(team).
enable_main_team = true,
-- Allow players to create their own force(team).
enable_separate_teams = true,
-- Allow players to choose to spawn with a moat
allow_moats_around_spawns = true,
-- If there is a moat, this makes a small path to land to avoid "turtling", but if the spawn
-- is in the middle of water, it won't do anything.
enable_moat_bridging = false,
-- 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!).
minimum_distance_to_existing_chunks = 10,
-- The range in which a player can select how close to the center of the map they want to spawn.
near_spawn_distance = 100,
far_spawn_distance = 500,
-- This allows 2 players to spawn next to each other, each with their own starting area.
enable_buddy_spawn = true,
-- This inhibits enemy attacks on bases where all players are offline. Not 100% guaranteed!
enable_offline_protection = true,
-- Enable shared vision between teams (all teams are COOP regardless)
enable_shared_team_vision = true,
-- 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,
-- Enable if players can allow others to join their base.
-- And specify how many including the host are allowed.
enable_shared_spawns = true,
number_of_players_per_shared_spawn = 3,
-- I like keeping this off... set to true if you want to shoot your own chests and stuff.
enable_friendly_fire = false,
-- The default starting surface.
default_surface = "nauvis",
-- Enable secondary spawns for players.
-- This automatically creates a new spawn point when they first move to a separate spawns enabled surface.
enable_secondary_spawns = false,
-- This scales resources so that even if you spawn "far away" from the center
-- of the map, resources near to your spawn point scale so you aren't
-- surrounded by 100M patches or something. This is useful depending on what
-- map gen settings you pick.
scale_resources_around_spawns = true,
-- 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.
modified_enemy_spawning = true,
-- Enemy evolution factor for the easy force (inside warning area).
modified_enemy_easy_evo = 0.0,
-- Enemy evolution factor for the medium force (inside danger area).
modified_enemy_medium_evo = 0.3,
-- Require playes to be online for at least X minutes
-- Else their character is removed and their spawn point is freed up for use
minimum_online_time = 15,
-- Respawn cooldown in minutes.
respawn_cooldown_min = 5,
-- Enable shared power between bases.
-- Creates a special power pole for cross surface connections.
enable_shared_power = false,
-- Enables a single shared chest using the native linked-chest entity in factorio.
enable_shared_chest = false,
},
-- This is a separate feature that is part of the mod that helps keep the map size down. Not required but useful.
regrowth = {
-- Cleans up unused chunks periodically. Helps keep map size down.
-- See description in regrowth_map.lua for more details.
enable_regrowth = false,
-- This is part of regrowth, and if both are enabled, any chunks which aren't active and have no entities
-- will eventually be deleted over time. If this is disabled, any chunk with a player built entity will be
-- marked permanently safe even if it is removed at a later time.
-- DO NOT USE THIS WITH MODS! (unless you know what you're doing?)
enable_world_eater = false,
-- This removes player bases when they leave shortly after joining.
enable_abandoned_base_cleanup = true,
-- This is the interval in minutes that the regrowth cleanup will run.
cleanup_interval = 60,
},
-- General spawn settings (size, shape, etc.)
spawn_general = {
-- Create a circle of land area for the spawn
-- If you make this much bigger than a few chunks, good luck!
-- (It takes a long time to generate new chunks!)
spawn_radius_tiles = CHUNK_SIZE*2,
-- Width of the moat around the spawn area.
-- If you change the spawn area size, you might have to adjust this as well.
moat_width_tiles = 8,
-- Width of the tree ring around the spawn area.
-- If you change the spawn area size, you might have to adjust this as well.
tree_width_tiles = 5,
-- Starting resources deposits shape.
resources_shape = RESOURCES_SHAPE_CHOICE_CIRCLE,
-- Force the land area circle at the spawn to be fully grass, otherwise it defaults to the existing terrain
-- or uses landfill.
force_grass = false,
-- Spawn a circle/octagon/square of trees around this base outline.
shape = SPAWN_SHAPE_CHOICE_CIRCLE,
},
-- Handle placement of starting resources within the spawn area.
resource_placement =
{
-- Autoplace resources (randomly in circle)
-- This will ignore the fixed x_offset/y_offset values in solid_resources.
-- Only works for solid_resources at the moment, not oil patches/water.
enabled = true,
-- Distance in tiles from the edge of spawn that resources are placed. Only applicable for circular spawns.
distance_to_edge = 20,
-- 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.
-- Vertical offset in tiles for the deposit resource placement. Starting from top-left corner.
-- Only applicable for square spawns.
vertical_offset = 20,
-- Horizontal offset in tiles for the deposit resource placement. Starting from top-left corner.
-- Only applicable for square spawns.
horizontal_offset = 20,
-- Spacing between resource deposits in tiles.
-- Only applicable for square spawns.
linear_spacing = 6,
-- Size multiplier for the starting resource deposits.
size_multiplier = 1.0,
-- Amount multiplier for the starting resource deposits.
amount_multiplier = 1.0,
},
-- Spawn configuration specific to each surface, including starting & respawn items.
---@type table<string, OarcConfigSurface>
surfaces_config =
{
["nauvis"] = {
starting_items = NAUVIS_STARTER_ITEMS,
spawn_config = NAUVIS_SPAWN_CONFIG
},
["vulcanus"] = {
starting_items = NAUVIS_STARTER_ITEMS,
spawn_config = NAUVIS_SPAWN_CONFIG
},
["fulgora"] = {
starting_items = NAUVIS_STARTER_ITEMS,
spawn_config = NAUVIS_SPAWN_CONFIG
},
["gleba"] = {
starting_items = NAUVIS_STARTER_ITEMS,
spawn_config = NAUVIS_SPAWN_CONFIG
},
["aquilo"] = {
starting_items = NAUVIS_STARTER_ITEMS,
spawn_config = NAUVIS_SPAWN_CONFIG
}
},
-- Surfaces blacklist (Ignore these surfaces completely for spawning and regrowth!)
---@type table<integer, string>
surfaces_blacklist = {
HOLDING_PEN_SURFACE_NAME,
},
-- Surfaces blacklist that match THE START of these strings
-- (Ignore these surfaces completely for spawning and regrowth!)
---@type table<integer, string>
surfaces_blacklist_match = {
-- Factorissimo Mod Surfaces
"factory-power",
"factory-floor",
},
}
--[[
_ _ _ _ _______ _____ ___ _ _ _ _ _ ___ _____ _ _____ ___ ___ _ _ ___
| | | | | |/_\ |_ _\ \ / / _ \ __| /_\ | \| | \| |/ _ \_ _/_\_ _|_ _/ _ \| \| / __|
| |_| |_| / _ \ | | \ V /| _/ _| / _ \| .` | .` | (_) || |/ _ \| | | | (_) | .` \__ \
|____\___/_/ \_\ |_| |_| |_| |___| /_/ \_\_|\_|_|\_|\___/ |_/_/ \_\_| |___\___/|_|\_|___/
These are LUA type annotations for development and editor support.
You can ignore this unless you're making changes to the mod, in which case it might be helpful.
]]
---@class OarcConfig
---@field server_info OarcConfigServerInfo Personalized server info for the welcome GUI and Info panel.
---@field gameplay OarcConfigGameplaySettings Various mod gameplay settings
---@field regrowth OarcConfigRegrowth Regrowth specific settings (keeps map size down)
---@field spawn_general OarcConfigSpawnGeneral General spawn settings (size, shape, etc.)
---@field resource_placement OarcConfigSpawnResourcePlacementSettings Resource placement settings
---@field surfaces_config table<string, OarcConfigSurface> Spawn configuration (starting items and spawn area config) for each surface.
---@field surfaces_blacklist table<string> List of surfaces to ignore automatically.
---@field surfaces_blacklist_match table<string> List of surfaces to ignore automatically if the start of the string matches the surface name.
---@class OarcConfigServerInfo
---@field welcome_msg_title string Title of welcome GUI window.
---@field welcome_msg string Main welcome message. (Should provide mod info.)
---@field discord_invite string Discord invite for easy copy paste.
---@class OarcConfigGameplaySettings
---@field default_allow_spawning_on_other_surfaces boolean Default setting for enabling spawning on other surfaces other than the default_surface. This is a STARTUP setting, so it can't be changed in game.
---@field main_force_name string The name of the main force. This is a STARTUP setting, so it can't be changed in game.
---@field enable_main_team boolean Allows all players to join a primary force(team).
---@field enable_separate_teams boolean Allows players to create their own force(team).
---@field allow_moats_around_spawns boolean Allow players to choose to spawn with a moat
---@field enable_moat_bridging boolean If there is a moat, this makes a small path to land to avoid "turtling", but if the spawn is in the middle of water, it won't do anything.
---@field minimum_distance_to_existing_chunks number 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.
---@field near_spawn_distance number The closest a player can spawn to the origin. (Not exact, but close).
---@field far_spawn_distance number The furthest a player can spawn from the origin. (Not exact, but close).
---@field enable_buddy_spawn boolean Allow 2 players to spawn next to each other, each with their own starting area.
---@field enable_offline_protection boolean Inhibits enemy attacks on bases where all players are offline. Not 100% guaranteed!
---@field enable_shared_team_vision boolean Enable shared vision between teams (all teams are COOP regardless)
---@field enable_shared_team_chat boolean Share local team chat with all teams
---@field enable_shared_spawns boolean Enable if players can allow others to join their spawn.
---@field number_of_players_per_shared_spawn number Number of players allowed to join a shared spawn.
---@field enable_friendly_fire boolean Set to true if you want to shoot your own chests and stuff.
---@field default_surface string The starting surface of the main force.
---@field enable_secondary_spawns boolean Enable secondary spawns for players. This automatically creates a new spawn point when they first move to a separate spawns enabled surface.
---@field scale_resources_around_spawns boolean Scales resources so that even if you spawn "far away" from the center of the map, resources near to your spawn point scale so you aren't surrounded by 100M patches or something. This is useful depending on what map gen settings you pick.
---@field modified_enemy_spawning boolean 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.
---@field modified_enemy_easy_evo number Enemy evolution factor for the easy force (inside warning area).
---@field modified_enemy_medium_evo number Enemy evolution factor for the medium force (inside danger area).
---@field minimum_online_time number Require playes to be online for at least X minutes Else their character is removed and their spawn point is freed up for use
---@field respawn_cooldown_min number Respawn cooldown in minutes.
---@field enable_shared_power boolean Enable shared power between bases. Creates a special power pole for cross surface connections.
---@field enable_shared_chest boolean Enables a single shared chest using the native linked-chest entity in factorio.
---@class OarcConfigRegrowth
---@field enable_regrowth boolean Cleans up unused chunks periodically. Helps keep map size down.
---@field enable_world_eater boolean Checks inactive chunks to see if they are empty of entities and deletes them periodically.
---@field enable_abandoned_base_cleanup boolean Removes player bases when they leave shortly after joining.
---@field cleanup_interval number This is the interval in minutes that the regrowth cleanup will run.
---@class OarcConfigSurface
---@field starting_items OarcConfigStartingItems Starting items for players on this surface (including crashed ship items)
---@field spawn_config OarcConfigSpawn Spawn area config for this surface
---@class OarcConfigStartingItems
---@field crashed_ship boolean Add a crashed ship like a vanilla game (create_crash_site) Resources go in the ship itself. (5 slots max!) Wreakage is distributed in small pieces. (I recommend only 1 item type.)
---@field crashed_ship_resources table Items to be placed in the crashed ship.
---@field crashed_ship_wreakage table Items to be placed in the crashed ship. (Recommend only 1 item type!)
---@field player_start_items table Items provided to the player the first time they join
---@field player_respawn_items table Items provided after EVERY respawn (disabled by default)
---@class OarcConfigSpawn
---@field safe_area OarcConfigSpawnSafeArea How safe is the spawn area?
---@field water OarcConfigSpawnWater Water strip settings
---@field shared_power_pole_position OarcOffsetPosition Location of shared power pole relative to spawn center (if enabled)
---@field shared_chest_position OarcOffsetPosition Location of shared chest relative to spawn center (if enabled)
---@field solid_resources table<string, OarcConfigSolidResource> Spawn area config for solid resource tiles
---@field fluid_resources table<string, OarcConfigFluidResource> Spawn area config for fluid resource patches (like oil)
---@class OarcConfigSpawnGeneral
---@field spawn_radius_tiles number 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.
---@field moat_width_tiles number Width of the moat around the spawn area. If you change the spawn area size, you might have to adjust this as well.
---@field tree_width_tiles number Width of the tree ring around the spawn area. If you change the spawn area size, you might have to adjust this as well.
---@field resources_shape SpawnResourcesShapeChoice The starting resources deposits shape.
---@field force_grass boolean Force the land area circle at the spawn to be fully grass, otherwise it defaults to the existing terrain.
---@field shape SpawnShapeChoice Spawn a circle/octagon/square of trees around this base outline.
---@class OarcConfigSpawnSafeArea
---@field safe_radius number Safe area has no aliens This is the radius in tiles of safe area.
---@field warn_radius number Warning area has significantly reduced aliens This is the radius in tiles of warning area.
---@field warn_reduction number 1 : X (spawners alive : spawners destroyed) in this area
---@field danger_radius number Danger area has slightly reduce aliens This is the radius in tiles of danger area.
---@field danger_reduction number 1 : X (spawners alive : spawners destroyed) in this area
---@class OarcConfigSpawnWater
---@field x_offset number Location of water strip within the spawn area (horizontal)
---@field y_offset number Location of water strip within the spawn area (vertical)
---@field length number Length of water strip within the spawn area
---@alias OarcOffsetPosition { x_offset: number, y_offset: number } An offset position intended to be relative to the spawn center.
---@class OarcConfigSpawnResourcePlacementSettings
---@field enabled boolean Autoplace resources. This will ignore the fixed x_offset/y_offset values in solid_resources. Only works for solid_resources at the moment, not oil patches/water.
---@field distance_to_edge number Distance in tiles from the edge of spawn that resources are placed. Only applicable for circular spawns.
---@field angle_offset number At what angle (in radians) do resources start. 0 means starts directly east. Resources are placed clockwise from there. Only applicable for circular spawns.
---@field angle_final number At what andle do we place the last resource. angle_offset and angle_final determine spacing and placement. Only applicable for circular spawns.
---@field vertical_offset number Vertical offset in tiles for the deposit resource placement. Only applicable for square spawns.
---@field horizontal_offset number Horizontal offset in tiles for the deposit resource placement. Only applicable for square spawns.
---@field linear_spacing number Spacing between resource deposits in tiles. Only applicable for square spawns.
---@field size_multiplier number Size multiplier for the starting resource deposits.
---@field amount_multiplier number Amount multiplier for the starting resource deposits.
---@alias OarcConfigSolidResource { amount: integer, size: integer, x_offset: integer, y_offset: integer } Amount and placement of solid resource tiles in the spawn area.
---@alias OarcConfigFluidResource { num_patches: integer, amount: integer, x_offset_start: integer, y_offset_start: integer, x_offset_next: integer, y_offset_next: integer } Amount and placement of fluid resource patches in the spawn area.

370
lib/config_parser.lua Normal file
View File

@ -0,0 +1,370 @@
-- This file is used to validate the config.lua file and handle any mod settings conflicts.
-- DON'T JUDGE ME! I wanted to try and make a nice in game setting GUI since the native mod settings GUI is so limited.
---Provides a way to look up the config settings key from the mod settings key.
---@alias OarcSettingsLookup { mod_key: string, ocfg_keys: table<integer, string>, type: string, text: LocalisedString? }
---@type table<string, OarcSettingsLookup>
OCFG_KEYS =
{
["server_info_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-server-info"}},
["server_info.welcome_msg_title"] = {mod_key = "oarc-mod-welcome-msg-title" , ocfg_keys = {"server_info", "welcome_msg_title"}, type = "string"},
["server_info.welcome_msg"] = {mod_key = "oarc-mod-welcome-msg" , ocfg_keys = {"server_info", "welcome_msg"}, type = "string"},
["server_info.discord_invite"] = {mod_key = "oarc-mod-discord-invite" , ocfg_keys = {"server_info", "discord_invite"}, type = "string"},
["gameplay_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-gameplay"}},
["gameplay_spawn_choices_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-spawn-choices"}},
["gameplay.enable_main_team"] = {mod_key = "oarc-mod-enable-main-team" , ocfg_keys = {"gameplay", "enable_main_team"}, type = "boolean"},
["gameplay.enable_separate_teams"] = {mod_key = "oarc-mod-enable-separate-teams" , ocfg_keys = {"gameplay", "enable_separate_teams"}, type = "boolean"},
-- STARTUP ["gameplay.enable_spawning_on_other_surfaces"] = {mod_key = "oarc-mod-default-allow-spawning-on-other-surfaces" , ocfg_keys = {"gameplay", "enable_spawning_on_other_surfaces"}, type = "boolean"},
["gameplay.allow_moats_around_spawns"] = {mod_key = "oarc-mod-allow-moats-around-spawns" , ocfg_keys = {"gameplay", "allow_moats_around_spawns"}, type = "boolean"},
["gameplay.enable_moat_bridging"] = {mod_key = "oarc-mod-enable-moat-bridging" , ocfg_keys = {"gameplay", "enable_moat_bridging"}, type = "boolean"},
["gameplay.minimum_distance_to_existing_chunks"] = {mod_key = "oarc-mod-minimum-distance-to-existing-chunks" , ocfg_keys = {"gameplay", "minimum_distance_to_existing_chunks"}, type = "integer"},
["gameplay.near_spawn_distance"] = {mod_key = "oarc-mod-near-spawn-distance" , ocfg_keys = {"gameplay", "near_spawn_distance"}, type = "integer"},
["gameplay.far_spawn_distance"] = {mod_key = "oarc-mod-far-spawn-distance" , ocfg_keys = {"gameplay", "far_spawn_distance"}, type = "integer"},
["gameplay.enable_buddy_spawn"] = {mod_key = "oarc-mod-enable-buddy-spawn" , ocfg_keys = {"gameplay", "enable_buddy_spawn"}, type = "boolean"},
["gameplay.enable_shared_spawns"] = {mod_key = "oarc-mod-enable-shared-spawns" , ocfg_keys = {"gameplay", "enable_shared_spawns"}, type = "boolean"},
["gameplay.number_of_players_per_shared_spawn"] = {mod_key = "oarc-mod-number-of-players-per-shared-spawn" , ocfg_keys = {"gameplay", "number_of_players_per_shared_spawn"}, type = "integer"},
["gameplay.default_surface"] = {mod_key = "oarc-mod-default-surface" , ocfg_keys = {"gameplay", "default_surface"}, type = "string"},
["gameplay.enable_secondary_spawns"] = {mod_key = "oarc-mod-enable-secondary-spawns" , ocfg_keys = {"gameplay", "enable_secondary_spawns"}, type = "boolean"},
-- STARTUP ["gameplay.main_force_name"] = {mod_key = "oarc-mod-main-force-name" , ocfg_keys = {"gameplay", "main_force_name"}, type = "string"},
["gameplay_difficulty_scaling_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-difficulty-scaling"}},
["gameplay.enable_offline_protection"] = {mod_key = "oarc-mod-enable-offline-protection" , ocfg_keys = {"gameplay", "enable_offline_protection"}, type = "boolean"},
["gameplay.scale_resources_around_spawns"] = {mod_key = "oarc-mod-scale-resources-around-spawns" , ocfg_keys = {"gameplay", "scale_resources_around_spawns"}, type = "boolean"},
["gameplay.modified_enemy_spawning"] = {mod_key = "oarc-mod-modified-enemy-spawning" , ocfg_keys = {"gameplay", "modified_enemy_spawning"}, type = "boolean"},
["gameplay.modified_enemy_easy_evo"] = {mod_key = "oarc-mod-modified-enemy-easy-evo" , ocfg_keys = {"gameplay", "modified_enemy_easy_evo"}, type = "double"},
["gameplay.modified_enemy_medium_evo"] = {mod_key = "oarc-mod-modified-enemy-medium-evo" , ocfg_keys = {"gameplay", "modified_enemy_medium_evo"}, type = "double"},
["gameplay_misc_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-gameplay-misc"}},
["gameplay.enable_friendly_fire"] = {mod_key = "oarc-mod-enable-friendly-fire" , ocfg_keys = {"gameplay", "enable_friendly_fire"}, type = "boolean"},
["gameplay.minimum_online_time"] = {mod_key = "oarc-mod-minimum-online-time" , ocfg_keys = {"gameplay", "minimum_online_time"}, type = "integer"},
["gameplay.respawn_cooldown_min"] = {mod_key = "oarc-mod-respawn-cooldown-min" , ocfg_keys = {"gameplay", "respawn_cooldown_min"}, type = "integer"},
["gameplay_sharing_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-sharing"}},
["gameplay.enable_shared_team_vision"] = {mod_key = "oarc-mod-enable-shared-team-vision" , ocfg_keys = {"gameplay", "enable_shared_team_vision"}, type = "boolean"},
["gameplay.enable_shared_team_chat"] = {mod_key = "oarc-mod-enable-shared-team-chat" , ocfg_keys = {"gameplay", "enable_shared_team_chat"}, type = "boolean"},
["gameplay.enable_shared_power"] = {mod_key = "oarc-mod-enable-shared-power" , ocfg_keys = {"gameplay", "enable_shared_power"}, type = "boolean"},
["gameplay.enable_shared_chest"] = {mod_key = "oarc-mod-enable-shared-chest" , ocfg_keys = {"gameplay", "enable_shared_chest"}, type = "boolean"},
["regrowth_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-regrowth"}},
["regrowth_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-regrowth-warning"}},
["regrowth.enable_regrowth"] = {mod_key = "oarc-mod-enable-regrowth" , ocfg_keys = {"regrowth", "enable_regrowth"}, type = "boolean"},
["regrowth.enable_world_eater"] = {mod_key = "oarc-mod-enable-world-eater" , ocfg_keys = {"regrowth", "enable_world_eater"}, type = "boolean"},
["regrowth.enable_abandoned_base_cleanup"] = {mod_key = "oarc-mod-enable-abandoned-base-cleanup" , ocfg_keys = {"regrowth", "enable_abandoned_base_cleanup"}, type = "boolean"},
["regrowth.cleanup_interval"] = {mod_key = "oarc-mod-regrowth-cleanup-interval-min" , ocfg_keys = {"regrowth", "cleanup_interval"}, type = "integer"},
["general_spawn_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-general-spawn"}},
["spawn_general.spawn_radius_tiles"] = {mod_key = "oarc-mod-spawn-general-radius-tiles" , ocfg_keys = {"spawn_general", "spawn_radius_tiles"}, type = "integer"},
["spawn_general.moat_width_tiles"] = {mod_key = "oarc-mod-spawn-general-moat-width-tiles" , ocfg_keys = {"spawn_general", "moat_width_tiles"}, type = "integer"},
["spawn_general.tree_width_tiles"] = {mod_key = "oarc-mod-spawn-general-tree-width-tiles" , ocfg_keys = {"spawn_general", "tree_width_tiles"}, type = "integer"},
["spawn_general.resources_shape"] = {mod_key = "oarc-mod-spawn-general-enable-resources-circle-shape" , ocfg_keys = {"spawn_general", "resources_shape"}, type = "string-list"},
["spawn_general.force_grass"] = {mod_key = "oarc-mod-spawn-general-enable-force-grass" , ocfg_keys = {"spawn_general", "force_grass"}, type = "boolean"},
["spawn_general.shape"] = {mod_key = "oarc-mod-spawn-general-shape" , ocfg_keys = {"spawn_general", "shape"}, type = "string-list"},
["resource_placement_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-resource-placement"}},
["resource_placement.enabled"] = {mod_key = "oarc-mod-resource-placement-enabled" , ocfg_keys = {"resource_placement", "enabled"}, type = "boolean"},
["resource_placement.size_multiplier"] = {mod_key = "oarc-mod-resource-placement-size-multiplier" , ocfg_keys = {"resource_placement", "size_multiplier"}, type = "double"},
["resource_placement.amount_multiplier"] = {mod_key = "oarc-mod-resource-placement-amount-multiplier" , ocfg_keys = {"resource_placement", "amount_multiplier"}, type = "double"},
["resource_placement_circle_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-resource-placement-circular"}},
["resource_placement.distance_to_edge"] = {mod_key = "oarc-mod-resource-placement-distance-to-edge" , ocfg_keys = {"resource_placement", "distance_to_edge"}, type = "integer"},
["resource_placement.angle_offset"] = {mod_key = "oarc-mod-resource-placement-angle-offset" , ocfg_keys = {"resource_placement", "angle_offset"}, type = "double"},
["resource_placement.angle_final"] = {mod_key = "oarc-mod-resource-placement-angle-final" , ocfg_keys = {"resource_placement", "angle_final"}, type = "double"},
["resource_placement_square_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-resource-placement-square"}},
["resource_placement.vertical_offset"] = {mod_key = "oarc-mod-resource-placement-vertical-offset" , ocfg_keys = {"resource_placement", "vertical_offset"}, type = "integer"},
["resource_placement.horizontal_offset"] = {mod_key = "oarc-mod-resource-placement-horizontal-offset" , ocfg_keys = {"resource_placement", "horizontal_offset"}, type = "integer"},
["resource_placement.linear_spacing"] = {mod_key = "oarc-mod-resource-placement-linear-spacing" , ocfg_keys = {"resource_placement", "linear_spacing"}, type = "integer"},
}
---Easy reverse lookup for mod settings keys.
---@type table<string, string>
OCFG_MOD_KEYS =
{
["oarc-mod-welcome-msg-title"] = "server_info.welcome_msg_title",
["oarc-mod-welcome-msg"] = "server_info.welcome_msg",
["oarc-mod-discord-invite"] = "server_info.discord_invite",
["oarc-mod-enable-main-team"] = "gameplay.enable_main_team",
["oarc-mod-enable-separate-teams"] = "gameplay.enable_separate_teams",
-- STARTUP ["oarc-mod-default-allow-spawning-on-other-surfaces"] = " ["gameplay.enable_spawning_on_other_surfaces",
["oarc-mod-allow-moats-around-spawns"] = "gameplay.allow_moats_around_spawns",
["oarc-mod-enable-moat-bridging"] = "gameplay.enable_moat_bridging",
["oarc-mod-minimum-distance-to-existing-chunks"] = "gameplay.minimum_distance_to_existing_chunks",
["oarc-mod-near-spawn-distance"] = "gameplay.near_spawn_distance",
["oarc-mod-far-spawn-distance"] = "gameplay.far_spawn_distance",
["oarc-mod-enable-buddy-spawn"] = "gameplay.enable_buddy_spawn",
["oarc-mod-enable-offline-protection"] = "gameplay.enable_offline_protection",
["oarc-mod-enable-shared-team-vision"] = "gameplay.enable_shared_team_vision",
["oarc-mod-enable-shared-team-chat"] = "gameplay.enable_shared_team_chat",
["oarc-mod-enable-shared-spawns"] = "gameplay.enable_shared_spawns",
["oarc-mod-number-of-players-per-shared-spawn"] = "gameplay.number_of_players_per_shared_spawn",
["oarc-mod-enable-friendly-fire"] = "gameplay.enable_friendly_fire",
-- STARTUP ["oarc-mod-main-force-name"] = "gameplay.main_force_name",
["oarc-mod-default-surface"] = "gameplay.default_surface",
["oarc-mod-enable-secondary-spawns"] = "gameplay.enable_secondary_spawns",
["oarc-mod-scale-resources-around-spawns"] = "gameplay.scale_resources_around_spawns",
["oarc-mod-modified-enemy-spawning"] = "gameplay.modified_enemy_spawning",
["oarc-mod-modified-enemy-easy-evo"] = "gameplay.modified_enemy_easy_evo",
["oarc-mod-modified-enemy-medium-evo"] = "gameplay.modified_enemy_medium_evo",
["oarc-mod-minimum-online-time"] = "gameplay.minimum_online_time",
["oarc-mod-respawn-cooldown-min"] = "gameplay.respawn_cooldown_min",
["oarc-mod-enable-shared-power"] = "gameplay.enable_shared_power",
["oarc-mod-enable-shared-chest"] = "gameplay.enable_shared_chest",
["oarc-mod-enable-regrowth"] = "regrowth.enable_regrowth",
["oarc-mod-enable-world-eater"] = "regrowth.enable_world_eater",
["oarc-mod-enable-abandoned-base-cleanup"] = "regrowth.enable_abandoned_base_cleanup",
["oarc-mod-regrowth-cleanup-interval-min"] = "regrowth.cleanup_interval",
["oarc-mod-spawn-general-radius-tiles"] = "spawn_general.spawn_radius_tiles",
["oarc-mod-spawn-general-moat-width-tiles"] = "spawn_general.moat_width_tiles",
["oarc-mod-spawn-general-tree-width-tiles"] = "spawn_general.tree_width_tiles",
["oarc-mod-spawn-general-enable-resources-circle-shape"] = "spawn_general.resources_shape",
["oarc-mod-spawn-general-enable-force-grass"] = "spawn_general.force_grass",
["oarc-mod-spawn-general-shape"] = "spawn_general.shape",
["oarc-mod-resource-placement-enabled"] = "resource_placement.enabled",
["oarc-mod-resource-placement-distance-to-edge"] = "resource_placement.distance_to_edge",
["oarc-mod-resource-placement-angle-offset"] = "resource_placement.angle_offset",
["oarc-mod-resource-placement-angle-final"] = "resource_placement.angle_final",
["oarc-mod-resource-placement-vertical-offset"] = "resource_placement.vertical_offset",
["oarc-mod-resource-placement-horizontal-offset"] = "resource_placement.horizontal_offset",
["oarc-mod-resource-placement-linear-spacing"] = "resource_placement.linear_spacing",
["oarc-mod-resource-placement-size-multiplier"] = "resource_placement.size_multiplier",
["oarc-mod-resource-placement-amount-multiplier"] = "resource_placement.amount_multiplier"
}
function ValidateAndLoadConfig()
-- Check that each of the OCFG_MOD_KEYS has a corresponding OCFG_KEYS entry.
for mod_key,ocfg_key in pairs(OCFG_MOD_KEYS) do
if (OCFG_KEYS[ocfg_key] == nil) then
error("OCFG_MOD_KEYS entry does not have a corresponding OCFG_KEYS entry: " .. mod_key .. " -> " .. ocfg_key)
end
end
-- And check the opposite.
for ocfg_key,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") and (OCFG_MOD_KEYS[entry.mod_key] == nil) then
error("OCFG_KEYS entry does not have a corresponding OCFG_MOD_KEYS entry: " .. ocfg_key .. " -> " .. entry.mod_key)
end
end
-- Load the template config into the global table.
---@class OarcConfig
global.ocfg = OCFG
-- Check that each entry in OCFG matches the default value of the mod setting. This is just for my own sanity.
-- Helps make sure mod default settings and my internal config are in sync.
for _,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") then
local mod_key = entry.mod_key
local oarc_key = entry.ocfg_keys
local mod_value = game.mod_setting_prototypes[mod_key].default_value
local oarc_value = GetGlobalOarcConfigUsingKeyTable(oarc_key)
if (mod_value ~= oarc_value) then
error("OCFG value does not match mod setting: " .. mod_key .. " = " .. tostring(mod_value) .. " -> " .. serpent.block(oarc_key) .. " = " .. tostring(oarc_value))
end
end
end
CacheModSettings() -- Get all mod settings and overwrite the defaults in OARC_CFG.
GetScenarioOverrideSettings() -- Get any scenario settings and overwrite both the mod settings and OARC_CFG.
ValidateSettings() -- These are validation checks that can't be done within the mod settings natively.
end
function ValidateSettings()
-- Validate enable_main_team and enable_separate_teams.
-- Force enable_main_team if both are disabled.
if (not global.ocfg.gameplay.enable_main_team and not global.ocfg.gameplay.enable_separate_teams) then
log("Both main force and separate teams are disabled! Enabling main force. Please check your mod settings or config!")
global.ocfg.gameplay.enable_main_team = true
settings.global["oarc-mod-enable-main-team"] = { value = true }
SendBroadcastMsg("Invalid setting! Both main force and separate teams are disabled! Enabling main force.")
end
-- Validate minimum is less than maximums
if (global.ocfg.gameplay.near_spawn_distance >= global.ocfg.gameplay.far_spawn_distance) then
log("Near spawn min distance is greater than or equal to near spawn max distance! Please check your mod settings or config!")
global.ocfg.gameplay.far_spawn_distance = global.ocfg.gameplay.near_spawn_distance + 1
settings.global["oarc-mod-far-spawn-distance"] = { value = global.ocfg.gameplay.far_spawn_distance }
SendBroadcastMsg("Invalid setting! Near spawn min distance is greater than or equal to near spawn max distance!")
end
-- Validate that regrowth is enabled if world eater is enabled.
if (global.ocfg.regrowth.enable_world_eater and not global.ocfg.regrowth.enable_regrowth) then
log("World eater is enabled but regrowth is not! Disabling world eater. Please check your mod settings or config!")
global.ocfg.regrowth.enable_world_eater = false
settings.global["oarc-mod-enable-world-eater"] = { value = false }
SendBroadcastMsg("Invalid setting! World eater is enabled but regrowth is not! Disabling world eater.")
end
-- Validate that default surface exists.
if (game.surfaces[global.ocfg.gameplay.default_surface] == nil) then
log("Default surface does not exist! Please check your mod settings or config!")
global.ocfg.gameplay.default_surface = "nauvis"
settings.global["oarc-mod-default-surface"] = { value = "nauvis" }
SendBroadcastMsg("Invalid setting! Default surface does not exist! Setting to nauvis.")
end
-- Validate that a "nauvis" surface config exists (nauvis is the default config fallback)
-- This should only break with a bad scenario custom config.
if (global.ocfg.surfaces_config["nauvis"] == nil) then
error("nauvis surface config does not exist! Please check your mod settings or config!")
end
end
-- Read in the mod settings and copy them to the OARC_CFG table, overwriting the defaults in config.lua.
function CacheModSettings()
log("Copying mod settings to OCFG table...")
-- Copy the global settings from the mod settings.
-- Find the matching OARC setting and update it.
for _,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") then
SetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys, settings.global[entry.mod_key].value)
end
end
-- Special case for startup settings
global.ocfg.gameplay.default_allow_spawning_on_other_surfaces = settings.startup["oarc-mod-default-allow-spawning-on-other-surfaces"].value --[[@as boolean]]
global.ocfg.gameplay.main_force_name = settings.startup["oarc-mod-main-force-name"].value --[[@as string]]
end
function GetScenarioOverrideSettings()
if remote.interfaces["oarc_scenario"] then
log("Getting scenario override settings...")
local scenario_settings = remote.call("oarc_scenario", "get_scenario_settings")
-- Overwrite the non mod settings with the scenario settings.
global.ocfg = scenario_settings
-- Override the mod settings with the scenario settings!
for _,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") then
local mod_key = entry.mod_key
local oarc_key = entry.ocfg_keys
local scenario_value = GetGlobalOarcConfigUsingKeyTable(oarc_key)
if (scenario_value ~= nil) then
local ok,result = pcall(function() settings.global[mod_key] = { value = scenario_value } end)
if not ok then
error("Error setting mod setting: " .. mod_key .. " = " .. tostring(scenario_value) .. "\n" .. "If you see this, you probably picked an invalid value for a setting override in the custom scenario.")
end
end
end
end
else
log("No scenario settings found.")
end
end
---Handles the event when a mod setting is changed in the mod settings menu.
---@param event EventData.on_runtime_mod_setting_changed
---@return nil
function RuntimeModSettingChanged(event)
if (event.setting_type ~= "runtime-global") then
return
end
log("on_runtime_mod_setting_changed: " .. event.setting .. " = " .. tostring(settings.global[event.setting].value))
-- Find the matching OARC setting and update it.
local found_setting = false
if (OCFG_MOD_KEYS[event.setting] ~= nil) then
local oarc_setting_index = OCFG_MOD_KEYS[event.setting]
local oarc_setting_table = OCFG_KEYS[oarc_setting_index]
SetGlobalOarcConfigUsingKeyTable(oarc_setting_table.ocfg_keys, settings.global[event.setting].value)
found_setting = true
end
if (not found_setting) then
error("Unknown oarc-mod setting changed: " .. event.setting)
else
ValidateSettings()
ApplyRuntimeChanges(OCFG_MOD_KEYS[event.setting])
end
end
---A probably quit stupid function to let me lookup and set the global.ocfg entries using a key table.
---@param key_table table<integer, string>
---@param value any
function SetGlobalOarcConfigUsingKeyTable(key_table, value)
local number_of_keys = #key_table
if (number_of_keys == 1) then
global.ocfg[key_table[1]] = value
elseif (number_of_keys == 2) then
global.ocfg[key_table[1]][key_table[2]] = value
elseif (number_of_keys == 3) then
global.ocfg[key_table[1]][key_table[2]][key_table[3]] = value
else
error("Invalid key_table length: " .. number_of_keys .. "\n" .. serpent.block(key_table))
end
end
---An equally stupid function to let me lookup the global.ocfg entries using a key table.
---@param key_table table<integer, string>
---@return any
function GetGlobalOarcConfigUsingKeyTable(key_table)
local number_of_keys = #key_table
if (number_of_keys == 1) then
if (global.ocfg[key_table[1]] == nil) then
error("Invalid key_table 1: " .. serpent.block(key_table))
end
return global.ocfg[key_table[1]]
elseif (number_of_keys == 2) then
if (global.ocfg[key_table[1]] == nil) or (global.ocfg[key_table[1]][key_table[2]] == nil) then
error("Invalid key_table 2: " .. serpent.block(key_table))
end
return global.ocfg[key_table[1]][key_table[2]]
elseif (number_of_keys == 3) then
if (global.ocfg[key_table[1]] == nil) or
(global.ocfg[key_table[1]][key_table[2]] == nil) or
(global.ocfg[key_table[1]][key_table[2]][key_table[3]] == nil) then
error("Invalid key_table 3: " .. serpent.block(key_table))
end
return global.ocfg[key_table[1]][key_table[2]][key_table[3]]
else
error("Invalid key_table length: " .. number_of_keys .. "\n" .. serpent.block(key_table))
end
end
---Handles any runtime changes that need more than just the setting change.
---@param oarc_setting_index string
---@return nil
function ApplyRuntimeChanges(oarc_setting_index)
---Handle changing enable_shared_team_vision
if (oarc_setting_index == "gameplay.enable_shared_team_vision") then
for _,force in pairs(game.forces) do
if (force.name ~= "neutral") and (force.name ~= "enemy") and (force.name ~= "enemy-easy") then
force.share_chart = global.ocfg.gameplay.enable_shared_team_vision
end
end
---Handle changing enable_friendly_fire
elseif (oarc_setting_index == "gameplay.enable_friendly_fire") then
for _,force in pairs(game.forces) do
if (force.name ~= "neutral") and (force.name ~= "enemy") and (force.name ~= "enemy-easy") then
force.friendly_fire = global.ocfg.gameplay.enable_friendly_fire
end
end
end
end

View File

@ -1,401 +0,0 @@
-- 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
local random_angle_offset = math.random(0, math.pi * 2)
for i=1,num_silos do
local theta = ((math.pi * 2) / num_silos);
local angle = (theta * i) + random_angle_offset;
local tx = (global.ocfg.frontier_silo_distance*CHUNK_SIZE * math.cos(angle))
local ty = (global.ocfg.frontier_silo_distance*CHUNK_SIZE * math.sin(angle))
-- Ensure it's centered around a chunk
local tx = (tx - (tx % CHUNK_SIZE)) + CHUNK_SIZE/2
local ty = (ty - (ty % CHUNK_SIZE)) + CHUNK_SIZE/2
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 = -6,5 do
for dy = -6,5 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
RegrowthMarkAreaSafeGivenTilePos(siloPosition, 5, true)
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()
-- Create each silo in the list
for idx,siloPos in pairs(global.siloPosition) do
CreateRocketSilo(game.surfaces[GAME_SURFACE_NAME], 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) <= 1) then
if (event.created_entity.name ~= "entity-ghost") then
SendBroadcastMsg("Rocket silo has been built!")
end
return -- THIS MEANS WE SUCCESFULLY BUILT THE SILO (ghost or actual building.)
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
function GenerateRocketSiloChunks()
-- Silo generation can take awhile depending on the number of silos.
-- if (game.tick < #global.siloPosition*10*TICKS_PER_SECOND) then
local surface = game.surfaces[GAME_SURFACE_NAME]
-- local chunkArea = event.area
-- local chunkAreaCenter = {x=chunkArea.left_top.x+(CHUNK_SIZE/2),
-- y=chunkArea.left_top.y+(CHUNK_SIZE/2)}
for _,siloPos in pairs(global.siloPosition) do
local siloArea = {left_top=
{x=siloPos.x-(CHUNK_SIZE*2),
y=siloPos.y-(CHUNK_SIZE*2)},
right_bottom=
{x=siloPos.x+(CHUNK_SIZE*2),
y=siloPos.y+(CHUNK_SIZE*2)}}
-- Clear enemies directly next to the rocket
-- if CheckIfInArea(chunkAreaCenter,siloArea) then
for _, entity in pairs(surface.find_entities_filtered{area = siloArea, force = "enemy"}) do
entity.destroy()
end
-- Remove trees/resources inside the spawn area
RemoveInCircle(surface, siloArea, "tree", siloPos, (CHUNK_SIZE*1.5)+5)
RemoveInCircle(surface, siloArea, "resource", siloPos, (CHUNK_SIZE*1.5)+5)
RemoveInCircle(surface, siloArea, "cliff", siloPos, (CHUNK_SIZE*1.5)+5)
RemoveDecorationsArea(surface, siloArea)
-- Create rocket silo
CreateCropOctagon(surface, siloPos, siloArea, (CHUNK_SIZE*1.5)+4, "landfill")
-- 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}, 1)
end
if (global.ocfg.frontier_silo_vision) then
ChartRocketSiloAreas(surface, game.forces[global.ocfg.main_force])
end
game.surfaces[GAME_SURFACE_NAME].force_generate_chunk_requests() -- Block and generate all to be sure.
GenerateRocketSiloChunks()
GenerateAllSilos()
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*1),
siloPos.y-(CHUNK_SIZE*1)},
{siloPos.x+(CHUNK_SIZE*1),
siloPos.y+(CHUNK_SIZE*1)}})
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

View File

@ -1,160 +0,0 @@
-- 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.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
RemoveOrResetPlayer(player, false, true, true, true)
SeparateSpawnsPlayerCreated(resetPlayer, true)
log("Resetting " .. resetPlayer)
end
end
end
end
-- Used by AddOarcGuiTab
function CreateGameOptionsTab(tab_container, player)
if global.oarc_announcements ~= nil then
AddLabel(tab_container, "announcement_info_label", "Server announcements:", my_label_header_style)
AddLabel(tab_container, "announcement_info_txt", global.oarc_announcements, my_longer_label_style)
AddSpacerLine(tab_container)
end
-- 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)
tab_container.add{type="textfield",
tooltip="Come join the discord (copy this invite)!",
text=DISCORD_INV}
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/pollution/destroy factors: " .. game.map_settings.enemy_evolution.time_factor .. "/" ..
game.map_settings.enemy_evolution.pollution_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)
-- 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
if (global.ocfg.enable_chest_sharing) then
soft_mods_string = soft_mods_string .. ", Item & Energy Sharing"
end
if (global.ocfg.enable_magic_factories) then
soft_mods_string = soft_mods_string .. ", Special Map Chunks"
end
if (global.ocfg.enable_offline_protect) then
soft_mods_string = soft_mods_string .. ", Offline Attack Inhibitor"
end
local game_info_str = "Soft Mods: " .. 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 (global.ocfg.enable_power_armor_start or global.ocfg.enable_modular_armor_start) then
game_info_str = game_info_str.."\n".."Quicker start enabled."
end
if (global.ocfg.lock_goodies_rocket_launch) then
game_info_str = game_info_str.."\n".."Some technologies and 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

View File

@ -0,0 +1,42 @@
-- Contains the GUI for the regrowth controls tab.
---Used by AddOarcGuiTab
---@param tab_container LuaGuiElement
---@param player LuaPlayer
---@return nil
function CreateModInfoTab(tab_container, player)
local scroll_pane = tab_container.add {
type = "scroll-pane",
vertical_scroll_policy = "auto",
}
scroll_pane.style.maximal_height = GENERIC_GUI_MAX_HEIGHT
scroll_pane.style.padding = 5
AddLabel(scroll_pane, nil, "Mod Info & FAQ", my_label_header2_style)
AddSpacerLine(scroll_pane)
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-what-is-this-mod" }, { "oarc-mod-faq-what-is-this-mod-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-other-surfaces" }, { "oarc-mod-faq-other-surfaces-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-secondary-spawns" }, { "oarc-mod-faq-secondary-spawns-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-what-are-teams" }, { "oarc-mod-faq-what-are-teams-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-shared-spawn" }, { "oarc-mod-faq-shared-spawn-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-buddy-spawn" }, { "oarc-mod-faq-buddy-spawn-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-regrowth" }, { "oarc-mod-faq-regrowth-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-cleanup-abandoned" }, { "oarc-mod-faq-cleanup-abandoned-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-offline-protection" }, { "oarc-mod-faq-offline-protection-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-shared-power" }, { "oarc-mod-faq-shared-power-answer" })
CreateFAQEntry(scroll_pane, { "oarc-mod-faq-shared-chest" }, { "oarc-mod-faq-shared-chest-answer" })
end
---Creates a FAQ entry in the tab
---@param container LuaGuiElement
---@param question LocalisedString
---@param answer LocalisedString
---@return nil
function CreateFAQEntry(container, question, answer)
AddLabel(container, nil, question, "caption_label")
AddLabel(container, nil, answer, my_longer_label_style)
AddSpacerLine(container)
end

View File

@ -0,0 +1,142 @@
---Display current game options and server info, maybe have some admin controls here
---Creates the content for the game settings used by AddOarcGuiTab
---@param tab_container LuaGuiElement
---@param player LuaPlayer
---@return nil
function CreateServerInfoTab(tab_container, player)
-- General Server Info:
if (global.ocfg.server_info.welcome_msg ~= " ") then
AddLabel(tab_container, nil, {"oarc-server-info-tab-welcome-msg-title"}, "caption_label")
AddLabel(tab_container, nil, global.ocfg.server_info.welcome_msg, my_longer_label_style)
AddSpacerLine(tab_container)
end
if (global.ocfg.server_info.discord_invite ~= " ") then
local horizontal_flow = tab_container.add{
type="flow", direction="horizontal"
}
AddLabel(horizontal_flow, nil, {"oarc-server-info-tab-discord-invite"}, "caption_label")
horizontal_flow.add{
type="textfield",
tooltip={"oarc-server-info-tab-discord-invite-tooltip"},
text=global.ocfg.server_info.discord_invite
}
AddSpacerLine(tab_container)
end
AddLabel(tab_container, nil, {"oarc-server-info-tab-map-info-label"}, "caption_label")
AddLabel(tab_container, nil, {"oarc-server-info-tab-server-run-time", FormatTimeHoursSecs(game.tick)}, my_label_style)
--TODO: Add more stuff here maybe? Like in the old version?
if (global.ocfg.regrowth.enable_abandoned_base_cleanup) then
local label = AddLabel(tab_container, nil, {"oarc-server-info-leave-warning", global.ocfg.gameplay.minimum_online_time}, my_longer_label_style)
label.style.font_color=my_color_red
end
-- Ending Spacer
AddSpacerLine(tab_container)
-- ADMIN CONTROLS
if (player.admin) then
player_list = {}
for _, p in pairs(game.connected_players) do
table.insert(player_list, p.name)
end
AddLabel(tab_container, nil, {"oarc-server-info-admin-controls"}, "caption_label")
local horizontal_flow = tab_container.add{
type="flow", direction="horizontal"
}
horizontal_flow.style.horizontally_stretchable = true
AddLabel(horizontal_flow, nil, {"oarc-server-info-ban-select-player"}, my_label_style)
local drop_down = horizontal_flow.add{
name = "ban_players_dropdown",
tags = { action = "oarc_server_info_tab", setting = "ban_players_dropdown" },
type = "drop-down",
items = player_list
}
-- If there is only one player, select it by default (for testing convenience)
if (#player_list == 1) then
drop_down.selected_index = 1
end
local dragger = horizontal_flow.add{
type="empty-widget",
style="draggable_space_header"
}
dragger.style.horizontally_stretchable = true
horizontal_flow.add{
name="ban_player",
tags = { action = "oarc_server_info_tab", setting = "ban_player" },
type="button",
caption={"oarc-server-info-button-ban-player"},
style = "red_button"
}
horizontal_flow.add{
name="restart_player",
tags = { action = "oarc_server_info_tab", setting = "restart_player" },
type="button",
caption={"oarc-server-info-button-restart-player"},
style = "red_button"
}
end
end
---Server info gui click event handler
---@param event EventData.on_gui_click
---@return nil
function ServerInfoGuiClick(event)
if not event.element.valid then return end
local player = game.players[event.player_index]
local tags = event.element.tags
if (tags.action ~= "oarc_server_info_tab") then
return
end
local player_dropdown = event.element.parent.ban_players_dropdown
if (tags.setting == "ban_player") then
local pIndex = player_dropdown.selected_index
if (pIndex ~= 0) then
local banPlayer = player_dropdown.get_item(pIndex)
if (game.players[banPlayer]) then
game.ban_player(banPlayer --[[@as string]], "Banned from admin panel by " .. player.name)
log("Banning " .. banPlayer)
end
end
end
if (tags.setting == "restart_player") then
local pIndex = player_dropdown.selected_index
if (pIndex ~= 0) then
local resetPlayer = player_dropdown.get_item(pIndex)
if not game.players[resetPlayer] or not game.players[resetPlayer].connected then
SendMsg(player.name, {"oarc-player-not-found", resetPlayer})
return
end
if PlayerHasDelayedSpawn(resetPlayer--[[@as string]]) then
SendMsg(player.name, {"oarc-player-about-to-spawn", resetPlayer})
return
end
log("Resetting " .. resetPlayer)
RemoveOrResetPlayer(game.players[resetPlayer], false)
SeparateSpawnsInitPlayer(resetPlayer --[[@as string]])
else
SendMsg(player.name, {"oarc-player-none-selected"})
return
end
end
end

View File

@ -0,0 +1,505 @@
-- Contains the GUI for the controlling various settings of the mod.
---Creates the content in the tab used by AddOarcGuiTab
---@param tab_container LuaGuiElement
---@param player LuaPlayer
function CreateSettingsControlsTab(tab_container, player)
if (player.admin) then
local label = AddLabel(tab_container, nil, { "oarc-settings-tab-admin-warning" }, my_warning_style)
else
local label = AddLabel(tab_container, nil, { "oarc-settings-tab-player-warning" }, my_warning_style)
end
local label = AddLabel(tab_container, nil, { "oarc-settings-tab-description" }, my_label_style)
label.style.bottom_padding = 5
local flow = tab_container.add { type = "flow", direction = "horizontal", }
local scroll_pane_left = flow.add {
type = "scroll-pane",
direction = "vertical",
vertical_scroll_policy = "always",
}
scroll_pane_left.style.maximal_height = GENERIC_GUI_MAX_HEIGHT
scroll_pane_left.style.padding = 5
scroll_pane_left.style.right_margin = 2
CreateModSettingsSection(scroll_pane_left, player)
local scroll_pane_right = flow.add {
type = "scroll-pane",
direction = "vertical",
vertical_scroll_policy = "always",
}
scroll_pane_right.style.maximal_height = GENERIC_GUI_MAX_HEIGHT
scroll_pane_right.style.padding = 5
scroll_pane_right.style.left_margin = 2
CreateSurfaceSettingsSection(scroll_pane_right, player)
end
---Create the content for the mod settings section
---@param container LuaGuiElement
---@param player LuaPlayer
---@return nil
function CreateModSettingsSection(container, player)
AddLabel(container, nil, { "oarc-settings-tab-title-mod-settings" }, my_label_header2_style)
for index,entry in pairs(OCFG_KEYS) do
if (entry.type == "header") then
AddSpacerLine(container)
AddLabel(container, nil, entry.text, "caption_label")
elseif (entry.type == "subheader") then
AddLabel(container, nil, entry.text, "bold_label")
elseif (entry.type == "boolean") then
AddCheckboxSetting(container, index, entry, player.admin)
elseif (entry.type == "string") then
AddTextfieldSetting(container, index, entry, player.admin)
elseif (entry.type == "integer") then
AddIntegerSetting(container, index, entry, player.admin)
elseif (entry.type == "double") then
AddDoubleSetting(container, index, entry, player.admin)
elseif (entry.type == "string-list") then
AddStringListDropdownSetting(container, index, entry, player.admin)
end
end
end
---Create the content for the surface settings section
---@param container LuaGuiElement
---@param player LuaPlayer
---@return nil
function CreateSurfaceSettingsSection(container, player)
AddLabel(container, nil, { "oarc-settings-tab-title-surface" }, my_label_header2_style)
--- Create a table with 3 columns. Surface Name, Spawning Enabled, Regrowth Enabled
local surface_table = container.add {
type = "table",
name = "surface_table",
column_count = 3,
style = "bordered_table",
}
--- Add the header row
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-column-header"}, "caption_label")
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-spawning-enabled"}, "caption_label")
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-regrowth-enabled"}, "caption_label")
--- Add the rows
for name, allowed in pairs(global.oarc_surfaces --[[@as table<string, boolean>]]) do
AddLabel(surface_table, nil, name, my_label_style)
AddSurfaceCheckboxSetting(surface_table, name, "spawn_enabled", allowed, player.admin,
{ "oarc-settings-tab-surface-checkbox-tooltip" })
local regrowth_enabled = TableContains(global.rg.active_surfaces, name)
AddSurfaceCheckboxSetting(surface_table, name, "regrowth_enabled", regrowth_enabled, player.admin,
{"oarc-settings-tab-surface-regrowth-checkbox-tooltip"})
end
end
---Handles the click event for the tab used by AddOarcGuiTab
---@param event EventData.on_gui_click
---@return nil
function SettingsControlsTabGuiClick(event)
if not (event.element.valid) then return end
local gui_elem = event.element
if (gui_elem.tags.action ~= "oarc_settings_tab") then return end
local index = gui_elem.tags.setting
local entry = OCFG_KEYS[index]
if (entry.type == "boolean") then
settings.global[entry.mod_key] = { value = gui_elem.state }
end
end
---Handles the text entry event for the tab used by AddOarcGuiTab
---@param event EventData.on_gui_text_changed
---@return nil
function SettingsControlsTabGuiTextChanged(event)
if not (event.element.valid) then return end
local gui_elem = event.element
if (gui_elem.tags.action ~= "oarc_settings_tab") then return end
local index = gui_elem.tags.setting
local value = gui_elem.text
local entry = OCFG_KEYS[index]
if (entry.type == "string") then
gui_elem.style = "invalid_value_textfield"
elseif (entry.type == "integer") then
gui_elem.style = "invalid_value_textfield"
gui_elem.style.width = 50
end
end
---Handles the confirmed text entry event
---@param event EventData.on_gui_confirmed
---@return nil
function SettingsControlsTabGuiTextconfirmed(event)
if not (event.element.valid) then return end
local gui_elem = event.element
if (gui_elem.tags.action ~= "oarc_settings_tab") then return end
local index = gui_elem.tags.setting
local value = gui_elem.text
local entry = OCFG_KEYS[index]
if (entry.type == "string") then
if value == "" then -- Force a non-empty string!
value = " "
gui_elem.text = " "
end
gui_elem.style = "textbox"
settings.global[entry.mod_key] = { value = gui_elem.text }
elseif (entry.type == "integer") then
local safe_value = GetSafeIntValueForModSetting(value, entry.mod_key)
if not pcall(function() settings.global[entry.mod_key] = { value = safe_value } end) then
settings.global[entry.mod_key] = { value = game.mod_setting_prototypes[entry.mod_key].default_value }
log("Error setting value for " .. entry.mod_key .. " to " .. safe_value)
end
gui_elem.text = tostring(settings.global[entry.mod_key].value)
gui_elem.style = "textbox"
gui_elem.style.width = 50
local slider = gui_elem.parent["slider"]
slider.slider_value = settings.global[entry.mod_key].value --[[@as integer]]
elseif (entry.type == "double") then
local safe_value = GetSafeDoubleValueForModSetting(value, entry.mod_key)
if not pcall(function() settings.global[entry.mod_key] = { value = safe_value } end) then
settings.global[entry.mod_key] = { value = game.mod_setting_prototypes[entry.mod_key].default_value }
log("Error setting value for " .. entry.mod_key .. " to " .. safe_value)
end
gui_elem.text = string.format("%.2f", settings.global[entry.mod_key].value)
gui_elem.style = "textbox"
gui_elem.style.width = 50
local slider = gui_elem.parent["slider"]
slider.slider_value = settings.global[entry.mod_key].value --[[@as number]]
end
end
---Handles slider value changes
---@param event EventData.on_gui_value_changed
---@return nil
function SettingsControlsTabGuiValueChanged(event)
if not (event.element.valid) then return end
local gui_elem = event.element
if (gui_elem.tags.action ~= "oarc_settings_tab_slider") then return end
local index = gui_elem.tags.setting
local value = gui_elem.slider_value
local entry = OCFG_KEYS[index]
if (entry.type == "integer") then
local textfield = gui_elem.parent["textfield"]
settings.global[entry.mod_key] = { value = value } -- Assumes that the slider can only produce valid inputs!
textfield.text = tostring(value)
elseif (entry.type == "double") then
local textfield = gui_elem.parent["textfield"]
settings.global[entry.mod_key] = { value = value } -- Assumes that the slider can only produce valid inputs!
textfield.text = string.format("%.2f", value)
end
end
---Makes sure a given value is within the min/max range of a mod setting
---@param input string|number|integer
---@param mod_key string
---@return integer
function GetSafeIntValueForModSetting(input, mod_key)
local value_num = tonumber(input)
if not value_num then
value_num = tonumber(game.mod_setting_prototypes[mod_key].default_value)
else
local minimum = game.mod_setting_prototypes[mod_key].minimum_value
local maximum = game.mod_setting_prototypes[mod_key].maximum_value
if minimum ~= nil then
value_num = math.max(value_num, minimum)
end
if maximum ~= nil then
value_num = math.min(value_num, maximum)
end
value_num = math.floor(value_num)
end
return value_num --[[@as integer]]
end
---Makes sure a given value is within the min/max range of a mod setting (double)
---@param input string|number|integer
---@param mod_key string
---@return number
function GetSafeDoubleValueForModSetting(input, mod_key)
local value_num = tonumber(input)
if not value_num then
value_num = tonumber(game.mod_setting_prototypes[mod_key].default_value)
else
local minimum = game.mod_setting_prototypes[mod_key].minimum_value
local maximum = game.mod_setting_prototypes[mod_key].maximum_value
if minimum ~= nil then
value_num = math.max(value_num, minimum)
end
if maximum ~= nil then
value_num = math.min(value_num, maximum)
end
end
return value_num --[[@as number]]
end
---Creates a checkbox setting
---@param tab_container LuaGuiElement
---@param index string
---@param entry OarcSettingsLookup
---@param enabled boolean
---@return nil
function AddCheckboxSetting(tab_container, index, entry, enabled)
tab_container.add{
type = "checkbox",
caption = { "mod-setting-name."..entry.mod_key },
state = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys),
enabled = enabled,
tooltip = { "mod-setting-description."..entry.mod_key },
tags = { action = "oarc_settings_tab", setting = index },
}
end
---Creates a textfield setting
---@param tab_container LuaGuiElement
---@param index string
---@param entry OarcSettingsLookup
---@param enabled boolean
---@return nil
function AddTextfieldSetting(tab_container, index, entry, enabled)
local horizontal_flow = tab_container.add {
type = "flow",
direction = "horizontal",
}
horizontal_flow.add {
type = "label",
caption = { "mod-setting-name."..entry.mod_key },
tooltip = { "mod-setting-description."..entry.mod_key },
}
local dragger = horizontal_flow.add {
type = "empty-widget",
}
dragger.style.horizontally_stretchable = true
local tooltip = {"", {"mod-setting-description."..entry.mod_key }, " ", { "oarc-settings-tab-text-field-enter-tooltip" }}
horizontal_flow.add {
type = "textfield",
caption = { "mod-setting-name."..entry.mod_key },
text = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys),
enabled = enabled,
tooltip = tooltip,
tags = { action = "oarc_settings_tab", setting = index },
}
end
---Creates an integer setting
---@param tab_container LuaGuiElement
---@param index string
---@param entry OarcSettingsLookup
---@param enabled boolean
---@return nil
function AddIntegerSetting(tab_container, index, entry, enabled)
local horizontal_flow = tab_container.add {
type = "flow",
direction = "horizontal",
}
horizontal_flow.add {
type = "label",
caption = { "mod-setting-name."..entry.mod_key },
tooltip = { "mod-setting-description."..entry.mod_key },
}
local dragger = horizontal_flow.add {
type = "empty-widget",
}
dragger.style.horizontally_stretchable = true
local slider = horizontal_flow.add {
name = "slider",
type = "slider",
minimum_value = game.mod_setting_prototypes[entry.mod_key].minimum_value,
maximum_value = game.mod_setting_prototypes[entry.mod_key].maximum_value,
value = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys),
enabled = enabled,
tooltip = { "mod-setting-description."..entry.mod_key },
tags = { action = "oarc_settings_tab_slider", setting = index },
discrete_values = true,
value_step = 1,
}
local tooltip = {"", {"mod-setting-description."..entry.mod_key }, " ", { "oarc-settings-tab-text-field-enter-tooltip" }}
local textfield = horizontal_flow.add {
name = "textfield",
type = "textfield",
numeric = true,
caption = { "mod-setting-name."..entry.mod_key },
text = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys),
enabled = enabled,
tooltip = tooltip,
tags = { action = "oarc_settings_tab", setting = index },
}
textfield.style.width = 50
end
---Creates a double setting
---@param tab_container LuaGuiElement
---@param index string
---@param entry OarcSettingsLookup
---@param enabled boolean
---@return nil
function AddDoubleSetting(tab_container, index, entry, enabled)
local horizontal_flow = tab_container.add {
type = "flow",
direction = "horizontal",
}
horizontal_flow.add {
type = "label",
caption = { "mod-setting-name."..entry.mod_key },
tooltip = { "mod-setting-description."..entry.mod_key },
}
local dragger = horizontal_flow.add {
type = "empty-widget",
}
dragger.style.horizontally_stretchable = true
local slider = horizontal_flow.add {
name = "slider",
type = "slider",
minimum_value = game.mod_setting_prototypes[entry.mod_key].minimum_value,
maximum_value = game.mod_setting_prototypes[entry.mod_key].maximum_value,
value = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys),
enabled = enabled,
tooltip = { "mod-setting-description."..entry.mod_key },
tags = { action = "oarc_settings_tab_slider", setting = index },
discrete_values = false,
value_step = 0.01,
}
local tooltip = {"", {"mod-setting-description."..entry.mod_key }, " ", { "oarc-settings-tab-text-field-enter-tooltip" }}
local textfield = horizontal_flow.add {
name = "textfield",
type = "textfield",
numeric = true,
allow_decimal = true,
caption = { "mod-setting-name."..entry.mod_key },
text = string.format("%.2f", GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys)),
enabled = enabled,
tooltip = tooltip,
tags = { action = "oarc_settings_tab", setting = index },
}
textfield.style.width = 50
end
---Create a dropdown setting for a string setting with allowed_values set
---@param tab_container LuaGuiElement
---@param index string
---@param entry OarcSettingsLookup
---@param enabled boolean
---@return nil
function AddStringListDropdownSetting(tab_container, index, entry, enabled)
local horizontal_flow = tab_container.add {
type = "flow",
direction = "horizontal",
}
horizontal_flow.add {
type = "label",
caption = { "mod-setting-name."..entry.mod_key },
tooltip = { "mod-setting-description."..entry.mod_key },
}
local dragger = horizontal_flow.add {
type = "empty-widget",
}
dragger.style.horizontally_stretchable = true
local allowed_values = game.mod_setting_prototypes[entry.mod_key].allowed_values --[[@as string[] ]]
local selected_index = 1
for i,v in pairs(allowed_values) do
if (v == GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys)) then
selected_index = i
break
end
end
local dropdown = horizontal_flow.add {
type = "drop-down",
items = allowed_values,
selected_index = selected_index,
enabled = enabled,
tooltip = { "mod-setting-description."..entry.mod_key },
tags = { action = "oarc_settings_tab", setting = index },
}
end
---Creates a checkbox setting for surface related settings.
---@param parent LuaGuiElement
---@param surface_name string
---@param setting_name string
---@param state boolean
---@param admin boolean
---@param tooltip LocalisedString
---@return nil
function AddSurfaceCheckboxSetting(parent, surface_name, setting_name, state, admin, tooltip)
parent.add{
name = surface_name.."_"..setting_name,
type = "checkbox",
state = state,
tags = { action = "oarc_settings_tab_surfaces", setting = setting_name, surface = surface_name },
enabled = admin,
tooltip = tooltip,
}
end
---Handles the click event for surface related settings
---@param event EventData.on_gui_click
---@return nil
function SettingsSurfaceControlsTabGuiClick(event)
if not (event.element.valid) then return end
local gui_elem = event.element
if (gui_elem.tags.action ~= "oarc_settings_tab_surfaces") then return end
local setting_name = gui_elem.tags.setting
local surface_name = gui_elem.tags.surface --[[@as string]]
if (setting_name == "spawn_enabled") then
global.oarc_surfaces[surface_name] = gui_elem.state
if (#GetAllowedSurfaces() == 0) then
log("Warning - GetAllowedSurfaces() - No surfaces found! Forcing default surface!")
global.oarc_surfaces[global.ocfg.gameplay.default_surface] = true
event.element.parent[global.ocfg.gameplay.default_surface.."_spawn_enabled"].state = true
end
elseif (setting_name == "regrowth_enabled") then
if (gui_elem.state) then
if not IsRegrowthEnabledOnSurface(surface_name) then
RegrowthEnableSurface(surface_name)
end
else
if IsRegrowthEnabledOnSurface(surface_name) then
RegrowthDisableSurface(surface_name)
end
end
end
end
---Handles dropdown selection events
---@param event EventData.on_gui_selection_state_changed
---@return nil
function SettingsControlsTabGuiSelectionStateChanged(event)
if not (event.element.valid) then return end
local gui_elem = event.element
if (gui_elem.tags.action ~= "oarc_settings_tab") then return end
local index = gui_elem.tags.setting
local entry = OCFG_KEYS[index]
if (entry.type == "string-list") then
settings.global[entry.mod_key] = { value = gui_elem.items[gui_elem.selected_index] }
end
end

View File

@ -0,0 +1,377 @@
-- Spawn control tab in the Oarc GUI, for things like sharing your base with others.
---Provides the content of the spawn control tab in the Oarc GUI.
---@param tab_container LuaGuiElement
---@param player LuaPlayer
---@return nil
function CreateSpawnControlsTab(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 = 1000
spwnCtrls.horizontal_scroll_policy = "never"
CreatePrimarySpawnInfo(player, spwnCtrls)
CreateSecondarySpawnInfo(player, spwnCtrls)
CreateSetRespawnLocationButton(player, spwnCtrls)
if global.ocfg.gameplay.enable_shared_spawns then
CreateSharedSpawnControls(player, spwnCtrls)
CreateJoinQueueControls(player, spwnCtrls)
end
end
---Display some general info about the player's home base (different from respawn point).
---There should ONLY be one of these per player? Or maybe there are one per surface
---@param player LuaPlayer
---@param container LuaGuiElement
---@return nil
function CreatePrimarySpawnInfo(player, container)
local primary_spawn = FindPrimaryUniqueSpawn(player.name)
if (primary_spawn == nil) then return end
AddLabel(container, nil, { "oarc-primary-spawn-info-header" }, "caption_label")
AddLabel(container, nil, { "oarc-primary-spawn-info-note" }, my_label_style)
local horizontal_flow = container.add { type = "flow", direction = "horizontal"}
horizontal_flow.style.vertical_align = "center"
AddLabel(horizontal_flow, nil, { "oarc-primary-spawn-info-surface-label",
primary_spawn.surface_name,
primary_spawn.position.x,
primary_spawn.position.y}, my_label_style)
--Add empty widget
local dragger = horizontal_flow.add {
type = "empty-widget",
style = "draggable_space_header"
}
dragger.style.horizontally_stretchable = true
CreateGPSButton(horizontal_flow, primary_spawn.surface_name, primary_spawn.position)
AddSpacerLine(container)
end
---Display some general info about the player's secondary spawn points.
---@param player LuaPlayer
---@param container LuaGuiElement
---@return nil
function CreateSecondarySpawnInfo(player, container)
local secondary_spawns = FindSecondaryUniqueSpawns(player.name)
if (secondary_spawns == nil) or (table_size(secondary_spawns) == 0) then return end
AddLabel(container, nil, { "oarc-secondary-spawn-info-header" }, "caption_label")
AddLabel(container, nil, { "oarc-secondary-spawn-info-note" }, my_label_style)
for _,secondary_spawn in pairs(secondary_spawns) do
local horizontal_flow = container.add { type = "flow", direction = "horizontal"}
horizontal_flow.style.vertical_align = "center"
AddLabel(horizontal_flow, nil, { "oarc-secondary-spawn-info-surface-label",
secondary_spawn.surface_name,
secondary_spawn.position.x,
secondary_spawn.position.y}, my_label_style)
--Add empty widget
local dragger = horizontal_flow.add {
type = "empty-widget",
style = "draggable_space_header"
}
dragger.style.horizontally_stretchable = true
CreateGPSButton(horizontal_flow, secondary_spawn.surface_name, secondary_spawn.position)
end
AddSpacerLine(container)
end
---Display the shared spawn controls
---@param player LuaPlayer
---@param container LuaGuiElement
---@return nil
function CreateSharedSpawnControls(player, container)
local primary_spawn = FindPrimaryUniqueSpawn(player.name)
if (primary_spawn == nil) then return end
AddLabel(container, nil, { "oarc-shared-spawn-controls" }, "caption_label")
local shared_spawn_open = IsSharedSpawnOpen(primary_spawn.surface_name, player.name)
local shared_spawn_full = IsSharedSpawnFull(primary_spawn.surface_name, player.name)
-- This checkbox allows people to join your base when they first start the game.
local toggle = container.add {
type = "checkbox",
name = "accessToggle",
tags = { action = "oarc_spawn_ctrl_tab", setting = "shared_access_toggle" },
caption = { "oarc-shared-spawn-allow-joiners" },
state = shared_spawn_open,
enabled = not shared_spawn_full -- Disable if the shared spawn is full
}
if shared_spawn_open and shared_spawn_full then
AddLabel(container, nil, { "oarc-shared-spawn-full" }, my_note_style)
end
ApplyStyle(toggle, my_fixed_width_style)
end
---Display the set respawn location button in the spawn control tab.
---@param player LuaPlayer
---@param container LuaGuiElement
---@return nil
function CreateSetRespawnLocationButton(player, container)
AddLabel(container, nil, { "oarc-set-respawn-loc-header" }, "caption_label")
--[[@type OarcPlayerSpawn]]
local respawn_info = global.player_respawns[player.name][player.surface.name]
if (respawn_info == nil) then
log("ERROR: No respawn info for player: " .. player.name)
return
end
local respawn_surface_name = respawn_info.surface
local respawn_position = respawn_info.position
-- Display the current respawn location
local horizontal_flow = container.add { type = "flow", direction = "horizontal"}
horizontal_flow.style.vertical_align = "center"
AddLabel(horizontal_flow, nil, { "oarc-set-respawn-loc-info-surface-label",
respawn_surface_name,
respawn_position.x,
respawn_position.y }, my_label_style)
--Add empty widget
local dragger = horizontal_flow.add {
type = "empty-widget",
style = "draggable_space_header"
}
dragger.style.horizontally_stretchable = true
CreateGPSButton(horizontal_flow, respawn_surface_name, respawn_position)
-- Sets the player's custom spawn point to their current location
if ((game.tick - global.player_cooldowns[player.name].setRespawn) >
(global.ocfg.gameplay.respawn_cooldown_min * TICKS_PER_MINUTE)) then
local change_respawn_button = container.add {
type = "button",
tags = { action = "oarc_spawn_ctrl_tab", setting = "set_respawn_location" },
name = "setRespawnLocation",
caption = { "oarc-set-respawn-loc" },
tooltip = { "oarc-set-respawn-loc-tooltip" },
style = "red_button"
}
change_respawn_button.style.font = "default-small-semibold"
else
AddLabel(container, nil,
{ "oarc-set-respawn-loc-cooldown", FormatTime((global.ocfg.gameplay.respawn_cooldown_min * TICKS_PER_MINUTE) -
(game.tick - global.player_cooldowns[player.name].setRespawn)) }, my_note_style)
end
AddLabel(container, nil, { "oarc-set-respawn-note" }, my_label_style)
AddSpacerLine(container)
end
---Display a list of people in the join queue for a shared spawn.
---@param player LuaPlayer
---@param container LuaGuiElement
---@return nil
function CreateJoinQueueControls(player, container)
local primary_spawn = FindPrimaryUniqueSpawn(player.name)
if (primary_spawn == nil) then return end
local shared_spawn_open = IsSharedSpawnOpen(primary_spawn.surface_name, player.name)
local shared_spawn_full = IsSharedSpawnFull(primary_spawn.surface_name, player.name)
-- Only show this if the player has an open and not full shared spawn
if (not shared_spawn_open or shared_spawn_full) then return end
if (table_size(primary_spawn.join_queue) > 0) then
AddLabel(container, nil, { "oarc-join-queue-header" }, "caption_label")
AddLabel(container, "drop_down_msg_lbl1", { "oarc-select-player-join-queue" }, my_label_style)
local horizontal_flow = container.add { type = "flow", direction = "horizontal" }
horizontal_flow.style.horizontally_stretchable = true
horizontal_flow.add {
name = "join_queue_dropdown",
type = "drop-down",
items = primary_spawn.join_queue
}
local dragger = horizontal_flow.add {
type = "empty-widget",
style = "draggable_space_header"
}
dragger.style.horizontally_stretchable = true
horizontal_flow.add {
name = "accept_player_request",
tags = { action = "oarc_spawn_ctrl_tab", setting = "accept_player_request" },
type = "button",
style = "green_button",
caption = { "oarc-accept" }
}
horizontal_flow.add {
name = "reject_player_request",
tags = { action = "oarc_spawn_ctrl_tab", setting = "reject_player_request" },
type = "button",
style = "red_button",
caption = { "oarc-reject" }
}
else
AddLabel(container, "empty_join_queue_note1", { "oarc-no-player-join-reqs" }, my_note_style)
end
end
---Display a GPS button for a specific location.
---@param container LuaGuiElement
---@param surface_name string
---@param position MapPosition
---@return nil
function CreateGPSButton(container, surface_name, position)
local gps_button = container.add {
type = "sprite-button",
sprite = "utility/gps_map_icon",
tags = {
action = "oarc_spawn_ctrl_tab",
setting = "show_location",
surface = surface_name,
position = position
},
style = "slot_button",
tooltip = { "oarc-spawn-info-location-button-tooltip" },
}
gps_button.style.width = 28
gps_button.style.height = 28
end
---Handle the gui checkboxes & radio buttons of the spawn control tab in the Oarc GUI.
---@param event EventData.on_gui_checked_state_changed
---@return nil
function SpawnCtrlGuiOptionsSelect(event)
if not event.element.valid then return end
local player = game.players[event.player_index]
local tags = event.element.tags
if (tags.action ~= "oarc_spawn_ctrl_tab") then
return
end
-- Handle changes to spawn sharing.
if (tags.setting == "shared_access_toggle") then
if event.element.state then
SendBroadcastMsg({ "oarc-start-shared-base", player.name })
else
SendBroadcastMsg({ "oarc-stop-shared-base", player.name })
end
local primary_spawn = FindPrimaryUniqueSpawn(player.name)
global.unique_spawns[primary_spawn.surface_name][player.name].open_access = event.element.state
OarcGuiRefreshContent(player)
-- Refresh the shared spawn spawn gui for all players
for _,p in pairs(game.connected_players) do
RefreshSharedSpawnFrameIfExist(p)
end
end
end
---Handle the gui click of the spawn control tab in the Oarc GUI.
---@param event EventData.on_gui_click
---@return nil
function SpawnCtrlGuiClick(event)
if not event.element.valid then return end
local player = game.players[event.player_index]
local tags = event.element.tags
if (tags.action ~= "oarc_spawn_ctrl_tab") then
return
end
-- Sets a new respawn point and resets the cooldown.
if (tags.setting == "set_respawn_location") then
SetPlayerRespawn(player.name, player.surface.name, player.position, true)
OarcGuiRefreshContent(player)
player.print({ "oarc-spawn-point-updated" })
-- Shows the spawn location on the map
elseif (tags.setting == "show_location") then
local surface_name = tags.surface --[[@as string]]
local position = tags.position --[[@as MapPosition]]
player.open_map(position, 0.05)
player.print({"", { "oarc-spawn-gps-location" }, GetGPStext(surface_name, position)})
-- Accept or reject pending player join requests to a shared base
elseif ((tags.setting == "accept_player_request") or (tags.setting == "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-valid" })
OarcGuiRefreshContent(player)
return
end
local join_queue_index = event.element.parent.join_queue_dropdown.selected_index
local join_queue_player_choice = event.element.parent.join_queue_dropdown.get_item(join_queue_index) --[[@as string]]
-- Shouldn't be able to hit this since we force a GUI refresh when they leave?
if ((game.players[join_queue_player_choice] == nil) or (not game.players[join_queue_player_choice].connected)) then
player.print({ "oarc-selected-player-not-wait" })
OarcGuiRefreshContent(player)
return
end
local primary_spawn = FindPrimaryUniqueSpawn(player.name)
if (tags.setting == "reject_player_request") then
RemovePlayerFromJoinQueue(join_queue_player_choice) -- This also refreshes the host gui
-- Inform the host that the player was rejected
player.print({ "oarc-reject-joiner", join_queue_player_choice })
-- Inform the player that their request was rejected
SendMsg(join_queue_player_choice, { "oarc-your-request-rejected" })
-- Close the waiting players menu
if (game.players[join_queue_player_choice].gui.screen.join_shared_spawn_wait_menu) then
game.players[join_queue_player_choice].gui.screen.join_shared_spawn_wait_menu.destroy()
DisplaySpawnOptions(game.players[join_queue_player_choice])
end
elseif (tags.setting == "accept_player_request") then
-- Check if there is space first
if (table_size(primary_spawn.joiners) >= global.ocfg.gameplay.number_of_players_per_shared_spawn - 1) then
player.print({ "oarc-shared-spawn-full" })
return
end
-- Send an announcement
SendBroadcastMsg({ "oarc-player-joining-base", join_queue_player_choice, player.name })
-- Close the waiting players menu
if (game.players[join_queue_player_choice].gui.screen.join_shared_spawn_wait_menu) then
game.players[join_queue_player_choice].gui.screen.join_shared_spawn_wait_menu.destroy()
end
-- Spawn the player
local joining_player = game.players[join_queue_player_choice]
SetPlayerRespawn(joining_player.name, primary_spawn.surface_name, primary_spawn.position, true)
SendPlayerToSpawn(primary_spawn.surface_name, joining_player)
GivePlayerStarterItems(joining_player)
table.insert(global.unique_spawns[primary_spawn.surface_name][player.name].joiners, joining_player.name)
joining_player.force = game.players[player.name].force
-- Render some welcoming text...
DisplayWelcomeGroundTextAtSpawn(joining_player, primary_spawn.surface_name, primary_spawn.position)
-- Unlock spawn control gui tab
SetOarcGuiTabEnabled(joining_player, OARC_SPAWN_CTRL_TAB_NAME, true)
RemovePlayerFromJoinQueue(join_queue_player_choice) -- This also refreshes the host gui
end
end
end

View File

@ -1,67 +0,0 @@
-- helper_commands.lua
-- Jan 2018
-- None of this is my code.
require("lib/oarc_utils")
commands.add_command("run", "change player speed bonus", function(command)
local player = game.players[command.player_index];
if player ~= nil then
if (command.parameter ~= nil) then
if command.parameter == "fast" then
player.character_running_speed_modifier = 1
elseif command.parameter == "slow" then
player.character_running_speed_modifier = 0
else
player.print("run fast | slow");
end
end
end
end)
commands.add_command("handcraft", "change player speed bonus", function(command)
local player = game.players[command.player_index];
if player ~= nil then
if (command.parameter ~= nil) then
if command.parameter == "fast" then
player.character_crafting_speed_modifier = 5
elseif command.parameter == "slow" then
player.character_crafting_speed_modifier = 0
else
player.print("handcraft fast | slow");
end
end
end
end)
commands.add_command("mine", "change player speed bonus", function(command)
local player = game.players[command.player_index];
if player ~= nil then
if (command.parameter ~= nil) then
if command.parameter == "fast" then
player.character_mining_speed_modifier = 2
elseif command.parameter == "slow" then
player.character_mining_speed_modifier = 0
else
player.print("mine fast | slow");
end
end
end
end)
commands.add_command("kit", "give a start kit", function(command)
local player = game.players[command.player_index];
if player ~= nil and player.admin then
local target = player
if (command.parameter ~= nil) then
target = game.players[command.parameter]
end
if target ~= nil then
GivePlayerStarterItems(target);
player.print("gave a kit to " .. target.name);
target.print("you have been given a start kit");
else
player.print("no player " .. command.parameter);
end
end
end)

143
lib/holding_pen.lua Normal file
View File

@ -0,0 +1,143 @@
-- This file is used to create the holding pen area where players spawn in before being teleported to their own area.
HOLDING_PEN_SURFACE_NAME = "oarc_holding_pen"
function CreateHoldingPenSurface()
if game.surfaces[HOLDING_PEN_SURFACE_NAME] ~= nil then
log("ERROR - Holding pen surface already exists!")
return
end
---@type MapGenSettings
---@diagnostic disable-next-line: missing-fields
local map_settings = {}
map_settings.terrain_segmentation = "none"
map_settings.water = "none"
map_settings.starting_area = "none"
map_settings.peaceful_mode = true
map_settings.width = 64
map_settings.height = 64
-- Create a new surface for the holding pen
local holding_pen_surface = game.create_surface(HOLDING_PEN_SURFACE_NAME, map_settings)
holding_pen_surface.always_day = true
holding_pen_surface.show_clouds = false
holding_pen_surface.generate_with_lab_tiles = true
RenderPermanentGroundText(holding_pen_surface, {x=9,y=-7}, 5, "O", {0.9, 0.7, 0.3, 0.5}, "center")
RenderPermanentGroundText(holding_pen_surface, {x=9,y=-4}, 5, "A", {0.9, 0.7, 0.3, 0.5}, "center")
RenderPermanentGroundText(holding_pen_surface, {x=9,y=-1}, 5, "R", {0.9, 0.7, 0.3, 0.5}, "center")
RenderPermanentGroundText(holding_pen_surface, {x=9,y=2}, 5, "C", {0.9, 0.7, 0.3, 0.5}, "center")
end
---Creates a holding pen area
---@param event EventData.on_chunk_generated
---@return nil
function CreateHoldingPenChunks(event)
local surface = event.surface
local chunk_area = event.area
local chunk_position = event.position
if (surface.name ~= HOLDING_PEN_SURFACE_NAME) then
return
end
-- Remove ALL entities in the chunk
for _, entity in pairs(surface.find_entities(chunk_area)) do
if entity.type ~= "character" then
entity.destroy()
end
end
-- Place tiles and trees and water for the holding pen
local tiles = {}
for x=chunk_area.left_top.x,chunk_area.right_bottom.x,1 do
for y=chunk_area.left_top.y,chunk_area.right_bottom.y,1 do
local distance_sqr = math.floor(x^2 + y^2)
if (distance_sqr < 15^2) then
table.insert(tiles, {name="grass-1", position={x, y}})
elseif (distance_sqr < 20^2) then
table.insert(tiles, {name="water", position={x, y}})
--10% chance of fish in water
if (math.random(1,10) == 1) then
surface.create_entity({name="fish", position={x + 0.5, y + 0.5}})
end
else
table.insert(tiles, {name="out-of-map", position={x, y}})
end
if (distance_sqr >= 13^2) and (distance_sqr <= 15^2) then
surface.create_entity({name="tree-01", position={x + 0.5, y + 0.5}})
end
end
end
surface.set_tiles(tiles)
-- If this is the bottom right chunk it's safe to place stuff inside the holding pen now.
if (chunk_position.x == 2 and chunk_position.y == 2) then
PlaceResourcesInSemiCircleHoldingPen(surface, {x=0,y=0}, 0.2, 0.1)
CreateWaterStrip(surface, {x=-2,y=-11}, 4)
CreateWaterStrip(surface, {x=-2,y=-10}, 4)
surface.create_entity({
name = "crude-oil",
amount = 90000,
position = { 0, 9 }
})
end
end
---A special version of PlaceResourcesInSemiCircle for the holding pen
---@param surface LuaSurface
---@param position TilePosition --The center of the spawn area
---@param size_mod number
---@param amount_mod number
---@return nil
function PlaceResourcesInSemiCircleHoldingPen(surface, position, size_mod, amount_mod)
local resources = global.ocfg.surfaces_config["nauvis"].spawn_config.solid_resources
-- Create list of resource tiles
---@type table<string>
local r_list = {}
for r_name, _ in pairs(resources) do
if (r_name ~= "") then
table.insert(r_list, r_name)
end
end
---@type table<string>
local shuffled_list = FYShuffle(r_list)
-- This places resources in a semi-circle
local angle_offset = 2.32
local num_resources = table_size(resources)
local theta = ((4.46 - 2.32) / num_resources);
local count = 0
-- Unique to the holding pen size
local radius = 15 - 6
for _, r_name in pairs(shuffled_list) do
local angle = (theta * count) + angle_offset;
local tx = (radius * math.cos(angle)) + position.x
local ty = (radius * math.sin(angle)) + position.y
local pos = { x = math.floor(tx), y = math.floor(ty) }
local resourceConfig = resources[r_name]
GenerateResourcePatch(surface, r_name, resourceConfig.size * size_mod, pos, resourceConfig.amount * amount_mod)
count = count + 1
end
end

View File

@ -1,970 +0,0 @@
-- map_features.lua
-- April 2020
-- Oarc's clone of whistlestop factories maybe?
-- Generic Utility Includes
require("lib/oarc_utils")
-- Used to generate placement of buildings.
MAGIC_BUILDING_MIN_DISTANCE = 40
MAGIC_BUILDING_MAX_DISTANCE = FAR_MAX_DIST + 50
MAGIC_BUILDING_CHUNK_SPREAD = 41
POWER_USAGE_SCALING_FACTOR = 2
-- This is a table indexed by the single INPUT item!
FURNACE_ENERGY_PER_CRAFT_SECOND = (180000 / 2) * POWER_USAGE_SCALING_FACTOR
FURNACE_RECIPES = {
["iron-ore"] = {recipe_name = "iron-plate",
recipe_energy = 3.2*FURNACE_ENERGY_PER_CRAFT_SECOND,
recipe_pollution = 0.053},
["copper-ore"] = {recipe_name = "copper-plate",
recipe_energy = 3.2*FURNACE_ENERGY_PER_CRAFT_SECOND,
recipe_pollution = 0.053},
["iron-plate"] = {recipe_name = "steel-plate",
recipe_energy = 16*FURNACE_ENERGY_PER_CRAFT_SECOND,
recipe_pollution = 0.267},
["stone"] = {recipe_name = "stone-brick",
recipe_energy = 3.2*FURNACE_ENERGY_PER_CRAFT_SECOND,
recipe_pollution = 0.053},
}
-- The chemplants/refineries/assemblers lookup their own recipes since they can be set by the player.
CHEMPLANT_ENERGY_PER_CRAFT_SECOND = 210000 * POWER_USAGE_SCALING_FACTOR
REFINERY_ENERGY_PER_CRAFT_SECOND = 420000 * POWER_USAGE_SCALING_FACTOR
ASSEMBLER3_ENERGY_PER_CRAFT_SECOND = (375000 / 1.25) * POWER_USAGE_SCALING_FACTOR
CENTRIFUGE_ENERGY_PER_CRAFT_SECOND = 350000 * POWER_USAGE_SCALING_FACTOR
CHEMPLANT_POLLUTION_PER_CRAFT_SECOND = 4/60
REFINERY_POLLUTION_PER_CRAFT_SECOND = 6/60
ASSEMBLER3_POLLUTION_PER_CRAFT_SECOND = 2/60
CENTRIFUGE_POLLUTION_PER_CRAFT_SECOND = 4/60
ENEMY_WORM_TURRETS =
{
[0] = "small-worm-turret",
[1] = "medium-worm-turret",
[2] = "big-worm-turret"
}
NEUTRAL_FORCE_RECIPES =
{
-- Science packs
["automation-science-pack"] = true,
["chemical-science-pack"] = true,
["logistic-science-pack"] = true,
["military-science-pack"] = true,
["production-science-pack"] = true,
["utility-science-pack"] = true,
-- Oil Stuff
["advanced-oil-processing"] = true,
["basic-oil-processing"] = true,
-- ["coal-liquefaction"] = true, -- Too difficult/costly to implement
["heavy-oil-cracking"] = true,
["light-oil-cracking"] = true,
["solid-fuel-from-heavy-oil"] = true,
["solid-fuel-from-light-oil"] = true,
["solid-fuel-from-petroleum-gas"] = true,
["lubricant"] = true,
["plastic-bar"] = true,
["sulfur"] = true,
["sulfuric-acid"] = true,
-- ["oil-refinery"] = true,
-- ["explosives"] = true,
-- Modules
["effectivity-module"] = true,
["effectivity-module-2"] = true,
["effectivity-module-3"] = true,
["productivity-module"] = true,
["productivity-module-2"] = true,
["productivity-module-3"] = true,
["speed-module"] = true,
["speed-module-2"] = true,
["speed-module-3"] = true,
-- Intermediates
["advanced-circuit"] = true,
["battery"] = true,
["copper-cable"] = true,
["copper-plate"] = true,
["electric-engine-unit"] = true,
["electronic-circuit"] = true,
["engine-unit"] = true,
["flying-robot-frame"] = true,
["iron-gear-wheel"] = true,
["iron-plate"] = true,
["iron-stick"] = true,
["low-density-structure"] = true,
["processing-unit"] = true,
["rocket-control-unit"] = true,
["rocket-fuel"] = true,
["steel-plate"] = true,
["stone-brick"] = true,
-- Misc
["concrete"] = true,
["landfill"] = true,
["rail"] = true,
["solar-panel"] = true,
["stone-wall"] = true,
["empty-barrel"] = true,
-- Nuclear
["uranium-processing"] = true,
-- ["kovarex-enrichment-process"] = true,
-- ["nuclear-fuel-reprocessing"] = true,
-- ["pipe"] = true,
-- ["pipe-to-ground"] = true,
}
function SetNeutralForceAllowedRecipes()
-- Neutral force requires recipes so that furnaces can smelt steel for example.
-- game.forces["neutral"].enable_all_recipes()
-- Disable ALL recipes
for i,v in pairs(game.forces["neutral"].recipes) do
game.forces["neutral"].recipes[i].enabled = false;
end
-- Enable only the ones we want
for i,v in pairs(NEUTRAL_FORCE_RECIPES) do
game.forces["neutral"].recipes[i].enabled = true;
end
end
function MagicFactoriesInit()
SetNeutralForceAllowedRecipes()
global.omagic = {}
global.omagic.building_total_count = 0
global.omagic.factory_positions = {}
global.omagic.furnaces = {}
global.omagic.chemplants = {}
global.omagic.refineries = {}
global.omagic.assemblers = {}
global.omagic.centrifuges = {}
MagicFactoryChunkGenerator()
game.surfaces[GAME_SURFACE_NAME].force_generate_chunk_requests() -- Block and generate all to be sure.
MagicalFactorySpawnAll()
end
function MagicFactoryChunkGenerator()
-- This generates several circles of randomized chunk positions.
for r=MAGIC_BUILDING_MIN_DISTANCE,MAGIC_BUILDING_MAX_DISTANCE,MAGIC_BUILDING_CHUNK_SPREAD do
local random_angle_offset = math.random(0, math.pi * 2)
local num_positions_for_circle = math.ceil((r/8)) -- This makes it so each circle has more dots, roughly spreads things out equally.
for i=1,num_positions_for_circle do
local theta = ((math.pi * 2) / num_positions_for_circle);
local angle = (theta * i) + random_angle_offset;
local chunk_x = MathRound((r * math.cos(angle)) + math.random(-2, 2))
local chunk_y = MathRound((r * math.sin(angle)) + math.random(-2, 2))
if (not game.surfaces[GAME_SURFACE_NAME].is_chunk_generated({chunk_x,chunk_y})) then
table.insert(global.omagic.factory_positions, {x=chunk_x, y=chunk_y})
game.surfaces[GAME_SURFACE_NAME].request_to_generate_chunks(GetCenterTilePosFromChunkPos({x=chunk_x, y=chunk_y}), 0)
log("Magic furnace position: " .. chunk_x .. ", " .. chunk_y .. ", " .. angle)
else
log("Magic furnace collided with silo location?" .. chunk_x .. ", " .. chunk_y)
end
end
end
SendBroadcastMsg("Number magic chunks: " .. #global.omagic.factory_positions)
end
function FindClosestMagicChunk(player)
if (not player or not player.character) then return end
return GetClosestPosFromTable(GetChunkPosFromTilePos(player.character.position), global.omagic.factory_positions)
end
function IndicateClosestMagicChunk(player)
local target_pos = GetCenterTilePosFromChunkPos(FindClosestMagicChunk(player))
rendering.draw_line{color={r=0.5,g=0.5,b=0.5,a=0.5},
width=2,
from=player.character,
to=target_pos,
surface=player.character.surface,
players={player},
draw_on_ground=true,
time_to_live=60*5}
end
function MagicalFactorySpawnAll()
for _,chunk_pos in pairs(global.omagic.factory_positions) do
local pos = GetCenterTilePosFromChunkPos(chunk_pos)
local c_area = GetAreaFromChunkPos(chunk_pos)
-- Remove any entities in the chunk area.
for _, entity in pairs(game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=c_area}) do
entity.destroy()
end
-- Place landfill underneath
local dirtTiles = {}
for i=c_area.left_top.x,c_area.right_bottom.x,1 do
for j=c_area.left_top.y,c_area.right_bottom.y,1 do
table.insert(dirtTiles, {name = "landfill", position ={i,j}})
end
end
game.surfaces[GAME_SURFACE_NAME].set_tiles(dirtTiles)
-- Yay colored tiles
CreateFixedColorTileArea(game.surfaces[GAME_SURFACE_NAME],
{left_top = {x=c_area.left_top.x+2, y=c_area.left_top.y+2},
right_bottom = {x=c_area.right_bottom.x-2, y=c_area.right_bottom.y-2}},
"black")
-- Make it safe from regrowth
if global.ocfg.enable_regrowth then
RegrowthMarkAreaSafeGivenTilePos(pos, 0, true)
end
end
end
function SpawnEnemyTurret(pos)
local turret = game.surfaces[GAME_SURFACE_NAME].create_entity{name="gun-turret", position=pos, force="enemy"}
local turret_inv = turret.get_inventory(defines.inventory.turret_ammo)
turret_inv.insert({name="uranium-rounds-magazine", count=200})
end
function RequestSpawnSpecialChunk(player, spawn_function, feature_name)
local closest_chunk = FindClosestMagicChunk(player)
local player_chunk = GetChunkPosFromTilePos(player.character.position)
if ((closest_chunk.x == player_chunk.x) and (closest_chunk.y == player_chunk.y)) then
local chunk_area = GetAreaFromChunkPos(closest_chunk)
local entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{
area={left_top = {chunk_area.left_top.x+1, chunk_area.left_top.y+1},
right_bottom = {chunk_area.right_bottom.x-1, chunk_area.right_bottom.y-1}},
force={"enemy"},
invert=true}
-- Either there are no entities in the chunk (player is just on the boundary), or the only entity is the player.
if ((#entities == 1) and (entities[1].player) and (entities[1].player == player)) or (#entities == 0) then
spawn_function(closest_chunk)
-- Teleport to center of chunk to be safe.
SafeTeleport(player, game.surfaces[GAME_SURFACE_NAME], GetCenterTilePosFromChunkPos(closest_chunk))
OarcMapFeaturePlayerCountChange(player, "special_chunks", feature_name, 1)
return true
else
player.print("Looks like this chunk already has something in it other than just you the player?! " .. entities[1].name)
return false
end
else
player.print("You need to be standing inside the special chunk!")
return false
end
return false
end
function SpecialChunkHelperText(pos)
RenderPermanentGroundText(game.surfaces[GAME_SURFACE_NAME].index,
{x=pos.x-3.5,y=pos.y+1},
1,
"Supply energy to this interface!",
{0.7,0.4,0.3,0.8})
RenderPermanentGroundText(game.surfaces[GAME_SURFACE_NAME].index,
{x=pos.x-4.5,y=pos.y+2},
1,
"Modules/beacons DO NOT have any effect!",
{0.7,0.4,0.3,0.8})
end
function spawnSpecialChunkInputElec(center_pos)
local inputElec = game.surfaces[GAME_SURFACE_NAME].create_entity{name="electric-energy-interface", position=center_pos, force="neutral"}
inputElec.destructible = false
inputElec.minable = false
inputElec.operable = false
inputElec.electric_buffer_size = 1000000000
inputElec.power_production = 0
inputElec.power_usage = 0
inputElec.energy = 0
return inputElec
end
function SpawnFurnaceChunk(chunk_pos)
center_pos = GetCenterTilePosFromChunkPos(chunk_pos)
local furnace_chunk = {["energy_input"] = spawnSpecialChunkInputElec(center_pos),
["entities"] = {}}
-- 4x furnaces
table.insert(furnace_chunk.entities, SpawnMagicBuilding("electric-furnace", {x=center_pos.x-12,y=center_pos.y-12}))
table.insert(furnace_chunk.entities, SpawnMagicBuilding("electric-furnace", {x=center_pos.x+11,y=center_pos.y-12}))
table.insert(furnace_chunk.entities, SpawnMagicBuilding("electric-furnace", {x=center_pos.x-12,y=center_pos.y+11}))
table.insert(furnace_chunk.entities, SpawnMagicBuilding("electric-furnace", {x=center_pos.x+11,y=center_pos.y+11}))
table.insert(global.omagic.furnaces, furnace_chunk)
SpecialChunkHelperText(center_pos)
end
function SpawnOilRefineryChunk(chunk_pos)
center_pos = GetCenterTilePosFromChunkPos(chunk_pos)
local oil_chunk = {["energy_input"] = spawnSpecialChunkInputElec(center_pos),
["chemplants"] = {},
["refineries"] = {}}
-- 2x Refineries
table.insert(oil_chunk.refineries, SpawnMagicBuilding("oil-refinery", {x=center_pos.x-5,y=center_pos.y-8}))
table.insert(oil_chunk.refineries, SpawnMagicBuilding("oil-refinery", {x=center_pos.x+5,y=center_pos.y-8}))
-- 6x Chem Plants
table.insert(oil_chunk.chemplants, SpawnMagicBuilding("chemical-plant", {x=center_pos.x-10,y=center_pos.y+8}))
table.insert(oil_chunk.chemplants, SpawnMagicBuilding("chemical-plant", {x=center_pos.x-6,y=center_pos.y+8}))
table.insert(oil_chunk.chemplants, SpawnMagicBuilding("chemical-plant", {x=center_pos.x-2,y=center_pos.y+8}))
table.insert(oil_chunk.chemplants, SpawnMagicBuilding("chemical-plant", {x=center_pos.x+2,y=center_pos.y+8}))
table.insert(oil_chunk.chemplants, SpawnMagicBuilding("chemical-plant", {x=center_pos.x+6,y=center_pos.y+8}))
table.insert(oil_chunk.chemplants, SpawnMagicBuilding("chemical-plant", {x=center_pos.x+10,y=center_pos.y+8}))
table.insert(global.omagic.refineries, oil_chunk)
table.insert(global.omagic.chemplants, oil_chunk)
SpecialChunkHelperText(center_pos)
end
function SpawnAssemblyChunk(chunk_pos)
center_pos = GetCenterTilePosFromChunkPos(chunk_pos)
local assembler_chunk = {["energy_input"] = spawnSpecialChunkInputElec(center_pos),
["entities"] = {}}
-- 6x Assemblers
table.insert(assembler_chunk.entities, SpawnMagicBuilding("assembling-machine-3", {x=center_pos.x-12,y=center_pos.y-12}))
table.insert(assembler_chunk.entities, SpawnMagicBuilding("assembling-machine-3", {x=center_pos.x,y=center_pos.y-12}))
table.insert(assembler_chunk.entities, SpawnMagicBuilding("assembling-machine-3", {x=center_pos.x+11,y=center_pos.y-12}))
table.insert(assembler_chunk.entities, SpawnMagicBuilding("assembling-machine-3", {x=center_pos.x-12,y=center_pos.y+11}))
table.insert(assembler_chunk.entities, SpawnMagicBuilding("assembling-machine-3", {x=center_pos.x-1,y=center_pos.y+11}))
table.insert(assembler_chunk.entities, SpawnMagicBuilding("assembling-machine-3", {x=center_pos.x+11,y=center_pos.y+11}))
table.insert(global.omagic.assemblers, assembler_chunk)
SpecialChunkHelperText(center_pos)
end
function SpawnCentrifugeChunk(chunk_pos)
center_pos = GetCenterTilePosFromChunkPos(chunk_pos)
local centrifuge_chunk = {["energy_input"] = spawnSpecialChunkInputElec(center_pos),
["entities"] = {}}
-- 1 Centrifuge (MORE THAN ENOUGH!)
table.insert(centrifuge_chunk.entities, SpawnMagicBuilding("centrifuge", {x=center_pos.x,y=center_pos.y-10}))
table.insert(global.omagic.centrifuges, centrifuge_chunk)
SpecialChunkHelperText(center_pos)
end
function SpawnSiloChunk(chunk_pos)
center_pos = GetCenterTilePosFromChunkPos(chunk_pos)
table.insert(global.siloPosition, center_pos)
RenderPermanentGroundText(game.surfaces[GAME_SURFACE_NAME].index,
{x=center_pos.x-3.25,y=center_pos.y+6},
1,
"You can build a silo here!",
{0.7,0.4,0.3,0.8})
-- Set tiles below the silo
tiles = {}
for dx = -6,5 do
for dy = -6,5 do
if (game.active_mods["oarc-restricted-build"]) then
table.insert(tiles, {name = global.ocfg.locked_build_area_tile,
position = {center_pos.x+dx, center_pos.y+dy}})
else
if ((dx % 2 == 0) or (dx % 2 == 0)) then
table.insert(tiles, {name = "concrete",
position = {center_pos.x+dx, center_pos.y+dy}})
else
table.insert(tiles, {name = "hazard-concrete-left",
position = {center_pos.x+dx, center_pos.y+dy}})
end
end
end
end
game.surfaces[GAME_SURFACE_NAME].set_tiles(tiles, true)
end
function SpawnMagicBuilding(entity_name, position)
local direction = defines.direction.north
if (entity_name == "oil-refinery") then
direction = defines.direction.south
end
local magic_building = game.surfaces[GAME_SURFACE_NAME].create_entity{name=entity_name, position=position, force="neutral", direction=direction}
magic_building.destructible = false
magic_building.minable = false
magic_building.operable = true
magic_building.active = false
global.omagic.building_total_count = global.omagic.building_total_count + 1
return magic_building
end
function MagicFactoriesOnTick()
MagicFurnaceOnTick()
MagicChemplantOnTick()
MagicRefineryOnTick()
MagicAssemblerOnTick()
MagicCentrifugeOnTick()
end
-- Some helpful math:
-- 94 per tick (max stack of ore in a smelter) (More like 2 or 3 ore per tick.)
-- blue belt = 45 / sec
-- 6 INPUT blue belts = 4.5 ore/tick (45 * 6 / 60) with productivity is an extra 0.9 maybe.
function MagicFurnaceOnTick()
if not global.omagic.furnaces then return end
for entry_idx,entry in pairs(global.omagic.furnaces) do
-- Validate the entry.
if (entry == nil) or (entry.entities == nil) or (entry.energy_input == nil) or (not entry.energy_input.valid) then
global.omagic.furnaces[entry_idx] = nil
log("MagicFurnaceOnTick - Magic furnace entry removed?")
goto next_furnace_entry
end
local energy_share = entry.energy_input.energy/#entry.entities
for idx,furnace in pairs(entry.entities) do
if (furnace == nil) or (not furnace.valid) then
global.omagic.furnaces[entry_idx] = nil
log("MagicFurnaceOnTick - Magic furnace removed?")
goto next_furnace_entry
end
local input_inv = furnace.get_inventory(defines.inventory.furnace_source)
local input_items = input_inv.get_contents()
-- We have something inside?
local input_item_name = next(input_items)
if not input_item_name then
goto next_furnace
end
-- Does the input item have a recipe?
if not FURNACE_RECIPES[input_item_name] then
log("MagicFurnaceOnTick - Missing FURNACE_RECIPES?")
goto next_furnace
end
local recipe = game.forces["neutral"].recipes[FURNACE_RECIPES[input_item_name].recipe_name]
if not recipe then
log("MagicFurnaceOnTick - Missing neutral force recipes?")
goto next_furnace
end
-- Verify 1 ingredient type and 1 product type (for furnaces)
if (#recipe.products ~= 1) or (#recipe.ingredients ~= 1) then
log("MagicFurnaceOnTick - Recipe product/ingredient more than 1?")
goto next_furnace
end
local recipe_ingredient = recipe.ingredients[next(recipe.ingredients)]
local recipe_product = recipe.products[next(recipe.products)]
local output_inv = furnace.get_inventory(defines.inventory.furnace_result)
-- Can we insert at least 1 of the recipe result?
-- if not output_inv.can_insert({name=recipe_product.name}) then goto next_furnace end
local output_space = output_inv.get_insertable_count(recipe_product.name)
-- Calculate how many times we can make the recipe.
local ingredient_limit = math.floor(input_items[input_item_name]/recipe_ingredient.amount)
local output_limit = math.floor(output_space/recipe_product.amount)
-- Use shared energy pool
local energy_limit = math.floor(energy_share/FURNACE_RECIPES[input_item_name].recipe_energy)
local recipe_count = math.min(ingredient_limit, output_limit, energy_limit)
-- Hit a limit somewhere?
if (recipe_count <= 0) then goto next_furnace end
-- Track energy usage
entry.energy_input.energy = entry.energy_input.energy - (FURNACE_RECIPES[input_item_name].recipe_energy*recipe_count)
furnace.surface.pollute(furnace.position, FURNACE_RECIPES[input_item_name].recipe_pollution*recipe_count)
-- Check if it has a last_user
if (not furnace.last_user) then
local player_entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{
position=furnace.position,
radius=10,
force={"enemy", "neutral"},
limit=1,
invert=true}
if (player_entities and player_entities[1] and player_entities[1].last_user) then
furnace.last_user = player_entities[1].last_user
end
end
-- Subtract recipe count from input and Add recipe count to output
input_inv.remove({name=recipe_ingredient.name, count=recipe_count*recipe_ingredient.amount})
output_inv.insert({name=recipe_product.name, count=recipe_count*recipe_product.amount})
furnace.products_finished = furnace.products_finished + recipe_count
-- If we have a user, do the stats
if (furnace.last_user) then
furnace.last_user.force.item_production_statistics.on_flow(recipe_ingredient.name, -recipe_count*recipe_ingredient.amount)
furnace.last_user.force.item_production_statistics.on_flow(recipe_product.name, recipe_count*recipe_product.amount)
end
::next_furnace::
end
::next_furnace_entry::
end
end
function MagicChemplantOnTick()
if not global.omagic.chemplants then return end
for entry_idx,entry in pairs(global.omagic.chemplants) do
-- Validate the entry.
if (entry == nil) or (entry.chemplants == nil) or (entry.energy_input == nil) or (not entry.energy_input.valid) then
global.omagic.chemplants[entry_idx] = nil
log("MagicChemplantOnTick - Magic assembler entry removed?")
goto next_chemplant_entry
end
local energy_share = entry.energy_input.energy/(#entry.chemplants + #entry.refineries)
for idx,chemplant in pairs(entry.chemplants) do
if (chemplant == nil) or (not chemplant.valid) then
global.omagic.chemplants[idx] = nil
log("Magic chemplant removed?")
goto next_chemplant_entry
end
recipe = chemplant.get_recipe()
if (not recipe) then
goto next_chemplant -- No recipe means do nothing.
end
local energy_cost = recipe.energy * CHEMPLANT_ENERGY_PER_CRAFT_SECOND
if (energy_share < energy_cost) then goto next_chemplant end -- Not enough energy!
local input_inv = chemplant.get_inventory(defines.inventory.assembling_machine_input)
local input_items = input_inv.get_contents()
local input_fluids = chemplant.get_fluid_contents()
for _,v in ipairs(recipe.ingredients) do
if (not input_items[v.name] or (input_items[v.name] < v.amount)) then
if (not input_fluids[v.name] or (input_fluids[v.name] < v.amount)) then
goto next_chemplant -- Not enough ingredients
end
end
end
local recipe_product = recipe.products[next(recipe.products)] -- Assume only 1 product.
if recipe_product.type == "fluid" then
if ((chemplant.get_fluid_count(recipe_product.name) + recipe_product.amount) > 100) then
goto next_chemplant -- Not enough space for ouput
end
chemplant.insert_fluid({name=recipe_product.name, amount=recipe_product.amount})
if (chemplant.last_user) then
chemplant.last_user.force.fluid_production_statistics.on_flow(recipe_product.name, recipe_product.amount)
end
-- Otherwise it must be an item type
else
local output_inv = chemplant.get_inventory(defines.inventory.assembling_machine_output)
-- Can we insert at least 1 of the recipe result?
if not output_inv.can_insert({name=recipe_product.name, amount=recipe_product.amount}) then goto next_chemplant end
-- Add recipe count to output
output_inv.insert({name=recipe_product.name, count=recipe_product.amount})
if (chemplant.last_user) then
chemplant.last_user.force.item_production_statistics.on_flow(recipe_product.name, recipe_product.amount)
end
end
-- Subtract ingredients from input
for _,v in ipairs(recipe.ingredients) do
if (input_items[v.name]) then
input_inv.remove({name=v.name, count=v.amount})
if (chemplant.last_user) then
chemplant.last_user.force.item_production_statistics.on_flow(v.name, -v.amount)
end
elseif (input_fluids[v.name]) then
chemplant.remove_fluid{name=v.name, amount=v.amount}
if (chemplant.last_user) then
chemplant.last_user.force.fluid_production_statistics.on_flow(v.name, -v.amount)
end
end
end
chemplant.products_finished = chemplant.products_finished + 1
-- Track energy usage
entry.energy_input.energy = entry.energy_input.energy - energy_cost
chemplant.surface.pollute(chemplant.position, recipe.energy*CHEMPLANT_POLLUTION_PER_CRAFT_SECOND)
::next_chemplant::
end
::next_chemplant_entry::
end
end
function MagicRefineryOnTick()
if not global.omagic.refineries then return end
for entry_idx,entry in pairs(global.omagic.refineries) do
-- Validate the entry.
if (entry == nil) or (entry.refineries == nil) or (entry.energy_input == nil) or (not entry.energy_input.valid) then
global.omagic.refineries[entry_idx] = nil
log("MagicRefineryOnTick - Magic assembler entry removed?")
goto next_refinery_entry
end
local energy_share = entry.energy_input.energy/(#entry.chemplants + #entry.refineries)
for idx,refinery in pairs(entry.refineries) do
if (refinery == nil) or (not refinery.valid) then
global.omagic.refineries[idx] = nil
log("Magic refinery removed?")
goto next_refinery_entry
end
recipe = refinery.get_recipe()
if (not recipe) then
goto next_refinery -- No recipe means do nothing.
end
local energy_cost = recipe.energy * REFINERY_ENERGY_PER_CRAFT_SECOND
if (energy_share < energy_cost) then goto next_refinery end -- Not enough energy!
local fluidbox_copy = refinery.fluidbox
-- If recipe is COAL LIQUEFACTION: heavy(1), steam(2), heavy(3), light(4), petro(5)
-- if (recipe.name == "coal-liquefaction") then
-- If recipe is Advanced OIL: water(1), crude(2), heavy(3), light(4), petro(5)
if (recipe.name == "advanced-oil-processing") then
if ((not refinery.fluidbox[1]) or (refinery.fluidbox[1].amount < 50)) then goto next_refinery end -- Not enough water
if ((not refinery.fluidbox[2]) or (refinery.fluidbox[2].amount < 100)) then goto next_refinery end -- Not enough crude
if ((refinery.fluidbox[3]) and (refinery.fluidbox[3].amount > 25)) then goto next_refinery end -- Not enough space for heavy
if ((refinery.fluidbox[4]) and (refinery.fluidbox[4].amount > 45)) then goto next_refinery end -- Not enough space for light
if ((refinery.fluidbox[5]) and (refinery.fluidbox[5].amount > 55)) then goto next_refinery end -- Not enough space for petro
refinery.remove_fluid{name="water", amount=50}
refinery.remove_fluid{name="crude-oil", amount=100}
refinery.insert_fluid({name="heavy-oil", amount=25})
refinery.insert_fluid({name="light-oil", amount=45})
refinery.insert_fluid({name="petroleum-gas", amount=55})
if (refinery.last_user) then
refinery.last_user.force.fluid_production_statistics.on_flow("water", -50)
refinery.last_user.force.fluid_production_statistics.on_flow("crude-oil", -100)
refinery.last_user.force.fluid_production_statistics.on_flow("heavy-oil", 25)
refinery.last_user.force.fluid_production_statistics.on_flow("light-oil", 45)
refinery.last_user.force.fluid_production_statistics.on_flow("petroleum-gas", 55)
end
-- If recipe is Basic OIL: crude(1), petro(2)
elseif (recipe.name == "basic-oil-processing") then
if ((not refinery.fluidbox[1]) or (refinery.fluidbox[1].amount < 100)) then goto next_refinery end -- Not enough crude
if ((refinery.fluidbox[2]) and (refinery.fluidbox[2].amount > 45)) then goto next_refinery end -- Not enough space for petro
refinery.remove_fluid{name="crude-oil", amount=100}
refinery.insert_fluid({name="petroleum-gas", amount=45})
if (refinery.last_user) then
refinery.last_user.force.fluid_production_statistics.on_flow("crude-oil", -100)
refinery.last_user.force.fluid_production_statistics.on_flow("petroleum-gas", 45)
end
else
goto next_refinery -- Shouldn't hit this...
end
refinery.products_finished = refinery.products_finished + 1
-- Track energy usage
entry.energy_input.energy = entry.energy_input.energy - energy_cost
refinery.surface.pollute(refinery.position, recipe.energy*REFINERY_POLLUTION_PER_CRAFT_SECOND)
::next_refinery::
end
::next_refinery_entry::
end
end
function MagicAssemblerOnTick()
if not global.omagic.assemblers then return end
for entry_idx,entry in pairs(global.omagic.assemblers) do
-- Validate the entry.
if (entry == nil) or (entry.entities == nil) or (entry.energy_input == nil) or (not entry.energy_input.valid) then
global.omagic.assemblers[entry_idx] = nil
log("MagicAssemblerOnTick - Magic assembler entry removed?")
goto next_assembler_entry
end
local energy_share = entry.energy_input.energy/#entry.entities
for idx,assembler in pairs(entry.entities) do
if (assembler == nil) or (not assembler.valid) then
global.omagic.assemblers[entry_idx] = nil
log("MagicAssemblerOnTick - Magic assembler removed?")
goto next_assembler_entry
end
recipe = assembler.get_recipe()
if (not recipe) then
goto next_assembler -- No recipe means do nothing.
end
local energy_cost = recipe.energy * ASSEMBLER3_ENERGY_PER_CRAFT_SECOND
if (energy_share < energy_cost) then goto next_assembler end -- Not enough energy!
-- Assume only 1 product and that it's an item!
local recipe_product = recipe.products[next(recipe.products)]
if recipe_product.type ~= "item" then goto next_assembler end
local input_inv = assembler.get_inventory(defines.inventory.assembling_machine_input)
local input_items = input_inv.get_contents()
local input_fluids = assembler.get_fluid_contents()
for _,v in ipairs(recipe.ingredients) do
if (not input_items[v.name] or (input_items[v.name] < v.amount)) then
if (not input_fluids[v.name] or (input_fluids[v.name] < v.amount)) then
goto next_assembler -- Not enough ingredients
end
end
end
local output_inv = assembler.get_inventory(defines.inventory.assembling_machine_output)
if not output_inv.can_insert({name=recipe_product.name, amount=recipe_product.amount}) then
goto next_assembler -- Can we insert the result?
end
-- Add recipe count to output
output_inv.insert({name=recipe_product.name, count=recipe_product.amount})
if (assembler.last_user) then
assembler.last_user.force.item_production_statistics.on_flow(recipe_product.name, recipe_product.amount)
end
-- Subtract ingredients from input
for _,v in ipairs(recipe.ingredients) do
if (input_items[v.name]) then
input_inv.remove({name=v.name, count=v.amount})
if (assembler.last_user) then
assembler.last_user.force.item_production_statistics.on_flow(v.name, -v.amount)
end
elseif (input_fluids[v.name]) then
assembler.remove_fluid{name=v.name, amount=v.amount}
if (assembler.last_user) then
assembler.last_user.force.fluid_production_statistics.on_flow(v.name, -v.amount)
end
end
end
-- Track energy usage
entry.energy_input.energy = entry.energy_input.energy - energy_cost
assembler.surface.pollute(assembler.position, recipe.energy*ASSEMBLER3_POLLUTION_PER_CRAFT_SECOND)
assembler.products_finished = assembler.products_finished + 1
::next_assembler::
end
::next_assembler_entry::
end
end
function MagicCentrifugeOnTick()
if not global.omagic.centrifuges then return end
for entry_idx,entry in pairs(global.omagic.centrifuges) do
-- Validate the entry.
if (entry == nil) or (entry.entities == nil) or (entry.energy_input == nil) or (not entry.energy_input.valid) then
global.omagic.centrifuges[entry_idx] = nil
log("MagicCentrifugeOnTick - Magic centrifuge entry removed?")
goto next_centrifuge_entry
end
local energy_share = entry.energy_input.energy/#entry.entities
for idx,centrifuge in pairs(entry.entities) do
if (centrifuge == nil) or (not centrifuge.valid) then
global.omagic.centrifuges[entry_idx] = nil
log("MagicCentrifugeOnTick - Magic centrifuge removed?")
goto next_centrifuge_entry
end
recipe = centrifuge.get_recipe()
if (not recipe) then
goto next_centrifuge -- No recipe means do nothing.
end
local energy_cost = recipe.energy * CENTRIFUGE_ENERGY_PER_CRAFT_SECOND
if (energy_share < energy_cost) then goto next_centrifuge end -- Not enough energy!
local input_inv = centrifuge.get_inventory(defines.inventory.assembling_machine_input)
local input_items = input_inv.get_contents()
for _,v in ipairs(recipe.ingredients) do
if (not input_items[v.name] or (input_items[v.name] < v.amount)) then
goto next_centrifuge -- Not enough ingredients
end
end
local output_inv = centrifuge.get_inventory(defines.inventory.assembling_machine_output)
local output_item, output_count
-- 10 uranium ore IN
-- .993 uranium-238 and .007 uranium-235 OUT
if (recipe.name == "uranium-processing") then
local rand_chance = math.random()
output_count = 1
if (rand_chance <= .007) then
output_item = "uranium-235"
else
output_item = "uranium-238"
end
-- Check if we can insert at least 1 of BOTH.
if not output_inv.can_insert({name="uranium-235", amount=output_count}) then
goto next_centrifuge
end
if not output_inv.can_insert({name= "uranium-238", amount=output_count}) then
goto next_centrifuge
end
output_inv.insert({name=output_item, count=output_count})
if (centrifuge.last_user) then
centrifuge.last_user.force.item_production_statistics.on_flow(output_item, output_count)
end
for _,v in ipairs(recipe.ingredients) do
if (input_items[v.name]) then
input_inv.remove({name=v.name, count=v.amount})
if (centrifuge.last_user) then
centrifuge.last_user.force.item_production_statistics.on_flow(v.name, -v.amount)
end
end
end
else
goto next_centrifuge -- Unsupported!
end
centrifuge.products_finished = centrifuge.products_finished + 1
-- Track energy usage
entry.energy_input.energy = entry.energy_input.energy - energy_cost
centrifuge.surface.pollute(centrifuge.position, recipe.energy*CENTRIFUGE_POLLUTION_PER_CRAFT_SECOND)
::next_centrifuge::
end
::next_centrifuge_entry::
end
end
COIN_MULTIPLIER = 2
COIN_GENERATION_CHANCES = {
["small-biter"] = 0.01,
["medium-biter"] = 0.02,
["big-biter"] = 0.05,
["behemoth-biter"] = 1,
["small-spitter"] = 0.01,
["medium-spitter"] = 0.02,
["big-spitter"] = 0.05,
["behemoth-spitter"] = 1,
["small-worm-turret"] = 5,
["medium-worm-turret"] = 10,
["big-worm-turret"] = 15,
["behemoth-worm-turret"] = 25,
["biter-spawner"] = 20,
["spitter-spawner"] = 20,
}
function CoinsFromEnemiesOnPostEntityDied(event)
if (not event.prototype or not event.prototype.name) then return end
local coin_chance = nil
if (COIN_GENERATION_CHANCES[event.prototype.name]) then
coin_chance = COIN_GENERATION_CHANCES[event.prototype.name]
end
if (coin_chance) then
DropCoins(event.position, coin_chance, event.force)
end
end
-- Drop coins, force is optional, decon is applied if force is not nil.
function DropCoins(pos, count, force)
local drop_amount = 0
-- If count is less than 1, it represents a probability to drop a single coin
if (count < 1) then
if (math.random() < count) then
drop_amount = 1
end
-- If count is 1 or more, it represents a probability to drop at least that amount and up to 3x
elseif (count >= 1) then
drop_amount = math.random(count,count*COIN_MULTIPLIER)
end
if drop_amount == 0 then return end
game.surfaces[GAME_SURFACE_NAME].spill_item_stack(pos, {name="coin", count=math.floor(drop_amount)}, true, force, false) -- Set nil to force to auto decon.
end

View File

@ -1,35 +0,0 @@
-- notepad.lua
-- Oarc's simple notepad cause I keep forgetting what I want to do next.
function CreateNotepadGuiTab(tab_container, player)
if global.oarc_notepad == nil then
global.oarc_notepad = {}
end
if global.oarc_notepad[player.name] == nil then
global.oarc_notepad[player.name] = "Write something here...!"
end
AddLabel(tab_container, "notepad_info", "Use this to take notes:", my_longer_label_style)
local txt_box = tab_container.add{type="text-box", name="oarc_notepad_textbox", text=global.oarc_notepad[player.name]}
ApplyStyle(txt_box, my_notepad_fixed_width_style)
txt_box.focus()
end
function NotepadOnGuiTextChange(event)
if (event.element.name ~= "oarc_notepad_textbox") then return end
local player = game.players[event.player_index]
if global.oarc_notepad == nil then
global.oarc_notepad = {}
end
global.oarc_notepad[player.name] = event.element.text
end

View File

@ -1,283 +0,0 @@
-- oarc_buy.lua
-- May 2020
-- Adding microtransactions.
require("lib/oarc_store_player_items")
require("lib/oarc_store_map_features")
local mod_gui = require("mod-gui")
-- NAME of the top level element (outer frame)
OARC_STORE_GUI = "oarc_store_gui"
OARC_PLAYER_STORE_GUI_TAB_NAME = "Item Store"
OARC_MAP_FEATURE_GUI_TAB_NAME = "Special Store"
local OARC_STORE_TAB_CONTENT_FUNCTIONS = {}
OARC_STORE_TAB_CONTENT_FUNCTIONS[OARC_PLAYER_STORE_GUI_TAB_NAME] = CreatePlayerStoreTab
OARC_STORE_TAB_CONTENT_FUNCTIONS[OARC_MAP_FEATURE_GUI_TAB_NAME] = CreateMapFeatureStoreTab
function InitOarcStoreGuiTabs(player)
CreateOarcStoreButton(player)
CreateOarcStoreTabsPane(player)
-- Store for personal items
AddOarcStoreTab(player, OARC_PLAYER_STORE_GUI_TAB_NAME)
SetOarcStoreTabEnabled(player, OARC_PLAYER_STORE_GUI_TAB_NAME, true)
-- Store for map feature stuff
AddOarcStoreTab(player, OARC_MAP_FEATURE_GUI_TAB_NAME)
SetOarcStoreTabEnabled(player, OARC_MAP_FEATURE_GUI_TAB_NAME, true)
HideOarcStore(player)
end
function CreateOarcStoreButton(player)
if (mod_gui.get_button_flow(player).oarc_store == nil) then
local b = mod_gui.get_button_flow(player).add{name="oarc_store",
type="sprite-button",
sprite="item/coin",
style=mod_gui.button_style}
b.style.padding=2
end
end
function DoesOarcStoreExist(player)
return (mod_gui.get_frame_flow(player)[OARC_STORE_GUI] ~= nil)
end
function IsOarcStoreVisible(player)
local of = mod_gui.get_frame_flow(player)[OARC_STORE_GUI]
return (of.visible)
end
function ShowOarcStore(player)
local of = mod_gui.get_frame_flow(player)[OARC_STORE_GUI]
if (of == nil) then return end
of.visible = true
player.opened = of
end
function HideOarcStore(player)
local of = mod_gui.get_frame_flow(player)[OARC_STORE_GUI]
if (of == nil) then return end
of.visible = false
player.opened = nil
end
function GetOarcStoreTabsPane(player)
if (mod_gui.get_frame_flow(player)[OARC_STORE_GUI] == nil) then
return nil
else
return mod_gui.get_frame_flow(player)[OARC_STORE_GUI].store_if.store_tabs
end
end
function ClickOarcStoreButton(event)
if not (event and event.element and event.element.valid) then return end
local button = event.element
local player = game.players[event.player_index]
-- Don't allow any clicks on the store while player is dead!
if (not player or player.ticks_to_respawn) then
if (DoesOarcStoreExist(player)) then
HideOarcStore(player)
end
return
end
if (button.name == "oarc_store") then
if (not DoesOarcStoreExist(player)) then
CreateOarcStoreTabsPane(player)
else
if (IsOarcStoreVisible(player)) then
HideOarcStore(player)
else
ShowOarcStore(player)
FakeTabChangeEventOarcStore(player)
end
end
elseif ((button.parent ~= nil) and (button.parent.parent ~= nil)) then
if (button.parent.parent.name == OARC_PLAYER_STORE_GUI_TAB_NAME.."_if") then
OarcPlayerStoreButton(event)
elseif (button.parent.parent.name == OARC_MAP_FEATURE_GUI_TAB_NAME.."_if") then
OarcMapFeatureStoreButton(event)
end
end
end
function TabChangeOarcStore(event)
if (event.element.name ~= "store_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
SetOarcStoreTabContent(player, selected_tab_name)
end
function FakeTabChangeEventOarcStore(player)
local event = {}
event.element = GetOarcStoreTabsPane(player)
event.player_index = player.index
TabChangeOarcStore(event)
end
function CreateOarcStoreTabsPane(player)
if (mod_gui.get_frame_flow(player)[OARC_STORE_GUI] == nil) then
-- OUTER FRAME (TOP GUI ELEMENT)
local frame = mod_gui.get_frame_flow(player).add{
type = 'frame',
name = OARC_STORE_GUI,
direction = "vertical"}
frame.style.padding = 5
-- INNER FRAME
local inside_frame = frame.add{
type = "frame",
name = "store_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",
direction = "vertical"}
AddLabel(subhead, "store_info", "OARC Microtransactions and DLC", "subheader_caption_label")
-- TABBED PANE
local store_tabs = inside_frame.add{
name="store_tabs",
type="tabbed-pane",
style="tabbed_pane"}
store_tabs.style.top_padding = 8
end
end
function AddOarcStoreTab(player, tab_name)
-- if (not DoesOarcStoreExist(player)) then
-- CreateOarcStoreTabsPane(player)
-- end
-- Get the tabbed pane
local otabs = GetOarcStoreTabsPane(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
end
function SetOarcStoreTabContent(player, tab_name)
if (not DoesOarcStoreExist(player)) then return end
local otabs = GetOarcStoreTabsPane(player)
for _,t in ipairs(otabs.tabs) do
if (t.tab.name == tab_name) then
t.content.clear()
OARC_STORE_TAB_CONTENT_FUNCTIONS[tab_name](t.content, player)
return
end
end
end
function SetOarcStoreTabEnabled(player, tab_name, enable)
if (not DoesOarcStoreExist(player)) then return end
local otabs = GetOarcStoreTabsPane(player)
for _,t in ipairs(otabs.tabs) do
if (t.tab.name == tab_name) then
t.tab.enabled = enable
return
end
end
end
function OarcStoreOnGuiClosedEvent(event)
if (event.element and (event.element.name == OARC_STORE_GUI)) then
HideOarcStore(game.players[event.player_index])
end
end
commands.add_command("donate-coins", "Toss a Coin to Your Witcher", function(command)
local player = game.players[command.player_index]
if (command.parameter == nil) then
player.print("Invalid parameters? /donate-coins [username] [amount]")
return
end
local target, amount
local count = 1
for i in string.gmatch(command.parameter, "%S+") do
if (count == 1) then
target = i
end
if (count == 2) then
amount = i
end
count = count + 1
end
if (count ~= 3) then
player.print("Invalid parameters (count = " ..count..")? /donate-coins [username] [amount]")
return
end
-- Validate all the things...
if (game.players[target] and
not game.players[target].ticks_to_respawn and
amount and
player and
player.get_main_inventory()) then
local target_player = game.players[target]
local amount_number = tonumber(amount)
if ((amount_number > 0) and (player.get_main_inventory().get_item_count("coin") >= amount_number)) then
local transfer = target_player.get_main_inventory().insert({name="coin", count=amount_number})
player.get_main_inventory().remove({name="coin", count=transfer})
player.print("You transfered " .. transfer .. " coins to " .. target .. ".")
target_player.print("You received " .. transfer .. " coins from " .. player.name .. ".")
else
player.print("You can't transfer what you don't have... (Not enough coins!)")
end
end
end)

View File

@ -1,163 +0,0 @@
-- oarc_enemies.lua
-- Feb 2020
-- This is my second attempt at modifying the normal enemy experience. The
-- first attempt ended up in a wave attack system which wasn't well received.
-- This attempt will try to intercept normal vanilla enemy groups and modify
-- them based on player activity.
-- Basic logic:
-- on_unit_group_finished_gathering we check what command is given.
-- find destination position
-- check for closest "player" using find_nearest_enemy function
-- if a player is found, check if player is part of a shared spawn
-- Remove the enemy group if no player in the shared spawn is online.
-- TODO:
-- Add options for modifying the default waves or spawning additional special waves.
-- Add option to disable attacks completely for a given spawn.
-- Generic Utility Includes
require("lib/oarc_utils")
function OarcModifyEnemyGroup(group)
-- Check validity
if ((group == nil) or (group.command == nil) or (group.force.name ~= "enemy")) then
log("OarcModifyEnemyGroup ignoring INVALID group/command")
return
end
-- Make sure the attack is of a TYPE that we care about.
if ((group.command.type == defines.command.attack) or
(group.command.type == defines.command.attack_area) or
(group.command.type == defines.command.build_base)) then
log("OarcModifyEnemyGroup MODIFYING command TYPE=" .. group.command.type)
else
log("OarcModifyEnemyGroup ignoring command TYPE=" .. group.command.type)
return
end
-- defines.command.attack --> target --> target.position
if (group.command.type == defines.command.attack) then
log("OarcModifyEnemyGroup defines.command.attack NOT IMPLEMENTED YET!")
return
-- defines.command.attack_area --> destination --> closest enemy (within 3 chunk radius?)
-- defines.command.build_base --> destination --> closest enemy (expansion chunk distance?)
else
local destination = group.command.destination
local distance = CHUNK_SIZE*3
if (group.command.type == defines.command.build_base) then
distance = CHUNK_SIZE*(game.map_settings.enemy_expansion.max_expansion_distance)
end
-- Find some enemies near the attack point.
local target_entities = group.surface.find_entities_filtered{
position=destination,
radius=distance,
force={"enemy", "neutral"},
limit=50,
invert=true}
-- Search through them all to find anything with a last_user.
local target_entity = nil
for _,target in ipairs(target_entities) do
if (target.last_user ~= nil) then
target_entity = target
break
end
end
-- No enemies nearby?
if (target_entity == nil) then
if (group.command.type == defines.command.attack_area) then
if (global.enable_oe_debug) then
SendBroadcastMsg("OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!? " .. GetGPStext(group.position) .. " Target: " .. GetGPStext(destination))
end
log("OarcModifyEnemyGroup UNEXPECTED find_nearest_enemy did not find anything!")
for _,member in pairs(group.members) do
member.destroy()
end
else
log("OarcModifyEnemyGroup find_nearest_enemy did not find anything!")
end
return
end
-- Probably don't need this I hope?
if (target_entity.force == "neutral") then
log("OarcModifyEnemyGroup UNEXPECTED find_nearest_enemy found neutral target?")
for _,member in pairs(group.members) do
member.destroy()
end
return
end
-- Most common target will be a built entity with a "last_user"
local target_player = target_entity.last_user
-- Target could also be a player character (more rare)
if (target_player == nil) and (target_entity.type == "character") then
target_player = target_entity.player
end
-- I don't think this should happen...
if ((target_player == nil) or (not target_player.valid)) then
if (global.enable_oe_debug) then
SendBroadcastMsg("ERROR?? target_player nil/invalid " .. GetGPStext(group.position) .. " Target: " .. GetGPStext(target_entity.position))
end
log("OarcModifyEnemyGroup ERROR?? target_player nil/invalid")
for _,member in pairs(group.members) do
member.destroy()
end
return
end
-- Is the target player online? Then the attack can go through.
if (target_player.connected) then
if (global.enable_oe_debug) then
SendBroadcastMsg("Enemy group released (player): " .. GetGPStext(group.position) .. " Target: " .. GetGPStext(target_entity.position) .. " " .. target_player.name)
end
log("OarcModifyEnemyGroup RELEASING enemy group since player is ONLINE")
return
end
-- Find the shared spawn that the player is part of.
-- This could be the own player's spawn (quite likely)
local sharedSpawnOwnerName = FindPlayerSharedSpawn(target_player.name)
-- Is someone in the shared spawn online?
if (sharedSpawnOwnerName ~= nil) then
if (GetOnlinePlayersAtSharedSpawn(sharedSpawnOwnerName) > 0) then
if (global.enable_oe_debug) then
SendBroadcastMsg("Enemy group released (shared): " .. GetGPStext(group.position) .. " Target: " .. GetGPStext(target_entity.position) .. " " .. target_player.name)
end
log("OarcModifyEnemyGroup RELEASING enemy group since someone in the group is ONLINE")
return
end
end
-- Is there a buddy spawn and is the buddy online?
local buddyName = global.ocore.buddyPairs[sharedSpawnOwnerName]
if (buddyName ~= nil) and (game.players[buddyName] ~= nil) then
if (game.players[buddyName].connected or (GetOnlinePlayersAtSharedSpawn(buddyName) > 0)) then
if (global.enable_oe_debug) then
SendBroadcastMsg("Enemy group released (buddy): " .. GetGPStext(group.position) .. " Target: " .. GetGPStext(target_entity.position) .. " " .. target_player.name)
end
log("OarcModifyEnemyGroup RELEASING enemy group since someone in the BUDDY PAIR is ONLINE")
return
end
end
-- Otherwise, we delete the group.
if (global.enable_oe_debug) then
SendBroadcastMsg("Enemy group deleted: " .. GetGPStext(group.position) .. " Target: " .. GetGPStext(target_entity.position) .. " " .. target_player.name)
end
for _,member in pairs(group.members) do
member.destroy()
end
log("OarcModifyEnemyGroup REMOVED enemy group since nobody was online?")
end
end

View File

@ -1,102 +0,0 @@
-- 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 (ONLY - no more mod version.)
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_miner_decon = ENABLE_MINER_AUTODECON
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.enable_coin_shop = ENABLE_COIN_SHOP
global.ocfg.enable_chest_sharing = ENABLE_ITEM_AND_ENERGY_SHARING
global.ocfg.enable_magic_factories = ENABLE_MAGIC_FACTORIES
global.ocfg.enable_offline_protect = ENABLE_OFFLINE_PROTECTION
global.ocfg.enable_power_armor_start = ENABLE_POWER_ARMOR_QUICK_START
global.ocfg.enable_modular_armor_start = ENABLE_MODULAR_ARMOR_QUICK_START
global.ocfg.lock_goodies_rocket_launch = LOCK_GOODIES_UNTIL_ROCKET_LAUNCH
global.ocfg.scale_resources_around_spawns = SCALE_RESOURCES_AROUND_SPAWNS
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_anti_grief = ENABLE_ANTI_GRIEFING
global.ocfg.ghost_ttl = GHOST_TIME_TO_LIVE
global.ocfg.enable_friendly_fire = ENABLE_FRIENDLY_FIRE
global.ocfg.enable_server_write_files = ENABLE_SERVER_WRITE_FILES
-----------------------
-- 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
if (not global.ocfg.enable_coin_shop) then
global.ocfg.enable_chest_sharing = false
end
if (not global.ocfg.enable_chest_sharing) then
global.ocfg.enable_magic_factories = false
end
end

View File

@ -1,6 +1,10 @@
-- oarc_gui_tabs.lua
-- A nice way to organize the GUI tabs.
local mod_gui = require("mod-gui")
require("lib/gui_tabs/server_info")
require("lib/gui_tabs/spawn_controls")
require("lib/gui_tabs/settings_controls")
require("lib/gui_tabs/mod_info_faq")
--------------------------------------------------------------------------------
-- GUI Tab Handler
@ -10,117 +14,121 @@ local mod_gui = require("mod-gui")
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"
OARC_SHARED_ITEMS_GUI_TAB_NAME = "Shared Items"
OARC_NOTEPAD_GUI_TAB_NAME = "Notepad"
OARC_SERVER_INFO_TAB_NAME = "server_info"
OARC_SPAWN_CTRL_TAB_NAME = "spawn_controls"
OARC_CONFIG_CTRL_TAB_NAME = "settings"
OARC_MOD_INFO_CTRL_TAB_NAME = "mod_info"
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
OARC_GUI_TAB_CONTENT_FUNCTIONS["Shared Items"] = CreateSharedItemsGuiTab
OARC_GUI_TAB_CONTENT_FUNCTIONS["Notepad"] = CreateNotepadGuiTab
OARC_SERVER_INFO_TAB_LOCALIZED = {"oarc-server-info-tab-title"}
OARC_SPAWN_CTRL_TAB_LOCALIZED = {"oarc-spawn-ctrls-tab-title"}
OARC_CONFIG_CTRL_TAB_LOCALIZED = {"oarc-settings-tab-title"}
OARC_MOD_INFO_CTRL_TAB_LOCALIZED = {"oarc-mod-info-tab-title"}
local OARC_GUI_TAB_CONTENT_FUNCTIONS = {
[OARC_SERVER_INFO_TAB_NAME] = CreateServerInfoTab,
[OARC_SPAWN_CTRL_TAB_NAME] = CreateSpawnControlsTab,
[OARC_MOD_INFO_CTRL_TAB_NAME] = CreateModInfoTab,
[OARC_CONFIG_CTRL_TAB_NAME] = CreateSettingsControlsTab,
}
---@param player LuaPlayer
---@return nil
function InitOarcGuiTabs(player)
-- Make safe to call multiple times
if (DoesOarcGuiExist(player)) then
return
end
CreateOarcGuiButton(player)
-- Add general info tab
AddOarcGuiTab(player, OARC_GAME_OPTS_GUI_TAB_NAME)
SetOarcGuiTabEnabled(player, OARC_GAME_OPTS_GUI_TAB_NAME, true)
AddOarcGuiTab(player, OARC_SERVER_INFO_TAB_NAME, OARC_SERVER_INFO_TAB_LOCALIZED)
SetOarcGuiTabEnabled(player, OARC_SERVER_INFO_TAB_NAME, true)
-- Spawn control tab, disabled by default
AddOarcGuiTab(player, OARC_SPAWN_CTRL_GUI_NAME)
AddOarcGuiTab(player, OARC_SPAWN_CTRL_TAB_NAME, OARC_SPAWN_CTRL_TAB_LOCALIZED)
-- 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
-- Regrowth control tab
AddOarcGuiTab(player, OARC_MOD_INFO_CTRL_TAB_NAME, OARC_MOD_INFO_CTRL_TAB_LOCALIZED)
SetOarcGuiTabEnabled(player, OARC_MOD_INFO_CTRL_TAB_NAME, true)
-- Player tags
if global.ocfg.enable_tags then
AddOarcGuiTab(player, OARC_TAGS_GUI_TAB_NAME)
SetOarcGuiTabEnabled(player, OARC_TAGS_GUI_TAB_NAME, true)
end
-- Settings control tab
AddOarcGuiTab(player, OARC_CONFIG_CTRL_TAB_NAME, OARC_CONFIG_CTRL_TAB_LOCALIZED)
SetOarcGuiTabEnabled(player, OARC_CONFIG_CTRL_TAB_NAME, true)
-- Rockets tab, only enable if one has been launched already
AddOarcGuiTab(player, OARC_ROCKETS_GUI_TAB_NAME)
if (global.ocore.satellite_sent) then
SetOarcGuiTabEnabled(player, OARC_ROCKETS_GUI_TAB_NAME, true)
end
if global.ocfg.enable_chest_sharing then
AddOarcGuiTab(player, OARC_SHARED_ITEMS_GUI_TAB_NAME)
SetOarcGuiTabEnabled(player, OARC_SHARED_ITEMS_GUI_TAB_NAME, true)
end
AddOarcGuiTab(player, OARC_NOTEPAD_GUI_TAB_NAME)
SetOarcGuiTabEnabled(player, OARC_NOTEPAD_GUI_TAB_NAME, true)
HideOarcGui(player)
end
---@param player LuaPlayer
---@return nil
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",
caption="CLICK ME FOR MORE INFO",
type="sprite-button",
-- sprite="utility/expand_dots",
style=mod_gui.button_style}
b.style.padding=2
-- b.style.width=20
if (mod_gui.get_button_flow(player).oarc_mod_gui_button == nil) then
local b = mod_gui.get_button_flow(player).add{
name="oarc_mod_gui_button",
type="sprite-button",
sprite = "oarc-mod-sprite-40",
style="slot_button",
tooltip={ "oarc-gui-tooltip" }
}
b.style.padding=0
end
end
---@param player LuaPlayer
---@return boolean
function DoesOarcGuiExist(player)
return (mod_gui.get_frame_flow(player)[OARC_GUI] ~= nil)
end
---@param player LuaPlayer
---@return boolean
function IsOarcGuiVisible(player)
---@type LuaGuiElement
local of = mod_gui.get_frame_flow(player)[OARC_GUI]
return (of.visible)
end
---@param player LuaPlayer
---@return nil
function ShowOarcGui(player)
---@type LuaGuiElement
local of = mod_gui.get_frame_flow(player)[OARC_GUI]
if (of == nil) then return end
of.visible = true
player.opened = of
end
---@param player LuaPlayer
---@return nil
function HideOarcGui(player)
---@type LuaGuiElement
local of = mod_gui.get_frame_flow(player)[OARC_GUI]
if (of == nil) then return end
of.visible = false
player.opened = nil
end
---@param player LuaPlayer
---@return LuaGuiElement?
function GetOarcGuiTabsPane(player)
if (mod_gui.get_frame_flow(player)[OARC_GUI] == nil) then
---@type LuaGuiElement
local of = mod_gui.get_frame_flow(player)[OARC_GUI]
if (of == nil) then
return nil
else
return mod_gui.get_frame_flow(player)[OARC_GUI].oarc_if.oarc_tabs
return of.oarc_if.oarc_tabs
end
end
---@param event EventData.on_gui_click
---@return nil
function ClickOarcGuiButton(event)
if not (event and event.element and event.element.valid) then return end
if not event.element.valid then return end
local player = game.players[event.player_index]
local name = event.element.name
if (name ~= "oarc_button") then return end
if (event.element.caption ~= "") then
event.element.caption = ""
event.element.style.width = 20
event.element.sprite="utility/expand_dots"
end
if (name ~= "oarc_mod_gui_button") then return end
if (not DoesOarcGuiExist(player)) then
CreateOarcGuiTabsPane(player)
@ -129,33 +137,48 @@ function ClickOarcGuiButton(event)
HideOarcGui(player)
else
ShowOarcGui(player)
FakeTabChangeEventOarcGui(player)
OarcGuiCreateContentOfTab(player)
end
end
end
function TabChangeOarcGui(event)
---@param event EventData.on_gui_selected_tab_changed
---@return nil
function OarcGuiSelectedTabChanged(event)
if (event.element.name ~= "oarc_tabs") then return end
OarcGuiCreateContentOfTab(game.players[event.player_index])
end
local player = game.players[event.player_index]
local otabs = event.element
local selected_tab_name = otabs.tabs[otabs.selected_tab_index].tab.name
---Set tab content to currently selected tab, clears all other tab content and refreshes the selected tab content!
---Safe to call just to refresh the current tab.
---@param player LuaPlayer
---@return nil
function OarcGuiCreateContentOfTab(player)
local otabs = GetOarcGuiTabsPane(player)
if (otabs == nil) then return end
-- Clear all tab contents
for i,t in pairs(otabs.tabs) do
local tab_name = otabs.tabs[otabs.selected_tab_index].tab.name
-- log("OarcGuiCreateContentOfTab: " .. tab_name)
for _,t in ipairs(otabs.tabs) do
t.content.clear()
if (t.tab.name == tab_name) then
OARC_GUI_TAB_CONTENT_FUNCTIONS[tab_name](t.content, player)
end
end
SetOarGuiTabContent(player, selected_tab_name)
end
function FakeTabChangeEventOarcGui(player)
local event = {}
event.element = GetOarcGuiTabsPane(player)
event.player_index = player.index
TabChangeOarcGui(event)
---Just an alias for OarcGuiCreateContentOfTab
---@param player LuaPlayer
---@return nil
function OarcGuiRefreshContent(player)
-- log("Hit OarcGuiRefreshContent" .. player.name)
OarcGuiCreateContentOfTab(player)
end
---@param player LuaPlayer
---@return nil
function CreateOarcGuiTabsPane(player)
if (mod_gui.get_frame_flow(player)[OARC_GUI] == nil) then
@ -179,8 +202,9 @@ function CreateOarcGuiTabsPane(player)
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")
style = "changelog_subheader_frame"
}
AddLabel(subhead, nil, {"oarc-gui-tab-header-label"}, "subheader_caption_label")
-- TABBED PANE
local oarc_tabs = inside_frame.add{
@ -193,7 +217,10 @@ end
-- Function creates a new tab.
-- It adds whatever it wants to the provided scroll-pane.
function AddOarcGuiTab(player, tab_name)
---@param player LuaPlayer
---@param tab_name string
---@param localized_name LocalisedString
function AddOarcGuiTab(player, tab_name, localized_name)
if (not DoesOarcGuiExist(player)) then
CreateOarcGuiTabsPane(player)
end
@ -201,11 +228,13 @@ function AddOarcGuiTab(player, tab_name)
-- Get the tabbed pane
local otabs = GetOarcGuiTabsPane(player)
if (otabs == nil) then return end
-- Create new tab
local new_tab = otabs.add{
type="tab",
name=tab_name,
caption=tab_name}
caption=localized_name}
-- Create inside frame for content
local tab_inside_frame = otabs.add{
@ -235,21 +264,10 @@ function AddOarcGuiTab(player, tab_name)
end
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
---This sets the enable state of a tab.
---@param player LuaPlayer
---@param tab_name string
---@param enable boolean
function SetOarcGuiTabEnabled(player, tab_name, enable)
if (not DoesOarcGuiExist(player)) then return end
@ -263,6 +281,9 @@ function SetOarcGuiTabEnabled(player, tab_name, enable)
end
end
---Switches the tab to the one specified.
---@param player LuaPlayer
---@param tab_name string
function SwitchOarcGuiTab(player, tab_name)
if (not DoesOarcGuiExist(player)) then return end
@ -271,14 +292,15 @@ function SwitchOarcGuiTab(player, tab_name)
for i,t in pairs(otabs.tabs) do
if (t.tab.name == tab_name) then
otabs.selected_tab_index = i
FakeTabChangeEventOarcGui(player)
OarcGuiCreateContentOfTab(player)
return
end
end
end
function OarcGuiOnGuiClosedEvent(event)
--@param event EventData.on_gui_closed
function OarcGuiClosed(event)
if (event.element and (event.element.name == "oarc_gui")) then
HideOarcGui(game.players[event.player_index])
end
end
end

View File

@ -1,16 +1,18 @@
-- oarc_gui_utils.lua
-- Mar 2019
-- Generic GUI stuff goes here.
GENERIC_GUI_MAX_HEIGHT = 500
--------------------------------------------------------------------------------
-- GUI Styles
--------------------------------------------------------------------------------
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_fixed_width_style = {
minimal_width = 450,
maximal_width = 450
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_label_style = {
-- minimal_width = 450,
-- maximal_width = 50,
@ -19,6 +21,8 @@ my_label_style = {
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_label_header_style = {
single_line = false,
font = "heading-1",
@ -26,6 +30,17 @@ my_label_header_style = {
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_label_header2_style = {
single_line = false,
font = "heading-2",
font_color = {r=1,g=1,b=1},
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_label_header_grey_style = {
single_line = false,
font = "heading-1",
@ -33,40 +48,55 @@ my_label_header_grey_style = {
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_note_style = {
-- minimal_width = 450,
single_line = false,
font = "default-small-semibold",
font = "default-semibold",
font_color = {r=1,g=0.5,b=0.5},
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_warning_style = {
-- minimal_width = 450,
-- maximal_width = 450,
single_line = false,
font_color = {r=1,g=0.1,b=0.1},
font = "default-bold",
font_color = {r=1,g=0.3,b=0.3},
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_spacer_style = {
minimal_height = 10,
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_small_button_style = {
font = "default-small-semibold"
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_player_list_fixed_width_style = {
minimal_width = 200,
maximal_width = 400,
maximal_height = 200
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_shared_item_list_fixed_width_style = {
minimal_width = 200,
maximal_width = 600,
maximal_height = 600
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_player_list_admin_style = {
font = "default-semibold",
font_color = {r=1,g=0.5,b=0.5},
@ -75,6 +105,8 @@ my_player_list_admin_style = {
bottom_padding = 0,
single_line = false,
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_player_list_style = {
font = "default-semibold",
minimal_width = 200,
@ -82,6 +114,8 @@ my_player_list_style = {
bottom_padding = 0,
single_line = false,
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_player_list_offline_style = {
-- font = "default-semibold",
font_color = {r=0.5,g=0.5,b=0.5},
@ -90,11 +124,16 @@ my_player_list_offline_style = {
bottom_padding = 0,
single_line = false,
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_player_list_style_spacer = {
minimal_height = 20,
}
---@type Color
my_color_red = {r=1,g=0.1,b=0.1}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_longer_label_style = {
maximal_width = 600,
single_line = false,
@ -102,6 +141,8 @@ my_longer_label_style = {
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_longer_warning_style = {
maximal_width = 600,
single_line = false,
@ -109,6 +150,8 @@ my_longer_warning_style = {
top_padding = 0,
bottom_padding = 0
}
---@type LuaStyle
---@diagnostic disable-next-line: missing-fields
my_notepad_fixed_width_style = {
minimal_width = 600,
maximal_width = 600,
@ -124,29 +167,42 @@ my_notepad_fixed_width_style = {
-- GUI Functions
--------------------------------------------------------------------------------
-- Apply a style option to a GUI
function ApplyStyle (guiIn, styleIn)
for k,v in pairs(styleIn) do
guiIn.style[k]=v
---Apply a style option to a GUI
---@param gui_element LuaGuiElement
---@param style_in table
---@return nil
function ApplyStyle (gui_element, style_in)
for k,v in pairs(style_in) do
gui_element.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}
---Shorter way to add a label with a style
---@param gui_element LuaGuiElement
---@param name string?
---@param message LocalisedString
---@param style table|string
---@return LuaGuiElement
function AddLabel(gui_element, name, message, style)
local g = gui_element.add{name = name, type = "label", caption=message}
if (type(style) == "table") then
ApplyStyle(g, style)
else
g.style = style
end
return g
end
-- Shorter way to add a spacer
function AddSpacer(guiIn)
ApplyStyle(guiIn.add{type = "label", caption=" "}, my_spacer_style)
---Shorter way to add a spacer
---@param gui_element LuaGuiElement
---@return nil
function AddSpacer(gui_element)
ApplyStyle(gui_element.add{type = "label", caption=" "}, my_spacer_style)
end
function AddSpacerLine(guiIn)
ApplyStyle(guiIn.add{type = "line", direction="horizontal"}, my_spacer_style)
---Shorter way to add a spacer line
---@param gui_element LuaGuiElement
---@return nil
function AddSpacerLine(gui_element)
ApplyStyle(gui_element.add{type = "line", direction="horizontal"}, my_spacer_style)
end

View File

@ -1,337 +0,0 @@
-- oarc_store_map_features.lua
-- May 2020
-- Adding microtransactions.
require("lib/shared_chests")
require("lib/map_features")
local mod_gui = require("mod-gui")
OARC_STORE_MAP_TEXT =
{
special_chests = "Special buildings for sharing or monitoring items and energy. This will convert the closest wooden chest (to you) within 16 tiles into a special building of your choice. Make sure to leave enough space! The combinators and accumulators can take up several tiles around them.",
special_chunks = "Map features that can be built on the special empty chunks found on the map. You must be standing inside an empty special chunk to be able to build these. Each player can only build one of each type. [color=red]THESE FEATURES ARE PERMANENT AND CAN NOT BE REMOVED![/color]",
special_buttons = "Special buttons like teleporting home and placing waterfill.",
reset_buttons = "Reset your player and base. [color=red]Choose carefully! Can't be undone.[/color] If you don't own a base and your own force, some options may not be available to you."
}
-- N = number already purchased
-- Cost = initial + (additional * ( N^multiplier ))
OARC_STORE_MAP_FEATURES =
{
special_chests = {
["logistic-chest-storage"] = {
initial_cost = 200,
additional_cost = 20,
multiplier_cost = 2,
max_cost = 2000,
-- limit = 100,
text="Input chest for storing shared items."},
["logistic-chest-requester"] = {
initial_cost = 200,
additional_cost = 50,
multiplier_cost = 2,
max_cost = 4000,
-- limit = 100,
text="Output chest for requesting shared items."},
["constant-combinator"] = {
initial_cost = 50,
text="Combinator setup to monitor shared items."},
["accumulator"] = {
initial_cost = 200,
additional_cost = 50,
multiplier_cost = 2,
max_cost = 2000,
-- limit = 100,
text="INPUT for shared energy system. [color=red]Only starts to share once it is charged to 50%.[/color]"},
["electric-energy-interface"] = {
initial_cost = 200,
additional_cost = 100,
multiplier_cost = 2,
max_cost = 4000,
-- limit = 100,
text="OUTPUT for shared energy system. [color=red]Will NOT power other special eletric interfaces! You especially can't power special chunks with this![/color]"},
["deconstruction-planner"] = {
initial_cost = 0,
text="Removes the closest special building within range. NO REFUNDS!"},
},
special_chunks = {
["electric-furnace"] = {
initial_cost = 1000,
additional_cost = 1000,
multiplier_cost = 2,
-- limit = 3,
text="Build a special furnace chunk here. Contains 4 furnaces that run at very high speeds. [color=red]Requires energy from the shared storage. Modules have no effect![/color]"},
["oil-refinery"] = {
initial_cost = 1000,
additional_cost = 1000,
multiplier_cost = 2,
-- limit = 3,
text="Build a special oil refinery chunk here. Contains 2 refineries and some chemical plants that run at very high speeds. [color=red]Requires energy from the shared storage. Modules have no effect![/color]"},
["assembling-machine-3"] = {
initial_cost = 1000,
additional_cost = 1000,
multiplier_cost = 2,
-- limit = 3,
text="Build a special assembly machine chunk here. Contains 6 assembling machines that run at very high speeds. [color=red]Requires energy from the shared storage. Modules have no effect![/color]"},
["centrifuge"] = {
initial_cost = 1000,
additional_cost = 1000,
multiplier_cost = 2,
-- limit = 1,
text="Build a special centrifuge chunk here. Contains 1 centrifuge that runs at very high speeds. [color=red]Requires energy from the shared storage. Modules have no effect![/color]"},
["rocket-silo"] = {
initial_cost = 1000,
additional_cost = 0,
multiplier_cost = 2,
max_cost = 10000,
-- limit = 2,
text="Convert this special chunk into a rocket launch pad. This allows you to build a rocket silo here!"},
},
-- special_chunks_upgrades = {
-- ["big-electric-pole"] = {
-- cost = 0,
-- text = "Upgrade your special chunk so that it pulls power from the cloud! Refills the accumulator from the cloud automatically if it falls below 50%."
-- }
-- }
special_buttons = {
["assembling-machine-1"] = {
initial_cost = 10,
text="Teleport home."},
["offshore-pump"] = {
initial_cost = 50,
text="Converts the closest empty wooden chest into a water tile!"
}
},
reset_buttons = {
["electronic-circuit"] = {
initial_cost = 5000,
solo_force = true,
text="DESTROY your base and restart. This allows you to choose a new spawn and will completely destroy all your buildings and your force. All technology progress will be reset. You get to keep your current items and armor! [color=red]THERE IS NO CONFIRMATION PROMPT! THIS CAN NOT BE UNDONE![/color]"
},
["advanced-circuit"] = {
initial_cost = 5000,
solo_force = true,
text="ABANDON your base and restart. This allows you to choose a new spawn and will move all your buildings to a neutral force. They will still be on the map and can be interacted with, but will not be owned by any player or player force. All radars will be destroyed to help trim map size. You get to keep your current items and armor! [color=red]THERE IS NO CONFIRMATION PROMPT! THIS CAN NOT BE UNDONE![/color]"
},
["processing-unit"] = {
initial_cost = 5000,
text="Restart your game. This will reset your player, your force and your base. [color=red]THERE IS NO CONFIRMATION PROMPT! THIS CAN NOT BE UNDONE![/color]"
}
}
}
function CreateMapFeatureStoreTab(tab_container, player)
local player_inv = player.get_main_inventory()
if (player_inv == nil) then return end
local wallet = player_inv.get_item_count("coin")
AddLabel(tab_container,
"map_feature_store_wallet_lbl",
"Coins Available: " .. wallet .. " [item=coin]",
{top_margin=5, bottom_margin=5})
AddLabel(tab_container, "coin_info", "Players start with some coins. Earn more coins by killing enemies.", my_note_style)
local line = tab_container.add{type="line", direction="horizontal"}
line.style.top_margin = 5
line.style.bottom_margin = 5
for category,section in pairs(OARC_STORE_MAP_FEATURES) do
if (not global.ocfg.enable_chest_sharing and (category == "special_chests")) then
goto SKIP_CATEGORY
end
if (not global.ocfg.enable_magic_factories and (category == "special_chunks")) then
goto SKIP_CATEGORY
end
AddLabel(tab_container,
nil,
OARC_STORE_MAP_TEXT[category],
{bottom_margin=5, maximal_width = 400, single_line = false})
local flow = tab_container.add{name = category, type="flow", direction="horizontal"}
for item_name,item in pairs(section) do
local blocked = false
if (item.solo_force and ((player.force.name == global.ocfg.main_force) or
(not global.ocore.playerSpawns[player.name]))) then
blocked = true
end
local count = OarcMapFeaturePlayerCountGet(player, category, item_name)
local cost = OarcMapFeatureCostScaling(player, category, item_name)
local color = "[color=green]"
if ((cost > wallet) or (cost < 0) or blocked) then
color = "[color=red]"
end
local btn = flow.add{name=item_name,
type="sprite-button",
-- number=item.count,
sprite="item/"..item_name,
-- tooltip=item.text.." Cost: "..color..cost.."[/color] [item=coin]",
style=mod_gui.button_style}
if (cost < 0) then
btn.enabled = false
btn.tooltip = item.text .. "\n "..color..
"Limit: ("..count.."/"..item.limit..") [/color]"
elseif (blocked) then
btn.enabled = false
btn.tooltip = item.text .. " (This is only allowed for players on their own force that own the spawn. If you have other players on your force, they must reset first before you can use this.)" .." Cost: "..color..cost.."[/color] [item=coin]"
elseif (item.limit) then
btn.tooltip = item.text .. "\nCost: "..color..cost.."[/color] [item=coin] "..
"Limit: ("..count.."/"..item.limit..")"
else
btn.tooltip = item.text.." Cost: "..color..cost.."[/color] [item=coin]"
end
end
-- Spacer
local line2 = tab_container.add{type="line", direction="horizontal"}
line2.style.top_margin = 5
line2.style.bottom_margin = 5
::SKIP_CATEGORY::
end
end
function OarcMapFeatureInitGlobalCounters()
global.oarc_store = {}
global.oarc_store.pmf_counts = {}
end
function OarcMapFeaturePlayerCreatedEvent(player)
global.oarc_store.pmf_counts[player.name] = {}
end
function OarcMapFeaturePlayerCountGet(player, category_name, feature_name)
if (not global.oarc_store.pmf_counts[player.name][feature_name]) then
global.oarc_store.pmf_counts[player.name][feature_name] = 0
return 0
end
return global.oarc_store.pmf_counts[player.name][feature_name]
end
function OarcMapFeaturePlayerCountChange(player, category_name, feature_name, change)
if (not global.oarc_store.pmf_counts[player.name][feature_name]) then
if (change < 0) then
log("ERROR - OarcMapFeaturePlayerCountChange - Removing when count is not set??")
end
global.oarc_store.pmf_counts[player.name][feature_name] = change
return
end
-- Update count
global.oarc_store.pmf_counts[player.name][feature_name] = global.oarc_store.pmf_counts[player.name][feature_name] + change
-- Make sure we don't go below 0.
if (global.oarc_store.pmf_counts[player.name][feature_name] < 0) then
global.oarc_store.pmf_counts[player.name][feature_name] = 0
end
end
-- Return cost (0 or more) or return -1 if disabled.
function OarcMapFeatureCostScaling(player, category_name, feature_name)
local map_feature = OARC_STORE_MAP_FEATURES[category_name][feature_name]
-- Check limit first.
local count = OarcMapFeaturePlayerCountGet(player, category_name, feature_name)
if (map_feature.limit and (count >= map_feature.limit)) then
return -1
end
if (map_feature.initial_cost and map_feature.additional_cost and map_feature.multiplier_cost) then
local calc_cost = (map_feature.initial_cost + (map_feature.additional_cost*(count^map_feature.multiplier_cost)))
if (map_feature.max_cost) then
return math.min(map_feature.max_cost, calc_cost)
else
return calc_cost
end
else
return map_feature.initial_cost
end
end
function OarcMapFeatureStoreButton(event)
local button = event.element
local player = game.players[event.player_index]
local player_inv = player.get_inventory(defines.inventory.character_main)
if (player_inv == nil) then return end
local wallet = player_inv.get_item_count("coin")
local map_feature = OARC_STORE_MAP_FEATURES[button.parent.name][button.name]
-- Calculate cost based on how many player has purchased?
local cost = OarcMapFeatureCostScaling(player, button.parent.name, button.name)
-- Check if we have enough money
if (wallet < cost) then
player.print("You're broke! Go kill some enemies or beg for change...")
return
end
if (player.vehicle) then
player.print("Sir, please step out of the vehicle before you try to make any purchases...")
return
end
-- Each button has a special function
local result = false
if (button.name == "logistic-chest-storage") then
result = ConvertWoodenChestToSharedChestInput(player)
elseif (button.name == "logistic-chest-requester") then
result = ConvertWoodenChestToSharedChestOutput(player)
elseif (button.name == "constant-combinator") then
result = ConvertWoodenChestToSharedChestCombinators(player)
elseif (button.name == "accumulator") then
result = ConvertWoodenChestToShareEnergyInput(player)
elseif (button.name == "electric-energy-interface") then
result = ConvertWoodenChestToShareEnergyOutput(player)
elseif (button.name == "deconstruction-planner") then
result = DestroyClosestSharedChestEntity(player)
elseif (button.name == "electric-furnace") then
result = RequestSpawnSpecialChunk(player, SpawnFurnaceChunk, button.name)
elseif (button.name == "oil-refinery") then
result = RequestSpawnSpecialChunk(player, SpawnOilRefineryChunk, button.name)
elseif (button.name == "assembling-machine-3") then
result = RequestSpawnSpecialChunk(player, SpawnAssemblyChunk, button.name)
elseif (button.name == "centrifuge") then
result = RequestSpawnSpecialChunk(player, SpawnCentrifugeChunk, button.name)
elseif (button.name == "rocket-silo") then
result = RequestSpawnSpecialChunk(player, SpawnSiloChunk, button.name)
elseif (button.name == "assembling-machine-1") then
SendPlayerToSpawn(player)
result = true
elseif (button.name == "offshore-pump") then
result = ConvertWoodenChestToWaterFill(player)
elseif (button.name == "electronic-circuit") then
ResetPlayerAndDestroyForce(player)
result = true
elseif (button.name == "advanced-circuit") then
ResetPlayerAndAbandonForce(player)
result = true
elseif (button.name == "processing-unit") then
ResetPlayerAndMergeForceToNeutral(player)
result = true
end
-- On success, we deduct money
if (result) then
player_inv.remove({name = "coin", count = cost})
end
-- Refresh GUI:
FakeTabChangeEventOarcStore(player)
end

View File

@ -1,160 +0,0 @@
-- oarc_store_player_items.lua
-- May 2020
-- Adding microtransactions.
local mod_gui = require("mod-gui")
OARC_STORE_PLAYER_ITEMS =
{
["Guns"] = {
["pistol"] = {cost = 1, count = 1, play_time_locked=false},
["shotgun"] = {cost = 5, count = 1, play_time_locked=false},
["submachine-gun"] = {cost = 10, count = 1, play_time_locked=false},
["flamethrower"] = {cost = 50, count = 1, play_time_locked=true},
["rocket-launcher"] = {cost = 50, count = 1, play_time_locked=true},
-- ["railgun"] = {cost = 250, count = 1, play_time_locked=true}, -- SAD
},
["Turrets"] = {
["gun-turret"] = {cost = 25, count = 1, play_time_locked=false},
["flamethrower-turret"] = {cost = 50, count = 1, play_time_locked=false},
["laser-turret"] = {cost = 75, count = 1, play_time_locked=false},
["artillery-turret"] = {cost = 500, count = 1, play_time_locked=true},
},
["Ammo"] = {
["firearm-magazine"] = {cost = 10, count = 10, play_time_locked=false},
["piercing-rounds-magazine"] = {cost = 30, count = 10, play_time_locked=false},
["shotgun-shell"] = {cost = 10, count = 10, play_time_locked=false},
["flamethrower-ammo"] = {cost = 50, count = 10, play_time_locked=true},
["rocket"] = {cost = 100, count = 10, play_time_locked=true},
-- ["railgun-dart"] = {cost = 250, count = 10, play_time_locked=true}, -- SAD
["atomic-bomb"] = {cost = 1000, count = 1, play_time_locked=true},
["artillery-shell"] = {cost = 50, count = 1, play_time_locked=true},
},
["Special"] = {
["repair-pack"] = {cost = 1, count = 1, play_time_locked=false},
["raw-fish"] = {cost = 1, count = 1, play_time_locked=false},
["grenade"] = {cost = 20, count = 10, play_time_locked=true},
["cliff-explosives"] = {cost = 20, count = 10, play_time_locked=true},
["artillery-targeting-remote"] = {cost = 500, count = 1, play_time_locked=true},
},
["Capsules/Mines"] = {
["land-mine"] = {cost = 20, count = 10, play_time_locked=false},
["defender-capsule"] = {cost = 20, count = 10, play_time_locked=false},
["distractor-capsule"] = {cost = 40, count = 10, play_time_locked=false},
["destroyer-capsule"] = {cost = 60, count = 10, play_time_locked=false},
["poison-capsule"] = {cost = 50, count = 10, play_time_locked=false},
["slowdown-capsule"] = {cost = 25, count = 10, play_time_locked=false},
},
["Armor"] = {
["light-armor"] = {cost = 10, count = 1, play_time_locked=false},
["heavy-armor"] = {cost = 20, count = 1, play_time_locked=false},
["modular-armor"] = {cost = 200, count = 1, play_time_locked=false},
["power-armor"] = {cost = 1000, count = 1, play_time_locked=false},
["power-armor-mk2"] = {cost = 5000, count = 1, play_time_locked=false},
},
["Power Equipment"] = {
["fusion-reactor-equipment"] = {cost = 1000, count = 1, play_time_locked=false},
["battery-equipment"] = {cost = 100, count = 1, play_time_locked=false},
["battery-mk2-equipment"] = {cost = 1000, count = 1, play_time_locked=false},
["solar-panel-equipment"] = {cost = 10, count = 1, play_time_locked=false},
},
["Bot Equipment"] = {
["personal-roboport-equipment"] = {cost = 100, count = 1, play_time_locked=false},
["personal-roboport-mk2-equipment"] = {cost = 500, count = 1, play_time_locked=false},
["construction-robot"] = {cost = 100, count = 10, play_time_locked=false},
["roboport"] = {cost = 1000, count = 1, play_time_locked=false},
["logistic-chest-storage"] = {cost = 100, count = 1, play_time_locked=false},
},
["Misc Equipment"] = {
["belt-immunity-equipment"] = {cost = 10, count = 1, play_time_locked=false},
["exoskeleton-equipment"] = {cost = 100, count = 1, play_time_locked=false},
["night-vision-equipment"] = {cost = 50, count = 1, play_time_locked=false},
["personal-laser-defense-equipment"] = {cost = 100, count = 1, play_time_locked=false},
-- ["discharge-defense-equipment"] = {cost = 1, count = 1, play_time_locked=false},
["energy-shield-equipment"] = {cost = 50, count = 1, play_time_locked=false},
["energy-shield-mk2-equipment"] = {cost = 500, count = 1, play_time_locked=false},
},
["Spidertron"] = {
["spidertron"] = {cost = 5000, count = 1, play_time_locked=false},
["spidertron-remote"] = {cost = 500, count = 1, play_time_locked=false},
},
}
function CreatePlayerStoreTab(tab_container, player)
local player_inv = player.get_main_inventory()
if (player_inv == nil) then return end
local wallet = player_inv.get_item_count("coin")
AddLabel(tab_container,
"player_store_wallet_lbl",
"Coins Available: " .. wallet .. " [item=coin]",
{top_margin=5, bottom_margin=5})
AddLabel(tab_container, "coin_info", "Players start with some coins. Earn more coins by killing enemies.", my_note_style)
AddLabel(tab_container,
"player_store_note_lbl",
"Locked items become available after playing for awhile...",
my_note_style)
local line = tab_container.add{type="line", direction="horizontal"}
line.style.top_margin = 5
line.style.bottom_margin = 5
for category,section in pairs(OARC_STORE_PLAYER_ITEMS) do
local flow = tab_container.add{name = category, type="flow", direction="horizontal"}
for item_name,item in pairs(section) do
local color = "[color=green]"
if (item.cost > wallet) then
color = "[color=red]"
end
local btn = flow.add{name=item_name,
type="sprite-button",
number=item.count,
sprite="item/"..item_name,
tooltip=item_name .. " Cost: "..color..item.cost.."[/color] [item=coin]",
style=mod_gui.button_style}
if (item.play_time_locked and (player.online_time < TICKS_PER_MINUTE*15)) then
btn.enabled = false
end
end
local line2 = tab_container.add{type="line", direction="horizontal"}
line2.style.top_margin = 5
line2.style.bottom_margin = 5
end
end
function OarcPlayerStoreButton(event)
local button = event.element
local player = game.players[event.player_index]
local player_inv = player.get_inventory(defines.inventory.character_main)
if (player_inv == nil) then return end
local category = button.parent.name
local item = OARC_STORE_PLAYER_ITEMS[category][button.name]
if (player_inv.get_item_count("coin") >= item.cost) then
player_inv.insert({name = button.name, count = item.count})
player_inv.remove({name = "coin", count = item.cost})
if (button.parent and button.parent.parent and button.parent.parent.player_store_wallet_lbl) then
local wallet = player_inv.get_item_count("coin")
button.parent.parent.player_store_wallet_lbl.caption = "Coins Available: " .. wallet .. " [item=coin]"
end
else
player.print("You're broke! Go kill some enemies or beg for change...")
end
end

214
lib/oarc_tests.lua Normal file
View File

@ -0,0 +1,214 @@
local mod_gui = require("mod-gui")
---Test out all the fonts available in the game.
---@param player LuaPlayer
---@return nil
function TestFonts(player)
local font_list = {
"compi",
"compilatron-message-font",
"count-font",
"default",
"default-bold",
"default-dialog-button",
"default-dropdown",
"default-game",
"default-large",
"default-large-bold",
"default-large-semibold",
"default-listbox",
"default-semibold",
"default-small",
"default-small-bold",
"default-small-semibold",
"default-tiny-bold",
"heading-1",
"heading-2",
"heading-3",
"locale-pick",
"scenario-message-dialog",
"technology-slot-level-font",
"var",
}
local test_frame = player.gui.screen.add{type="frame", name="font_test_frame", direction="vertical"}
for _,font in pairs(font_list) do
local test_text = test_frame.add{type="label", caption=font}
test_text.style.font = font
end
test_frame.auto_center = true
end
function ClearTestFonts(player)
if player.gui.screen.font_test_frame then
player.gui.screen.font_test_frame.destroy()
end
end
---Test out all the button styles available in the game.
---@param player LuaPlayer
---@return nil
function TestButtons(player)
local button_styles = {
"back_button",
"big_slot_button",
-- "blueprint_drop_slot_button",
"blueprint_record_selection_button",
"blueprint_record_slot_button",
"browse_games_gui_toggle_favorite_off_button",
"browse_games_gui_toggle_favorite_on_button",
"cancel_close_button",
-- "character_gui_entity_button",
"choose_chat_icon_button",
"choose_chat_icon_in_textbox_button",
"close_button",
"compact_slot_sized_button",
"confirm_button",
"confirm_double_arrow_button",
"confirm_in_load_game_button",
"control_settings_button",
"control_settings_section_button",
"current_research_info_button",
"dark_button",
"dark_rounded_button",
"dialog_button",
"drop_target_button",
"dropdown_button",
"entity_variation_button",
"forward_button",
"frame_action_button",
"frame_button",
"green_button",
"highlighted_tool_button",
"inventory_limit_slot_button",
"left_slider_button",
"locomotive_minimap_button",
"logistic_slot_button",
"map_generator_close_preview_button",
"map_generator_confirm_button",
"map_generator_preview_button",
"map_view_add_button",
"map_view_options_button",
"menu_button",
"mini_button",
"mod_gui_button",
"not_working_weapon_button",
"open_armor_button",
"other_settings_gui_button",
"quick_bar_page_button",
"quick_bar_slot_button",
"recipe_slot_button",
"red_back_button",
"red_button",
"red_confirm_button",
"red_logistic_slot_button",
"red_slot_button",
"research_queue_cancel_button",
"right_slider_button",
"rounded_button",
"shortcut_bar_button",
"shortcut_bar_expand_button",
"side_menu_button",
"slider_button",
"slot_button",
"slot_sized_button",
"station_train_status_button",
"statistics_slot_button",
"tile_variation_button",
"tip_notice_button",
"tip_notice_close_button",
"tool_bar_open_button",
"tool_button",
"tracking_off_button",
"tracking_on_button",
"train_schedule_action_button",
"train_schedule_add_station_button",
"train_schedule_add_wait_condition_button",
"train_schedule_comparison_type_button",
"train_schedule_condition_time_selection_button",
"train_schedule_delete_button",
"train_schedule_fulfilled_delete_button",
"train_schedule_fulfilled_item_select_button",
"train_schedule_item_select_button",
"train_schedule_temporary_station_delete_button",
"train_status_button",
-- "train_stop_entity_button",
-- "wide_entity_button",
"working_weapon_button",
"yellow_logistic_slot_button",
}
local test_frame = player.gui.screen.add{type="scroll-pane", name="button_test_frame", direction="vertical"}
-- test_frame.auto_center = true
test_frame.vertical_scroll_policy = "auto"
test_frame.style.maximal_height = 800
for _,button_style in pairs(button_styles) do
local test_button = test_frame.add
{
type="button",
caption=button_style,
style=button_style
}
end
end
function ClearTestButtons(player)
if player.gui.screen.button_test_frame then
player.gui.screen.button_test_frame.destroy()
end
end
function RecreateOarcGui(player)
if (mod_gui.get_button_flow(player).oarc_button ~= nil) then
mod_gui.get_button_flow(player).oarc_button.destroy()
end
if (mod_gui.get_frame_flow(player)[OARC_GUI] ~= nil) then
mod_gui.get_frame_flow(player)[OARC_GUI].destroy()
end
InitOarcGuiTabs(player)
end
function SetNauvisChunksGenerated()
local nauvis = game.surfaces["nauvis"]
for x = -100, 100, 1 do
for y = -100, 100, 1 do
nauvis.set_chunk_generated_status({x=x, y=y}, defines.chunk_generated_status.entities)
end
end
end
function FlagEnemyForce(player, enemy_force_name)
local enemy_force = game.forces[enemy_force_name]
player.force.set_friend(enemy_force, true)
player.force.set_cease_fire(enemy_force, true)
end
function UnflagEnemyForce(player, enemy_force_name)
local enemy_force = game.forces[enemy_force_name]
player.force.set_friend(enemy_force, false)
player.force.set_cease_fire(enemy_force, false)
end
function CreateTestSurfaces()
game.create_surface("vulcanus")
game.create_surface("fulgora")
game.create_surface("gleba")
game.create_surface("aquilo")
end

File diff suppressed because it is too large Load Diff

130
lib/offline_protection.lua Normal file
View File

@ -0,0 +1,130 @@
-- This attempt will try to intercept normal vanilla enemy groups and modify them based on player activity.
-- Basic logic:
-- on_unit_group_finished_gathering we check what command is given.
-- find destination position
-- check for closest "player" using find_nearest_enemy function
-- if a player is found, check if player is part of a shared spawn
-- Remove the enemy group if no player in the shared spawn is online.
-- Generic Utility Includes
require("lib/oarc_utils")
---This function is called when a unit group finishes gathering.
---@param event EventData.on_unit_group_finished_gathering
---@return nil
function OarcModifyEnemyGroup(event)
local group = event.group
-- Check validity
if ((group == nil) or (group.command == nil) or not TableContains(ENEMY_FORCES_NAMES, group.force.name)) then
log("OarcModifyEnemyGroup ignoring INVALID group/command")
return
end
-- Make sure the attack is of a TYPE that we care about.
if ((group.command.type == defines.command.attack_area) or
(group.command.type == defines.command.build_base)) then
-- log("OarcModifyEnemyGroup MODIFYING command TYPE=" .. group.command.type)
else
-- log("OarcModifyEnemyGroup ignoring command TYPE=" .. group.command.type)
return
end
-- (group.command.type == defines.command.attack) or
-- defines.command.attack --> target --> target.position
-- if (group.command.type == defines.command.attack) then
-- log("OarcModifyEnemyGroup defines.command.attack NOT IMPLEMENTED YET!")
-- return
-- end
-- defines.command.attack_area --> destination --> closest enemy (within 3 chunk radius?)
-- defines.command.build_base --> destination --> closest enemy (expansion chunk distance?)
local destination = group.command.destination
local search_radius = CHUNK_SIZE*3
if (group.command.type == defines.command.build_base) then
search_radius = CHUNK_SIZE * (game.map_settings.enemy_expansion.max_expansion_distance)
end
-- Look for any player force targets near the destination point.
local target_entities = group.surface.find_entities_filtered{
position=destination,
radius=search_radius,
force=ENEMY_FORCES_NAMES_INCL_NEUTRAL,
limit=50,
invert=true}
-- Search through them all to find anything with a non-nil last_user.
local target_entity = nil
for _,target in ipairs(target_entities) do
if (target.last_user ~= nil) then
target_entity = target
break
end
end
-- No targets found with a last_user
if (target_entity == nil) then
-- This is unexpected, not sure under which conditions this would happen.
if (group.command.type == defines.command.attack_area) then
-- SendBroadcastMsg("OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!? " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, group.command.destination))
log("ERROR - OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!?")
-- for _,member in pairs(group.members) do
-- member.destroy()
-- end
-- This is fine, as the enemy group is just expanding / building bases
else
-- log("OarcModifyEnemyGroup find_nearest_enemy did not find anything!")
end
return
end
-- Most common target will be a built entity with a "last_user"
local target_player = target_entity.last_user
-- -- Target could also be a player character (more rare)
-- if (target_player == nil) and (target_entity.type == "character") then
-- target_player = target_entity.player
-- end
-- I don't think this should happen ever...
if ((target_player == nil) or (not target_player.valid)) then
-- SendBroadcastMsg("ERROR?? target_player nil/invalid " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position))
log("ERROR - OarcModifyEnemyGroup target_player nil/invalid?")
-- for _,member in pairs(group.members) do
-- member.destroy()
-- end
return
end
-- Is the target player online? Then the attack can go through.
if (target_player.connected) then
-- SendBroadcastMsg("Enemy group released (player): " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name)
-- log("OarcModifyEnemyGroup RELEASING enemy group since player is ONLINE " .. target_player.name)
return
end
-- Find the shared spawn that the player is part of.
-- This could be the own player's spawn (quite likely)
local online_players = GetPlayersFromSameSpawn(target_player.name, false)
-- Is someone in the group online?
if (#online_players > 0) then
-- SendBroadcastMsg("Enemy group released (shared): " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name)
-- log("OarcModifyEnemyGroup RELEASING enemy group since someone in the group is ONLINE " .. target_player.name)
return
end
-- Otherwise, we delete the group.
for _,member in pairs(group.members) do
member.destroy()
end
-- SendBroadcastMsg("Enemy group deleted: " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name)
log("OarcModifyEnemyGroup REMOVED enemy group since nobody was online? " .. target_player.name)
end

View File

@ -1,36 +0,0 @@
-- 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

View File

@ -1,7 +1,3 @@
-- 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:
@ -13,241 +9,506 @@
-- the on_sector_scanned event.
-- 5. Chunks timeout after 1 hour-ish, configurable
require("lib/oarc_utils")
require("config")
--- These chunks are marked for removal. They will be deleted by the regrowth system.
--- If it gets refreshed before it is removed, then it will be marked safe again
REGROWTH_FLAG_REMOVAL = -1
REGROWTH_TIMEOUT_TICKS = TICKS_PER_HOUR -- TICKS_PER_HOUR TICKS_PER_MINUTE
--- If a chunk is marked "active", then it will only be checked by the "world eater" system if that is enabled.
--- World eater does more extensive checks to see if a chunk might be safe to delete. For example, if a player builds
--- stuff in a chunk it will be marked as "active" and won't be checked by the regrowth system.
REGROWTH_FLAG_ACTIVE = -2
-- Init globals and set player join area to be off limits.
--- These chunks will NEVER be deleted by the regrowth + world eater systems. However, they can be overwritten in some
--- cases. Like when a player leaves the game early and their spawn is deleted.
REGROWTH_FLAG_PERMANENT = -3
--- Radius in chunks around a player to mark as safe.
REGROWTH_ACTIVE_AREA_AROUND_PLAYER = 2
---The removal list contains chunks that are marked for removal. Each entry is a table with the following fields:
---@alias RemovalListEntry { pos : ChunkPosition, force: boolean, surface: string }
---Init globals for regrowth
---@return nil
function RegrowthInit()
global.rg = {}
global.rg.player_refresh_index = nil
global.rg.force_removal_flag = -2000
global.rg.map = {}
global.rg.force_removal_flag = -2000 -- Set to a negative number to disable it by default
global.rg.current_surface = nil -- The current surface we are iterating through
global.rg.current_surface_index = 1
global.rg.active_surfaces = {} -- List of all surfaces with regrowth enabled
global.rg.chunk_iter = nil -- We only iterate through onface at a time
global.rg.we_chunk_iter = nil
global.rg.we_current_surface = nil
global.rg.we_current_surface_index = 1
---@type LuaEntity[]
global.rg.spidertrons = {} -- List of all spidertrons in the game
global.rg_spidertron_index = 1
---@type RemovalListEntry[]
global.rg.removal_list = {}
global.rg.chunk_iter = nil
global.rg.world_eater_iter = nil
global.rg.timeout_ticks = REGROWTH_TIMEOUT_TICKS
for surface_name,_ in pairs(game.surfaces) do
InitSurface(surface_name --[[@as string]])
end
end
---Called when a new surface is created. This is used to add new surfaces to the regrowth map.
---@param event EventData.on_surface_created
---@return nil
function RegrowthSurfaceCreated(event)
InitSurface(game.surfaces[event.surface_index].name)
end
---Called when a surface is deleted. This is used to remove surfaces from the regrowth map.
---@param event EventData.on_pre_surface_deleted
---@return nil
function RegrowthSurfaceDeleted(event)
log("WARNING - RegrowthSurfaceDeleted: " .. game.surfaces[event.surface_index].name)
local surface_name = game.surfaces[event.surface_index].name
RegrowthDisableSurface(surface_name )
end
---Initialize the new surface for regrowth
---@param surface_name string - The surface name to act on
---@return nil
function InitSurface(surface_name)
if (not IsSurfaceBlacklisted(surface_name) and not TableContains(global.rg.active_surfaces, surface_name)) then
log("Adding surface to regrowth: " .. surface_name)
-- Add a new surface to the regrowth map (Don't overwrite if it already exists)
if (global.rg[surface_name] == nil) then
global.rg[surface_name] = {}
end
-- This is a 2D array of chunk positions and their last tick updated / status (Don't overwrite if it already exists)
if (global.rg[surface_name].map == nil) then
global.rg[surface_name].map = {}
end
-- Set the current surface to the first one found if none are set.
if (global.rg.current_surface == nil) then
global.rg.current_surface = surface_name
global.rg.we_current_surface = surface_name
end
global.rg[surface_name].active = true
table.insert(global.rg.active_surfaces, surface_name)
end
end
function RegrowthDisableSurface(surface_name)
-- We don't want to delete the surface history in case it's re-enabled later!
-- global.rg[surface_name] = nil
global.rg[surface_name].active = false
TableRemoveOneUsingPairs(global.rg.active_surfaces, surface_name)
-- Make sure indices are reset if needed
if (global.rg.current_surface == surface_name) then
global.rg.current_surface = nil
global.rg.current_surface_index = 1
end
if (global.rg.we_current_surface == surface_name) then
global.rg.we_current_surface = nil
global.rg.we_current_surface_index = 1
end
if #global.rg.active_surfaces > 0 then
global.rg.current_surface = global.rg.active_surfaces[1]
global.rg.we_current_surface = global.rg.active_surfaces[1]
end
end
---Simple check to see if a surface is enabled for regrowth
---@param surface_name string - The surface name to act on
---@return boolean
function IsRegrowthEnabledOnSurface(surface_name)
if (global.rg[surface_name] == nil) then return false end
return global.rg[surface_name].active
end
function RegrowthEnableSurface(surface_name)
InitSurface(surface_name)
end
---Trigger an immediate cleanup of any chunks that are marked for removal.
---@return nil
function TriggerCleanup()
global.rg.force_removal_flag = game.tick
end
function RegrowthForceRemoveChunksCmd(cmd_table)
if (game.players[cmd_table.player_index].admin) then
TriggerCleanup()
end
end
-- Turn this into a admin GUI button.
-- function RegrowthForceRemoveChunksCmd(cmd_table)
-- if (game.players[cmd_table.player_index].admin) then
-- TriggerCleanup()
-- end
-- end
-- Get the next player index available
function GetNextPlayerIndex(player_index)
if (not global.rg.player_refresh_index or not game.players[global.rg.player_refresh_index]) then
---Get the next player index available. This is used to loop through ONLINE players to refresh the areas around them.
---@return integer
function GetNextConnectedPlayerIndex()
if (global.rg.player_refresh_index == nil) or (game.connected_players[global.rg.player_refresh_index] == nil) then
global.rg.player_refresh_index = 1
else
global.rg.player_refresh_index = global.rg.player_refresh_index + 1
end
if (global.rg.player_refresh_index > #game.players) then
if (global.rg.player_refresh_index > #game.connected_players) then
global.rg.player_refresh_index = 1
end
return global.rg.player_refresh_index
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 c_pos = GetChunkPosFromTilePos(event.area.left_top)
---@alias ActiveSurfaceInfo { surface : string, index : integer }
-- Surface must be "added" first.
if (global.rg == nil) then return end
---Sets the current surface to the next active surface. This is used to loop through surfaces.
---@param current_index integer - The current index in the active surfaces list
---@return ActiveSurfaceInfo - The new current surface name and index
function GetNextActiveSurface(current_index)
local count = #(global.rg.active_surfaces)
local next_index = current_index + 1
if (next_index > count) then
next_index = 1
end
local next_surface = global.rg.active_surfaces[next_index]
return { surface = next_surface, index = next_index }
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...)
---@param event EventData.on_chunk_generated
---@return nil
function RegrowthChunkGenerate(event)
local c_pos = event.position
local surface_name = event.surface.name
-- Surface not init or not active, ignore it.
if not IsRegrowthEnabledOnSurface(surface_name) then return end
-- If this is the first chunk in that row:
if (global.rg.map[c_pos.x] == nil) then
global.rg.map[c_pos.x] = {}
if (global.rg[surface_name].map[c_pos.x] == nil) then
global.rg[surface_name].map[c_pos.x] = {}
end
-- Only update it if it isn't already set!
if (global.rg.map[c_pos.x][c_pos.y] == nil) then
global.rg.map[c_pos.x][c_pos.y] = game.tick
if (global.rg[surface_name].map[c_pos.x][c_pos.y] == nil) then
global.rg[surface_name].map[c_pos.x][c_pos.y] = game.tick
-- log("RegrowthChunkGenerate: " .. c_pos.x .. "," .. c_pos.y .. " on surface: " .. surface_name)
end
end
-- Mark an area for "immediate" forced removal
function RegrowthMarkAreaForRemoval(pos, chunk_radius)
---Mark an area for "immediate" forced removal, this will override any pemranent flags.
---@param surface_name string - The surface name to act on
---@param pos TilePosition - The tile position to mark for removal
---@param chunk_radius integer - The radius in chunks around the position to mark for removal
---@return nil
function RegrowthMarkAreaForRemoval(surface_name, pos, chunk_radius)
local c_pos = GetChunkPosFromTilePos(pos)
for i=-chunk_radius,chunk_radius do
local x = c_pos.x+i
for k=-chunk_radius,chunk_radius do
local y = c_pos.y+k
for i = -chunk_radius, chunk_radius do
local x = c_pos.x + i
for k = -chunk_radius, chunk_radius do
local y = c_pos.y + k
if (global.rg.map[x] ~= nil) then
global.rg.map[x][y] = nil
if (global.rg[surface_name].map[x] == nil) then
global.rg[surface_name].map[x] = {}
end
table.insert(global.rg.removal_list, {pos={x=x,y=y},force=true})
end
if (table_size(global.rg.map[x]) == 0) then
global.rg.map[x] = nil
global.rg[surface_name].map[x][y] = REGROWTH_FLAG_REMOVAL
---@type RemovalListEntry
local removal_entry = { pos = { x = x, y = y }, force = true, surface = surface_name }
table.insert(global.rg.removal_list, removal_entry)
end
end
end
-- Downgrades permanent flag to semi-permanent.
function RegrowthMarkAreaNotPermanentOVERWRITE(pos, chunk_radius)
local c_pos = GetChunkPosFromTilePos(pos)
for i=-chunk_radius,chunk_radius do
local x = c_pos.x+i
for k=-chunk_radius,chunk_radius do
local y = c_pos.y+k
-- ---Downgrades permanent flag to semi-permanent.
-- ---@param surface_name string - The surface name to act on
-- ---@param pos TilePosition - The tile position to mark
-- ---@param chunk_radius integer - The radius in chunks around the position to mark
-- ---@return nil
-- function RegrowthMarkAreaNotPermanentOVERWRITE(surface_name, pos, chunk_radius)
-- local c_pos = GetChunkPosFromTilePos(pos)
-- for i = -chunk_radius, chunk_radius do
-- local x = c_pos.x + i
-- for k = -chunk_radius, chunk_radius do
-- local y = c_pos.y + k
if (global.rg.map[x] and global.rg.map[x][y] and (global.rg.map[x][y] == -2)) then
global.rg.map[x][y] = -1
end
end
end
end
-- if (global.rg[surface_name].map[x] and
-- global.rg[surface_name].map[x][y] and
-- (global.rg[surface_name].map[x][y] == REGROWTH_FLAG_PERMANENT)) then
-- global.rg[surface_name].map[x][y] = REGROWTH_FLAG_ACTIVE
-- end
-- end
-- end
-- end
-- Marks a chunk containing a position to be relatively permanent.
function MarkChunkSafe(c_pos, permanent)
if (global.rg.map[c_pos.x] == nil) then
global.rg.map[c_pos.x] = {}
---Marks a chunk containing a position to be relatively permanent.
---@param surface_name string - The surface name to act on
---@param c_pos ChunkPosition - The chunk position to mark
---@param permanent boolean - If true, the chunk will be marked as permanent
---@return nil
function MarkChunkSafe(surface_name, c_pos, permanent)
if (global.rg[surface_name].map[c_pos.x] == nil) then
global.rg[surface_name].map[c_pos.x] = {}
end
if (permanent) then
global.rg.map[c_pos.x][c_pos.y] = -2
global.rg[surface_name].map[c_pos.x][c_pos.y] = REGROWTH_FLAG_PERMANENT
-- Make sure we don't overwrite...
elseif (global.rg.map[c_pos.x][c_pos.y] and (global.rg.map[c_pos.x][c_pos.y] ~= -2)) then
global.rg.map[c_pos.x][c_pos.y] = -1
-- Make sure we don't overwrite unless it's a permanent flag
elseif (global.rg[surface_name].map[c_pos.x][c_pos.y] and
(global.rg[surface_name].map[c_pos.x][c_pos.y] ~= REGROWTH_FLAG_PERMANENT)) then
global.rg[surface_name].map[c_pos.x][c_pos.y] = REGROWTH_FLAG_ACTIVE
end
end
-- Marks a safe area around a CHUNK position to be relatively permanent.
function RegrowthMarkAreaSafeGivenChunkPos(c_pos, chunk_radius, permanent)
if (global.rg == nil) then return end
---Marks a safe area around a CHUNK position to be relatively permanent.
---@param surface_name string - The surface name to act on
---@param c_pos ChunkPosition - The chunk position to mark
---@param chunk_radius integer - The radius in chunks around the position to mark
---@param permanent boolean - If true, the chunk will be marked as permanent
---@return nil
function RegrowthMarkAreaSafeGivenChunkPos(surface_name, c_pos, chunk_radius, permanent)
if (global.rg[surface_name] == nil) then return end
for i=-chunk_radius,chunk_radius do
for j=-chunk_radius,chunk_radius do
MarkChunkSafe({x=c_pos.x+i,y=c_pos.y+j}, permanent)
for i = -chunk_radius, chunk_radius do
for j = -chunk_radius, chunk_radius do
MarkChunkSafe(surface_name, { x = c_pos.x + i, y = c_pos.y + j }, permanent)
end
end
end
-- Marks a safe area around a TILE position to be relatively permanent.
function RegrowthMarkAreaSafeGivenTilePos(pos, chunk_radius, permanent)
if (global.rg == nil) then return end
---Marks a safe area around a TILE position to be relatively permanent.
---@param surface_name string - The surface name to act on
---@param pos TilePosition - The tile position to mark
---@param chunk_radius integer - The radius in chunks around the position to mark
---@param permanent boolean - If true, the chunk will be marked as permanent
---@return nil
function RegrowthMarkAreaSafeGivenTilePos(surface_name, pos, chunk_radius, permanent)
if not IsRegrowthEnabledOnSurface(surface_name) then return end
local c_pos = GetChunkPosFromTilePos(pos)
RegrowthMarkAreaSafeGivenChunkPos(c_pos, chunk_radius, permanent)
RegrowthMarkAreaSafeGivenChunkPos(surface_name, c_pos, chunk_radius, permanent)
end
-- Refreshes timers on a chunk containing position
function RefreshChunkTimer(pos, bonus_time)
---Refreshes timers on a chunk containing position
---@param surface_name string - The surface name to act on
---@param pos TilePosition - The tile position to mark
---@param bonus_time integer - The bonus time to add to the current game tick
---@return nil
function RefreshChunkTimer(surface_name, pos, bonus_time)
local c_pos = GetChunkPosFromTilePos(pos)
if (global.rg.map[c_pos.x] == nil) then
global.rg.map[c_pos.x] = {}
if (global.rg[surface_name].map[c_pos.x] == nil) then
global.rg[surface_name].map[c_pos.x] = {}
end
if (global.rg.map[c_pos.x][c_pos.y] >= 0) then
global.rg.map[c_pos.x][c_pos.y] = game.tick + bonus_time
if (global.rg[surface_name].map[c_pos.x][c_pos.y] >= REGROWTH_FLAG_REMOVAL) then
global.rg[surface_name].map[c_pos.x][c_pos.y] = game.tick + bonus_time
end
end
-- Refreshes timers on all chunks around a certain area
function RefreshArea(pos, chunk_radius, bonus_time)
---Refreshes timers on all chunks around a certain area
---@param surface_name string - The surface name to act on
---@param pos TilePosition - The tile position to mark
---@param chunk_radius integer - The radius in chunks around the position to mark
---@param bonus_time integer - The bonus time to add to the current game tick
---@return nil
function RefreshArea(surface_name, pos, chunk_radius, bonus_time)
local c_pos = GetChunkPosFromTilePos(pos)
for i=-chunk_radius,chunk_radius do
local x = c_pos.x+i
for k=-chunk_radius,chunk_radius do
local y = c_pos.y+k
RefreshAreaChunkPosition(surface_name, c_pos, chunk_radius, bonus_time)
end
if (global.rg.map[x] == nil) then
global.rg.map[x] = {}
---Refreshes timers on all chunks around a certain area
---@param surface_name string - The surface name to act on
---@param c_pos ChunkPosition - The chunk position to mark
---@param chunk_radius integer - The radius in chunks around the position to mark
---@param bonus_time integer - The bonus time to add to the current game tick
---@return nil
function RefreshAreaChunkPosition(surface_name, c_pos, chunk_radius, bonus_time)
for i = -chunk_radius, chunk_radius do
local x = c_pos.x + i
for k = -chunk_radius, chunk_radius do
local y = c_pos.y + k
if (global.rg[surface_name].map[x] == nil) then
global.rg[surface_name].map[x] = {}
end
if ((global.rg.map[x][y] == nil) or (global.rg.map[x][y] >= 0)) then
global.rg.map[x][y] = game.tick + bonus_time
if ((global.rg[surface_name].map[x][y] == nil) or (global.rg[surface_name].map[x][y] >= REGROWTH_FLAG_REMOVAL)) then
global.rg[surface_name].map[x][y] = game.tick + bonus_time
end
end
end
end
-- Refreshes timers on all chunks near an ACTIVE radar
---Refreshes timers on all chunks near an ACTIVE radar
---@param event EventData.on_sector_scanned
---@return nil
function RegrowthSectorScan(event)
if (event.radar.surface.name ~= GAME_SURFACE_NAME) then return end
local surface_name = event.radar.surface.name
RefreshArea(event.radar.position, 14, 0)
-- Surface not in regrowth map, ignore it.
if (global.rg[surface_name] == nil) then return end
---@type integer
local radar_range = event.radar.prototype.max_distance_of_nearby_sector_revealed --TODO: Space age quality might affect this?
RefreshAreaChunkPosition(surface_name, event.chunk_position, radar_range, 0)
end
-- Refresh all chunks near a single player. Cyles through all connected players.
---Refresh all chunks near a single player. Cyles through all connected players.
---@return nil
function RefreshPlayerArea()
player_index = GetNextPlayerIndex()
player_index = GetNextConnectedPlayerIndex()
if (player_index and game.connected_players[player_index]) then
local player = game.connected_players[player_index]
if (not player.character) then return end
if (player.character.surface.name ~= GAME_SURFACE_NAME) then return end
local surface_name = player.surface.name
RefreshArea(player.position, 4, 0)
if (not player.character) then return end
if (global.rg[surface_name] == nil) or (not global.rg[surface_name].active) then return end
RefreshArea(surface_name, player.position, REGROWTH_ACTIVE_AREA_AROUND_PLAYER, 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()
---Updates the chunk_iter and returns the next chunk from it. May not be valid if it no chunks exist.
---@return ChunkPositionAndArea?
function GetNextChunkAndUpdateIter()
-- Make sure we have a valid iterator!
if (not global.rg.chunk_iter or not global.rg.chunk_iter.valid) then
global.rg.chunk_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks()
global.rg.chunk_iter = game.surfaces[global.rg.current_surface].get_chunks()
end
local next_chunk = global.rg.chunk_iter()
-- Check if we reached the end
if (not next_chunk) then
global.rg.chunk_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks()
if (next_chunk == nil) then
-- Switch to the next active surface
local next_surface_info = GetNextActiveSurface(global.rg.current_surface_index)
global.rg.current_surface = next_surface_info.surface
global.rg.current_surface_index = next_surface_info.index
global.rg.chunk_iter = game.surfaces[global.rg.current_surface].get_chunks()
next_chunk = global.rg.chunk_iter()
end
-- Do we have it in our map?
if (not global.rg.map[next_chunk.x] or not global.rg.map[next_chunk.x][next_chunk.y]) then
return -- Chunk isn't in our map so we don't care?
end
return next_chunk
end
---Updates the chunk_iter (for World Eater) and returns the next chunk from it. May not be valid if it no chunks exist.
---@return ChunkPositionAndArea?
function GetNextChunkAndUpdateWorldEaterIter()
-- Make sure we have a valid iterator!
if (not global.rg.we_chunk_iter or not global.rg.we_chunk_iter.valid) then
global.rg.we_chunk_iter = game.surfaces[global.rg.we_current_surface].get_chunks()
end
local next_chunk = global.rg.we_chunk_iter()
-- Check if we reached the end
if (next_chunk == nil) then
-- Switch to the next active surface
local next_surface_info = GetNextActiveSurface(global.rg.we_current_surface_index)
global.rg.we_current_surface = next_surface_info.surface
global.rg.we_current_surface_index = next_surface_info.index
global.rg.we_chunk_iter = game.surfaces[global.rg.we_current_surface].get_chunks()
next_chunk = global.rg.we_chunk_iter()
end
return next_chunk
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.
---@return nil
function RegrowthSingleStepArray()
local next_chunk = GetNextChunkAndUpdateIter()
if (next_chunk == nil) then return end
local current_surface = global.rg.current_surface
-- It's possible that if regrowth is disabled/enabled during runtime we might miss on_chunk_generated.
-- This will catch that case and add the chunk to the map.
if (global.rg[current_surface].map[next_chunk.x] == nil) then
global.rg[current_surface].map[next_chunk.x] = {}
end
if (global.rg[current_surface].map[next_chunk.x][next_chunk.y] == nil and game.surfaces[current_surface].is_chunk_generated(next_chunk)) then
log("RegrowthSingleStepArray: Chunk not in map: " .. next_chunk.x .. "," .. next_chunk.y .. " on surface: " .. current_surface)
local has_player_entities = CheckIfChunkHasAnyPlayerEntities(current_surface, next_chunk)
if has_player_entities then
global.rg[current_surface].map[next_chunk.x][next_chunk.y] = REGROWTH_FLAG_ACTIVE
else
global.rg[current_surface].map[next_chunk.x][next_chunk.y] = game.tick
end
return
end
-- If the chunk has timed out, add it to the removal list
local c_timer = global.rg.map[next_chunk.x][next_chunk.y]
if ((c_timer ~= nil) and (c_timer >= 0) and ((c_timer + global.rg.timeout_ticks) < game.tick)) then
local c_timer = global.rg[current_surface].map[next_chunk.x][next_chunk.y]
local interval_ticks = global.ocfg.regrowth.cleanup_interval * TICKS_PER_MINUTE
if ((c_timer ~= nil) and (c_timer >= 0) and ((c_timer + interval_ticks) < game.tick)) then
-- Check chunk actually exists
if (game.surfaces[GAME_SURFACE_NAME].is_chunk_generated({x=next_chunk.x, y=next_chunk.y})) then
table.insert(global.rg.removal_list, {pos={x=next_chunk.x, y=next_chunk.y}, force=false})
global.rg.map[next_chunk.x][next_chunk.y] = nil
if (game.surfaces[current_surface].is_chunk_generated({ x = next_chunk.x, y = next_chunk.y })) then
---@type RemovalListEntry
local removal_entry = {pos = {x = next_chunk.x, y = next_chunk.y }, force = false, surface = current_surface}
table.insert(global.rg.removal_list, removal_entry)
global.rg[current_surface].map[next_chunk.x][next_chunk.y] = REGROWTH_FLAG_REMOVAL
else
log("WARN - RegrowthSingleStepArray: Chunk not generated?: " .. next_chunk.x .. "," .. next_chunk.y .. " on surface: " .. current_surface)
global.rg[current_surface].map[next_chunk.x][next_chunk.y] = nil
end
end
end
-- Remove all chunks at same time to reduce impact to FPS/UPS
---Remove all chunks at same time to reduce impact to FPS/UPS
---@return nil
function OarcRegrowthRemoveAllChunks()
for key,c_remove in pairs(global.rg.removal_list) do
for key, c_remove in pairs(global.rg.removal_list) do
local c_pos = c_remove.pos
local surface_name = c_remove.surface
-- Confirm chunk is still expired
if (not global.rg.map[c_pos.x] or not global.rg.map[c_pos.x][c_pos.y]) then
-- Confirm chunk is still marked for removal or is a force removal, if it's nil, something else happened?
if (global.rg[surface_name].map[c_pos.x] ~= nil) then
-- If it is FORCE removal, then remove it regardless of pollution.
if (c_remove.force) then
game.surfaces[GAME_SURFACE_NAME].delete_chunk(c_pos)
game.surfaces[surface_name].delete_chunk(c_pos)
-- If it is a normal timeout removal, don't do it if there is pollution in the chunk.
elseif (game.surfaces[GAME_SURFACE_NAME].get_pollution({c_pos.x*32,c_pos.y*32}) > 0) then
global.rg.map[c_pos.x][c_pos.y] = game.tick
elseif (global.rg[surface_name].map[c_pos.x][c_pos.y] == REGROWTH_FLAG_REMOVAL) then
-- Else delete the chunk
else
game.surfaces[GAME_SURFACE_NAME].delete_chunk(c_pos)
-- If it is a normal timeout removal, don't do it if there is pollution in the chunk.
if (game.surfaces[surface_name].get_pollution({ c_pos.x * 32, c_pos.y * 32 }) > 0) then
global.rg[surface_name].map[c_pos.x][c_pos.y] = game.tick
-- Else delete the chunk
else
game.surfaces[surface_name].delete_chunk(c_pos)
global.rg[surface_name].map[c_pos.x][c_pos.y] = nil
end
end
else
-- This should never happen, TODO: check if it does?
error("ERROR - OarcRegrowthRemoveAllChunks: Chunk not in map: " .. c_pos.x .. "," .. c_pos.y .. " on surface: " .. surface_name)
end
-- Remove entry
@ -256,41 +517,46 @@ function OarcRegrowthRemoveAllChunks()
-- MUST GET A NEW CHUNK ITERATOR ON DELETE CHUNK!
global.rg.chunk_iter = nil
global.rg.world_eater_iter = nil
global.rg.we_chunk_iter = nil
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.
---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.
---@return nil
function RegrowthOnTick()
-- 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
if (#global.rg.active_surfaces > 0) then
-- Every tick, check a few points in the 2d array of the only active surface According to /measured-command this
-- shouldn't take more than 0.1ms on average
for i=1,20 do
RegrowthSingleStepArray()
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
if (not global.world_eater_disable) then
WorldEaterSingleStep()
-- Refresh a single spidertron every tick
RefreshSpidertronArea()
-- Every tick, check a few points in the 2d array of the only active surface
for i = 1, 20 do
RegrowthSingleStepArray()
end
if (global.ocfg.regrowth.enable_world_eater) then
WorldEaterSingleStep()
end
end
-- Allow enable/disable of auto cleanup, can change during runtime.
local interval_ticks = global.rg.timeout_ticks
local interval_ticks = global.ocfg.regrowth.cleanup_interval * TICKS_PER_MINUTE
-- Send a broadcast warning before it happens.
if ((game.tick % interval_ticks) == interval_ticks-(60*30 + 1)) then
if ((game.tick % interval_ticks) == interval_ticks - (60 * 30 + 1)) then
if (#global.rg.removal_list > 100) then
SendBroadcastMsg("Map cleanup in 30 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 ((game.tick % interval_ticks) == interval_ticks - 1) then
if (#global.rg.removal_list > 100) then
OarcRegrowthRemoveAllChunks()
SendBroadcastMsg("Map cleanup done, sorry for your loss.")
@ -300,14 +566,13 @@ 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
if (game.tick == global.rg.force_removal_flag + 60) then
SendBroadcastMsg("Map cleanup (forced) in 30 seconds... Unused and old map chunks will be deleted!")
end
if (game.tick == global.rg.force_removal_flag+(60*30 + 60)) then
if (game.tick == global.rg.force_removal_flag + (60 * 30 + 60)) then
OarcRegrowthRemoveAllChunks()
SendBroadcastMsg("Map cleanup done, sorry for your loss.")
end
@ -315,71 +580,143 @@ end
function WorldEaterSingleStep()
-- Make sure we have a valid iterator!
if (not global.rg.world_eater_iter or not global.rg.world_eater_iter.valid) then
global.rg.world_eater_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks()
end
local next_chunk = global.rg.world_eater_iter()
-- Check if we reached the end
if (not next_chunk) then
global.rg.world_eater_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks()
next_chunk = global.rg.world_eater_iter()
end
local next_chunk = GetNextChunkAndUpdateWorldEaterIter()
if (not next_chunk) then return end
local current_surface = global.rg.we_current_surface
-- Do we have it in our map?
if (not global.rg.map[next_chunk.x] or not global.rg.map[next_chunk.x][next_chunk.y]) then
if (not global.rg[current_surface].map[next_chunk.x] or not global.rg[current_surface].map[next_chunk.x][next_chunk.y]) then
return -- Chunk isn't in our map so we don't care?
end
end
-- Search for any abandoned radars and destroy them?
local entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=next_chunk.area,
force={global.ocore.abandoned_force},
name="radar"}
for k,v in pairs(entities) do
local abandoned_radars = game.surfaces[current_surface].find_entities_filtered { area = next_chunk.area,
force = { ABANDONED_FORCE_NAME },
name = "radar" }
for k, v in pairs(abandoned_radars) do
v.die(nil)
end
-- Search for any entities with _DESTROYED_ force and kill them.
entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=next_chunk.area,
force={global.ocore.destroyed_force}}
for k,v in pairs(entities) do
v.die(nil)
-- local destroy_entities = game.surfaces[current_surface].find_entities_filtered { area = next_chunk.area,
-- force = { DESTROYED_FORCE_NAME } }
-- for k, v in pairs(destroy_entities) do
-- v.die(nil)
-- end
local c_timer = global.rg[current_surface].map[next_chunk.x][next_chunk.y]
-- Only check chunnks that are flagged as "active".
-- Others are either permanent or will be handled by the default regrowth checks.
if (c_timer == REGROWTH_FLAG_ACTIVE) then
local area = {
left_top = { next_chunk.area.left_top.x - 8, next_chunk.area.left_top.y - 8 },
right_bottom = { next_chunk.area.right_bottom.x + 8, next_chunk.area.right_bottom.y + 8 }
}
local entities = game.surfaces[current_surface].find_entities_filtered { area = area, force = ENEMY_FORCES_NAMES_INCL_NEUTRAL, invert = true }
for _, v in pairs(entities) do
if (v.last_user) then
return -- This means we're done checking this chunk. It has an active player entity.
end
end
local moving_entities = game.surfaces[current_surface].find_entities_filtered {
area = area,
type = { "character", "logistics-robot", "construction-robot", "car", "spider-vehicle" },
}
if (#moving_entities > 0) then
return -- It's possible there are some moving entities with no last user set.
end
-- Destroy the entities that lack an owner! (player was removed)
for _, v in pairs(entities) do
if (v and v.valid) then
v.die(nil)
end
end
-- SendBroadcastMsg(next_chunk.x .. "," .. next_chunk.y .. " WorldEaterSingleStep")
global.rg[current_surface].map[next_chunk.x][next_chunk.y] = game.tick -- Set the timer on it.
end
end
---Checks if a chunk has any player entities in or near it.
---@param surface_name string - The surface name to act on
---@param chunk ChunkPositionAndArea - The chunk position to check
---@return boolean
function CheckIfChunkHasAnyPlayerEntities(surface_name, chunk)
-- Check around the chunk for anything overlapping to be safe!
local area = {
left_top = { chunk.area.left_top.x - 8, chunk.area.left_top.y - 8 },
right_bottom = { chunk.area.right_bottom.x + 8, chunk.area.right_bottom.y + 8 }
}
local entities = game.surfaces[surface_name].find_entities_filtered { area = area, force = ENEMY_FORCES_NAMES_INCL_NEUTRAL, invert = true }
for _, v in pairs(entities) do
if (v.last_user) then
return true -- YES there is player stuff here.
end
end
-- If the chunk isn't marked permament, then check if we can remove it
local c_timer = global.rg.map[next_chunk.x][next_chunk.y]
if (c_timer == -1) then
local moving_entities = game.surfaces[surface_name].find_entities_filtered {
area = area,
type = { "character", "logistics-robot", "construction-robot", "car", "spider-vehicle" },
}
if (#moving_entities > 0) then
return true -- Any of these entities are player controlled and count!
end
local area = {left_top = {next_chunk.area.left_top.x-8, next_chunk.area.left_top.y-8},
right_bottom = {next_chunk.area.right_bottom.x+8, next_chunk.area.right_bottom.y+8}}
return false
end
local entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=area, force={"enemy", "neutral"}, invert=true}
local total_count = #entities
local has_last_user_set = false
if (total_count > 0) then
for k,v in pairs(entities) do
if (v.last_user or (v.type == "character") or string.contains(v.type, "robot")) then
has_last_user_set = true
return -- This means we're done checking this chunk.
end
end
---When an entity is built, if it is a spidertron we add it to our list
---@param event EventData.on_built_entity
---@return nil
function RegrowthOnBuiltEntity(event)
if (event.created_entity and event.created_entity.valid and event.created_entity.type == "spider-vehicle") then
table.insert(global.rg.spidertrons, event.created_entity)
log("Added spidertron to regrowth tracking")
-- If all entities found have no last user, then KILL all entities!
if (not has_last_user_set) then
for k,v in pairs(entities) do
if (v and v.valid) then
v.die(nil)
end
end
-- SendBroadcastMsg(next_chunk.x .. "," .. next_chunk.y .. " WorldEaterSingleStep - ENTITIES FOUND")
global.rg.map[next_chunk.x][next_chunk.y] = game.tick -- Set the timer on it.
end
else
-- SendBroadcastMsg(next_chunk.x .. "," .. next_chunk.y .. " WorldEaterSingleStep - NO ENTITIES FOUND")
global.rg.map[next_chunk.x][next_chunk.y] = game.tick -- Set the timer on it.
if global.rg.spidertron_chunk_radius == nil then
global.rg.spidertron_chunk_radius = game.entity_prototypes["spidertron"].chunk_exploration_radius
end
end
end
---On tick, we refresh a single spidertron's area.
---@return nil
function RefreshSpidertronArea()
if (#global.rg.spidertrons > 0) then
local spidertron = global.rg.spidertrons[global.rg_spidertron_index]
if (spidertron and spidertron.valid) then
--Check if this surface is active.
local surface_name = spidertron.surface.name
if (global.rg[surface_name] ~= nil) and (global.rg[surface_name].active) then
RefreshArea(spidertron.surface.name, spidertron.position, global.rg.spidertron_chunk_radius, 0)
end
UpdateSpidertronIndex() -- Go to next valid spidertron on the next tick
else
table.remove(global.rg.spidertrons, global.rg_spidertron_index)
log("Removed spidertron from regrowth tracking")
end
end
end
---Updates the spidertron index to the next one in the list.
---@return nil
function UpdateSpidertronIndex()
global.rg_spidertron_index = global.rg_spidertron_index + 1
if (global.rg_spidertron_index > #global.rg.spidertrons) then
global.rg_spidertron_index = 1
end
end

View File

@ -1,49 +0,0 @@
local function RemoveTileGhosts()
local surface = game.player.surface
for c in surface.get_chunks() do
for key, entity in pairs(surface.find_entities_filtered({area={{c.x * 32, c.y * 32}, {c.x * 32 + 32, c.y * 32 + 32}}, name= "tile-ghost"})) do
entity.destroy()
end
end
end
local function RemoveBlueprintedModulesGhosts()
local surface = game.player.surface
for c in surface.get_chunks() do
for key, entity in pairs(surface.find_entities_filtered({area={{c.x * 32, c.y * 32}, {c.x * 32 + 32, c.y * 32 + 32}}, name= "item-request-proxy"})) do
entity.destroy()
end
end
end
local function RemoveGhostEntities()
local surface = game.player.surface
for c in surface.get_chunks() do
for key, entity in pairs(surface.find_entities_filtered({area={{c.x * 32, c.y * 32}, {c.x * 32 + 32, c.y * 32 + 32}}, name= "entity-ghost"})) do
entity.destroy()
end
end
end
commands.add_command("rg", "remove ghosts", function(command)
local player = game.players[command.player_index];
if player ~= nil and player.admin then
if (command.parameter ~= nil) then
if command.parameter == "all" then
RemoveTileGhosts()
RemoveBlueprintedModulesGhosts()
RemoveGhostEntities()
elseif command.parameter == "tiles" then
RemoveTileGhosts()
elseif command.parameter == "modules" then
RemoveBlueprintedModulesGhosts()
elseif command.parameter == "entities" then
RemoveGhostEntities()
else
player.print("remove all ghostes | tiles | modules | entities");
end
end
end
end)

View File

@ -1,78 +0,0 @@
-- 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.ocore.satellite_sent then
global.ocore.satellite_sent = {}
SendBroadcastMsg("Team " .. force.name .. " was the first to launch a rocket!")
ServerWriteFile("rocket_events", "Team " .. 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.ocore.satellite_sent[force.name] then
global.ocore.satellite_sent[force.name] = global.ocore.satellite_sent[force.name] + 1
SendBroadcastMsg("Team " .. force.name .. " launched another rocket. Total " .. global.ocore.satellite_sent[force.name])
ServerWriteFile("rocket_events", "Team " .. force.name .. " launched another rocket. Total " .. global.ocore.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.ocore.satellite_sent[force.name] = 1
SendBroadcastMsg("Team " .. force.name .. " launched their first rocket!")
ServerWriteFile("rocket_events", "Team " .. force.name .. " launched their first rocket!" .. "\n")
-- Unlock research and recipes
if global.ocfg.lock_goodies_rocket_launch then
for _,v in ipairs(LOCKED_TECHNOLOGIES) do
EnableTech(force, v.t)
end
for _,v in ipairs(LOCKED_RECIPES) do
if (force.technologies[v.r].researched) then
AddRecipe(force, v.r)
end
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.ocore.satellite_sent == nil) then
AddLabel(tab_container, nil, "No launches yet.", my_label_style)
else
for force_name,sat_count in pairs(global.ocore.satellite_sent) do
AddLabel(tab_container,
"rc_"..force_name,
"Team " .. force_name .. ": " .. tostring(sat_count),
my_label_style)
end
end
end

369
lib/scaled_enemies.lua Normal file
View File

@ -0,0 +1,369 @@
-- This handles scaling enemies in a few different ways to make sure that all players can have a reasonable experience
-- even if they join the game late or are playing at a slower pace.
-- TODO: Plan for new enemies in space DLC.
-- TODO: Plan for new enemies in space DLC.
-- TODO: Plan for new enemies in space DLC.
ENEMY_FORCE_EASY = "enemy-easy"
ENEMY_FORCE_MEDIUM = "enemy-medium"
ENEMY_FORCES_NAMES = {"enemy", ENEMY_FORCE_EASY, ENEMY_FORCE_MEDIUM}
ENEMY_FORCES_NAMES_INCL_NEUTRAL = {"enemy", ENEMY_FORCE_EASY, ENEMY_FORCE_MEDIUM, "neutral"}
ENEMY_BUILT_TYPES = { "biter-spawner", "spitter-spawner", "small-worm-turret", "medium-worm-turret", "big-worm-turret", "behemoth-worm-turret" }
---Create a few extra enemy forces with fixed evolution factors for scaling down near player bases.
---@return nil
function CreateEnemyForces()
-- Create the enemy forces if they don't exist
for _,force_name in pairs(ENEMY_FORCES_NAMES) do
if (game.forces[force_name] == nil) then
game.create_force(force_name)
end
local enemy_force = game.forces[force_name]
enemy_force.ai_controllable = true
end
ConfigureEnemyForceRelationships()
end
---Configures the friend and cease fire relationships between all forces and enemy forces.
---@return nil
function ConfigureEnemyForceRelationships()
for _,force in pairs(game.forces) do
-- If this is an enemy force
if (TableContains(ENEMY_FORCES_NAMES, force.name)) then
-- Make sure it IS friends with all other enemy forces.
for _,enemy_force_name in pairs(ENEMY_FORCES_NAMES) do
if (force.name ~= enemy_force_name) then -- Exclude self
local enemy_force = game.forces[enemy_force_name]
force.set_friend(enemy_force, true)
force.set_cease_fire(enemy_force, true)
enemy_force.set_friend(force, true)
enemy_force.set_cease_fire(force, true)
end
end
-- If this is a non-enemy force
else
-- Make sure this force is NOT friends with any enemy forces.
for _,enemy_force_name in pairs(ENEMY_FORCES_NAMES) do
local enemy_force = game.forces[enemy_force_name]
force.set_friend(enemy_force, false)
force.set_cease_fire(enemy_force, false)
enemy_force.set_friend(force, false)
enemy_force.set_cease_fire(force, false)
end
end
end
end
---Configures the friend and cease fire relationships between all enemy forces and a new player force.
---@param new_player_force LuaForce
---@return nil
function ConfigureEnemyForceRelationshipsForNewPlayerForce(new_player_force)
for _,enemy_force_name in pairs(ENEMY_FORCES_NAMES) do
local enemy_force = game.forces[enemy_force_name]
new_player_force.set_friend(enemy_force, false)
new_player_force.set_cease_fire(enemy_force, false)
enemy_force.set_friend(new_player_force, false)
enemy_force.set_cease_fire(new_player_force, false)
end
end
--- Keep the enemy evolution factor in check.
---@return nil
function RestrictEnemyEvolutionOnTick()
local base_evo_factor = game.forces["enemy"].evolution_factor
-- Restrict the evolution factor of the enemy forces
game.forces[ENEMY_FORCE_EASY].evolution_factor = math.min(base_evo_factor, global.ocfg.gameplay.modified_enemy_easy_evo)
game.forces[ENEMY_FORCE_MEDIUM].evolution_factor = math.min(base_evo_factor, global.ocfg.gameplay.modified_enemy_medium_evo)
end
---Downgrades worms based on distance from origin and near/far spawn distances.
---This helps make sure worms aren't too overwhelming even at these further spawn distances.
---@param event EventData.on_chunk_generated
---@return nil
function DowngradeWormsDistanceBasedOnChunkGenerate(event)
---@type OarcConfigGameplaySettings
local gameplay = global.ocfg.gameplay
if (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.near_spawn_distance * CHUNK_SIZE)) then
DowngradeWormsInArea(event.surface, event.area, 100, 100, 100)
elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.far_spawn_distance * CHUNK_SIZE)) then
DowngradeWormsInArea(event.surface, event.area, 50, 90, 100)
elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.far_spawn_distance * CHUNK_SIZE * 2)) then
DowngradeWormsInArea(event.surface, event.area, 20, 80, 97)
else
DowngradeWormsInArea(event.surface, event.area, 0, 20, 90)
end
end
---Downgrades enemies based on distance from origin and near/far spawn distances.
---@param event EventData.on_chunk_generated
---@return nil
function DowngradeAndReduceEnemiesOnChunkGenerate(event)
local surface = event.surface
local chunk_area = event.area
local closest_spawn = GetClosestUniqueSpawn(surface.name, chunk_area.left_top)
if (closest_spawn == nil) then return end
local spawn_config --[[@as OarcConfigSpawn]] = global.ocfg.surfaces_config[surface.name].spawn_config
local chunkAreaCenter = {
x = chunk_area.left_top.x + (CHUNK_SIZE / 2),
y = chunk_area.left_top.y + (CHUNK_SIZE / 2)
}
-- Make chunks near a spawn safe by removing enemies
if (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.safe_radius) then
RemoveEnemiesInArea(surface, chunk_area)
-- Create a warning area with heavily reduced enemies
elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.warn_radius) then
-- TODO: Refactor this to reduce calls to find_entities_filtered!
ReduceEnemiesInArea(surface, chunk_area, spawn_config.safe_area.warn_reduction)
RemoveWormsInArea(surface, chunk_area, false, true, true, true) -- remove all non-small worms.
ConvertEnemiesToOtherForceInArea(surface, chunk_area, ENEMY_FORCE_EASY)
-- Create a third area with moderately reduced enemies
elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.danger_radius) then
-- TODO: Refactor this to reduce calls to find_entities_filtered!
ReduceEnemiesInArea(surface, chunk_area, spawn_config.safe_area.danger_reduction)
RemoveWormsInArea(surface, chunk_area, false, false, true, true) -- remove all huge/behemoth worms.
ConvertEnemiesToOtherForceInArea(surface, chunk_area, ENEMY_FORCE_MEDIUM)
end
end
---Convenient way to remove aliens, just provide an area
---@param surface LuaSurface
---@param area BoundingBox
---@return nil
function RemoveEnemiesInArea(surface, area)
for _, entity in pairs(surface.find_entities_filtered { area = area, force = "enemy" }) do
entity.destroy()
end
end
---Make an area safer, randomly removes enemies based on a reduction factor.
---@param surface LuaSurface
---@param area BoundingBox
---@param reductionFactor integer Reduction factor divides the enemy spawns by that number. 2 = half, 3 = third, etc...
---@return nil
function ReduceEnemiesInArea(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.
---@param surface LuaSurface
---@param area BoundingBox
---@param small_percent integer ---Chance to change to small worm
---@param medium_percent integer
---@param big_percent integer
---@return nil
function DowngradeWormsInArea(surface, area, small_percent, medium_percent, big_percent)
-- Leave out "small-worm-turret" as it's the lowest.
local worm_types = { "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
---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.
---@param surface LuaSurface
---@param area BoundingBox
---@param small boolean
---@param medium boolean
---@param big boolean
---@param behemoth boolean
---@return nil
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 (#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
---Converts all enemies in the base enemy force in an area to easy force.
---@param surface LuaSurface
---@param area BoundingBox
---@param force_name string
---@return nil
function ConvertEnemiesToOtherForceInArea(surface, area, force_name)
for _, entity in pairs(surface.find_entities_filtered { area = area, force = "enemy" }) do
entity.force = game.forces[force_name]
end
end
---Converts new enemy bases to different enemy forces if they are near to player bases.
---@param event EventData.on_biter_base_built
---@return nil
function ChangeEnemySpawnersToOtherForceOnBuilt(event)
if (not event.entity or not event.entity.position or not TableContains(ENEMY_FORCES_NAMES, event.entity.force.name)) then
log("ChangeEnemySpawnersToOtherForceOnBuilt Unexpected entity or force?")
return
end
local enemy_pos = event.entity.position
local surface = event.entity.surface
local enemy_name = event.entity.name
if (not TableContains(ENEMY_BUILT_TYPES, enemy_name)) then
log("ChangeEnemySpawnersToOtherForceOnBuilt Unexpected entity name? " .. enemy_name)
return
end
local closest_spawn = GetClosestUniqueSpawn(surface.name, enemy_pos)
if (closest_spawn == nil) then return end
-- No enemies inside safe radius!
if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius) then
event.entity.destroy()
-- Warn distance should be EASY
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius) then
event.entity.force = game.forces[ENEMY_FORCE_EASY]
-- Danger distance should be MEDIUM
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius) then
event.entity.force = game.forces[ENEMY_FORCE_MEDIUM]
-- Otherwise make sure they are on the base enemy force (stops easy enemies from spreading out too far).
else
event.entity.force = game.forces["enemy"]
end
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.
---@param event EventData.on_entity_spawned|EventData.on_biter_base_built
---@return nil
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(surface.name, enemy_pos)
if (closest_spawn == nil) then
-- log("GetClosestUniqueSpawn ERROR - None found?")
return
end
-- No enemies inside safe radius!
if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius) then
event.entity.destroy()
-- Warn distance is all SMALL only.
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius) then
if ((enemy_name == "biter-spawner") or (enemy_name == "spitter-spawner")) then
event.entity.force = game.forces["enemy-easy"]
elseif ((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-easy"] }
-- 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-easy"] }
-- 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-easy"] }
-- log("Downgraded worm close to spawn.")
end
-- Danger distance is MEDIUM max.
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,689 +0,0 @@
-- shared_chests.lua
-- Feb 2020
-- Oarc's silly idea for a scripted item sharing solution.
-- Buffer size is the limit of joules/tick so multiply by 60 to get /sec.
SHARED_ELEC_OUTPUT_BUFFER_SIZE = 1000000000
SHARED_ELEC_INPUT_BUFFER_SIZE = 1000000001
SHARED_ENERGY_STARTING_VALUE = 0 -- 100GJ
function SharedChestInitItems()
global.oshared = {}
global.oshared.chests = {}
global.oshared.requests = {}
global.oshared.requests_totals = {}
global.oshared.electricity_inputs = {}
global.oshared.electricity_outputs = {}
global.oshared.chests_combinators = {}
global.oshared.items = {}
global.oshared.items['red-wire'] = 10000
global.oshared.items['green-wire'] = 10000
global.oshared.items['raw-fish'] = 10000
global.oshared.energy_stored = SHARED_ENERGY_STARTING_VALUE
global.oshared.energy_stored_history = {start=SHARED_ENERGY_STARTING_VALUE, after_input=SHARED_ENERGY_STARTING_VALUE, after_output=SHARED_ENERGY_STARTING_VALUE}
end
function SharedEnergySpawnInput(player, pos)
local inputElec = game.surfaces[GAME_SURFACE_NAME].create_entity{name="electric-energy-interface", position=pos, force="neutral"}
inputElec.destructible = false
inputElec.minable = false
inputElec.operable = false
inputElec.last_user = player
inputElec.electric_buffer_size = SHARED_ELEC_INPUT_BUFFER_SIZE
inputElec.power_production = 0
inputElec.power_usage = 0
inputElec.energy = 0
local inputElecCombi = game.surfaces[GAME_SURFACE_NAME].create_entity{name="constant-combinator", position={x=pos.x+1, y=pos.y}, force="neutral"}
inputElecCombi.destructible = false
inputElecCombi.minable = false
inputElecCombi.operable = true -- Input combi can be set by the player!
inputElecCombi.last_user = player
-- Default share is 1MW
inputElecCombi.get_or_create_control_behavior().set_signal(1,
{signal={type="virtual", name="signal-M"},
count=1})
TemporaryHelperText("Connect to electric network to contribute shared energy.", {pos.x+1.5, pos.y-1}, TICKS_PER_MINUTE*2)
TemporaryHelperText("Use combinator to limit number of MW shared.", {pos.x+2.5, pos.y}, TICKS_PER_MINUTE*2)
table.insert(global.oshared.electricity_inputs, {eei=inputElec, combi=inputElecCombi})
end
function SharedEnergySpawnOutput(player, pos)
local outputElec = game.surfaces[GAME_SURFACE_NAME].create_entity{name="electric-energy-interface", position=pos, force="neutral"}
outputElec.destructible = false
outputElec.minable = false
outputElec.operable = false
outputElec.last_user = player
outputElec.electric_buffer_size = SHARED_ELEC_OUTPUT_BUFFER_SIZE
outputElec.power_production = 0
outputElec.power_usage = 0
outputElec.energy = 0
local outputElecCombi = game.surfaces[GAME_SURFACE_NAME].create_entity{name="constant-combinator", position={x=pos.x+1, y=pos.y}, force="neutral"}
outputElecCombi.destructible = false
outputElecCombi.minable = false
outputElecCombi.operable = false -- Output combi is set my script!
outputElec.last_user = player
TemporaryHelperText("Connect to electric network to consume shared energy.", {pos.x+1.5, pos.y-1}, TICKS_PER_MINUTE*2)
TemporaryHelperText("Combinator outputs number of MJ currently stored.", {pos.x+2.5, pos.y}, TICKS_PER_MINUTE*2)
table.insert(global.oshared.electricity_outputs, {eei=outputElec, combi=outputElecCombi})
end
function SharedEnergyStoreInputOnTick()
global.oshared.energy_stored_history.start = global.oshared.energy_stored
for idx,input in pairs(global.oshared.electricity_inputs) do
-- Check for entity no longer valid:
if (input.eei == nil) or (not input.eei.valid) or (input.combi == nil) or (not input.combi.valid) then
global.oshared.electricity_inputs[idx] = nil
-- Is input at least half full, then we can start to store energy.
elseif (input.eei.energy > (SHARED_ELEC_INPUT_BUFFER_SIZE/2)) then
-- Calculate the max we can share
local max_input_allowed = input.eei.energy - (SHARED_ELEC_INPUT_BUFFER_SIZE/2)
-- Get the combinator limit
local limit = 0
local sig = input.combi.get_or_create_control_behavior().get_signal(1)
if ((sig ~= nil) and (sig.signal ~= nil) and (sig.signal.name == "signal-M")) then
limit = sig.count
end
-- Get the minimum
input.eei.power_usage = math.min(max_input_allowed, math.floor(limit*1000000/60))
global.oshared.energy_stored = global.oshared.energy_stored + input.eei.power_usage
-- Switch off contribution if not at least half full.
else
input.eei.power_usage = 0
end
end
global.oshared.energy_stored_history.after_input = global.oshared.energy_stored
end
-- If there is room to distribute energy, we take shared amount split by players.
function SharedEnergyDistributeOutputOnTick()
-- Share limit is total amount stored divided by outputs
local energyShareCap = math.floor(global.oshared.energy_stored / (#global.oshared.electricity_outputs))
-- Iterate through and fill up outputs if they are under 50%
for idx,output in pairs(global.oshared.electricity_outputs) do
-- Check for entity no longer valid:
if (output.eei == nil) or (not output.eei.valid) or (output.combi == nil) or (not output.combi.valid) then
global.oshared.electricity_outputs[idx] = nil
else
-- If it's not full, set production to fill (or as much as is allowed.)
if (output.eei.energy < (SHARED_ELEC_OUTPUT_BUFFER_SIZE/2)) then
local outBufferSpace = ((SHARED_ELEC_OUTPUT_BUFFER_SIZE/2) - output.eei.energy)
output.eei.power_production = math.min(outBufferSpace, energyShareCap)
global.oshared.energy_stored = global.oshared.energy_stored - math.min(outBufferSpace, energyShareCap)
-- Switch off if we're more than half full.
else
output.eei.power_production = 0
end
-- Update output combinator
output.combi.get_or_create_control_behavior().set_signal(1,
{signal={type="virtual", name="signal-M"},
count=clampInt32(math.floor(global.oshared.energy_stored/1000000))})
end
end
global.oshared.energy_stored_history.after_output = global.oshared.energy_stored
end
-- Returns NIL or position of destroyed chest.
function FindClosestWoodenChestAndDestroy(player)
local target_chest = FindClosestPlayerOwnedEntity(player, "wooden-chest", 16)
if (not target_chest) then
player.print("Failed to find wooden-chest?")
return nil
end
if (not target_chest.get_inventory(defines.inventory.chest).is_empty()) then
player.print("Chest is NOT empty! Please empty it and try again.")
return nil
end
local pos = target_chest.position
if (not target_chest.destroy()) then
player.print("ERROR - Can't remove wooden chest??")
return nil
end
return {x=math.floor(pos.x),y=math.floor(pos.y)}
end
function ConvertWoodenChestToSharedChestInput(player)
local pos = FindClosestWoodenChestAndDestroy(player)
if (pos) then
SharedChestsSpawnInput(player, pos)
OarcMapFeaturePlayerCountChange(player, "special_chests", "logistic-chest-storage", 1)
return true
end
return false
end
function ConvertWoodenChestToSharedChestOutput(player)
local pos = FindClosestWoodenChestAndDestroy(player)
if (pos) then
SharedChestsSpawnOutput(player, pos)
OarcMapFeaturePlayerCountChange(player, "special_chests", "logistic-chest-requester", 1)
return true
end
return false
end
function ConvertWoodenChestToSharedChestCombinators(player)
local pos = FindClosestWoodenChestAndDestroy(player)
if (pos) then
if (player.surface.can_place_entity{name="constant-combinator", position={pos.x,pos.y-1}}) and
(player.surface.can_place_entity{name="constant-combinator", position={pos.x,pos.y+1}}) then
SharedChestsSpawnCombinators(player, {x=pos.x,y=pos.y-1}, {x=pos.x,y=pos.y+1})
return true
else
player.print("Failed to place the special combinators. Please check there is enough space in the surrounding tiles!")
end
end
return false
end
function ConvertWoodenChestToShareEnergyInput(player)
local pos = FindClosestWoodenChestAndDestroy(player)
if (pos) then
if (player.surface.can_place_entity{name="electric-energy-interface", position=pos}) and
(player.surface.can_place_entity{name="constant-combinator", position={x=pos.x+1, y=pos.y}}) then
SharedEnergySpawnInput(player, pos)
OarcMapFeaturePlayerCountChange(player, "special_chests", "accumulator", 1)
return true
else
player.print("Failed to place the shared energy input. Please check there is enough space in the surrounding tiles!")
end
end
return false
end
function ConvertWoodenChestToShareEnergyOutput(player)
local pos = FindClosestWoodenChestAndDestroy(player)
if (pos) then
if (player.surface.can_place_entity{name="electric-energy-interface", position=pos}) and
(player.surface.can_place_entity{name="constant-combinator", position={x=pos.x+1, y=pos.y}}) then
SharedEnergySpawnOutput(player, pos)
OarcMapFeaturePlayerCountChange(player, "special_chests", "electric-energy-interface", 1)
return true
else
player.print("Failed to place the shared energy input. Please check there is enough space in the surrounding tiles!")
end
end
return false
end
function ConvertWoodenChestToWaterFill(player)
local pos = FindClosestWoodenChestAndDestroy(player)
if (pos) then
if (getDistance(pos, player.position) > 2) then
player.surface.set_tiles({[1]={name = "water", position=pos}})
return true
else
player.print("Failed to place waterfill. Don't stand so close FOOL!")
end
end
return false
end
function DestroyClosestSharedChestEntity(player)
local special_entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{
name={"electric-energy-interface", "constant-combinator", "logistic-chest-storage", "logistic-chest-requester"},
position=player.position,
radius=16,
force={"neutral"}}
if (#special_entities == 0) then
player.print("Special entity not found? Are you close enough?")
return
end
local closest = game.surfaces[GAME_SURFACE_NAME].get_closest(player.position, special_entities)
if (closest) then
if (closest.last_user and (closest.last_user ~= player)) then
player.print("You can't remove other players chests!")
else
-- Subtract from feature counter...
local name = closest.name
if (name == "electric-energy-interface") then
if (closest.electric_buffer_size == SHARED_ELEC_INPUT_BUFFER_SIZE) then
OarcMapFeaturePlayerCountChange(player, "special_chests", "accumulator", -1)
else
OarcMapFeaturePlayerCountChange(player, "special_chests", "electric-energy-interface", -1)
end
elseif (name == "logistic-chest-storage") then
OarcMapFeaturePlayerCountChange(player, "special_chests", "logistic-chest-storage", -1)
elseif (name == "logistic-chest-requester") then
OarcMapFeaturePlayerCountChange(player, "special_chests", "logistic-chest-requester", -1)
end
closest.destroy()
player.print("Special entity removed!")
end
else
player.print("Special entity not found? Are you close enough? -- ERROR")
end
end
function SharedChestsSpawnInput(player, pos)
local inputChest = game.surfaces[GAME_SURFACE_NAME].create_entity{name="logistic-chest-storage", position={pos.x, pos.y}, force="neutral"}
inputChest.destructible = false
inputChest.minable = false
inputChest.last_user = player
if global.oshared.chests == nil then
global.oshared.chests = {}
end
local chestInfoIn = {player=player.name,type="INPUT",entity=inputChest}
table.insert(global.oshared.chests, chestInfoIn)
TemporaryHelperText("Place items in to share.", {pos.x+1.5, pos.y}, TICKS_PER_MINUTE*2)
end
function SharedChestsSpawnOutput(player, pos, enable_example)
local outputChest = game.surfaces[GAME_SURFACE_NAME].create_entity{name="logistic-chest-requester", position={pos.x, pos.y}, force="neutral"}
outputChest.destructible = false
outputChest.minable = false
outputChest.last_user = player
if (enable_example) then
outputChest.set_request_slot({name="raw-fish", count=1}, 1)
end
if global.oshared.chests == nil then
global.oshared.chests = {}
end
local chestInfoOut = {player=player.name,type="OUTPUT",entity=outputChest}
table.insert(global.oshared.chests, chestInfoOut)
TemporaryHelperText("Set filters to request items.", {pos.x+1.5, pos.y}, TICKS_PER_MINUTE*2)
end
function SharedChestsSpawnCombinators(player, posCtrl, posStatus)
local combiCtrl = game.surfaces[GAME_SURFACE_NAME].create_entity{name="constant-combinator", position=posCtrl, force="neutral"}
combiCtrl.destructible = false
combiCtrl.minable = false
combiCtrl.last_user = player
-- Fish as an example.
combiCtrl.get_or_create_control_behavior().set_signal(1, {signal={type="item", name="raw-fish"}, count=1})
local combiStat = game.surfaces[GAME_SURFACE_NAME].create_entity{name="constant-combinator", position=posStatus, force="neutral"}
combiStat.destructible = false
combiStat.minable = false
combiStat.operable = false
combiStat.last_user = player
if global.oshared.chests_combinators == nil then
global.oshared.chests_combinators = {}
end
local combiPair = {player=player.name,ctrl=combiCtrl,status=combiStat}
table.insert(global.oshared.chests_combinators, combiPair)
TemporaryHelperText("Set signals here to monitor item counts.", {posCtrl.x+1.5, posCtrl.y}, TICKS_PER_MINUTE*2)
TemporaryHelperText("Receive signals here to see available items.", {posStatus.x+1.5, posStatus.y}, TICKS_PER_MINUTE*2)
end
function SharedChestsUpdateCombinators()
if global.oshared.chests_combinators == nil then
global.oshared.chests_combinators = {}
end
for idx,combiPair in pairs(global.oshared.chests_combinators) do
-- Check if combinators still exist
if (combiPair.ctrl == nil) or (combiPair.status == nil) or
(not combiPair.ctrl.valid) or (not combiPair.status.valid) then
global.oshared.chests_combinators[idx] = nil
else
local combiCtrlBehav = combiPair.ctrl.get_or_create_control_behavior()
local ctrlSignals = {}
-- Get signals on the ctrl combi:
for i=1,combiCtrlBehav.signals_count do
local sig = combiCtrlBehav.get_signal(i)
if ((sig ~= nil) and (sig.signal ~= nil) and (sig.signal.type == "item")) then
table.insert(ctrlSignals, sig.signal.name)
end
end
local combiStatBehav = combiPair.status.get_or_create_control_behavior()
-- Set signals on the status combi:
for i=1,combiCtrlBehav.signals_count do
if (ctrlSignals[i] ~= nil) then
local availAmnt = global.oshared.items[ctrlSignals[i]]
if availAmnt == nil then availAmnt = 0 end
combiStatBehav.set_signal(i, {signal={type="item", name=ctrlSignals[i]}, count=clampInt32(availAmnt)})
else
combiStatBehav.set_signal(i, nil)
end
end
end
end
end
function SharedChestUploadItem(item_name, count)
if (not game.item_prototypes[item_name].has_flag("hidden")) then
if (global.oshared.items[item_name] == nil) then
global.oshared.items[item_name] = count
else
global.oshared.items[item_name] = global.oshared.items[item_name] + count
end
return true
else
return false
end
end
function SharedChestEmptyEquipment(item_stack)
if (item_stack == nil) then
return
end
if (item_stack.grid == nil) then
return
end
local contents = item_stack.grid.get_contents()
for item_name,count in pairs(contents) do
SharedChestUploadItem(item_name, count)
end
end
function SharedChestUploadChest(entity)
local chest_inv = entity.get_inventory(defines.inventory.chest)
if (chest_inv == nil) then return end
if (chest_inv.is_empty()) then return end
local contents = chest_inv.get_contents()
for item_name,count in pairs(contents) do
if (game.item_prototypes[item_name].equipment_grid ~= nil) then
local item_stack = chest_inv.find_item_stack(item_name)
while (item_stack ~= nil) do
SharedChestEmptyEquipment(item_stack)
item_stack.clear()
item_stack = chest_inv.find_item_stack(item_name)
end
end
if (SharedChestUploadItem(item_name, count)) then
chest_inv.remove({name=item_name, count=count})
end
end
end
-- Pull all items in the deposit chests
function SharedChestsDepositAll()
if global.oshared.items == nil then
global.oshared.items = {}
end
for idx,chest_info in pairs(global.oshared.chests) do
local chest_entity = chest_info.entity
-- Delete any chest that is no longer valid.
if ((chest_entity == nil) or (not chest_entity.valid)) then
global.oshared.chests[idx] = nil
-- Take inputs and store.
elseif (chest_info.type == "INPUT") then
SharedChestUploadChest(chest_entity)
end
end
end
-- Tally up requests by item.
function SharedChestsTallyRequests()
-- Clear existing requests. Also serves as an init
global.oshared.requests = {}
global.oshared.requests_totals = {}
-- For each output chest.
for idx,chestInfo in pairs(global.oshared.chests) do
local chestEntity = chestInfo.entity
-- Delete any chest that is no longer valid.
if ((chestEntity == nil) or (not chestEntity.valid)) then
global.oshared.chests[idx] = nil
elseif (chestInfo.type == "OUTPUT") then
-- For each request slot
for i = 1, chestEntity.request_slot_count, 1 do
local req = chestEntity.get_request_slot(i)
-- If there is a request, add the request count to our request table.
if (req ~= nil) then
if global.oshared.requests[req.name] == nil then
global.oshared.requests[req.name] = {}
end
if global.oshared.requests[req.name][chestInfo.player] == nil then
global.oshared.requests[req.name][chestInfo.player] = 0
end
if global.oshared.requests_totals[req.name] == nil then
global.oshared.requests_totals[req.name] = 0
end
-- Calculate actual request to fill remainder
local existingAmount = chestEntity.get_inventory(defines.inventory.chest).get_item_count(req.name)
local requestAmount = math.max(req.count-existingAmount, 0)
-- Add the request counts
global.oshared.requests[req.name][chestInfo.player] = global.oshared.requests[req.name][chestInfo.player] + requestAmount
global.oshared.requests_totals[req.name] = global.oshared.requests_totals[req.name] + requestAmount
end
end
end
end
-- If demand is more than supply, limit each player's total item request to shared amount
for reqName,reqTally in pairs(global.oshared.requests) do
local cap = 0
local mustCap = false
-- No shared items means nothing to supply.
if (global.oshared.items[reqName] == nil) or (global.oshared.items[reqName] == 0) then
mustCap = true
cap = 0
-- Otherwise, limit by dividing by players.
elseif (global.oshared.requests_totals[reqName] > global.oshared.items[reqName]) then
mustCap = true
cap = math.floor(global.oshared.items[reqName] / TableLength(global.oshared.requests[reqName]))
-- In the case where we are rounding down to 0, let's bump the minimum distribution to 1.
if (cap == 0) then
cap = 1
end
end
-- Limit each request to the cap.
if mustCap then
for player,reqCount in pairs(global.oshared.requests[reqName]) do
if (reqCount > cap) then
global.oshared.requests[reqName][player] = cap
end
end
end
end
end
-- Distribute requests based on demand
function SharedChestsDistributeRequests()
-- For each output chest.
for idx,chestInfo in pairs(global.oshared.chests) do
if (chestInfo.type == "OUTPUT") then
local chestEntity = chestInfo.entity
-- Delete any chest that is no longer valid.
if ((chestEntity == nil) or (not chestEntity.valid)) then
global.oshared.chests[idx] = nil
-- For each request slot
else
for i = 1, chestEntity.request_slot_count, 1 do
local req = chestEntity.get_request_slot(i)
-- If there is a request, distribute items
if (req ~= nil) then
-- Make sure requests have been created.
-- Make sure shared items exist.
if (global.oshared.requests_totals[req.name] ~= nil) and
(global.oshared.items[req.name] ~= nil) and
(global.oshared.requests[req.name][chestInfo.player] ~= nil) then
if (global.oshared.requests[req.name][chestInfo.player] > 0)and (global.oshared.items[req.name] > 0) then
-- How much is already in the chest?
local existingAmount = chestEntity.get_inventory(defines.inventory.chest).get_item_count(req.name)
-- How much is required to fill the remainder request?
local requestAmount = math.max(req.count-existingAmount, 0)
-- How much is allowed based on the player's current request amount?
local allowedAmount = math.min(requestAmount, global.oshared.requests[req.name][chestInfo.player])
if (allowedAmount > 0) then
local chestInv = chestEntity.get_inventory(defines.inventory.chest)
if chestInv.can_insert({name=req.name}) then
local amnt = chestInv.insert({name=req.name, count=math.min(allowedAmount, global.oshared.items[req.name])})
global.oshared.items[req.name] = global.oshared.items[req.name] - amnt
global.oshared.requests[req.name][chestInfo.player] = global.oshared.requests[req.name][chestInfo.player] - amnt
global.oshared.requests_totals[req.name] = global.oshared.requests_totals[req.name] - amnt
end
end
end
end
end
end
end
end
end
end
function SharedChestsOnTick()
-- Every tick we share power
SharedEnergyStoreInputOnTick()
SharedEnergyDistributeOutputOnTick()
-- Every second, we check the input chests and deposit stuff.
if ((game.tick % (60)) == 37) then
SharedChestsDepositAll()
end
-- Every second, we check the output chests for requests
if ((game.tick % (60)) == 38) then
SharedChestsTallyRequests()
end
-- Every second, we distribute to the output chests.
if ((game.tick % (60)) == 39) then
SharedChestsDistributeRequests()
end
-- Every second, we update our combinator status info.
if ((game.tick % (60)) == 40) then
SharedChestsUpdateCombinators()
end
end
function CreateSharedItemsGuiTab(tab_container, player)
local scrollFrame = tab_container.add{type="scroll-pane",
name="sharedItems-panel",
direction = "vertical"}
ApplyStyle(scrollFrame, my_shared_item_list_fixed_width_style)
scrollFrame.horizontal_scroll_policy = "never"
AddLabel(scrollFrame, "share_items_info", "Place items into the [color=yellow]yellow storage chests to share[/color].\nRequest items from the [color=blue]blue requestor chests to pull out items[/color].\nTo refresh this view, click the tab again.\nShared items are accessible by [color=red]EVERYONE and all teams[/color].\nThe combinator pair allows you to 'set' item types to watch for. Set items in the top one, and connect the bottom one to a circuit network to view the current available inventory. Items with 0 amount do not generate any signal.\nThe special accumulators share energy. The top one acts as an input, the bottom is the output.", my_longer_label_style)
AddSpacerLine(scrollFrame)
-- MW charging/discharging rate. (delta change * sample rate per second)
local energy_change_add = (global.oshared.energy_stored_history.after_input - global.oshared.energy_stored_history.start)*60/1000000
local energy_change_sub = (((global.oshared.energy_stored_history.after_input - global.oshared.energy_stored_history.after_output)*60))/1000000
local energy_add_str = string.format("+%.3fMW", energy_change_add)
local energy_sub_str = string.format("-%.3fMW", energy_change_sub)
local rate_color = "green"
if (energy_change_add <= energy_change_sub) then
rate_color = "red"
elseif (energy_change_add < (energy_change_sub+10)) then
rate_color = "orange"
end
AddLabel(scrollFrame, "elec_avail_info", "[color=acid]Current electricity available: " .. string.format("%.3f", global.oshared.energy_stored/1000000) .. "MJ[/color] [color=" .. rate_color .. "](" .. energy_add_str .. " " .. energy_sub_str ..")[/color]", my_longer_label_style)
AddSpacerLine(scrollFrame)
AddLabel(scrollFrame, "share_items_title_msg", "Shared Items:", my_label_header_style)
local sorted_items = {}
for k in pairs(global.oshared.items) do table.insert(sorted_items, k) end
table.sort(sorted_items)
for idx,itemName in pairs(sorted_items) do
if (global.oshared.items[itemName] > 0) then
local caption_str = "[item="..itemName.."] " .. itemName..": "..global.oshared.items[itemName]
AddLabel(scrollFrame, itemName.."_itemlist", caption_str, my_player_list_style)
end
end
end

105
lib/sharing.lua Normal file
View File

@ -0,0 +1,105 @@
-- This handles the shared power logic for the Oarc scenario.
-- Won't work too hard on this since 2.0 might change things...
STARTING_X_OFFSET_SHARING_POLE = -5
Y_OFFSET_SHARING_POLE = 20
---Create and connect a pair of power poles for a new base given surface and position.
---@param surface LuaSurface
---@param position MapPosition
---@return nil
function CreateSharedPowerPolePair(surface, position)
if global.shared_power_poles == nil then
---@type LuaEntity[]
global.shared_power_poles = {}
end
--Get an open sharing pole from the holding pen surface if one exists, otherwise create a new one.
local hidden_pole = FindSharedPowerPole()
if not hidden_pole then
local poles_count = table_size(global.shared_power_poles)
local new_position = { x = poles_count + STARTING_X_OFFSET_SHARING_POLE, y = Y_OFFSET_SHARING_POLE }
hidden_pole = CreateSpecialPole(game.surfaces[HOLDING_PEN_SURFACE_NAME], new_position)
if not hidden_pole then
log("ERROR - Failed to create shared power poles!? " .. serpent.block(position) .. " on " .. surface.name)
return
end
table.insert(global.shared_power_poles, hidden_pole)
end
--Create the base pole on the new spawn area surface and connect it to the hidden pole.
local base_pole = CreateSpecialPole(surface, position)
if not base_pole then
log("ERROR - Failed to create shared power poles!? " .. serpent.block(position) .. " on " .. surface.name)
return
end
base_pole.connect_neighbour(hidden_pole)
TemporaryHelperText(
{ "oarc-shared-power-pole-helper-txt" },
surface,
{position.x, position.y},
TICKS_PER_MINUTE*2,
"right"
)
end
---Find the first shared power pole that doesn't exceed the max number of connections.
---@return LuaEntity?
function FindSharedPowerPole()
if global.shared_power_poles == nil then return nil end
for _,pole in pairs(global.shared_power_poles) do
-- 5 is the hard coded engine limit and we need to leave one open for the connection to the next hidden pole.
if pole.neighbours["copper"] and table_size(pole.neighbours["copper"]) < 4 then
return pole
end
end
return nil
end
---Creates a special pole on the surface at the given position on the neutral force.
---@param surface LuaSurface
---@param position MapPosition
---@return LuaEntity?
function CreateSpecialPole(surface, position)
local pole = surface.create_entity
{
name="oarc-linked-power",
position=position,
force="neutral"
}
pole.destructible = false
pole.minable = false
pole.rotatable = false
return pole
end
---Creates a special linked-chest on the surface at the given position on the neutral force.
---@param surface LuaSurface
---@param position MapPosition
---@return LuaEntity?
function CreateSharedChest(surface, position)
local chest = surface.create_entity
{
name="oarc-linked-chest",
position=position,
force="neutral"
}
chest.destructible = false
chest.minable = false
chest.rotatable = false
TemporaryHelperText(
{ "oarc-shared-chest-helper-txt" },
surface,
{position.x, position.y},
TICKS_PER_MINUTE*2,
"right"
)
return chest
end

View File

@ -1,52 +0,0 @@
-- 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.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

View File

@ -1,122 +0,0 @@
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 naher Entfernung 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 WURDE!
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...

View File

@ -0,0 +1,122 @@
[mod-description]
oarc-mod=This is a multiplayer mod that allows every player to create their own spawn point when they join the game. There are a lot of helpful features to ensure that new players can join at anytime in the game or even join other player's spawn areas.\n\n[color=red][font=default-bold]Please check out the github page and discord for more information and support.[/font][/color]\n\n[font=default-small]This USED to be available as a scenario, but now is only provided as a mod. To start a new game with this mod, just use the default freeplay scenario. The scenario included in this mod only provides a template to overwrite the default freeplay scenario. It also provides a way for experienced server hosts to configure settings from a file instead of through the usual mod settings. Please read the control.lua file inside the scenario for notes.[/font]
[mod-setting-name]
oarc-mod-default-allow-spawning-on-other-surfaces=Default to allow spawns on other surfaces
oarc-mod-linked-chest-size=Sharing chest capacity
oarc-mod-welcome-msg-title=Welcome message title
oarc-mod-welcome-msg=Welcome message
oarc-mod-discord-invite=Discord Invite
oarc-mod-enable-main-team=Enable main team
oarc-mod-enable-separate-teams=Enable separate teams
oarc-mod-allow-moats-around-spawns=Allow moats around spawns
oarc-mod-enable-moat-bridging=Moat bridges
oarc-mod-minimum-distance-to-existing-chunks=Minimum distance to existing chunks
oarc-mod-near-spawn-distance=Spawn minimum distance
oarc-mod-far-spawn-distance=Spawn maximum distance
oarc-mod-enable-buddy-spawn=Enable buddy spawn
oarc-mod-enable-offline-protection=Offline protection
oarc-mod-enable-shared-team-vision=Shared team vision
oarc-mod-enable-shared-team-chat=Shared team chat
oarc-mod-enable-shared-spawns=Shared spawns
oarc-mod-number-of-players-per-shared-spawn=Number of players per shared spawn
oarc-mod-enable-friendly-fire=Enable friendly fire
oarc-mod-main-force-name=Main force name
oarc-mod-default-surface=Default starting surface
oarc-mod-enable-secondary-spawns=Enable secondary spawns
oarc-mod-scale-resources-around-spawns=Scale resources around spawns
oarc-mod-modified-enemy-spawning=Scale enemies around spawns
oarc-mod-modified-enemy-easy-evo=Easy enemy evolution
oarc-mod-modified-enemy-medium-evo=Medium enemy evolution
oarc-mod-minimum-online-time=Minimum online time
oarc-mod-respawn-cooldown-min=Reset respawn point cooldown
oarc-mod-enable-shared-power=Shared power
oarc-mod-enable-shared-chest=Shared chest
oarc-mod-enable-regrowth=Regrowth (map cleanup)
oarc-mod-enable-world-eater=World eater (map cleanup - additional)
oarc-mod-enable-abandoned-base-cleanup=Cleanup abandoned bases
oarc-mod-regrowth-cleanup-interval-min=Regrowth cleanup interval
oarc-mod-spawn-general-radius-tiles=Spawn area radius
oarc-mod-spawn-general-moat-width-tiles=Spawn moat width
oarc-mod-spawn-general-tree-width-tiles=Spawn tree ring width
oarc-mod-spawn-general-enable-resources-circle-shape=Spawn resource deposits shape
oarc-mod-spawn-general-enable-force-grass=Force spawn area grass
oarc-mod-spawn-general-shape=Spawn area shape
oarc-mod-resource-placement-enabled=Starting resource auto placement
oarc-mod-resource-placement-distance-to-edge=Starting resource distance to edge
oarc-mod-resource-placement-angle-offset=Starting resource angle offset
oarc-mod-resource-placement-angle-final=Starting resource angle final
oarc-mod-resource-placement-vertical-offset=Starting resource vertical offset
oarc-mod-resource-placement-horizontal-offset=Starting resource horizontal offset
oarc-mod-resource-placement-linear-spacing=Starting resource linear spacing
oarc-mod-resource-placement-size-multiplier=Starting resource size multiplier
oarc-mod-resource-placement-amount-multiplier=Starting resource amount multiplier
[mod-setting-description]
oarc-mod-default-allow-spawning-on-other-surfaces=This controls the default starting setting for whether to allow spawning on other surfaces. If enabled, by default all other surfaces will be available for players to spawn on. [color=red]If you have other mods installed that add additional surfaces, I recommend leaving this disabled. Regardless of this setting, you can configure which surfaces allow spawning using the in game settings menu.[/color]
oarc-mod-linked-chest-size=This is the size of the shared chest that players can use to share items with other players. This is only meaningful if the shared chest feature is enabled.
oarc-mod-welcome-msg-title=This is the title of the welcome message that will be displayed to players when they join the game.
oarc-mod-welcome-msg=This is the welcome message that will be displayed to players when they join the game and in the info panel. [color=red]Leave a single space to disable.[/color]
oarc-mod-discord-invite=Place your discord invite here so players can easily copy it from the in game info panel. [color=red]Leave a single space to disable.[/color]
oarc-mod-enable-main-team=Allow players to join the main team. This is the default team that is created when the game starts. This lets players share research progress.\n[color=red][font=default-bold]You must enable one or both of the main team and separate team options. Otherwise it will default to main team allowed only.[/font][/color]
oarc-mod-enable-separate-teams=Allow players to start their own teams (CO-OP only, No PVP). This lets players have their own research progress.\n[color=red][font=default-bold]You must enable one or both of the main team and separate team options. Otherwise it will default to main team allowed only.[/font][/color]
oarc-mod-allow-moats-around-spawns=Allow players to choose spawns with a moat around them.
oarc-mod-enable-moat-bridging=If the spawn area is surrounded by land, the moat will have a small land bridge connecting it.
oarc-mod-minimum-distance-to-existing-chunks=This is the minimum distance a new spawn will be created from existing chunks (even if not visible).
oarc-mod-near-spawn-distance=The minimum distance a player can choose to spawn from the origin. This is used as a starting point for the search algorithm so is not a guaranteed distance.
oarc-mod-far-spawn-distance=The maximum distance a player can choose to spawn from the origin. This is used as a starting point for the search algorithm so is not a guaranteed distance.
oarc-mod-enable-buddy-spawn=Allow spawning with a buddy. 2 players can spawn next to each other with their own starting areas.
oarc-mod-enable-offline-protection=This inhibits enemy attacks on bases where all players are offline. Not 100% guaranteed!
oarc-mod-enable-shared-team-vision=Makes sure all teams can see each other's map and radar coverage.
oarc-mod-enable-shared-team-chat=All teams can see each other's chat messages.
oarc-mod-enable-shared-spawns=Allow players to share their spawn areas for other players to join.
oarc-mod-number-of-players-per-shared-spawn=Number of players that can join a shared spawn, including the original player.
oarc-mod-enable-friendly-fire=Enables friendly fire. So you can shoot your chests (or run over your friends with a train). This lets you damage your OWN team.
oarc-mod-main-force-name=The name of the main force. This is the default team that is created when the game starts.
oarc-mod-default-surface=The default surface that players will spawn on if they join the main team or if spawning on other surfaces is not enabled.
oarc-mod-enable-secondary-spawns=Enabling this will provide players with a secondary spawn point when they first move to a new surface/planet. This is only applicable if the other surface is enabled for separate spawns.
oarc-mod-scale-resources-around-spawns=This scales resources around every spawn area so far away spawns aren't immediately next to very rich deposits.
oarc-mod-modified-enemy-spawning=This scales the enemy spawning globally based on the allowed spawn distances to avoid every spawn being surrounded by behemoth worms.
oarc-mod-modified-enemy-easy-evo=This is the maximum evolution that the enemies in the warning zone (closest) to a spawn will be FIXED at to ensure all players have a safe start! The warning zone area can be configured via the custom scenario.
oarc-mod-modified-enemy-medium-evo=This is the maximum evolution that the enemies in the danger zone (next closest) to a spawn will be allowed to reach. Helps ensure players have a safe start! The danger zone area can be configured via the custom scenario.
oarc-mod-minimum-online-time=The minimum time a player must be online before they leave, otherwise their spawn area will be cleaned up.
oarc-mod-respawn-cooldown-min=The minimum time a player must wait before they can change their spawn point to a new location.
oarc-mod-enable-shared-power=This allows players to share their power network with other players. It will create a special power pole at their spawn point that connects to the shared power network wirelessly.
oarc-mod-enable-shared-chest=This gives players a special chest they can use to share items with other players. It uses the "linked-chest" item that is a hidden feature in factorio.
oarc-mod-enable-regrowth=Enables regrowth. This slowly removes inactive chunks over time. Any chunk with player structures will not be removed. This helps to keep the map (and file size) smaller over time.
oarc-mod-enable-world-eater=Enables world eater. This requires regrowth. This slowly checks all chunks for a lack of player structures and entities and marks them for removal. This helps to keep the map (and file size) smaller over time. This is more of an experimental feature, use at your own risk.
oarc-mod-enable-abandoned-base-cleanup=Abandoned bases will be cleaned up if players leave within a short time of joining. [color=red]This does NOT require regrowth or world eater to be enabled.[/color]
oarc-mod-regrowth-cleanup-interval-min=This is the interval in minutes that the regrowth cleanup will run. Whenever a map cleanup happens, the game freezes for a second, so you don't want to run this too often! Abandoned base cleanup happens immediately when a player leaves and is not affected by this setting.
oarc-mod-spawn-general-radius-tiles=This is the radius of the spawn area in tiles. I would not make this much smaller then 2 chunks (64) and not too huge unless you want spawns to take a long time to generate.
oarc-mod-spawn-general-moat-width-tiles=This is the width of the moat around the spawn area in tiles, if a moat is enabled and selected at spawn time.
oarc-mod-spawn-general-tree-width-tiles=This is the width of the tree ring around the spawn area in tiles. It guarantees some trees near to spawn.
oarc-mod-spawn-general-enable-resources-circle-shape=This is the shape of the starting area resource deposits.
oarc-mod-spawn-general-enable-force-grass=Enabling this will make the entire spawn area pure grass. Disabling will use landfill as needed instead.
oarc-mod-spawn-general-shape=This is the shape of the spawn area.
oarc-mod-resource-placement-enabled=You should leave this enabled unless you are manually specifying resource placements in the custom scenario!
oarc-mod-resource-placement-distance-to-edge=This is the distance from the edge of the spawn area that resources will be placed. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-angle-offset=This is the starting angle offset (in radians) for the resource placement. At what angle (in radians) do resources start. 0 = east. 3.14 = west. Resources are placed clockwise starting at this angle. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-angle-final=This is the final angle offset (in radians) for the resource placement. At what angle (in radians) do resources end. 0 = east. 3.14 = west. Resources are placed clockwise ending at this angle. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-vertical-offset=This is the vertical offset (in tiles) for the resource placement from the top-left of the spawn. Only applicable for square shaped spawns.
oarc-mod-resource-placement-horizontal-offset=This is the horizontal offset (in tiles) for the resource placement from the top-left of the spawn. Only applicable for square shaped spawns.
oarc-mod-resource-placement-linear-spacing=This is the linear spacing (in tiles) between resources. Only applicable for square shaped spawns.
oarc-mod-resource-placement-size-multiplier=This changes the size of the resource deposits. Default settings should provide close to a vanilla starting experience.
oarc-mod-resource-placement-amount-multiplier=This changes the richness of the resource deposits. Default settings should provide close to a vanilla starting experience.

View File

@ -1,53 +1,86 @@
scenario-name=Oarc's Multiplayer Spawning Scenario
scenario-name=OARC
description=This scenario allows every player to create their own spawn point when they join the game. There are a lot of helpful features to ensure that new players can join at anytime in the game or even join other players. This is a multiplayer only scenario.\n[color=red][font=default-bold]You must first create a config.lua file if one does not yet exist![/font][/color]\nIn the scenario folder, copy "example-config.lua" and name it "config.lua". Edit the file with any text editor. This is the only way to configure your scenario options and it MUST be done before starting a new game.\n[color=yellow][font=default-bold]Always start a new game when changing the config or updating the scenario![/font][/color]\nPlease visit https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn for the scenario source code, Wiki, and if you want to file any bugs. The wiki has instructions on how to best start games on dedicated/headless servers!\n[color=yellow][font=default-bold]If you got this scenario from the mod portal or by joining a server, make sure you remove any blueprint.zip files in the scenario folder![/font][/color]
description=This mod provides a scenario that overhauls multiplayer spawning.\n[color=red][font=default-bold]Please check out the github page and discord for more information and support.[/font][/color]
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-scenario-info-warn-msg=This scenario allows players to create their own spawn points far from the center of the map. For more information, click the OARC button in the top left corner.
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-click-info-btn-help=Click the OARC button in the top left to learn more about this scenario! This is your ONLY chance to choose a spawn option. Choose carefully...
oarc-gui-tooltip=Click to open and close the mod gui. This provides settings and information about the mod.
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-surface-select-tooltip=Select the surface you would like to spawn on. (Disabled if only one surface is available.)
oarc-join-main-team-radio=Join Main Team (shared research)
oarc-join-main-team-tooltip=You will share research progress with other players on the main team.
oarc-create-own-team-radio=Create Your Own Team (own research tree)
oarc-create-own-team-tooltip=You will have your own research tree and will not share research with players on the main team.
oarc-moat-option=Surround your spawn with a moat
oarc-moat-option-tooltip=If you spawn near land, a ring of water will be generated around your spawn area. Depending on the mod options, there may be a small land bridge created as well. If you spawn in water already, this option has no effect.
oarc-solo-spawn-near=Solo Spawn (Near)
oarc-solo-spawn-far=Solo Spawn (Far)
oarc-solo-spawn=Create Your Own Spawn
oarc-solo-spawn-tooltip=You will spawn in your own starting area with pre-set starting resources.
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-spawn-menu-settings-info=These settings apply when creating a new spawn. They do not have any effect if you are joining a shared spawn.
oarc-spawn-menu-settings-header=Spawn Settings
oarc-spawn-menu-solo-header=Create Your Own Spawn
oarc-spawn-menu-shared-header=Join Another Spawn
oarc-spawn-menu-buddy-header=Create A Buddy Spawn
oarc-spawn-distance-slider-label=Distance From Map Center:
oarc-spawn-distance-slider-tooltip=This is the minimum distance from the center of the map in chunks that you will spawn. You may spawn further away if the map has already been explored. Resources and enemies are both more abundant the further you go from the center, but you will still be guaranteed a safe starting area.
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-join-someone-dropdown-label=Select Shared Spawn:
oarc-join-someone-dropdown-tooltip=Select a player to REQUEST to join their shared spawn area.
oarc-join-shared-button-enable=Join (__1__ on __2__)
oarc-join-shared-button-disable=Select A Player First!
oarc-join-shared-button-tooltip=This will send a request to join the selected player's shared spawn area. You can cancel the request if you change your mind.
oarc-join-someone-info=You can request to join someone else's starting area. This requires at least 1 person to have allowed access to their base and to have space for you to join.
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-no-shared-spawn-selected=You have not selected a valid player to join. Please select one from the list.
oarc-invalid-host-shared-spawn=Selected player is no longer available! Please try again.
oarc-buddy-spawn=Buddy Spawn
oarc-buddy-spawn=Request Buddy Spawn
oarc-buddy-spawn-tooltip=This will send the request to your selected buddy. They may choose to accept or deny the request. You may also cancel the request if you change your mind.
oarc-buddy-spawn-disabled=Buddy spawns are disabled in this mode.
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-max-players-shared-spawn=You can allow up to __1__ other players to join you.
oarc-spawn-dist-notes=Spawn distance is in CHUNKS from the center of the map.\nExpect 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-player-is-joining=__1__ is joining the game on __2__!
oarc-player-started-own-team=__1__ has started their own team!
oarc-player-no-new-teams-sorry=Sorry, no new teams can be created. You were assigned to the default team instead.
oarc-no-ungenerated-land-error=[color=red]Failed to find ungenerated land to spawn on! Please try again. If you see this message multiple times, it could be a settings or mod conflict issue.[/color]
oarc-buddies-are-joining=__1__ and __2__ are joining the game together on __3__!
oarc-player-new-secondary=__1__ has started a secondary base on __2__!
oarc-please-wait=PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!
oarc-player-left-early=Player (__1__) left the game before __2__ minutes of play time. Their spawn area has been marked for cleanup.
oarc-generating-spawn-please-wait=Generating your spawn now, please wait...
oarc-host-left-new-host=__1__ has left so __2__ now owns their base.
oarc-new-owner-msg=You have been given ownership of this base!
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-cancel-button-caption=Cancel
oarc-return-to-previous-tooltip=Return to the previous menu.
oarc-player-requesting-join-you=__1__ is requesting to join your base!
oarc-waiting-for-spawn-owner=Waiting for spawn owner to respond...
@ -55,18 +88,39 @@ 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-primary-spawn-info-header=Primary Spawn Information
oarc-primary-spawn-info-note=This is your home base location. If you let other players join your base, they will spawn here.
oarc-primary-spawn-info-surface-label=Home: __1__ (x=__2__, y=__3__)
oarc-set-respawn-loc=Set New Respawn Location (has a cooldown)
oarc-secondary-spawn-info-header=Secondary Spawn Information
oarc-secondary-spawn-info-note=These are your secondary base locations.
oarc-secondary-spawn-info-surface-label=Secondary: __1__ (x=__2__, y=__3__)
oarc-spawn-info-location-button=Show Map Location
oarc-spawn-info-location-button-tooltip=Click to show the location on the map.
oarc-spawn-gps-location=Location:
oarc-shared-spawn-controls=Shared Spawn Controls
oarc-shared-spawn-allow-joiners=Allow others to join your base.
oarc-shared-spawn-full=Your base is full! No more players can join.
oarc-set-respawn-loc-header=Respawn Location
oarc-set-respawn-loc-info-surface-label=Current Surface Respawn: __1__ (x=__2__, y=__3__)
oarc-set-respawn-loc=Set Respawn Location
oarc-set-respawn-loc-tooltip=This will set your respawn point to your current location. There is a cooldown before you can change it again.
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-set-respawn-note=This will set your respawn point to your current location. Change tabs to update the timer.
oarc-join-queue-header=Shared Spawn Queue
oarc-select-player-join-queue=Select a player from the join queue:
oarc-accept=Accept
oarc-reject=Reject
oarc-enabled=enabled
oarc-disabled=disabled
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!
@ -74,20 +128,24 @@ 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-selected-player-not-valid=Please select a valid player!
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-select-label=Select Buddy:
oarc-buddy-select-tooltip=Select a buddy from the list to request a buddy spawn.
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-spawn-instructions=You and your buddy must be in this menu at the same time. Only one of you can send the request. Select your your spawn options first, then select your buddy from the list. Click Request Buddy Spawn. 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.
oarc-buddy-select-info=First, select a buddy from the waiting list. Then choose the spawn options and send your request:
oarc-buddy-spawn-request-header=Buddy Request!
oarc-buddy-refresh=Refresh Buddy List
oarc-create-buddy-team=Create Your Own Buddy Team (buddy and you share research)
oarc-create-buddy-team-tooltip=If this is enabled, you and your buddy will share research progress.
oarc-buddy-spawn-near=Request Buddy Spawn (Near)
oarc-buddy-spawn-far=Request Buddy Spawn (Far)
@ -102,16 +160,100 @@ oarc-buddy-cancel-request=__1__ cancelled their buddy request!
oarc-buddy-requesting-from-you=__1__ is requesting a buddy spawn from you!
# [BUDDY NAME] oarc-buddy-txt-would-like {team choice} oarc-buddy-txt-next-to-you {moat choice} {surface choice} {distance choice}
oarc-buddy-txt-would-like= would like to join
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-txt-moat= surrounded by a moat
oarc-buddy-txt-surface= on __1__
oarc-buddy-txt-distance= at a distance of __1__ chunks from the center of the map!
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...
oarc-wait-text=Your spawn is being created now.\nYou will be teleported there in __1__ seconds!\nPlease standby...
oarc-gui-tab-header-label=Scenario Info and Controls
oarc-server-info-tab-title=Server Info
oarc-spawn-ctrls-tab-title=Spawn Controls
oarc-settings-tab-title=Settings
oarc-mod-info-tab-title=Mod Info
oarc-server-info-tab-welcome-msg-title=Welcome Message
oarc-server-info-tab-discord-invite=Discord Invite:
oarc-server-info-tab-discord-invite-tooltip=Come join the discord (copy this invite)!
oarc-server-info-tab-map-info-label=Map Info
oarc-server-info-tab-server-run-time=Server Run Time: __1__
oarc-server-info-leave-warning=If you leave within __1__ minutes of joining, your base and character will be deleted.
oarc-server-info-admin-controls=Admin Controls
oarc-server-info-ban-select-player=Select Player:
oarc-server-info-button-ban-player=Ban Player
oarc-server-info-button-restart-player=Restart Player
oarc-player-not-found=Player __1__ is not found?
oarc-player-about-to-spawn=Player __1__ is about to spawn, try again later.
oarc-player-none-selected=No player selected!
oarc-settings-tab-title-mod-settings=Mod Settings
oarc-settings-tab-admin-warning=You are an admin. Changing these settings late in the game may cause issues!\nChanging settings will not modify existing spawns. BE CAREFUL!
oarc-settings-tab-player-warning=You are not an admin. These settings are read-only for you.
oarc-settings-tab-description=This tab contains the same mod settings in the mod settings menu AND some additional settings that can only be changed in game.
oarc-settings-tab-text-field-enter-tooltip=[color=red]You must press ENTER after typing in a text field to save the value![/color]
oarc-settings-tab-title-surface=Surface Settings
oarc-settings-tab-surface-checkbox-tooltip=Enabling this will allow custom spawn areas (the main feature of this mod) on this surface. You need at least one of these enabled for the mod to work.
oarc-settings-tab-surface-regrowth-checkbox-tooltip=Enabling this will allow the regrowth and world eater features to work on this surface, if those are enabled.
oarc-settings-tab-surface-column-header=Surface
oarc-settings-tab-surface-spawning-enabled=Custom Spawns
oarc-settings-tab-surface-regrowth-enabled=Regrowth
oarc-settings-section-header-server-info=Server Info
oarc-settings-section-header-gameplay=Gameplay
oarc-settings-section-header-regrowth=Regrowth
oarc-settings-section-header-general-spawn=General Spawn Area Config
oarc-settings-section-header-resource-placement=Starting Resources Placement
oarc-settings-section-subheader-spawn-choices=Spawn choices
oarc-settings-section-subheader-difficulty-scaling=Enemy and resource scaling
oarc-settings-section-subheader-gameplay-misc=Misc
oarc-settings-section-subheader-sharing=Sharing and co-op
oarc-settings-section-subheader-resource-placement-circular=For circle/octagon spawns
oarc-settings-section-subheader-resource-placement-square=For square spawns
oarc-settings-section-subheader-regrowth-warning=These features can help reduce save file size but can be more UPS intensive.\n
oarc-shared-power-pole-helper-txt=This connects to a shared electric network for all players.
oarc-shared-chest-helper-txt=This connects to a shared storage chest for all players.
oarc-mod-faq-what-is-this-mod=What is this mod?
oarc-mod-faq-what-is-this-mod-answer=This mod overhauls multiplayer spawning. Players can create their own spawn points away from the center of the map. Players can also join other players' bases, or create buddy spawns. The mod does not change the core gameplay of Factorio, but it does change the way players start the game. The starting area is a preset area with resources, not a "natural" spawn point.
oarc-mod-faq-other-surfaces=Can I start on a different surface/planet?
oarc-mod-faq-other-surfaces-answer=[color=red]This feature is currently a work in progress. As such, only spawning on "nauvis" or a default nauvis-like surface will work correctly.[/color]
oarc-mod-faq-secondary-spawns=What are secondary spawns?
oarc-mod-faq-secondary-spawns-answer=If enabled, when a player first travels to a new surface that has custom spawning enabled, they will automatically be given a new starting area based on their primary spawn choice (the first spawn they create). [color=red]This feature is currently a work in progress. Secondary spawning on other surfaces may cause issues. Buddy and shared spawns are not implemented yet at this time.[/color]
oarc-mod-faq-what-are-teams=What are the different team options?
oarc-mod-faq-what-are-teams-answer=I wrote this mod for co-op play only, not pvp. Depending on the mod settings, you can either join the main team, which shares research, or create your own team, which has its own research tree. All teams/forces are friendly to each other and can communicate in chat and (optionally) share map vision. The only reason to create your own team is if you want to have your own research progress.
oarc-mod-faq-shared-spawn=What is a shared spawn?
oarc-mod-faq-shared-spawn-answer=Players can choose to allow other players to join their base. This allows new players to spawn in the same starting area as the host player. The host player can control who can join their base.
oarc-mod-faq-buddy-spawn=What is a buddy spawn?
oarc-mod-faq-buddy-spawn-answer=If you and another player are in the spawn menu at the same time, you can request a buddy spawn. This will spawn you and your buddy right next to each other on the map, each with your own starting area.
oarc-mod-faq-regrowth=What are the regrowth and world eater features?
oarc-mod-faq-regrowth-answer="Regrowth" helps keep the save file size down by removing inactive map chunks over time. This is useful for long-running servers with many players. No chunks with player activity will be removed. "World Eater" slowly checks every chunk to see if there are any player structures in it. This is an additional check on top of the regrowth feature that will catch any chunks that players built on, but then later removed all structures from.
oarc-mod-faq-cleanup-abandoned=What is the cleanup abandoned bases feature?
oarc-mod-faq-cleanup-abandoned-answer=This removes the spawn area if the player leaves within the "minimum online time" and will also delete their character. This feature is useful for keeping the map clean and the save file size down. Public servers likely want to enable this feature.
oarc-mod-faq-offline-protection=What is the offline protection feature?
oarc-mod-faq-offline-protection-answer=If a base has all players offline, it will be protected from enemy attacks. This is not 100% guaranteed, but it will help prevent the base from being destroyed while players are offline.
oarc-mod-faq-shared-power=What is the shared power feature?
oarc-mod-faq-shared-power-answer=This allows players to share their power network with other players. It will create a special power pole at their spawn point that connects to the shared power network wirelessly. It utilizes cross-surface power poles to make "wireless" connections.
oarc-mod-faq-shared-chest=What is the shared chest feature?
oarc-mod-faq-shared-chest-answer=This gives players a special chest at their spawn area that they can use to share items with other players. It uses the "linked-chest" item that is a hidden feature in Factorio. This allows players to share items with other players in a secure way.
[entity-name]
oarc-linked-chest=OARC Linked Chest
oarc-linked-power=OARC Linked Power

View File

@ -1,113 +0,0 @@
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 ...

View File

@ -1,117 +0,0 @@
scenario-name=Oarc多人游戏场景。
description=该场景允许每个玩家在加入游戏时创建自己的生成点。这里有许多便利功能,确保新玩家可以随时加入游戏或加入其他玩家的队伍。这是一个仅限多人游戏的场景。\n[color=red][font=default-bold]首先您必须创建一个config.lua文件,如果还没有的话![/font][/color]\n在场景文件夹内,复制"example-config.lua"并将其命名为"config.lua"。用任何文本编辑器编辑该文件。这是唯一配置场景选项的方法,必须在开始新游戏之前完成。\n[color=yellow][font=default-bold]更改配置或更新场景时,请始终开始新游戏![/font][/color]\n请访问https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn获取场景源代码、Wiki以及提交任何错误报告。Wiki上有关于如何在专用/无头服务器上最佳开始游戏的说明。\n[color=yellow][font=default-bold]如果你是从模组门户或通过加入服务器得到这个场景的,确保在场景文件夹中删除任何blueprint.zip文件![/font][/color]
oarc-spawn-time-warning-msg=由于这个场景的运作方式,你新出生点周围的土地可能需要一些时间来生成...当你选择你的第一个生成点时,请等待10-20秒。
oarc-i-understand=我明白了
oarc-spawn-options=生成选项
oarc-click-info-btn-help=点击左上角的信息按钮以了解更多关于这个场景的信息!这是你唯一选择生成选项的机会,请慎重选择...
oarc-vanilla-spawn=原版生成
oarc-default-spawn-behavior=这是原版游戏的默认生成行为。你将加入地图中心的默认队伍。
oarc-join-main-team-radio=加入主队(共享科技)
oarc-create-own-team-radio=创建你自己的队伍(独立科技树)
oarc-moat-option=用护城河包围你的出生点
oarc-solo-spawn-near=单人出生点(近)
oarc-solo-spawn-far=单人出生点(远)
oarc-starting-area-vanilla=你将在自己的起始区域生成(原版风格)。
oarc-vanilla-spawns-available=目前有 __1__ 个原版生成点可用。
oarc-starting-area-normal=你将在一个新出生点出生。
oarc-join-someone-avail=加入他人(有 __1__ 个可用)
oarc-join-someone-info=你将在别人的基地出生。这需要至少有1个人开放了他们的基地。这个选择是最终的,你将无法再次创建自己的基地。
oarc-no-shared-avail=当前没有可用的共享基地。
oarc-join-check-again=再次检查
oarc-shared-spawn-disabled=本模式中禁用了共享生成。
oarc-buddy-spawn=和好友一起出生
oarc-buddy-spawn-info=好友系统需要两个玩家同时在这个菜单中,你们将彼此并肩生成,各自拥有自己的资源。
oarc-max-players-shared-spawn=如果你创建自己的出生基地,则最多可以允许多达 __1__ 名其他在线玩家加入。
oarc-spawn-dist-notes=近距离生成距离地图中心 __1__-__2__ 块。\n远距离生成距离地图中心 __3__-__4__ 块。\n单人出生有危险!要开荒到达其他玩家的地方,请做好准备。
oarc-player-is-joining-main-force=__ 1__正在加入主要团队!
oarc-player-is-joining-near=__1__ 从远处加入了游戏!
oarc-player-is-joining-far=___1__ 从很远的地方加入游戏!
oarc-please-wait=请稍候,正在生成您的出生点!
oarc-looking-for-buddy=__1__正在寻找队友。
oarc-avail-bases-join=可加入的基地:
oarc-spawn-spots-remaining=__1__(还剩 __2__ 个名额)
oarc-cancel-return-to-previous=取消(返回到之前的选项)
oarc-player-requesting-join-you=__1__ 请求加入你的基地!
oarc-waiting-for-spawn-owner=等待基地拥有者的回应...
oarc-you-will-spawn-once-host=一旦对方大佬选择是,你便会加入...
oarc-player-cancel-join-request=__1__ 取消了他加入你基地的请求。
oarc-spawn-ctrl=生成控制
oarc-spawn-controls=生成控制选项:
oarc-spawn-allow-joiners=允许其他人加入你的基地。
oarc-set-respawn-loc=设置新的重生点(有冷却时间)
oarc-set-respawn-loc-cooldown=重生点冷却剩余时间:__1__
oarc-set-respawn-note=这将把你的重生点设置为你当前的位置。
oarc-select-player-join-queue=从申请队列中选择一个玩家:
oarc-accept=欣然接受
oarc-reject=残忍拒绝
oarc-no-player-join-reqs=目前没有玩家请求加入你的基地。
oarc-start-shared-base=新玩家现在可以加入 __1__的基地!
oarc-stop-shared-base=新玩家不能再加入 __1__ 的基地!
oarc-spawn-point-updated=重生点已更新!
oarc-selected-player-not-wait=选中的玩家不再等待加入。
oarc-reject-joiner=您已拒绝 __1__ 加入您的基地的请求。
oarc-your-request-rejected=您的加入请求被拒绝了。
oarc-player-joining-base=__1__ 正在加入 __2__ 的基地!
oarc-player-left-while-joining=__1__ 退出了游戏,真是无情。
oarc-buddy-spawn-options=好友生成选项
oarc-buddy-spawn-instructions=要使用此功能,请确保您和您的好友同时处于此菜单中。只需其中一人发送请求即可。从列表中选择你的好友(如果看不到好友的名字,请刷新),然后选择您的出生选项。单击请求按钮发送请求。然后,另一个伙伴可以接受(或拒绝)该请求。这将使你们俩都能在彼此旁边生成,每人都有自己的出生点。一旦伙伴接受了生成请求,便不可更改!
oarc-buddy-select-info=首先,从待选列表中选择一个好友。然后选择生成选项并发送您的请求:
oarc-buddy-refresh=刷新好友列表
oarc-create-buddy-team=创建你自己的好友团队 (好友和你共享研究)
oarc-buddy-spawn-near=请求和好友出生 (近)
oarc-buddy-spawn-far=请求和好友出生 (远)
oarc-invalid-buddy=你尚未选择有效的好友!请再试一遍。
oarc-buddy-not-avail=已选择的好友不再可用!请再试一遍。
oarc-waiting-for-buddy=正在等待好友响应...
oarc-wait-buddy-select-yes=一旦您的好友选择是,您将会出生…
oarc-buddy-cancel-request=__1__ 取消了其好友请求!
oarc-buddy-requesting-from-you=__1__ 正在向你请求好友生成!
oarc-buddy-txt-main-team=默认团队
oarc-buddy-txt-new-teams=独立团队
oarc-buddy-txt-buddy-team=好友团队
oarc-buddy-txt-moat=被护城河环绕
oarc-buddy-txt-near=靠近地图中心!
oarc-buddy-txt-far=远离地图中心!
oarc-buddy-txt-would-like=希望加入
oarc-buddy-txt-next-to-you=在您旁边
oarc-buddy-declined=__1__ 拒绝了你的好友请求!
oarc-spawn-wait=请稍候!
oarc-wait-text=您的出生点正在创建中。\n您将在 __1__ 秒内被传送到那里!\n请稍候…

View File

@ -0,0 +1,46 @@
-- To edit this scenario, you must make a copy of it and place it in your own scenarios folder first!
-- Do not edit the scenario provided by the mod install directly!
-- I provide this empty scenario to avoid the freeplay scenario extra baggage.
-- You can use the freeplay scenario too just fine if you want.
-- The main benefit of the scenario is that it lets you modify the any of the config during on_init of the mod.
-- 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 lose it if you update the
-- scenario. I will try to avoid making breaking changes to this, but no guarantees.
-- To see what settings are available, look at the config_mod.lua file in the mod folder.
-- Check if the OARC mod is loaded. Other than that, it's an empty scenario!
script.on_init(function(event)
if not game.active_mods["oarc-mod"] then
error("OARC mod not found! This scenario is intended to be run with the OARC mod!")
end
end)
local oarc_scenario_interface =
{
get_scenario_settings = function()
---@type OarcConfig
local modified_settings = remote.call("oarc_mod", "get_mod_settings")
-- Overwrite whatever settings you want here:
-- If you provide an invalid value for a mod setting, it will error and not load the scenario.
----------------------------------------------------------------------------------------------------------------
modified_settings.server_info.welcome_msg_title = "THIS IS A TEMPLATE SCENARIO"
modified_settings.server_info.welcome_msg = "This is a template scenario. You can modify the settings in the control.lua file. If you are seeing this message, you did not modify the scenario correctly."
modified_settings.spawn_general.shape = "circle"
-- Some examples of overriding surface config (which is not accessible from the mod settings!)
modified_settings.surfaces_config["nauvis"].starting_items.player_start_items = {
["coal"] = 1, -- You're on the naughty list!
}
----------------------------------------------------------------------------------------------------------------
return modified_settings
end
}
remote.add_interface("oarc_scenario", oarc_scenario_interface)

View File

Before

(image error) Size: 263 KiB

After

(image error) Size: 263 KiB

View File

@ -0,0 +1,3 @@
scenario-name=OARC-TEMPLATE
description=This is an EMPTY TEMPLATE scenario! It is provided as an example of how to override the default freeplay scenario if you want to. Additionally, experienced server hosts can configure all settings from a file instead of through the mod settings. Please read the control.lua file inside the scenario for notes. [color=red]To edit this scenario, you must make a copy of it and place it in your own scenarios folder first. Do not edit this scenario directly.[/color]

403
settings.lua Normal file
View File

@ -0,0 +1,403 @@
data:extend({
{
type = "bool-setting",
name = "oarc-mod-default-allow-spawning-on-other-surfaces",
setting_type = "startup",
default_value = false,
order = "a1"
},
{
type = "string-setting",
name = "oarc-mod-main-force-name",
setting_type = "startup",
default_value = "Main Force",
order = "a2"
},
{
type = "int-setting",
name = "oarc-mod-linked-chest-size",
setting_type = "startup",
default_value = 100,
minimum_value = 1,
maximum_value = 1000,
order = "a3"
},
{
type = "string-setting",
name = "oarc-mod-welcome-msg-title",
setting_type = "runtime-global",
default_value = "Insert Server Title Here!",
order = "a1"
},
{
type = "string-setting",
name = "oarc-mod-welcome-msg",
setting_type = "runtime-global",
default_value = "Insert Server Welcome Message Here!",
order = "a2"
},
{
type = "string-setting",
name = "oarc-mod-discord-invite",
setting_type = "runtime-global",
default_value = "Insert Discord Invite Here!",
order = "a4"
},
{
type = "bool-setting",
name = "oarc-mod-enable-main-team",
setting_type = "runtime-global",
default_value = true,
order = "b1"
},
{
type = "bool-setting",
name = "oarc-mod-enable-separate-teams",
setting_type = "runtime-global",
default_value = true,
order = "b2"
},
{
type = "bool-setting",
name = "oarc-mod-allow-moats-around-spawns",
setting_type = "runtime-global",
default_value = true,
order = "b4"
},
{
type = "bool-setting",
name = "oarc-mod-enable-moat-bridging",
setting_type = "runtime-global",
default_value = false,
order = "b5"
},
{
type = "int-setting",
name = "oarc-mod-minimum-distance-to-existing-chunks",
setting_type = "runtime-global",
default_value = 10,
minimum_value = 5,
maximum_value = 25,
order = "c1"
},
{
type = "int-setting",
name = "oarc-mod-near-spawn-distance",
setting_type = "runtime-global",
default_value = 100,
minimum_value = 50,
maximum_value = 250,
order = "c2"
},
{
type = "int-setting",
name = "oarc-mod-far-spawn-distance",
setting_type = "runtime-global",
default_value = 500,
minimum_value = 250,
maximum_value = 5000,
order = "c3"
},
{
type = "bool-setting",
name = "oarc-mod-enable-buddy-spawn",
setting_type = "runtime-global",
default_value = true,
order = "d1"
},
{
type = "bool-setting",
name = "oarc-mod-enable-offline-protection",
setting_type = "runtime-global",
default_value = true,
order = "d2"
},
{
type = "bool-setting",
name = "oarc-mod-enable-shared-team-vision",
setting_type = "runtime-global",
default_value = true,
order = "d3"
},
{
type = "bool-setting",
name = "oarc-mod-enable-shared-team-chat",
setting_type = "runtime-global",
default_value = true,
order = "d4"
},
{
type = "bool-setting",
name = "oarc-mod-enable-shared-spawns",
setting_type = "runtime-global",
default_value = true,
order = "d5"
},
{
type = "int-setting",
name = "oarc-mod-number-of-players-per-shared-spawn",
setting_type = "runtime-global",
default_value = 3,
minimum_value = 2,
maximum_value = 10,
order = "d6"
},
{
type = "bool-setting",
name = "oarc-mod-enable-friendly-fire",
setting_type = "runtime-global",
default_value = false,
order = "d7"
},
{
type = "string-setting",
name = "oarc-mod-default-surface",
setting_type = "runtime-global",
default_value = "nauvis",
order = "e2"
},
{
type = "bool-setting",
name = "oarc-mod-enable-secondary-spawns",
setting_type = "runtime-global",
default_value = false,
order = "e3"
},
{
type = "bool-setting",
name = "oarc-mod-scale-resources-around-spawns",
setting_type = "runtime-global",
default_value = true,
order = "f1"
},
{
type = "bool-setting",
name = "oarc-mod-modified-enemy-spawning",
setting_type = "runtime-global",
default_value = true,
order = "f2"
},
{
type = "double-setting",
name = "oarc-mod-modified-enemy-easy-evo",
setting_type = "runtime-global",
default_value = 0,
minimum_value = 0,
maximum_value = 1,
order = "f21"
},
{
type = "double-setting",
name = "oarc-mod-modified-enemy-medium-evo",
setting_type = "runtime-global",
default_value = 0.3,
minimum_value = 0,
maximum_value = 1,
order = "f22"
},
{
type = "int-setting",
name = "oarc-mod-minimum-online-time",
setting_type = "runtime-global",
default_value = 15,
minimum_value = 0,
maximum_value = 60,
order = "f3"
},
{
type = "int-setting",
name = "oarc-mod-respawn-cooldown-min",
setting_type = "runtime-global",
default_value = 5,
minimum_value = 0,
maximum_value = 60,
order = "f4"
},
{
type = "bool-setting",
name = "oarc-mod-enable-shared-power",
setting_type = "runtime-global",
default_value = false,
order = "f10"
},
{
type = "bool-setting",
name = "oarc-mod-enable-shared-chest",
setting_type = "runtime-global",
default_value = false,
order = "f11"
},
{
type = "bool-setting",
name = "oarc-mod-enable-regrowth",
setting_type = "runtime-global",
default_value = false,
order = "g1"
},
{
type = "bool-setting",
name = "oarc-mod-enable-world-eater",
setting_type = "runtime-global",
default_value = false,
order = "g2"
},
{
type = "bool-setting",
name = "oarc-mod-enable-abandoned-base-cleanup",
setting_type = "runtime-global",
default_value = true,
order = "g3"
},
{
type = "int-setting",
name = "oarc-mod-regrowth-cleanup-interval-min",
setting_type = "runtime-global",
default_value = 60,
minimum_value = 15,
maximum_value = 180,
order = "g4"
},
{
type = "int-setting",
name = "oarc-mod-spawn-general-radius-tiles",
setting_type = "runtime-global",
default_value = 64,
minimum_value = 32,
maximum_value = 320,
order = "h1"
},
{
type = "int-setting",
name = "oarc-mod-spawn-general-moat-width-tiles",
setting_type = "runtime-global",
default_value = 8,
minimum_value = 1,
maximum_value = 32,
order = "h2"
},
{
type = "int-setting",
name = "oarc-mod-spawn-general-tree-width-tiles",
setting_type = "runtime-global",
default_value = 5,
minimum_value = 1,
maximum_value = 32,
order = "h3"
},
{
type = "string-setting",
name = "oarc-mod-spawn-general-enable-resources-circle-shape",
setting_type = "runtime-global",
default_value = "circle",
allowed_values = {"circle", "square"},
order = "h4"
},
{
type = "bool-setting",
name = "oarc-mod-spawn-general-enable-force-grass",
setting_type = "runtime-global",
default_value = false,
order = "h5"
}, {
type = "string-setting",
name = "oarc-mod-spawn-general-shape",
setting_type = "runtime-global",
default_value = "circle",
allowed_values = {"circle", "octagon", "square"},
order = "h6"
},
{
type = "bool-setting",
name = "oarc-mod-resource-placement-enabled",
setting_type = "runtime-global",
default_value = true,
order = "i1"
},
{
type = "int-setting",
name = "oarc-mod-resource-placement-distance-to-edge",
setting_type = "runtime-global",
default_value = 20,
minimum_value = 0,
maximum_value = 96,
order = "i2"
},
{
type = "double-setting",
name = "oarc-mod-resource-placement-angle-offset",
setting_type = "runtime-global",
default_value = 2.32,
minimum_value = 0,
maximum_value = 6.28,
order = "i3"
},
{
type = "double-setting",
name = "oarc-mod-resource-placement-angle-final",
setting_type = "runtime-global",
default_value = 4.46,
minimum_value = 0,
maximum_value = 6.28,
order = "i4"
},
{
type = "int-setting",
name = "oarc-mod-resource-placement-vertical-offset",
setting_type = "runtime-global",
default_value = 20,
minimum_value = 0,
maximum_value = 96,
order = "i5"
},
{
type = "int-setting",
name = "oarc-mod-resource-placement-horizontal-offset",
setting_type = "runtime-global",
default_value = 20,
minimum_value = 0,
maximum_value = 96,
order = "i6"
},
{
type = "int-setting",
name = "oarc-mod-resource-placement-linear-spacing",
setting_type = "runtime-global",
default_value = 6,
minimum_value = 0,
maximum_value = 32,
order = "i7"
},
{
type = "double-setting",
name = "oarc-mod-resource-placement-size-multiplier",
setting_type = "runtime-global",
default_value = 1.0,
minimum_value = 0,
maximum_value = 10,
order = "i8"
},
{
type = "double-setting",
name = "oarc-mod-resource-placement-amount-multiplier",
setting_type = "runtime-global",
default_value = 1.0,
minimum_value = 0,
maximum_value = 10,
order = "i9"
},
})

BIN
thumbnail.png Normal file

Binary file not shown.

After

(image error) Size: 464 KiB