diff --git a/data/modules/TradeShips/Debug.lua b/data/modules/TradeShips/Debug.lua index 03be910660a..b3316994904 100644 --- a/data/modules/TradeShips/Debug.lua +++ b/data/modules/TradeShips/Debug.lua @@ -105,8 +105,10 @@ debugView.registerTab('debug-trade-ships', function() property("Total flow", string.format("%.2f ship/hour", Core.params.total_flow)) ui.sameLine() property("Last spawn interval", ui.Format.Duration(Core.last_spawn_interval)) - ui.sameLine() - property("Lawlessness", string.format("%.4f", Game.system.lawlessness)) + if Game.system then + ui.sameLine() + property("Lawlessness", string.format("%.4f", Game.system.lawlessness)) + end ui.sameLine() property("Total bodies in space", #Space.GetBodies()) end @@ -126,38 +128,41 @@ debugView.registerTab('debug-trade-ships', function() totals.label = "Total for " .. utils.count(Core.params.port_params) .. " ports" local obj = Game.systemView:GetSelectedObject() local sb_selected = obj.type == Engine.GetEnumValue("ProjectableTypes", "OBJECT") and obj.base == Engine.GetEnumValue("ProjectableBases", "SYSTEMBODY") - arrayTable.draw("tradeships_stationinfo2", Core.params.port_params, arrayTable.addKeys(pairs, { - port = function(k,_) return k end, - label = function(k,_) return k:GetLabel() end, - parent = function(k,_) return k:GetSystemBody().parent.name end, - dist = function(k,_) return k:DistanceTo(k:GetSystemBody().nearestJumpable.body) end, - docks = function(k,_) totals.docks = totals.docks + k.numDocks return k.numDocks end, - busy_s = function(k,_) totals.busy_s = totals.busy_s + k.numShipsDocked return k.numShipsDocked end, - inbound = function(k,_) return inbound[k] end, - landed = function(_,v) totals.landed = totals.landed + v.landed return v.landed end, - flow = function(_,v) totals.flow = totals.flow + v.flow return v.flow end - }),{ - { name = "Port", key = "label", string = true }, - { name = "Parent", key = "parent", string = true }, - { name = "Distance", key = "dist", fnc = distanceInAU }, - { name = "Docks", key = "docks" }, - { name = "Busy", key = "busy", fnc = format("%.2f") }, - { name = "Landed", key = "busy_s" }, - { name = "Calculated", key = "landed", fnc = format("%.2f") }, - { name = "Dock time", key = "time", fnc = format("%.2fh") }, - { name = "Inbound", key = "inbound" }, - { name = "Ship flow", key = "flow", fnc = format("%.2f ship/h") }, - },{ - totals = { totals }, - callbacks = { - onClick = function(row) - Game.systemView:SetSelectedObject(Engine.GetEnumValue("ProjectableTypes", "OBJECT"), - Engine.GetEnumValue("ProjectableBases", "SYSTEMBODY"), row.port:GetSystemBody()) - end, - isSelected = function(row) - return sb_selected and Game.systemView:GetSelectedObject().ref == row.port:GetSystemBody() - end - }}) + if Game.system then + arrayTable.draw("tradeships_stationinfo2", Core.params.port_params, arrayTable.addKeys(pairs, { + port = function(k,_) return k end, + label = function(k,_) return k:GetLabel() end, + parent = function(k,_) return k:GetSystemBody().parent.name end, + dist = function(k,_) return k:DistanceTo(k:GetSystemBody().nearestJumpable.body) end, + docks = function(k,_) totals.docks = totals.docks + k.numDocks return k.numDocks end, + busy_s = function(k,_) totals.busy_s = totals.busy_s + k.numShipsDocked return k.numShipsDocked end, + inbound = function(k,_) return inbound[k] end, + landed = function(_,v) totals.landed = totals.landed + v.landed return v.landed end, + flow = function(_,v) totals.flow = totals.flow + v.flow return v.flow end + }),{ + { name = "Port", key = "label", string = true }, + { name = "Parent", key = "parent", string = true }, + { name = "Distance", key = "dist", fnc = distanceInAU }, + { name = "Docks", key = "docks" }, + { name = "Busy", key = "busy", fnc = format("%.2f") }, + { name = "Landed", key = "busy_s" }, + { name = "Calculated", key = "landed", fnc = format("%.2f") }, + { name = "Dock time", key = "time", fnc = format("%.2fh") }, + { name = "Inbound", key = "inbound" }, + { name = "Ship flow", key = "flow", fnc = format("%.2f ship/h") }, + },{ + totals = { totals }, + callbacks = { + onClick = function(row) + Game.systemView:SetSelectedObject(Engine.GetEnumValue("ProjectableTypes", "OBJECT"), + Engine.GetEnumValue("ProjectableBases", "SYSTEMBODY"), row.port:GetSystemBody()) + end, + isSelected = function(row) + return sb_selected and Game.systemView:GetSelectedObject().ref == row.port:GetSystemBody() + end + } + }) + end end if ui.collapsingHeader("Local routes") then diff --git a/data/modules/TradeShips/Events.lua b/data/modules/TradeShips/Events.lua index ff7295dad2b..be3188ba2de 100644 --- a/data/modules/TradeShips/Events.lua +++ b/data/modules/TradeShips/Events.lua @@ -58,7 +58,6 @@ local onEnterSystem = function (ship) elseif Core.ships[ship] ~= nil then local trader = Core.ships[ship] Core.log:add(ship, 'Entered '..Game.system.name..' from '..trader.from_path:GetStarSystem().name) - if trader.route then ship:AIDockWith(trader.route.to) Core.ships[ship]['starport'] = trader.route.to @@ -100,23 +99,6 @@ local onLeaveSystem = function (ship) end Event.Register("onLeaveSystem", onLeaveSystem) -local onFrameChanged = function (ship) - if not ship:isa("Ship") or Core.ships[ship] == nil then return end - local trader = Core.ships[ship] - Core.log:add(ship, "Entered frame " .. (ship.frameBody and ship.frameBody:GetLabel() or "unknown")) - - if trader.status == 'outbound' then - -- the cloud inherits the ship velocity and vector - ship:CancelAI() - if Trader.getSystemAndJump(ship) ~= 'OK' then - ship:AIDockWith(trader.starport) - trader['status'] = 'inbound' - trader.ts_error = 'cnt_jump_frame' - end - end -end -Event.Register("onFrameChanged", onFrameChanged) - local onShipDocked = function (ship, starport) if Core.ships[ship] == nil then return end local trader = Core.ships[ship] @@ -159,11 +141,10 @@ Event.Register("onShipDocked", onShipDocked) local onShipUndocked = function (ship, starport) if Core.ships[ship] == nil then return end - - -- fly to the limit of the starport frame - ship:AIFlyTo(starport) - - Core.ships[ship]['status'] = 'outbound' + local trader = Core.ships[ship] + ship:AIEnterLowOrbit(trader.starport:GetSystemBody().system:GetStars()[1].body) + Trader.assignTask(ship, Game.time + 10, 'hyperjumpAtDistance') + trader['status'] = 'outbound' end Event.Register("onShipUndocked", onShipUndocked) @@ -171,15 +152,9 @@ local onAICompleted = function (ship, ai_error) if Core.ships[ship] == nil then return end local trader = Core.ships[ship] if ai_error ~= 'NONE' then - Core.log:add(ship, 'AICompleted: Error: '..ai_error..' Status: '..trader.status) end - - if trader.status == 'outbound' then - if Trader.getSystemAndJump(ship) ~= 'OK' then - ship:AIDockWith(trader.starport) - trader['status'] = 'inbound' - trader.ts_error = 'cnt_jump_aicomp' - end - elseif trader.status == 'orbit' then + Core.log:add(ship, 'AICompleted: Error: '..ai_error..' Status: '..trader.status) + end + if trader.status == 'orbit' then if ai_error == 'NONE' then trader.ts_error = "wait_6h" Trader.assignTask(ship, Game.time + 21600, 'doRedock') @@ -258,7 +233,7 @@ local onShipHit = function (ship, attacker) elseif trader.starport and Engine.rand:Number(1) < trader.chance then local distance = ship:DistanceTo(trader.starport) if distance > Core.AU * (2 - trader.chance) then - if Trader.getSystemAndJump(ship) then + if Trader.getSystemAndJump(ship) == 'OK' then return else trader['no_jump'] = true diff --git a/data/modules/TradeShips/Flow.lua b/data/modules/TradeShips/Flow.lua index 0f4f7bd6938..3dcf2b60426 100644 --- a/data/modules/TradeShips/Flow.lua +++ b/data/modules/TradeShips/Flow.lua @@ -494,6 +494,47 @@ Flow.spawnInitialShips = function() return ships_in_space end +Flow.setPlayerAsTraderDocked = function() + local ship = Game.player + --if player is not a trader + if Core.ships[ship] then + print("Flow.setPlayerAsTraderDocked: player is already a trader") + return + end + --if player is currently docked + if ship.flightState ~= 'DOCKED' then + print("Flow.setPlayerAsTraderDocked: can't set player as docked trader when player is not currently docked") + return + end + local dockedStation = ship:GetDockedWith() + Core.ships[ship] = { ts_error = "OK", status = 'docked', starport = dockedStation, ship_name = Game.player.shipId } + Trader.assignTask(ship, Game.time + utils.deviation(Core.params.port_params[Core.ships[ship].starport].time * 3600, 0.8), 'doUndock') +end + +Flow.setPlayerAsTraderInbound = function() + local ship = Game.player + --if player is not a trader + if Core.ships[ship] then + print("Flow.setPlayerAsTraderInbound: player is already a trader") + return + end + -- Space.PutShipOnRoute will teleport player to star's surface when player is docked. We don't want that + if ship.flightState ~= 'FLYING' then + print("Flow.setPlayerAsTraderInbound: can't set player as inbound trader when player is not currently flying") + return + end + -- if there's any station in the system + local nearestStation = ship:FindNearestTo("SPACESTATION") + if not nearestStation then + print("Flow.setPlayerAsTraderInbound: no nearby station is found to set player as inbound trader") + return + end + Core.ships[ship] = { ts_error = "OK", status = 'inbound', starport = nearestStation, ship_name = Game.player.shipId } + Space.PutShipIntoOrbit(ship, Game.system:GetStars()[1].body) + Space.PutShipOnRoute(ship, Core.ships[ship].starport, Engine.rand:Number(0.0, 0.999))-- don't generate at the destination + ship:AIDockWith(Core.ships[ship].starport) +end + Flow.run = function() local ship_name = utils.chooseEqual(Core.params.ship_names) local cloud = utils.chooseEqual(Core.params.hyper_routes[ship_name]) diff --git a/data/modules/TradeShips/Trader.lua b/data/modules/TradeShips/Trader.lua index f1efea44372..8aa8438d8c0 100644 --- a/data/modules/TradeShips/Trader.lua +++ b/data/modules/TradeShips/Trader.lua @@ -200,10 +200,6 @@ Trader.getSystemAndJump = function (ship) if Core.ships[ship].starport then local body = Space.GetBody(Core.ships[ship].starport.path:GetSystemBody().parent.index) local port = Core.ships[ship].starport - -- boost away from the starport before jumping if it is too close - if (ship:DistanceTo(port) < 20000) then - ship:AIEnterLowOrbit(body) - end return jumpToSystem(ship, getSystem(ship)) end end @@ -275,6 +271,19 @@ Trader.removeFuel = function (ship, count) return removed end +Trader.checkDistBetweenStarport = function (ship) + local trader = Core.ships[ship] + if not trader then return nil end + local distance + if trader.starport.type == "STARPORT_ORBITAL" then + distance = ship:DistanceTo(trader.starport) + else + local stationsParent = trader.starport:GetSystemBody().parent.body + distance = ship:GetAltitudeRelTo(stationsParent) + end + return distance >= trader.hyperjumpDist +end + -- TRADER DEFERRED TASKS -- -- call the passed function in a specified time, checking whether we are still @@ -293,6 +302,36 @@ local trader_task = {} -- the task function prototype should be: -- function(ship) +trader_task.hyperjumpAtDistance = function(ship) + -- the player may have left the system + local trader = Core.ships[ship] + if not trader then return end + if trader.status == "outbound" and trader.ts_error ~= "HIT" then + -- if trader is not under attack and started to leave station + if trader.starport ~= nil then + -- if trader has not started to hyperjump + if trader.hyperjumpDist == nil then + trader.hyperjumpDist = Engine.rand:Integer(20000, 240000) + end + if Trader.checkDistBetweenStarport(ship) then + -- if distance is large enough attempt to hyperjump + local status = Trader.getSystemAndJump(ship) + if status ~= 'OK' then + ship:CancelAI() + ship:AIDockWith(trader.starport) + trader['status'] = 'inbound' + trader.ts_error = 'cnt_jump_aicomp' + end + trader.hyperjumpDist = nil + else + Trader.assignTask(ship, Game.time + 10, 'hyperjumpAtDistance') + end + end + else + trader.hyperjumpDist = nil + end +end + trader_task.doUndock = function(ship) -- the player may have left the system or the ship may have already undocked if ship:exists() and ship:GetDockedWith() then diff --git a/src/lua/LuaSpace.cpp b/src/lua/LuaSpace.cpp index 7072c66786d..7ae6ad067c2 100644 --- a/src/lua/LuaSpace.cpp +++ b/src/lua/LuaSpace.cpp @@ -18,6 +18,7 @@ #include "Space.h" #include "SpaceStation.h" #include "ship/PrecalcPath.h" +#include "EnumStrings.h" /* * Interface: Space @@ -96,6 +97,27 @@ static Body *_maybe_wrap_ship_with_cloud(Ship *ship, SystemPath *path, double du return cloud; } +// sb - central systembody, pos - absolute coordinates of given object +static vector3d _orbital_velocity_random_direction(const SystemBody* sb, const vector3d& pos) +{ + // If we got a zero mass of central body - there is no orbit + if (sb->GetMass() < 0.01) + return vector3d(0.0); + // calculating basis from radius - vector + vector3d k = pos.Normalized(); + vector3d i; + if (std::fabs(k.z) > 0.999999) // very vertical = z + i = vector3d(1.0, 0.0, 0.0); // second ort = x + else + i = k.Cross(vector3d(0.0, 0.0, 1.0)).Normalized(); + vector3d j = k.Cross(i); + // generating random 2d direction and putting it into basis + vector3d randomOrthoDirection = MathUtil::RandomPointOnCircle(1.0) * matrix3x3d::FromVectors(i, j, k).Transpose(); + // calculate the value of the orbital velocity + double orbitalVelocity = sqrt(G * sb->GetMass() / pos.Length()); + return randomOrthoDirection * orbitalVelocity; +} + /* * Function: SpawnShip * @@ -227,7 +249,6 @@ extern double MaxEffectRad(const Body *body, Propulsion *prop); * * experimental */ - static int l_space_put_ship_on_route(lua_State *l) { LUA_DEBUG_START(l); @@ -298,6 +319,62 @@ static int l_space_put_ship_on_route(lua_State *l) return 0; } +/* + * Method: PutShipIntoOrbit + * + * Puts ship into orbit of target planet, moon or star + * + * > Space.PutShipIntoOrbit(ship, target) + * + * Parameters: + * + * ship - a object to be moved + * + * target - the or to orbit + * + * Availability: + * + * October 2023 + * + * Status: + * + * experimental + */ +static int l_put_ship_into_orbit(lua_State* l) +{ + Ship *s = LuaObject::CheckFromLua(1); + Body *b = LuaObject::CheckFromLua(2); + const SystemBody* sbody = b->GetSystemBody(); + if (!sbody) + { + return luaL_error(l, "the target body doesn't have a system body"); + } + if (!sbody->IsPlanet() && !sbody->IsMoon() && sbody->GetSuperType() != SystemBodyType::SUPERTYPE_STAR) + { + return luaL_error(l, "the target body is not a planet, moon, or star"); + } + Ship::FlightState currentState = s->GetFlightState(); + if (currentState != Ship::FlightState::FLYING) + { + return luaL_error(l, "the ship is not in the \"FLYING\" state. Current state: \"%s\"", + EnumStrings::GetString("ShipFlightState", currentState)); + } + // calculations are borrowed from Space::GetHyperspaceExitParams + // calculate distance to primary body relative to body's mass and radius + const double max_orbit_vel = 100e3; + double dist = G * sbody->GetMass() / (max_orbit_vel * max_orbit_vel); + // ensure an absolute minimum and an absolute maximum distance + // the minimum distance from the center of the body should not be less than the radius of the body + // use physical radius, because radius of sbody can be a lot less than physical radius + double radius = b->GetPhysRadius(); + dist = Clamp(dist, radius * 1.1, std::max(radius * 1.1, 100 * AU)); + vector3d pos{ MathUtil::RandomPointOnSphere(dist) }; + s->SetFrame(b->GetFrame()); + s->SetPosition(pos); + s->SetVelocity(_orbital_velocity_random_direction(sbody, s->GetPosition())); + return 0; +} + /* * Function: SpawnShipNear * @@ -698,27 +775,6 @@ static int l_space_spawn_ship_landed_near(lua_State *l) return 1; } -// sb - central systembody, pos - absolute coordinates of given object -static vector3d _orbital_velocity_random_direction(const SystemBody *sb, const vector3d &pos) -{ - // If we got a zero mass of central body - there is no orbit - if (sb->GetMass() < 0.01) - return vector3d(0.0); - // calculating basis from radius - vector - vector3d k = pos.Normalized(); - vector3d i; - if (std::fabs(k.z) > 0.999999) // very vertical = z - i = vector3d(1.0, 0.0, 0.0); // second ort = x - else - i = k.Cross(vector3d(0.0, 0.0, 1.0)).Normalized(); - vector3d j = k.Cross(i); - // generating random 2d direction and putting it into basis - vector3d randomOrthoDirection = MathUtil::RandomPointOnCircle(1.0) * matrix3x3d::FromVectors(i, j, k).Transpose(); - // calculate the value of the orbital velocity - double orbitalVelocity = sqrt(G * sb->GetMass() / pos.Length()); - return randomOrthoDirection * orbitalVelocity; -} - /* * Function: SpawnCargoNear * @@ -1033,6 +1089,7 @@ void LuaSpace::Register() { "SpawnCargoNear", l_space_spawn_cargo_near }, { "SpawnShipOrbit", l_space_spawn_ship_orbit }, { "PutShipOnRoute", l_space_put_ship_on_route }, + { "PutShipIntoOrbit", l_put_ship_into_orbit }, { "GetBody", l_space_get_body }, { "GetBodies", l_space_get_bodies },