564 lines
11 KiB
Plaintext
564 lines
11 KiB
Plaintext
/*
|
|
* Copyright (c) 2016-2024 Vera Visions LLC.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
void
|
|
NSClientSpectator::NSClientSpectator(void)
|
|
{
|
|
flags |= FL_CLIENT;
|
|
spec_mode = SPECMODE_FREE;
|
|
}
|
|
|
|
#ifdef SERVER
|
|
void
|
|
NSClientSpectator::Save(float handle)
|
|
{
|
|
super::Save(handle);
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::Restore(string strKey, string strValue)
|
|
{
|
|
switch (strKey) {
|
|
default:
|
|
super::Restore(strKey, strValue);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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::ProcessInput(void)
|
|
{
|
|
#ifdef SERVER
|
|
if (input_buttons & INPUT_PRIMARY) {
|
|
InputNext();
|
|
} else if (input_buttons & INPUT_SECONDARY) {
|
|
InputPrevious();
|
|
} else if (input_buttons & INPUT_JUMP) {
|
|
InputMode();
|
|
} else {
|
|
spec_flags &= ~SPECFLAG_BUTTON_RELEASED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::WarpToTarget(void)
|
|
{
|
|
entity b = edict_num(spec_ent);
|
|
|
|
setorigin(this, b.origin);
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::SharedInputFrame(void)
|
|
{
|
|
SetSize(g_vec_null, g_vec_null);
|
|
|
|
if (spec_mode == SPECMODE_FREE || spec_mode == SPECMODE_FREEOVERVIEW) {
|
|
if (spec_mode == SPECMODE_FREEOVERVIEW) {
|
|
input_angles[0] = 0.0f;
|
|
}
|
|
|
|
SetSolid(SOLID_NOT);
|
|
SetMovetype(MOVETYPE_NOCLIP);
|
|
} else {
|
|
SetSolid(SOLID_NOT);
|
|
SetMovetype(MOVETYPE_NONE);
|
|
}
|
|
}
|
|
|
|
#ifdef SERVER
|
|
float
|
|
NSClientSpectator::SendEntity(entity ePVSent, float flChanged)
|
|
{
|
|
if (this != ePVSent) {
|
|
return (0);
|
|
}
|
|
|
|
if (clienttype(ePVSent) != CLIENTTYPE_REAL) {
|
|
return (0);
|
|
}
|
|
|
|
WriteByte(MSG_ENTITY, ENT_SPECTATOR);
|
|
WriteFloat(MSG_ENTITY, flChanged);
|
|
|
|
SENDENTITY_COORD(origin[0], SPECFL_ORIGIN)
|
|
SENDENTITY_COORD(origin[1], SPECFL_ORIGIN)
|
|
SENDENTITY_COORD(origin[2], SPECFL_ORIGIN)
|
|
|
|
SENDENTITY_FLOAT(velocity[0], SPECFL_VELOCITY)
|
|
SENDENTITY_FLOAT(velocity[1], SPECFL_VELOCITY)
|
|
SENDENTITY_FLOAT(velocity[2], SPECFL_VELOCITY)
|
|
|
|
SENDENTITY_BYTE(spec_ent, SPECFL_TARGET)
|
|
SENDENTITY_BYTE(spec_mode, SPECFL_MODE)
|
|
SENDENTITY_BYTE(spec_flags, SPECFL_FLAGS)
|
|
SENDENTITY_BYTE(movetype, SPECFL_TYPE)
|
|
SENDENTITY_BYTE(solid, SPECFL_TYPE)
|
|
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::ServerInputFrame(void)
|
|
{
|
|
movetype = MOVETYPE_NOCLIP;
|
|
|
|
if (spec_mode == SPECMODE_FREE) {
|
|
runstandardplayerphysics(this);
|
|
} else {
|
|
WarpToTarget();
|
|
}
|
|
|
|
/* since we are not using Physics_Run(), we have to call this manually */
|
|
ProcessInput();
|
|
}
|
|
|
|
#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) {
|
|
input_impulse = 0;
|
|
input_buttons = 0;
|
|
input_movevalues = g_vec_null;
|
|
return;
|
|
}
|
|
|
|
vector movementDir = vectorNormalize(input_movevalues);
|
|
|
|
/* normalize movement values */
|
|
if (movementDir[0] > 0)
|
|
input_movevalues[0] = movementDir[0] * GetForwardSpeed();
|
|
else
|
|
input_movevalues[0] = movementDir[0] * GetBackSpeed();
|
|
|
|
input_movevalues[1] = movementDir[1] * GetSideSpeed();
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::ReceiveEntity(float new, float flChanged)
|
|
{
|
|
if (spec_mode == SPECMODE_FREE || spec_mode == SPECMODE_FREEOVERVIEW)
|
|
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);
|
|
ProcessInput();
|
|
}
|
|
|
|
/* any differences in things that are read below are now
|
|
* officially from prediction misses. */
|
|
}
|
|
}
|
|
|
|
/* seed for our prediction table */
|
|
sequence = servercommandframe;
|
|
|
|
READENTITY_COORD(origin[0], SPECFL_ORIGIN)
|
|
READENTITY_COORD(origin[1], SPECFL_ORIGIN)
|
|
READENTITY_COORD(origin[2], SPECFL_ORIGIN)
|
|
|
|
READENTITY_FLOAT(velocity[0], SPECFL_VELOCITY)
|
|
READENTITY_FLOAT(velocity[1], SPECFL_VELOCITY)
|
|
READENTITY_FLOAT(velocity[2], SPECFL_VELOCITY)
|
|
|
|
READENTITY_BYTE(spec_ent, SPECFL_TARGET)
|
|
READENTITY_BYTE(spec_mode, SPECFL_MODE)
|
|
READENTITY_BYTE(spec_flags, SPECFL_FLAGS)
|
|
READENTITY_BYTE(movetype, SPECFL_TYPE)
|
|
READENTITY_BYTE(solid, SPECFL_TYPE)
|
|
|
|
if (flChanged & SPECFL_TARGET) {
|
|
m_flLastSpecTargetChange = time;
|
|
}
|
|
};
|
|
|
|
float
|
|
NSClientSpectator::predraw(void)
|
|
{
|
|
addentity(this);
|
|
return (PREDRAW_NEXT);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
NSClientSpectator::InputNext(void)
|
|
{
|
|
if (m_flDeathCam > time)
|
|
return;
|
|
|
|
if (spec_flags & SPECFLAG_BUTTON_RELEASED) {
|
|
return;
|
|
}
|
|
|
|
if (spec_mode == SPECMODE_DEATHCAM && m_flDeathCam < time) {
|
|
spec_mode = SPECMODE_THIRDPERSON;
|
|
InputNext();
|
|
return;
|
|
} else if (spec_mode == SPECMODE_FREE) {
|
|
spec_mode = SPECMODE_THIRDPERSON;
|
|
} else if (spec_mode == SPECMODE_FREEOVERVIEW) {
|
|
spec_mode = SPECMODE_CHASEOVERVIEW;
|
|
}
|
|
|
|
spec_flags |= SPECFLAG_BUTTON_RELEASED;
|
|
|
|
#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
|
|
|
|
WarpToTarget();
|
|
|
|
if (spec_mode == SPECMODE_CHASEOVERVIEW)
|
|
spec_mode = SPECMODE_CHASEOVERVIEW;
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::InputPrevious(void)
|
|
{
|
|
if (m_flDeathCam > time)
|
|
return;
|
|
|
|
if (spec_flags & SPECFLAG_BUTTON_RELEASED) {
|
|
return;
|
|
}
|
|
|
|
if (spec_mode == SPECMODE_DEATHCAM && m_flDeathCam < time) {
|
|
spec_mode = SPECMODE_THIRDPERSON;
|
|
InputNext();
|
|
return;
|
|
} else if (spec_mode == SPECMODE_FREE) {
|
|
spec_mode = SPECMODE_THIRDPERSON;
|
|
} else if (spec_mode == SPECMODE_FREEOVERVIEW) {
|
|
spec_mode = SPECMODE_CHASEOVERVIEW;
|
|
}
|
|
|
|
spec_flags |= SPECFLAG_BUTTON_RELEASED;
|
|
|
|
#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
|
|
|
|
|
|
WarpToTarget();
|
|
|
|
if (spec_mode == SPECMODE_LOCKEDCHASE)
|
|
spec_mode = SPECMODE_CHASEOVERVIEW;
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::InputMode(void)
|
|
{
|
|
if (spec_mode == SPECMODE_DEATHCAM && m_flDeathCam < time) {
|
|
spec_mode = SPECMODE_THIRDPERSON;
|
|
InputNext();
|
|
return;
|
|
}
|
|
|
|
if (spec_flags & SPECFLAG_BUTTON_RELEASED) {
|
|
return;
|
|
}
|
|
|
|
spec_flags |= SPECFLAG_BUTTON_RELEASED;
|
|
|
|
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") {
|
|
if (spec_mode != SPECMODE_FREE)
|
|
spec_mode = SPECMODE_FREE;
|
|
else
|
|
spec_mode = SPECMODE_FREEOVERVIEW;
|
|
} else {
|
|
spec_mode++;
|
|
|
|
if (spec_mode > SPECMODE_CHASEOVERVIEW)
|
|
spec_mode = SPECMODE_LOCKEDCHASE;
|
|
}
|
|
}
|
|
|
|
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);
|
|
ProcessInput();
|
|
}
|
|
#endif
|
|
|
|
SpectatorTrackPlayer();
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::SpectatorTrackPlayer(void)
|
|
{
|
|
if (spec_mode == SPECMODE_DEATHCAM
|
|
|| spec_mode == SPECMODE_THIRDPERSON
|
|
|| spec_mode == SPECMODE_FIRSTPERSON
|
|
|| spec_mode == SPECMODE_CHASEOVERVIEW
|
|
|| spec_mode == SPECMODE_LOCKEDCHASE ) {
|
|
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 */
|
|
EVALUATE_FIELD(origin, SPECFL_ORIGIN)
|
|
EVALUATE_FIELD(velocity, SPECFL_VELOCITY)
|
|
EVALUATE_FIELD(spec_ent, SPECFL_TARGET)
|
|
EVALUATE_FIELD(spec_mode, SPECFL_MODE)
|
|
EVALUATE_FIELD(spec_flags, SPECFL_FLAGS)
|
|
EVALUATE_FIELD(movetype, SPECFL_TYPE)
|
|
EVALUATE_FIELD(solid, SPECFL_TYPE)
|
|
}
|
|
|
|
void
|
|
NSClientSpectator::SpectatorDeathcam(NSRenderableEntity ourCorpse, NSEntity trackEntity, float waitTime)
|
|
{
|
|
spec_ent = num_for_edict(ourCorpse);
|
|
spec_mode = SPECMODE_DEATHCAM;
|
|
m_flDeathCam = time + waitTime;
|
|
}
|
|
#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
|
|
}
|
|
|
|
#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
|