BotLib: lot of work done on recognizing weapon types, making proper use of
Weapons_IsEmpty() checks - more fixes for pathfinding; the ability to set walk and movespeed externally via method overrides; several bugfixes in relation to firing (and getting stuck on pressing other keys.
This commit is contained in:
parent
f1f12623d1
commit
d885e2e643
5 changed files with 197 additions and 50 deletions
|
@ -14,6 +14,15 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/* for AI identification purposes */
|
||||
typedef enum
|
||||
{
|
||||
WPNTYPE_INVALID, /* no logic */
|
||||
WPNTYPE_RANGED, /* will want to keep their distance mostly */
|
||||
WPNTYPE_THROW, /* has to keep some distance, but not too far */
|
||||
WPNTYPE_CLOSE /* have to get really close */
|
||||
} weapontype_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
string name;
|
||||
|
@ -22,6 +31,7 @@ typedef struct
|
|||
int slot_pos;
|
||||
int allow_drop;
|
||||
int weight; /* required for bestweapon */
|
||||
weapontype_t(void) type; /* required for bot-AI */
|
||||
|
||||
void(void) draw;
|
||||
void(void) holster;
|
||||
|
@ -65,6 +75,7 @@ void Weapons_UpdateAmmo(base_player, int, int, int);
|
|||
int Weapons_GetAnimation(void);
|
||||
void Weapons_EnableModel(void);
|
||||
void Weapons_DisableModel(void);
|
||||
weapontype_t Weapons_GetType(player, int);
|
||||
|
||||
void Weapons_SetLeftModel(string);
|
||||
void Weapons_SetRightModel(string);
|
||||
|
|
|
@ -283,14 +283,30 @@ Weapons_PreDraw(int thirdperson)
|
|||
int
|
||||
Weapons_IsEmpty(player pl, int w)
|
||||
{
|
||||
int i = pl.activeweapon;
|
||||
int r = 0;
|
||||
|
||||
entity oself = self;
|
||||
self = pl;
|
||||
|
||||
if (g_weapons[i].isempty != __NULL__)
|
||||
r = g_weapons[i].isempty();
|
||||
if (g_weapons[w].isempty != __NULL__)
|
||||
r = g_weapons[w].isempty();
|
||||
|
||||
self = oself;
|
||||
|
||||
return (r);
|
||||
}
|
||||
|
||||
|
||||
weapontype_t
|
||||
Weapons_GetType(player pl, int w)
|
||||
{
|
||||
weapontype_t r = WPNTYPE_INVALID;
|
||||
|
||||
entity oself = self;
|
||||
self = pl;
|
||||
|
||||
if (g_weapons[w].type != __NULL__)
|
||||
r = g_weapons[w].type();
|
||||
|
||||
self = oself;
|
||||
|
||||
|
|
|
@ -45,6 +45,10 @@ class bot:player
|
|||
/* visual */
|
||||
float m_flSeeTime;
|
||||
|
||||
/* cache, these are just here so we won't have to calc them often */
|
||||
float m_flEnemyDist;
|
||||
weapontype_t m_wtWeaponType;
|
||||
|
||||
void(void) bot;
|
||||
|
||||
virtual void(string) ChatSay;
|
||||
|
@ -61,6 +65,9 @@ class bot:player
|
|||
virtual void(void) PreFrame;
|
||||
virtual void(void) PostFrame;
|
||||
virtual void(void) UseButton;
|
||||
virtual void(entity) SetEnemy;
|
||||
virtual float(void) GetRunSpeed;
|
||||
virtual float(void) GetWalkSpeed;
|
||||
};
|
||||
|
||||
entity Bot_AddQuick(void);
|
||||
|
|
|
@ -19,6 +19,29 @@
|
|||
|
||||
void SV_SendChat(entity sender, string msg, entity eEnt, float fType);
|
||||
|
||||
float
|
||||
bot::GetWalkSpeed(void)
|
||||
{
|
||||
return 120;
|
||||
}
|
||||
|
||||
float
|
||||
bot::GetRunSpeed(void)
|
||||
{
|
||||
return 240;
|
||||
}
|
||||
|
||||
void
|
||||
bot::SetEnemy(entity en)
|
||||
{
|
||||
m_eTarget = en;
|
||||
|
||||
if (m_eTarget)
|
||||
m_flEnemyDist = vlen(origin - m_eTarget.origin);
|
||||
else
|
||||
m_flEnemyDist = -1;
|
||||
}
|
||||
|
||||
void
|
||||
bot::ChatSay(string msg)
|
||||
{
|
||||
|
@ -44,8 +67,22 @@ bot::Pain(void)
|
|||
player::Pain();
|
||||
|
||||
if (rules.IsTeamPlay()) {
|
||||
if (g_dmg_eAttacker.flags & FL_CLIENT && g_dmg_eAttacker.team == team)
|
||||
if (g_dmg_eAttacker.flags & FL_CLIENT && g_dmg_eAttacker.team == team) {
|
||||
ChatSayTeam("Stop shooting me!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* make this pain our new enemy! */
|
||||
if (g_dmg_eAttacker && g_dmg_eAttacker != this) {
|
||||
float enemydist = vlen(origin - m_eTarget.origin);
|
||||
float newdist = vlen(origin - g_dmg_eAttacker.origin);
|
||||
|
||||
if (m_eTarget)
|
||||
if (newdist < enemydist)
|
||||
SetEnemy(g_dmg_eAttacker);
|
||||
else
|
||||
SetEnemy(g_dmg_eAttacker);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,27 +101,48 @@ bot::RouteClear(void)
|
|||
void
|
||||
bot::WeaponThink(void)
|
||||
{
|
||||
/* clip empty */
|
||||
if (a_ammo1 == 0) {
|
||||
/* still got ammo left, reload! */
|
||||
if (a_ammo2 != 0) {
|
||||
input_buttons &= ~INPUT_BUTTON0;
|
||||
input_buttons |= INPUT_BUTTON4;
|
||||
} else {
|
||||
Weapons_SwitchBest(this);
|
||||
}
|
||||
int r = Weapons_IsEmpty(this, activeweapon);
|
||||
|
||||
/* clip empty, but the whole weapon isn't */
|
||||
if (r == 0 && a_ammo1 <= 0) {
|
||||
input_buttons &= ~INPUT_BUTTON0;
|
||||
input_buttons |= INPUT_BUTTON4;
|
||||
} else if (r == 1) {
|
||||
Weapons_SwitchBest(this, activeweapon);
|
||||
}
|
||||
|
||||
m_wtWeaponType = Weapons_GetType(this, activeweapon);
|
||||
}
|
||||
|
||||
void
|
||||
bot::WeaponAttack(void)
|
||||
{
|
||||
if (m_flAttackTime < time) {
|
||||
int should_attack = 0;
|
||||
|
||||
if (m_wtWeaponType == WPNTYPE_RANGED) {
|
||||
if (m_flEnemyDist <= 1024)
|
||||
should_attack = 1;
|
||||
} else if (m_wtWeaponType == WPNTYPE_THROW) {
|
||||
if (m_flEnemyDist <= 512)
|
||||
should_attack = 1;
|
||||
} else if (m_wtWeaponType == WPNTYPE_CLOSE) {
|
||||
if (m_flEnemyDist <= 128)
|
||||
should_attack = 1;
|
||||
} else {
|
||||
should_attack = 1;
|
||||
}
|
||||
|
||||
if (should_attack && m_flAttackTime < time) {
|
||||
if (!m_iAttackMode) {
|
||||
input_buttons |= INPUT_BUTTON0; // Attack
|
||||
input_buttons |= INPUT_BUTTON0;
|
||||
}
|
||||
m_flAttackTime = time + 0.1f;
|
||||
} else {
|
||||
input_buttons &= ~INPUT_BUTTON0;
|
||||
input_buttons &= ~INPUT_BUTTON4;
|
||||
input_buttons &= ~INPUT_BUTTON5;
|
||||
}
|
||||
|
||||
m_iAttackMode = 1 - m_iAttackMode;
|
||||
}
|
||||
|
||||
|
@ -92,9 +150,11 @@ 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();
|
||||
if (m_eTarget) {
|
||||
if (m_eTarget.health <= 0) {
|
||||
SetEnemy(__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);
|
||||
|
@ -157,8 +217,8 @@ bot::SeeThink(void)
|
|||
{
|
||||
CGameRules rules = (CGameRules)g_grMode;
|
||||
|
||||
if (m_eTarget)
|
||||
return;
|
||||
/*if (m_eTarget)
|
||||
return; */
|
||||
|
||||
if (m_flSeeTime > time)
|
||||
return;
|
||||
|
@ -171,8 +231,11 @@ bot::SeeThink(void)
|
|||
for (entity w = world; (w = findfloat(w, ::takedamage, DAMAGE_YES));) {
|
||||
float flDot;
|
||||
|
||||
/* is w a client? */
|
||||
if (!(w.flags & FL_CLIENT))
|
||||
continue;
|
||||
|
||||
/* is w alive? */
|
||||
if (w.health <= 0)
|
||||
continue;
|
||||
|
||||
|
@ -189,11 +252,12 @@ bot::SeeThink(void)
|
|||
if (flDot < 90/180)
|
||||
continue;
|
||||
|
||||
other = world;
|
||||
traceline(origin, w.origin, MOVE_OTHERONLY, this);
|
||||
/* is it even physically able to be seen? */
|
||||
traceline(origin, w.origin, MOVE_NORMAL, this);
|
||||
|
||||
if (trace_fraction == 1.0f) {
|
||||
m_eTarget = w;
|
||||
/* break out if at all a valid trace */
|
||||
if (trace_ent == w) {
|
||||
SetEnemy(w);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +277,7 @@ bot::CheckRoute(void)
|
|||
/* level out position/node stuff */
|
||||
if (m_iCurNode < 0) {
|
||||
evenpos = m_vecLastNode - origin;
|
||||
rad = 64;
|
||||
rad = 128; /* destination is not a node, therefore has a virtual radius */
|
||||
} else {
|
||||
evenpos = m_pRoute[m_iCurNode].m_vecDest - origin;
|
||||
rad = m_pRoute[m_iCurNode].m_flRadius;
|
||||
|
@ -221,15 +285,14 @@ bot::CheckRoute(void)
|
|||
|
||||
flDist = floor(vlen(evenpos));
|
||||
|
||||
/* we're inside the radius */
|
||||
if (flDist <= rad) {
|
||||
dprint(sprintf("^2bot::^3CheckRoute^7: " \
|
||||
"%s reached node\n", this.targetname));
|
||||
m_iCurNode--;
|
||||
|
||||
/* if we're inside an actual node (not a virtual one */
|
||||
if (m_iCurNode >= 0) {
|
||||
if (Route_GetNodeFlags(&m_pRoute[m_iCurNode]))
|
||||
dprint(sprintf("NODE FLAGS: %i\n", Route_GetNodeFlags(&m_pRoute[m_iCurNode])));
|
||||
|
||||
/* if a node is flagged as jumpy, jump! */
|
||||
if (Route_GetNodeFlags(&m_pRoute[m_iCurNode]) & LF_JUMP)
|
||||
input_buttons |= INPUT_BUTTON2;
|
||||
|
@ -239,7 +302,6 @@ bot::CheckRoute(void)
|
|||
UseButton();
|
||||
}
|
||||
|
||||
#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) {
|
||||
|
@ -252,11 +314,10 @@ bot::CheckRoute(void)
|
|||
m_iCurNode = -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
traceline(origin + view_ofs, m_pRoute[m_iCurNode].m_vecDest, MOVE_NORMAL, this);
|
||||
} else { /* we're not near the node quite yet */
|
||||
traceline(origin, m_pRoute[m_iCurNode].m_vecDest, MOVE_NORMAL, this);
|
||||
|
||||
/* we can't trace against our next waypoint... that should never happen */
|
||||
/* we can't trace against our next node... that should never happen */
|
||||
if (trace_fraction != 1.0f) {
|
||||
m_flNodeGiveup += frametime;
|
||||
} else {
|
||||
|
@ -307,7 +368,7 @@ bot::RunAI(void)
|
|||
if (health <= 0) {
|
||||
RouteClear();
|
||||
WeaponAttack();
|
||||
m_eTarget = __NULL__;
|
||||
SetEnemy(__NULL__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -328,34 +389,51 @@ bot::RunAI(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* prepare our weapons for firing */
|
||||
WeaponThink();
|
||||
|
||||
/* see if enemies are nearby */
|
||||
SeeThink();
|
||||
|
||||
/* calculate enemy distance _once_ */
|
||||
if (m_eTarget) {
|
||||
m_flEnemyDist = vlen(origin - m_eTarget.origin);
|
||||
} else {
|
||||
m_flEnemyDist = -1;
|
||||
}
|
||||
|
||||
enemyvisible = FALSE;
|
||||
enemydistant = FALSE;
|
||||
|
||||
if (m_eTarget != __NULL__) {
|
||||
traceline(origin + view_ofs, m_eTarget.origin, TRUE, this);
|
||||
|
||||
/* is it 'visible'? can we 'see' them? */
|
||||
enemyvisible = (trace_ent == m_eTarget || trace_fraction == 1.0f);
|
||||
|
||||
if (vlen(trace_endpos - origin) > 1024) {
|
||||
/* if they're distant, remember that */
|
||||
if (m_flEnemyDist > 1024) {
|
||||
enemydistant = TRUE;
|
||||
}
|
||||
|
||||
/* attack if visible! */
|
||||
if (enemyvisible) {
|
||||
WeaponAttack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BrainThink(enemyvisible, enemydistant);
|
||||
CheckRoute();
|
||||
|
||||
aimpos = [0,0,0];
|
||||
|
||||
/* if we've got a path (we always should) move the bot */
|
||||
if (m_iNodes) {
|
||||
float goroute;
|
||||
vector vecNewAngles;
|
||||
vector vecDirection;
|
||||
|
||||
/* no enemy, or it isn't visible... then stare at nodes! */
|
||||
if (!m_eTarget || !enemyvisible) {
|
||||
/* aim at the next node */
|
||||
if (m_iCurNode == BOTROUTE_DESTINATION)
|
||||
|
@ -371,7 +449,7 @@ bot::RunAI(void)
|
|||
aimpos = m_eTarget.origin;
|
||||
}
|
||||
|
||||
/* aim ahead */
|
||||
/* aim ahead if aimpos is somehow invalid */
|
||||
if (aimpos == [0,0,0]) {
|
||||
makevectors(angles);
|
||||
aimpos = origin + v_forward * 128;
|
||||
|
@ -406,14 +484,36 @@ bot::RunAI(void)
|
|||
angles[2] = Math_FixDelta(v_angle[2]);
|
||||
input_angles = v_angle;
|
||||
|
||||
/* always look ahead as the default */
|
||||
aimpos = origin + (v_forward * 32);
|
||||
float shouldwalk = 0;
|
||||
|
||||
/* 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_wtWeaponType == WPNTYPE_RANGED) {
|
||||
/* walk _directly_ towards the enemy if we're less than 512 units away */
|
||||
if (m_eTarget && enemyvisible && m_flEnemyDist < 1024) {
|
||||
aimpos = m_eTarget.origin;
|
||||
} else {
|
||||
goroute = 1;
|
||||
}
|
||||
|
||||
/* we should probably walk we're distant enough to be more accurate */
|
||||
if ((m_eTarget && enemyvisible && m_flEnemyDist > 512))
|
||||
shouldwalk = 1;
|
||||
} else if (m_wtWeaponType == WPNTYPE_CLOSE) {
|
||||
/* move directly towards the enemy if we're 256 units away */
|
||||
if (m_eTarget && enemyvisible && m_flEnemyDist < 256) {
|
||||
/* we are far away, inch closer */
|
||||
aimpos = m_eTarget.origin;
|
||||
} else {
|
||||
goroute = 1;
|
||||
}
|
||||
} else if (m_wtWeaponType == WPNTYPE_THROW) {
|
||||
if ((m_eTarget && enemyvisible && !enemydistant) && m_flEnemyDist < 512) {
|
||||
aimpos = m_eTarget.origin;
|
||||
} else {
|
||||
goroute = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (goroute) {
|
||||
if (m_iCurNode <= BOTROUTE_DESTINATION)
|
||||
aimpos = m_vecLastNode;
|
||||
else
|
||||
|
@ -421,20 +521,22 @@ bot::RunAI(void)
|
|||
}
|
||||
|
||||
/* now we'll set the movevalues relative to the input_angle */
|
||||
if ((m_iCurNode >= 0 && Route_GetNodeFlags(&m_pRoute[m_iCurNode]) & LF_WALK) || m_eTarget && enemyvisible && vlen(aimpos-origin) < 512)
|
||||
vecDirection = normalize(aimpos - origin) * 120;
|
||||
if ((m_iCurNode >= 0 && Route_GetNodeFlags(&m_pRoute[m_iCurNode]) & LF_WALK) || shouldwalk)
|
||||
vecDirection = normalize(aimpos - origin) * GetWalkSpeed();
|
||||
else
|
||||
vecDirection = normalize(aimpos - origin) * 240;
|
||||
vecDirection = normalize(aimpos - origin) * GetRunSpeed();
|
||||
|
||||
makevectors(input_angles);
|
||||
input_movevalues = [v_forward * vecDirection, v_right * vecDirection, v_up * vecDirection];
|
||||
input_movevalues[2] = 0;
|
||||
|
||||
#if 0
|
||||
/* duck and stand still when our enemy is far away */
|
||||
if (m_eTarget && enemyvisible && vlen(aimpos-origin) > 512) {
|
||||
input_buttons |= INPUT_BUTTON8;
|
||||
input_movevalues = [0,0,0];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* press any buttons needed */
|
||||
|
|
|
@ -83,19 +83,30 @@ Switch to the 'best' weapon according to our weight system.
|
|||
=================
|
||||
*/
|
||||
void
|
||||
Weapons_SwitchBest(base_player pl)
|
||||
Weapons_SwitchBest(base_player pl, optional float skip = 0)
|
||||
{
|
||||
entity oldself = self;
|
||||
self = pl;
|
||||
float old = pl.activeweapon;
|
||||
|
||||
/* loop through n weapon count */
|
||||
for (float i = g_weapons.length - 1; i >= 1 ; i--) {
|
||||
int x = g_weapon_weights[i];
|
||||
if ((pl.g_items & g_weapons[x].id) && (!Weapons_IsEmpty((player)pl, x))) {
|
||||
int x = g_weapon_weights[i]; /* map i to weapon table weight */
|
||||
|
||||
/* skip the weapon inside skip */
|
||||
if (x == (int)skip)
|
||||
continue;
|
||||
|
||||
/* do we have the weapon and is not not empty? */
|
||||
if ((pl.g_items & g_weapons[x].id) && (Weapons_IsEmpty((player)pl, x) == 0)) {
|
||||
pl.activeweapon = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (old == pl.activeweapon)
|
||||
return;
|
||||
|
||||
Weapons_Draw();
|
||||
self = oldself;
|
||||
pl.gflags |= GF_SEMI_TOGGLED;
|
||||
|
|
Loading…
Reference in a new issue