From 95739c7a201e95f11c3a767addd559e75a9236a4 Mon Sep 17 00:00:00 2001 From: Marco Hladik Date: Wed, 24 Mar 2021 07:50:30 +0100 Subject: [PATCH] Basic spectator implementation for all games. --- src/botlib/cmd.qc | 1 + src/client/chat.qc | 2 + src/client/defs.h | 1 + src/client/entry.qc | 181 ++++++++++++------- src/client/predict.qc | 34 +++- src/client/view.qc | 1 + src/server/entry.qc | 51 ++++++ src/shared/defs.h | 1 + src/shared/entities.h | 1 + src/shared/include.src | 1 + src/shared/player.h | 2 + src/shared/spectator.h | 52 ++++++ src/shared/spectator.qc | 378 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 642 insertions(+), 64 deletions(-) create mode 100644 src/shared/spectator.h create mode 100644 src/shared/spectator.qc diff --git a/src/botlib/cmd.qc b/src/botlib/cmd.qc index a0df532d..60b11a6a 100644 --- a/src/botlib/cmd.qc +++ b/src/botlib/cmd.qc @@ -33,6 +33,7 @@ Bot_AddQuick(void) { /* we've got no nodes, so generate some fake waypoints */ if (!g_nodes_present) { + print("^1Bot_AddQuick^7: Can't add bot. No waypoints.\n"); return world; } diff --git a/src/client/chat.qc b/src/client/chat.qc index b3ed3547..1f0a8a50 100644 --- a/src/client/chat.qc +++ b/src/client/chat.qc @@ -31,6 +31,8 @@ Chat_Draw(void) { int i; + drawfont = FONT_CON; + /* the voting stuff resides here too, for now */ if (serverkey("vote_cmd")) { string tempstr; diff --git a/src/client/defs.h b/src/client/defs.h index 48d2f085..3d84657c 100644 --- a/src/client/defs.h +++ b/src/client/defs.h @@ -62,6 +62,7 @@ var int MUZZLE_RIFLE; var int MUZZLE_WEIRD; var int SHELL_DEFAULT; +var int SHELL_SHOTGUN; /* misc globals */ vector video_mins; diff --git a/src/client/entry.qc b/src/client/entry.qc index d62a031e..c2dce0a7 100644 --- a/src/client/entry.qc +++ b/src/client/entry.qc @@ -140,6 +140,7 @@ void CSQC_UpdateView(float w, float h, float focus) { player pl; + spectator spec; int s; if (w == 0 || h == 0) { @@ -210,50 +211,58 @@ CSQC_UpdateView(float w, float h, float focus) continue; } - pl = (player)self; + if (self.classname == "player") { + pl = (player)self; + Predict_PlayerPreFrame(pl); - Predict_PreFrame((player)self); + pSeat->m_vecPredictedOrigin = pl.origin; + pSeat->m_vecPredictedVelocity = pl.velocity; + pSeat->m_flPredictedFlags = pl.flags; - pSeat->m_vecPredictedOrigin = pl.origin; - pSeat->m_vecPredictedVelocity = pl.velocity; - pSeat->m_flPredictedFlags = pl.flags; - - /* Don't hide the player entity */ - if (autocvar_cl_thirdperson == TRUE && pl.health) { - setproperty(VF_VIEWENTITY, (float)0); - } else { - setproperty(VF_VIEWENTITY, (float)player_localentnum); - } - - float oldzoom = pl.viewzoom; - if (pl.viewzoom == 1.0f) { - pl.viewzoom = 1.0 - (0.5 * pSeat->m_flZoomTime); - - /* +zoomin requested by Slacer */ - if (pSeat->m_iZoomed) { - pSeat->m_flZoomTime += clframetime * 15; + /* Don't hide the player entity */ + if (autocvar_cl_thirdperson == TRUE && pl.health) { + setproperty(VF_VIEWENTITY, (float)0); } else { - pSeat->m_flZoomTime -= clframetime * 15; + setproperty(VF_VIEWENTITY, (float)player_localentnum); } - pSeat->m_flZoomTime = bound(0, pSeat->m_flZoomTime, 1); + + float oldzoom = pl.viewzoom; + if (pl.viewzoom == 1.0f) { + pl.viewzoom = 1.0 - (0.5 * pSeat->m_flZoomTime); + + /* +zoomin requested by Slacer */ + if (pSeat->m_iZoomed) { + pSeat->m_flZoomTime += clframetime * 15; + } else { + pSeat->m_flZoomTime -= clframetime * 15; + } + pSeat->m_flZoomTime = bound(0, pSeat->m_flZoomTime, 1); + } + + setproperty(VF_AFOV, cvar("fov") * pl.viewzoom); + + if (autocvar_zoom_sensitivity && pl.viewzoom < 1.0f) { + setsensitivityscaler(pl.viewzoom * autocvar_zoom_sensitivity); + } else { + setsensitivityscaler(pl.viewzoom); + } + + if (pl.viewzoom <= 0.0f) { + setsensitivityscaler(1.0f); + } + + pl.viewzoom = oldzoom; + + View_PreDraw(); + } else if (self.classname == "spectator") { + spec = (spectator)self; + Predict_SpectatorPreFrame(spec); + + pSeat->m_vecPredictedOrigin = spec.origin; + pSeat->m_vecPredictedVelocity = spec.velocity; + pSeat->m_flPredictedFlags = spec.flags; } - setproperty(VF_AFOV, cvar("fov") * pl.viewzoom); - - if (autocvar_zoom_sensitivity && pl.viewzoom < 1.0f) { - setsensitivityscaler(pl.viewzoom * autocvar_zoom_sensitivity); - } else { - setsensitivityscaler(pl.viewzoom); - } - - if (pl.viewzoom <= 0.0f) { - setsensitivityscaler(1.0f); - } - - pl.viewzoom = oldzoom; - - View_PreDraw(); - if (pSeat->m_pWeaponFX) { CBaseFX p = (CBaseFX)pSeat->m_pWeaponFX; p.Draw(); @@ -267,18 +276,58 @@ CSQC_UpdateView(float w, float h, float focus) setproperty(VF_CL_VIEWANGLES, view_angles); setproperty(VF_ANGLES, view_angles); } else { - if (pl.health) { - if (autocvar_cl_thirdperson == TRUE) { - makevectors(view_angles); - vector vStart = [pSeat->m_vecPredictedOrigin[0], pSeat->m_vecPredictedOrigin[1], pSeat->m_vecPredictedOrigin[2] + 16] + (v_right * 4); - vector vEnd = vStart + (v_forward * -48) + [0,0,16] + (v_right * 4); - traceline(vStart, vEnd, FALSE, self); - setproperty(VF_ORIGIN, trace_endpos + (v_forward * 5)); + if (self.classname == "player") { + if (pl.health) { + if (autocvar_cl_thirdperson == TRUE) { + makevectors(view_angles); + vector vStart = [pSeat->m_vecPredictedOrigin[0], pSeat->m_vecPredictedOrigin[1], pSeat->m_vecPredictedOrigin[2] + 16] + (v_right * 4); + vector vEnd = vStart + (v_forward * -48) + [0,0,16] + (v_right * 4); + traceline(vStart, vEnd, FALSE, self); + setproperty(VF_ORIGIN, trace_endpos + (v_forward * 5)); + } else { + setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin + pl.view_ofs); + } } else { - setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin + pl.view_ofs); + setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin); + } + + if (pSeat->m_flShakeDuration > 0.0) { + vector vecShake = [0,0,0]; + vecShake[0] += random() * 3; + vecShake[1] += random() * 3; + vecShake[2] += random() * 3; + pl.punchangle += (vecShake * pSeat->m_flShakeAmp) * (pSeat->m_flShakeDuration / pSeat->m_flShakeTime); + pSeat->m_flShakeDuration -= clframetime; + } + setproperty(VF_ANGLES, view_angles + pl.punchangle); + } else if (self.classname == "spectator") { + switch (spec.spec_mode) { + case SPECMODE_THIRDPERSON: + makevectors(view_angles); + vector vecStart; + vecStart[0] = pSeat->m_vecPredictedOrigin[0]; + vecStart[1] = pSeat->m_vecPredictedOrigin[1]; + vecStart[2] = pSeat->m_vecPredictedOrigin[2] + 16; + vecStart += (v_right * 4); + + vector vecEnd = vecStart + (v_forward * -48) + [0,0,16] + (v_right * 4); + traceline(vecStart, vecEnd, FALSE, self); + setproperty(VF_ORIGIN, trace_endpos + (v_forward * 5)); + break; + case SPECMODE_FIRSTPERSON: + entity b; + b = findfloat(world, ::entnum, spec.spec_ent); + + if (b.classname == "player") { + player bp = (player)b; + setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin + bp.view_ofs); + setproperty(VF_ANGLES, bp.v_angle); + setproperty(VF_CL_VIEWANGLES, [bp.pitch, bp.angles[1], bp.angles[2]]); + } + break; + default: + setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin); } - } else { - setproperty(VF_ORIGIN, pSeat->m_vecPredictedOrigin); } if (g_iIntermission) { @@ -287,16 +336,6 @@ CSQC_UpdateView(float w, float h, float focus) setproperty(VF_ORIGIN, pSeat->m_vecCameraOrigin); setproperty(VF_CL_VIEWANGLES, view_angles); } - - if (pSeat->m_flShakeDuration > 0.0) { - vector vecShake = [0,0,0]; - vecShake[0] += random() * 3; - vecShake[1] += random() * 3; - vecShake[2] += random() * 3; - pl.punchangle += (vecShake * pSeat->m_flShakeAmp) * (pSeat->m_flShakeDuration / pSeat->m_flShakeTime); - pSeat->m_flShakeDuration -= clframetime; - } - setproperty(VF_ANGLES, view_angles + pl.punchangle); } setproperty(VF_DRAWWORLD, 1); @@ -335,9 +374,9 @@ CSQC_UpdateView(float w, float h, float focus) GameText_Draw(); PointMessage_Draw(); - if (getplayerkeyvalue(player_localnum, "*spec") != "0") { + if (self.classname == "spectator") { HUD_DrawSpectator(); - } else { + } else if (self.classname == "player") { HUD_Draw(); } @@ -354,7 +393,10 @@ CSQC_UpdateView(float w, float h, float focus) } } - Predict_PostFrame((player)self); + if (self.classname == "player") + Predict_PlayerPostFrame((player)self); + else if (self.classname == "spectator") + Predict_SpectatorPostFrame((spectator)self); } DSP_UpdateListener(); @@ -588,10 +630,13 @@ CSQC_Parse_Event(void) setproperty(VF_ANGLES, a); break; case EV_SHAKE: + if (self.classname == "spectator") + break; pSeat->m_flShakeDuration = readfloat(); pSeat->m_flShakeAmp = readfloat(); pSeat->m_flShakeFreq = readfloat(); pSeat->m_flShakeTime = pSeat->m_flShakeDuration; + break; default: ClientGame_EventParse(fHeader); } @@ -833,7 +878,7 @@ CSQC_Ent_Update(float new) break; case ENT_PLAYER: player pl = (player)self; - if (new) { + if (new || self.classname != "player") { spawnfunc_player(); pl.classname = "player"; pl.solid = SOLID_SLIDEBOX; @@ -843,6 +888,18 @@ CSQC_Ent_Update(float new) } pl.ReceiveEntity(new); break; + case ENT_SPECTATOR: + spectator spec = (spectator)self; + if (new || self.classname != "spectator") { + spawnfunc_spectator(); + spec.classname = "spectator"; + spec.solid = SOLID_SLIDEBOX; + spec.drawmask = MASK_ENGINE; + spec.customphysics = Empty; + setsize(spec, [0,0,0], [0,0,0]); + } + spec.ReceiveEntity(new); + break; case ENT_SPRITE: env_sprite spr = (env_sprite)self; if (new) { diff --git a/src/client/predict.qc b/src/client/predict.qc index d4b0edbc..8e6da07d 100644 --- a/src/client/predict.qc +++ b/src/client/predict.qc @@ -24,7 +24,7 @@ Propagate our pmove state to whatever the current frame before its stomped on ================= */ void -Predict_PreFrame(player pl) +Predict_PlayerPreFrame(player pl) { /* base player attributes/fields we're going to roll back */ pl.net_origin = pl.origin; @@ -78,7 +78,7 @@ Rewind our pmove state back to before we started predicting. ================= */ void -Predict_PostFrame(player pl) +Predict_PlayerPostFrame(player pl) { /* finally roll the values back */ pl.origin = pl.net_origin; @@ -99,3 +99,33 @@ Predict_PostFrame(player pl) /* update bounds */ setorigin(pl, pl.origin); } + +/* +================= +Predict_PreFrame + +We're part way through parsing new player data. +Propagate our pmove state to whatever the current frame before its stomped on +(so any non-networked state updates locally). +================= +*/ +void +Predict_SpectatorPreFrame(spectator pl) +{ + pl.PreFrame(); +} + +/* +================= +Predict_SpectatorPostFrame + +We're part way through parsing new player data. +Rewind our pmove state back to before we started predicting. +(to give consistent state instead of accumulating errors) +================= +*/ +void +Predict_SpectatorPostFrame(spectator pl) +{ + pl.PostFrame(); +} diff --git a/src/client/view.qc b/src/client/view.qc index 7c0dba52..87c5168f 100644 --- a/src/client/view.qc +++ b/src/client/view.qc @@ -40,6 +40,7 @@ View_Init(void) MUZZLE_SMALL = (int)getmodelindex("sprites/muzzleflash2.spr"); MUZZLE_WEIRD = (int)getmodelindex("sprites/muzzleflash3.spr"); SHELL_DEFAULT = (int)getmodelindex("models/shell.mdl"); + SHELL_SHOTGUN = (int)getmodelindex("models/shotgunshell.mdl"); } void diff --git a/src/server/entry.qc b/src/server/entry.qc index 73ef762d..6c25b2f1 100644 --- a/src/server/entry.qc +++ b/src/server/entry.qc @@ -123,6 +123,13 @@ void SpectatorThink(void) { Game_SpectatorThink(); + + if (self.classname == "spectator") { + spectator spec = (spectator)self; + spec.PreFrame(); + spec.PostFrame(); + return; + } } /* @@ -137,6 +144,7 @@ void SpectatorConnect(void) { Game_SpectatorConnect(); + spawnfunc_spectator(); } /* @@ -194,6 +202,12 @@ times the amount of players in a given game. void PlayerPreThink(void) { + if (self.classname == "spectator") { + //spectator spec = (spectator)self; + //spec.PreFrame(); + return; + } + if (self.classname != "player") { return; } @@ -220,6 +234,11 @@ times the amount of players in a given game. void PlayerPostThink(void) { + if (self.classname == "spectator") { + SpectatorThink(); + return; + } + if (self.classname != "player") { return; } @@ -289,6 +308,11 @@ with the input_X globals being set to the appropriate data. void SV_RunClientCommand(void) { + if (self.classname == "spectator") { + spectator spec = (spectator)self; + spec.RunClientCommand(); + } + if (self.classname != "player") { return; } @@ -325,6 +349,23 @@ SV_ParseClientCommand(string cmd) Game_ParseClientCommand(cmd); else Game_ParseClientCommand(newcmd); + + tokenize(cmd); + + switch (argv(0)) { + case "spectate": + if (self.classname != "player") + break; + ClientKill(); + spawnfunc_spectator(); + break; + case "play": + if (self.classname != "spectator") + break; + spawnfunc_player(); + PutClientInServer(); + break; + } } /* @@ -542,6 +583,16 @@ ConsoleCmd(string cmd) } } } + + if (!self) { + for ( other = world; ( other = find( other, classname, "spectator" ) ); ) { + if ( clienttype( other ) == CLIENTTYPE_REAL ) { + self = other; + break; + } + } + } + pl = (player)self; /* give the game-mode a chance to override us */ diff --git a/src/shared/defs.h b/src/shared/defs.h index 0402fa48..28671611 100644 --- a/src/shared/defs.h +++ b/src/shared/defs.h @@ -27,6 +27,7 @@ #include "sound.h" #include "pmove.h" #include "memory.h" +#include "spectator.h" #define BSPVER_PREREL 28 #define BSPVER_Q1 29 diff --git a/src/shared/entities.h b/src/shared/entities.h index b4cbbee3..8df584ad 100644 --- a/src/shared/entities.h +++ b/src/shared/entities.h @@ -20,6 +20,7 @@ enum ENT_NONE, ENT_ENTITY, ENT_PLAYER, + ENT_SPECTATOR, ENT_AMBIENTSOUND, ENT_DLIGHT, ENT_PROJECTEDTEXTURE, diff --git a/src/shared/include.src b/src/shared/include.src index 700b6b83..3b98c030 100644 --- a/src/shared/include.src +++ b/src/shared/include.src @@ -1,4 +1,5 @@ #includelist +spectator.qc pmove.qc sound.qc math.qc diff --git a/src/shared/player.h b/src/shared/player.h index c2c8d6cb..65bbd531 100644 --- a/src/shared/player.h +++ b/src/shared/player.h @@ -30,6 +30,8 @@ class base_player vector view_ofs; float weapontime; + vector v_angle; + /* any mods that use hooks */ entity hook; diff --git a/src/shared/spectator.h b/src/shared/spectator.h new file mode 100644 index 00000000..7e1c50a7 --- /dev/null +++ b/src/shared/spectator.h @@ -0,0 +1,52 @@ +enumflags +{ + SPECFL_ORIGIN, + SPECFL_VELOCITY, + SPECFL_TARGET, + SPECFL_MODE, + SPECFL_FLAGS +}; + +enum +{ + SPECMODE_FREE, + SPECMODE_THIRDPERSON, + SPECMODE_FIRSTPERSON, + SPECMODE_OVERVIEW +}; + +#ifdef SERVER +class spectator:CBaseEntity +#else +class spectator +#endif +{ + vector origin_net; + vector velocity_net; + float spec_ent; float spec_ent_net; + float spec_mode; float spec_mode_net; + float spec_flags; float spec_flags_net; + + int sequence; + + void(void) spectator; + + virtual void(void) playernext; + virtual void(void) playerprevious; + virtual void(void) modeswitch; + + virtual void(void) PreFrame; + virtual void(void) PostFrame; + + virtual void(void) Input; + + virtual void(void) WarpToTarget; + +#ifdef SERVER + virtual float(entity, float) SendEntity; + virtual void(void) RunClientCommand; +#else + virtual void(float) ReceiveEntity; + virtual float(void) predraw; +#endif +}; diff --git a/src/shared/spectator.qc b/src/shared/spectator.qc new file mode 100644 index 00000000..6fe3a469 --- /dev/null +++ b/src/shared/spectator.qc @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2016-2021 Marco Hladik + * + * 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 +spectator::WarpToTarget(void) +{ + entity b = edict_num(spec_ent); + + setorigin(this, b.origin); +} + +void +spectator::Input(void) +{ + if (input_buttons & INPUT_BUTTON0) { + playernext(); + } else if (input_buttons & INPUT_BUTTON3) { + playerprevious(); + } else if (input_buttons & INPUT_BUTTON2) { + if (spec_flags & GF_SEMI_TOGGLED) + return; + spec_mode++; + + if (spec_mode > SPECMODE_FIRSTPERSON) + spec_mode = SPECMODE_FREE; + + spec_flags |= GF_SEMI_TOGGLED; + } else { + spec_flags &= ~GF_SEMI_TOGGLED; + } + + input_buttons = 0; +} + +#ifdef SERVER +float +spectator::SendEntity(entity ePVSent, float flChangedFlags) +{ + if (this != ePVSent) { + return FALSE; + } + + if (clienttype(ePVSent) != CLIENTTYPE_REAL) { + return FALSE; + } + + 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 TRUE; +} + +void +spectator::RunClientCommand(void) +{ + runstandardplayerphysics(this); + Input(); +} + +#else +void +spectator::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); + Input(); + } + + /* any differences in things that are read below are now + * officially from prediction misses. */ + } + } + + /* seed for our prediction table */ + sequence = servercommandframe; + + fl = readfloat(); + + 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 +spectator::predraw(void) +{ + addentity(this); + return PREDRAW_NEXT; +} +#endif + +void +spectator::playernext(void) +{ + if (spec_flags & GF_SEMI_TOGGLED) + 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; + + 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") { + best = i; + } + } + + if (i > sep) { + f = edict_num(i); + if (f && f.classname == "player") { + best = i; + break; + } + } + } + + if (best == 0) + return; + + spec_ent = best; +#endif + spec_flags |= GF_SEMI_TOGGLED; + WarpToTarget(); + + if (spec_mode == SPECMODE_FREE) + spec_mode = SPECMODE_THIRDPERSON; +} + +void +spectator::playerprevious(void) +{ + if (spec_flags & GF_SEMI_TOGGLED) + 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; + + 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") { + best = i; + } + } + + /* find the first good one and take it */ + if (i < sep) { + f = edict_num(i); + if (f && f.classname == "player") { + best = i; + break; + } + } + } + + if (best == 0) + return; + + spec_ent = best; +#endif + + print(sprintf("dddd: %d\n", spec_ent)); + spec_flags |= GF_SEMI_TOGGLED; + + WarpToTarget(); + + if (spec_mode == SPECMODE_FREE) + spec_mode = SPECMODE_THIRDPERSON; +} + +void +spectator::modeswitch(void) +{ + +} + +void +spectator::PreFrame(void) +{ +#ifdef SERVER + +#else + /* base player attributes/fields we're going to roll back */ + origin_net = origin; + velocity_net = velocity; + spec_ent_net = spec_ent; + spec_mode_net = spec_mode; + spec_flags_net = 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); + Input(); + } +#endif + + if (spec_mode == SPECMODE_THIRDPERSON || spec_mode == SPECMODE_FIRSTPERSON ) { + entity b; + + #ifdef CLIENT + b = findfloat(world, ::entnum, spec_ent); + #else + b = edict_num(spec_ent); + #endif + setorigin(this, b.origin); + } +} + +void +spectator::PostFrame(void) +{ +#ifdef SERVER + /* check for which values have changed in this frame + and announce to network said changes */ + if (origin != origin_net) + SendFlags |= SPECFL_ORIGIN; + + if (velocity != velocity_net) + SendFlags |= SPECFL_VELOCITY; + + if (spec_ent != spec_ent_net) + SendFlags |= SPECFL_TARGET; + + if (spec_mode != spec_mode_net) + SendFlags |= SPECFL_MODE; + + if (spec_flags != spec_flags_net) + SendFlags |= SPECFL_FLAGS; + + origin_net = origin; + velocity_net = velocity; + spec_ent_net = spec_ent; + spec_mode_net = spec_mode; + spec_flags_net = spec_flags; +#else + /* finally roll the values back */ + origin = origin_net; + velocity = velocity_net; + spec_ent = spec_ent_net; + spec_mode = spec_mode_net; + spec_flags = spec_flags_net; + setorigin(this, origin); +#endif +} + +void +spectator::spectator(void) +{ + modelindex = 0; + flags = 0; + solid = SOLID_NOT; + movetype = MOVETYPE_NOCLIP; + think = __NULL__; + nextthink = 0.0f; + maxspeed = 250; + +#ifdef SERVER + forceinfokey(this, "*spec", "1"); +#endif +}