From 7e023ca2b8a5e7bec45f99d9d6c7f87da396879e Mon Sep 17 00:00:00 2001 From: Max5377 <69468517+Max5377@users.noreply.github.com> Date: Fri, 13 Oct 2023 22:34:20 +0300 Subject: [PATCH] TradeShips hyperjump code change TradeShips now will check for distance(altitude) between orbital station(station's parent body) before making the jump. This should address accumulating hyperclouds around stations and rare illegal jumps. The algorithm will be: For orbital stations: 1. Undock; 2. Fly to limits of current orbital station (AIFlyTo); 3. OnAICompleted event fires, tell ship to fly to current system's star; 4. Assign task to check distance between current orbital station and ship, if distance is large enough, make the jump. For ground stations; 1. Undock; 2. Tell ship to fly to current system's star; 3. Assign task to check altitude between current orbital station's parent and ship, if altitude is high enough, make the jump. Additionally: 1. Fixed lua error when hyperjumping with TradeShips debug tab opened; 2. Added function "PutShipIntoOrbit" in "LuaSpace.cpp" to put ship into orbit of the target body; 3. Added two new methods "setPlayerAsTraderDocked" and "setPlayerAsTraderInbound" in "Flow.lua". Co-Authored-By: Gliese852 <18342621+gliese852@users.noreply.github.com> --- data/modules/TradeShips/Debug.lua | 73 +++++++++++---------- data/modules/TradeShips/Events.lua | 41 +++--------- data/modules/TradeShips/Flow.lua | 41 ++++++++++++ data/modules/TradeShips/Trader.lua | 47 ++++++++++++-- src/lua/LuaSpace.cpp | 101 ++++++++++++++++++++++------- 5 files changed, 210 insertions(+), 93 deletions(-) diff --git a/data/modules/TradeShips/Debug.lua b/data/modules/TradeShips/Debug.lua index 300a4465bc1..4836392eb7e 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.GetNumBodies()) 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 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..26d086ccbbd 100644 --- a/src/lua/LuaSpace.cpp +++ b/src/lua/LuaSpace.cpp @@ -21,6 +21,7 @@ #include "SpaceStation.h" #include "profiler/Profiler.h" #include "ship/PrecalcPath.h" +#include "EnumStrings.h" /* * Interface: Space @@ -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); @@ -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() && !b->GetPhysRadius()) + { + return luaL_error(l, "the target body has zero mass and 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 },