Добавлены все обновления от сообщества, вплоть до #148
This commit is contained in:
765
mining-patch-planner/mpp/mpp_util.lua
Normal file
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
|
||||
Reference in New Issue
Block a user