Forgot to add these files (NSClient/Player/Spectator)

This commit is contained in:
Marco Cawthorne 2022-05-11 13:18:02 -07:00
parent 25c54af62c
commit 6653d18417
Signed by: eukara
GPG Key ID: C196CD8BA993248A
6 changed files with 1743 additions and 0 deletions

38
src/shared/NSClient.h Normal file
View File

@ -0,0 +1,38 @@
/* both NSClientPlayer and base_NSClientSpectator are based off this class */
class
NSClient:NSSurfacePropEntity
{
vector origin_net;
vector velocity_net;
NSXRSpace m_xrSpace;
NSXRInput m_xrInputHead;
NSXRInput m_xrInputLeft;
NSXRInput m_xrInputRight;
void(void) NSClient;
/* final input handling of the client */
virtual void(void) ClientInput;
virtual void(void) PreFrame;
virtual void(void) PostFrame;
virtual bool(void) IsFakeSpectator;
virtual bool(void) IsRealSpectator;
virtual bool(void) IsDead;
virtual bool(void) IsPlayer;
virtual void(void) OnRemoveEntity;
#ifdef CLIENT
/* gives the chance to override input variables before networking */
virtual void(void) ClientInputFrame;
/* our camera when we're dead */
virtual void(void) UpdateDeathcam;
/* run every frame before renderscene() */
virtual float(void) predraw;
#endif
};

84
src/shared/NSClient.qc Normal file
View File

@ -0,0 +1,84 @@
void
NSClient::OnRemoveEntity(void)
{
XR_Shutdown(this);
}
void
NSClient::ClientInput(void)
{
}
void
NSClient::PreFrame(void)
{
}
void
NSClient::PostFrame(void)
{
}
bool
NSClient::IsFakeSpectator(void)
{
return (false);
}
bool
NSClient::IsRealSpectator(void)
{
return (false);
}
bool
NSClient::IsDead(void)
{
return (false);
}
bool
NSClient::IsPlayer(void)
{
return (false);
}
#ifdef CLIENT
void
NSClient::ClientInputFrame(void)
{
}
void
NSClient::UpdateDeathcam(void)
{
/* death cam */
view_angles[2] = 45.0f;
setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin);
setproperty(VF_CL_VIEWANGLES, view_angles);
setproperty(VF_ANGLES, view_angles);
}
float
NSClient::predraw(void)
{
return (PREDRAW_NEXT);
}
#endif
void
NSClient::NSClient(void)
{
XR_Init(this);
}
float
Client_InIntermission(void)
{
#ifdef CLIENT
return g_iIntermission;
#else
return (float)g_grMode.InIntermission();
#endif
}

122
src/shared/NSClientPlayer.h Normal file
View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2016-2021 Marco Cawthorne <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.
*/
class
NSClientPlayer:NSClientSpectator
{
#ifdef SERVER
PREDICTED_INT_N(weaponframe);
#else
PREDICTED_INT(weaponframe);
PREDICTED_FLOAT(vehicle_entnum);
#endif
PREDICTED_FLOAT(health);
PREDICTED_FLOAT(armor);
PREDICTED_FLOAT_N(colormap);
PREDICTED_FLOAT_N(gflags);
PREDICTED_FLOAT(viewzoom);
PREDICTED_VECTOR_N(view_ofs);
PREDICTED_VECTOR(v_angle);
PREDICTED_FLOAT_N(pmove_flags);
PREDICTED_FLOAT(w_attack_next);
PREDICTED_FLOAT(w_idle_next);
PREDICTED_FLOAT(teleport_time);
PREDICTED_FLOAT(weapontime);
PREDICTED_VECTOR(punchangle);
/* We can't use the default .items field, because FTE will assume
* effects of some bits. Such as invisibility, quad, etc.
* also, modders probably want 32 bits for items. */
PREDICTED_INT(g_items);
PREDICTED_FLOAT(activeweapon);
/* vehicle info */
PREDICTED_ENT(vehicle);
/* these are NOT networked */
int a_ammo1;
int a_ammo2;
int a_ammo3;
/* any mods that use hooks */
entity hook;
void(void) NSClientPlayer;
virtual void(void) ClientInput;
virtual void(void) PreFrame;
virtual void(void) PostFrame;
virtual void(float) Physics_Fall;
virtual void(void) Physics_Crouch;
virtual void(void) Physics_Jump;
virtual void(float) Physics_CheckJump;
virtual void(void) Physics_SetViewParms;
virtual void(void) Physics_WaterJump;
virtual void(void) Physics_WaterMove;
virtual float(void) Physics_MaxSpeed;
virtual void(void) Physics_InputPreMove;
virtual void(void) Physics_InputPostMove;
virtual void(void) Physics_Run;
virtual bool(void) IsFakeSpectator;
virtual bool(void) IsRealSpectator;
virtual bool(void) IsDead;
virtual bool(void) IsPlayer;
#ifdef CLIENT
int sequence;
/* external weapon model */
entity p_model;
int p_hand_bone;
int p_model_bone;
float lastweapon;
virtual void(void) VehicleRelink;
virtual void(void) OnRemoveEntity;
virtual void(float, float) ReceiveEntity;
virtual void(void) PredictPreFrame;
virtual void(void) PredictPostFrame;
virtual void(void) ClientInputFrame;
#else
int voted;
int step;
float step_time;
float underwater_time;
float underwater_dmg;
float pain_time;
entity last_used;
virtual void(float) Save;
virtual void(string,string) Restore;
virtual void(void) Respawn;
virtual void(void) EvaluateEntity;
virtual float(entity, float) SendEntity;
virtual void(void) Death;
virtual void(void) MakePlayer;
virtual void(void) MakeTempSpectator;
virtual void(void) InputUse_Down;
virtual void(void) InputUse_Up;
#endif
};

View File

@ -0,0 +1,951 @@
/*
* Copyright (c) 2016-2021 Marco Cawthorne <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.
*/
bool
NSClientPlayer::IsRealSpectator(void)
{
return (false);
}
bool
NSClientPlayer::IsDead(void)
{
if (health > 0)
return (false);
else
return (true);
}
bool
NSClientPlayer::IsPlayer(void)
{
return (false);
}
bool
NSClientPlayer::IsFakeSpectator(void)
{
if (GetFlags() & FL_FAKESPEC)
return (true);
return (false);
}
void
NSClientPlayer::PreFrame(void)
{
#ifdef CLIENT
/* this is where a game/mod would decide to add more prediction rollback
* information. */
PredictPreFrame();
if (vehicle) {
NSVehicle veh = (NSVehicle)vehicle;
veh.PredictPreFrame();
}
/* run physics code for all the input frames which we've not heard back
* from yet. This continues on in Player_ReceiveEntity! */
for (int i = sequence + 1; i <= clientcommandframe; i++) {
float flSuccess = getinputstate(i);
if (flSuccess == FALSE) {
continue;
}
/* don't do partial frames, aka incomplete input packets */
if (input_timelength == 0) {
break;
}
if (i==clientcommandframe){
CSQC_Input_Frame();
}
/* this global is for our shared random number seed */
input_sequence = i;
/* run our custom physics */
Physics_Run();
}
#endif
}
void
NSClientPlayer::PostFrame(void)
{
#ifdef CLIENT
/* give the game/mod a chance to roll back its values too */
PredictPostFrame();
setorigin(this, origin); /* update bounds */
if (vehicle) {
NSVehicle veh = (NSVehicle)vehicle;
veh.PredictPostFrame();
setorigin(veh, veh.origin);
}
#endif
}
void
NSClientPlayer::ClientInput(void)
{
XR_InputFrame(this);
if (!Client_InIntermission() && IsFakeSpectator()) {
NSClientSpectator::ClientInput();
SpectatorTrackPlayer();
return;
}
/* allow vehicles to prevent weapon logic from happening */
if (vehicle) {
NSVehicle veh = (NSVehicle)vehicle;
if (veh.PlayerInput)
veh.PlayerInput();
}
/* weapon/item logic of what the player controls */
Game_Input((player)this);
}
#ifdef CLIENT
void
NSClientPlayer::VehicleRelink(void)
{
if (!vehicle_entnum)
vehicle = __NULL__;
else
vehicle = findentity(world, ::entnum, vehicle_entnum);
}
void
NSClientPlayer::OnRemoveEntity(void)
{
if (p_model)
remove(p_model);
super::OnRemoveEntity();
}
/*
=================
NSClientPlayer::ClientInputFrame
This is basically CSQC_Input_Frame! So games can override this as they please.
=================
*/
void
NSClientPlayer::ClientInputFrame(void)
{
if (IsFakeSpectator()) {
NSClientSpectator::ClientInputFrame();
return;
}
/* If we are inside a VGUI, don't let the client do stuff outside */
if (VGUI_Active()) {
input_impulse = 0;
input_buttons = 0;
return;
}
/* background maps have no input */
if (serverkeyfloat("background") == 1)
return;
if (pSeat->m_iInputAttack2 == TRUE) {
input_buttons |= INPUT_BUTTON3;
}
if (pSeat->m_iInputReload == TRUE) {
input_buttons |= INPUT_BUTTON4;
}
if (pSeat->m_iInputUse == TRUE) {
input_buttons |= INPUT_BUTTON5;
}
if (pSeat->m_iInputDuck == TRUE) {
input_buttons |= INPUT_BUTTON8;
}
/* The HUD needs more time */
if (pSeat->m_iHUDWeaponSelected) {
if ((input_buttons & INPUT_BUTTON0))
HUD_DrawWeaponSelect_Trigger();
else if ((input_buttons & INPUT_BUTTON3))
pSeat->m_iHUDWeaponSelected = pSeat->m_flHUDWeaponSelectTime = 0;
pSeat->m_flInputBlockTime = time + 0.2;
}
/* prevent accidental input packets */
if (pSeat->m_flInputBlockTime > time) {
input_buttons &= ~INPUT_BUTTON0;
input_buttons &= ~INPUT_BUTTON3;
pSeat->m_iInputAttack2 = FALSE;
return;
}
/* some input overrides for XR */
if (XR_Available(this)) {
if (pSeat->m_bMoveForward) {
input_movevalues[0] = 100;
}
if (pSeat->m_iInputAttack) {
input_buttons |= INPUT_BUTTON0;
}
}
/* compat*/
if (input_impulse == 201) {
sendevent("Spraylogo", "");
}
if (pSeat->m_flCameraTime > time) {
/* TODO: Supress the changing of view_angles/input_angles. */
}
}
/*
=================
NSClientPlayer::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
NSClientPlayer::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] = readshort() / (32767 / 360);
v_angle[1] = readshort() / (32767 / 360);
v_angle[2] = readshort() / (32767 / 360);
}
if (fl & PLAYER_ANGLES_Y) {
angles[0] = readshort() / (32767 / 360);
angles[1] = readshort() / (32767 / 360);
angles[2] = 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();
weaponframe = (int)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();
solid = 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_entnum = readentitynum();
VehicleRelink();
/* FIXME: Make this temp spec only */
spec_ent = readbyte();
spec_mode = readbyte();
spec_flags = readbyte();
PredictPreFrame();
}
/*
=================
NSClientPlayer::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
NSClientPlayer::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(solid);
SAVE_STATE(view_ofs);
/* TO OPTIMISE */
SAVE_STATE(teleport_time);
SAVE_STATE(viewzoom);
SAVE_STATE(weaponframe);
SAVE_STATE(weapontime);
SAVE_STATE(w_attack_next);
SAVE_STATE(w_idle_next);
SAVE_STATE(punchangle);
SAVE_STATE(vehicle_entnum);
SAVE_STATE(spec_ent);
SAVE_STATE(spec_mode);
SAVE_STATE(spec_flags);
}
/*
=================
NSClientPlayer::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
NSClientPlayer::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(solid);
ROLL_BACK(view_ofs);
/* TO OPTIMISE */
ROLL_BACK(teleport_time);
ROLL_BACK(viewzoom);
ROLL_BACK(weaponframe);
ROLL_BACK(weapontime);
ROLL_BACK(w_attack_next);
ROLL_BACK(w_idle_next);
ROLL_BACK(punchangle);
ROLL_BACK(vehicle_entnum);
ROLL_BACK(spec_ent);
ROLL_BACK(spec_mode);
ROLL_BACK(spec_flags);
}
#else
void
NSClientPlayer::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);
SaveInt(handle, "weaponframe", weaponframe);
SaveFloat(handle, "weapontime", weapontime);
SaveInt(handle, "g_items", g_items);
SaveFloat(handle, "activeweapon", activeweapon);
SaveFloat(handle, "vehicle", num_for_edict(vehicle));
}
void
NSClientPlayer::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 "weaponframe":
weaponframe = ReadInt(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);
}
}
/*
=================
NSClientPlayer::Respawn
it'd be pretty unfortunate if 'sv respawn_ents' or something called this
=================
*/
void
NSClientPlayer::Respawn(void)
{
/* make sure nothing happens here */
}
/*
=================
NSClientPlayer::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
NSClientPlayer::MakeTempSpectator(void)
{
classname = "player";
flags = FL_CLIENT;
SetModelindex(0);
SetSolid(SOLID_NOT);
SetMovetype(MOVETYPE_NOCLIP);
SetTakedamage(DAMAGE_NO);
maxspeed = 250;
flags |= FL_FAKESPEC;
max_health = health = 0;
armor = 0;
g_items = 0;
activeweapon = 0;
effects = 0;
alpha = 0.0f;
}
/*
=================
NSClientPlayer::MakeDead
Sets all the appropriate attributes to make sure we're dead
=================
*/
void
NSClientPlayer::Death(void)
{
classname = "player";
health = max_health = 0;
armor = 0;
g_items = 0;
activeweapon = 0;
effects = 0;
alpha = 1.0f;
SetModelindex(0);
SetMovetype(MOVETYPE_NONE);
SetSolid(SOLID_NOT);
SetTakedamage(DAMAGE_NO);
viewzoom = 1.0;
view_ofs = [0,0,0];
vehicle = __NULL__;
SetVelocity([0,0,0]);
SetGravity(1.0f);
customphysics = Empty;
iBleeds = FALSE;
forceinfokey(this, "*deaths", ftos(deaths));
setsize(this, [0,0,0], [0,0,0]);
}
/*
=================
NSClientPlayer::MakePlayer
True participating player, can walk around and everything.
=================
*/
void
NSClientPlayer::MakePlayer(void)
{
classname = "player";
flags = FL_CLIENT;
health = max_health = 100;
armor = 0;
g_items = 0;
activeweapon = 0;
effects = 0;
alpha = 1.0f;
SetSolid(SOLID_SLIDEBOX);
SetMovetype(MOVETYPE_WALK);
SetTakedamage(DAMAGE_YES);
SetVelocity([0,0,0]);
viewzoom = 1.0;
vehicle = __NULL__;
SetGravity(1.0f);
SendFlags = UPDATE_ALL;
customphysics = Empty;
iBleeds = TRUE;
forceinfokey(this, "*deaths", ftos(deaths));
SetSize(VEC_HULL_MIN, VEC_HULL_MAX);
}
/*
=================
NSClientPlayer::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
NSClientPlayer::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(weaponframe))
SetSendFlags(PLAYER_WEAPON);
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(solid))
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(solid);
SAVE_STATE(view_ofs);
/* TO OPTIMISE */
SAVE_STATE(teleport_time);
SAVE_STATE(viewzoom);
SAVE_STATE(weaponframe);
SAVE_STATE(weapontime);
SAVE_STATE(w_attack_next);
SAVE_STATE(w_idle_next);
SAVE_STATE(punchangle);
SAVE_STATE(vehicle);
/* FIXME: Make this temp spec only */
SAVE_STATE(spec_ent);
SAVE_STATE(spec_mode);
SAVE_STATE(spec_flags);
}
/*
=================
NSClientPlayer::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
NSClientPlayer::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);
WriteByte(MSG_ENTITY, weaponframe);
}
/* 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);
WriteByte(MSG_ENTITY, solid);
}
/* 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__);
/* FIXME: Make this fake NSClientSpectator only. */
WriteByte(MSG_ENTITY, spec_ent);
WriteByte(MSG_ENTITY, spec_mode);
WriteByte(MSG_ENTITY, spec_flags);
return (1);
}
/*
====================
_NSClientPlayer_useworkaround
A wrapper to cleanly reset 'self' as to not mess up the QC VM
====================
*/
void
_NSClientPlayer_useworkaround(entity eTarget)
{
eActivator = self;
entity eOldSelf = self;
self = eTarget;
self.PlayerUse();
self = eOldSelf;
}
/*
====================
_NSClientPlayer_useworkaround
A wrapper to cleanly reset 'self' as to not mess up the QC VM
====================
*/
void
_NSClientPlayer_unuseworkaround(entity eTarget)
{
eActivator = self;
entity eOldSelf = self;
self = eTarget;
if (self.PlayerUseUnpressed)
self.PlayerUseUnpressed();
self = eOldSelf;
}
/*
=================
NSClientPlayer:: InputUse_Down
Called when we hold down the +use button for the first time,
looks for an entity that has the .PlayerUse field set to a function and calls it.
=================
*/
void
NSClientPlayer::InputUse_Down(void)
{
if (health <= 0) {
return;
} else if (!(flags & FL_USE_RELEASED)) {
return;
}
vector vecSource;
entity eRad;
bool found_use = false;
makevectors(v_angle);
vecSource = origin + view_ofs;
traceline(vecSource, vecSource + (v_forward * 64), MOVE_EVERYTHING, this);
/* first see if we traced something head-on, else we'll findradius something */
if (trace_ent.PlayerUse) {
found_use = true;
eRad = trace_ent;
} else {
/* find anything in a 8 unit radius, including certain non-solids (func_door, func_rot_button etc. */
eRad = findradius(trace_endpos, 8);
/* loop through our chain and just pick the first valid one */
while (eRad) {
if (eRad.PlayerUse) {
found_use = true;
break;
}
eRad = eRad.chain;
}
}
/* TODO: maybe eRad will return something in the future that'll suppress a successfull use? */
if (eRad && found_use == true) {
flags &= ~FL_USE_RELEASED;
_NSClientPlayer_useworkaround(eRad);
last_used = eRad;
/* Some entities want to support Use spamming */
if (!(flags & FL_USE_RELEASED)) {
sound(this, CHAN_ITEM, "common/wpn_select.wav", 0.25, ATTN_IDLE);
}
} else {
sound(this, CHAN_ITEM, "common/wpn_denyselect.wav", 0.25, ATTN_IDLE);
flags &= ~FL_USE_RELEASED;
}
}
/*
=================
NSClientPlayer:: InputUse_Down
Called when we let go of the +use button
=================
*/
void
NSClientPlayer::InputUse_Up(void)
{
if (!(flags & FL_USE_RELEASED)) {
_NSClientPlayer_unuseworkaround(last_used);
last_used = world;
flags |= FL_USE_RELEASED;
}
}
#endif
void
NSClientPlayer::NSClientPlayer(void)
{
super::NSClientSpectator();
vehicle = __NULL__;
}

View File

@ -0,0 +1,65 @@
typedef enumflags
{
SPECFL_ORIGIN,
SPECFL_VELOCITY,
SPECFL_TARGET,
SPECFL_MODE,
SPECFL_FLAGS
} NSClientSpectatorFlags_t;
typedef enum
{
SPECMODE_FREE,
SPECMODE_THIRDPERSON,
SPECMODE_FIRSTPERSON,
SPECMODE_OVERVIEW
} NSClientSpectatorMode_t;
typedef enumflags
{
SPECFLAG_BUTTON_RELEASED,
};
class NSClientSpectator:NSClient
{
PREDICTED_FLOAT(spec_ent);
PREDICTED_FLOAT(spec_flags);
NSClientSpectatorMode_t spec_mode; NSClientSpectatorMode_t spec_mode_net;
vector spec_org;
int sequence;
void(void) NSClientSpectator;
virtual void(void) ClientInput;
virtual void(void) InputNext;
virtual void(void) InputPrevious;
virtual void(void) InputMode;
virtual void(void) WarpToTarget;
virtual void(void) PreFrame;
virtual void(void) PostFrame;
virtual void(void) SpectatorTrackPlayer;
virtual bool(void) IsFakeSpectator;
virtual bool(void) IsRealSpectator;
virtual bool(void) IsDead;
virtual bool(void) IsPlayer;
#ifdef SERVER
virtual void(void) EvaluateEntity;
virtual float(entity, float) SendEntity;
virtual void(void) RunClientCommand;
#else
virtual void(void) ClientInputFrame;
virtual void(float,float) ReceiveEntity;
virtual float(void) predraw;
#endif
};
#ifdef CLIENT
void Spectator_ReadEntity(float new);
#endif

View File

@ -0,0 +1,483 @@
/*
* Copyright (c) 2016-2021 Marco Cawthorne <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.
*/
bool
NSClientSpectator::IsRealSpectator(void)
{
return (true);
}
bool
NSClientSpectator::IsDead(void)
{
return (false);
}
bool
NSClientSpectator::IsPlayer(void)
{
return (false);
}
bool
NSClientSpectator::IsFakeSpectator(void)
{
return (false);
}
void
NSClientSpectator::ClientInput(void)
{
if (input_buttons & INPUT_BUTTON0) {
InputNext();
} else if (input_buttons & INPUT_BUTTON3) {
InputPrevious();
} else if (input_buttons & INPUT_BUTTON2) {
InputMode();
} else {
spec_flags &= ~SPECFLAG_BUTTON_RELEASED;
}
input_buttons = 0;
//crossprint(sprintf("%d %d %d\n", spec_ent, spec_mode, spec_flags));
}
void
NSClientSpectator::WarpToTarget(void)
{
entity b = edict_num(spec_ent);
setorigin(this, b.origin);
}
#ifdef SERVER
float
NSClientSpectator::SendEntity(entity ePVSent, float flChangedFlags)
{
if (this != ePVSent) {
return (0);
}
if (clienttype(ePVSent) != CLIENTTYPE_REAL) {
return (0);
}
WriteByte(MSG_ENTITY, ENT_SPECTATOR);
WriteFloat(MSG_ENTITY, flChangedFlags);
if (flChangedFlags & SPECFL_ORIGIN) {
WriteCoord(MSG_ENTITY, origin[0]);
WriteCoord(MSG_ENTITY, origin[1]);
WriteCoord(MSG_ENTITY, origin[2]);
}
if (flChangedFlags & SPECFL_VELOCITY) {
WriteFloat(MSG_ENTITY, velocity[0]);
WriteFloat(MSG_ENTITY, velocity[1]);
WriteFloat(MSG_ENTITY, velocity[2]);
}
if (flChangedFlags & SPECFL_TARGET)
WriteByte(MSG_ENTITY, spec_ent);
if (flChangedFlags & SPECFL_MODE)
WriteByte(MSG_ENTITY, spec_mode);
if (flChangedFlags & SPECFL_FLAGS)
WriteByte(MSG_ENTITY, spec_flags);
return (1);
}
void
NSClientSpectator::RunClientCommand(void)
{
runstandardplayerphysics(this);
ClientInput();
}
#else
void
NSClientSpectator::ClientInputFrame(void)
{
/* If we are inside a VGUI, don't let the client do stuff outside */
if (VGUI_Active()) {
input_impulse = 0;
input_buttons = 0;
return;
}
/* background maps have no input */
if (serverkeyfloat("background") == 1)
return;
}
void
NSClientSpectator::ReceiveEntity(float new, float fl)
{
if (new == FALSE) {
/* Go through all the physics code between the last received frame
* and the newest frame and keep the changes this time around instead
* of rolling back, because we'll apply the new server-verified values
* right after anyway. */
/* FIXME: splitscreen */
if (entnum == player_localentnum) {
/* FIXME: splitscreen */
pSeat = &g_seats[0];
for (int i = sequence+1; i <= servercommandframe; i++) {
/* ...maybe the input state is too old? */
if (!getinputstate(i)) {
break;
}
input_sequence = i;
runstandardplayerphysics(this);
ClientInput();
}
/* any differences in things that are read below are now
* officially from prediction misses. */
}
}
/* seed for our prediction table */
sequence = servercommandframe;
if (fl & SPECFL_ORIGIN) {
origin[0] = readcoord();
origin[1] = readcoord();
origin[2] = readcoord();
}
if (fl & SPECFL_VELOCITY) {
velocity[0] = readfloat();
velocity[1] = readfloat();
velocity[2] = readfloat();
}
if (fl & SPECFL_TARGET)
spec_ent = readbyte();
if (fl & SPECFL_MODE)
spec_mode = readbyte();
if (fl & SPECFL_FLAGS)
spec_flags = readbyte();
};
float
NSClientSpectator::predraw(void)
{
addentity(this);
return (PREDRAW_NEXT);
}
#endif
void
NSClientSpectator::InputNext(void)
{
if (spec_flags & SPECFLAG_BUTTON_RELEASED)
return;
#if 0
float max_edict;
max_edict = serverkeyfloat("sv_playerslots");
spec_ent++;
if (spec_ent > max_edict)
spec_ent = 1;
print(sprintf("edict: %d\n", spec_ent));
#else
float max_edict;
float sep = spec_ent;
float best = 0;
NSClient cl;
max_edict = serverkeyfloat("sv_playerslots");
for (float i = 1; i <= max_edict; i++) {
entity f;
if (i <= sep && best == 0) {
f = edict_num(i);
if (f && f.classname == "player" && f != this) {
cl = (NSClient)f;
if (!cl.IsFakeSpectator())
best = i;
}
}
if (i > sep) {
f = edict_num(i);
if (f && f.classname == "player" && f != this) {
cl = (NSClient)f;
if (!cl.IsFakeSpectator()) {
best = i;
break;
}
}
}
}
if (best == 0)
return;
spec_ent = best;
#endif
spec_flags |= SPECFLAG_BUTTON_RELEASED;
WarpToTarget();
if (spec_mode == SPECMODE_FREE)
spec_mode = SPECMODE_THIRDPERSON;
}
void
NSClientSpectator::InputPrevious(void)
{
if (spec_flags & SPECFLAG_BUTTON_RELEASED)
return;
#if 0
float max_edict;
max_edict = serverkeyfloat("sv_playerslots");
spec_ent--;
if (spec_ent < 1)
spec_ent = max_edict;
#else
float max_edict;
float sep = spec_ent;
float best = 0;
NSClient cl;
max_edict = serverkeyfloat("sv_playerslots");
for (float i = max_edict; i > 0; i--) {
entity f;
/* remember the first valid one here */
if (i >= sep && best == 0) {
f = edict_num(i);
if (f && f.classname == "player") {
cl = (NSClient)f;
if (!cl.IsFakeSpectator())
best = i;
}
}
/* find the first good one and take it */
if (i < sep) {
f = edict_num(i);
if (f && f.classname == "player") {
cl = (NSClient)f;
if (!cl.IsFakeSpectator()) {
best = i;
break;
}
}
}
}
if (best == 0)
return;
spec_ent = best;
#endif
spec_flags |= SPECFLAG_BUTTON_RELEASED;
WarpToTarget();
if (spec_mode == SPECMODE_FREE)
spec_mode = SPECMODE_THIRDPERSON;
}
void
NSClientSpectator::InputMode(void)
{
if (spec_flags & SPECFLAG_BUTTON_RELEASED)
return;
NSClient f;
#ifdef CLIENT
f = (NSClient)findfloat(world, ::entnum, spec_ent);
#else
f = (NSClient)edict_num(spec_ent);
#endif
if (f == this || f.classname != "player")
spec_mode = SPECMODE_FREE;
else {
spec_mode++;
if (spec_mode > SPECMODE_FIRSTPERSON)
spec_mode = SPECMODE_FREE;
}
spec_flags |= SPECFLAG_BUTTON_RELEASED;
}
void
NSClientSpectator::PreFrame(void)
{
#ifdef CLIENT
/* base player attributes/fields we're going to roll back */
SAVE_STATE(origin);
SAVE_STATE(velocity);
SAVE_STATE(spec_ent);
SAVE_STATE(spec_mode);
SAVE_STATE(spec_flags);
/* run physics code for all the input frames which we've not heard back
* from yet. This continues on in Player_ReceiveEntity! */
for (int i = sequence + 1; i <= clientcommandframe; i++) {
float flSuccess = getinputstate(i);
if (flSuccess == FALSE) {
continue;
}
if (i==clientcommandframe){
CSQC_Input_Frame();
}
/* don't do partial frames, aka incomplete input packets */
if (input_timelength == 0) {
break;
}
/* this global is for our shared random number seed */
input_sequence = i;
/* run our custom physics */
runstandardplayerphysics(this);
ClientInput();
}
#endif
SpectatorTrackPlayer();
}
void
NSClientSpectator::SpectatorTrackPlayer(void)
{
if (spec_mode == SPECMODE_THIRDPERSON || spec_mode == SPECMODE_FIRSTPERSON ) {
NSClient b;
#ifdef CLIENT
b = (NSClient)findfloat(world, ::entnum, spec_ent);
#else
b = (NSClient)edict_num(spec_ent);
#endif
if (b && b.classname == "player")
if (b.IsFakeSpectator()) {
b = 0;
spec_mode = SPECMODE_FREE;
InputNext();
}
/* if the ent is dead... or not available in this current frame
just warp to the last 'good' one */
if (b) {
setorigin(this, b.origin);
spec_org = b.origin;
} else {
setorigin(this, spec_org);
}
}
}
#ifdef SERVER
void
NSClientSpectator::EvaluateEntity(void)
{
/* check for which values have changed in this frame
and announce to network said changes */
if (origin != origin_net)
SetSendFlags(SPECFL_ORIGIN);
if (velocity != velocity_net)
SetSendFlags(SPECFL_VELOCITY);
if (spec_ent != spec_ent_net)
SetSendFlags(SPECFL_TARGET);
if (spec_mode != spec_mode_net)
SetSendFlags(SPECFL_MODE);
if (spec_flags != spec_flags_net)
SetSendFlags(SPECFL_FLAGS);
SAVE_STATE(origin);
SAVE_STATE(velocity);
SAVE_STATE(spec_ent);
SAVE_STATE(spec_mode);
SAVE_STATE(spec_flags);
}
#endif
void
NSClientSpectator::PostFrame(void)
{
#ifdef CLIENT
ROLL_BACK(origin);
ROLL_BACK(velocity);
ROLL_BACK(spec_ent);
ROLL_BACK(spec_mode);
ROLL_BACK(spec_flags);
#endif
}
void
NSClientSpectator::NSClientSpectator(void)
{
super::NSClient();
modelindex = 0;
flags = FL_CLIENT;
SetSolid(SOLID_NOT);
SetMovetype(MOVETYPE_NOCLIP);
think = __NULL__;
nextthink = 0.0f;
maxspeed = 250;
spec_ent = 0;
spec_mode = 0;
}
#ifdef CLIENT
void
Spectator_ReadEntity(float new)
{
NSClientSpectator spec = (NSClientSpectator)self;
if (new || self.classname != "spectator") {
spawnfunc_NSClientSpectator();
spec.classname = "spectator";
spec.solid = SOLID_NOT;
spec.drawmask = MASK_ENGINE;
spec.customphysics = Empty;
setsize(spec, [0,0,0], [0,0,0]);
}
float flags = readfloat();
spec.ReceiveEntity(new, flags);
}
#endif