/////////////////////////////////////////////////////////////////////// // Check for adding ladder nodes /////////////////////////////////////////////////////////////////////// qboolean ACEND_CheckForLadder(edict_t *self) { int closest_node; // If there is a ladder and we are moving up, see if we should add a ladder node if (gi.pointcontents(self->s.origin) & CONTENTS_LADDER && self->velocity[2] > 0) { //debug_printf("contents: %x\n",tr.contents); closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_LADDER); if(closest_node == -1) { closest_node = ACEND_AddNode(self,NODE_LADDER); // Now add link ACEND_UpdateNodeEdge(self->last_node,closest_node); // Set current to last self->last_node = closest_node; } else { ACEND_UpdateNodeEdge(self->last_node,closest_node); self->last_node = closest_node; // set visited to last } return true; } return false; }
/////////////////////////////////////////////////////////////////////// // Capture when the grappling hook has been fired for mapping purposes. /////////////////////////////////////////////////////////////////////// void ACEND_GrapFired(edict_t *self) { int closest_node; if(!self->owner) return; // should not be here // Check to see if the grapple is in pull mode if(self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) { // Look for the closest node of type grapple closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_GRAPPLE); if(closest_node == -1 ) // we need to drop a node { closest_node = ACEND_AddNode(self,NODE_GRAPPLE); // Add an edge ACEND_UpdateNodeEdge(self->owner->last_node,closest_node); self->owner->last_node = closest_node; } else self->owner->last_node = closest_node; // zero out so other nodes will not be linked } }
void ACESP_SetupBotState(gentity_t * self) { int clientNum; char userinfo[MAX_INFO_STRING]; //char *team; //G_Printf("ACESP_SetupBotState()\n"); clientNum = self->client - level.clients; trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo)); //self->classname = "acebot"; self->enemy = NULL; //self->bs.turnSpeed = 35; // 100 is deadly fast self->bs.moveTarget = NULL; self->bs.state = STATE_MOVE; // set the current node ACEND_setCurrentNode(self, ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_MOVE)); self->bs.goalNode = self->bs.currentNode; ACEND_setNextNode(self, self->bs.currentNode); //self->bs.nextNode = self->bs.currentNode; self->bs.lastNode = INVALID; self->bs.next_move_time = level.time; self->bs.suicide_timeout = level.time + 15000; /* // is the bot part of a team when gameplay has changed? team = Info_ValueForKey(userinfo, "team"); if(!team || !*team) { if(g_gametype.integer >= GT_TEAM) { if(PickTeam(clientNum) == TEAM_RED) { team = "red"; } else { team = "blue"; } } else { team = "red"; } //Info_SetValueForKey(userinfo, "team", team); // need to send this or bots will be spectators trap_BotClientCommand(self - g_entities, va("team %s", team)); } */ //if(g_gametype.integer >= GT_TEAM) // trap_BotClientCommand(self - g_entities, va("team %s", Info_ValueForKey(userinfo, "team"))); }
// Turns on showing of the path, set goal to -1 to // shut off. (utility function) void ACEND_ShowPath(gentity_t * self, int goalNode) { int currentNode; currentNode = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL); if(currentNode == INVALID) { trap_SendServerCommand(self - g_entities, "print \"no closest reachable node!\n\""); return; } ACEND_DrawPath(currentNode, goalNode); }
/////////////////////////////////////////////////////////////////////// // Special command processor /////////////////////////////////////////////////////////////////////// qboolean ACECM_Commands(edict_t *ent) { char *cmd; int node; cmd = gi.argv(0); if ((Q_strcasecmp(cmd, "addnode") == 0) && debug_mode) { ent->last_node = ACEND_AddNode(ent, atoi(gi.argv(1))); } else if ((Q_strcasecmp(cmd, "removelink") == 0) && debug_mode) { ACEND_RemoveNodeEdge(ent, atoi(gi.argv(1)), atoi(gi.argv(2))); } else if ((Q_strcasecmp(cmd, "addlink") == 0) && debug_mode) { ACEND_UpdateNodeEdge(atoi(gi.argv(1)), atoi(gi.argv(2))); } else if ((Q_strcasecmp(cmd, "showpath") == 0) && debug_mode) { ACEND_ShowPath(ent, atoi(gi.argv(1))); } else if ((Q_strcasecmp(cmd, "findnode") == 0) && debug_mode) { node = ACEND_FindClosestReachableNode(ent, NODE_DENSITY, NODE_ALL); safe_bprintf(PRINT_MEDIUM, "node: %d type: %d x: %f y: %f z %f\n", node, nodes[node].type, nodes[node].origin[0], nodes[node].origin[1], nodes[node].origin[2]); } else if ((Q_strcasecmp(cmd, "movenode") == 0) && debug_mode) { node = atoi(gi.argv(1)); nodes[node].origin[0] = atof(gi.argv(2)); nodes[node].origin[1] = atof(gi.argv(3)); nodes[node].origin[2] = atof(gi.argv(4)); safe_bprintf(PRINT_MEDIUM, "node: %d moved to x: %f y: %f z %f\n", node, nodes[node].origin[0], nodes[node].origin[1], nodes[node].origin[2]); } else { return false; } return true; }
/////////////////////////////////////////////////////////////////////// // Set up the goal /////////////////////////////////////////////////////////////////////// void ACEND_SetGoal(edict_t *self, int goal_node) { int node; self->goal_node = goal_node; node = ACEND_FindClosestReachableNode(self, NODE_DENSITY*3, NODE_ALL); if(node == -1) return; if(debug_mode) debug_printf("%s new start node selected %d\n",self->client->pers.netname,node); self->current_node = node; self->next_node = self->current_node; // make sure we get to the nearest node first self->node_timeout = 0; }
void ACEND_SetGoal(gentity_t * self, int goalNode) { int node; self->bs.goalNode = goalNode; node = ACEND_FindClosestReachableNode(self, NODE_DENSITY * 3, NODE_ALL); if(node == INVALID) return; if(ace_debug.integer) trap_SendServerCommand(-1, va("print \"%s: new start node selected %d\n\"", self->client->pers.netname, node)); self->bs.currentNode = node; self->bs.nextNode = self->bs.currentNode; // make sure we get to the nearest node first self->bs.node_timeout = 0; if(ace_showPath.integer) { // draw path to LR goal ACEND_DrawPath(self->bs.currentNode, self->bs.goalNode); } }
/////////////////////////////////////////////////////////////////////// // Modified version of id's code /////////////////////////////////////////////////////////////////////// void ACESP_PutClientInServer(edict_t *bot, qboolean respawn, int team) { vec3_t mins = {-16, -16, -24}; vec3_t maxs = {16, 16, 32}; int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; client_persistant_t saved; client_respawn_t resp; char *s; int spawn_style; int spawn_health; // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client SelectSpawnPoint (bot, spawn_origin, spawn_angles, &spawn_style, &spawn_health); index = bot-g_edicts-1; client = bot->client; // deathmatch wipes most client data every spawn if (deathmatch->value) { char userinfo[MAX_INFO_STRING]; resp = bot->client->resp; memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); InitClientPersistant (client, spawn_style); ClientUserinfoChanged (bot, userinfo); } else memset(&resp, 0, sizeof(resp)); // clear everything but the persistant data saved = client->pers; memset(client, 0, sizeof(*client)); client->pers = saved; client->resp = resp; // copy some data from the client to the entity FetchClientEntData (bot); // clear entity values bot->groundentity = NULL; bot->client = &game.clients[index]; bot->takedamage = DAMAGE_AIM; bot->movetype = MOVETYPE_WALK; bot->viewheight = 24; bot->classname = "bot"; bot->mass = 200; bot->solid = SOLID_BBOX; bot->deadflag = DEAD_NO; bot->air_finished = level.time + 12; bot->clipmask = MASK_PLAYERSOLID; bot->model = "players/male/tris.md2"; bot->pain = player_pain; bot->die = player_die; bot->waterlevel = 0; bot->watertype = 0; bot->flags &= ~FL_NO_KNOCKBACK; bot->svflags &= ~SVF_DEADMONSTER; bot->is_jumping = false; if (ctf->value) { client->resp.ctf_team = team; client->resp.ctf_state = CTF_STATE_START; s = Info_ValueForKey(client->pers.userinfo, "skin"); CTFAssignSkin(bot, s); } VectorCopy(mins, bot->mins); VectorCopy(maxs, bot->maxs); VectorClear(bot->velocity); // clear playerstate values memset(&bot->client->ps, 0, sizeof(client->ps)); client->ps.pmove.origin[0] = spawn_origin[0]*8; client->ps.pmove.origin[1] = spawn_origin[1]*8; client->ps.pmove.origin[2] = spawn_origin[2]*8; //ZOID client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; //ZOID if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) { client->ps.fov = 90; } else { client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); if (client->ps.fov < 1) client->ps.fov = 90; else if (client->ps.fov > 160) client->ps.fov = 160; } // Knightmare- fix for null model? if (client->pers.weapon && client->pers.weapon->view_model) client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); // clear entity state values bot->s.effects = 0; bot->s.skinnum = bot - g_edicts - 1; bot->s.modelindex = MAX_MODELS-1; // will use the skin specified model bot->s.modelindex2 = MAX_MODELS-1; // custom gun model bot->s.frame = 0; VectorCopy(spawn_origin, bot->s.origin); bot->s.origin[2] += 1; // make sure off ground // set the delta angle for (int i = 0; i < 3; i++) client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); bot->s.angles[PITCH] = 0; bot->s.angles[YAW] = spawn_angles[YAW]; bot->s.angles[ROLL] = 0; VectorCopy(bot->s.angles, client->ps.viewangles); VectorCopy(bot->s.angles, client->v_angle); // force the current weapon up client->newweapon = client->pers.weapon; ChangeWeapon (bot); bot->enemy = NULL; bot->movetarget = NULL; bot->state = STATE_MOVE; // Set the current node bot->current_node = ACEND_FindClosestReachableNode(bot,NODE_DENSITY, NODE_ALL); bot->goal_node = bot->current_node; bot->next_node = bot->current_node; bot->next_move_time = level.time; bot->suicide_timeout = level.time + 15.0; // If we are not respawning hold off for up to three seconds before releasing into game if (!respawn) { bot->think = ACESP_HoldSpawn; //bot->nextthink = level.time + 0.1; //mxd bot->nextthink = level.time + random()*3.0f; // up to three seconds } else { if (!KillBox(bot)) { // could't spawn in? } gi.linkentity(bot); bot->think = ACEAI_Think; bot->nextthink = level.time + FRAMETIME; // send effect gi.WriteByte(svc_muzzleflash); gi.WriteShort(bot-g_edicts); gi.WriteByte(MZ_LOGIN); gi.multicast(bot->s.origin, MULTICAST_PVS); } }
/////////////////////////////////////////////////////////////////////// // Turns on showing of the path, set goal to -1 to // shut off. (utility function) /////////////////////////////////////////////////////////////////////// void ACEND_ShowPath(edict_t *self, int goal_node) { show_path_from = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL); show_path_to = goal_node; }
/////////////////////////////////////////////////////////////////////// // This routine is called to hook in the pathing code and sets // the current node if valid. /////////////////////////////////////////////////////////////////////// void ACEND_PathMap(edict_t *self) { int closest_node; static float last_update=0; // start off low vec3_t v; if(level.time < last_update) return; last_update = level.time + 0.15; // slow down updates a bit // Special node drawing code for debugging if(show_path_to != -1) ACEND_DrawPath(); //////////////////////////////////////////////////////// // Special check for ladder nodes /////////////////////////////////////////////////////// if(ACEND_CheckForLadder(self)) // check for ladder nodes return; // Not on ground, and not in the water, so bail if(!self->groundentity && !self->waterlevel) return; //////////////////////////////////////////////////////// // Lava/Slime //////////////////////////////////////////////////////// VectorCopy(self->s.origin,v); v[2] -= 18; if(gi.pointcontents(v) & (CONTENTS_LAVA|CONTENTS_SLIME)) return; // no nodes in slime //////////////////////////////////////////////////////// // Jumping /////////////////////////////////////////////////////// if(self->is_jumping) { // See if there is a closeby jump landing node (prevent adding too many) closest_node = ACEND_FindClosestReachableNode(self, 64, NODE_JUMP); if(closest_node == INVALID) closest_node = ACEND_AddNode(self,NODE_JUMP); // Now add link if(self->last_node != -1) ACEND_UpdateNodeEdge(self->last_node, closest_node); self->is_jumping = false; return; } //////////////////////////////////////////////////////////// // Grapple // Do not add nodes during grapple, added elsewhere manually //////////////////////////////////////////////////////////// if(ctf->value && self->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) return; // Iterate through all nodes to make sure far enough apart closest_node = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL); //////////////////////////////////////////////////////// // Special Check for Platforms //////////////////////////////////////////////////////// if(self->groundentity && self->groundentity->use == Use_Plat) { if(closest_node == INVALID) return; // Do not want to do anything here. // Here we want to add links if(closest_node != self->last_node && self->last_node != INVALID) ACEND_UpdateNodeEdge(self->last_node,closest_node); self->last_node = closest_node; // set visited to last return; } //////////////////////////////////////////////////////// // Add Nodes as needed //////////////////////////////////////////////////////// if(closest_node == INVALID) { // Add nodes in the water as needed if(self->waterlevel) closest_node = ACEND_AddNode(self,NODE_WATER); else closest_node = ACEND_AddNode(self,NODE_MOVE); // Now add link if(self->last_node != -1) ACEND_UpdateNodeEdge(self->last_node, closest_node); } else if(closest_node != self->last_node && self->last_node != INVALID) ACEND_UpdateNodeEdge(self->last_node,closest_node); self->last_node = closest_node; // set visited to last }
/////////////////////////////////////////////////////////////////////// // Evaluate the best long range goal and send the bot on // its way. This is a good time waster, so use it sparingly. // Do not call it for every think cycle. /////////////////////////////////////////////////////////////////////// void ACEAI_PickLongRangeGoal(edict_t *self) { int i; int node; float weight,best_weight=0.0; int current_node,goal_node; edict_t *goal_ent; float cost; // look for a target current_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_ALL); self->current_node = current_node; if(current_node == -1) { self->state = STATE_WANDER; self->wander_timeout = level.time + 1.0; self->goal_node = -1; return; } /////////////////////////////////////////////////////// // Items /////////////////////////////////////////////////////// for (i=0; i<num_items; i++) { // ignore items that are not there. if (!item_table[i].ent || !item_table[i].ent->inuse || item_table[i].ent->solid == SOLID_NOT) continue; cost = ACEND_FindCost(current_node,item_table[i].node); if (cost == INVALID || cost < 2) // ignore invalid and very short hops continue; weight = ACEIT_ItemNeed(self, item_table[i].item); // If I am on team one and I have the flag for the other team....return it // Knightmare- rewrote for 3Team CTF //if (ctf->value && (item_table[i].item == ITEMLIST_FLAG2 || item_table[i].item == ITEMLIST_FLAG1) && // (self->client->resp.ctf_team == CTF_TEAM1 && self->client->pers.inventory[ITEMLIST_FLAG2] || // self->client->resp.ctf_team == CTF_TEAM2 && self->client->pers.inventory[ITEMLIST_FLAG1])) if (ctf->value && ( (item_table[i].item == ITEMLIST_FLAG1 && self->client->resp.ctf_team == CTF_TEAM1 && (self->client->pers.inventory[ITEMLIST_FLAG2] || self->client->pers.inventory[ITEMLIST_FLAG3]) ) || (item_table[i].item == ITEMLIST_FLAG2 && self->client->resp.ctf_team == CTF_TEAM2 && (self->client->pers.inventory[ITEMLIST_FLAG1] || self->client->pers.inventory[ITEMLIST_FLAG3]) ) || (item_table[i].item == ITEMLIST_FLAG3 && self->client->resp.ctf_team == CTF_TEAM3 && (self->client->pers.inventory[ITEMLIST_FLAG1] || self->client->pers.inventory[ITEMLIST_FLAG2]) ) )) weight = 10.0; // Knightmare- in 3Team CTF mode, make double captures a lower priority than // getting back to base with the flag we already have. if (ttctf->value && ( (self->client->resp.ctf_team == CTF_TEAM1 && (self->client->pers.inventory[ITEMLIST_FLAG2] || self->client->pers.inventory[ITEMLIST_FLAG3]) && (item_table[i].item == ITEMLIST_FLAG2 || item_table[i].item == ITEMLIST_FLAG3) ) || (self->client->resp.ctf_team == CTF_TEAM2 && (self->client->pers.inventory[ITEMLIST_FLAG1] || self->client->pers.inventory[ITEMLIST_FLAG3]) && (item_table[i].item == ITEMLIST_FLAG1 || item_table[i].item == ITEMLIST_FLAG3) ) || (self->client->resp.ctf_team == CTF_TEAM3 && (self->client->pers.inventory[ITEMLIST_FLAG1] || self->client->pers.inventory[ITEMLIST_FLAG2]) && (item_table[i].item == ITEMLIST_FLAG1 || item_table[i].item == ITEMLIST_FLAG2) ) )) weight = 6.5; weight *= random(); // Allow random variations weight /= cost; // Check against cost of getting there if (weight > best_weight) { best_weight = weight; goal_node = item_table[i].node; goal_ent = item_table[i].ent; } } /////////////////////////////////////////////////////// // Players /////////////////////////////////////////////////////// // This should be its own function and is for now just // finds a player to set as the goal. for(i=0;i<num_players;i++) { if(players[i] == self) continue; node = ACEND_FindClosestReachableNode(players[i],NODE_DENSITY,NODE_ALL); cost = ACEND_FindCost(current_node, node); if(cost == INVALID || cost < 3) // ignore invalid and very short hops continue; // Player carrying the flag? if(ctf->value && (players[i]->client->pers.inventory[ITEMLIST_FLAG2] || players[i]->client->pers.inventory[ITEMLIST_FLAG1])) weight = 2.0; else weight = 0.3; weight *= random(); // Allow random variations weight /= cost; // Check against cost of getting there if(weight > best_weight) { best_weight = weight; goal_node = node; goal_ent = players[i]; } } // If do not find a goal, go wandering.... if(best_weight == 0.0 || goal_node == INVALID) { self->goal_node = INVALID; self->state = STATE_WANDER; self->wander_timeout = level.time + 1.0; if(debug_mode) debug_printf("%s did not find a LR goal, wandering.\n",self->client->pers.netname); return; // no path? } // OK, everything valid, let's start moving to our goal. self->state = STATE_MOVE; self->tries = 0; // Reset the count of how many times we tried this goal if(goal_ent != NULL && debug_mode) debug_printf("%s selected a %s at node %d for LR goal.\n",self->client->pers.netname, goal_ent->classname, goal_node); ACEND_SetGoal(self,goal_node); }
// This routine is called to hook in the pathing code and sets // the current node if valid. void ACEND_PathMap(gentity_t * self) { int closestNode; static float lastUpdate = 0; // start off low vec3_t v; //qboolean isJumping; //int i; #if 0 if(level.time < lastUpdate) return; #endif lastUpdate = level.time + 150; // slow down updates a bit #if 0 if(self->r.svFlags & SVF_BOT) return; #endif // don't add links when you went into a trap if(self->health <= 0) return; #if 1 if(self->s.groundEntityNum == ENTITYNUM_NONE && !(self->r.svFlags & SVF_BOT)) { #if 0 isJumping = qfalse; for(i = 0; i < self->client->ps.eventSequence; i++) { if(self->client->ps.events[i] == EV_JUMP) isJumping = qtrue; } if(isJumping) #else if((self->client->ps.pm_flags & PMF_JUMP_HELD)) #endif { if(ace_debug.integer) trap_SendServerCommand(-1, va("print \"%s: jumping\n\"", self->client->pers.netname)); // see if there is a closeby jump landing node (prevent adding too many) closestNode = ACEND_FindClosestReachableNode(self, 64, NODE_JUMP); if(closestNode == INVALID) closestNode = ACEND_AddNode(self, NODE_JUMP); // now add link if(self->bs.lastNode != INVALID) ACEND_UpdateNodeEdge(self->bs.lastNode, closestNode); self->bs.isJumping = qfalse; return; } } #endif // not on ground, and not in the water, so bail if(self->s.groundEntityNum == ENTITYNUM_NONE) { /* if(self->bs.lastNode != INVALID) { // we might have been pushed by a jump pad if(nodes[self->bs.lastNode].type != NODE_JUMPPAD) return; } else if(!self->waterlevel) { return; } */ } // lava / slime VectorCopy(self->client->ps.origin, v); v[2] -= 18; if(trap_PointContents(self->client->ps.origin, -1) & (CONTENTS_LAVA | CONTENTS_SLIME)) return; // no nodes in slime // Grapple // Do not add nodes during grapple, added elsewhere manually /* if(ctf->value && self->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) return; */ // iterate through all nodes to make sure far enough apart closestNode = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL); // Special Check for Platforms /* FIXME if(self->groundentity && self->groundentity->use == Use_Plat) { if(closestNode == INVALID) return; // Do not want to do anything here. // Here we want to add links if(closestNode != self->lastNode && self->lastNode != INVALID) ACEND_UpdateNodeEdge(self->lastNode, closestNode); self->lastNode = closestNode; // set visited to last return; } */ if(closestNode != INVALID) { // add automatically some links between nodes if(closestNode != self->bs.lastNode && self->bs.lastNode != INVALID) { ACEND_UpdateNodeEdge(self->bs.lastNode, closestNode); if(ace_showLinks.integer) ACEND_DrawPath(self->bs.lastNode, closestNode); } self->bs.lastNode = closestNode; // set visited to last } #if 1 else if(closestNode == INVALID && self->s.groundEntityNum != ENTITYNUM_NONE) { // add nodes in the water as needed if(self->waterlevel) closestNode = ACEND_AddNode(self, NODE_WATER); else closestNode = ACEND_AddNode(self, NODE_MOVE); // now add link if(self->bs.lastNode != INVALID) { ACEND_UpdateNodeEdge(self->bs.lastNode, closestNode); if(ace_showLinks.integer) ACEND_DrawPath(self->bs.lastNode, closestNode); } self->bs.lastNode = closestNode; // set visited to last } #endif }