qzdoom/src/d_netinfo.cpp

773 lines
18 KiB
C++
Raw Normal View History

/*
** d_netinfo.cpp
** Manages transport of user and "server" cvars across a network
**
**---------------------------------------------------------------------------
** Copyright 1998-2005 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "doomtype.h"
#include "doomdef.h"
#include "doomstat.h"
#include "d_netinf.h"
#include "d_net.h"
#include "d_protocol.h"
#include "c_dispatch.h"
#include "v_palette.h"
#include "v_video.h"
#include "i_system.h"
#include "r_draw.h"
#include "r_state.h"
#include "sbar.h"
#include "gi.h"
#include "m_random.h"
static FRandom pr_pickteam ("PickRandomTeam");
extern BOOL st_firsttime;
EXTERN_CVAR (Bool, teamplay)
CVAR (Float, autoaim, 5000.f, CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (String, name, "Player", CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (Color, color, 0x40cf00, CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (String, skin, "base", CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (Int, team, 255, CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (String, gender, "male", CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (Bool, neverswitchonpickup, false, CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (Float, movebob, 0.25f, CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (Float, stillbob, 0.f, CVAR_USERINFO | CVAR_ARCHIVE);
CVAR (String, playerclass, "Fighter", CVAR_USERINFO | CVAR_ARCHIVE);
enum
{
INFO_Name,
INFO_Autoaim,
INFO_Color,
INFO_Skin,
INFO_Team,
INFO_Gender,
INFO_NeverSwitchOnPickup,
INFO_MoveBob,
INFO_StillBob,
INFO_PlayerClass,
};
const char *PlayerClassNames[4] =
{
"Random", "Fighter", "Cleric", "Mage"
};
const char *TeamNames[NUM_TEAMS] =
{
"Red", "Blue", "Green", "Gold"
};
float TeamHues[NUM_TEAMS] =
{
0.f, 240.f, 120.f, 60.f
};
const char *GenderNames[3] = { "male", "female", "other" };
static const char *UserInfoStrings[] =
{
"name",
"autoaim",
"color",
"skin",
"team",
"gender",
"neverswitchonpickup",
"movebob",
"stillbob",
"playerclass",
NULL
};
int D_GenderToInt (const char *gender)
{
if (!stricmp (gender, "female"))
return GENDER_FEMALE;
else if (!stricmp (gender, "other") || !stricmp (gender, "cyborg"))
return GENDER_NEUTER;
else
return GENDER_MALE;
}
static int D_PlayerClassToInt (const char *classname)
{
for (int i = 1; i <= 3; ++i)
{
if (stricmp (PlayerClassNames[i], classname) == 0)
{
return i-1;
}
}
return -1;
}
void D_GetPlayerColor (int player, float *h, float *s, float *v)
{
int color = players[player].userinfo.color;
RGBtoHSV (RPART(color)/255.f, GPART(color)/255.f, BPART(color)/255.f,
h, s, v);
if (teamplay && players[player].userinfo.team < NUM_TEAMS)
{
// In team play, force the player to use the team's hue
// and adjust the saturation and value so that the team
// hue is visible in the final color.
*h = TeamHues[players[player].userinfo.team];
if (gameinfo.gametype == GAME_Doom)
{
*s = *s*0.15f+0.8f;
*v = *v*0.5f+0.4f;
}
else
{
// I'm not really happy with the red team color in Heretic...
*s = team == 0 ? 0.6f : 0.8f;
*v = *v*0.4f+0.3f;
}
}
}
// Find out which teams are present. If there is only one,
// then another team should be chosen at random.
//
// Otherwise, join whichever team has fewest players. If
// teams are tied for fewest players, pick one of those
// at random.
void D_PickRandomTeam (int player)
{
static char teamline[8] = "\\team\\X";
byte *foo = (byte *)teamline;
teamline[6] = D_PickRandomTeam() + '0';
D_ReadUserInfoStrings (player, &foo, teamplay);
}
int D_PickRandomTeam ()
{
int teamPresent[NUM_TEAMS] = { 0 };
int numTeams = 0;
int team;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i])
{
if (players[i].userinfo.team < NUM_TEAMS)
{
if (teamPresent[players[i].userinfo.team]++ == 0)
{
numTeams++;
}
}
}
}
if (numTeams < 2)
{
do
{
team = pr_pickteam() % NUM_TEAMS;
} while (teamPresent[team] != 0);
}
else
{
int lowest = INT_MAX, lowestTie = 0, i;
int ties[NUM_TEAMS];
for (i = 0; i < NUM_TEAMS; ++i)
{
if (teamPresent[i] > 0)
{
if (teamPresent[i] < lowest)
{
lowest = teamPresent[i];
lowestTie = 0;
ties[0] = i;
}
else if (teamPresent[i] == lowest)
{
ties[++lowestTie] = i;
}
}
}
if (lowestTie == 0)
{
team = ties[0];
}
else
{
team = ties[pr_pickteam() % (lowestTie+1)];
}
}
return team;
}
static void UpdateTeam (int pnum, int team, bool update)
{
userinfo_t *info = &players[pnum].userinfo;
int oldteam;
oldteam = info->team;
info->team = team;
if (teamplay && info->team >= NUM_TEAMS)
{ // Force players onto teams in teamplay mode
info->team = D_PickRandomTeam ();
}
if (update && oldteam != info->team)
{
if (info->team < NUM_TEAMS)
Printf ("%s joined the %s team\n", info->netname, TeamNames[info->team]);
else
Printf ("%s is now a loner\n", info->netname);
}
// Let the player take on the team's color
R_BuildPlayerTranslation (pnum);
if (StatusBar != NULL && StatusBar->GetPlayer() == pnum)
{
StatusBar->AttachToPlayer (&players[pnum]);
}
if ((unsigned)info->team >= NUM_TEAMS)
info->team = TEAM_None;
}
int D_GetFragCount (player_t *player)
{
if (!teamplay || player->userinfo.team >= NUM_TEAMS)
{
return player->fragcount;
}
else
{
// Count total frags for this player's team
const int team = player->userinfo.team;
int count = 0;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i] && players[i].userinfo.team == team)
{
count += players[i].fragcount;
}
}
return count;
}
}
void D_SetupUserInfo ()
{
int i;
userinfo_t *coninfo = &players[consoleplayer].userinfo;
for (i = 0; i < MAXPLAYERS; i++)
memset (&players[i].userinfo, 0, sizeof(userinfo_t));
strncpy (coninfo->netname, name, MAXPLAYERNAME);
if (teamplay && team >= NUM_TEAMS)
{
coninfo->team = D_PickRandomTeam ();
}
else
{
coninfo->team = team;
}
if (autoaim > 35.f || autoaim < 0.f)
{
coninfo->aimdist = ANGLE_1*35;
}
else
{
coninfo->aimdist = abs ((int)(autoaim * (float)ANGLE_1));
}
coninfo->color = color;
coninfo->skin = R_FindSkin (skin);
coninfo->gender = D_GenderToInt (gender);
coninfo->neverswitch = neverswitchonpickup;
coninfo->MoveBob = (fixed_t)(65536.f * movebob);
coninfo->StillBob = (fixed_t)(65536.f * stillbob);
coninfo->PlayerClass = D_PlayerClassToInt (playerclass);
R_BuildPlayerTranslation (consoleplayer);
}
void D_UserInfoChanged (FBaseCVar *cvar)
{
UCVarValue val;
char foo[256];
if (cvar == &autoaim)
{
if (autoaim < 0.0f)
{
autoaim = 0.0f;
return;
}
else if (autoaim > 5000.0f)
{
autoaim = 5000.f;
return;
}
}
val = cvar->GetGenericRep (CVAR_String);
if (4 + strlen (cvar->GetName ()) + strlen (val.String) > 256)
I_Error ("User info descriptor too big");
sprintf (foo, "\\%s\\%s", cvar->GetName (), val.String);
Net_WriteByte (DEM_UINFCHANGED);
Net_WriteString (foo);
}
static const char *SetServerVar (char *name, ECVarType type, byte **stream, bool singlebit)
{
FBaseCVar *var = FindCVar (name, NULL);
UCVarValue value;
if (singlebit)
{
if (var != NULL)
{
int bitdata;
int mask;
value = var->GetFavoriteRep (&type);
if (type != CVAR_Int)
{
return NULL;
}
bitdata = ReadByte (stream);
mask = 1 << (bitdata & 31);
if (bitdata & 32)
{
value.Int |= mask;
}
else
{
value.Int &= ~mask;
}
}
}
else
{
switch (type)
{
case CVAR_Bool: value.Bool = ReadByte (stream) ? 1 : 0; break;
case CVAR_Int: value.Int = ReadLong (stream); break;
case CVAR_Float: value.Float = ReadFloat (stream); break;
case CVAR_String: value.String = ReadString (stream); break;
default: break; // Silence GCC
}
}
if (var)
{
var->ForceSet (value, type);
}
if (type == CVAR_String)
{
delete[] value.String;
}
if (var == &teamplay)
{
// Put players on teams if teamplay turned on
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i])
{
UpdateTeam (i, players[i].userinfo.team, true);
}
}
}
if (var)
{
value = var->GetGenericRep (CVAR_String);
return value.String;
}
return NULL;
}
EXTERN_CVAR (Float, sv_gravity)
void D_SendServerInfoChange (const FBaseCVar *cvar, UCVarValue value, ECVarType type)
{
size_t namelen;
namelen = strlen (cvar->GetName ());
Net_WriteByte (DEM_SINFCHANGED);
Net_WriteByte ((BYTE)(namelen | (type << 6)));
Net_WriteBytes ((BYTE *)cvar->GetName (), (int)namelen);
switch (type)
{
case CVAR_Bool: Net_WriteByte (value.Bool); break;
case CVAR_Int: Net_WriteLong (value.Int); break;
case CVAR_Float: Net_WriteFloat (value.Float); break;
case CVAR_String: Net_WriteString (value.String); break;
default: break; // Silence GCC
}
}
void D_SendServerFlagChange (const FBaseCVar *cvar, int bitnum, bool set)
{
int namelen;
namelen = (int)strlen (cvar->GetName ());
Net_WriteByte (DEM_SINFCHANGEDXOR);
Net_WriteByte (namelen);
Net_WriteBytes ((BYTE *)cvar->GetName (), namelen);
Net_WriteByte (bitnum | (set << 5));
}
void D_DoServerInfoChange (byte **stream, bool singlebit)
{
const char *value;
char name[64];
int len;
int type;
len = ReadByte (stream);
type = len >> 6;
len &= 0x3f;
if (len == 0)
return;
memcpy (name, *stream, len);
*stream += len;
name[len] = 0;
if ( (value = SetServerVar (name, (ECVarType)type, stream, singlebit)) && netgame)
{
Printf ("%s changed to %s\n", name, value);
}
}
void D_WriteUserInfoStrings (int i, byte **stream, bool compact)
{
if (i >= MAXPLAYERS)
{
WriteByte (0, stream);
}
else
{
userinfo_t *info = &players[i].userinfo;
if (!compact)
{
sprintf (*((char **)stream),
"\\name\\%s"
"\\autoaim\\%g"
"\\color\\%x %x %x"
"\\skin\\%s"
"\\team\\%d"
"\\gender\\%s"
"\\neverswitchonpickup\\%d"
"\\movebob\\%g"
"\\stillbob\\%g"
"\\playerclass\\%s"
,
info->netname,
(double)info->aimdist / (float)ANGLE_1,
RPART(info->color), GPART(info->color), BPART(info->color),
skins[info->skin].name, info->team,
info->gender == GENDER_FEMALE ? "female" :
info->gender == GENDER_NEUTER ? "other" : "male",
info->neverswitch,
(float)(info->MoveBob) / 65536.f,
(float)(info->StillBob) / 65536.f,
PlayerClassNames[info->PlayerClass+1]
);
}
else
{
sprintf (*((char **)stream),
"\\"
"\\%s" // name
"\\%g" // autoaim
"\\%x %x %x" // color
"\\%s" // skin
"\\%d" // team
"\\%s" // gender
"\\%d" // neverswitchonpickup
"\\%g" // movebob
"\\%g" // stillbob
"\\%s" // playerclass
,
info->netname,
(double)info->aimdist / (float)ANGLE_1,
RPART(info->color), GPART(info->color), BPART(info->color),
skins[info->skin].name,
info->team,
info->gender == GENDER_FEMALE ? "female" :
info->gender == GENDER_NEUTER ? "other" : "male",
info->neverswitch,
(float)(info->MoveBob) / 65536.f,
(float)(info->StillBob) / 65536.f,
PlayerClassNames[info->PlayerClass+1]
);
}
}
*stream += strlen (*((char **)stream)) + 1;
}
void D_ReadUserInfoStrings (int i, byte **stream, bool update)
{
userinfo_t *info = &players[i].userinfo;
char *ptr = *((char **)stream);
char *breakpt;
char *value;
bool compact;
int infotype = -1;
if (*ptr++ != '\\')
return;
compact = (*ptr == '\\') ? ptr++, true : false;
if (i < MAXPLAYERS)
{
for (;;)
{
breakpt = strchr (ptr, '\\');
if (breakpt != NULL)
*breakpt = 0;
if (compact)
{
value = ptr;
infotype++;
}
else
{
value = breakpt + 1;
if ( (breakpt = strchr (value, '\\')) )
*breakpt = 0;
int j = 0;
while (UserInfoStrings[j] && stricmp (UserInfoStrings[j], ptr) != 0)
j++;
if (UserInfoStrings[j] == NULL)
{
infotype = -1;
}
else
{
infotype = j;
}
}
switch (infotype)
{
case INFO_Autoaim: {
double angles;
angles = atof (value);
if (angles > 35.f || angles < 0.f)
{
info->aimdist = ANGLE_1*35;
}
else
{
info->aimdist = abs ((int)(angles * (float)ANGLE_1));
}
}
break;
case INFO_Name:
{
char oldname[MAXPLAYERNAME+1];
strncpy (oldname, info->netname, MAXPLAYERNAME);
oldname[MAXPLAYERNAME] = 0;
strncpy (info->netname, value, MAXPLAYERNAME);
info->netname[MAXPLAYERNAME] = 0;
if (update && strcmp (oldname, info->netname) != 0)
{
Printf ("%s is now known as %s\n", oldname, info->netname);
}
}
break;
case INFO_Team:
UpdateTeam (i, atoi(value), update);
break;
case INFO_Color:
info->color = V_GetColorFromString (NULL, value);
R_BuildPlayerTranslation (i);
if (StatusBar != NULL && i == StatusBar->GetPlayer())
{
StatusBar->AttachToPlayer (&players[i]);
}
break;
case INFO_Skin:
info->skin = R_FindSkin (value);
if (gameinfo.gametype != GAME_Hexen)
{
if (players[i].mo != NULL && gameinfo.gametype != GAME_Hexen)
{
if (players[i].cls != NULL &&
players[i].mo->state->sprite.index ==
GetDefaultByType (players[i].cls)->SpawnState->sprite.index)
{ // Only change the sprite if the player is using a standard one
players[i].mo->sprite = skins[info->skin].sprite;
players[i].mo->xscale = players[i].mo->yscale = skins[info->skin].scale;
}
}
players[i].skin = &skins[info->skin];
// Rebuild translation in case the new skin uses a different range
// than the old one.
R_BuildPlayerTranslation (i);
}
if (StatusBar != NULL && i == StatusBar->GetPlayer())
{
StatusBar->SetFace (&skins[info->skin]);
}
break;
case INFO_Gender:
info->gender = D_GenderToInt (value);
break;
case INFO_NeverSwitchOnPickup:
if (*value >= '0' && *value <= '9')
{
info->neverswitch = atoi (value) ? true : false;
}
else if (stricmp (value, "true") == 0)
{
info->neverswitch = 1;
}
else
{
info->neverswitch = 0;
}
break;
case INFO_MoveBob:
info->MoveBob = (fixed_t)(atof (value) * 65536.f);
break;
case INFO_StillBob:
info->StillBob = (fixed_t)(atof (value) * 65536.f);
break;
case INFO_PlayerClass:
info->PlayerClass = D_PlayerClassToInt (value);
break;
default:
break;
}
if (!compact)
{
*(value - 1) = '\\';
}
if (breakpt)
{
*breakpt = '\\';
ptr = breakpt + 1;
}
else
{
break;
}
}
}
*stream += strlen (*((char **)stream)) + 1;
}
FArchive &operator<< (FArchive &arc, userinfo_t &info)
{
if (arc.IsStoring ())
{
arc.Write (&info.netname, sizeof(info.netname));
arc.Write (&info.team, sizeof(info.team));
}
else
{
arc.Read (&info.netname, sizeof(info.netname));
arc.Read (&info.team, sizeof(info.team));
}
arc << info.aimdist << info.color << info.skin << info.gender << info.neverswitch;
return arc;
}
CCMD (playerinfo)
{
if (argv.argc() < 2)
{
int i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
Printf ("%d. %s\n", i, players[i].userinfo.netname);
}
}
}
else
{
int i = atoi (argv[1]);
Printf ("Name: %s\n", players[i].userinfo.netname);
Printf ("Team: %d\n", players[i].userinfo.team);
Printf ("Aimdist: %d\n", players[i].userinfo.aimdist);
Printf ("Color: %06x\n", players[i].userinfo.color);
Printf ("Skin: %d\n", players[i].userinfo.skin);
Printf ("Gender: %d\n", players[i].userinfo.gender);
Printf ("NeverSwitch: %d\n", players[i].userinfo.neverswitch);
Printf ("MoveBob: %g\n", players[i].userinfo.MoveBob/65536.f);
Printf ("StillBob: %g\n", players[i].userinfo.StillBob/65536.f);
Printf ("PlayerClass: %d\n", players[i].userinfo.PlayerClass);
}
}