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 #ifdef HAVE_CONFIG_H
61 #include "config.h"
62 #endif
63 
64 #include "game/g_local.h"
65 #include "acebot.h"
66 
67 // flags
68 qboolean newmap=true;
69 
70 // Total number of nodes that are items
71 int numitemnodes;
72 
73 // Total number of nodes
74 int bot_numnodes;
75 
76 // For debugging paths
77 int show_path_from = -1;
78 int show_path_to = -1;
79 
80 // array for node data
81 node_t nodes[MAX_NODES];
82 edict_t *node_showents[MAX_NODES];
83 short int path_table[MAX_NODES][MAX_NODES];
84 
85 ///////////////////////////////////////////////////////////////////////
86 // NODE INFORMATION FUNCTIONS
87 ///////////////////////////////////////////////////////////////////////
88 
89 ///////////////////////////////////////////////////////////////////////
90 // Determine cost of moving from one node to another
91 ///////////////////////////////////////////////////////////////////////
ACEND_FindCost(int from,int to)92 int ACEND_FindCost(int from, int to)
93 {
94 	int curnode;
95 	int cost=1; // Shortest possible is 1
96 
97 	// If we can not get there then return invalid
98 	if (path_table[from][to] == INVALID)
99 	{
100 		//if(debug_mode)
101 		//		debug_printf("Found invalid path!");
102 		return INVALID;
103 	}
104 	// Otherwise check the path and return the cost
105 	curnode = path_table[from][to];
106 
107 	// Find a path (linear time, very fast)
108 	while(curnode != to)
109 	{
110 		curnode = path_table[curnode][to];
111 		if(curnode == INVALID) // something has corrupted the path abort
112 		{
113 			//if(debug_mode)
114 			//	debug_printf("Found invalid path!");
115 
116 			return INVALID;
117 		}
118 		cost++;
119 		if(cost > 500) {
120 			if(debug_mode)
121 				debug_printf("Cost exceeded maximum!\n");
122 			break;
123 		}
124 	}
125 
126 	return cost;
127 }
128 
129 ///////////////////////////////////////////////////////////////////////
130 // Find the closest node to the player within a certain range
131 ///////////////////////////////////////////////////////////////////////
ACEND_FindClosestReachableNode(edict_t * self,int range,int type)132 int ACEND_FindClosestReachableNode(edict_t *self, int range, int type)
133 {
134 	int i;
135 	float closest = 99999;
136 	float dist;
137 	int node=-1;
138 	vec3_t v;
139 	trace_t tr;
140 	float rng;
141 	vec3_t maxs,mins;
142 
143 	VectorCopy(self->mins,mins);
144 	VectorCopy(self->maxs,maxs);
145 
146 	// For Ladders, do not worry so much about reachability
147 	if(type == NODE_LADDER)
148 	{
149 		VectorCopy(vec3_origin,maxs);
150 		VectorCopy(vec3_origin,mins);
151 	}
152 	else
153 		mins[2] += 18; // Stepsize
154 
155 	rng = (float)(range); // square range for distance comparison (eliminate sqrt)
156 
157 	for(i=0;i<bot_numnodes;i++)
158 	{
159 		if(type == NODE_ALL || type == nodes[i].type) // check node type
160 		{
161 			if(type == NODE_ALL && (nodes[i].type == NODE_REDBASE || nodes[i].type == NODE_BLUEBASE))
162 				continue; //we don't want to look for these unless specifically doing so
163 
164 			VectorSubtract(nodes[i].origin, self->s.origin, v); // subtract first
165 
166 			dist = VectorLength(v);
167 
168 			if(self->current_node != -1)
169 			{
170 				dist = dist + abs(self->current_node - i); //try to keep the bot on the current path(i.e - nodes in sequence are weighted over those out)
171 			}
172 
173 			if(dist < closest && dist < rng)
174 			{
175 				// make sure it is visible
176 				tr = gi.trace (self->s.origin, mins, maxs, nodes[i].origin, self, MASK_SOLID);
177 				if(tr.fraction == 1.0)
178 				{
179 					node = i;
180 					closest = dist;
181 				}
182 			}
183 		}
184 	}
185 
186 	return node;
187 }
188 
189 ///////////////////////////////////////////////////////////////////////
190 // BOT NAVIGATION ROUTINES
191 ///////////////////////////////////////////////////////////////////////
192 
193 ///////////////////////////////////////////////////////////////////////
194 // Set up the goal
195 ///////////////////////////////////////////////////////////////////////
ACEND_SetGoal(edict_t * self,int goal_node)196 void ACEND_SetGoal(edict_t *self, int goal_node)
197 {
198 	int node;
199 	gitem_t *flag1_item, *flag2_item;
200 	self->goal_node = goal_node;
201 
202 	//if flag in possession only use base nodes
203 	if(ctf->value)
204 	{
205 		flag1_item = FindItemByClassname("item_flag_red");
206 		flag2_item = FindItemByClassname("item_flag_blue");
207 
208 		if (self->client->pers.inventory[ITEM_INDEX(flag1_item)])
209 		{
210 			node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_BLUEBASE);
211 			if(node == -1)
212 				node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_ALL);
213 		}
214 		else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)])
215 		{
216 			node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_REDBASE);
217 			if(node == -1)
218 				node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_ALL);
219 		}
220 		else
221 			node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_ALL);
222 	}
223 	else if(g_tactical->value) //when a base's laser barriers shut off, go into a more direct attack route
224 	{
225 		if (self->ctype == 1 && (!tacticalScore.alienComputer || !tacticalScore.alienPowerSource))
226 		{
227 			node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_BLUEBASE);
228 			if(node == -1)
229 				node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_ALL);
230 		}
231 		else if (self->ctype == 0 && (!tacticalScore.humanComputer || !tacticalScore.humanPowerSource))
232 		{
233 			node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_REDBASE);
234 			if(node == -1)
235 				node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_ALL);
236 		}
237 		else
238 			node = ACEND_FindClosestReachableNode(self,NODE_DENSITY*3,NODE_ALL);
239 	}
240 	else
241 		node = ACEND_FindClosestReachableNode(self, NODE_DENSITY*3, NODE_ALL);
242 
243 	if(node == -1)
244 		return;
245 
246 	if(debug_mode)
247 		debug_printf("%s new start node (type: %i) selected %d\n",self->client->pers.netname, nodes[node].type, node);
248 
249 	self->current_node = node;
250 	self->next_node = self->current_node; // make sure we get to the nearest node first
251 	self->node_timeout = 0;
252 
253 }
254 
255 ///////////////////////////////////////////////////////////////////////
256 // Move closer to goal by pointing the bot to the next node
257 // that is closer to the goal
258 ///////////////////////////////////////////////////////////////////////
ACEND_FollowPath(edict_t * self)259 qboolean ACEND_FollowPath(edict_t *self)
260 {
261 	vec3_t v;
262 
263 	//////////////////////////////////////////
264 	// Show the path
265 	if(debug_mode) {
266 		show_path_from = self->current_node;
267 		show_path_to = self->goal_node;
268 	}
269 	//////////////////////////////////////////
270 
271 	// Try again?
272 	if(self->node_timeout ++ > 30)
273 	{
274 		if(self->tries++ > 3)
275 			return false;
276 		else
277 			ACEND_SetGoal(self,self->goal_node);
278 	}
279 
280 	// Are we there yet?
281 	VectorSubtract(self->s.origin,nodes[self->next_node].origin,v);
282 
283 	if(VectorLength(v) < 32)
284 	{
285 		// reset timeout
286 		self->node_timeout = 0;
287 
288 		if(self->next_node == self->goal_node)
289 		{
290 			if(debug_mode)
291 				debug_printf("%s reached goal!\n",self->client->pers.netname);
292 
293 			ACEAI_PickLongRangeGoal(self); // Pick a new goal
294 		}
295 		else
296 		{
297 			self->current_node = self->next_node;
298 			self->next_node = path_table[self->current_node][self->goal_node];
299 		}
300 	}
301 
302 	if(self->current_node == -1 || self->next_node ==-1)
303 		return false;
304 
305 	// Set bot's movement vector
306 	VectorSubtract (nodes[self->next_node].origin, self->s.origin , self->move_vector);
307 
308 	return true;
309 }
310 
311 ///////////////////////////////////////////////////////////////////////
312 // Init node array (set all to INVALID)
313 ///////////////////////////////////////////////////////////////////////
ACEND_InitNodes(void)314 void ACEND_InitNodes(void)
315 {
316 	bot_numnodes = 1;
317 	numitemnodes = 1;
318 	memset(nodes,0,sizeof(node_t) * MAX_NODES);
319 	memset(path_table,INVALID,sizeof(short int)*MAX_NODES*MAX_NODES);
320 	memset(node_showents,0,sizeof(edict_t *)*MAX_NODES);
321 
322 }
323 
324 ///////////////////////////////////////////////////////////////////////
325 // Show the node for debugging (utility function)
326 // Previously there was a warning comment here about overflows, however it
327 // seems to be just fine on a private server.
328 ///////////////////////////////////////////////////////////////////////
ACEND_ShowNode(int node)329 void ACEND_ShowNode(int node)
330 {
331 	edict_t *ent;
332 
333     if (node_showents[node]) {
334         safe_bprintf(PRINT_MEDIUM, "node %d already being shown\n", node);
335         return;
336     }
337 
338 	ent = G_Spawn();
339 
340 	ent->movetype = MOVETYPE_NONE;
341 	ent->solid = SOLID_NOT;
342 
343 	if(nodes[node].type == NODE_MOVE)
344 		ent->s.renderfx = RF_SHELL_BLUE;
345 	else if (nodes[node].type == NODE_WATER)
346 		ent->s.renderfx = RF_SHELL_RED;
347 	else
348 		ent->s.renderfx = RF_SHELL_GREEN; // action nodes
349 
350 	ent->s.modelindex = gi.modelindex ("models/items/ammo/grenades/medium/tris.md2");
351 	ent->owner = ent;
352 	ent->nextthink = level.time + 200000.0;
353 	ent->think = G_FreeEdict;
354 	ent->dmg = 0;
355 
356 	VectorCopy(nodes[node].origin,ent->s.origin);
357 	gi.linkentity (ent);
358 
359 	node_showents[node] = ent;
360 
361 }
362 
363 ///////////////////////////////////////////////////////////////////////
364 // Draws the current path (utility function)
365 ///////////////////////////////////////////////////////////////////////
ACEND_DrawPath()366 void ACEND_DrawPath()
367 {
368 	int current_node, goal_node, next_node;
369 
370 	if (!debug_mode)
371 	    return;
372 
373 	current_node = show_path_from;
374 	goal_node = show_path_to;
375 
376 	next_node = path_table[current_node][goal_node];
377 
378 	// Now set up and display the path
379 	while(current_node != goal_node && current_node != -1)
380 	{
381 		gi.WriteByte (svc_temp_entity);
382 		gi.WriteByte (TE_REDLASER);
383 		gi.WritePosition (nodes[current_node].origin);
384 		gi.WritePosition (nodes[next_node].origin);
385 		gi.multicast (nodes[current_node].origin, MULTICAST_PVS);
386 		current_node = next_node;
387 		next_node = path_table[current_node][goal_node];
388 	}
389 }
390 
391 ///////////////////////////////////////////////////////////////////////
392 // Turns on showing of the path, set goal to -1 to
393 // shut off. (utility function)
394 ///////////////////////////////////////////////////////////////////////
ACEND_ShowPath(edict_t * self,int goal_node)395 void ACEND_ShowPath(edict_t *self, int goal_node)
396 {
397 	show_path_from = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL);
398 	show_path_to = goal_node;
399 }
400 
401 ///////////////////////////////////////////////////////////////////////
402 // Add a node of type ?
403 ///////////////////////////////////////////////////////////////////////
ACEND_AddNode(edict_t * self,int type)404 int ACEND_AddNode(edict_t *self, int type)
405 {
406 	vec3_t v1,v2;
407 
408 	// Block if we exceed maximum
409 	if (bot_numnodes + 1 > MAX_NODES)
410 		return false;
411 
412 	// Set location
413 	VectorCopy(self->s.origin,nodes[bot_numnodes].origin);
414 
415 	// Set type
416 	nodes[bot_numnodes].type = type;
417 
418 	/////////////////////////////////////////////////////
419 	// ITEMS
420 	// Move the z location up just a bit.
421 	if(type == NODE_ITEM)
422 	{
423 		nodes[bot_numnodes].origin[2] += 16;
424 		numitemnodes++;
425 	}
426 
427 	// Teleporters
428 	if(type == NODE_TELEPORTER)
429 	{
430 		// Up 32
431 		nodes[bot_numnodes].origin[2] += 32;
432 	}
433 
434 	if(type == NODE_LADDER)
435 	{
436 		nodes[bot_numnodes].type = NODE_LADDER;
437 
438 		if(debug_mode)
439 		{
440 			debug_printf("Node added %d type: Ladder\n",bot_numnodes);
441 			ACEND_ShowNode(bot_numnodes);
442 		}
443 
444 		bot_numnodes++;
445 		return bot_numnodes-1; // return the node added
446 
447 	}
448 
449 	// For platforms drop two nodes one at top, one at bottom
450 	if(type == NODE_PLATFORM)
451 	{
452 		VectorCopy(self->maxs,v1);
453 		VectorCopy(self->mins,v2);
454 
455 		// To get the center
456 		nodes[bot_numnodes].origin[0] = (v1[0] - v2[0]) / 2 + v2[0];
457 		nodes[bot_numnodes].origin[1] = (v1[1] - v2[1]) / 2 + v2[1];
458 		nodes[bot_numnodes].origin[2] = self->maxs[2];
459 
460 		if(debug_mode)
461 			ACEND_ShowNode(bot_numnodes);
462 
463 		bot_numnodes++;
464 
465 		nodes[bot_numnodes].origin[0] = nodes[bot_numnodes-1].origin[0];
466 		nodes[bot_numnodes].origin[1] = nodes[bot_numnodes-1].origin[1];
467 		nodes[bot_numnodes].origin[2] = self->mins[2]+64;
468 
469 		nodes[bot_numnodes].type = NODE_PLATFORM;
470 
471 		// Add a link
472 		ACEND_UpdateNodeEdge(bot_numnodes,bot_numnodes-1);
473 
474 		if(debug_mode)
475 		{
476 			debug_printf("Node added %d type: Platform\n",bot_numnodes);
477 			ACEND_ShowNode(bot_numnodes);
478 		}
479 
480 		bot_numnodes++;
481 
482 		return bot_numnodes -1;
483 	}
484 
485 	if(debug_mode)
486 	{
487 		if(nodes[bot_numnodes].type == NODE_MOVE)
488 			debug_printf("Node added %d type: Move\n",bot_numnodes);
489 		else if(nodes[bot_numnodes].type == NODE_TELEPORTER)
490 			debug_printf("Node added %d type: Teleporter\n",bot_numnodes);
491 		else if(nodes[bot_numnodes].type == NODE_ITEM)
492 			debug_printf("Node added %d type: Item\n",bot_numnodes);
493 		else if(nodes[bot_numnodes].type == NODE_WATER)
494 			debug_printf("Node added %d type: Water\n",bot_numnodes);
495 		else if(nodes[bot_numnodes].type == NODE_GRAPPLE)
496 			debug_printf("Node added %d type: Grapple\n",bot_numnodes);
497 		else if(nodes[bot_numnodes].type == NODE_REDBASE)
498 			debug_printf("Node added %d type: Red Base\n",bot_numnodes);
499 		else if(nodes[bot_numnodes].type == NODE_BLUEBASE)
500 			debug_printf("Node added %d type: Blue Base\n",bot_numnodes);
501 		else if(nodes[bot_numnodes].type == NODE_DEFEND)
502 			debug_printf("Node added %d type: Defend Base\n",bot_numnodes);
503 
504 		ACEND_ShowNode(bot_numnodes);
505 	}
506 
507 	bot_numnodes++;
508 
509 	return bot_numnodes-1; // return the node added
510 }
511 
512 ///////////////////////////////////////////////////////////////////////
513 // Add/Update node connections (paths)
514 ///////////////////////////////////////////////////////////////////////
ACEND_UpdateNodeEdge(int from,int to)515 void ACEND_UpdateNodeEdge(int from, int to)
516 {
517 	int i;
518 
519 	if(from == -1 || to == -1 || from == to)
520 		return; // safety
521 
522 	// Add the link
523 	path_table[from][to] = to;
524 
525 	// Now for the self-referencing part, linear time for each link added
526 	for(i=0;i<bot_numnodes;i++)
527 		if(path_table[i][from] != INVALID)
528 		{
529 			if(i == to)
530 				path_table[i][to] = INVALID; // make sure we terminate
531 			else
532 				path_table[i][to] = path_table[i][from];
533 		}
534 
535 	if(debug_mode)
536 		debug_printf("Link %d -> %d\n", from, to);
537 }
538 
539 ///////////////////////////////////////////////////////////////////////
540 // Remove a node edge
541 ///////////////////////////////////////////////////////////////////////
ACEND_RemoveNodeEdge(edict_t * self,int from,int to)542 void ACEND_RemoveNodeEdge(edict_t *self, int from, int to)
543 {
544 	int i;
545 
546 	if(debug_mode)
547 		debug_printf("%s: Removing Edge %d -> %d\n", self->client->pers.netname, from, to);
548 
549 	path_table[from][to] = INVALID; // set to invalid
550 
551 	// Make sure this gets updated in our path array
552 	for(i=0;i<bot_numnodes;i++)
553 		if(path_table[from][i] == to)
554 			path_table[from][i] = INVALID;
555 }
556 
557 ///////////////////////////////////////////////////////////////////////
558 // This function will resolve all paths that are incomplete
559 // usually called before saving to disk
560 ///////////////////////////////////////////////////////////////////////
ACEND_ResolveAllPaths()561 void ACEND_ResolveAllPaths()
562 {
563 	int i, from, to;
564 	int num=0;
565 
566 	safe_bprintf(PRINT_HIGH,"Resolving all paths...");
567 
568 	for(from=0;from<bot_numnodes;from++)
569 	for(to=0;to<bot_numnodes;to++)
570 	{
571 		// update unresolved paths
572 		// Not equal to itself, not equal to -1 and equal to the last link
573 		if(from != to && path_table[from][to] == to)
574 		{
575 			num++;
576 
577 			// Now for the self-referencing part linear time for each link added
578 			for(i=0;i<bot_numnodes;i++)
579 				if(path_table[i][from] != -1)
580 				{
581 					if(i == to)
582 						path_table[i][to] = -1; // make sure we terminate
583 					else
584 						path_table[i][to] = path_table[i][from];
585 				}
586 		}
587 	}
588 
589 	safe_bprintf(PRINT_MEDIUM,"done (%d updated)\n",num);
590 }
591 
592 ///////////////////////////////////////////////////////////////////////
593 // Save to disk file
594 //
595 // Since my compression routines are one thing I did not want to
596 // release, I took out the compressed format option. Most levels will
597 // save out to a node file around 50-200k, so compression is not really
598 // a big deal.
599 ///////////////////////////////////////////////////////////////////////
ACEND_SaveNodes()600 void ACEND_SaveNodes()
601 {
602 	FILE *pOut;
603 	char filename[MAX_OSPATH];
604 	char relative_path[MAX_QPATH];
605 	int i,j;
606 	int version = 1;
607 	size_t sz;
608 
609 	// Resolve paths
610 	ACEND_ResolveAllPaths();
611 
612 	safe_bprintf(PRINT_MEDIUM,"Saving node table...");
613 
614 	strcpy( relative_path, BOT_GAMEDATA"/nav/" );
615 	strcat( relative_path, level.mapname );
616 	strcat( relative_path, ".nod" );
617 
618 	gi.FullWritePath( filename, sizeof(filename), relative_path );
619 
620 	if((pOut = fopen(filename, "wb" )) == NULL)
621 	{
622 		gi.dprintf("ACEND_SaveNodes: failed fopen for write: %s\n", filename );
623 		return;
624 	}
625 
626 	sz = fwrite(&version,sizeof(int),1,pOut); // write version
627 	sz = fwrite(&bot_numnodes,sizeof(int),1,pOut); // write count
628 	sz = fwrite(&num_items,sizeof(int),1,pOut); // write facts count
629 
630 	sz = fwrite(nodes,sizeof(node_t),bot_numnodes,pOut); // write nodes
631 
632 	for(i=0;i<bot_numnodes;i++)
633 		for(j=0;j<bot_numnodes;j++)
634 			sz = fwrite(&path_table[i][j],sizeof(short int),1,pOut); // write count
635 
636 	sz = fwrite(item_table,sizeof(item_table_t),num_items,pOut); 		// write out the fact table
637 
638 	fclose(pOut);
639 
640 	safe_bprintf(PRINT_MEDIUM,"done.\n");
641 }
642 
643 ///////////////////////////////////////////////////////////////////////
644 // Read from disk file
645 ///////////////////////////////////////////////////////////////////////
ACEND_LoadNodes(void)646 void ACEND_LoadNodes(void)
647 {
648 	FILE *pIn;
649 	int i,j;
650 	char relative_path[MAX_QPATH];
651 	char filename[MAX_OSPATH];
652 	int version;
653 	size_t sz;
654 
655 	strcpy( relative_path, BOT_GAMEDATA"/nav/" );
656 	strcat( relative_path, level.mapname );
657 	strcat( relative_path, ".nod" );
658 
659 	if ( !gi.FullPath( filename, sizeof(filename), relative_path ) )
660 	{
661 		gi.dprintf("ACEND_LoadNodes: not found: %s\n", relative_path );
662 	}
663 
664 	if((pIn = fopen(filename, "rb" )) == NULL)
665     {
666 		// Create item table
667 		gi.dprintf("ACE: No node file found, creating new one...");
668 		ACEIT_BuildItemNodeTable(false);
669 		safe_bprintf(PRINT_MEDIUM, "done.\n");
670 		return;
671 	}
672 
673 	// determine version
674 	sz = fread(&version,sizeof(int),1,pIn); // read version
675 
676 	if(version == 1)
677 	{
678 		gi.dprintf("ACE: Loading node table...");
679 
680         //FIXME: This code sucks so bad. Didn't anyone ever tell this Yeager
681         //fellow that dumps of C structs don't make a proper file format?
682         //If your compiler does padding differently, it breaks. If you want to
683         //add new field types, it breaks.
684 		sz = fread(&bot_numnodes,sizeof(int),1,pIn); // read count
685 		sz = fread(&num_items,sizeof(int),1,pIn); // read facts count
686 		sz = fread(nodes,sizeof(node_t),bot_numnodes,pIn);
687 
688 		for(i=0;i<bot_numnodes;i++)
689 			for(j=0;j<bot_numnodes;j++)
690 				sz = fread(&path_table[i][j],sizeof(short int),1,pIn); // write count
691 
692 	    for(i=0;i<num_items;i++)
693 			sz = fread(item_table,sizeof(item_table_t),1,pIn);
694 		fclose(pIn);
695 	}
696 	else
697 	{
698 		// Create item table
699 		gi.dprintf("ACE: No node file found, creating new one...");
700 		ACEIT_BuildItemNodeTable(false);
701 		safe_bprintf(PRINT_MEDIUM, "done.\n");
702 		return; // bail
703 	}
704 
705 	gi.dprintf("done.\n");
706 
707 	ACEIT_BuildItemNodeTable(true);
708 
709 }
710 
711