KeyComm

From OrbiterWiki
Jump to navigation Jump to search

Orbiter 2010: There is a new Vessel function, SendBufferedKey, which should be used instead of calling clbkConsumeBufferedKey directly.

From http://orbit.m6.net/Forum/default.aspx?g=posts&t=17569

We needed a way for a ship (or the Targetting MFD we made) to communicate with turret to tell it to pick up a target, start firing, stop firing, etc. The initial plan was to obtain the vessel pointer, cast it to a Turret, and call a method specifically. However, this has several undesired properties, such as requiring the calling MFD or ship code to include the turret header, etc, and thus isn't very extensible. Additionally, if we attached something other than a Turret (say for a fighter-type craft, where you have a missile directly attached to the ship), the object wouldn't cast nicely to a Turret. Thus, we needed a function that's part of the normal VESSEL or VESSEL2 class that we can overload to serve as our messaging function. clbkConsumeBufferedKey works very well for this purpose--it takes as parameters a DWORD, a boolean, and a char*, and returns an int. Normally, the DWORD parameter will not be greater than about 0xDF (the highest OAPI_KEY_xxx constant), and since there's 32 bits in a DWORD, that gives us only about 4.3 billion possible messages we can send to this function that won't be misinterpreted as keyboard input. More concretely: The MFD and the turret both define a constant COMM_SET_TARGET to be some number greater than 0xFF. The MFD asks the user to input the name of the ship to target, obtains the turret's VESSEL pointer, casts it to a VESSEL2*, then calls

turret.clbkConsumeBufferedKey(COMM_SET_TARGET,false,name);

where the boolean (here false) doesn't actually matter and name is the char * pointer that is the name of the ship to target. This function will return an int; <= 0 indicates failure and any other value indicates success. The turret has included a case for COMM_SET_TARGET in its clbkConsumeBufferedKey function which obtains the target vessel's handle from the name and initiates the target tracking process to point itself in the right direction.

As long as the sender and receiver both know the message number, this will work, and it'll work well. For a ship that isn't expecting messages, nothing will happen, and since the constants chosen are high enough, you aren't limiting keys you can read. The only place it will fail is if the target ship is a VESSEL instead of a VESSEL2, and in that case you could check that and prevent the cast/call.


Fun Stuff: To retrieve the turret's target, the MFD declares a char array buffer large enough to contain it, sets the first character of that char array to be the size of the array, then calls

turret.clbkConsumeBufferedKey(COMM_GET_TARGET,false,buf);

The turret will call oapiGetObjectName on its target and pass in the buffer that's sent, with buf[0] being the length of the buffer (to pass with the buffer to the oapiGetName method). The clbkConsumeBufferedKey will return a 0 if it didn't have a target, or a 1 if it did, in which case the name of the ship is stored in the char array pointed to by buf.


More cool hacks with the function:

Say you want a communication function that'll return a double, perhaps "What is the range to your current target?" clbkConsumeBufferedKey only returns an int, not a double--but it also takes a char * as a parameter. Soooo....declare a double (we'll call it "doubl" ) and pass it to clbkConsumeBufferedKey as

ship.clbkConsumeBufferedKey(COMM_GET_RANGE,false,(char *)&doubl);

Presumably, ship's clbkConsumeBufferedKey method will know how to interpret that pointer, cast it back to a double *, and assign the range to it. When the function returns, "doubl" will now contain the range.

Another advantage of this method is that ships inherently ignore communications they don't handle. I could easily send messages to say, a DeltaGlider, and it would (probably) return 0 since it's not a key that it recognizes.

The current code we have gets a handle to the vessel that the messege should be sent to, then calls that vessels clbkConsumeBufferedKey method with a flag that is defined for both the sender and reciever. Here's an example of the minimum needed to send/receive a message.

Code to send the message: Code:

   const DWORD COMM_SET_TARGET = 257;//defined in header, 256 is max defined key value, so this must be higher than that
   OBJHANDLE tarVessel = oapiGetVesselByName("messageTarget");
   char *str = "target";
   VESSEL2* ves = (VESSEL2*)oapiGetVesselInterface(tarVessel);
   bool validTargetRecieved = ves->clbkConsumeBufferedKey(COM_SET_TARGET,false,str);


This would be the corresponding code to receive this message: Code:

   const DWORD COMM_SET_TARGET = 257;//defined in header
   bool Turret::clbkConsumeBufferedKey(DWORD key, bool down, char *kstate) {
       if (key == COMM_SET_TARGET) {
           OBJHANDLE tarVessel = oapiGetVesselByName(kstate);
           if(tarVessel == NULL)
               return 0;
           return 1;
       }
   //the normal key handling code could go here


The prefix discussed above isn't shown here, this is just an example of what you need to get it working. Also, the incoming message handling could be done in another method if you pass the parameters to it, but here I just put directly in the clbkConsumeBufferedKey method.

Another code example, this time with the receiving ship returning a string in the payload.

The sending ship (actually an MFD in this case) prepares a character array of a given length and sets the 0th element of that array to be the length of that array. This is so the receiving ship can easily know the length of the buffer, so there's no overflow.

Code to send the message: (I apologize for the difficulty of reading it, this is how w3asel writes code. sigh.) Code:

   const DWORD COMM_GET_TARGET = 259; //defined in header
   char Buffer[100];
   Buffer[0] = 100;
   int hasTar = ((VESSEL2*)oapiGetVesselInterface(curVessel->GetAttachmentStatus(attachPoints[i])))->clbkConsumeBufferedKey(COMM_GET_TARGET, false, Buffer);
   TextOut(hDC, 190, 60 + i * 25, Buffer, strlen(Buffer));


This would be the corresponding code to receive this message: Code:

   const DWORD COMM_GET_TARGET = 259;//defined in header
   bool Turret::clbkConsumeBufferedKey(DWORD key, bool down, char *kstate) {
       if (key == COMM_GET_TARGET)
       {
           if (tvessel == NULL)
               return 0;
           char length = kstate[0];
           oapiGetObjectName(tvessel,kstate,(int)length);
           return 1;
       }
       //the normal key handling code could go here


Note that there is still no prefix handled here, but this demonstrates how the "payload" can be used to return a value other than an int to the sending ship.