//========================================== // AI_AddNode_Door // Drop a node at each side of the door // and force them to link. Only typical // doors are covered. //========================================== int AI_AddNode_Door( edict_t *ent ) { edict_t *other; vec3_t mins, maxs; vec3_t forward, right, up; vec3_t door_origin; if (ent->flags & FL_TEAMSLAVE) return INVALID; //only team master will drop the nodes //make box formed by all team members boxes VectorCopy (ent->absmin, mins); VectorCopy (ent->absmax, maxs); for (other = ent->teamchain ; other ; other=other->teamchain) { AddPointToBounds (other->absmin, mins, maxs); AddPointToBounds (other->absmax, mins, maxs); } door_origin[0] = (maxs[0] - mins[0]) / 2 + mins[0]; door_origin[1] = (maxs[1] - mins[1]) / 2 + mins[1]; door_origin[2] = (maxs[2] - mins[2]) / 2 + mins[2]; //now find the crossing angle AngleVectors( ent->s.angles, forward, right, up ); VectorNormalize( right ); //add node nodes[nav.num_nodes].flags = 0; VectorMA( door_origin, 32, right, nodes[nav.num_nodes].origin); AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ); nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, "models/powerups/health/mega_sphere.md3" ); #endif nav.num_nodes++; //add node 2 nodes[nav.num_nodes].flags = 0; VectorMA( door_origin, -32, right, nodes[nav.num_nodes].origin); AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ); nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, "models/powerups/health/mega_sphere.md3" ); #endif //add links in both directions AI_AddLink( nav.num_nodes, nav.num_nodes-1, LINK_MOVE ); AI_AddLink( nav.num_nodes-1, nav.num_nodes, LINK_MOVE ); nav.num_nodes++; return nav.num_nodes-1; }
/* * AI_AddNode_Platform_FindLowerLinkableCandidate * helper to AI_AddNode_Platform */ static int AI_AddNode_Platform_FindLowerLinkableCandidate( edict_t *ent ) { trace_t trace; float plat_dist; float platlip; int numtries = 0, maxtries = 10; int candidate; vec3_t candidate_origin, virtualorigin; float mindist = 0; if( ent->flags & FL_TEAMSLAVE ) return NODE_INVALID; plat_dist = ent->moveinfo.start_origin[2] - ent->moveinfo.end_origin[2]; platlip = ( ent->r.maxs[2] - ent->r.mins[2] ) - plat_dist; //find a good candidate for lower candidate_origin[0] = ( ent->r.maxs[0] - ent->r.mins[0] ) / 2 + ent->r.mins[0]; candidate_origin[1] = ( ent->r.maxs[1] - ent->r.mins[1] ) / 2 + ent->r.mins[1]; candidate_origin[2] = ent->r.mins[2] + platlip; //try to find the closer reachable node to the bottom of the plat do { candidate = AI_FindClosestNode( candidate_origin, mindist, NODE_DENSITY * 2, NODE_ALL ); if( candidate != NODE_INVALID ) { mindist = DistanceFast( candidate_origin, nodes[candidate].origin ); //check to see if it would be valid if( fabs( candidate_origin[2] - nodes[candidate].origin[2] ) < ( fabs( platlip ) + AI_JUMPABLE_HEIGHT ) ) { //put at linkable candidate height virtualorigin[0] = candidate_origin[0]; virtualorigin[1] = candidate_origin[1]; virtualorigin[2] = nodes[candidate].origin[2]; G_Trace( &trace, virtualorigin, vec3_origin, vec3_origin, nodes[candidate].origin, ent, MASK_NODESOLID ); //trace = gi.trace( virtualorigin, vec3_origin, vec3_origin, nodes[candidate].origin, ent, MASK_NODESOLID ); if( trace.fraction == 1.0 && !trace.startsolid ) { #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( virtualorigin, "models/objects/grenade/tris.md2" ); #endif return candidate; } } } } while( candidate != NODE_INVALID && numtries++ < maxtries ); return NODE_INVALID; }
//========================================== // AI_PredictJumpadDestity // Make a guess on where a jumpad will send // the player. //========================================== qboolean AI_PredictJumpadDestity( edict_t *ent, vec3_t out ) { int i; edict_t *target; trace_t trace; vec3_t pad_origin, v1, v2; float htime, vtime, tmpfloat, player_factor; //player movement guess vec3_t floor_target_origin, target_origin; vec3_t floor_dist_vec, floor_movedir; VectorClear( out ); if( !ent->target ) //jabot092 return false; // get target entity target = G_Find ( NULL, FOFS(targetname), ent->target ); if (!target) return false; // find pad origin VectorCopy( ent->maxs, v1 ); VectorCopy( ent->mins, v2 ); pad_origin[0] = (v1[0] - v2[0]) / 2 + v2[0]; pad_origin[1] = (v1[1] - v2[1]) / 2 + v2[1]; pad_origin[2] = ent->maxs[2]; //make a projection 'on floor' of target origin VectorCopy( target->s.origin, target_origin ); VectorCopy( target->s.origin, floor_target_origin ); floor_target_origin[2] = pad_origin[2]; //put at pad's height //make a guess on how player movement will affect the trajectory tmpfloat = AI_Distance( pad_origin, floor_target_origin ); htime = sqrt ((tmpfloat)); vtime = sqrt ((target->s.origin[2] - pad_origin[2])); if(!vtime) return false; htime *= 4;vtime *= 4; if( htime > vtime ) htime = vtime; player_factor = vtime - htime; // find distance vector, on floor, from pad_origin to target origin. for ( i=0 ; i<3 ; i++ ) floor_dist_vec[i] = floor_target_origin[i] - pad_origin[i]; // movement direction on floor VectorCopy( floor_dist_vec, floor_movedir ); VectorNormalize( floor_movedir ); // move both target origin and target origin on floor by player movement factor. VectorMA ( target_origin, player_factor, floor_movedir, target_origin); VectorMA ( floor_target_origin, player_factor, floor_movedir, floor_target_origin); // move target origin on floor by floor distance, and add another player factor step to it VectorMA ( floor_target_origin, 1, floor_dist_vec, floor_target_origin); VectorMA ( floor_target_origin, player_factor, floor_movedir, floor_target_origin); #ifdef SHOW_JUMPAD_GUESS // this is our top of the curve point, and the original target AI_JumpadGuess_ShowPoint( target_origin, "models/powerups/health/mega_sphere.md3" ); AI_JumpadGuess_ShowPoint( target->s.origin, "models/powerups/health/large_cross.md3" ); #endif //trace from target origin to endPoint. // trap_Trace ( &trace, target_origin, tv(-15, -15, -8), tv(15, 15, 8), floor_target_origin, NULL, MASK_NODESOLID); trace = gi.trace( target_origin, tv(-15, -15, -8), tv(15, 15, 8), floor_target_origin, NULL, MASK_NODESOLID); if (trace.fraction == 1.0 && trace.startsolid || trace.allsolid && trace.startsolid){ // G_Printf("JUMPAD LAND: ERROR: trace was in solid.\n"); //started inside solid (target should never be inside solid, this is a mapper error) return false; } else if ( trace.fraction == 1.0 ) { //didn't find solid. Extend Down (I have to improve this part) vec3_t target_origin2, extended_endpoint, extend_dist_vec; VectorCopy( floor_target_origin, target_origin2 ); for ( i=0 ; i<3 ; i++ ) extend_dist_vec[i] = floor_target_origin[i] - target_origin[i]; VectorMA ( target_origin2, 1, extend_dist_vec, extended_endpoint); //repeat tracing // trap_Trace ( &trace, target_origin2, tv(-15, -15, -8), tv(15, 15, 8), extended_endpoint, NULL, MASK_NODESOLID); trace = gi.trace( target_origin2, tv(-15, -15, -8), tv(15, 15, 8), extended_endpoint, NULL, MASK_NODESOLID); if ( trace.fraction == 1.0 ) return false;//still didn't find solid } #ifdef SHOW_JUMPAD_GUESS // destiny found AI_JumpadGuess_ShowPoint( trace.endpos, "models/powerups/health/mega_sphere.md3" ); #endif VectorCopy ( trace.endpos, out ); return true; }
/* * AI_AddNode_Teleporter * Drop two nodes, one at trigger and other * at target entity */ static int AI_AddNode_Teleporter( edict_t *ent ) { vec3_t v1, v2; edict_t *dest; if( nav.num_nodes + 1 > MAX_NODES ) return NODE_INVALID; dest = G_Find( NULL, FOFS( targetname ), ent->target ); if( !dest ) return NODE_INVALID; // NODE_TELEPORTER_IN nodes[nav.num_nodes].flags = ( NODEFLAGS_TELEPORTER_IN|NODEFLAGS_SERVERLINK|NODEFLAGS_REACHATTOUCH ); if( !strcmp( ent->classname, "misc_teleporter" ) ) //jabot092(2) { nodes[nav.num_nodes].origin[0] = ent->s.origin[0]; nodes[nav.num_nodes].origin[1] = ent->s.origin[1]; nodes[nav.num_nodes].origin[2] = ent->s.origin[2]+16; } else { VectorCopy( ent->r.maxs, v1 ); VectorCopy( ent->r.mins, v2 ); nodes[nav.num_nodes].origin[0] = ( v1[0] - v2[0] ) / 2 + v2[0]; nodes[nav.num_nodes].origin[1] = ( v1[1] - v2[1] ) / 2 + v2[1]; nodes[nav.num_nodes].origin[2] = ent->r.mins[2]+32; } nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, ent ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif //put into ents table nav.navigableEnts[nav.num_navigableEnts].ent = ent; nav.navigableEnts[nav.num_navigableEnts].node = nav.num_nodes; nav.num_navigableEnts++; nav.num_nodes++; //NODE_TELEPORTER_OUT nodes[nav.num_nodes].flags = ( NODEFLAGS_TELEPORTER_OUT|NODEFLAGS_SERVERLINK ); VectorCopy( dest->s.origin, nodes[nav.num_nodes].origin ); if( ent->spawnflags & 1 ) // droptofloor nodes[nav.num_nodes].flags |= NODEFLAGS_FLOAT; else AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ); nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, ent ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif //put into ents table nav.navigableEnts[nav.num_navigableEnts].ent = ent; nav.navigableEnts[nav.num_navigableEnts].node = nav.num_nodes; nav.num_navigableEnts++; // link from teleport_in AI_AddLink( nav.num_nodes-1, nav.num_nodes, LINK_TELEPORT ); nav.num_nodes++; return nav.num_nodes -1; }
/* * AI_AddNode_Platform * drop two nodes one at top, one at bottom */ static int AI_AddNode_Platform( edict_t *ent ) { float plat_dist; float platlip; int candidate; vec3_t lorg; if( nav.num_nodes + 2 > MAX_NODES ) return NODE_INVALID; if( ent->flags & FL_TEAMSLAVE ) return NODE_INVALID; // only team master will drop the nodes plat_dist = ent->moveinfo.start_origin[2] - ent->moveinfo.end_origin[2]; platlip = ( ent->r.maxs[2] - ent->r.mins[2] ) - plat_dist; //make a guess on lower plat position candidate = AI_AddNode_Platform_FindLowerLinkableCandidate( ent ); if( candidate != NODE_INVALID ) { //base lower on cadidate's height lorg[0] = ( ent->r.maxs[0] - ent->r.mins[0] ) / 2 + ent->r.mins[0]; lorg[1] = ( ent->r.maxs[1] - ent->r.mins[1] ) / 2 + ent->r.mins[1]; lorg[2] = nodes[candidate].origin[2]; } else { lorg[0] = ( ent->r.maxs[0] - ent->r.mins[0] ) / 2 + ent->r.mins[0]; lorg[1] = ( ent->r.maxs[1] - ent->r.mins[1] ) / 2 + ent->r.mins[1]; lorg[2] = ent->r.mins[2] + platlip + 16; } // Upper node nodes[nav.num_nodes].flags = ( NODEFLAGS_PLATFORM|NODEFLAGS_SERVERLINK|NODEFLAGS_FLOAT ); nodes[nav.num_nodes].origin[0] = lorg[0]; nodes[nav.num_nodes].origin[1] = lorg[1]; nodes[nav.num_nodes].origin[2] = lorg[2] + plat_dist; #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); //put into ents table nav.navigableEnts[nav.num_navigableEnts].ent = ent; nav.navigableEnts[nav.num_navigableEnts].node = nav.num_nodes; nav.num_navigableEnts++; nav.num_nodes++; // Lower node nodes[nav.num_nodes].flags = ( NODEFLAGS_PLATFORM|NODEFLAGS_SERVERLINK|NODEFLAGS_FLOAT ); nodes[nav.num_nodes].origin[0] = lorg[0]; nodes[nav.num_nodes].origin[1] = lorg[1]; nodes[nav.num_nodes].origin[2] = lorg[2] + 16; #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); //put into ents table nav.navigableEnts[nav.num_navigableEnts].ent = ent; nav.navigableEnts[nav.num_navigableEnts].node = nav.num_nodes; nav.num_navigableEnts++; // link lower to upper AI_AddLink( nav.num_nodes, nav.num_nodes-1, LINK_PLATFORM ); //next nav.num_nodes++; return nav.num_nodes-1; }
/* * AI_AddNode_Door * Drop a node at each side of the door * and force them to link. Only typical * doors are covered. */ static int AI_AddNode_Door( edict_t *ent ) { edict_t *other; vec3_t mins, maxs; vec3_t door_origin, movedir, moveangles; vec3_t moveaxis[3]; vec3_t MOVEDIR_UP = { 0, 0, 1 }; float nodeOffset = NODE_DENSITY * 0.75f; int i, j; int dropped[4]; if( ent->flags & FL_TEAMSLAVE ) return NODE_INVALID; // only team master will drop the nodes for( i = 0; i < 4; i++ ) dropped[i] = NODE_INVALID; //make box formed by all team members boxes VectorCopy( ent->r.absmin, mins ); VectorCopy( ent->r.absmax, maxs ); for( other = ent->teamchain; other; other = other->teamchain ) { AddPointToBounds( other->r.absmin, mins, maxs ); AddPointToBounds( other->r.absmax, mins, maxs ); } for( i = 0; i < 3; i++ ) door_origin[i] = ( maxs[i] + mins[i] ) * 0.5; VectorSubtract( ent->moveinfo.end_origin, ent->moveinfo.start_origin, movedir ); VectorNormalizeFast( movedir ); VecToAngles( movedir, moveangles ); AnglesToAxis( moveangles, moveaxis ); //add nodes in "side" direction nodes[nav.num_nodes].flags = 0; VectorMA( door_origin, nodeOffset, moveaxis[1], nodes[nav.num_nodes].origin ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif if( AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ) ) { nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); dropped[0] = nav.num_nodes; nav.num_nodes++; } nodes[nav.num_nodes].flags = 0; VectorMA( door_origin, -nodeOffset, moveaxis[1], nodes[nav.num_nodes].origin ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif if( AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ) ) { nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); dropped[1] = nav.num_nodes; nav.num_nodes++; } // if moving in the Y axis drop also in the other crossing direction and hope the // bad ones are inhibited by a solid if( DotProduct( MOVEDIR_UP, moveaxis[0] ) > 0.8 || DotProduct( MOVEDIR_UP, moveaxis[0] ) < -0.8 ) { nodes[nav.num_nodes].flags = 0; VectorMA( door_origin, nodeOffset, moveaxis[2], nodes[nav.num_nodes].origin ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif if( AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ) ) { nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); dropped[2] = nav.num_nodes; nav.num_nodes++; } nodes[nav.num_nodes].flags = 0; VectorMA( door_origin, -nodeOffset, moveaxis[2], nodes[nav.num_nodes].origin ); #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( nodes[nav.num_nodes].origin, PATH_AMMO_BOX_MODEL ); #endif if( AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, NULL ) ) { nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, NULL ); dropped[3] = nav.num_nodes; nav.num_nodes++; } } // link those we dropped for( i = 0; i < 4; i++ ) { if( dropped[i] == NODE_INVALID ) continue; //put into ents table nav.navigableEnts[nav.num_navigableEnts].ent = ent; nav.navigableEnts[nav.num_navigableEnts].node = dropped[i]; nav.num_navigableEnts++; for( j = 0; j < 4; j++ ) { if( dropped[j] == NODE_INVALID ) continue; AI_AddLink( dropped[i], dropped[j], LINK_MOVE|LINK_DOOR ); } } return nav.num_nodes-1; }