Добавлены все обновления от сообщества, вплоть до #148
21
mining-patch-planner/LICENSE
Normal 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.
|
||||
3
mining-patch-planner/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Mining Patch Planner
|
||||
|
||||
[Factorio Mod portal](https://mods.factorio.com/mod/mining-patch-planner)
|
||||
359
mining-patch-planner/algorithm.lua
Normal 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
|
||||
261
mining-patch-planner/changelog.txt
Normal 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.
|
||||
199
mining-patch-planner/configuration.lua
Normal 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
|
||||
238
mining-patch-planner/control.lua
Normal 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)
|
||||
3
mining-patch-planner/data.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
require("prototypes.data")
|
||||
require("prototypes.graphics")
|
||||
require("prototypes.styles")
|
||||
97
mining-patch-planner/defs.lua
Normal 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
|
||||
BIN
mining-patch-planner/graphics/advanced-settings-32x.png
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
mining-patch-planner/graphics/advanced-settings-black.png
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
mining-patch-planner/graphics/advanced-settings.png
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
mining-patch-planner/graphics/align_start.png
Normal file
|
After Width: | Height: | Size: 318 B |
BIN
mining-patch-planner/graphics/arrow-east.png
Normal file
|
After Width: | Height: | Size: 316 B |
BIN
mining-patch-planner/graphics/arrow-north.png
Normal file
|
After Width: | Height: | Size: 351 B |
BIN
mining-patch-planner/graphics/arrow-south.png
Normal file
|
After Width: | Height: | Size: 365 B |
BIN
mining-patch-planner/graphics/arrow-west.png
Normal file
|
After Width: | Height: | Size: 315 B |
BIN
mining-patch-planner/graphics/blueprint_add.png
Normal file
|
After Width: | Height: | Size: 395 B |
BIN
mining-patch-planner/graphics/cross.png
Normal file
|
After Width: | Height: | Size: 413 B |
BIN
mining-patch-planner/graphics/deconstruct.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
mining-patch-planner/graphics/display-lane-filling-disabled.png
Normal file
|
After Width: | Height: | Size: 517 B |
BIN
mining-patch-planner/graphics/display-lane-filling-enabled.png
Normal file
|
After Width: | Height: | Size: 517 B |
BIN
mining-patch-planner/graphics/drill-icon-toolbar-disabled.png
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
mining-patch-planner/graphics/drill-icon-toolbar-white.png
Normal file
|
After Width: | Height: | Size: 194 B |
BIN
mining-patch-planner/graphics/drill-icon.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
mining-patch-planner/graphics/entity_filtered_overlay.png
Normal file
|
After Width: | Height: | Size: 287 B |
BIN
mining-patch-planner/graphics/entity_filtering_mode_disabled.png
Normal file
|
After Width: | Height: | Size: 459 B |
BIN
mining-patch-planner/graphics/entity_filtering_mode_enabled.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
mining-patch-planner/graphics/force_pipe_disabled.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
mining-patch-planner/graphics/force_pipe_enabled.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
mining-patch-planner/graphics/grid_convolution.png
Normal file
|
After Width: | Height: | Size: 380 B |
BIN
mining-patch-planner/graphics/miner_coverage.png
Normal file
|
After Width: | Height: | Size: 423 B |
BIN
mining-patch-planner/graphics/miner_coverage_disabled.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
mining-patch-planner/graphics/no-entity.png
Normal file
|
After Width: | Height: | Size: 527 B |
BIN
mining-patch-planner/graphics/no_lamp.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
mining-patch-planner/graphics/no_module.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
mining-patch-planner/graphics/no_pipe.png
Normal file
|
After Width: | Height: | Size: 478 B |
BIN
mining-patch-planner/graphics/omit_deconstruct.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
mining-patch-planner/graphics/omit_landfill.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
mining-patch-planner/graphics/plus.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
mining-patch-planner/graphics/print-placement-info-disabled.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
mining-patch-planner/graphics/print-placement-info-enabled.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
mining-patch-planner/graphics/show_all_miners.png
Normal file
|
After Width: | Height: | Size: 528 B |
BIN
mining-patch-planner/graphics/tooltip-category-area.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
mining-patch-planner/graphics/tooltip-category-mining-area.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
mining-patch-planner/graphics/tooltip-category-size.png
Normal file
|
After Width: | Height: | Size: 219 B |
BIN
mining-patch-planner/graphics/tooltip-category-supply-area.png
Normal file
|
After Width: | Height: | Size: 518 B |
BIN
mining-patch-planner/graphics/undo_disabled.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
mining-patch-planner/graphics/undo_enabled.png
Normal file
|
After Width: | Height: | Size: 388 B |
1274
mining-patch-planner/gui.lua
Normal file
9
mining-patch-planner/info.json
Normal 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."
|
||||
}
|
||||
107
mining-patch-planner/layouts/base.lua
Normal 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
|
||||
1158
mining-patch-planner/layouts/blueprints.lua
Normal file
400
mining-patch-planner/layouts/common.lua
Normal 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
|
||||
592
mining-patch-planner/layouts/compact.lua
Normal 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
|
||||
123
mining-patch-planner/layouts/compact_logistics.lua
Normal 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
|
||||
93
mining-patch-planner/layouts/logistics.lua
Normal 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
|
||||
1275
mining-patch-planner/layouts/simple.lua
Normal file
402
mining-patch-planner/layouts/sparse.lua
Normal 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
|
||||
43
mining-patch-planner/layouts/sparse_logistics.lua
Normal 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
|
||||
420
mining-patch-planner/layouts/super_compact.lua
Normal 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
|
||||
82
mining-patch-planner/locale/en/translation.cfg
Normal 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
|
||||
82
mining-patch-planner/locale/lt/translation.cfg
Normal 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
|
||||
72
mining-patch-planner/locale/pl/translation.cfg
Normal 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__
|
||||
42
mining-patch-planner/locale/ru/translation.cfg
Normal 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=потому что он не может добывать ресурсы
|
||||
42
mining-patch-planner/locale/uk/translation.cfg
Normal 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=бо він не може добувати ресурси
|
||||
69
mining-patch-planner/migration.lua
Normal 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)
|
||||
24
mining-patch-planner/mpp/blacklist.lua
Normal 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
|
||||
171
mining-patch-planner/mpp/blueprintmeta.lua
Normal 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
|
||||
65
mining-patch-planner/mpp/builder.lua
Normal 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
|
||||
176
mining-patch-planner/mpp/color.lua
Normal 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
|
||||
131
mining-patch-planner/mpp/compatibility.lua
Normal 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
|
||||
143
mining-patch-planner/mpp/drawing.lua
Normal 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
|
||||
134
mining-patch-planner/mpp/enums.lua
Normal 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
|
||||
45
mining-patch-planner/mpp/global_extends.lua
Normal 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
|
||||
385
mining-patch-planner/mpp/grid_mt.lua
Normal 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
|
||||
34
mining-patch-planner/mpp/list.lua
Normal 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
|
||||
765
mining-patch-planner/mpp/mpp_util.lua
Normal 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
|
||||
236
mining-patch-planner/mpp/pole_grid_mt.lua
Normal 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
|
||||
788
mining-patch-planner/mpp/render_util.lua
Normal 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
|
||||
25
mining-patch-planner/mpp/structs.lua
Normal 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
|
||||
67
mining-patch-planner/prototypes/data.lua
Normal 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",
|
||||
},
|
||||
}
|
||||
300
mining-patch-planner/prototypes/graphics.lua
Normal 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,
|
||||
},
|
||||
}
|
||||
133
mining-patch-planner/prototypes/styles.lua
Normal 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",
|
||||
}
|
||||
15
mining-patch-planner/settings.lua
Normal 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,
|
||||
},
|
||||
}
|
||||
BIN
mining-patch-planner/thumbnail.png
Normal file
|
After Width: | Height: | Size: 13 KiB |