openmohaa/code/fgame/playerbot.cpp

1055 lines
26 KiB
C++
Raw Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team
2016-03-27 11:49:47 +02:00
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// playerbot.cpp: Multiplayer bot system.
#include "g_local.h"
#include "actor.h"
#include "playerbot.h"
2023-01-29 20:59:31 +01:00
#include "consoleevent.h"
2023-04-29 21:56:38 +02:00
#include "debuglines.h"
#include "scriptexception.h"
#include "vehicleturret.h"
#include "weaputils.h"
2016-03-27 11:49:47 +02:00
// We assume that we have limited access to the server-side
// and that most logic come from the playerstate_s structure
cvar_t *bot_manualmove;
CLASS_DECLARATION(Listener, BotController, NULL) {
{NULL, NULL}
};
BotController::botfunc_t BotController::botfuncs[MAX_BOT_FUNCTIONS];
BotController::BotController()
{
2023-11-13 20:33:06 +01:00
if (LoadingSavegame) {
return;
}
m_botCmd.serverTime = 0;
m_botCmd.msec = 0;
m_botCmd.buttons = 0;
m_botCmd.angles[0] = ANGLE2SHORT(0);
m_botCmd.angles[1] = ANGLE2SHORT(0);
m_botCmd.angles[2] = ANGLE2SHORT(0);
m_botCmd.forwardmove = 0;
m_botCmd.rightmove = 0;
m_botCmd.upmove = 0;
m_botEyes.angles[0] = 0;
m_botEyes.angles[1] = 0;
m_botEyes.ofs[0] = 0;
m_botEyes.ofs[1] = 0;
m_botEyes.ofs[2] = DEFAULT_VIEWHEIGHT;
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0;
m_iAttackTime = 0;
m_iNextTauntTime = 0;
2023-11-13 20:33:06 +01:00
m_StateFlags = 0;
m_RunLabel.TrySetScript("global/bot_run.scr");
2016-03-27 11:49:47 +02:00
}
BotMovement& BotController::GetMovement()
{
return movement;
}
void BotController::Init(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
bot_manualmove = gi.Cvar_Get("bot_manualmove", "0", 0);
for (int i = 0; i < MAX_BOT_FUNCTIONS; i++) {
botfuncs[i].BeginState = &BotController::State_DefaultBegin;
botfuncs[i].EndState = &BotController::State_DefaultEnd;
2023-11-13 20:33:06 +01:00
}
InitState_Attack(&botfuncs[0]);
InitState_Curious(&botfuncs[1]);
InitState_Grenade(&botfuncs[2]);
InitState_Idle(&botfuncs[3]);
InitState_Weapon(&botfuncs[4]);
2016-03-27 11:49:47 +02:00
}
void BotController::GetUsercmd(usercmd_t *ucmd)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
*ucmd = m_botCmd;
2016-03-27 11:49:47 +02:00
}
void BotController::GetEyeInfo(usereyes_t *eyeinfo)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
*eyeinfo = m_botEyes;
2016-03-27 11:49:47 +02:00
}
void BotController::UpdateBotStates(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
if (bot_manualmove->integer) {
memset(&m_botCmd, 0, sizeof(usercmd_t));
return;
}
if (!controlledEnt->client->pers.dm_primary[0]) {
Event *event;
//
// Primary weapon
//
event = new Event(EV_Player_PrimaryDMWeapon);
event->AddString("auto");
controlledEnt->ProcessEvent(event);
}
if (controlledEnt->GetTeam() == TEAM_NONE || controlledEnt->GetTeam() == TEAM_SPECTATOR) {
float time;
// Add some delay to avoid telefragging
time = controlledEnt->entnum / 20.0;
if (controlledEnt->EventPending(EV_Player_AutoJoinDMTeam)) {
return;
}
//
// Team
//
controlledEnt->PostEvent(EV_Player_AutoJoinDMTeam, time);
return;
}
if (controlledEnt->IsDead() || controlledEnt->IsSpectator()) {
2023-11-13 20:33:06 +01:00
// The bot should respawn
m_botCmd.buttons ^= BUTTON_ATTACKLEFT;
return;
}
m_botCmd.buttons |= BUTTON_RUN;
m_botCmd.serverTime = level.svsTime;
2023-11-13 20:33:06 +01:00
m_botEyes.ofs[0] = 0;
m_botEyes.ofs[1] = 0;
m_botEyes.ofs[2] = controlledEnt->viewheight;
2023-11-13 20:33:06 +01:00
m_botEyes.angles[0] = 0;
m_botEyes.angles[1] = 0;
CheckStates();
movement.MoveThink(m_botCmd);
rotation.TurnThink(m_botCmd, m_botEyes);
2023-11-13 20:33:06 +01:00
CheckUse();
2016-03-27 11:49:47 +02:00
}
void BotController::CheckUse(void)
{
Vector dir;
Vector start;
Vector end;
trace_t trace;
controlledEnt->angles.AngleVectorsLeft(&dir);
start = controlledEnt->origin + Vector(0, 0, controlledEnt->viewheight);
end = controlledEnt->origin + Vector(0, 0, controlledEnt->viewheight) + dir * 32;
trace = G_Trace(start, vec_zero, vec_zero, end, controlledEnt, MASK_USABLE, false, "BotController::CheckUse");
// It may be a door
if ((trace.allsolid || trace.startsolid || trace.fraction != 1.0f) && trace.ent) {
if (trace.ent->entity->IsSubclassOfDoor()) {
Door *door = static_cast<Door *>(trace.ent->entity);
if (door->isOpen()) {
m_botCmd.buttons &= ~BUTTON_USE;
return;
}
}
//
// Toggle the use button
//
m_botCmd.buttons ^= BUTTON_USE;
} else {
m_botCmd.buttons &= ~BUTTON_USE;
}
}
void BotController::SendCommand(const char *text)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
char *buffer;
char *data;
size_t len;
ConsoleEvent ev;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
len = strlen(text) + 1;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
buffer = (char *)gi.Malloc(len);
data = buffer;
Q_strncpyz(data, text, len);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
const char *com_token = COM_Parse(&data);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (!com_token) {
return;
}
2016-03-27 11:49:47 +02:00
controlledEnt->m_lastcommand = com_token;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (!Event::GetEvent(com_token)) {
return;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
ev = ConsoleEvent(com_token);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (!(ev.GetEventFlags(ev.eventnum) & EV_CONSOLE)) {
gi.Free(buffer);
return;
}
2016-03-27 11:49:47 +02:00
ev.SetConsoleEdict(controlledEnt->edict);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
while (1) {
com_token = COM_Parse(&data);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (!com_token || !*com_token) {
break;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
ev.AddString(com_token);
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
gi.Free(buffer);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
try {
controlledEnt->ProcessEvent(ev);
2023-11-13 20:33:06 +01:00
} catch (ScriptException& exc) {
gi.DPrintf("*** Bot Command Exception *** %s\n", exc.string.c_str());
}
2016-03-27 11:49:47 +02:00
}
/*
====================
AimAtAimNode
Make the bot face toward the current path
====================
*/
void BotController::AimAtAimNode(void)
2016-03-27 11:49:47 +02:00
{
Vector goal;
if (!movement.IsMoving()) {
return;
}
goal = movement.GetCurrentGoal();
if (goal != controlledEnt->origin) {
rotation.AimAt(goal);
2023-11-13 20:33:06 +01:00
}
Vector targetAngles = rotation.GetTargetAngles();
targetAngles.x = 0;
rotation.SetTargetAngles(targetAngles);
2016-03-27 11:49:47 +02:00
}
/*
====================
CheckReload
Make the bot reload if necessary
====================
*/
void BotController::CheckReload(void)
2016-03-27 11:49:47 +02:00
{
Weapon *weap = controlledEnt->GetActiveWeapon(WEAPON_MAIN);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (weap && weap->CheckReload(FIRE_PRIMARY)) {
SendCommand("reload");
}
2016-03-27 11:49:47 +02:00
}
/*
====================
NoticeEvent
Warn the bot of an event
====================
*/
void BotController::NoticeEvent(Vector vPos, int iType, Entity *pEnt, float fDistanceSquared, float fRadiusSquared)
2016-03-27 11:49:47 +02:00
{
Sentient *pSentOwner;
float fRangeFactor;
2024-10-06 01:01:03 +02:00
2024-10-06 14:24:32 +02:00
fRangeFactor = 1.0 - (fDistanceSquared / fRadiusSquared);
2024-10-06 01:01:03 +02:00
if (fRangeFactor < random()) {
return;
}
if (pEnt->IsSubclassOfSentient()) {
pSentOwner = static_cast<Sentient *>(pEnt);
} else if (pEnt->IsSubclassOfVehicleTurretGun()) {
VehicleTurretGun *pVTG = static_cast<VehicleTurretGun *>(pEnt);
pSentOwner = pVTG->GetSentientOwner();
} else if (pEnt->IsSubclassOfItem()) {
Item *pItem = static_cast<Item *>(pEnt);
pSentOwner = pItem->GetOwner();
} else if (pEnt->IsSubclassOfProjectile()) {
Projectile *pProj = static_cast<Projectile *>(pEnt);
pSentOwner = pProj->GetOwner();
} else {
pSentOwner = NULL;
}
if (pSentOwner) {
if (pSentOwner == controlledEnt) {
// Ignore self
return;
}
2023-11-13 20:33:06 +01:00
if ((pSentOwner->flags & FL_NOTARGET) || pSentOwner->getSolidType() == SOLID_NOT) {
2023-11-13 20:33:06 +01:00
return;
}
// Ignore teammates
if (pSentOwner->IsSubclassOfPlayer()) {
Player *p = static_cast<Player *>(pSentOwner);
2023-11-13 20:33:06 +01:00
if (g_gametype->integer >= GT_TEAM && p->GetTeam() == controlledEnt->GetTeam()) {
2023-11-13 20:33:06 +01:00
return;
}
}
}
switch (iType) {
case AI_EVENT_MISC:
case AI_EVENT_MISC_LOUD:
break;
case AI_EVENT_WEAPON_FIRE:
case AI_EVENT_WEAPON_IMPACT:
case AI_EVENT_EXPLOSION:
case AI_EVENT_AMERICAN_VOICE:
case AI_EVENT_GERMAN_VOICE:
case AI_EVENT_AMERICAN_URGENT:
case AI_EVENT_GERMAN_URGENT:
case AI_EVENT_FOOTSTEP:
case AI_EVENT_GRENADE:
default:
m_iCuriousTime = level.inttime + 20000;
m_vNewCuriousPos = vPos;
2023-11-13 20:33:06 +01:00
break;
}
2016-03-27 11:49:47 +02:00
}
/*
====================
ClearEnemy
Clear the bot's enemy
====================
*/
void BotController::ClearEnemy(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_iAttackTime = 0;
m_pEnemy = NULL;
m_vOldEnemyPos = vec_zero;
m_vLastEnemyPos = vec_zero;
2016-03-27 11:49:47 +02:00
}
/*
====================
Bot states
--------------------
____________________
--------------------
____________________
--------------------
____________________
--------------------
____________________
====================
*/
void BotController::CheckStates(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_StateCount = 0;
for (int i = 0; i < MAX_BOT_FUNCTIONS; i++) {
botfunc_t *func = &botfuncs[i];
if (func->CheckCondition) {
if ((this->*func->CheckCondition)()) {
if (!(m_StateFlags & (1 << i))) {
m_StateFlags |= 1 << i;
if (func->BeginState) {
(this->*func->BeginState)();
}
}
if (func->ThinkState) {
m_StateCount++;
(this->*func->ThinkState)();
}
} else {
if ((m_StateFlags & (1 << i))) {
m_StateFlags &= ~(1 << i);
if (func->EndState) {
(this->*func->EndState)();
}
}
}
} else {
if (func->ThinkState) {
m_StateCount++;
(this->*func->ThinkState)();
}
}
}
assert(m_StateCount);
if (!m_StateCount) {
gi.DPrintf("*** WARNING *** %s was stuck with no states !!!", controlledEnt->client->pers.netname);
2023-11-13 20:33:06 +01:00
State_Reset();
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Default state
====================
*/
void BotController::State_DefaultBegin(void)
2016-03-27 11:49:47 +02:00
{
movement.ClearMove();
2016-03-27 11:49:47 +02:00
}
void BotController::State_DefaultEnd(void) {}
2016-03-27 11:49:47 +02:00
void BotController::State_Reset(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0;
m_iAttackTime = 0;
m_vLastCuriousPos = vec_zero;
m_vOldEnemyPos = vec_zero;
m_vLastEnemyPos = vec_zero;
m_vLastDeathPos = vec_zero;
m_pEnemy = NULL;
2016-03-27 11:49:47 +02:00
}
/*
====================
Idle state
Make the bot move to random directions
====================
*/
void BotController::InitState_Idle(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
func->CheckCondition = &BotController::CheckCondition_Idle;
func->ThinkState = &BotController::State_Idle;
2016-03-27 11:49:47 +02:00
}
bool BotController::CheckCondition_Idle(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
if (m_iCuriousTime) {
return false;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (m_iAttackTime) {
return false;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
return true;
2016-03-27 11:49:47 +02:00
}
void BotController::State_Idle(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
AimAtAimNode();
CheckReload();
if (!movement.MoveToBestAttractivePoint() && !movement.IsMoving()) {
2023-11-13 20:33:06 +01:00
if (m_vLastDeathPos != vec_zero) {
movement.MoveTo(m_vLastDeathPos);
2023-11-13 20:33:06 +01:00
if (movement.MoveDone()) {
2023-11-13 20:33:06 +01:00
m_vLastDeathPos = vec_zero;
}
} else {
2024-10-03 22:23:46 +02:00
Vector randomDir(G_CRandom(16), G_CRandom(16), G_CRandom(16));
Vector preferredDir = Vector(controlledEnt->orientation[0]) * (rand() % 5 ? 1024 : -1024);
float radius = 512 + G_Random(2048);
2024-10-03 20:22:54 +02:00
movement.AvoidPath(controlledEnt->origin + randomDir, radius, preferredDir);
2023-11-13 20:33:06 +01:00
}
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Curious state
Forward to the last event position
====================
*/
void BotController::InitState_Curious(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
func->CheckCondition = &BotController::CheckCondition_Curious;
func->ThinkState = &BotController::State_Curious;
2016-03-27 11:49:47 +02:00
}
bool BotController::CheckCondition_Curious(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
if (m_iAttackTime) {
m_iCuriousTime = 0;
return false;
}
if (level.inttime > m_iCuriousTime) {
if (m_iCuriousTime) {
movement.ClearMove();
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0;
}
return false;
}
return true;
2016-03-27 11:49:47 +02:00
}
void BotController::State_Curious(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
AimAtAimNode();
if (!movement.MoveToBestAttractivePoint(3) && (!movement.IsMoving() || m_vLastCuriousPos != m_vNewCuriousPos)) {
movement.MoveTo(m_vNewCuriousPos);
2024-10-06 14:24:32 +02:00
m_vLastCuriousPos = m_vNewCuriousPos;
2023-11-13 20:33:06 +01:00
}
if (movement.MoveDone()) {
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0;
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Attack state
Attack the enemy
====================
*/
void BotController::InitState_Attack(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
func->CheckCondition = &BotController::CheckCondition_Attack;
func->EndState = &BotController::State_EndAttack;
func->ThinkState = &BotController::State_Attack;
2016-03-27 11:49:47 +02:00
}
static Vector bot_origin;
2023-11-13 20:33:06 +01:00
static int sentients_compare(const void *elem1, const void *elem2)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Entity *e1, *e2;
float delta[3];
float d1, d2;
e1 = *(Entity **)elem1;
e2 = *(Entity **)elem2;
VectorSubtract(bot_origin, e1->origin, delta);
d1 = VectorLengthSquared(delta);
VectorSubtract(bot_origin, e2->origin, delta);
d2 = VectorLengthSquared(delta);
if (d2 <= d1) {
return d1 > d2;
} else {
return -1;
}
2016-03-27 11:49:47 +02:00
}
bool BotController::IsValidEnemy(Sentient *sent) const
{
if (sent == controlledEnt) {
return false;
}
if (sent->hidden() || (sent->flags & FL_NOTARGET)) {
// Ignore hidden / non-target enemies
return false;
}
if (sent->IsDead()) {
// Ignore dead enemies
return false;
}
if (sent->getSolidType() == SOLID_NOT) {
// Ignore non-solid, like spectators
return false;
}
if (sent->IsSubclassOfPlayer()) {
Player *player = static_cast<Player *>(sent);
if (g_gametype->integer >= GT_TEAM && player->GetTeam() == controlledEnt->GetTeam()) {
return false;
}
} else {
if (sent->m_Team == controlledEnt->m_Team) {
return false;
}
}
return true;
}
bool BotController::CheckCondition_Attack(void)
2016-03-27 11:49:47 +02:00
{
Container<Sentient *> sents = SentientList;
float maxDistance = 0;
2023-11-13 20:33:06 +01:00
bot_origin = controlledEnt->origin;
2023-11-13 20:33:06 +01:00
sents.Sort(sentients_compare);
for (int i = 1; i <= sents.NumObjects(); i++) {
Sentient *sent = sents.ObjectAt(i);
2023-11-13 20:33:06 +01:00
if (!IsValidEnemy(sent)) {
2023-11-13 20:33:06 +01:00
continue;
}
maxDistance = Q_min(world->m_fAIVisionDistance, world->farplane_distance * 0.828);
if (controlledEnt->CanSee(sent, 80, maxDistance, false)) {
2023-11-13 20:33:06 +01:00
m_pEnemy = sent;
m_iAttackTime = level.inttime + 1000;
2023-11-13 20:33:06 +01:00
return true;
}
}
if (level.inttime > m_iAttackTime) {
if (m_iAttackTime) {
movement.ClearMove();
2023-11-13 20:33:06 +01:00
m_iAttackTime = 0;
}
return false;
}
return true;
2016-03-27 11:49:47 +02:00
}
void BotController::State_EndAttack(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
controlledEnt->ZoomOff();
2016-03-27 11:49:47 +02:00
}
void BotController::State_Attack(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
bool bMelee = false;
float fMinDistance = 128;
float fMinDistanceSquared = fMinDistance * fMinDistance;
Weapon *pWeap = controlledEnt->GetActiveWeapon(WEAPON_MAIN);
bool bNoMove = false;
2023-11-13 20:33:06 +01:00
if (!m_pEnemy || !IsValidEnemy(m_pEnemy)) {
// Ignore dead enemies
2023-11-13 20:33:06 +01:00
m_iAttackTime = 0;
return;
}
float fDistanceSquared = (m_pEnemy->origin - controlledEnt->origin).lengthSquared();
2023-11-13 20:33:06 +01:00
if (controlledEnt->CanSee(
m_pEnemy, 20, Q_min(world->m_fAIVisionDistance, world->farplane_distance * 0.828), false
)) {
2023-11-13 20:33:06 +01:00
if (!pWeap) {
return;
}
float fPrimaryBulletRange = pWeap->GetBulletRange(FIRE_PRIMARY) / 1.25f;
float fPrimaryBulletRangeSquared = fPrimaryBulletRange * fPrimaryBulletRange;
float fSecondaryBulletRange = pWeap->GetBulletRange(FIRE_SECONDARY);
float fSecondaryBulletRangeSquared = fSecondaryBulletRange * fSecondaryBulletRange;
float fSpreadFactor = pWeap->GetSpreadFactor(FIRE_PRIMARY);
2023-11-13 20:33:06 +01:00
//
// check the fire movement speed if the weapon has a max fire movement
//
if (pWeap->GetMaxFireMovement() < 1 && pWeap->HasAmmoInClip(FIRE_PRIMARY)) {
float length;
length = controlledEnt->velocity.length();
if ((length / sv_runspeed->value) > (pWeap->GetMaxFireMovementMult())) {
bNoMove = true;
movement.ClearMove();
}
}
2023-11-13 20:33:06 +01:00
fMinDistance = fPrimaryBulletRange;
if (fMinDistance > 256) {
fMinDistance = 256;
}
fMinDistanceSquared = fMinDistance * fMinDistance;
if (controlledEnt->client->ps.stats[STAT_AMMO] > 0 || controlledEnt->client->ps.stats[STAT_CLIPAMMO] > 0) {
2023-11-13 20:33:06 +01:00
if (fDistanceSquared <= fPrimaryBulletRangeSquared) {
if (pWeap->IsSemiAuto()) {
if (controlledEnt->client->ps.iViewModelAnim == VM_ANIM_IDLE || controlledEnt->client->ps.iViewModelAnim >= VM_ANIM_IDLE_0 && controlledEnt->client->ps.iViewModelAnim <= VM_ANIM_IDLE_2) {
if (fSpreadFactor < 0.25) {
m_botCmd.buttons ^= BUTTON_ATTACKLEFT;
if (pWeap->GetZoom()) {
if (!controlledEnt->IsZoomed()) {
m_botCmd.buttons |= BUTTON_ATTACKRIGHT;
} else {
m_botCmd.buttons &= ~BUTTON_ATTACKRIGHT;
}
}
} else {
bNoMove = true;
movement.ClearMove();
}
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
controlledEnt->ZoomOff();
}
2023-11-13 20:33:06 +01:00
} else {
m_botCmd.buttons |= BUTTON_ATTACKLEFT;
}
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
controlledEnt->ZoomOff();
2023-11-13 20:33:06 +01:00
}
} else if (pWeap->GetFireType(FIRE_SECONDARY) == FT_MELEE) {
bMelee = true;
if (fDistanceSquared <= fSecondaryBulletRangeSquared) {
m_botCmd.buttons ^= BUTTON_ATTACKRIGHT;
} else {
m_botCmd.buttons &= ~BUTTON_ATTACKRIGHT;
}
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
controlledEnt->ZoomOff();
2023-11-13 20:33:06 +01:00
}
m_iAttackTime = level.inttime + 1000;
m_vOldEnemyPos = m_vLastEnemyPos;
m_vLastEnemyPos = m_pEnemy->centroid;
2023-11-13 20:33:06 +01:00
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
fMinDistanceSquared = 0;
}
rotation.AimAt(m_pEnemy->centroid + Vector(G_CRandom(8), G_CRandom(8), G_CRandom(8)));
if (bNoMove) {
return;
}
if ((!movement.MoveToBestAttractivePoint(5) && !movement.IsMoving())
|| (m_vOldEnemyPos != m_vLastEnemyPos && !movement.MoveDone())) {
2023-11-13 20:33:06 +01:00
if (!bMelee) {
if ((controlledEnt->origin - m_vLastEnemyPos).lengthSquared() < fMinDistanceSquared) {
Vector vDir = controlledEnt->origin - m_vLastEnemyPos;
2023-11-13 20:33:06 +01:00
VectorNormalizeFast(vDir);
movement.AvoidPath(m_vLastEnemyPos, fMinDistance, Vector(controlledEnt->orientation[1]) * 512);
2023-11-13 20:33:06 +01:00
} else {
movement.MoveNear(m_vLastEnemyPos, fMinDistance);
2023-11-13 20:33:06 +01:00
}
} else {
movement.MoveTo(m_vLastEnemyPos);
2023-11-13 20:33:06 +01:00
}
}
2024-10-06 01:13:12 +02:00
if (movement.IsMoving()) {
2024-10-06 01:13:12 +02:00
m_iAttackTime = level.inttime + 1000;
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Grenade state
Avoid any grenades
====================
*/
void BotController::InitState_Grenade(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
func->CheckCondition = &BotController::CheckCondition_Grenade;
func->ThinkState = &BotController::State_Grenade;
2016-03-27 11:49:47 +02:00
}
bool BotController::CheckCondition_Grenade(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
// FIXME: TODO
return false;
2016-03-27 11:49:47 +02:00
}
void BotController::State_Grenade(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
// FIXME: TODO
2016-03-27 11:49:47 +02:00
}
/*
====================
Weapon state
Change weapon when necessary
====================
*/
void BotController::InitState_Weapon(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
func->CheckCondition = &BotController::CheckCondition_Weapon;
func->BeginState = &BotController::State_BeginWeapon;
2016-03-27 11:49:47 +02:00
}
bool BotController::CheckCondition_Weapon(void)
2016-03-27 11:49:47 +02:00
{
return controlledEnt->GetActiveWeapon(WEAPON_MAIN)
!= controlledEnt->BestWeapon(NULL, false, WEAPON_CLASS_THROWABLE);
2016-03-27 11:49:47 +02:00
}
void BotController::State_BeginWeapon(void)
2016-03-27 11:49:47 +02:00
{
Weapon *weap = controlledEnt->BestWeapon(NULL, false, WEAPON_CLASS_THROWABLE);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (weap == NULL) {
SendCommand("safeholster 1");
return;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
SendCommand(va("use \"%s\"", weap->model.c_str()));
2016-03-27 11:49:47 +02:00
}
void BotController::Spawned(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
ClearEnemy();
m_iCuriousTime = 0;
m_botCmd.buttons = 0;
2016-03-27 11:49:47 +02:00
}
void BotController::Think()
2016-03-27 11:49:47 +02:00
{
usercmd_t ucmd;
usereyes_t eyeinfo;
UpdateBotStates();
GetUsercmd(&ucmd);
GetEyeInfo(&eyeinfo);
G_ClientThink(controlledEnt->edict, &ucmd, &eyeinfo);
}
void BotController::Killed(Event *ev)
{
Entity *attacker;
2023-11-13 20:33:06 +01:00
// send the respawn buttons
if (!(m_botCmd.buttons & BUTTON_ATTACKLEFT)) {
m_botCmd.buttons |= BUTTON_ATTACKLEFT;
} else {
m_botCmd.buttons &= ~BUTTON_ATTACKLEFT;
}
m_botEyes.ofs[0] = 0;
m_botEyes.ofs[1] = 0;
m_botEyes.ofs[2] = 0;
m_botEyes.angles[0] = 0;
m_botEyes.angles[1] = 0;
attacker = ev->GetEntity(1);
if (attacker && rand() % 5 == 0) {
// 1/5 chance to go back to the attacker position
m_vLastDeathPos = attacker->origin;
} else {
m_vLastDeathPos = vec_zero;
}
// Choose a new random primary weapon
Event event(EV_Player_PrimaryDMWeapon);
event.AddString("auto");
controlledEnt->ProcessEvent(event);
2016-03-27 11:49:47 +02:00
}
void BotController::GotKill(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
ClearEnemy();
m_iCuriousTime = 0;
if (level.inttime >= m_iNextTauntTime && (rand() % 5) == 0) {
//
// Randomly play a taunt
//
Event event("dmmessage");
event.AddInteger(0);
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
event.AddString("*5" + str(1 + (rand() % 8)));
} else {
event.AddString("*4" + str(1 + (rand() % 9)));
}
controlledEnt->ProcessEvent(event);
m_iNextTauntTime = level.inttime + 5000;
}
2016-03-27 11:49:47 +02:00
}
void BotController::EventStuffText(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
SendCommand(ev->GetString(1));
2016-03-27 11:49:47 +02:00
}
void BotController::setControlledEntity(Player *player)
{
controlledEnt = player;
movement.SetControlledEntity(player);
rotation.SetControlledEntity(player);
}
Player *BotController::getControlledEntity() const
{
return controlledEnt;
}
BotController *BotControllerManager::createController(Player *player)
{
BotController *controller = new BotController();
controller->setControlledEntity(player);
controllers.AddObject(controller);
return controller;
}
void BotControllerManager::removeController(BotController *controller)
{
controllers.RemoveObject(controller);
delete controller;
}
BotController *BotControllerManager::findController(Entity *ent)
{
int i;
for (i = 1; i <= controllers.NumObjects(); i++) {
BotController *controller = controllers.ObjectAt(i);
if (controller->getControlledEntity() == ent) {
return controller;
}
}
return nullptr;
}
const Container<BotController *>& BotControllerManager::getControllers() const
{
return controllers;
}
BotControllerManager::~BotControllerManager()
{
Cleanup();
}
void BotControllerManager::Init()
{
BotController::Init();
}
void BotControllerManager::Cleanup()
{
int i;
BotController::Init();
for (i = 1; i <= controllers.NumObjects(); i++) {
BotController *controller = controllers.ObjectAt(i);
delete controller;
}
controllers.FreeObjectList();
}
void BotControllerManager::ThinkControllers()
{
int i;
for (i = 1; i <= controllers.NumObjects(); i++) {
BotController *controller = controllers.ObjectAt(i);
controller->Think();
}
}
CLASS_DECLARATION(Player, PlayerBot, NULL) {
{&EV_Killed, &PlayerBot::Killed },
{&EV_GotKill, &PlayerBot::GotKill },
{&EV_Player_StuffText, &PlayerBot::EventStuffText},
{NULL, NULL }
};
PlayerBot::PlayerBot()
{
entflags |= ECF_BOT;
controller = NULL;
}
void PlayerBot::setController(BotController *controlledBy)
{
controller = controlledBy;
}
void PlayerBot::Spawned(void)
{
controller->Spawned();
Player::Spawned();
}
void PlayerBot::Killed(Event *ev)
{
Player::Killed(ev);
controller->Killed(ev);
}
void PlayerBot::GotKill(Event *ev)
{
Player::GotKill(ev);
controller->GotKill(ev);
}