2022-12-17 13:32:43 +01:00
|
|
|
/***
|
2013-08-30 13:34:05 -07:00
|
|
|
*
|
|
|
|
* Copyright (c) 1996-2001, Valve LLC. All rights reserved.
|
|
|
|
*
|
|
|
|
* This product contains software technology licensed from Id
|
|
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
|
|
* All Rights Reserved.
|
|
|
|
*
|
|
|
|
* This source code contains proprietary and confidential information of
|
|
|
|
* Valve LLC and its suppliers. Access to this code is restricted to
|
|
|
|
* persons who have executed a written SDK license with Valve. Any access,
|
|
|
|
* use or distribution of this code by or to any unlicensed person is illegal.
|
|
|
|
*
|
|
|
|
****/
|
|
|
|
//=========================================================
|
|
|
|
// human scientist (passive lab worker)
|
|
|
|
//=========================================================
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
#include "extdll.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "monsters.h"
|
|
|
|
#include "talkmonster.h"
|
|
|
|
#include "schedule.h"
|
|
|
|
#include "defaultai.h"
|
|
|
|
#include "scripted.h"
|
|
|
|
#include "animation.h"
|
|
|
|
#include "soundent.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
HEAD_GLASSES = 0,
|
|
|
|
HEAD_EINSTEIN = 1,
|
|
|
|
HEAD_LUTHER = 2,
|
|
|
|
HEAD_SLICK = 3
|
|
|
|
};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1,
|
|
|
|
SCHED_FEAR,
|
|
|
|
SCHED_PANIC,
|
|
|
|
SCHED_STARTLE,
|
|
|
|
SCHED_TARGET_CHASE_SCARED,
|
|
|
|
SCHED_TARGET_FACE_SCARED,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1,
|
|
|
|
TASK_HEAL,
|
|
|
|
TASK_SAY_FEAR,
|
|
|
|
TASK_RUN_PATH_SCARED,
|
|
|
|
TASK_SCREAM,
|
|
|
|
TASK_RANDOM_SCREAM,
|
|
|
|
TASK_MOVE_TO_TARGET_RANGE_SCARED,
|
|
|
|
};
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// Monster's Anim Events Go Here
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
#define SCIENTIST_AE_HEAL (1)
|
|
|
|
#define SCIENTIST_AE_NEEDLEON (2)
|
|
|
|
#define SCIENTIST_AE_NEEDLEOFF (3)
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
//=======================================================
|
|
|
|
// Scientist
|
|
|
|
//=======================================================
|
|
|
|
|
|
|
|
class CScientist : public CTalkMonster
|
|
|
|
{
|
|
|
|
public:
|
2021-03-05 23:07:22 +01:00
|
|
|
void Spawn() override;
|
|
|
|
void Precache() override;
|
|
|
|
|
|
|
|
void SetYawSpeed() override;
|
2021-11-28 16:54:48 +01:00
|
|
|
int Classify() override;
|
|
|
|
void HandleAnimEvent(MonsterEvent_t* pEvent) override;
|
|
|
|
void RunTask(Task_t* pTask) override;
|
|
|
|
void StartTask(Task_t* pTask) override;
|
2021-11-29 20:31:17 +01:00
|
|
|
int ObjectCaps() override { return CTalkMonster::ObjectCaps() | FCAP_IMPULSE_USE; }
|
2021-11-28 16:54:48 +01:00
|
|
|
bool TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) override;
|
|
|
|
int FriendNumber(int arrayNumber) override;
|
|
|
|
void SetActivity(Activity newActivity) override;
|
2021-03-05 23:07:22 +01:00
|
|
|
Activity GetStoppedActivity() override;
|
|
|
|
int ISoundMask() override;
|
|
|
|
void DeclineFollowing() override;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
float CoverRadius() override { return 1200; } // Need more room for cover because scientists want to get far away!
|
|
|
|
bool DisregardEnemy(CBaseEntity* pEnemy) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; }
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
bool CanHeal();
|
|
|
|
void Heal();
|
|
|
|
void Scream();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Override these to set behavior
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t* GetScheduleOfType(int Type) override;
|
|
|
|
Schedule_t* GetSchedule() override;
|
|
|
|
MONSTERSTATE GetIdealState() override;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-03-05 23:07:22 +01:00
|
|
|
void DeathSound() override;
|
|
|
|
void PainSound() override;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void TalkInit();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2024-08-28 09:09:16 +02:00
|
|
|
char* GetScientistModel() const;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void Killed(entvars_t* pevAttacker, int iGib) override;
|
|
|
|
|
|
|
|
bool Save(CSave& save) override;
|
|
|
|
bool Restore(CRestore& restore) override;
|
|
|
|
static TYPEDESCRIPTION m_SaveData[];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
CUSTOM_SCHEDULES;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
private:
|
2013-08-30 13:34:05 -07:00
|
|
|
float m_painTime;
|
|
|
|
float m_healTime;
|
|
|
|
float m_fearTime;
|
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
LINK_ENTITY_TO_CLASS(monster_scientist, CScientist);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
TYPEDESCRIPTION CScientist::m_SaveData[] =
|
|
|
|
{
|
|
|
|
DEFINE_FIELD(CScientist, m_painTime, FIELD_TIME),
|
|
|
|
DEFINE_FIELD(CScientist, m_healTime, FIELD_TIME),
|
|
|
|
DEFINE_FIELD(CScientist, m_fearTime, FIELD_TIME),
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
IMPLEMENT_SAVERESTORE(CScientist, CTalkMonster);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2024-08-28 09:09:16 +02:00
|
|
|
char* CScientist::GetScientistModel() const
|
|
|
|
{
|
|
|
|
char* pszOverride = (char*)CVAR_GET_STRING("_sv_override_scientist_mdl");
|
|
|
|
if (pszOverride && strlen(pszOverride) > 5) // at least requires ".mdl"
|
|
|
|
{
|
|
|
|
return pszOverride;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "models/scientist.mdl";
|
|
|
|
}
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
|
|
|
// AI Schedules Specific to this monster
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlFollow[] =
|
|
|
|
{
|
|
|
|
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW}, // If you fail, bail out of follow
|
|
|
|
{TASK_MOVE_TO_TARGET_RANGE, (float)128}, // Move within 128 of target ent (client)
|
2021-11-29 20:55:01 +01:00
|
|
|
// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE },
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slFollow[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlFollow,
|
|
|
|
ARRAYSIZE(tlFollow),
|
|
|
|
bits_COND_NEW_ENEMY |
|
|
|
|
bits_COND_LIGHT_DAMAGE |
|
|
|
|
bits_COND_HEAVY_DAMAGE |
|
|
|
|
bits_COND_HEAR_SOUND,
|
|
|
|
bits_SOUND_COMBAT |
|
|
|
|
bits_SOUND_DANGER,
|
|
|
|
"Follow"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlFollowScared[] =
|
|
|
|
{
|
|
|
|
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE}, // If you fail, follow normally
|
|
|
|
{TASK_MOVE_TO_TARGET_RANGE_SCARED, (float)128}, // Move within 128 of target ent (client)
|
2021-11-29 20:55:01 +01:00
|
|
|
// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED },
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slFollowScared[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlFollowScared,
|
|
|
|
ARRAYSIZE(tlFollowScared),
|
|
|
|
bits_COND_NEW_ENEMY |
|
|
|
|
bits_COND_HEAR_SOUND |
|
|
|
|
bits_COND_LIGHT_DAMAGE |
|
|
|
|
bits_COND_HEAVY_DAMAGE,
|
|
|
|
bits_SOUND_DANGER,
|
|
|
|
"FollowScared"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlFaceTargetScared[] =
|
|
|
|
{
|
|
|
|
{TASK_FACE_TARGET, (float)0},
|
|
|
|
{TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE},
|
|
|
|
{TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slFaceTargetScared[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlFaceTargetScared,
|
|
|
|
ARRAYSIZE(tlFaceTargetScared),
|
|
|
|
bits_COND_HEAR_SOUND |
|
|
|
|
bits_COND_NEW_ENEMY,
|
|
|
|
bits_SOUND_DANGER,
|
|
|
|
"FaceTargetScared"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlStopFollowing[] =
|
|
|
|
{
|
|
|
|
{TASK_CANT_FOLLOW, (float)0},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slStopFollowing[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlStopFollowing,
|
|
|
|
ARRAYSIZE(tlStopFollowing),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
"StopFollowing"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlHeal[] =
|
|
|
|
{
|
|
|
|
{TASK_MOVE_TO_TARGET_RANGE, (float)50}, // Move within 60 of target ent (client)
|
|
|
|
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE}, // If you fail, catch up with that guy! (change this to put syringe away and then chase)
|
|
|
|
{TASK_FACE_IDEAL, (float)0},
|
|
|
|
{TASK_SAY_HEAL, (float)0},
|
|
|
|
{TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM}, // Whip out the needle
|
|
|
|
{TASK_HEAL, (float)0}, // Put it in the player
|
|
|
|
{TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM}, // Put away the needle
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slHeal[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlHeal,
|
|
|
|
ARRAYSIZE(tlHeal),
|
|
|
|
0, // Don't interrupt or he'll end up running around with a needle all the time
|
|
|
|
0,
|
|
|
|
"Heal"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlFaceTarget[] =
|
|
|
|
{
|
|
|
|
{TASK_STOP_MOVING, (float)0},
|
|
|
|
{TASK_FACE_TARGET, (float)0},
|
|
|
|
{TASK_SET_ACTIVITY, (float)ACT_IDLE},
|
|
|
|
{TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slFaceTarget[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlFaceTarget,
|
|
|
|
ARRAYSIZE(tlFaceTarget),
|
|
|
|
bits_COND_CLIENT_PUSH |
|
|
|
|
bits_COND_NEW_ENEMY |
|
|
|
|
bits_COND_HEAR_SOUND,
|
|
|
|
bits_SOUND_COMBAT |
|
|
|
|
bits_SOUND_DANGER,
|
|
|
|
"FaceTarget"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlSciPanic[] =
|
|
|
|
{
|
|
|
|
{TASK_STOP_MOVING, (float)0},
|
|
|
|
{TASK_FACE_ENEMY, (float)0},
|
|
|
|
{TASK_SCREAM, (float)0},
|
|
|
|
{TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED}, // This is really fear-stricken excitement
|
|
|
|
{TASK_SET_ACTIVITY, (float)ACT_IDLE},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slSciPanic[] =
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
{tlSciPanic,
|
|
|
|
ARRAYSIZE(tlSciPanic),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
"SciPanic"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
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
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slIdleSciStand[] =
|
|
|
|
{
|
|
|
|
{tlIdleSciStand,
|
|
|
|
ARRAYSIZE(tlIdleSciStand),
|
|
|
|
bits_COND_NEW_ENEMY |
|
|
|
|
bits_COND_LIGHT_DAMAGE |
|
|
|
|
bits_COND_HEAVY_DAMAGE |
|
|
|
|
bits_COND_HEAR_SOUND |
|
|
|
|
bits_COND_SMELL |
|
|
|
|
bits_COND_CLIENT_PUSH |
|
|
|
|
bits_COND_PROVOKED,
|
|
|
|
|
|
|
|
bits_SOUND_COMBAT | // sound flags
|
|
|
|
//bits_SOUND_PLAYER |
|
|
|
|
//bits_SOUND_WORLD |
|
|
|
|
bits_SOUND_DANGER |
|
|
|
|
bits_SOUND_MEAT | // scents
|
|
|
|
bits_SOUND_CARCASS |
|
|
|
|
bits_SOUND_GARBAGE,
|
|
|
|
"IdleSciStand"
|
|
|
|
|
|
|
|
},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlScientistCover[] =
|
|
|
|
{
|
|
|
|
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC}, // If you fail, just panic!
|
|
|
|
{TASK_STOP_MOVING, (float)0},
|
|
|
|
{TASK_FIND_COVER_FROM_ENEMY, (float)0},
|
|
|
|
{TASK_RUN_PATH_SCARED, (float)0},
|
|
|
|
{TASK_TURN_LEFT, (float)179},
|
|
|
|
{TASK_SET_SCHEDULE, (float)SCHED_HIDE},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slScientistCover[] =
|
|
|
|
{
|
|
|
|
{tlScientistCover,
|
|
|
|
ARRAYSIZE(tlScientistCover),
|
|
|
|
bits_COND_NEW_ENEMY,
|
|
|
|
0,
|
|
|
|
"ScientistCover"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlScientistHide[] =
|
|
|
|
{
|
|
|
|
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC}, // If you fail, just panic!
|
|
|
|
{TASK_STOP_MOVING, (float)0},
|
|
|
|
{TASK_PLAY_SEQUENCE, (float)ACT_CROUCH},
|
|
|
|
{TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE}, // FIXME: This looks lame
|
|
|
|
{TASK_WAIT_RANDOM, (float)10.0},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slScientistHide[] =
|
|
|
|
{
|
|
|
|
{tlScientistHide,
|
|
|
|
ARRAYSIZE(tlScientistHide),
|
|
|
|
bits_COND_NEW_ENEMY |
|
|
|
|
bits_COND_HEAR_SOUND |
|
|
|
|
bits_COND_SEE_ENEMY |
|
|
|
|
bits_COND_SEE_HATE |
|
|
|
|
bits_COND_SEE_FEAR |
|
|
|
|
bits_COND_SEE_DISLIKE,
|
|
|
|
bits_SOUND_DANGER,
|
|
|
|
"ScientistHide"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlScientistStartle[] =
|
|
|
|
{
|
|
|
|
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC}, // If you fail, just panic!
|
|
|
|
{TASK_RANDOM_SCREAM, (float)0.3}, // Scream 30% of the time
|
|
|
|
{TASK_STOP_MOVING, (float)0},
|
|
|
|
{TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH},
|
|
|
|
{TASK_RANDOM_SCREAM, (float)0.1}, // Scream again 10% of the time
|
|
|
|
{TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE},
|
|
|
|
{TASK_WAIT_RANDOM, (float)1.0},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slScientistStartle[] =
|
|
|
|
{
|
|
|
|
{tlScientistStartle,
|
|
|
|
ARRAYSIZE(tlScientistStartle),
|
|
|
|
bits_COND_NEW_ENEMY |
|
|
|
|
bits_COND_SEE_ENEMY |
|
|
|
|
bits_COND_SEE_HATE |
|
|
|
|
bits_COND_SEE_FEAR |
|
|
|
|
bits_COND_SEE_DISLIKE,
|
|
|
|
0,
|
|
|
|
"ScientistStartle"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-09-30 05:06:07 -05:00
|
|
|
// Marphy Fact Files Fix - Restore fear display animation
|
2021-11-28 16:54:48 +01:00
|
|
|
Task_t tlFear[] =
|
|
|
|
{
|
|
|
|
{TASK_STOP_MOVING, (float)0},
|
|
|
|
{TASK_FACE_ENEMY, (float)0},
|
|
|
|
{TASK_SAY_FEAR, (float)0},
|
2022-08-06 13:41:59 +02:00
|
|
|
{TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_FEAR_DISPLAY},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t slFear[] =
|
|
|
|
{
|
|
|
|
{tlFear,
|
|
|
|
ARRAYSIZE(tlFear),
|
|
|
|
bits_COND_NEW_ENEMY,
|
|
|
|
0,
|
|
|
|
"Fear"},
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
DEFINE_CUSTOM_SCHEDULES(CScientist){
|
2013-08-30 13:34:05 -07:00
|
|
|
slFollow,
|
|
|
|
slFaceTarget,
|
|
|
|
slIdleSciStand,
|
|
|
|
slFear,
|
|
|
|
slScientistCover,
|
|
|
|
slScientistHide,
|
|
|
|
slScientistStartle,
|
|
|
|
slHeal,
|
|
|
|
slStopFollowing,
|
|
|
|
slSciPanic,
|
|
|
|
slFollowScared,
|
|
|
|
slFaceTargetScared,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
IMPLEMENT_CUSTOM_SCHEDULES(CScientist, CTalkMonster);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void CScientist::DeclineFollowing()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
Talk(10);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_hTalkTarget = m_hEnemy;
|
2021-11-28 16:54:48 +01:00
|
|
|
PlaySentence("SC_POK", 2, VOL_NORM, ATTN_NORM);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::Scream()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2019-09-30 05:12:52 -05:00
|
|
|
// Marphy Fact Files Fix - This speech check always fails during combat, so removing
|
|
|
|
//if ( FOkToSpeak() )
|
|
|
|
//{
|
2022-08-06 13:41:59 +02:00
|
|
|
Talk(10);
|
|
|
|
m_hTalkTarget = m_hEnemy;
|
|
|
|
PlaySentence("SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM);
|
2019-09-30 05:12:52 -05:00
|
|
|
//}
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
Activity CScientist::GetStoppedActivity()
|
2021-11-28 16:54:48 +01:00
|
|
|
{
|
|
|
|
if (m_hEnemy != NULL)
|
2013-08-30 13:34:05 -07:00
|
|
|
return ACT_EXCITED;
|
|
|
|
return CTalkMonster::GetStoppedActivity();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::StartTask(Task_t* pTask)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (pTask->iTask)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
case TASK_SAY_HEAL:
|
2021-11-28 16:54:48 +01:00
|
|
|
// if ( FOkToSpeak() )
|
|
|
|
Talk(2);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_hTalkTarget = m_hTargetEnt;
|
2021-11-28 16:54:48 +01:00
|
|
|
PlaySentence("SC_HEAL", 2, VOL_NORM, ATTN_IDLE);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
TaskComplete();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_SCREAM:
|
|
|
|
Scream();
|
|
|
|
TaskComplete();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_RANDOM_SCREAM:
|
2021-11-28 16:54:48 +01:00
|
|
|
if (RANDOM_FLOAT(0, 1) < pTask->flData)
|
2013-08-30 13:34:05 -07:00
|
|
|
Scream();
|
|
|
|
TaskComplete();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_SAY_FEAR:
|
2019-09-30 05:12:52 -05:00
|
|
|
// Marphy Fact FIles Fix - This speech check always fails during combat, so removing
|
|
|
|
//if ( FOkToSpeak() )
|
2023-06-18 19:38:28 +02:00
|
|
|
if (m_hEnemy)
|
|
|
|
{
|
|
|
|
Talk(2);
|
|
|
|
m_hTalkTarget = m_hEnemy;
|
|
|
|
if (m_hEnemy->IsPlayer())
|
|
|
|
PlaySentence("SC_PLFEAR", 5, VOL_NORM, ATTN_NORM);
|
|
|
|
else
|
|
|
|
PlaySentence("SC_FEAR", 5, VOL_NORM, ATTN_NORM);
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
TaskComplete();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_HEAL:
|
|
|
|
m_IdealActivity = ACT_MELEE_ATTACK1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TASK_RUN_PATH_SCARED:
|
|
|
|
m_movementActivity = ACT_RUN_SCARED;
|
|
|
|
break;
|
|
|
|
|
2021-11-29 20:55:01 +01:00
|
|
|
case TASK_MOVE_TO_TARGET_RANGE_SCARED:
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((m_hTargetEnt->pev->origin - pev->origin).Length() < 1)
|
|
|
|
TaskComplete();
|
|
|
|
else
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_vecMoveGoal = m_hTargetEnt->pev->origin;
|
|
|
|
if (!MoveToTarget(ACT_WALK_SCARED, 0.5))
|
|
|
|
TaskFail();
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
break;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
default:
|
2021-11-28 16:54:48 +01:00
|
|
|
CTalkMonster::StartTask(pTask);
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::RunTask(Task_t* pTask)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (pTask->iTask)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
case TASK_RUN_PATH_SCARED:
|
2021-11-28 16:54:48 +01:00
|
|
|
if (MovementIsComplete())
|
2013-08-30 13:34:05 -07:00
|
|
|
TaskComplete();
|
2022-08-06 13:41:59 +02:00
|
|
|
|
2019-09-30 05:12:52 -05:00
|
|
|
// Marphy Fact Files Fix - Reducing scream (which didn't work before) chance significantly
|
|
|
|
//if ( RANDOM_LONG(0,31) < 8 )
|
2022-08-06 13:41:59 +02:00
|
|
|
if (RANDOM_LONG(0, 63) < 1)
|
2013-08-30 13:34:05 -07:00
|
|
|
Scream();
|
|
|
|
break;
|
|
|
|
|
2021-11-29 20:55:01 +01:00
|
|
|
case TASK_MOVE_TO_TARGET_RANGE_SCARED:
|
|
|
|
{
|
2022-08-06 13:41:59 +02:00
|
|
|
// Marphy Fact Files Fix - Removing redundant scream
|
|
|
|
//if ( RANDOM_LONG(0,63)< 8 )
|
|
|
|
//Scream();
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
if (m_hEnemy == NULL)
|
|
|
|
{
|
|
|
|
TaskFail();
|
|
|
|
}
|
|
|
|
else
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
float distance;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
distance = (m_vecMoveGoal - pev->origin).Length2D();
|
|
|
|
// Re-evaluate when you think your finished, or the target has moved too far
|
|
|
|
if ((distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_vecMoveGoal = m_hTargetEnt->pev->origin;
|
|
|
|
distance = (m_vecMoveGoal - pev->origin).Length2D();
|
|
|
|
FRefreshRoute();
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// Set the appropriate activity based on an overlapping range
|
|
|
|
// overlap the range to prevent oscillation
|
|
|
|
if (distance < pTask->flData)
|
|
|
|
{
|
|
|
|
TaskComplete();
|
|
|
|
RouteClear(); // Stop moving
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (distance < 190 && m_movementActivity != ACT_WALK_SCARED)
|
|
|
|
m_movementActivity = ACT_WALK_SCARED;
|
|
|
|
else if (distance >= 270 && m_movementActivity != ACT_RUN_SCARED)
|
|
|
|
m_movementActivity = ACT_RUN_SCARED;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
break;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
case TASK_HEAL:
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_fSequenceFinished)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
TaskComplete();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (TargetDistance() > 90)
|
2013-08-30 13:34:05 -07:00
|
|
|
TaskComplete();
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->ideal_yaw = UTIL_VecToYaw(m_hTargetEnt->pev->origin - pev->origin);
|
|
|
|
ChangeYaw(pev->yaw_speed);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2021-11-28 16:54:48 +01:00
|
|
|
CTalkMonster::RunTask(pTask);
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// Classify - indicates this monster's place in the
|
2013-08-30 13:34:05 -07:00
|
|
|
// relationship table.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CScientist::Classify()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
return CLASS_HUMAN_PASSIVE;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// SetYawSpeed - allows each sequence to have a different
|
|
|
|
// turn rate associated with it.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::SetYawSpeed()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int ys;
|
|
|
|
|
|
|
|
ys = 90;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (m_Activity)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
case ACT_IDLE:
|
|
|
|
ys = 120;
|
|
|
|
break;
|
|
|
|
case ACT_WALK:
|
|
|
|
ys = 180;
|
|
|
|
break;
|
|
|
|
case ACT_RUN:
|
|
|
|
ys = 150;
|
|
|
|
break;
|
|
|
|
case ACT_TURN_LEFT:
|
|
|
|
case ACT_TURN_RIGHT:
|
|
|
|
ys = 120;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pev->yaw_speed = ys;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// HandleAnimEvent - catches the monster-specific messages
|
|
|
|
// that occur when tagged animation frames are played.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::HandleAnimEvent(MonsterEvent_t* pEvent)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (pEvent->event)
|
|
|
|
{
|
|
|
|
case SCIENTIST_AE_HEAL: // Heal my target (if within range)
|
2013-08-30 13:34:05 -07:00
|
|
|
Heal();
|
|
|
|
break;
|
2021-11-29 20:55:01 +01:00
|
|
|
case SCIENTIST_AE_NEEDLEON:
|
|
|
|
{
|
2013-08-30 13:34:05 -07:00
|
|
|
int oldBody = pev->body;
|
|
|
|
pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1;
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
break;
|
2021-11-29 20:55:01 +01:00
|
|
|
case SCIENTIST_AE_NEEDLEOFF:
|
|
|
|
{
|
2013-08-30 13:34:05 -07:00
|
|
|
int oldBody = pev->body;
|
|
|
|
pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0;
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
break;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
default:
|
2021-11-28 16:54:48 +01:00
|
|
|
CTalkMonster::HandleAnimEvent(pEvent);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// Spawn
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::Spawn()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2022-06-25 18:26:48 +03:00
|
|
|
if (pev->body == -1)
|
|
|
|
{ // -1 chooses a random head
|
|
|
|
pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS - 1); // pick a head, any head
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Precache();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2024-08-28 09:09:16 +02:00
|
|
|
SET_MODEL(ENT(pev), GetScientistModel());
|
2013-08-30 13:34:05 -07:00
|
|
|
UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX);
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->solid = SOLID_SLIDEBOX;
|
|
|
|
pev->movetype = MOVETYPE_STEP;
|
|
|
|
m_bloodColor = BLOOD_COLOR_RED;
|
|
|
|
pev->health = gSkillData.scientistHealth;
|
|
|
|
pev->view_ofs = Vector(0, 0, 50); // position of the eyes relative to monster's origin.
|
|
|
|
m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello
|
|
|
|
m_MonsterState = MONSTERSTATE_NONE;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// m_flDistTooFar = 256.0;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// White hands
|
|
|
|
pev->skin = 0;
|
|
|
|
|
|
|
|
// Luther is black, make his hands black
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pev->body == HEAD_LUTHER)
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->skin = 1;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
MonsterInit();
|
2021-11-28 16:54:48 +01:00
|
|
|
SetUse(&CScientist::FollowerUse);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// Precache - precaches all resources this monster needs
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::Precache()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2024-08-28 09:09:16 +02:00
|
|
|
PRECACHE_MODEL(GetScientistModel());
|
2013-08-30 13:34:05 -07:00
|
|
|
PRECACHE_SOUND("scientist/sci_pain1.wav");
|
|
|
|
PRECACHE_SOUND("scientist/sci_pain2.wav");
|
|
|
|
PRECACHE_SOUND("scientist/sci_pain3.wav");
|
|
|
|
PRECACHE_SOUND("scientist/sci_pain4.wav");
|
|
|
|
PRECACHE_SOUND("scientist/sci_pain5.wav");
|
|
|
|
|
|
|
|
// every new scientist must call this, otherwise
|
|
|
|
// when a level is loaded, nobody will talk (time is reset to 0)
|
|
|
|
TalkInit();
|
|
|
|
|
|
|
|
CTalkMonster::Precache();
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Init talk data
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::TalkInit()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
CTalkMonster::TalkInit();
|
|
|
|
|
|
|
|
// scientist will try to talk to friends in this order:
|
|
|
|
|
|
|
|
m_szFriends[0] = "monster_scientist";
|
|
|
|
m_szFriends[1] = "monster_sitting_scientist";
|
|
|
|
m_szFriends[2] = "monster_barney";
|
|
|
|
|
|
|
|
// scientists speach group names (group names are in sentences.txt)
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_szGrp[TLK_ANSWER] = "SC_ANSWER";
|
|
|
|
m_szGrp[TLK_QUESTION] = "SC_QUESTION";
|
|
|
|
m_szGrp[TLK_IDLE] = "SC_IDLE";
|
|
|
|
m_szGrp[TLK_STARE] = "SC_STARE";
|
|
|
|
m_szGrp[TLK_USE] = "SC_OK";
|
|
|
|
m_szGrp[TLK_UNUSE] = "SC_WAIT";
|
|
|
|
m_szGrp[TLK_STOP] = "SC_STOP";
|
|
|
|
m_szGrp[TLK_NOSHOOT] = "SC_SCARED";
|
|
|
|
m_szGrp[TLK_HELLO] = "SC_HELLO";
|
|
|
|
|
|
|
|
m_szGrp[TLK_PLHURT1] = "!SC_CUREA";
|
|
|
|
m_szGrp[TLK_PLHURT2] = "!SC_CUREB";
|
|
|
|
m_szGrp[TLK_PLHURT3] = "!SC_CUREC";
|
|
|
|
|
|
|
|
m_szGrp[TLK_PHELLO] = "SC_PHELLO";
|
|
|
|
m_szGrp[TLK_PIDLE] = "SC_PIDLE";
|
2013-08-30 13:34:05 -07:00
|
|
|
m_szGrp[TLK_PQUESTION] = "SC_PQUEST";
|
2021-11-28 16:54:48 +01:00
|
|
|
m_szGrp[TLK_SMELL] = "SC_SMELL";
|
|
|
|
|
|
|
|
m_szGrp[TLK_WOUND] = "SC_WOUND";
|
|
|
|
m_szGrp[TLK_MORTAL] = "SC_MORTAL";
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// get voice for head
|
2022-06-25 18:26:48 +03:00
|
|
|
switch (pev->body % NUM_SCIENTIST_HEADS)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
default:
|
2021-11-28 16:54:48 +01:00
|
|
|
case HEAD_GLASSES:
|
|
|
|
m_voicePitch = 105;
|
|
|
|
break; //glasses
|
|
|
|
case HEAD_EINSTEIN:
|
|
|
|
m_voicePitch = 100;
|
|
|
|
break; //einstein
|
|
|
|
case HEAD_LUTHER:
|
|
|
|
m_voicePitch = 95;
|
|
|
|
break; //luther
|
|
|
|
case HEAD_SLICK:
|
|
|
|
m_voicePitch = 100;
|
|
|
|
break; //slick
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CScientist::TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pevInflictor && (pevInflictor->flags & FL_CLIENT) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
Remember(bits_MEMORY_PROVOKED);
|
|
|
|
StopFollowing(true);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure friends talk about it if player hurts scientist...
|
|
|
|
return CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// ISoundMask - returns a bit mask indicating which types
|
|
|
|
// of sounds this monster regards. In the base class implementation,
|
|
|
|
// monsters care about all sounds, but no scents.
|
|
|
|
//=========================================================
|
2019-09-30 05:04:43 -05:00
|
|
|
// Marphy Fact Files Fix - Restore scientist's sense of smell
|
2021-11-29 20:31:17 +01:00
|
|
|
int CScientist::ISoundMask()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
return bits_SOUND_WORLD |
|
|
|
|
bits_SOUND_COMBAT |
|
|
|
|
bits_SOUND_CARCASS |
|
|
|
|
bits_SOUND_MEAT |
|
|
|
|
bits_SOUND_GARBAGE |
|
|
|
|
bits_SOUND_DANGER |
|
|
|
|
bits_SOUND_PLAYER;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
|
|
|
// PainSound
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::PainSound()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (gpGlobals->time < m_painTime)
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75);
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (RANDOM_LONG(0, 4))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
case 0:
|
|
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch());
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch());
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch());
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch());
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch());
|
|
|
|
break;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// DeathSound
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::DeathSound()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
PainSound();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void CScientist::Killed(entvars_t* pevAttacker, int iGib)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
SetUse(NULL);
|
|
|
|
CTalkMonster::Killed(pevAttacker, iGib);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CScientist::SetActivity(Activity newActivity)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int iSequence;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
iSequence = LookupActivity(newActivity);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Set to the desired anim, or default anim if the desired is not present
|
2021-11-28 16:54:48 +01:00
|
|
|
if (iSequence == ACTIVITY_NOT_AVAILABLE)
|
2013-08-30 13:34:05 -07:00
|
|
|
newActivity = ACT_IDLE;
|
2021-11-28 16:54:48 +01:00
|
|
|
CTalkMonster::SetActivity(newActivity);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
Schedule_t* CScientist::GetScheduleOfType(int Type)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
Schedule_t* psched;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (Type)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Hook these to make a looping schedule
|
|
|
|
case SCHED_TARGET_FACE:
|
|
|
|
// call base class default so that scientist will talk
|
2021-11-28 16:54:48 +01:00
|
|
|
// when 'used'
|
2013-08-30 13:34:05 -07:00
|
|
|
psched = CTalkMonster::GetScheduleOfType(Type);
|
|
|
|
|
|
|
|
if (psched == slIdleStand)
|
2021-11-28 16:54:48 +01:00
|
|
|
return slFaceTarget; // override this for different target face behavior
|
2013-08-30 13:34:05 -07:00
|
|
|
else
|
|
|
|
return psched;
|
|
|
|
|
|
|
|
case SCHED_TARGET_CHASE:
|
|
|
|
return slFollow;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
case SCHED_CANT_FOLLOW:
|
|
|
|
return slStopFollowing;
|
|
|
|
|
|
|
|
case SCHED_PANIC:
|
|
|
|
return slSciPanic;
|
|
|
|
|
|
|
|
case SCHED_TARGET_CHASE_SCARED:
|
|
|
|
return slFollowScared;
|
|
|
|
|
|
|
|
case SCHED_TARGET_FACE_SCARED:
|
|
|
|
return slFaceTargetScared;
|
|
|
|
|
|
|
|
case SCHED_IDLE_STAND:
|
|
|
|
// call base class default so that scientist will talk
|
|
|
|
// when standing during idle
|
|
|
|
psched = CTalkMonster::GetScheduleOfType(Type);
|
|
|
|
|
|
|
|
if (psched == slIdleStand)
|
|
|
|
return slIdleSciStand;
|
|
|
|
else
|
|
|
|
return psched;
|
|
|
|
|
|
|
|
case SCHED_HIDE:
|
|
|
|
return slScientistHide;
|
|
|
|
|
|
|
|
case SCHED_STARTLE:
|
|
|
|
return slScientistStartle;
|
|
|
|
|
|
|
|
case SCHED_FEAR:
|
|
|
|
return slFear;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
return CTalkMonster::GetScheduleOfType(Type);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
Schedule_t* CScientist::GetSchedule()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// so we don't keep calling through the EHANDLE stuff
|
2021-11-28 16:54:48 +01:00
|
|
|
CBaseEntity* pEnemy = m_hEnemy;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_HEAR_SOUND))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CSound* pSound;
|
2013-08-30 13:34:05 -07:00
|
|
|
pSound = PBestSound();
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ASSERT(pSound != NULL);
|
|
|
|
if (pSound && (pSound->m_iType & bits_SOUND_DANGER) != 0)
|
|
|
|
return GetScheduleOfType(SCHED_TAKE_COVER_FROM_BEST_SOUND);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (m_MonsterState)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
case MONSTERSTATE_ALERT:
|
2013-08-30 13:34:05 -07:00
|
|
|
case MONSTERSTATE_IDLE:
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pEnemy)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_SEE_ENEMY))
|
2013-08-30 13:34:05 -07:00
|
|
|
m_fearTime = gpGlobals->time;
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (DisregardEnemy(pEnemy)) // After 15 seconds of being hidden, return to alert
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_hEnemy = NULL;
|
|
|
|
pEnemy = NULL;
|
2022-08-06 13:41:59 +02:00
|
|
|
|
2019-09-30 06:21:31 -05:00
|
|
|
// Marphy Fact Files Fix - Fix scientists not disregarding enemy after hiding
|
|
|
|
m_fearTime = gpGlobals->time;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// flinch if hurt
|
2021-11-28 16:54:48 +01:00
|
|
|
return GetScheduleOfType(SCHED_SMALL_FLINCH);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Cower when you hear something scary
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_HEAR_SOUND))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CSound* pSound;
|
2013-08-30 13:34:05 -07:00
|
|
|
pSound = PBestSound();
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ASSERT(pSound != NULL);
|
|
|
|
if (pSound)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((pSound->m_iType & (bits_SOUND_DANGER | bits_SOUND_COMBAT)) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (gpGlobals->time - m_fearTime > 3) // Only cower every 3 seconds or so
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_fearTime = gpGlobals->time; // Update last fear
|
|
|
|
return GetScheduleOfType(SCHED_STARTLE); // This will just duck for a second
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Behavior for following the player
|
2021-11-28 16:54:48 +01:00
|
|
|
if (IsFollowing())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!m_hTargetEnt->IsAlive())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// UNDONE: Comment about the recently dead player here?
|
2021-11-28 16:54:48 +01:00
|
|
|
StopFollowing(false);
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
int relationship = R_NO;
|
|
|
|
|
|
|
|
// Nothing scary, just me and the player
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pEnemy != NULL)
|
|
|
|
relationship = IRelationship(pEnemy);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// UNDONE: Model fear properly, fix R_FR and add multiple levels of fear
|
2021-11-28 16:54:48 +01:00
|
|
|
if (relationship != R_DL && relationship != R_HT)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// If I'm already close enough to my target
|
2021-11-28 16:54:48 +01:00
|
|
|
if (TargetDistance() <= 128)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (CanHeal()) // Heal opportunistically
|
2013-08-30 13:34:05 -07:00
|
|
|
return slHeal;
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_CLIENT_PUSH)) // Player wants me to move
|
|
|
|
return GetScheduleOfType(SCHED_MOVE_AWAY_FOLLOW);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
return GetScheduleOfType(SCHED_TARGET_FACE); // Just face and follow.
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_NEW_ENEMY)) // I just saw something new and scary, react
|
|
|
|
return GetScheduleOfType(SCHED_FEAR); // React to something scary
|
|
|
|
return GetScheduleOfType(SCHED_TARGET_FACE_SCARED); // face and follow, but I'm scared!
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_CLIENT_PUSH)) // Player wants me to move
|
|
|
|
return GetScheduleOfType(SCHED_MOVE_AWAY);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// try to say something about smells
|
|
|
|
TrySmellTalk();
|
|
|
|
break;
|
|
|
|
case MONSTERSTATE_COMBAT:
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_NEW_ENEMY))
|
|
|
|
return slFear; // Point and scream!
|
2022-08-06 13:41:59 +02:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_SEE_ENEMY))
|
2019-09-30 06:21:31 -05:00
|
|
|
{
|
|
|
|
// Marphy Fact Files Fix - Fix scientists not disregarding enemy after hiding
|
|
|
|
m_fearTime = gpGlobals->time;
|
2021-11-28 16:54:48 +01:00
|
|
|
return slScientistCover; // Take Cover
|
2019-09-30 06:21:31 -05:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
if (HasConditions(bits_COND_HEAR_SOUND))
|
|
|
|
return slTakeCoverFromBestSound; // Cower and panic from the scary sound!
|
|
|
|
|
2019-09-30 06:21:31 -05:00
|
|
|
// Marphy Fact Files Fix - Fix scientists not disregarding enemy after hiding
|
2022-08-06 13:41:59 +02:00
|
|
|
if (pEnemy)
|
2019-09-30 06:21:31 -05:00
|
|
|
{
|
2022-08-06 13:41:59 +02:00
|
|
|
if (HasConditions(bits_COND_SEE_ENEMY))
|
2019-09-30 06:21:31 -05:00
|
|
|
m_fearTime = gpGlobals->time;
|
2022-08-06 13:41:59 +02:00
|
|
|
else if (DisregardEnemy(pEnemy)) // After 15 seconds of being hidden, return to alert
|
2019-09-30 06:21:31 -05:00
|
|
|
{
|
|
|
|
m_hEnemy = NULL;
|
|
|
|
pEnemy = NULL;
|
2022-08-06 13:41:59 +02:00
|
|
|
|
2019-09-30 06:21:31 -05:00
|
|
|
m_fearTime = gpGlobals->time;
|
2022-08-06 13:41:59 +02:00
|
|
|
|
|
|
|
if (IsFollowing())
|
2019-09-30 06:21:31 -05:00
|
|
|
{
|
2022-08-06 13:41:59 +02:00
|
|
|
return slScientistStartle;
|
2019-09-30 06:21:31 -05:00
|
|
|
}
|
2022-08-06 13:41:59 +02:00
|
|
|
|
|
|
|
return slScientistHide; // Hide after disregard
|
2019-09-30 06:21:31 -05:00
|
|
|
}
|
|
|
|
}
|
2022-08-06 13:41:59 +02:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
return slScientistCover; // Run & Cower
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
return CTalkMonster::GetSchedule();
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
MONSTERSTATE CScientist::GetIdealState()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (m_MonsterState)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
case MONSTERSTATE_ALERT:
|
|
|
|
case MONSTERSTATE_IDLE:
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_NEW_ENEMY))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (IsFollowing())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int relationship = IRelationship(m_hEnemy);
|
|
|
|
if (relationship != R_FR || relationship != R_HT && !HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Don't go to combat if you're following the player
|
|
|
|
m_IdealMonsterState = MONSTERSTATE_ALERT;
|
|
|
|
return m_IdealMonsterState;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
StopFollowing(true);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Stop following if you take damage
|
2021-11-28 16:54:48 +01:00
|
|
|
if (IsFollowing())
|
|
|
|
StopFollowing(true);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-11-29 20:55:01 +01:00
|
|
|
case MONSTERSTATE_COMBAT:
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CBaseEntity* pEnemy = m_hEnemy;
|
|
|
|
if (pEnemy != NULL)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (DisregardEnemy(pEnemy)) // After 15 seconds of being hidden, return to alert
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
// Strip enemy when going to alert
|
|
|
|
m_IdealMonsterState = MONSTERSTATE_ALERT;
|
|
|
|
m_hEnemy = NULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2022-08-06 13:41:59 +02:00
|
|
|
// Marphy Fact Files Fix - Fix scientists not disregarding enemy after hiding
|
|
|
|
m_fearTime = gpGlobals->time;
|
2021-11-28 16:54:48 +01:00
|
|
|
return m_IdealMonsterState;
|
|
|
|
}
|
|
|
|
// Follow if only scared a little
|
|
|
|
if (m_hTargetEnt != NULL)
|
|
|
|
{
|
|
|
|
m_IdealMonsterState = MONSTERSTATE_ALERT;
|
|
|
|
return m_IdealMonsterState;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (HasConditions(bits_COND_SEE_ENEMY))
|
|
|
|
{
|
|
|
|
m_fearTime = gpGlobals->time;
|
|
|
|
m_IdealMonsterState = MONSTERSTATE_COMBAT;
|
|
|
|
return m_IdealMonsterState;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
break;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return CTalkMonster::GetIdealState();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-19 14:31:11 +01:00
|
|
|
bool CScientist::CanHeal()
|
2021-11-28 16:54:48 +01:00
|
|
|
{
|
|
|
|
if ((m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)))
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void CScientist::Heal()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!CanHeal())
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
Vector target = m_hTargetEnt->pev->origin - pev->origin;
|
2021-11-28 16:54:48 +01:00
|
|
|
if (target.Length() > 100)
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_hTargetEnt->TakeHealth(gSkillData.scientistHeal, DMG_GENERIC);
|
2013-08-30 13:34:05 -07:00
|
|
|
// Don't heal again for 1 minute
|
|
|
|
m_healTime = gpGlobals->time + 60;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int CScientist::FriendNumber(int arrayNumber)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
static int array[3] = {1, 2, 0};
|
|
|
|
if (arrayNumber < 3)
|
|
|
|
return array[arrayNumber];
|
2013-08-30 13:34:05 -07:00
|
|
|
return arrayNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// Dead Scientist PROP
|
|
|
|
//=========================================================
|
|
|
|
class CDeadScientist : public CBaseMonster
|
|
|
|
{
|
|
|
|
public:
|
2021-03-05 23:07:22 +01:00
|
|
|
void Spawn() override;
|
2021-11-28 16:54:48 +01:00
|
|
|
int Classify() override { return CLASS_HUMAN_PASSIVE; }
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2024-08-28 09:09:16 +02:00
|
|
|
// passed into Precache which is non-const
|
|
|
|
char* GetScientistModel() const;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
bool KeyValue(KeyValueData* pkvd) override;
|
|
|
|
int m_iPose; // which sequence to display
|
|
|
|
static const char* m_szPoses[7];
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
2021-11-28 16:54:48 +01:00
|
|
|
const char* CDeadScientist::m_szPoses[] = {"lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3"};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2024-08-28 09:09:16 +02:00
|
|
|
char* CDeadScientist::GetScientistModel() const
|
|
|
|
{
|
|
|
|
char* pszOverride = (char*)CVAR_GET_STRING("_sv_override_scientist_mdl");
|
|
|
|
if (pszOverride && strlen(pszOverride) > 5) // at least requires ".mdl"
|
|
|
|
{
|
|
|
|
return pszOverride;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "models/scientist.mdl";
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
bool CDeadScientist::KeyValue(KeyValueData* pkvd)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (FStrEq(pkvd->szKeyName, "pose"))
|
|
|
|
{
|
|
|
|
m_iPose = atoi(pkvd->szValue);
|
2021-11-28 15:32:26 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 15:32:26 +01:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
return CBaseMonster::KeyValue(pkvd);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
LINK_ENTITY_TO_CLASS(monster_scientist_dead, CDeadScientist);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
//
|
|
|
|
// ********** DeadScientist SPAWN **********
|
|
|
|
//
|
2021-11-29 20:31:17 +01:00
|
|
|
void CDeadScientist::Spawn()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2024-08-28 09:09:16 +02:00
|
|
|
PRECACHE_MODEL(GetScientistModel());
|
|
|
|
SET_MODEL(ENT(pev), GetScientistModel());
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
pev->effects = 0;
|
|
|
|
pev->sequence = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
// Corpses have less health
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->health = 8; //gSkillData.scientistHealth;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
m_bloodColor = BLOOD_COLOR_RED;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pev->body == -1)
|
|
|
|
{ // -1 chooses a random head
|
|
|
|
pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS - 1); // pick a head, any head
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
// Luther is black, make his hands black
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pev->body == HEAD_LUTHER)
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->skin = 1;
|
|
|
|
else
|
|
|
|
pev->skin = 0;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->sequence = LookupSequence(m_szPoses[m_iPose]);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (pev->sequence == -1)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_console, "Dead scientist with bad pose\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again!
|
|
|
|
MonsterInitDead();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// Sitting Scientist PROP
|
|
|
|
//=========================================================
|
|
|
|
|
|
|
|
class CSittingScientist : public CScientist // kdb: changed from public CBaseMonster so he can speak
|
|
|
|
{
|
|
|
|
public:
|
2021-03-05 23:07:22 +01:00
|
|
|
void Spawn() override;
|
2021-11-28 16:54:48 +01:00
|
|
|
void Precache() override;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void EXPORT SittingThink();
|
2021-11-28 16:54:48 +01:00
|
|
|
int Classify() override;
|
|
|
|
bool Save(CSave& save) override;
|
|
|
|
bool Restore(CRestore& restore) override;
|
|
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
|
|
|
|
|
|
void SetAnswerQuestion(CTalkMonster* pSpeaker) override;
|
|
|
|
int FriendNumber(int arrayNumber) override;
|
|
|
|
|
|
|
|
bool FIdleSpeak();
|
|
|
|
int m_baseSequence;
|
|
|
|
int m_headTurn;
|
|
|
|
float m_flResponseDelay;
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
LINK_ENTITY_TO_CLASS(monster_sitting_scientist, CSittingScientist);
|
|
|
|
TYPEDESCRIPTION CSittingScientist::m_SaveData[] =
|
|
|
|
{
|
|
|
|
// Don't need to save/restore m_baseSequence (recalced)
|
|
|
|
DEFINE_FIELD(CSittingScientist, m_headTurn, FIELD_INTEGER),
|
|
|
|
DEFINE_FIELD(CSittingScientist, m_flResponseDelay, FIELD_FLOAT),
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
IMPLEMENT_SAVERESTORE(CSittingScientist, CScientist);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// animation sequence aliases
|
2013-08-30 13:34:05 -07:00
|
|
|
typedef enum
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
SITTING_ANIM_sitlookleft,
|
|
|
|
SITTING_ANIM_sitlookright,
|
|
|
|
SITTING_ANIM_sitscared,
|
|
|
|
SITTING_ANIM_sitting2,
|
|
|
|
SITTING_ANIM_sitting3
|
2013-08-30 13:34:05 -07:00
|
|
|
} SITTING_ANIM;
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// ********** Scientist SPAWN **********
|
|
|
|
//
|
2021-11-29 20:31:17 +01:00
|
|
|
void CSittingScientist::Spawn()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2024-08-28 09:09:16 +02:00
|
|
|
PRECACHE_MODEL(GetScientistModel());
|
|
|
|
SET_MODEL(ENT(pev), GetScientistModel());
|
2013-08-30 13:34:05 -07:00
|
|
|
Precache();
|
|
|
|
InitBoneControllers();
|
|
|
|
|
|
|
|
UTIL_SetSize(pev, Vector(-14, -14, 0), Vector(14, 14, 36));
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->solid = SOLID_SLIDEBOX;
|
|
|
|
pev->movetype = MOVETYPE_STEP;
|
|
|
|
pev->effects = 0;
|
|
|
|
pev->health = 50;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
m_bloodColor = BLOOD_COLOR_RED;
|
2021-11-28 16:54:48 +01:00
|
|
|
m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only!
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pev->body == -1)
|
|
|
|
{ // -1 chooses a random head
|
|
|
|
pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS - 1); // pick a head, any head
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
// Luther is black, make his hands black
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pev->body == HEAD_LUTHER)
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->skin = 1;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
m_baseSequence = LookupSequence("sitlookleft");
|
|
|
|
pev->sequence = m_baseSequence + RANDOM_LONG(0, 4);
|
|
|
|
ResetSequenceInfo();
|
|
|
|
|
|
|
|
SetThink(&CSittingScientist::SittingThink);
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
DROP_TO_FLOOR(ENT(pev));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CSittingScientist::Precache()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_baseSequence = LookupSequence("sitlookleft");
|
2013-08-30 13:34:05 -07:00
|
|
|
TalkInit();
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// ID as a passive human
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CSittingScientist::Classify()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
return CLASS_HUMAN_PASSIVE;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int CSittingScientist::FriendNumber(int arrayNumber)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
static int array[3] = {2, 1, 0};
|
|
|
|
if (arrayNumber < 3)
|
|
|
|
return array[arrayNumber];
|
2013-08-30 13:34:05 -07:00
|
|
|
return arrayNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// sit, do stuff
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CSittingScientist::SittingThink()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CBaseEntity* pent;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
StudioFrameAdvance();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// try to greet player
|
|
|
|
if (FIdleHello())
|
|
|
|
{
|
2021-11-19 13:45:16 +01:00
|
|
|
pent = FindNearestFriend(true);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (pent)
|
|
|
|
{
|
|
|
|
float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (yaw > 180)
|
|
|
|
yaw -= 360;
|
|
|
|
if (yaw < -180)
|
|
|
|
yaw += 360;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
if (yaw > 0)
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft;
|
|
|
|
else
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
ResetSequenceInfo();
|
|
|
|
pev->frame = 0;
|
|
|
|
SetBoneController(0, 0);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (m_fSequenceFinished)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int i = RANDOM_LONG(0, 99);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_headTurn = 0;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-11-28 15:32:26 +01:00
|
|
|
if (0 != m_flResponseDelay && gpGlobals->time > m_flResponseDelay)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// respond to question
|
|
|
|
IdleRespond();
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitscared;
|
|
|
|
m_flResponseDelay = 0;
|
|
|
|
}
|
|
|
|
else if (i < 30)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitting3;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// turn towards player or nearest friend and speak
|
|
|
|
|
|
|
|
if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer))
|
2021-11-19 13:45:16 +01:00
|
|
|
pent = FindNearestFriend(true);
|
2013-08-30 13:34:05 -07:00
|
|
|
else
|
2021-11-19 13:43:33 +01:00
|
|
|
pent = FindNearestFriend(false);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
if (!FIdleSpeak() || !pent)
|
2021-11-28 16:54:48 +01:00
|
|
|
{
|
|
|
|
m_headTurn = RANDOM_LONG(0, 8) * 10 - 40;
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitting3;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// only turn head if we spoke
|
|
|
|
float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (yaw > 180)
|
|
|
|
yaw -= 360;
|
|
|
|
if (yaw < -180)
|
|
|
|
yaw += 360;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
if (yaw > 0)
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft;
|
|
|
|
else
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright;
|
|
|
|
|
|
|
|
//ALERT(at_console, "sitting speak\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (i < 60)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitting3;
|
|
|
|
m_headTurn = RANDOM_LONG(0, 8) * 10 - 40;
|
|
|
|
if (RANDOM_LONG(0, 99) < 5)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
//ALERT(at_console, "sitting speak2\n");
|
|
|
|
FIdleSpeak();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (i < 80)
|
|
|
|
{
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitting2;
|
|
|
|
}
|
|
|
|
else if (i < 100)
|
|
|
|
{
|
|
|
|
pev->sequence = m_baseSequence + SITTING_ANIM_sitscared;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ResetSequenceInfo();
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->frame = 0;
|
2021-11-28 16:54:48 +01:00
|
|
|
SetBoneController(0, m_headTurn);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare sitting scientist to answer a question
|
2021-11-29 20:31:17 +01:00
|
|
|
void CSittingScientist::SetAnswerQuestion(CTalkMonster* pSpeaker)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT(3, 4);
|
2021-11-28 16:54:48 +01:00
|
|
|
m_hTalkTarget = (CBaseMonster*)pSpeaker;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// FIdleSpeak
|
|
|
|
// ask question of nearby friend, or make statement
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CSittingScientist::FIdleSpeak()
|
2021-11-28 16:54:48 +01:00
|
|
|
{
|
2013-08-30 13:34:05 -07:00
|
|
|
// try to start a conversation, or make statement
|
|
|
|
int pitch;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
if (!FOkToSpeak())
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// set global min delay for next conversation
|
|
|
|
CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2);
|
|
|
|
|
|
|
|
pitch = GetVoicePitch();
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// if there is a friend nearby to speak to, play sentence, set friend's response time, return
|
|
|
|
|
|
|
|
// try to talk to any standing or sitting scientists nearby
|
2021-11-28 16:54:48 +01:00
|
|
|
CBaseEntity* pentFriend = FindNearestFriend(false);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pentFriend && RANDOM_LONG(0, 1))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CTalkMonster* pTalkMonster = GetClassPtr((CTalkMonster*)pentFriend->pev);
|
|
|
|
pTalkMonster->SetAnswerQuestion(this);
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
IdleHeadTurn(pentFriend->pev->origin);
|
2021-11-28 16:54:48 +01:00
|
|
|
SENTENCEG_PlayRndSz(ENT(pev), m_szGrp[TLK_PQUESTION], 1.0, ATTN_IDLE, 0, pitch);
|
2013-08-30 13:34:05 -07:00
|
|
|
// set global min delay for next conversation
|
|
|
|
CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2);
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise, play an idle statement
|
2021-11-28 16:54:48 +01:00
|
|
|
if (RANDOM_LONG(0, 1))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
SENTENCEG_PlayRndSz(ENT(pev), m_szGrp[TLK_PIDLE], 1.0, ATTN_IDLE, 0, pitch);
|
2013-08-30 13:34:05 -07:00
|
|
|
// set global min delay for next conversation
|
|
|
|
CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2);
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// never spoke
|
|
|
|
CTalkMonster::g_talkWaitTime = 0;
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|