1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 #include "b_local.h"
24 #include "g_nav.h"
25 #include "g_navigator.h"
26
27 //Global navigator
28 //CNavigator navigator;
29
30 extern qboolean G_EntIsUnlockedDoor( int entityNum );
31 extern qboolean G_EntIsDoor( int entityNum );
32 extern qboolean G_EntIsRemovableUsable( int entNum );
33 extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result );
34 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
35 //For debug graphics
36 extern void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha );
37 extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha );
38 extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha );
39 extern qboolean FlyingCreature( gentity_t *ent );
40
41
42 extern vec3_t NPCDEBUG_RED;
43
44
45 /*
46 -------------------------
47 NPC_SetMoveGoal
48 -------------------------
49 */
50
NPC_SetMoveGoal(gentity_t * ent,vec3_t point,int radius,qboolean isNavGoal,int combatPoint,gentity_t * targetEnt)51 void NPC_SetMoveGoal( gentity_t *ent, vec3_t point, int radius, qboolean isNavGoal, int combatPoint, gentity_t *targetEnt )
52 {
53 //Must be an NPC
54 if ( ent->NPC == NULL )
55 {
56 return;
57 }
58
59 if ( ent->NPC->tempGoal == NULL )
60 {//must still have a goal
61 return;
62 }
63
64 //Copy the origin
65 //VectorCopy( point, ent->NPC->goalPoint ); //FIXME: Make it use this, and this alone!
66 VectorCopy( point, ent->NPC->tempGoal->currentOrigin );
67
68 //Copy the mins and maxs to the tempGoal
69 VectorCopy( ent->mins, ent->NPC->tempGoal->mins );
70 VectorCopy( ent->mins, ent->NPC->tempGoal->maxs );
71
72 //FIXME: TESTING let's try making sure the tempGoal isn't stuck in the ground?
73 if ( 0 )
74 {
75 trace_t trace;
76 vec3_t bottom = {ent->NPC->tempGoal->currentOrigin[0],ent->NPC->tempGoal->currentOrigin[1],ent->NPC->tempGoal->currentOrigin[2]+ent->NPC->tempGoal->mins[2]};
77 gi.trace( &trace, ent->NPC->tempGoal->currentOrigin, vec3_origin, vec3_origin, bottom, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 );
78 if ( trace.fraction < 1.0f )
79 {//in the ground, raise it up
80 ent->NPC->tempGoal->currentOrigin[2] -= ent->NPC->tempGoal->mins[2]*(1.0f-trace.fraction)-0.125f;
81 }
82 }
83
84 ent->NPC->tempGoal->target = NULL;
85 ent->NPC->tempGoal->clipmask = ent->clipmask;
86 ent->NPC->tempGoal->svFlags &= ~SVF_NAVGOAL;
87 if ( targetEnt && targetEnt->waypoint >= 0 )
88 {
89 ent->NPC->tempGoal->waypoint = targetEnt->waypoint;
90 }
91 else
92 {
93 ent->NPC->tempGoal->waypoint = WAYPOINT_NONE;
94 }
95 ent->NPC->tempGoal->noWaypointTime = 0;
96
97 if ( isNavGoal )
98 {
99 assert(ent->NPC->tempGoal->owner);
100 ent->NPC->tempGoal->svFlags |= SVF_NAVGOAL;
101 }
102
103 ent->NPC->tempGoal->combatPoint = combatPoint;
104 ent->NPC->tempGoal->enemy = targetEnt;
105
106 ent->NPC->goalEntity = ent->NPC->tempGoal;
107 ent->NPC->goalRadius = radius;
108 ent->NPC->aiFlags &= ~NPCAI_STOP_AT_LOS;
109
110 gi.linkentity( ent->NPC->goalEntity );
111 }
112 /*
113 -------------------------
114 waypoint_testDirection
115 -------------------------
116 */
117
waypoint_testDirection(vec3_t origin,float yaw,float minDist)118 static float waypoint_testDirection( vec3_t origin, float yaw, float minDist )
119 {
120 vec3_t trace_dir, test_pos;
121 vec3_t maxs, mins;
122 trace_t tr;
123
124 //Setup the mins and max
125 VectorSet( maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2 );
126 VectorSet( mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2 + STEPSIZE );
127
128 //Get our test direction
129 vec3_t angles = { 0, yaw, 0 };
130 AngleVectors( angles, trace_dir, NULL, NULL );
131
132 //Move ahead
133 VectorMA( origin, minDist, trace_dir, test_pos );
134
135 gi.trace( &tr, origin, mins, maxs, test_pos, ENTITYNUM_NONE, ( CONTENTS_SOLID | CONTENTS_MONSTERCLIP | CONTENTS_BOTCLIP ), (EG2_Collision)0, 0 );
136
137 return ( minDist * tr.fraction ); //return actual dist completed
138 }
139
140 /*
141 -------------------------
142 waypoint_getRadius
143 -------------------------
144 */
145
waypoint_getRadius(gentity_t * ent)146 static float waypoint_getRadius( gentity_t *ent )
147 {
148 float minDist = MAX_RADIUS_CHECK + 1; // (unsigned int) -1;
149 float dist;
150
151 for ( int i = 0; i < YAW_ITERATIONS; i++ )
152 {
153 dist = waypoint_testDirection( ent->currentOrigin, ((360.0f/YAW_ITERATIONS) * i), minDist );
154
155 if ( dist < minDist )
156 minDist = dist;
157 }
158
159 return minDist + DEFAULT_MAXS_0;
160 }
161
162 /*QUAKED waypoint (0.7 0.7 0) (-20 -20 -24) (20 20 45) SOLID_OK DROP_TO_FLOOR
163 a place to go.
164
165 SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
166 DROP_TO_FLOOR - will cause the point to auto drop to the floor
167
168 radius is automatically calculated in-world.
169 "targetJump" is a special edge that only guys who can jump will cross (so basically Jedi)
170 */
171 extern int delayedShutDown;
SP_waypoint(gentity_t * ent)172 void SP_waypoint ( gentity_t *ent )
173 {
174 VectorSet(ent->mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2);
175 VectorSet(ent->maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2);
176
177 ent->contents = CONTENTS_TRIGGER;
178 ent->clipmask = MASK_DEADSOLID;
179
180 gi.linkentity( ent );
181
182 ent->count = -1;
183 ent->classname = "waypoint";
184
185 if (ent->spawnflags&2)
186 {
187 ent->currentOrigin[2] += 128.0f;
188 }
189
190 if( !(ent->spawnflags&1) && G_CheckInSolid (ent, qtrue))
191 {//if not SOLID_OK, and in solid
192 ent->maxs[2] = CROUCH_MAXS_2;
193 if(G_CheckInSolid (ent, qtrue))
194 {
195 gi.Printf(S_COLOR_RED"ERROR: Waypoint %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
196 assert(0 && "Waypoint in solid!");
197 // if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
198 // G_Error("Waypoint %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
199 // }
200 delayedShutDown = level.time + 100;
201 G_FreeEntity(ent);
202 return;
203 }
204 }
205
206 //G_SpawnString("targetJump", "", &ent->targetJump);
207 ent->radius = waypoint_getRadius( ent );
208 NAV::SpawnedPoint(ent);
209
210 G_FreeEntity(ent);
211 return;
212 }
213
214 /*QUAKED waypoint_small (0.7 0.7 0) (-2 -2 -24) (2 2 32) SOLID_OK
215 SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
216 DROP_TO_FLOOR - will cause the point to auto drop to the floor
217 */
SP_waypoint_small(gentity_t * ent)218 void SP_waypoint_small (gentity_t *ent)
219 {
220 VectorSet(ent->mins, -2, -2, DEFAULT_MINS_2);
221 VectorSet(ent->maxs, 2, 2, DEFAULT_MAXS_2);
222
223 ent->contents = CONTENTS_TRIGGER;
224 ent->clipmask = MASK_DEADSOLID;
225
226 gi.linkentity( ent );
227
228 ent->count = -1;
229 ent->classname = "waypoint";
230
231 if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qtrue ) )
232 {
233 ent->maxs[2] = CROUCH_MAXS_2;
234 if ( G_CheckInSolid( ent, qtrue ) )
235 {
236 gi.Printf(S_COLOR_RED"ERROR: Waypoint_small %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
237 assert(0);
238 #ifndef FINAL_BUILD
239 if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
240 G_Error("Waypoint_small %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
241 }
242 #endif
243 G_FreeEntity(ent);
244 return;
245 }
246 }
247
248 ent->radius = 2; // radius
249 NAV::SpawnedPoint(ent);
250
251 G_FreeEntity(ent);
252 return;
253 }
254
255
256 /*QUAKED waypoint_navgoal (0.3 1 0.3) (-20 -20 -24) (20 20 40) SOLID_OK DROP_TO_FLOOR NO_AUTO_CONNECT
257 A waypoint for script navgoals
258 Not included in navigation data
259
260 DROP_TO_FLOOR - will cause the point to auto drop to the floor
261 NO_AUTO_CONNECT - will not automatically connect to any other points, you must then connect it by hand
262
263
264 SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
265
266 targetname - name you would use in script when setting a navgoal (like so:)
267
268 For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so:
269
270 set ("navgoal", "console");
271
272 radius - how far from the navgoal an ent can be before it thinks it reached it - default is "0" which means no radius check, just have to touch it
273
274 */
275
SP_waypoint_navgoal(gentity_t * ent)276 void SP_waypoint_navgoal( gentity_t *ent )
277 {
278 int radius = ( ent->radius ) ? (ent->radius) : 12;
279
280 VectorSet( ent->mins, -16, -16, -24 );
281 VectorSet( ent->maxs, 16, 16, 32 );
282 ent->s.origin[2] += 0.125;
283 if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) )
284 {
285 gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
286 assert(0);
287 #ifndef FINAL_BUILD
288 if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
289 G_Error("Waypoint_navgoal %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
290 }
291 #endif
292 }
293 TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, radius, RTF_NAVGOAL );
294
295 ent->classname = "navgoal";
296
297 NAV::SpawnedPoint(ent, NAV::PT_GOALNODE);
298
299 G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe?
300 }
301
302 /*
303 -------------------------
304 Svcmd_Nav_f
305 -------------------------
306 */
307
Svcmd_Nav_f(void)308 void Svcmd_Nav_f( void )
309 {
310 const char *cmd = gi.argv( 1 );
311
312 if ( Q_stricmp( cmd, "show" ) == 0 )
313 {
314 cmd = gi.argv( 2 );
315
316 if ( Q_stricmp( cmd, "all" ) == 0 )
317 {
318 NAVDEBUG_showNodes = !NAVDEBUG_showNodes;
319
320 //NOTENOTE: This causes the two states to sync up if they aren't already
321 NAVDEBUG_showCollision = NAVDEBUG_showNavGoals =
322 NAVDEBUG_showCombatPoints = NAVDEBUG_showEnemyPath =
323 NAVDEBUG_showEdges = NAVDEBUG_showNearest = NAVDEBUG_showRadius = NAVDEBUG_showNodes;
324 }
325 else if ( Q_stricmp( cmd, "nodes" ) == 0 )
326 {
327 NAVDEBUG_showNodes = !NAVDEBUG_showNodes;
328 }
329 else if ( Q_stricmp( cmd, "radius" ) == 0 )
330 {
331 NAVDEBUG_showRadius = !NAVDEBUG_showRadius;
332 }
333 else if ( Q_stricmp( cmd, "edges" ) == 0 )
334 {
335 NAVDEBUG_showEdges = !NAVDEBUG_showEdges;
336 }
337 else if ( Q_stricmp( cmd, "testpath" ) == 0 )
338 {
339 NAVDEBUG_showTestPath = !NAVDEBUG_showTestPath;
340 }
341 else if ( Q_stricmp( cmd, "enemypath" ) == 0 )
342 {
343 NAVDEBUG_showEnemyPath = !NAVDEBUG_showEnemyPath;
344 }
345 else if ( Q_stricmp( cmd, "combatpoints" ) == 0 )
346 {
347 NAVDEBUG_showCombatPoints = !NAVDEBUG_showCombatPoints;
348 }
349 else if ( Q_stricmp( cmd, "navgoals" ) == 0 )
350 {
351 NAVDEBUG_showNavGoals = !NAVDEBUG_showNavGoals;
352 }
353 else if ( Q_stricmp( cmd, "collision" ) == 0 )
354 {
355 NAVDEBUG_showCollision = !NAVDEBUG_showCollision;
356 }
357 else if ( Q_stricmp( cmd, "grid" ) == 0 )
358 {
359 NAVDEBUG_showGrid = !NAVDEBUG_showGrid;
360 }
361 else if ( Q_stricmp( cmd, "nearest" ) == 0 )
362 {
363 NAVDEBUG_showNearest = !NAVDEBUG_showNearest;
364 }
365 else if ( Q_stricmp( cmd, "lines" ) == 0 )
366 {
367 NAVDEBUG_showPointLines = !NAVDEBUG_showPointLines;
368 }
369 }
370 else if ( Q_stricmp( cmd, "set" ) == 0 )
371 {
372 cmd = gi.argv( 2 );
373
374 if ( Q_stricmp( cmd, "testgoal" ) == 0 )
375 {
376 // NAVDEBUG_curGoal = navigator.GetNearestNode( &g_entities[0], g_entities[0].waypoint, NF_CLEAR_PATH, WAYPOINT_NONE );
377 }
378 }
379 else if ( Q_stricmp( cmd, "goto" ) == 0 )
380 {
381 cmd = gi.argv( 2 );
382 NAV::TeleportTo(&(g_entities[0]), cmd);
383 }
384 else if ( Q_stricmp( cmd, "gotonum" ) == 0 )
385 {
386 cmd = gi.argv( 2 );
387 NAV::TeleportTo(&(g_entities[0]), atoi(cmd));
388 }
389 else if ( Q_stricmp( cmd, "totals" ) == 0 )
390 {
391 NAV::ShowStats();
392 }
393 else
394 {
395 //Print the available commands
396 Com_Printf("nav - valid commands\n---\n" );
397 Com_Printf("show\n - nodes\n - edges\n - testpath\n - enemypath\n - combatpoints\n - navgoals\n---\n");
398 Com_Printf("goto\n ---\n" );
399 Com_Printf("gotonum\n ---\n" );
400 Com_Printf("totals\n ---\n" );
401 Com_Printf("set\n - testgoal\n---\n" );
402 }
403 }
404
405 //
406 //JWEIER ADDITIONS START
407
408 bool navCalculatePaths = false;
409
410 bool NAVDEBUG_showNodes = false;
411 bool NAVDEBUG_showRadius = false;
412 bool NAVDEBUG_showEdges = false;
413 bool NAVDEBUG_showTestPath = false;
414 bool NAVDEBUG_showEnemyPath = false;
415 bool NAVDEBUG_showCombatPoints = false;
416 bool NAVDEBUG_showNavGoals = false;
417 bool NAVDEBUG_showCollision = false;
418 int NAVDEBUG_curGoal = 0;
419 bool NAVDEBUG_showGrid = false;
420 bool NAVDEBUG_showNearest = false;
421 bool NAVDEBUG_showPointLines = false;
422
423
424 //
425 //JWEIER ADDITIONS END
426