Добавлены все обновления от сообщества, вплоть до #148

This commit is contained in:
2024-09-12 14:28:43 +03:00
parent 98159766c4
commit 487a0e6e16
8841 changed files with 23077 additions and 20175 deletions

View File

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

View File

@@ -0,0 +1,3 @@
# Mining Patch Planner
[Factorio Mod portal](https://mods.factorio.com/mod/mining-patch-planner)

View File

@@ -0,0 +1,359 @@
local enums = require("mpp.enums")
local mpp_util = require("mpp.mpp_util")
local compatibility = require("mpp.compatibility")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local algorithm = {}
---@type table<string, Layout>
local layouts = {}
algorithm.layouts = layouts
local function require_layout(layout)
layouts[layout] = require("layouts."..layout)
layouts[#layouts+1] = layouts[layout]
end
require_layout("simple")
require_layout("compact")
require_layout("super_compact")
require_layout("sparse")
require_layout("logistics")
require_layout("compact_logistics")
require_layout("sparse_logistics")
require_layout("blueprints")
---@class MininimumPreservedState
---@field layout_choice string
---@field player LuaPlayer
---@field surface LuaSurface
---@field resources LuaEntity[] Filtered resources
---@field coords Coords
---@field _previous_state MininimumPreservedState?
---@field _collected_ghosts LuaEntity[]
---@field _preview_rectangle nil|uint64 LuaRendering.draw_rectangle
---@field _lane_info_rendering uint64[]
---@field _render_objects uint64[] LuaRendering objects
---@class State : MininimumPreservedState
---@field _callback string -- callback to be used in the tick
---@field tick number
---@field is_space boolean
---@field resource_tiles GridTile
---@field found_resources LuaEntity[] Resource name -> resource category mapping
---@field resource_counts {name: string, count: number}[] Highest count resource first
---@field requires_fluid boolean
---@field mod_version string
---
---@field layout_choice string
---@field direction_choice string
---@field miner_choice string
---@field pole_choice string
---@field belt_choice string Belt name
---@field space_belt_choice string
---@field lamp_choice boolean Lamp placement
---@field coverage_choice boolean
---@field logistics_choice string
---@field landfill_choice boolean
---@field space_landfill_choice string
---@field start_choice boolean
---@field deconstruction_choice boolean
---@field pipe_choice string
---@field module_choice string
---@field force_pipe_placement_choice boolean
---@field print_placement_info_choice boolean
---@field display_lane_filling_choice boolean
---
---@field grid Grid
---@field deconstruct_specification DeconstructSpecification
---@field miner MinerStruct
---@field pole PoleStruct
---@field belt BeltStruct
---@field blueprint_choice LuaGuiElement
---@field blueprint_inventory LuaInventory
---@field blueprint LuaItemStack
---@field cache EvaluatedBlueprint
--- Return value for layout callbacks
--- string - name of next callback
--- true - repeat the current callback
--- false - job is finished
---@alias CallbackState string|boolean
---@param event EventData.on_player_selected_area
---@return State|nil
---@return LocalisedString error status
local function create_state(event)
---@diagnostic disable-next-line: missing-fields
local state = {} --[[@as State]]
state._callback = "start"
state.tick = 0
state.mod_version = game.active_mods["mining-patch-planner"]
state._preview_rectangle = nil
state._collected_ghosts = {}
state._render_objects = {}
state._lane_info_rendering = {}
---@type PlayerData
local player_data = global.players[event.player_index]
-- game state properties
state.surface = event.surface
state.is_space = compatibility.is_space(event.surface.index)
state.player = game.players[event.player_index]
-- fill in player option properties
local player_choices = player_data.choices
for k, v in pairs(player_choices) do
state[k] = util.copy(v)
end
if state.is_space then
if game.entity_prototypes["se-space-pipe"] then
state.pipe_choice = "se-space-pipe"
else
state.pipe_choice = "none"
end
state.belt_choice = state.space_belt_choice
end
state.debug_dump = mpp_util.get_dump_state(event.player_index)
if state.layout_choice == "blueprints" then
local blueprint = player_data.choices.blueprint_choice
if blueprint == nil then
return nil, {"mpp.msg_unselected_blueprint"}
end
-- state.blueprint_inventory = game.create_inventory(1)
-- state.blueprint = state.blueprint_inventory.find_empty_stack()
-- state.blueprint.set_stack(blueprint)
state.cache = player_data.blueprints.cache[blueprint.item_number]
end
return state
end
---Filters resource entity list and returns patch coordinates and size
---@param entities LuaEntity[]
---@param available_resource_categories table<string, true>
---@return Coords @bounds of found resources
---@return LuaEntity[] @Filtered entities
---@return table<string, string> @key:resource name; value:resource category
---@return boolean @requires fluid
---@return table<string, number> @resource counts table
local function process_entities(entities, available_resource_categories)
local filtered, found_resources, counts = {}, {}, {}
local x1, y1 = math.huge, math.huge
local x2, y2 = -math.huge, -math.huge
local _, cached_resource_categories = enums.get_available_miners()
local checked, requires_fluid = {}, false
for _, entity in pairs(entities) do
local name, proto = entity.name, entity.prototype
local category = proto.resource_category
if not checked[name] then
checked[name] = true
if proto.mineable_properties.required_fluid then requires_fluid = true end
end
found_resources[name] = category
if cached_resource_categories[category] and available_resource_categories[category] then
counts[name] = 1 + (counts[name] or 0)
filtered[#filtered+1] = entity
local x, y = entity.position.x, entity.position.y
if x < x1 then x1 = x end
if y < y1 then y1 = y end
if x2 < x then x2 = x end
if y2 < y then y2 = y end
end
end
local resource_counts = {}
for k, v in pairs(counts) do table.insert(resource_counts, {name=k, count=v}) end
table.sort(resource_counts, function(a, b) return a.count > b.count end)
local coords = {
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
ix1 = floor(x1), iy1 = floor(y1),
ix2 = ceil(x2), iy2 = ceil(y2),
gx = x1 - 1, gy = y1 - 1,
}
coords.w, coords.h = coords.ix2 - coords.ix1, coords.iy2 - coords.iy1
return coords, filtered, found_resources, requires_fluid, resource_counts
end
---@param state State
---@param layout Layout
local function get_miner_categories(state, layout)
if layout.name == "blueprints" then
return state.cache:get_resource_categories()
else
return game.entity_prototypes[state.miner_choice].resource_categories or {}
end
end
--- Algorithm hook
--- Returns nil if it fails
---@param event EventData.on_player_selected_area
function algorithm.on_player_selected_area(event)
---@type PlayerData
local player_data = global.players[event.player_index]
local state, err = create_state(event)
if not state then return nil, err end
local layout = layouts[player_data.choices.layout_choice]
if state.miner_choice == "none" then
return nil, {"mpp.msg_miner_err_3"}
end
local layout_categories = get_miner_categories(state, layout)
local coords, filtered, found_resources, requires_fluid, resource_counts = process_entities(event.entities, layout_categories)
state.coords = coords
state.resources = filtered
state.found_resources = found_resources
state.requires_fluid = requires_fluid
state.resource_counts = resource_counts
if #state.resources == 0 then
for resource, category in pairs(state.found_resources) do
if not layout_categories[category] then
local miner_name = game.entity_prototypes[state.miner_choice].localised_name
if layout.name == "blueprints" then
miner_name = {"mpp.choice_none"}
for k, v in pairs(state.cache.miners) do
miner_name = game.entity_prototypes[k].localised_name
break
end
end
local resource_name = game.entity_prototypes[resource].localised_name
return nil, {"", {"mpp.msg_miner_err_2_1"}, " \"", miner_name, "\" ", {"mpp.msg_miner_err_2_2"}, " \"", resource_name, "\""}
end
end
return nil, {"mpp.msg_miner_err_0"}
end
local last_state = player_data.last_state --[[@as MininimumPreservedState]]
if last_state ~= nil then
local renderables = last_state._render_objects
local old_resources = last_state.resources
local same = mpp_util.coords_overlap(coords, last_state.coords)
-- if same then
-- for i, v in pairs(old_resources) do
-- if v ~= filtered[i] then
-- same = false
-- break
-- end
-- end
-- end
if same then
for _, id in ipairs(renderables) do
rendering.destroy(id)
end
state._previous_state = last_state
else
local ttl = mpp_util.get_display_duration(event.player_index)
for _, id in ipairs(renderables) do
if rendering.is_valid(id) then
rendering.set_time_to_live(id, ttl)
end
end
end
player_data.last_state = nil
end
local validation_result, error = layout:validate(state)
if validation_result then
layout:initialize(state)
state.player.play_sound{path="utility/blueprint_selection_ended"}
-- "Progress" bar
local c = state.coords
state._preview_rectangle = rendering.draw_rectangle{
surface=state.surface,
left_top={state.coords.ix1, state.coords.iy1},
right_bottom={state.coords.ix1 + c.w, state.coords.iy1 + c.h},
filled=false, color={0, 0.8, 0.3, 1},
width = 8,
draw_on_ground = true,
players={state.player},
}
return state
else
return nil, error
end
end
---Sets renderables timeout
---@param player_data PlayerData
function algorithm.on_gui_open(player_data)
local last_state = player_data.last_state
if last_state == nil or last_state._render_objects == nil then return end
local ttl = mpp_util.get_display_duration(last_state.player.index)
if ttl > 0 then
for _, id in ipairs(last_state._render_objects) do
if rendering.is_valid(id) then
rendering.set_time_to_live(id, 0)
end
end
end
end
---Sets renderables timeout
---@param player_data PlayerData
function algorithm.on_gui_close(player_data)
local last_state = player_data.last_state
if last_state == nil or last_state._render_objects == nil then return end
local ttl = mpp_util.get_display_duration(last_state.player.index)
if ttl > 0 then
for _, id in ipairs(last_state._render_objects) do
if rendering.is_valid(id) then
rendering.set_time_to_live(id, ttl)
end
end
else
for _, id in ipairs(last_state._render_objects) do
rendering.destroy(id)
end
end
end
---@param player_data PlayerData
function algorithm.cleanup_last_state(player_data)
local state = player_data.last_state
if not state then return end
local force, ply = state.player.force, state.player
if type(state._collected_ghosts) == "table" then
for _, ghost in pairs(state._collected_ghosts) do
if ghost.valid then
ghost.order_deconstruction(force, ply)
end
end
state._collected_ghosts = {}
end
if type(state._render_objects) == "table" then
for _, id in ipairs(state._render_objects) do
if rendering.is_valid(id) then
rendering.destroy(id)
end
end
state._render_objects = {}
end
rendering.destroy(state._preview_rectangle)
mpp_util.update_undo_button(player_data)
player_data.last_state = nil
end
return algorithm

View File

@@ -0,0 +1,261 @@
---------------------------------------------------------------------------------------------------
Version: 1.6.0
Features:
- Simple, Compact, and Sparse layouts can now use even-sized mining drills. (2x2 and 4x4 drills work now)
- Added button hiding for drill/belt/power/logistic choices. (Shift+Right-click on an icon)
- Added an undo button.
- Now removes existing ghosts if new selection overlaps the previous selection.
- Tooltip and information display improvements.
- Power pole placement made more conservative with placement and smarter in connecting gaps.
Blueprints:
- Blueprint placement improvements and fixes.
- Intelligent transport belt placement. Belt lanes now don't extend past the patch.
- Now automatically places landfill under entity ghosts.
- Saved blueprints had to be reset. You will need to update the blueprint items to the new format before importing them.
Bugfixes:
- Now tries to find related underground belts by name when they are not properly defined in prototypes.
- Space Exploration compatibility fixes
---------------------------------------------------------------------------------------------------
Version: 1.5.8
Date: 2023-11-29
Bugfixes:
- Fixed another crash in lane filling overlay when using Super Compact mode.
---------------------------------------------------------------------------------------------------
Version: 1.5.7
Date: 2023-09-23
Translation:
- Added Polish translation. (provided by WukuWukaCiapanos)
---------------------------------------------------------------------------------------------------
Version: 1.5.6
Date: 2023-09-14
Bugfixes:
- Added padding to the start of tooltips as a workaround to avoid text getting cut off.
---------------------------------------------------------------------------------------------------
Version: 1.5.5
Date: 2023-08-27
Bugfixes:
- Fixed a crash in lane filling overlay when using Super Compact mode on irregular resource patches.
---------------------------------------------------------------------------------------------------
Version: 1.5.4
Date: 2023-07-31
Bugfixes:
- Fixed active overlays not clearing after a migration.
---------------------------------------------------------------------------------------------------
Version: 1.5.3
Date: 2023-07-28
Bugfixes:
- Fixed migrations breaking in saves with removed players.
---------------------------------------------------------------------------------------------------
Version: 1.5.2
Date: 2023-07-25
Features:
- Added a belt throughput estimate to lane saturation overlay.
---------------------------------------------------------------------------------------------------
Version: 1.5.1
Date: 2023-07-10
Bugfixes:
- Fixed a crash in lane filling overlay when using modules with no speed.
---------------------------------------------------------------------------------------------------
Version: 1.5.0
Date: 2023-07-05
Features:
- Added a simple placement info printout.
- Added a lane filling overlay for belt layouts.
- Proper deconstruction only in places where entity ghosts were placed.
- When retrying on the same patch all previous ghosts are automatically removed.
- Overfill mode uses more of mining drill's external area.
- Added a Sparse Logistics layout.
Bugfixes:
- Fixed a bug in drill placement logic that caused suboptimal layouts in overfill mode.
- Fixed the planner selecting tile ghosts.
- Fixed GUI and cursor mismatch when using Space Exploration Satellite View.
---------------------------------------------------------------------------------------------------
Version: 1.4.3
Date: 2023-04-02
Bugfixes:
- Fixed a crash when updating the mod from versions before 1.3.0.
---------------------------------------------------------------------------------------------------
Version: 1.4.2
Date: 2023-03-28
Bugfixes:
- Fixed a crash when using Blueprint Sandboxes mod together with Space Exploration.
---------------------------------------------------------------------------------------------------
Version: 1.4.1
Date: 2023-03-25
Bugfixes:
- Fixed a crash when launching custom scenarios.
---------------------------------------------------------------------------------------------------
Version: 1.4.0
Date: 2023-02-06
Features:
- Added a pipe type option. Pipes are placed automatically if resource requires a fluid.
- Added an option to force pipe placement in advanced settings.
- Added sound effects.
- Updated icons for misc options.
- Space Exploration compatibility when placing patches in space. Added space transport belt and scaffold type options.
Optimizations:
- Internal layout handler refactoring.
---------------------------------------------------------------------------------------------------
Version: 1.3.5
Date: 2023-01-18
Bugfixes:
- Fixed a crash in Compact Logistics layout.
---------------------------------------------------------------------------------------------------
Version: 1.3.4
Date: 2022-12-31
Bugfixes:
- Fixed a crash when playing with Industrial Revolution 3.
Features:
- Added an advanced option to show non-electric powered miners.
No warranties about usability of generated layouts.
---------------------------------------------------------------------------------------------------
Version: 1.3.3
Date: 2022-11-24
Features:
- Added a Module fill option.
Bugfixes:
- Made shortcut icon more distinct from "Blueprint".
---------------------------------------------------------------------------------------------------
Version: 1.3.2
Date: 2022-11-14
Bugfixes:
- Clicking a blueprint button while in add mode exits to placement mode.
- Fixed beacons in blueprints not getting placed in certain placements.
- Fixed inserter settings in blueprints not being preserved.
---------------------------------------------------------------------------------------------------
Version: 1.3.1
Date: 2022-11-04
Features:
- Moved all miscellaneous settings from being hidden behind advanced options.
Bugfixes:
- Fixed crash because of a missing flags check in miner guard clause.
- Fixed migration logic on mod changes.
- Pyanodons compatibility. (more so to support Py miners in custom blueprints)
- Fixed splitter settings in blueprints not being preserved.
- Fixed Super Compact layout sometimes missing single resource tiles.
---------------------------------------------------------------------------------------------------
Version: 1.3.0
Date: 2022-10-11
Features:
- Blueprint support.
- Omit deconstruction step option.
- Better coverage tuning option.
---------------------------------------------------------------------------------------------------
Version: 1.2.11
Date: 2022-07-22
Bugfixes:
- Fixed missing sanity check when changing layouts.
---------------------------------------------------------------------------------------------------
Version: 1.2.10
Date: 2022-07-20
Bugfixes:
- Fixed crash in Logistics layout
- Fixed edge case crash due to patch shape in Compact layout.
---------------------------------------------------------------------------------------------------
Version: 1.2.9
Date: 2022-06-29
Translation:
- Added Ukrainian translation. (provided by Deps Exploits)
---------------------------------------------------------------------------------------------------
Version: 1.2.8
Date: 2022-06-11
Bugfixes:
- Fixed regression with Cursed Filter Mining Drill mod.
Translation:
- Added Russian translation. (provided by Aquilo)
---------------------------------------------------------------------------------------------------
Version: 1.2.7
Date: 2022-06-09
Features:
- Adden an option to omit landfill placement. Will still place ghosts for entities.
Bugfixes:
- Fixed crash regression caused by a flag check on entity prototypes.
- Fixed crash in super compact layout with missing lanes.
---------------------------------------------------------------------------------------------------
Version: 1.2.6
Date: 2022-06-09
Bugfixes:
- Nullius compatibility.
---------------------------------------------------------------------------------------------------
Version: 1.2.5
Date: 2022-05-10
Bugfixes:
- Fixed crash when belts don't have a defined related underground belt.
- Added missing control setting translation.
---------------------------------------------------------------------------------------------------
Version: 1.2.4
Date: 2022-03-15
Bugfixes:
- Fixed deconstruction step marking tiles for deconstruction.
---------------------------------------------------------------------------------------------------
Version: 1.2.3
Date: 2022-02-21
Bugfixes:
- Implemented a blacklist for internal entities of other mods.
---------------------------------------------------------------------------------------------------
Version: 1.2.2
Date: 2022-02-20
Bugfixes:
- Fixed crash when placing belts on fragmented mining patches.
- Fixed belt placement sometimes stopping midway.
---------------------------------------------------------------------------------------------------
Version: 1.2.1
Date: 2022-02-19
Bugfixes:
- Changed power pole fallback not to expect "medium-electric-pole" prototype exists.
---------------------------------------------------------------------------------------------------
Version: 1.2.0
Date: 2022-02-11
Features:
- Added Super Compact layout.
- Added Logistics and Compact Logistics layouts.
Bugfixes:
- Changed power pole logic to be more robust with modded variants.
- Tuned iteration parameters.
---------------------------------------------------------------------------------------------------
Version: 1.1.2
Date: 2022-01-31
Bugfixes:
- Fixed interaction with planners from other mods.
---------------------------------------------------------------------------------------------------
Version: 1.1.1
Date: 2022-01-30
Bugfixes:
- Compatibility for Cursed Filter Mining Drills mod.
---------------------------------------------------------------------------------------------------
Version: 1.1.0
Date: 2022-01-29
Features:
- Added two more layouts - compact, and sparse.
- Added power poles choice.
- Added a progress indicator rectangle over a resource patch.
- Improved GUI handling.
Optimizations:
- Rewrote the algorithm to be modular.
- Rewrote the logic to work on separate ticks.
- Simplified layout algorithms with rotation invariant logic.
---------------------------------------------------------------------------------------------------
Version: 1.0.3
Date: 2021-11-30
Optimizations:
- Improved placement logic of miners with large radius.
Bugfixes:
- Fixed GUI crash in icon logic for mod entities.
---------------------------------------------------------------------------------------------------
Version: 1.0.2
Date: 2021-11-12
Bugfixes:
- Fixed GUI checkbox crash.
---------------------------------------------------------------------------------------------------
Version: 1.0.1
Date: 2021-11-11
Features:
- Lamp placement.
Bugfixes:
- Fixed GUI being created on start.
- Space Exploration Iridium compatibility.
---------------------------------------------------------------------------------------------------
Version: 1.0.0
Date: 2021-11-09
Features:
- Initial release.

View File

@@ -0,0 +1,199 @@
local util = require("util")
local conf = {}
---@class PlayerData
---@field advanced boolean Preserve in migrations
---@field blueprint_add_mode boolean Preserve in migrations
---@field entity_filtering_mode boolean Preserve in migrations
---@field gui PlayerGui
---@field blueprint_items LuaInventory Preserve in migrations
---@field choices PlayerChoices Preserve in migrations
---@field blueprints PlayerGuiBlueprints
---@field last_state MininimumPreservedState? Preserve in migrations
---@field filtered_entities table<string, true>
---@field tick_expires integer When was gui closed, for undo button disabling
---@class PlayerChoices
---@field layout_choice string
---@field blueprint_choice LuaItemStack? Currently selected blueprint (flow)
---@field direction_choice string
---@field miner_choice string
---@field pole_choice string
---@field lamp_choice boolean
---@field belt_choice string
---@field space_belt_choice string
---@field logistics_choice string
---@field landfill_choice boolean
---@field space_landfill_choice string
---@field coverage_choice boolean
---@field start_choice boolean
---@field deconstruction_choice boolean
---@field pipe_choice string
---@field module_choice string
---@field show_non_electric_miners_choice boolean
---@field force_pipe_placement_choice boolean
---@field print_debug_info_choice boolean
---@field display_lane_filling_choice boolean
---@field dumb_power_connectivity_choice boolean
---@field debugging_choice string Debugging only value
---@class PlayerGui
---@field section table<MppSettingSections, LuaGuiElement>
---@field tables table<string, LuaGuiElement>
---@field selections table<string, LuaGuiElement>
---@field advanced_settings LuaGuiElement
---@field filtering_settings LuaGuiElement
---@field undo_button LuaGuiElement
---@field layout_dropdown LuaGuiElement
---@field blueprint_add_button LuaGuiElement
---@field blueprint_add_section LuaGuiElement
---@field blueprint_receptacle LuaGuiElement
---@class PlayerGuiBlueprints All subtables are indexed by blueprint's item number
---@field mapping table<number, LuaItemStack>
---@field flow table<number, LuaGuiElement> Root blueprint element
---@field button table<number, LuaGuiElement> Blueprint button toggle
---@field delete table<number, LuaGuiElement> Blueprint delete button
---@field cache table<number, EvaluatedBlueprint>
---@field original_id table<number, number> Inventory blueprint id to
---Small hack have proper typing in all other places
---@type LuaGuiElement
local nil_element_placeholder = nil
---@type LuaInventory
local nil_inventory_placeholder = nil
---@type PlayerData
conf.default_config = {
advanced = false,
entity_filtering_mode = false,
blueprint_add_mode = false,
blueprint_items = nil_inventory_placeholder,
filtered_entities = {},
tick_expires = 0,
choices = {
layout_choice = "simple",
direction_choice = "north",
miner_choice = "electric-mining-drill",
pole_choice = "medium-electric-pole",
belt_choice = "transport-belt",
space_belt_choice = "se-space-transport-belt",
lamp_choice = false,
logistics_choice = "logistic-chest-passive-provider",
landfill_choice = false,
space_landfill_choice = "se-space-platform-scaffold",
coverage_choice = false,
start_choice = false,
deconstruction_choice = false,
pipe_choice = "pipe",
module_choice = "none",
blueprint_choice = nil,
dumb_power_connectivity_choice = false,
debugging_choice = "none",
-- non layout/convienence/advanced settings
show_non_electric_miners_choice = false,
force_pipe_placement_choice = false,
print_debug_info_choice = false,
display_lane_filling_choice = false,
},
gui = {
section = {},
tables = {},
selections = {},
advanced_settings = nil_element_placeholder,
filtering_settings = nil_element_placeholder,
undo_button = nil_element_placeholder,
blueprint_add_button = nil_element_placeholder,
blueprint_add_section = nil_element_placeholder,
blueprint_receptacle = nil_element_placeholder,
layout_dropdown = nil_element_placeholder,
},
blueprints = {
mapping = {},
cache = {},
flow = {},
button = {},
delete = {},
original_id = {},
}
}
local function pass_same_type(old, new)
if old and type(old) == type(new) then
return old
end
return new
end
function conf.update_player_data(player_index)
---@type PlayerData
local old_config = global.players[player_index]
local new_config = table.deepcopy(conf.default_config) --[[@as PlayerData]]
new_config.advanced = pass_same_type(old_config.advanced, new_config.advanced)
new_config.blueprint_add_mode = pass_same_type(old_config.blueprint_add_mode, new_config.blueprint_add_mode)
new_config.blueprint_items = old_config.blueprint_items or game.create_inventory(1)
new_config.last_state = old_config.last_state
local old_choices = old_config.choices or {}
for key, new_choice in pairs(new_config.choices) do
new_config.choices[key] = pass_same_type(old_choices[key], new_choice)
end
global.players[player_index] = new_config
end
---@param player_index number
function conf.initialize_global(player_index)
local old_data = global.players[player_index]
global.players[player_index] = table.deepcopy(conf.default_config)
if old_data and old_data.blueprint_items then
global.players[player_index].blueprint_items = old_data.blueprint_items
else
global.players[player_index].blueprint_items = game.create_inventory(1)
end
end
function conf.initialize_deconstruction_filter()
if global.script_inventory then
global.script_inventory.destroy()
end
---@type LuaInventory
local inventory = game.create_inventory(2)
do
---@type LuaItemStack
local basic = inventory[1]
basic.set_stack("deconstruction-planner")
basic.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.never
end
do
---@type LuaItemStack
local ghosts = inventory[2]
ghosts.set_stack("deconstruction-planner")
ghosts.entity_filter_mode = defines.deconstruction_item.entity_filter_mode.whitelist
ghosts.entity_filters = {"entity-ghost", "tile-ghost"}
end
global.script_inventory = inventory
end
script.on_event(defines.events.on_player_created, function(e)
---@cast e EventData.on_player_created
conf.initialize_global(e.player_index)
end)
script.on_event(defines.events.on_player_removed, function(e)
---@cast e EventData.on_player_removed
if global.players[e.player_index].blueprint_items then
global.players[e.player_index].blueprint_items.destroy()
end
global.players[e.player_index] = nil
end)
return conf

View File

@@ -0,0 +1,238 @@
require("mpp.global_extends")
local conf = require("configuration")
local compatibility = require("mpp.compatibility")
require("migration")
algorithm = require("algorithm")
local gui = require("gui")
local bp_meta = require("mpp.blueprintmeta")
local render_util = require("mpp.render_util")
local mpp_util = require("mpp.mpp_util")
---@class __MiningPatchPlanner__global
---@field tasks any
script.on_init(function()
global.players = {}
---@type State[]
global.tasks = {}
conf.initialize_deconstruction_filter()
for _, player in pairs(game.players) do
conf.initialize_global(player.index)
end
end)
---@param event EventData
local function task_runner(event)
if #global.tasks == 0 then
return script.on_event(defines.events.on_tick, nil)
end
local state = global.tasks[1]
local layout = algorithm.layouts[state.layout_choice]
local last_callback = state._callback
---@type TickResult
local tick_result
if not __DebugAdapter then
tick_result = layout:tick(state)
else
local success
success, tick_result = pcall(layout.tick, layout, state)
if success == false then
game.print(tick_result)
tick_result = false
end
end
if last_callback == tick_result then
if __DebugAdapter then
table.remove(global.tasks, 1)
else
error("Layout "..state.layout_choice.." step "..tostring(tick_result).." called itself again")
end
elseif tick_result == nil then
if __DebugAdapter then
game.print(("Callback for layout %s after call %s has no result"):format(state.layout_choice, state._callback))
table.remove(global.tasks, 1)
---@type PlayerData
local player_data = global.players[state.player.index]
player_data.last_state = nil
rendering.destroy(state._preview_rectangle)
mpp_util.update_undo_button(player_data)
else
error("Layout "..state.layout_choice.." missing a callback name")
end
elseif tick_result == false then
local player = state.player
if state.blueprint then state.blueprint.clear() end
if state.blueprint_inventory then state.blueprint_inventory.destroy() end
rendering.destroy(state._preview_rectangle)
---@type PlayerData
local player_data = global.players[player.index]
state._previous_state = nil
player_data.tick_expires = math.huge
if __DebugAdapter then
player_data.last_state = state
else
player_data.last_state = {
player = state.player,
surface = state.surface,
layout_choice = state.layout_choice,
resources = state.resources,
coords = state.coords,
_preview_rectangle = state._preview_rectangle,
_collected_ghosts = state._collected_ghosts,
_render_objects = state._render_objects,
_lane_info_rendering = state._lane_info_rendering,
}
end
table.remove(global.tasks, 1)
player.play_sound{path="utility/build_blueprint_medium"}
mpp_util.update_undo_button(player_data)
elseif tick_result ~= true then
state._callback = tick_result
end
end
script.on_event(defines.events.on_player_selected_area, function(event)
---@cast event EventData.on_player_selected_area
local player = game.get_player(event.player_index)
if not player then return end
local cursor_stack = player.cursor_stack
if not cursor_stack or not cursor_stack.valid or not cursor_stack.valid_for_read then return end
if cursor_stack and cursor_stack.valid and cursor_stack.valid_for_read and cursor_stack.name ~= "mining-patch-planner" then return end
if #event.entities == 0 then return end
for _, task in ipairs(global.tasks) do
if task.player == player then
return
end
end
local state, error = algorithm.on_player_selected_area(event)
--rendering.clear("mining-patch-planner")
if state then
table.insert(global.tasks, state)
script.on_event(defines.events.on_tick, task_runner)
elseif error then
player.print(error)
end
end)
script.on_event(defines.events.on_player_alt_reverse_selected_area, function(event)
if not __DebugAdapter then return end
local player = game.get_player(event.player_index)
if not player then return end
local cursor_stack = player.cursor_stack
if not cursor_stack or not cursor_stack.valid or not cursor_stack.valid_for_read then return end
if cursor_stack and cursor_stack.valid and cursor_stack.valid_for_read and cursor_stack.name ~= "mining-patch-planner" then return end
---@type PlayerData
local player_data = global.players[event.player_index]
local debugging_choice = player_data.choices.debugging_choice
debugging_func = render_util[debugging_choice]
if debugging_func then
local res, error = pcall(
debugging_func,
player_data, event
)
if res == false then
game.print(error)
end
else
game.print("No valid debugging function selected")
end
end)
script.on_event(defines.events.on_player_reverse_selected_area, function(event)
if not __DebugAdapter then return end
local player = game.get_player(event.player_index)
if not player then return end
local cursor_stack = player.cursor_stack
if not cursor_stack or not cursor_stack.valid or not cursor_stack.valid_for_read then return end
if cursor_stack and cursor_stack.valid and cursor_stack.valid_for_read and cursor_stack.name ~= "mining-patch-planner" then return end
rendering.clear("mining-patch-planner")
end)
script.on_load(function()
if global.players then
for _, ply in pairs(global.players) do
---@cast ply PlayerData
if ply.blueprints then
for _, bp in pairs(ply.blueprints.cache) do
setmetatable(bp, bp_meta)
end
end
end
end
if global.tasks and #global.tasks > 0 then
script.on_event(defines.events.on_tick, task_runner)
for _, task in ipairs(global.tasks) do
---@type Layout
local layout = algorithm.layouts[task.layout_choice]
layout:on_load(task)
end
end
end)
local function cursor_stack_check(e)
---@cast e EventData.on_player_cursor_stack_changed
local player = game.get_player(e.player_index)
if not player then return end
---@type PlayerData
local player_data = global.players[e.player_index]
if not player_data then return end
local frame = player.gui.screen["mpp_settings_frame"]
if player_data.blueprint_add_mode and frame and frame.visible then
return
end
local cursor_stack = player.cursor_stack
if (cursor_stack and
cursor_stack.valid and
cursor_stack.valid_for_read and
cursor_stack.name == "mining-patch-planner"
) then
gui.show_interface(player)
algorithm.on_gui_open(player_data)
else
local duration = mpp_util.get_display_duration(e.player_index)
if e.tick < player_data.tick_expires then
player_data.tick_expires = e.tick + duration
end
gui.hide_interface(player)
algorithm.on_gui_close(player_data)
end
end
script.on_event(defines.events.on_player_cursor_stack_changed, cursor_stack_check)
script.on_event(defines.events.on_player_changed_surface, cursor_stack_check)
do
local events = compatibility.get_se_events()
for k, v in pairs(events) do
script.on_event(v, cursor_stack_check)
end
end
-- script.on_event(defines.events.on_player_main_inventory_changed, function(e)
-- --change_handler(e)
-- end)

View File

@@ -0,0 +1,3 @@
require("prototypes.data")
require("prototypes.graphics")
require("prototypes.styles")

View File

@@ -0,0 +1,97 @@
---@meta
---@diagnostic disable
---@class EventDataPlayerSelectedArea : EventData
---@field item string
---@field player_index uint
---@field entities LuaEntity[]
---@field tiles LuaTile[]
---@field surface LuaSurface
---@class EventDataPlayerCreated : EventData
---@field player_index uint
---@class EventDataGuiCheckedStateChanged : EventData
---@field player_index uint
---@field element LuaGuiElement
---@class EventDataGuiClick : EventData
---@field player_index uint
---@field element LuaGuiElement
---@field button defines.mouse_button_type
---@field alt boolean
---@field control boolean
---@field shift boolean
---@class EventDataGuiSelectionStateChanged : EventData
---@field player_index uint
---@field element LuaGuiElement
---@class PoleCharacteristics
---@field width number The entity width
---@field reach number Wire connection reach
---@field area number Supply area width
---@class Layout
---@field name string
---@field defaults LayoutDefaults
---@field restrictions Restrictions
---@field on_load fun(self, state: State)
---@field validate fun(self, state: State): boolean
---@field initialize fun(self, state: State)
---@field tick fun(self, state: State): TickResult
---@alias TickResult string | boolean | nil
---@class LayoutDefaults
---@field miner string
---@field belt string
---@field pole string
---@field logistics string
---@field pipe string
---@class Restrictions
---@field miner_available boolean
---@field miner_size number[] Supported size of a mining drill
---@field miner_radius number[] Supported radius of mining drill
---@field belt_available boolean
---@field uses_underground_belts boolean
---@field pole_available boolean
---@field pole_omittable boolean Allow a no electric pole option
---@field pole_width number[]
---@field pole_length number[]
---@field pole_supply_area number[]
---@field logistics_available boolean
---@field lamp_available boolean Enable lamp placement option
---@field coverage_tuning boolean
---@field landfill_omit_available boolean
---@field start_alignment_tuning boolean
---@field deconstruction_omit_available boolean
---@field module_available boolean
---@field pipe_available boolean
---@field placement_info_available boolean
---@field lane_filling_info_available boolean
---@class DeconstructSpecification
---@field x number
---@field y number
---@field width number
---@field height number
---@class BeltSpecification
---@field x1 number Start
---@field x2 number End
---@field y number
---@field built boolean? Do miners exist on this belt
---@field lane1 MinerPlacement[]
---@field lane2 MinerPlacement[]
---@alias LuaRenderingFunction fun(RendererParams): uint64
---@class MppRendering
---@field draw_line LuaRenderingFunction
---@field draw_circle LuaRenderingFunction
---@field draw_rectangle LuaRenderingFunction
---@field draw_text LuaRenderingFunction
table.deepcopy = function(t) end

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

1274
mining-patch-planner/gui.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
{
"name": "mining-patch-planner",
"version": "1.6.0",
"title": "Mining patch planner",
"author": "Rimbas",
"factorio_version": "1.1",
"dependencies": ["base >= 1.1.75"],
"description": "Adds a planner that designs a miner layout for a resource patch."
}

View File

@@ -0,0 +1,107 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local mpp_util = require("mpp.mpp_util")
---@class Layout
local layout = {}
layout.name = "Base"
layout.translation = {"mpp.settings_layout_choice_base"}
---@diagnostic disable-next-line: missing-fields
layout.defaults = {}
layout.defaults.miner = "electric-mining-drill"
layout.defaults.belt = "transport-belt"
layout.defaults.pole = "medium-electric-pole"
layout.defaults.logistics = "logistic-chest-passive-provider"
layout.defaults.pipe = "pipe"
---@diagnostic disable-next-line: missing-fields
layout.restrictions = {}
layout.restrictions.miner_available = true
layout.restrictions.miner_size = {0, 10}
layout.restrictions.miner_radius = {0, 20}
layout.restrictions.belt_available = true
layout.restrictions.uses_underground_belts = false
layout.restrictions.pole_available = true
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.lamp_available = true
layout.restrictions.coverage_tuning = false
layout.restrictions.logistics_available = false
layout.restrictions.landfill_omit_available = true
layout.restrictions.start_alignment_tuning = false
layout.restrictions.deconstruction_omit_available = true
layout.restrictions.module_available = false
layout.restrictions.pipe_available = false
layout.restrictions.placement_info_available = false
layout.restrictions.lane_filling_info_available = false
--- Called from script.on_load
--- ONLY FOR SETMETATABLE USE
---@param self Layout
---@param state State
function layout:on_load(state) end
---@param proto MinerStruct
function layout:restriction_miner(proto)
return true
end
--- Validate the selection
---@param self Layout
---@param state State
function layout:validate(state)
local r = self.restrictions
return true
end
---Layout-specific state initialisation
---@param self Layout
---@param state State
function layout:initialize(state)
state.miner = mpp_util.miner_struct(state.miner_choice)
state.pole = mpp_util.pole_struct(state.pole_choice)
if layout.restrictions.belt_available then
state.belt = mpp_util.belt_struct(state.belt_choice)
end
end
---Starting step
---@param self Layout
---@param state State
---@return CallbackState
function layout:start(state)
return false
end
---Probably too much indirection at this point
---@param self Layout
---@param state State
function layout:tick(state)
state.tick = state.tick + 1
return self[state._callback](self, state)
end
---@param self Layout
---@param state State
function layout:deconstruct_previous_ghosts(state)
local next_step = "initialize_grid"
if state._previous_state == nil or state._previous_state._collected_ghosts == nil then
return next_step
end
local force, player = state.player.force, state.player
for _, ghost in pairs(state._previous_state._collected_ghosts) do
if ghost.valid then
ghost.order_deconstruction(force, player)
end
end
return next_step
end
return layout

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,400 @@
local mpp_util = require("mpp.mpp_util")
local common = {}
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local sqrt, log = math.sqrt, math.log
---@alias HeuristicMinerPlacement fun(tile: GridTile): boolean
---@param miner MinerStruct
---@return HeuristicMinerPlacement
function common.simple_miner_placement(miner)
local size, area = miner.size, miner.area
local neighbor_cap = (size / 2) ^ 2
return function(tile)
return tile.neighbors_inner > neighbor_cap
-- return tile.neighbor_count > neighbor_cap or tile.far_neighbor_count > leech
end
end
---@param miner MinerStruct
---@return HeuristicMinerPlacement
function common.overfill_miner_placement(miner)
local size, area = miner.size, miner.area
local neighbor_cap = (size/ 2) ^ 2 - 1
local leech = (area * 0.5) ^ 2 - 1
return function(tile)
return tile.neighbors_inner > 0 or tile.neighbors_outer > leech
end
end
---@param heuristic HeuristicsBlock
function common.simple_layout_heuristic(heuristic)
local lane_mult = 1 + ceil(heuristic.lane_count / 2) * 0.05
local unconsumed = 1 + log(max(1, heuristic.unconsumed), 10)
local value =
(heuristic.inner_density + heuristic.empty_space / heuristic.drill_count)
--* heuristic.centricity
* lane_mult
* unconsumed
return value
end
---@param heuristic HeuristicsBlock
function common.overfill_layout_heuristic(heuristic)
local lane_mult = 1 + ceil(heuristic.lane_count / 2) * 0.05
local unconsumed = 1 + log(max(1, heuristic.unconsumed), 10)
local value =
heuristic.outer_density
--* heuristic.centricity
* lane_mult
* unconsumed
return value
end
---@class HeuristicsBlock
--- Values counted per miner placement
---@field inner_neighbor_sum number Sum of resource tiles drills physically cover
---@field outer_neighbor_sum number Sum of all resource tiles drills physically cover
---@field empty_space number Sum of empty tiles drills physically cover
---@field leech_sum number Sum of resources only in the outer reach
---@field postponed_count number Number of postponed drills
---
--- Values calculated after completed layout placement
---@field drill_count number Total number of mining drills
---@field lane_count number Number of lanes
---@field inner_density number Density of tiles physically covered by drills
---@field outer_density number Pseudo (because of overlap) density of all tiles reached by drills
---@field centricity number How centered is the layout in respect to patch bounds, 1 to inf
---@field unconsumed number Unreachable resources count (usually insufficient drill reach)
---@return HeuristicsBlock
function common.init_heuristic_values()
return {
inner_neighbor_sum = 0,
outer_neighbor_sum = 0,
empty_space = 0,
leech_sum = 0,
postponed_count = 0,
drill_count = 1,
lane_count = 1,
inner_density = 1,
outer_density = 1,
centricity = -(0/0),
unconsumed = 0,
}
end
---@param H HeuristicsBlock
---@param M MinerStruct
---@param tile GridTile
---@param postponed boolean?
function common.add_heuristic_values(H, M, tile, postponed)
H.inner_neighbor_sum = H.inner_neighbor_sum + tile.neighbors_inner
H.outer_neighbor_sum = H.outer_neighbor_sum + tile.neighbors_outer
H.empty_space = H.empty_space + (M.size_sq - tile.neighbors_inner)
H.leech_sum = H.leech_sum + max(0, tile.neighbors_outer - tile.neighbors_inner)
if postponed then H.postponed_count = H.postponed_count + 1 end
end
---@param attempt PlacementAttempt
---@param block HeuristicsBlock
---@param coords Coords
function common.finalize_heuristic_values(attempt, block, coords)
block.drill_count = #attempt.miners
block.lane_count = #attempt.lane_layout
block.inner_density = block.inner_neighbor_sum / block.drill_count
block.outer_density = block.outer_neighbor_sum / block.drill_count
local function centricity(m1, m2, size)
local center = size / 2
local drill = m1 + (m2-m1)/2
return center - drill
end
local x = centricity(attempt.sx-1, attempt.bx, coords.w)
local y = centricity(attempt.sy-1, attempt.by, coords.h)
block.centricity = 1 + (x * x + y * y) ^ 0.5
block.unconsumed = attempt.unconsumed
end
---Utility to fill in postponed miners on unconsumed resources
---@param state SimpleState
---@param attempt PlacementAttempt
---@param miners MinerPlacement[]
---@param postponed MinerPlacement[]
function common.process_postponed(state, attempt, miners, postponed)
local grid = state.grid
local M = state.miner
local bx, by = attempt.sx + M.size - 1, attempt.sy + M.size - 1
for _, miner in ipairs(miners) do
grid:consume(miner.x+M.extent_negative, miner.y+M.extent_negative, M.area)
bx, by = max(bx, miner.x + M.size -1), max(by, miner.y + M.size -1)
end
for _, miner in ipairs(postponed) do
miner.unconsumed = grid:get_unconsumed(miner.x+M.extent_negative, miner.y+M.extent_negative, M.area)
bx, by = max(bx, miner.x + M.size -1), max(by, miner.y + M.size -1)
end
table.sort(postponed, function(a, b)
if a.unconsumed == b.unconsumed then
local atile, btile = a.tile, b.tile
if atile.neighbors_outer == btile.neighbors_outer then
return atile.neighbors_inner > btile.neighbors_inner
end
return atile.neighbors_outer > btile.neighbors_outer
end
return a.unconsumed > b.unconsumed
end)
for _, miner in ipairs(postponed) do
local tile = miner.tile
local unconsumed_count = grid:get_unconsumed(miner.x+M.extent_negative, miner.y+M.extent_negative, M.area)
if unconsumed_count > 0 then
common.add_heuristic_values(attempt.heuristics, M, tile, true)
grid:consume(tile.x+M.extent_negative, tile.y+M.extent_negative, M.area)
miners[#miners+1] = miner
miner.postponed = true
bx, by = max(bx, miner.x + M.size - 1), max(by, miner.y + M.size - 1)
end
end
local unconsumed_sum = 0
for _, tile in ipairs(state.resource_tiles) do
if not tile.consumed then unconsumed_sum = unconsumed_sum + 1 end
end
attempt.unconsumed = unconsumed_sum
attempt.bx, attempt.by = bx, by
grid:clear_consumed(state.resource_tiles)
end
local seed
local function get_map_seed()
if seed then return seed end
local game_exchange_string = game.get_map_exchange_string()
local map_data = game.parse_map_exchange_string(game_exchange_string)
local seed_number = map_data.map_gen_settings.seed
seed = string.format("%x", seed_number)
return seed
end
---Dump state to json for inspection
---@param state SimpleState
function common.save_state_to_file(state, type_)
local c = state.coords
local gx, gy = floor(c.gx), floor(c.gy)
local dir = state.direction_choice
local coverage = state.coverage_choice and "t" or "f"
local filename = string.format("layout_%s_%i;%i_%s_%i_%s_%s_%x.%s", get_map_seed(), gx, gy, state.miner_choice, #state.resources, dir, coverage, game.tick, type_)
if type_ == "json" then
game.print(string.format("Dumped data to %s ", filename))
game.write_file("mpp-inspect/"..filename, game.table_to_json(state), false, state.player.index)
elseif type_ == "lua" then
game.print(string.format("Dumped data to %s ", filename))
game.write_file("mpp-inspect/"..filename, serpent.dump(state, {}), false, state.player.index)
end
end
function common.calculate_patch_slack(state)
end
---@param miner MinerStruct
---@param restrictions Restrictions
---@return boolean
function common.is_miner_restricted(miner, restrictions)
return false
or miner.size < restrictions.miner_size[1]
or restrictions.miner_size[2] < miner.size
or miner.radius < restrictions.miner_radius[1]
or restrictions.miner_radius[2] < miner.radius
end
---@param belt BeltStruct
---@param restrictions Restrictions
function common.is_belt_restricted(belt, restrictions)
return false
or (restrictions.uses_underground_belts and not belt.related_underground_belt)
end
---@param pole PoleStruct
---@param restrictions Restrictions
function common.is_pole_restricted(pole, restrictions)
return false
or pole.size < restrictions.pole_width[1]
or restrictions.pole_width[2] < pole.size
or pole.supply_area_distance < restrictions.pole_supply_area[1]
or restrictions.pole_supply_area[2] < pole.supply_area_distance
or pole.wire < restrictions.pole_length[1]
or restrictions.pole_length[2] < pole.wire
end
local triangles = {
west={
{{target={-.6, 0}}, {target={.6, -0.6}}, {target={.6, 0.6}}},
{{target={-.4, 0}}, {target={.5, -0.45}}, {target={.5, 0.45}}},
},
east={
{{target={.6, 0}}, {target={-.6, -0.6}}, {target={-.6, 0.6}}},
{{target={.4, 0}}, {target={-.5, -0.45}}, {target={-.5, 0.45}}},
},
north={
{{target={0, -.6}}, {target={-.6, .6}}, {target={.6, .6}}},
{{target={0, -.4}}, {target={-.45, .5}}, {target={.45, .5}}},
},
south={
{{target={0, .6}}, {target={-.6, -.6}}, {target={.6, -.6}}},
{{target={0, .4}}, {target={-.45, -.5}}, {target={.45, -.5}}},
},
}
local alignment = {
west={"center", "center"},
east={"center", "center"},
north={"left", "right"},
south={"right", "left"},
}
local bound_alignment = {
west="right",
east="left",
north="center",
south="center",
}
---Draws a belt lane overlay
---@param state State
---@param belt BeltSpecification
function common.draw_belt_lane(state, belt)
local r = state._render_objects
local c, ttl, player = state.coords, 0, {state.player}
local x1, y1, x2, y2 = belt.x1, belt.y, math.max(belt.x1+2, belt.x2), belt.y
local function l2w(x, y) -- local to world
return mpp_util.revert(c.gx, c.gy, state.direction_choice, x, y, c.tw, c.th)
end
local c1, c2, c3 = {.9, .9, .9}, {0, 0, 0}, {.4, .4, .4}
local w1, w2 = 4, 10
if not belt.lane1 and not belt.lane2 then c1 = c3 end
r[#r+1] = rendering.draw_line{ -- background main line
surface=state.surface, players=player, only_in_alt_mode=true,
width=w2, color=c2, time_to_live=ttl or 1,
from=l2w(x1, y1), to=l2w(x2+.5, y1),
}
r[#r+1] = rendering.draw_line{ -- background vertical cap
surface=state.surface, players=player, only_in_alt_mode=true,
width=w2, color=c2, time_to_live=ttl or 1,
from=l2w(x2+.5, y1-.6), to=l2w(x2+.5, y2+.6),
}
r[#r+1] = rendering.draw_polygon{ -- background arrow
surface=state.surface, players=player, only_in_alt_mode=true,
width=w2, color=c2, time_to_live=ttl or 1,
target=l2w(x1, y1),
vertices=triangles[state.direction_choice][1],
}
r[#r+1] = rendering.draw_line{ -- main line
surface=state.surface, players=player, only_in_alt_mode=true,
width=w1, color=c1, time_to_live=ttl or 1,
from=l2w(x1-.2, y1), to=l2w(x2+.5, y1),
}
r[#r+1] = rendering.draw_line{ -- vertical cap
surface=state.surface, players=player, only_in_alt_mode=true,
width=w1, color=c1, time_to_live=ttl or 1,
from=l2w(x2+.5, y1-.5), to=l2w(x2+.5, y2+.5),
}
r[#r+1] = rendering.draw_polygon{ -- arrow
surface=state.surface, players=player, only_in_alt_mode=true,
width=0, color=c1, time_to_live=ttl or 1,
target=l2w(x1, y1),
vertices=triangles[state.direction_choice][2],
}
end
---Draws a belt lane overlay
---@param state State
---@param belt BeltSpecification
function common.draw_belt_stats(state, belt, belt_speed, speed1, speed2)
local r = state._render_objects
local c, ttl, player = state.coords, 0, {state.player}
local x1, y1, x2, y2 = belt.x1, belt.y, belt.x2, belt.y
local function l2w(x, y) -- local to world
return mpp_util.revert(c.gx, c.gy, state.direction_choice, x, y, c.tw, c.th)
end
local c1, c2, c3, c4 = {.9, .9, .9}, {0, 0, 0}, {.9, 0, 0}, {.4, .4, .4}
local ratio1 = speed1 / belt_speed
local ratio2 = speed2 / belt_speed
local function get_color(ratio)
return ratio > 1.01 and c3 or ratio == 0 and c4 or c1
end
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=get_color(ratio1), time_to_live=ttl or 1,
alignment=alignment[state.direction_choice][1], vertical_alignment="middle",
target=l2w(x1-2, y1-.6), scale=1.6,
text=string.format("%.2fx", ratio1),
}
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=get_color(ratio2), time_to_live=ttl or 1,
alignment=alignment[state.direction_choice][2], vertical_alignment="middle",
target=l2w(x1-2, y1+.6), scale=1.6,
text=string.format("%.2fx", ratio2),
}
end
---Draws a belt lane overlay
---@param state State
---@param pos_x number
---@param pos_y number
---@param speed1 number
---@param speed2 number
function common.draw_belt_total(state, pos_x, pos_y, speed1, speed2)
local r = state._render_objects
local c, ttl, player = state.coords, 0, {state.player}
local function l2w(x, y, b) -- local to world
if ({south=true, north=true})[state.direction_choice] then
x = x + (b and -.5 or .5)
y = y + (b and -.5 or .5)
end
return mpp_util.revert(c.gx, c.gy, state.direction_choice, x, y, c.tw, c.th)
end
local c1 = {0.7, 0.7, 1.0}
local lower_bound = math.min(speed1, speed2)
local upper_bound = math.max(speed1, speed2)
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=c1, time_to_live=ttl or 1,
alignment=bound_alignment[state.direction_choice], vertical_alignment="middle",
target=l2w(pos_x-4, pos_y-.6, false), scale=2,
text={"mpp.msg_print_info_lane_saturation_belts", string.format("%.2fx", upper_bound), string.format("%.2fx", (lower_bound+upper_bound)/2)},
}
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=c1, time_to_live=ttl or 1,
alignment=bound_alignment[state.direction_choice], vertical_alignment="middle",
target=l2w(pos_x-4, pos_y+.6, true), scale=2,
text={"mpp.msg_print_info_lane_saturation_bounds", string.format("%.2fx", lower_bound), string.format("%.2fx", upper_bound)},
}
end
return common

View File

@@ -0,0 +1,592 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local common = require("layouts.common")
local simple = require("layouts.simple")
local mpp_util = require("mpp.mpp_util")
local pole_grid_mt = require("mpp.pole_grid_mt")
local drawing = require("mpp.drawing")
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local table_insert = table.insert
---@class CompactLayout : SimpleLayout
local layout = table.deepcopy(simple)
---@class CompactState : SimpleState
layout.name = "compact"
layout.translation = {"", "[entity=underground-belt] ", {"mpp.settings_layout_choice_compact"}}
layout.restrictions.miner_size = {3, 10e3}
layout.restrictions.miner_radius = {1, 10e3}
layout.restrictions.uses_underground_belts = true
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {7.5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.coverage_tuning = true
layout.restrictions.lamp_available = true
layout.restrictions.module_available = true
layout.restrictions.pipe_available = true
---@param state CompactState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local M = state.miner
local size = M.size
local miners, postponed = {}, {}
local heuristic_values = common.init_heuristic_values()
local lane_layout = {}
local bx, by = shift_x + M.size - 1, shift_y + M.size - 1
local heuristic = self:_get_miner_placement_heuristic(state)
local row_index = 1
for y_float = shift_y, state.coords.th + size, size + 0.5 do
local y = ceil(y_float)
local column_index = 1
lane_layout[#lane_layout+1] = {y = y, row_index = row_index}
for x = shift_x, state.coords.tw, size do
local tile = grid:get_tile(x, y) --[[@as GridTile]]
local miner = {
x = x,
y = y,
origin_x = x + M.x,
origin_y = y + M.y,
tile = tile,
line = row_index,
column = column_index,
}
if tile.neighbors_outer > 0 and heuristic(tile) then
miners[#miners+1] = miner
common.add_heuristic_values(heuristic_values, M, tile)
elseif tile.neighbors_outer > 0 then
postponed[#postponed+1] = miner
end
column_index = column_index + 1
end
row_index = row_index + 1
end
local result = {
sx = shift_x,
sy = shift_y,
bx = bx,
by = by,
miners = miners,
lane_layout = lane_layout,
heuristics = heuristic_values,
heuristic_score = -(0/0),
unconsumed = 0,
}
common.process_postponed(state, result, miners, postponed)
common.finalize_heuristic_values(result, heuristic_values, state.coords)
return result
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_belt_layout(state)
local belts = {}
state.belts = belts
state.builder_belts = {}
local coverage = mpp_util.calculate_pole_spacing(state, state.miner_max_column, state.miner_lane_count, true)
state.builder_power_poles = {}
if coverage.capable_span then
self:_placement_capable(state)
else
self:_placement_incapable(state)
end
return "prepare_belts"
end
---Placement of belts+poles when they tile nicely (enough reach/supply area)
---@param self CompactLayout
---@param state CompactState
function layout:_placement_capable(state)
local M, C, P, G = state.miner, state.coords, state.pole, state.grid
local attempt = state.best_attempt
local miner_lane_count = state.miner_lane_count
local coverage = mpp_util.calculate_pole_spacing(state, state.miner_max_column, state.miner_lane_count, true)
local steps = {}
for i = coverage.pole_start, coverage.full_miner_width, coverage.pole_step do
table.insert(steps, i)
end
local power_grid = pole_grid_mt.new()
state.power_grid = power_grid
--local transport_layout = {}
--state.transport_layout = transport_layout
local iy = 1
for i = 1, miner_lane_count, 2 do
y = attempt.sy + floor((M.size + 0.5) * i)
--local transport_layout_lane = {}
--transport_layout[ceil(i / 2)] = transport_layout_lane
local backtrack_build = false
for step_i = #steps, 1, -1 do
local x = attempt.sx + steps[step_i]
local has_consumers = G:needs_power(x, y, P)
if backtrack_build or step_i == 1 or has_consumers then
backtrack_build = true
power_grid:add_pole{
grid_x = x, grid_y = y,
ix = step_i, iy = iy,
backtracked = backtrack_build,
has_consumers = has_consumers,
built = has_consumers,
connections = {},
}
--transport_layout_lane[step_i] = {x=x, y=y, built=true}
-- table_insert(builder_power_poles, {
-- name=P.name,
-- thing="pole",
-- grid_x=x,
-- grid_y=y,
-- ix = step_i,
-- iy = iy,
-- })
else
power_grid:add_pole{
grid_x = x, grid_y = y,
ix = step_i, iy = iy,
backtracked = false,
has_consumers = has_consumers,
built = false,
connections = {},
}
end
end
iy = iy + 1
end
local connectivity = power_grid:find_connectivity(state.pole)
state.power_connectivity = connectivity
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_belts(state)
local M, C, P, G = state.miner, state.coords, state.pole, state.grid
local attempt = state.best_attempt
local miner_lanes = state.miner_lanes
local miner_lane_count = state.miner_lane_count
local builder_power_poles = state.builder_power_poles
local connectivity = state.power_connectivity
local power_grid = state.power_grid
--local debug_draw = drawing(state, false, false)
if power_grid then
local connected = power_grid:ensure_connectivity(connectivity)
for _, pole in pairs(connected) do
table_insert(builder_power_poles, {
name = P.name,
thing = "pole",
grid_x = pole.grid_x,
grid_y = pole.grid_y,
ix = pole.ix,
iy = pole.iy,
})
end
end
local builder_belts = state.builder_belts
local belt_name = state.belt.name
local underground_name = state.belt.related_underground_belt --[[@as string]]
local belts = {}
state.belts = belts
state.belt_count = 0
local pipe_adjust = state.place_pipes and -1 or 0
for i_lane = 1, miner_lane_count, 2 do
local iy = ceil(i_lane / 2)
local lane1 = miner_lanes[i_lane]
local lane2 = miner_lanes[i_lane+1]
--local transport_layout_lane = transport_layout[ceil(i_lane / 2)]
local function get_lane_length(lane, out_x) if lane and lane[#lane] then return lane[#lane].x+out_x end return 0 end
local y = attempt.sy + floor((M.size + 0.5) * i_lane)
local x1 = attempt.sx + pipe_adjust
local belt = {x1=x1, x2=attempt.sx, y=y, built=false, lane1=lane1, lane2=lane2}
belts[#belts+1] = belt
if not lane1 and not lane2 then goto continue_lane end
local x2 = max(get_lane_length(lane1, M.output_rotated[SOUTH].x), get_lane_length(lane2, M.out_x))
state.belt_count = state.belt_count + 1
belt.x2, belt.built = x2, true
-- debug_draw:draw_circle{x = x1, y = y, width = 7}
-- debug_draw:draw_circle{x = x2, y = y, width = 7}
if not power_grid then goto continue_lane end
local previous_start, first_interval = x1, true
local power_poles, pole_count, belt_intervals = power_grid[iy], #power_grid[iy], {}
for i, pole in pairs(power_poles) do
if not pole.built then goto continue_poles end
local next_x = pole.grid_x
table_insert(belt_intervals, {
x1 = previous_start, x2 = min(x2, next_x - 1),
is_first = first_interval,
})
previous_start = next_x + 2
first_interval = false
if x2 < next_x then
--debug_draw:draw_circle{x = x2, y = y, color = {1, 0, 0}}
break
end
::continue_poles::
end
if previous_start <= x2 then
--debug_draw:draw_circle{x = x2, y = y, color = {0, 1, 0}}
table_insert(belt_intervals, {
x1 = previous_start, x2 = x2, is_last = true,
})
end
for i, interval in pairs(belt_intervals) do
local bx1, bx2 = interval.x1, interval.x2
builder_belts[#builder_belts+1] = {
name = interval.is_first and belt_name or underground_name,
thing = "belt", direction=WEST,
grid_x = bx1, grid_y = y,
type = not interval.is_first and "input" or nil
}
for x = bx1 + 1, bx2 - 1 do
builder_belts[#builder_belts+1] = {
name = belt_name,
thing = "belt", direction=WEST,
grid_x = x, grid_y = y,
}
end
builder_belts[#builder_belts+1] = {
name = i == #belt_intervals and belt_name or underground_name,
thing = "belt", direction=WEST,
grid_x = bx2, grid_y = y,
type = not interval.is_last and "output" or nil
}
end
::continue_lane::
end
return "prepare_pole_layout"
end
---Placement of belts+poles when they don't tile nicely (not enough reach/supply area)
---@param self CompactLayout
---@param state CompactState
function layout:_placement_incapable(state)
end
---@param self CompactLayout
---@param state CompactState
function layout:_placement_belts_small(state)
local m = state.miner
local attempt = state.best_attempt
local belt_choice = state.belt_choice
local underground_belt = state.belt.related_underground_belt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local miner_lanes = {}
local miner_lane_count = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local belts = state.belts
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_count = max(miner_lane_count, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line[#line+1] = miner
end
for _, lane in ipairs(miner_lanes) do
table.sort(lane, function(a, b) return a.x < b.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane then return lane[#lane].x + m.out_x end return 0 end
---@param lane MinerPlacement[]
local function get_lane_column(lane) if lane and #lane > 0 then return lane[#lane].column end return 0 end
state.belt_count = 0
local que_entity = create_entity_que(state.builder_belts)
local function belts_filled(x1, y, w)
for x = x1, x1 + w do
que_entity{name=belt_choice, direction=WEST, grid_x=x, grid_y=y, thing="belt"}
end
end
local pipe_adjust = state.place_pipes and -1 or 0
for i = 1, miner_lane_count, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy + m.size * i + ceil(i/2)
local x0 = attempt.sx + 1
local column_count = max(get_lane_column(lane1), get_lane_column(lane2))
if column_count == 0 then goto continue_lane end
state.belt_count = state.belt_count + 1
local indices = {}
if lane1 then for _, v in ipairs(lane1) do indices[v.column] = v end end
if lane2 then for _, v in ipairs(lane2) do indices[v.column] = v end end
if state.place_pipes then
que_entity{
name=state.belt_choice,
thing="belt",
grid_x = x0 + pipe_adjust,
grid_y = y,
direction=WEST,
}
end
belts[#belts+1] = {
x1 = x0 + pipe_adjust, x2 = x0 + column_count * m.size,
y = y, lane1 = lane1, lane2 = lane2,
}
for j = 1, column_count do
local x1 = x0 + (j-1) * m.size
if j % 2 == 1 then -- part one
if indices[j] or indices[j+1] then
que_entity{
name=state.belt_choice, thing="belt", grid_x=x1, grid_y=y, direction=WEST,
}
local stopper = (j+1 > column_count) and state.belt_choice or underground_belt
que_entity{
name=stopper, thing="belt", grid_x=x1+1, grid_y=y, direction=WEST, type="output",
}
-- power_poles[#power_poles+1] = {
-- x=x1+3, y=y,
-- ix=1+floor(i/2), iy=1+floor(j/2),
-- built = true,
-- }
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x1+3,
grid_y = y,
ix=1+floor(i/2), iy=1+floor(j/2),
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
elseif j % 2 == 0 then -- part two
if indices[j-1] or indices[j] then
que_entity{
name=belt_choice, thing="belt", grid_x=x1+2, grid_y=y, direction=WEST,
}
que_entity{
name=underground_belt, thing="belt", grid_x=x1+1, grid_y=y, direction=WEST,
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
end
end
::continue_lane::
end
end
---@param self CompactLayout
function layout:_placement_belts_large(state)
local m = state.miner
local attempt = state.best_attempt
local belt_choice = state.belt_choice
local underground_belt = game.entity_prototypes[belt_choice].related_underground_belt.name
local power_poles = {}
state.builder_power_poles = power_poles
local belts = state.belts
---@type table<number, MinerPlacement[]>
local miner_lanes = {{}}
local miner_lane_count = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_count = max(miner_lane_count, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line[#line+1] = miner
end
state.miner_lane_count = miner_lane_count
local que_entity = create_entity_que(state.builder_belts)
for _, lane in pairs(miner_lanes) do
table.sort(lane, function(a, b) return a.center.x < b.center.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane and #lane > 0 then return lane[#lane].center.x or 0 end return 0 end
---@param lane MinerPlacement[]
local function get_lane_column(lane) if lane and #lane > 0 then return lane[#lane].column or 0 end return 0 end
local function belts_filled(x1, y, w)
for x = x1, x1 + w do
que_entity{name=belt_choice, direction=WEST, grid_x=x, grid_y=y, thing="belt"}
end
end
local pipe_adjust = state.place_pipes and -1 or 0
for i = 1, miner_lane_count, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy + m.size * i + ceil(i/2)
local x0 = attempt.sx + 1
local column_count = max(get_lane_column(lane1), get_lane_column(lane2))
if column_count == 0 then goto continue_lane end
local indices = {}
if lane1 then for _, v in ipairs(lane1) do indices[v.column] = v end end
if lane2 then for _, v in ipairs(lane2) do indices[v.column] = v end end
if state.place_pipes then
que_entity{
name=state.belt_choice,
thing="belt",
grid_x = x0 + pipe_adjust,
grid_y = y,
direction=WEST,
}
end
belts[#belts+1] = {
x1 = x0 + pipe_adjust, x2 = x0 + column_count * m.size,
y = y, lane1 = lane1, lane2 = lane2,
}
for j = 1, column_count do
local x1 = x0 + (j-1) * m.size
if j % 3 == 1 then -- part one
if indices[j] or indices[j+1] or indices[j+2] then
que_entity{
name=belt_choice, grid_x=x1, grid_y=y, thing="belt", direction=WEST,
}
local stopper = (j+1 > column_count) and state.belt_choice or underground_belt
que_entity{
name=stopper, grid_x=x1+1, grid_y=y, thing="belt", direction=WEST,
type="output",
}
-- power_poles[#power_poles+1] = {
-- x=x1+3, y=y,
-- ix=1+floor(i/2), iy=1+floor(j/2),
-- built = true,
-- }
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x1+3,
grid_y = y,
ix=1+floor(i/2),
iy=1+floor(j/2),
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
elseif j % 3 == 2 then -- part two
if indices[j-1] or indices[j] or indices[j+1] then
que_entity{
name=underground_belt, grid_x=x1+1, grid_y=y, thing="belt", direction=WEST,
type="input",
}
que_entity{
name=belt_choice, grid_x=x1+2, grid_y=y, thing="belt", direction=WEST,
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
elseif j % 3 == 0 then
belts_filled(x1, y, m.size - 1)
end
end
::continue_lane::
end
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_pole_layout(state)
return "prepare_lamp_layout"
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_lamp_layout(state)
local next_step = "expensive_deconstruct"
if state.lamp_choice ~= true then return next_step end
local lamps = {}
state.builder_lamps = lamps
local grid = state.grid
local sx, sy = state.pole.size, 0
local lamp_spacing = true
if state.pole.wire > 7 then lamp_spacing = false end
for _, pole in ipairs(state.builder_power_poles) do
local x, y = pole.grid_x + sx, pole.grid_y + sy
local ix, iy = pole.ix, pole.iy
local tile = grid:get_tile(x, y)
local skippable_lamp = iy % 2 == 1 and ix % 2 == 1
if tile and (not lamp_spacing or skippable_lamp) then
lamps[#lamps+1] = {
name="small-lamp",
thing="lamp",
grid_x = x,
grid_y = y,
}
end
end
return next_step
end
return layout

View File

@@ -0,0 +1,123 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local super_compact = require("layouts.super_compact")
local logistics =require("layouts.logistics")
local builder = require("mpp.builder")
---@class CompactLogisticsLayout: SuperCompactLayout
local layout = table.deepcopy(super_compact)
layout.name = "compact_logistics"
layout.translation = {"", "[entity=logistic-chest-passive-provider] ", {"mpp.settings_layout_choice_compact_logistics"}}
layout.restrictions.lamp_available = false
layout.restrictions.belt_available = false
layout.restrictions.logistics_available = true
layout.restrictions.lane_filling_info_available = false
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local g = state.grid
local C = state.coords
local attempt = state.best_attempt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local miner_lanes = {{}}
local miner_lane_number = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local builder_belts = {}
state.builder_belts = builder_belts
local function que_entity(t) builder_belts[#builder_belts+1] = t end
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_number = max(miner_lane_number, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line._index = index
local out_x = m.output_rotated[defines.direction[miner.direction]][1]
if line.last_x == nil or (miner.x+out_x) > line.last_x then
line.last_x = miner.x + out_x
line.last_miner = miner
end
line[#line+1] = miner
end
local shift_x, shift_y = state.best_attempt.sx, state.best_attempt.sy
local function place_logistics(lane, start_x, end_x, y)
local belt_start = 1 + shift_x + start_x
if start_x ~= 0 then
local miner = g:get_tile(shift_x+m.size, y)
if miner and miner.built_on == "miner" then
que_entity{
name=state.logistics_choice,
thing="belt",
grid_x=shift_x+m.size+1,
grid_y=y,
}
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = shift_x,
grid_y = y,
}
end
end
for x = belt_start, end_x, m.size * 2 do
local miner1 = g:get_tile(x, y-1) --[[@as GridTile]]
local miner2 = g:get_tile(x, y+1) --[[@as GridTile]]
local miner3 = g:get_tile(x+3, y) --[[@as GridTile]]
local built = (miner1 and miner1.built_on == "miner") or (miner2 and miner2.built_on == "miner")
local capped = miner3 and miner3.built_on == "miner"
local pole_built = built or capped
if capped then
que_entity{
name=state.logistics_choice,
thing="belt",
grid_x=x+m.size*2,
grid_y=y,
}
end
if built then
que_entity{
name=state.logistics_choice,
thing="belt",
grid_x=x+1,
grid_y=y,
}
end
if pole_built then
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x + 2,
grid_y = y,
}
end
end
end
for i = 1, miner_lane_number do
local lane = miner_lanes[i]
if lane and lane.last_x then
local y = m.size + shift_y - 1 + (m.size + 2) * (i-1)
local x_start = i % 2 == 0 and 3 or 0
place_logistics(lane, x_start, lane.last_x, y)
end
end
return "expensive_deconstruct"
end
layout.finish = logistics.finish
return layout

View File

@@ -0,0 +1,93 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local simple = require("layouts.simple")
local mpp_util = require("mpp.mpp_util")
local mpp_revert = mpp_util.revert
local pole_grid_mt = require("mpp.pole_grid_mt")
---@class LogisticsLayout:SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "logistics"
layout.translation = {"", "[entity=logistic-chest-passive-provider] ", {"mpp.settings_layout_choice_logistics"}}
layout.restrictions.belt_available = false
layout.restrictions.logistics_available = true
layout.restrictions.lane_filling_info_available = false
---@param self LogisticsLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local attempt = state.best_attempt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local miner_lanes = {{}}
local miner_lane_number = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local miner_max_column = 0
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_number = max(miner_lane_number, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line[#line+1] = miner
miner_max_column = max(miner_max_column, miner.column)
end
state.miner_lane_count = miner_lane_number
state.miner_max_column = miner_max_column
for _, lane in pairs(miner_lanes) do
table.sort(lane, function(a, b) return a.x < b.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane then return lane[#lane].x end return 0 end
---@param lane MinerPlacement[]
local function get_lane_column(lane) if lane and #lane > 0 then return lane[#lane].column or 0 end return 0 end
local belts = {}
state.builder_belts = belts
for i = 1, miner_lane_number, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy - 1 + (m.size + 1) * i
local x0 = attempt.sx
local column_count = max(get_lane_column(lane1), get_lane_column(lane2))
local indices = {}
if lane1 then for _, v in ipairs(lane1) do indices[v.column] = v end end
if lane2 then for _, v in ipairs(lane2) do indices[v.column] = v end end
for j = 1, column_count do
local x = x0 + m.out_x + m.size * (j-1)
if indices[j] then
belts[#belts+1] = {
name=state.logistics_choice,
thing="belt",
grid_x=x,
grid_y=y,
}
end
end
end
return "prepare_pole_layout"
end
---@param self SimpleLayout
---@param state SimpleState
---@return CallbackState
function layout:finish(state)
if state.print_placement_info_choice and state.player.valid then
state.player.print({"mpp.msg_print_info_miner_placement_no_lanes", #state.best_attempt.miners, #state.resources})
end
return false
end
return layout

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
local mpp_util = require("mpp.mpp_util")
local common = require("layouts.common")
local renderer = require("mpp.render_util")
local drawing = require("mpp.drawing")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local simple = require("layouts.simple")
---@class SparseLayout : SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "sparse"
layout.translation = {"", "[entity=transport-belt] ", {"mpp.settings_layout_choice_sparse"}}
layout.restrictions.miner_size = {1, 10e3}
layout.restrictions.miner_radius = {1, 10e3}
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {7.5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.lamp_available = true
layout.restrictions.module_available = true
layout.restrictions.pipe_available = true
layout.restrictions.coverage_tuning = false
---@param state SimpleState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local M = state.miner
local size, area = state.miner.size, state.miner.area
local miners = {}
local heuristic_values = common.init_heuristic_values()
local row_index = 1
local lane_layout = {}
for uy = shift_y, state.coords.th + size, area do
local column_index = 1
local y = uy - M.extent_negative
lane_layout[#lane_layout+1] = {y=y, row_index = row_index}
for ux = shift_x, state.coords.tw, area do
local x = ux - M.extent_negative
local tile = grid:get_tile(x, y) --[[@as GridTile]]
local miner = {
x = x,
y = y,
origin_x = x + M.x,
origin_y = y + M.y,
tile = tile,
line = row_index,
column = column_index,
direction = row_index % 2 == 1 and SOUTH or NORTH
}
if tile.neighbors_outer > 0 then
miners[#miners+1] = miner
end
column_index = column_index + 1
end
row_index = row_index + 1
end
local result = {
sx=shift_x,
sy=shift_y,
bx = 1,
by = 1,
miners=miners,
lane_layout=lane_layout,
heuristics = heuristic_values,
heuristic_score = #miners,
unconsumed = 0,
}
common.process_postponed(state, result, miners, {})
common.finalize_heuristic_values(result, heuristic_values, state.coords)
result.heuristic_score = #miners
return result
end
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_layout_attempts(state)
local c = state.coords
local m = state.miner
local attempts = {}
state.attempts = attempts
state.best_attempt_index = 1
state.attempt_index = 1 -- first attempt is used up
local function calc_slack(tw, near, far)
local fullsize = far * 2 + 1
local count = ceil(tw / fullsize)
local overrun = count * fullsize - tw
local slack = overrun % 2
--local start = far-near-floor(overrun / 2) - slack
local start = (far-near)-floor(overrun / 2) - slack
return count, start, slack
end
local countx, slackw2, modx = calc_slack(c.tw, floor(m.size/2), floor(m.area/2))
local county, slackh2, mody = calc_slack(c.th, floor(m.size/2), floor(m.area/2))
for sy = slackh2, slackh2 + mody do
for sx = slackw2, slackw2 + modx do
attempts[#attempts+1] = {
sx,
sy,
cx = countx,
cy = county,
slackw = slackw2,
slackh = slackh2,
modx = modx,
mody = mody,
}
end
end
return "init_layout_attempt"
end
---@param self SimpleLayout
---@param state SimpleState
function layout:init_layout_attempt(state)
local attempt = state.attempts[state.attempt_index]
state.best_attempt = self:_placement_attempt(state, attempt[1], attempt[2])
state.best_attempt_score = #state.best_attempt.miners
if state.debug_dump then
state.best_attempt.heuristic_score = state.best_attempt_score
state.saved_attempts = {}
state.saved_attempts[#state.saved_attempts+1] = state.best_attempt
end
if #state.attempts > 1 then
return "layout_attempt"
end
return "prepare_miner_layout"
end
function layout:_get_layout_heuristic(state)
return function(attempt) return #attempt.miners end
end
---Bruteforce the best solution
---@param self SimpleLayout
---@param state SimpleState
function layout:layout_attempt(state)
local attempt_state = state.attempts[state.attempt_index]
---@type PlacementAttempt
local current_attempt = self:_placement_attempt(state, attempt_state[1], attempt_state[2])
local current_attempt_score = #current_attempt.miners
if current_attempt_score < state.best_attempt_score then
state.best_attempt_index = state.attempt_index
state.best_attempt = current_attempt
state.best_attempt_score = current_attempt_score
end
if state.attempt_index >= #state.attempts then
return "prepare_miner_layout"
end
state.attempt_index = state.attempt_index + 1
return true
end
---@param self SimpleLayout
---@param state SimpleState
---@return PlacementSpecification[]
function layout:_get_pipe_layout_specification(state)
local pipe_layout = {}
local M = state.miner
local attempt = state.best_attempt
local gutter = M.outer_span
for _, pre_lane in pairs(state.miner_lanes) do
if not pre_lane[1] then goto continue_lanes end
local y = pre_lane[1].y + M.pipe_left
local sx = attempt.sx + M.outer_span - 1
---@type MinerPlacement[]
local lane = table.mapkey(pre_lane, function(t) return t.column end) -- make array with intentional gaps between miners
local current_start, current_length = nil, 0
for i = 1, state.miner_max_column do
local miner = lane[i]
if miner and current_start then
local start_shift = 0
if current_start == 1 then start_shift = gutter * 2 end
pipe_layout[#pipe_layout+1] = {
structure="horizontal",
x=sx + start_shift + (current_start-1) * M.area - gutter * 2 + 1,
y=y,
w=current_length * M.area+gutter * 2 - 1 - start_shift,
}
current_start, current_length = nil, 0
elseif not miner and not current_start then
current_start, current_length = i, 1
elseif current_start then
current_length = current_length + 1
elseif i > 1 then
pipe_layout[#pipe_layout+1] = {
structure="horizontal",
x=sx+(i-1)*M.area-gutter*2+1,
y=y,
w=gutter*2-1
}
end
end
::continue_lanes::
end
for i = 1, state.miner_lane_count do
local lane = attempt.lane_layout[i]
pipe_layout[#pipe_layout+1] = {
structure="cap_vertical",
x=attempt.sx + M.outer_span - 1,
y=lane.y+M.pipe_left,
skip_up=i == 1,
skip_down=i == state.miner_lane_count,
}
end
return pipe_layout
end
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local g = state.grid
local attempt = state.best_attempt
local miner_lanes = state.miner_lanes
local miner_lane_count = state.miner_lane_count
local miner_max_column = state.miner_max_column
for _, lane in pairs(miner_lanes) do
table.sort(lane, function(a, b) return a.x < b.x end)
end
local builder_belts = {}
state.builder_belts = builder_belts
local function get_lane_length(lane) if lane then return lane[#lane].x end return 0 end
local function que_entity(t) builder_belts[#builder_belts+1] = t end
local belt_lanes = {}
state.belts = belt_lanes
local longest_belt = 0
local pipe_adjust = state.place_pipes and -1 or 0
for i = 1, miner_lane_count, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy + m.area - m.outer_span + m.area * (i-1)
local belt = {x1=attempt.sx + 1, x2=attempt.sx + 1, y=y, built=false, lane1=lane1, lane2=lane2}
belt_lanes[#belt_lanes+1] = belt
if lane1 or lane2 then
local x1 = attempt.sx + 1 + pipe_adjust
local x2 = max(get_lane_length(lane1)+m.out_x+1, get_lane_length(lane2)+ m.out_x + 1)
longest_belt = max(longest_belt, x2 - x1 + 1)
belt.x1, belt.x2, belt.built = x1, x2, true
for x = x1, x2 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=x,
grid_y=y,
direction=defines.direction.west,
}
end
end
if lane2 then
for _, miner in ipairs(lane2) do
local out_x = m.out_x
for ny = y + 1, y + m.outer_span * 2 - 1 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=miner.x + out_x,
grid_y=ny,
direction=defines.direction.north,
}
end
end
end
end
state.belt_count = #belt_lanes
return "prepare_pole_layout"
end
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_pole_layout(state)
local c = state.coords
local m = state.miner
local G = state.grid
local P = state.pole
local attempt = state.best_attempt
local pole_proto = game.entity_prototypes[state.pole_choice] or {supply_area_distance=3, max_wire_distance=9}
local supply_area_distance, supply_radius, supply_area = 3.5, 3, 6
if pole_proto then
supply_area = pole_proto.supply_area_distance
supply_area_distance = pole_proto.supply_area_distance or supply_area_distance
supply_radius = floor(supply_area_distance)
supply_area = floor(supply_area_distance * 2)
end
local builder_power_poles = {}
state.builder_power_poles = builder_power_poles
local pole_step = min(floor(pole_proto.max_wire_distance), supply_area + 2)
state.pole_step = pole_step
local miner_lane_width = (state.miner_max_column-1) * m.area + state.miner_max_column
local pole_start = m.outer_span
local function place_pole_lane(y, iy, skip_light)
local pole_lane = {}
local ix = 1
for x = attempt.sx + pole_start, c.tw+m.size, pole_step do
local built = false
if G:needs_power(x, y, P) then
built = true
end
local pole = {x=x, y=y, ix=ix, iy=iy, built=built}
pole_lane[ix] = pole
ix = ix + 1
end
local backtrack_built = false
for pole_i = #pole_lane, 1, -1 do
local no_light = (P.wire < 9) and (pole_i % 2 == 0) or nil
---@type GridPole
local backtrack_pole = pole_lane[pole_i]
if backtrack_built or backtrack_pole.built then
backtrack_built = true
backtrack_pole.built = true
builder_power_poles[#builder_power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = backtrack_pole.x,
grid_y = backtrack_pole.y,
no_light = skip_light or no_light,
}
end
end
return pole_lane
end
local initial_y = attempt.sy + m.outer_span - 1
local iy, between_lane = 1, 0
local y_max, y_step = initial_y + c.th + m.area + 1, m.area * 2
for y = initial_y, y_max, y_step do
if y + y_step > y_max then -- last pole lane
local backstep = m.outer_span * 2 - 1
if state.miner_lanes[between_lane+1] then
backstep = ceil(m.size/2)
end
place_pole_lane(y - backstep)
elseif (m.outer_span * 2 + 2) > supply_area then -- single pole can't supply two lanes
place_pole_lane(y, iy)
if y ~= initial_y then
iy = iy + 1
place_pole_lane(y - m.outer_span * 2 + 1, iy, true)
end
else
local backstep = y == initial_y and 0 or ceil(m.size/2)
place_pole_lane(y - backstep)
end
iy = iy + 1
between_lane = between_lane + 2
end
return "prepare_lamp_layout"
end
return layout

View File

@@ -0,0 +1,43 @@
local mpp_util = require("mpp.mpp_util")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local sparse = require("layouts.sparse")
local logistics = require("layouts.logistics")
---@class SparseLogisticsLayout : SparseLayout
local layout = table.deepcopy(sparse)
layout.name = "sparse_logistics"
layout.translation = {"", "[entity=logistic-chest-passive-provider] ", {"mpp.settings_layout_choice_sparse_logistics"}}
layout.restrictions.belt_available = false
layout.restrictions.logistics_available = true
layout.restrictions.lane_filling_info_available = false
---@param self SparseLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local M = state.miner
local belts = {}
state.builder_belts = belts
for _, miner in ipairs(state.best_attempt.miners) do
local out_pos = state.miner.output_rotated[miner.direction]
belts[#belts+1] = {
name=state.logistics_choice,
thing="belt",
grid_x=miner.x + out_pos.x,
grid_y=miner.y + out_pos.y,
}
end
return "prepare_pole_layout"
end
layout.finish = logistics.finish
return layout

View File

@@ -0,0 +1,420 @@
local common = require("layouts.common")
local simple = require("layouts.simple")
local mpp_util = require("mpp.mpp_util")
local builder = require("mpp.builder")
local table_insert = table.insert
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
---@class SuperCompactLayout : SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "super_compact"
layout.translation = {"", "[entity=underground-belt] ", {"mpp.settings_layout_choice_super_compact"}}
layout.restrictions.miner_size = {3, 3}
layout.restrictions.miner_radius = {1, 20}
layout.restrictions.uses_underground_belts = true
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.coverage_tuning = true
layout.restrictions.lamp_available = false
layout.restrictions.module_available = true
layout.restrictions.pipe_available = false
---@class SuperCompactState : SimpleState
---@field miner_bounds any
-- Validate the selection
---@param self SuperCompactLayout
---@param state SimpleState
function layout:validate(state)
local c = state.coords
if (state.direction_choice == "west" or state.direction_choice == "east") then
if c.h < 3 then
return nil, {"mpp.msg_miner_err_1_w", 3}
end
else
if c.w < 3 then
return nil, {"mpp.msg_miner_err_1_h", 3}
end
end
return true
end
---@param proto MinerStruct
function layout:restriction_miner(proto)
return proto.symmetric
end
---@param self SuperCompactLayout
---@param state SimpleState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local M = state.miner
local size, area = M.size, M.area
local miners, postponed = {}, {}
local heuristic_values = common.init_heuristic_values()
local lane_layout = {}
local bx, by = shift_x + size - 1, shift_y + size - 1
--@param tile GridTile
--local function heuristic(tile) return tile.neighbor_count > 2 end
local heuristic = self:_get_miner_placement_heuristic(state)
local function miner_stagger(start_x, start_y, direction, row_start, mark_lane)
local row_index = row_start
for y = 1 + shift_y + start_y, state.coords.th + 2, size * 3 + 1 do
if mark_lane then lane_layout[#lane_layout+1] = {y=y+1, row_index=row_index} end
local ix = 1
for x = 1 + shift_x + start_x, state.coords.tw + 2, size * 2 do
local tile = grid:get_tile(x, y) --[[@as GridTile]]
---@type MinerPlacementInit
local miner = {
x = x,
y = y,
origin_x = x + M.x,
origin_y = y + M.y,
tile = tile,
direction = direction,
stagger = row_start,
line = row_index,
column = ix,
}
if tile.neighbors_outer > 0 and heuristic(tile) then
table_insert(miners, miner)
common.add_heuristic_values(heuristic_values, M, tile)
elseif tile.neighbors_outer > 0 then
postponed[#postponed+1] = miner
end
ix = ix + 1
end
row_index = row_index + 2
end
end
miner_stagger(0, -2, "south", 1)
miner_stagger(3, 0, "east", 1, true)
miner_stagger(0, 2, "north", 1)
-- the redundant calculation makes it easier to find the stagger offset
miner_stagger(0+size, -2+size+2, "south", 2)
miner_stagger(3-size, 0+size+2, "east", 2, true)
miner_stagger(0+size, 2+size+2, "north", 2)
local result = {
sx = shift_x,
sy = shift_y,
bx = bx,
by = by,
miners = miners,
lane_layout = lane_layout,
heuristics = heuristic_values,
heuristic_score = -(0/0),
unconsumed = 0,
}
common.process_postponed(state, result, miners, postponed)
common.finalize_heuristic_values(result, heuristic_values, state.coords)
for _, miner in pairs(miners) do
---@cast miner MinerPlacement
local current_lane = lane_layout[miner.line]
if not current_lane then
current_lane = {}
lane_layout[miner.line] = current_lane
end
table_insert(current_lane, miner)
end
return result
end
---@param self SimpleLayout
---@param state SimpleState
---@return CallbackState
function layout:prepare_miner_layout(state)
local grid = state.grid
local M = state.miner
local builder_miners = {}
state.builder_miners = builder_miners
for _, miner in ipairs(state.best_attempt.miners) do
grid:build_miner(miner.x, miner.y, state.miner.size-1)
-- used for deconstruction, not ghost placement
builder_miners[#builder_miners+1] = {
thing="miner",
extent_=state.miner.size,
grid_x = miner.origin_x,
grid_y = miner.origin_y,
radius = M.size / 2,
}
--[[ debug visualisation - miner placement
local color = {1, 0, 0}
if miner.direction == "west" then
color = {0, 1, 0}
elseif miner.direction == "north" then
color = {0, 0, 1}
end
local rect_color = miner.stagger == 1 and {1, 1, 1} or {0, 0, 0}
local off = state.miner.size / 2 - 0.1
local tx, ty = coord_revert[DIR](center.x, center.y, c.tw, c.th)
rendering.draw_rectangle{
surface = state.surface,
filled = false,
--color = miner.postponed and {1, 0, 0} or {0, 1, 0},
color = rect_color,
width = 3,
--target = {c.x1 + x, c.y1 + y},
left_top = {c.gx+tx-off, c.gy + ty - off},
right_bottom = {c.gx+tx+off, c.gy + ty + off},
}
rendering.draw_text{
surface=state.surface,
color=color,
text=miner_dir,
target=mpp_revert(c.gx, c.gy, DIR, center.x, center.y, c.tw, c.th),
vertical_alignment = "top",
alignment = "center",
}
rendering.draw_text{
surface=state.surface,
color={1, 1, 1},
text=miner.line * 2 + miner.stagger - 2,
target={c.gx + tx, c.gy + ty},
vertical_alignment = "bottom",
alignment = "center",
}
--]]
end
return "prepare_belt_layout"
end
---@param self SuperCompactLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local g = state.grid
local attempt = state.best_attempt
local underground_belt = state.belt.related_underground_belt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local belt_lanes = attempt.lane_layout
local miner_lane_number = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local builder_belts = {}
state.builder_belts = builder_belts
local function que_entity(t) builder_belts[#builder_belts+1] = t end
state.belt_count = 0
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_number = max(miner_lane_number, index)
if not belt_lanes[index] then belt_lanes[index] = {} end
local line = belt_lanes[index]
line._index = index
local out_x = m.output_rotated[defines.direction[miner.direction]][1]
if line.last_x == nil or (miner.x + out_x) > line.last_x then
line.last_x = miner.x + out_x
line.last_miner = miner
end
line[#line+1] = miner
end
local temp_belts = {}
for k, v in pairs(belt_lanes) do temp_belts[#temp_belts+1] = v end
table.sort(temp_belts, function(a, b) return a.row_index < b.row_index end)
state.belts = temp_belts
local shift_x, shift_y = state.best_attempt.sx, state.best_attempt.sy
local function place_belts(start_x, end_x, y)
local belt_start, belt_end = 1 + shift_x + start_x, end_x
local pre_miner = g:get_tile(shift_x+m.size, y)
local built_miner = pre_miner and pre_miner.built_on == "miner"
if start_x == 0 then
-- straight runoff
for sx = 0, 2 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=belt_start-sx,
grid_y=y,
direction=WEST,
}
end
elseif not built_miner then
for sx = 0, m.size+2 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=belt_start-sx,
grid_y=y,
direction=WEST,
}
end
else
-- underground exit
que_entity{
name=underground_belt,
type="output",
thing="belt",
grid_x=shift_x-1,
grid_y=y,
direction=WEST,
}
que_entity{
name=underground_belt,
type="input",
thing="belt",
grid_x=shift_x+m.size+1,
grid_y=y,
direction=WEST,
}
if built_miner then
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = shift_x,
grid_y = y,
}
end
end
for x = belt_start, end_x, m.size * 2 do
local miner1 = g:get_tile(x, y-1) --[[@as GridTile]]
local miner2 = g:get_tile(x, y+1) --[[@as GridTile]]
local miner3 = g:get_tile(x+3, y) --[[@as GridTile]]
local built = (miner1 and miner1.built_on == "miner") or (miner2 and miner2.built_on == "miner")
local capped = miner3 and miner3.built_on == "miner"
local pole_built = built or capped
local last = x + m.size * 2 > end_x
if last and not capped then
-- last passtrough and no trailing miner
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=x+1,
grid_y=y,
direction=WEST,
}
elseif capped or built then
que_entity{
name=underground_belt,
type="output",
thing="belt",
grid_x=x+1,
grid_y=y,
direction=WEST,
}
que_entity{
name=underground_belt,
type="input",
thing="belt",
grid_x=x+m.size*2,
grid_y=y,
direction=WEST,
}
else
for sx = 1, 6 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=x+sx,
grid_y=y,
direction=WEST,
}
end
end
if last and capped then belt_end = x+6 end
if pole_built then
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x + 2,
grid_y = y,
}
end
end
return belt_start, belt_end
end
for i = 1, miner_lane_number do
local belt = belt_lanes[i]
if belt and belt.last_x then
local y = m.size + shift_y - 1 + (m.size + 2) * (i-1)
local x_start = i % 2 == 0 and 3 or 0
local bx1, bx2 = place_belts(x_start, belt.last_x, y)
belt.x1, belt.x2, belt.y = bx1-3, bx2, y
state.belt_count = state.belt_count + 1
local lane1, lane2 = {}, {}
for _, miner in ipairs(belt) do
if miner.direction == "north" then
lane2[#lane2+1] = miner
else
lane1[#lane1+1] = miner
end
end
if #lane1 > 0 then belt.lane1 = lane1 end
if #lane2 > 0 then belt.lane2 = lane2 end
end
end
return "expensive_deconstruct"
end
---@param self SuperCompactLayout
---@param state SimpleState
---@return CallbackState
function layout:placement_miners(state)
local create_entity = builder.create_entity_builder(state)
local M = state.miner
local grid = state.grid
local module_inv_size = state.miner.module_inventory_size --[[@as uint]]
for _, miner in ipairs(state.best_attempt.miners) do
local ghost = create_entity{
name = state.miner_choice,
thing = "miner",
grid_x = miner.origin_x,
grid_y = miner.origin_y,
direction = defines.direction[miner.direction],
}
if state.module_choice ~= "none" then
ghost.item_requests = {[state.module_choice] = module_inv_size}
end
end
return "placement_belts"
end
return layout

View File

@@ -0,0 +1,82 @@
[item-name]
mining-patch-planner=Mining patch planner
[shortcut-name]
mining-patch-planner-shortcut=Mining patch planner
[controls]
mining-patch-planner-keybind=Open mining patch planner
[mod-setting-name]
mpp-lane-filling-info-duration=Lane saturation overlay linger duration in seconds
mpp-dump-heuristics-data=DEBUG: Dump state info for inspection
[mod-setting-description]
mpp-lane-filling-info-duration=Linger duration starts after the planner GUI is closed
[mpp]
advanced_settings=Advanced settings
entity_filtering_mode=Entity filtering\nHide/unhide available entities in GUI with __CONTROL_KEY_SHIFT____CONTROL_STYLE_BEGIN__ + __CONTROL_STYLE_END____CONTROL_LEFT_CLICK__
settings_frame=Planner settings
blueprint_add_mode=Add blueprints
show_non_electric_miners=Show non-electric miners
force_pipe_placement=Force pipe placement
print_placement_info=Print placement info
display_lane_filling=Show lane saturation overlay in ALT mode.\nWill fade out after closing GUI\n(change display duration in mod settings)
settings_layout_label=Layout type
settings_direction_label=Layout direction
settings_miner_label=Mining drill type
settings_belt_label=Belt type
settings_space_belt_label=Space belt type
settings_logistics_label=Logistics type
settings_pole_label=Electric pole type
settings_misc_label=Miscellaneous settings
settings_blueprints_label=Blueprint selection
settings_debugging_label=Debugging options
settings_checkbox_lamps=Add lamps
settings_layout_choice_base=Base
settings_layout_choice_simple=Simple
settings_layout_choice_compact=Compact
settings_layout_choice_super_compact=Super Compact
settings_layout_choice_sparse=Sparse
settings_layout_choice_logistics=Logistics
settings_layout_choice_compact_logistics=Compact Logistics
settings_layout_choice_sparse_logistics=Sparse Logistics
settings_layout_choice_blueprints=Blueprints
choice_none=None
choice_lamp=Place lamps
choice_coverage=Overfill with mining drills
choice_landfill=Omit landfill
choice_start=Align to patch start
choice_deconstruction=Don't deconstruct entities
label_add_blueprint=Insert blueprint
label_insufficient_area=Insufficient mining area for full coverage.
label_no_fluid_mining=Drill doesn't support fluid connections.
label_oversized_drill=Huge mining drill. Creating a layout might take a while.
label_not_available=Not available in this layout
label_belt_find_underground=Could not find related underground belt.
msg_miner_err_0=No suitable resources selected
msg_miner_err_1_w=Span is too small to create a layout (minimum width is __1__)
msg_miner_err_1_h=Span is too small to create a layout (minimum height is __1__)
msg_miner_err_2_1=Can't build on this resource patch with selected miner
msg_miner_err_2_2=because it can't mine resource
msg_miner_err_3=No suitable miner exists for this layout
msg_blueprint_valid=Not a valid blueprint item. Create a copy from blueprint library first.
msg_blueprint_undefined_grid=Blueprint doesn't have a defined grid size.
msg_unselected_blueprint=No blueprint selected
msg_blueprint_existing=Blueprint already exists
msg_blueprint_no_miner=No mining drill found in blueprint
msg_blueprint_different_miners=More than one type of mining drill found:
msg_blueprint_fatal_error=Could not copy blueprint to internal inventory. Please report this bug with save file to mod author.
msg_print_info_miner_placement=Placed __1__ mining __plural_for_parameter_1_{1=drill|rest=drills}__ with __2__ __plural_for_parameter_2_{1=belt|rest=belts}__ on __3__ __plural_for_parameter_3_{1=resource|rest=resources}__.
msg_print_info_miner_placement_no_lanes=Placed __1__ mining __plural_for_parameter_1_{1=drill|rest=drills}__ on __2__ __plural_for_parameter_2_{1=resource|rest=resources}__.
msg_print_info_lane_saturation_belts=Belts __1__, throughput __2__
msg_print_info_lane_saturation_bounds=Lane sums __1__ - __2__
msg_print_cant_hide_last_choice=Can't hide last available choice

View File

@@ -0,0 +1,82 @@
[item-name]
mining-patch-planner=Kasybos lauko planuotojas
[shortcut-name]
mining-patch-planner-shortcut=Kasybos lauko planuotojas
[controls]
mining-patch-planner-keybind=Atidaryti kasybos lauko planuotoją
[mod-setting-name]
mpp-lane-filling-info-duration=Juostos užpildymo informacijos rodymo trukmė sekundėmis
mpp-dump-heuristics-data=DEBUG: Išsaugoti išdėstymo derinimo informaciją
[mod-setting-description]
mpp-lane-filling-info-duration=Rodymo pabaigos laikmatis prasideda uždarius planuotojo sąsają
[mpp]
advanced_settings=Išplėstiniai nustatymai
entity_filtering_mode=Objektų filtravimas\nSlėpti/rodyti objektus planuotojo sąsajoje su __CONTROL_KEY_SHIFT____CONTROL_STYLE_BEGIN__ + __CONTROL_STYLE_END____CONTROL_LEFT_CLICK__
settings_frame=Planuotojo nustatymai
blueprint_add_mode=Pridėti brėžinius
show_non_electric_miners=Rodyti neelektrinius kasybos grąžtus
force_pipe_placement=Priverstinai dėti vamzdžius
print_placement_info=Spaustinti išdėstymo informaciją
display_lane_filling=Rodyti konvejerių užpildymą ALT režime.\nIšnyks uždarius sąsają\n(rodymo trukmę galima pakeisti modifikacijų nustatymuose)
settings_layout_label=Išdėstymas
settings_direction_label=Kryptis
settings_miner_label=Kasybos grąžtai
settings_belt_label=Konvejeriai
settings_space_belt_label=Kosmoso konvejeriai
settings_logistics_label=Logistika
settings_pole_label=Elektros stulpai
settings_misc_label=Įvairūs
settings_blueprints_label=Brėžinio pasirinkimas
settings_debugging_label=Derinimo nustatymai
settings_checkbox_lamps=Pridėti lempas
settings_layout_choice_base=Bazinis
settings_layout_choice_simple=Paprastas
settings_layout_choice_compact=Kompaktiškas
settings_layout_choice_super_compact=Super Kompaktiškas
settings_layout_choice_sparse=Išsklaidytas
settings_layout_choice_logistics=Logistinis
settings_layout_choice_compact_logistics=Kompaktiškas Logistinis
settings_layout_choice_sparse_logistics=Išsklaidytas Logistinis
settings_layout_choice_blueprints=Brėžiniai
choice_none=Niekas
choice_lamp=Dėti lempas
choice_coverage=Perpildyti kasybos grąžtais
choice_landfill=Praleisti užpildymą žeme
choice_start=Prilyginti su lauko pradžia
choice_deconstruction=Negriauti objektų
label_add_blueprint=Pridėti brėžinį
label_insufficient_area=Nepakankama kasimo aprėptis pilnam padengimui.
label_no_fluid_mining=Kasybos grąžtas nepalaiko skysčių prijungimų.
label_oversized_drill=Milžiniškas grąžtas. Sukurti išplanavimą gali ilgai užtrukti.
label_not_available=Negalimas šiame išdėstyme
label_belt_find_underground=Nerastas susijęs požeminis konvejeris.
msg_miner_err_0=Nepasirinkta jokių tinkamų resursų
msg_miner_err_1_w=Per mažas plotis lauko išplanavimui (minimali apimtis yra __1__)
msg_miner_err_1_h=Per mažas aukštis lauko išplanavimui (minimali apimtis yra __1__)
msg_miner_err_2_1=Neįmanoma pastatyti ant šio lauko pažymėto grąžto
msg_miner_err_2_2=nes juo negalima kasti resurso
msg_miner_err_3=Nėra tinkamo kasybos grąžto šiam išdėstymui
msg_blueprint_valid=Netinkamas brėžinys. Pirmiausia sukurkite kopiją.
msg_blueprint_undefined_grid=Brėžinys neturi apibrėžtų tinklelio matmenų.
msg_unselected_blueprint=Nėra pasirinkto brėžinio.
msg_blueprint_existing=Brėžinys jau pridėtas.
msg_blueprint_no_miner=Brėžinyje nėra kasybos grąžto.
msg_blueprint_different_miners=Rasta daugiau nei vienas kasybos grąžtų tipas:
msg_blueprint_fatal_error=Neįmanoma įkelti brėžinio į vidinį inventorių. Praneškite apie šią klaidą kartu su išsaugotu žaidimu modifikacijos autoriui.
msg_print_info_miner_placement=__plural_for_parameter_1_{1=Suplanuotas|rest=Suplanuoti}__ __1__ kasybos __plural_for_parameter_1_{1=grąžtas|rest=grąžtai}__ su __2__ __plural_for_parameter_2_{1=konvejeriu|rest=konvejeriais}__ ant __3__ __plural_for_parameter_3_{1=resurso|rest=resursų}__.
msg_print_info_miner_placement_no_lanes=__plural_for_parameter_1_{1=Suplanuotas|rest=Suplanuoti}__ __1__ kasybos __plural_for_parameter_1_{1=grąžtas|rest=grąžtai}__ and __2__ __plural_for_parameter_2_{1=resurso|rest=resursų}__.
msg_print_info_lane_saturation_belts=Konvejeriai __1__, pralaidumas __2__
msg_print_info_lane_saturation_bounds=Juostų sumos __1__ - __2__
msg_print_cant_hide_last_choice=Negalima paslėpti paskutinio pasirinkimo

View File

@@ -0,0 +1,72 @@
[item-name]
mining-patch-planner=Mining patch planner
[shortcut-name]
mining-patch-planner-shortcut=Mining patch planner
[controls]
mining-patch-planner-keybind=Otwórz mining patch planner
[mod-setting-name]
mpp-lane-filling-info-duration=Czas wyświetlania informacji o napełnieniu pasa ruchu w sekundach
mpp-dump-heuristics-data=DEBUG: Zachowaj dane o układzie konfiguracji
[mod-setting-description]
mpp-lane-filling-info-duration=Czas trwania opóźnienia rozpoczyna się po zamknięciu GUI planu
[mpp]
advanced_settings=Zaawansowane ustawienia
settings_frame=Ustawienia planu
blueprint_add_mode=Dodaj projekty
show_non_electric_miners=Pokaż wiertnice górnicze, które nie używają energii elektrycznej
force_pipe_placement=Wymuś umieszczenie rurociągu
print_placement_info=Drukuj informacje o położeniu
display_lane_filling=Pokaż nakładkę nasycenia pasa ruchu w trybie ALT.\nZniknie po zamknięciu interfejsu graficznego (GUI).\n(Zmień czas wyświetlania w ustawieniach modyfikacji)
settings_layout_label=Rodzaj układu
settings_direction_label=Kierunek układu
settings_miner_label=Typ wiertnicy górniczej
settings_belt_label=Typ taśmociągu
settings_space_belt_label=Typ taśmociągu kosmicznego
settings_logistics_label=Typ logistyki
settings_pole_label=Typ słupów elektrycznych
settings_misc_label=Ustawienia różnorodne
settings_blueprints_label=Wybór projektu
settings_checkbox_lamps=Dodaj lampy
settings_layout_choice_base=Podstawowy
settings_layout_choice_simple=Prosty
settings_layout_choice_compact=Kompaktowy
settings_layout_choice_super_compact=Super kompaktowy
settings_layout_choice_sparse=Rozproszony
settings_layout_choice_logistics=Logistyczny
settings_layout_choice_compact_logistics=Kompaktowy logistyczny
settings_layout_choice_sparse_logistics=Rozproszony logistyczny
settings_layout_choice_blueprints=Projekty
choice_none=Brak
choice_lamp=Postaw lampy
choice_coverage=Przesycenie wiertnicami górniczymi
choice_landfill=Pomin zasypywanie terenu
choice_start=Ustaw do początku miejsca wydobycia
choice_deconstruction=Nie usuwaj obiektów
label_add_blueprint=Wstaw projekt
msg_miner_err_0=Nie wybrano odpowiednich zasobów
msg_miner_err_1_w=Rozpiętość jest zbyt mała, aby utworzyć układ (minimalna szerokość wynosi __1__)
msg_miner_err_1_h=Rozpiętość jest zbyt mała, aby utworzyć układ (minimalna wysokość wynosi __1__)
msg_miner_err_2_1=Nie można wybudować na tym złożu z wybraną wiertnicą górniczą.
msg_miner_err_2_2=ponieważ nie jest w stanie wydobywać zasobów
msg_miner_err_3=Brak odpowiedniej wiertnicy górniczej do tego układu
msg_blueprint_valid=To nie jest prawidłowy projekt. Najpierw stwórz kopię z biblioteki projektów
msg_blueprint_undefined_grid=BProjekt nie ma określonej wielkości siatki.
msg_unselected_blueprint=Żaden projekt nie jest wybrany
msg_blueprint_existing=Projekt już istnieje
msg_blueprint_no_miner=W projekcie nie znaleziono wiertnic górniczych.
msg_blueprint_fatal_error=Nie można skopiować projektu do wewnętrznego ekwipunku. Proszę zgłosić ten błąd wraz z plikiem zapisu do autora modyfikacji.
msg_print_info_miner_placement=Umieszczono __1__ wiertnicę __plural_for_parameter_1_{1=wiertło|rest=wiertła}__ with __2__ __plural_for_parameter_2_{1=taśmociąg|rest=taśmociągi}__ on __3__ __plural_for_parameter_3_{1=surowiec|rest=surowce}__.
msg_print_info_miner_placement_no_lanes=Umieszczono __1__ wiertnicę __plural_for_parameter_1_{1=wiertło|rest=wiertła}__ on __2__ __plural_for_parameter_2_{1=surowiec|rest=surowce}__.
msg_print_info_lane_saturation_belts=taśmociągi __1__, przepustowość __2__
msg_print_info_lane_saturation_bounds=sumy pasów __1__ - __2__

View File

@@ -0,0 +1,42 @@
[item-name]
mining-patch-planner=Планировщик шахт
[shortcut-name]
mining-patch-planner-shortcut=Планировщик шахт
[controls]
mining-patch-planner-keybind=Открыть Планировщик шахт
[mpp]
advanced_settings=Расширенные настройки
settings_frame=Настройщики Планировщика шахт
settings_layout_label=Тип макета
settings_direction_label=Направление макета
settings_miner_label=Тип бура
settings_belt_label=Тип конвеера
settings_logistics_label=Тип логистики
settings_pole_label=Тип электрического столба
settings_misc_label=Разные настройки
settings_checkbox_lamps=Добавить лампы
choice_landfill=Пропустить свалку
settings_layout_choice_base=Базовый
settings_layout_choice_simple=Простой
settings_layout_choice_compact=Компактный
settings_layout_choice_super_compact=Супер Компактный
settings_layout_choice_sparse=Редкий
settings_layout_choice_logistics=Логистический
settings_layout_choice_compact_logistics=Компактный Логистический
settings_layout_choice_sparse_logistics=Редкий Логистический
settings_layout_choice_blueprints=Чертёж
choice_none=None
choice_lamp=Разместите лампы
choice_coverage=Обеспечьте покрытие ресурсов
msg_miner_err_0=Подходящие ресурсы не выбраны
msg_miner_err_1_w=План слишком мал для создания макета (минимальная ширина __1__)
msg_miner_err_1_h=План слишком мал для создания макета (минимальная высота __1__)
msg_miner_err_2_1=Невозможно использовать это поле ресурса с выбранным майнером
msg_miner_err_2_2=потому что он не может добывать ресурсы

View File

@@ -0,0 +1,42 @@
[item-name]
mining-patch-planner=Планувальник шахт
[shortcut-name]
mining-patch-planner-shortcut=Планувальник шахт
[controls]
mining-patch-planner-keybind=Відкрити Планувальник шахт
[mpp]
advanced_settings=Розширені налаштування
settings_frame=Налаштувачі Планувальника шахт
settings_layout_label=Тип макета
settings_direction_label=Напрямок макета
settings_miner_label=Тип бура
settings_belt_label=Тип конвейєра
settings_logistics_label=Тип логістики
settings_pole_label=Тип опори
settings_misc_label=Різні налаштування
settings_checkbox_lamps=Додати лампи
choice_landfill=Пропустити звалище
settings_layout_choice_base=Базовий
settings_layout_choice_simple=Простий
settings_layout_choice_compact=Компактный
settings_layout_choice_super_compact=Супер Компактний
settings_layout_choice_sparse=Рідкісний
settings_layout_choice_logistics=Логістичний
settings_layout_choice_compact_logistics=Компактний Логістичний
settings_layout_choice_sparse_logistics=Рідкісний Логістичний
settings_layout_choice_blueprints=Креслення
choice_none=Жодного
choice_lamp=Розмістіть лампи
choice_coverage=Забезпечте покриття ресурсів
msg_miner_err_0=Відповідні ресурси не вибрані
msg_miner_err_1_w=План занадто малий для створення макета (мінімальна ширина __1__)
msg_miner_err_1_h=План занадто малий для створення макета (мінімальна висота __1__)
msg_miner_err_2_1=Неможливо використовувати це поле ресурсу з вибраним буром
msg_miner_err_2_2=бо він не може добувати ресурси

View File

@@ -0,0 +1,69 @@
local conf = require("configuration")
local enums = require("mpp.enums")
local current_version = 010600 -- 1.6.0
-- resetting a GUI manually from console
-- /c __mining-patch-planner__ game.player.gui.screen.mpp_settings_frame.destroy()
---@param player LuaPlayer
local function reset_gui(player)
local root = player.gui.left["mpp_settings_frame"] or player.gui.screen["mpp_settings_frame"]
if root then
root.destroy()
end
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid and cursor_stack.valid_for_read and cursor_stack.name == "mining-patch-planner" then
cursor_stack.clear()
end
end
script.on_configuration_changed(function(config_changed_data)
local version = global.version or 0
if config_changed_data.mod_changes["mining-patch-planner"] and version < current_version then
global.tasks = global.tasks or {}
conf.initialize_deconstruction_filter()
for player_index, data in pairs(global.players) do
---@cast data PlayerData
local player = game.players[player_index]
reset_gui(player)
--conf.initialize_global(player_index)
conf.update_player_data(player_index)
end
else
for player_index, data in pairs(global.players) do
reset_gui(game.players[player_index])
end
end
if version < 010504 then
rendering.clear("mining-patch-planner")
end
if version < 010600 then
for player_index, data in pairs(global.players) do
---@cast data PlayerData
local blueprints = data.blueprints
local bp_inventory = data.blueprint_items
for k, v in pairs(blueprints.flow) do
v.destroy()
end
if bp_inventory and bp_inventory.valid then
bp_inventory.clear()
bp_inventory.resize(1)
end
blueprints.original_id = {}
blueprints.mapping = {}
blueprints.cache = {}
blueprints.flow = {}
blueprints.button = {}
blueprints.delete = {}
blueprints.original_id = {}
end
end
global.version = current_version
end)

View File

@@ -0,0 +1,24 @@
local b = {}
b["robotMiningSite-extra"] = true
b["robotMiningSite-large"] = true
b["robotMiningSite-new"] = true
b["invisible-logistic-chest-storage"] = true
b["robot-chest-provider"] = true
b["robot-chest-requester"] = true
b["construction-recaller"] = true
b["logistic-recaller"] = true
b["se-space-elevator"] = true
b["se-space-elevator-energy-interface"] = true
b["se-space-elevator-energy-pole"] = true
b["se-spaceship-clamp"] = true
b["se-spaceship-clamp-place"] = true
b["se-spaceship-clamp-power-pole-external-east"] = true
b["se-spaceship-clamp-power-pole-external-west"] = true
b["se-spaceship-clamp-power-pole-internal"] = true
b["kr-quarry-drill"] = true
return b

View File

@@ -0,0 +1,171 @@
local EAST = defines.direction.east
local NORTH = defines.direction.north
local SOUTH = defines.direction.south
local WEST = defines.direction.west
---@class EvaluatedBlueprint
---@field valid boolean Are all blueprint entities valid
---@field w number
---@field h number
---@field tw number Runtime transposed width
---@field th number Runtime transposed height
---@field ox number Start offset x
---@field oy number Start offset y
---@field entities BlueprintEntityEx[] All entities in the blueprint
---@field entity_names table<string, number>
---@field miner_name string Mining drill name
local bp_meta = {}
bp_meta.__index = bp_meta
---@class BlueprintEntityEx : BlueprintEntity
---@field capstone_x boolean
---@field capstone_y boolean
---Blueprint analysis data
---@param bp LuaItemStack
---@return EvaluatedBlueprint
function bp_meta:new(bp)
---@type EvaluatedBlueprint
local new = setmetatable({}, self)
new.valid = true
new.w, new.h = bp.blueprint_snap_to_grid.x, bp.blueprint_snap_to_grid.y
new.ox, new.oy = 0, 0
if bp.blueprint_position_relative_to_grid then
new.ox = bp.blueprint_position_relative_to_grid.x
new.oy = bp.blueprint_position_relative_to_grid.y
end
new.entities = bp.get_blueprint_entities() or {}
new.entity_names = bp.cost_to_build
new:evaluate_tiling()
new:evaluate_miners()
return new
end
---Marks capstone BlueprintEntities
function bp_meta:evaluate_tiling()
local sw, sh = self.w, self.h
local buckets_x, buckets_y = {}, {}
for i, ent in pairs(self.entities) do
local x, y = ent.position.x, ent.position.y
ent.direction = ent.direction or NORTH
if not buckets_x[x] then buckets_x[x] = {} end
table.insert(buckets_x[x], ent)
if not buckets_y[y] then buckets_y[y] = {} end
table.insert(buckets_y[y], ent)
end
for _, bucket in pairs(buckets_y) do
for i = 1, #bucket-1 do
local e1 = bucket[i] ---@type BlueprintEntityEx
local e1x = e1.position.x
for j = 2, #bucket do
local e2 = bucket[j] ---@type BlueprintEntityEx
local e2x = e2.position.x
if e1x + sw == e2x or e1x - sw == e2x then
e2.capstone_x = true
end
end
end
end
for _, bucket in pairs(buckets_x) do
for i = 1, #bucket-1 do
local e1 = bucket[i] ---@type BlueprintEntityEx
local e1y = e1.position.y
for j = 2, #bucket do
local e2 = bucket[j] ---@type BlueprintEntityEx
local e2y = e2.position.y
if e1y + sh == e2y or e1y - sh == e2y then
e2.capstone_y = true
end
end
end
end
end
function bp_meta:evaluate_miners()
for _, ent in pairs(self.entities) do
local name = ent.name
if game.entity_prototypes[name].type == "mining-drill" then
--local proto = game.entity_prototypes[name]
self.miner_name = name
return
end
end
end
---@return BlueprintEntityEx[]
function bp_meta:get_mining_drills()
local miner_name = self.miner_name
local mining_drills = {}
for _, ent in pairs(self.entities) do
if ent.name == miner_name then
mining_drills[#mining_drills+1] = ent
end
end
return mining_drills
end
function bp_meta:check_valid()
for k, v in pairs(self.entity_names) do
if not game.entity_prototypes[k] and not game.item_prototypes[k] then
self.valid = false
return false
end
end
self.valid = true
return true
end
---Returns resource categories for a blueprint
---@return table<string, boolean>
function bp_meta:get_resource_categories()
local categories = {}
local proto = game.entity_prototypes[self.miner_name]
if proto.resource_categories then
for cat, bool in pairs(proto.resource_categories) do
categories[cat] = bool
end
end
return categories
end
---@return table<string, GridBuilding>
function bp_meta:get_entity_categories()
---@type table<string, GridBuilding>
local categories = {
["mining-drill"] = "miner",
["beacon"] = "beacon",
["transport-belt"] = "belt",
["underground-belt"] = "belt",
["splitter"] = "belt",
["inserter"] = "inserter",
["container"] = "container",
["logistic-container"] = "container",
["loader"] = "inserter",
["loader-1x1"] = "inserter",
["electric-pole"] = "pole",
}
---@type table<string, GridBuilding>
local category_map = {}
for _, ent in pairs(self.entities) do
local name = ent.name
local category = category_map[name]
if not category then
local ent_type = game.entity_prototypes[name].type
category = categories[ent_type] or "other"
category_map[name] = category
end
end
return category_map
end
return bp_meta

View File

@@ -0,0 +1,65 @@
local mpp_util = require("mpp.mpp_util")
local coord_revert_world = mpp_util.revert_world
local builder = {}
---@class GhostSpecification : LuaSurface.create_entity_param.entity_ghost
---@field grid_x number Grid x coordinate
---@field grid_y number Grid x coordinate
---@field radius number? Object radius or default to 0.5 if nil
---@field extent_w number? Object extent from origin, converted from radius if nil
---@field extent_h number? Object extent from origin, converted from radius if nil
---@field thing GridBuilding Enum for the grid
---@field items table<string, number>? Item requests
---@class PowerPoleGhostSpecification : GhostSpecification
---@field no_light boolean
---@field ix number
---@field iy number
--- Builder for a convenience function that automatically translates
--- internal grid state for a surface.create_entity call
---@param state State
---@return fun(ghost: GhostSpecification, check_allowed: boolean?): LuaEntity?
function builder.create_entity_builder(state)
local c = state.coords
local grid = state.grid
local DIR = state.direction_choice
local surface = state.surface
local gx, gy, tw, th = c.gx, c.gy, c.tw, c.th
local direction_conv = mpp_util.bp_direction[state.direction_choice]
local collected_ghosts = state._collected_ghosts
local is_space = state.is_space
return function(ghost, check_allowed)
ghost.raise_built = true
ghost.player = state.player
ghost.force = state.player.force
ghost.inner_name=ghost.name
ghost.name="entity-ghost"
ghost.position=coord_revert_world(gx, gy, DIR, ghost.grid_x, ghost.grid_y, tw, th)
ghost.direction=direction_conv[ghost.direction or defines.direction.north]
--local can_place = surface.can_place_entity(ghost)
if check_allowed and not surface.can_place_entity{
name = ghost.inner_name,
-- name = "entity-ghost",
-- inner_name = ghost.inner_name,
force = state.player.force,
position = ghost.position,
direction = ghost.direction,
build_check_type = defines.build_check_type.blueprint_ghost,
forced = true,
} then
return
end
local result = surface.create_entity(ghost)
if result then
grid:build_specification(ghost)
collected_ghosts[#collected_ghosts+1] = result
end
return result
end
end
return builder

View File

@@ -0,0 +1,176 @@
local sin, cos, pi = math.sin, math.cos, math.pi
local min, max = math.min, math.max
local function clamp(x, a, b)
return max(min(x, b or 1), a or 0)
end
local color = {}
-- OKLAB, OKHSV conversion functions yoinked from Björn Ottosson
-- https://bottosson.github.io/posts/colorpicker/
function color.oklab_to_linear_srgb(L, a, b)
local l_ = L + 0.3963377774 * a + 0.2158037573 * b;
local m_ = L - 0.1055613458 * a - 0.0638541728 * b;
local s_ = L - 0.0894841775 * a - 1.2914855480 * b;
local l = l_*l_*l_;
local m = m_*m_*m_;
local s = s_*s_*s_;
return
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
end
-- Finds the maximum saturation possible for a given hue that fits in sRGB
-- Saturation here is defined as S = C/L
-- a and b must be normalized so a^2 + b^2 == 1
local function compute_max_saturation(a, b)
-- Max saturation will be when one of r, g or b goes below zero.
-- Select different coefficients depending on which component goes below zero first
local k0, k1, k2, k3, k4, wl, wm, ws;
if -1.88170328 * a - 0.80936493 * b > 1 then
-- Red component
k0 = 1.19086277; k1 = 1.76576728; k2 = 0.59662641; k3 = 0.75515197; k4 = 0.56771245;
wl = 4.0767416621; wm = -3.3077115913; ws = 0.2309699292;
elseif 1.81444104 * a - 1.19445276 * b > 1 then
-- Green component
k0 = 0.73956515; k1 = -0.45954404; k2 = 0.08285427; k3 = 0.12541070; k4 = 0.14503204;
wl = -1.2684380046; wm = 2.6097574011; ws = -0.3413193965;
else
-- Blue component
k0 = 1.35733652; k1 = -0.00915799; k2 = -1.15130210; k3 = -0.50559606; k4 = 0.00692167;
wl = -0.0041960863; wm = -0.7034186147; ws = 1.7076147010;
end
-- Approximate max saturation using a polynomial:
local S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
-- Do one step Halley's method to get closer
-- this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
-- this should be sufficient for most applications, otherwise do two/three steps
local k_l = 0.3963377774 * a + 0.2158037573 * b;
local k_m = -0.1055613458 * a - 0.0638541728 * b;
local k_s = -0.0894841775 * a - 1.2914855480 * b;
do
local l_ = 1 + S * k_l;
local m_ = 1 + S * k_m;
local s_ = 1 + S * k_s;
local l = l_ * l_ * l_;
local m = m_ * m_ * m_;
local s = s_ * s_ * s_;
local l_dS = 3 * k_l * l_ * l_;
local m_dS = 3 * k_m * m_ * m_;
local s_dS = 3 * k_s * s_ * s_;
local l_dS2 = 6 * k_l * k_l * l_;
local m_dS2 = 6 * k_m * k_m * m_;
local s_dS2 = 6 * k_s * k_s * s_;
local f = wl * l + wm * m + ws * s;
local f1 = wl * l_dS + wm * m_dS + ws * s_dS;
local f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
S = S - f * f1 / (f1*f1 - 0.5 * f * f2);
end
return S
end
function find_cusp(a, b)
-- First, find the maximum saturation (saturation S = C/L)
local S_cusp = compute_max_saturation(a, b);
-- Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
local rgb_at_max = {color.oklab_to_linear_srgb(1, S_cusp * a, S_cusp * b)}
local L_cusp = (1 / max(max(rgb_at_max[1], rgb_at_max[2]), rgb_at_max[3])) ^ (1/3)
local C_cusp = L_cusp * S_cusp;
return L_cusp , C_cusp
end
local function get_ST_max(a_, b_, cusp)
if not cusp then
cusp = {find_cusp(a_, b_)}
end
local L = cusp[1];
local C = cusp[2];
return C/L, C/(1-L)
end
local function toe_inv(x)
local k_1 = 0.206
local k_2 = 0.03
local k_3 = (1+k_1)/(1+k_2)
return (x*x + k_1*x)/(k_3*(x+k_2))
end
local function srgb_transfer_function(value)
-- if value <= 0.04045 then
-- return value / 12.92
-- end
-- return ((value + 0.055) / 1.055) ^ 2.4;
return .0031308 >= value and (12.92 * value) or (1.055 * math.pow(value, .4166666666666667) - .055)
end
function color.linear_srgb_to_rgb(sr, sg, sb)
return
srgb_transfer_function(sr),
srgb_transfer_function(sg),
srgb_transfer_function(sb)
end
function color.okhsv_to_srgb(h, s, v)
local a_ = cos(2*pi*h)
local b_ = sin(2*pi*h)
local ST_max = {get_ST_max(a_,b_)};
local S_max = ST_max[1];
local S_0 = 0.5;
local T = ST_max[2]
local k = 1 - S_0/S_max;
local L_v = 1 - s*S_0/(S_0+T - T*k*s)
local C_v = s*T*S_0/(S_0+T-T*k*s)
local L = v*L_v;
local C = v*C_v;
local L_vt = toe_inv(L_v);
local C_vt = C_v * L_vt/L_v;
local L_new = toe_inv(L); -- * L_v/L_vt;
C = C * L_new/L;
L = L_new;
local rgb_scale = {color.oklab_to_linear_srgb(L_vt,a_*C_vt,b_*C_vt)};
local scale_L = (1/(max(rgb_scale[1],rgb_scale[2],rgb_scale[3],0))) ^ (1/3)
L = L * scale_L;
C = C * scale_L;
--local rgb = color.oklab_to_linear_srgb(L, C*a_, C*b_);
-- apply srgb transfer function
return color.oklab_to_linear_srgb(L, C*a_, C*b_)
end
function color.hue_sequence(n)
local r,g,b = color.okhsv_to_srgb((n * math.phi) % 1, 1, 1)
return {clamp(r), clamp(g), clamp(b)}
--return {r, g, b}
end
return color

View File

@@ -0,0 +1,131 @@
local enum = require("mpp.enums")
local compatibility = {}
--[[----------------------------------------------------------------------------
Space Exploration
----------------------------------------------------------------------------]]--
local space_exploration_active = nil
---@return boolean
compatibility.is_space_exploration_active = function()
if space_exploration_active == nil then
space_exploration_active = game.active_mods["space-exploration"] and true or false
end
return space_exploration_active
end
--- @class SERemoteViewToggledEventData: EventData
--- @field player_index uint
-- Thanks Raiguard
compatibility.get_se_events = function()
local se, events = remote.interfaces["space-exploration"], {}
if not se then return events end
if se.get_on_remote_view_started_event then
events["get_on_remote_view_started_event"] = remote.call("space-exploration", "get_on_remote_view_started_event")
end
if se.get_on_remote_view_stopped_event then
events["get_on_remote_view_stopped_event"] = remote.call("space-exploration", "get_on_remote_view_stopped_event")
end
return events
end
local memoize_space_surfaces = {}
--- Wrapper for Space Exploration get_zone_is_space remote interface calls
---@param surface_identification SurfaceIdentification
---@return boolean
compatibility.is_space = function(surface_identification)
local surface_index = surface_identification
if type(surface_identification) == "string" then
surface_identification = game.get_surface(surface_identification).index
elseif type(surface_identification) == "userdata" then
---@cast surface_identification LuaSurface
surface_identification = surface_identification.index
end
local memoized = memoize_space_surfaces[surface_index]
if memoized ~= nil then return memoized end
if game.active_mods["space-exploration"] then
local zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = surface_index} --[[@as table]]) --[[@as LuaSurface?]]
if not zone then
memoize_space_surfaces[surface_index] = false
return false
end
local result = remote.call("space-exploration", "get_zone_is_space", {zone_index = zone.index} --[[@as table]]) --[[@as boolean]]
memoize_space_surfaces[surface_index] = result
return result
end
memoize_space_surfaces[surface_index] = false
return false
end
---@type string
local space_collision_mask_name = nil
---@type table<string, boolean>
local memoize_space_buildable = {}
function compatibility.is_buildable_in_space(name)
local buildable_status = memoize_space_buildable[name]
if buildable_status ~= nil then
return buildable_status
end
-- if something goes wrong, give up, allow to build
if space_collision_mask_name == nil then
local scaffold_tile = game.tile_prototypes["se-space-platform-scaffold"]
if not scaffold_tile then
memoize_space_buildable[name] = true
return true
end
space_collision_mask_name = next(scaffold_tile.collision_mask)
if space_collision_mask_name == nil then
memoize_space_buildable[name] = true
return true
end
end
local entity_proto = game.entity_prototypes[name]
local allowed = not entity_proto.collision_mask[space_collision_mask_name]
memoize_space_buildable[name] = allowed
return allowed
end
--- Return true to skip non space item
---@param is_space boolean
---@param protype LuaEntityPrototype
---@return boolean
compatibility.guess_space_item = function(is_space, protype)
if not is_space then return false end
return string.match(protype.name, "^se%-")
end
--[[----------------------------------------------------------------------------
Pyanodons
----------------------------------------------------------------------------]]--
local pyanodons_active = nil
---@return boolean
compatibility.is_pyanodons_active = function()
if pyanodons_active == nil then
local active_mods = game.active_mods
-- pyanodons_active = game.active_mods["space-exploration"] and true or false
for k, v in ipairs{"pyrawores", "pycoalprocessing", "pyalienlife"} do
if active_mods[v] then
pyanodons_active = true
break
end
end
pyanodons_active = false
end
return pyanodons_active
end
return compatibility

View File

@@ -0,0 +1,143 @@
-- debug rendering library
local table_insert = table.insert
---@class DebugRendering
---@field _state State
---@field _enabled boolean Does drawing do anything
---@field _register boolean Add to render objects to be removed
local drawing_meta = {}
drawing_meta.__index = drawing_meta
function drawing_meta:draw_circle(t)
if not self._enabled then return end
local state = self._state --[[@as State]]
local C = state.coords
local x, y
if t.integer then
x, y = C.ix1, C.iy1
else
x, y = C.gx, C.gy
end
local tx, ty = t.x, t.y
t.surface = state.surface
t.players = {state.player}
t.width = t.width or 3
t.color = t.color or {1, 1, 1}
t.radius = t.radius or 0.5
t.target = { x + tx, y + ty }
local id = rendering.draw_circle(t)
if self._register then
table_insert(state._render_objects, id)
end
end
function drawing_meta:draw_line(t)
if not self._enabled then return end
local state = self._state --[[@as State]]
local C = state.coords
local x, y
if t.integer then
x, y = C.ix1, C.iy1
else
x, y = C.gx, C.gy
end
local tx, ty = t.x or t.x1, t.y or t.y1
local target1 = {x + tx, y + ty}
local target2
if t.x2 and t.y2 then
target2 = {x + t.x2, y + t.y2}
elseif t.w and t.h then
target2 = {x + tx + (t.w or 0), y + ty + (t.h or 0)}
else
return
end
t.surface = state.surface
t.players = {state.player}
t.width = t.width or 3
t.color = t.color or {1, 1, 1}
t.from = t.from or target1
t.to = t.to or target2
local id = rendering.draw_line(t)
if self._register then
table_insert(state._render_objects, id)
end
end
function drawing_meta:draw_rectangle(t)
if not self._enabled then return end
local state = self._state --[[@as State]]
local C = state.coords
local x, y
if t.integer then
x, y = C.ix1, C.iy1
else
x, y = C.gx, C.gy
end
local tx, ty = t.x or t.x1, t.y or t.y1
local target1 = {x + tx, y + ty}
local target2
if t.x2 and t.y2 then
target2 = {x + t.x2, y + t.y2}
elseif t.w and t.h then
target2 = {x + tx + (t.w or 0), y + ty + (t.h or 0)}
else
return
end
t.surface = state.surface
t.players = {state.player}
t.width = t.width or 3
t.color = t.color or {1, 1, 1}
t.left_top = t.left_top or target1
t.right_bottom = t.right_bottom or target2
local id = rendering.draw_rectangle(t)
if self._register then
table_insert(state._render_objects, id)
end
end
function drawing_meta:draw_text(t)
if not self._enabled then return end
local state = self._state --[[@as State]]
local C = state.coords
local x, y
if t.integer then
x, y = C.ix1, C.iy1
else
x, y = C.gx, C.gy
end
local tx, ty = t.x, t.y
t.surface = state.surface
t.players = {state.player}
t.width = t.width or 3
t.color = t.color or {1, 1, 1}
t.target = { x + tx, y + ty }
t.scale = t.scale or 1
t.alignment = t.alignment or "center"
t.vertical_alignment = t.vertical_alignment or "middle"
local id = rendering.draw_text(t)
if self._register then
table_insert(state._render_objects, id)
end
end
---comment
---@param state State
---@param enabled boolean Enable drawing
local function drawing(state, enabled, register)
return setmetatable({_state=state, _enabled=(not not enabled), _register=(not not register)}, drawing_meta)
end
return drawing

View File

@@ -0,0 +1,134 @@
local enums = {}
---@type LuaEntityPrototype[]
local cached_miners = {}
---@type table<string, boolean>
local cached_resource_categories = {}
local invalid_resource = { --fluid or otherwise
["se-core-mining"] = true,
}
local miner_blacklist = {
["se-core-miner-drill"] = true
}
function enums.get_default_miner()
if game.active_mods["nullius"] then
return "nullius-medium-miner-1"
end
return "electric-mining-drill"
end
---Get mining drills and resource categories
---@return LuaEntityPrototype[], table
function enums.get_available_miners()
enums.get_available_miners = function() return cached_miners, cached_resource_categories end
local all_miners = game.get_filtered_entity_prototypes{{filter="type", type="mining-drill"}}
---@type table<string, LuaEntityPrototype>
--local all_fluids = game.get_filtered_item_prototypes({filter="type", type="
local all_resources = game.get_filtered_entity_prototypes{{filter="type", type="resource"}}
---@type table<string, LuaResourceCategoryPrototype>
for name, proto in pairs(all_resources) do
---@cast proto LuaEntityPrototype
local mineable_properties = proto.mineable_properties
if mineable_properties.products then
for _, product in ipairs(mineable_properties.products) do
if product.type == "fluid" then
invalid_resource[name] = true
break
end
end
else
invalid_resource[name] = true
break
end
end
for miner_name, miner_proto in pairs(all_miners) do
if miner_blacklist[miner_name] then goto continue_miner end
if miner_proto.resource_categories then
for resource_cat, bool in pairs(miner_proto.resource_categories) do
if invalid_resource[resource_cat] then
miner_blacklist[miner_name] = true
end
end
else
miner_blacklist[miner_name] = true
goto continue_miner
end
---@cast miner_proto LuaEntityPrototype
local fluidboxes = miner_proto.fluidbox_prototypes
for _, fluidbox in pairs(fluidboxes) do
---@cast fluidbox LuaFluidBoxPrototype
if fluidbox.production_type == "output" then
miner_blacklist[miner_name] = true
end
end
::continue_miner::
end
if game.active_mods["Cursed-FMD"] then
local mangled_categories = {}
local miners = {}
for name, proto in pairs(all_miners) do
if string.find(name, ";") then -- Cursed-FMD hack
for resource_name, _ in pairs(proto.resource_categories) do
if not invalid_resource[resource_name] and not string.find(resource_name, "core-fragment") then
mangled_categories[resource_name] = true
end
end
else
if proto.flags and proto.flags.hidden then goto continue_miner end
if miner_blacklist[name] then goto continue_miner end
for resource_name, _ in pairs(proto.resource_categories) do
if not invalid_resource[resource_name] and not string.find(resource_name, "core-fragment") then
miners[name] = proto
end
end
end
::continue_miner::
end
cached_miners = miners
cached_resource_categories = mangled_categories
else
local miners = {}
local resource_categories = {
["basic-solid"] = true,
["hard-resource"] = true,
}
for name, proto in pairs(all_miners) do
if proto.flags and proto.flags.hidden then goto continue_miner end
if miner_blacklist[name] then goto continue_miner end
--if not proto.resource_categories["basic-solid"] then goto continue_miner end
for resource_category, bool in pairs(proto.resource_categories) do
resource_categories[resource_category] = bool
end
miners[name] = proto
::continue_miner::
end
cached_miners = miners
cached_resource_categories = resource_categories
--[[ {
["basic-solid"] = true,
["hard-resource"] = true,
}]]
end
return enums.get_available_miners()
end
enums.space_surfaces = {
["asteroid-belt"] = true,
["asteroid-field"] = true,
["orbit"] = true,
["anomaly"] = true,
}
return enums

View File

@@ -0,0 +1,45 @@
local table_insert = table.insert
List = require("mpp.list")
math.phi = 1.618033988749894
---Filters out a list
---@param t any
---@param func any
function table.filter(t, func)
local new = {}
for k, v in ipairs(t) do
if func(v) then new[#new+1] = v end
end
return new
end
function table.map(t, func)
local new = {}
for k, v in pairs(t) do
new[k] = func(v)
end
return new
end
function table.mapkey(t, func)
local new = {}
for k, v in pairs(t) do
new[func(v)] = v
end
return new
end
---Appends a list to the target table
---@param target table
---@param other any[]
function table.append(target, other)
for _, value in pairs(other) do
table_insert(target, value)
end
end
function math.divmod(a, b)
return math.floor(a / b), a % b
end

View File

@@ -0,0 +1,385 @@
local floor, ceil, min, max = math.floor, math.ceil, math.min, math.max
local mpp_util = require("mpp.mpp_util")
---@class GridRow: table<number, GridTile>
---@field [number] GridTile
---@class Grid
---@field [number] GridRow
local grid_mt = {}
grid_mt.__index = grid_mt
---Coordinate aggregate of the resource patch
---@class Coords
---@field x1 double Top left corner of the patch
---@field y1 double Top left corner of the patch
---@field x2 double Bottom right corner of the patch
---@field y2 double Bottom right corner of the patch
---@field ix1 number Integer top left corner of the patch
---@field iy1 number Integer top left corner of the patch
---@field ix2 number Integer bottom right corner
---@field iy2 number Integer bottom right corner
---@field w integer Width of the patch
---@field h integer Height of the patch
---@field tw integer Width Rotation invariant width
---@field th integer Height Rotation invariant height
---@field gx double x1 but -1 for grid rendering
---@field gy double y1 but -1 for grid rendering
---@field extent_x1 number Internal grid dimensions
---@field extent_y1 number Internal grid dimensions
---@field extent_x2 number Internal grid dimensions
---@field extent_y2 number Internal grid dimensions
---@alias GridBuilding
---| nil
---| "miner"
---| "pole"
---| "beacon"
---| "belt"
---| "inserter"
---| "container"
---| "lamp"
---| "other"
local need_electricity = {
miner = true,
beacon = true,
inserter = true
}
---@class GridTile
---@field amount number Amount of resource on tile
---@field neighbor_amount number Total resource sum of neighbors (performance killer?)
---@field neighbors_inner number Physical drill coverage
---@field neighbors_outer number Drill radius coverage
---@field x integer
---@field y integer
---@field gx double actual coordinate in surface
---@field gy double actual coordinate in surface
---@field built_on GridBuilding Is tile occupied by a building entity
---@field consumed boolean Is a miner consuming this tile
---@class BlueprintGridTile : GridTile
---@field neighbor_counts table<number, number>
---@field neighbors_inner nil
---@field neighbors_outer nil
---comment
---@param x integer Grid coordinate
---@param y integer Grid coordinate
---@return GridTile|nil
function grid_mt:get_tile(x, y)
local row = self[y]
if row then return row[x] end
end
---Convolves resource count for a grid cell
---For usage in blueprint layouts
---@param ox any
---@param oy any
---@param size any
function grid_mt:convolve(ox, oy, size)
local nx1, nx2 = ox, ox+size - 1
local ny1, ny2 = oy, oy+size - 1
for y = ny1, ny2 do
---@type table<number, BlueprintGridTile>
local row = self[y]
if row == nil then goto continue_row end
for x = nx1, nx2 do
local tile = row[x]
if tile == nil then goto continue_column end
local counts = tile.neighbor_counts
counts[size] = counts[size] + 1
::continue_column::
end
::continue_row::
end
end
---Convolves resource count for a grid cell
---@param ox any
---@param oy any
---@param size any
function grid_mt:convolve_inner(ox, oy, size)
local nx1, nx2 = ox, ox+size - 1
local ny1, ny2 = oy, oy+size - 1
for y = ny1, ny2 do
---@type table<number, GridTile>
local row = self[y]
if row == nil then goto continue_row end
for x = nx1, nx2 do
local tile = row[x]
if tile == nil then goto continue_column end
tile.neighbors_inner = tile.neighbors_inner + 1
::continue_column::
end
::continue_row::
end
end
---Convolves resource count for a grid cell
---@param ox any
---@param oy any
---@param size any
function grid_mt:convolve_outer(ox, oy, size, amount)
local nx1, nx2 = ox, ox+size - 1
local ny1, ny2 = oy, oy+size - 1
for y = ny1, ny2 do
---@type table<number, GridTile>
local row = self[y]
if row == nil then goto continue_row end
for x = nx1, nx2 do
local tile = row[x]
if tile == nil then goto continue_column end
tile.neighbors_outer = tile.neighbors_outer + 1
tile.neighbor_amount = tile.neighbor_amount + amount
::continue_column::
end
::continue_row::
end
end
---@param ox number
---@param oy number
---@param drill MinerStruct
function grid_mt:convolve_miner(ox, oy, drill)
local x1, y1 = ox-drill.extent_positive, ox-drill.extent_positive
local x2, y2 = x1+drill.area, y1+drill.area
local ix1, iy1 = ox-drill.size+1, oy-drill.size+1
local ix2, iy2 = x1+drill.size, y1+drill.size
for y = y1, y2 do
local row = self[y]
if row then
for x = x1, x2 do
---@type GridTile
local tile = row[x]
if tile then
tile.neighbors_outer = tile.neighbors_outer + 1
if ix1 < x and x < ix2 and iy1 < y and y < iy2 then
tile.neighbors_outer = tile.neighbors_outer + 1
end
end
end
end
end
end
---Marks tiles (with resources) as consumed by a mining drill
---@param ox integer
---@param oy integer
function grid_mt:consume(ox, oy, size)
local nx1, nx2 = ox, ox+size - 1
local ny1, ny2 = oy, oy+size - 1
for y = ny1, ny2 do
local row = self[y]
if row == nil then goto continue_row end
for x = nx1, nx2 do
local tile = row[x]
if tile and tile.amount > 0 then
tile.consumed = true
end
end
::continue_row::
end
end
---@param cx number
---@param cy number
---@param w number
---@param evenw boolean
---@param evenh boolean
---@deprecated
function grid_mt:consume_custom(cx, cy, w, evenw, evenh)
local ox, oy = evenw and 1 or 0, evenh and 1 or 0
local x1, x2 = cx+ox-w, cx+w
for y = cy+oy-w, cy+w do
local row = self[y]
if row == nil then goto continue_row end
for x = x1, x2 do
local tile = row[x]
if tile and tile.amount then
tile.consumed = true
end
end
::continue_row::
end
end
---Marks tiles as consumed by a miner
---@param tiles GridTile[]
function grid_mt:clear_consumed(tiles)
for _, tile in pairs(tiles) do
---@cast tile GridTile
tile.consumed = false
end
end
---Builder function
---@param cx number x coord
---@param cy number y coord
---@param size_w number
---@param thing GridBuilding Type of building
function grid_mt:build_thing(cx, cy, thing, size_w, size_h)
size_h = size_h or size_w
for y = cy, cy + size_h do
local row = self[y]
if row == nil then goto continue_row end
for x = cx, cx + size_w do
local tile = row[x]
if tile then
tile.built_on = thing
end
end
::continue_row::
end
end
function grid_mt:build_thing_simple(cx, cy, thing)
local row = self[cy]
if row then
local tile = row[cx]
if tile then
tile.built_on = thing
return true
end
end
end
---@param t GhostSpecification
function grid_mt:build_specification(t)
local cx, cy = t.grid_x, t.grid_y
local left, right = t.padding_pre, t.padding_post
local thing = t.thing
if left == nil and right == nil then
local row = self[cy]
if row then
local tile = row[cx]
if tile then
tile.built_on = thing
end
end
else
left, right = left or 0, right or 0
local x1, x2 = cx-left, cx+right
for y = cy-left, cy+right do
local row = self[y]
if row == nil then goto continue_row end
for x = x1, x2 do
local tile = row[x]
if tile then
tile.built_on = thing
end
end
::continue_row::
end
end
end
---Finds if an entity type is built near
---@param cx number x coord
---@param cy number y coord
---@param thing GridBuilding Type of building
---@return boolean
function grid_mt:find_thing(cx, cy, thing, size)
local x1, x2 = cx, cx + size
for y = cy, cy+size do
local row = self[y]
if row == nil then goto continue_row end
for x = x1, x2 do
local tile = row[x]
if tile and tile.built_on == thing then
return true
end
end
::continue_row::
end
return false
end
---Finds if an entity type is built near
---@param cx number x coord
---@param cy number y coord
---@param things table<string, true> Types of entities
---@param r number Radius
---@param even boolean Is even width building
---@return boolean
function grid_mt:find_thing_in(cx, cy, things, r, even)
things = mpp_util.list_to_keys(things)
local o = even and 1 or 0
for y = cy+o-r, cy+r do
local row = self[y]
if row == nil then goto continue_row end
for x = cx+o-r, cx+r do
local tile = row[x]
if tile and things[tile.built_on] then
return true
end
end
::continue_row::
end
return false
end
function grid_mt:build_miner(cx, cy, size)
self:build_thing(cx, cy, "miner", size, size)
end
function grid_mt:get_unconsumed(ox, oy, size)
local nx1, nx2 = ox, ox+size - 1
local ny1, ny2 = oy, oy+size - 1
local count = 0
for y = ny1, ny2 do
local row = self[y]
if row == nil then goto continue_row end
for x = nx1, nx2 do
local tile = row[x]
if tile and tile.amount > 0 and not tile.consumed then
count = count + 1
end
end
::continue_row::
end
return count
end
---@param mx number
---@param my number
---@param pole number|PoleStruct
---@return boolean
function grid_mt:needs_power(mx, my, pole)
local nx1, nx2, ny1, ny2
if type(pole) == "table" then
local size = pole.size
local extent = ceil((pole.supply_width-size) / 2)
nx1, nx2 = mx - extent, mx + extent
ny1, ny2 = my - extent, my + extent
else
nx1, nx2 = mx, mx+pole-1
ny1, ny2 = my, my+pole-1
end
for y = ny1, ny2 do
local row = self[y]
if row == nil then goto continue_row end
for x = nx1, nx2 do
---@type GridTile
local tile = row[x]
if tile and need_electricity[tile.built_on] then
return true
end
end
::continue_row::
end
return false
end
return grid_mt

View File

@@ -0,0 +1,34 @@
local table_insert = table.insert
---@class List
local list_mt = {}
list_mt.__index = list_mt
function list_mt:push(value)
table_insert(self, value)
return self
end
function list_mt:unshift(value)
table_insert(self, 1, value)
return self
end
function list_mt:append(...)
for _, value in pairs({...}) do
table_insert(self, value)
end
return self
end
function list_mt:contitional_append(check, ...)
if not check then return self end
for _, value in pairs({...}) do
table_insert(self, value)
end
return self
end
return function(t)
return setmetatable(t or {}, list_mt)
end

View File

@@ -0,0 +1,765 @@
local enums = require("mpp.enums")
local blacklist = require("mpp.blacklist")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST = defines.direction.east
local NORTH = defines.direction.north
local SOUTH = defines.direction.south
local WEST = defines.direction.west
---@alias DirectionString
---| "west"
---| "east"
---| "south"
---| "north"
local mpp_util = {}
---@alias CoordinateConverterFunction fun(number, number, number, number): number, number
---@type table<DirectionString, CoordinateConverterFunction>
local coord_convert = {
west = function(x, y, w, h) return x, y end,
east = function(x, y, w, h) return w-x, h-y end,
south = function(x, y, w, h) return h-y, x end,
north = function(x, y, w, h) return y, w-x end,
}
mpp_util.coord_convert = coord_convert
---@type table<DirectionString, CoordinateConverterFunction>
local coord_revert = {
west = coord_convert.west,
east = coord_convert.east,
north = coord_convert.south,
south = coord_convert.north,
}
mpp_util.coord_revert = coord_revert
mpp_util.miner_direction = {west="south",east="north",north="west",south="east"}
mpp_util.belt_direction = {west="north", east="south", north="east", south="west"}
mpp_util.opposite = {west="east",east="west",north="south",south="north"}
do
-- switch to (direction + EAST) % ROTATION
local d = defines.direction
mpp_util.bp_direction = {
west = {
[d.north] = d.north,
[d.east] = d.east,
[d.south] = d.south,
[d.west] = d.west,
},
north = {
[d.north] = d.east,
[d.east] = d.south,
[d.south] = d.west,
[d.west] = d.north,
},
east = {
[d.north] = d.south,
[d.east] = d.west,
[d.south] = d.north,
[d.west] = d.east,
},
south = {
[d.north] = d.west,
[d.east] = d.north,
[d.south] = d.east,
[d.west] = d.south,
},
}
end
---@type table<defines.direction, MapPosition.0>
local direction_coord = {
[NORTH] = {x=0, y=-1},
[WEST] = {x=-1, y=0},
[SOUTH] = {x=0, y=1},
[EAST] = {x=1, y=0},
}
mpp_util.direction_coord = direction_coord
---@class EntityStruct
---@field name string
---@field type string
---@field w number Collision width
---@field h number Collision height
---@field x number x origin
---@field y number y origin
---@field size number?
---@field extent_w number Half of width
---@field extent_h number Half of height
---@type table<string, EntityStruct>
local entity_cache = {}
---@param entity_name string
---@return EntityStruct
function mpp_util.entity_struct(entity_name)
local cached = entity_cache[entity_name]
if cached then return cached end
---@diagnostic disable-next-line: missing-fields
local struct = {} --[[@as EntityStruct]]
local proto = game.entity_prototypes[entity_name]
local cbox = proto.collision_box
local cbox_tl, cbox_br = cbox.left_top, cbox.right_bottom
local cw, ch = cbox_br.x - cbox_tl.x, cbox_br.y - cbox_tl.y
struct.name = entity_name
struct.type = proto.type
struct.w, struct.h = ceil(cw), ceil(ch)
struct.size = max(struct.w, struct.h)
struct.x = struct.w / 2 - 0.5
struct.y = struct.h / 2 - 0.5
struct.extent_w, struct.extent_h = struct.w / 2, struct.h / 2
entity_cache[entity_name] = struct
return struct
end
---@param struct EntityStruct
---@param direction defines.direction
---@return number, number, number, number
function mpp_util.rotate_struct(struct, direction)
if direction == NORTH or direction == SOUTH then
return struct.x, struct.y, struct.w, struct.h
end
return struct.y, struct.x, struct.h, struct.w
end
---A mining drill's origin (0, 0) is the top left corner
---The spawn location is (x, y), rotations need to rotate around
---@class MinerStruct : EntityStruct
---@field size number Physical miner size
---@field size_sq number Size squared
---@field symmetric boolean
---@field parity (-1|0) Parity offset for even sized drills, -1 when odd
---@field resource_categories table<string, boolean>
---@field radius float Mining area reach
---@field area number Full coverage span of the miner
---@field area_sq number Squared area
---@field outer_span number Lenght between physical size and end of radius
---@field module_inventory_size number
---@field middle number "Center" x position
---@field drop_pos MapPosition Raw drop position
---@field out_x integer Resource drop position x
---@field out_y integer Resource drop position y
---@field extent_negative number
---@field extent_positive number
---@field supports_fluids boolean
---@field skip_outer boolean Skip outer area calculations
---@field pipe_left number Y height on left side
---@field pipe_right number Y height on right side
---@field output_rotated table<defines.direction, MapPosition> Rotated output positions in reference to (0, 0) origin
---@field power_source_tooltip (string|table)?
---@type table<string, MinerStruct>
local miner_struct_cache = {}
---Calculates values for drill sizes and extents
---@param mining_drill_name string
---@return MinerStruct
function mpp_util.miner_struct(mining_drill_name)
local cached = miner_struct_cache[mining_drill_name]
if cached then return cached end
local miner_proto = game.entity_prototypes[mining_drill_name]
---@diagnostic disable-next-line: missing-fields
local miner = mpp_util.entity_struct(mining_drill_name) --[[@as MinerStruct]]
if miner.w ~= miner.h then
-- we have a problem ?
end
miner.size_sq = miner.size ^ 2
miner.symmetric = miner.size % 2 == 1
miner.parity = miner.size % 2 - 1
miner.radius = miner_proto.mining_drill_radius
miner.area = ceil(miner_proto.mining_drill_radius * 2)
miner.area_sq = miner.area ^ 2
miner.outer_span = floor((miner.area - miner.size) / 2)
miner.resource_categories = miner_proto.resource_categories
miner.name = miner_proto.name
miner.module_inventory_size = miner_proto.module_inventory_size
miner.extent_negative = floor(miner.size * 0.5) - floor(miner_proto.mining_drill_radius) + miner.parity
miner.extent_positive = miner.extent_negative + miner.area - 1
miner.middle = floor(miner.size / 2) + miner.parity
local nauvis = game.get_surface("nauvis") --[[@as LuaSurface]]
local dummy = nauvis.create_entity{
name = mining_drill_name,
position = {miner.x, miner.y},
}
if dummy then
miner.drop_pos = dummy.drop_position
miner.out_x = floor(dummy.drop_position.x)
miner.out_y = floor(dummy.drop_position.y)
dummy.destroy()
else
-- hardcoded fallback
local dx, dy = floor(miner.size / 2) + miner.parity, -1
miner.drop_pos = { dx+.5, -0.296875, x = dx+.5, y = -0.296875 }
miner.out_x = dx
miner.out_y = dy
end
local output_rotated = {
[defines.direction.north] = {miner.out_x, miner.out_y},
[defines.direction.south] = {miner.size - miner.out_x - 1, miner.size },
[defines.direction.west] = {miner.size, miner.out_x},
[defines.direction.east] = {-1, miner.size - miner.out_x -1},
}
output_rotated[NORTH].x = output_rotated[NORTH][1]
output_rotated[NORTH].y = output_rotated[NORTH][2]
output_rotated[SOUTH].x = output_rotated[SOUTH][1]
output_rotated[SOUTH].y = output_rotated[SOUTH][2]
output_rotated[WEST].x = output_rotated[WEST][1]
output_rotated[WEST].y = output_rotated[WEST][2]
output_rotated[EAST].x = output_rotated[EAST][1]
output_rotated[EAST].y = output_rotated[EAST][2]
miner.output_rotated = output_rotated
--pipe height stuff
if miner_proto.fluidbox_prototypes and #miner_proto.fluidbox_prototypes > 0 then
local connections = miner_proto.fluidbox_prototypes[1].pipe_connections
for _, conn in pairs(connections) do
---@cast conn FluidBoxConnection
-- pray a mod that does weird stuff with pipe connections doesn't appear
end
miner.pipe_left = floor(miner.size / 2) + miner.parity
miner.pipe_right = floor(miner.size / 2) + miner.parity
miner.supports_fluids = true
else
miner.supports_fluids = false
end
-- If larger than a large mining drill
miner.skip_outer = miner.size > 7 or miner.area > 13
if miner_proto.electric_energy_source_prototype then
miner.power_source_tooltip = {
"", " [img=tooltip-category-electricity] ",
{"tooltip-category.consumes"}, " ", {"tooltip-category.electricity"},
}
elseif miner_proto.burner_prototype then
local burner = miner_proto.burner_prototype --[[@as LuaBurnerPrototype]]
if burner.fuel_categories["nuclear"] then
miner.power_source_tooltip = {
"", "[img=tooltip-category-nuclear]",
{"tooltip-category.consumes"}, " ", {"fuel-category-name.nuclear"},
}
else
miner.power_source_tooltip = {
"", "[img=tooltip-category-chemical]",
{"tooltip-category.consumes"}, " ", {"fuel-category-name.chemical"},
}
end
elseif miner_proto.fluid_energy_source_prototype then
miner.power_source_tooltip = {
"", "[img=tooltip-category-water]",
{"tooltip-category.consumes"}, " ", {"tooltip-category.fluid"},
}
end
return miner
end
---@class PoleStruct : EntityStruct
---@field name string
---@field place boolean Flag if poles are to be actually placed
---@field size number
---@field radius number Power supply reach
---@field supply_width number Full width of supply reach
---@field wire number Max wire distance
---@field supply_area_distance number
---@field extent_negative number Negative extent of the supply reach
---@type table<string, PoleStruct>
local pole_struct_cache = {}
---@param pole_name string
---@return PoleStruct
function mpp_util.pole_struct(pole_name)
local cached_struct = pole_struct_cache[pole_name]
if cached_struct then return cached_struct end
local pole_proto = game.entity_prototypes[pole_name]
if pole_proto then
local pole = mpp_util.entity_struct(pole_name) --[[@as PoleStruct]]
local radius = pole_proto.supply_area_distance --[[@as number]]
pole.supply_area_distance = radius
pole.supply_width = floor(radius * 2)
pole.radius = pole.supply_width / 2
pole.wire = pole_proto.max_wire_distance
-- local distance = beacon_proto.supply_area_distance
-- beacon.area = beacon.size + distance * 2
-- beacon.extent_negative = -distance
local extent = (pole.supply_width - pole.size) / 2
pole.extent_negative = -extent
pole_struct_cache[pole_name] = pole
return pole
end
return {
place = false, -- nonexistent pole, use fallbacks and don't place
size = 1,
supply_width = 7,
radius = 3.5,
wire = 9,
}
end
---@class BeaconStruct : EntityStruct
---@field extent_negative number
---@field area number
local beacon_cache = {}
---@param beacon_name string
---@return BeaconStruct
function mpp_util.beacon_struct(beacon_name)
local cached_struct = beacon_cache[beacon_name]
if cached_struct then return cached_struct end
local beacon_proto = game.entity_prototypes[beacon_name]
local beacon = mpp_util.entity_struct(beacon_name) --[[@as BeaconStruct]]
local distance = beacon_proto.supply_area_distance
beacon.area = beacon.size + distance * 2
beacon.extent_negative = -distance
beacon_cache[beacon_name] = beacon
return beacon
end
local hardcoded_pipes = {}
---@param pipe_name string Name of the normal pipe
---@return string|nil, LuaEntityPrototype|nil
function mpp_util.find_underground_pipe(pipe_name)
if hardcoded_pipes[pipe_name] then
return hardcoded_pipes[pipe_name], game.entity_prototypes[hardcoded_pipes[pipe_name]]
end
local ground_name = pipe_name.."-to-ground"
local ground_proto = game.entity_prototypes[ground_name]
if ground_proto then
return ground_name, ground_proto
end
return nil, nil
end
function mpp_util.revert(gx, gy, direction, x, y, w, h)
local tx, ty = coord_revert[direction](x-.5, y-.5, w, h)
return {gx + tx+.5, gy + ty + .5}
end
---comment
---@param gx any
---@param gy any
---@param direction any
---@param x any
---@param y any
---@param w any
---@param h any
---@return unknown
---@return unknown
function mpp_util.revert_ex(gx, gy, direction, x, y, w, h)
local tx, ty = coord_revert[direction](x-.5, y-.5, w, h)
return gx + tx+.5, gy + ty + .5
end
function mpp_util.revert_world(gx, gy, direction, x, y, w, h)
local tx, ty = coord_revert[direction](x-.5, y-.5, w, h)
return {gx + tx, gy + ty}
end
---@class BeltStruct
---@field name string
---@field related_underground_belt string?
---@field underground_reach number?
---@type table<string, BeltStruct>
local belt_struct_cache = {}
function mpp_util.belt_struct(belt_name)
local cached = belt_struct_cache[belt_name]
if cached then return cached end
---@diagnostic disable-next-line: missing-fields
local belt = {} --[[@as BeltStruct]]
local belt_proto = game.entity_prototypes[belt_name]
belt.name = belt_name
local related = belt_proto.related_underground_belt
if related then
belt.related_underground_belt = related.name
belt.underground_reach = related.max_underground_distance
else
local match_attempts = {
["transport"] = "underground",
["belt"] = "underground-belt",
}
for pattern, replacement in pairs(match_attempts) do
local new_name = string.gsub(belt_name, pattern, replacement)
if new_name == belt_name then goto continue end
related = game.entity_prototypes[new_name]
if related then
belt.related_underground_belt = new_name
belt.underground_reach = related.max_underground_distance
break
end
::continue::
end
end
belt_struct_cache[belt_name] = belt
return belt
end
---@class InserterStruct : EntityStruct
---@field pickup_rotated table<defines.direction, MapPosition.0>
---@field drop_rotated table<defines.direction, MapPosition.0>
---@type table<string, InserterStruct>
local inserter_struct_cache = {}
function mpp_util.inserter_struct(inserter_name)
local cached = inserter_struct_cache[inserter_name]
if cached then return cached end
local inserter_proto = game.entity_prototypes[inserter_name]
local inserter = mpp_util.entity_struct(inserter_name) --[[@as InserterStruct]]
local function rotations(_x, _y)
_x, _y = floor(_x), floor(_y)
return {
[NORTH] = { x = _x, y = _y},
[EAST] = { x = -_y, y = -_x},
[SOUTH] = { x = -_x, y = -_y},
[WEST] = { x = _y, y = _x},
}
end
local pickup_position = inserter_proto.inserter_pickup_position --[[@as MapPosition.1]]
local drop_position = inserter_proto.inserter_drop_position --[[@as MapPosition.1]]
inserter.pickup_rotated = rotations(pickup_position[1], pickup_position[2])
inserter.drop_rotated = rotations(drop_position[1], drop_position[2])
inserter_struct_cache[inserter_name] = inserter
return inserter
end
---Calculates needed power pole count
---@param state SimpleState
function mpp_util.calculate_pole_coverage(state, miner_count, lane_count)
local cov = {}
local m = mpp_util.miner_struct(state.miner_choice)
local p = mpp_util.pole_struct(state.pole_choice)
-- Shift subtract
local covered_miners = ceil(p.supply_width / m.size)
local miner_step = covered_miners * m.size
-- Special handling to shift back small radius power poles so they don't poke out
local capable_span = false
if floor(p.wire) >= miner_step and m.size ~= p.supply_width then
capable_span = true
else
miner_step = floor(p.wire)
end
cov.capable_span = capable_span
local pole_start = m.middle
if capable_span then
if covered_miners % 2 == 0 then
pole_start = m.size-1
elseif miner_count % covered_miners == 0 then
pole_start = pole_start + m.size
end
end
cov.pole_start = pole_start
cov.pole_step = miner_step
cov.full_miner_width = miner_count * m.size
cov.lane_start = 0
cov.lane_step = m.size * 2 + 2
local lane_pairs = floor(lane_count / 2)
local lane_coverage = ceil((p.radius-1) / (m.size + 0.5))
if lane_coverage > 1 then
cov.lane_start = (ceil(lane_pairs / 2) % 2 == 0 and 1 or 0) * (m.size * 2 + 2)
cov.lane_step = lane_coverage * (m.size * 2 + 2)
end
cov.lamp_alter = miner_step < 9 and true or false
return cov
end
---Calculates the spacing for belt interleaved power poles
---@param state State
---@param miner_count number
---@param lane_count number
---@param force_capable (number|true)?
function mpp_util.calculate_pole_spacing(state, miner_count, lane_count, force_capable)
local cov = {}
local m = mpp_util.miner_struct(state.miner_choice)
local p = mpp_util.pole_struct(state.pole_choice)
-- Shift subtract
local covered_miners = ceil(p.supply_width / m.size)
local miner_step = covered_miners * m.size
if force_capable then
miner_step = force_capable == true and miner_step or force_capable --[[@as number]]
force_capable = miner_step
end
-- Special handling to shift back small radius power poles so they don't poke out
local capable_span = false
if floor(p.wire) >= miner_step then
capable_span = true
elseif force_capable and force_capable > 0 then
return mpp_util.calculate_pole_spacing(
state, miner_count, lane_count, miner_step - m.size
)
else
miner_step = floor(p.wire)
end
cov.capable_span = capable_span
local pole_start = m.size-1
if capable_span then
if covered_miners % 2 == 0 then
pole_start = m.size-1
elseif miner_count % covered_miners == 0 and miner_step ~= m.size then
pole_start = pole_start + m.size
end
end
cov.pole_start = pole_start
cov.pole_step = miner_step
cov.full_miner_width = miner_count * m.size
cov.lane_start = 0
cov.lane_step = m.size * 2 + 2
local lane_pairs = floor(lane_count / 2)
local lane_coverage = ceil((p.radius-1) / (m.size + 0.5))
if lane_coverage > 1 then
cov.lane_start = (ceil(lane_pairs / 2) % 2 == 0 and 1 or 0) * (m.size * 2 + 2)
cov.lane_step = lane_coverage * (m.size * 2 + 2)
end
return cov
end
---@param t table
---@param func function
---@return true | nil
function mpp_util.table_find(t, func)
for k, v in pairs(t) do
if func(v) then return true end
end
end
---@param t table
---@param m LuaObject
function mpp_util.table_mapping(t, m)
for k, v in pairs(t) do
if k == m then return v end
end
end
---@param player LuaPlayer
---@param blueprint LuaItemStack
function mpp_util.validate_blueprint(player, blueprint)
if not blueprint.blueprint_snap_to_grid then
player.print({"", "[color=red]", {"mpp.msg_blueprint_undefined_grid"}, "[/color]"}, {sound_path="utility/cannot_build"})
return false
end
local miners, _ = enums.get_available_miners()
local cost = blueprint.cost_to_build
local drills = {}
for name, drill in pairs(miners) do
if cost[name] then
drills[#drills+1] = drill.localised_name
end
end
if #drills > 1 then
local msg = {"", "[color=red]", {"mpp.msg_blueprint_different_miners"}, "[/color]" }
for _, name in pairs(drills) do
msg[#msg+1] = "\n"
msg[#msg+1] = name
end
player.print(msg, {sound_path="utility/cannot_build"})
return false
elseif #drills == 0 then
player.print({"", "[color=red]", {"mpp.msg_blueprint_no_miner"}, "[/color]"}, {sound_path="utility/cannot_build"})
return false
end
return true
end
function mpp_util.keys_to_set(...)
local set, temp = {}, {}
for _, t in pairs{...} do
for k, _ in pairs(t) do
temp[k] = true
end
end
for k, _ in pairs(temp) do
set[#set+1] = k
end
table.sort(set)
return set
end
function mpp_util.list_to_keys(t)
local temp = {}
for _, k in ipairs(t) do
temp[k] = true
end
return temp
end
---@param bp LuaItemStack
function mpp_util.blueprint_label(bp)
local label = bp.label
if label then
if #label > 30 then
return string.sub(label, 0, 28) .. "...", label
end
return label
else
return {"", {"gui-blueprint.unnamed-blueprint"}, " ", bp.item_number}
end
end
---@class CollisionBoxProperties
---@field w number
---@field h number
---@field near number
---@field [1] boolean
---@field [2] boolean
-- LuaEntityPrototype#tile_height was added in 1.1.64, I'm developing on 1.1.61
local even_width_memoize = {}
---Gets properties of entity collision box
---@param name string
---@return CollisionBoxProperties
function mpp_util.entity_even_width(name)
local check = even_width_memoize[name]
if check then return check end
local proto = game.entity_prototypes[name]
local cbox = proto.collision_box
local cbox_tl, cbox_br = cbox.left_top, cbox.right_bottom
local cw, ch = cbox_br.x - cbox_tl.x, cbox_br.y - cbox_tl.y
local w, h = ceil(cw), ceil(ch)
local res = {w % 2 ~= 1, h % 2 ~= 1, w=w, h=h, near=floor(w/2)}
even_width_memoize[name] = res
return res
end
--- local EAST, NORTH, SOUTH, WEST, ROTATION = mpp_util.directions()
function mpp_util.directions()
return
defines.direction.east,
defines.direction.north,
defines.direction.south,
defines.direction.west,
table_size(defines.direction)
end
---@param player_index uint
---@return uint
function mpp_util.get_display_duration(player_index)
return settings.get_player_settings(player_index)["mpp-lane-filling-info-duration"].value * 60 --[[@as uint]]
end
---@param player_index uint
---@return boolean
function mpp_util.get_dump_state(player_index)
return settings.get_player_settings(player_index)["mpp-dump-heuristics-data"].value --[[@as boolean]]
end
function mpp_util.wrap_tooltip(...)
return select(1, ...) and {"", " ", ...} or nil
end
function mpp_util.tooltip_entity_not_available(check, arg)
if check then
return mpp_util.wrap_tooltip(arg, "\n[color=red]", {"mpp.label_not_available"}, "[/color]")
end
return mpp_util.wrap_tooltip(arg)
end
---@param c1 Coords
---@param c2 Coords
function mpp_util.coords_overlap(c1, c2)
local x = (c1.ix1 <= c2.ix1 and c2.ix1 <= c1.ix2) or (c1.ix1 <= c2.ix2 and c2.ix2 <= c1.ix2) or
(c2.ix1 <= c1.ix1 and c1.ix1 <= c2.ix2) or (c2.ix1 <= c1.ix2 and c1.ix2 <= c2.ix2)
local y = (c1.iy1 <= c2.iy1 and c2.iy1 <= c1.iy2) or (c1.iy1 <= c2.iy2 and c2.iy2 <= c1.iy2) or
(c2.iy1 <= c1.iy1 and c1.iy1 <= c2.iy2) or (c2.iy1 <= c1.iy2 and c1.iy2 <= c2.iy2)
return x and y
end
---Checks if thing (entity) should never appear as a choice
---@param thing LuaEntityPrototype|MinerStruct
---@return boolean|nil
function mpp_util.check_filtered(thing)
return
blacklist[thing.name]
or (thing.flags and thing.flags.hidden)
end
---@param player_data any
---@param category MppSettingSections
---@param name string
function mpp_util.set_entity_hidden(player_data, category, name, value)
player_data.filtered_entities[category..":"..name] = value
end
function mpp_util.get_entity_hidden(player_data, category, name)
return player_data.filtered_entities[category..":"..name]
end
---Checks if a player has hidden the entity choice
---@param player_data any
---@param category MppSettingSections
---@param thing MinerStruct|LuaEntityPrototype
---@return false
function mpp_util.check_entity_hidden(player_data, category, thing)
return (not player_data.entity_filtering_mode and player_data.filtered_entities[category..":"..thing.name])
end
---@param player_data PlayerData
function mpp_util.update_undo_button(player_data)
local enabled = false
local undo_button = player_data.gui.undo_button
local last_state = player_data.last_state
if last_state then
local duration = mpp_util.get_display_duration(last_state.player.index)
enabled = enabled or (last_state and last_state._collected_ghosts and #last_state._collected_ghosts > 0 and game.tick < player_data.tick_expires)
end
undo_button.enabled = enabled
undo_button.sprite = enabled and "mpp_undo_enabled" or "mpp_undo_disabled"
undo_button.tooltip = mpp_util.wrap_tooltip(enabled and {"controls.undo"} or {"", {"controls.undo"}," (", {"gui.not-available"}, ")"})
end
return mpp_util

View File

@@ -0,0 +1,236 @@
local table_insert = table.insert
local min, max = math.min, math.max
-- broke: implement power pole connection calculation yourself
-- woke: place ghosts to make factorio calculate the connections
---@class PowerPoleGrid
---@field [number] table<number, GridPole>
local pole_grid_mt = {}
pole_grid_mt.__index = pole_grid_mt
---@class GridPole
---@field ix number Position in the pole grid
---@field iy number Position in the pole grid
---@field grid_x number Position in the full grid
---@field grid_y number Position in the full grid
---@field built boolean? Does the pole need to be built
---@field entity LuaEntity? Pole ghost LuaEntity
---@field has_consumers boolean Does pole cover any powered items
---@field backtracked boolean
---@field connections table<GridPole, true>
---@field set_id number?
---@field no_light boolean?
function pole_grid_mt.new()
local new = {
_max_x = 1,
_max_y = 1,
}
return setmetatable(new, pole_grid_mt)
end
---@param x number
---@param y number
---@param p GridPole
function pole_grid_mt:set_pole(x, y, p)
if p.connections == nil then p.connections = {} end
if not self[y] then self[y] = {} end
self._max_x = max(self._max_x, x)
self._max_y = max(self._max_y, y)
self[y][x] = p
end
---@param p GridPole
function pole_grid_mt:add_pole(p)
self:set_pole(p.ix, p.iy, p)
end
---@param x number
---@param y number
---@return GridPole | nil
function pole_grid_mt:get_pole(x, y)
if self[y] then return self[y][x] end
end
---@param p1 GridPole
---@param p2 GridPole
---@param struct PoleStruct
---@return boolean
function pole_grid_mt:pole_reaches(p1, p2, struct)
local x, y = p1.grid_x - p2.grid_x, p1.grid_y - p2.grid_y
return (x * x + y * y) ^ 0.5 <= (struct.wire)
end
---@param P PoleStruct
---@return table<number, table<GridPole, true>>
function pole_grid_mt:find_connectivity(P)
-- this went off the rails
local all_poles = {}
---@type table<GridPole, true>
local not_visited = {}
-- Make connections
for y1 = 1, #self do
local row = self[y1]
for x1 = 1, #row do
local pole = row[x1]
if pole == nil then goto continue end
table.insert(all_poles, pole)
local right = self:get_pole(x1+1, y1)
local bottom = self:get_pole(x1, y1+1)
if right and self:pole_reaches(pole, right, P) then
pole.connections[right], right.connections[pole] = true, true
end
if bottom and self:pole_reaches(pole, bottom, P) then
pole.connections[bottom], bottom.connections[pole] = true, true
end
::continue::
end
end
-- Process network connection sets
local unconnected = {}
---@type number, table<GridPole, true>
local set_id, current_set = 1, {}
---@type PoleConnectivity
local sets = {[0]=unconnected, [1]=current_set}
for _, v in pairs(all_poles) do not_visited[v] = true end
---@param pole GridPole
---@return number?
local function is_continuation(pole)
for other, _ in pairs(pole.connections) do
if other.set_id then
return other.set_id
end
end
end
---@param start_pole GridPole
local function iterate_connections(start_pole)
local continuation = is_continuation(start_pole)
if not continuation and table_size(current_set) > 0 then
if sets[set_id] == nil then
sets[set_id] = current_set
end
current_set, set_id = {}, set_id + 1
end
---@param pole GridPole
---@param depth_remaining number
local function recurse_pole(pole, depth_remaining)
not_visited[start_pole] = nil
if not pole.has_consumers then
unconnected[pole] = true
return
end
if pole.set_id and pole.set_id < set_id then
--Encountered a different set, merge to it
local target_set_id = pole.set_id
local target_set = sets[target_set_id]
for current_pole, v in pairs(current_set) do
current_set[current_pole], target_set[current_pole] = nil, true
current_pole.set_id = target_set_id
end
set_id = pole.set_id
current_set = target_set
else
pole.set_id = set_id
end
current_set[pole] = true
for other, _ in pairs(pole.connections) do
local other_not_visited = not_visited[other]
if other_not_visited and depth_remaining > 0 then
recurse_pole(other, depth_remaining-1)
end
end
end
recurse_pole(start_pole, 5)
end
local remaining = next(not_visited)
while remaining do
iterate_connections(remaining)
remaining = next(not_visited)
end
sets[set_id] = current_set
return sets
end
---List of sets
---set at index 0 are unconnected power poles
---@alias PoleConnectivity table<number, table<GridPole, true>>
---@param connectivity PoleConnectivity
---@return GridPole[]
function pole_grid_mt:ensure_connectivity(connectivity)
---@type GridPole[]
local connected = {}
local unconnecteds_set, main_set = connectivity[0], connectivity[1]
-- connect trivial power poles
for connector_pole in pairs(unconnecteds_set) do
--Set id is key and value is merge marker
local different_sets = {} --[[@as table<number, boolean>]]
for connection in pairs(connector_pole.connections) do
if connection.set_id ~= nil then
different_sets[connection.set_id] = true
end
end
if table_size(different_sets) < 2 then goto skip_pole end
local target_set_id = different_sets[1] and 1 or next(different_sets)
local target_set = connectivity[target_set_id]
for source_set_id in pairs(different_sets) do
if source_set_id == target_set_id then goto skip_merge_set end
local source_set = connectivity[source_set_id]
if different_sets[source_set_id] == true then
for source_pole in pairs(source_set) do
target_set[source_pole], source_set[source_pole] = true, nil
source_pole.set_id = target_set_id
different_sets[source_set_id] = false
end
end
::skip_merge_set::
end
connector_pole.built = true
connector_pole.set_id = target_set_id
target_set[connector_pole] = true
unconnecteds_set[connector_pole] = nil
::skip_pole::
end
---- append the main set to output list
--for pole in pairs(main_set) do table_insert(connected, pole) end
for set_id, pole_set in pairs(connectivity) do
if set_id == 0 then goto continue_set end
--if set_id == 1 then goto continue_set end
for pole in pairs(pole_set) do table_insert(connected, pole) end
::continue_set::
end
return connected
end
return pole_grid_mt

View File

@@ -0,0 +1,788 @@
local mpp_util = require("mpp.mpp_util")
local color = require("mpp.color")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local render_util = {}
---@class RendererParams
---@field origin MapPosition?
---@field target MapPosition?
---@field x number?
---@field y number?
---@field w number?
---@field h number?
---@field r number?
---@field color Color?
---@field width number?
---@field c Color?
---@field left_top MapPosition?
---@field right_bottom MapPosition?
---this went off the rails
---@param event EventData.on_player_reverse_selected_area
---@return MppRendering
function render_util.renderer(event)
---@param t RendererParams
local function parametrizer(t, overlay)
for k, v in pairs(overlay or {}) do t[k] = v end
if t.x and t.y then t.origin = {t.x, t.y} end
local target = t.origin or t.left_top --[[@as MapPosition]]
local left_top, right_bottom = t.left_top or t.origin or target, t.right_bottom or t.origin
if t.origin and t.w or t.h then
t.w, t.h = t.w or t.h, t.h or t.w
right_bottom = {(target[1] or target.x) + t.w, (target[2] or target.y) + t.h}
elseif t.r then
local r = t.r
local ox, oy = target[1] or target.x, target[2] or target.y
left_top = {ox-r, oy-r}
right_bottom = {ox+r, oy+r}
end
local new = {
surface = event.surface,
players = {event.player_index},
filled = false,
radius = t.r or 1,
color = t.c or t.color or {1, 1, 1},
left_top = left_top,
right_bottom = right_bottom,
target = target, -- circles
from = left_top,
to = right_bottom, -- lines
width = 1,
}
for k, v in pairs(t) do new[k]=v end
for _, v in ipairs{"x", "y", "h", "w", "r", "origin"} do new[v]=nil end
return new
end
local meta_renderer_meta = {}
meta_renderer_meta.__index = function(self, k)
return function(t, t2)
return {
rendering[k](
parametrizer(t, t2)
)
}
end end
local rendering = setmetatable({}, meta_renderer_meta)
---@class MppRendering
local rendering_extension = {}
---Draws an x between left_top and right_bottom
---@param params RendererParams
function rendering_extension.draw_cross(params)
rendering.draw_line(params)
rendering.draw_line({
width = params.width,
color = params.color,
left_top={
params.right_bottom[1],
params.left_top[2]
},
right_bottom={
params.left_top[1],
params.right_bottom[2],
}
})
end
function rendering_extension.draw_rectangle_dashed(params)
rendering.draw_line(params, {
from={params.left_top[1], params.left_top[2]},
to={params.right_bottom[1], params.left_top[2]},
dash_offset = 0.0,
})
rendering.draw_line(params, {
from={params.left_top[1], params.right_bottom[2]},
to={params.right_bottom[1], params.right_bottom[2]},
dash_offset = 0.5,
})
rendering.draw_line(params, {
from={params.right_bottom[1], params.left_top[2]},
to={params.right_bottom[1], params.right_bottom[2]},
dash_offset = 0.0,
})
rendering.draw_line(params, {
from={params.left_top[1], params.left_top[2]},
to={params.left_top[1], params.right_bottom[2]},
dash_offset = 0.5,
})
end
local meta = {}
function meta:__index(k)
return function(t, t2)
if rendering_extension[k] then
rendering_extension[k](parametrizer(t, t2))
else
rendering[k](parametrizer(t, t2))
end
end
end
return setmetatable({}, meta)
end
function render_util.draw_clear_rendering(player_data, event)
rendering.clear("mining-patch-planner")
end
---Draws the properties of a mining drill
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_drill_struct(player_data, event)
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local x, y = fx1 + 0.5, fy1 + 0.5
local fx2, fy2 = event.area.right_bottom.x, event.area.right_bottom.y
fx2, fy2 = ceil(fx2), ceil(fy2)
--renderer.draw_cross{x=fx1, y=fy1, w=fx2-fx1, h=fy2-fy1}
--renderer.draw_cross{x=fx1, y=fy1, w=2}
local drill = mpp_util.miner_struct(player_data.choices.miner_choice)
renderer.draw_circle{
x = fx1 + drill.drop_pos.x,
y = fy1 + drill.drop_pos.y,
c = {0, 1, 0},
r = 0.2,
}
-- drop pos
renderer.draw_cross{
x = fx1 + 0.5 + drill.out_x,
y = fy1 + 0.5 + drill.out_y,
r = 0.3,
}
for _, pos in pairs(drill.output_rotated) do
renderer.draw_cross{
x = fx1 + 0.5 + pos[1],
y = fy1 + 0.5 + pos[2],
r = 0.15,
width = 3,
c={0, 0, 0, .5},
}
end
renderer.draw_line{
from={x + drill.x, y},
to={x + drill.x, y + 2},
width = 2, color={0.5, 0.5, 0.5}
}
renderer.draw_line{
from={x + drill.x, y},
to={x + drill.x-.5, y + .65},
width = 2, color={0.5, 0.5, 0.5}
}
renderer.draw_line{
from={x + drill.x, y},
to={x + drill.x+.5, y + .65},
width = 2, color={0.5, 0.5, 0.5}
}
-- drill origin
renderer.draw_circle{
x = fx1 + 0.5,
y = fy1 + 0.5,
width = 2,
r = 0.4,
}
renderer.draw_text{
target={fx1 + .5, fy1 + .5},
text = "(0, 0)",
alignment = "center",
vertical_alignment="middle",
scale = 0.6,
}
-- negative extent - cyan
renderer.draw_cross{
x = fx1 +.5 + drill.extent_negative,
y = fy1 +.5 + drill.extent_negative,
r = 0.25,
c = {0, 0.8, 0.8},
}
-- positive extent - purple
renderer.draw_cross{
x = fx1 +.5 + drill.extent_positive,
y = fy1 +.5 + drill.extent_positive,
r = 0.25,
c = {1, 0, 1},
}
renderer.draw_rectangle{
x=fx1,
y=fy1,
w=drill.size,
h=drill.size,
width=3,
gap_length=0.5,
dash_length=0.5,
}
renderer.draw_rectangle_dashed{
x=fx1 + drill.extent_negative,
y=fy1 + drill.extent_negative,
w=drill.area,
h=drill.area,
c={0.5, 0.5, 0.5},
width=5,
gap_length=0.5,
dash_length=0.5,
}
if drill.supports_fluids then
-- pipe connections
renderer.draw_line{
width=4, color = {0, .7, 1},
from={fx1-.3, y+drill.pipe_left-.5},
to={fx1-.3, y+drill.pipe_left+.5},
}
renderer.draw_line{
width=4, color = {.7, .7, 0},
from={fx1+drill.size+.3, y+drill.pipe_left-.5},
to={fx1+drill.size+.3, y+drill.pipe_left+.5},
}
end
renderer.draw_text{
target={fx1 + drill.extent_negative, fy1 + drill.extent_negative-1.5},
text = string.format("skip_outer: %s", drill.skip_outer),
alignment = "left",
vertical_alignment="middle",
}
renderer.draw_circle{x = fx1, y = fy1, r = 0.1}
--renderer.draw_circle{ x = fx2, y = fy2, r = 0.15, color={1, 0, 0} }
end
---Preview the pole coverage
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_pole_layout(player_data, event)
rendering.clear("mining-patch-planner")
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
--renderer.draw_cross{x=fx1, y=fy1, w=fx2-fx1, h=fy2-fy1}
--renderer.draw_cross{x=fx1, y=fy1, w=2}
local drill = mpp_util.miner_struct(player_data.choices.miner_choice)
local pole = mpp_util.pole_struct(player_data.choices.pole_choice)
local function draw_lane(x, y, count)
for i = 0, count-1 do
renderer.draw_rectangle{
x = x + drill.size * i + 0.15 , y = y+.15,
w = drill.size-.3, h=1-.3,
color = i % 2 == 0 and {143/255, 86/255, 59/255} or {223/255, 113/255, 38/255},
width=2,
}
end
---@diagnostic disable-next-line: param-type-mismatch
local coverage = mpp_util.calculate_pole_coverage(player_data.choices, count, 1)
renderer.draw_circle{
x=x+.5, y=y-0.5, radius = .25, color={0.7, 0.7, 0.7},
}
for i = coverage.pole_start, coverage.full_miner_width, coverage.pole_step do
renderer.draw_circle{
x = x + i + .5,
y = y - .5,
radius = 0.3, width=2,
color = {0, 1, 1},
}
renderer.draw_line{
x = x + i +.5 - pole.supply_width / 2+.2,
y = y - .2,
h = 0,
w = pole.supply_width-.4,
color = {0, 1, 1},
width = 2,
}
renderer.draw_line{
x = x + i +.5 - pole.supply_width / 2 + .2,
y = y - .7,
h = .5,
w = 0,
color = {0, 1, 1},
width = 2,
}
renderer.draw_line{
x = x + i +.5 + pole.supply_width / 2 - .2,
y = y - .7,
h = .5,
w = 0,
color = {0, 1, 1},
width = 2,
}
end
end
for i = 1, 10 do
draw_lane(fx1, fy1+(i-1)*3, i)
end
end
---Preview the pole coverage
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_pole_layout_compact(player_data, event)
rendering.clear("mining-patch-planner")
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
--renderer.draw_cross{x=fx1, y=fy1, w=fx2-fx1, h=fy2-fy1}
--renderer.draw_cross{x=fx1, y=fy1, w=2}
local drill = mpp_util.miner_struct(player_data.choices.miner_choice)
local pole = mpp_util.pole_struct(player_data.choices.pole_choice)
local function draw_lane(x, y, count)
for i = 0, count-1 do
renderer.draw_rectangle{
x = x + drill.size * i + 0.15 , y = y+.15,
w = drill.size-.3, h=1-.3,
color = i % 2 == 0 and {143/255, 86/255, 59/255} or {223/255, 113/255, 38/255},
width=2,
}
end
---@diagnostic disable-next-line: param-type-mismatch
local coverage = mpp_util.calculate_pole_spacing(player_data.choices, count, 1)
renderer.draw_circle{
x=x+.5, y=y-0.5, radius = .25, color={0.7, 0.7, 0.7},
}
for i = coverage.pole_start, coverage.full_miner_width, coverage.pole_step do
renderer.draw_circle{
x = x + i + .5,
y = y - .5,
radius = 0.3, width=2,
color = {0, 1, 1},
}
renderer.draw_line{
x = x + i +.5 - pole.supply_width / 2+.2,
y = y - .2,
h = 0,
w = pole.supply_width-.4,
color = {0, 1, 1},
width = 2,
}
renderer.draw_line{
x = x + i +.5 - pole.supply_width / 2 + .2,
y = y - .7,
h = .5,
w = 0,
color = {0, 1, 1},
width = 2,
}
renderer.draw_line{
x = x + i +.5 + pole.supply_width / 2 - .2,
y = y - .7,
h = .5,
w = 0,
color = {0, 1, 1},
width = 2,
}
end
end
for i = 1, 10 do
draw_lane(fx1, fy1+(i-1)*3, i)
end
end
---Displays the labels of built things on the grid
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_built_things(player_data, event)
rendering.clear("mining-patch-planner")
local renderer = render_util.renderer(event)
local state = player_data.last_state
if not state then return end
local C = state.coords
local G = state.grid
for _, row in pairs(G) do
for _, tile in pairs(row) do
---@cast tile GridTile
local thing = tile.built_on
if thing then
-- renderer.draw_circle{
-- x = C.gx + tile.x, y = C.gy + tile.y,
-- w = 1,
-- color = {0, 0.5, 0, 0.1},
-- r = 0.5,
-- }
renderer.draw_rectangle{
x = C.ix1 + tile.x -.9, y = C.iy1 + tile.y -.9,
w = .8,
color = {0, 0.2, 0, 0.1},
}
renderer.draw_text{
x = C.gx + tile.x, y = C.gy + tile.y - .3,
alignment = "center",
vertical_alignment = "top",
--vertical_alignment = tile.x % 2 == 1 and "top" or "bottom",
text = thing,
scale = 0.6,
}
end
end
end
end
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_drill_convolution(player_data, event)
rendering.clear("mining-patch-planner")
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local state = player_data.last_state
if not state then return end
local C = state.coords
local grid = state.grid
for _, row in pairs(grid) do
for _, tile in pairs(row) do
---@cast tile GridTile
--local c1, c2 = tile.neighbor_counts[m_size], tile.neighbor_counts[m_area]
local c1, c2 = tile.neighbors_inner, tile.neighbors_outer
if c1 == 0 and c2 == 0 then goto continue end
rendering.draw_circle{
surface = state.surface, filled=false, color = {0.3, 0.3, 1},
width=1, radius = 0.5,
target={C.gx + tile.x, C.gy + tile.y},
}
local stagger = (.5 - (tile.x % 2)) * .25
local col = c1 == 0 and {0.3, 0.3, 0.3} or {0.6, 0.6, 0.6}
rendering.draw_text{
surface = state.surface, filled = false, color = col,
target={C.gx + tile.x, C.gy + tile.y + stagger},
text = string.format("%i,%i", c1, c2),
alignment = "center",
vertical_alignment="middle",
}
::continue::
end
end
end
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_power_grid(player_data, event)
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local state = player_data.last_state
if not state then return end
---@cast state SimpleState
local C = state.coords
local grid = state.grid
local connectivity = state.power_connectivity
if not connectivity then
game.print("No connectivity exists")
return
end
local rendered = {}
for set_id, set in pairs(connectivity) do
local set_color = color.hue_sequence(set_id)
if set_id == 0 then set_color = {1, 1, 1} end
for pole, _ in pairs(set) do
---@cast pole GridPole
-- if rendered[pole] then goto continue end
-- rendered[pole] = true
local pole_color = set_color
-- if not pole.backtracked and not pole.has_consumers then
-- pole_color = {0, 0, 0}
-- end
renderer.draw_circle{
surface = state.surface,
filled = not pole.backtracked,
color = pole_color,
width = 5,
target = {C.gx + pole.grid_x, C.gy + pole.grid_y},
radius = 0.65,
}
renderer.draw_text{
surface = state.surface,
target={C.gx + pole.grid_x, C.gy + pole.grid_y},
text = set_id,
alignment = "center",
vertical_alignment="middle",
scale = 2,
}
renderer.draw_text{
surface = state.surface,
target={C.gx + pole.grid_x, C.gy + pole.grid_y-1.25},
text = ("%i,%i"):format(pole.ix, pole.iy),
alignment = "center",
vertical_alignment="middle",
scale = 2,
}
::continue::
end
end --]]
end
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_centricity(player_data, event)
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local state = player_data.last_state
if not state then return end
local C = state.coords
local grid = state.grid
end
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_blueprint_data(player_data, event)
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local x, y = fx1 + 0.5, fy1 + 0.5
local id = player_data.choices.blueprint_choice and player_data.choices.blueprint_choice.item_number
if not id then return end
local bp = player_data.blueprints.cache[id]
if not bp then return end
renderer.draw_line{x = fx1, y = fy1-1, w = 0, h = 2, width = 7, color={0, 0, 0}}
renderer.draw_line{x = fx1-1, y = fy1, w = 2, h = 0, width = 7, color={0, 0, 0}}
renderer.draw_rectangle{
x = fx1,
y = fy1,
w = bp.w,
h = bp.h,
}
for _, ent in pairs(bp.entities) do
local struct = mpp_util.entity_struct(ent.name)
local clr = {1, 1, 1}
if ent.capstone_x and ent.capstone_y then
clr = {0, 1, 1}
elseif ent.capstone_x then
clr = {0, 1, 0}
elseif ent.capstone_y then
clr = {0, 0, 1}
end
renderer.draw_circle{
x = fx1 + ent.position.x,
y = fy1 + ent.position.y,
r = struct.size / 2,
color = clr,
}
end
end
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_deconstruct_preview(player_data, event)
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local state = player_data.last_state
if not state then return end
local c = state.coords
local grid = state.grid
local layout = algorithm.layouts[state.layout_choice]
if not layout._get_deconstruction_objects then return end
local objects = layout:_get_deconstruction_objects(state)
local DIR = state.direction_choice
for _, t in pairs(objects) do
for _, object in ipairs(t) do
---@cast object GhostSpecification
local extent_w = object.extent_w or object.radius or 0.5
local extent_h = object.extent_h or extent_w
local x1, y1 = object.grid_x-extent_w, object.grid_y-extent_h
local x2, y2 = object.grid_x+extent_w, object.grid_y+extent_h
x1, y1 = mpp_util.revert_ex(c.gx, c.gy, DIR, x1, y1, c.tw, c.th)
x2, y2 = mpp_util.revert_ex(c.gx, c.gy, DIR, x2, y2, c.tw, c.th)
rendering.draw_rectangle{
surface=state.surface,
players={state.player},
filled=false,
width=3,
color={1, 0, 0},
left_top={x1+.1,y1+.1},
right_bottom={x2-.1,y2-.1},
}
end
end
end
---@param player_data PlayerData
---@param event EventData.on_player_reverse_selected_area
function render_util.draw_can_place_entity(player_data, event)
local renderer = render_util.renderer(event)
local fx1, fy1 = event.area.left_top.x, event.area.left_top.y
fx1, fy1 = floor(fx1), floor(fy1)
local x, y = fx1 + 0.5, fy1 + 0.5
local id = player_data.choices.blueprint_choice and player_data.choices.blueprint_choice.item_number
if not id then return end
local bp = player_data.blueprints.cache[id]
if not bp then return end
renderer.draw_line{x = fx1, y = fy1-1, w = 0, h = 1, width = 7, color={0.3, 0.3, 0.3}}
renderer.draw_line{x = fx1-1, y = fy1, w = 1, h = 0, width = 7, color={0.3, 0.3, 0.3}}
local build_check_type = defines.build_check_type
local can_forced = {
[{false, false}] = 0,
[{true, false}] = 1,
[{false, true}] = 2,
[{true, true}] = 3,
}
for check_type, i1 in pairs(build_check_type) do
for forced_ghost, i2 in pairs(can_forced) do
local forced, ghost = forced_ghost[1], forced_ghost[2]
local nx = x + (bp.w + 3) * i1
local ny = y + (bp.h + 3) * i2
renderer.draw_rectangle{
x = nx-.5,
y = ny-.5,
w = bp.w,
h = bp.h,
color = {0.2, 0.2, .7},
}
-- renderer.draw_text{
-- x = nx+(bp.w-1)/2, y = ny-.5,
-- text = ("%i,%i"):format(i1, i2),
-- alignment = "center",
-- vertical_alignment="bottom",
-- }
renderer.draw_text{
x = nx+(bp.w-1)/2, y = ny-1.5,
text = ("%s"):format(check_type),
alignment = "center",
vertical_alignment="bottom",
}
renderer.draw_text{
x = nx+(bp.w-1)/2, y = ny-1,
text = ("forced: %s"):format(forced),
alignment = "center",
vertical_alignment="bottom",
}
renderer.draw_text{
x = nx+(bp.w-1)/2, y = ny-.5,
text = ("ghost: %s"):format(ghost),
alignment = "center",
vertical_alignment="bottom",
}
for _, ent in pairs(bp.entities) do
local ex = nx + ent.position.x - .5
local ey = ny + ent.position.y - .5
local t = {
name = ent.name,
position = {ex, ey},
direction = ent.direction,
build_check_type = defines.build_check_type[check_type],
force = game.get_player(event.player_index).force,
forced = forced,
}
if ghost then
t.name, t.inner_name = "entity-ghost", t.name
end
local can_place = event.surface.can_place_entity(t)
renderer.draw_circle{x = ex, y = ey, r = 0.5, width = 3,
color = can_place and {0.1, .9, .1} or {0.9, .1, .1},
}
if can_place then
if not ghost then
t.name, t.inner_name = "entity-ghost", t.name
end
event.surface.create_entity(t)
end
end
end
end
end
return render_util

View File

@@ -0,0 +1,25 @@
local set_mt = {}
set_mt.__index = set_mt
function set_mt.new(t)
local new = {}
for k, v in pairs(t or {}) do
new[v] = true
end
return setmetatable(new, set_mt)
end
local list_mt = {}
list_mt.__index = list_mt
function list_mt.new(t)
local new = {}
for k, v in pairs(t or {}) do
end
return setmetatable(new, list_mt)
end
return set_mt, list_mt

View File

@@ -0,0 +1,67 @@
local graphics = "__mining-patch-planner__/graphics/"
data:extend{
{
type="selection-tool",
name="mining-patch-planner",
icon=graphics.."drill-icon.png",
icon_size = 64,
flags = {"only-in-cursor", "hidden", "spawnable", "not-stackable"},
stack_size = 1,
order="c[automated-construction]-e[miner-planner]",
draw_label_for_cursor_render = false,
selection_color = {r=0, g=0, b=1, a=0.5},
selection_cursor_box_type="entity",
selection_mode={"any-entity"},
entity_filter_mode="whitelist",
entity_type_filters = {"resource"},
tile_filter_mode = "whitelist",
tile_filters = {"water-wube"},
alt_selection_color = {r=0, g=0, b=1, a=0.5},
alt_selection_cursor_box_type="entity",
alt_selection_mode={"any-entity"},
reverse_selction_color = {1, 1, 0, 1},
reverse_selection_mode={"any-entity"},
reverse_selection_cursor_box_type="pair",
reverse_entity_filter_mode="whitelist",
reverse_entity_type_filters = {"resource"},
reverse_tile_filters = {"water-wube" },
},
{
type="custom-input",
name="mining-patch-planner-keybind",
key_sequence="CONTROL + M",
action="spawn-item",
item_to_spawn="mining-patch-planner",
},
{
type="shortcut",
name="mining-patch-planner-shortcut",
icon={
filename=graphics.."drill-icon-toolbar-white.png",
priority = "extra-high-no-scale",
size=32,
flags={"gui-icon"}
},
small_icon={
filename=graphics.."drill-icon-toolbar-white.png",
priority = "extra-high-no-scale",
size=32,
scale=1,
flags={"gui-icon"}
},
disabled_small_icon={
filename=graphics.."drill-icon-toolbar-disabled.png",
priority = "extra-high-no-scale",
size=32,
scale=1,
flags={"gui-icon"}
},
order="b[blueprints]-i[miner-planner]",
action = "spawn-item",
icon_size = 64,
item_to_spawn="mining-patch-planner",
style="blue",
associated_control_input="mining-patch-planner-keybind",
},
}

View File

@@ -0,0 +1,300 @@
local graphics = "__mining-patch-planner__/graphics/"
data:extend{
{
type = "sprite",
name = "mpp_advanced_settings",
filename = graphics.."advanced-settings.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_advanced_settings_black",
filename = graphics.."advanced-settings-black.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_no_entity",
filename = graphics.."no-entity.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_plus",
filename = graphics.."plus.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_cross",
filename = graphics.."cross.png",
size = 64,
mipmap_count = 2,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_blueprint_add",
filename = graphics.."blueprint_add.png",
size = 64,
mipmap_count = 2,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_direction_north",
filename = graphics.."arrow-north.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_direction_east",
filename = graphics.."arrow-east.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_direction_south",
filename = graphics.."arrow-south.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_direction_west",
filename = graphics.."arrow-west.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_miner_coverage",
filename = graphics.."miner_coverage.png",
size = 64,
mipmap_count = 2,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_miner_coverage_disabled",
filename = graphics.."miner_coverage_disabled.png",
size = 64,
mipmap_count = 2,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_omit_landfill",
filename = graphics.."omit_landfill.png",
size = 64,
mipmap_count = 3,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_align_start",
filename = graphics.."align_start.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_omit_deconstruct",
filename = graphics.."omit_deconstruct.png",
size = 64,
mipmap_count = 2,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_deconstruct",
filename = graphics.."deconstruct.png",
size = 64,
mipmap_count = 2,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_no_module",
filename = graphics.."no_module.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_show_all_miners",
filename = graphics.."show_all_miners.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_no_pipe",
filename = graphics.."no_pipe.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_no_lamp",
filename = graphics.."no_lamp.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_force_pipe_disabled",
filename = graphics.."force_pipe_disabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_force_pipe_enabled",
filename = graphics.."force_pipe_enabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_print_placement_info_disabled",
filename = graphics.."print-placement-info-disabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_print_placement_info_enabled",
filename = graphics.."print-placement-info-enabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_display_lane_filling_disabled",
filename = graphics.."display-lane-filling-disabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_display_lane_filling_enabled",
filename = graphics.."display-lane-filling-enabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_debugging_grid_convolution",
filename = graphics.."grid_convolution.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_entity_filtering_mode_enabled",
filename = graphics.."entity_filtering_mode_enabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_entity_filtering_mode_disabled",
filename = graphics.."entity_filtering_mode_disabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_entity_filtered_overlay",
filename = graphics.."entity_filtered_overlay.png",
size = 64,
mipmap_count = 0,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_undo_enabled",
filename = graphics.."undo_enabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_undo_disabled",
filename = graphics.."undo_disabled.png",
size = 64,
mipmap_count = 1,
flags = { "icon" },
},
{
type = "sprite",
name = "mpp_tooltip_category_size",
filename = graphics.."tooltip-category-size.png",
priority = "extra-high-no-scale",
size = 40,
mipmap_count = 2,
flags = { "gui-icon" },
scale = 0.5,
},
{
type = "sprite",
name = "mpp_tooltip_category_area",
filename = graphics.."tooltip-category-area.png",
priority = "extra-high-no-scale",
size = 40,
mipmap_count = 2,
flags = { "gui-icon" },
scale = 0.5,
},
{
type = "sprite",
name = "mpp_tooltip_category_mining_area",
filename = graphics.."tooltip-category-mining-area.png",
priority = "extra-high-no-scale",
size = 40,
mipmap_count = 2,
flags = { "gui-icon" },
scale = 0.5,
},
{
type = "sprite",
name = "mpp_tooltip_category_supply_area",
filename = graphics.."tooltip-category-supply-area.png",
priority = "extra-high-no-scale",
size = 40,
mipmap_count = 2,
flags = { "gui-icon" },
scale = 0.5,
},
}

View File

@@ -0,0 +1,133 @@
local default_style = data.raw["gui-style"].default
--- taken from flib
default_style.mpp_selected_frame_action_button = {
type = "button_style",
parent = "frame_action_button",
default_graphical_set = {
base = {position = {225, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
},
hovered_graphical_set = {
base = {position = {369, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
},
clicked_graphical_set = {
base = {position = {352, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
}
}
default_style.mpp_blueprint_mode_button = {
type = "button_style",
parent = "recipe_slot_button",
size = 28,
}
default_style.mpp_blueprint_mode_button_active = {
type = "button_style",
parent = "recipe_slot_button",
size = 28,
default_graphical_set = {
base = {position = {225, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
},
hovered_graphical_set = {
base = {position = {369, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
},
clicked_graphical_set = {
base = {position = {352, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
}
}
default_style.mpp_fake_item_placeholder = {
type="image_style",
stretch_image_to_widget_size = true,
size=32,
}
default_style.mpp_fake_item_placeholder_blueprint = {
type="image_style",
stretch_image_to_widget_size = true,
left_margin=4,
top_margin=4,
size=32,
}
default_style.mpp_fake_blueprint_button = {
type="button_style",
parent="shortcut_bar_button_blue",
padding=3,
size=48,
}
default_style.mpp_delete_blueprint_button = {
type="button_style",
parent="shortcut_bar_button_red",
padding=0,
size=24,
stretch_image_to_widget_size=true,
}
default_style.mpp_fake_blueprint_button_selected = {
type="button_style",
parent="shortcut_bar_button_blue",
padding=3,
size=48,
default_graphical_set = {
base = {position = {225, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
},
hovered_graphical_set = {
base = {position = {369, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
},
clicked_graphical_set = {
base = {position = {352, 17}, corner_size = 8},
shadow = {position = {440, 24}, corner_size = 8, draw_type = "outer"},
}
}
default_style.mpp_fake_blueprint_button_invalid = {
type="button_style",
parent="shortcut_bar_button_red",
padding=3,
size=48,
stretch_image_to_widget_size=true,
}
default_style.mpp_fake_blueprint_table = {
type="table_style",
padding=0,
cell_padding=0,
horizontal_spacing=2,
vertical_spacing=2,
}
default_style.mpp_fake_blueprint_sprite = {
type="image_style",
size=16,
stretch_image_to_widget_size=true,
}
default_style.mpp_filtered_entity = {
type="image_style",
size=32,
stretch_image_to_widget_size=true,
}
default_style.mpp_section = {
type="vertical_flow_style",
parent="vertical_flow",
natural_width=240,
}
default_style.mpp_label_warning_style = {
type = "label_style",
font_color = {1, 1, 0},
single_line = false,
horizontally_squashable = "on",
horizontally_stretchable = "on",
}

View File

@@ -0,0 +1,15 @@
data:extend{
{
type="int-setting",
name="mpp-lane-filling-info-duration",
setting_type="runtime-per-user",
default_value=10,
},
{
type="bool-setting",
name="mpp-dump-heuristics-data",
setting_type="runtime-per-user",
default_value=false,
hidden=true,
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB