diff --git a/data/modules/TradeShips/Debug.lua b/data/modules/TradeShips/Debug.lua index 300a4465bc1..cb377ecdd75 100644 --- a/data/modules/TradeShips/Debug.lua +++ b/data/modules/TradeShips/Debug.lua @@ -55,7 +55,7 @@ local statuses = { } debugView.registerTab('debug-trade-ships', function() - if not Core.ships and not Core.params then return end + if not Core.ships and not Core.params or not Game.system then return end if not ui.beginTabItem("Tradeships") then return end local function property(key, value) diff --git a/data/modules/TradeShips/Events.lua b/data/modules/TradeShips/Events.lua index c2c3bdea6d5..6f26f79e3e5 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 8ff66460412..9dd1f4cad55 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 b5c1696d07b..36cfb331473 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 792d1242aef..80aef3cd491 100644 --- a/src/lua/LuaSpace.cpp +++ b/src/lua/LuaSpace.cpp @@ -3,11 +3,12 @@ #include "LuaSpace.h" #include "Body.h" -#include "LuaBody.h" #include "CargoBody.h" +#include "EnumStrings.h" #include "Frame.h" #include "Game.h" #include "HyperspaceCloud.h" +#include "LuaBody.h" #include "LuaManager.h" #include "LuaObject.h" #include "LuaUtils.h" @@ -99,6 +100,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 * @@ -230,7 +252,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); @@ -272,7 +293,7 @@ static int l_space_put_ship_on_route(lua_State *l) // target is above the effective radius of obstructor - rotate the ship's position // around the target position, so that the obstructor's "effective radius" does not cross the path // direction obstructor -> target - const vector3d z = targpos/targAlt; + const vector3d z = targpos / targAlt; // the axis around which the position of the ship will rotate const vector3d y = z.Cross(shippos).NormalizedSafe(); // just the third axis of this basis @@ -292,7 +313,7 @@ static int l_space_put_ship_on_route(lua_State *l) ship->SetPosition(safe2 + targpos); } else { // target below the effective radius of obstructor. Position the ship direct above the target - ship->SetPosition(targpos + targpos/targAlt * targdist); + ship->SetPosition(targpos + targpos / targAlt * targdist); } // update velocity direction ship->SetVelocity((targpos - ship->GetPosition()).Normalized() * pp.getVel() + targetbody->GetVelocityRelTo(ship->GetFrame())); @@ -301,6 +322,62 @@ static int l_space_put_ship_on_route(lua_State *l) return 0; } +/* + * Method: PutShipIntoOrbit + * + * Puts ship into orbit of target body with SystemBody. + * + * > 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->GetMass()) { + return luaL_error(l, "the target body has zero mass"); + } + if (!b->GetPhysRadius()) { + return luaL_error(l, "the target body has a zero physical radius"); + } + 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 * @@ -701,27 +778,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 * @@ -1118,6 +1174,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 }, { "GetNumBodies", l_space_get_num_bodies },