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:
Marco Cawthorne 2020-12-23 07:53:43 +01:00
parent 545b66a786
commit c76cdc5903
17 changed files with 866 additions and 37 deletions

View File

@ -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;
}

View File

@ -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
View 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
View 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
View 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"

View File

@ -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
View 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
View 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
View 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();
}
}
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -201,6 +201,8 @@ Nodes_Init(void)
void
SV_AddDebugPolygons(void)
{
Way_DrawDebugInfo();
if (!g_iNodes) {
return;
}

View File

@ -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
View 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_

View File

@ -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;

View File

@ -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;

View File

@ -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;
}