1 ///////////////////////////////////////////////////////////////////////
2 //
3 //  ACE - Quake II Bot Base Code
4 //
5 //  Version 1.0
6 //
7 //  This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
8 //
9 //
10 //	All other files are Copyright(c) Id Software, Inc.
11 //
12 //	Please see liscense.txt in the source directory for the copyright
13 //	information regarding those files belonging to Id Software, Inc.
14 //
15 //	Should you decide to release a modified version of ACE, you MUST
16 //	include the following text (minus the BEGIN and END lines) in the
17 //	documentation for your modification.
18 //
19 //	--- BEGIN ---
20 //
21 //	The ACE Bot is a product of Steve Yeager, and is available from
22 //	the ACE Bot homepage, at http://www.axionfx.com/ace.
23 //
24 //	This program is a modification of the ACE Bot, and is therefore
25 //	in NO WAY supported by Steve Yeager.
26 
27 //	This program MUST NOT be sold in ANY form. If you have paid for
28 //	this product, you should contact Steve Yeager immediately, via
29 //	the ACE Bot homepage.
30 //
31 //	--- END ---
32 //
33 //	I, Steve Yeager, hold no responsibility for any harm caused by the
34 //	use of this source code, especially to small children and animals.
35 //  It is provided as-is with no implied warranty or support.
36 //
37 //  I also wish to thank and acknowledge the great work of others
38 //  that has helped me to develop this code.
39 //
40 //  John Cricket    - For ideas and swapping code.
41 //  Ryan Feltrin    - For ideas and swapping code.
42 //  SABIN           - For showing how to do true client based movement.
43 //  BotEpidemic     - For keeping us up to date.
44 //  Telefragged.com - For giving ACE a home.
45 //  Microsoft       - For giving us such a wonderful crash free OS.
46 //  id              - Need I say more.
47 //
48 //  And to all the other testers, pathers, and players and people
49 //  who I can't remember who the heck they were, but helped out.
50 //
51 ///////////////////////////////////////////////////////////////////////
52 
53 ///////////////////////////////////////////////////////////////////////
54 //
55 //  acebot_nodes.c -   This file contains all of the
56 //                     pathing routines for the ACE bot.
57 //
58 ///////////////////////////////////////////////////////////////////////
59 
60 #include "../g_local.h"
61 #include "acebot.h"
62 
63 // flags
64 qboolean newmap=true;
65 
66 // Total number of nodes that are items
67 int numitemnodes;
68 
69 // Total number of nodes
70 int numnodes;
71 
72 // For debugging paths
73 int show_path_from = -1;
74 int show_path_to = -1;
75 
76 // array for node data
77 node_t nodes[MAX_NODES];
78 short int path_table[MAX_NODES][MAX_NODES];
79 
80 ///////////////////////////////////////////////////////////////////////
81 // NODE INFORMATION FUNCTIONS
82 ///////////////////////////////////////////////////////////////////////
83 
84 ///////////////////////////////////////////////////////////////////////
85 // Determin cost of moving from one node to another
86 ///////////////////////////////////////////////////////////////////////
ACEND_FindCost(int from,int to)87 int ACEND_FindCost(int from, int to)
88 {
89 	int curnode;
90 	int cost=1; // Shortest possible is 1
91 
92 	// If we can not get there then return invalid
93 	if (path_table[from][to] == INVALID)
94 		return INVALID;
95 
96 	// Otherwise check the path and return the cost
97 	curnode = path_table[from][to];
98 
99 	// Find a path (linear time, very fast)
100 	while(curnode != to)
101 	{
102 		curnode = path_table[curnode][to];
103 		if(curnode == INVALID) // something has corrupted the path abort
104 			return INVALID;
105 		cost++;
106 	}
107 
108 	return cost;
109 }
110 
111 ///////////////////////////////////////////////////////////////////////
112 // Find a close node to the player within dist.
113 //
114 // Faster than looking for the closest node, but not very
115 // accurate.
116 ///////////////////////////////////////////////////////////////////////
ACEND_FindCloseReachableNode(edict_t * self,int range,int type)117 int ACEND_FindCloseReachableNode(edict_t *self, int range, int type)
118 {
119 	vec3_t v;
120 	int i;
121 	trace_t tr;
122 	float dist;
123 
124 	range *= range;
125 
126 	for(i=0;i<numnodes;i++)
127 	{
128 		if(type == NODE_ALL || type == nodes[i].type) // check node type
129 		{
130 
131 			VectorSubtract(nodes[i].origin,self->s.origin,v); // subtract first
132 
133 			dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
134 
135 			if(dist < range) // square range instead of sqrt
136 			{
137 				// make sure it is visible
138 				//trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, nodes[i].origin, self, MASK_OPAQUE);
139 				tr = gi.trace (self->s.origin, self->mins, self->maxs, nodes[i].origin, self, MASK_OPAQUE);
140 
141 				if(tr.fraction == 1.0)
142 					return i;
143 			}
144 		}
145 	}
146 
147 	return -1;
148 }
149 
150 ///////////////////////////////////////////////////////////////////////
151 // Find the closest node to the player within a certain range
152 ///////////////////////////////////////////////////////////////////////
ACEND_FindClosestReachableNode(edict_t * self,int range,int type)153 int ACEND_FindClosestReachableNode(edict_t *self, int range, int type)
154 {
155 	int i;
156 	float closest = 99999;
157 	float dist;
158 	int node=-1;
159 	vec3_t v;
160 	trace_t tr;
161 	float rng;
162 	vec3_t maxs,mins;
163 
164 	VectorCopy(self->mins,mins);
165 	VectorCopy(self->maxs,maxs);
166 
167 	// For Ladders, do not worry so much about reachability
168 	if(type == NODE_LADDER)
169 	{
170 		VectorCopy(vec3_origin,maxs);
171 		VectorCopy(vec3_origin,mins);
172 	}
173 	else
174 		mins[2] += 18; // Stepsize
175 
176 	rng = (float)(range * range); // square range for distance comparison (eliminate sqrt)
177 
178 	for(i=0;i<numnodes;i++)
179 	{
180 		if(type == NODE_ALL || type == nodes[i].type) // check node type
181 		{
182 			VectorSubtract(nodes[i].origin, self->s.origin,v); // subtract first
183 
184 			dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
185 
186 			if(dist < closest && dist < rng)
187 			{
188 				// make sure it is visible
189 				tr = gi.trace (self->s.origin, mins, maxs, nodes[i].origin, self, MASK_OPAQUE);
190 				if(tr.fraction == 1.0)
191 				{
192 					node = i;
193 					closest = dist;
194 				}
195 			}
196 		}
197 	}
198 
199 	return node;
200 }
201 
202 ///////////////////////////////////////////////////////////////////////
203 // BOT NAVIGATION ROUTINES
204 ///////////////////////////////////////////////////////////////////////
205 
206 ///////////////////////////////////////////////////////////////////////
207 // Set up the goal
208 ///////////////////////////////////////////////////////////////////////
ACEND_SetGoal(edict_t * self,int goal_node)209 void ACEND_SetGoal(edict_t *self, int goal_node)
210 {
211 	int node;
212 
213 	self->goal_node = goal_node;
214 	node = ACEND_FindClosestReachableNode(self, NODE_DENSITY*3, NODE_ALL);
215 
216 	if(node == -1)
217 		return;
218 
219 	if(debug_mode)
220 		debug_printf("%s new start node selected %d\n",self->client->pers.netname,node);
221 
222 
223 	self->current_node = node;
224 	self->next_node = self->current_node; // make sure we get to the nearest node first
225 	self->node_timeout = 0;
226 
227 }
228 
229 ///////////////////////////////////////////////////////////////////////
230 // Move closer to goal by pointing the bot to the next node
231 // that is closer to the goal
232 ///////////////////////////////////////////////////////////////////////
ACEND_FollowPath(edict_t * self)233 qboolean ACEND_FollowPath(edict_t *self)
234 {
235 	vec3_t v;
236 
237 	//////////////////////////////////////////
238 	// Show the path (uncomment for debugging)
239 //	show_path_from = self->current_node;
240 //	show_path_to = self->goal_node;
241 //	ACEND_DrawPath();
242 	//////////////////////////////////////////
243 
244 	// Try again?
245 	if(self->node_timeout ++ > 30)
246 	{
247 		if(self->tries++ > 3)
248 			return false;
249 		else
250 			ACEND_SetGoal(self,self->goal_node);
251 	}
252 
253 	// Are we there yet?
254 	VectorSubtract(self->s.origin,nodes[self->next_node].origin,v);
255 
256 	if(VectorLength(v) < 32)
257 	{
258 		// reset timeout
259 		self->node_timeout = 0;
260 
261 		if(self->next_node == self->goal_node)
262 		{
263 			if(debug_mode)
264 				debug_printf("%s reached goal!\n",self->client->pers.netname);
265 
266 			ACEAI_PickLongRangeGoal(self); // Pick a new goal
267 		}
268 		else
269 		{
270 			self->current_node = self->next_node;
271 			self->next_node = path_table[self->current_node][self->goal_node];
272 		}
273 	}
274 
275 	if(self->current_node == -1 || self->next_node ==-1)
276 		return false;
277 
278 	// Set bot's movement vector
279 	VectorSubtract (nodes[self->next_node].origin, self->s.origin , self->move_vector);
280 
281 	return true;
282 }
283 
284 
285 ///////////////////////////////////////////////////////////////////////
286 // MAPPING CODE
287 ///////////////////////////////////////////////////////////////////////
288 
289 ///////////////////////////////////////////////////////////////////////
290 // Capture when the grappling hook has been fired for mapping purposes.
291 ///////////////////////////////////////////////////////////////////////
292 /*void ACEND_GrapFired(edict_t *self)
293 {
294 	int closest_node;
295 
296 	if(!self->owner)
297 		return; // should not be here
298 
299 	// Check to see if the grapple is in pull mode
300 	if(self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL)
301 	{
302 		// Look for the closest node of type grapple
303 		closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_GRAPPLE);
304 		if(closest_node == -1 ) // we need to drop a node
305 		{
306 			closest_node = ACEND_AddNode(self,NODE_GRAPPLE);
307 
308 			// Add an edge
309 			ACEND_UpdateNodeEdge(self->owner->last_node,closest_node);
310 
311 			self->owner->last_node = closest_node;
312 		}
313 		else
314 			self->owner->last_node = closest_node; // zero out so other nodes will not be linked
315 	}
316 }
317 */
318 
319 ///////////////////////////////////////////////////////////////////////
320 // Check for adding ladder nodes
321 ///////////////////////////////////////////////////////////////////////
ACEND_CheckForLadder(edict_t * self)322 qboolean ACEND_CheckForLadder(edict_t *self)
323 {
324 	int closest_node;
325 
326 	// If there is a ladder and we are moving up, see if we should add a ladder node
327 	if (gi.pointcontents(self->s.origin) & CONTENTS_LADDER && self->velocity[2] > 0)
328 	{
329 		//debug_printf("contents: %x\n",tr.contents);
330 
331 		closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_LADDER);
332 		if(closest_node == -1)
333 		{
334 			closest_node = ACEND_AddNode(self,NODE_LADDER);
335 
336 			// Now add link
337 		    ACEND_UpdateNodeEdge(self->last_node,closest_node);
338 
339 			// Set current to last
340 			self->last_node = closest_node;
341 		}
342 		else
343 		{
344 			ACEND_UpdateNodeEdge(self->last_node,closest_node);
345 			self->last_node = closest_node; // set visited to last
346 		}
347 		return true;
348 	}
349 	return false;
350 }
351 
352 ///////////////////////////////////////////////////////////////////////
353 // This routine is called to hook in the pathing code and sets
354 // the current node if valid.
355 ///////////////////////////////////////////////////////////////////////
ACEND_PathMap(edict_t * self)356 void ACEND_PathMap(edict_t *self)
357 {
358 	int closest_node;
359 	static float last_update=0; // start off low
360 	vec3_t v;
361 
362 	if(level.time < last_update)
363 		return;
364 
365 	last_update = level.time + 0.15; // slow down updates a bit
366 
367 	// Special node drawing code for debugging
368     if(show_path_to != -1)
369 		ACEND_DrawPath();
370 
371 	////////////////////////////////////////////////////////
372 	// Special check for ladder nodes
373 	///////////////////////////////////////////////////////
374 	if(ACEND_CheckForLadder(self)) // check for ladder nodes
375 		return;
376 
377 	// Not on ground, and not in the water, so bail
378     if(!self->groundentity && !self->waterlevel)
379 		return;
380 
381 	////////////////////////////////////////////////////////
382 	// Lava/Slime
383 	////////////////////////////////////////////////////////
384 	VectorCopy(self->s.origin,v);
385 	v[2] -= 18;
386 	if(gi.pointcontents(v) & (CONTENTS_LAVA|CONTENTS_SLIME))
387 		return; // no nodes in slime
388 
389     ////////////////////////////////////////////////////////
390 	// Jumping
391 	///////////////////////////////////////////////////////
392 	if(self->is_jumping)
393 	{
394 	   // See if there is a closeby jump landing node (prevent adding too many)
395 		closest_node = ACEND_FindClosestReachableNode(self, 64, NODE_JUMP);
396 
397 		if(closest_node == INVALID)
398 			closest_node = ACEND_AddNode(self,NODE_JUMP);
399 
400 		// Now add link
401 		if(self->last_node != -1)
402 			ACEND_UpdateNodeEdge(self->last_node, closest_node);
403 
404 		self->is_jumping = false;
405 		return;
406 	}
407 
408 	////////////////////////////////////////////////////////////
409 	// Grapple
410 	// Do not add nodes during grapple, added elsewhere manually
411 	////////////////////////////////////////////////////////////
412 	/*if(ctf->value && self->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL)
413 		return;*/
414 
415 	// Iterate through all nodes to make sure far enough apart
416 	closest_node = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL);
417 
418 	////////////////////////////////////////////////////////
419 	// Special Check for Platforms
420 	////////////////////////////////////////////////////////
421 	if(self->groundentity && self->groundentity->use == Use_Plat)
422 	{
423 		if(closest_node == INVALID)
424 			return; // Do not want to do anything here.
425 
426 		// Here we want to add links
427 		if(closest_node != self->last_node && self->last_node != INVALID)
428 			ACEND_UpdateNodeEdge(self->last_node,closest_node);
429 
430 		self->last_node = closest_node; // set visited to last
431 		return;
432 	}
433 
434 	 ////////////////////////////////////////////////////////
435 	 // Add Nodes as needed
436 	 ////////////////////////////////////////////////////////
437 	 if(closest_node == INVALID)
438 	 {
439 		// Add nodes in the water as needed
440 		if(self->waterlevel)
441 			closest_node = ACEND_AddNode(self,NODE_WATER);
442 		else
443 		    closest_node = ACEND_AddNode(self,NODE_MOVE);
444 
445 		// Now add link
446 		if(self->last_node != -1)
447 			ACEND_UpdateNodeEdge(self->last_node, closest_node);
448 
449 	 }
450 	 else if(closest_node != self->last_node && self->last_node != INVALID)
451 	 	ACEND_UpdateNodeEdge(self->last_node,closest_node);
452 
453 	 self->last_node = closest_node; // set visited to last
454 
455 }
456 
457 ///////////////////////////////////////////////////////////////////////
458 // Init node array (set all to INVALID)
459 ///////////////////////////////////////////////////////////////////////
ACEND_InitNodes(void)460 void ACEND_InitNodes(void)
461 {
462 	numnodes = 1;
463 	numitemnodes = 1;
464 	memset(nodes,0,sizeof(node_t) * MAX_NODES);
465 	memset(path_table,INVALID,sizeof(short int)*MAX_NODES*MAX_NODES);
466 
467 }
468 
469 ///////////////////////////////////////////////////////////////////////
470 // Show the node for debugging (utility function)
471 ///////////////////////////////////////////////////////////////////////
ACEND_ShowNode(int node)472 void ACEND_ShowNode(int node)
473 {
474 	edict_t *ent;
475 
476 	return; // commented out for now. uncommend to show nodes during debugging,
477 	        // but too many will cause overflows. You have been warned.
478 
479 	ent = G_Spawn();
480 
481 	ent->movetype = MOVETYPE_NONE;
482 	ent->solid = SOLID_NOT;
483 
484 	if(nodes[node].type == NODE_MOVE)
485 		ent->s.renderfx = RF_SHELL_BLUE;
486 	else if (nodes[node].type == NODE_WATER)
487 		ent->s.renderfx = RF_SHELL_RED;
488 	else
489 		ent->s.renderfx = RF_SHELL_GREEN; // action nodes
490 
491 	ent->s.modelindex = gi.modelindex ("models/items/ammo/grenades/medium/tris.md2");
492 	ent->owner = ent;
493 	ent->nextthink = level.time + 200000.0;
494 	ent->think = G_FreeEdict;
495 	ent->dmg = 0;
496 
497 	VectorCopy(nodes[node].origin,ent->s.origin);
498 	gi.linkentity (ent);
499 
500 }
501 
502 ///////////////////////////////////////////////////////////////////////
503 // Draws the current path (utility function)
504 ///////////////////////////////////////////////////////////////////////
ACEND_DrawPath()505 void ACEND_DrawPath()
506 {
507 	int current_node, goal_node, next_node;
508 
509 	current_node = show_path_from;
510 	goal_node = show_path_to;
511 
512 	next_node = path_table[current_node][goal_node];
513 
514 	// Now set up and display the path
515 	while(current_node != goal_node && current_node != -1)
516 	{
517 		gi.WriteByte (svc_temp_entity);
518 		gi.WriteByte (TE_BFG_LASER);
519 		gi.WritePosition (nodes[current_node].origin);
520 		gi.WritePosition (nodes[next_node].origin);
521 		gi.multicast (nodes[current_node].origin, MULTICAST_PVS);
522 		current_node = next_node;
523 		next_node = path_table[current_node][goal_node];
524 	}
525 }
526 
527 ///////////////////////////////////////////////////////////////////////
528 // Turns on showing of the path, set goal to -1 to
529 // shut off. (utility function)
530 ///////////////////////////////////////////////////////////////////////
ACEND_ShowPath(edict_t * self,int goal_node)531 void ACEND_ShowPath(edict_t *self, int goal_node)
532 {
533 	show_path_from = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL);
534 	show_path_to = goal_node;
535 }
536 
537 ///////////////////////////////////////////////////////////////////////
538 // Add a node of type ?
539 ///////////////////////////////////////////////////////////////////////
ACEND_AddNode(edict_t * self,int type)540 int ACEND_AddNode(edict_t *self, int type)
541 {
542 	vec3_t v1,v2;
543 
544 	// Block if we exceed maximum
545 	if (numnodes + 1 > MAX_NODES)
546 		return false;
547 
548 	// Set location
549 	VectorCopy(self->s.origin,nodes[numnodes].origin);
550 
551 	// Set type
552 	nodes[numnodes].type = type;
553 
554 	/////////////////////////////////////////////////////
555 	// ITEMS
556 	// Move the z location up just a bit.
557 	if(type == NODE_ITEM)
558 	{
559 		nodes[numnodes].origin[2] += 16;
560 		numitemnodes++;
561 	}
562 
563 	// Teleporters
564 	if(type == NODE_TELEPORTER)
565 	{
566 		// Up 32
567 		nodes[numnodes].origin[2] += 32;
568 	}
569 
570 	if(type == NODE_LADDER)
571 	{
572 		nodes[numnodes].type = NODE_LADDER;
573 
574 		if(debug_mode)
575 		{
576 			debug_printf("Node added %d type: Ladder\n",numnodes);
577 			ACEND_ShowNode(numnodes);
578 		}
579 
580 		numnodes++;
581 		return numnodes-1; // return the node added
582 
583 	}
584 
585 	// For platforms drop two nodes one at top, one at bottom
586 	if(type == NODE_PLATFORM)
587 	{
588 		VectorCopy(self->maxs,v1);
589 		VectorCopy(self->mins,v2);
590 
591 		// To get the center
592 		nodes[numnodes].origin[0] = (v1[0] - v2[0]) / 2 + v2[0];
593 		nodes[numnodes].origin[1] = (v1[1] - v2[1]) / 2 + v2[1];
594 		nodes[numnodes].origin[2] = self->maxs[2];
595 
596 		if(debug_mode)
597 			ACEND_ShowNode(numnodes);
598 
599 		numnodes++;
600 
601 		nodes[numnodes].origin[0] = nodes[numnodes-1].origin[0];
602 		nodes[numnodes].origin[1] = nodes[numnodes-1].origin[1];
603 		nodes[numnodes].origin[2] = self->mins[2]+64;
604 
605 		nodes[numnodes].type = NODE_PLATFORM;
606 
607 		// Add a link
608 		ACEND_UpdateNodeEdge(numnodes,numnodes-1);
609 
610 		if(debug_mode)
611 		{
612 			debug_printf("Node added %d type: Platform\n",numnodes);
613 			ACEND_ShowNode(numnodes);
614 		}
615 
616 		numnodes++;
617 
618 		return numnodes -1;
619 	}
620 
621 	if(debug_mode)
622 	{
623 		if(nodes[numnodes].type == NODE_MOVE)
624 			debug_printf("Node added %d type: Move\n",numnodes);
625 		else if(nodes[numnodes].type == NODE_TELEPORTER)
626 			debug_printf("Node added %d type: Teleporter\n",numnodes);
627 		else if(nodes[numnodes].type == NODE_ITEM)
628 			debug_printf("Node added %d type: Item\n",numnodes);
629 		else if(nodes[numnodes].type == NODE_WATER)
630 			debug_printf("Node added %d type: Water\n",numnodes);
631 		else if(nodes[numnodes].type == NODE_GRAPPLE)
632 			debug_printf("Node added %d type: Grapple\n",numnodes);
633 
634 		ACEND_ShowNode(numnodes);
635 	}
636 
637 	numnodes++;
638 
639 	return numnodes-1; // return the node added
640 }
641 
642 ///////////////////////////////////////////////////////////////////////
643 // Add/Update node connections (paths)
644 ///////////////////////////////////////////////////////////////////////
ACEND_UpdateNodeEdge(int from,int to)645 void ACEND_UpdateNodeEdge(int from, int to)
646 {
647 	int i;
648 
649 	if(from == -1 || to == -1 || from == to)
650 		return; // safety
651 
652 	// Add the link
653 	path_table[from][to] = to;
654 
655 	// Now for the self-referencing part, linear time for each link added
656 	for(i=0;i<numnodes;i++)
657 		if(path_table[i][from] != INVALID)
658 			if(i == to)
659 				path_table[i][to] = INVALID; // make sure we terminate
660 			else
661 				path_table[i][to] = path_table[i][from];
662 
663 	if(debug_mode)
664 		debug_printf("Link %d -> %d\n", from, to);
665 }
666 
667 ///////////////////////////////////////////////////////////////////////
668 // Remove a node edge
669 ///////////////////////////////////////////////////////////////////////
ACEND_RemoveNodeEdge(edict_t * self,int from,int to)670 void ACEND_RemoveNodeEdge(edict_t *self, int from, int to)
671 {
672 	int i;
673 
674 	if(debug_mode)
675 		debug_printf("%s: Removing Edge %d -> %d\n", self->client->pers.netname, from, to);
676 
677 	path_table[from][to] = INVALID; // set to invalid
678 
679 	// Make sure this gets updated in our path array
680 	for(i=0;i<numnodes;i++)
681 		if(path_table[from][i] == to)
682 			path_table[from][i] = INVALID;
683 }
684 
685 ///////////////////////////////////////////////////////////////////////
686 // This function will resolve all paths that are incomplete
687 // usually called before saving to disk
688 ///////////////////////////////////////////////////////////////////////
ACEND_ResolveAllPaths()689 void ACEND_ResolveAllPaths()
690 {
691 	int i, from, to;
692 	int num=0;
693 
694 	safe_bprintf(PRINT_HIGH,"Resolving all paths...");
695 
696 	for(from=0;from<numnodes;from++)
697 	for(to=0;to<numnodes;to++)
698 	{
699 		// update unresolved paths
700 		// Not equal to itself, not equal to -1 and equal to the last link
701 		if(from != to && path_table[from][to] == to)
702 		{
703 			num++;
704 
705 			// Now for the self-referencing part linear time for each link added
706 			for(i=0;i<numnodes;i++)
707 				if(path_table[i][from] != -1)
708 					if(i == to)
709 						path_table[i][to] = -1; // make sure we terminate
710 					else
711 						path_table[i][to] = path_table[i][from];
712 		}
713 	}
714 
715 	safe_bprintf(PRINT_MEDIUM,"done (%d updated)\n",num);
716 }
717 
718 ///////////////////////////////////////////////////////////////////////
719 // Save to disk file
720 //
721 // Since my compression routines are one thing I did not want to
722 // release, I took out the compressed format option. Most levels will
723 // save out to a node file around 50-200k, so compression is not really
724 // a big deal.
725 ///////////////////////////////////////////////////////////////////////
ACEND_SaveNodes()726 void ACEND_SaveNodes()
727 {
728 	FILE *pOut;
729 	char filename[60];
730 	int i,j;
731 	int version = 1;
732 
733 	// Resolve paths
734 	ACEND_ResolveAllPaths();
735 
736 	safe_bprintf(PRINT_MEDIUM,"Saving node table...");
737 
738 	strcpy(filename,"lights/nav/");
739 	strcat(filename,level.mapname);
740 	strcat(filename,".nod");
741 
742 	if((pOut = fopen(filename, "wb" )) == NULL)
743 		return; // bail
744 
745 	fwrite(&version,sizeof(int),1,pOut); // write version
746 	fwrite(&numnodes,sizeof(int),1,pOut); // write count
747 	fwrite(&num_items,sizeof(int),1,pOut); // write facts count
748 
749 	fwrite(nodes,sizeof(node_t),numnodes,pOut); // write nodes
750 
751 	for(i=0;i<numnodes;i++)
752 		for(j=0;j<numnodes;j++)
753 			fwrite(&path_table[i][j],sizeof(short int),1,pOut); // write count
754 
755 	fwrite(item_table,sizeof(item_table_t),num_items,pOut); 		// write out the fact table
756 
757 	fclose(pOut);
758 
759 	safe_bprintf(PRINT_MEDIUM,"done.\n");
760 }
761 
762 ///////////////////////////////////////////////////////////////////////
763 // Read from disk file
764 ///////////////////////////////////////////////////////////////////////
ACEND_LoadNodes(void)765 void ACEND_LoadNodes(void)
766 {
767 	FILE *pIn;
768 	int i,j;
769 	char filename[60];
770 	int version;
771 
772 	strcpy(filename,"lights/nav/");
773 	strcat(filename,level.mapname);
774 	strcat(filename,".nod");
775 
776 	if((pIn = fopen(filename, "rb" )) == NULL)
777     {
778 		// Create item table
779 		safe_bprintf(PRINT_MEDIUM, "ACE: No node file found, creating new one...");
780 		ACEIT_BuildItemNodeTable(false);
781 		safe_bprintf(PRINT_MEDIUM, "done.\n");
782 		return;
783 	}
784 
785 	// determin version
786 	fread(&version,sizeof(int),1,pIn); // read version
787 
788 	if(version == 1)
789 	{
790 		safe_bprintf(PRINT_MEDIUM,"ACE: Loading node table...");
791 
792 		fread(&numnodes,sizeof(int),1,pIn); // read count
793 		fread(&num_items,sizeof(int),1,pIn); // read facts count
794 
795 		fread(nodes,sizeof(node_t),numnodes,pIn);
796 
797 		for(i=0;i<numnodes;i++)
798 			for(j=0;j<numnodes;j++)
799 				fread(&path_table[i][j],sizeof(short int),1,pIn); // write count
800 
801 		fread(item_table,sizeof(item_table_t),num_items,pIn);
802 		fclose(pIn);
803 	}
804 	else
805 	{
806 		// Create item table
807 		safe_bprintf(PRINT_MEDIUM, "ACE: No node file found, creating new one...");
808 		ACEIT_BuildItemNodeTable(false);
809 		safe_bprintf(PRINT_MEDIUM, "done.\n");
810 		return; // bail
811 	}
812 
813 	safe_bprintf(PRINT_MEDIUM, "done.\n");
814 
815 	ACEIT_BuildItemNodeTable(true);
816 
817 }
818 
819 
820