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
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();
}
}
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 becauseCBasePlayer::UpdateClientData()is being send twice. Why? Because when you join the server,UpdateClientData()gets send, but when we want to set player to spec, insideCBasePlayer::StartObserver(), the functionRemoveAllItems()send againsUpdateClientData(). Sending it twice is the origin of these glitches. https://github.com/rtxa/BugfixedHL/blob/master/dlls/observer.cpp#L52If we come to a fix, the benefits are:
spectate, making code more easy to reason or read.spectatorcooldown without worrying about player not being send to spec.Solution 1: Prevent BHL from calling
UpdateClientData()on PutInServerWe can fix this by implementing a check for
RemoveAllItems().Solution 2: Implement set_user_spectator() from AMXX side
Benefits
spectate.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
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.