nuclide/src/botlib/bot.cpp

369 lines
8.7 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define BOTROUTE_DESTINATION -1
#define BOTROUTE_END -2
void
bot::Pain(void)
{
player::Pain();
/* might as well target our attacker */
m_eTarget = g_dmg_eAttacker;
}
void
bot::RouteClear(void)
{
if (!m_iNodes)
return;
m_iCurNode = BOTROUTE_END;
m_iNodes = 0;
m_flNodeGiveup = 0.0f;
memfree(m_pRoute);
}
void
bot::WeaponThink(void)
{
/* clip empty */
if (a_ammo1 == 0) {
/* still got ammo left */
if (a_ammo2 != 0) {
input_buttons &= ~INPUT_BUTTON0;
input_buttons |= INPUT_BUTTON4;
} else {
Weapons_SwitchBest(this);
}
}
}
void
bot::WeaponAttack(void)
{
if (m_flAttackTime < time) {
if (!m_iAttackMode) {
input_buttons |= INPUT_BUTTON0; // Attack
}
m_flAttackTime = time + 0.1f;
}
m_iAttackMode = 1 - m_iAttackMode;
}
void
bot::BrainThink(int enemyvisible, int enemydistant)
{
/* we had a target and it's now dead. now what? */
if (m_eTarget && m_eTarget.health <= 0) {
m_eTarget = __NULL__;
RouteClear();
} else if (m_eTarget && enemyvisible && enemydistant) {
/* we can see the player, but are too far away, plot a route */
route_calculate(this, m_eTarget.origin, 0, Bot_RouteCB);
}
}
void
bot::SeeThink(void)
{
if (m_eTarget)
return;
if (m_flSeeTime > time)
return;
if (autocvar_bot_pacifist)
return;
m_flSeeTime = time + 0.25f;
for (entity w = world; (w = findfloat(w, ::takedamage, DAMAGE_YES));) {
float flDot;
if (!(w.flags & FL_CLIENT))
continue;
if (w.health <= 0)
continue;
/* ain't go hurt our brothers and sisters */
if (Rules_IsTeamPlay() == TRUE)
if (team == w.team)
continue;
/* first, is the potential enemy in our field of view? */
makevectors(v_angle);
vector v = normalize(w.origin - origin);
flDot = v * v_forward;
if (flDot < 90/180)
continue;
other = world;
traceline(origin, w.origin, MOVE_OTHERONLY, this);
if (trace_fraction == 1.0f) {
m_eTarget = w;
return;
}
}
}
void
bot::CheckRoute(void)
{
float flDist;
vector evenpos;
float rad;
if (!m_iNodes) {
return;
}
/* level out position/node stuff */
if (m_iCurNode < 0) {
evenpos = m_vecLastNode - origin;
rad = 64;
} else {
evenpos = m_pRoute[m_iCurNode].m_vecDest - origin;
rad = m_pRoute[m_iCurNode].m_flRadius;
}
flDist = floor(vlen(evenpos));
if (flDist <= rad) {
dprint(sprintf("^2CBaseMonster::^3CheckRoute^7: " \
"%s reached node\n", this.targetname));
m_iCurNode--;
if (m_iCurNode >= 0) {
if (m_pRoute[m_iCurNode].m_iFlags)
print(sprintf("NODE FLAGS: %i\n", m_pRoute[m_iCurNode].m_iFlags));
/* if a node is flagged as jumpy, jump! */
if (m_pRoute[m_iCurNode].m_iFlags & LF_JUMP)
input_buttons |= INPUT_BUTTON2;
}
#if 0
/* we've still traveling and from this node we may be able to walk
* directly to our end-destination */
if (m_iCurNode > -1) {
tracebox(origin, mins, maxs, m_vecLastNode, MOVE_NORMAL, this);
/* can we walk directly to our target destination? */
if (trace_fraction == 1.0) {
dprint("^2CBaseMonster::^3CheckRoute^7: " \
"Walking directly to last node\n");
m_iCurNode = -1;
}
}
#endif
} else {
traceline(origin + view_ofs, m_pRoute[m_iCurNode].m_vecDest, MOVE_NORMAL, this);
/* we can't trace against our next waypoint... that should never happen */
if (trace_fraction != 1.0f) {
m_flNodeGiveup += frametime;
} else {
/* if we're literally stuck in a corner aiming at something we should
* not, also give up */
if (flDist == m_flLastDist) {
m_flNodeGiveup += frametime;
} else {
m_flNodeGiveup = bound(0, m_flNodeGiveup - frametime, 1.0);
}
}
}
m_flLastDist = flDist;
/* after one second, also give up the route */
if (m_flNodeGiveup >= 1.0f || m_iCurNode <= BOTROUTE_END) {
RouteClear();
} else if (m_flNodeGiveup >= 0.5f) {
/* attempt a jump after half a second */
input_buttons |= INPUT_BUTTON2;
} else {
/* entire way-link needs to be crouched. that's the law of the land */
if (m_pRoute[m_iCurNode].m_iFlags & LF_CROUCH)
input_buttons |= INPUT_BUTTON8;
}
}
void
bot::RunAI(void)
{
vector aimdir, aimpos;
int enemyvisible, enemydistant;
float flLerp;
/* reset input frame */
input_buttons = 0;
input_movevalues = [0,0,0];
input_angles = [0,0,0];
/* attempt to respawn when dead */
if (health <= 0) {
RouteClear();
WeaponAttack();
m_eTarget = __NULL__;
return;
}
/* freeze the bot */
if (autocvar_bot_wait)
return;
/* create our first route */
if (!m_iNodes && autocvar_bot_aimless == 0) {
route_calculate(this, Route_SelectDestination(this), 0, Bot_RouteCB);
dprint(sprintf("bot::RunAI: %s is calculating first bot route\n",
this.netname));
/* our route probably has not been processed yet */
if (!m_iNodes) {
return;
}
}
WeaponThink();
SeeThink();
enemyvisible = FALSE;
enemydistant = FALSE;
if (m_eTarget != __NULL__) {
traceline(origin + view_ofs, m_eTarget.origin, TRUE, this);
enemyvisible = (trace_ent == m_eTarget || trace_fraction == 1.0f);
if (vlen(trace_endpos - origin) > 1024) {
enemydistant = TRUE;
}
if (enemyvisible) {
WeaponAttack();
}
}
BrainThink(enemyvisible, enemydistant);
CheckRoute();
if (m_iNodes) {
vector vecNewAngles;
vector vecDirection;
if (!m_eTarget || !enemyvisible) {
/* aim at the next node */
if (m_iCurNode == BOTROUTE_DESTINATION)
aimpos = m_vecLastNode;
else {
if (m_iCurNode > 0)
aimpos = m_pRoute[m_iCurNode - 1].m_vecDest;
else
aimpos = m_pRoute[m_iCurNode].m_vecDest;
}
} else {
/* aim towards the enemy */
aimpos = m_eTarget.origin;
}
/* lerping speed, faster when we've got a target */
if (m_eTarget && enemyvisible)
flLerp = bound(0.0f, frametime * 45, 1.0f);
else
flLerp = bound(0.0f, frametime * 30, 1.0f);
/* that's the old angle */
makevectors(v_angle);
vecNewAngles = v_forward;
/* aimdir = new final angle */
aimdir = vectoangles(aimpos - origin);
makevectors(aimdir);
/* slowly lerp towards the final angle */
vecNewAngles[0] = Math_Lerp(vecNewAngles[0], v_forward[0], flLerp);
vecNewAngles[1] = Math_Lerp(vecNewAngles[1], v_forward[1], flLerp);
vecNewAngles[2] = Math_Lerp(vecNewAngles[2], v_forward[2], flLerp);
/* make sure we're aiming tight */
v_angle = vectoangles(vecNewAngles);
v_angle[0] = Math_FixDelta(v_angle[0]);
v_angle[1] = Math_FixDelta(v_angle[1]);
v_angle[2] = Math_FixDelta(v_angle[2]);
angles[0] = Math_FixDelta(v_angle[0]);
angles[1] = Math_FixDelta(v_angle[1]);
angles[2] = Math_FixDelta(v_angle[2]);
input_angles = v_angle;
/* now that aiming is sorted, we need to correct the movement */
if ((m_eTarget && enemyvisible && !enemydistant) && vlen(aimpos - origin) > 256) {
/* we are far away, inch closer */
aimpos = m_eTarget.origin;
} else {
if (m_iCurNode <= BOTROUTE_DESTINATION)
aimpos = m_vecLastNode;
else
aimpos = m_pRoute[m_iCurNode].m_vecDest;
}
/* now we'll set the movevalues relative to the input_angle */
if (m_iCurNode >= 0 && m_pRoute[m_iCurNode].m_iFlags & LF_WALK)
vecDirection = normalize(aimpos - origin) * 120;
else
vecDirection = normalize(aimpos - origin) * 240;
makevectors(input_angles);
input_movevalues = [v_forward * vecDirection, v_right * vecDirection, v_up * vecDirection];
}
/* press any buttons needed */
button0 = input_buttons & INPUT_BUTTON0; // attack
button2 = input_buttons & INPUT_BUTTON2; // jump
button3 = input_buttons & INPUT_BUTTON3; // tertiary
button4 = input_buttons & INPUT_BUTTON4; // reload
button5 = input_buttons & INPUT_BUTTON5; // secondary
button6 = input_buttons & INPUT_BUTTON6; // use
button7 = input_buttons & INPUT_BUTTON7; // unused
button8 = input_buttons & INPUT_BUTTON8; // duck
movement = input_movevalues;
}
void
bot::PreFrame(void)
{
}
void
bot::PostFrame(void)
{
/* we've picked something new up */
if (m_iOldItems != g_items) {
Weapons_SwitchBest(this);
print(sprintf("%s is now using %s (%d)\n", netname, g_weapons[activeweapon].name, activeweapon));
m_iOldItems = g_items;
}
}
void
bot::bot(void)
{
classname = "player";
}