2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
7 #include <ctype.h> // for isalnum()
16 // These are all for DPCPlayerCrash:
24 #include <rect.h> // for Point
27 #include <objdef.h> // for kObjectConcrete
39 // Other subsystems, which we want to initialize networking for:
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:
62 for (pChar
= gHostName
;
66 if (!isalnum(*pChar
) &&
75 const char *DPCGetHostName()
80 ////////////////////////////////////
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
= {
111 // Display the text on a specific player's HUD.
112 static cNetMsg
*g_pAddTextMsg
= NULL
;
114 static sNetMsgDesc sAddTextDesc
= {
125 void DPCSendAddText(ObjID player
, const char *pText
, int time
)
127 if (player
== OBJ_NULL
) {
128 g_pAddTextAllMsg
->Send(OBJ_NULL
, pText
, time
);
130 g_pAddTextMsg
->Send(player
, pText
, time
);
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
=
151 "Remove From Container",
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"},
163 // Tell the clients to take the given object out of the container. This
164 // will contact all clients, *including this one*
167 void DPCBroadcastRemoveContainee(ObjID o
, ObjID cont
)
169 if (cont
== OBJ_NULL
)
172 g_pRemoveContaineeMsg
->Send(OBJ_NULL
, o
, cont
);
174 // Tell our own database as well as the other clients
175 handleRemoveContainee(o
, cont
);
182 static cNetMsg
*g_pHostNameMsg
= NULL
;
184 static void handleHostName(const char *name
)
186 DPCSetHostName(name
);
189 static sNetMsgDesc sHostNameDesc
=
191 kNMF_MetagameBroadcast
,
196 {{kNMPT_String
, kNMPF_None
, "Name"},
204 static cNetMsg
*g_pHilightObjectMsg
= NULL
;
206 #define SHOW_HILIGHT_TIME 10000
208 static void handleHilightObject(ObjID obj
, ObjID from
)
210 if (!IsPlayerObj(from
))
213 ObjGetObjShortNameSubst(obj
, objName
, sizeof(objName
));
214 AutoAppIPtr(NetManager
);
215 const char *fromName
= pNetManager
->GetPlayerName(from
);
217 if (DPCStringFetch(temp
, sizeof(temp
), "HilightString", "misc"))
220 sprintf(buf
, temp
, objName
, fromName
);
221 DPCOverlayAddText(buf
, SHOW_HILIGHT_TIME
);
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
231 gPropHUDTime
->Set(obj
, GetSimTime() + SHOW_HILIGHT_TIME
);
235 static sNetMsgDesc sHilightObjectDesc
=
237 kNMF_Broadcast
| kNMF_AppendSenderID
,
242 {{kNMPT_GlobalObjID
, kNMPF_None
, "Object"},
246 void DPCBroadcastHilightObject(ObjID obj
)
248 g_pHilightObjectMsg
->Send(OBJ_NULL
, obj
);
249 handleHilightObject(obj
, PlayerObject());
252 ////////////////////////////////////
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);
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
];
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
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"));
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:
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
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
);
346 for (pIter
= pContainSys
->IterStart(player
);
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);
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
);
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
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"));
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);
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
));
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
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",
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!",
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
,
487 abstractPlayer
= pObjectSystem
->GetObjectNamed(abstractName
);
488 AssertMsg1(abstractPlayer
,
489 "Default avatar %s not in gamesys!",
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();
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
,
518 void * /* pClientData */)
521 case kNetMsgPlayerLost
:
522 DPCPlayerCrash(data
);
524 case kNetMsgNetworkLost
:
527 case kNetMsgPlayerConnect
:
530 case kNetMsgNetworking
:
531 DPCPlayerIconSetup();
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
,
545 void * /* pClientData */)
549 DPCNetAddText("NetWait", 1);
550 g_enteringGame
= TRUE
;
552 DPCNetAddText("NetPause", 1);
553 g_enteringGame
= FALSE
;
556 if (g_enteringGame
) {
557 DPCNetAddText("NetJoined", 5000);
559 DPCNetAddText("NetResume", 5000);
565 // Get the color appropriate for the specified player, by either player
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
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};
581 return FindColor(red
);
583 return FindColor(blue
);
585 return FindColor(purple
);
587 return FindColor(yellow
);
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
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
,
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",
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:
645 // Shut down Deep Cover-specific networking. Doesn't currently need to do
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: