local Beacon = {} -- Note: supply_area_distance -- extends from edge of collision box Beacon.affected_types = {"assembling-machine", "furnace", "lab", "mining-drill", "rocket-silo"} function Beacon.get_beacon_prototypes() if not Beacon.list_beacon_prototypes then Beacon.list_beacon_prototypes = {} for name, prototype in pairs(game.entity_prototypes) do if prototype.type == "beacon" and prototype.supply_area_distance then table.insert(Beacon.list_beacon_prototypes, prototype) end end end return Beacon.list_beacon_prototypes end function Beacon.get_max_beacon_range() if not Beacon.max_beacon_range then Beacon.max_beacon_range = 0 for _, prototype in pairs(Beacon.list_beacon_prototypes()) do if prototype.supply_area_distance > Beacon.max_beacon_range then Beacon.max_beacon_range = prototype.supply_area_distance end end end return Beacon.max_beacon_range end function Beacon.count_affecting_beacons(entity) local count = 0 for _, prototype in pairs(Beacon.get_beacon_prototypes()) do local area = util.area_extend(entity.bounding_box, prototype.supply_area_distance) count = count + entity.surface.count_entities_filtered{type="beacon", name=prototype.name, area=area} end return count end function Beacon.set_overload_state(entity) if entity.active == true then entity.active = false entity.surface.create_entity{ name = "flying-text", position = entity.position, text = {"space-exploration.beacon-overload"} } global.beacon_overloaded_entities = global.beacon_overloaded_entities or {} global.beacon_overloaded_entities[entity.unit_number] = entity global.beacon_overloaded_shapes = global.beacon_overloaded_shapes or {} local shape_id = global.beacon_overloaded_shapes[entity.unit_number] if shape_id and rendering.is_valid(shape_id) then rendering.destroy(shape_id) end -- shouldn't happen but best to check if other mods broke something shape_id = rendering.draw_sprite{ sprite = "virtual-signal/"..mod_prefix.."beacon-overload", surface = entity.surface, target = entity, x_scale = 1, y_scale = 1, target_offset = entity.prototype.alert_icon_shift } global.beacon_overloaded_shapes[entity.unit_number] = shape_id end -- Regardless of whether entity is active, issue a beacon overload alert to all -- players on the entity's force. Alert is only issued if the reason for inactivation -- was beacon overload if global.beacon_overloaded_entities[entity.unit_number] then for _, player in pairs(entity.force.players) do player.add_custom_alert(entity, {type="virtual", name=mod_prefix.."beacon-overload"}, {"space-exploration.beacon-overload-alert", "[img=virtual-signal/" .. mod_prefix .. "beacon-overload]", "[img=entity/" .. entity.name .. "]"}, true) end end end function Beacon.unset_overload_state(entity) if not entity.active then -- Before reactivating entity, make sure it is present in the overloaded entities list -- Otherwise it was probably deactivated for a different reason altogether and should -- not be reactivated by this function global.beacon_overloaded_entities = global.beacon_overloaded_entities or {} if global.beacon_overloaded_entities[entity.unit_number] then entity.active = true entity.surface.create_entity{ name = "flying-text", position = entity.position, text = {"space-exploration.beacon-overload-ended"} } global.beacon_overloaded_entities[entity.unit_number] = nil for interface, functions in pairs(remote.interfaces) do -- allow other mods to deactivate after if interface ~= "space-exploration" and functions["on_entity_activated"] then remote.call(interface, "on_entity_activated", {entity=entity, mod="space-exploration"}) end end end -- Remove associated overload shapes if they exist. global.beacon_overloaded_shapes = global.beacon_overloaded_shapes or {} if global.beacon_overloaded_shapes[entity.unit_number] then local shape_id = global.beacon_overloaded_shapes[entity.unit_number] if rendering.is_valid(shape_id) then rendering.destroy(shape_id) end global.beacon_overloaded_shapes[entity.unit_number] = nil end end end function Beacon.validate_entity(entity, ignore_count) -- make sure not affected by more than 1 beacon if (not entity.prototype.allowed_effects) or table_size(entity.prototype.allowed_effects) == 0 or (not entity.prototype.module_inventory_size) or entity.prototype.module_inventory_size == 0 then return end local ignore_count = ignore_count or 0 local beacons = Beacon.count_affecting_beacons(entity) if beacons > 1 + ignore_count then Beacon.set_overload_state(entity) else -- TODO: add hook here so other things can cancel Beacon.unset_overload_state(entity) end end function Beacon.validate_beacon(entity, is_deconstructing) local prototype = entity.prototype local area = util.area_extend(entity.bounding_box, prototype.supply_area_distance) local structures = entity.surface.find_entities_filtered{type = Beacon.affected_types, area = area} local ignore_count = is_deconstructing and 1 or 0 for _, structure in pairs(structures) do Beacon.validate_entity(structure, ignore_count) end end function Beacon.on_entity_created(event) local entity if event.entity and event.entity.valid then entity = event.entity end if event.created_entity and event.created_entity.valid then entity = event.created_entity end if not entity then return end if entity.type == "beacon" then Beacon.validate_beacon(entity) elseif util.table_contains(Beacon.affected_types, entity.type) then Beacon.validate_entity(entity) end end Event.addListener(defines.events.on_built_entity, Beacon.on_entity_created) Event.addListener(defines.events.on_robot_built_entity, Beacon.on_entity_created) Event.addListener(defines.events.script_raised_built, Beacon.on_entity_created) Event.addListener(defines.events.script_raised_revive, Beacon.on_entity_created) function Beacon.on_entity_removed(event) if event.entity and event.entity.valid then if event.entity.type == "beacon" then -- do validation but counting 1 beacon fewer Beacon.validate_beacon(event.entity, true) end end end Event.addListener(defines.events.on_player_mined_entity, Beacon.on_entity_removed) Event.addListener(defines.events.on_robot_mined_entity, Beacon.on_entity_removed) Event.addListener(defines.events.on_entity_died, Beacon.on_entity_removed) Event.addListener(defines.events.script_raised_destroy, Beacon.on_entity_removed) function Beacon.validate_overloaded_entities() -- Cleanup function to be run every 10 seconds, re-evaluating overload status -- to determine if it's still appropriate, removing references to no-longer-valid -- entities, and re-issuing alerts if necessary global.beacon_overloaded_entities = global.beacon_overloaded_entities or {} global.beacon_overloaded_shapes = global.beacon_overloaded_shapes or {} for entity_number, entity in pairs(global.beacon_overloaded_entities) do if entity.valid then -- If entity is still valid, re-evaluate whether it is still correctly in overload -- in case the causative beacon was removed without firing an event Beacon.validate_entity(entity) else -- Remove references to these entities as they no longer exist global.beacon_overloaded_entities[entity_number] = nil global.beacon_overloaded_shapes[entity_number] = nil end end end Event.addListener("on_nth_tick_600", Beacon.validate_overloaded_entities) return Beacon