Files
bearstonem--openmohaa-vr/code/fgame/playerbot.cpp
T
smallmodel 876829a742 Properly aim the enemy
The bot wasn't aiming properly at the enemy because the bot was aiming from the centroid. It also caused issues when the enemy was crouching
2024-12-16 19:45:42 +01:00

1100 lines
27 KiB
C++

/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team
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"
#include "consoleevent.h"
#include "debuglines.h"
#include "scriptexception.h"
#include "vehicleturret.h"
#include "weaputils.h"
// 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()
{
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;
m_iCuriousTime = 0;
m_iAttackTime = 0;
m_iEnemyEyesTag = -1;
m_iNextTauntTime = 0;
m_StateFlags = 0;
m_RunLabel.TrySetScript("global/bot_run.scr");
}
BotMovement& BotController::GetMovement()
{
return movement;
}
void BotController::Init(void)
{
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;
}
InitState_Attack(&botfuncs[0]);
InitState_Curious(&botfuncs[1]);
InitState_Grenade(&botfuncs[2]);
InitState_Idle(&botfuncs[3]);
InitState_Weapon(&botfuncs[4]);
}
void BotController::GetUsercmd(usercmd_t *ucmd)
{
*ucmd = m_botCmd;
}
void BotController::GetEyeInfo(usereyes_t *eyeinfo)
{
*eyeinfo = m_botEyes;
}
void BotController::UpdateBotStates(void)
{
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()) {
// The bot should respawn
m_botCmd.buttons ^= BUTTON_ATTACKLEFT;
return;
}
m_botCmd.buttons |= BUTTON_RUN;
m_botCmd.serverTime = level.svsTime;
m_botEyes.ofs[0] = 0;
m_botEyes.ofs[1] = 0;
m_botEyes.ofs[2] = controlledEnt->viewheight;
m_botEyes.angles[0] = 0;
m_botEyes.angles[1] = 0;
CheckStates();
movement.MoveThink(m_botCmd);
rotation.TurnThink(m_botCmd, m_botEyes);
CheckUse();
}
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)
{
char *buffer;
char *data;
size_t len;
ConsoleEvent ev;
len = strlen(text) + 1;
buffer = (char *)gi.Malloc(len);
data = buffer;
Q_strncpyz(data, text, len);
const char *com_token = COM_Parse(&data);
if (!com_token) {
return;
}
controlledEnt->m_lastcommand = com_token;
if (!Event::GetEvent(com_token)) {
return;
}
ev = ConsoleEvent(com_token);
if (!(ev.GetEventFlags(ev.eventnum) & EV_CONSOLE)) {
gi.Free(buffer);
return;
}
ev.SetConsoleEdict(controlledEnt->edict);
while (1) {
com_token = COM_Parse(&data);
if (!com_token || !*com_token) {
break;
}
ev.AddString(com_token);
}
gi.Free(buffer);
try {
controlledEnt->ProcessEvent(ev);
} catch (ScriptException& exc) {
gi.DPrintf("*** Bot Command Exception *** %s\n", exc.string.c_str());
}
}
/*
====================
AimAtAimNode
Make the bot face toward the current path
====================
*/
void BotController::AimAtAimNode(void)
{
Vector goal;
if (!movement.IsMoving()) {
return;
}
goal = movement.GetCurrentGoal();
if (goal != controlledEnt->origin) {
rotation.AimAt(goal);
}
Vector targetAngles = rotation.GetTargetAngles();
targetAngles.x = 0;
rotation.SetTargetAngles(targetAngles);
}
/*
====================
CheckReload
Make the bot reload if necessary
====================
*/
void BotController::CheckReload(void)
{
Weapon *weap = controlledEnt->GetActiveWeapon(WEAPON_MAIN);
if (weap && weap->CheckReload(FIRE_PRIMARY)) {
SendCommand("reload");
}
}
/*
====================
NoticeEvent
Warn the bot of an event
====================
*/
void BotController::NoticeEvent(Vector vPos, int iType, Entity *pEnt, float fDistanceSquared, float fRadiusSquared)
{
Sentient *pSentOwner;
float fRangeFactor;
fRangeFactor = 1.0 - (fDistanceSquared / fRadiusSquared);
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;
}
if ((pSentOwner->flags & FL_NOTARGET) || pSentOwner->getSolidType() == SOLID_NOT) {
return;
}
// Ignore teammates
if (pSentOwner->IsSubclassOfPlayer()) {
Player *p = static_cast<Player *>(pSentOwner);
if (g_gametype->integer >= GT_TEAM && p->GetTeam() == controlledEnt->GetTeam()) {
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;
break;
}
}
/*
====================
ClearEnemy
Clear the bot's enemy
====================
*/
void BotController::ClearEnemy(void)
{
m_iAttackTime = 0;
m_pEnemy = NULL;
m_iEnemyEyesTag = -1;
m_vOldEnemyPos = vec_zero;
m_vLastEnemyPos = vec_zero;
}
/*
====================
Bot states
--------------------
____________________
--------------------
____________________
--------------------
____________________
--------------------
____________________
====================
*/
void BotController::CheckStates(void)
{
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);
State_Reset();
}
}
/*
====================
Default state
====================
*/
void BotController::State_DefaultBegin(void)
{
movement.ClearMove();
}
void BotController::State_DefaultEnd(void) {}
void BotController::State_Reset(void)
{
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;
m_iEnemyEyesTag = -1;
}
/*
====================
Idle state
Make the bot move to random directions
====================
*/
void BotController::InitState_Idle(botfunc_t *func)
{
func->CheckCondition = &BotController::CheckCondition_Idle;
func->ThinkState = &BotController::State_Idle;
}
bool BotController::CheckCondition_Idle(void)
{
if (m_iCuriousTime) {
return false;
}
if (m_iAttackTime) {
return false;
}
return true;
}
void BotController::State_Idle(void)
{
AimAtAimNode();
CheckReload();
if (!movement.MoveToBestAttractivePoint() && !movement.IsMoving()) {
if (m_vLastDeathPos != vec_zero) {
movement.MoveTo(m_vLastDeathPos);
if (movement.MoveDone()) {
m_vLastDeathPos = vec_zero;
}
} else {
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);
movement.AvoidPath(controlledEnt->origin + randomDir, radius, preferredDir);
}
}
}
/*
====================
Curious state
Forward to the last event position
====================
*/
void BotController::InitState_Curious(botfunc_t *func)
{
func->CheckCondition = &BotController::CheckCondition_Curious;
func->ThinkState = &BotController::State_Curious;
}
bool BotController::CheckCondition_Curious(void)
{
if (m_iAttackTime) {
m_iCuriousTime = 0;
return false;
}
if (level.inttime > m_iCuriousTime) {
if (m_iCuriousTime) {
movement.ClearMove();
m_iCuriousTime = 0;
}
return false;
}
return true;
}
void BotController::State_Curious(void)
{
AimAtAimNode();
if (!movement.MoveToBestAttractivePoint(3) && (!movement.IsMoving() || m_vLastCuriousPos != m_vNewCuriousPos)) {
movement.MoveTo(m_vNewCuriousPos);
m_vLastCuriousPos = m_vNewCuriousPos;
}
if (movement.MoveDone()) {
m_iCuriousTime = 0;
}
}
/*
====================
Attack state
Attack the enemy
====================
*/
void BotController::InitState_Attack(botfunc_t *func)
{
func->CheckCondition = &BotController::CheckCondition_Attack;
func->EndState = &BotController::State_EndAttack;
func->ThinkState = &BotController::State_Attack;
}
static Vector bot_origin;
static int sentients_compare(const void *elem1, const void *elem2)
{
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;
}
}
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)
{
Container<Sentient *> sents = SentientList;
float maxDistance = 0;
bot_origin = controlledEnt->origin;
sents.Sort(sentients_compare);
for (int i = 1; i <= sents.NumObjects(); i++) {
Sentient *sent = sents.ObjectAt(i);
if (!IsValidEnemy(sent)) {
continue;
}
maxDistance = Q_min(world->m_fAIVisionDistance, world->farplane_distance * 0.828);
if (controlledEnt->CanSee(sent, 80, maxDistance, false)) {
if (m_pEnemy != sent) {
m_iEnemyEyesTag = -1;
}
m_pEnemy = sent;
m_vLastEnemyPos = m_pEnemy->centroid;
m_iAttackTime = level.inttime + 1000;
return true;
}
}
if (level.inttime > m_iAttackTime) {
if (m_iAttackTime) {
movement.ClearMove();
m_iAttackTime = 0;
}
return false;
}
return true;
}
void BotController::State_EndAttack(void)
{
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
controlledEnt->ZoomOff();
}
void BotController::State_Attack(void)
{
bool bMelee = false;
bool bCanSee = false;
float fMinDistance = 128;
float fMinDistanceSquared = fMinDistance * fMinDistance;
float fEnemyDistanceSquared;
Weapon *pWeap = controlledEnt->GetActiveWeapon(WEAPON_MAIN);
bool bNoMove = false;
if (!m_pEnemy || !IsValidEnemy(m_pEnemy)) {
// Ignore dead enemies
m_iAttackTime = 0;
return;
}
float fDistanceSquared = (m_pEnemy->origin - controlledEnt->origin).lengthSquared();
m_vOldEnemyPos = m_vLastEnemyPos;
bCanSee = controlledEnt->CanSee(m_pEnemy, 20, Q_min(world->m_fAIVisionDistance, world->farplane_distance * 0.828), false);
if (bCanSee) {
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);
//
// 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();
}
}
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) {
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();
}
} else {
m_botCmd.buttons |= BUTTON_ATTACKLEFT;
}
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
controlledEnt->ZoomOff();
}
} 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();
}
m_iAttackTime = level.inttime + 1000;
m_iAttackStopAimTime = level.inttime + 3000;
m_vLastEnemyPos = m_pEnemy->centroid;
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
fMinDistanceSquared = 0;
}
if (bCanSee || level.inttime < m_iAttackStopAimTime) {
Vector vRandomOffset;
Vector vTarget;
orientation_t eyes_or;
if (m_iEnemyEyesTag == -1) {
// Cache the tag
m_iEnemyEyesTag = gi.Tag_NumForName(m_pEnemy->edict->tiki, "eyes bone");
}
if (m_iEnemyEyesTag != -1) {
// Use the enemy's eyes bone
m_pEnemy->GetTag(m_iEnemyEyesTag, &eyes_or);
vRandomOffset = Vector(G_CRandom(8), G_CRandom(8), -G_Random(32));
vTarget = eyes_or.origin + vRandomOffset;
rotation.AimAt(eyes_or.origin + vRandomOffset);
} else {
vRandomOffset = Vector(G_CRandom(8), G_CRandom(8), 16 + G_Random(m_pEnemy->viewheight - 16));
vTarget = m_pEnemy->origin + vRandomOffset;
}
rotation.AimAt(vTarget);
} else {
AimAtAimNode();
}
if (bNoMove) {
return;
}
fEnemyDistanceSquared = (controlledEnt->origin - m_vLastEnemyPos).lengthSquared();
if ((!movement.MoveToBestAttractivePoint(5) && !movement.IsMoving())
|| (m_vOldEnemyPos != m_vLastEnemyPos && !movement.MoveDone())
|| fEnemyDistanceSquared < fMinDistanceSquared) {
if (!bMelee || !bCanSee) {
if (fEnemyDistanceSquared < fMinDistanceSquared) {
Vector vDir = controlledEnt->origin - m_vLastEnemyPos;
VectorNormalizeFast(vDir);
movement.AvoidPath(m_vLastEnemyPos, fMinDistance, Vector(controlledEnt->orientation[1]) * 512);
} else {
movement.MoveTo(m_vLastEnemyPos);
}
if (!bCanSee && movement.MoveDone()) {
// Lost track of the enemy
ClearEnemy();
return;
}
} else {
movement.MoveTo(m_vLastEnemyPos);
}
}
if (movement.IsMoving()) {
m_iAttackTime = level.inttime + 1000;
}
}
/*
====================
Grenade state
Avoid any grenades
====================
*/
void BotController::InitState_Grenade(botfunc_t *func)
{
func->CheckCondition = &BotController::CheckCondition_Grenade;
func->ThinkState = &BotController::State_Grenade;
}
bool BotController::CheckCondition_Grenade(void)
{
// FIXME: TODO
return false;
}
void BotController::State_Grenade(void)
{
// FIXME: TODO
}
/*
====================
Weapon state
Change weapon when necessary
====================
*/
void BotController::InitState_Weapon(botfunc_t *func)
{
func->CheckCondition = &BotController::CheckCondition_Weapon;
func->BeginState = &BotController::State_BeginWeapon;
}
bool BotController::CheckCondition_Weapon(void)
{
return controlledEnt->GetActiveWeapon(WEAPON_MAIN)
!= controlledEnt->BestWeapon(NULL, false, WEAPON_CLASS_THROWABLE);
}
void BotController::State_BeginWeapon(void)
{
Weapon *weap = controlledEnt->BestWeapon(NULL, false, WEAPON_CLASS_THROWABLE);
if (weap == NULL) {
SendCommand("safeholster 1");
return;
}
SendCommand(va("use \"%s\"", weap->model.c_str()));
}
void BotController::Spawned(void)
{
ClearEnemy();
m_iCuriousTime = 0;
m_botCmd.buttons = 0;
}
void BotController::Think()
{
usercmd_t ucmd;
usereyes_t eyeinfo;
UpdateBotStates();
GetUsercmd(&ucmd);
GetEyeInfo(&eyeinfo);
G_ClientThink(controlledEnt->edict, &ucmd, &eyeinfo);
}
void BotController::Killed(Event *ev)
{
Entity *attacker;
// 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);
}
void BotController::GotKill(Event *ev)
{
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;
}
}
void BotController::EventStuffText(Event *ev)
{
SendCommand(ev->GetString(1));
}
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);
}