781 lines
31 KiB
Lua
781 lines
31 KiB
Lua
math2d = require "math2d"
|
|
|
|
local Search = {}
|
|
|
|
local default_surface_data = { consumers = {}, producers = {}, storage = {}, logistics = {}, modules = {}, requesters = {}, ground_items = {}, entities = {}, signals = {}, map_tags = {} }
|
|
|
|
local function extend(t1, t2)
|
|
local t1_len = #t1
|
|
local t2_len = #t2
|
|
for i=1, t2_len do
|
|
t1[t1_len + i] = t2[i]
|
|
end
|
|
end
|
|
|
|
local function signal_eq(sig1, sig2)
|
|
return sig1 and sig2 and sig1.type == sig2.type and sig1.name == sig2.name
|
|
end
|
|
|
|
-- Mod-specific overrides for "Entity" search
|
|
local mod_placeholder_entities = {
|
|
['ff-ferrous-nodule'] = {'ff-seamount'}, -- Freight Forwarding
|
|
['ff-cupric-nodule'] = {'ff-seamount'},
|
|
['ff-cobalt-crust'] = {'ff-seamount'},
|
|
|
|
['ff-hot-titansteel-plate'] = -- Freight Forwarding
|
|
{'ff-lava-pool', 'ff-lava-pool-small'},
|
|
|
|
['se-core-fragment-omni'] = {'se-core-fragment-omni', 'se-core-fragment-omni-sealed'}, -- space-exploration
|
|
['se-core-fragment-iron-ore'] = {'se-core-fragment-iron-ore', 'se-core-fragment-iron-ore-sealed'},
|
|
['se-core-fragment-copper-ore'] = {'se-core-fragment-copper-ore', 'se-core-fragment-copper-ore-sealed'},
|
|
['se-core-fragment-coal'] = {'se-core-fragment-coal', 'se-core-fragment-coal-sealed'},
|
|
['se-core-fragment-stone'] = {'se-core-fragment-stone', 'se-core-fragment-stone-sealed'},
|
|
['se-core-fragment-uranium-ore'] = {'se-core-fragment-uranium-ore', 'se-core-fragment-uranium-ore-sealed'},
|
|
['se-core-fragment-crude-oil'] = {'se-core-fragment-crude-oil', 'se-core-fragment-crude-oil-sealed'},
|
|
['se-core-fragment-se-beryllium-ore'] = {'se-core-fragment-beryllium-ore', 'se-core-fragment-beryllium-ore-sealed'},
|
|
['se-core-fragment-se-cryonite'] = {'se-core-fragment-se-cryonite', 'se-core-fragment-se-cryonite-sealed'},
|
|
['se-core-fragment-se-holmium-ore'] = {'se-core-fragment-se-holmium-ore', 'se-core-fragment-se-holmium-ore-sealed'},
|
|
['se-core-fragment-se-iridium-ore'] = {'se-core-fragment-se-iridium-ore', 'se-core-fragment-se-iridium-ore-sealed'},
|
|
['se-core-fragment-se-vulcanite'] = {'se-core-fragment-se-vulcanite', 'se-core-fragment-se-vulcanite-sealed'},
|
|
['se-core-fragment-se-vitemelange'] = {'se-core-fragment-se-vitemelange', 'se-core-fragment-se-vitemelange-sealed'},
|
|
}
|
|
|
|
local list_to_map = util.list_to_map
|
|
local ingredient_entities = list_to_map{ "assembling-machine", "furnace", "mining-drill", "boiler", "burner-generator", "generator", "reactor", "inserter", "lab", "car", "spider-vehicle", "locomotive" }
|
|
local item_ammo_ingredient_entities = list_to_map{ "artillery-turret", "artillery-wagon", "ammo-turret" } -- spider-vehicle, character
|
|
local fluid_ammo_ingredient_entities = list_to_map { "fluid-turret" }
|
|
local product_entities = list_to_map{ "assembling-machine", "furnace", "offshore-pump", "mining-drill" } -- TODO add rocket-silo
|
|
local item_storage_entities = list_to_map{ "container", "logistic-container", "linked-container", "roboport", "character", "car", "artillery-wagon", "cargo-wagon", "spider-vehicle" }
|
|
local neutral_item_storage_entities = list_to_map{ "character-corpse" } -- force = "neutral"
|
|
local fluid_storage_entities = list_to_map{ "storage-tank", "fluid-wagon" }
|
|
local modules_entities = list_to_map{ "assembling-machine", "furnace", "rocket-silo", "mining-drill", "lab", "beacon" }
|
|
local request_entities = list_to_map{ "logistic-container", "character", "spider-vehicle", "item-request-proxy" }
|
|
local item_logistic_entities = list_to_map{ "transport-belt", "splitter", "underground-belt", "loader", "loader-1x1", "inserter", "logistic-robot", "construction-robot" }
|
|
local fluid_logistic_entities = list_to_map{ "pipe", "pipe-to-ground", "pump" }
|
|
local ground_entities = list_to_map{ "item-entity" } -- force = "neutral"
|
|
local signal_entities = list_to_map{ "roboport", "train-stop", "arithmetic-combinator", "decider-combinator", "constant-combinator", "accumulator", "rail-signal", "rail-chain-signal", "wall", "container", "logistic-container", "inserter", "storage-tank" }
|
|
|
|
local function add_entity_type(type_list, to_add_list)
|
|
for name, _ in pairs(to_add_list) do
|
|
type_list[name] = true
|
|
end
|
|
end
|
|
|
|
local function map_to_list(map)
|
|
local i = 1
|
|
local list = {}
|
|
for name, _ in pairs(map) do
|
|
list[i] = name
|
|
i = i + 1
|
|
end
|
|
return list
|
|
end
|
|
|
|
local function generate_distance_data(surface_data, player_position)
|
|
local distance = math2d.position.distance
|
|
for _, entity_groups in pairs(surface_data) do
|
|
for _, groups in pairs(entity_groups) do
|
|
for _, group in pairs(groups) do
|
|
group.distance = distance(group.avg_position, player_position)
|
|
end
|
|
table.sort(groups, function (k1, k2) return k1.distance < k2.distance end)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function to_chunk_position(map_position)
|
|
return { math.floor(map_position.x / 32), math.floor(map_position.y / 32) }
|
|
end
|
|
|
|
local function remove_uncharted_groups(surface_data, surface, force)
|
|
for _, entity_groups in pairs(surface_data) do
|
|
for _, groups in pairs(entity_groups) do
|
|
for i, group in pairs(groups) do
|
|
if not force.is_chunk_charted(surface, to_chunk_position(group.avg_position)) then
|
|
table.remove(groups, i)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function is_wire_connected(entity, entity_type)
|
|
if entity_type == "arithmetic-combinator" or entity_type == "decider-combinator" then
|
|
return entity.get_circuit_network(defines.wire_type.red, defines.circuit_connector_id.combinator_output) or entity.get_circuit_network(defines.wire_type.green, defines.circuit_connector_id.combinator_output)
|
|
else
|
|
return entity.get_circuit_network(defines.wire_type.red) or entity.get_circuit_network(defines.wire_type.green)
|
|
end
|
|
end
|
|
|
|
function Search.process_found_entities(entities, state, surface_data, target_item)
|
|
-- Not used for Entity and Tag search modes
|
|
local target_name = target_item.name
|
|
local target_type = target_item.type
|
|
local target_is_item = target_type == "item"
|
|
local target_is_fluid = target_type == "fluid"
|
|
local target_is_virtual = target_type == "virtual"
|
|
|
|
for _, entity in pairs(entities) do
|
|
local entity_type = entity.type
|
|
|
|
-- Signals
|
|
if state.signals then
|
|
if signal_entities[entity_type] then
|
|
local control_behavior = entity.get_control_behavior()
|
|
if control_behavior and is_wire_connected(entity, entity_type) then
|
|
-- Does everything except mining drill, as API doesn't support that
|
|
if entity_type == "constant-combinator" then
|
|
-- If prototype's `item_slot_count = 0` then .parameters will be nil
|
|
for _, parameter in pairs(control_behavior.parameters or {}) do
|
|
if signal_eq(parameter.signal, target_item) then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, parameter.count)
|
|
end
|
|
end
|
|
elseif entity_type == "arithmetic-combinator" or entity_type == "decider-combinator" then
|
|
local signal_count = control_behavior.get_signal_last_tick(target_item)
|
|
if signal_count ~= nil then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
elseif entity_type == "roboport" then
|
|
for _, signal in pairs({ control_behavior.available_logistic_output_signal, control_behavior.total_logistic_output_signal, control_behavior.available_construction_output_signal, control_behavior.total_construction_output_signal }) do
|
|
if signal_eq(signal, target_item) then
|
|
SearchResults.add_entity(entity, surface_data.signals)
|
|
break
|
|
end
|
|
end
|
|
if target_is_item and control_behavior.read_logistics then
|
|
local logistic_network = entity.logistic_network
|
|
if logistic_network then
|
|
local signal_count = logistic_network.get_item_count(target_name)
|
|
if signal_count > 0 then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
end
|
|
end
|
|
elseif entity_type == "train-stop" then
|
|
if signal_eq(control_behavior.stopped_train_signal, target_item) or signal_eq(control_behavior.trains_count_signal, target_item) then
|
|
SearchResults.add_entity(entity, surface_data.signals)
|
|
elseif control_behavior.read_from_train then
|
|
local train = entity.get_stopped_train()
|
|
if train then
|
|
if target_is_item then
|
|
local signal_count = train.get_item_count(target_name)
|
|
if signal_count > 0 then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
elseif target_is_fluid then
|
|
local signal_count = train.get_fluid_count(target_name)
|
|
if signal_count > 0 then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif entity_type == "accumulator" or entity_type == "wall" then
|
|
if signal_eq(control_behavior.output_signal, target_item) then
|
|
SearchResults.add_entity(entity, surface_data.signals)
|
|
end
|
|
elseif entity_type == "rail-signal" then
|
|
for _, signal in pairs({ control_behavior.red_signal, control_behavior.orange_signal, control_behavior.green_signal }) do
|
|
if signal_eq(signal, target_item) then
|
|
SearchResults.add_entity(entity, surface_data.signals)
|
|
break
|
|
end
|
|
end
|
|
elseif entity_type == "rail-chain-signal" then
|
|
for _, signal in pairs({ control_behavior.red_signal, control_behavior.orange_signal, control_behavior.green_signal, control_behavior.blue_signal }) do
|
|
if signal_eq(signal, target_item) then
|
|
SearchResults.add_entity(entity, surface_data.signals)
|
|
break
|
|
end
|
|
end
|
|
elseif entity_type == "container" and target_is_item then
|
|
local signal_count = entity.get_item_count(target_name)
|
|
if signal_count > 0 then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
elseif entity_type == "logistic-container" and target_is_item then
|
|
if control_behavior.circuit_mode_of_operation == defines.control_behavior.logistic_container.circuit_mode_of_operation.send_contents then
|
|
local signal_count = entity.get_item_count(target_name)
|
|
if signal_count > 0 then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
end
|
|
elseif entity_type == "inserter" and target_is_item then
|
|
-- Doesn't check inserter if in pulse mode
|
|
if control_behavior.circuit_read_hand_contents and control_behavior.circuit_hand_read_mode == defines.control_behavior.inserter.hand_read_mode.hold then
|
|
local held_stack = entity.held_stack
|
|
if held_stack and held_stack.valid_for_read and held_stack.name == target_name then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, held_stack.count)
|
|
end
|
|
end
|
|
elseif entity_type == "storage-tank" and target_is_fluid then
|
|
local signal_count = entity.get_fluid_count(target_name)
|
|
if signal_count > 0 then
|
|
SearchResults.add_entity_signal(entity, surface_data.signals, signal_count)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if target_is_virtual then
|
|
-- We've done all processing that there is to be done on virtual signals
|
|
goto continue
|
|
end
|
|
|
|
-- Ingredients / Consumers
|
|
if state.consumers then
|
|
local recipe
|
|
if entity_type == "assembling-machine" then
|
|
recipe = entity.get_recipe()
|
|
elseif entity_type == "furnace" then
|
|
recipe = entity.get_recipe()
|
|
if recipe == nil then
|
|
-- Even if the furnace has stopped smelting, this records the last item it was smelting
|
|
recipe = entity.previous_recipe
|
|
end
|
|
end
|
|
if recipe then
|
|
local ingredients = recipe.ingredients
|
|
for _, ingredient in pairs(ingredients) do
|
|
local name = ingredient.name
|
|
if name == target_name then
|
|
SearchResults.add_entity_product(entity, surface_data.consumers, recipe)
|
|
end
|
|
end
|
|
end
|
|
if target_is_item and entity_type == "lab" then
|
|
local item_count = entity.get_item_count(target_name)
|
|
if item_count > 0 then
|
|
SearchResults.add_entity(entity, surface_data.consumers)
|
|
end
|
|
end
|
|
if target_is_fluid and entity_type == "generator" then
|
|
local fluid_count = entity.get_fluid_count(target_name)
|
|
if fluid_count > 0 then
|
|
SearchResults.add_entity(entity, surface_data.consumers)
|
|
end
|
|
end
|
|
local burner = entity.burner
|
|
if burner then
|
|
local currently_burning = burner.currently_burning
|
|
if currently_burning then
|
|
if currently_burning.name == target_name then
|
|
SearchResults.add_entity(entity, surface_data.consumers)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Consuming ammo
|
|
if target_is_item and (entity_type == "artillery-turret" or entity_type == "artillery-wagon" or entity_type == "ammo-turret") then
|
|
local item_count = entity.get_item_count(target_name)
|
|
if item_count > 0 then
|
|
SearchResults.add_entity_storage(entity, surface_data.consumers, item_count)
|
|
end
|
|
elseif target_is_fluid and entity_type == "fluid-turret" then
|
|
local fluid_count = entity.get_fluid_count(target_name)
|
|
if fluid_count > 0 then
|
|
SearchResults.add_entity_storage_fluid(entity, surface_data.consumers, fluid_count)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Producers
|
|
if state.producers then
|
|
local recipe
|
|
if entity_type == "assembling-machine" then
|
|
recipe = entity.get_recipe()
|
|
elseif entity_type == "furnace" then
|
|
recipe = entity.get_recipe()
|
|
if recipe == nil then
|
|
-- Even if the furnace has stopped smelting, this records the last item it was smelting
|
|
recipe = entity.previous_recipe
|
|
end
|
|
elseif entity_type == "mining-drill" then
|
|
local mining_target = entity.mining_target
|
|
if mining_target then
|
|
local mineable_properties = mining_target.prototype.mineable_properties
|
|
for _, product in pairs(mineable_properties.products or {}) do
|
|
if product.name == target_name then
|
|
SearchResults.add_entity(entity, surface_data.producers)
|
|
end
|
|
end
|
|
end
|
|
elseif target_is_fluid and entity_type == "offshore-pump" then
|
|
if entity.get_fluid_count(target_name) > 0 then
|
|
SearchResults.add_entity(entity, surface_data.producers)
|
|
end
|
|
end
|
|
if recipe then
|
|
local products = recipe.products
|
|
for _, product in pairs(products) do
|
|
local name = product.name
|
|
if name == target_name then
|
|
SearchResults.add_entity_product(entity, surface_data.producers, recipe)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Storage
|
|
if state.storage then
|
|
if target_is_fluid and (entity_type == "storage-tank" or entity_type == "fluid-wagon") then
|
|
local fluid_count = entity.get_fluid_count(target_name)
|
|
if fluid_count > 0 then
|
|
SearchResults.add_entity_storage_fluid(entity, surface_data.storage, fluid_count)
|
|
end
|
|
elseif target_is_item and (entity_type == "character-corpse" or item_storage_entities[entity_type]) then
|
|
-- Entity is an inventory entity
|
|
local item_count = entity.get_item_count(target_name)
|
|
if item_count > 0 then
|
|
SearchResults.add_entity_storage(entity, surface_data.storage, item_count)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Modules
|
|
if state.modules then
|
|
if target_is_item and modules_entities[entity_type] then
|
|
local inventory
|
|
if entity_type == "beacon" then
|
|
inventory = entity.get_inventory(defines.inventory.beacon_modules)
|
|
elseif entity_type == "lab" then
|
|
inventory = entity.get_inventory(defines.inventory.lab_modules)
|
|
elseif entity_type == "mining-drill" then
|
|
inventory = entity.get_inventory(defines.inventory.mining_drill_modules)
|
|
elseif entity_type == "assembling-machine" or entity_type == "furnace" or entity_type == "rocket-silo" then
|
|
inventory = entity.get_inventory(defines.inventory.assembling_machine_modules)
|
|
end
|
|
if inventory then
|
|
local item_count = inventory.get_item_count(target_name)
|
|
if item_count > 0 then
|
|
SearchResults.add_entity_module(entity, surface_data.modules, item_count)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Requesters
|
|
if target_is_item and state.requesters then
|
|
-- Buffer and Requester chests
|
|
if entity_type == "logistic-container" then
|
|
for i=1, entity.request_slot_count do
|
|
local request = entity.get_request_slot(i)
|
|
if request and request.name == target_name then
|
|
local count = request.count
|
|
if count then
|
|
SearchResults.add_entity_request(entity, surface_data.requesters, count)
|
|
end
|
|
end
|
|
end
|
|
elseif entity_type == "character" then
|
|
for i=1, entity.request_slot_count do
|
|
local request = entity.get_personal_logistic_slot(i)
|
|
if request and request.name == target_name then
|
|
local count = request.min
|
|
if count and count > 0 then
|
|
SearchResults.add_entity_request(entity, surface_data.requesters, request.min)
|
|
end
|
|
end
|
|
end
|
|
elseif entity_type == "spider-vehicle" then
|
|
for i=1, entity.request_slot_count do
|
|
local request = entity.get_vehicle_logistic_slot(i)
|
|
if request and request.name == target_name then
|
|
local count = request.min
|
|
if count and count > 0 then
|
|
SearchResults.add_entity_request(entity, surface_data.requesters, request.min)
|
|
end
|
|
end
|
|
end
|
|
elseif entity_type == "item-request-proxy" then
|
|
local request_count = entity.item_requests[target_name]
|
|
if request_count ~= nil then
|
|
SearchResults.add_entity_request(entity.proxy_target, surface_data.requesters, request_count)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Ground
|
|
if target_is_item and state.ground_items then
|
|
if entity_type == "item-entity" and entity.name == "item-on-ground" then
|
|
if entity.stack.name == target_name then
|
|
SearchResults.add_entity(entity, surface_data.ground_items)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Logistics
|
|
if state.logistics then
|
|
if item_logistic_entities[entity_type] then
|
|
if entity_type == "inserter" then
|
|
local held_stack = entity.held_stack
|
|
if held_stack and held_stack.valid_for_read and held_stack.name == target_name then
|
|
SearchResults.add_entity_storage(entity, surface_data.logistics, held_stack.count)
|
|
end
|
|
else
|
|
local item_count = entity.get_item_count(target_name)
|
|
if item_count > 0 then
|
|
SearchResults.add_entity_storage(entity, surface_data.logistics, item_count)
|
|
end
|
|
end
|
|
elseif fluid_logistic_entities[entity_type] then
|
|
-- So target.type == "fluid"
|
|
local fluid_count = entity.get_fluid_count(target_name)
|
|
if fluid_count > 0 then
|
|
SearchResults.add_entity_storage_fluid(entity, surface_data.logistics, fluid_count)
|
|
end
|
|
end
|
|
end
|
|
::continue::
|
|
end
|
|
end
|
|
|
|
function Search.blocking_search(force, state, target_item, surface_list, type_list, neutral_type_list, player)
|
|
local target_name = target_item.name
|
|
local target_type = target_item.type
|
|
local target_is_item = target_type == "item"
|
|
local target_is_fluid = target_type == "fluid"
|
|
local target_is_virtual = target_type == "virtual"
|
|
|
|
local data = {}
|
|
|
|
for _, surface in pairs(surface_list) do
|
|
local surface_data = table.deepcopy(default_surface_data)
|
|
|
|
local entities = {}
|
|
if next(type_list) then
|
|
entities = surface.find_entities_filtered{
|
|
type = type_list,
|
|
force = force,
|
|
}
|
|
end
|
|
|
|
-- Corpses and items on ground don't have a force: find seperately
|
|
if next(neutral_type_list) then
|
|
local neutral_entities = surface.find_entities_filtered{
|
|
type = neutral_type_list,
|
|
}
|
|
extend(entities, neutral_entities)
|
|
end
|
|
|
|
Search.process_found_entities(entities, state, surface_data, target_item)
|
|
|
|
-- Map tags
|
|
if state.map_tags then
|
|
local tags = force.find_chart_tags(surface.name)
|
|
for _, tag in pairs(tags) do
|
|
local tag_icon = tag.icon
|
|
if tag_icon and tag_icon.type == target_type and tag_icon.name == target_name then
|
|
SearchResults.add_tag(tag, surface_data.map_tags)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Entities
|
|
if state.entities then
|
|
local target_entity_name = mod_placeholder_entities[target_name]
|
|
|
|
if not target_entity_name then
|
|
-- Check if the item is produced by mining any entities
|
|
target_entity_name = global.item_to_entities[target_name]
|
|
if not target_entity_name then
|
|
-- Otherwise, check for the item's place_result
|
|
local item_prototype = game.item_prototypes[target_name]
|
|
if item_prototype and item_prototype.place_result then
|
|
target_entity_name = item_prototype.place_result.name
|
|
else
|
|
-- Or just try an entity with the same name as the item
|
|
target_entity_name = target_name
|
|
end
|
|
end
|
|
end
|
|
|
|
entities = surface.find_entities_filtered{
|
|
name = target_entity_name,
|
|
force = { force, "neutral" },
|
|
}
|
|
for _, entity in pairs(entities) do
|
|
if entity.type == "resource" then
|
|
local amount
|
|
if entity.initial_amount then
|
|
amount = entity.amount / 3000 -- Calculate yield from amount
|
|
else
|
|
amount = entity.amount
|
|
end
|
|
SearchResults.add_entity_resource(entity, surface_data.entities, amount)
|
|
else
|
|
SearchResults.add_entity(entity, surface_data.entities)
|
|
end
|
|
end
|
|
end
|
|
if surface == player.surface then
|
|
generate_distance_data(surface_data, player.position)
|
|
end
|
|
remove_uncharted_groups(surface_data, surface, force)
|
|
data[surface.name] = surface_data
|
|
end
|
|
return data
|
|
end
|
|
|
|
function Search.non_blocking_search(force, state, target_item, surface_list, type_list, neutral_type_list, player)
|
|
search_data = {
|
|
force = force,
|
|
state = state,
|
|
target_item = target_item,
|
|
type_list = type_list,
|
|
neutral_type_list = neutral_type_list,
|
|
player = player,
|
|
data = {},
|
|
not_started_surfaces = surface_list,
|
|
completed_surfaces = {}
|
|
}
|
|
|
|
global.current_searches[player.index] = search_data
|
|
end
|
|
|
|
function Search.on_tick()
|
|
local player_index, search_data = next(global.current_searches)
|
|
if not search_data then return end
|
|
|
|
if search_data.search_complete then
|
|
local player_data = global.players[player_index]
|
|
local refs = player_data.refs
|
|
Gui.build_results(search_data.data, refs.result_flow)
|
|
global.current_searches[player_index] = nil
|
|
end
|
|
|
|
local current_surface = search_data.current_surface
|
|
if not current_surface or not current_surface.valid then
|
|
-- Start next surface
|
|
current_surface = table.remove(search_data.not_started_surfaces)
|
|
if not current_surface then
|
|
-- All surfaces are complete
|
|
local player = search_data.player
|
|
local surface_data = search_data.data[player.surface.name]
|
|
if surface_data then
|
|
generate_distance_data(surface_data, player.position)
|
|
end
|
|
search_data.search_complete = true
|
|
return
|
|
end
|
|
|
|
if not current_surface.valid then return end -- Will try another surface next tick
|
|
|
|
-- Setup next surface data
|
|
search_data.current_surface = current_surface
|
|
search_data.surface_data = table.deepcopy(default_surface_data)
|
|
search_data.chunk_iterator = current_surface.get_chunks()
|
|
|
|
-- Update results
|
|
local player_data = global.players[player_index]
|
|
local refs = player_data.refs
|
|
Gui.build_results(search_data.data, refs.result_flow, false, true)
|
|
Gui.add_loading_results(refs.result_flow)
|
|
return -- Start next surface processing on next tick
|
|
end
|
|
|
|
|
|
local chunk_iterator = search_data.chunk_iterator
|
|
if not chunk_iterator.valid then
|
|
search_data.current_surface = nil
|
|
return
|
|
end
|
|
|
|
local force = search_data.force
|
|
local chunks_processed = 0
|
|
local chunks_per_tick = settings.global["fs-chunks-per-tick"].value
|
|
while chunks_processed < chunks_per_tick do
|
|
local chunk = chunk_iterator()
|
|
if not chunk then
|
|
-- Surface is complete
|
|
search_data.data[current_surface.name] = search_data.surface_data
|
|
search_data.current_surface = nil
|
|
return
|
|
end
|
|
|
|
if force.is_chunk_charted(current_surface, chunk) then
|
|
chunks_processed = chunks_processed + 1
|
|
else
|
|
goto continue
|
|
end
|
|
|
|
local target_item = search_data.target_item
|
|
local target_name = target_item.name
|
|
local target_type = target_item.type
|
|
local target_is_item = target_type == "item"
|
|
local target_is_fluid = target_type == "fluid"
|
|
local target_is_virtual = target_type == "virtual"
|
|
|
|
local state = search_data.state
|
|
local surface_data = search_data.surface_data
|
|
|
|
local chunk_area = chunk.area
|
|
|
|
local entities = {}
|
|
if next(search_data.type_list) then
|
|
entities = current_surface.find_entities_filtered{
|
|
area = chunk_area,
|
|
type = search_data.type_list,
|
|
force = force,
|
|
}
|
|
end
|
|
|
|
-- Corpses and items on ground don't have a force: find seperately
|
|
if next(search_data.neutral_type_list) then
|
|
local neutral_entities = current_surface.find_entities_filtered{
|
|
area = chunk_area,
|
|
type = search_data.neutral_type_list,
|
|
}
|
|
extend(entities, neutral_entities)
|
|
end
|
|
|
|
for i, entity in pairs(entities) do
|
|
if not math2d.bounding_box.contains_point(chunk_area, entity.position) then
|
|
entities[i] = nil
|
|
end
|
|
end
|
|
|
|
Search.process_found_entities(entities, state, surface_data, target_item)
|
|
|
|
-- Map tags
|
|
if state.map_tags then
|
|
local tags = force.find_chart_tags(current_surface.name, chunk_area)
|
|
for _, tag in pairs(tags) do
|
|
local tag_icon = tag.icon
|
|
if tag_icon and tag_icon.type == target_type and tag_icon.name == target_name then
|
|
SearchResults.add_tag(tag, surface_data.map_tags)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Entities
|
|
if state.entities then
|
|
local target_entity_name = mod_placeholder_entities[target_name]
|
|
|
|
if not target_entity_name then
|
|
-- Check if the item is produced by mining any entities
|
|
target_entity_name = global.item_to_entities[target_name]
|
|
if not target_entity_name then
|
|
-- Otherwise, check for the item's place_result
|
|
local item_prototype = game.item_prototypes[target_name]
|
|
if item_prototype and item_prototype.place_result then
|
|
target_entity_name = item_prototype.place_result.name
|
|
else
|
|
-- Or just try an entity with the same name as the item
|
|
target_entity_name = target_name
|
|
end
|
|
end
|
|
end
|
|
|
|
entities = current_surface.find_entities_filtered{
|
|
area = chunk_area,
|
|
name = target_entity_name,
|
|
force = { force, "neutral" },
|
|
}
|
|
for i, entity in pairs(entities) do
|
|
if not math2d.bounding_box.contains_point(chunk_area, entity.position) then
|
|
entities[i] = nil
|
|
end
|
|
end
|
|
for _, entity in pairs(entities) do
|
|
if entity.type == "resource" then
|
|
local amount
|
|
if entity.initial_amount then
|
|
amount = entity.amount / 3000 -- Calculate yield from amount
|
|
else
|
|
amount = entity.amount
|
|
end
|
|
SearchResults.add_entity_resource(entity, surface_data.entities, amount)
|
|
else
|
|
SearchResults.add_entity(entity, surface_data.entities)
|
|
end
|
|
end
|
|
end
|
|
::continue::
|
|
end
|
|
|
|
end
|
|
event.on_tick(Search.on_tick)
|
|
|
|
function Search.find_machines(target_item, force, state, player, override_surface)
|
|
local data = {}
|
|
local target_name = target_item.name
|
|
if target_name == nil then
|
|
-- 'Unknown signal selected'
|
|
return data
|
|
end
|
|
|
|
-- Crafting Combinator adds signals for recipes, which players sometimes mistake for items/fluids
|
|
if target_item.type == "virtual" and not state.signals
|
|
and (game.active_mods["crafting_combinator"] or game.active_mods["crafting_combinator_xeraph"]) then
|
|
local recipe = game.recipe_prototypes[target_name]
|
|
if recipe then
|
|
player.print("[Factory Search] It looks like you selected a recipe from the \"Crafting combinator recipes\" tab. Instead select an item or fluid from a different tab.")
|
|
return data
|
|
end
|
|
end
|
|
|
|
local target_type = target_item.type
|
|
local target_is_item = target_type == "item"
|
|
local target_is_fluid = target_type == "fluid"
|
|
local target_is_virtual = target_type == "virtual"
|
|
|
|
local entity_types = {}
|
|
local neutral_entity_types = {}
|
|
if (target_is_item or target_is_fluid) and state.consumers then
|
|
add_entity_type(entity_types, ingredient_entities)
|
|
|
|
-- Only add turrets if target is ammo
|
|
if target_is_item and game.get_filtered_item_prototypes({{filter = "type", type = "ammo"}})[target_name] then
|
|
add_entity_type(entity_types, item_ammo_ingredient_entities)
|
|
elseif target_is_fluid then
|
|
add_entity_type(entity_types, fluid_ammo_ingredient_entities)
|
|
end
|
|
end
|
|
if (target_is_item or target_is_fluid) and state.producers then
|
|
add_entity_type(entity_types, product_entities)
|
|
end
|
|
if target_is_item and state.storage then
|
|
add_entity_type(entity_types, item_storage_entities)
|
|
add_entity_type(neutral_entity_types, neutral_item_storage_entities)
|
|
end
|
|
if target_is_fluid and state.storage then
|
|
add_entity_type(entity_types, fluid_storage_entities)
|
|
end
|
|
if target_is_item and state.requesters then
|
|
add_entity_type(entity_types, request_entities)
|
|
end
|
|
if target_is_item and state.modules then
|
|
add_entity_type(entity_types, modules_entities)
|
|
end
|
|
if target_is_item and state.logistics then
|
|
add_entity_type(entity_types, item_logistic_entities)
|
|
end
|
|
if target_is_fluid and state.logistics then
|
|
add_entity_type(entity_types, fluid_logistic_entities)
|
|
end
|
|
if target_is_item and state.ground_items then
|
|
add_entity_type(neutral_entity_types, ground_entities)
|
|
end
|
|
if state.signals then
|
|
add_entity_type(entity_types, signal_entities)
|
|
end
|
|
local type_list = map_to_list(entity_types)
|
|
local neutral_type_list = map_to_list(neutral_entity_types)
|
|
|
|
local surface_list = filtered_surfaces(override_surface, player.surface)
|
|
|
|
local non_blocking_search = settings.global["fs-non-blocking-search"].value
|
|
if non_blocking_search == "on" or (non_blocking_search == "multiplayer" and game.is_multiplayer()) then
|
|
-- Do non blocking search
|
|
Search.non_blocking_search(force, state, target_item, surface_list, type_list, neutral_type_list, player)
|
|
data = { non_blocking_search = true }
|
|
else
|
|
data = Search.blocking_search(force, state, target_item, surface_list, type_list, neutral_type_list, player)
|
|
end
|
|
|
|
return data
|
|
end
|
|
|
|
return Search |