sidebarimage

Logo Logo2v1.0


Home

FAQ

Downloads

Screenshots

Forums

Links

GPDS

Tutorials


Updated for CVE-lite version 0.9


Creating a single-stage CVE-lite vessel


Here we shall use the Fregat source code as an example.

I shall assume you have installed cvel.h into ~/orbitersdk/include, and cvel.lib into ~/orbitersdk/lib and make sure the vital inter-vessel communications dll, cvelmessage.dll, is in ~/Modules.

The CVE-lite programmer's guide and function reference.

Included in the CVE-lite library, as of version 0.9 is a full PDF copy of the programmer's guide and function reference. This provides a more detailed breakdown of CVE-lite's functionality.


CVE-Lite's requirements

The main requirement of CVE-lite vessels is that they are able to respond to GPDS signals at any time. These signals are transmitted via the cvelmessage.dll and can be sent by any vessel to any other using SendCentralMessage(). They consequently need to be polled for by all CVE-lite vessels. The polling function is bool CheckCentralMessage(). This returns true when a command to this vessel has been processed (and consequently the vessel needs to perform a refresh) and false, when there is nothing pending.

It is therefore imperitive that there exists a function you can call on demand and as many times as you like that can clear out all the meshes and (optionally) thrusters, and re-draw without disrupting normal ship operation (for example, it shouldn't re-fill the fuel tank!).

Below we find an walked-through example (using the Fregat code), that shows how this can be done, and how to make a complete single-stage CVE-lite vessel.


Basics


First thing is to include cvel.h into the project, either in the header file (as here) or in the main .cpp file, and make sure cvel.lib is linked into your project, normally by right-clicking and selecting "add file to project" in the file view on the left of the MSVC++ IDE.

Second, create your own vessel class, subclassing from COMMON_VESSEL_EX and containing all the Orbiter usual suspects.

class FREGAT : public COMMON_VESSEL_EX {
public:
    FREGAT (OBJHANDLE hObj, int fmodel);
    ~FREGAT();
    void TimeStep(  double simt );
// need a timestep loop to conduct the GPDS polling
    void VisualCreated( VESSEL *vessel, VISHANDLE vis, int refcount );
// This a good place to do initial drawing.
// WARNING since v0.9 you need to call this yourself, the ovc overide is no longer in place!
    void SetClassCaps(  FILEHANDLE cfg );
    void LoadState(  FILEHANDLE scn, void *vs );
    void SaveState(  FILEHANDLE scn );
    void SetMainStage();
// IMPORTANT -- this is the "refresh" function (see later)
    void SetAttControlsFregat();
// this is just to set up the thrusters. I like to keep them separate.
    int  ConsumeKey(  const char *keystate );
    int  ConsumeBufferedKey(DWORD dwKey, bool fPressed, const char *keystate);
protected:
    PROPELLANT_HANDLE ph_first;
    THRUSTER_HANDLE   th_first[1];
    THGROUP_HANDLE    thg_first,thg_pitchup, thg_pitchdown;
    MESHHANDLE          hFregat;
    SURFHANDLE          tex;

};

Thirdly, for all the important housekeeping functions, cast the vessel pointer as one of your class members, and re-direct Orbiter's callbacks. Eg.

DLLCLBK void ovcTimestep (VESSEL *vessel, double simt)
{
FREGAT *fregat = (FREGAT*)vessel;
fregat->TimeStep( simt );
}

Repeat this for the other functions that need to be triggered from the ovc* callbacks INCLUDING ovcVisualCreated() which USED TO be called by CVE-lite itself! Since v0.9 you need to replace the ovcVisualCreated() callback, else your function will not be accessed!

DLLCLBK void ovcVisualCreated ( VESSEL *vessel, VISHANDLE vis, int refcount)
{
FREGAT *fregat = (FREGAT*)vessel;
fregat->VisualCreated( vessel, vis, refcount);
}



Nuts and bolts

Here we move on to the main Fregat.cpp file.

One of the first things to do at vessel creation is to make sure we have collected all the GPDS lines due to us (including the InitFuelLevel variable), so that we can draw them from the off. This is best accomplished by putting it in the vessel constructor, before the propellent resources are allocated and meshes drawn. We use the aforementioned CheckCentralMessage() -- in this case we don't need to test the output, since we know a redraw will take place soon after the constructor has run, anyway.

FREGAT::FREGAT (OBJHANDLE hObj, int fmodel) : COMMON_VESSEL_EX (hObj)
{
hFregat = oapiLoadMeshGlobal ("fregat");
tex = oapiRegisterExhaustTexture("Exhaust_atsme");

/* set the propellant handle to NULL for good measure */
ph_first=NULL;

}


A critical piece of programming for CVE-lite functionality is to ensure that whether your vessel is created from startup (ovcLoadState is called) or dynamically, the vessel initialises correctly. The easiest way to do this is to create a "SetStageX()" function, that cleans out the whole set of thrusters, meshes et al, checks propellents have been created and re-initialises everything. This can be then called as often as necesasry. For example at LoadState, and also in VisualCreated(). This multiply-executable setup stage is called the Refresh function.

void FREGAT::VisualCreated ( VESSEL *vessel, VISHANDLE vis, int refcount) {
    SetMainStage(); // this is a good place to ask for the initial setup.
}


We need to create our propellants in SetClassCaps() if we should ever be used as an upper stage in a user's custom vessel. Remember it's better to create all your propellant resources here, and delete them in the Refresh function, if need be. Otherwise initial fuel levels will not be passed through.

void FREGAT::SetClassCaps(  FILEHANDLE cfg ) {
/* verify that the propellent hasn't been created before making it */
if (ph_first==NULL)
ph_first = CreatePropellantResource(FIRST_PROP_MASS);
}



There are two functions that deal with GPDS lines in scenario files. ParsePayloadString() should be called for every LoadState line, and SaveDefaultStateEx() should be called during the SaveState function. You'll notice we call the Refresh function (SetMainStage()) after the loadstate, since we'll have loaded GPDS lines from the scenario.

void FREGAT::SaveState( FILEHANDLE scn )
{
SaveDefaultState (scn);

SaveDefaultStateEx(scn);
/* save payload lines */
}

void FREGAT::LoadState( FILEHANDLE scn, void *vs )
{
char *line;
while (oapiReadScenario_nextline (scn, line))
{
ParsePayloadString(line);
/* load payload lines */

ParseScenarioLineEx (line, vs);
}

SetMainStage();
/* always set the stage whenever something may have changed */
}



When dealing with user-defined payloads, it's important to tell your vessel how much they all weigh! The CVE-lite function CalculateMass() calculates the additional mass added by your payloads. Here we call it in the Refresh function SetMainStage(). Equally we want to erase all the meshes, so we can re-draw them. The enhanced ClearMeshesEx() takes care of this.

	/* always add payload mass [CalculateMass()] to the vessel's empty mass */
SetEmptyMass ( FREGAT_DRYMASS + CalculateMass() );
/* Clear old meshes before we redraw them */
ClearMeshesEx();



So, we've cleaned out our meshes, we've re-set our mass and, if the tank hasn't yet been created, we've created the fuel tank and filled it to the required level. How do we draw the payload meshes? Simple. Draw().

	/* call draw to render the payloads */
Draw();

Right, now we need to think about polling the GPDS message stack to see if we've been sent anything new. The simplest way to do this is to run a little test in Timestep(). We've already seen CheckCentralMessage() in action in the constructor, but this time it's different -- we do need to test the output to see if we have to redraw. But fortunately if we do, we can simply call the Refresh function!

void FREGAT::TimeStep(  double simt )
{
/* This function loops through the pending GPDS requests and returns a bool (true/false)
/* if any GPDS requests have been implemented.
/* If any have (eg adding a new payload), you will need to re-draw your ship, hence the
/* call to the (repeatable) SetMainStage(); */

if( CheckCentralMessage() ) { // at least one message pending, we need to refresh
while( CheckCentralMessage() ) ;// loop through any remaining
SetMainStage(); // refresh
}
}


OK. We're flying, we're able to update on demand, and we've been able to inherit payloads from previous stages, we can set our fuel level after creation, and even accept payloads from other vessels at any time. We can load and save our payload configuration and our empty mass. What else do we need? How about being able to jettison the payloads and extra stages?

This is actually one of the hardest parts of multipayload programming, but CVE-lite takes care of it all for you! Simply call DeployPayload(). The function will calculate which ship is the last on the stack and should be jettisoned first, work out any upper stages that need to go with it and handle all the outbound inter-vessel communications. It returns a VESSEL pointer, should you wish to then use any normal Orbiter calls to interact with it.

In Fregat, we bind this to the "J" key -- it doesn't hurt to call it if you have no payloads to jettison, so there are no checks in place. Remember to call the Refresh function -- as possibly you've lost a payload!

	case OAPI_KEY_J:
result = 1;

/* call DeployPayload() to jettison the GPDS payloads in sequence */

DeployPayload();

/* Always re-set the stage after any kind of change */
SetMainStage();





Caveats


There are a some things that are worth bearing in mind throughout coding with COMMON_VESSEL_EX:

  • DLLCLBK void ovcVisualCreated()
This used to be stolen by the library to initialise the VISHANDLE for internal mesh rendering purposes. This is no longer the case. It's vital that you re-include an ovcVisualCreated() function if you're going to call an internal one.

  • Modelling
It's important to note, when designing payloads for GPDS vessels, that the position at which the vessel loads its mesh is presumed to be the origin (0,0,0). Therefore it's best to model your meshes with the centre of mass at the origin of the mesh. Equally, your payload will always be loaded with its axes aligned with the launch vehicle (this is a limitation of the current Orbiter API. Sorry).



CVE-lite compatible?

In order to be able to describe your vessel as CVE-lite compatible it must meet the following criteria:
  • be able to read standard GPDS lines from scenario files, and write its payloads back out using the same system (implement the ParseScenarioLineEx() and SaveDefaultStateEx() functions as above);
  • be able to be created by another vessel during the game -- this means no reliance on ovcLoadState() to start up;
  • be able to refresh meshes correctly after a separation (implement the "Refresh" function as above);
  • be able to accept GDPS lines from the cvelmessage.dll and correcly refresh (implement the startup and TimeStep() calls to CheckCentralMessage(), plus the "Refresh" function as above);
  • be able to carry payloads taking into account their mass, jettison them and transmit the neccessary GPDS commands to them using cvelmessage.dll (implement CalculateMass(), Draw(), DeployPayload() as above);
  • be able to act as an upperstage, even if it's not realistic (for example, in a multistaged rocket, default to the final stage in the event no LoadState() is available and implement the CheckCentralMessage()s and "Refresh" function as above);
If you would like your CVE-lite compatible vessel listed on the links page, please contact the webmaster on orbiter@aibs.org.uk

Instructions


You may wish to instruct your users in the art of the GPDS language. Never fear! Aside from directing them to the GPDS page, you can also download this HOWTO to include with your package's distribution.

Good luck!