Skip to content

Explore refactoring of how we sends player to spec mode on put in server #47

@rtxa

Description

@rtxa

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 spectator cooldown 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 Spectator message 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();
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions