1057 lines
30 KiB
C++
1057 lines
30 KiB
C++
// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#include "LuaSpace.h"
|
|
#include "CargoBody.h"
|
|
#include "Frame.h"
|
|
#include "Game.h"
|
|
#include "HyperspaceCloud.h"
|
|
#include "LuaManager.h"
|
|
#include "LuaObject.h"
|
|
#include "LuaUtils.h"
|
|
#include "LuaVector.h"
|
|
#include "MathUtil.h"
|
|
#include "Pi.h"
|
|
#include "Planet.h"
|
|
#include "Player.h"
|
|
#include "Ship.h"
|
|
#include "Space.h"
|
|
#include "SpaceStation.h"
|
|
#include "ship/PrecalcPath.h"
|
|
|
|
/*
|
|
* Interface: Space
|
|
*
|
|
* Various functions to create and find objects in the current physics space.
|
|
*/
|
|
|
|
static void _unpack_hyperspace_args(lua_State *l, int index, SystemPath *&path, double &due)
|
|
{
|
|
if (lua_isnone(l, index)) return;
|
|
|
|
luaL_checktype(l, index, LUA_TTABLE);
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
lua_pushinteger(l, 1);
|
|
lua_gettable(l, index);
|
|
if (!(path = LuaObject<SystemPath>::GetFromLua(-1)))
|
|
luaL_error(l, "bad value for hyperspace path at position 1 (SystemPath expected, got %s)", luaL_typename(l, -1));
|
|
lua_pop(l, 1);
|
|
|
|
lua_pushinteger(l, 2);
|
|
lua_gettable(l, index);
|
|
if (!(lua_isnumber(l, -1)))
|
|
luaL_error(l, "bad value for hyperspace exit time at position 2 (%s expected, got %s)", lua_typename(l, LUA_TNUMBER), luaL_typename(l, -1));
|
|
due = lua_tonumber(l, -1);
|
|
if (due < 0)
|
|
luaL_error(l, "bad value for hyperspace exit time at position 2 (must be >= 0)");
|
|
lua_pop(l, 1);
|
|
|
|
LUA_DEBUG_END(l, 0);
|
|
}
|
|
|
|
// overload for the case when we explicitly want to specify the destination star
|
|
static void _unpack_hyperspace_args(lua_State *l, int index, SystemPath *&source, SystemPath *&dest, double &due)
|
|
{
|
|
if (lua_isnone(l, index)) return;
|
|
|
|
luaL_checktype(l, index, LUA_TTABLE);
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
lua_pushinteger(l, 1);
|
|
lua_gettable(l, index);
|
|
if (!(source = LuaObject<SystemPath>::GetFromLua(-1)))
|
|
luaL_error(l, "bad value for hyperspace source path at position 1 (SystemPath expected, got %s)", luaL_typename(l, -1));
|
|
lua_pop(l, 1);
|
|
|
|
lua_pushinteger(l, 2);
|
|
lua_gettable(l, index);
|
|
if (!(dest = LuaObject<SystemPath>::GetFromLua(-1)))
|
|
luaL_error(l, "bad value for hyperspace dest path at position 2 (SystemPath expected, got %s)", luaL_typename(l, -1));
|
|
lua_pop(l, 1);
|
|
|
|
lua_pushinteger(l, 3);
|
|
lua_gettable(l, index);
|
|
if (!(lua_isnumber(l, -1)))
|
|
luaL_error(l, "bad value for hyperspace exit time at position 3 (%s expected, got %s)", lua_typename(l, LUA_TNUMBER), luaL_typename(l, -1));
|
|
due = lua_tonumber(l, -1);
|
|
if (due < 0)
|
|
luaL_error(l, "bad value for hyperspace exit time at position 3 (must be >= 0)");
|
|
lua_pop(l, 1);
|
|
|
|
LUA_DEBUG_END(l, 0);
|
|
}
|
|
|
|
static Body *_maybe_wrap_ship_with_cloud(Ship *ship, SystemPath *path, double due)
|
|
{
|
|
if (!path) return ship;
|
|
if (due <= 0) return ship;
|
|
|
|
HyperspaceCloud *cloud = new HyperspaceCloud(ship, due, true);
|
|
ship->SetHyperspaceDest(path);
|
|
ship->SetFlightState(Ship::HYPERSPACE);
|
|
|
|
return cloud;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShip
|
|
*
|
|
* Create a ship and place it somewhere in space.
|
|
*
|
|
* > ship = Space.SpawnShip(type, min, max, hyperspace)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* min - minimum distance from the system centre (usually the primary star)
|
|
* to place the ship, in AU
|
|
*
|
|
* max - maximum distance to place the ship
|
|
*
|
|
* hyperspace - optional table containing hyperspace entry information. If
|
|
* this is provided the ship will not spawn directly. Instead,
|
|
* a hyperspace cloud will be created that the ship will exit
|
|
* from. The table contains two elements, a <SystemPath> for
|
|
* the system the ship is travelling from, and the due
|
|
* date/time that the ship should emerge from the cloud.
|
|
* In this case min and max arguments are ignored.
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship
|
|
*
|
|
* Examples:
|
|
*
|
|
* > -- spawn a ship 5-6AU from the system centre
|
|
* > local ship = Space.SpawnShip("eagle_lrf", 5, 6)
|
|
*
|
|
* > -- spawn a ship in the ~11AU hyperspace area and make it appear that it
|
|
* > -- came from Sol and will arrive in ten minutes
|
|
* > local ship = Space.SpawnShip(
|
|
* > "flowerfairy", 9, 11,
|
|
* > { SystemPath:New(0,0,0), Game.time + 600 }
|
|
* > )
|
|
*
|
|
* Availability:
|
|
*
|
|
* alpha 10
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_ship(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
float min_dist = luaL_checknumber(l, 2);
|
|
float max_dist = luaL_checknumber(l, 3);
|
|
|
|
SystemPath *source = 0;
|
|
SystemPath *dest = 0;
|
|
double due = -1;
|
|
_unpack_hyperspace_args(l, 4, source, dest, due);
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
Body *thing = _maybe_wrap_ship_with_cloud(ship, source, due);
|
|
|
|
// XXX protect against spawning inside the body
|
|
thing->SetFrame(Pi::game->GetSpace()->GetRootFrame());
|
|
if (!source)
|
|
thing->SetPosition(MathUtil::RandomPointOnSphere(min_dist, max_dist) * AU);
|
|
else
|
|
// XXX broken. this is ignoring min_dist & max_dist. otoh, what's the
|
|
// correct behaviour given there's now a fixed hyperspace exit point?
|
|
thing->SetPosition(Pi::game->GetSpace()->GetHyperspaceExitPoint(*source, *dest));
|
|
thing->SetVelocity(vector3d(0, 0, 0));
|
|
Pi::game->GetSpace()->AddBody(thing);
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
// functions from ShipAiCmd.cpp
|
|
extern int CheckCollision(DynamicBody *dBody, const vector3d &pathdir, double pathdist, const vector3d &tpos, double endvel, double r);
|
|
extern double MaxEffectRad(const Body *body, Propulsion *prop);
|
|
|
|
/*
|
|
* Function: PutShipOnRoute
|
|
*
|
|
* The ship rearranged from its current position to a given body in space, for a
|
|
* given part of the path, as if it were flying in a straight line, consuming
|
|
* fuel and changing speed. the ship's current speed is ignored and is
|
|
* considered to be equal to target's.
|
|
* This function changes the coordinates, the mass of fuel in the tank, and the
|
|
* speed of the given ship.
|
|
*
|
|
* > Space.PutShipOnRoute(ship, targetbody, t_ratio)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ship - a <Ship> object to be moved
|
|
*
|
|
* targetbody - the <Body> - route goal
|
|
*
|
|
* t_ratio - route completion rate, by time: 0.0 (begin) ... 1.0 (end)
|
|
*
|
|
* Return:
|
|
*
|
|
* nothing
|
|
*
|
|
* Examples:
|
|
*
|
|
* > -- move a ship to the middle of the route (by time)
|
|
* > Space.PutShipOnRoute(ship, some_staport, 0.5)
|
|
*
|
|
* Availability:
|
|
*
|
|
* 2020
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
|
|
static int l_space_put_ship_on_route(lua_State *l)
|
|
{
|
|
LUA_DEBUG_START(l);
|
|
Ship *ship = LuaObject<Ship>::CheckFromLua(1);
|
|
const Body *targetbody = LuaObject<Body>::CheckFromLua(2);
|
|
const double t_ratio = LuaPull<double>(l, 3);
|
|
const ShipType *st = ship->GetShipType();
|
|
const shipstats_t ss = ship->GetStats();
|
|
const vector3d route = targetbody->GetPositionRelTo(ship->GetFrame()) - ship->GetPosition();
|
|
PrecalcPath pp(
|
|
route.Length(), // distance
|
|
0.0, // velocity at start
|
|
st->effectiveExhaustVelocity,
|
|
st->linThrust[THRUSTER_FORWARD],
|
|
st->linAccelerationCap[THRUSTER_FORWARD],
|
|
1000 * (ss.static_mass + ss.fuel_tank_mass_left), // 100% mass of the ship
|
|
1000 * ss.fuel_tank_mass_left * 0.8, // multipied to 0.8 have fuel reserve
|
|
0.85); // braking margin
|
|
// determine the place of the ship on the route
|
|
pp.setTRatio(t_ratio);
|
|
ship->SetPosition(ship->GetPosition() + route.Normalized() * pp.getDist());
|
|
ship->SetVelocity(route.Normalized() * pp.getVel() + targetbody->GetVelocityRelTo(ship->GetFrame()));
|
|
ship->SetFuel((0.001 * pp.getMass() - ss.static_mass) / st->fuelTankMass);
|
|
|
|
ship->UpdateFrame();
|
|
Frame *shipFrame = Frame::GetFrame(ship->GetFrame());
|
|
Frame *targFrame = Frame::GetFrame(targetbody->GetFrame());
|
|
|
|
// check for collision at spawn position
|
|
const vector3d shippos = ship->GetPosition();
|
|
const vector3d targpos = targetbody->GetPositionRelTo(ship->GetFrame());
|
|
const vector3d relpos = targpos - shippos;
|
|
const vector3d reldir = relpos.NormalizedSafe();
|
|
const double targdist = relpos.Length();
|
|
Body *body = shipFrame->GetBody();
|
|
const double erad = MaxEffectRad(body, ship->GetPropulsion());
|
|
const int coll = CheckCollision(ship, reldir, targdist, targpos, 0, erad);
|
|
if (coll) {
|
|
// need to correct positon, to avoid collision
|
|
if (Frame::GetFrame(shipFrame->GetNonRotFrame()) != Frame::GetFrame(targFrame->GetNonRotFrame()) && targpos.Length() > erad) {
|
|
// the ship is not in the target's frame or 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.Normalized();
|
|
// 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
|
|
const vector3d x = y.Cross(z);
|
|
// this is the basis in which the position of the ship will rotate
|
|
const matrix3x3d corrCS = matrix3x3d::FromVectors(x, y, z);
|
|
const double len = targpos.Length();
|
|
// two possible positions of the ship, when flying around the obstructor to the right or left
|
|
// rotate (in the given basis) the direction from the target to the obstructor, so that it passes tangentially to the obstructor
|
|
const vector3d safe1 = corrCS.Transpose() * (matrix3x3d::RotateY(+asin(erad / len)) * corrCS * -targpos).Normalized() * targdist;
|
|
const vector3d safe2 = corrCS.Transpose() * (matrix3x3d::RotateY(-asin(erad / len)) * corrCS * -targpos).Normalized() * targdist;
|
|
// choose the one that is closer to the current position oh the ship
|
|
if ((safe1 + relpos).Length() < (safe2 + relpos).Length())
|
|
ship->SetPosition(safe1 + targpos);
|
|
else
|
|
ship->SetPosition(safe2 + targpos);
|
|
} else {
|
|
// we are in target's frame, and target below the effective radius of planet. Position the ship direct above the target
|
|
ship->SetPosition(targpos + targpos.Normalized() * targdist);
|
|
}
|
|
// update velocity direction
|
|
ship->SetVelocity((targpos - ship->GetPosition()).Normalized() * pp.getVel() + targetbody->GetVelocityRelTo(ship->GetFrame()));
|
|
}
|
|
LUA_DEBUG_END(l, 1);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShipNear
|
|
*
|
|
* Create a ship and place it in space near the given <Body>.
|
|
*
|
|
* > ship = Space.SpawnShipNear(type, body, min, max, hyperspace)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* body - the <Body> near which the ship should be spawned
|
|
*
|
|
* min - minimum distance from the body to place the ship, in Km
|
|
*
|
|
* max - maximum distance to place the ship
|
|
*
|
|
* hyperspace - optional table containing hyperspace entry information. If
|
|
* this is provided the ship will not spawn directly. Instead,
|
|
* a hyperspace cloud will be created that the ship will exit
|
|
* from. The table contains two elements, a <SystemPath> for
|
|
* the system the ship is travelling from, and the due
|
|
* date/time that the ship should emerge from the cloud.
|
|
*
|
|
* velocity - vector containing the velocity to give to the ship
|
|
*
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship
|
|
*
|
|
* Examples:
|
|
*
|
|
* > -- spawn a ship 10km from the player
|
|
* > local ship = Space.SpawnShipNear("viper_police_craft", Game.player, 10, 10)
|
|
*
|
|
* > -- spawn a ship 10km from the player with the players velocity
|
|
* > local ship = Space.SpawnShipNear("viper_police_craft", Game.player, 10, 10, nil, Game.player:velocity)
|
|
*
|
|
* Availability:
|
|
*
|
|
* alpha 10
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_ship_near(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
Body *nearbody = LuaObject<Body>::CheckFromLua(2);
|
|
float min_dist = luaL_checknumber(l, 3);
|
|
float max_dist = luaL_checknumber(l, 4);
|
|
|
|
SystemPath *path = 0;
|
|
double due = -1;
|
|
_unpack_hyperspace_args(l, 5, path, due);
|
|
|
|
vector3d newVelocity(nearbody->GetVelocity());
|
|
if (!lua_isnone(l, 6))
|
|
newVelocity = *LuaVector::CheckFromLua(l, 6); // If we have a 6th argument, it better be a vector, otherwise ERROR!!! Hence Check and not Get
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
Body *thing = _maybe_wrap_ship_with_cloud(ship, path, due);
|
|
|
|
// XXX protect against spawning inside the body
|
|
FrameId newframeId = nearbody->GetFrame();
|
|
Frame *newframe = Frame::GetFrame(newframeId);
|
|
|
|
const vector3d newPosition = (MathUtil::RandomPointOnSphere(min_dist, max_dist) * 1000.0) + nearbody->GetPosition();
|
|
|
|
// If the frame is rotating and the chosen position is too far, use non-rotating parent.
|
|
// Otherwise the ship will be given a massive initial velocity when it's bumped out of the
|
|
// rotating frame in the next update
|
|
if (newframe->IsRotFrame() && newframe->GetRadius() < newPosition.Length()) {
|
|
assert(newframe->GetParent());
|
|
newframe = Frame::GetFrame(newframe->GetParent());
|
|
}
|
|
|
|
thing->SetFrame(newframe->GetId());
|
|
thing->SetPosition(newPosition);
|
|
thing->SetVelocity(newVelocity);
|
|
Pi::game->GetSpace()->AddBody(thing);
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShipDocked
|
|
*
|
|
* Create a ship and place it inside the given <SpaceStation>.
|
|
*
|
|
* > ship = Space.SpawnShipDocked(type, station)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* station - the <SpaceStation> to place the ship inside
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship, or nil if there was no space
|
|
* inside the station
|
|
*
|
|
* Availability:
|
|
*
|
|
* alpha 10
|
|
*
|
|
* Status:
|
|
*
|
|
* stable
|
|
*/
|
|
static int l_space_spawn_ship_docked(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
SpaceStation *station = LuaObject<SpaceStation>::CheckFromLua(2);
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
int port = station->GetFreeDockingPort(ship); // pass in the ship to get a port we fit into
|
|
if (port < 0) {
|
|
delete ship;
|
|
return 0;
|
|
}
|
|
|
|
ship->SetFrame(station->GetFrame());
|
|
Pi::game->GetSpace()->AddBody(ship);
|
|
ship->SetDockedWith(station, port);
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShipParked
|
|
*
|
|
* Create a ship and place it in one of the given <SpaceStation's> parking spots.
|
|
*
|
|
* > ship = Space.SpawnShipParked(type, station)
|
|
*
|
|
* For orbital stations the parking spots are some distance from the door, out
|
|
* of the path of ships entering and leaving the station. For group stations
|
|
* the parking spots are directly above the station, usually some distance
|
|
* away.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* station - the <SpaceStation> to place the near
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship, or nil if there was no space
|
|
* inside the station
|
|
* Availability:
|
|
*
|
|
* alpha 10
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_ship_parked(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
SpaceStation *station = LuaObject<SpaceStation>::CheckFromLua(2);
|
|
|
|
int slot;
|
|
if (!station->AllocateStaticSlot(slot))
|
|
return 0;
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
const double parkDist = station->GetStationType()->ParkingDistance() - ship->GetPhysRadius(); // park inside parking radius
|
|
const double parkOffset = (0.5 * station->GetStationType()->ParkingGapSize()) + ship->GetPhysRadius(); // but outside the docking gap
|
|
|
|
double xpos = (slot == 0 || slot == 3) ? -parkOffset : parkOffset;
|
|
double zpos = (slot == 0 || slot == 1) ? -parkOffset : parkOffset;
|
|
const vector3d parkPos = station->GetPosition() + station->GetOrient() * vector3d(xpos, parkDist, zpos);
|
|
|
|
// orbital stations have Y as axis of rotation
|
|
const matrix3x3d rot = matrix3x3d::RotateX(M_PI / 2) * station->GetOrient();
|
|
|
|
ship->SetFrame(station->GetFrame());
|
|
ship->SetVelocity(vector3d(0.0));
|
|
ship->SetPosition(parkPos);
|
|
ship->SetOrient(rot);
|
|
|
|
Pi::game->GetSpace()->AddBody(ship);
|
|
|
|
ship->AIHoldPosition();
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShipLanded
|
|
*
|
|
* Create a ship and place it landed on the given <Body>.
|
|
*
|
|
* > ship = Space.SpawnShipLanded(type, body, lat, long)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* body - the <Body> near which the ship should be spawned
|
|
*
|
|
* lat - latitude in radians (like in custom body defintions)
|
|
*
|
|
* long - longitude in radians (like in custom body definitions)
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship
|
|
*
|
|
* Example:
|
|
*
|
|
* > -- spawn 16km from L.A. when in Sol system
|
|
* > local earth = Space.GetBody(3)
|
|
* > local ship = Space.SpawnShipLanded("viper_police", earth, math.deg2rad(34.06473923), math.deg2rad(-118.1591568))
|
|
*
|
|
* Availability:
|
|
*
|
|
* July 2013
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_ship_landed(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
Planet *planet = LuaObject<Planet>::CheckFromLua(2);
|
|
if (planet->GetSystemBody()->GetSuperType() != SystemBody::SUPERTYPE_ROCKY_PLANET)
|
|
luaL_error(l, "Body is not a rocky planet");
|
|
float latitude = luaL_checknumber(l, 3);
|
|
float longitude = luaL_checknumber(l, 4);
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
Pi::game->GetSpace()->AddBody(ship);
|
|
ship->SetLandedOn(planet, latitude, longitude);
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShipLandedNear
|
|
*
|
|
* Create a ship and place it on the surface near the given <Body>.
|
|
*
|
|
* > ship = Space.SpawnShipLandedNear(type, body, min, max)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* body - the <Body> near which the ship should be spawned. It must be on the ground or close to it,
|
|
* i.e. it must be in the rotating frame of the planetary body.
|
|
*
|
|
* min - minimum distance from the surface point below the body to place the ship, in Km
|
|
*
|
|
* max - maximum distance to place the ship
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship
|
|
*
|
|
* Example:
|
|
*
|
|
* > -- spawn a ship 10km from the player
|
|
* > local ship = Space.SpawnShipLandedNear("viper_police", Game.player, 10, 10)
|
|
*
|
|
* Availability:
|
|
*
|
|
* July 2013
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_ship_landed_near(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
Body *nearbody = LuaObject<Body>::CheckFromLua(2);
|
|
const float min_dist = luaL_checknumber(l, 3);
|
|
const float max_dist = luaL_checknumber(l, 4);
|
|
if (min_dist > max_dist)
|
|
luaL_error(l, "min_dist must not be larger than max_dist");
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
// XXX protect against spawning inside the body
|
|
Frame *newframe = Frame::GetFrame(Frame::GetFrame(nearbody->GetFrame())->GetRotFrame());
|
|
if (!newframe->IsRotFrame())
|
|
luaL_error(l, "Body must be in rotating frame");
|
|
SystemBody *sbody = newframe->GetSystemBody();
|
|
if (sbody->GetSuperType() != SystemBody::SUPERTYPE_ROCKY_PLANET)
|
|
luaL_error(l, "Body is not on a rocky planet");
|
|
if (max_dist > sbody->GetRadius())
|
|
luaL_error(l, "max_dist too large for planet radius");
|
|
// We assume that max_dist is much smaller than the planet radius, i.e. that our area is reasonably flat
|
|
// So, we
|
|
const vector3d up = nearbody->GetPosition().Normalized();
|
|
vector3d x;
|
|
vector3d y;
|
|
// Calculate a orthonormal basis for a horizontal plane. For numerical reasons we do that determining the smallest
|
|
// coordinate and take the cross product with (1,0,0), (0,1,0) or (0,0,1) respectively to calculate the first vector.
|
|
// The second vector is just the cross product of the up-vector and out first vector.
|
|
if (up.x <= up.y && up.x <= up.z) {
|
|
x = vector3d(0.0, up.z, -up.y).Normalized();
|
|
y = vector3d(-up.y * up.y - up.z * up.z, up.x * up.y, up.x * up.z).Normalized();
|
|
} else if (up.y <= up.x && up.y <= up.z) {
|
|
x = vector3d(-up.z, 0.0, up.x).Normalized();
|
|
y = vector3d(up.x * up.y, -up.x * up.x - up.z * up.z, up.y * up.z).Normalized();
|
|
} else {
|
|
x = vector3d(up.y, -up.x, 0.0).Normalized();
|
|
y = vector3d(up.x * up.z, up.y * up.z, -up.x * up.x - up.y * up.y).Normalized();
|
|
}
|
|
Planet *planet = static_cast<Planet *>(newframe->GetBody());
|
|
const double radius = planet->GetSystemBody()->GetRadius();
|
|
const vector3d planar = MathUtil::RandomPointInCircle(min_dist * 1000.0, max_dist * 1000.0);
|
|
vector3d pos = (radius * up + x * planar.x + y * planar.y).Normalized();
|
|
float latitude = atan2(pos.y, sqrt(pos.x * pos.x + pos.z * pos.z));
|
|
float longitude = atan2(pos.x, pos.z);
|
|
|
|
Pi::game->GetSpace()->AddBody(ship);
|
|
ship->SetLandedOn(planet, latitude, longitude);
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
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
|
|
*
|
|
* Create a cargo container and place near the given <Body>
|
|
*
|
|
* > body = Space.SpawnCargoNear(item, body, min, max, lifetime)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* item - the item to put into the container
|
|
*
|
|
* body - the <Body> near which the container should be spawned
|
|
*
|
|
* min - minimum distance from the body to place the container, in m
|
|
*
|
|
* max - maximum distance to place the container
|
|
*
|
|
* lifetime - optional time in seconds until self destruct, default is 24h
|
|
*
|
|
* Return:
|
|
*
|
|
* body - the <Body> object for the requested container
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_cargo_near(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
CargoBody *c_body;
|
|
const char *model;
|
|
|
|
lua_getfield(l, 1, "model_name");
|
|
if (lua_isstring(l, -1))
|
|
model = lua_tostring(l, -1);
|
|
else
|
|
model = "cargo";
|
|
|
|
if (lua_gettop(l) >= 5) {
|
|
float lifetime = lua_tonumber(l, 5);
|
|
c_body = new CargoBody(model, LuaRef(l, 1), lifetime);
|
|
} else {
|
|
c_body = new CargoBody(model, LuaRef(l, 1));
|
|
}
|
|
Body *nearbody = LuaObject<Body>::CheckFromLua(2);
|
|
float min_dist = luaL_checknumber(l, 3);
|
|
float max_dist = luaL_checknumber(l, 4);
|
|
if (min_dist > max_dist)
|
|
luaL_error(l, "min_dist must not be larger than max_dist");
|
|
|
|
FrameId frameId = nearbody->GetFrame();
|
|
Frame *frame = Frame::GetFrame(frameId);
|
|
// if the frame is rotating, use non-rotating parent
|
|
if (frame->IsRotFrame()) {
|
|
assert(frame->GetParent());
|
|
frame = Frame::GetFrame(frame->GetParent());
|
|
frameId = frame->GetId();
|
|
}
|
|
c_body->SetFrame(frameId);
|
|
c_body->SetPosition(MathUtil::RandomPointOnSphere(min_dist, max_dist) + nearbody->GetPosition());
|
|
c_body->SetVelocity(_orbital_velocity_random_direction(frame->GetSystemBody(), c_body->GetPosition()));
|
|
Pi::game->GetSpace()->AddBody(c_body);
|
|
|
|
LuaObject<Body>::PushToLua(c_body);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: SpawnShipOrbit
|
|
*
|
|
* Create a ship and place it in orbit near the given <Body>.
|
|
*
|
|
* > ship = Space.SpawnShip(type, body, min, max)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* type - the name of the ship
|
|
*
|
|
* body - the <Body> near which the ship should be spawned
|
|
*
|
|
* min - minimum distance from the body to place the ship, in m
|
|
*
|
|
* max - maximum distance to place the ship
|
|
*
|
|
*
|
|
* Return:
|
|
*
|
|
* ship - a <Ship> object for the new ship
|
|
*
|
|
* Status:
|
|
*
|
|
* experimental
|
|
*/
|
|
static int l_space_spawn_ship_orbit(lua_State *l)
|
|
{
|
|
if (!Pi::game)
|
|
luaL_error(l, "Game is not started");
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
const char *type = luaL_checkstring(l, 1);
|
|
if (!ShipType::Get(type))
|
|
luaL_error(l, "Unknown ship type '%s'", type);
|
|
|
|
Body *nearbody = LuaObject<Body>::CheckFromLua(2);
|
|
float min_dist = luaL_checknumber(l, 3);
|
|
float max_dist = luaL_checknumber(l, 4);
|
|
if (min_dist > max_dist)
|
|
luaL_error(l, "min_dist must not be larger than max_dist");
|
|
|
|
Ship *ship = new Ship(type);
|
|
assert(ship);
|
|
|
|
FrameId frameId = nearbody->GetFrame();
|
|
Frame *frame = Frame::GetFrame(frameId);
|
|
// if the frame is rotating, use non-rotating parent
|
|
if (frame->IsRotFrame()) {
|
|
assert(frame->GetParent());
|
|
frame = Frame::GetFrame(frame->GetParent());
|
|
frameId = frame->GetId();
|
|
}
|
|
ship->SetFrame(frameId);
|
|
ship->SetPosition(MathUtil::RandomPointOnSphere(min_dist, max_dist) + nearbody->GetPosition());
|
|
ship->SetVelocity(_orbital_velocity_random_direction(frame->GetSystemBody(), ship->GetPosition()));
|
|
Pi::game->GetSpace()->AddBody(ship);
|
|
|
|
LuaObject<Ship>::PushToLua(ship);
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: GetBody
|
|
*
|
|
* Get the <Body> with the specificed body index.
|
|
*
|
|
* > body = Space.GetBody(index)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* index - the body index
|
|
*
|
|
* Return:
|
|
*
|
|
* body - the <Body> object for the requested body, or nil if no such body
|
|
* exists
|
|
*
|
|
* Availability:
|
|
*
|
|
* alpha 10
|
|
*
|
|
* Status:
|
|
*
|
|
* stable
|
|
*/
|
|
static int l_space_get_body(lua_State *l)
|
|
{
|
|
if (!Pi::game) {
|
|
luaL_error(l, "Game is not started");
|
|
return 0;
|
|
}
|
|
|
|
int id = luaL_checkinteger(l, 1);
|
|
|
|
SystemPath path = Pi::game->GetSpace()->GetStarSystem()->GetPath();
|
|
path.bodyIndex = id;
|
|
|
|
Body *b = Pi::game->GetSpace()->FindBodyForPath(&path);
|
|
if (!b) return 0;
|
|
|
|
LuaObject<Body>::PushToLua(b);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Function: GetBodies
|
|
*
|
|
* Get all the <Body> objects that match the specified filter
|
|
*
|
|
* bodies = Space.GetBodies(filter)
|
|
*
|
|
* Parameters:
|
|
*
|
|
* filter - an option function. If specificed the function will be called
|
|
* once for each body with the <Body> object as the only parameter.
|
|
* If the filter function returns true then the <Body> will be
|
|
* included in the array returned by <GetBodies>, otherwise it will
|
|
* be omitted. If no filter function is specified then all bodies
|
|
* are returned.
|
|
*
|
|
* Return:
|
|
*
|
|
* bodies - an array containing zero or more <Body> objects that matched the
|
|
* filter
|
|
*
|
|
* Example:
|
|
*
|
|
* > -- get all the ground-based stations
|
|
* > local stations = Space.GetBodies(function (body)
|
|
* > return body.type == "STARPORT_SURFACE"
|
|
* > end)
|
|
*
|
|
* Availability:
|
|
*
|
|
* alpha 10
|
|
*
|
|
* Status:
|
|
*
|
|
* stable
|
|
*/
|
|
static int l_space_get_bodies(lua_State *l)
|
|
{
|
|
if (!Pi::game) {
|
|
luaL_error(l, "Game is not started");
|
|
return 0;
|
|
}
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
bool filter = false;
|
|
if (lua_gettop(l) >= 1) {
|
|
luaL_checktype(l, 1, LUA_TFUNCTION); // any type of function
|
|
filter = true;
|
|
}
|
|
|
|
lua_newtable(l);
|
|
|
|
for (Body *b : Pi::game->GetSpace()->GetBodies()) {
|
|
if (filter) {
|
|
lua_pushvalue(l, 1);
|
|
LuaObject<Body>::PushToLua(b);
|
|
if (int ret = lua_pcall(l, 1, 1, 0)) {
|
|
const char *errmsg("Unknown error");
|
|
if (ret == LUA_ERRRUN)
|
|
errmsg = lua_tostring(l, -1);
|
|
else if (ret == LUA_ERRMEM)
|
|
errmsg = "memory allocation failure";
|
|
else if (ret == LUA_ERRERR)
|
|
errmsg = "error in error handler function";
|
|
luaL_error(l, "Error in filter function: %s", errmsg);
|
|
}
|
|
if (!lua_toboolean(l, -1)) {
|
|
lua_pop(l, 1);
|
|
continue;
|
|
}
|
|
lua_pop(l, 1);
|
|
}
|
|
|
|
lua_pushinteger(l, lua_rawlen(l, -1) + 1);
|
|
LuaObject<Body>::PushToLua(b);
|
|
lua_rawset(l, -3);
|
|
}
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int l_space_dump_frames(lua_State *l)
|
|
{
|
|
if (!Pi::game) {
|
|
luaL_error(l, "Game is not started");
|
|
return 0;
|
|
}
|
|
|
|
bool details = (lua_gettop(l) >= 1) ? true : false;
|
|
Pi::game->GetSpace()->DebugDumpFrames(details);
|
|
return 0;
|
|
}
|
|
|
|
static int l_space_attr_root_system_body(lua_State *l)
|
|
{
|
|
if (!Pi::game) {
|
|
luaL_error(l, "Game is not started");
|
|
return 0;
|
|
}
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
FrameId rootId = Pi::game->GetSpace()->GetRootFrame();
|
|
Frame *root = Frame::GetFrame(rootId);
|
|
LuaObject<SystemBody>::PushToLua(root->GetSystemBody());
|
|
|
|
LUA_DEBUG_END(l, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void LuaSpace::Register()
|
|
{
|
|
lua_State *l = Lua::manager->GetLuaState();
|
|
|
|
LUA_DEBUG_START(l);
|
|
|
|
static const luaL_Reg l_methods[] = {
|
|
{ "SpawnShip", l_space_spawn_ship },
|
|
{ "SpawnShipNear", l_space_spawn_ship_near },
|
|
{ "SpawnShipDocked", l_space_spawn_ship_docked },
|
|
{ "SpawnShipParked", l_space_spawn_ship_parked },
|
|
{ "SpawnShipLanded", l_space_spawn_ship_landed },
|
|
{ "SpawnShipLandedNear", l_space_spawn_ship_landed_near },
|
|
{ "SpawnCargoNear", l_space_spawn_cargo_near },
|
|
{ "SpawnShipOrbit", l_space_spawn_ship_orbit },
|
|
{ "PutShipOnRoute", l_space_put_ship_on_route },
|
|
|
|
{ "GetBody", l_space_get_body },
|
|
{ "GetBodies", l_space_get_bodies },
|
|
|
|
{ "DbgDumpFrames", l_space_dump_frames },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const luaL_Reg l_attrs[] = {
|
|
{ "rootSystemBody", l_space_attr_root_system_body },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
lua_getfield(l, LUA_REGISTRYINDEX, "CoreImports");
|
|
LuaObjectBase::CreateObject(l_methods, l_attrs, 0);
|
|
lua_setfield(l, -2, "Space");
|
|
lua_pop(l, 1);
|
|
|
|
LUA_DEBUG_END(l, 0);
|
|
}
|