convert line ends
[canaan.git] / prj / cam / src / deepc / mp / dpcnet.cpp
blobac43877fb44c3095b4f177ed4c97b5e621ca57b3
1 /*
2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
4 */
6 #include <lg.h>
7 #include <ctype.h> // for isalnum()
8 #include <appagg.h>
9 #include <netman.h>
10 #include <playrobj.h>
11 #include <netmsg.h>
12 #include <netnotif.h>
13 #include <netsim.h>
14 #include <config.h>
16 // These are all for DPCPlayerCrash:
17 #include <iobjnet.h>
18 #include <contain.h>
19 #include <rendprop.h>
20 #include <physapi.h>
21 #include <objpos.h>
22 #include <phprops.h>
23 #include <phprop.h>
24 #include <rect.h> // for Point
25 #include <dpcinvpr.h>
26 #include <dpcinv.h>
27 #include <objdef.h> // for kObjectConcrete
28 #include <traitman.h>
30 #include <dpcfsys.h>
31 #include <dpcnet.h>
32 #include <dpcaipbs.h>
33 #include <dpcovcst.h>
34 #include <dpcovrly.h>
35 #include <dpccont.h>
36 #include <dpcutils.h>
37 #include <dpcprop.h>
39 // Other subsystems, which we want to initialize networking for:
40 #include <dpcchat.h>
41 #include <dpcelev.h>
43 #include <ghostshk.h>
45 // Must be last:
46 #include <dbmem.h>
48 //////////
50 // Host name
52 // The host should have a distinct name for sessions he runs; this is mainly
53 // used to keep savegames from different hosts distinct.
55 static char gHostName[MAX_HOSTNAME_LEN + 1];
57 void DPCSetHostName(const char *name)
59 strncpy(gHostName, name, MAX_HOSTNAME_LEN);
60 // Make sure it's made up only of legal characters:
61 char *pChar;
62 for (pChar = gHostName;
63 *pChar != '\0';
64 pChar++)
66 if (!isalnum(*pChar) &&
67 (*pChar != ' ') &&
68 (*pChar != '_'))
70 *pChar = '_';
75 const char *DPCGetHostName()
77 return gHostName;
80 ////////////////////////////////////
82 // MESSAGES
85 //////////
87 // Display text
89 static void handleAddText(const char *pText, int time)
91 DPCOverlayAddText(pText, time);
95 // Display the text on the specified player's HUD.
97 static cNetMsg *g_pAddTextAllMsg = NULL;
99 static sNetMsgDesc sAddTextAllDesc = {
100 kNMF_Broadcast,
101 "AddText",
102 "Add Text",
103 NULL,
104 handleAddText,
105 {{kNMPT_String},
106 {kNMPT_Int},
107 {kNMPT_End}}
111 // Display the text on a specific player's HUD.
112 static cNetMsg *g_pAddTextMsg = NULL;
114 static sNetMsgDesc sAddTextDesc = {
115 kNMF_None,
116 "AddText",
117 "Add Text",
118 NULL,
119 handleAddText,
120 {{kNMPT_String},
121 {kNMPT_Int},
122 {kNMPT_End}}
125 void DPCSendAddText(ObjID player, const char *pText, int time)
127 if (player == OBJ_NULL) {
128 g_pAddTextAllMsg->Send(OBJ_NULL, pText, time);
129 } else {
130 g_pAddTextMsg->Send(player, pText, time);
134 ///////
136 // The remove-from-container message
138 static cNetMsg *g_pRemoveContaineeMsg = NULL;
140 static void handleRemoveContainee(ObjID o, ObjID cont)
142 DPCContainerCheckRemove(o, cont);
143 AutoAppIPtr(ContainSys);
144 pContainSys->Remove(cont, o);
147 static sNetMsgDesc sRemoveContaineeDesc =
149 kNMF_Broadcast,
150 "RemoveCont",
151 "Remove From Container",
152 NULL,
153 handleRemoveContainee,
154 {{kNMPT_SenderObjID, kNMPF_None, "Containee"},
155 // This must be a global, because it is possible for Container and
156 // Containee to have different owners. This will happen in the case
157 // of pre-placed loot within a handed-off AI:
158 {kNMPT_GlobalObjID, kNMPF_None, "Container"},
159 {kNMPT_End}}
163 // Tell the clients to take the given object out of the container. This
164 // will contact all clients, *including this one*
165 // Host code
167 void DPCBroadcastRemoveContainee(ObjID o, ObjID cont)
169 if (cont == OBJ_NULL)
170 return;
172 g_pRemoveContaineeMsg->Send(OBJ_NULL, o, cont);
174 // Tell our own database as well as the other clients
175 handleRemoveContainee(o, cont);
178 //////////
180 // Send Host's name
182 static cNetMsg *g_pHostNameMsg = NULL;
184 static void handleHostName(const char *name)
186 DPCSetHostName(name);
189 static sNetMsgDesc sHostNameDesc =
191 kNMF_MetagameBroadcast,
192 "HostName",
193 "Host Name",
194 NULL,
195 handleHostName,
196 {{kNMPT_String, kNMPF_None, "Name"},
197 {kNMPT_End}}
200 //////////
202 // Hilight an object
204 static cNetMsg *g_pHilightObjectMsg = NULL;
206 #define SHOW_HILIGHT_TIME 10000
208 static void handleHilightObject(ObjID obj, ObjID from)
210 if (!IsPlayerObj(from))
212 char objName[128];
213 ObjGetObjShortNameSubst(obj, objName, sizeof(objName));
214 AutoAppIPtr(NetManager);
215 const char *fromName = pNetManager->GetPlayerName(from);
216 char temp[255];
217 if (DPCStringFetch(temp, sizeof(temp), "HilightString", "misc"))
219 char buf[255];
220 sprintf(buf, temp, objName, fromName);
221 DPCOverlayAddText(buf, SHOW_HILIGHT_TIME);
225 int expireTime;
226 if (gPropHUDTime->Get(obj, &expireTime) && (expireTime == 0))
228 // Don't do anything; this sucker's under our cursor, and should
229 // be highlighted until it's explicitly turned off
230 } else {
231 gPropHUDTime->Set(obj, GetSimTime() + SHOW_HILIGHT_TIME);
235 static sNetMsgDesc sHilightObjectDesc =
237 kNMF_Broadcast | kNMF_AppendSenderID,
238 "Hilight",
239 "Hilight an Object",
240 NULL,
241 handleHilightObject,
242 {{kNMPT_GlobalObjID, kNMPF_None, "Object"},
243 {kNMPT_End}}
246 void DPCBroadcastHilightObject(ObjID obj)
248 g_pHilightObjectMsg->Send(OBJ_NULL, obj);
249 handleHilightObject(obj, PlayerObject());
252 ////////////////////////////////////
254 // NETWORK LISTENERS
258 // Deal with a player who has crashed out of the game. This will get called
259 // by the networking system (or possibly at other times) if needed. This
260 // isn't trying to be perfect -- it's trying to clean up so that the
261 // remaining players can keep going...
263 // Explicit assumption: containment links are replicated on all machines,
264 // so we know what the player had in his inventory.
266 static void DPCPlayerCrash(DWORD data)
268 #define MAX_TEMPLATE_LEN 64
269 char templ[MAX_TEMPLATE_LEN];
270 ObjID player = (ObjID) data;
272 if (player == OBJ_NULL)
274 // Looks like the crash was sometime very early in the game, before
275 // we had a player ID for him. In which case, there's not much we
276 // can do here, except print a generic message.
277 if (DPCStringFetch(templ, MAX_TEMPLATE_LEN,
278 "UnknownDisconnected", "Network", -1))
280 DPCSendAddText(OBJ_NULL, templ, 5000);
281 DPCOverlayAddText(templ, 5000);
284 return;
287 // This should only get called on the default host...
288 AutoAppIPtr(NetManager);
289 Assert_(pNetManager->AmDefaultHost());
291 // Say that the player's gone:
292 if (DPCStringFetch(templ, MAX_TEMPLATE_LEN,
293 "PlayerDisconnected", "Network", -1))
295 char msgBuf[MAX_PLAYER_NAME_LEN + MAX_TEMPLATE_LEN];
296 sprintf(msgBuf,
297 templ,
298 pNetManager->GetPlayerName(player));
299 DPCSendAddText(OBJ_NULL, msgBuf, 5000);
300 DPCOverlayAddText(msgBuf, 5000);
303 // Pick up all of the player's stuff, reassign it back to the world,
304 // and put it in a nice box. This code is liberally adapted from the
305 // throw-to-world code; we might want to restructure that a little and
306 // use it.
307 // First, set up for the boxes.
308 ObjID currentBox = OBJ_NULL;
309 AutoAppIPtr(ObjectSystem);
310 ObjID boxArch = pObjectSystem->GetObjectNamed("Personal Effects Box");
311 if (boxArch == OBJ_NULL) {
312 Warning(("Couldn't create personal effects boxes!\n"));
313 return;
315 mxs_real boxRadius = 0;
316 // Get the dimensions for each box:
317 Point boxDims = ContainDimsGetSize(boxArch);
318 // Buffer to keep track of the layout within the box, so that we
319 // don't overfill it:
320 ObjID *containerContents =
321 (ObjID *) malloc((boxDims.x * boxDims.y) * sizeof(ObjID));
322 // The slot to slap each object into:
323 int slot = 0;
324 // Figure out the location to place the lowest box. Basically,
325 // we're trying to place the boxes within the two big spheres
326 // that make up the bulk of the player's body, because that's
327 // the only space we can be pretty sure is clear. Note that we
328 // implicitly assume that the boxes are pretty small, probably
329 // less than a foot. Note that we can't use the physics location
330 // of the player, because we can't count upon him being physical
331 // any more.
332 mxs_vector boxLoc = *ObjPosGetLocVector(player);
333 // PhysGetSubModLocation(player, PLAYER_BODY, &boxLoc);
334 boxLoc.z -= (PLAYER_RADIUS / 2);
335 mxs_angvec nullAngle = {0, 0, 0};
337 // Find the player's keyring, so we don't actually copy it. I'm
338 // sure there must be a way to do this without hardcoding the name
339 // of the damned archetype, but I'm not thinking of it right now...
340 ObjID keyarch = pObjectSystem->GetObjectNamed("fakekeys");
342 // Now actually go through the objects...
343 AutoAppIPtr(ObjectNetworking);
344 AutoAppIPtr(ContainSys);
345 sContainIter *pIter;
346 for (pIter = pContainSys->IterStart(player);
347 !pIter->finished;
348 pContainSys->IterNext(pIter))
350 // For each object the player had...
351 ObjID hisObj = pIter->containee;
353 // ... take that object over. This is a little tricky; since we're
354 // getting the objnum from the dead player's inventory, we have to
355 // remember that we're currently holding a proxy representation;
356 // we now make that the official one.
357 // @TBD (justin 5/16): What? That makes no sense...
358 // ObjID obj = pObjectNetworking->ObjGetProxy(player, hisObj);
359 ObjID obj = hisObj;
360 pObjectNetworking->ObjTakeOver(obj);
362 // If it's a fakekeys, just destroy it. There's no point
363 // in putting it back in world.
364 AutoAppIPtr(TraitManager);
365 ObjID arch = pTraitManager->GetArchetype(obj);
366 if (arch == keyarch) {
367 pObjectSystem->Destroy(obj);
368 continue;
371 // Put the object back into the world. Figure out which slot to
372 // stick the object into:
373 if ((currentBox == OBJ_NULL) ||
374 (!DPCInvFindSpace(containerContents, boxDims, obj, &slot)))
376 // Either there's no box, or the previous one is full, so
377 // create a new one:
378 currentBox = pObjectSystem->Create(boxArch, kObjectConcrete);
379 PhysRegisterSphereDefault(currentBox);
380 // Get the physical height of the box, so we can stack them up:
381 cPhysDimsProp *pBoxSize;
382 if (!g_pPhysDimsProp->Get(currentBox, &pBoxSize)) {
383 Warning(("Personal Effects box has no size!\n"));
384 return;
386 boxRadius = pBoxSize->radius[0];
387 // Put the box where the player was.
388 // @TBD: give the boxes different orientations
389 PhysSetModLocation(currentBox, &boxLoc);
390 boxLoc.z += (boxRadius * 4);
391 int i;
392 for (i=0; i < boxDims.x * boxDims.y; i++)
393 containerContents[i] = OBJ_NULL;
394 // Now find the right slot in this new box:
395 if (!DPCInvFindSpace(containerContents, boxDims, obj, &slot)) {
396 Warning(("Empty personal effects box can't hold obj %d!\n", obj));
397 return;
400 // Take the object out from the player. We don't need to do this
401 // explicitly for our own sake, but we might for the other players
402 // who are watching:
403 pContainSys->Remove(player, obj);
404 // Now slap the object into that slot:
405 pContainSys->Add(currentBox, obj, slot, CTF_NONE);
406 DPCInvFillObjPos(obj, containerContents, slot, boxDims);
408 pContainSys->IterEnd(pIter);
412 // Utility method, to add a fetched network string.
414 #define MAX_NETSTRING_LEN 64
415 static void DPCNetAddText(const char *msgName, int time)
417 char msg[MAX_NETSTRING_LEN];
418 if (DPCStringFetch(msg, MAX_NETSTRING_LEN, msgName, "Network", -1)) {
419 DPCOverlayAddText(msg, time);
424 // What to do when our connection to the server gets lost. For the time
425 // being, we just put a message on the HUD if he's in sim mode. We don't
426 // yet do anything if he's in metagame.
428 // @TBD: Do something clearer and more user-friendly. Be nice to the poor
429 // user who has just gotten his session cut off. Recommend running with
430 // a longer timeout, if he doesn't think he should have been cut off.
431 // Remember that this may occur when the host simply exits normally.
432 static void DPCNetworkLost()
434 if (IsSimTimePassing()) {
435 DPCNetAddText("NetworkLost", 30000);
440 // A new player has connected. If we're the host, broadcast our host name.
441 // This is a smidgeon inefficient, since it means we'll send once per
442 // player, but that's not too bad.
444 static void DPCPlayerConnect()
446 AutoAppIPtr(NetManager);
447 if (pNetManager->AmDefaultHost()) {
448 g_pHostNameMsg->Send(OBJ_NULL, gHostName);
453 // @HACK: this is basically ripped out of netman. Properly speaking, it
454 // *should* be here, because it's a very app-dependent kinda function.
455 // In the long run, netman should define an interface that the app is
456 // supposed to implement, to return app-dependent stuff like this. But
457 // I'm not messing around at that level this late...
459 #define DEFAULT_AVATAR_NAME "Default Avatar"
460 static ObjID MyAvatarArchetype()
462 char abstractName[128];
463 ObjID abstractPlayer = OBJ_NULL;
464 AutoAppIPtr(NetManager);
465 AutoAppIPtr(ObjectSystem);
466 int MyPlayerNum = pNetManager->MyPlayerNum();
468 // The net_abstract_player is the archetype to be created for
469 // our player on other player's machines.
470 if (!config_get_raw("net_avatar",
471 abstractName,
472 sizeof abstractName))
474 // Use the default player
475 sprintf(abstractName, "%s %d", DEFAULT_AVATAR_NAME, MyPlayerNum);
476 abstractPlayer = pObjectSystem->GetObjectNamed(abstractName);
477 AssertMsg1(abstractPlayer,
478 "Default avatar %s not in gamesys!",
479 abstractName);
480 } else {
481 abstractPlayer = pObjectSystem->GetObjectNamed(abstractName);
482 if (abstractPlayer == OBJ_NULL) {
483 Warning(("Unknown abstract player object %s",abstractName));
484 // Use the default player
485 sprintf(abstractName, "%s %d", DEFAULT_AVATAR_NAME,
486 MyPlayerNum);
487 abstractPlayer = pObjectSystem->GetObjectNamed(abstractName);
488 AssertMsg1(abstractPlayer,
489 "Default avatar %s not in gamesys!",
490 abstractName);
494 return abstractPlayer;
498 // We've begun networking. Make sure that our own map icon is colored
499 // appropriately. (The other players get their colors from their avatars.)
501 static void DPCPlayerIconSetup()
503 ObjID myAvatar = MyAvatarArchetype();
504 Label *iconName;
505 if ((myAvatar != OBJ_NULL) &&
506 gPropMapObjIcon->Get(myAvatar, &iconName))
508 // Set the player's map icon to the avatar's version
509 gPropMapObjIcon->Set(PlayerObject(), iconName);
514 // Listen into the network, and see if anything interesting happens...
516 static void networkListener(eNetListenMsgs situation,
517 DWORD data,
518 void * /* pClientData */)
520 switch (situation) {
521 case kNetMsgPlayerLost:
522 DPCPlayerCrash(data);
523 break;
524 case kNetMsgNetworkLost:
525 DPCNetworkLost();
526 break;
527 case kNetMsgPlayerConnect:
528 DPCPlayerConnect();
529 break;
530 case kNetMsgNetworking:
531 DPCPlayerIconSetup();
532 break;
537 // App-specific callback from netsim, to deal with when we have to
538 // pause the game while someone isn't in the sim.
540 // @TBD: change these messages to come from the strings file...
542 static BOOL g_enteringGame = FALSE;
543 static void DPCNetDisplayPause(BOOL paused,
544 BOOL enterGame,
545 void * /* pClientData */)
547 if (paused) {
548 if (enterGame) {
549 DPCNetAddText("NetWait", 1);
550 g_enteringGame = TRUE;
551 } else {
552 DPCNetAddText("NetPause", 1);
553 g_enteringGame = FALSE;
555 } else {
556 if (g_enteringGame) {
557 DPCNetAddText("NetJoined", 5000);
558 } else {
559 DPCNetAddText("NetResume", 5000);
565 // Get the color appropriate for the specified player, by either player
566 // number or object.
568 // These colors were chosen more or less arbitrarily; they're roughly the
569 // ones Gareth decided to put on the player armbands, so we might as well
570 // be consistent.
572 int DPCPlayerNumColor(int playerNum)
574 static int red[3] = {255, 150, 150};
575 static int blue[3] = {150, 150, 255};
576 static int purple[3] = {255, 150, 255};
577 static int yellow[3] = {255, 255, 150};
578 switch(playerNum)
580 case 1:
581 return FindColor(red);
582 case 2:
583 return FindColor(blue);
584 case 3:
585 return FindColor(purple);
586 case 4:
587 return FindColor(yellow);
588 default:
589 return gDPCTextColor;
593 int DPCPlayerColor(ObjID playerObj)
595 AutoAppIPtr(NetManager);
596 return DPCPlayerNumColor(pNetManager->ObjToPlayerNum(playerObj));
599 ////////////////////////////////////
601 // STARTUP and SHUTDOWN
603 // Set up the network client. We assume that NetManager has been initialized
605 void DPCNetInit()
607 AutoAppIPtr(AIManager);
608 IAIBehaviorSet *pBehaviorSet = new cAIDPCProxyBehaviorSet;
609 pAIManager->InstallBehaviorSet(pBehaviorSet);
610 SafeRelease(pBehaviorSet);
612 // Tell the NetManager what to do if a player crashes:
613 AutoAppIPtr(NetManager);
614 pNetManager->Listen(networkListener,
615 kNetMsgPlayerLost | kNetMsgNetworkLost
616 | kNetMsgPlayerConnect | kNetMsgNetworking,
617 NULL);
619 // Tell the NetSim system what to do when the game is paused:
620 NetSimRegisterCallback(DPCNetDisplayPause, NULL);
622 // Set up the initial host name:
623 if (!config_get_raw("net_hostname",
624 gHostName,
625 sizeof gHostName))
627 // A backup default, just to make sure it's not garbage:
628 DPCSetHostName("Host");
631 // Create the standalone messages:
632 g_pAddTextMsg = new cNetMsg(&sAddTextDesc);
633 g_pAddTextAllMsg = new cNetMsg(&sAddTextAllDesc);
634 g_pRemoveContaineeMsg = new cNetMsg(&sRemoveContaineeDesc);
635 g_pHostNameMsg = new cNetMsg(&sHostNameDesc);
636 g_pHilightObjectMsg = new cNetMsg(&sHilightObjectDesc);
638 // Also create any messages that other subsystems want created:
639 DPCChatNetInit();
640 DPCElevNetInit();
642 ShockGhostInit();
645 // Shut down Deep Cover-specific networking. Doesn't currently need to do
646 // anything.
647 void DPCNetTerm()
649 // Delete the standalone messages:
650 delete g_pAddTextMsg;
651 delete g_pAddTextAllMsg;
652 delete g_pRemoveContaineeMsg;
653 delete g_pHostNameMsg;
654 delete g_pHilightObjectMsg;
656 // And the messages in other subsystems:
657 DPCChatNetTerm();
658 DPCElevNetTerm();