nuclide/src/shared/player.qc

551 lines
13 KiB
Plaintext

/*
* Copyright (c) 2016-2021 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef CLIENT
/*
=================
base_player::ReceiveEntity
Receive the generic client attributes from the server.
If you want to override this, do not call this
at the top of player::ReceiveEntity
=================
*/
void
base_player::ReceiveEntity(float new, float fl)
{
/* store which input sequence we're on, this helps us
* later when we run prediction again between last/latest
* servercommandframe */
sequence = servercommandframe;
/* HACK: we need to make this more reliable */
if (fl == UPDATE_ALL) {
/* we respawned */
gravity = 1.0f;
}
if (fl & PLAYER_MODELINDEX)
modelindex = readshort();
if (fl & PLAYER_ORIGIN) {
origin[0] = readcoord();
origin[1] = readcoord();
}
if (fl & PLAYER_ORIGIN_Z)
origin[2] = readcoord();
if (fl & PLAYER_ANGLES_X) {
v_angle[0] = pitch = readshort() / (32767 / 360);
v_angle[1] = pitch = readshort() / (32767 / 360);
v_angle[2] = pitch = readshort() / (32767 / 360);
}
if (fl & PLAYER_ANGLES_Y) {
angles[0] = readshort() / (32767 / 360);
angles[1] = readshort() / (32767 / 360);
angles[1] = readshort() / (32767 / 360);
}
if (fl & PLAYER_COLORMAP)
colormap = readbyte();
if (fl & PLAYER_VELOCITY) {
velocity[0] = readcoord();
velocity[1] = readcoord();
}
if (fl & PLAYER_VELOCITY_Z)
velocity[2] = readcoord();
if (fl & PLAYER_FLAGS) {
flags = readfloat();
gflags = readfloat();
pmove_flags = readfloat();
/* mainly used for other players receiving us */
if (flags & FL_CROUCHING)
setsize(self, PHY_HULL_CROUCHED_MIN, PHY_HULL_CROUCHED_MAX);
else
setsize(self, PHY_HULL_MIN, PHY_HULL_MAX);
}
if (fl & PLAYER_WEAPON)
activeweapon = readbyte();
if (fl & PLAYER_ITEMS)
g_items = (__variant)readfloat();
if (fl & PLAYER_HEALTH)
health = readbyte();
if (fl & PLAYER_ARMOR)
armor = readbyte();
if (fl & PLAYER_MOVETYPE)
movetype = readbyte();
if (fl & PLAYER_VIEWOFS)
view_ofs[2] = readfloat();
/* TO OPTIMISE */
teleport_time = readfloat();
viewzoom = readfloat();
weapontime = readfloat();
w_attack_next = readfloat();
w_idle_next = readfloat();
punchangle[0] = readfloat();
punchangle[1] = readfloat();
punchangle[2] = readfloat();
vehicle = findfloat(world, ::entnum, readentitynum());
}
/*
=================
base_player::PredictPreFrame
Save the state of the last server-confirmed attributes.
If you want to override this, do not call this
at the top of player::PredictPreFrame
=================
*/
void
base_player::PredictPreFrame(void)
{
/* base player attributes/fields we're going to roll back */
SAVE_STATE(modelindex);
SAVE_STATE(origin);
SAVE_STATE(angles);
SAVE_STATE(v_angle);
SAVE_STATE(colormap);
SAVE_STATE(velocity);
SAVE_STATE(flags);
SAVE_STATE(gflags);
SAVE_STATE(pmove_flags);
SAVE_STATE(activeweapon);
SAVE_STATE(g_items);
SAVE_STATE(health);
SAVE_STATE(armor);
SAVE_STATE(movetype);
SAVE_STATE(view_ofs);
/* TO OPTIMISE */
SAVE_STATE(teleport_time);
SAVE_STATE(viewzoom);
SAVE_STATE(weapontime);
SAVE_STATE(w_attack_next);
SAVE_STATE(w_idle_next);
SAVE_STATE(punchangle);
SAVE_STATE(vehicle);
}
/*
=================
base_player::PredictPostFrame
After running prediction on the client, roll back the values
to the server's confirmed saved attributes from PredictPreFrame.
If you want to override this, do not call this
at the top of player::PredictPostFrame
=================
*/
void
base_player::PredictPostFrame(void)
{
/* finally roll the values back */
ROLL_BACK(modelindex);
ROLL_BACK(origin);
ROLL_BACK(angles);
ROLL_BACK(v_angle);
ROLL_BACK(colormap);
ROLL_BACK(velocity);
ROLL_BACK(flags);
ROLL_BACK(gflags);
ROLL_BACK(pmove_flags);
ROLL_BACK(activeweapon);
ROLL_BACK(g_items);
ROLL_BACK(health);
ROLL_BACK(armor);
ROLL_BACK(movetype);
ROLL_BACK(view_ofs);
/* TO OPTIMISE */
ROLL_BACK(teleport_time);
ROLL_BACK(viewzoom);
ROLL_BACK(weapontime);
ROLL_BACK(w_attack_next);
ROLL_BACK(w_idle_next);
ROLL_BACK(punchangle);
ROLL_BACK(vehicle);
}
#else
void
base_player::Save(float handle)
{
SaveFloat(handle, "health", health);
SaveFloat(handle, "armor", armor);
SaveFloat(handle, "modelindex", modelindex);
SaveVector(handle, "origin", origin);
SaveVector(handle, "velocity", velocity);
SaveVector(handle, "angles", angles);
SaveFloat(handle, "colormap", colormap);
SaveFloat(handle, "flags", flags);
SaveFloat(handle, "gflags", gflags);
SaveFloat(handle, "viewzoom", viewzoom);
SaveVector(handle, "view_ofs", view_ofs);
SaveVector(handle, "v_angle", v_angle);
SaveVector(handle, "punchangle", punchangle);
SaveFloat(handle, "movetype", movetype);
SaveFloat(handle, "pmove_flags", pmove_flags);
SaveFloat(handle, "w_attack_next", w_attack_next);
SaveFloat(handle, "w_idle_next", w_idle_next);
SaveFloat(handle, "teleport_time", teleport_time);
SaveFloat(handle, "weapontime", weapontime);
SaveInt(handle, "g_items", g_items);
SaveFloat(handle, "activeweapon", activeweapon);
SaveFloat(handle, "vehicle", num_for_edict(vehicle));
}
void
base_player::Restore(string strKey, string strValue)
{
switch (strKey) {
case "health":
health = ReadFloat(strValue);
break;
case "armor":
armor = ReadFloat(strValue);
break;
case "modelindex":
modelindex = ReadFloat(strValue);
break;
case "origin":
origin = ReadVector(strValue);
break;
case "velocity":
velocity = ReadVector(strValue);
break;
case "angles":
angles = ReadVector(strValue);
break;
case "colormap":
colormap = ReadFloat(strValue);
break;
case "flags":
flags = ReadFloat(strValue);
break;
case "gflags":
gflags = ReadFloat(strValue);
break;
case "view_ofs":
view_ofs = ReadVector(strValue);
break;
case "v_angle":
v_angle = ReadVector(strValue);
break;
case "punchangle":
punchangle = ReadVector(strValue);
break;
case "movetype":
movetype = ReadFloat(strValue);
break;
case "pmove_flags":
pmove_flags = ReadFloat(strValue);
break;
case "w_attack_next":
w_attack_next = ReadFloat(strValue);
break;
case "w_idle_next":
w_idle_next = ReadFloat(strValue);
break;
case "teleport_time":
teleport_time = ReadFloat(strValue);
break;
case "weapontime":
weapontime = ReadFloat(strValue);
break;
case "g_items":
g_items = ReadInt(strValue);
break;
case "activeweapon":
activeweapon = ReadFloat(strValue);
break;
case "vehicle":
vehicle = edict_num(ReadFloat(strValue));
break;
default:
super::Restore(strKey, strValue);
}
}
/*
=================
base_player::Respawn
it'd be pretty unfortunate if 'sv respawn_ents' or something called this
=================
*/
void
base_player::Respawn(void)
{
/* make sure nothing happens here */
}
/*
=================*
base_player::MakeTempSpectator
This is what dead players in round matches become, or when we spawn
for the first time before selecting a loadout or something.
=================
*/
void
base_player::MakeTempSpectator(void)
{
classname = "player";
flags = FL_CLIENT;
max_health = health = 0;
armor = 0;
g_items = 0;
activeweapon = 0;
effects = 0;
alpha = 0.0f;
solid = SOLID_NOT;
movetype = MOVETYPE_NOCLIP;
maxspeed = 250;
takedamage = DAMAGE_NO;
forceinfokey(this, "*spec", "2");
}
/*
=================*
base_player::MakeTempSpectator
This is what dead players in round matches become, or when we spawn
for the first time before selecting a loadout or something.
=================
*/
void
base_player::MakePlayer(void)
{
classname = "player";
flags = FL_CLIENT;
health = max_health = 100;
armor = 0;
g_items = 0;
activeweapon = 0;
effects = 0;
alpha = 1.0f;
takedamage = DAMAGE_YES;
solid = SOLID_SLIDEBOX;
movetype = MOVETYPE_WALK;
takedamage = DAMAGE_YES;
forceinfokey(this, "*spec", "0");
viewzoom = 1.0;
vehicle = __NULL__;
velocity = [0,0,0];
gravity = __NULL__;
SendFlags = UPDATE_ALL;
customphysics = Empty;
iBleeds = TRUE;
forceinfokey(this, "*deaths", ftos(deaths));
setsize(this, VEC_HULL_MIN, VEC_HULL_MAX);
}
/*
=================
base_player::EvaluateEntity
Check which attributes have changed and flag the ones that did.
If you want to override this, do not call this
at the top of player::EvaluateEntity
=================
*/
void
base_player::EvaluateEntity(void)
{
SetSendFlags(PLAYER_KEEPALIVE);
if (ATTR_CHANGED(modelindex))
SetSendFlags(PLAYER_MODELINDEX);
if (VEC_CHANGED(origin, 0))
SetSendFlags(PLAYER_ORIGIN);
if (VEC_CHANGED(origin, 1))
SetSendFlags(PLAYER_ORIGIN);
if (VEC_CHANGED(origin, 2))
SetSendFlags(PLAYER_ORIGIN_Z);
if (VEC_CHANGED(v_angle, 0) || VEC_CHANGED(v_angle, 1) || VEC_CHANGED(v_angle, 2))
SetSendFlags(PLAYER_ANGLES_X);
if (VEC_CHANGED(angles, 0) || VEC_CHANGED(angles, 1) || VEC_CHANGED(angles, 2))
SetSendFlags(PLAYER_ANGLES_Y);
if (ATTR_CHANGED(colormap))
SetSendFlags(PLAYER_COLORMAP);
if (VEC_CHANGED(velocity, 0))
SetSendFlags(PLAYER_VELOCITY);
if (VEC_CHANGED(velocity, 1))
SetSendFlags(PLAYER_VELOCITY);
if (VEC_CHANGED(velocity, 2))
SetSendFlags(PLAYER_VELOCITY_Z);
if (ATTR_CHANGED(flags))
SetSendFlags(PLAYER_FLAGS);
if (ATTR_CHANGED(gflags))
SetSendFlags(PLAYER_FLAGS);
if (ATTR_CHANGED(pmove_flags))
SetSendFlags(PLAYER_FLAGS);
if (ATTR_CHANGED(activeweapon))
SetSendFlags(PLAYER_WEAPON);
if (ATTR_CHANGED(g_items))
SetSendFlags(PLAYER_ITEMS);
if (ATTR_CHANGED(health))
SetSendFlags(PLAYER_HEALTH);
if (ATTR_CHANGED(armor))
SetSendFlags(PLAYER_ARMOR);
if (ATTR_CHANGED(movetype))
SetSendFlags(PLAYER_MOVETYPE);
if (ATTR_CHANGED(view_ofs))
SetSendFlags(PLAYER_VIEWOFS);
SAVE_STATE(modelindex);
SAVE_STATE(origin);
SAVE_STATE(angles);
SAVE_STATE(colormap);
SAVE_STATE(velocity);
SAVE_STATE(flags);
SAVE_STATE(gflags);
SAVE_STATE(pmove_flags);
SAVE_STATE(activeweapon);
SAVE_STATE(g_items);
SAVE_STATE(health);
SAVE_STATE(armor);
SAVE_STATE(movetype);
SAVE_STATE(view_ofs);
/* TO OPTIMISE */
SAVE_STATE(teleport_time);
SAVE_STATE(viewzoom);
SAVE_STATE(weapontime);
SAVE_STATE(w_attack_next);
SAVE_STATE(w_idle_next);
SAVE_STATE(punchangle);
SAVE_STATE(vehicle);
}
/*
=================
base_player::SendEntity
Network any attributes that have been flagged for networking.
If you want to override this, do not call this
at the top of player::SendEntity
=================
*/
float
base_player::SendEntity(entity ePEnt, float fChanged)
{
/* really trying to get our moneys worth with 23 bits of mantissa */
if (fChanged & PLAYER_MODELINDEX)
WriteShort(MSG_ENTITY, modelindex);
/* if origin[0] changes, it's very likely [1] changes too, since
* we rarely ever walk in a straight line on the world grid */
if (fChanged & PLAYER_ORIGIN) {
WriteCoord(MSG_ENTITY, origin[0]);
WriteCoord(MSG_ENTITY, origin[1]);
}
/* the height doesn't change as much */
if (fChanged & PLAYER_ORIGIN_Z)
WriteCoord(MSG_ENTITY, origin[2]);
if (fChanged & PLAYER_ANGLES_X) {
WriteShort(MSG_ENTITY, v_angle[0] * 32767 / 360);
WriteShort(MSG_ENTITY, v_angle[1] * 32767 / 360);
WriteShort(MSG_ENTITY, v_angle[2] * 32767 / 360);
}
if (fChanged & PLAYER_ANGLES_Y) {
WriteShort(MSG_ENTITY, angles[0] * 32767 / 360);
WriteShort(MSG_ENTITY, angles[1] * 32767 / 360);
WriteShort(MSG_ENTITY, angles[2] * 32767 / 360);
}
if (fChanged & PLAYER_COLORMAP)
WriteByte(MSG_ENTITY, colormap);
/* similar as with origin, we separate x/y from z */
if (fChanged & PLAYER_VELOCITY) {
WriteCoord(MSG_ENTITY, velocity[0]);
WriteCoord(MSG_ENTITY, velocity[1]);
}
if (fChanged & PLAYER_VELOCITY_Z)
WriteCoord(MSG_ENTITY, velocity[2]);
if (fChanged & PLAYER_FLAGS) {
WriteFloat(MSG_ENTITY, flags);
WriteFloat(MSG_ENTITY, gflags);
WriteFloat(MSG_ENTITY, pmove_flags);
}
if (fChanged & PLAYER_WEAPON)
WriteByte(MSG_ENTITY, activeweapon);
/* g_items is a proper integer, so we can't let WriteFloat truncate it (hence __variant) */
if (fChanged & PLAYER_ITEMS)
WriteFloat(MSG_ENTITY, (__variant)g_items);
/* only got byte precision, clamp to avoid weird values on the client-side */
if (fChanged & PLAYER_HEALTH)
WriteByte(MSG_ENTITY, bound(0, health, 255));
if (fChanged & PLAYER_ARMOR)
WriteByte(MSG_ENTITY, bound(0, armor, 255));
if (fChanged & PLAYER_MOVETYPE)
WriteByte(MSG_ENTITY, movetype);
/* the view_ofs[0] and [1] are rarely changed */
if (fChanged & PLAYER_VIEWOFS)
WriteFloat(MSG_ENTITY, view_ofs[2]);
/* TO OPTIMISE */
WriteFloat(MSG_ENTITY, teleport_time);
WriteFloat(MSG_ENTITY, viewzoom);
WriteFloat(MSG_ENTITY, weapontime);
WriteFloat(MSG_ENTITY, w_attack_next);
WriteFloat(MSG_ENTITY, w_idle_next);
WriteFloat(MSG_ENTITY, punchangle[0]);
WriteFloat(MSG_ENTITY, punchangle[1]);
WriteFloat(MSG_ENTITY, punchangle[2]);
if (vehicle)
WriteEntity(MSG_ENTITY, vehicle);
else
WriteEntity(MSG_ENTITY, __NULL__);
return (1);
}
#endif
void
base_player::base_player(void)
{
vehicle = __NULL__;
}