Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
GameSource/GameAEvents.c
#include "GameAEvents.h" |
#include "ZAM.h" |
#include "WindowDispatch.h" |
#include "TankSprite.h" |
#include "MissileSprite.h" |
#include "CoreGlobals.h" |
#include "ZAMProtos.h" |
#define rNotifySICN 128 |
#define rWakeUpSND 128 |
#define kDiamondMark 1 |
#define rReqGameAlert 130 |
#define AcceptBtnItem 1 |
pascal OSErr AERequestGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon); |
pascal OSErr AEAcceptGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon); |
pascal OSErr AERefuseGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon); |
pascal OSErr AEAnswer (AppleEvent *theAE, AppleEvent *reply, long rfCon); |
pascal OSErr AEGoodbye (AppleEvent *theAE, AppleEvent *reply, long rfCon); |
pascal OSErr AEAckTime(AppleEvent *theAE, AppleEvent *reply, long rfCon); |
pascal OSErr AESynchTank ( AppleEvent *theAE, AppleEvent *reply, long rfCon); |
Boolean gByeNeeded; |
long gLastSynchTime; |
long gLocalTime; |
long gLastReturnTime; |
void InstallCustomEvents(gamePtr game) |
/* |
This installs the apple event handlers used by the game. |
*/ |
{ |
OSErr err = noErr; |
gByeNeeded = false; |
gLastSynchTime = 0; |
gLastReturnTime = 0; |
gLocalTime = 0; |
err = AEInstallEventHandler (kCoreEventClass, kAEAnswer, AEAnswer, (long)game, false); |
if(err) { |
ErrMsgCode("\pCould not install AE handler.",err); |
ExitToShell(); |
} |
err = AEInstallEventHandler (kZAMEventClass, kRequestGameID, AERequestGame,(long)game, false); |
if(err) { |
ErrMsgCode("\pCould not install AE handler.",err); |
ExitToShell(); |
} |
err = AEInstallEventHandler (kZAMEventClass, kGoodByeID, AEGoodbye,(long)game, false); |
if(err) { |
ErrMsgCode("\pCould not install AE handler.",err); |
ExitToShell(); |
} |
err = AEInstallEventHandler (kZAMEventClass, kTankSynchID, AESynchTank, (long)game, false); |
if(err) { |
ErrMsgCode("\pCould not install AE handler.",err); |
ExitToShell(); |
} |
} |
void SendGoodBye(void) |
/* |
When quit is selected it calls this function, which sends a message to the other player, |
letting them know you are no longer around. |
It would be nice if the quit handler displayed even a simple dialog, |
but this was a pain for my testing, so I made it just quit. Of course a real |
application would not behave this way. |
*/ |
{ |
AppleEvent goodByeEvt; |
AppleEvent reply; |
OSErr err = noErr; |
Boolean disposeNeeded = false; |
#ifdef NO_NET |
return; |
#endif |
if(gByeNeeded) { |
err = AECreateAppleEvent(kZAMEventClass, kGoodByeID, &gGame->oppAddr, |
kAnyTransactionID, gGame->gameID, &goodByeEvt); |
if(err != noErr) { |
ErrMsgCode("\p Failure: SendGoodBye AECreateAppleEvent",err); |
} |
if(err == noErr) { |
disposeNeeded = true; |
err = AESend(&goodByeEvt, &reply, kAENoReply + kAECanInteract, |
kAEHighPriority, 60 * 60, nil, nil); |
if(err != noErr) { |
ErrMsgCode("\p SendGoodBye: AESend goodByeEvt",err); |
} |
} |
if(disposeNeeded) { |
AEDisposeDesc(&goodByeEvt); |
} |
} |
} |
pascal OSErr AEGoodbye (AppleEvent *theAE, AppleEvent *reply, long rfCon) |
/* |
This is the handler for when the remote mac has quit. |
It does not display any warning dialog box, because as I mentioned before, |
in a code-build-test cycle, it was too much to be navigating dialogs and stuff. |
So, it just causes the program to quit without warning. |
*/ |
{ |
gDone = true; |
gByeNeeded = false; |
} |
pascal OSErr AEAnswer(AppleEvent *theAE, AppleEvent *reply, long rfCon) |
/* |
This handler is used for the apple events that return a result asynchronously. |
The type of reply is stored in the keyReturnIDAttr. The only events that require |
a reply are the request game event, and the synch event. Another synch event will |
not be sent until an ack is received. |
*/ |
{ |
long actType,actSize; |
short retID; |
OSErr err= noErr; |
err = AEGetAttributePtr(theAE, keyReturnIDAttr, typeShortInteger, &actType, |
&retID, sizeof(short), &actSize); |
if(err != noErr) { |
ErrMsgCode("\pAEAnswer: AEGetAttr keyReturnIDAttr",err); |
} |
if(err == noErr) { |
switch(retID) { |
case kAcceptID: err = AEAcceptGame(theAE, reply, rfCon); |
break; |
case kTimeID: err = AEAckTime(theAE, reply, rfCon); |
break; |
default: ErrMsgCode("\pUnknown Return ID.",retID); |
break; |
} |
} |
return err; |
} |
pascal OSErr AEAckTime(AppleEvent *theAE, AppleEvent *reply, long rfCon) |
/* |
just grab the time so the synch event will not flood the wire with |
unhandled events. This is not a REAL appleevent handler, it just |
looks like one, because it was easier that way. This is actually called |
by the return event handler, above. |
*/ |
{ |
OSErr err = 0; |
long len; |
DescType actualType; |
long synchTime; |
err = AEGetParamPtr(theAE, keySynchTime, typeLongInteger, &actualType, |
&gLastReturnTime, sizeof(long), &len); |
if(err != noErr) { |
ErrMsgCode("\p Failed: AEAckTime keySynchTime, typeLongInteger",err); |
} |
return err; |
} |
pascal OSErr AEAcceptGame(AppleEvent *theAE, AppleEvent *reply, long rfCon) |
/* |
This event comes back when the other player accepts the game. Any ZAM game will |
accept if it is not already playing. I used to have code to allow you to type in |
a name, and request a game, and the other player would be able to decide if they |
wanted to or not. Again, this really hampered my code-compile-test cycle, so I stripped |
or commented it out. You could probably revive it if you wanted to. |
This is not a REAL appleevent handler, it just |
looks like one, because it was easier that way. This is actually called |
by the return event handler, above. |
*/ |
{ |
OSErr err = noErr; |
long actualType,longSize; |
Boolean acceptFlag; |
if(gGame == nil) { |
ErrMsg("\pReceived accept when never made window"); |
err = paramErr; |
} |
if(gGame->gameState == kWaitingForAccept) { |
err = AEGetParamPtr(theAE, keyAnswer, typeBoolean, &actualType, |
&acceptFlag, sizeof(Boolean), &longSize); |
if(err != noErr) { |
ErrMsgCode("\p Failed: AEAcceptGame AEGetParamPtr typeBoolean",err); |
} |
if(err == noErr) |
if(acceptFlag) { |
gGame->gameState = kGameInProgress; |
/* Set up tank indexes here */ |
gGame->localTankIndex = 0; |
gGame->remoteTankIndex = 1; |
PlaceTankSprites(gGame); |
gByeNeeded = true; |
} else { |
/* display rejection notice */ |
ParamText("\pThat person has refused your challenge. Try another.",nil,nil,nil); |
(void) Alert(131,nil); // evil numbers must go |
gGame->gameState = kWaitingForRequest; |
} |
} |
return err; |
} |
pascal OSErr AERequestGame( AppleEvent *theAE, AppleEvent *reply, long rfCon) |
/* |
This BIG ugly function handles requests for a game from another player |
and starts things rolling. It changes the game state flag, but |
this flag is mostly ignored by the rest of the program. At one time |
I was using it, but I was not setting it properly all the time, and there |
were more important bugs to fix so I just crippled it. |
*/ |
{ |
OSErr err = noErr; |
short itemHit; |
DescType returnedType; |
Size length; |
long replyID; |
Str255 nameStr; |
AEAddressDesc targetAddress; |
AppleEvent replyEvent; |
Boolean acceptFlag = true; |
ulong tranID; |
if(err == noErr) { |
if(gGame->gameState != kWaitingForRequest) { |
/* just automagically refuse the game cause we are busy */ |
acceptFlag = false; |
err = AEPutParamPtr(reply, keyAnswer, typeBoolean, &acceptFlag, sizeof(Boolean)); |
if(err != noErr) { |
ErrMsgCode("\p AERequestGame: AEPutParamPtr keyAnswer FALSE",err); |
} |
} else { |
/* get the address of the person requesting we play */ |
if(err == noErr) { |
err = AEGetAttributeDesc ( theAE, keyAddressAttr, typeWildCard, &targetAddress); |
if(err != noErr) { |
ErrMsgCode("\p AERequestGame: AEGetAttr keyAddressAttr",err); |
} |
} |
if(err == noErr) { |
err = AEGetAttributePtr(theAE, keyTransactionIDAttr, typeLongInteger, &returnedType, |
&tranID, sizeof(long), &length); |
if(err != noErr) { |
ErrMsgCode("\p AEGetAttr keyTransactionIDAttr",err); |
} |
} |
if(err == noErr) { |
/* ask user if they want to play */ |
/* NOTE: this is where I used to display the dialog asking */ |
/* if a game was wanted or not */ |
/* now it always accepts */ |
acceptFlag = true; |
if(err == noErr) { |
gGame->oppAddr = targetAddress; |
gGame->gameID = tranID; |
gGame->localTankIndex = 1; |
gGame->remoteTankIndex = 0; |
gGame->gameState = kGameInProgress; |
PlaceTankSprites(gGame); |
gByeNeeded = true; |
} |
else if(err == paramErr) { |
acceptFlag = false; |
err = noErr; |
} |
if(err == noErr) { |
err = AEPutParamPtr(reply, keyAnswer, typeBoolean, &acceptFlag, sizeof(Boolean)); |
if(err != noErr) { |
ErrMsgCode("\p AERequestGame: AEPutParamPtr keyAnswer",err); |
} |
} |
} |
} |
} |
return err; |
} |
pascal OSErr AESynchTank ( AppleEvent *theAE, AppleEvent *reply, long rfCon) |
/* |
This is the network heartbeat of the game. It recieves the state of the remote machine. |
It does some things with AppleEvents for speed |
reasons that a normal application would not want to do. I send large structures |
and arrays, instead of building platform independant AEDescLists and that sort of thing. |
I send the current state of the tank, and the current state of all the missiles (from |
a subroutine call, see the code). |
*/ |
{ |
OSErr err = 0; |
long len; |
DescType actualType; |
long synchTime; |
TankStatus tStatus; |
/* first thing is to get the synch time to see if this one |
should be ignored. The synch time is saved off each time |
so the network is not flooded with synch events. |
It is very easy to flood a network with these things. |
Sometimes a mac may be busy and miss a few events, and then when it gets back |
to it, it caused a hyper-spurt of animation while the mac caught up |
and processed the apple events. This was undesireable for my sample, so |
I implemented this strategy of synching */ |
if(err == noErr) { |
/* get the synch time to see if we should ignore this or not*/ |
err = AEGetParamPtr(theAE, keySynchTime, typeLongInteger, &actualType, |
&synchTime, sizeof(long), &len); |
if(err != noErr) { |
ErrMsgCode("\p Failed: AESynchTank keyTankPosition, typefixPt",err); |
} |
} |
if(err == noErr) { |
/* send the synch time back */ |
err = AEPutParamPtr(reply, keySynchTime, typeLongInteger, |
&synchTime, sizeof(long)); |
if(err != noErr) { |
ErrMsgCode("\p Failure: AESynchTank AEPutParamPtr reply",err); |
} |
} |
if(err == noErr) { |
if(gLastSynchTime > synchTime) |
err = 1; |
else |
gLastSynchTime = synchTime; |
} |
/* |
this gets the status of the tank from the event */ |
if(err == noErr) { |
err = AEGetParamPtr(theAE, keyTankStatus, typeTankStatus, &actualType, |
&tStatus, sizeof(TankStatus), &len); |
if(err != noErr) { |
ErrMsgCode("\p Failed: AESynchTank keyTankStatus typeTankStatus",err); |
} |
} |
/* if we got the status of the tank ok, then we call the Synch. Tank function |
(See TankSprites.c) to make sure the tank is in the right place doin the right |
thing. |
After that, the remote missiles are updated by passing the whole applevent |
to MissileSprites.c via ProcessMissilePositions. |
*/ |
if(err == noErr) { |
SynchronizeTank(gGame, &tStatus.position, tStatus.direction, tStatus.speed); |
err = ProcessMissilePositions(theAE); |
} |
/* eat the synch time errors */ |
if(err == 1) err = noErr; |
return err; |
} |
Boolean TankSynchTask(xthing *xtp, spritePtr spr) |
/* |
This is a timed task managed by the xthing time manager. |
It sends the state of the tank and the state of the missiles |
to the remote mac. |
See the notes above about abusing and sending structures using AppleEvents. |
Good. Now that you have read that, I'll elaborate more. |
One of the key concepts with scriptable AppleEvent programs is to |
use tagged data so that any application can determine the format of |
parameters and send them along in a happy manner. This may require many |
calls to AEPutParamPtr and such, which may also cause the AppleEvent record |
to grow multiple times, and can cause a slow down for a real time application |
like this. So, to minimize this, I kind go against the grain of AppleEvents and |
just send a big block of data. The good design thing for me to have done, which I |
didn't, would have been to isolate the network code a little more, so that any layer could |
be plugged in. |
*/ |
{ |
OSErr err = noErr; |
AppleEvent tankSynchEvent; |
AppleEvent reply; |
tankInfoRec *tInfo; |
Boolean disposeNeeded = false; |
long tickTime; |
TankStatus tStatus; |
if(gLastReturnTime != gLocalTime) return true; |
tInfo = (tankInfoRec*)spr->refCon; |
err = AECreateAppleEvent(kZAMEventClass, kTankSynchID, &gGame->oppAddr, |
kTimeID, gGame->gameID, &tankSynchEvent); |
if(err != noErr) { |
ErrMsgCode("\p Failure: FireNetworkMissile AECreateAppleEvent",err); |
} |
/* |
This is where the time stamp is incremented, and placed into the appleevnet. |
This is sent back in the reply event */ |
if(err == noErr) { |
disposeNeeded = true; |
gLocalTime++; |
err = AEPutParamPtr(&tankSynchEvent, keySynchTime, typeLongInteger, |
&gLocalTime, sizeof(long)); |
if(err != noErr) { |
ErrMsgCode("\p Failure: AEPutParamPtr",err); |
} |
} |
/* load up the tank info and stuff it into the event */ |
tStatus.position = spr->loc; |
tStatus.direction = tInfo->dir; |
tStatus.speed = tInfo->speed; |
if(err == noErr) { |
err = AEPutParamPtr(&tankSynchEvent, keyTankStatus, typeTankStatus, |
&tStatus, sizeof(TankStatus)); |
if(err != noErr) { |
ErrMsgCode("\p Failure: AEPutParamPtr keyTankStatus, typeTankStatus",err); |
} |
} |
/* getting a little more structured here, I call out to add the missile information */ |
/* to the event. CAn you tell that I did the tank stuff first, and missile stuff |
later? */ |
if(err == noErr) { |
err = AppendMissilePositions(&tankSynchEvent); |
if(err != noErr) { |
ErrMsgCode("\pError appending missile positions.",err); |
} |
} |
if(err == noErr) { |
err = AESend(&tankSynchEvent, &reply, kAEQueueReply + kAECanInteract, |
kAEHighPriority, kNoTimeOut, nil, nil); |
if(err != noErr) { |
ErrMsgCode("\p Failure: AESend",err); |
} |
} |
if(disposeNeeded) { |
AEDisposeDesc(&tankSynchEvent); |
} |
return true; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14