766 lines
23 KiB
Lua
766 lines
23 KiB
Lua
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
|