-
Notifications
You must be signed in to change notification settings - Fork 11
Description
Origin of the issue
When player joins the server (PutInServer), he can't be send to spec mode instantly, because it glitchs the scoreboard, I workaround this by using set_task() with the minimum value (0.1). This is because CBasePlayer::UpdateClientData() is being send twice. Why? Because when you join the server, UpdateClientData() gets send, but when we want to set player to spec, inside CBasePlayer::StartObserver(), the function RemoveAllItems() send agains UpdateClientData(). Sending it twice is the origin of these glitches. https://github.com/rtxa/BugfixedHL/blob/master/dlls/observer.cpp#L52
If we come to a fix, the benefits are:
- Not need to rely on set_tasks() or using cmd
spectate, making code more easy to reason or read. - No need to manipulate BHL cvars about
spectatorcooldown without worrying about player not being send to spec.
Solution 1: Prevent BHL from calling UpdateClientData() on PutInServer
We can fix this by implementing a check for RemoveAllItems().
void CBasePlayer::StartObserver( void )
{
...
// Remove all the player's stuff.
// Also prevent calling UpdateClientData() twice when sending to spec on put in server
if (m_bPutInServer)
RemoveAllItems(FALSE, /*UpdateClientData =*/ FALSE);
else
RemoveAllItems(FALSE);Solution 2: Implement set_user_spectator() from AMXX side
Benefits
- Not need to rely on set_task() or using cmd
spectate. - We can remove the send of
Spectatormessage in LTS/LMS to allow players to be send to spectator without displaying it in the scoreboard. This avoids hooking messages at the right moment.
Disadvantages
- We need to keep it in sync with BHL code in case of future updates.
This AMXX implementation tries to mirror how the GameDLL (BugfixedHL) sets spectators. There is some stuff I couldn't mirror well like the removal of players weapons, because from AMXX side, this requires creating an entity to call player_weaponstrip (bad for servers who don't have too much entities to spares). Also, I don't have access to all the members from AMXX side.
// Matchs BugfixedHL behaviour, this should avoid using set_task and fix
// bug when using it on putinserver
stock ag_set_user_spectator(id, bool:spectator = true, onPutInServer = false) {
if (spectator) {
StartObserver(id, onPutInServer);
} else {
StopObserver(id);
}
}
StartObserver(id, onPutInServer = false) {
new Float:origin[3];
pev(id, pev_origin, origin);
message_begin_f(MSG_PAS, SVC_TEMPENTITY, origin);
write_byte(TE_KILLPLAYERATTACHMENTS);
write_byte(id);
message_end();
new pTank = get_ent_data_entity(id, "CBasePlayer", "m_pTank");
if (pTank != FM_NULLENT) {
ExecuteHamB(Ham_Use, pTank, id, id, USE_OFF, 0.0);
}
// Strip user weapons by default send UpdateClientData()
// which is already sent when putting in server, sending it again
// brokes the scoreboard for the player.
if (onPutInServer) {
set_pev(id, pev_weapons, 0);
} else {
hl_strip_user_weapons(id); // Remove all items (not the HEV suit)
}
// Set HEV sounds off
for (new i; i < get_ent_data_size("CBasePlayer", "m_rgSuitPlayList"); i++) {
set_ent_data(id, "CBasePlayer", "m_rgSuitPlayList", i);
}
static CurWeapon;
if (CurWeapon || (CurWeapon = get_user_msgid("CurWeapon"))) {
message_begin(MSG_ONE, CurWeapon, .player = id);
write_byte(0);
write_byte(0);
write_byte(0);
message_end();
}
set_ent_data(id, "CBasePlayer", "m_iClientFOV", 0);
set_ent_data(id, "CBasePlayer", "m_iFOV", 0);
set_pev(id, pev_fov, 0);
static SetFOV;
if (SetFOV || (SetFOV = get_user_msgid("SetFOV"))) {
message_begin(MSG_ONE, get_user_msgid("SetFOV"), .player = id);
write_byte(0);
message_end();
}
// store view_ofs
new Float:view_ofs[3];
pev(id, pev_view_ofs, view_ofs);
// setup flags
set_ent_data(id, "CBasePlayer", "m_iHideHUD", HIDEHUD_WEAPONS | HIDEHUD_HEALTH);
set_ent_data(id, "CBasePlayer", "m_afPhysicsFlags", get_ent_data(id, "CBasePlayer", "m_afPhysicsFlags") | PFLAG_OBSERVER);
set_pev(id, pev_view_ofs, NULL_VECTOR);
set_pev(id, pev_fixangle, 1);
set_pev(id, pev_solid, SOLID_NOT);
set_pev(id, pev_takedamage, DAMAGE_NO);
set_pev(id, pev_movetype, MOVETYPE_NONE);
set_ent_data(id, "CBasePlayer", "m_afPhysicsFlags", get_ent_data(id, "CBasePlayer", "m_afPhysicsFlags") & ~PFLAG_DUCKING);
set_pev(id, pev_flags, pev(id, pev_flags) & ~FL_DUCKING);
set_pev(id, pev_deadflag, DEAD_RESPAWNABLE);
set_pev(id, pev_health, 1.0);
set_pev(id, pev_effects, EF_NODRAW);
// Clear out the status bar
set_ent_data(id, "CBasePlayer", "m_fInitHUD", 1);
// Clear welcome cam status
set_ent_data(id, "CBasePlayer", "m_bInWelcomeCam", 0);
// set spectator at te same position of spawn
new Float:specPos[3];
xs_vec_add(origin, view_ofs, specPos);
entity_set_origin(id, specPos);
set_ent_data_float(id, "CBasePlayer", "m_flNextObserverInput", 0.0);
// Observer_SetMode()
set_pev(id, pev_iuser1, OBS_ROAMING);
set_pev(id, pev_iuser3, 0);
static TeamInfo;
if (TeamInfo || (TeamInfo = get_user_msgid("TeamInfo"))) {
message_begin(MSG_ALL, TeamInfo);
write_byte(id);
write_string("");
message_end();
}
// Message used by AG/OpenAG clients
static Spectator;
if (Spectator || (Spectator = get_user_msgid("Spectator"))) {
message_begin(MSG_ALL, Spectator);
write_byte(id);
write_byte(1);
message_end();
}
}
StopObserver(id) {
// Turn off spectator
set_pev(id, pev_iuser1, 0);
set_pev(id, pev_iuser2, 0);
set_ent_data(id, "CBasePlayer", "m_iHideHUD", 0);
// dllfunc(DllFunc_Spawn, id);
ExecuteHamB(Ham_Spawn, id);
set_pev(id, pev_nextthink, -1);
// Message used by AG/OpenAG clients
static Spectator;
if (Spectator || (Spectator = get_user_msgid("Spectator"))) {
message_begin(MSG_ALL, Spectator);
write_byte(id);
write_byte(0);
message_end();
}
new teamName[HL_MAX_TEAMNAME_LENGTH];
// Only get it when fully connected
if (pev_valid(id) == 2) {
get_ent_data_string(id, "CBasePlayer", "m_szTeamName", teamName, charsmax(teamName));
}
new Float:isTeamPlay;
global_get(glb_teamplay, isTeamPlay);
// Update Team Status
static TeamInfo;
if (TeamInfo || (TeamInfo = get_user_msgid("TeamInfo"))) {
message_begin(MSG_ALL, TeamInfo);
write_byte(id);
// if (get_cvar_num("mp_teamplay") == 1)
if (isTeamPlay == 1.0)
write_string(teamName);
else
write_string("Players");
message_end();
}
}