Fix unwanted head turn reset - FreeSlave/halflife-updated GitHub Wiki

Talk monster (scientist or security guard) may stop looking at their talk target during the speech. E.g you might noticed scientists stop looking at you as a player, even though they still have much to say. Let's search for the root of the issue.

Take a look at code choosing the idle schedule for talkmonster

if (IsTalking())
	// look at who we're talking to
	return slTlkIdleEyecontact;
else
	// regular standing idle
	return slIdleStand;

If monster is currently talking then slTlkIdleEyecontact is chosen and everything is okay, as TASK_TLK_EYECONTACT (which is part of the schedule) constantly makes a monster to turn the head to its talk target. Otherwise slIdleStand is chosen. Scientist code checks for that schedule and leads to choosing the slIdleSciStand instead.

psched = CTalkMonster::GetScheduleOfType(Type);

if (psched == slIdleStand)
	return slIdleSciStand;
else
	return psched;

We won't discuss how smelly this code is, instead we'll take a look at the task list

Task_t tlIdleSciStand[] =
{
	{TASK_STOP_MOVING, 0},
	{TASK_SET_ACTIVITY, (float)ACT_IDLE},
	{TASK_WAIT, (float)2},			// repick IDLESTAND every two seconds.
	{TASK_TLK_HEADRESET, (float)0}, // reset head position
};

The TASK_TLK_HEADRESET in its turn clears the talk target

case TASK_TLK_HEADRESET:
	// reset head position after looking at something
	m_hTalkTarget = NULL;
	TaskComplete();
	break;

Also notice the TASK_WAIT which makes the monster to wait 2 seconds before performing the head reset, just playing the idle animation.

Now imagine the situation when the monster gets something to say to other npc or a player during these 2 seconds. The monster gets talk target, but schedule continues performing as usual. There's a common code for talkmonster's tasks to check if monster has a talk target and turn the head to them, and TASK_WAIT falls here too.

if (IsTalking() && m_hTalkTarget != NULL)
{
	IdleHeadTurn(m_hTalkTarget->pev->origin);
}
else
{
	SetBoneController(0, 0);
}

But when TASK_WAIT is completed, the schedule proceeds to TASK_TLK_HEADRESET which resets the talk monster and common code above falls into another branch resetting the head bone controller. At this point the m_hTalkTarget for monster is lost and any other task will not be able to make a monster turn their head to the talk target as there's none.

Yay, we've got to the root of evil! What can we do about that? As a simple fix you can go to the TASK_TLK_HEADRESET implementation and apply this modification:

case TASK_TLK_HEADRESET:
	// reset head position after looking at something
-	m_hTalkTarget = NULL;
+	if (!IsTalking())
+		m_hTalkTarget = NULL;
	TaskComplete();
	break;

If the monster's still talking the talk target won't be lost. Eventually it will reset in some other idle schedule when monster is not talking anymore.