Исправлен мод heroturrets

Добавлен мод blueprint-sandboxes
Добавляет метку версии в zzzparanoidal (#72)
This commit is contained in:
Aleksei-bird 2024-08-16 10:59:02 +03:00
parent b5ed31105c
commit 46d69f3a45
56 changed files with 4537 additions and 3 deletions

View File

@ -0,0 +1,77 @@
## Blueprint Sandboxes
Temporary Editor-lite permissions in Lab-like environments for designing and experimenting.
Inspired by previous mods such as [Edit-Blueprints](https://mods.factorio.com/mod/Edit-Blueprints) and [Blueprint Designer Lab](https://mods.factorio.com/mod/BlueprintLab_design), [this mod](https://mods.factorio.com/mod/blueprint-sandboxes) aims to handle the situations where you want to design or tweak some Blueprints in a God-mode-like way, but without saving your active game, loading a different sandbox game, then leaving that to go back to the original once done.
To that end, it supports personal and team Sandbox Surfaces which enable: God-mode, extra Recipes (and Technologies if you wish), and automated construction. Getting in and out of Sandboxes is immediate and toggle-able via shortcuts (defaults to Shift+B).
To teach you the basics and provide many more details, the in-game Tips-and-Tricks are used; the first is visible after a few seconds, and the rest after you start using the Sandbox. The rest of this is considered a non-exhaustive summary - if you want to know more, see those Tips/Tricks!
* Multiple Sandboxes: your own and one for your force/team. If you're using Space Exploration, there's also Planetary and Orbital Sandboxes.
* Blueprint Intput/Output: Copy/Paste, Blueprint Library, and in-Cursor.
* Item Input/Output: Infinity chests and loaders are available.
* God-mode: Fly around and construct/deconstruct much faster.
* Persistent Inventory: Your Inventory is saved and restored when exiting/entering.
* Automated Construction: Ghosts are automatically built for you.
* All Recipes: If desired, use all Technology (instead of what you already know).
* Resource Generation: Draw then use any kind of Resource Patch.
* Water placement: Place water, so you can then landfill it (or not).
* Default Equipment: You can decide what an empty Sandbox starts with.
# FAQ
* Pollution counts towards Evolution.
* Production Statistics cannot be segregated.
* Crafting Counts cannot be segregated - this does not work for Lazy Bastard.
* You can die in the real world while in a Sandbox.
* When Resetting the Sandbox and the game crashes with any other mod listed in the error - it's _that_ mod's fault for not handling `on_pre_surface_cleared`.
# Known Issues
### Cannot Undo in (Real World/Sandbox) after coming from (Sandbox/Real World)
This is an issue with Factorio, and there's nothing this mod can do about it (while still being this mod).
### Space Exploration Sandboxes report incorrect Daylight on their first use
This also seems to be an issue with Factorio. Although the Daylight property is forcefully set and told to not change, the next read of the value will be zero (or perhaps what the value originally was). It's purely a cosmetic bug.
### Space Exploration sometimes blocks placements due to Zone/Force issues
In its original form, this mod had great compatibility with Space Exploration - from this mod's POV. From SE's POV, however, at least two things were bad. First, it's technically possible to cheat (well, it _always_ is, since you're in control of the games you play), even though this mod discourages it and aims to prevent it. Second, SE's handling of Forces had some assumptions that were not true when using this mod. As such, there were a few bugs, like having twice as many CMEs as normal.
Recently, SE became "aware" of this mod and have made changes to _prevent_ placement of some important SE Entities. Around the same time, this mod introduced "Illusions" to safely swap out scripted Entities with basic placeholders.
At this time, _most_ of those Entities cannot _normally_ be placed within the Sandboxes - you will get an error from SE instead. They _can_ be Ghost-built (holding shift, or using a Blueprint), in _most_ cases. This is because SE does not check the types of the Ghosts, and this mod can safely replace them with Illusions. For the real Entities, SE blocks the placement _before_ this mod can do anything about it.
I've reached out to their team to improve the compatibility, but nothing came of it.
### Selecting new contents for some Blueprints will include Illusions instead of Real Entities
There is a significant flaw in Factorio's handling of Blueprints that have already been created when you want to "select new contents" for them; to quote a Factorio dev, it's "kind of a giant hack in my opinion and I don't see it getting re-worked any time soon." This is the only real acknowledgement of this issue, whereas all other responses seem to deflect or feign ignorance. As far as I have found, this is the only (and for our purposes, quite a large) shortcoming of the otherwise excellent Modding API.
In short, this mod has _no_ access or capability to adjust a Blueprint when you are "selecting new contents." This capability is necessary to swap our Fake Illusions (script-less Entities that replace other, more complicated ones for various reasons) with their Real Counterparts. This cannot be overcome without Factorio itself being fixed by the development team. That said, there is _potentially_ a hackish and unnecessary workaround when you do this to a Blueprint in your Inventory.
I have found at least three existing discussions on this topic, for reference:
* [New contents for blueprint broken vs. new blueprint](https://forums.factorio.com/viewtopic.php?f=29&t=88793)
* [Blueprints missing entity list when reused](https://forums.factorio.com/viewtopic.php?f=7&t=99323)
* [Updated blueprint has no entities during on_player_setup_blueprint](https://forums.factorio.com/viewtopic.php?f=48&t=88100)
### Blueprint Library sourced Blueprints will not transfer via Cursor
Similar to above, another Factorio bug describes Blueprints in your cursor that are sourced from the Blueprint Library will be described as __not__ `valid_for_read`, thus accessing their contents is not possible, so this mod cannot transfer them into your Sandbox cursor because of that.
I have found at least three existing discussions on this topic, for reference:
* [How to access temporary BP in player's hand?](https://test.forums.factorio.com/viewtopic.php?t=93956)
* [Updated blueprint has no entities during on_player_setup_blueprint](https://forums.factorio.com/viewtopic.php?f=48&t=88100)
* [get blueprint-book from library link](https://test.forums.factorio.com/viewtopic.php?t=95272)
### Editor Extensions Lab Setting is incompatible
When Editor Extensions is enabled, its Lab Setting is disabled because it is incompatible with this mod.
## Credits
* undermark5: Factorissimo Performance Improvements

View File

@ -0,0 +1,310 @@
---------------------------------------------------------------------------------------------------
Version: 1.17.1
Date: 2024-01-09
Changes:
- Neutral Entities in Sandboxes are now considered the same as those belonging to the Sandbox Force
Bugfixes:
- Items on the ground in Sandboxes were not being destroyed when marked for deconstruction
---------------------------------------------------------------------------------------------------
Version: 1.17.0
Date: 2024-01-03
Changes:
- A Player changing Forces now has their Sandbox Entities assigned to the new Force
---------------------------------------------------------------------------------------------------
Version: 1.16.8
Date: 2023-11-07
Bugfixes:
- Another potential startup crash from last release
---------------------------------------------------------------------------------------------------
Version: 1.16.7
Date: 2023-11-07
Bugfixes:
- Startup crash from last release
---------------------------------------------------------------------------------------------------
Version: 1.16.6
Date: 2023-11-06
Changes:
- Editor Extensions' Lab setting is forcefully disabled due to compatibility issues
Bugfixes:
- Updated support for Editor Extensions' Recipes in the Sandbox
---------------------------------------------------------------------------------------------------
Version: 1.16.5
Date: 2023-10-31
Changes:
- Support for ignoring Smarter Inserter's temporary Entities
---------------------------------------------------------------------------------------------------
Version: 1.16.4
Date: 2023-07-30
Changes:
- Necessary updates for 1.16.0 and Factorio 1.1.87: large refactor of Equipment Blueprint placement
---------------------------------------------------------------------------------------------------
Version: 1.16.3
Date: 2023-07-08
Bugfixes:
- Regression fix when leaving a Lab into a non-Character controller
---------------------------------------------------------------------------------------------------
Version: 1.16.2
Date: 2023-07-02
Bugfixes:
- Crash when deleting Labs
---------------------------------------------------------------------------------------------------
Version: 1.16.1
Date: 2023-06-15
Bugfixes:
- Potential crashes and incorrect Sandbox surface generation when paired with mods such as RSO caused by 1.15
---------------------------------------------------------------------------------------------------
Version: 1.16.0
Date: 2023-06-10
Changes:
- Illusion replacements of Container-based Entities are now Infinity Containers
Bugfixes:
- Some much older versions of this mod would crash in a rare scenario when loading a recent version
---------------------------------------------------------------------------------------------------
Version: 1.15.0
Date: 2023-06-02
Changes:
- Blueprints can be provided as the default Equipment to be used when Resetting each Sandbox
Bugfixes:
- script_raised_built is now handled like on_built_entity
---------------------------------------------------------------------------------------------------
Version: 1.14.0
Date: 2023-05-30
Changes:
- Blueprints can be brought into and out of the Sandbox via the Cursor (in most cases)
---------------------------------------------------------------------------------------------------
Version: 1.13.0
Date: 2023-05-28
Changes:
- Lab speed within Sandboxes is now a setting
---------------------------------------------------------------------------------------------------
Version: 1.12.2
Date: 2023-04-30
Changes:
- Large performance improvements when paired with Factorissimo2, courtesy of undermark5
---------------------------------------------------------------------------------------------------
Version: 1.12.1
Date: 2023-01-07
Changes:
- Adding a warning for selecting new contents for a Blueprint in your Blueprint Library, since Factorio does not allow mods to handle this situation
Bugfixes:
- Selecting new contents for a Blueprint in your Inventory was not always replacing Illusions with Real Entities
---------------------------------------------------------------------------------------------------
Version: 1.12.0
Date: 2023-01-04
Changes:
- The "Extra Mining Speed" applied in Sandboxes is now a Runtime Setting
---------------------------------------------------------------------------------------------------
Version: 1.11.5
Date: 2023-01-03
Bugfixes:
- Tapelines were sometimes auto-built which prevents the intended use
---------------------------------------------------------------------------------------------------
Version: 1.11.4
Date: 2023-01-02
Bugfixes:
- Logistic Train Stop Inputs/Outputs were sometimes auto-built resulting in circuit network issues
---------------------------------------------------------------------------------------------------
Version: 1.11.3
Date: 2022-12-30
Bugfixes:
- Crash after Space Exploration deletes a Surface that was used by this mod
- Crash after a Space Exploration update from 0.5 to 0.6 and certain Entities were used inside a Sandbox
---------------------------------------------------------------------------------------------------
Version: 1.11.2
Date: 2022-11-22
Changes:
- Illusions no longer appear in the "Made in" tooltips
Bugfixes:
- Crash when Ghost-building an Illusion over an Illusion
---------------------------------------------------------------------------------------------------
Version: 1.11.1
Date: 2022-11-09
Changes:
- dangOreus will ignore any created Sandbox Surfaces
---------------------------------------------------------------------------------------------------
Version: 1.11.0
Date: 2022-10-31
Changes:
- New Illusions framework: Entities that are swapped to cardboard-cutouts inside of Sandboxes to avoid issues or cheating
- Nearly all script-driven Space Exploration Entities are now Illusions in the Sandboxes
---------------------------------------------------------------------------------------------------
Version: 1.10.1
Date: 2022-09-04
Changes:
- Moons within the Calidus system and Moons with Threats are no longer chosen for Planetary Sandboxes. Existing Planetary Sandboxes fitting this criteria will be destroyed
- Performance improvements related to some cases of creating Entities with the Factorissimo integration
Bugfixes:
- Crash when something other than this mod lets the Planer enter then leave a Sandbox
---------------------------------------------------------------------------------------------------
Version: 1.10.0
Date: 2022-08-23
Changes:
- Asynchronous Sandbox Operations are now maintained in an internal Queue
- Factorissimo Factories within Sandboxes are treated similarly to Sandboxes
Bugfixes:
- Crash with a combination of entering/existing Factorissimo Factories within Sandboxes
- Crash after renaming or deleting certain Surfaces in the middle of asynchronous operations
---------------------------------------------------------------------------------------------------
Version: 1.9.3
Date: 2022-08-08
Bugfixes:
- Adding Mods that alter Research would not be accounted for in the Sandbox
---------------------------------------------------------------------------------------------------
Version: 1.9.2
Date: 2022-07-29
Bugfixes:
- Non-Admins could not set Filters on Infinity Chests/Pipes
---------------------------------------------------------------------------------------------------
Version: 1.9.0
Date: 2022-07-23
Changes:
- Proxy chats between Players inside Sandboxes and those outside of Sandboxes
Bugfixes:
- Space Exploration Core Seams are not exposed as Resource Planners in <= 0.5
---------------------------------------------------------------------------------------------------
Version: 1.8.3
Date: 2022-07-23
Changes:
- Space Exploration Core Seams are now exposed as Resource Planners
- Creations in Sandboxes are no longer async by default, since Factorio 1.1.61
Bugfixes:
- Crash with 1.6.x versions of Space Exploration
---------------------------------------------------------------------------------------------------
Version: 1.8.2
Date: 2022-06-24
Bugfixes:
- Crash when combined with other mods that remove some Autoplace Controls from Nauvis
---------------------------------------------------------------------------------------------------
Version: 1.8.1
Date: 2022-06-16
Bugfixes:
- Crash when starting a new game
---------------------------------------------------------------------------------------------------
Version: 1.8.0
Date: 2022-06-15
Changes:
- New Setting to prevent non-Admins from Resetting Force-wide Sandboxes in Multiplayer games
---------------------------------------------------------------------------------------------------
Version: 1.7.4
Date: 2022-06-13
Bugfixes:
- The migration for 1.7.3 wasn't always applied
- The Unlock-all-Technology setting was sometimes reverting gained bonuses
---------------------------------------------------------------------------------------------------
Version: 1.7.3
Date: 2022-05-30
Bugfixes:
- The Daylight Slider icon was mistakenly sending a Click Event
---------------------------------------------------------------------------------------------------
Version: 1.7.2
Date: 2022-05-29
Bugfixes:
- Crash when adding this mod back to a save after having removed it
---------------------------------------------------------------------------------------------------
Version: 1.7.1
Date: 2022-05-19
Bugfixes:
- The default Resource Planner density is much larger
---------------------------------------------------------------------------------------------------
Version: 1.7.0
Date: 2022-05-17
Changes:
- New Tile Planners for creating Tiles that might not otherwise be possible, only within Sandboxes
- New daylight slider within Sandboxes to alter the daytime on the Sandbox Surface
- Performance improvements related to any built Entities
Bugfixes:
- When paired with Editor Extensions, if Cheat Mode was already enabled outside of the Sandboxes, it would not be "re-enabled" within the Sandboxes, preventing the use of the additional Recipes within Sandboxes
---------------------------------------------------------------------------------------------------
Version: 1.6.1
Date: 2022-04-25
Changes:
- Resource Planners consider the Map Generator's Richness and Resource-specific values when determining how much to place
- Resource Planners consider the Bounding Box of the Resource when determining how often to place
---------------------------------------------------------------------------------------------------
Version: 1.6.0
Date: 2022-05-03
Changes:
- Last-known-Positions within Sandboxes are persisted so that they can be returned to when swapping between Sandboxes and other Surfaces
- Cheat-mode is toggled between the previous state and true, instead of reverting back to false
- Players that are toggled into the Map Editor can no longer enter a Sandbox (otherwise their original Character would be lost)
- Non-Character Inventories are persisted so that they can be restored when exiting a Sandbox
Bugfixes:
- Removed Players wouldn't clean up their Persistent Inventories
---------------------------------------------------------------------------------------------------
Version: 1.5.1
Date: 2022-04-25
Changes:
- Craft-to-Cursor is smarter about exactly which Item ends up in the Cursor for Recipes with multiple Products
Bugfixes:
- Crafting a Recipe in a Sandbox that produces a Liquid would crash
---------------------------------------------------------------------------------------------------
Version: 1.5.0
Date: 2022-04-21
Changes:
- Bonus Inventory Slots for Sandboxes Setting
- Prevent Sandbox Inventories from becoming full by spilling their last slot
- Provide a "Trash can" in new Sandboxes
---------------------------------------------------------------------------------------------------
Version: 1.4.2
Date: 2022-04-18
Bugfixes:
- Ghosting certain Entities with Modules into a Space-based Sandbox could crash
---------------------------------------------------------------------------------------------------
Version: 1.4.1
Date: 2022-04-17
Bugfixes:
- Level-based Research wasn't correctly synchronized
---------------------------------------------------------------------------------------------------
Version: 1.4.0
Date: 2022-04-16
Changes:
- Loaders will only be "owned" by this Mod if no other Mod has already activated them in the data stage
- More Item Requests can be automatically handled within Sandboxes
Bugfixes:
- Entering a Sandbox with the Editor Extensions Mod loaded would give the Player too many capabilities outside of the Sandbox (but not within)
---------------------------------------------------------------------------------------------------
Version: 1.3.0
Date: 2022-04-15
Changes:
- Craft-to-Cursor is now a Player Mod Setting
---------------------------------------------------------------------------------------------------
Version: 1.2.0
Date: 2022-04-12
Changes:
- The Research Queue for Sandboxes mimics the real Research Queue
---------------------------------------------------------------------------------------------------
Version: 1.1.1
Date: 2022-04-10
Bugfixes:
- New games beginning with a Cutscene would crash
---------------------------------------------------------------------------------------------------
Version: 1.1.0
Date: 2022-04-08
Changes:
- The Inventory within Sandboxes is now persisted
---------------------------------------------------------------------------------------------------
Version: 1.0.3
Date: 2022-04-05
Changes:
- The Space Exploration Planetary Lab now uses a Moon instead of a Star
- Existing Space Exploration Planetary Labs will be deleted
Bugfixes:
- Passengers in Rockets that are launched while the Player is inside a Sandbox would crash
- Various fixes related to the Space Exploration Planetary Lab
---------------------------------------------------------------------------------------------------
Version: 1.0.2
Date: 2022-04-03
Bugfixes:
- The Sandbox Force could have become out-of-sync with its original Force
---------------------------------------------------------------------------------------------------
Version: 1.0.1
Date: 2022-04-02
Bugfixes:
- Placing Blueprints with Tiles in a Planetary Lab would revert the Tiles to Empty Space
---------------------------------------------------------------------------------------------------
Version: 1.0.0
Date: 2022-03-29
Changes:
- Initial Release
Bugfixes:
- None

View File

@ -0,0 +1,237 @@
-- Required by basically everything immediately
BPSB = require("scripts.bpsb")
Events = require("scripts.events")
Settings = require("scripts.settings")
Debug = require("scripts.debug")
Queue = require("scripts.queue")
-- Required by some
Illusion = require("scripts.illusion")
EditorExtensionsCheats = require("scripts.editor-extensions-cheats")
-- Required, but not ordered importantly
Init = require("scripts.init")
Chat = require("scripts.chat")
Equipment = require("scripts.equipment")
Factorissimo = require("scripts.factorissimo")
Force = require("scripts.force")
God = require("scripts.god")
Inventory = require("scripts.inventory")
Lab = require("scripts.lab")
Migrate = require("scripts.migrate")
Research = require("scripts.research")
Resources = require("scripts.resources")
Tiles = require("scripts.tiles")
ToggleGUI = require("scripts.toggle-gui")
-- Required by Sandbox
SpaceExploration = require("scripts.space-exploration")
-- Requires SpaceExploration method immediately
Sandbox = require("scripts.sandbox")
require('scripts.remote-interface')
-- Initializations
script.on_init(function()
Init.FirstTimeInit()
Settings.SetupConditionalHandlers()
end)
script.on_event(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
log("on_player_created index: " .. event.player_index)
Force.Init(player.force)
Init.Player(player)
end)
script.on_event(defines.events.on_player_removed, function(event)
local playerData = global.players[event.player_index]
log("on_player_removed index: " .. event.player_index)
Lab.DeleteLab(playerData.labName)
if playerData.sandboxInventory then
playerData.sandboxInventory.destroy()
end
if playerData.preSandboxInventory then
playerData.preSandboxInventory.destroy()
end
global.players[event.player_index] = nil
end)
script.on_event(defines.events.on_force_created, function(event)
log("on_force_created name: " .. event.force.name)
Force.Init(event.force)
end)
-- Conditional Event Listeners
script.on_load(Settings.SetupConditionalHandlers)
script.on_event(defines.events.on_runtime_mod_setting_changed, Settings.OnRuntimeSettingChanged)
-- Important Game Events
script.on_configuration_changed(function(event)
for _, sandboxForceData in pairs(global.sandboxForces) do
-- Ensure that new automatic recipes aren't hidden
sandboxForceData.hiddenItemsUnlocked = false
end
--[[ TODO:
There's a bug related to this section if a Player is currently
inside of a Sandbox while loading a Save where the Configuration
Changed. The above hiddenItemsUnlocked flag is reset, since for
some reason the Recipes will have disappeared again, but of
course there's nothing here to enable them again!
That's because the enabling code doesn't seem to work anywhere
outside of right-after the Player has been swapped to their
Sandbox Force (see the to-do over there).
Currently, if that same code is used here, the flag will be true,
but the Recipes are still hidden, so they'll be stuck hidden!
]]
Migrate.Run()
Research.SyncAllForces()
end)
script.on_event(defines.events.on_player_changed_force, function(event)
local player = game.players[event.player_index]
Force.Init(player.force)
Sandbox.OnPlayerForceChanged(player)
end)
script.on_event(defines.events.on_forces_merged, function(event)
Force.Init(event.destination)
Force.Merge(event.source_name, event.destination)
end)
script.on_event(defines.events.on_player_promoted, function(event)
local player = game.players[event.player_index]
ToggleGUI.Update(player)
end)
script.on_event(defines.events.on_player_demoted, function(event)
local player = game.players[event.player_index]
ToggleGUI.Update(player)
end)
script.on_event(defines.events.on_console_chat, Chat.OnChat)
script.on_event(defines.events.on_research_finished, Research.OnResearched)
script.on_event(defines.events.on_research_reversed, Research.OnResearched)
script.on_event(defines.events.on_research_started, Research.OnResearchStarted)
script.on_event(defines.events.on_player_changed_surface, function(event)
local player = game.players[event.player_index]
Sandbox.OnPlayerSurfaceChanged(player)
ToggleGUI.Update(player)
end)
script.on_event(defines.events.on_surface_created, function(event)
local surface = game.surfaces[event.surface_index]
local _ = Lab.AfterCreate(surface) or SpaceExploration.AfterCreate(surface)
local _ = Lab.Equip(surface) or SpaceExploration.Equip(surface)
end)
script.on_event(defines.events.on_surface_cleared, function(event)
return Lab.Equip(game.surfaces[event.surface_index])
or SpaceExploration.Equip(game.surfaces[event.surface_index])
end)
script.on_event(defines.events.on_chunk_generated, function(event)
local equipmentData = global.equipmentInProgress[event.surface.name]
if equipmentData then
Equipment.BuildBlueprint(
equipmentData.stack,
equipmentData.surface,
equipmentData.forceName
)
end
end)
script.on_event(defines.events.on_pre_surface_deleted, function(event)
local surface = game.surfaces[event.surface_index]
if global.labSurfaces[surface.name] then
global.labSurfaces[surface.name] = nil
end
local surfaceData = global.seSurfaces[surface.name]
if surfaceData then
local sandboxForceData = global.sandboxForces[surfaceData.sandboxForceName]
SpaceExploration.PreDeleteSandbox(sandboxForceData, surface.name)
end
end)
script.on_event(defines.events.on_surface_renamed, function(event)
-- TODO: Renaming surfaces likely doesn't really work
if global.labSurfaces[event.old_name] then
global.labSurfaces[event.new_name] = global.labSurfaces[event.old_name]
global.labSurfaces[event.old_name] = nil
end
local surfaceData = global.seSurfaces[event.old_name]
if surfaceData then
local sandboxForceData = global.sandboxForces[surfaceData.sandboxForceName]
if sandboxForceData.sePlanetaryLabZoneName == event.old_name then
sandboxForceData.sePlanetaryLabZoneName = event.new_name
end
if sandboxForceData.seOrbitalSandboxZoneName == event.old_name then
sandboxForceData.seOrbitalSandboxZoneName = event.new_name
end
global.seSurfaces[event.new_name] = global.seSurfaces[event.old_name]
global.seSurfaces[event.old_name] = nil
end
end)
script.on_event(defines.events.on_marked_for_deconstruction, God.OnMarkedForDeconstruct)
script.on_event(defines.events.on_marked_for_upgrade, God.OnMarkedForUpgrade)
script.on_event(defines.events.on_built_entity, function(event)
God.OnBuiltEntity(event.created_entity)
end, God.onBuiltEntityFilters)
script.on_event(defines.events.script_raised_built, function(event)
God.OnBuiltEntity(event.entity)
end, God.onBuiltEntityFilters)
script.on_event(defines.events.on_player_crafted_item, God.OnPlayerCraftedItem)
script.on_event(defines.events.on_player_main_inventory_changed, God.OnInventoryChanged)
script.on_event(defines.events.on_player_setup_blueprint, Illusion.OnBlueprintSetup)
-- TODO: on_entity_settings_pasted
script.on_event(defines.events.on_player_selected_area, function(event)
Resources.OnAreaSelected(event, true)
end)
script.on_event(defines.events.on_player_alt_selected_area, function(event)
Resources.OnAreaSelected(event, false)
end)
-- Internal
script.on_event(Events.on_daylight_changed_event, function(event)
log("on_daylight_changed_event from player: " .. event.player_index)
for _, player in pairs(game.players) do
if player.index ~= event.player_index
and player.surface.name == event.surface_name
then
ToggleGUI.Update(player)
end
end
end)
-- Shortcuts
script.on_event(ToggleGUI.toggleShortcut, ToggleGUI.OnToggleShortcut)
script.on_event(defines.events.on_lua_shortcut, ToggleGUI.OnToggleShortcut)
-- GUI
script.on_event(defines.events.on_gui_click, ToggleGUI.OnGuiClick)
script.on_event(defines.events.on_gui_value_changed, ToggleGUI.OnGuiValueChanged)
script.on_event(defines.events.on_gui_selection_state_changed, ToggleGUI.OnGuiDropdown)
script.on_event(defines.events.on_gui_closed, function(event)
if (event.gui_type == defines.gui_type.blueprint_library) then
-- We know this won't work, but we'll do it to print a message anyway
Illusion.OnBlueprintGUIClosed(event)
elseif (event.gui_type == defines.gui_type.item) then
Illusion.OnBlueprintGUIClosed(event)
end
end)

View File

@ -0,0 +1,6 @@
BPSB = require("scripts.bpsb")
require("prototypes.recipes.hidden-vanilla-final-fixes")
require("prototypes.recipes.resources-final-fixes")
require("prototypes.recipes.tiles-final-fixes")
require("prototypes.illusions-final-fixes")

View File

@ -0,0 +1,3 @@
BPSB = require("scripts.bpsb")
require("prototypes.recipes.hidden-vanilla-updates")

View File

@ -0,0 +1,7 @@
BPSB = require("scripts.bpsb")
ToggleGUI = require("scripts.toggle-gui")
require("prototypes.item-groups")
require("prototypes.shortcuts")
require("prototypes.styles")
require("prototypes.tips-and-tricks")

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,16 @@
{
"name": "blueprint-sandboxes",
"version": "1.17.1",
"factorio_version": "1.1",
"title": "Blueprint Sandboxes",
"author": "somethingtohide",
"description": "Temporary Editor-lite permissions in Lab-like environments for designing and experimenting.",
"homepage": "https://github.com/cameronleger/blueprint-sandboxes",
"dependencies": [
"base >= 1.1.87",
"? space-exploration >= 0.5.18",
"? EditorExtensions >= 2.0.0",
"? Cursed-FMD",
"? factorissimo-2-notnotmelon"
]
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Cameron Leger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,105 @@
[mod-name]
blueprint-sandboxes=Blueprint Sandboxes
[mod-description]
blueprint-sandboxes=Temporary Editor-lite permissions in Lab-like environments for designing and experimenting.
[controls]
bpsb-toggle-gui-sb-toggle-shortcut=Toggle Sandbox
[controls-description]
bpsb-toggle-gui-sb-toggle-shortcut=Enter or exit the Selected Sandbox
[shortcut-name]
bpsb-toggle-gui-sb-toggle-shortcut=Toggle Sandbox
[shortcut-description]
bpsb-toggle-gui-sb-toggle-shortcut=Enter or exit the Selected Sandbox
[mod-setting-name]
bpsb-allow-all-technology=Unlock all Technology inside Sandboxes
bpsb-only-admins-force-reset=Restrict Reset to Admins
bpsb-scan-all-chunks=Continuously scan Sandbox Chunks
bpsb-craft-to-cursor=Craft to Cursor
bpsb-bonus-inventory-slots=Bonus Inventory Slots
bpsb-extra-mining-speed=Extra Mining Speed
bpsb-extra-lab-speed=Extra Lab Speed
bpsb-god-async-tick=Async Tick Interval
bpsb-god-async-create-per-tick=Async-per-tick Creations
bpsb-god-async-upgrade-per-tick=Async-per-tick Upgrades
bpsb-god-async-delete-per-tick=Async-per-tick Deletions
[mod-setting-description]
bpsb-only-admins-force-reset=In multiplayer, prevent non-Admins from Resetting any Force-wide Sandboxes
bpsb-scan-all-chunks=Acts like a Radar to reveal all Chunks within Sandboxes. This will eventually slow down your game and increase the file-size, but is reversible by Resetting the Sandbox.
bpsb-craft-to-cursor=When "Crafting" in Sandboxes, place the result directly into the Cursor.
bpsb-extra-mining-speed=Additional multiplier on the Mining Speed within Sandboxes. An extremely large value is required if using Space Exploration.
bpsb-extra-lab-speed=Additional multiplier on the Laboratory Speed within Sandboxes. A negative value is applied to discourage researching in the Sandbox, since it would be overwritten by your outside progress anyway, but it would allow testing lab builds.
bpsb-god-async-tick=How often to handle the Async entities.
bpsb-god-async-create-per-tick=If 0, handle immediately; if >0, schedule handling on a future tick for at-most this amount of entities. See Tips-and-Tricks for more info.
bpsb-god-async-upgrade-per-tick=If 0, handle immediately; if >0, schedule handling on a future tick for at-most this amount of entities. See Tips-and-Tricks for more info.
bpsb-god-async-delete-per-tick=If 0, handle immediately; if >0, schedule handling on a future tick for at-most this amount of entities. See Tips-and-Tricks for more info.
[item-group-name]
blueprint-sandboxes=Blueprint Sandboxes
[gui]
bpsb-toggle-gui=Blueprint Sandboxes __CONTROL__bpsb-toggle-gui-sb-toggle-shortcut__
bpsb-toggle-gui-selected-sandbox-dropdown=Sandbox
bpsb-toggle-gui-reset-button=Reset
bpsb-toggle-gui-daytime-slider=Daytime
[gui-description]
bpsb-toggle-gui-selected-sandbox-dropdown=Choose which Sandbox to play in
bpsb-toggle-gui-reset-button=Reset this Sandbox.\nClick with a Blueprint to set the default equipment; shift + click to reset the default equipment.
bpsb-toggle-gui-reset-button-only-admins=Reset is Limited to Admins by a Setting
bpsb-toggle-gui-daytime-slider=Controls the amount of daylight
[ils]
bpsb-ils-prefix=Fake __1__
[sandbox]
bpsb-sb-player-lab=My own Lab
bpsb-sb-force-lab=My Force's Lab
bpsb-sb-space-exploration-disabled=(Requires SE)
bpsb-sb-force-lab-space-exploration=My Force's Planetary Lab
bpsb-sb-force-orbit-space-exploration=My Force's Orbital Sandbox
[tips-and-tricks-item-name]
bpsb-intro-introduction=Blueprint Sandboxes
bpsb-intro-multiple-sandboxes=Sandbox choices
bpsb-intro-reset-v2=Cleanup
bpsb-intro-daylight=Daylight
bpsb-intro-sandbox-force=Linked Force
bpsb-intro-new-recipes=Sandbox Recipes
bpsb-intro-god-mode=God-mode
bpsb-intro-auto-building=Automated building
bpsb-intro-illusions=Illusions
bpsb-space-exploration-introduction=Space Exploration Sandboxes
bpsb-space-exploration-inner-star-tech=How it works
bpsb-space-exploration-planetary-lab=Planetary Lab
bpsb-space-exploration-orbital-sandbox=Orbital Sandbox
bpsb-space-exploration-remote-view=Navigation Satellite
bpsb-space-exploration-mining=Mining
bpsb-factorissimo-introduction=Factories in Sandboxes
[tips-and-tricks-item-description]
bpsb-intro-introduction=Welcome to the Blueprint Sandboxes mod!\nThis allows you to use some Editor-mode and God-mode functionality, without leaving your current game, within lab-like environments.\n\nThe best ways to get Blueprints in and out of your Sandboxes are to Copy-and-Paste and utilize your Blueprint Library. Sometimes, you can also "bring" a Blueprint in and out by having it in your cursor.\n\nTo jump into your first Sandbox, use the shortcut __CONTROL__bpsb-toggle-gui-sb-toggle-shortcut__ or click the Toggle Button in your Shortcut Bar. Use the same method again to exit Sandboxes.
bpsb-intro-multiple-sandboxes=A Sandbox GUI appears while you are inside a Sandbox, and the first thing you'll notice is that you can change which Sandbox you are in:\n\nMy own Lab: this is a Lab-like Sandbox that is unique to you.\nMy Force's Lab: this is another Lab-like Sandbox that is shared between your entire Force.\nMy Force's Planetary Lab: yet another Lab-like Sandbox, but it is known by Space Exploration.\nMy Force's Orbital Sandbox: a Sandbox that exists somewhere in Space.
bpsb-intro-reset-v2=The next thing you might notice in the Sandbox GUI is a Reset Button. The Reset Button will completely reset the current Sandbox that you are in, in case you want to start-over.\n\nThe default equipment that is provided in the reset Sandbox can be changed with a Blueprint, if you click this button while holding the Blueprint. You can also reset this Blueprint back to the default by Shift+clicking the button without a Blueprint.\n\nBe mindful of others when you use this inside of a Force-wide Sandbox!
bpsb-intro-daylight=Another thing you will notice in the Sandbox GUI Daylight slider. This indirectly controls the Sandbox's Time of Day to give you different levels of Daylight, helpful for placing lights in your Blueprints.
bpsb-intro-sandbox-force=While you are in a Sandbox environment, you are actually transferred to another Force which strongly resembles your original Force. Since you have more-powerful permissions inside Sandboxes, this allows those to be cleanly separated from your normal activities.\n\nDepending on your Mod Settings, this Force will either synchronize the Research that you've unlocked, or have all Research unlocked already. For the former, the "progress" of an individual Technology cannot be real-time, but the Research Queue is kept in-sync.\n\nSince this Force is based off of your own Force, if you change Forces then you will have different Force-related Sandboxes, and your Personal Sandbox might require a Reset (since you no longer own anything inside of it).
bpsb-intro-new-recipes=While you are in a Sandbox environment, you also have access to some new Recipes for easier experimenting.\n\nFirst, Factorio's own hidden Loaders are enabled, if they weren't already enabled by another mod. For the same load-testing purposes, the Infinity Accumulator, Chest, and Pipe are also enabled. You can use these as abstract inputs and outputs to test your designs.\n\nThen, every Resource will have a corresponding "Resource Planner." Using the default selection method (__CONTROL__select-for-blueprint__), you can draw that Resource into the Sandbox; using the alternative selection method (__CONTROL__select-for-cancel-deconstruct__), you can remove it!\n\nSome special Tiles (like Water) will have a "Tile Planner." These give you Items that you can place as Tiles.\n\nFinally, inside Sandboxes you can craft items immediately and directly to your cursor; instead of crafting to your inventory and then selecting that result, anything you click-to-craft is ready for use! This also replaces what was in your cursor before, so you can simply __CONTROL__open-character-gui__, click a recipe, __CONTROL__confirm-gui__, keep designing, rinse, and repeat. This is also configurable per-user.
bpsb-intro-god-mode=You are treated like a God within Sandboxes. Your body stays where you left it (be careful with that!) and you can float freely in here. Your inventory here is persistent and separate from your normal player's inventory.\n\nIn Mod Settings, you can opt-in to having all Technology already Researched (as opposed to being linked to what you know outside of here), and having each Sandbox fully-visible in Map-view. Continuously-scanning the Sandboxes will potentially increase the size of your save files, but that can be reversed by Resetting the large Sandboxes; it can also potentially slow down your game, but only while inside very large Sandboxes.
bpsb-intro-auto-building=In addition to God-mode, you have some Editor-mode capabilities as well! Normally, God-mode would place ghost-entities without the items in your inventory, but here, ghosts are automatically filled-in for you. Upgrade and Deconstruction Planners also take effect immediately.\n\nSince this cannot use the Editor-mode - thus must occur in Lua - this is unfortunately slower than Editor-mode's "immediately construct" settings. Also, to workaround some potential bugs concerning orders-of-operations, there are a few settings relating to these behaviors.\n\nCreating, Destroying, and Upgrading can each happen synchronously (on the same tick as the Event) or asynchronously (on a periodic tick scheduled after the Event). By default in Factorio < 1.1.61, to handle a bug concerning the Smart-Belts setting, only Creation is set to asynchronous.\n\nIn the Mod Settings, setting the Async-per-tick value to anything above zero will enable this behavior, controlling the amount handled for each Sandbox individually. When disabled, exactly when the Event is fired is also when that Request is handled. When enabled, the Requests are left-alone, instead scheduled to be handled on a future tick. The setting then controls how many of those Requests are handled each iteration; naturally, this is a performance setting - do you care for throughput or latency?\n\nLastly, manual deconstruction is also handled immediately by default. This is handled by an insanely large increase to the Sandbox's Mining Speed. This increase is adjustable in Mod Settings.
bpsb-intro-illusions=Complex Entities have the potential to cause many sorts of issues when used within Sandboxes; sometimes this or another mod would crash, an odd or irreversible gameplay issue would occur, or it simply would not be desired to have that Entity in the Sandbox.\n\nIllusions are the solution to this problem. When certain problematic Entities are placed in a Sandbox, they are replaced with an Illusion of that Entity. This "fake" Entity has the same size, graphics, and base functionality as the original, but it will not have any script-driven functionality. It's slightly more advanced than a cardboard cutout.\n\nIllusions are automatically translated for you. When you place the Entity in a Sandbox it's automatically converted to the Illusion; and when you create a blueprint the Illusions are automatically converted to their real counterparts. However, sometimes another mod might have already attempted to prevent you from placing the real Entity in the Sandbox; for this situation, you can try building a ghost instead with __ALT_CONTROL__1__build-ghost__, since that might not have been blocked.
bpsb-space-exploration-introduction=Space Exploration compatibility is a goal of this Mod, however a few limitations exist that complicate the relationship.
bpsb-space-exploration-inner-star-tech=The reason why Space Exploration won't let you build in the default Lab Sandboxes is that it must "own" all Surfaces that it knows about, and it does this by generating a plan for the entire Universe and not considering anything that falls outside of that plan. In order to work with Space Exploration, the Surfaces must have "come from" Space Exploration. Naturally, this poses problems for us trying to generate completely new Surfaces!\n\nA fine middle-ground seems to be asking Space Exploration for that Universe, and then dedicating some Stars to Forces for use as Sandboxes. Note that a Star is not the same as a Star's Orbit; the Orbit is what you see everywhere in Space Exploration, but the Star itself seems unusable. This means we can get a Surface that Space Exploration owns, but doesn't use, so we remain out of the way!\n\nHowever, Stars are of limited quantity, which is why there aren't any Personal Space Sandboxes, only Force-wide Space Sandboxes.
bpsb-space-exploration-planetary-lab=The first Space Exploration Sandbox is the Planetary Lab. If you notice that you cannot place certain Space Exploration entities in your other Labs, then you probably want to build here instead. Technically, this is a somewhat-randomly-chosen Moon with Lab-Tile overrides so that you can place ground-based Entities. Unfortunately, that means you probably can't use this Moon for your own purposes!
bpsb-space-exploration-orbital-sandbox=The second Space Exploration Sandbox is the Orbital Sandbox. Similar to the Planetary Lab, this allows you to place Space-related entities, and is what you want if you're designing for Space Stations. Unlike most other Sandboxes, this is not a Lab-like environment. It's technically inside of a Star, so it won't affect your normal play-through.
bpsb-space-exploration-remote-view=Because of the extremely-similar implementations, the Navigation Satellite has a few interesting interactions with Blueprint Sandboxes.\n\nEntering a Sandbox from the Satellite works well, since we'll exit that mode before entering the Sandbox mode.\n\nEntering the Satellite from the Sandbox is a little different, because you aren't from the same Force anymore! You cannot interact with anything you owned before, but you also probably cannot even see those Planets (etc.) anyway. For this reason, it's not encouraged to actually build and launch Satellites from Sandboxes, which enables this functionality for your Sandbox's Force.
bpsb-space-exploration-mining=There can only be one God-mode in Factorio, and both Space Exploration and Blueprint Sandboxes want to use it.\n\nSpace Exploration uses it for the Navigation Satellite. This allows you to affect the locations you are remotely viewing. However, it sets the Mining Speed to near-zero so that you can remove ghosts, but not actual entities.\n\nThis is problematic for Blueprint Sandboxes because it means you can't mine entities to remove them! There is a small workaround here but it does not play nicely with the Navigation Satellite for the Sandbox's Force: the Sandbox Force has an extremely fast Mining Speed boost. This boost is configurable via Mod Settings, but when using Space Exploration the value must be insanely large.
bpsb-factorissimo-introduction=Compatibility with Factorissimo is possible as long as you are using notnotmelon's fork, since it introduces some APIs that make the integration possible.\n\nFor the most part, Factories placed inside of Sandboxes will gain the extra functionality provided by that Sandbox. For example, automated ghost-filling and quick-crafting both work inside of Factorissimo Factories. However, there are a few limitations that are unavoidable.\n\nSince Factories are stored in a shared-surface, some surface-wide functionality has been disabled inside of them. You cannot adjust the Brightness within Factories, and you cannot reset them either. Also, the "persistence" of your invisible-body would be overly complex to handle, so you cannot leave the Sandbox or transfer to another Sandbox if you are inside of a Factory within it - you must exit the Factory first.
[bpsb-messages]
space-exploration-delete-sandbox=You cannot delete a Zone that's being used as a Blueprint Sandbox.

View File

@ -0,0 +1,7 @@
if data.raw["string-setting"] ~= nil
and data.raw["string-setting"]["ee-testing-lab"] ~= nil
then
data.raw["string-setting"]["ee-testing-lab"].hidden = true
data.raw["string-setting"]["ee-testing-lab"].allowed_values = { "off" }
data.raw["string-setting"]["ee-testing-lab"].default_value = "off"
end

View File

@ -0,0 +1,47 @@
Illusion = require("scripts.illusion")
for _, mapping in pairs(Illusion.mappings) do
local type = mapping[1]
local name = mapping[2]
local item = mapping[3]
if data.raw[type] ~= nil and data.raw[type][name] ~= nil then
local entity = data.raw[type][name]
local group = entity.name
if entity.fast_replaceable_group ~= nil then
group = entity.fast_replaceable_group
end
entity.fast_replaceable_group = group
local illusion = table.deepcopy(entity)
illusion.name = Illusion.realToIllusionMap[name]
illusion.placeable_by = { item = item, count = 1 }
if illusion.flags ~= nil then
table.insert(illusion.flags, "not-in-made-in")
else
illusion.flags = { "not-in-made-in" }
end
illusion.localised_description = { "entity-description." .. name }
if entity.localised_name ~= nil then
illusion.localised_name = {
'ils.bpsb-ils-prefix',
{ entity.localised_name },
}
else
illusion.localised_name = {
'ils.bpsb-ils-prefix',
{ 'entity-name.' .. name },
}
end
if illusion.type == "container" then
illusion.type = "infinity-container"
illusion.erase_contents_when_mined = true
end
data:extend({ illusion })
else
log("data[" .. type .. "][" .. name .. "] not found; cannot create Illusion")
end
end

View File

@ -0,0 +1,38 @@
Resources = require("scripts.resources")
Tiles = require("scripts.tiles")
-- New Group for New Recipes
data:extend({
{
type = "item-group",
name = BPSB.name,
icon = BPSB.path .. "/graphics/icon-x64.png",
icon_size = 64,
inventory_order = "z",
order = "z",
},
{
type = "item-subgroup",
name = BPSB.pfx .. "loaders",
group = BPSB.name,
order = "a[loaders]",
},
{
type = "item-subgroup",
name = BPSB.pfx .. "infinity",
group = BPSB.name,
order = "b[infinity]",
},
{
type = "item-subgroup",
name = Resources.name,
group = BPSB.name,
order = "c[resources]",
},
{
type = "item-subgroup",
name = Tiles.name,
group = BPSB.name,
order = "d[tiles]",
},
})

View File

@ -0,0 +1,26 @@
-- Move Loaders to our Tab (only if they exist)
if data.raw.recipe[BPSB.pfx .. "loader"] then
data.raw.item["loader"].subgroup = BPSB.pfx .. "loaders"
data.raw.item["loader"].order = "a-a"
data.raw.item["fast-loader"].subgroup = BPSB.pfx .. "loaders"
data.raw.item["fast-loader"].order = "a-b"
data.raw.item["express-loader"].subgroup = BPSB.pfx .. "loaders"
data.raw.item["express-loader"].order = "a-c"
end
-- Move Infinity Entities to our Tab
data.raw.item["electric-energy-interface"].subgroup = BPSB.pfx .. "infinity"
data.raw.item["electric-energy-interface"].order = "a-a"
data.raw.item["infinity-chest"].subgroup = BPSB.pfx .. "infinity"
data.raw.item["infinity-chest"].order = "a-b"
data.raw.item["infinity-pipe"].subgroup = BPSB.pfx .. "infinity"
data.raw.item["infinity-pipe"].order = "a-c"
-- Allow anyone to use Infinity Filters
data.raw["electric-energy-interface"]["electric-energy-interface"].gui_mode = "all"
data.raw["infinity-container"]["infinity-chest"].gui_mode = "all"
data.raw["infinity-pipe"]["infinity-pipe"].gui_mode = "all"

View File

@ -0,0 +1,44 @@
-- Recipes for hidden/infinity items, only unlocked in Lab
function createLockedRecipeForHiddenItem(name)
if data.raw.item[name] then
data:extend({
{
type = "recipe",
name = BPSB.pfx .. name,
energy_required = 1,
enabled = false,
ingredients = {},
result = name,
}
})
end
end
-- Loaders will only be enabled if nothing else shows them
local shouldEnableLoaders = true
for _, recipe in pairs(data.raw.recipe) do
if not recipe.hidden then
if recipe.result == "loader" then
shouldEnableLoaders = false
break
end
if recipe.results then
for _, result in pairs(recipe.results) do
if result.name == "loader" then
shouldEnableLoaders = false
break
end
end
end
end
end
if shouldEnableLoaders then
createLockedRecipeForHiddenItem("loader")
createLockedRecipeForHiddenItem("fast-loader")
createLockedRecipeForHiddenItem("express-loader")
end
-- Infinity Entities will always be enabled
createLockedRecipeForHiddenItem("electric-energy-interface")
createLockedRecipeForHiddenItem("infinity-chest")
createLockedRecipeForHiddenItem("infinity-pipe")

View File

@ -0,0 +1,79 @@
PlannerIcons = require("scripts.planner-icons")
Resources = require("scripts.resources")
local function startsWith(str, beginning)
return str:sub(1, #beginning) == beginning
end
local function endsWith(str, ending)
return ending == "" or str:sub(-#ending) == ending
end
-- Helpers for Resource Planners
function shouldSkipResourcePlanner(resource)
local skipCoreMining = true
if mods["space-exploration"]
and startsWith(mods["space-exploration"], "0.6")
then
skipCoreMining = false
end
return (resource.category == "se-core-mining" and skipCoreMining)
or (resource.category == "se-core-mining" and endsWith(resource.name, "-sealed"))
end
function createResourcePlannerPrototypes(resource)
-- First, find a way to name the Planner based on the mining result
local localisedName = resource.localised_name
if resource.minable.result then
localisedName = { "item-name." .. resource.minable.result }
elseif resource.minable.results then
local firstResult = resource.minable.results[1]
if firstResult then
if firstResult.type == "item" then
localisedName = { "item-name." .. firstResult.name }
elseif firstResult.type == "fluid" then
localisedName = { "fluid-name." .. firstResult.name }
end
end
end
-- Finally, create the Selection Tool and its Recipe
return {
{
type = "selection-tool",
name = Resources.pfx .. resource.name,
localised_name = localisedName,
icons = PlannerIcons.CreateLayeredIcon(resource),
subgroup = Resources.name,
order = resource.order,
stack_size = 1,
stackable = false,
selection_color = { r = 0, g = 1, b = 0 },
alt_selection_color = { r = 1, g = 0, b = 0 },
selection_mode = { "any-tile" },
alt_selection_mode = { "any-entity" },
selection_cursor_box_type = "pair",
alt_selection_cursor_box_type = "pair",
alt_entity_filters = { resource.name },
always_include_tiles = true,
},
{
type = "recipe",
name = Resources.pfx .. resource.name,
localised_name = localisedName,
energy_required = 1,
enabled = false,
ingredients = {},
result = Resources.pfx .. resource.name,
hide_from_stats = true,
}
}
end
-- New Items/Recipes for Resource Planners
for _, resource in pairs(data.raw.resource) do
-- Some Resources are better left alone
if not shouldSkipResourcePlanner(resource) then
data:extend(createResourcePlannerPrototypes(resource))
end
end

View File

@ -0,0 +1,48 @@
PlannerIcons = require("scripts.planner-icons")
Tiles = require("scripts.tiles")
-- Helpers for Tile Planners
function shouldSkipTilePlanner(tile)
return tile.name ~= "water"
end
function createTilePlannerPrototypes(tile)
local localisedName = tile.localised_name or { "tile-name." .. tile.name }
local icons = PlannerIcons.CreateLayeredIcon(tile)
return {
{
type = "item",
name = Tiles.pfx .. tile.name,
localised_name = localisedName,
icons = icons,
subgroup = Tiles.name,
order = tile.order,
stack_size = 1000,
stackable = true,
place_as_tile = {
result = tile.name,
condition = {},
condition_size = 1,
},
},
{
type = "recipe",
name = Tiles.pfx .. tile.name,
localised_name = localisedName,
icons = icons,
energy_required = 1,
enabled = false,
ingredients = {},
result = Tiles.pfx .. tile.name,
hide_from_stats = true,
}
}
end
-- New Items/Recipes for Tile Planners
for _, tile in pairs(data.raw.tile) do
-- Some Tiles are better left alone
if not shouldSkipTilePlanner(tile) then
data:extend(createTilePlannerPrototypes(tile))
end
end

View File

@ -0,0 +1,25 @@
data:extend({
{
type = "custom-input",
name = ToggleGUI.toggleShortcut,
order = "a[toggle]",
key_sequence = "SHIFT + B"
},
{
type = "shortcut",
name = ToggleGUI.toggleShortcut,
order = "b[blueprints]-z[sandbox]",
action = "lua",
associated_control_input = ToggleGUI.toggleShortcut,
style = "green",
toggleable = true,
icon = {
filename = BPSB.path .. "/graphics/icon-x64.png",
priority = "extra-high-no-scale",
size = 64,
scale = 0.5,
mipmap_count = 3,
flags = { "gui-icon" },
},
},
})

View File

@ -0,0 +1,18 @@
data.raw["gui-style"]["default"][BPSB.pfx .. "padded-horizontal-flow"] = {
type = "horizontal_flow_style",
parent = "horizontal_flow",
horizontal_spacing = 6,
}
data.raw["gui-style"]["default"][BPSB.pfx .. "centered-horizontal-flow"] = {
type = "horizontal_flow_style",
parent = BPSB.pfx .. "padded-horizontal-flow",
vertical_align = "center",
}
data.raw["gui-style"]["default"][BPSB.pfx .. "sprite-like-tool-button"] = {
type = "image_style",
parent = "image",
natural_size = 28,
stretch_image_to_widget_size = true,
}

View File

@ -0,0 +1,281 @@
local category = BPSB.pfx .. "intro"
local pfxOrder = BPSB.pfx
local pfxCategory = category .. "-"
data:extend({
{
type = "tips-and-tricks-item-category",
name = category,
order = pfxOrder .. "a",
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=item-group." .. BPSB.name .. "]",
name = pfxCategory .. "introduction",
order = pfxOrder .. "a",
is_title = true,
starting_status = "unlocked",
trigger = {
type = "time-elapsed",
ticks = 60 * 5 -- 5 seconds
},
image = BPSB.path .. "/graphics/toggle-gui.png",
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "multiple-sandboxes",
indent = 1,
order = pfxOrder .. "b",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
image = BPSB.path .. "/graphics/choose-sandbox.png",
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=utility/reset_white]",
name = pfxCategory .. "reset-v2",
indent = 1,
order = pfxOrder .. "c",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
image = BPSB.path .. "/graphics/reset-sandbox.png",
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=utility/select_icon_white]",
name = pfxCategory .. "daylight",
indent = 1,
order = pfxOrder .. "d",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
image = BPSB.path .. "/graphics/daylight-slider.png",
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "sandbox-force",
indent = 1,
order = pfxOrder .. "e",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "new-recipes",
indent = 1,
order = pfxOrder .. "f",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
image = BPSB.path .. "/graphics/recipes.png",
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "god-mode",
indent = 1,
order = pfxOrder .. "g",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "auto-building",
indent = 1,
order = pfxOrder .. "h",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
simulation = {
init = [[
local stack = game.create_inventory(1)[1]
stack.import_stack("0eNqV1NuKwyAQBuB3mWtb4ilpfZWyLEkri5CYoHbZEHz3muambFOYufT0ifLPLND1dzsF5xOYBdx19BHMZYHofnzbr3NpniwYcMkOwMC3wzpKofVxGkM6dLZPkBk4f7N/YHj+YmB9csnZTXoO5m9/HzobyoZPBoNpjOXY6NdbC3UQR81gBiOPulxwc8Fet2WR2Zsr8G5FcSXe5RRXoV0Sq9Es6RdqNCs/sWqHbdAsKQsnesZUcXekM13S+xKv6EHS/x9b78GcnnwcLMhZwrmSHH2cq8hpwrmaHH6cSy8q9V5Upd0+W7N56eQMfm2IW3mcuGrOotFcc1lXOT8AiIPzfg==")
stack.build_blueprint {
surface = game.surfaces[1],
force = game.forces.player,
position = { 0, 0 },
}
script.on_nth_tick(30, function()
local requestsHandled = 0
local requestedRevives = game.surfaces[1].find_entities_filtered({
type = "entity-ghost",
limit = 1,
})
for _, request in pairs(requestedRevives) do
requestsHandled = requestsHandled + 1
request.revive()
end
if requestsHandled == 0 then
script.on_nth_tick(60, nil)
end
end)
]]
},
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "illusions",
indent = 1,
order = pfxOrder .. "i",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
})
if mods["space-exploration"] then
category = BPSB.pfx .. "space-exploration"
pfxCategory = category .. "-"
data:extend({
{
type = "tips-and-tricks-item-category",
name = category,
order = pfxOrder .. "b",
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=virtual-signal.se-spaceship] [img=item-group." .. BPSB.name .. "]",
name = pfxCategory .. "introduction",
order = pfxOrder .. "a",
is_title = true,
trigger = {
type = "and",
triggers = {
{
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
}, {
type = "unlock-recipe",
recipe = "se-medpack",
}
},
},
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=virtual-signal.se-star]",
name = pfxCategory .. "inner-star-tech",
indent = 1,
order = pfxOrder .. "b",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=virtual-signal.se-planet]",
name = pfxCategory .. "planetary-lab",
indent = 1,
order = pfxOrder .. "c",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=virtual-signal.se-planet-orbit]",
name = pfxCategory .. "orbital-sandbox",
indent = 1,
order = pfxOrder .. "d",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=virtual-signal.se-remote-view]",
name = pfxCategory .. "remote-view",
indent = 1,
order = pfxOrder .. "e",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
{
type = "tips-and-tricks-item",
category = category,
name = pfxCategory .. "mining",
indent = 1,
order = pfxOrder .. "f",
dependencies = { pfxCategory .. "introduction" },
trigger = {
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
},
},
})
end
if mods["factorissimo-2-notnotmelon"] then
category = BPSB.pfx .. "factorissimo"
pfxCategory = category .. "-"
data:extend({
{
type = "tips-and-tricks-item-category",
name = category,
order = pfxOrder .. "c",
},
{
type = "tips-and-tricks-item",
category = category,
tag = "[img=item.factory-1] [img=item-group." .. BPSB.name .. "]",
name = pfxCategory .. "introduction",
order = pfxOrder .. "a",
is_title = true,
trigger = {
type = "and",
triggers = {
{
type = "unlock-recipe",
recipe = BPSB.pfx .. "electric-energy-interface",
}, {
type = "unlock-recipe",
recipe = "factory-1",
}
},
},
},
})
end

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
name=$(cat info.json | jq -r .name)
version=$(cat info.json | jq -r .version)
# Create git tag for this version
git tag -f "$version"
mkdir release
# Prepare zip for Factorio native use and mod portal
git archive --prefix "${name}_$version/" \
-o "release/${name}_$version.zip" \
HEAD

View File

@ -0,0 +1,7 @@
local BPSB = {}
BPSB.name = "blueprint-sandboxes"
BPSB.path = "__" .. BPSB.name .. "__"
BPSB.pfx = "bpsb-"
return BPSB

View File

@ -0,0 +1,22 @@
-- Chat helpers to proxy messages between Sandboxes and the normal Surfaces
local Chat = {}
-- Proxy Chats between Sandbox Force <-> Original Force
function Chat.OnChat(event)
if event.player_index == nil then
return
end
local player = game.players[event.player_index]
local playerData = global.players[event.player_index]
if Sandbox.IsPlayerInsideSandbox(player) then
game.forces[playerData.forceName].print(player.name .. ": " .. event.message, player.chat_color)
else
local sandboxForce = game.forces[playerData.sandboxForceName]
if sandboxForce ~= nil then
sandboxForce.print(player.name .. ": " .. event.message, player.chat_color)
end
end
end
return Chat

View File

@ -0,0 +1,34 @@
local Debug = {}
function Debug.ItemStack(value)
data = {}
if (value == nil) then
data["nil"] = true
return data
end
data["nil"] = false
data["valid"] = value.valid
data["valid_for_read"] = value.valid_for_read
if not value.valid_for_read then
return data
end
data["is_blueprint"] = value.is_blueprint
if value.is_blueprint then
data["is_blueprint_setup"] = value.is_blueprint_setup()
end
if value.is_blueprint then
local entities = value.get_blueprint_entities()
if entities then
data["entities"] = #entities
else
data["entities"] = "nil"
end
end
return data
end
return Debug

View File

@ -0,0 +1,15 @@
-- EditorExtensionsCheats related functionality
local EditorExtensionsCheats = {}
EditorExtensionsCheats.name = "ee_cheat_mode"
EditorExtensionsCheats.enabled = not not remote.interfaces[EditorExtensionsCheats.name]
-- Enables EE's Recipes for a Force
function EditorExtensionsCheats.EnableTestingRecipes(force)
if not EditorExtensionsCheats.enabled then
return false
end
return remote.call(EditorExtensionsCheats.name, "enable_testing_recipes", force)
end
return EditorExtensionsCheats

View File

@ -0,0 +1,143 @@
-- Equipment-related methods
local Equipment = {}
-- Initializes an Inventory for the default equipment Blueprint(s)
function Equipment.Init(default)
local equipment = game.create_inventory(1)
Equipment.Set(equipment, default)
return equipment
end
-- Updates the default equipment Blueprint(s)
function Equipment.Set(equipment, default)
equipment[1].import_stack(default)
end
--[[
Before 1.1.87, there was a bug that did not correctly forcefully generate
chunks for surfaces with the Lab Tiles setting, which required us to
fix those tiles after generation but before placing the Blueprint.
This but was fixed in 1.1.87, however, it introduced another bug where
building a blueprint then did not immediately work on those tiles,
but building entities seemed to. So, the workaround is to delay the blueprint
building, so we do some work when the surface is generated, then the rest
as soon as possible (aligning to generated chunks seems faster (in ticks)
than waiting any number of specific ticks).
]]
function Equipment.Place(stack, surface, forceName)
if stack.is_blueprint then
log("Beginning Equipment Placement")
Equipment.Prepare(stack, surface)
global.equipmentInProgress[surface.name] = {
stack = stack,
surface = surface,
forceName = forceName,
retries = 100,
}
Equipment.BuildBlueprint(stack, surface, forceName)
else
global.equipmentInProgress[surface.name] = nil
end
return true
end
-- Prepares a Surface for an Equipment Blueprint
function Equipment.Prepare(stack, surface)
-- We need to know how many Chunks must be generated to fit this Blueprint
local radius = 0
local function updateRadius(thing)
local x = math.abs(thing.position.x)
local y = math.abs(thing.position.y)
radius = math.max(radius, x, y)
end
local entities = stack.get_blueprint_entities()
local tiles = stack.get_blueprint_tiles()
if entities then
for _, thing in pairs(entities) do
updateRadius(thing)
end
end
if tiles then
for _, thing in pairs(tiles) do
updateRadius(thing)
end
end
-- Then, we can forcefully generate the necessary Chunks
local chunkRadius = 1 + math.ceil(radius / 32)
log("Requesting Chunks for Blueprint Placement: " .. chunkRadius)
surface.request_to_generate_chunks({ x = 0, y = 0 }, chunkRadius)
surface.force_generate_chunk_requests()
log("Chunks allegedly generated")
end
-- Applies an Equipment Blueprint to a Surface
function Equipment.IsReadyForBlueprint(stack, surface)
local entities = stack.get_blueprint_entities()
local tiles = stack.get_blueprint_tiles()
local function is_chunk_generated(thing)
return surface.is_chunk_generated({
thing.position.x / 32,
thing.position.y / 32,
})
end
if entities then
for _, thing in pairs(entities) do
if not is_chunk_generated(thing) then
return false
end
end
end
if tiles then
for _, thing in pairs(tiles) do
if not is_chunk_generated(thing) then
return false
end
end
end
return true
end
-- Applies an Equipment Blueprint to a Surface
function Equipment.BuildBlueprint(stack, surface, forceName)
local equipmentData = global.equipmentInProgress[surface.name]
-- First, let's check if the Chunks are ready for us
if not Equipment.IsReadyForBlueprint(stack, surface) then
equipmentData.retries = equipmentData.retries - 1
return false
end
-- Then, place the Tiles ourselves since it might prevent placing the Blueprint
local tiles = stack.get_blueprint_tiles()
if tiles then
surface.set_tiles(tiles, true, true, true, true)
end
-- Finally, we can place the Blueprint
local ghosts = stack.build_blueprint({
surface = surface.name,
force = forceName,
position = { 0, 0 },
skip_fog_of_war = true,
raise_built = true,
})
-- But that may have not been successful, despite our attempts to ensure it!
if #ghosts > 0 then
log("Some ghosts created, ending repeated attempts; assuming Blueprint is placed")
global.equipmentInProgress[surface.name] = nil
return true
elseif equipmentData.retries <= 0 then
log("No ghosts created, but we've exceeded retry limit, ending repeated attempts")
surface.print("Failed to place Equipment Blueprint after too many retries")
global.equipmentInProgress[surface.name] = nil
return false
else
equipmentData.retries = equipmentData.retries - 1
return false
end
end
return Equipment

View File

@ -0,0 +1,13 @@
local Events = {}
Events.on_daylight_changed_event = script.generate_event_name()
function Events.SendDaylightChangedEvent(player_index, surface_name, daytime)
script.raise_event(Events.on_daylight_changed_event, {
surface_name = surface_name,
player_index = player_index,
daytime = daytime,
})
end
return Events

View File

@ -0,0 +1,68 @@
-- Factorissimo related functionality
local Factorissimo = {}
Factorissimo.name = "factorissimo"
Factorissimo.enabled = not not remote.interfaces[Factorissimo.name]
Factorissimo.surfacePfx = "factory-floor-"
local surfacePfxLength = string.len(Factorissimo.surfacePfx)
function Factorissimo.GetAllFactories()
if Factorissimo.enabled then
return remote.call(Factorissimo.name, "get_global", { "factories" })
else
return {}
end
end
-- Whether the Surface is a Factory
function Factorissimo.IsFactory(thingWithName)
if not Factorissimo.enabled then
return false
end
return string.sub(thingWithName.name, 1, surfacePfxLength) == Factorissimo.surfacePfx
end
-- Whether the Surface is a Factory inside of a Sandbox
function Factorissimo.IsFactoryInsideSandbox(surface, position)
if not Factorissimo.enabled then
return false
end
local factory = Factorissimo.GetFactory(surface, position)
if not factory then
return false
end
return Sandbox.IsSandboxForce(factory.force)
end
-- Find a Factory given a Surface and Position (if possible)
function Factorissimo.GetFactory(surface, position)
return remote.call(Factorissimo.name, "find_surrounding_factory", surface, position)
end
-- Find a Factory's Outside Surface recursively
function Factorissimo.GetOutsideSurfaceForFactory(surface, position)
if not Factorissimo.IsFactory(surface) then
return nil
end
local factory = Factorissimo.GetFactory(surface, position)
if not factory then
return nil
end
if Factorissimo.IsFactory(factory.outside_surface) then
return Factorissimo.GetOutsideSurfaceForFactory(factory.outside_surface, {
x = factory.outside_door_x,
y = factory.outside_door_y,
})
else
return factory.outside_surface
end
return nil
end
return Factorissimo

View File

@ -0,0 +1,149 @@
-- Managing Forces and their Sandbox Forces
local Force = {}
-- Properties from the original Force that are synced to the Sandbox Force (in not-all-tech mode)
Force.syncedProperties = {
-- "manual_mining_speed_modifier", Forcibly set
"manual_crafting_speed_modifier",
-- "laboratory_speed_modifier", Forcibly set
"laboratory_productivity_bonus",
"worker_robots_speed_modifier",
"worker_robots_battery_modifier",
"worker_robots_storage_bonus",
"inserter_stack_size_bonus",
"stack_inserter_capacity_bonus",
"character_trash_slot_count",
"maximum_following_robot_count",
"following_robots_lifetime_modifier",
"character_running_speed_modifier",
"artillery_range_modifier",
"character_build_distance_bonus",
"character_item_drop_distance_bonus",
"character_reach_distance_bonus",
"character_resource_reach_distance_bonus",
"character_item_pickup_distance_bonus",
"character_loot_pickup_distance_bonus",
-- "character_inventory_slots_bonus", Set with a bonus
"character_health_bonus",
"mining_drill_productivity_bonus",
"train_braking_force_bonus",
}
-- Setup Force, if necessary
function Force.Init(force)
if global.forces[force.name]
or Sandbox.IsSandboxForce(force)
or #force.players < 1
then
log("Skip Force.Init: " .. force.name)
return
end
log("Force.Init: " .. force.name)
local forceLabName = Lab.NameFromForce(force)
local sandboxForceName = Sandbox.NameFromForce(force)
global.forces[force.name] = {
sandboxForceName = sandboxForceName,
}
global.sandboxForces[sandboxForceName] = {
forceName = force.name,
hiddenItemsUnlocked = false,
labName = forceLabName,
sePlanetaryLabZoneName = nil,
seOrbitalSandboxZoneName = nil,
}
end
-- Delete Force's information, if necessary
function Force.Merge(oldForceName, newForce)
-- Double-check we know about this Force
local oldForceData = global.forces[oldForceName]
local newForceData = global.forces[newForce.name]
if not oldForceData or not newForceData then
log("Skip Force.Merge: " .. oldForceName .. " -> " .. newForce.name)
return
end
local sandboxForceName = oldForceData.sandboxForceName
local oldSandboxForceData = global.sandboxForces[sandboxForceName]
local oldSandboxForce = game.forces[sandboxForceName]
-- Bounce any Players currently using the older Sandboxes
if oldSandboxForce then
for _, player in pairs(oldSandboxForce.players) do
if Sandbox.IsPlayerInsideSandbox(player) then
log("Force.Merge must manually change Sandbox Player's Force: " .. player.name .. " -> " .. newForce.name)
player.force = newForce
end
end
end
-- Delete the old Force-related Surfaces/Forces
Lab.DeleteLab(oldSandboxForceData.labName)
SpaceExploration.DeleteSandbox(oldSandboxForceData, oldSandboxForceData.sePlanetaryLabZoneName)
SpaceExploration.DeleteSandbox(oldSandboxForceData, oldSandboxForceData.seOrbitalSandboxZoneName)
if oldSandboxForce then
log("Force.Merge must merge Sandbox Forces: " .. oldSandboxForce.name .. " -> " .. newForceData.sandboxForceName)
game.merge_forces(oldSandboxForce, newForceData.sandboxForceName)
end
-- Delete the old Force's data
global.forces[oldForceName] = nil
global.sandboxForces[sandboxForceName] = nil
end
-- Configure Sandbox Force
function Force.ConfigureSandboxForce(force, sandboxForce)
-- Ensure the two Forces don't attack each other
force.set_cease_fire(sandboxForce, true)
sandboxForce.set_cease_fire(force, true)
-- Sync a few properties just in case, but only if they should be linked
if not settings.global[Settings.allowAllTech].value then
for _, property in pairs(Force.syncedProperties) do
sandboxForce[property] = force[property]
end
end
-- Counteract Space Exploration's slow Mining Speed for Gods
sandboxForce.manual_mining_speed_modifier = settings.global[Settings.extraMiningSpeed].value
-- Make research faster/slower based on play-style
sandboxForce.laboratory_speed_modifier = settings.global[Settings.extraLabSpeed].value
-- You should have a little more space too
sandboxForce.character_inventory_slots_bonus =
force.character_inventory_slots_bonus
+ settings.global[Settings.bonusInventorySlots].value
return sandboxForce
end
-- Create Sandbox Force, if necessary
function Force.GetOrCreateSandboxForce(force)
local sandboxForceName = global.forces[force.name].sandboxForceName
local sandboxForce = game.forces[sandboxForceName]
if sandboxForce then
Force.ConfigureSandboxForce(force, sandboxForce)
return sandboxForce
end
log("Creating Sandbox Force: " .. sandboxForceName)
sandboxForce = game.create_force(sandboxForceName)
Force.ConfigureSandboxForce(force, sandboxForce)
Research.Sync(force, sandboxForce)
return sandboxForce
end
-- For all Forces with Sandboxes, Configure them again
function Force.SyncAllForces()
for _, force in pairs(game.forces) do
if not Sandbox.IsSandboxForce(force) then
local sandboxForce = game.forces[Sandbox.NameFromForce(force)]
if sandboxForce then
Force.ConfigureSandboxForce(force, sandboxForce)
end
end
end
end
return Force

View File

@ -0,0 +1,301 @@
-- Custom Extensions to the God-Controller
local God = {}
God.onBuiltEntityFilters = {
{ filter = "type", type = "tile-ghost" },
{ filter = "type", type = "entity-ghost" },
{ filter = "type", type = "item-request-proxy" },
}
for realEntityName, illusionName in pairs(Illusion.realToIllusionMap) do
table.insert(
God.onBuiltEntityFilters,
{ filter = "name", name = realEntityName }
)
end
-- TODO: Perhaps this can be determined by flags?
God.skipHandlingEntities = {
["logistic-train-stop-input"] = true,
["logistic-train-stop-output"] = true,
["tl-dummy-entity"] = true,
["si-in-world-drop-entity"] = true,
["si-in-world-pickup-entity"] = true,
}
-- Immediately destroy an Entity (and perhaps related Entities)
function God.Destroy(entity)
if entity.valid
and entity.can_be_destroyed()
and entity.to_be_deconstructed()
then
-- If the Entity has Transport Lines, also delete any Items on it
if entity.prototype.belt_speed ~= nil then
for i = 1, entity.get_max_transport_line_index() do
entity.get_transport_line(i).clear()
end
end
-- If the Entity represents a Hidden Tile underneath
if entity.type == "deconstructible-tile-proxy" then
local hiddenTile = entity.surface.get_hidden_tile(entity.position)
entity.surface.set_tiles {
{
name = hiddenTile,
position = entity.position,
}
}
end
entity.destroy({ raise_destroy = true })
end
end
-- Immediately Insert an Entity's Requests
function God.InsertRequests(entity)
if entity.valid
and entity.type == "item-request-proxy"
and entity.proxy_target then
-- Insert any Requested Items (like Modules, Fuel)
for name, count in pairs(entity.item_requests) do
entity.proxy_target.insert({
name = name,
count = count,
})
end
entity.destroy()
end
end
-- Immediately Revive a Ghost Entity
function God.Create(entity)
Illusion.ReplaceIfNecessary(entity)
if entity.valid then
if entity.type == "tile-ghost" then
-- Tiles are simple Revives
entity.silent_revive({ raise_revive = true })
elseif entity.type == "item-request-proxy" then
-- Requests are simple
God.InsertRequests(entity)
elseif entity.type == "entity-ghost" then
-- Entities might also want Items after Reviving
local _, revived, request = entity.silent_revive({
return_item_request_proxy = true,
raise_revive = true
})
if revived and request then
God.InsertRequests(request)
end
end
end
end
-- Immediately turn one Entity into another
function God.Upgrade(entity)
if entity.valid
and entity.to_be_upgraded()
then
local target = entity.get_upgrade_target()
local direction = entity.get_upgrade_direction()
if Illusion.IsIllusion(entity.name) and
Illusion.GetActualName(entity.name) == target.name
then
log("Cancelling an Upgrade from an Illusion to its Real Entity: " .. entity.name)
entity.cancel_upgrade(entity.force)
return
end
local options = {
name = target.name,
position = entity.position,
direction = direction or entity.direction,
force = entity.force,
fast_replace = true,
spill = false,
raise_built = true,
}
-- Otherwise it fails to place "output" sides (it defaults to "input")
if entity.type == "underground-belt" then
options.type = entity.belt_to_ground_type
end
local result = entity.surface.create_entity(options)
if result == nil and entity.valid then
log("Upgrade Failed, Cancelling: " .. entity.name)
entity.cancel_upgrade(entity.force)
else
log("Upgrade Failed, Old Entity Gone too!")
end
end
end
-- Ensure the God's Inventory is kept in-sync
function God.OnInventoryChanged(event)
local player = game.players[event.player_index]
local playerData = global.players[event.player_index]
if Sandbox.IsPlayerInsideSandbox(player) then
Inventory.Prune(player)
playerData.sandboxInventory = Inventory.Persist(
player.get_main_inventory(),
playerData.sandboxInventory
)
end
end
-- Ensure newly-crafted Items are put into the Cursor for use
function God.OnPlayerCraftedItem(event)
local player = game.players[event.player_index]
if Sandbox.IsPlayerInsideSandbox(player)
and player.cursor_stack
and player.cursor_stack.valid
and event.item_stack.valid
and event.item_stack.valid_for_read
and event.recipe.valid
and (
#event.recipe.products == 1
or (
event.recipe.prototype.main_product
and event.recipe.prototype.main_product.name == event.item_stack.name
)
)
and player.mod_settings[Settings.craftToCursor].value
then
event.item_stack.count = event.item_stack.prototype.stack_size
player.cursor_stack.clear()
player.cursor_stack.transfer_stack(event.item_stack)
end
end
function God.AsyncWrapper(setting, queue, handler, entity)
if settings.global[setting].value == 0 then
handler(entity)
else
Queue.Push(queue, entity)
end
end
function God.ShouldHandleEntity(entity)
if entity.force.name ~= "neutral"
and not Sandbox.IsSandboxForce(entity.force) then
return false
end
local name = Illusion.GhostOrRealName(entity)
if God.skipHandlingEntities[name] then
return false
end
return Lab.IsLab(entity.surface)
or SpaceExploration.IsSandbox(entity.surface)
or (Factorissimo.IsFactory(entity.surface)
and Factorissimo.IsFactoryInsideSandbox(entity.surface, entity.position))
end
-- Ensure new Orders are handled
function God.OnMarkedForDeconstruct(event)
-- log("Entity Deconstructing: " .. event.entity.unit_number .. " " .. event.entity.type)
if God.ShouldHandleEntity(event.entity) then
God.AsyncWrapper(
Settings.godAsyncDeleteRequestsPerTick,
global.asyncDestroyQueue,
God.Destroy,
event.entity
)
end
end
-- Ensure new Orders are handled
function God.OnMarkedForUpgrade(event)
-- log("Entity Upgrading: " .. event.entity.unit_number .. " " .. event.entity.type)
if God.ShouldHandleEntity(event.entity) then
God.AsyncWrapper(
Settings.godAsyncUpgradeRequestsPerTick,
global.asyncUpgradeQueue,
God.Upgrade,
event.entity
)
end
end
-- Ensure new Ghosts are handled
function God.OnBuiltEntity(entity)
-- log("Entity Creating: " .. entity.unit_number .. " " .. entity.type)
if God.ShouldHandleEntity(entity) then
God.AsyncWrapper(
Settings.godAsyncCreateRequestsPerTick,
global.asyncCreateQueue,
God.Create,
entity
)
end
end
-- For each known Sandbox Surface, handle any async God functionality
function God.HandleAllSandboxRequests(event)
local createRequestsPerTick = settings.global[Settings.godAsyncCreateRequestsPerTick].value
local upgradeRequestsPerTick = settings.global[Settings.godAsyncUpgradeRequestsPerTick].value
local deleteRequestsPerTick = settings.global[Settings.godAsyncDeleteRequestsPerTick].value
local destroyRequestsHandled = 0
while Queue.Size(global.asyncDestroyQueue) > 0
and deleteRequestsPerTick > 0
do
God.Destroy(Queue.Pop(global.asyncDestroyQueue))
destroyRequestsHandled = destroyRequestsHandled + 1
deleteRequestsPerTick = deleteRequestsPerTick - 1
end
if Queue.Size(global.asyncDestroyQueue) == 0
and destroyRequestsHandled > 0
then
global.asyncDestroyQueue = Queue.New()
end
local upgradeRequestsHandled = 0
while Queue.Size(global.asyncUpgradeQueue) > 0
and upgradeRequestsPerTick > 0
do
God.Upgrade(Queue.Pop(global.asyncUpgradeQueue))
upgradeRequestsHandled = upgradeRequestsHandled + 1
upgradeRequestsPerTick = upgradeRequestsPerTick - 1
end
if Queue.Size(global.asyncUpgradeQueue) == 0
and upgradeRequestsHandled > 0
then
global.asyncUpgradeQueue = Queue.New()
end
local createRequestsHandled = 0
while Queue.Size(global.asyncCreateQueue) > 0
and createRequestsPerTick > 0
do
God.Create(Queue.Pop(global.asyncCreateQueue))
createRequestsHandled = createRequestsHandled + 1
createRequestsPerTick = createRequestsPerTick - 1
end
if Queue.Size(global.asyncCreateQueue) == 0
and createRequestsHandled > 0
then
global.asyncCreateQueue = Queue.New()
end
end
-- Charts each Sandbox that a Player is currently inside of
function God.ChartAllOccupiedSandboxes()
if settings.global[Settings.scanSandboxes].value then
local charted = {}
for _, player in pairs(game.players) do
local hash = player.force.name .. player.surface.name
if Sandbox.IsSandbox(player.surface) and not charted[hash] then
player.force.chart_all(player.surface)
charted[hash] = true
end
end
end
end
return God

View File

@ -0,0 +1,209 @@
-- Illusion magic for swapping real/complex entities with fake/simple variants
local Illusion = {}
Illusion.pfx = BPSB.pfx .. "ils-"
local pfxLength = string.len(Illusion.pfx)
-- Full list of Entities that require Illusions
Illusion.mappings = {
-- { "type", "entity-name", "item-name" },
{ "ammo-turret", "se-meteor-defence-container", "se-meteor-defence" },
{ "ammo-turret", "se-meteor-point-defence-container", "se-meteor-point-defence" },
{ "assembling-machine", "se-delivery-cannon", "se-delivery-cannon" },
{ "assembling-machine", "se-delivery-cannon-weapon", "se-delivery-cannon-weapon" },
{ "assembling-machine", "se-energy-transmitter-injector", "se-energy-transmitter-injector" },
{ "assembling-machine", "se-energy-transmitter-emitter", "se-energy-transmitter-emitter" },
{ "assembling-machine", "se-space-elevator", "se-space-elevator" },
{ "boiler", "se-energy-transmitter-chamber", "se-energy-transmitter-chamber" },
{ "container", "se-rocket-launch-pad", "se-rocket-launch-pad" },
{ "container", "se-rocket-landing-pad", "se-rocket-landing-pad" },
{ "electric-energy-interface", "se-energy-beam-defence", "se-energy-beam-defence" },
{ "mining-drill", "se-core-miner-drill", "se-core-miner" },
}
Illusion.realToIllusionMap = {}
for _, mapping in ipairs(Illusion.mappings) do
Illusion.realToIllusionMap[mapping[2]] = Illusion.pfx .. mapping[2]
end
Illusion.realNameFilters = {}
for realEntityName, illusionName in pairs(Illusion.realToIllusionMap) do
table.insert(Illusion.realNameFilters, realEntityName)
end
-- Whether the Thing is an Illusion
function Illusion.IsIllusion(name)
return string.sub(name, 1, pfxLength) == Illusion.pfx
end
-- Extract the Name from an Illusion
function Illusion.GetActualName(name)
return string.sub(name, pfxLength + 1)
end
-- Extract the Name from an Entity
function Illusion.GhostOrRealName(entity)
local realName = entity.name
if entity.type == "entity-ghost" then
realName = entity.ghost_name
end
return realName
end
-- Convert a built Entity into an Illusion (if possible)
function Illusion.ReplaceIfNecessary(entity)
if not entity.valid then
return
end
local realName = Illusion.GhostOrRealName(entity)
local illusionName = Illusion.realToIllusionMap[realName]
if illusionName == nil then
return
end
local options = {
name = illusionName,
position = entity.position,
direction = entity.direction,
force = entity.force,
fast_replace = true,
spill = false,
raise_built = true,
}
local result = entity.surface.create_entity(options)
if result == nil then
log("Could not replace " .. realName .. " with " .. illusionName)
else
log("Replaced " .. realName .. " with " .. illusionName)
end
end
--[[
Holy shit, this is perhaps __the__ most gross portion of the Modding API.
on_player_setup_blueprint _should_ be the primary and only method for handling
Blueprints, but it is not. It is only _correctly_ called when the Blueprint
is first created. If a Blueprint has its contents reselected, then it is still
called, but the event data is entirely useless. Allegedly, you'd have to find
the Blueprint in the Player's inventory - but it might not always be there either!
It seems most in the community went for on_gui_closed which _probably_ has the
Blueprint, however it will occur after selection so I imagine there's potential
for it to _appear_ wrong before saving. on_gui_opened would work the same,
but of course it would not catch any updates, so it's useless in this case.
Worse still, this _only_ works for Blueprints in your Inventory - not from
the Library! For that situation, we'll warn the Player.
See:
[kovarex] [1.0.0] Updated blueprint has no entities during on_player_setup_blueprint
https://forums.factorio.com/viewtopic.php?f=48&t=88100
[kovarex] [1.1.36] Blueprints missing entity list when reused
https://forums.factorio.com/viewtopic.php?f=7&t=99323
[kovarex] [1.0.0] New contents for blueprint broken vs. new blueprint
https://forums.factorio.com/viewtopic.php?f=29&t=88793
Copying (Ctrl+C):
- on_blueprint_setup is called
- blueprint_to_setup NOT valid_for_read
- cursor_stack valid_for_read
- cursor_stack is setup, has entity mapping, and entities
Copying into new Blueprint (Shift+Ctrl+C):
Empty Blueprint in cursor, from Inventory or Book in Inventory, selecting new area (Alt+B):
- on_blueprint_setup is called
- blueprint_to_setup valid_for_read
- blueprint_to_setup is setup, has entity mapping, and entities
- cursor_stack NOT valid_for_read
Selecting new contents, from Inventory or Book in Inventory:
Selecting new contents, from Library or Book in Library:
- on_blueprint_setup is called
- blueprint_to_setup NOT valid_for_read
- cursor_stack valid_for_read
- cursor_stack NOT setup, NO entity mapping, NO entities
Closing/Confirming Blueprint GUI, from Inventory or Book in Inventory:
- on_gui_closed is called
- item valid_for_read
- item NOT setup, NO entity mapping, but has entities
Closing/Confirming Blueprint GUI, from Library or Book in Library:
- on_gui_closed is called
- item valid_for_read
- item NOT setup, NO entity mapping, NO entities
]]
-- Convert an entire Blueprint's contents from Illusions (if possible)
function Illusion.HandleBlueprintEvent(player, potentialItemStacks)
if not Sandbox.IsPlayerInsideSandbox(player) then
return
end
local blueprint = nil
for _, itemStack in pairs(potentialItemStacks) do
if itemStack and itemStack.valid_for_read and itemStack.is_blueprint then
blueprint = itemStack
break
end
end
-- We must catch more events than we need, so this is expected
if not blueprint then
return
end
-- Some events won't have a functional Blueprint, so we're screwed!
local entities = blueprint.get_blueprint_entities()
if not entities then
log("Cannot handle Blueprint update: no entities in Blueprint (caused by selecting new contents)")
local playerData = global.players[player.index]
local lastWarningForNewContents = playerData.lastWarningForNewContents or 0
if game.tick - lastWarningForNewContents > (216000) then -- 1 hour
player.print("WARNING: Known issues in Factorio prevent mods from seeing or updating Blueprints when using 'Select new contents'.")
player.print("This mod requires that ability to swap the Fake Illusions for their Real Entities in your Blueprints.")
player.print("If you are including any Fake Illusions in this Blueprint, they likely will NOT be replaced, especially if the source Blueprint is within the Library instead of your Inventory.")
player.print("This message will only appear periodically. See the mod's page for more details.")
playerData.lastWarningForNewContents = game.tick
end
return
end
local replaced = 0
for _, entity in pairs(entities) do
if Illusion.IsIllusion(entity.name) then
entity.name = Illusion.GetActualName(entity.name)
replaced = replaced + 1
end
end
if replaced > 0 then
blueprint.set_blueprint_entities(entities)
end
log("Replaced " .. replaced .. " entities in Sandbox Blueprint")
end
-- A Player is creating a new Blueprint from a selection
function Illusion.OnBlueprintSetup(event)
local player = game.players[event.player_index]
Illusion.HandleBlueprintEvent(player, {
player.blueprint_to_setup,
player.cursor_stack,
})
end
-- A Player might be updating an existing Blueprint (only works in the Inventory!)
function Illusion.OnBlueprintGUIClosed(event)
if not event.item then
return
end
Illusion.HandleBlueprintEvent(game.players[event.player_index], {
event.item,
})
end
return Illusion

View File

@ -0,0 +1,50 @@
-- First-time Setup for new Games and new Players
local Init = {}
-- Setup Player, if necessary
function Init.Player(player)
if global.players[player.index] then
log("Skip Init.Player: " .. player.name)
return
end
log("Init.Player: " .. player.name)
local playerLabName = Lab.NameFromPlayer(player)
local sandboxForceName = Sandbox.NameFromForce(player.force)
global.players[player.index] = {
forceName = player.force.name,
labName = playerLabName,
sandboxForceName = sandboxForceName,
selectedSandbox = Sandbox.player,
sandboxInventory = nil,
insideSandbox = nil,
lastSandboxPositions = {},
}
ToggleGUI.Init(player)
end
-- Reset all Mod data
function Init.FirstTimeInit()
log("Init.FirstTimeInit")
global.version = Migrate.version
global.forces = {}
global.players = {}
global.labSurfaces = {}
global.sandboxForces = {}
global.seSurfaces = {}
global.equipmentInProgress = {}
global.asyncCreateQueue = Queue.New()
global.asyncUpgradeQueue = Queue.New()
global.asyncDestroyQueue = Queue.New()
global.lastSettingForAsyncGodTick = settings.global[Settings.godAsyncTick].value
-- Warning: do not rely on this alone; new Saves have no Players/Forces yet
for _, force in pairs(game.forces) do
Force.Init(force)
end
for _, player in pairs(game.players) do
Init.Player(player)
end
end
return Init

View File

@ -0,0 +1,100 @@
-- Inventory-related methods
local Inventory = {}
-- TODO: Consider the Cursor Inventory too (otherwise items can be lost during transition)
-- TODO: Consider Filters (otherwise they are lost during transition)
-- Extracts a Player Cursor's Blueprint as a string (if present)
function Inventory.GetCursorBlueprintString(player)
local blueprint = nil
if player.is_cursor_blueprint() then
if player.character
and player.character.cursor_stack
and player.character.cursor_stack.valid
and player.character.cursor_stack.valid_for_read
then
blueprint = player.character.cursor_stack.export_stack()
elseif player.cursor_stack
and player.cursor_stack.valid
and player.cursor_stack.valid_for_read
then
blueprint = player.cursor_stack.export_stack()
else
player.print("There was a Blueprint in your cursor, but Factorio incorrectly describes it as invalid. This is most likely because it's currently in the Blueprint Library (a known bug in Factorio).")
end
end
return blueprint
end
-- Whether a Player's Cursor can non-destructively be replaced
function Inventory.WasCursorSafelyCleared(player)
if not player or not player.cursor_stack.valid then
return false
end
if player.is_cursor_empty() then return true end
if player.is_cursor_blueprint() then return true end
--[[ TODO:
This doesn't usually happen, since the "source location" of the item
seems to be lost after swapping the character.
]]
if not player.cursor_stack_temporary then
player.clear_cursor()
return true
end
return false
end
-- Whether a Player's Inventory is vulnerable to going missing due to lack of a body
function Inventory.ShouldPersist(controller)
return controller ~= defines.controllers.character
end
-- Ensure a Player's Inventory isn't full
function Inventory.Prune(player)
local inventory = player.get_main_inventory()
if not inventory then
return
end
if inventory.count_empty_stacks() == 0 then
player.print("Your inventory is almost full. Please throw some items away.")
player.surface.spill_item_stack(player.position, inventory[#inventory])
inventory[#inventory].clear()
end
end
-- Persist one Inventory into another mod-created one
function Inventory.Persist(from, to)
if not from then
return nil
end
if not to then
to = game.create_inventory(#from)
else
to.resize(#from)
end
for i = 1, #from do
to[i].set_stack(from[i])
end
return to
end
-- Restore one Inventory into another
function Inventory.Restore(from, to)
if not from or not to then
return
end
local transition = math.min(#from, #to)
for i = 1, transition do
to[i].set_stack(from[i])
end
if transition < #to then
for i = transition + 1, #to do
to[i].set_stack()
end
end
end
return Inventory

View File

@ -0,0 +1,203 @@
-- Manage the Lab-like Surfaces
local Lab = {}
Lab.pfx = BPSB.pfx .. "lab-"
local pfxLength = string.len(Lab.pfx)
Lab.chartAllLabsTick = 300
Lab.equipmentString = "0eNqNkd1ugzAMhd/F10kF6bq2eZVqQpAaZgkMSkI1hnj3OVRC1dT95M6x/Z2TkxmqdsTBE0ewM5DrOYC9zBCo4bJNd3EaECxQxA4UcNmlClt00ZPTyOibScs++rp0CIsC4it+gM0X9SenokZvrKFvH/fN8qYAOVIkvJtai6ngsavQi8AvGAVDH2Sz56QttEzBBFabJbn6BjL/eNcPwEz8VmNdoy8CfQoiz7bzRGm/KRHXxNLS7h1DfILfHVYBszuskdyni4AxEjchTXns+hsWo/RasYnXIoUrrehHXFJ6a9j24Y8V3NCHVcWc8pfj2RxfzyY77SWXL6PBsLw="
-- A unique per-Player Lab Name
function Lab.NameFromPlayer(player)
return Lab.pfx .. "p-" .. player.name
end
-- A unique per-Force Lab Name
function Lab.NameFromForce(force)
return Lab.pfx .. "f-" .. force.name
end
-- Whether the Surface (or Force) is specific to a Lab
function Lab.IsLab(thingWithName)
return string.sub(thingWithName.name, 1, pfxLength) == Lab.pfx
-- return not not global.labSurfaces[thingWithName.name]
end
-- Create a new Lab Surface, if necessary
function Lab.GetOrCreateSurface(labName, sandboxForce)
local surface = game.surfaces[labName]
if not Lab.IsLab({ name = labName }) then
log("Not a Lab, won't Create: " .. labName)
return
end
if surface then
if global.labSurfaces[labName] then
return surface
end
log("Found a Lab Surface, but not the Data; recreating it for safety")
end
log("Creating Lab: " .. labName)
global.labSurfaces[labName] = {
sandboxForceName = sandboxForce.name,
equipmentBlueprints = Equipment.Init(Lab.equipmentString),
daytime = 0.95,
}
if not surface then
surface = game.create_surface(labName, {
default_enable_all_autoplace_controls = false,
cliff_settings = { cliff_elevation_0 = 1024 },
})
end
return surface
end
-- Set a Lab's Daytime to a specific value
function Lab.SetDayTime(player, surface, daytime)
if Lab.IsLab(surface) then
surface.freeze_daytime = true
surface.daytime = daytime
global.labSurfaces[surface.name].daytime = daytime
Events.SendDaylightChangedEvent(player.index, surface.name, daytime)
return true
else
return false
end
end
-- Delete a Lab Surface, if present
function Lab.DeleteLab(surfaceName)
if game.surfaces[surfaceName] and global.labSurfaces[surfaceName] then
log("Deleting Lab: " .. surfaceName)
local equipmentBlueprints = global.labSurfaces.equipmentBlueprints
if equipmentBlueprints and equipmentBlueprints.valid() then
equipmentBlueprints.destroy()
end
global.labSurfaces[surfaceName] = nil
game.delete_surface(surfaceName)
return true
else
log("Not a Lab, won't Delete: " .. surfaceName)
return false
end
end
-- Set the Lab's equipment Blueprint for a Surface
function Lab.SetEquipmentBlueprint(surface, equipmentString)
if Lab.IsLab(surface) then
log("Setting Lab equipment: " .. surface.name)
Equipment.Set(
global.labSurfaces[surface.name].equipmentBlueprints,
equipmentString
)
surface.print("The equipment Blueprint for this Lab has been changed")
return true
else
log("Not a Lab, won't Set equipment: " .. surface.name)
return false
end
end
-- Reset the Lab's equipment Blueprint for a Surface
function Lab.ResetEquipmentBlueprint(surface)
if Lab.IsLab(surface) then
log("Resetting Lab equipment: " .. surface.name)
Equipment.Set(
global.labSurfaces[surface.name].equipmentBlueprints,
Lab.equipmentString
)
surface.print("The equipment Blueprint for this Lab has been reset")
return true
else
log("Not a Lab, won't Reset equipment: " .. surface.name)
return false
end
end
-- Reset the Lab a Player is currently in
function Lab.Reset(player)
if Lab.IsLab(player.surface) then
log("Resetting Lab: " .. player.surface.name)
player.teleport({ 0, 0 }, player.surface.name)
player.surface.clear(false)
return true
else
log("Not a Lab, won't Reset: " .. player.surface.name)
return false
end
end
-- Set some important Surface settings for a Lab
function Lab.AfterCreate(surface)
local surfaceData = global.labSurfaces[surface.name]
if not surfaceData then
log("Not a Lab, won't handle Creation: " .. surface.name)
return false
end
log("Handling Creation of Lab: " .. surface.name)
if remote.interfaces["RSO"] then
pcall(remote.call, "RSO", "ignoreSurface", surface.name)
end
if remote.interfaces["dangOreus"] then
pcall(remote.call, "dangOreus", "toggle", surface.name)
end
if remote.interfaces["AbandonedRuins"] then
pcall(remote.call, "AbandonedRuins", "exclude_surface", surface.name)
end
surface.freeze_daytime = true
surface.daytime = 0.95
surface.show_clouds = false
surface.generate_with_lab_tiles = true
return true
end
-- Add some helpful initial Entities to a Lab
function Lab.Equip(surface)
local surfaceData = global.labSurfaces[surface.name]
if not surfaceData then
log("Not a Lab, won't Equip: " .. surface.name)
return false
end
log("Equipping Lab: " .. surface.name)
Equipment.Place(
surfaceData.equipmentBlueprints[1],
surface,
surfaceData.sandboxForceName
)
return true
end
-- Update all Entities in Lab with a new Force
function Lab.AssignEntitiesToForce(surface, force)
local surfaceData = global.labSurfaces[surface.name]
if not surfaceData then
log("Not a Lab, won't Reassign: " .. surface.name)
return false
end
log("Reassigning Lab to: " .. surface.name .. " -> " .. force.name)
for _, entity in pairs(surface.find_entities_filtered {
force = surfaceData.sandboxForceName,
invert = true,
}) do
entity.force = force
end
return true
end
return Lab

View File

@ -0,0 +1,404 @@
local Migrate = {}
Migrate.version = 011701
function Migrate.Run()
if not global.version then
global.version = 0
end
if global.version < Migrate.version then
if global.version < 010003 then Migrate.v1_0_3() end
if global.version < 010100 then Migrate.v1_1_0() end
if global.version < 010401 then Migrate.v1_4_1() end
if global.version < 010500 then Migrate.v1_5_0() end
if global.version < 010600 then Migrate.v1_6_0() end
if global.version < 010700 then Migrate.v1_7_0() end
if global.version < 010703 then Migrate.v1_7_3() end
if global.version < 010704 then Migrate.v1_7_4() end
if global.version < 011000 then Migrate.v1_10_0() end
if global.version < 011001 then Migrate.v1_10_1() end
if global.version < 011101 then Migrate.v1_11_1() end
if global.version < 011103 then Migrate.v1_11_3() end
if global.version < 011500 then Migrate.v1_15_0() end
if global.version < 011604 then Migrate.v1_16_4() end
if global.version < 011606 then Migrate.v1_16_6() end
end
global.version = Migrate.version
end
function Migrate.RecreateGuis()
for _, player in pairs(game.players) do
ToggleGUI.Destroy(player)
ToggleGUI.Init(player)
end
end
function Migrate.v1_0_3()
--[[
It was discovered that in on_configuration_changed Space Exploration would
"fix" all Tiles for all Zones that it knows of, which causes problems
specifically for the Planetary Sandbox, which initially used Stars.
At this point, we unfortunately have to completely remove those Sandboxes,
which is unavoidable because by the nature of this update we would have
triggered the complete-reset of that Surface anyway.
]]
log("Migration 1.0.3 Starting")
if SpaceExploration.enabled then
local planetaryLabId = 3
local planetaryLabsOnStars = {}
local playersToKickFromPlanetaryLabs = {}
for name, surfaceData in pairs(global.seSurfaces) do
if (not surfaceData.orbital) and SpaceExploration.IsStar(name) then
table.insert(planetaryLabsOnStars, {
zoneName = name,
sandboxForceName = surfaceData.sandboxForceName,
})
end
end
for index, player in pairs(game.players) do
local playerData = global.players[index]
if playerData.insideSandbox == planetaryLabId
and SpaceExploration.IsStar(player.surface.name)
then
table.insert(playersToKickFromPlanetaryLabs, player)
end
end
for _, player in pairs(playersToKickFromPlanetaryLabs) do
log("Kicking Player out of Planetary Lab: " .. player.name)
Sandbox.Exit(player)
end
for _, surfaceData in pairs(planetaryLabsOnStars) do
log("Destroying Planetary Lab inside Star: " .. surfaceData.zoneName)
SpaceExploration.DeleteSandbox(
global.sandboxForces[surfaceData.sandboxForceName],
surfaceData.zoneName
)
end
end
log("Migration 1.0.3 Finished")
end
function Migrate.v1_1_0()
--[[
A "persistent" Sandbox Inventory was created for each Player.
]]
log("Migration 1.1.0 Starting")
for index, player in pairs(game.players) do
local playerData = global.players[index]
playerData.sandboxInventory = game.create_inventory(#player.get_main_inventory())
if Sandbox.IsPlayerInsideSandbox(player) then
log("Player inside Sandbox, fully-syncing the inventory.")
Inventory.Persist(
player.get_main_inventory(),
playerData.sandboxInventory
)
end
end
log("Migration 1.1.0 Finished")
end
function Migrate.v1_4_1()
--[[
The levels for level-based Research wasn't being synchronized.
]]
log("Migration 1.4.1 Starting")
Research.SyncAllForces()
log("Migration 1.4.1 Finished")
end
function Migrate.v1_5_0()
--[[
Bonus Slots for Sandbox Force Inventories were added.
]]
log("Migration 1.5.0 Starting")
Force.SyncAllForces()
log("Migration 1.5.0 Finished")
end
function Migrate.v1_6_0()
--[[
Last-known positions inside Sandboxes were added.
]]
log("Migration 1.6.0 Starting")
for index, _ in pairs(game.players) do
local playerData = global.players[index]
playerData.lastSandboxPositions = {}
end
log("Migration 1.6.0 Finished")
end
function Migrate.v1_7_0()
--[[
Configurable-per-Sandbox daytime was added.
]]
log("Migration 1.7.0 Starting")
for surfaceName, _ in pairs(global.labSurfaces) do
local surface = game.surfaces[surfaceName]
if surface then
surface.always_day = false
surface.freeze_daytime = true
surface.daytime = 0.95
global.labSurfaces[surfaceName].daytime = 0.95
end
end
for surfaceName, _ in pairs(global.seSurfaces) do
local surface = game.surfaces[surfaceName]
if surface then
surface.always_day = false
surface.freeze_daytime = true
surface.daytime = 0.95
global.seSurfaces[surfaceName].daytime = 0.95
end
end
Migrate.RecreateGuis()
log("Migration 1.7.0 Finished")
end
function Migrate.v1_7_3()
--[[
The daylight portrait icon had the same name as the Reset Button.
]]
log("Migration 1.7.3 Starting")
Migrate.RecreateGuis()
log("Migration 1.7.3 Finished")
end
function Migrate.v1_7_4()
--[[
The 1.7.3 migration wasn't correctly applied to 1.7.x
Allow-all-Tech was incorrectly applying the existing Force's bonuses
]]
Migrate.v1_7_3()
log("Migration 1.7.4 Starting")
if settings.global[Settings.allowAllTech].value then
game.print("Blueprint Sandboxes Notice: You had the Unlock-all-Technologies " ..
"Setting enabled, but there was a bug pre-1.7.4 that was incorrectly " ..
"overriding some of the bonuses from leveled-research. You should " ..
"disable, then re-enable this setting in order to fix that.")
end
log("Migration 1.7.4 Finished")
end
function Migrate.v1_10_0()
--[[
Internal Queues for Asynchronous Sandbox requests
replace the old find_entities_filtered
]]
log("Migration 1.10.0 Starting")
global.asyncCreateQueue = Queue.New()
global.asyncUpgradeQueue = Queue.New()
global.asyncDestroyQueue = Queue.New()
for _, surfaceData in pairs(global.labSurfaces) do
surfaceData.hasRequests = nil
end
for _, surfaceData in pairs(global.seSurfaces) do
surfaceData.hasRequests = nil
end
log("Migration 1.10.0 Finished")
end
function Migrate.v1_10_1()
--[[
Planetary Labs were possibly created within a Player's Home System
and on Planets that could be dangerous.
]]
log("Migration 1.10.1 Starting")
if SpaceExploration.enabled then
local planetaryLabId = 3
local badPlanetaryLabs = {}
local badPlanetaryLabNames = {}
local playersToKickFromPlanetaryLabs = {}
local zoneIndex = remote.call(SpaceExploration.name, "get_zone_index", {})
for name, surfaceData in pairs(global.seSurfaces) do
if not surfaceData.orbital then
local zone = remote.call(SpaceExploration.name, "get_zone_from_name", {
zone_name = name,
})
local rootZone = SpaceExploration.GetRootZone(zoneIndex, zone)
if SpaceExploration.IsZoneThreatening(zone)
or rootZone.special_type == "homesystem" then
table.insert(badPlanetaryLabs, {
zoneName = name,
sandboxForceName = surfaceData.sandboxForceName,
})
badPlanetaryLabNames[name] = true
end
end
end
for index, player in pairs(game.players) do
local playerData = global.players[index]
if playerData.insideSandbox == planetaryLabId
and badPlanetaryLabNames[player.surface.name]
then
table.insert(playersToKickFromPlanetaryLabs, player)
end
end
for _, player in pairs(playersToKickFromPlanetaryLabs) do
log("Kicking Player out of Planetary Lab: " .. player.name)
Sandbox.Exit(player)
end
for _, surfaceData in pairs(badPlanetaryLabs) do
log("Destroying Planetary Lab: " .. surfaceData.zoneName)
SpaceExploration.DeleteSandbox(
global.sandboxForces[surfaceData.sandboxForceName],
surfaceData.zoneName
)
local message = "Unfortunately, your Planetary Sandbox was generated in a " ..
"non-ideal or dangerous location, so it was destroyed. Accessing " ..
"the Sandbox again will create a new one in a safer location."
game.forces[surfaceData.sandboxForceName].print(message)
game.forces[global.sandboxForces[surfaceData.sandboxForceName].forceName].print(message)
end
end
log("Migration 1.10.1 Finished")
end
function Migrate.v1_11_1()
--[[
dangOreus was applying to Labs and causing significant lag
]]
log("Migration 1.11.1 Starting")
if remote.interfaces["dangOreus"] then
for labName, _ in pairs(global.labSurfaces) do
pcall(remote.call, "dangOreus", "toggle", labName)
end
end
log("Migration 1.11.1 Finished")
end
function Migrate.v1_11_3_surface(surfaceName)
local surface = game.surfaces[surfaceName]
if not surface then
return
end
local entitiesToSwap = surface.find_entities_filtered({ name = Illusion.realNameFilters, })
for _, entity in pairs(entitiesToSwap) do
Illusion.ReplaceIfNecessary(entity)
end
local ghostsToSwap = surface.find_entities_filtered({ ghost_name = Illusion.realNameFilters, })
for _, entity in pairs(ghostsToSwap) do
Illusion.ReplaceIfNecessary(entity)
end
end
function Migrate.v1_11_3()
--[[
1.11.0 did not include a migration of real-to-illusion Entities,
but it was found that some older Entities combined with Space Exploration 0.6
could cause a crash.
]]
log("Migration 1.11.3 Starting")
for surfaceName, _ in pairs(global.labSurfaces) do
Migrate.v1_11_3_surface(surfaceName)
end
for surfaceName, _ in pairs(global.seSurfaces) do
Migrate.v1_11_3_surface(surfaceName)
end
log("Migration 1.11.3 Finished")
end
function Migrate.v1_15_0()
--[[
1.15.0 introduced a default Equipment Inventory for each Sandbox
]]
log("Migration 1.15.0 Starting")
for surfaceName, surfaceData in pairs(global.labSurfaces) do
surfaceData.equipmentBlueprints = Equipment.Init(Lab.equipmentString)
end
for surfaceName, surfaceData in pairs(global.seSurfaces) do
if (surfaceData.orbital) then
surfaceData.equipmentBlueprints = Equipment.Init(SpaceExploration.orbitalEquipmentString)
else
surfaceData.equipmentBlueprints = Equipment.Init(Lab.equipmentString)
end
end
log("Migration 1.15.0 Finished")
end
function Migrate.v1_16_4()
--[[
1.16.4 introduced an alternative Equipment placement technique
]]
log("Migration 1.16.4 Starting")
global.equipmentInProgress = {}
log("Migration 1.16.4 Finished")
end
function Migrate.v1_16_6()
--[[
1.16.6 added Remote Interface support for Editor Extensions
]]
log("Migration 1.16.6 Starting")
for _, force in pairs(game.forces) do
if Sandbox.IsSandboxForce(force) then
EditorExtensionsCheats.EnableTestingRecipes(force)
end
end
log("Migration 1.16.6 Finished")
end
return Migrate

View File

@ -0,0 +1,70 @@
local PlannerIcons = {}
function PlannerIcons.CreateLayeredIcon(prototype)
local backgroundIconSize = 64
local overallLayeredIconScale = 0.4
local layeredIcons = {
{
icon = BPSB.path .. "/graphics/icon-x64.png",
icon_size = backgroundIconSize,
icon_mipmaps = 3,
tint = { r = 0.75, g = 0.75, b = 0.75, a = 1 },
},
}
local foundIcon = false
if prototype.icons then
foundIcon = true
-- Complex Icons approach (layer but re-scale each)
for _, icon in pairs(prototype.icons) do
local thisIconScale = 1.0
if icon.scale then
thisIconScale = icon.scale
end
table.insert(layeredIcons, {
icon = icon.icon,
icon_size = icon.icon_size or prototype.icon_size,
tint = icon.tint,
shift = icon.shift,
scale = thisIconScale * overallLayeredIconScale * (backgroundIconSize / (icon.icon_size or prototype.icon_size)),
icon_mipmaps = icon.icon_mipmaps,
})
end
elseif prototype.icon then
foundIcon = true
-- The simplest Icon approach
table.insert(layeredIcons, {
icon = prototype.icon,
icon_size = prototype.icon_size,
icon_mipmaps = prototype.icon_mipmaps,
scale = overallLayeredIconScale * (backgroundIconSize / prototype.icon_size),
})
elseif prototype.variants then
foundIcon = true
-- Slightly complex Tile approach
local image = prototype.variants.main[1]
if prototype.variants.material_background then
image = prototype.variants.material_background
end
local thisImageScale = 1.0
if image.scale then
thisImageScale = image.scale
end
local thisImageSize = (image.size or 1.0) * 32 / thisImageScale
table.insert(layeredIcons, {
icon = image.picture,
icon_size = thisImageSize,
tint = prototype.tint,
scale = thisImageScale * overallLayeredIconScale * (backgroundIconSize / thisImageSize),
})
end
if not foundIcon then
log("No icon found for prototype: " .. serpent.block(prototype))
end
return layeredIcons
end
return PlannerIcons

View File

@ -0,0 +1,30 @@
-- https://www.lua.org/pil/11.4.html
local Queue = {}
function Queue.New()
return { first = 0, last = -1 }
end
function Queue.Push(list, value)
local last = list.last + 1
list.last = last
list[last] = value
end
function Queue.Pop(list)
local first = list.first
if first > list.last then
return nil
end
local value = list[first]
list[first] = nil
list.first = first + 1
return value
end
function Queue.Size(list)
return list.last - list.first + 1
end
return Queue

View File

@ -0,0 +1,14 @@
remote.add_interface(
"blueprint-sandboxes",
{
space_exploration_delete_surface = function(event)
local surface = game.surfaces[event.surface_index]
if SpaceExploration.IsSandbox(surface) then
return {
allow_delete = false,
message = {"bpsb-messages.space-exploration-delete-sandbox"},
}
end
end,
}
)

View File

@ -0,0 +1,105 @@
-- Managing the Research of each Force's Sandboxes
local Research = {}
-- Set a Force's Sandboxes Research equal to that of the Force's (or all research)
function Research.Sync(originalForce, sandboxForce)
if settings.global[Settings.allowAllTech].value then
sandboxForce.research_all_technologies()
log("Researching everything for: " .. sandboxForce.name)
else
for tech, _ in pairs(game.technology_prototypes) do
sandboxForce.technologies[tech].researched = originalForce.technologies[tech].researched
sandboxForce.technologies[tech].level = originalForce.technologies[tech].level
end
log("Copied all Research from: " .. originalForce.name .. " -> " .. sandboxForce.name)
end
end
-- Set a Force's Sandboxes Research Queue equal to that of the Force's
function Research.SyncQueue(originalForce, sandboxForce)
if settings.global[Settings.allowAllTech].value then
sandboxForce.research_queue = nil
log("Emptying Research Queue for: " .. sandboxForce.name)
else
local newQueue = {}
for _, tech in pairs(originalForce.research_queue) do
table.insert(newQueue, tech.name)
end
sandboxForce.research_queue = newQueue
log("Copied Research Queue from: " .. originalForce.name .. " -> " .. sandboxForce.name)
end
end
-- Enable the Infinity Input/Output Recipes
function Research.EnableSandboxSpecificResearch(force)
if global.sandboxForces[force.name].hiddenItemsUnlocked == true then
return
end
log("Unlocking hidden Recipes for: " .. force.name)
if force.recipes[BPSB.pfx .. "loader"] then
force.recipes[BPSB.pfx .. "loader"].enabled = true
force.recipes[BPSB.pfx .. "fast-loader"].enabled = true
force.recipes[BPSB.pfx .. "express-loader"].enabled = true
end
force.recipes[BPSB.pfx .. "electric-energy-interface"].enabled = true
force.recipes[BPSB.pfx .. "infinity-chest"].enabled = true
force.recipes[BPSB.pfx .. "infinity-pipe"].enabled = true
for name, recipe in pairs(force.recipes) do
if Resources.IsResourcePlanner(name) or Tiles.IsTilePlanner(name) then
recipe.enabled = true
end
end
EditorExtensionsCheats.EnableTestingRecipes(force)
global.sandboxForces[force.name].hiddenItemsUnlocked = true
end
-- For all Forces with Sandboxes, Sync their Research
function Research.SyncAllForces()
for _, force in pairs(game.forces) do
if not Sandbox.IsSandboxForce(force) then
local sandboxForce = game.forces[Sandbox.NameFromForce(force)]
if sandboxForce then
Research.Sync(force, sandboxForce)
Research.SyncQueue(force, sandboxForce)
end
end
end
end
-- As a Force's Research changes, keep the Force's Sandboxes in-sync
function Research.OnResearched(event)
if not settings.global[Settings.allowAllTech].value then
local force = event.research.force
if not Sandbox.IsSandboxForce(force) then
local sandboxForce = game.forces[Sandbox.NameFromForce(force)]
if sandboxForce then
log("New Research: " .. event.research.name .. " from " .. force.name .. " -> " .. sandboxForce.name)
sandboxForce.technologies[event.research.name].researched = force.technologies[event.research.name].researched
sandboxForce.technologies[event.research.name].level = force.technologies[event.research.name].level
sandboxForce.play_sound { path = "utility/research_completed" }
Research.SyncQueue(force, sandboxForce)
end
end
end
end
-- As a Force's Research Queue changes, show it in the Force's Sandboxes
function Research.OnResearchStarted(event)
if not settings.global[Settings.allowAllTech].value then
local force = event.research.force
if not Sandbox.IsSandboxForce(force) then
local sandboxForce = game.forces[Sandbox.NameFromForce(force)]
if sandboxForce then
log("New Research Queued: " .. event.research.name .. " from " .. force.name .. " -> " .. sandboxForce.name)
Research.SyncQueue(force, sandboxForce)
end
end
end
end
return Research

View File

@ -0,0 +1,97 @@
-- Custom Planners to add/remove Resources
local Resources = {}
Resources.name = BPSB.pfx .. "sandbox-resources"
Resources.pfx = BPSB.pfx .. "sbr-"
local pfxLength = string.len(Resources.pfx)
Resources.nameScalar = { default = 1 }
Resources.nameScalar["crude-oil"] = 1
Resources.nameScalar["mineral-water"] = 5
Resources.categoryScalar = { default = 10000 }
Resources.categoryScalar["basic-fluid"] = 1
Resources.categoryScalar["basic-solid"] = 10000
Resources.categoryScalar["oil"] = 1
Resources.categoryScalar["hard-resource"] = 8000
Resources.categoryScalar["kr-quarry"] = 1500
-- Whether the Thing is a Resource Planner
function Resources.IsResourcePlanner(name)
return string.sub(name, 1, pfxLength) == Resources.pfx
end
-- Extract the Resource Name from a Resource Planner
function Resources.GetResourceName(name)
return string.sub(name, pfxLength + 1)
end
-- Determine the amount to spawn for a Resource Planner
function Resources.GetResourceAmount(resourceName)
local resourcePrototype = game.entity_prototypes[resourceName]
local nameScalar = Resources.nameScalar[resourceName] or Resources.nameScalar["default"]
local categoryScalar = Resources.categoryScalar[resourcePrototype.resource_category] or Resources.categoryScalar["default"]
local richness = 1
local autoplace_controls = game.surfaces["nauvis"].map_gen_settings.autoplace_controls[resourceName]
if autoplace_controls then
richness = autoplace_controls.richness
if richness < 0 then richness = 1
else richness = math.max(0.5, richness)
end
end
local normal = resourcePrototype.normal_resource_amount
local minimum = resourcePrototype.minimum_resource_amount
return nameScalar * categoryScalar * richness * math.max(normal, minimum)
end
-- Determine how often to spawn for a Resource Planner
function Resources.GetResourceSpacing(resourceName)
local box = game.entity_prototypes[resourceName].map_generator_bounding_box
return {
x = math.max(1, math.ceil(box.right_bottom.x - box.left_top.x)),
y = math.max(1, math.ceil(box.right_bottom.y - box.left_top.y)),
}
end
-- Add Resources when a Resource Planner is used
function Resources.OnAreaSelectedForAdd(event)
local resourceName = Resources.GetResourceName(event.item)
local density = Resources.GetResourceAmount(resourceName)
local spacing = Resources.GetResourceSpacing(resourceName)
for x = event.area.left_top.x, event.area.right_bottom.x, spacing.x do
for y = event.area.left_top.y, event.area.right_bottom.y, spacing.y do
event.surface.create_entity({
name = resourceName,
position = { x = x, y = y },
amount = density,
raise_built = true,
})
end
end
end
-- Removed Resources when a Resource Planner is used
function Resources.OnAreaSelectedForRemove(event)
for _, entity in pairs(event.entities) do
entity.destroy({ raise_destroy = true })
end
end
-- Add/Remove Resources when a Resource Planner is used
function Resources.OnAreaSelected(event, add)
if (Lab.IsLab(event.surface) or SpaceExploration.IsSandbox(event.surface))
and Resources.IsResourcePlanner(event.item)
then
if add then
Resources.OnAreaSelectedForAdd(event)
else
Resources.OnAreaSelectedForRemove(event)
end
end
end
return Resources

View File

@ -0,0 +1,402 @@
-- Managing multiple Sandboxes for each Player/Force
local Sandbox = {}
Sandbox.pfx = BPSB.pfx .. "sb-"
-- GUI Dropdown items for Sandboxes
Sandbox.choices = {
{ "sandbox." .. Sandbox.pfx .. "player-lab" },
{ "sandbox." .. Sandbox.pfx .. "force-lab" },
{ "sandbox." .. Sandbox.pfx .. "force-lab-space-exploration" },
{ "sandbox." .. Sandbox.pfx .. "force-orbit-space-exploration" },
}
if not SpaceExploration.enabled then
Sandbox.choices[3] = { "sandbox." .. Sandbox.pfx .. "space-exploration-disabled" }
Sandbox.choices[4] = { "sandbox." .. Sandbox.pfx .. "space-exploration-disabled" }
end
-- Constants to represent indexes for Sandbox.choices
Sandbox.player = 1
Sandbox.force = 2
Sandbox.forcePlanetaryLab = 3
Sandbox.forceOrbitalSandbox = 4
-- A unique per-Force Sandbox Name
function Sandbox.NameFromForce(force)
return Sandbox.pfx .. "f-" .. force.name
end
-- Whether the Force is specific to Blueprint Sandboxes
function Sandbox.IsSandboxForce(force)
-- return string.sub(force.name, 1, pfxLength) == Sandbox.pfx
return not not global.sandboxForces[force.name]
end
-- Whether something is any type of Sandbox
function Sandbox.IsSandbox(thingWithName)
return Lab.IsLab(thingWithName)
or SpaceExploration.IsSandbox(thingWithName)
end
-- Whether something is any type of Sandbox
function Sandbox.IsPlayerInsideSandbox(player)
return global.players[player.index].preSandboxPosition ~= nil
and Sandbox.IsSandbox(player.surface)
end
-- Whether a Sandbox choice is allowed
function Sandbox.IsEnabled(selectedSandbox)
if selectedSandbox == Sandbox.player then
return true
elseif selectedSandbox == Sandbox.force then
return true
elseif selectedSandbox == Sandbox.forceOrbitalSandbox then
return SpaceExploration.enabled
elseif selectedSandbox == Sandbox.forcePlanetaryLab then
return SpaceExploration.enabled
else
log("Impossible Choice for Sandbox: " .. selectedSandbox)
return false
end
end
-- Which Surface Name to use for this Player based on their Selected Sandbox
function Sandbox.GetOrCreateSandboxSurface(player, sandboxForce)
local playerData = global.players[player.index]
if playerData.selectedSandbox == Sandbox.player
then
return Lab.GetOrCreateSurface(playerData.labName, sandboxForce)
elseif playerData.selectedSandbox == Sandbox.force
then
return Lab.GetOrCreateSurface(global.sandboxForces[sandboxForce.name].labName, sandboxForce)
elseif SpaceExploration.enabled
and playerData.selectedSandbox == Sandbox.forceOrbitalSandbox
then
return SpaceExploration.GetOrCreateOrbitalSurfaceForForce(player, sandboxForce)
elseif SpaceExploration.enabled
and playerData.selectedSandbox == Sandbox.forcePlanetaryLab
then
return SpaceExploration.GetOrCreatePlanetarySurfaceForForce(player, sandboxForce)
else
log("Impossible Choice for Sandbox: " .. playerData.selectedSandbox)
return
end
end
-- Convert the Player to God-mode, save their previous State, and enter Selected Sandbox
function Sandbox.Enter(player)
local playerData = global.players[player.index]
if Sandbox.IsPlayerInsideSandbox(player) then
log("Already inside Sandbox: " .. playerData.insideSandbox)
return
end
if player.stashed_controller_type
and player.stashed_controller_type ~= defines.controllers.editor
then
player.print("You are already detached from your Character, so you cannot enter a Sandbox. Return to your Character first.")
return
end
local sandboxForce = Force.GetOrCreateSandboxForce(game.forces[playerData.forceName])
local surface = Sandbox.GetOrCreateSandboxSurface(player, sandboxForce)
if surface == nil then
log("Completely Unknown Sandbox Surface, cannot use")
return
end
log("Entering Sandbox: " .. surface.name)
-- Store some temporary State to use once inside the Sandbox
local inputBlueprint = Inventory.GetCursorBlueprintString(player)
--[[
Otherwise, there is a Factorio "bug" that can destroy what was in the Cursor.
It seems to happen with something from the Inventory being in the Stack, then
entering the Sandbox, then copying something from the Sandbox, then exiting the
Sandbox. At this point, the Cursor Stack is still fine and valid, but it seems
to have lost its original location, so "clearing" it out will destroy it.
]]
player.clear_cursor()
-- Store the Player's previous State (that must be referenced to Exit)
playerData.insideSandbox = playerData.selectedSandbox
playerData.preSandboxForceName = player.force.name
playerData.preSandboxCharacter = player.character
playerData.preSandboxController = player.controller_type
playerData.preSandboxPosition = player.position
playerData.preSandboxSurfaceName = player.surface.name
playerData.preSandboxCheatMode = player.cheat_mode
-- Sometimes a Player has a volatile Inventory that needs restoring later
if Inventory.ShouldPersist(playerData.preSandboxController) then
playerData.preSandboxInventory = Inventory.Persist(
player.get_main_inventory(),
playerData.preSandboxInventory
)
else
if playerData.preSandboxInventory then
playerData.preSandboxInventory.destroy()
playerData.preSandboxInventory = nil
end
end
-- Harmlessly detach the Player from their Character
player.set_controller({ type = defines.controllers.god })
-- Harmlessly teleport their God-body to the Sandbox
player.teleport(playerData.lastSandboxPositions[surface.name] or { 0, 0 }, surface)
-- Swap to the new Force; it has different bonuses!
player.force = sandboxForce
-- Since the Sandbox might have Cheat Mode enabled, EditorExtensions won't receive an Event for this otherwise
if player.cheat_mode then
player.cheat_mode = false
end
-- Enable Cheat mode _afterwards_, since EditorExtensions will alter the Force (now the Sandbox Force) based on this
player.cheat_mode = true
-- Harmlessly ensure our own Recipes are enabled
-- TODO: It's unclear why this must happen _after_ the above code
Research.EnableSandboxSpecificResearch(sandboxForce)
-- Now that everything has taken effect, restoring the Inventory is safe
Inventory.Restore(
playerData.sandboxInventory,
player.get_main_inventory()
)
-- Then, restore the Blueprint in the Cursor
if inputBlueprint then
player.cursor_stack.import_stack(inputBlueprint)
player.cursor_stack_temporary = true
end
end
-- Convert the Player to their previous State, and leave Selected Sandbox
function Sandbox.Exit(player)
local playerData = global.players[player.index]
if not Sandbox.IsPlayerInsideSandbox(player) then
log("Already outside Sandbox")
return
end
log("Exiting Sandbox: " .. player.surface.name)
-- Store some temporary State to use once outside the Sandbox
local outputBlueprint = Inventory.GetCursorBlueprintString(player)
-- Remember where they left off
playerData.lastSandboxPositions[player.surface.name] = player.position
-- Attach the Player back to their original Character (also changes force)
Sandbox.RecoverPlayerCharacter(player, playerData)
-- Swap to their original Force (in case they're not sent back to a Character)
player.force = playerData.preSandboxForceName
-- Toggle Cheat mode _afterwards_, just in case EditorExtensions ever listens to this Event
player.cheat_mode = playerData.preSandboxCheatMode or false
-- Sometimes a Player is already a God (like in Sandbox), and their Inventory wasn't on a body
if Inventory.ShouldPersist(playerData.preSandboxController) then
Inventory.Restore(
playerData.preSandboxInventory,
player.get_main_inventory()
)
end
-- Reset the Player's previous State
playerData.insideSandbox = nil
playerData.preSandboxForceName = nil
playerData.preSandboxCharacter = nil
playerData.preSandboxController = nil
playerData.preSandboxPosition = nil
playerData.preSandboxSurfaceName = nil
playerData.preSandboxCheatMode = nil
if playerData.preSandboxInventory then
playerData.preSandboxInventory.destroy()
playerData.preSandboxInventory = nil
end
-- Potentially, restore the Blueprint in the Cursor
if outputBlueprint and Inventory.WasCursorSafelyCleared(player) then
player.cursor_stack.import_stack(outputBlueprint)
player.cursor_stack_temporary = true
end
end
-- Ensure the Player has a Character to go back to
function Sandbox.RecoverPlayerCharacter(player, playerData)
-- Typical situation, there wasn't a Character, or there was a valid one
if (not playerData.preSandboxCharacter) or playerData.preSandboxCharacter.valid then
player.teleport(playerData.preSandboxPosition, playerData.preSandboxSurfaceName)
player.set_controller({
type = playerData.preSandboxController,
character = playerData.preSandboxCharacter
})
return
end
-- Space Exploration deletes and recreates Characters; check that out next
local fromSpaceExploration = SpaceExploration.GetPlayerCharacter(player)
if fromSpaceExploration and fromSpaceExploration.valid then
player.teleport(fromSpaceExploration.position, fromSpaceExploration.surface.name)
player.set_controller({
type = defines.controllers.character,
character = fromSpaceExploration
})
return
end
-- We might at-least have a Surface to go back to
if playerData.preSandboxSurfaceName and game.surfaces[playerData.preSandboxSurfaceName] then
player.print("Unfortunately, your previous Character was lost, so it had to be recreated.")
player.teleport(playerData.preSandboxPosition, playerData.preSandboxSurfaceName)
local recreated = game.surfaces[playerData.preSandboxSurfaceName].create_entity {
name = "character",
position = playerData.preSandboxPosition,
force = playerData.preSandboxForceName,
raise_built = true,
}
player.set_controller({
type = playerData.preSandboxController,
character = recreated
})
return
end
-- Otherwise, we need a completely clean slate :(
player.print("Unfortunately, your previous Character was completely lost, so you must start anew.")
player.teleport({ 0, 0 }, "nauvis")
local recreated = game.surfaces["nauvis"].create_entity {
name = "character",
position = { 0, 0 },
force = playerData.preSandboxForceName,
raise_built = true,
}
player.set_controller({
type = playerData.preSandboxController,
character = recreated
})
end
-- Keep a Player's God-state, but change between Selected Sandboxes
function Sandbox.Transfer(player)
local playerData = global.players[player.index]
if not Sandbox.IsPlayerInsideSandbox(player) then
log("Outside Sandbox, cannot transfer")
return
end
local sandboxForce = Force.GetOrCreateSandboxForce(game.forces[playerData.forceName])
local surface = Sandbox.GetOrCreateSandboxSurface(player, sandboxForce)
if surface == nil then
log("Completely Unknown Sandbox Surface, cannot use")
return
end
log("Transferring to Sandbox: " .. surface.name)
playerData.lastSandboxPositions[player.surface.name] = player.position
player.teleport(playerData.lastSandboxPositions[surface.name] or { 0, 0 }, surface)
playerData.insideSandbox = playerData.selectedSandbox
end
-- Update Sandboxes Player if a Player actually changes Forces (outside of this mod)
function Sandbox.OnPlayerForceChanged(player)
local playerData = global.players[player.index]
local force = player.force
if not Sandbox.IsSandboxForce(force)
and playerData.forceName ~= force.name
then
log("Storing changed Player's Force: " .. player.name .. " -> " .. force.name)
playerData.forceName = force.name
local sandboxForceName = Sandbox.NameFromForce(force)
playerData.sandboxForceName = sandboxForceName
local labData = global.labSurfaces[playerData.labName]
if labData then
labData.sandboxForceName = sandboxForceName
end
local labForce = Force.GetOrCreateSandboxForce(force)
if Sandbox.IsPlayerInsideSandbox(player) then
if Sandbox.GetSandboxChoiceFor(player, player.surface) ~= Sandbox.player then
player.print("Your Force changed, so you have been removed from a Sandbox that you are no longer allowed in")
playerData.preSandboxForceName = force.name
Sandbox.Exit(player)
else
player.force = labForce
end
end
local labSurface = game.surfaces[Lab.NameFromPlayer(player)]
if labSurface then
Lab.AssignEntitiesToForce(labSurface, labForce)
end
end
end
-- Determine whether the Player is inside a known Sandbox
function Sandbox.GetSandboxChoiceFor(player, surface)
local playerData = global.players[player.index]
if surface.name == playerData.labName then
return Sandbox.player
elseif surface.name == global.sandboxForces[playerData.sandboxForceName].labName then
return Sandbox.force
elseif surface.name == global.sandboxForces[playerData.sandboxForceName].seOrbitalSandboxZoneName then
return Sandbox.forceOrbitalSandbox
elseif surface.name == global.sandboxForces[playerData.sandboxForceName].sePlanetaryLabZoneName then
return Sandbox.forcePlanetaryLab
elseif Factorissimo.IsFactory(surface) then
local outsideSurface = Factorissimo.GetOutsideSurfaceForFactory(
surface,
player.position
)
if outsideSurface then
return Sandbox.GetSandboxChoiceFor(player, outsideSurface)
end
end
return nil
end
-- Update whether the Player is inside a known Sandbox
function Sandbox.OnPlayerSurfaceChanged(player)
if Sandbox.IsPlayerInsideSandbox(player) then
global.players[player.index].insideSandbox = Sandbox.GetSandboxChoiceFor(player, player.surface)
end
end
-- Enter, Exit, or Transfer a Player across Sandboxes
function Sandbox.Toggle(player_index)
local player = game.players[player_index]
local playerData = global.players[player.index]
if Factorissimo.IsFactoryInsideSandbox(player.surface, player.position) then
player.print("You are inside of a Factory, so you cannot change Sandboxes")
return
end
if not Sandbox.IsEnabled(playerData.selectedSandbox) then
playerData.selectedSandbox = Sandbox.player
end
if Sandbox.IsPlayerInsideSandbox(player)
and playerData.insideSandbox ~= playerData.selectedSandbox
then
Sandbox.Transfer(player)
elseif Sandbox.IsPlayerInsideSandbox(player) then
Sandbox.Exit(player)
else
SpaceExploration.ExitRemoteView(player)
Sandbox.Enter(player)
end
end
return Sandbox

View File

@ -0,0 +1,51 @@
local Settings = {}
Settings.scanSandboxes = BPSB.pfx .. "scan-all-chunks"
Settings.allowAllTech = BPSB.pfx .. "allow-all-technology"
Settings.onlyAdminsForceReset = BPSB.pfx .. "only-admins-force-reset"
Settings.craftToCursor = BPSB.pfx .. "craft-to-cursor"
Settings.bonusInventorySlots = BPSB.pfx .. "bonus-inventory-slots"
Settings.extraMiningSpeed = BPSB.pfx .. "extra-mining-speed"
Settings.extraLabSpeed = BPSB.pfx .. "extra-lab-speed"
Settings.godAsyncTick = BPSB.pfx .. "god-async-tick"
Settings.godAsyncCreateRequestsPerTick = BPSB.pfx .. "god-async-create-per-tick"
Settings.godAsyncUpgradeRequestsPerTick = BPSB.pfx .. "god-async-upgrade-per-tick"
Settings.godAsyncDeleteRequestsPerTick = BPSB.pfx .. "god-async-delete-per-tick"
function Settings.SetupScanSandboxes()
if settings.global[Settings.scanSandboxes].value then
script.on_nth_tick(Lab.chartAllLabsTick, God.ChartAllOccupiedSandboxes)
else
script.on_nth_tick(Lab.chartAllLabsTick, nil)
end
end
function Settings.SetupConditionalHandlers()
Settings.SetupScanSandboxes()
script.on_nth_tick(settings.global[Settings.godAsyncTick].value, God.HandleAllSandboxRequests)
end
function Settings.OnRuntimeSettingChanged(event)
if event.setting == Settings.scanSandboxes then
Settings.SetupScanSandboxes()
elseif event.setting == Settings.allowAllTech then
Research.SyncAllForces()
elseif event.setting == Settings.onlyAdminsForceReset then
for _, player in pairs(game.players) do
ToggleGUI.Update(player)
end
elseif event.setting == Settings.bonusInventorySlots then
Force.SyncAllForces()
elseif event.setting == Settings.extraMiningSpeed then
Force.SyncAllForces()
elseif event.setting == Settings.extraLabSpeed then
Force.SyncAllForces()
elseif event.setting == Settings.godAsyncTick then
local newValue = settings.global[Settings.godAsyncTick].value
script.on_nth_tick(global.lastSettingForAsyncGodTick, nil)
script.on_nth_tick(newValue, God.HandleAllSandboxRequests)
global.lastSettingForAsyncGodTick = newValue
end
end
return Settings

View File

@ -0,0 +1,328 @@
-- Space Exploration related functionality
local SpaceExploration = {}
SpaceExploration.name = "space-exploration"
SpaceExploration.enabled = not not remote.interfaces[SpaceExploration.name]
SpaceExploration.orbitalEquipmentString = "0eNqllt2OgyAQhd9lrqEB2m5bX6XZGLWjS4JoAJt1G999wW5M07T7N94hzDfDzDkJFyjNgL3TNkB2AV111kN2vIDXjS1M+hfGHiEDHbAFBrZo0woNVsHpiqNF14w8xqOriwphYqDtCd8hkxP7kVPqhi+svjO38Wp6ZYA26KDxWtS8GHM7tCW6mOAbDIO+8zGysyl3pAkGI2RcTamqO5D6xb2eAEWstxzqGl3u9UdESLF8DzKtl0za1trGLV69oQ8P8KvtnECttnNLrqdzjyFo2/h0ymHbnTEf4p6JZeIpT82NW8ENOKXuBW2+WndH5+rajs20FOSR+z7elPemCHXnWu6roq47c4J0kSeANRWgqABJBAhiPDU/tQH/n4CkakBSNSCpGpBUDUiiBiRRA5KoAUnTgCBKQBAVIIgCEMT5C9r4BW36gjZ8QZs91f5U91PNT/U+0fpE5xON/yffx5fA/OzKbl57DM7o/AxUe7nZHdTu5aDEfh1fSJ/ZLl9g"
-- Whether the Surface has been taken as a Space Sandbox
function SpaceExploration.IsSandbox(surface)
return SpaceExploration.enabled
and global.seSurfaces[surface.name]
end
-- Whether the Surface has been taken as a Planetary Lab Sandbox
function SpaceExploration.IsPlanetarySandbox(surface)
return SpaceExploration.enabled
and global.seSurfaces[surface.name]
and not global.seSurfaces[surface.name].orbital
end
-- Whether the Zone is Star
function SpaceExploration.IsStar(zoneName)
if not SpaceExploration.enabled then
return false
end
return remote.call(SpaceExploration.name, "get_zone_from_name", {
zone_name = zoneName,
}).type == "star"
end
-- Ask Space Exploration for the Player's current Character
function SpaceExploration.GetPlayerCharacter(player)
if not SpaceExploration.enabled then
return
end
return remote.call(SpaceExploration.name, "get_player_character", {
player = player,
})
end
-- Whether the Sandbox might have Biters falling
function SpaceExploration.IsZoneThreatening(zone)
return (zone.type == "planet" or zone.type == "moon")
and zone.controls
and zone.controls["se-vitamelange"]
and zone.controls["se-vitamelange"].richness > 0
end
-- Walk Parent Indexes to find the Root Zone (Star)
function SpaceExploration.GetRootZone(zoneIndex, zone)
local rootZone = zone
while rootZone.parent_index do
rootZone = zoneIndex[rootZone.parent_index]
end
return rootZone
end
-- Chooses a non-home-system Star or Moon for a Force's Space Sandbox, if necessary
-- Notably, Star _Orbits_ are "usable" Zones, but not Stars themselves
-- In other words, these should be completely safe and invisible outside of this mod!
-- Moons, on the other hand, will take a valuable resource away from the player
-- We also carefully choose Moons in order to not take away too much from them,
-- and to not be too dangerous.
function SpaceExploration.ChooseZoneForForce(player, sandboxForce, type)
if not SpaceExploration.enabled then
return
end
local zoneIndex = remote.call(SpaceExploration.name, "get_zone_index", {})
for _, zone in pairs(zoneIndex) do
if zone.type == type
and not zone.is_homeworld
and not zone.ruins
and not zone.glyph
and zone.special_type ~= "homesystem"
and not global.seSurfaces[zone.name]
then
local rootZone = SpaceExploration.GetRootZone(zoneIndex, zone)
if not SpaceExploration.IsZoneThreatening(zone)
and rootZone.special_type ~= "homesystem"
then
log("Choosing SE Zone " .. zone.name .. " as Sandbox for " .. sandboxForce.name)
return zone.name
end
end
end
end
function SpaceExploration.GetOrCreateSurface(zoneName)
if not SpaceExploration.enabled then
return
end
return remote.call(SpaceExploration.name, "zone_get_make_surface", {
zone_index = remote.call(SpaceExploration.name, "get_zone_from_name", {
zone_name = zoneName,
}).index,
})
end
-- Chooses a non-home-system Star for a Force's Space Sandbox, if necessary
function SpaceExploration.GetOrCreatePlanetarySurfaceForForce(player, sandboxForce)
if not SpaceExploration.enabled then
return
end
local zoneName = global.sandboxForces[sandboxForce.name].sePlanetaryLabZoneName
if zoneName == nil then
zoneName = SpaceExploration.ChooseZoneForForce(player, sandboxForce, "moon")
global.sandboxForces[sandboxForce.name].sePlanetaryLabZoneName = zoneName
global.seSurfaces[zoneName] = {
sandboxForceName = sandboxForce.name,
equipmentBlueprints = Equipment.Init(Lab.equipmentString),
daytime = 0.95,
orbital = false,
}
end
return SpaceExploration.GetOrCreateSurface(zoneName)
end
-- Chooses a non-home-system Star for a Force's Planetary Sandbox, if necessary
function SpaceExploration.GetOrCreateOrbitalSurfaceForForce(player, sandboxForce)
if not SpaceExploration.enabled then
return
end
local zoneName = global.sandboxForces[sandboxForce.name].seOrbitalSandboxZoneName
if zoneName == nil then
zoneName = SpaceExploration.ChooseZoneForForce(player, sandboxForce, "star")
global.sandboxForces[sandboxForce.name].seOrbitalSandboxZoneName = zoneName
global.seSurfaces[zoneName] = {
sandboxForceName = sandboxForce.name,
equipmentBlueprints = Equipment.Init(SpaceExploration.orbitalEquipmentString),
daytime = 0.95,
orbital = true,
}
end
return SpaceExploration.GetOrCreateSurface(zoneName)
end
-- Set a Sandbox's Daytime to a specific value
function SpaceExploration.SetDayTime(player, surface, daytime)
if SpaceExploration.IsSandbox(surface) then
surface.freeze_daytime = true
surface.daytime = daytime
global.seSurfaces[surface.name].daytime = daytime
Events.SendDaylightChangedEvent(player.index, surface.name, daytime)
return true
else
return false
end
end
-- Reset the Sandbox's equipment Blueprint for a Surface
function SpaceExploration.ResetEquipmentBlueprint(surface)
if not SpaceExploration.enabled then
return
end
if SpaceExploration.IsSandbox(surface) then
log("Resetting SE equipment: " .. surface.name)
if global.seSurfaces[surface.name].orbital then
Equipment.Set(
global.seSurfaces[surface.name].equipmentBlueprints,
SpaceExploration.orbitalEquipmentString
)
else
Equipment.Set(
global.seSurfaces[surface.name].equipmentBlueprints,
Lab.equipmentString
)
end
surface.print("The equipment Blueprint for this Sandbox has been reset")
return true
else
log("Not a SE Sandbox, won't Reset equipment: " .. surface.name)
return false
end
end
-- Set the Sandbox's equipment Blueprint for a Surface
function SpaceExploration.SetEquipmentBlueprint(surface, equipmentString)
if not SpaceExploration.enabled then
return
end
if SpaceExploration.IsSandbox(surface) then
log("Setting SE equipment: " .. surface.name)
Equipment.Set(
global.seSurfaces[surface.name].equipmentBlueprints,
equipmentString
)
surface.print("The equipment Blueprint for this Sandbox has been changed")
return true
else
log("Not a SE Sandbox, won't Set equipment: " .. surface.name)
return false
end
end
-- Reset the Space Sandbox a Player is currently in
function SpaceExploration.Reset(player)
if not SpaceExploration.enabled then
return
end
if SpaceExploration.IsSandbox(player.surface) then
log("Resetting SE Sandbox: " .. player.surface.name)
player.teleport({ 0, 0 }, player.surface.name)
player.surface.clear(false)
return true
else
log("Not a SE Sandbox, won't Reset: " .. player.surface.name)
return false
end
end
-- Return a Sandbox to the available Zones
function SpaceExploration.PreDeleteSandbox(sandboxForceData, zoneName)
if not SpaceExploration.enabled or not zoneName then
return
end
if global.seSurfaces[zoneName] then
log("Pre-Deleting SE Sandbox: " .. zoneName)
local equipmentBlueprints = global.seSurfaces[zoneName].equipmentBlueprints
if equipmentBlueprints and equipmentBlueprints.valid() then
equipmentBlueprints.destroy()
end
global.seSurfaces[zoneName] = nil
if sandboxForceData.sePlanetaryLabZoneName == zoneName then
sandboxForceData.sePlanetaryLabZoneName = nil
end
if sandboxForceData.seOrbitalSandboxZoneName == zoneName then
sandboxForceData.seOrbitalSandboxZoneName = nil
end
else
log("Not a SE Sandbox, won't Pre-Delete: " .. zoneName)
end
end
-- Delete a Space Sandbox and return it to the available Zones
function SpaceExploration.DeleteSandbox(sandboxForceData, zoneName)
if not SpaceExploration.enabled or not zoneName then
return
end
if global.seSurfaces[zoneName] then
SpaceExploration.PreDeleteSandbox(sandboxForceData, zoneName)
log("Deleting SE Sandbox: " .. zoneName)
game.delete_surface(zoneName)
return true
else
log("Not a SE Sandbox, won't Delete: " .. zoneName)
return false
end
end
-- Set some important Surface settings for Space Sandbox
function SpaceExploration.AfterCreate(surface)
if not SpaceExploration.enabled then
return
end
local surfaceData = global.seSurfaces[surface.name]
if not surfaceData then
log("Not a SE Sandbox, won't handle Creation: " .. surface.name)
return false
end
log("Handling Creation of SE Sandbox: " .. surface.name)
surface.freeze_daytime = true
surface.daytime = surfaceData.daytime
surface.show_clouds = false
if (surfaceData.orbital) then
surface.generate_with_lab_tiles = false
else
surface.generate_with_lab_tiles = true
end
return true
end
-- Add some helpful initial Entities to a Space Sandbox
function SpaceExploration.Equip(surface)
if not SpaceExploration.enabled then
return
end
local surfaceData = global.seSurfaces[surface.name]
if not surfaceData then
log("Not a SE Sandbox, won't Equip: " .. surface.name)
return false
end
log("Equipping SE Sandbox: " .. surface.name)
if not surfaceData.orbital then
surface.generate_with_lab_tiles = true
end
Equipment.Place(
surfaceData.equipmentBlueprints[1],
surface,
surfaceData.sandboxForceName
)
return true
end
--[[ Ensure that NavSat is not active
NOTE: This was not necessary in SE < 0.5.109 (the NavSat QoL Update)
Now, without this, the Inventory-differences after entering a Sandbox while
in the Navigation Satellite would be persisted, and without any good way
to undo that override.
--]]
function SpaceExploration.ExitRemoteView(player)
if not SpaceExploration.enabled then
return
end
remote.call(SpaceExploration.name, "remote_view_stop", { player = player })
end
return SpaceExploration

View File

@ -0,0 +1,18 @@
-- Custom Planners to add/remove Resources
local Tiles = {}
Tiles.name = BPSB.pfx .. "sandbox-tiles"
Tiles.pfx = BPSB.pfx .. "sbt-"
local pfxLength = string.len(Tiles.pfx)
-- Whether the Thing is a Tile Planner
function Tiles.IsTilePlanner(name)
return string.sub(name, 1, pfxLength) == Tiles.pfx
end
-- Extract the Resource Name from a Tile Planner
function Tiles.GetResourceName(name)
return string.sub(name, pfxLength + 1)
end
return Tiles

View File

@ -0,0 +1,186 @@
local ToggleGUI = {}
ToggleGUI.name = BPSB.pfx .. "toggle-gui"
ToggleGUI.pfx = ToggleGUI.name .. "-"
ToggleGUI.toggleShortcut = ToggleGUI.pfx .. "sb-toggle-shortcut"
ToggleGUI.selectedSandboxDropdown = ToggleGUI.pfx .. "selected-sandbox-dropdown"
ToggleGUI.resetButton = ToggleGUI.pfx .. "reset-button"
ToggleGUI.daytimeSlider = ToggleGUI.pfx .. "daytime-slider"
function ToggleGUI.Init(player)
if player.gui.left[ToggleGUI.name] then
return
end
local frame = player.gui.left.add {
type = "frame",
name = ToggleGUI.name,
caption = { "gui." .. ToggleGUI.name },
visible = false,
}
local innerFrame = frame.add {
type = "frame",
name = "innerFrame",
direction = "vertical",
style = "inside_shallow_frame_with_padding",
}
local topLineFlow = innerFrame.add {
type = "flow",
name = "topLineFlow",
direction = "horizontal",
style = BPSB.pfx .. "centered-horizontal-flow",
}
topLineFlow.add {
type = "sprite-button",
name = ToggleGUI.resetButton,
tooltip = { "gui-description." .. ToggleGUI.resetButton },
style = "tool_button",
sprite = "utility/reset_white",
}
topLineFlow.add {
type = "drop-down",
name = ToggleGUI.selectedSandboxDropdown,
tooltip = { "gui-description." .. ToggleGUI.selectedSandboxDropdown },
items = Sandbox.choices,
selected_index = global.players[player.index].selectedSandbox,
}.style.horizontally_stretchable = true
local daylightFlow = innerFrame.add {
type = "flow",
name = "daylightFlow",
direction = "horizontal",
style = BPSB.pfx .. "centered-horizontal-flow",
}
daylightFlow.add {
type = "sprite",
tooltip = { "gui-description." .. ToggleGUI.daytimeSlider },
sprite = "utility/select_icon_white",
resize_to_sprite = false,
style = BPSB.pfx .. "sprite-like-tool-button",
}
daylightFlow.add {
type = "slider",
name = ToggleGUI.daytimeSlider,
value = 0.0,
minimum_value = 0.5,
maximum_value = 0.975,
value_step = 0.025,
style = "notched_slider",
}.style.horizontally_stretchable = true
ToggleGUI.Update(player)
end
function ToggleGUI.Destroy(player)
if not player.gui.left[ToggleGUI.name] then
return
end
player.gui.left[ToggleGUI.name].destroy()
end
function ToggleGUI.FindDescendantByName(instance, name)
for _, child in pairs(instance.children) do
if child.name == name then
return child
end
local found = ToggleGUI.FindDescendantByName(child, name)
if found then return found end
end
end
function ToggleGUI.FindByName(player, name)
return ToggleGUI.FindDescendantByName(player.gui.left[ToggleGUI.name], name)
end
function ToggleGUI.Update(player)
if not player.gui.left[ToggleGUI.name] then
return
end
ToggleGUI.FindByName(player, ToggleGUI.selectedSandboxDropdown).selected_index = global.players[player.index].selectedSandbox
if Sandbox.IsPlayerInsideSandbox(player) then
local playerData = global.players[player.index]
player.set_shortcut_toggled(ToggleGUI.toggleShortcut, true)
player.gui.left[ToggleGUI.name].visible = true
local resetButton = ToggleGUI.FindByName(player, ToggleGUI.resetButton)
if game.is_multiplayer
and not player.admin
and playerData.selectedSandbox ~= Sandbox.player
and settings.global[Settings.onlyAdminsForceReset].value
then
resetButton.enabled = false
resetButton.tooltip = { "gui-description." .. ToggleGUI.resetButton .. "-only-admins" }
else
resetButton.enabled = true
resetButton.tooltip = { "gui-description." .. ToggleGUI.resetButton }
end
ToggleGUI.FindByName(player, ToggleGUI.daytimeSlider).slider_value = player.surface.daytime
else
player.set_shortcut_toggled(ToggleGUI.toggleShortcut, false)
player.gui.left[ToggleGUI.name].visible = false
ToggleGUI.FindByName(player, ToggleGUI.resetButton).enabled = false
end
end
function ToggleGUI.OnGuiValueChanged(event)
local player = game.players[event.player_index]
if event.element.name == ToggleGUI.daytimeSlider then
local daytime = event.element.slider_value
return Lab.SetDayTime(player, player.surface, daytime)
or SpaceExploration.SetDayTime(player, player.surface, daytime)
end
end
function ToggleGUI.OnGuiDropdown(event)
local player = game.players[event.player_index]
if event.element.name == ToggleGUI.selectedSandboxDropdown then
local choice = event.element.selected_index
if Sandbox.IsEnabled(choice) then
global.players[player.index].selectedSandbox = event.element.selected_index
Sandbox.Toggle(event.player_index)
else
player.print("That Sandbox is not possible.")
event.element.selected_index = global.players[player.index].selectedSandbox
ToggleGUI.Update(player)
end
end
end
function ToggleGUI.OnGuiClick(event)
local player = game.players[event.player_index]
if event.element.name == ToggleGUI.toggleShortcut then
Sandbox.Toggle(event.player_index)
elseif event.element.name == ToggleGUI.resetButton then
if event.shift then
return Lab.ResetEquipmentBlueprint(player.surface)
or SpaceExploration.ResetEquipmentBlueprint(player.surface)
else
local blueprintString = Inventory.GetCursorBlueprintString(player)
if blueprintString then
return Lab.SetEquipmentBlueprint(player.surface, blueprintString)
or SpaceExploration.SetEquipmentBlueprint(player.surface, blueprintString)
else
return Lab.Reset(player)
or SpaceExploration.Reset(player)
end
end
end
end
function ToggleGUI.OnToggleShortcut(event)
if (event.input_name or event.prototype_name) == ToggleGUI.toggleShortcut then
Sandbox.Toggle(event.player_index)
end
end
return ToggleGUI

View File

@ -0,0 +1,4 @@
BPSB = require("scripts.bpsb")
Settings = require("scripts/settings")
require("prototypes.editor-extensions-final-fixes")

View File

@ -0,0 +1,97 @@
BPSB = require("scripts/bpsb")
Settings = require("scripts/settings")
data:extend({
{
type = "bool-setting",
name = Settings.allowAllTech,
setting_type = "runtime-global",
hidden = true,
order = "a[common]-a",
default_value = false,
},
{
type = "bool-setting",
name = Settings.onlyAdminsForceReset,
setting_type = "runtime-global",
order = "a[common]-b",
default_value = false,
},
{
type = "bool-setting",
name = Settings.scanSandboxes,
setting_type = "runtime-global",
order = "a[common]-c",
default_value = false,
},
{
type = "int-setting",
name = Settings.bonusInventorySlots,
setting_type = "runtime-global",
order = "a[common]-d",
default_value = 30,
minimum_value = 0,
maximum_value = 300,
},
{
type = "int-setting",
name = Settings.extraMiningSpeed,
setting_type = "runtime-global",
order = "a[common]-e",
default_value = 1000000000,
minimum_value = 0,
maximum_value = 1000000000,
},
{
type = "double-setting",
name = Settings.extraLabSpeed,
setting_type = "runtime-global",
order = "a[common]-f",
default_value = -0.999,
minimum_value = -0.999,
maximum_value = 10.0,
},
{
type = "bool-setting",
name = Settings.craftToCursor,
setting_type = "runtime-per-user",
order = "a[player]-a",
default_value = true,
},
{
type = "int-setting",
name = Settings.godAsyncTick,
setting_type = "runtime-global",
order = "b[god]-a",
default_value = 15,
minimum_value = 1,
maximum_value = 120,
},
{
type = "int-setting",
name = Settings.godAsyncCreateRequestsPerTick,
setting_type = "runtime-global",
order = "b[god]-b",
default_value = 0,
minimum_value = 0,
maximum_value = 10000,
},
{
type = "int-setting",
name = Settings.godAsyncUpgradeRequestsPerTick,
setting_type = "runtime-global",
order = "b[god]-c",
default_value = 0,
minimum_value = 0,
maximum_value = 10000,
},
{
type = "int-setting",
name = Settings.godAsyncDeleteRequestsPerTick,
setting_type = "runtime-global",
order = "b[god]-d",
default_value = 0,
minimum_value = 0,
maximum_value = 10000,
},
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,4 +1,4 @@
--todo check entity creation ammo_category = "cannon-shell" if so record max dist and update ammo --todo check entity creation ammo_category = "cannon-shell" if so record max dist and update ammo
log("Creating Types") log("Creating Types")
if not heroturrets.defines then require ("prototypes.scripts.defines") end if not heroturrets.defines then require ("prototypes.scripts.defines") end

View File

@ -148,6 +148,11 @@
"enabled": true "enabled": true
}, },
{
"name": "blueprint-sandboxes",
"enabled": true
},
{ {
"name": "blueprint_flip_and_turn", "name": "blueprint_flip_and_turn",
"enabled": true "enabled": true

View File

@ -1,12 +1,12 @@
{ {
"name": "zzzparanoidal", "name": "zzzparanoidal",
"version": "1.1.1", "version": "7.1.1",
"title": "!_Paranoidal", "title": "!_Paranoidal",
"factorio_version": "1.1", "factorio_version": "1.1",
"author": "Sovigod", "author": "Sovigod",
"contact": "https://discord.gg/MnXGAmC", "contact": "https://discord.gg/MnXGAmC",
"homepage": "https://discord.gg/MnXGAmC", "homepage": "https://discord.gg/MnXGAmC",
"description": "Paranoidal core mod for 1.1", "description": "PARANOIDAL 8 - RC1. Core mod for PARANOIDAL\n2024.06",
"dependencies": [ "dependencies": [
"base >= 1.1.100", "base >= 1.1.100",
"boblibrary", "boblibrary",