| 
               
            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.  
             
             
             
            
            
            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!
            
              
              
             |