BotLib: Added basic waypointing and pathfinding system. They'll go about
their business but not do much else just yet.
This commit is contained in:
parent
545b66a786
commit
c76cdc5903
17 changed files with 866 additions and 37 deletions
|
@ -15,44 +15,192 @@
|
|||
*/
|
||||
|
||||
void
|
||||
bot::PickName(void)
|
||||
bot::CheckRoute(void)
|
||||
{
|
||||
int n = 0;
|
||||
entity pbot = world;
|
||||
for (int i = 1; (pbot = edict_num(i)); i++) {
|
||||
if (clienttype(pbot) == CLIENTTYPE_BOT) {
|
||||
n++;
|
||||
float flDist;
|
||||
vector evenpos;
|
||||
|
||||
if (!m_iNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* level out position/node stuff */
|
||||
if (m_iCurNode < 0) {
|
||||
evenpos = m_vecLastNode;
|
||||
evenpos[2] = origin[2];
|
||||
} else {
|
||||
evenpos = m_pRoute[m_iCurNode].m_vecDest;
|
||||
evenpos[2] = origin[2];
|
||||
}
|
||||
|
||||
flDist = floor(vlen(evenpos - origin));
|
||||
|
||||
if ( flDist < 16 ) {
|
||||
dprint(sprintf("^2CBaseMonster::^3CheckRoute^7: " \
|
||||
"%s reached node\n", this.targetname));
|
||||
m_iCurNode--;
|
||||
velocity = [0,0,0]; /* clamp friction */
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
} 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
forceinfokey(this, "name", sprintf("Bot %i", n));
|
||||
|
||||
m_flLastDist = flDist;
|
||||
|
||||
if ( m_flNodeGiveup >= 1.0f ) {
|
||||
dprint(sprintf("bot::CheckRoute: %s gave up route\n",
|
||||
this.netname));
|
||||
|
||||
m_iCurNode = -2;
|
||||
m_flNodeGiveup = 0.0f;
|
||||
} else if ( m_flNodeGiveup >= 0.5f ) {
|
||||
input_buttons |= INPUT_BUTTON2;
|
||||
}
|
||||
|
||||
if (m_iCurNode < -1) {
|
||||
dprint(sprintf("bot::CheckRoute: %s calculates new route\n",
|
||||
this.netname));
|
||||
|
||||
m_iNodes = 0;
|
||||
memfree( m_pRoute );
|
||||
route_calculate( this, Route_SelectDestination( this ), 0, Bot_RouteCB );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
input_buttons |= INPUT_BUTTON0;
|
||||
}
|
||||
|
||||
/* create our first route */
|
||||
if (!m_iNodes) {
|
||||
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();
|
||||
//PickEnemy();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
CheckRoute();
|
||||
|
||||
if (m_iNodes) {
|
||||
if (!m_eTarget || !enemyvisible) {
|
||||
/* aim at the next node */
|
||||
if (m_iCurNode == -1)
|
||||
aimpos = m_vecLastNode;
|
||||
else
|
||||
aimpos = m_pRoute[m_iCurNode].m_vecDest;
|
||||
} else {
|
||||
/* aim towards the enemy */
|
||||
aimpos = m_eTarget.origin;
|
||||
}
|
||||
|
||||
/* lerping speed */
|
||||
flLerp = bound(0.0f, 1.0f - (frametime * 15), 1.0f);
|
||||
|
||||
/* that's the old angle */
|
||||
makevectors(v_angle);
|
||||
vector vNewAngles = v_forward;
|
||||
|
||||
/* aimdir = final angle */
|
||||
aimdir = vectoangles(aimpos - origin);
|
||||
makevectors(aimdir);
|
||||
|
||||
/* slowly lerp towards the final angle */
|
||||
vNewAngles[0] = Math_Lerp(vNewAngles[0], v_forward[0], flLerp);
|
||||
vNewAngles[1] = Math_Lerp(vNewAngles[1], v_forward[1], flLerp);
|
||||
vNewAngles[2] = Math_Lerp(vNewAngles[2], v_forward[2], flLerp);
|
||||
|
||||
/* make sure we're aiming tight */
|
||||
v_angle = vectoangles(vNewAngles);
|
||||
v_angle[0] = Math_FixDelta(v_angle[0]);
|
||||
v_angle[1] = Math_FixDelta(v_angle[1]);
|
||||
v_angle[2] = Math_FixDelta(v_angle[2]);
|
||||
input_angles = v_angle;
|
||||
angles[0] = Math_FixDelta(v_angle[0]);
|
||||
angles[1] = Math_FixDelta(v_angle[1]);
|
||||
angles[2] = Math_FixDelta(v_angle[2]);
|
||||
|
||||
/* now we'll set the movevalues relative to the input_angle */
|
||||
vector direction = normalize(aimpos - origin) * 240;
|
||||
makevectors(input_angles);
|
||||
input_movevalues = [v_forward * direction, v_right * direction, v_up * direction];
|
||||
}
|
||||
|
||||
/* 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::bot(void)
|
||||
{
|
||||
PickName();
|
||||
ClientConnect();
|
||||
PutClientInServer();
|
||||
}
|
||||
|
||||
entity
|
||||
Bot_AddQuick(void)
|
||||
{
|
||||
entity newbot;
|
||||
entity oself;
|
||||
|
||||
oself = self;
|
||||
self = world;
|
||||
self = spawnclient();
|
||||
|
||||
if (!self) {
|
||||
print("^1Bot_AddQuick^7: Can't add bot. Server is full\n");
|
||||
self = oself;
|
||||
return world;
|
||||
}
|
||||
|
||||
spawnfunc_bot();
|
||||
newbot = self;
|
||||
self = oself;
|
||||
return newbot;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#define COST_INFINITE 99999
|
||||
|
||||
enum
|
||||
{
|
||||
BOT_PERSONALITY_NORMAL,
|
||||
|
@ -23,8 +25,22 @@ enum
|
|||
|
||||
class bot:player
|
||||
{
|
||||
/* routing */
|
||||
int m_iNodes;
|
||||
int m_iCurNode;
|
||||
nodeslist_t *m_pRoute;
|
||||
float m_flNodeGiveup;
|
||||
float m_flLastDist;
|
||||
entity m_eDestination;
|
||||
vector m_vecLastNode;
|
||||
|
||||
/* combat */
|
||||
entity m_eTarget;
|
||||
|
||||
void(void) bot;
|
||||
virtual void(void) PickName;
|
||||
|
||||
virtual void(void) RunAI;
|
||||
virtual void(void) CheckRoute;
|
||||
};
|
||||
|
||||
entity Bot_AddQuick(void);
|
||||
|
|
23
src/botlib/botinfo.h
Normal file
23
src/botlib/botinfo.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
enum
|
||||
{
|
||||
BOTINFO_HEALTH,
|
||||
BOTINFO_ARMOR,
|
||||
BOTINFO_SPAWNPOINT,
|
||||
BOTINFO_END
|
||||
};
|
58
src/botlib/cmd.c
Normal file
58
src/botlib/cmd.c
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
void
|
||||
Bot_PickName(entity target)
|
||||
{
|
||||
int n = 0;
|
||||
entity pbot = world;
|
||||
for (int i = 1; (pbot = edict_num(i)); i++) {
|
||||
if (clienttype(pbot) == CLIENTTYPE_BOT) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
forceinfokey(target, "name", sprintf("Bot %i", n));
|
||||
}
|
||||
|
||||
entity
|
||||
Bot_AddQuick(void)
|
||||
{
|
||||
/* we've got no nodes, so generate some fake waypoints */
|
||||
if (!g_nodes_present) {
|
||||
return world;
|
||||
}
|
||||
|
||||
entity newbot;
|
||||
entity oself;
|
||||
|
||||
oself = self;
|
||||
self = world;
|
||||
self = spawnclient();
|
||||
|
||||
if (!self) {
|
||||
print("^1Bot_AddQuick^7: Can't add bot. Server is full\n");
|
||||
self = oself;
|
||||
return world;
|
||||
}
|
||||
|
||||
Bot_PickName(self);
|
||||
ClientConnect();
|
||||
PutClientInServer();
|
||||
|
||||
newbot = self;
|
||||
self = oself;
|
||||
return newbot;
|
||||
}
|
19
src/botlib/defs.h
Normal file
19
src/botlib/defs.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "bot.h"
|
||||
#include "botinfo.h"
|
||||
#include "route.h"
|
|
@ -1,6 +1,9 @@
|
|||
#define BOT_INCLUDED
|
||||
|
||||
#includelist
|
||||
../botlib/bot.h
|
||||
../botlib/bot.cpp
|
||||
defs.h
|
||||
bot.cpp
|
||||
route.c
|
||||
way.c
|
||||
cmd.c
|
||||
#endlist
|
||||
|
|
115
src/botlib/route.c
Normal file
115
src/botlib/route.c
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Begin calculating a route.
|
||||
* The callback function will be called once the route has finished being calculated.
|
||||
* The route must be memfreed once it is no longer needed.
|
||||
* The route must be followed in reverse order (ie: the first node that must be reached
|
||||
* is at index numnodes-1). If no route is available then the callback will be called with no nodes.
|
||||
*/
|
||||
|
||||
int Route_RoundDistance( float flDist )
|
||||
{
|
||||
float r = fabs( flDist ) % 2;
|
||||
if ( r == 0 ) {
|
||||
return flDist;
|
||||
}
|
||||
|
||||
if ( flDist < 0 ) {
|
||||
return -( fabs( flDist ) - r );
|
||||
} else {
|
||||
return flDist + 2 - r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
Spawn_SelectRandom
|
||||
================
|
||||
*/
|
||||
entity Route_SelectRandom ( string sEntname )
|
||||
{
|
||||
static entity eLastSpot;
|
||||
eLastSpot = find( eLastSpot, classname, sEntname );
|
||||
return eLastSpot;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Route_SelectRandomSpot
|
||||
================
|
||||
*/
|
||||
entity Route_SelectRandomSpot(void)
|
||||
{
|
||||
static entity eLastSpot;
|
||||
eLastSpot = findfloat( eLastSpot, ::botinfo, BOTINFO_SPAWNPOINT );
|
||||
return eLastSpot;
|
||||
}
|
||||
|
||||
void Bot_RouteCB( entity ent, vector dest, int numnodes, nodeslist_t *nodelist )
|
||||
{
|
||||
bot b = (bot)ent;
|
||||
b.m_iNodes = numnodes;
|
||||
b.m_iCurNode = numnodes - 1;
|
||||
b.m_pRoute = nodelist;
|
||||
b.m_vecLastNode = dest;
|
||||
|
||||
//dprint( "Bot: Route calculated.\n" );
|
||||
//dprint( sprintf( "Bot: # of nodes: %i\n", bot.m_iNodes ) );
|
||||
//dprint( sprintf( "Bot: # current node: %i\n", bot.m_iCurNode ) );
|
||||
//dprint( sprintf( "Bot: # endpos: %v\n", dest ) );
|
||||
}
|
||||
|
||||
vector Route_SelectDestination( bot target )
|
||||
{
|
||||
entity dest = world;
|
||||
|
||||
// Need health!
|
||||
if ( target.health < 50 ) {
|
||||
entity temp;
|
||||
int bestrange = COST_INFINITE;
|
||||
int range;
|
||||
for ( temp = world; ( temp = findfloat( temp, ::botinfo, BOTINFO_HEALTH ) ); ) {
|
||||
range = vlen( temp.origin - target.origin );
|
||||
if ( ( range < bestrange ) && ( temp.solid = SOLID_TRIGGER ) ) {
|
||||
bestrange = range;
|
||||
dest = temp;
|
||||
}
|
||||
}
|
||||
|
||||
if ( dest ) {
|
||||
//dprint( "Route: Going for health!" );
|
||||
return dest.origin + '0 0 32';
|
||||
}
|
||||
}
|
||||
|
||||
dest = Route_SelectRandomSpot();
|
||||
target.m_eDestination = dest;
|
||||
return dest.origin;
|
||||
}
|
||||
|
||||
int Route_CanCrouch(bot target, vector endpos)
|
||||
{
|
||||
traceline(target.origin + [0,0,-18], endpos, FALSE, target);
|
||||
|
||||
if ( trace_fraction != 1.0f ) {
|
||||
return FALSE;
|
||||
} else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
20
src/botlib/route.h
Normal file
20
src/botlib/route.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
int Route_RoundDistance( float flDist );
|
||||
vector Route_SelectDestination( bot target );
|
||||
int Route_CanCrouch(bot target, vector endpos);
|
||||
void Bot_RouteCB( entity ent, vector dest, int numnodes, nodeslist_t *nodelist );
|
332
src/botlib/way.c
Normal file
332
src/botlib/way.c
Normal file
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* 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 COST_INFINITE 99999
|
||||
|
||||
enumflags
|
||||
{
|
||||
WP_JUMP, /* also implies that the bot must first go behind the wp... */
|
||||
WP_CLIMB,
|
||||
WP_CROUCH,
|
||||
WP_USE
|
||||
};
|
||||
|
||||
typedef struct waypoint_s
|
||||
{
|
||||
vector vecOrigin;
|
||||
float flRadius; /* used for picking the closest waypoint. aka proximity weight. also relaxes routes inside the area. */
|
||||
struct wpneighbour_s
|
||||
{
|
||||
int node;
|
||||
float linkcost;
|
||||
int iFlags;
|
||||
} *neighbour;
|
||||
int iNeighbours;
|
||||
} waypoint_t;
|
||||
|
||||
static waypoint_t *g_pWaypoints;
|
||||
static int g_iWaypoints;
|
||||
|
||||
static void
|
||||
Way_WipeWaypoints(void)
|
||||
{
|
||||
for (int i = 0; i < g_iWaypoints; i++) {
|
||||
memfree(g_pWaypoints[i].neighbour);
|
||||
}
|
||||
|
||||
memfree(g_pWaypoints);
|
||||
g_iWaypoints = 0;
|
||||
}
|
||||
|
||||
void
|
||||
Way_DumpWaypoints(string filename)
|
||||
{
|
||||
filestream file = fopen(filename, FILE_WRITE);
|
||||
|
||||
if (file < 0) {
|
||||
print("RT_DumpWaypoints: unable to open ", filename, "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
fputs(file, sprintf("%i\n", g_iWaypoints));
|
||||
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
fputs(file, sprintf("%v %f %i\n", g_pWaypoints[i].vecOrigin, g_pWaypoints[i].flRadius, g_pWaypoints[i].iNeighbours));
|
||||
|
||||
for(int j = 0i; j < g_pWaypoints[i].iNeighbours; j++) {
|
||||
fputs(file, sprintf(" %i %f %x\n", g_pWaypoints[i].neighbour[j].node, g_pWaypoints[i].neighbour[j].linkcost, (float)g_pWaypoints[i].neighbour[j].iFlags));
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void
|
||||
Way_ReadWaypoints(string strFile)
|
||||
{
|
||||
float file = fopen(strFile, FILE_READ);
|
||||
if (file < 0) {
|
||||
print("Way_DumpWaypoints: unable to open ", strFile, "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Way_WipeWaypoints();
|
||||
|
||||
tokenize(fgets(file));
|
||||
g_iWaypoints = stoi(argv(0));
|
||||
g_pWaypoints = memalloc(sizeof(*g_pWaypoints) * g_iWaypoints);
|
||||
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
tokenize(fgets(file));
|
||||
g_pWaypoints[i].vecOrigin[0] = stof(argv(0));
|
||||
g_pWaypoints[i].vecOrigin[1] = stof(argv(1));
|
||||
g_pWaypoints[i].vecOrigin[2] = stof(argv(2));
|
||||
g_pWaypoints[i].flRadius = stof(argv(3));
|
||||
g_pWaypoints[i].iNeighbours = stoi(argv(4));
|
||||
g_pWaypoints[i].neighbour = memalloc(sizeof(*g_pWaypoints[i].neighbour) * g_pWaypoints[i].iNeighbours);
|
||||
|
||||
for (int j = 0i; j < g_pWaypoints[i].iNeighbours; j++) {
|
||||
tokenize(fgets(file));
|
||||
g_pWaypoints[i].neighbour[j].node = stoi(argv(0));
|
||||
g_pWaypoints[i].neighbour[j].linkcost = stof(argv(1));
|
||||
g_pWaypoints[i].neighbour[j].iFlags = stoh(argv(2));
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
static void
|
||||
Way_LinkWaypoints(waypoint_t *wp, waypoint_t *w2)
|
||||
{
|
||||
int w2n = w2 - g_pWaypoints;
|
||||
for (int i = 0i; i < wp->iNeighbours; i++) {
|
||||
if (wp->neighbour[i].node == w2n) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int idx = wp->iNeighbours++;
|
||||
wp->neighbour = memrealloc(wp->neighbour, sizeof(*wp->neighbour), idx, wp->iNeighbours);
|
||||
local struct wpneighbour_s *n = wp->neighbour+idx;
|
||||
n->node = w2n;
|
||||
n->linkcost = vlen(w2->vecOrigin - wp->vecOrigin);
|
||||
n->iFlags = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
Way_AutoLink(waypoint_t *wp)
|
||||
{
|
||||
int wpidx = wp-g_pWaypoints;
|
||||
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
//don't link to ourself...
|
||||
if (i == wpidx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//autolink distance cutoff.
|
||||
if (vlen(wp->vecOrigin - g_pWaypoints[i].vecOrigin) > autocvar(nav_linksize, 256, "Cuttoff distance between links")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//not going to use the full player size because that makes steps really messy.
|
||||
//however, we do need a little size, for sanity's sake
|
||||
tracebox(wp->vecOrigin, '-16 -16 0', '16 16 32', g_pWaypoints[i].vecOrigin, TRUE, world);
|
||||
|
||||
//light of sight blocked, don't try autolinking.
|
||||
if (trace_fraction < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Way_LinkWaypoints(wp, &g_pWaypoints[i]);
|
||||
Way_LinkWaypoints(&g_pWaypoints[i], wp);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Way_Waypoint_Create(entity ePlayer, int iAutoLink)
|
||||
{
|
||||
int iID = g_iWaypoints++;
|
||||
g_pWaypoints = memrealloc(g_pWaypoints, sizeof(waypoint_t), iID, g_iWaypoints);
|
||||
waypoint_t *n = g_pWaypoints + iID;
|
||||
n->vecOrigin = ePlayer.origin;
|
||||
n->neighbour = __NULL__;
|
||||
n->iNeighbours = 0;
|
||||
|
||||
if (iAutoLink) {
|
||||
Way_AutoLink(n);
|
||||
} else {
|
||||
if (iID != 0) {
|
||||
Way_LinkWaypoints(n, &g_pWaypoints[iID-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Way_Waypoint_CreateSpawns()
|
||||
{
|
||||
for (entity a = world; (a = find(a, ::classname, "info_player_deathmatch"));) {
|
||||
Way_Waypoint_Create(a, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Way_Waypoint_Delete(int iID)
|
||||
{
|
||||
if (iID < 0i || iID >= g_iWaypoints) {
|
||||
print("RT_DeleteWaypoint: invalid waypoint\n");
|
||||
return;
|
||||
}
|
||||
|
||||
//wipe the waypoint
|
||||
memfree(g_pWaypoints[iID].neighbour);
|
||||
memcpy(g_pWaypoints + iID, g_pWaypoints + iID + 1, (g_iWaypoints - (iID + 1)) * sizeof(*g_pWaypoints));
|
||||
g_iWaypoints--;
|
||||
|
||||
//clean up any links to it.
|
||||
for (int i = 0; i < g_iWaypoints; i++) {
|
||||
for (int j = 0; j < g_pWaypoints[i].iNeighbours;) {
|
||||
int l = g_pWaypoints[i].neighbour[j].node;
|
||||
if (l == iID) {
|
||||
memcpy(g_pWaypoints[i].neighbour+j, g_pWaypoints[i].neighbour+j+1, (g_pWaypoints[i].iNeighbours-(j+1))*sizeof(*g_pWaypoints[i].neighbour));
|
||||
g_pWaypoints[i].iNeighbours--;
|
||||
continue;
|
||||
} else if (l > iID) {
|
||||
g_pWaypoints[i].neighbour[j].node = l-1;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Way_Waypoint_SetRadius(int iID, float flRadValue)
|
||||
{
|
||||
if (iID < 0i || iID >= g_iWaypoints) {
|
||||
print("RT_Waypoint_SetRadius: invalid waypoint\n");
|
||||
return;
|
||||
}
|
||||
g_pWaypoints[iID].flRadius = flRadValue;
|
||||
}
|
||||
|
||||
void
|
||||
Way_Waypoint_MakeJump(int iID)
|
||||
{
|
||||
if (iID < 0i || iID >= g_iWaypoints) {
|
||||
print("RT_Waypoint_SetRadius: invalid waypoint\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int j = 0i; j < g_pWaypoints[iID].iNeighbours; j++) {
|
||||
int iTarget = g_pWaypoints[iID].neighbour[j].node;
|
||||
|
||||
for (int b = 0i; b < g_pWaypoints[iTarget].iNeighbours; b++) {
|
||||
if (g_pWaypoints[iTarget].neighbour[b].node == iID) {
|
||||
g_pWaypoints[iTarget].neighbour[b].iFlags = WP_JUMP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Way_FindClosestWaypoint(vector vecOrigin)
|
||||
{
|
||||
|
||||
/* -1 for no nodes anywhere... */
|
||||
int r = -1i;
|
||||
float flBestDist = COST_INFINITE;
|
||||
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
float fDist = vlen(g_pWaypoints[i].vecOrigin - vecOrigin) - g_pWaypoints[i].flRadius;
|
||||
if (fDist < flBestDist) {
|
||||
/* within the waypoint's radius */
|
||||
if (fDist < 0) {
|
||||
flBestDist = fDist;
|
||||
r = i;
|
||||
} else {
|
||||
/* outside the waypoint, make sure its valid. */
|
||||
traceline(vecOrigin, g_pWaypoints[i].vecOrigin, TRUE, world);
|
||||
if (trace_fraction == 1) {
|
||||
/* FIXME: sort them frst, to avoid traces? */
|
||||
flBestDist = fDist;
|
||||
r = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void
|
||||
Way_DrawDebugInfo(void)
|
||||
{
|
||||
if (!g_iWaypoints) {
|
||||
return;
|
||||
}
|
||||
|
||||
int iNearest = Way_FindClosestWaypoint(self.origin);
|
||||
makevectors(self.v_angle);
|
||||
R_BeginPolygon("textures/dev/info_node", 0, 0);
|
||||
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
waypoint_t *w = g_pWaypoints + i;
|
||||
vector org = w->vecOrigin;
|
||||
vector rgb = [1,1,1];
|
||||
|
||||
if (iNearest == i) {
|
||||
rgb = [0,1,0];
|
||||
}
|
||||
|
||||
R_PolygonVertex(org + v_right * 8 - v_up * 8, '1 1', rgb, 1);
|
||||
R_PolygonVertex(org - v_right * 8 - v_up * 8, '0 1', rgb, 1);
|
||||
R_PolygonVertex(org - v_right * 8 + v_up * 8, [0,0], rgb, 1);
|
||||
R_PolygonVertex(org + v_right * 8 + v_up * 8, '1 0', rgb, 1);
|
||||
R_EndPolygon();
|
||||
}
|
||||
|
||||
R_BeginPolygon("", 0, 0);
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
waypoint_t *w = g_pWaypoints + i;
|
||||
vector org = w->vecOrigin;
|
||||
|
||||
for (float j = 0; j < (2 * M_PI); j += (2 * M_PI) / 4) {
|
||||
R_PolygonVertex(org + [sin(j), cos(j)]*w->flRadius, '1 1', '0 0.25 0', 1);
|
||||
}
|
||||
R_EndPolygon();
|
||||
}
|
||||
|
||||
R_BeginPolygon("", 1, 0);
|
||||
|
||||
for (int i = 0i; i < g_iWaypoints; i++) {
|
||||
waypoint_t *w = g_pWaypoints+i;
|
||||
vector org = w->vecOrigin;
|
||||
vector rgb = [1,1,1];
|
||||
|
||||
for (int j = 0i; j < w->iNeighbours; j++) {
|
||||
int k = w->neighbour[j].node;
|
||||
|
||||
if (k < 0i || k >= g_iWaypoints) {
|
||||
break;
|
||||
}
|
||||
|
||||
waypoint_t *w2 = &g_pWaypoints[k];
|
||||
|
||||
R_PolygonVertex(org, '0 1', '1 0 1', 1);
|
||||
R_PolygonVertex(w2->vecOrigin, '1 1', [0,1,0], 1);
|
||||
R_EndPolygon();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,6 +62,7 @@ entity g_eAttacker;
|
|||
.float material;
|
||||
.float deaths;
|
||||
.float identity;
|
||||
.float botinfo;
|
||||
|
||||
/* in idTech the .owner field causes collisions to fail against set entity,
|
||||
* we don't want this all of the time. so use this as a fallback */
|
||||
|
|
|
@ -80,6 +80,12 @@ void PutClientInServer(void)
|
|||
{
|
||||
g_grMode.PlayerSpawn((base_player)self);
|
||||
|
||||
#ifdef BOT_INCLUDED
|
||||
if (clienttype(self) == CLIENTTYPE_BOT) {
|
||||
spawnfunc_bot();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* activate all game_playerspawn entities */
|
||||
for (entity a = world; (a = find(a, ::targetname, "game_playerspawn"));) {
|
||||
CBaseTrigger t = (CBaseTrigger)a;
|
||||
|
@ -113,6 +119,13 @@ void SetChangeParms(void)
|
|||
|
||||
void SV_RunClientCommand(void)
|
||||
{
|
||||
#ifdef BOT_INCLUDED
|
||||
if (time > 5.0)
|
||||
if (clienttype(self) == CLIENTTYPE_BOT) {
|
||||
((bot)self).RunAI();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!Plugin_RunClientCommand()) {
|
||||
Game_RunClientCommand();
|
||||
}
|
||||
|
@ -253,7 +266,18 @@ void worldspawn(void)
|
|||
|
||||
float ConsoleCmd(string cmd)
|
||||
{
|
||||
player pl = (player)self;
|
||||
player pl;
|
||||
|
||||
/* some sv commands can only be executed by a player in-world */
|
||||
if ( !self ) {
|
||||
for ( other = world; ( other = find( other, classname, "player" ) ); ) {
|
||||
if ( clienttype( other ) == CLIENTTYPE_REAL ) {
|
||||
self = other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pl = (player)self;
|
||||
|
||||
/* give the game-mode a chance to override us */
|
||||
if (g_grMode.ConsoleCommand(pl, cmd) == TRUE)
|
||||
|
@ -274,6 +298,50 @@ float ConsoleCmd(string cmd)
|
|||
t.Trigger(self, TRIG_TOGGLE);
|
||||
}
|
||||
break;
|
||||
#ifdef BOT_INCLUDED
|
||||
case "way_add":
|
||||
if ( !self ) {
|
||||
return TRUE;
|
||||
}
|
||||
Way_Waypoint_Create( self, TRUE );
|
||||
break;
|
||||
case "way_addchain":
|
||||
if ( !self ) {
|
||||
return TRUE;
|
||||
}
|
||||
Way_Waypoint_Create( self, FALSE );
|
||||
break;
|
||||
case "way_addspawns":
|
||||
if ( !self ) {
|
||||
return TRUE;
|
||||
}
|
||||
Way_Waypoint_CreateSpawns();
|
||||
break;
|
||||
case "way_delete":
|
||||
if ( !self ) {
|
||||
return TRUE;
|
||||
}
|
||||
Way_Waypoint_Delete( Way_FindClosestWaypoint( self.origin ) );
|
||||
break;
|
||||
case "way_radius":
|
||||
if ( !self ) {
|
||||
return TRUE;
|
||||
}
|
||||
Way_Waypoint_SetRadius( Way_FindClosestWaypoint( self.origin ), stof( argv( 1 ) ) );
|
||||
break;
|
||||
case "way_makejump":
|
||||
if ( !self ) {
|
||||
return TRUE;
|
||||
}
|
||||
Way_Waypoint_MakeJump( Way_FindClosestWaypoint( self.origin ) );
|
||||
break;
|
||||
case "way_save":
|
||||
Way_DumpWaypoints( argv( 1 ) );
|
||||
break;
|
||||
case "way_load":
|
||||
Way_ReadWaypoints( argv( 1 ) );
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
@ -201,6 +201,8 @@ Nodes_Init(void)
|
|||
void
|
||||
SV_AddDebugPolygons(void)
|
||||
{
|
||||
Way_DrawDebugInfo();
|
||||
|
||||
if (!g_iNodes) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -40,4 +40,4 @@ class info_node { };
|
|||
/* write current nodes to disk */
|
||||
void Nodes_Save(string);
|
||||
void Nodes_Load(string);
|
||||
void Nodes_Init(void)
|
||||
void Nodes_Init(void);
|
||||
|
|
18
src/server/valve/bot.cpp
Normal file
18
src/server/valve/bot.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
void
|
||||
Bot_
|
|
@ -70,6 +70,7 @@ void item_battery::Respawn(void)
|
|||
SetSize([-16,-16,0],[16,16,16]);
|
||||
SetOrigin(m_oldOrigin);
|
||||
SetModel(m_oldModel);
|
||||
// botinfo = BOTINFO_ARMOR;
|
||||
|
||||
think = __NULL__;
|
||||
nextthink = -1;
|
||||
|
|
|
@ -59,6 +59,7 @@ void item_healthkit::Respawn(void)
|
|||
SetSize([-16,-16,0],[16,16,16]);
|
||||
SetOrigin(m_oldOrigin);
|
||||
SetModel(m_oldModel);
|
||||
//botinfo = BOTINFO_HEALTH;
|
||||
|
||||
think = __NULL__;
|
||||
nextthink = -1;
|
||||
|
|
|
@ -18,20 +18,24 @@ void info_player_start(void)
|
|||
{
|
||||
self.solid = SOLID_TRIGGER;
|
||||
setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
|
||||
self.botinfo = BOTINFO_SPAWNPOINT;
|
||||
}
|
||||
|
||||
void info_player_deathmatch(void)
|
||||
{
|
||||
self.solid = SOLID_TRIGGER;
|
||||
setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
|
||||
self.botinfo = BOTINFO_SPAWNPOINT;
|
||||
}
|
||||
|
||||
void info_player_team1(void)
|
||||
{
|
||||
self.classname = "info_player_deathmatch";
|
||||
self.botinfo = BOTINFO_SPAWNPOINT;
|
||||
}
|
||||
|
||||
void info_player_team2(void)
|
||||
{
|
||||
self.classname = "info_player_deathmatch";
|
||||
self.botinfo = BOTINFO_SPAWNPOINT;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue