openmohaa/code/fgame/playerbot.cpp

1509 lines
35 KiB
C++
Raw Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
2023-11-13 20:33:06 +01:00
Copyright (C) 2023 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;
2023-11-13 20:33:06 +01:00
CLASS_DECLARATION(Player, PlayerBot, NULL) {
{&EV_Killed, &PlayerBot::Killed },
{&EV_GotKill, &PlayerBot::GotKill },
{&EV_Player_StuffText, &PlayerBot::EventStuffText},
{NULL, NULL }
2016-03-27 11:49:47 +02:00
};
2023-11-13 20:33:06 +01:00
PlayerBot::botfunc_t PlayerBot::botfuncs[MAX_BOT_FUNCTIONS];
2016-03-27 11:49:47 +02:00
PlayerBot::PlayerBot()
{
entflags |= ECF_BOT;
2023-11-13 20:33:06 +01:00
if (LoadingSavegame) {
return;
}
m_Path.SetFallHeight(400);
2023-11-13 20:33:06 +01:00
m_bPathing = false;
m_bTempAway = false;
m_bDeltaMove = true;
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] = viewheight;
m_vAngSpeed = vec_zero;
m_vTargetAng = vec_zero;
m_vCurrentAng = vec_zero;
m_iCheckPathTime = 0;
m_iTempAwayTime = 0;
m_iNumBlocks = 0;
2023-11-13 20:33:06 +01:00
m_fYawSpeedMult = 1.0f;
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
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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 = &PlayerBot::State_DefaultBegin;
botfuncs[i].EndState = &PlayerBot::State_DefaultEnd;
}
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
}
2023-11-13 20:33:06 +01:00
float AngleDifference(float ang1, float ang2)
{
float diff;
diff = ang1 - ang2;
if (ang1 > ang2) {
if (diff > 180.0) {
diff -= 360.0;
}
} else {
if (diff < -180.0) {
diff += 360.0;
}
}
return diff;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::TurnThink(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
float diff, factor, maxchange, anglespeed, desired_speed;
int i;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (m_vTargetAng[PITCH] > 180) {
m_vTargetAng[PITCH] -= 360;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
factor = 0.25f;
maxchange = 360;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (maxchange < 240) {
maxchange = 240;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
maxchange *= level.frametime;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
for (i = 0; i < 2; i++) {
//over reaction view model
m_vCurrentAng[i] = AngleMod(m_vCurrentAng[i]);
m_vTargetAng[i] = AngleMod(m_vTargetAng[i]);
diff = AngleDifference(m_vCurrentAng[i], m_vTargetAng[i]);
desired_speed = diff * factor;
2016-03-27 11:49:47 +02:00
m_vAngSpeed[i] = Q_clamp_float(m_vAngSpeed[i] + (m_vAngSpeed[i] - desired_speed), -180, 180);
anglespeed = Q_clamp_float(m_vAngSpeed[i], -maxchange, maxchange);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
m_vCurrentAng[i] += anglespeed;
m_vCurrentAng[i] = AngleMod(m_vCurrentAng[i]);
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
//demping
m_vAngSpeed[i] *= 0.2 * (1 - factor);
2023-11-13 20:33:06 +01:00
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (m_vCurrentAng[PITCH] > 180) {
m_vCurrentAng[PITCH] -= 360;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
m_botEyes.angles[0] = m_vCurrentAng[0];
m_botEyes.angles[1] = m_vCurrentAng[1];
m_botCmd.angles[0] = ANGLE2SHORT(m_vCurrentAng[0]) - client->ps.delta_angles[0];
m_botCmd.angles[1] = ANGLE2SHORT(m_vCurrentAng[1]) - client->ps.delta_angles[1];
m_botCmd.angles[2] = ANGLE2SHORT(m_vCurrentAng[2]) - client->ps.delta_angles[2];
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::CheckAttractiveNodes(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
for (int i = m_attractList.NumObjects(); i > 0; i--) {
nodeAttract_t *a = m_attractList.ObjectAt(i);
if (a->m_pNode == NULL || !a->m_pNode->CheckTeam(this) || level.time > a->m_fRespawnTime) {
delete a;
m_attractList.RemoveObjectAt(i);
}
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::MoveThink(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Vector vDir;
Vector vAngles;
Vector vWishDir;
m_botCmd.forwardmove = 0;
m_botCmd.rightmove = 0;
CheckAttractiveNodes();
if (!IsMoving()) {
return;
}
if (m_bTempAway && level.inttime >= m_iTempAwayTime) {
m_bTempAway = false;
m_Path.FindPath(origin, m_vTargetPos, this, 0, NULL, 0);
}
if (!m_bTempAway) {
if (m_Path.CurrentNode()) {
m_Path.UpdatePos(origin, 8);
2023-11-13 20:33:06 +01:00
m_vCurrentGoal = origin;
VectorAdd2D(m_vCurrentGoal, m_Path.CurrentDelta(), m_vCurrentGoal);
2023-11-13 20:33:06 +01:00
2024-10-06 14:24:32 +02:00
if (MoveDone()) {
// Clear the path
m_Path.Clear();
}
2023-11-13 20:33:06 +01:00
}
}
if (ai_debugpath->integer) {
G_DebugLine(centroid, m_vCurrentGoal + Vector(0, 0, 36), 1, 1, 0, 1);
}
// Check if we're blocked
if (level.inttime >= m_iCheckPathTime) {
m_bDeltaMove = false;
m_iCheckPathTime = level.inttime + 1000;
2023-11-13 20:33:06 +01:00
2024-10-03 20:06:12 +02:00
if (m_iNumBlocks >= 5) {
// Give up
ClearMove();
2023-11-13 20:33:06 +01:00
}
m_bTempAway = false;
if (groundentity || client->ps.walking) {
if (GetMoveResult() >= MOVERESULT_BLOCKED || velocity.lengthSquared() <= Square(8)) {
m_bTempAway = true;
} else if ((origin - m_vLastCheckPos[0]).lengthSquared() <= Square(32) && (origin - m_vLastCheckPos[1]).lengthSquared() <= Square(32)) {
m_bTempAway = true;
}
} else {
// falling
if (GetMoveResult() >= MOVERESULT_BLOCKED) {
// stuck while falling
m_bTempAway = true;
}
}
2024-10-03 20:06:12 +02:00
if (m_bTempAway) {
m_bTempAway = true;
m_bDeltaMove = false;
m_iTempAwayTime = level.inttime + 750;
m_iNumBlocks++;
// Try to backward a little
m_Path.Clear();
m_Path.ForceShortLookahead();
m_vCurrentGoal = origin + Vector(G_CRandom(512), G_CRandom(512), G_CRandom(512));
} else {
m_iNumBlocks = 0;
if (!m_Path.CurrentNode()) {
m_vTargetPos = origin + Vector(G_CRandom(512), G_CRandom(512), G_CRandom(512));
m_vCurrentGoal = m_vTargetPos;
}
}
m_vLastCheckPos[1] = m_vLastCheckPos[0];
m_vLastCheckPos[0] = origin;
2023-11-13 20:33:06 +01:00
}
if (ai_debugpath->integer) {
PathInfo *pos = m_Path.CurrentNode();
if (pos != NULL) {
while (pos != m_Path.LastNode()) {
Vector vStart = pos->point + Vector(0, 0, 32);
pos--;
Vector vEnd = pos->point + Vector(0, 0, 32);
G_DebugLine(vStart, vEnd, 1, 0, 0, 1);
}
}
}
if (m_Path.CurrentNode()) {
if ((m_vTargetPos - origin).lengthSquared() <= Square(16)) {
ClearMove();
}
} else {
if ((m_vTargetPos - origin).lengthXYSquared() <= Square(16)) {
ClearMove();
}
2023-11-13 20:33:06 +01:00
}
// Rotate the dir
if (m_Path.CurrentNode()) {
vDir[0] = m_Path.CurrentDelta()[0];
vDir[1] = m_Path.CurrentDelta()[1];
} else {
vDir = m_vCurrentGoal - origin;
}
2023-11-13 20:33:06 +01:00
vDir[2] = 0;
VectorNormalize2D(vDir);
2023-11-13 20:33:06 +01:00
vAngles = vDir.toAngles() - angles;
vAngles.AngleVectorsLeft(&vWishDir);
2024-10-04 21:23:49 +02:00
m_vLastValidDir = vDir;
2023-11-13 20:33:06 +01:00
m_vLastValidGoal = m_vCurrentGoal;
// Forward to the specified direction
float x = vWishDir.x * 127;
float y = -vWishDir.y * 127;
2023-11-13 20:33:06 +01:00
m_botCmd.forwardmove = (signed char)Q_clamp(x, -127, 127);
m_botCmd.rightmove = (signed char)Q_clamp(y, -127, 127);
2023-11-13 20:33:06 +01:00
CheckJump();
Weapon *pWeap = GetActiveWeapon(WEAPON_MAIN);
if (pWeap && !pWeap->ShouldReload()) {
m_RunLabel.Execute(this);
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::CheckJump(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Vector start;
Vector end;
Vector dir;
trace_t trace;
if (m_pLadder) {
if (!m_botCmd.upmove) {
m_botCmd.upmove = 127;
} else {
m_botCmd.upmove = 0;
}
return;
}
dir = m_vLastValidDir;
2023-11-13 20:33:06 +01:00
start = origin + Vector(0, 0, STEPSIZE);
end = origin + Vector(0, 0, STEPSIZE) + dir * (maxs.y - mins.y);
2023-11-13 20:33:06 +01:00
2024-10-04 21:23:49 +02:00
if (ai_debugpath->integer) {
G_DebugLine(start, end, 1, 0, 1, 1);
}
2023-11-13 20:33:06 +01:00
// Check if the bot needs to jump
trace = G_Trace(start, mins, maxs, end, this, MASK_PLAYERSOLID, false, "PlayerBot::CheckJump");
2023-11-13 20:33:06 +01:00
// No need to jump
2024-10-04 21:23:49 +02:00
if (trace.fraction > 0.5f) {
2023-11-13 20:33:06 +01:00
m_botCmd.upmove = 0;
return;
}
2024-10-04 21:23:49 +02:00
start = origin;
end = origin + Vector(0, 0, STEPSIZE * 3);
2023-11-13 20:33:06 +01:00
2024-10-04 21:23:49 +02:00
if (ai_debugpath->integer) {
G_DebugLine(start, end, 1, 0, 1, 1);
}
// Check if the bot can jump up
2023-11-13 20:33:06 +01:00
trace = G_Trace(start, mins, maxs, end, this, MASK_PLAYERSOLID, true, "PlayerBot::CheckJump");
2024-10-04 21:23:49 +02:00
start = trace.endpos;
end = trace.endpos + dir * (maxs.y - mins.y);
if (ai_debugpath->integer) {
G_DebugLine(start, end, 1, 0, 1, 1);
}
Vector bounds[2];
bounds[0] = Vector(mins[0], mins[1], 0);
bounds[1] = Vector(maxs[0], maxs[1], (maxs[0] + maxs[1]) * 0.5);
// Check if the bot can jump at the location
trace = G_Trace(start, bounds[0], bounds[1], end, this, MASK_PLAYERSOLID, false, "PlayerBot::CheckJump");
if (trace.fraction < 1) {
m_botCmd.upmove = 0;
return;
}
2023-11-13 20:33:06 +01:00
// Make the bot climb walls
2024-10-04 21:23:49 +02:00
if (!m_botCmd.upmove) {
m_botCmd.upmove = 127;
2023-11-13 20:33:06 +01:00
} else {
m_botCmd.upmove = 0;
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::CheckEndPos(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Vector start;
Vector end;
trace_t trace;
if (!m_Path.LastNode()) {
return;
}
start = m_Path.LastNode()->point;
end = m_vTargetPos;
trace = G_Trace(start, mins, maxs, end, this, MASK_TARGETPATH, true, "PlayerBot::CheckEndPos");
if (trace.fraction < 0.95f) {
m_vTargetPos = trace.endpos;
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::CheckUse(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Vector dir;
Vector start;
Vector end;
trace_t trace;
angles.AngleVectorsLeft(&dir);
start = origin + Vector(0, 0, viewheight);
end = origin + Vector(0, 0, viewheight) + dir * 32;
trace = G_Trace(start, vec_zero, vec_zero, end, this, MASK_USABLE, false, "PlayerBot::CheckUse");
// It may be a door
if ((trace.allsolid || trace.startsolid || trace.fraction != 1.0f) && trace.entityNum) {
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;
2024-10-03 20:06:12 +02:00
m_Path.ForceShortLookahead();
} else {
m_botCmd.buttons &= ~BUTTON_USE;
2023-11-13 20:33:06 +01:00
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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 (IsDead() || 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] = viewheight;
m_botEyes.angles[0] = 0;
m_botEyes.angles[1] = 0;
CheckStates();
MoveThink();
TurnThink();
CheckUse();
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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
2023-11-13 20:33:06 +01:00
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
2023-11-13 20:33:06 +01:00
ev.SetConsoleEdict(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 {
ProcessEvent(ev);
} catch (ScriptException& exc) {
gi.DPrintf("*** Bot Command Exception *** %s\n", exc.string.c_str());
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::setAngles(Vector ang)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Entity::setAngles(ang);
SetTargetAngles(angles);
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::updateOrigin(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Entity::updateOrigin();
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (origin == client->ps.origin) {
// no change because of Pmove
return;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
m_pPrimaryAttract = NULL;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (m_Path.CurrentNode()) {
// recalculate paths because of a new origin
m_Path.ReFindPath(origin, this);
}
2016-03-27 11:49:47 +02:00
}
/*
====================
SetTargetAngles
Set the bot's angle
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::SetTargetAngles(Vector vAngles)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_vTargetAng = vAngles;
2016-03-27 11:49:47 +02:00
}
/*
====================
AimAt
Make the bot face to the specified direction
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::AimAt(Vector vPos)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Vector vDelta = vPos - centroid;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
VectorNormalize(vDelta);
vectoangles(vDelta, m_vTargetAng);
2016-03-27 11:49:47 +02:00
}
/*
====================
AimAtAimNode
Make the bot face toward the current path
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::AimAtAimNode(void)
2016-03-27 11:49:47 +02:00
{
if (!m_bPathing) {
return;
}
if (!m_Path.CurrentNode()) {
2023-11-13 20:33:06 +01:00
AimAt(m_vCurrentGoal);
} else if (!m_Path.Complete(origin)) {
AimAt(origin + Vector(m_Path.CurrentDelta()[0], m_Path.CurrentDelta()[1], 0));
//int maxIndex = Q_min(3, m_Path.CurrentNode() - m_Path.LastNode());
//AimAt((m_Path.CurrentNode() - maxIndex)->point);
2023-11-13 20:33:06 +01:00
}
m_vTargetAng[PITCH] = 0;
2016-03-27 11:49:47 +02:00
}
/*
====================
CheckReload
Make the bot reload if necessary
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::CheckReload(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Weapon *weap = 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
}
2024-10-03 20:06:12 +02:00
/*
====================
NewMove
Called when there is a new move
====================
*/
void PlayerBot::NewMove() {
m_bPathing = true;
m_iCheckPathTime = level.inttime + 2000;
m_vLastCheckPos[0] = origin;
m_vLastCheckPos[1] = origin;
}
2016-03-27 11:49:47 +02:00
/*
====================
MoveTo
Move to the specified position
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::MoveTo(Vector vPos, float *vLeashHome, float fLeashRadius)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_vTargetPos = vPos;
2024-10-06 14:24:32 +02:00
m_Path.FindPath(origin, m_vTargetPos, this, 0, vLeashHome, fLeashRadius * fLeashRadius);
2016-03-27 11:49:47 +02:00
2024-10-03 20:06:12 +02:00
NewMove();
2023-11-13 20:33:06 +01:00
if (!m_Path.CurrentNode()) {
m_bPathing = false;
return;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
CheckEndPos();
2016-03-27 11:49:47 +02:00
}
/*
====================
MoveTo
Move to the nearest attractive point with a minimum priority
Returns true if no attractive point was found
====================
*/
2023-11-13 20:33:06 +01:00
bool PlayerBot::MoveToBestAttractivePoint(int iMinPriority)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Container<AttractiveNode *> list;
AttractiveNode *bestNode;
float bestDistanceSquared;
int bestPriority;
if (m_pPrimaryAttract) {
MoveTo(m_pPrimaryAttract->origin);
if (!IsMoving()) {
m_pPrimaryAttract = NULL;
} else {
if (MoveDone()) {
if (!m_fAttractTime) {
m_fAttractTime = level.time + m_pPrimaryAttract->m_fMaxStayTime;
}
if (level.time > m_fAttractTime) {
nodeAttract_t *a = new nodeAttract_t;
a->m_fRespawnTime = level.time + m_pPrimaryAttract->m_fRespawnTime;
a->m_pNode = m_pPrimaryAttract;
m_pPrimaryAttract = NULL;
}
}
return true;
}
}
if (!attractiveNodes.NumObjects()) {
return false;
}
bestNode = NULL;
bestDistanceSquared = 99999999.0f;
bestPriority = iMinPriority;
for (int i = attractiveNodes.NumObjects(); i > 0; i--) {
AttractiveNode *node = attractiveNodes.ObjectAt(i);
float distSquared;
bool m_bRespawning = false;
for (int j = m_attractList.NumObjects(); j > 0; j--) {
AttractiveNode *node2 = m_attractList.ObjectAt(j)->m_pNode;
if (node2 == node) {
m_bRespawning = true;
break;
}
}
if (m_bRespawning) {
continue;
}
if (node->m_iPriority < bestPriority) {
continue;
}
if (!node->CheckTeam(this)) {
continue;
}
distSquared = VectorLengthSquared(origin - node->origin);
if (node->m_fMaxDistanceSquared >= 0 && distSquared > node->m_fMaxDistanceSquared) {
continue;
}
if (!CanMoveTo(node->origin)) {
continue;
}
if (distSquared < bestDistanceSquared) {
bestDistanceSquared = distSquared;
bestNode = node;
bestPriority = node->m_iPriority;
}
}
if (bestNode) {
m_pPrimaryAttract = bestNode;
m_fAttractTime = 0;
MoveTo(bestNode->origin);
return true;
} else {
// No attractive point found
return false;
}
2016-03-27 11:49:47 +02:00
}
/*
====================
CanMoveTo
Returns true if the bot has done moving
====================
*/
2023-11-13 20:33:06 +01:00
bool PlayerBot::CanMoveTo(Vector vPos)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
return m_Path.DoesTheoreticPathExist(origin, vPos, NULL, 0, NULL, 0);
2016-03-27 11:49:47 +02:00
}
/*
====================
MoveDone
Returns true if the bot has done moving
====================
*/
2023-11-13 20:33:06 +01:00
bool PlayerBot::MoveDone(void)
2016-03-27 11:49:47 +02:00
{
2024-10-06 14:24:32 +02:00
PathInfo* next;
if (!m_bPathing) {
return true;
}
if (m_bTempAway) {
return false;
}
if (!m_Path.CurrentNode()) {
return true;
}
2024-10-06 14:24:32 +02:00
Vector delta = Vector(m_Path.CurrentPathGoal()) - origin;
if (delta.lengthXYSquared() < Square(16) && delta.z < maxs.z) {
return true;
}
return false;
2016-03-27 11:49:47 +02:00
}
/*
====================
IsMoving
Returns true if the bot has a current path
====================
*/
2023-11-13 20:33:06 +01:00
bool PlayerBot::IsMoving(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
return m_bPathing;
2016-03-27 11:49:47 +02:00
}
/*
====================
ClearMove
Stop the bot from moving
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::ClearMove(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_Path.Clear();
m_bPathing = false;
m_iNumBlocks = 0;
2016-03-27 11:49:47 +02:00
}
/*
====================
MoveNear
Move near the specified position within the radius
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::MoveNear(Vector vNear, float fRadius, float *vLeashHome, float fLeashRadius)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_Path.FindPathNear(origin, vNear, this, 0, fRadius * fRadius, vLeashHome, fLeashRadius * fLeashRadius);
2024-10-03 20:06:12 +02:00
NewMove();
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if (!m_Path.CurrentNode()) {
m_bPathing = false;
return;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
m_vTargetPos = m_Path.LastNode()->point;
2016-03-27 11:49:47 +02:00
}
/*
====================
AvoidPath
Avoid the specified position within the radius and start from a direction
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::AvoidPath(
Vector vAvoid, float fAvoidRadius, Vector vPreferredDir, float *vLeashHome, float fLeashRadius
)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Vector vDir;
if (vPreferredDir == vec_zero) {
vDir = origin - vAvoid;
VectorNormalizeFast(vDir);
} else {
vDir = vPreferredDir;
}
m_Path.FindPathAway(origin, vAvoid, vDir, this, fAvoidRadius, vLeashHome, fLeashRadius * fLeashRadius);
2024-10-03 20:06:12 +02:00
NewMove();
2023-11-13 20:33:06 +01:00
if (!m_Path.CurrentNode()) {
// Random movements
m_vTargetPos = origin + Vector(G_Random(256) - 128, G_Random(256) - 128, G_Random(256) - 128);
m_vCurrentGoal = m_vTargetPos;
2023-11-13 20:33:06 +01:00
return;
}
m_vTargetPos = m_Path.LastNode()->point;
2016-03-27 11:49:47 +02:00
}
/*
====================
NoticeEvent
Warn the bot of an event
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::NoticeEvent(Vector vPos, int iType, Entity *pEnt, float fDistanceSquared, float fRadiusSquared)
2016-03-27 11:49:47 +02:00
{
Sentient* pSentOwner;
2024-10-06 01:01:03 +02:00
float fRangeFactor;
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 == this) {
// 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() == 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;
2024-10-06 14:24:32 +02:00
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
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::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
--------------------
____________________
--------------------
____________________
--------------------
____________________
--------------------
____________________
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::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 !!!", client->pers.netname);
State_Reset();
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Default state
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::State_DefaultBegin(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
ClearMove();
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::State_DefaultEnd(void) {}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
void PlayerBot::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
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::InitState_Idle(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
func->CheckCondition = &PlayerBot::CheckCondition_Idle;
func->ThinkState = &PlayerBot::State_Idle;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
bool PlayerBot::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
}
2023-11-13 20:33:06 +01:00
void PlayerBot::State_Idle(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
AimAtAimNode();
CheckReload();
if (!MoveToBestAttractivePoint() && !IsMoving()) {
if (m_vLastDeathPos != vec_zero) {
MoveTo(m_vLastDeathPos);
if (MoveDone()) {
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(orientation[0]) * (rand() % 5 ? 1024 : -1024);
float radius = 512 + G_Random(2048);
2024-10-03 20:22:54 +02:00
2024-10-03 22:23:46 +02:00
AvoidPath(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
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::InitState_Curious(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
func->CheckCondition = &PlayerBot::CheckCondition_Curious;
func->ThinkState = &PlayerBot::State_Curious;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
bool PlayerBot::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) {
ClearMove();
m_iCuriousTime = 0;
}
return false;
}
return true;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::State_Curious(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
AimAtAimNode();
2024-10-06 14:24:32 +02:00
if (!MoveToBestAttractivePoint(3) && (!IsMoving() || m_vLastCuriousPos != m_vNewCuriousPos)) {
MoveTo(m_vNewCuriousPos);
m_vLastCuriousPos = m_vNewCuriousPos;
2023-11-13 20:33:06 +01:00
}
if (MoveDone()) {
m_iCuriousTime = 0;
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Attack state
Attack the enemy
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::InitState_Attack(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
func->CheckCondition = &PlayerBot::CheckCondition_Attack;
func->EndState = &PlayerBot::State_EndAttack;
func->ThinkState = &PlayerBot::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 PlayerBot::IsValidEnemy(Sentient* sent) const {
if (sent == this) {
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() == GetTeam()) {
return false;
}
}
else {
if (sent->m_Team == m_Team) {
return false;
}
}
return true;
}
2023-11-13 20:33:06 +01:00
bool PlayerBot::CheckCondition_Attack(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Container<Sentient *> sents = SentientList;
float maxDistance = 0;
2023-11-13 20:33:06 +01:00
bot_origin = origin;
sents.Sort(sentients_compare);
for (int i = 1; i <= sents.NumObjects(); i++) {
Sentient *sent = sents.ObjectAt(i);
if (!IsValidEnemy(sent)) {
2023-11-13 20:33:06 +01:00
continue;
}
maxDistance = Q_min(world->m_fAIVisionDistance, world->farplane_distance * 0.828);
if (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) {
ClearMove();
m_iAttackTime = 0;
}
return false;
}
return true;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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);
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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 = 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 - origin).lengthSquared();
if (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;
//
// check the fire movement speed if the weapon has a max fire movement
//
if (pWeap->m_fMaxFireMovement < 1 && pWeap->HasAmmoInClip(FIRE_PRIMARY)) {
float length;
length = velocity.length();
if ((length / sv_runspeed->value) > (pWeap->m_fMaxFireMovement * pWeap->m_fMovementSpeed)) {
bNoMove = true;
ClearMove();
}
}
2023-11-13 20:33:06 +01:00
fMinDistance = fPrimaryBulletRange;
if (fMinDistance > 256) {
fMinDistance = 256;
}
fMinDistanceSquared = fMinDistance * fMinDistance;
if (client->ps.stats[STAT_AMMO] > 0 || client->ps.stats[STAT_CLIPAMMO] > 0) {
if (fDistanceSquared <= fPrimaryBulletRangeSquared) {
if (pWeap->IsSemiAuto()) {
m_botCmd.buttons ^= BUTTON_ATTACKLEFT;
} else {
m_botCmd.buttons |= BUTTON_ATTACKLEFT;
}
} else {
m_botCmd.buttons &= ~BUTTON_ATTACKLEFT;
}
} 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);
}
m_iAttackTime = level.inttime + 1000;
2023-11-13 20:33:06 +01:00
} else {
m_botCmd.buttons &= ~(BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT);
fMinDistanceSquared = 0;
}
m_vOldEnemyPos = m_vLastEnemyPos;
m_vLastEnemyPos = m_pEnemy->centroid;
AimAt(m_vLastEnemyPos + Vector(G_CRandom(8), G_CRandom(8), G_CRandom(8)));
if (bNoMove) {
return;
}
2023-11-13 20:33:06 +01:00
if ((!MoveToBestAttractivePoint(5) && !IsMoving()) || (m_vOldEnemyPos != m_vLastEnemyPos && !MoveDone())) {
if (!bMelee) {
if ((origin - m_vLastEnemyPos).lengthSquared() < fMinDistanceSquared) {
Vector vDir = origin - m_vLastEnemyPos;
VectorNormalizeFast(vDir);
2024-10-05 13:43:09 +02:00
AvoidPath(m_vLastEnemyPos, fMinDistance, Vector(orientation[1]) * 512);
2023-11-13 20:33:06 +01:00
} else {
MoveNear(m_vLastEnemyPos, fMinDistance);
}
} else {
MoveTo(m_vLastEnemyPos);
}
}
2024-10-06 01:13:12 +02:00
if (IsMoving()) {
m_iAttackTime = level.inttime + 1000;
}
2016-03-27 11:49:47 +02:00
}
/*
====================
Grenade state
Avoid any grenades
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::InitState_Grenade(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
func->CheckCondition = &PlayerBot::CheckCondition_Grenade;
func->ThinkState = &PlayerBot::State_Grenade;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
bool PlayerBot::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
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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
====================
*/
2023-11-13 20:33:06 +01:00
void PlayerBot::InitState_Weapon(botfunc_t *func)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
func->CheckCondition = &PlayerBot::CheckCondition_Weapon;
func->BeginState = &PlayerBot::State_BeginWeapon;
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
bool PlayerBot::CheckCondition_Weapon(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
return GetActiveWeapon(WEAPON_MAIN) != BestWeapon(NULL, false, WEAPON_CLASS_THROWABLE);
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::State_BeginWeapon(void)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Weapon *weap = 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
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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
2023-11-13 20:33:06 +01:00
Player::Spawned();
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::Killed(Event *ev)
2016-03-27 11:49:47 +02:00
{
Entity* attacker;
2023-11-13 20:33:06 +01:00
Player::Killed(ev);
// 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");
ProcessEvent(event);
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::GotKill(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Player::GotKill(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)));
}
ProcessEvent(event);
m_iNextTauntTime = level.inttime + 5000;
}
2016-03-27 11:49:47 +02:00
}
2023-11-13 20:33:06 +01:00
void PlayerBot::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
}