pioneerspacesim / pioneer

A game of lonely space adventure
https://pioneerspacesim.net
1.63k stars 377 forks source link

Ships stuck in docking phase #3331

Open ghost opened 9 years ago

ghost commented 9 years ago

There is something strange going on with ships set to dock (ship:AIDockWith(starport)). The AI triggers the event "onAICompleted", but flight status of the ship is stuck in "DOCKING". Either the docking procedure is not complete (in which case the AI should not trigger "onAICompleted"), or the flight status should be changed to "DOCKED".

This does not happen for every docking event. I have not actually seen the ships stuck in docking (are they hanging in mid-air?). But I notice this because the game immediately crashes when I ask ships to undock that are stuck in this limbo. I can't figure out what exactly triggers this. Has anybody else seen it?

I'm currently using the 20150120 build for 64-bit linux.

corundscale commented 9 years ago

I'm still out of dev process, but could it be similar to what happens to player when forcing time compression when landing using autopilot? The ship effectively seems to land on the pad, but the permission times out before that so it never actually completes docking.

ghost commented 9 years ago

That would be in keeping with my observations. I have only seen this when speeding up time. I will try another game where I don't speed up and just keep it running in the background. Let's see if the same happens.

I can also confirm that the ships that are "stuck" stay there for long. I have re-checked their status every minute and they just stay in that "non-docked" state for several minutes (I just aborted the script after that).

In a script modeling ~1000 ships in the Sol system I had 20 ships enter the "non-docked" state within 1.5 hours of game play. The problem is, over time, more and more ships get stuck in this state. Maybe it hasn't been noticed before because the traffic was kept low?

ghost commented 9 years ago

I just did another simulation where I did not speed up time. Within 5 minutes of (realtime) game play 3 ships got stuck. I was using the same simulation (~1000 ships in Sol) as before.

I am playing with the 20150120 64-bit linux release.

ghost commented 9 years ago

Some more information collected in the last few simulations:

  1. Ships get "stuck" in ground bases and starports in space.
  2. The ships being "stuck" look like they have landed appropriately. For example, there were 8 ships "stuck" at Gates Starport. All eight had landed in an appropriate spot within the space station (looked pretty cool). They did not hover over the surface or were "stuck" in mid-flight or something like that. No thrusters visible. Position lights were blinking on each ship.
  3. The stuck ships don't show up on your scanner when approaching the station where they are "stuck".

All that leads me to believe that the ships are actually docked all right - they just need to change their flight status.

If you want me to test anything else - just let me know.

fluffyfreak commented 9 years ago

How are you detecting that they're getting stuck? I've been sticking breakpoints all over the place and they all seem to be docking but I might be missing something. Or I'm only getting the ones that are working and not the ones that fail.

ghost commented 9 years ago

I first noticed it when my script crashed pioneer when trying to undock ships that apparently were not docked in the first place. I now catch and collect ships that I have set to dock somewhere if the Ai claims to be done (onaicompleted) but the flight status did not change to DOCKED. Over time this bin fills up with more and more ships (hundreds over time). I then went and flew to an actual location where they were stuck. All 8 were sitting there. I can send you the script if you like. I do simulate 1000+ ships though.

corundscale commented 9 years ago

It really does look like what happens to the player when trying to dock in accelerated time. Could it be something like permission timing out without AI being notified in any manner?

fluffyfreak commented 9 years ago

@clausimu yes if you could share your changes somehow that would be good as it sounds like it rarely happens, I'm sure that I can fix it.

@corundscale it does sound like that but when that happens to ships I still see them docking and changing status so there must be some other condition associated with it.

ghost commented 9 years ago

The following is a crude script that simulates local traffic. In the function "afterDocking" I check for the flight status (lines 180-192) and print all stuck ships together with their starport where they are stuck whenever a new ship is added to the list.

If there is a better way to share this, please let me know. Can I mark the following as code to have it formatted a certain way? I'm totally new to git and the game debugging process. Please also excuse my really hacky way of lua scripting. I'm an absolute beginner.

-- This script adds local traffic (between starports within the system). Currently, cargo traffic is random (= random items are
-- delivered from starport to starport). In the future it is planned to adapt this to the actual market.
-- 
-- Adapted from and copied in part from TradeShips.lua.
--
-- Claudius Mueller
--
-- 01-16-15

local Engine = import("Engine")
local Game = import("Game")
local Space = import("Space")
local Comms = import("Comms")
local Timer = import("Timer")
local Event = import("Event")
-- local Serializer = import("Serializer")
local ShipDef = import("ShipDef")
local Ship = import("Ship")
local utils = import("utils")
local e = import ("Equipment")

local starports = {}        -- all starports in this system
local vacuum_starports = {} -- just starports in space in this system
local ships = {}        -- all ships managed by this script
local stuck_ships = {}      -- ships stuck in "docking" (bug!)

local addShipEquip = function (ship)
  -- add standard equipment to ship

  local ship_data = ships[ship]
  local ship_def = ship_data['ship_def']

  -- add atmospheric shielding if appropriate and flag
  if ship_def.equipSlotCapacity.atmo_shield > 0 then
    ship:AddEquip(e.misc.atmospheric_shielding)
    ship_data.atmoshield = true
  else
    ship_data.atmoshield = false
  end

  -- add general equipment
  ship:AddEquip(e.misc.scanner)
  ship:AddEquip(e.misc.autopilot)
  ship:AddEquip(e.misc.cargo_life_support)

  -- add defensive equipment based on lawlessness, luck and size
  local lawlessness = Game.system.lawlessness
  local size_factor = ship.freeCapacity ^ 2 / 2000000
  if Engine.rand:Number(1) - 0.1 < lawlessness then
    local num = math.floor(math.sqrt(ship.freeCapacity / 50)) -
               ship:CountEquip(e.misc.shield_generator)
    if num > 0 then ship:AddEquip(e.misc.shield_generator, num) end
    if ship_def.equipSlotCapacity.energy_booster > 0 and
      Engine.rand:Number(1) + 0.5 - size_factor < lawlessness then
      ship:AddEquip(e.misc.shield_energy_booster)
    end
  end

  -- add auto hull repair (rare)
  if ship_def.equipSlotCapacity.hull_autorepair > 0 and
    Engine.rand:Number(1) + 0.75 - size_factor < lawlessness then
    ship:AddEquip(e.misc.hull_autorepair)
  end
end

local alterShipCargo = function (ship, starport)
  -- load and unload ship (currently just randomly loads stuff)

  -- debug 
  print(ship.label.." | status: "..ships[ship]['status'].." | flight state: "..ship.flightState.." - loading/unloading cargo")
  --

  local total = 0
  local empty_space = math.min(ship.freeCapacity, ship:GetEquipFree("cargo"))
  local cargo = {}
  local ship_data = ships[ship]

  -- remove old cargo and add to starbase stock if anything there
  local num_removed = 0
  if ship_data.cargo then
    for equip, amount in pairs(ship_data.cargo) do
      num_removed = num_removed + ship:RemoveEquip(equip, amount)
      starport:AddEquipmentStock(equip, amount)
    end
  end
  ship_data.cargo = nil

  -- add new cargo (currently random type)
  local cargo_types = {}
  local i = 0
  for cargo_type, cargo_data in pairs(e.cargo) do
     i=i+1
     cargo_types[i] = cargo_type
  end
  local equip = cargo_types[Engine.rand:Integer(1, #cargo_types)]
  local num_added = ship:AddEquip(e.cargo[equip], empty_space)
  cargo[e.cargo[equip]] = num_added
  ship_data.cargo = cargo
  return num_added + num_removed
end

local afterUndocking = function (ship, ship_data)
  -- procedure after undocking (find target, fly there to dock)

  -- debug 
  print(ship.label.." | status: "..ship_data['status'].." | flight state: "..ship.flightState.." - after undocking")
  --

  -- set ship status to 'transit'
  ship_data['status'] = 'transit'

  -- find appropriate new target (based on presence of atmoshield)
  if ship_data.atmoshield == true then
    ship_data['starport'] = starports[Engine.rand:Integer(1, #starports)]
  else
    ship_data['starport'] = vacuum_starports[Engine.rand:Integer(1, #vacuum_starports)]
  end

  -- fly to and dock with target
  ship:AIDockWith(ship_data.starport)
end

local onShipUndocked = function (ship, starport)
  -- triggered after a ship has undocked from a starport
  -- filter to apply only to ships managed by this script
  if ships[ship] == nil then return end
  afterUndocking(ship, ships[ship])
end
Event.Register("onShipUndocked", onShipUndocked)

local doUndock
doUndock = function (ship, ship_data)
  -- undock ship from starport

  -- debug 
  print(ship.label.." | status: "..ship_data['status'].." | flight state: "..ship.flightState.." - undocking")
  --

  -- change ship status to 'undocking'
  ship_data['status'] = 'undocking'

--   if can't undock try again in 1 minute
  if not ship:Undock() then
    ship_data['delay'] = Game.time + 60
    Timer:CallAt(ship_data.delay, function () doUndock(ship, ship_data) end)
  else
    ship_data['delay'] = nil
  end
end

-- debug
local checkStuckShip
checkStuckShip = function (ship)
  -- check if a stuck ship is finally docked
  if ship.flightState == "Docked" then
    print("======================================> Ship now docked after "..stuck_ships[ship].. " minute(s).")
    table.remove(stuck_ships, ship)
  else
    stuck_ships[ship] = stuck_ships[ship] + 1
    local test_delay = Game.time + 60
    Timer:CallAt(test_delay, function () checkStuckShip(ship) end)
    for ship, stuckcheck in pairs(stuck_ships) do print(ship.label, stuckcheck) end
  end
end
--

local afterDocking = function (ship, ship_data)
  -- procedure after docking (transfer cargo, go to relaunch phase)

  -- debug 
  print(ship.label.." | status: "..ship_data['status'].." | flight state: "..ship.flightState.." - after docking")
  if ship.flightState ~= "DOCKED" then
    local stuckcheck = 1
    stuck_ships[ship] = stuckcheck
    local stuckshipcount = 0
    for _ in pairs(stuck_ships) do stuckshipcount = stuckshipcount + 1 end
    local shipcount = 0
    for _ in pairs(ships) do shipcount = shipcount + 1 end
    print("==========================================> New ship stuck! A total of "..stuckshipcount.." ships are now stuck of a total of "..shipcount..".")
    for ship,_ in pairs(stuck_ships) do print(ship.label, ships[ship].starport.label) end
    local test_delay = Game.time + 60
--    Timer:CallAt(test_delay, function () checkStuckShip(ship) end)
  else
  --

    -- change ship status to 'docked'
    ship_data['status'] = 'docked'

    -- unload/load ship with cargo
    local delay = alterShipCargo(ship, ship_data.starport)

    -- have ship wait 1-5 seconds per unit of cargo
    if delay > 0 then
      ship_data['delay'] = Game.time + (delay * Engine.rand:Number(1, 5))
    end

    -- undock from starport
    Timer:CallAt(ship_data.delay, function () doUndock(ship, ship_data) end)
  end
end

local getAcceptableShips = function ()
  -- get ships without hyperdrives

  local filter_function

  -- if no more than 1 vacuum_starports get only ships with atmospheric shielding
  if #vacuum_starports < 2 then
    filter_function = function(k,def)
      return def.tag == 'SHIP' and def.hyperdriveClass == 0 and def.equipSlotCapacity.atmo_shield > 0 and def.hullMass < 100
    end

  -- if both types of starports present get with and without atmospheric shielding
  else
    filter_function = function(k,def)
      return def.tag == 'SHIP' and def.hyperdriveClass == 0 and def.hullMass < 100
    end
  end

  -- pull appropriate ships and return as array
  return utils.build_array(utils.map(function (k,def) return k,def end, utils.filter(filter_function, pairs(ShipDef))))
end

local chooseShipType = function (ship_defs, atmoshield)
  -- get a ship type that fits specific criteria (i.e. has to fit atmoshield)

  -- if atmospheric shield required
  if atmoshield == true then

    -- filter function for atmoshield
    atmoshield_filter = function(k,def)
    return def.equipSlotCapacity.atmo_shield > 0
    end

    -- pull appropriate ships and return as array
    atmoshield_shipdefs = utils.build_array(utils.map(function (k,def) return k,def end, utils.filter(atmoshield_filter, pairs(ship_defs))))

    -- get ship
    return ship_defs[Engine.rand:Integer(1, #atmoshield_shipdefs)]

  -- if not requirements get random ship
  else
    return ship_defs[Engine.rand:Integer(1, #ship_defs)]
  end
end

local spawnInitialShips = function ()
  --spawn initial ships and setup system variable (starports, trading tables, etc.)

  --get starports (not more than 1 starport = no local traffic)
  starports = Space.GetBodies(function (body) return body.superType == 'STARPORT' end)
  if #starports < 2 then return nil end
  vacuum_starports = Space.GetBodies(function (body)
    return body.superType == 'STARPORT' and (body.type == 'STARPORT_ORBITAL' or (not body.path:GetSystemBody().parent.hasAtmosphere)) end)

  -- get population (no population = no local traffic)
  local population = Game.system.population
  if population == 0 then return nil end

  -- get appropriate ship types (no types = no local traffic)
  local ship_defs = getAcceptableShips()
  if #ship_defs == 0 then return nil end

  -- determine how many trade ships to spawn
  local lawlessness = Game.system.lawlessness
  -- start with 100 ships per 1 billion population
  local num_trade_ships = population * 100
  -- reduce based on lawlessness
  num_trade_ships = num_trade_ships * (1 - lawlessness)

  -- compute average distance of spawned ships in space from core of system [AU]
  local range = 3

  -- spawn the initial trade ships
  for i = 0, num_trade_ships do

  -- get a ship_def
--   local ship_def = ship_defs[Engine.rand:Integer(1, #ship_defs)]
--   local ship = nil

    -- spawn the first quarter in starport
    if i < num_trade_ships / 4 then
      local starport = starports[Engine.rand:Integer(1, #starports)]

      -- if starport is on planet pull ship that can fit atmospheric shielding
      local ship_def = chooseShipType(ship_defs, starport.isGroundStation)

      ship = Space.SpawnShipDocked(ship_def.id, starport)
      if ship ~= nil then
    ship:SetLabel(Ship.MakeRandomLabel())
    ships[ship] = {status = 'docked',
               starport = starport,
               ship_def = ship_def,
               atmoshield = nil}
    addShipEquip(ship)

      -- if the starport is already full
      else
    ship = Space.SpawnShipNear(ship_def.id, starport, 5000, 10000000) -- 10 Mio km = 1AU
    ship:SetLabel(Ship.MakeRandomLabel())
    ships[ship] = {status = 'transit',
               starport = starport,
               ship_def = ship_def,
               atmoshield = nil}
    addShipEquip(ship)
      end

    -- spawn the rest inbound to some starport
    else
      local starport = starports[Engine.rand:Integer(1, #starports)]

      -- if starport is on planet pull ship that can fit atmospheric shielding
      local ship_def = chooseShipType(ship_defs, starport.isGroundStation)

      ship = Space.SpawnShipNear(ship_def.id, starport, 5000, 10000000) -- 10 Mio km = 1AU
      ship:SetLabel(Ship.MakeRandomLabel())
      ships[ship] = {status = 'transit',
             starport = starport,
             ship_def = ship_def,
             atmoshield = nil}
      addShipEquip(ship)
    end

    local ship_data = ships[ship]

    -- if ship is docked
    if ship_data.status == 'docked' then
    afterDocking(ship, ship_data)

    -- if ship is in space
    elseif ship_data.status == 'transit' then

      -- dock with target starport
      ship:AIDockWith(ship_data.starport)
    end
  end
end

local onEnterSystem = function (ship)
-- if the player has entered the systemafterdocking
-- spawn initial ships and get them going
  if ship ~= Game.player then return end

  spawnInitialShips()

end
Event.Register("onEnterSystem", onEnterSystem)

local onAICompleted = function (ship, ai_error)
  -- if the AI is done with the task assigned find a new task
  -- only applies if ship is managed by this script
  if ships[ship] == nil then return end

  local ship_data = ships[ship]

  -- done with flying and docking
  if ship_data.status == 'transit' then
    afterDocking(ship, ship_data)
  else
    print("Ship done with AI but not in transit "..ship.label)
  end

--   -- done with undocking
--   elseif ship_data.status == 'leaving' then
--     afterUndocking(ship, ship_data)
--   end
end
Event.Register("onAICompleted", onAICompleted)

local onGameStart = function ()
    spawnInitialShips()
end
Event.Register("onGameStart", onGameStart)
corundscale commented 9 years ago

@fluffyfreak Ok, so another question: What is supposed to happen after following sequence of events:

Could we ensure that things like permission timing out always happen as the last thing in a timestep and that stuff like permission duration is always rounded up (so regardless of timestep length it always takes at least as long as it's supposed to)? (I'm still out of the loop, alas.)

ghost commented 9 years ago

I think someone smart needs to take a look at my script again (see previous post). I just reduced the number of ships by 5 (20 * population) to make it easier to track each ship. Starting at Barnard's star I see 1 or two ships. Both get stuck upon landing each time. If that does happen every time a ship lands (using my script) - maybe there is something wrong with my script, rather than the game itself. I can't figure out what though. My steps are:

1) undock 2) find new target within the system that is suitable for ship (atmoshields) 3) set AI to dock with target 4) when AI is complete, trigger "afterdocking" stuff (unload cargo, etc.) 5) repeat from (1)

Everything works as planned, except that at (4) the flight status of the ship is not set to "DOCKED" but is still "DOCKING". Which leads to the ships being stuck, since I can't undock them anymore. I have actually followed a ship to its target destination and quickly landed before them. Then I watched the ship dock. Everything looks perfect. Except, it does not change its flight status.

ghost commented 9 years ago

I think I have it traced down to catching onAICompleted versus onShipDocked. If I proceed after onAICompleted is triggered (after having given the AIDockWith command), the ship never changes status to "Docked" but stays in "Docking" flight status (even though physically it has landed - and even after sitting for a long time). If I proceed after onShipDocked is triggered (also after having given the AIDockWith command), the ship docks fine and status is changed to "Docked".

Somebody more knowledgable than me will have to explain why it works like that though...

corundscale commented 9 years ago

Does it work for all ships now, or do you still have a population that fails to dock properly, but doesn't cause visible problems because they no longer attempt to undock without being docked?

ghost commented 9 years ago

As of right now I have not seen another ship that got "stuck" in the docking process.

impaktor commented 9 years ago

I think I have it traced down to catching onAICompleted versus onShipDocked. If I proceed after onAICompleted is triggered (after having given the AIDockWith command), the ship never changes status to "Docked" but stays in "Docking" flight status (even though physically it has landed - and even after sitting for a long time). If I proceed after onShipDocked is triggered (also after having given the AIDockWith command), the ship docks fine and status is changed to "Docked".

@clausimu Could you please clarify what you mean by your use of "proceed"? I'm afraid I don't quite follow.

ghost commented 9 years ago

I'm sorry for not being clear. With "proceed" I mean call the next function in the script (handling the loading/unloading of the ship and then undocking from the starport). If I wait for the onShipDocked event and then follow this with loading/unloading/undocking everything seems fine. If I wait for the onAICompleted (after having told the ship to dock via the AIDockWith command) event and then try to load/unload/undock the ship stays stuck in the "undocking" state (even though physically it has landed).

walterar commented 9 years ago

Years ago I observe the same as @clausimu and @corundscale The adjust of final sequence of docking is critical and no one has been able to solve yet.

Web-eWorks commented 8 months ago

@Gliese852 what's the status of this issue after manual docking changes? Is it still valid, or can it now be closed?