Ghost Warnings


Gives a warning when a ghost or module request is placed and there isn't an item in the logistic network to build it. Particularly useful with Space Exploration's Navigation Satellite.

Utilities
1 year, 6 months ago
1.1
11.0K

FAQ

Source code

local function get_warning_history(player_index)
  local warning_history = global.warning_history
  local player_history = warning_history[player_index] or {}
  warning_history[player_index] = player_history
  return player_history
end


local function check_update_history(current_tick, item_name, player, surface_name)
  local player_index = player.index
  local player_history = get_warning_history(player_index)


  if surface_name ~= player_history.surface or (player_history.last_tick and current_tick - player_history.last_tick > 7200) then
    -- Player has changed surface or the last event was a long time ago: reset history
    global.warning_history[player_index] = {
      surface = surface_name,
      last_tick = current_tick,
      items = {
        [item_name] = current_tick
      },
      since_last_click = {},
    }
    return true
  end
  if not player_history.since_last_click[item_name] then
    player_history.since_last_click[item_name] = true

    local last_item_tick = player_history.items[item_name]
    local cooldown = player.mod_settings["gw-warning-cooldown"].value * 60

    if not last_item_tick or (current_tick - last_item_tick > cooldown) then
      player_history.items[item_name] = current_tick
      return true
    end
  end
end

local function at_least_one_item_in_network(items, networks)
  local all_hidden = true  -- LTN stations etc (composite entities)
  local item_prototypes
  for _, item in pairs(items) do
    for _, network in pairs(networks) do
      if network.get_item_count(item.name) >= item.count then
        return true
      end
    end
    item_prototypes = item_prototypes or game.item_prototypes
    if not item_prototypes[item.name].has_flag("hidden") then
      all_hidden = false
    end
  end
  if all_hidden then
    return true
  end
  return false
end

local function all_items_in_network(items, networks)
  for item, count in pairs(items) do
    local found_count = 0
    for _, network in pairs(networks) do
      found_count = found_count + network.get_item_count(item)
    end
    if found_count < count then
      return false, item
    end
  end
  return true
end

local function on_built_entity(entity, player, player_index, override_requests)
  -- override_requests used when dealing with custom event from Module Inserter Simplified
  local setting = player.mod_settings["gw-enable-warnings"].value
  if setting == "off" then return end
  if setting == "navsat-only" then
    if not (remote.interfaces["space-exploration"] and remote.call("space-exploration", "remote_view_is_active", { player = player })) then
      return
    end
  end
  local success = false
  local message = {"ghost-warnings.not-in-construction-network"}

  local force = entity.force
  local surface = entity.surface
  local position = entity.position
  local logistic_networks = surface.find_logistic_networks_by_construction_area(position, force)

  local missing_name
  -- entity.type is either entity-ghost, tile-ghost, or item-request-proxy
  -- (or any other entity if override_requests is set)
  if next(logistic_networks) then
    if entity.type ~= "item-request-proxy" and not override_requests then
      local prototype = entity.ghost_prototype
      local items_to_place_this = prototype.items_to_place_this
      success = at_least_one_item_in_network(items_to_place_this, logistic_networks)
      missing_name = prototype.name
      message = {"missing-item", prototype.localised_name}
    end
    if entity.type ~= "tile-ghost" then
      local item_requests = override_requests or entity.item_requests
      if (entity.type == "item-request-proxy" or success or override_requests) and next(item_requests) then
        success, missing_name = all_items_in_network(item_requests, logistic_networks)
        if missing_name then
          message = {"missing-item", game.item_prototypes[missing_name].localised_name}
        end
      end
    end
  end

  local tick = game.tick
  if not success and check_update_history(tick, missing_name or "gw-not-in-construction-network", player, surface.name) then
    player.create_local_flying_text{
      text = message,
      position = position,
    }
    local sound_history = global.sound_history
    if player.mod_settings["gw-play-warning-sound"].value and (not sound_history[player_index] or sound_history[player_index] ~= tick) then
      player.play_sound{ path = "gw-warning-sound" }
      sound_history[player_index] = tick
    end
  end
end
script.on_event(defines.events.on_built_entity,
  function(event)
    local entity = event.created_entity
    local player_index = event.player_index
    local player = game.get_player(player_index)
    on_built_entity(entity, player, player_index)
  end,
  {{ filter = "ghost" }}
)
script.on_event(defines.events.script_raised_built,
  function(event)
    -- script_raised_built doesn't give a player, so raise for all players on force
    local entity = event.entity
    local force = entity.force

    -- Check each player to see if any of them have used a module inserter this tick. If so assume that it was them.
    for _, player in pairs(force.connected_players) do
      if global.script_raised_built_history[player.index] == event.tick then
        return
      end
    end

    for _, player in pairs(force.connected_players) do
      if player.surface == entity.surface then
        on_built_entity(entity, player, player.index)
      end
    end
  end,
  {{ filter = "type", type = "item-request-proxy" }}
)



script.on_event({"gw-build", "gw-build-ghost", "gw-build-with-obstacle-avoidance", "gw-select-for-blueprint", "gw-reverse-select", "gw-select-for-cancel-deconstruct"},
  function(event)
    local player_history = get_warning_history(event.player_index)
    player_history.since_last_click = {}
  end
)

local function setup_mis_event()
  if remote.interfaces["ModuleInserterSimplified"] then
    local on_module_inserted = remote.call("ModuleInserterSimplified", "get_events").on_module_inserted
    script.on_event(on_module_inserted,
      function(event)
        -- {modules = {[module] = count}, player = player, entity = entity}
        local player_index = event.player.index
        global.script_raised_built_history[player_index] = event.tick
        on_built_entity(event.entity, event.player, player_index, event.modules)
      end
    )
  end
end

local function init_global()
  -- Reset all history in on_configuration_changed
  global.warning_history = {}
  global.sound_history = {}
  global.script_raised_built_history = {}
end
script.on_init(
  function()
    init_global()
    setup_mis_event()
  end
)
script.on_configuration_changed(init_global)
script.on_load(setup_mis_event)