From 46d69f3a4553dfb8b4cf7ba61c999cdf27f6f3ba Mon Sep 17 00:00:00 2001 From: Aleksei-bird Date: Fri, 16 Aug 2024 10:59:02 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BC=D0=BE=D0=B4=20heroturrets=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BC=D0=BE=D0=B4=20blu?= =?UTF-8?q?eprint-sandboxes=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BC=D0=B5=D1=82=D0=BA=D1=83=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=B8=20=D0=B2=20zzzparanoidal=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blueprint-sandboxes/README.md | 77 ++++ blueprint-sandboxes/changelog.txt | 310 ++++++++++++++ blueprint-sandboxes/control.lua | 237 ++++++++++ blueprint-sandboxes/data-final-fixes.lua | 6 + blueprint-sandboxes/data-updates.lua | 3 + blueprint-sandboxes/data.lua | 7 + .../graphics/choose-sandbox.png | Bin 0 -> 7505 bytes .../graphics/daylight-slider.png | Bin 0 -> 8439 bytes blueprint-sandboxes/graphics/icon-x64.png | Bin 0 -> 338 bytes blueprint-sandboxes/graphics/recipes.png | Bin 0 -> 92343 bytes .../graphics/reset-sandbox.png | Bin 0 -> 28874 bytes blueprint-sandboxes/graphics/toggle-gui.png | Bin 0 -> 4920 bytes blueprint-sandboxes/info.json | 16 + blueprint-sandboxes/license.txt | 21 + .../locale/en/blueprint-sandboxes.cfg | 105 +++++ .../editor-extensions-final-fixes.lua | 7 + .../prototypes/illusions-final-fixes.lua | 47 ++ .../prototypes/item-groups.lua | 38 ++ .../recipes/hidden-vanilla-final-fixes.lua | 26 ++ .../recipes/hidden-vanilla-updates.lua | 44 ++ .../recipes/resources-final-fixes.lua | 79 ++++ .../prototypes/recipes/tiles-final-fixes.lua | 48 +++ blueprint-sandboxes/prototypes/shortcuts.lua | 25 ++ blueprint-sandboxes/prototypes/styles.lua | 18 + .../prototypes/tips-and-tricks.lua | 281 ++++++++++++ blueprint-sandboxes/release.sh | 14 + blueprint-sandboxes/scripts/bpsb.lua | 7 + blueprint-sandboxes/scripts/chat.lua | 22 + blueprint-sandboxes/scripts/debug.lua | 34 ++ .../scripts/editor-extensions-cheats.lua | 15 + blueprint-sandboxes/scripts/equipment.lua | 143 +++++++ blueprint-sandboxes/scripts/events.lua | 13 + blueprint-sandboxes/scripts/factorissimo.lua | 68 +++ blueprint-sandboxes/scripts/force.lua | 149 +++++++ blueprint-sandboxes/scripts/god.lua | 301 +++++++++++++ blueprint-sandboxes/scripts/illusion.lua | 209 +++++++++ blueprint-sandboxes/scripts/init.lua | 50 +++ blueprint-sandboxes/scripts/inventory.lua | 100 +++++ blueprint-sandboxes/scripts/lab.lua | 203 +++++++++ blueprint-sandboxes/scripts/migrate.lua | 404 ++++++++++++++++++ blueprint-sandboxes/scripts/planner-icons.lua | 70 +++ blueprint-sandboxes/scripts/queue.lua | 30 ++ .../scripts/remote-interface.lua | 14 + blueprint-sandboxes/scripts/research.lua | 105 +++++ blueprint-sandboxes/scripts/resources.lua | 97 +++++ blueprint-sandboxes/scripts/sandbox.lua | 402 +++++++++++++++++ blueprint-sandboxes/scripts/settings.lua | 51 +++ .../scripts/space-exploration.lua | 328 ++++++++++++++ blueprint-sandboxes/scripts/tiles.lua | 18 + blueprint-sandboxes/scripts/toggle-gui.lua | 186 ++++++++ blueprint-sandboxes/settings-final-fixes.lua | 4 + blueprint-sandboxes/settings.lua | 97 +++++ blueprint-sandboxes/thumbnail.png | Bin 0 -> 1062 bytes heroturrets/prototypes/scripts/types.lua | 2 +- mod-list.json | 5 + zzzparanoidal/info.json | 4 +- 56 files changed, 4537 insertions(+), 3 deletions(-) create mode 100644 blueprint-sandboxes/README.md create mode 100644 blueprint-sandboxes/changelog.txt create mode 100644 blueprint-sandboxes/control.lua create mode 100644 blueprint-sandboxes/data-final-fixes.lua create mode 100644 blueprint-sandboxes/data-updates.lua create mode 100644 blueprint-sandboxes/data.lua create mode 100644 blueprint-sandboxes/graphics/choose-sandbox.png create mode 100644 blueprint-sandboxes/graphics/daylight-slider.png create mode 100644 blueprint-sandboxes/graphics/icon-x64.png create mode 100644 blueprint-sandboxes/graphics/recipes.png create mode 100644 blueprint-sandboxes/graphics/reset-sandbox.png create mode 100644 blueprint-sandboxes/graphics/toggle-gui.png create mode 100644 blueprint-sandboxes/info.json create mode 100644 blueprint-sandboxes/license.txt create mode 100644 blueprint-sandboxes/locale/en/blueprint-sandboxes.cfg create mode 100644 blueprint-sandboxes/prototypes/editor-extensions-final-fixes.lua create mode 100644 blueprint-sandboxes/prototypes/illusions-final-fixes.lua create mode 100644 blueprint-sandboxes/prototypes/item-groups.lua create mode 100644 blueprint-sandboxes/prototypes/recipes/hidden-vanilla-final-fixes.lua create mode 100644 blueprint-sandboxes/prototypes/recipes/hidden-vanilla-updates.lua create mode 100644 blueprint-sandboxes/prototypes/recipes/resources-final-fixes.lua create mode 100644 blueprint-sandboxes/prototypes/recipes/tiles-final-fixes.lua create mode 100644 blueprint-sandboxes/prototypes/shortcuts.lua create mode 100644 blueprint-sandboxes/prototypes/styles.lua create mode 100644 blueprint-sandboxes/prototypes/tips-and-tricks.lua create mode 100644 blueprint-sandboxes/release.sh create mode 100644 blueprint-sandboxes/scripts/bpsb.lua create mode 100644 blueprint-sandboxes/scripts/chat.lua create mode 100644 blueprint-sandboxes/scripts/debug.lua create mode 100644 blueprint-sandboxes/scripts/editor-extensions-cheats.lua create mode 100644 blueprint-sandboxes/scripts/equipment.lua create mode 100644 blueprint-sandboxes/scripts/events.lua create mode 100644 blueprint-sandboxes/scripts/factorissimo.lua create mode 100644 blueprint-sandboxes/scripts/force.lua create mode 100644 blueprint-sandboxes/scripts/god.lua create mode 100644 blueprint-sandboxes/scripts/illusion.lua create mode 100644 blueprint-sandboxes/scripts/init.lua create mode 100644 blueprint-sandboxes/scripts/inventory.lua create mode 100644 blueprint-sandboxes/scripts/lab.lua create mode 100644 blueprint-sandboxes/scripts/migrate.lua create mode 100644 blueprint-sandboxes/scripts/planner-icons.lua create mode 100644 blueprint-sandboxes/scripts/queue.lua create mode 100644 blueprint-sandboxes/scripts/remote-interface.lua create mode 100644 blueprint-sandboxes/scripts/research.lua create mode 100644 blueprint-sandboxes/scripts/resources.lua create mode 100644 blueprint-sandboxes/scripts/sandbox.lua create mode 100644 blueprint-sandboxes/scripts/settings.lua create mode 100644 blueprint-sandboxes/scripts/space-exploration.lua create mode 100644 blueprint-sandboxes/scripts/tiles.lua create mode 100644 blueprint-sandboxes/scripts/toggle-gui.lua create mode 100644 blueprint-sandboxes/settings-final-fixes.lua create mode 100644 blueprint-sandboxes/settings.lua create mode 100644 blueprint-sandboxes/thumbnail.png diff --git a/blueprint-sandboxes/README.md b/blueprint-sandboxes/README.md new file mode 100644 index 00000000..374e9e3a --- /dev/null +++ b/blueprint-sandboxes/README.md @@ -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 diff --git a/blueprint-sandboxes/changelog.txt b/blueprint-sandboxes/changelog.txt new file mode 100644 index 00000000..d619d089 --- /dev/null +++ b/blueprint-sandboxes/changelog.txt @@ -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 diff --git a/blueprint-sandboxes/control.lua b/blueprint-sandboxes/control.lua new file mode 100644 index 00000000..217a9073 --- /dev/null +++ b/blueprint-sandboxes/control.lua @@ -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) diff --git a/blueprint-sandboxes/data-final-fixes.lua b/blueprint-sandboxes/data-final-fixes.lua new file mode 100644 index 00000000..a7a72d77 --- /dev/null +++ b/blueprint-sandboxes/data-final-fixes.lua @@ -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") diff --git a/blueprint-sandboxes/data-updates.lua b/blueprint-sandboxes/data-updates.lua new file mode 100644 index 00000000..7b8bbc87 --- /dev/null +++ b/blueprint-sandboxes/data-updates.lua @@ -0,0 +1,3 @@ +BPSB = require("scripts.bpsb") + +require("prototypes.recipes.hidden-vanilla-updates") diff --git a/blueprint-sandboxes/data.lua b/blueprint-sandboxes/data.lua new file mode 100644 index 00000000..45e2056e --- /dev/null +++ b/blueprint-sandboxes/data.lua @@ -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") diff --git a/blueprint-sandboxes/graphics/choose-sandbox.png b/blueprint-sandboxes/graphics/choose-sandbox.png new file mode 100644 index 0000000000000000000000000000000000000000..23f43cc2d633dbb652f930cc5457821ab4b69109 GIT binary patch literal 7505 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 OLUc8aG(M?4!2Lg_Qu6@- literal 0 HcmV?d00001 diff --git a/blueprint-sandboxes/graphics/daylight-slider.png b/blueprint-sandboxes/graphics/daylight-slider.png new file mode 100644 index 0000000000000000000000000000000000000000..e1b166f82bb899419265ea0ddbd2d448720ae7ed GIT binary patch literal 8439 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 )z5Fj+)q-HtC|1Q>cb=HlJduxhP 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 Z@CSqu#osl^n2-Pf002ovPDHLkV1k06K@R`` literal 0 HcmV?d00001 diff --git a/blueprint-sandboxes/graphics/icon-x64.png b/blueprint-sandboxes/graphics/icon-x64.png new file mode 100644 index 0000000000000000000000000000000000000000..d560768af7643af7bc6c6d80b9d6b5d44dcaf500 GIT binary patch literal 338 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 N&^6Ji)PDZ= 1.1.87", + "? space-exploration >= 0.5.18", + "? EditorExtensions >= 2.0.0", + "? Cursed-FMD", + "? factorissimo-2-notnotmelon" + ] +} diff --git a/blueprint-sandboxes/license.txt b/blueprint-sandboxes/license.txt new file mode 100644 index 00000000..25511cc6 --- /dev/null +++ b/blueprint-sandboxes/license.txt @@ -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. diff --git a/blueprint-sandboxes/locale/en/blueprint-sandboxes.cfg b/blueprint-sandboxes/locale/en/blueprint-sandboxes.cfg new file mode 100644 index 00000000..02aaebef --- /dev/null +++ b/blueprint-sandboxes/locale/en/blueprint-sandboxes.cfg @@ -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. diff --git a/blueprint-sandboxes/prototypes/editor-extensions-final-fixes.lua b/blueprint-sandboxes/prototypes/editor-extensions-final-fixes.lua new file mode 100644 index 00000000..290911d4 --- /dev/null +++ b/blueprint-sandboxes/prototypes/editor-extensions-final-fixes.lua @@ -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 diff --git a/blueprint-sandboxes/prototypes/illusions-final-fixes.lua b/blueprint-sandboxes/prototypes/illusions-final-fixes.lua new file mode 100644 index 00000000..7d227184 --- /dev/null +++ b/blueprint-sandboxes/prototypes/illusions-final-fixes.lua @@ -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 diff --git a/blueprint-sandboxes/prototypes/item-groups.lua b/blueprint-sandboxes/prototypes/item-groups.lua new file mode 100644 index 00000000..15a324b4 --- /dev/null +++ b/blueprint-sandboxes/prototypes/item-groups.lua @@ -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]", + }, +}) diff --git a/blueprint-sandboxes/prototypes/recipes/hidden-vanilla-final-fixes.lua b/blueprint-sandboxes/prototypes/recipes/hidden-vanilla-final-fixes.lua new file mode 100644 index 00000000..255e1e4f --- /dev/null +++ b/blueprint-sandboxes/prototypes/recipes/hidden-vanilla-final-fixes.lua @@ -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" diff --git a/blueprint-sandboxes/prototypes/recipes/hidden-vanilla-updates.lua b/blueprint-sandboxes/prototypes/recipes/hidden-vanilla-updates.lua new file mode 100644 index 00000000..114b3dac --- /dev/null +++ b/blueprint-sandboxes/prototypes/recipes/hidden-vanilla-updates.lua @@ -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") diff --git a/blueprint-sandboxes/prototypes/recipes/resources-final-fixes.lua b/blueprint-sandboxes/prototypes/recipes/resources-final-fixes.lua new file mode 100644 index 00000000..77d11808 --- /dev/null +++ b/blueprint-sandboxes/prototypes/recipes/resources-final-fixes.lua @@ -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 diff --git a/blueprint-sandboxes/prototypes/recipes/tiles-final-fixes.lua b/blueprint-sandboxes/prototypes/recipes/tiles-final-fixes.lua new file mode 100644 index 00000000..59292bd8 --- /dev/null +++ b/blueprint-sandboxes/prototypes/recipes/tiles-final-fixes.lua @@ -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 diff --git a/blueprint-sandboxes/prototypes/shortcuts.lua b/blueprint-sandboxes/prototypes/shortcuts.lua new file mode 100644 index 00000000..446bb031 --- /dev/null +++ b/blueprint-sandboxes/prototypes/shortcuts.lua @@ -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" }, + }, + }, +}) diff --git a/blueprint-sandboxes/prototypes/styles.lua b/blueprint-sandboxes/prototypes/styles.lua new file mode 100644 index 00000000..e7842b80 --- /dev/null +++ b/blueprint-sandboxes/prototypes/styles.lua @@ -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, +} diff --git a/blueprint-sandboxes/prototypes/tips-and-tricks.lua b/blueprint-sandboxes/prototypes/tips-and-tricks.lua new file mode 100644 index 00000000..d33ad880 --- /dev/null +++ b/blueprint-sandboxes/prototypes/tips-and-tricks.lua @@ -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 diff --git a/blueprint-sandboxes/release.sh b/blueprint-sandboxes/release.sh new file mode 100644 index 00000000..61439ed7 --- /dev/null +++ b/blueprint-sandboxes/release.sh @@ -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 diff --git a/blueprint-sandboxes/scripts/bpsb.lua b/blueprint-sandboxes/scripts/bpsb.lua new file mode 100644 index 00000000..2ff51633 --- /dev/null +++ b/blueprint-sandboxes/scripts/bpsb.lua @@ -0,0 +1,7 @@ +local BPSB = {} + +BPSB.name = "blueprint-sandboxes" +BPSB.path = "__" .. BPSB.name .. "__" +BPSB.pfx = "bpsb-" + +return BPSB diff --git a/blueprint-sandboxes/scripts/chat.lua b/blueprint-sandboxes/scripts/chat.lua new file mode 100644 index 00000000..7b2ae60c --- /dev/null +++ b/blueprint-sandboxes/scripts/chat.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/debug.lua b/blueprint-sandboxes/scripts/debug.lua new file mode 100644 index 00000000..5f59700d --- /dev/null +++ b/blueprint-sandboxes/scripts/debug.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/editor-extensions-cheats.lua b/blueprint-sandboxes/scripts/editor-extensions-cheats.lua new file mode 100644 index 00000000..1926db3b --- /dev/null +++ b/blueprint-sandboxes/scripts/editor-extensions-cheats.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/equipment.lua b/blueprint-sandboxes/scripts/equipment.lua new file mode 100644 index 00000000..02741512 --- /dev/null +++ b/blueprint-sandboxes/scripts/equipment.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/events.lua b/blueprint-sandboxes/scripts/events.lua new file mode 100644 index 00000000..4b0a563f --- /dev/null +++ b/blueprint-sandboxes/scripts/events.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/factorissimo.lua b/blueprint-sandboxes/scripts/factorissimo.lua new file mode 100644 index 00000000..196e46f1 --- /dev/null +++ b/blueprint-sandboxes/scripts/factorissimo.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/force.lua b/blueprint-sandboxes/scripts/force.lua new file mode 100644 index 00000000..41d20599 --- /dev/null +++ b/blueprint-sandboxes/scripts/force.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/god.lua b/blueprint-sandboxes/scripts/god.lua new file mode 100644 index 00000000..13e2d5f3 --- /dev/null +++ b/blueprint-sandboxes/scripts/god.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/illusion.lua b/blueprint-sandboxes/scripts/illusion.lua new file mode 100644 index 00000000..ee41ab8f --- /dev/null +++ b/blueprint-sandboxes/scripts/illusion.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/init.lua b/blueprint-sandboxes/scripts/init.lua new file mode 100644 index 00000000..483fb674 --- /dev/null +++ b/blueprint-sandboxes/scripts/init.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/inventory.lua b/blueprint-sandboxes/scripts/inventory.lua new file mode 100644 index 00000000..844fe243 --- /dev/null +++ b/blueprint-sandboxes/scripts/inventory.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/lab.lua b/blueprint-sandboxes/scripts/lab.lua new file mode 100644 index 00000000..091c18be --- /dev/null +++ b/blueprint-sandboxes/scripts/lab.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/migrate.lua b/blueprint-sandboxes/scripts/migrate.lua new file mode 100644 index 00000000..18647280 --- /dev/null +++ b/blueprint-sandboxes/scripts/migrate.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/planner-icons.lua b/blueprint-sandboxes/scripts/planner-icons.lua new file mode 100644 index 00000000..fda96f2d --- /dev/null +++ b/blueprint-sandboxes/scripts/planner-icons.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/queue.lua b/blueprint-sandboxes/scripts/queue.lua new file mode 100644 index 00000000..fa41512a --- /dev/null +++ b/blueprint-sandboxes/scripts/queue.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/remote-interface.lua b/blueprint-sandboxes/scripts/remote-interface.lua new file mode 100644 index 00000000..9f69ff1c --- /dev/null +++ b/blueprint-sandboxes/scripts/remote-interface.lua @@ -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, + } +) diff --git a/blueprint-sandboxes/scripts/research.lua b/blueprint-sandboxes/scripts/research.lua new file mode 100644 index 00000000..b91d59cd --- /dev/null +++ b/blueprint-sandboxes/scripts/research.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/resources.lua b/blueprint-sandboxes/scripts/resources.lua new file mode 100644 index 00000000..0986e702 --- /dev/null +++ b/blueprint-sandboxes/scripts/resources.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/sandbox.lua b/blueprint-sandboxes/scripts/sandbox.lua new file mode 100644 index 00000000..9f892195 --- /dev/null +++ b/blueprint-sandboxes/scripts/sandbox.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/settings.lua b/blueprint-sandboxes/scripts/settings.lua new file mode 100644 index 00000000..82a232eb --- /dev/null +++ b/blueprint-sandboxes/scripts/settings.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/space-exploration.lua b/blueprint-sandboxes/scripts/space-exploration.lua new file mode 100644 index 00000000..9a8fb43f --- /dev/null +++ b/blueprint-sandboxes/scripts/space-exploration.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/tiles.lua b/blueprint-sandboxes/scripts/tiles.lua new file mode 100644 index 00000000..136f55f5 --- /dev/null +++ b/blueprint-sandboxes/scripts/tiles.lua @@ -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 diff --git a/blueprint-sandboxes/scripts/toggle-gui.lua b/blueprint-sandboxes/scripts/toggle-gui.lua new file mode 100644 index 00000000..8ca5949f --- /dev/null +++ b/blueprint-sandboxes/scripts/toggle-gui.lua @@ -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 diff --git a/blueprint-sandboxes/settings-final-fixes.lua b/blueprint-sandboxes/settings-final-fixes.lua new file mode 100644 index 00000000..d6fe4d7a --- /dev/null +++ b/blueprint-sandboxes/settings-final-fixes.lua @@ -0,0 +1,4 @@ +BPSB = require("scripts.bpsb") +Settings = require("scripts/settings") + +require("prototypes.editor-extensions-final-fixes") diff --git a/blueprint-sandboxes/settings.lua b/blueprint-sandboxes/settings.lua new file mode 100644 index 00000000..8ee92ee3 --- /dev/null +++ b/blueprint-sandboxes/settings.lua @@ -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, + }, +}) diff --git a/blueprint-sandboxes/thumbnail.png b/blueprint-sandboxes/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..db03038b0c53db6b092f2d9fd3b21d1aa7404290 GIT binary patch literal 1062 zcmV+>1ljwEP)KR76b9gPf*T1BL8uX;4A~j*KG~FkfdK=# zRB8t=Fpwqt@+DwLVyZ+ zOJDpx-?@(O=^QqTe?JylacrEB%D6iGn5HjMM5#oRr~gGXL%-#_V^ib*LJF@im7A=@ zN9oCkC&)Twq_cuboJW<3V2pF}V?igAHyIsQhN+CdhSc?ObblHu24m=sIF8PvpGBO| zJw&OJab=)g?2g^c0;0On9z#KbxI}XzLQWPPxxHe?8v9zT2^46AYHNC95shWH&zJGM zsciL`bFem!f_h-=+UTwT4F_LE0a$h5Wl*_8$TxOfgf_76;6sGGF$DM$;ghfhy3V(^ z;-)}gq-dcIc*Q{9Gq!OZTBqDIyqwT_<-l#1?k5l37C_fj-gCCmg4WX{qC@U>^~DH% z-5Ny5cnw5oFhXsfq98Ahp^K9v1@|I!NYmAxX}w!C-Cc=xYZ;`4gaV@tlf@UlBayR{ zK>~xKCXZGB0rByz_yLhCTq0kM1fE}gEk?U z^6G*kt}mi*ABF7N>Rg!L(AKN?I6_Txo+<1Ci=RZh!-9WGIdC!q$k0CRXv`-BFB-nbEp(_Qy{rwnWY$$uwm%|d7g&MrI@gcjC&dbbYW z@ZC?MGP+NVr%QzN9hk@u@FD^@Xfmks>!>8R91~$C+mO?H!5KpQ2S}MR(rnZIr(ToJ0-C+z gp8i9}EdKfU4jnQ^wG!8A^#A|>07*qoM6N<$f=XWHwEzGB literal 0 HcmV?d00001 diff --git a/heroturrets/prototypes/scripts/types.lua b/heroturrets/prototypes/scripts/types.lua index 0abd90c1..2141b8b9 100644 --- a/heroturrets/prototypes/scripts/types.lua +++ b/heroturrets/prototypes/scripts/types.lua @@ -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") if not heroturrets.defines then require ("prototypes.scripts.defines") end diff --git a/mod-list.json b/mod-list.json index 272f7901..22e6bf96 100644 --- a/mod-list.json +++ b/mod-list.json @@ -148,6 +148,11 @@ "enabled": true }, + { + "name": "blueprint-sandboxes", + "enabled": true + }, + { "name": "blueprint_flip_and_turn", "enabled": true diff --git a/zzzparanoidal/info.json b/zzzparanoidal/info.json index 93432d4e..d4966ae8 100644 --- a/zzzparanoidal/info.json +++ b/zzzparanoidal/info.json @@ -1,12 +1,12 @@ { "name": "zzzparanoidal", - "version": "1.1.1", + "version": "7.1.1", "title": "!_Paranoidal", "factorio_version": "1.1", "author": "Sovigod", "contact": "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": [ "base >= 1.1.100", "boblibrary",