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