Skip to content

Forget AI Memory & Barnacle Prediction Fixes #205

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

Merged
merged 6 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripting/include/srccoop.inc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#define ENTPATCH_FUNC_TRACKAUTOCHANGE
#define ENTPATCH_FUNC_TRACKTRAIN
#define ENTPATCH_NPC_RUNTASK
#define ENTPATCH_BARNACLE_PREDICTION

#define PLAYERPATCH_SUIT_SOUNDS
#define PLAYERPATCH_PICKUP_FORCEPLAYERTODROPTHISOBJECT
Expand Down Expand Up @@ -97,6 +98,7 @@
#define ENTPATCH_FUNC_TRACKAUTOCHANGE
#define ENTPATCH_FUNC_TRACKTRAIN
#define ENTPATCH_NPC_RUNTASK
#define ENTPATCH_BARNACLE_PREDICTION
#define ENTPATCH_BM_XENTURRET
#define ENTPATCH_BM_ICHTHYOSAUR
#define ENTPATCH_BM_GARGANTUA
Expand Down
12 changes: 9 additions & 3 deletions scripting/include/srccoop/blackmesa/entitypatch.inc
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,10 @@ public void Timer_SetTripmineSpriteColor(Handle timer, CSpriteTeam pSpriteTeam)
//------------------------------------------------------
public void Hook_GrenadeFragSpawn(const int iEntIndex)
{
g_bIsMultiplayerOverride = false;
if (CoopManager.IsFeatureEnabled(FT_SP_WEAPONS))
{
g_bIsMultiplayerOverride = false;
}
}

//------------------------------------------------------
Expand All @@ -766,7 +769,10 @@ public void Hook_GrenadeFragSpawn(const int iEntIndex)
//------------------------------------------------------
public void Hook_GrenadeFragSpawnPost(const int iEntIndex)
{
g_bIsMultiplayerOverride = true;
if (CoopManager.IsFeatureEnabled(FT_SP_WEAPONS))
{
g_bIsMultiplayerOverride = true;
}
}

//------------------------------------------------------
Expand Down Expand Up @@ -1126,7 +1132,7 @@ public MRESReturn Hook_CParamsManager_InitInstances(Address _this, Handle hRetur
// TODO:
// Currently, this is getting only called during level init, but
// we also need to call this if the feature is turned on/off mid-round.
CParamsManager pParamsManager = CParamsManager(_this);
CParamsManager pParamsManager = CParamsManager.FromAddress(_this);
pParamsManager.SetMultiplayer(!CoopManager.IsFeatureEnabled(FT_SP_WEAPONS));
return MRES_Ignored;
}
58 changes: 56 additions & 2 deletions scripting/include/srccoop/entitypatch.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1513,7 +1513,8 @@ public MRESReturn Hook_UTIL_FindClientInPVSGuts(DHookReturn hReturn, DHookParam
{
// Naive solution with fast execution.
// The original implementation below this implementation causes issues with NPCs doing nothing.
DHookSetReturn(hReturn, GetNearestPlayerPreferAlive(CBaseEntity(hParams.Get(1))).entindex);
CBaseEntity pEntity = CBaseEntity(hParams.Get(1));
DHookSetReturn(hReturn, (pEntity != NULL_CBASEENTITY) ? GetNearestPlayerPreferAlive(pEntity).entindex : -1);
return MRES_Supercede;

/*
Expand Down Expand Up @@ -1568,4 +1569,57 @@ public MRESReturn Hook_ScriptedSequenceStartScript(int _this)
}
}
return MRES_Ignored;
}
}

//------------------------------------------------------
// npc_barnacle
// Fixes prediction and view jitter while being grabbed by a barnacle.
//------------------------------------------------------
public void Hook_Barnacle_OnGrab(const char[] szOutput, const int iCaller, const int iActivator, const float flDelay)
{
CNPC_Barnacle pBarnacle = CNPC_Barnacle(iCaller);

// The enemy of the barnacle is not set until after `CNPC_Barnacle::AttachTongueToTarget` is finished executing.
// https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/hl2/npc_barnacle.cpp#L1359
CreateTimer(0.0, Timer_Barnacle_OnGrab, pBarnacle, TIMER_FLAG_NO_MAPCHANGE);
}

//------------------------------------------------------
// npc_barnacle
// Fixes prediction and view jitter while being grabbed by a barnacle.
//------------------------------------------------------
public void Timer_Barnacle_OnGrab(Handle hTimer, const CNPC_Barnacle pBarnacle)
{
if (pBarnacle.IsValid() && pBarnacle.IsAlive())
{
CBaseEntity pEnemy = pBarnacle.GetEnemy();
if (pEnemy != NULL_CBASEENTITY && pEnemy.IsPlayer())
{
CBasePlayer pPlayer = view_as<CBasePlayer>(pEnemy);

// Prevents client-side prediction errors for movement while being pulled up.
pPlayer.m_fFlags |= FL_ATCONTROLS;
// Removes the initial view jitter when being pulled up.
pPlayer.SetMoveType(MOVETYPE_FLY);
// Remove the view jitter while being pulled up.
pPlayer.SetLaggedMovement(0.0);
}
}
}

//------------------------------------------------------
// npc_barnacle
// Fixes prediction and view jitter while being grabbed by a barnacle.
//------------------------------------------------------
public void Hook_Barnacle_OnRelease(const char[] szOutput, const int iCaller, const int iActivator, const float flDelay)
{
CNPC_Barnacle pBarnacle = CNPC_Barnacle(iCaller);
CBaseEntity pEnemy = pBarnacle.GetEnemy();
if (pEnemy != NULL_CBASEENTITY && pEnemy.IsPlayer())
{
CBasePlayer pPlayer = view_as<CBasePlayer>(pEnemy);
pPlayer.m_fFlags &= ~FL_ATCONTROLS;
pPlayer.SetMoveType(MOVETYPE_WALK);
pPlayer.SetLaggedMovement(1.0);
}
}
185 changes: 113 additions & 72 deletions scripting/include/srccoop/playerpatch.inc
Original file line number Diff line number Diff line change
Expand Up @@ -353,80 +353,79 @@ public MRESReturn Hook_PlayerSpawnPost(int iClient, Handle hReturn, Handle hPara
return MRES_Ignored;

CBasePlayer pPlayer = CBasePlayer(iClient);
if (pPlayer.IsValid())
if (pPlayer.IsAlive())
{
if (pPlayer.IsAlive())
ClearNpcMemoryForPlayer(pPlayer);

// Ensure death camera and ragdoll get removed
CBaseEntity pRagdoll = pPlayer.GetRagdoll();
if (pRagdoll != NULL_CBASEENTITY)
{
// Ensure death camera and ragdoll get removed
CBaseEntity pRagdoll = pPlayer.GetRagdoll();
if (pRagdoll.IsValid())
{
pRagdoll.Kill();
}
pRagdoll.Kill();
}

// Ensure view entity is player and observer mode is off
// Set before spawnsystem SpawnPlayer in case view entitiy is changed there
pPlayer.SetViewEntity(pPlayer);
pPlayer.SetObserverMode(OBS_MODE_NONE);
// Ensure view entity is player and observer mode is off
// Set before spawnsystem SpawnPlayer in case view entity is changed there
pPlayer.SetViewEntity(pPlayer);
pPlayer.SetObserverMode(OBS_MODE_NONE);

// Fix for if player died on a ladder
SetEntPropEnt(pPlayer.entindex, Prop_Data, "m_hLadder", -1);
// Fix for if player died on a ladder
pPlayer.SetLadder(NULL_CBASEENTITY);

// Apply on-spawn convars
SendClientConvars(iClient, true);
// Apply on-spawn convars
SendClientConvars(iClient, true);

// Correct the 'damages' from the prethink hook hack
#if defined SRCCOOP_BLACKMESA
pPlayer.SetSuit(false);
pPlayer.SetMaxSpeed(190.0);
pPlayer.SetIsSprinting(false);
pPlayer.m_usSolidFlags &= ~FSOLID_NOT_SOLID;
#endif
// Correct the 'damages' from the prethink hook hack
#if defined SRCCOOP_BLACKMESA
pPlayer.SetSuit(false);
pPlayer.SetMaxSpeed(190.0);
pPlayer.SetIsSprinting(false);
pPlayer.m_usSolidFlags &= ~FSOLID_NOT_SOLID;
#endif

// Stop HEV sounds
#if defined PLAYERPATCH_SUIT_SOUNDS
pPlayer.ClearSuitQueue();
pPlayer.StopSound(SNDCHAN_STATIC, szLastSuitSound[iClient]);
#endif

CoopManager.OnPlayerSpawned(pPlayer);
}
else // Not alive => entered the game
{
// Set initial view
CCoopSpawnSystem.TeleportPlayerOnSpawn(pPlayer);

// Stop HEV sounds
#if defined PLAYERPATCH_SUIT_SOUNDS
pPlayer.ClearSuitQueue();
StopSound(iClient, SNDCHAN_STATIC, szLastSuitSound[iClient]);
#endif
// Initialize clientside convars from skill.cfg, which may have not executed.
#if defined SRCCOOP_BLACKMESA
pPlayer.QueryConvar("r_bloomtintexponent_nextgen", QueryConVar_Bloom, 2.2);
pPlayer.QueryConvar("r_bloomtintr_nextgen", QueryConVar_Bloom, 0.25);
pPlayer.QueryConvar("r_bloomtintg_nextgen", QueryConVar_Bloom, 0.25);
pPlayer.QueryConvar("r_bloomtintb_nextgen", QueryConVar_Bloom, 0.25);
pPlayer.QueryConvar("gb_flashlight_intensity", QueryConVar_Bloom, 0.8);
#endif

#if defined GAMEPATCH_TEAMSELECT_UI

CoopManager.OnPlayerSpawned(pPlayer);
}
else // Not alive => entered the game
if (pPlayer.GetTeam() == TEAM_SPECTATOR)
{
// Set initial view
CCoopSpawnSystem.TeleportPlayerOnSpawn(pPlayer);

// Initialize clientside convars from skill.cfg, which may have not executed.
#if defined SRCCOOP_BLACKMESA
QueryClientConVar(iClient, "r_bloomtintexponent_nextgen", QueryConVar_Bloom, 2.2);
QueryClientConVar(iClient, "r_bloomtintr_nextgen", QueryConVar_Bloom, 0.25);
QueryClientConVar(iClient, "r_bloomtintg_nextgen", QueryConVar_Bloom, 0.25);
QueryClientConVar(iClient, "r_bloomtintb_nextgen", QueryConVar_Bloom, 0.25);
QueryClientConVar(iClient, "gb_flashlight_intensity", QueryConVar_Bloom, 0.8);
#endif

#if defined GAMEPATCH_TEAMSELECT_UI

if (GetClientTeam(iClient) == TEAM_SPECTATOR)
if (g_pConvarDisableTeamSelect.BoolValue)
{
if (g_pConvarDisableTeamSelect.BoolValue)
{
// Hide team select
ClientCommand(iClient, "hidepanel team");
ClientCommand(iClient, "hidepanel deathmatch");
ChangeClientTeam(iClient, TEAM_SCIENTIST);
g_bPostTeamSelect[iClient] = true;
}
else
{
// Always show DM variant of team select
ClientCommand(iClient, "hidepanel team");
ClientCommand(iClient, "showpanel deathmatch");
}
// Hide team select
pPlayer.SendCommand("hidepanel team");
pPlayer.SendCommand("hidepanel deathmatch");
pPlayer.SetTeam(TEAM_SCIENTIST);
g_bPostTeamSelect[iClient] = true;
}
else
{
// Always show DM variant of team select
pPlayer.SendCommand("hidepanel team");
pPlayer.SendCommand("showpanel deathmatch");
}

#endif // GAMEPATCH_TEAMSELECT_UI
}

#endif // GAMEPATCH_TEAMSELECT_UI
}
return MRES_Ignored;
}
Expand Down Expand Up @@ -460,10 +459,11 @@ public MRESReturn Hook_PlayerKilledPost(int _this, DHookParam hParams)
return MRES_Ignored;

CBasePlayer pPlayer = CBasePlayer(_this);
ClearNpcMemoryForPlayer(pPlayer);

#if defined PLAYERPATCH_SERVERSIDE_RAGDOLLS
Address pTakeDmgInfo = hParams.GetAddress(1);
PlayerPatch_ApplyServerRagdoll(pPlayer, pTakeDmgInfo);
CTakeDamageInfo pTakeDamageInfo = CTakeDamageInfo.FromAddress(hParams.GetAddress(1));
PlayerPatch_ApplyServerRagdoll(pPlayer, pTakeDamageInfo);
#endif

SurvivalManager.OnPlayerDeath(pPlayer);
Expand All @@ -473,16 +473,16 @@ public MRESReturn Hook_PlayerKilledPost(int _this, DHookParam hParams)

#if defined PLAYERPATCH_SERVERSIDE_RAGDOLLS

void PlayerPatch_ApplyServerRagdoll(CBasePlayer pPlayer, Address pTakeDmgInfo)
void PlayerPatch_ApplyServerRagdoll(const CBasePlayer pPlayer, const CTakeDamageInfo pTakeDamageInfo)
{
int iDmgType = LoadFromAddress(pTakeDmgInfo + view_as<Address>(60), NumberType_Int32);
if (iDmgType & DMG_DISSOLVE)
int iDamageType = pTakeDamageInfo.GetDamageType();
if (iDamageType & DMG_DISSOLVE)
{
// dont bother
return;
}

CBaseAnimating pRagdoll = pPlayer.GetRagdoll();
CBaseEntity pRagdoll = pPlayer.GetRagdoll();
if (pRagdoll != NULL_CBASEENTITY)
{
pRagdoll.Kill();
Expand Down Expand Up @@ -513,12 +513,34 @@ void PlayerPatch_ApplyServerRagdoll(CBasePlayer pPlayer, Address pTakeDmgInfo)
#else
int iRagdollInheritIndex = pPlayer.entindex;
#endif

pRagdoll = CBaseAnimating(SDKCall(g_pCreateServerRagdoll, iRagdollInheritIndex, pPlayer.GetForceBone(), pTakeDmgInfo, COLLISION_GROUP_DEBRIS_TRIGGER, false));
if (pRagdoll.IsValid())

// TODO:
// Move `g_pCreateServerRagdoll` into `CRagdollProp`.
pRagdoll = CRagdollProp(SDKCall(g_pCreateServerRagdoll, iRagdollInheritIndex, pPlayer.GetForceBone(), pTakeDamageInfo, COLLISION_GROUP_DEBRIS_TRIGGER, false));
if (pRagdoll != NULL_CBASEENTITY)
{
SDKHook(pRagdoll.entindex, SDKHook_OnTakeDamage, Hook_NoDmg);

// TODO: If killed by a player, attach ragdoll to barnacle
//CBaseEntity pInflictor = pTakeDamageInfo.GetInflictor();
//if (pInflictor != NULL_CBASEENTITY)
//{
// char szClassname[MAX_CLASSNAME];
// pInflictor.GetClassname(szClassname, sizeof(szClassname));
// if (strcmp(szClassname, "npc_barnacle") == 0)
// {
// CNPC_Barnacle pBarnacle = view_as<CNPC_Barnacle>(pInflictor);
//
// float vec3GrabPosition[3];
// pPlayer.GetEyePosition(vec3GrabPosition);
//
// float vec3Position[3];
// pPlayer.GetAbsOrigin(vec3Position);
// pBarnacle.AttachTongueToTarget(pRagdoll, vec3GrabPosition);
// pRagdoll.m_iEFlags |= EFL_IS_BEING_LIFTED_BY_BARNACLE;
// }
//}

pPlayer.SetRagdoll(pRagdoll);
pPlayer.SetObserverMode(OBS_MODE_CHASE);
pPlayer.SetObserverTarget(pRagdoll);
Expand Down Expand Up @@ -675,10 +697,11 @@ public MRESReturn Hook_PlayerChangeTeamPost(int iClient, Handle hParams)
if (iTeamNum == TEAM_SPECTATOR)
{
CBaseEntity pRagdoll = pPlayer.GetRagdoll();
if (pRagdoll.IsValid())
if (pRagdoll != NULL_CBASEENTITY)
{
pRagdoll.Kill();
}
ClearNpcMemoryForPlayer(pPlayer);
}

SurvivalManager.OnPlayerChangeTeam(pPlayer, iTeamNum);
Expand Down Expand Up @@ -1009,3 +1032,21 @@ public bool TraceEntityFilter_IgnorePlayers(int iEntIndex, int iMask, any pData)
return false;
return (iEntIndex != pData);
}

// Clears the player out of NPC memories and clears flags.
//
static void ClearNpcMemoryForPlayer(const CBasePlayer pPlayer)
{
int iEntIndex = -1;
while ((iEntIndex = FindEntityByClassname(iEntIndex, "*")) != -1)
{
CBaseEntity pEntity = CBaseEntity(iEntIndex);
if (pEntity.IsNPC())
{
CAI_BaseNPC pNPC = view_as<CAI_BaseNPC>(pEntity);
pNPC.ForgetEntity(pPlayer);
}
}

pPlayer.m_iEFlags &= ~EFL_IS_BEING_LIFTED_BY_BARNACLE;
}
5 changes: 5 additions & 0 deletions scripting/include/srccoop/utils.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1070,3 +1070,8 @@ stock void SetBitOnInt(int& i, const int iBitIndex, const bool bSetBit)
i &= ~(1 << iBitIndex);
}
}

stock CBaseEntity GetEntityFromEHandlePointer(const Address pAddress)
{
return CBaseEntity(LoadFromAddress(pAddress, NumberType_Int32) | (1 << 31));
}
Loading
Loading