Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
rtxa opened this issue Sep 5, 2024 · 0 comments
Open

Comments

@rtxa
Copy link
Owner

rtxa commented Sep 5, 2024

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();
	}
}
@rtxa rtxa changed the title Explore refactoring how we sends player to spec mode Explore refactoring of how we sends player to spec mode Sep 5, 2024
@rtxa rtxa pinned this issue Sep 24, 2024
@rtxa rtxa changed the title Explore refactoring of how we sends player to spec mode Explore refactoring of how we sends player to spec mode on put in server Oct 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant