local Event = require('__stdlib__/stdlib/event/event').set_protected_mode(true) local Area = require('__stdlib__/stdlib/area/area') local Position = require('__stdlib__/stdlib/area/position') local table = require('__stdlib__/stdlib/utils/table') local time = require('__stdlib__/stdlib/utils/defines/time') local Queue = require('scripts/hash_queue') local queue local cfg -- Local functions for commonly used math functions local max, floor = math.max, math.floor local config = require('config') local armormods = require('scripts/armor-mods') local bot_radius = config.BOT_RADIUS local queue_speed = config.QUEUE_SPEED_BONUS local function unique(tbl) return table.keys(table.invert(tbl)) end local inv_list = unique { defines.inventory.character_trash, defines.inventory.character_main, defines.inventory.god_main, defines.inventory.chest, defines.inventory.character_vehicle, defines.inventory.car_trunk, defines.inventory.cargo_wagon } local explosives = { { name = 'cliff-explosives', count = 1 }, { name = 'explosives', count = 10 }, { name = 'explosive-rocket', count = 4 }, { name = 'explosive-cannon-shell', count = 4 }, { name = 'cluster-grenade', count = 2 }, { name = 'grenade', count = 14 }, { name = 'land-mine', count = 5 }, { name = 'artillery-shell', count = 1 } } local function update_settings() local setting = settings['global'] cfg = { poll_rate = setting['nanobots-nano-poll-rate'].value, queue_rate = setting['nanobots-nano-queue-rate'].value, queue_cycle = setting['nanobots-nano-queue-per-cycle'].value, build_tiles = setting['nanobots-nano-build-tiles'].value, network_limits = setting['nanobots-network-limits'].value, nanobots_auto = setting['nanobots-nanobots-auto'].value, equipment_auto = setting['nanobots-equipment-auto'].value, afk_time = setting['nanobots-afk-time'].value * time.second, do_proxies = setting['nanobots-nano-fullfill-requests'].value } end Event.register(defines.events.on_runtime_mod_setting_changed, update_settings) update_settings() -- table.find functions local table_find = table.find -- return the name of the item found for table.find if we found at least 1 item or cheat_mode is enabled. -- Don't return items with inventory local _find_item = function(item_prototype, _, player, at_least_one) local item, count = item_prototype.name, item_prototype.count count = at_least_one and 1 or count local prototype = game.item_prototypes[item] if prototype.type ~= 'item-with-inventory' then if player.cheat_mode or player.get_item_count(item) >= count then return true else local vehicle = player.vehicle local train = vehicle and vehicle.train return vehicle and ((vehicle.get_item_count(item) >= count) or (train and train.get_item_count(item) >= count)) end end end -- Is the player connected, not afk, and have an attached character -- @param player: the player object -- @return bool: player is connected and ready local function is_connected_player_ready(player) return (cfg.afk_time <= 0 or player.afk_time < cfg.afk_time) and player.character end local function has_powered_equipment(character, eq_name) local grid = character.grid if grid and grid.get_contents()[eq_name] then return table_find(grid.equipment, function(v) return v.name == eq_name and v.energy > 0 end) end end -- Is the player not in a logistic network or has a working nano-interface -- @param player: the player character object -- @return bool: true if has chip or not in network local function nano_network_check(character, e) if has_powered_equipment(character, 'equipment-bot-chip-nanointerface') then return true else local c = character local networks = e and e.surface.find_logistic_networks_by_construction_area(e.position, e.force) or c.surface.find_logistic_networks_by_construction_area(c.position, c.force) -- Con bots in network local pnetwork = c.logistic_cell and c.logistic_cell and c.logistic_cell.mobile and c.logistic_cell.logistic_network local has_pbots = c.logistic_cell and c.logistic_cell.construction_radius > 0 and c.logistic_cell.logistic_network and c.logistic_cell.logistic_network.all_construction_robots > 0 local has_nbots = table.any(networks, function(network) return network ~= pnetwork and network.all_construction_robots > 0 end) return not (has_pbots or has_nbots) end end local moveables = { train = true, car = true, spidertron = true } -- Can nanobots repair this entity. -- @param entity: the entity object -- @return bool: is damaged and repairable by nanobots local function nano_repairable_entity(entity) if entity.has_flag('not-repairable') or entity.type:find('robot') then return false end -- Can't repair tracks with trains on them. if (entity.type == 'straight-rail' or entity.type == 'curved-rail') and entity.minable == false then return false end if (entity.get_health_ratio() or 1) >= 1 then return false end if moveables[entity.type] and entity.speed > 0 then return false end return table_size(entity.prototype.collision_mask) > 0 end -- Get the gun, ammo and ammo name for the named gun: will return nil -- for all returns if there is no ammo for the gun. -- @param player: the player object -- @param gun_name: the name of the gun to get -- @return the gun object or nil -- @return the ammo object or nil -- @return string: the name of the ammo or nil --- @param player LuaPlayer local function get_gun_ammo_name(player, gun_name) local gun_inv = player.get_inventory(defines.inventory.character_guns) local ammo_inv = player.get_inventory(defines.inventory.character_ammo) local gun --- @type LuaItemStack local ammo --- @type LuaItemStack if not player.mod_settings['nanobots-active-emitter-mode'].value then local index gun, index = gun_inv.find_item_stack(gun_name) ammo = gun and ammo_inv[index] else local index = player.character.selected_gun_index gun, ammo = gun_inv[index], ammo_inv[index] end if gun and gun.valid_for_read and gun.name == gun_name and ammo.valid_for_read then return gun, ammo, ammo.name end return nil, nil, nil end -- Attempt to insert an item_stack or array of item_stacks into the entity -- Spill to the ground at the entity/player anything that doesn't get inserted -- @param entity: the entity or player object -- @param item_stacks: a SimpleItemStack or array of SimpleItemStacks to insert -- @return bool : there was some items inserted or spilled local function insert_or_spill_items(entity, item_stacks, is_return_cheat) if is_return_cheat then return end local new_stacks = {} if item_stacks then if item_stacks[1] and item_stacks[1].name then new_stacks = item_stacks elseif item_stacks and item_stacks.name then new_stacks = { item_stacks } end for _, stack in pairs(new_stacks) do local name, count, health = stack.name, stack.count, stack.health or 1 if game.item_prototypes[name] and not game.item_prototypes[name].has_flag('hidden') then local inserted = entity.insert({ name = name, count = count, health = health }) if inserted ~= count then entity.surface.spill_item_stack(entity.position, { name = name, count = count - inserted, health = health }, true) end end end return new_stacks[1] and new_stacks[1].name and true end end -- Attempt to insert an arrary of items stacks into an entity -- @param entity: the entity object -- @param item_stacks: a SimpleItemStack or array of SimpleitemStacks to insert -- @return table: an array of SimpleItemStacks not inserted local function insert_into_entity(entity, item_stacks) item_stacks = item_stacks or {} if item_stacks and item_stacks.name then item_stacks = { item_stacks } end local new_stacks = {} for _, stack in pairs(item_stacks) do local name, count, health = stack.name, stack.count, stack.health or 1 local inserted = entity.insert(stack) if inserted ~= count then new_stacks[#new_stacks + 1] = { name = name, count = count - inserted, health = health } end end return new_stacks end -- Scan the ground under a ghost entities collision box for items and insert them into the player. -- @param entity: the entity object to scan under -- @return table: a table of SimpleItemStacks or nil if empty local function get_all_items_on_ground(entity, existing_stacks) local item_stacks = existing_stacks or {} local surface, position, bouding_box = entity.surface, entity.position, entity.ghost_prototype.selection_box local area = Area.offset(bouding_box, position) for _, item_on_ground in pairs(surface.find_entities_filtered { name = 'item-on-ground', area = area }) do item_stacks[#item_stacks + 1] = { name = item_on_ground.stack.name, count = item_on_ground.stack.count, health = item_on_ground.health or 1 } item_on_ground.destroy() end local inserter_area = Area.expand(area, 3) for _, inserter in pairs(surface.find_entities_filtered { area = inserter_area, type = 'inserter' }) do local stack = inserter.held_stack if stack.valid_for_read and Position.inside(inserter.held_stack_position, area) then item_stacks[#item_stacks + 1] = { name = stack.name, count = stack.count, health = stack.health or 1 } stack.clear() end end return (item_stacks[1] and item_stacks) or {} end -- Get items with health data from the inventory -- @param entity: the entity object to search -- @param item: the item to look for -- @return item_stack; SimpleItemStack local function get_items_from_inv(entity, item_stack, cheat, at_least_one) if cheat then return { name = item_stack.name, count = item_stack.count, health = 1 } else local sources if entity.vehicle and entity.vehicle.train then sources = entity.vehicle.train.cargo_wagons sources[#sources + 1] = entity elseif entity.vehicle then sources = { entity.vehicle, entity } else sources = { entity } end local new_item_stack = { name = item_stack.name, count = 0, health = 1 } local count = item_stack.count for _, source in pairs(sources) do for _, inv in pairs(inv_list) do local inventory = source.get_inventory(inv) if inventory and inventory.valid and inventory.get_item_count(item_stack.name) > 0 then local stack = inventory.find_item_stack(item_stack.name) while stack do local removed = math.min(stack.count, count) new_item_stack.count = new_item_stack.count + removed new_item_stack.health = new_item_stack.health * stack.health stack.count = stack.count - removed count = count - removed if new_item_stack.count == item_stack.count then return new_item_stack end stack = inventory.find_item_stack(item_stack.name) end end end end -- If we havn't returned here check the hand! if entity.is_player() then local stack = entity.cursor_stack if stack and stack.valid_for_read and stack.name == item_stack.name then local removed = math.min(stack.count, count) new_item_stack.count = new_item_stack.count + removed new_item_stack.health = new_item_stack.health * stack.health stack.count = stack.count - count end end if new_item_stack.count == item_stack.count then return new_item_stack elseif new_item_stack.count > 0 and at_least_one then return new_item_stack else return nil end end end -- Manually drain ammo, if it is the last bit of ammo in the stack pull in more ammo from inventory if available -- @param player: the player object -- @param ammo: the ammo itemstack -- @return bool: this was the last one to be drained local function ammo_drain(player, ammo, amount) if player.cheat_mode then return true end amount = amount or 1 local name = ammo.name ammo.drain_ammo(amount) if not ammo.valid_for_read then local new = player.get_main_inventory().find_item_stack(name) if new then ammo.set_stack(new) new.clear() end return true end end -- Get the radius to use based on tehnology and player defined radius -- @param player: the player entity to check -- @param nano_ammo: the ammo to check local function get_ammo_radius(player, nano_ammo) local data = global.players[player.index] local max_radius = bot_radius[player.force.get_ammo_damage_modifier(nano_ammo.prototype.get_ammo_type().category)] or 7 local custom_radius = data.ranges[nano_ammo.name] or max_radius return custom_radius <= max_radius and custom_radius or max_radius end -- Attempt to satisfy module requests from player inventory -- @param requests: the item request proxy to get requests from -- @param entity: the entity to satisfy requests for -- @param player: the entity to get modules from local function satisfy_requests(requests, entity, player) local pinv = player.get_main_inventory() local new_requests = {} for name, count in pairs(requests.item_requests) do if count > 0 and entity.can_insert(name) then local removed = player.cheat_mode and count or pinv.remove({ name = name, count = count }) local inserted = removed > 0 and entity.insert({ name = name, count = removed }) or 0 local balance = count - inserted new_requests[name] = balance > 0 and balance or nil else new_requests[name] = count end end requests.item_requests = new_requests end -- Create a projectile from source to target -- @param name: the name of the projecticle -- @param surface: the surface to create the projectile on -- @param force: the force this projectile belongs too -- @param source: position table to start at -- @param target: position table to end at local function create_projectile(name, surface, force, source, target, speed) speed = speed or 1 force = force or 'player' surface.create_entity { name = name, force = force, position = source, target = target, speed = speed } end --[[Nano Emitter Queue Handler --]] -- Queued items are handled one at a time, --check validity of all stored objects at this point, They could have become -- invalidated between the time they were entered into the queue and now. function Queue.cliff_deconstruction(data) local entity, player = data.entity, game.get_player(data.player_index) if not (player and player.valid) then return end if not (entity and entity.valid and entity.to_be_deconstructed(player.force)) then return insert_or_spill_items(player, { data.item_stack }) end create_projectile('nano-projectile-deconstructors', entity.surface, entity.force, player.position, entity.position) local exp_name = data.item_stack.name == 'artillery-shell' and 'big-artillery-explosion' or 'big-explosion' entity.surface.create_entity { name = exp_name, position = entity.position } entity.destroy({ do_cliff_correction = true, raise_destroy = true }) end -- Handles all of the deconstruction and scrapper related tasks. function Queue.deconstruction(data) local entity, player = data.entity, game.get_player(data.player_index) if not (player and player.valid) then return end if not (entity and entity.valid and entity.to_be_deconstructed(player.force)) then return end local surface, force = data.surface or entity.surface, data.force or entity.force local ppos, epos = player.position, entity.position create_projectile('nano-projectile-deconstructors', surface, force, ppos, epos) create_projectile('nano-projectile-return', surface, force, epos, ppos) if entity.name == 'deconstructible-tile-proxy' then local tile = surface.get_tile(epos) if tile then player.mine_tile(tile) entity.destroy() end else player.mine_entity(entity) end end function Queue.build_entity_ghost(data) local ghost, player, ghost_surf, ghost_pos = data.entity, game.get_player(data.player_index), data.surface, data.position if not (player and player.valid) then return end if not (ghost.valid and data.entity.ghost_name == data.entity_name) then return insert_or_spill_items(player, { data.item_stack }, player.cheat_mode) end local item_stacks = get_all_items_on_ground(ghost) if not player.surface.can_place_entity { name = ghost.ghost_name, position = ghost.position, direction = ghost.direction, force = ghost.force } then return insert_or_spill_items(player, { data.item_stack }, player.cheat_mode) end local revived, entity, requests = ghost.revive { return_item_request_proxy = true, raise_revive = true } if not revived then return insert_or_spill_items(player, { data.item_stack }, player.cheat_mode) end if not entity then if insert_or_spill_items(player, item_stacks, player.cheat_mode) then create_projectile('nano-projectile-return', ghost_surf, player.force, ghost_pos, player.position) end return end create_projectile('nano-projectile-constructors', entity.surface, entity.force, player.position, entity.position) entity.health = (entity.health > 0) and ((data.item_stack.health or 1) * entity.prototype.max_health) if insert_or_spill_items(player, insert_into_entity(entity, item_stacks)) then create_projectile('nano-projectile-return', ghost_surf, player.force, ghost_pos, player.position) end if requests then satisfy_requests(requests, entity, player) end end function Queue.build_tile_ghost(data) local ghost, surface, position, player = data.entity, data.surface, data.position, game.get_player(data.player_index) if not (player and player.valid) then return end if not ghost.valid then return insert_or_spill_items(player, { data.item_stack }) end local tile, hidden_tile = surface.get_tile(position), surface.get_hidden_tile(position) local force = ghost.force local tile_was_mined = hidden_tile and tile.prototype.can_be_part_of_blueprint and player.mine_tile(tile) local ghost_was_revived = ghost.valid and ghost.revive({ raise_revive = true }) -- Mining tiles invalidates ghosts if not (tile_was_mined or ghost_was_revived) then return insert_or_spill_items(player, { data.item_stack }) end local item_ptype = data.item_stack and game.item_prototypes[data.item_stack.name] local tile_ptype = item_ptype and item_ptype.place_as_tile_result.result create_projectile('nano-projectile-constructors', surface, force, player.position, position) Position.floored(position) -- if the tile was mined, we need to manually place the tile. -- checking if the ghost was revived is likely unnecessary but felt safer. if tile_was_mined and not ghost_was_revived then create_projectile('nano-projectile-return', surface, force, position, player.position) surface.set_tiles({ { name = tile_ptype.name, position = position } }, true, true, false, true) end surface.play_sound { path = 'nano-sound-build-tiles', position = position } end function Queue.upgrade_direction(data) local ghost, player, surface = data.entity, game.get_player(data.player_index), data.surface if not (player and player.valid) then return end if not ghost.valid and not ghost.to_be_upgraded() then return end ghost.direction = data.direction ghost.cancel_upgrade(player.force, player) create_projectile('nano-projectile-constructors', ghost.surface, ghost.force, player.position, ghost.position) surface.play_sound { path = 'utility/build_small', position = ghost.position } end function Queue.upgrade_ghost(data) local ghost, player, surface, position = data.entity, game.get_player(data.player_index), data.surface, data.position if not (player and player.valid) then return end if not ghost.valid then return insert_or_spill_items(player, { data.item_stack }) end local entity = surface.create_entity { name = data.entity_name or data.item_stack.name, direction = ghost.direction, force = ghost.force, position = position, fast_replace = true, player = player, type = ghost.type == 'underground-belt' and ghost.belt_to_ground_type or nil, raise_built = true } if not entity then return insert_or_spill_items(player, { data.item_stack }) end create_projectile('nano-projectile-constructors', entity.surface, entity.force, player.position, entity.position) surface.play_sound { path = 'utility/build_small', position = entity.position } entity.health = (entity.health > 0) and ((data.item_stack.health or 1) * entity.prototype.max_health) end function Queue.item_requests(data) local proxy, player = data.entity, game.get_player(data.player_index) local target = proxy.valid and proxy.proxy_target if not (player and player.valid) then return end if not (proxy.valid and target and target.valid) then return insert_or_spill_items(player, { data.item_stack }) end if not target.can_insert(data.item_stack) then return insert_or_spill_items(player, { data.item_stack }) end create_projectile('nano-projectile-constructors', proxy.surface, proxy.force, player.position, proxy.position) local item_stack = data.item_stack local requests = proxy.item_requests local inserted = target.insert(item_stack) item_stack.count = item_stack.count - inserted if item_stack.count > 0 then insert_or_spill_items(player, { item_stack }) end requests[item_stack.name] = requests[item_stack.name] - inserted for k, count in pairs(requests) do if count == 0 then requests[k] = nil end end if table_size(requests) > 0 then proxy.item_requests = requests else proxy.destroy() end end --[[ Nano Emmitter --]] -- Extension of the tick handler, This functions decide what to do with their -- assigned robots and insert them into the queue accordingly. -- TODO: replace table_find entity-match with hashed lookup -- Nano Constructors -- Queue the ghosts in range for building, heal stuff needing healed --- @param player LuaPlayer local function queue_ghosts_in_range(player, pos, nano_ammo) -- local queue = global.nano_queue local pdata = global.players[player.index] local force = player.force local _next_nano_tick = (pdata._next_nano_tick and pdata._next_nano_tick < (game.tick + 2000) and pdata._next_nano_tick) or game.tick local tick_spacing = max(1, cfg.queue_rate - (queue_speed[force.get_gun_speed_modifier('nano-ammo')] or queue_speed[4])) local next_tick, queue_count = queue:next(_next_nano_tick, tick_spacing) local radius = get_ammo_radius(player, nano_ammo) local area = Position.expand_to_area(pos, radius) for _, ghost in pairs(player.surface.find_entities(area)) do local same_force = ghost.force == force local deconstruct = ghost.to_be_deconstructed() local upgrade = ghost.to_be_upgraded() and ghost.force == force if (deconstruct or upgrade or same_force) then if nano_ammo.valid and nano_ammo.valid_for_read then if not cfg.network_limits or nano_network_check(player.character, ghost) then if queue_count() < cfg.queue_cycle then if not queue:get_hash(ghost) then local data = { player_index = player.index, ammo = nano_ammo, position = ghost.position, surface = ghost.surface, unit_number = ghost.unit_number, entity = ghost } if deconstruct then if ghost.type == 'cliff' then if player.force.technologies['nanobots-cliff'].researched then local item_stack = table_find(explosives, _find_item, player) if item_stack then local explosive = get_items_from_inv(player, item_stack, player.cheat_mode) if explosive then data.item_stack = explosive data.action = 'cliff_deconstruction' queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end end end elseif ghost.minable then data.action = 'deconstruction' data.deconstructors = true queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end elseif upgrade then local prototype = ghost.get_upgrade_target() if prototype then if prototype.name == ghost.name then local dir = ghost.get_upgrade_direction() if ghost.direction ~= dir then data.action = 'upgrade_direction' data.direction = dir queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end else local item_stack = table_find(prototype.items_to_place_this, _find_item, player) if item_stack then data.action = 'upgrade_ghost' local place_item = get_items_from_inv(player, item_stack, player.cheat_mode) if place_item then data.entity_name = prototype.name data.item_stack = place_item queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end end end end elseif ghost.name == 'entity-ghost' or (ghost.name == 'tile-ghost' and cfg.build_tiles) then -- get first available item that places entity from inventory that is not in our hand. local proto = ghost.ghost_prototype local item_stack = table_find(proto.items_to_place_this, _find_item, player) if item_stack then if ghost.name == 'entity-ghost' then local place_item = get_items_from_inv(player, item_stack, player.cheat_mode) if place_item then data.action = 'build_entity_ghost' data.entity_name = proto.name data.item_stack = place_item queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end elseif ghost.name == 'tile-ghost' then -- Don't queue tile ghosts if entity ghost is on top of it. if ghost.surface.count_entities_filtered { name = 'entity-ghost', area = Area(ghost.bounding_box):non_zero(), limit = 1 } == 0 then local tile = ghost.surface.get_tile(ghost.position) if tile then local place_item = get_items_from_inv(player, item_stack, player.cheat_mode) if place_item then data.item_stack = place_item data.action = 'build_tile_ghost' queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end end end end end elseif nano_repairable_entity(ghost) then -- Check if entity needs repair, TODO: Better logic for this? if ghost.surface.count_entities_filtered { name = 'nano-cloud-small-repair', position = ghost.position } == 0 then ghost.surface.create_entity { name = 'nano-projectile-repair', position = player.position, force = force, target = ghost.position, speed = 0.5 } queue_count(1) ammo_drain(player, nano_ammo, 1) end -- repair elseif ghost.name == 'item-request-proxy' and cfg.do_proxies then local items = {} for item, count in pairs(ghost.item_requests) do items[#items + 1] = { name = item, count = count } end local item_stack = table_find(items, _find_item, player, true) if item_stack then local place_item = get_items_from_inv(player, item_stack, player.cheat_mode, true) if place_item and place_item.count > 0 then data.action = 'item_requests' data.item_stack = place_item queue:insert(data, next_tick()) ammo_drain(player, nano_ammo, 1) end end end -- deconstruct, build or repair end -- hash_check() else break end -- queue_count() end -- network check else -- ran out of ammo, break out here break end -- valid ammo end -- not flag not_on_map end -- looping through entities pdata._next_nano_tick = next_tick() or game.tick end -- function -- Nano Termites -- Kill the trees! Kill them dead local function everyone_hates_trees(player, pos, nano_ammo) local radius = get_ammo_radius(player, nano_ammo) local force = player.force for _, stupid_tree in pairs(player.surface.find_entities_filtered { position = pos, radius = radius, type = 'tree', limit = 200 }) do if nano_ammo.valid and nano_ammo.valid_for_read then if not stupid_tree.to_be_deconstructed(player.force) then local tree_area = Area.expand(stupid_tree.bounding_box, .5) if player.surface.count_entities_filtered { area = tree_area, name = 'nano-cloud-small-termites' } == 0 then player.surface .create_entity { name = 'nano-projectile-termites', position = player.position, force = force, target = stupid_tree, speed = .5 } ammo_drain(player, nano_ammo, 1) end end else break end end end --[[ EVENTS --]] -- The Tick Handler local function poll_players(event) -- Run logic for nanobots and power armor modules -- if event.tick % math.ceil(#game.connected_players/cfg.poll_rate) == 0 then if event.tick % max(1, floor(cfg.poll_rate / #game.connected_players)) == 0 then local last_player, player = next(game.connected_players, global._last_player) -- Establish connected, non afk, player character if player and is_connected_player_ready(player) then if cfg.nanobots_auto and (not cfg.network_limits or nano_network_check(player.character)) then local gun, nano_ammo, ammo_name = get_gun_ammo_name(player, 'gun-nano-emitter') if gun then if ammo_name == 'ammo-nano-constructors' then queue_ghosts_in_range(player, player.position, nano_ammo) elseif ammo_name == 'ammo-nano-termites' then everyone_hates_trees(player, player.position, nano_ammo) end end -- Gun and Ammo check end if cfg.equipment_auto then armormods.prepare_chips(player) end -- Auto Equipment end -- Player Ready global._last_player = last_player end -- NANO Automatic scripts queue:execute(event) end Event.register(defines.events.on_tick, poll_players) -- Reset last player when players join or leave local function players_changed() global._last_player = nil end Event.register({ defines.events.on_player_joined_game, defines.events.on_player_left_game }, players_changed) local function on_nano_init() global.nano_queue = Queue() queue = global.nano_queue game.print('Nanobots are now ready to serve') end Event.register(Event.core_events.init, on_nano_init) local function on_nano_load() queue = Queue(global.nano_queue) end Event.register(Event.core_events.load, on_nano_load) local function reset_nano_queue() global.nano_queue = nil queue = nil global.nano_queue = Queue() queue = global.nano_queue for _, player in pairs(global.players) do player._next_nano_tick = 0 end end Event.register(Event.generate_event_name('reset_nano_queue'), reset_nano_queue)