1 /*
2 ** d_netinfo.cpp
3 ** Manages transport of user and "server" cvars across a network
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 #include <math.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <assert.h>
39
40 #include "doomtype.h"
41 #include "doomdef.h"
42 #include "doomstat.h"
43 #include "d_netinf.h"
44 #include "d_net.h"
45 #include "d_protocol.h"
46 #include "d_player.h"
47 #include "c_dispatch.h"
48 #include "v_palette.h"
49 #include "v_video.h"
50 #include "i_system.h"
51 #include "r_state.h"
52 #include "sbar.h"
53 #include "gi.h"
54 #include "m_random.h"
55 #include "teaminfo.h"
56 #include "r_data/r_translate.h"
57 #include "templates.h"
58 #include "cmdlib.h"
59 #include "farchive.h"
60
61 static FRandom pr_pickteam ("PickRandomTeam");
62
63 CVAR (Float, autoaim, 35.f, CVAR_USERINFO | CVAR_ARCHIVE);
64 CVAR (String, name, "Player", CVAR_USERINFO | CVAR_ARCHIVE);
65 CVAR (Color, color, 0x40cf00, CVAR_USERINFO | CVAR_ARCHIVE);
66 CVAR (Int, colorset, 0, CVAR_USERINFO | CVAR_ARCHIVE);
67 CVAR (String, skin, "base", CVAR_USERINFO | CVAR_ARCHIVE);
68 CVAR (Int, team, TEAM_NONE, CVAR_USERINFO | CVAR_ARCHIVE);
69 CVAR (String, gender, "male", CVAR_USERINFO | CVAR_ARCHIVE);
70 CVAR (Bool, neverswitchonpickup, false, CVAR_USERINFO | CVAR_ARCHIVE);
71 CVAR (Float, movebob, 0.25f, CVAR_USERINFO | CVAR_ARCHIVE);
72 CVAR (Float, stillbob, 0.f, CVAR_USERINFO | CVAR_ARCHIVE);
73 CVAR (String, playerclass, "Fighter", CVAR_USERINFO | CVAR_ARCHIVE);
74
75 enum
76 {
77 INFO_Name,
78 INFO_Autoaim,
79 INFO_Color,
80 INFO_Skin,
81 INFO_Team,
82 INFO_Gender,
83 INFO_NeverSwitchOnPickup,
84 INFO_MoveBob,
85 INFO_StillBob,
86 INFO_PlayerClass,
87 INFO_ColorSet,
88 };
89
90 const char *GenderNames[3] = { "male", "female", "other" };
91
92 // Replace \ with %/ and % with %%
D_EscapeUserInfo(const char * str)93 FString D_EscapeUserInfo (const char *str)
94 {
95 FString ret;
96
97 for (; *str != '\0'; ++str)
98 {
99 if (*str == '\\')
100 {
101 ret << '%' << '/';
102 }
103 else if (*str == '%')
104 {
105 ret << '%' << '%';
106 }
107 else
108 {
109 ret << *str;
110 }
111 }
112 return ret;
113 }
114
115 // Replace %/ with \ and %% with %
D_UnescapeUserInfo(const char * str,size_t len)116 FString D_UnescapeUserInfo (const char *str, size_t len)
117 {
118 const char *end = str + len;
119 FString ret;
120
121 while (*str != '\0' && str < end)
122 {
123 if (*str == '%')
124 {
125 if (*(str + 1) == '/')
126 {
127 ret << '\\';
128 str += 2;
129 continue;
130 }
131 else if (*(str + 1) == '%')
132 {
133 str++;
134 }
135 }
136 ret << *str++;
137 }
138 return ret;
139 }
140
D_GenderToInt(const char * gender)141 int D_GenderToInt (const char *gender)
142 {
143 if (!stricmp (gender, "female"))
144 return GENDER_FEMALE;
145 else if (!stricmp (gender, "other") || !stricmp (gender, "cyborg"))
146 return GENDER_NEUTER;
147 else
148 return GENDER_MALE;
149 }
150
D_PlayerClassToInt(const char * classname)151 int D_PlayerClassToInt (const char *classname)
152 {
153 if (PlayerClasses.Size () > 1)
154 {
155 for (unsigned int i = 0; i < PlayerClasses.Size (); ++i)
156 {
157 const PClass *type = PlayerClasses[i].Type;
158
159 if (stricmp (type->Meta.GetMetaString (APMETA_DisplayName), classname) == 0)
160 {
161 return i;
162 }
163 }
164 return -1;
165 }
166 else
167 {
168 return 0;
169 }
170 }
171
D_GetPlayerColor(int player,float * h,float * s,float * v,FPlayerColorSet ** set)172 void D_GetPlayerColor (int player, float *h, float *s, float *v, FPlayerColorSet **set)
173 {
174 userinfo_t *info = &players[player].userinfo;
175 FPlayerColorSet *colorset = NULL;
176 uint32 color;
177 int team;
178
179 if (players[player].mo != NULL)
180 {
181 colorset = P_GetPlayerColorSet(players[player].mo->GetClass()->TypeName, info->GetColorSet());
182 }
183 if (colorset != NULL)
184 {
185 color = GPalette.BaseColors[GPalette.Remap[colorset->RepresentativeColor]];
186 }
187 else
188 {
189 color = info->GetColor();
190 }
191
192 RGBtoHSV (RPART(color)/255.f, GPART(color)/255.f, BPART(color)/255.f,
193 h, s, v);
194
195 if (teamplay && TeamLibrary.IsValidTeam((team = info->GetTeam())) && !Teams[team].GetAllowCustomPlayerColor())
196 {
197 // In team play, force the player to use the team's hue
198 // and adjust the saturation and value so that the team
199 // hue is visible in the final color.
200 float ts, tv;
201 int tcolor = Teams[team].GetPlayerColor ();
202
203 RGBtoHSV (RPART(tcolor)/255.f, GPART(tcolor)/255.f, BPART(tcolor)/255.f,
204 h, &ts, &tv);
205
206 *s = clamp(ts + *s * 0.15f - 0.075f, 0.f, 1.f);
207 *v = clamp(tv + *v * 0.5f - 0.25f, 0.f, 1.f);
208
209 // Make sure not to pass back any colorset in teamplay.
210 colorset = NULL;
211 }
212 if (set != NULL)
213 {
214 *set = colorset;
215 }
216 }
217
218 // Find out which teams are present. If there is only one,
219 // then another team should be chosen at random.
220 //
221 // Otherwise, join whichever team has fewest players. If
222 // teams are tied for fewest players, pick one of those
223 // at random.
224
D_PickRandomTeam(int player)225 void D_PickRandomTeam (int player)
226 {
227 static char teamline[8] = "\\team\\X";
228
229 BYTE *foo = (BYTE *)teamline;
230 teamline[6] = (char)D_PickRandomTeam() + '0';
231 D_ReadUserInfoStrings (player, &foo, teamplay);
232 }
233
D_PickRandomTeam()234 int D_PickRandomTeam ()
235 {
236 for (unsigned int i = 0; i < Teams.Size (); i++)
237 {
238 Teams[i].m_iPresent = 0;
239 Teams[i].m_iTies = 0;
240 }
241
242 int numTeams = 0;
243 int team;
244
245 for (int i = 0; i < MAXPLAYERS; ++i)
246 {
247 if (playeringame[i])
248 {
249 team = players[i].userinfo.GetTeam();
250 if (TeamLibrary.IsValidTeam(team))
251 {
252 if (Teams[team].m_iPresent++ == 0)
253 {
254 numTeams++;
255 }
256 }
257 }
258 }
259
260 if (numTeams < 2)
261 {
262 do
263 {
264 team = pr_pickteam() % Teams.Size();
265 } while (Teams[team].m_iPresent != 0);
266 }
267 else
268 {
269 int lowest = INT_MAX, lowestTie = 0;
270 unsigned int i;
271
272 for (i = 0; i < Teams.Size (); ++i)
273 {
274 if (Teams[i].m_iPresent > 0)
275 {
276 if (Teams[i].m_iPresent < lowest)
277 {
278 lowest = Teams[i].m_iPresent;
279 lowestTie = 0;
280 Teams[0].m_iTies = i;
281 }
282 else if (Teams[i].m_iPresent == lowest)
283 {
284 Teams[++lowestTie].m_iTies = i;
285 }
286 }
287 }
288 if (lowestTie == 0)
289 {
290 team = Teams[0].m_iTies;
291 }
292 else
293 {
294 team = Teams[pr_pickteam() % (lowestTie+1)].m_iTies;
295 }
296 }
297
298 return team;
299 }
300
UpdateTeam(int pnum,int team,bool update)301 static void UpdateTeam (int pnum, int team, bool update)
302 {
303 userinfo_t *info = &players[pnum].userinfo;
304
305 if ((dmflags2 & DF2_NO_TEAM_SWITCH) && (alwaysapplydmflags || deathmatch) && TeamLibrary.IsValidTeam (info->GetTeam()))
306 {
307 Printf ("Team changing has been disabled!\n");
308 return;
309 }
310
311 int oldteam;
312
313 if (!TeamLibrary.IsValidTeam (team))
314 {
315 team = TEAM_NONE;
316 }
317 oldteam = info->GetTeam();
318 team = info->TeamChanged(team);
319
320 if (update && oldteam != team)
321 {
322 if (TeamLibrary.IsValidTeam (team))
323 Printf ("%s joined the %s team\n", info->GetName(), Teams[team].GetName ());
324 else
325 Printf ("%s is now a loner\n", info->GetName());
326 }
327 // Let the player take on the team's color
328 R_BuildPlayerTranslation (pnum);
329 if (StatusBar != NULL && StatusBar->GetPlayer() == pnum)
330 {
331 StatusBar->AttachToPlayer (&players[pnum]);
332 }
333 // Double-check
334 if (!TeamLibrary.IsValidTeam (team))
335 {
336 *static_cast<FIntCVar *>((*info)[NAME_Team]) = TEAM_NONE;
337 }
338 }
339
D_GetFragCount(player_t * player)340 int D_GetFragCount (player_t *player)
341 {
342 const int team = player->userinfo.GetTeam();
343 if (!teamplay || !TeamLibrary.IsValidTeam(team))
344 {
345 return player->fragcount;
346 }
347 else
348 {
349 // Count total frags for this player's team
350 int count = 0;
351
352 for (int i = 0; i < MAXPLAYERS; ++i)
353 {
354 if (playeringame[i] && players[i].userinfo.GetTeam() == team)
355 {
356 count += players[i].fragcount;
357 }
358 }
359 return count;
360 }
361 }
362
D_SetupUserInfo()363 void D_SetupUserInfo ()
364 {
365 int i;
366 userinfo_t *coninfo;
367
368 // Reset everybody's userinfo to a default state.
369 for (i = 0; i < MAXPLAYERS; i++)
370 {
371 players[i].userinfo.Reset();
372 }
373 // Initialize the console player's user info
374 coninfo = &players[consoleplayer].userinfo;
375
376 for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->GetNext())
377 {
378 if ((cvar->GetFlags() & (CVAR_USERINFO|CVAR_IGNORE)) == CVAR_USERINFO)
379 {
380 FBaseCVar **newcvar;
381 FName cvarname(cvar->GetName());
382
383 switch (cvarname.GetIndex())
384 {
385 // Some cvars don't copy their original value directly.
386 case NAME_Team: coninfo->TeamChanged(team); break;
387 case NAME_Skin: coninfo->SkinChanged(skin, players[consoleplayer].CurrentPlayerClass); break;
388 case NAME_Gender: coninfo->GenderChanged(gender); break;
389 case NAME_PlayerClass: coninfo->PlayerClassChanged(playerclass); break;
390 // The rest do.
391 default:
392 newcvar = coninfo->CheckKey(cvarname);
393 (*newcvar)->SetGenericRep(cvar->GetGenericRep(CVAR_String), CVAR_String);
394 break;
395 }
396 }
397 }
398 R_BuildPlayerTranslation(consoleplayer);
399 }
400
Reset()401 void userinfo_t::Reset()
402 {
403 // Clear this player's userinfo.
404 TMapIterator<FName, FBaseCVar *> it(*this);
405 TMap<FName, FBaseCVar *>::Pair *pair;
406
407 while (it.NextPair(pair))
408 {
409 delete pair->Value;
410 }
411 Clear();
412
413 // Create userinfo vars for this player, initialized to their defaults.
414 for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->GetNext())
415 {
416 if ((cvar->GetFlags() & (CVAR_USERINFO|CVAR_IGNORE)) == CVAR_USERINFO)
417 {
418 ECVarType type;
419 FName cvarname(cvar->GetName());
420 FBaseCVar *newcvar;
421
422 // Some cvars have different types for their shadow copies.
423 switch (cvarname.GetIndex())
424 {
425 case NAME_Skin: type = CVAR_Int; break;
426 case NAME_Gender: type = CVAR_Int; break;
427 case NAME_PlayerClass: type = CVAR_Int; break;
428 default: type = cvar->GetRealType(); break;
429 }
430 newcvar = C_CreateCVar(NULL, type, cvar->GetFlags() & CVAR_MOD);
431 newcvar->SetGenericRepDefault(cvar->GetGenericRepDefault(CVAR_String), CVAR_String);
432 Insert(cvarname, newcvar);
433 }
434 }
435 }
436
TeamChanged(int team)437 int userinfo_t::TeamChanged(int team)
438 {
439 if (teamplay && !TeamLibrary.IsValidTeam(team))
440 { // Force players onto teams in teamplay mode
441 team = D_PickRandomTeam();
442 }
443 *static_cast<FIntCVar *>((*this)[NAME_Team]) = team;
444 return team;
445 }
446
SkinChanged(const char * skinname,int playerclass)447 int userinfo_t::SkinChanged(const char *skinname, int playerclass)
448 {
449 int skinnum = R_FindSkin(skinname, playerclass);
450 *static_cast<FIntCVar *>((*this)[NAME_Skin]) = skinnum;
451 return skinnum;
452 }
453
SkinNumChanged(int skinnum)454 int userinfo_t::SkinNumChanged(int skinnum)
455 {
456 *static_cast<FIntCVar *>((*this)[NAME_Skin]) = skinnum;
457 return skinnum;
458 }
459
GenderChanged(const char * gendername)460 int userinfo_t::GenderChanged(const char *gendername)
461 {
462 int gendernum = D_GenderToInt(gendername);
463 *static_cast<FIntCVar *>((*this)[NAME_Gender]) = gendernum;
464 return gendernum;
465 }
466
PlayerClassChanged(const char * classname)467 int userinfo_t::PlayerClassChanged(const char *classname)
468 {
469 int classnum = D_PlayerClassToInt(classname);
470 *static_cast<FIntCVar *>((*this)[NAME_PlayerClass]) = classnum;
471 return classnum;
472 }
473
PlayerClassNumChanged(int classnum)474 int userinfo_t::PlayerClassNumChanged(int classnum)
475 {
476 *static_cast<FIntCVar *>((*this)[NAME_PlayerClass]) = classnum;
477 return classnum;
478 }
479
ColorSetChanged(int setnum)480 int userinfo_t::ColorSetChanged(int setnum)
481 {
482 *static_cast<FIntCVar *>((*this)[NAME_ColorSet]) = setnum;
483 return setnum;
484 }
485
ColorChanged(const char * colorname)486 uint32 userinfo_t::ColorChanged(const char *colorname)
487 {
488 FColorCVar *color = static_cast<FColorCVar *>((*this)[NAME_Color]);
489 assert(color != NULL);
490 UCVarValue val;
491 val.String = const_cast<char *>(colorname);
492 color->SetGenericRep(val, CVAR_String);
493 *static_cast<FIntCVar *>((*this)[NAME_ColorSet]) = -1;
494 return *color;
495 }
496
ColorChanged(uint32 colorval)497 uint32 userinfo_t::ColorChanged(uint32 colorval)
498 {
499 FColorCVar *color = static_cast<FColorCVar *>((*this)[NAME_Color]);
500 assert(color != NULL);
501 UCVarValue val;
502 val.Int = colorval;
503 color->SetGenericRep(val, CVAR_Int);
504 // This version is called by the menu code. Do not implicitly set colorset.
505 return colorval;
506 }
507
D_UserInfoChanged(FBaseCVar * cvar)508 void D_UserInfoChanged (FBaseCVar *cvar)
509 {
510 UCVarValue val;
511 FString escaped_val;
512 char foo[256];
513
514 if (cvar == &autoaim)
515 {
516 if (autoaim < 0.0f)
517 {
518 autoaim = 0.0f;
519 return;
520 }
521 else if (autoaim > 35.0f)
522 {
523 autoaim = 35.f;
524 return;
525 }
526 }
527
528 val = cvar->GetGenericRep (CVAR_String);
529 escaped_val = D_EscapeUserInfo(val.String);
530 if (4 + strlen(cvar->GetName()) + escaped_val.Len() > 256)
531 I_Error ("User info descriptor too big");
532
533 mysnprintf (foo, countof(foo), "\\%s\\%s", cvar->GetName(), escaped_val.GetChars());
534
535 Net_WriteByte (DEM_UINFCHANGED);
536 Net_WriteString (foo);
537 }
538
SetServerVar(char * name,ECVarType type,BYTE ** stream,bool singlebit)539 static const char *SetServerVar (char *name, ECVarType type, BYTE **stream, bool singlebit)
540 {
541 FBaseCVar *var = FindCVar (name, NULL);
542 UCVarValue value;
543
544 if (singlebit)
545 {
546 if (var != NULL)
547 {
548 int bitdata;
549 int mask;
550
551 value = var->GetFavoriteRep (&type);
552 if (type != CVAR_Int)
553 {
554 return NULL;
555 }
556 bitdata = ReadByte (stream);
557 mask = 1 << (bitdata & 31);
558 if (bitdata & 32)
559 {
560 value.Int |= mask;
561 }
562 else
563 {
564 value.Int &= ~mask;
565 }
566 }
567 }
568 else
569 {
570 switch (type)
571 {
572 case CVAR_Bool: value.Bool = ReadByte (stream) ? 1 : 0; break;
573 case CVAR_Int: value.Int = ReadLong (stream); break;
574 case CVAR_Float: value.Float = ReadFloat (stream); break;
575 case CVAR_String: value.String = ReadString (stream); break;
576 default: break; // Silence GCC
577 }
578 }
579
580 if (var)
581 {
582 var->ForceSet (value, type);
583 }
584
585 if (type == CVAR_String)
586 {
587 delete[] value.String;
588 }
589
590 if (var == &teamplay)
591 {
592 // Put players on teams if teamplay turned on
593 for (int i = 0; i < MAXPLAYERS; ++i)
594 {
595 if (playeringame[i])
596 {
597 UpdateTeam (i, players[i].userinfo.GetTeam(), true);
598 }
599 }
600 }
601
602 if (var)
603 {
604 value = var->GetGenericRep (CVAR_String);
605 return value.String;
606 }
607
608 return NULL;
609 }
610
EXTERN_CVAR(Float,sv_gravity)611 EXTERN_CVAR (Float, sv_gravity)
612
613 void D_SendServerInfoChange (const FBaseCVar *cvar, UCVarValue value, ECVarType type)
614 {
615 size_t namelen;
616
617 namelen = strlen (cvar->GetName ());
618
619 Net_WriteByte (DEM_SINFCHANGED);
620 Net_WriteByte ((BYTE)(namelen | (type << 6)));
621 Net_WriteBytes ((BYTE *)cvar->GetName (), (int)namelen);
622 switch (type)
623 {
624 case CVAR_Bool: Net_WriteByte (value.Bool); break;
625 case CVAR_Int: Net_WriteLong (value.Int); break;
626 case CVAR_Float: Net_WriteFloat (value.Float); break;
627 case CVAR_String: Net_WriteString (value.String); break;
628 default: break; // Silence GCC
629 }
630 }
631
D_SendServerFlagChange(const FBaseCVar * cvar,int bitnum,bool set)632 void D_SendServerFlagChange (const FBaseCVar *cvar, int bitnum, bool set)
633 {
634 int namelen;
635
636 namelen = (int)strlen (cvar->GetName ());
637
638 Net_WriteByte (DEM_SINFCHANGEDXOR);
639 Net_WriteByte ((BYTE)namelen);
640 Net_WriteBytes ((BYTE *)cvar->GetName (), namelen);
641 Net_WriteByte (BYTE(bitnum | (set << 5)));
642 }
643
D_DoServerInfoChange(BYTE ** stream,bool singlebit)644 void D_DoServerInfoChange (BYTE **stream, bool singlebit)
645 {
646 const char *value;
647 char name[64];
648 int len;
649 int type;
650
651 len = ReadByte (stream);
652 type = len >> 6;
653 len &= 0x3f;
654 if (len == 0)
655 return;
656 memcpy (name, *stream, len);
657 *stream += len;
658 name[len] = 0;
659
660 if ( (value = SetServerVar (name, (ECVarType)type, stream, singlebit)) && netgame)
661 {
662 Printf ("%s changed to %s\n", name, value);
663 }
664 }
665
userinfosortfunc(const void * a,const void * b)666 static int STACK_ARGS userinfosortfunc(const void *a, const void *b)
667 {
668 TMap<FName, FBaseCVar *>::ConstPair *pair1 = *(TMap<FName, FBaseCVar *>::ConstPair **)a;
669 TMap<FName, FBaseCVar *>::ConstPair *pair2 = *(TMap<FName, FBaseCVar *>::ConstPair **)b;
670 return stricmp(pair1->Key.GetChars(), pair2->Key.GetChars());
671 }
672
namesortfunc(const void * a,const void * b)673 static int STACK_ARGS namesortfunc(const void *a, const void *b)
674 {
675 FName *name1 = (FName *)a;
676 FName *name2 = (FName *)b;
677 return stricmp(name1->GetChars(), name2->GetChars());
678 }
679
D_WriteUserInfoStrings(int pnum,BYTE ** stream,bool compact)680 void D_WriteUserInfoStrings (int pnum, BYTE **stream, bool compact)
681 {
682 if (pnum >= MAXPLAYERS)
683 {
684 WriteByte (0, stream);
685 return;
686 }
687
688 userinfo_t *info = &players[pnum].userinfo;
689 TArray<TMap<FName, FBaseCVar *>::Pair *> userinfo_pairs(info->CountUsed());
690 TMap<FName, FBaseCVar *>::Iterator it(*info);
691 TMap<FName, FBaseCVar *>::Pair *pair;
692 UCVarValue cval;
693
694 // Create a simple array of all userinfo cvars
695 while (it.NextPair(pair))
696 {
697 userinfo_pairs.Push(pair);
698 }
699 // For compact mode, these need to be sorted. Verbose mode doesn't matter.
700 if (compact)
701 {
702 qsort(&userinfo_pairs[0], userinfo_pairs.Size(), sizeof(pair), userinfosortfunc);
703 // Compact mode is signified by starting the string with two backslash characters.
704 // We output one now. The second will be output as part of the first value.
705 *(*stream)++ = '\\';
706 }
707 for (unsigned int i = 0; i < userinfo_pairs.Size(); ++i)
708 {
709 pair = userinfo_pairs[i];
710
711 if (!compact)
712 { // In verbose mode, prepend the cvar's name
713 *stream += sprintf(*((char **)stream), "\\%s", pair->Key.GetChars());
714 }
715 // A few of these need special handling for compatibility reasons.
716 switch (pair->Key.GetIndex())
717 {
718 case NAME_Gender:
719 *stream += sprintf(*((char **)stream), "\\%s",
720 *static_cast<FIntCVar *>(pair->Value) == GENDER_FEMALE ? "female" :
721 *static_cast<FIntCVar *>(pair->Value) == GENDER_NEUTER ? "other" : "male");
722 break;
723
724 case NAME_PlayerClass:
725 *stream += sprintf(*((char **)stream), "\\%s", info->GetPlayerClassNum() == -1 ? "Random" :
726 D_EscapeUserInfo(info->GetPlayerClassType()->Meta.GetMetaString(APMETA_DisplayName)).GetChars());
727 break;
728
729 case NAME_Skin:
730 *stream += sprintf(*((char **)stream), "\\%s", D_EscapeUserInfo(skins[info->GetSkin()].name).GetChars());
731 break;
732
733 default:
734 cval = pair->Value->GetGenericRep(CVAR_String);
735 *stream += sprintf(*((char **)stream), "\\%s", cval.String);
736 break;
737 }
738 }
739 *(*stream)++ = '\0';
740 }
741
D_ReadUserInfoStrings(int pnum,BYTE ** stream,bool update)742 void D_ReadUserInfoStrings (int pnum, BYTE **stream, bool update)
743 {
744 userinfo_t *info = &players[pnum].userinfo;
745 TArray<FName> compact_names(info->CountUsed());
746 FBaseCVar **cvar_ptr;
747 const char *ptr = *((const char **)stream);
748 const char *breakpt;
749 FString value;
750 bool compact;
751 FName keyname;
752 unsigned int infotype = 0;
753
754 if (*ptr++ != '\\')
755 return;
756
757 compact = (*ptr == '\\') ? ptr++, true : false;
758
759 // We need the cvar names in sorted order for compact mode
760 if (compact)
761 {
762 TMap<FName, FBaseCVar *>::Iterator it(*info);
763 TMap<FName, FBaseCVar *>::Pair *pair;
764
765 while (it.NextPair(pair))
766 {
767 compact_names.Push(pair->Key);
768 }
769 qsort(&compact_names[0], compact_names.Size(), sizeof(FName), namesortfunc);
770 }
771
772 if (pnum < MAXPLAYERS)
773 {
774 for (breakpt = ptr; breakpt != NULL; ptr = breakpt + 1)
775 {
776 breakpt = strchr(ptr, '\\');
777
778 if (compact)
779 {
780 // Compact has just the value.
781 if (infotype >= compact_names.Size())
782 { // Too many entries! OMG!
783 break;
784 }
785 keyname = compact_names[infotype++];
786 value = D_UnescapeUserInfo(ptr, breakpt != NULL ? breakpt - ptr : strlen(ptr));
787 }
788 else
789 {
790 // Verbose has both the key name and its value.
791 assert(breakpt != NULL);
792 // A malicious remote machine could invalidate the above assert.
793 if (breakpt == NULL)
794 {
795 break;
796 }
797 const char *valstart = breakpt + 1;
798 if ( (breakpt = strchr (valstart, '\\')) != NULL )
799 {
800 value = D_UnescapeUserInfo(valstart, breakpt - valstart);
801 }
802 else
803 {
804 value = D_UnescapeUserInfo(valstart, strlen(valstart));
805 }
806 keyname = FName(ptr, valstart - ptr - 1, true);
807 }
808
809 // A few of these need special handling.
810 switch (keyname)
811 {
812 case NAME_Gender:
813 info->GenderChanged(value);
814 break;
815
816 case NAME_PlayerClass:
817 info->PlayerClassChanged(value);
818 break;
819
820 case NAME_Skin:
821 info->SkinChanged(value, players[pnum].CurrentPlayerClass);
822 if (players[pnum].mo != NULL)
823 {
824 if (players[pnum].cls != NULL &&
825 !(players[pnum].mo->flags4 & MF4_NOSKIN) &&
826 players[pnum].mo->state->sprite ==
827 GetDefaultByType (players[pnum].cls)->SpawnState->sprite)
828 { // Only change the sprite if the player is using a standard one
829 players[pnum].mo->sprite = skins[info->GetSkin()].sprite;
830 }
831 }
832 // Rebuild translation in case the new skin uses a different range
833 // than the old one.
834 R_BuildPlayerTranslation(pnum);
835 break;
836
837 case NAME_Team:
838 UpdateTeam(pnum, atoi(value), update);
839 break;
840
841 case NAME_Color:
842 info->ColorChanged(value);
843 break;
844
845 default:
846 cvar_ptr = info->CheckKey(keyname);
847 if (cvar_ptr != NULL)
848 {
849 assert(*cvar_ptr != NULL);
850 UCVarValue val;
851 FString oldname;
852
853 if (keyname == NAME_Name)
854 {
855 val = (*cvar_ptr)->GetGenericRep(CVAR_String);
856 oldname = val.String;
857 }
858 val.String = CleanseString(value.LockBuffer());
859 (*cvar_ptr)->SetGenericRep(val, CVAR_String);
860 value.UnlockBuffer();
861 if (keyname == NAME_Name && update && oldname.Compare (value))
862 {
863 Printf("%s is now known as %s\n", oldname.GetChars(), value.GetChars());
864 }
865 }
866 break;
867 }
868 if (keyname == NAME_Color || keyname == NAME_ColorSet)
869 {
870 R_BuildPlayerTranslation(pnum);
871 if (StatusBar != NULL && pnum == StatusBar->GetPlayer())
872 {
873 StatusBar->AttachToPlayer(&players[pnum]);
874 }
875 }
876 }
877 }
878 *stream += strlen (*((char **)stream)) + 1;
879 }
880
ReadCompatibleUserInfo(FArchive & arc,userinfo_t & info)881 void ReadCompatibleUserInfo(FArchive &arc, userinfo_t &info)
882 {
883 char netname[MAXPLAYERNAME + 1];
884 BYTE team;
885 int aimdist, color, colorset, skin, gender;
886 bool neverswitch;
887 //fixed_t movebob, stillbob; These were never serialized!
888 //int playerclass; "
889
890 info.Reset();
891
892 arc.Read(&netname, sizeof(netname));
893 arc << team << aimdist << color << skin << gender << neverswitch << colorset;
894
895 *static_cast<FStringCVar *>(info[NAME_Name]) = netname;
896 *static_cast<FIntCVar *>(info[NAME_Team]) = team;
897 *static_cast<FFloatCVar *>(info[NAME_Autoaim]) = (float)aimdist / ANGLE_1;
898 *static_cast<FIntCVar *>(info[NAME_Skin]) = skin;
899 *static_cast<FIntCVar *>(info[NAME_Gender]) = gender;
900 *static_cast<FBoolCVar *>(info[NAME_NeverSwitchOnPickup]) = neverswitch;
901 *static_cast<FIntCVar *>(info[NAME_ColorSet]) = colorset;
902
903 UCVarValue val;
904 val.Int = color;
905 static_cast<FColorCVar *>(info[NAME_Color])->SetGenericRep(val, CVAR_Int);
906 }
907
WriteUserInfo(FArchive & arc,userinfo_t & info)908 void WriteUserInfo(FArchive &arc, userinfo_t &info)
909 {
910 TMapIterator<FName, FBaseCVar *> it(info);
911 TMap<FName, FBaseCVar *>::Pair *pair;
912 FName name;
913 UCVarValue val;
914 int i;
915
916 while (it.NextPair(pair))
917 {
918 name = pair->Key;
919 arc << name;
920 switch (name.GetIndex())
921 {
922 case NAME_Skin:
923 arc.WriteString(skins[info.GetSkin()].name);
924 break;
925
926 case NAME_PlayerClass:
927 i = info.GetPlayerClassNum();
928 arc.WriteString(i == -1 ? "Random" : PlayerClasses[i].Type->Meta.GetMetaString(APMETA_DisplayName));
929 break;
930
931 default:
932 val = pair->Value->GetGenericRep(CVAR_String);
933 arc.WriteString(val.String);
934 break;
935 }
936 }
937 name = NAME_None;
938 arc << name;
939 }
940
ReadUserInfo(FArchive & arc,userinfo_t & info,FString & skin)941 void ReadUserInfo(FArchive &arc, userinfo_t &info, FString &skin)
942 {
943 FName name;
944 FBaseCVar **cvar;
945 char *str = NULL;
946 UCVarValue val;
947
948 if (SaveVersion < 4253)
949 {
950 ReadCompatibleUserInfo(arc, info);
951 return;
952 }
953
954 info.Reset();
955 skin = NULL;
956 for (arc << name; name != NAME_None; arc << name)
957 {
958 cvar = info.CheckKey(name);
959 arc << str;
960 if (cvar != NULL && *cvar != NULL)
961 {
962 switch (name)
963 {
964 case NAME_Team: info.TeamChanged(atoi(str)); break;
965 case NAME_Skin: skin = str; break; // Caller must call SkinChanged() once current calss is known
966 case NAME_PlayerClass: info.PlayerClassChanged(str); break;
967 default:
968 val.String = str;
969 (*cvar)->SetGenericRep(val, CVAR_String);
970 break;
971 }
972 }
973 }
974 if (str != NULL)
975 {
976 delete[] str;
977 }
978 }
979
CCMD(playerinfo)980 CCMD (playerinfo)
981 {
982 if (argv.argc() < 2)
983 {
984 int i;
985
986 for (i = 0; i < MAXPLAYERS; i++)
987 {
988 if (playeringame[i])
989 {
990 Printf("%d. %s\n", i, players[i].userinfo.GetName());
991 }
992 }
993 }
994 else
995 {
996 int i = atoi(argv[1]);
997
998 if (i < 0 || i >= MAXPLAYERS)
999 {
1000 Printf("Bad player number\n");
1001 return;
1002 }
1003 userinfo_t *ui = &players[i].userinfo;
1004
1005 if (!playeringame[i])
1006 {
1007 Printf(TEXTCOLOR_ORANGE "Player %d is not in the game\n", i);
1008 return;
1009 }
1010
1011 // Print special info
1012 Printf("%20s: %s\n", "Name", ui->GetName());
1013 Printf("%20s: %s (%d)\n", "Team", ui->GetTeam() == TEAM_NONE ? "None" : Teams[ui->GetTeam()].GetName(), ui->GetTeam());
1014 Printf("%20s: %s (%d)\n", "Skin", skins[ui->GetSkin()].name, ui->GetSkin());
1015 Printf("%20s: %s (%d)\n", "Gender", GenderNames[ui->GetGender()], ui->GetGender());
1016 Printf("%20s: %s (%d)\n", "PlayerClass",
1017 ui->GetPlayerClassNum() == -1 ? "Random" : ui->GetPlayerClassType()->Meta.GetMetaString (APMETA_DisplayName),
1018 ui->GetPlayerClassNum());
1019
1020 // Print generic info
1021 TMapIterator<FName, FBaseCVar *> it(*ui);
1022 TMap<FName, FBaseCVar *>::Pair *pair;
1023
1024 while (it.NextPair(pair))
1025 {
1026 if (pair->Key != NAME_Name && pair->Key != NAME_Team && pair->Key != NAME_Skin &&
1027 pair->Key != NAME_Gender && pair->Key != NAME_PlayerClass)
1028 {
1029 UCVarValue val = pair->Value->GetGenericRep(CVAR_String);
1030 Printf("%20s: %s\n", pair->Key.GetChars(), val.String);
1031 }
1032 }
1033 if (argv.argc() > 2)
1034 {
1035 PrintMiscActorInfo(players[i].mo);
1036 }
1037 }
1038 }
1039
~userinfo_t()1040 userinfo_t::~userinfo_t()
1041 {
1042 TMapIterator<FName, FBaseCVar *> it(*this);
1043 TMap<FName, FBaseCVar *>::Pair *pair;
1044
1045 while (it.NextPair(pair))
1046 {
1047 delete pair->Value;
1048 }
1049 this->Clear();
1050 }
1051