1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2 
3 /*
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include "build.h"
20 #include "starmap.h"
21 #include "gendef.h"
22 #include "libs/file.h"
23 #include "globdata.h"
24 #include "intel.h"
25 #include "state.h"
26 #include "grpintrn.h"
27 
28 #include "libs/mathlib.h"
29 #include "libs/log.h"
30 
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 
35 static BYTE LastEncGroup;
36 		// Last encountered group, saved into state files
37 
38 void
ReadGroupHeader(GAME_STATE_FILE * fp,GROUP_HEADER * pGH)39 ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH)
40 {
41 	sread_8   (fp, &pGH->NumGroups);
42 	sread_8   (fp, &pGH->day_index);
43 	sread_8   (fp, &pGH->month_index);
44 	sread_8   (fp, NULL); /* padding */
45 	sread_16  (fp, &pGH->star_index);
46 	sread_16  (fp, &pGH->year_index);
47 	sread_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1);
48 }
49 
50 void
WriteGroupHeader(GAME_STATE_FILE * fp,const GROUP_HEADER * pGH)51 WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH)
52 {
53 	swrite_8   (fp, pGH->NumGroups);
54 	swrite_8   (fp, pGH->day_index);
55 	swrite_8   (fp, pGH->month_index);
56 	swrite_8   (fp, 0); /* padding */
57 	swrite_16  (fp, pGH->star_index);
58 	swrite_16  (fp, pGH->year_index);
59 	swrite_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1);
60 }
61 
62 void
ReadShipFragment(GAME_STATE_FILE * fp,SHIP_FRAGMENT * FragPtr)63 ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr)
64 {
65 	BYTE tmpb;
66 
67 	sread_16 (fp, NULL); /* unused: was which_side */
68 	sread_8  (fp, &FragPtr->captains_name_index);
69 	sread_8  (fp, NULL); /* padding; for savegame compat */
70 	sread_16 (fp, NULL); /* unused: was ship_flags */
71 	sread_8  (fp, &FragPtr->race_id);
72 	sread_8  (fp, &FragPtr->index);
73 	// XXX: reading crew as BYTE to maintain savegame compatibility
74 	sread_8  (fp, &tmpb);
75 	FragPtr->crew_level = tmpb;
76 	sread_8  (fp, &tmpb);
77 	FragPtr->max_crew = tmpb;
78 	sread_8  (fp, &FragPtr->energy_level);
79 	sread_8  (fp, &FragPtr->max_energy);
80 	sread_16 (fp, NULL); /* unused; was loc.x */
81 	sread_16 (fp, NULL); /* unused; was loc.y */
82 }
83 
84 void
WriteShipFragment(GAME_STATE_FILE * fp,const SHIP_FRAGMENT * FragPtr)85 WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr)
86 {
87 	swrite_16 (fp, 0); /* unused: was which_side */
88 	swrite_8  (fp, FragPtr->captains_name_index);
89 	swrite_8  (fp, 0); /* padding; for savegame compat */
90 	swrite_16 (fp, 0); /* unused: was ship_flags */
91 	swrite_8  (fp, FragPtr->race_id);
92 	swrite_8  (fp, FragPtr->index);
93 	// XXX: writing crew as BYTE to maintain savegame compatibility
94 	swrite_8  (fp, FragPtr->crew_level);
95 	swrite_8  (fp, FragPtr->max_crew);
96 	swrite_8  (fp, FragPtr->energy_level);
97 	swrite_8  (fp, FragPtr->max_energy);
98 	swrite_16 (fp, 0); /* unused; was loc.x */
99 	swrite_16 (fp, 0); /* unused; was loc.y */
100 }
101 
102 void
ReadIpGroup(GAME_STATE_FILE * fp,IP_GROUP * GroupPtr)103 ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr)
104 {
105 	BYTE tmpb;
106 
107 	sread_16 (fp, NULL); /* unused; was which_side */
108 	sread_8  (fp, NULL); /* unused; was captains_name_index */
109 	sread_8  (fp, NULL); /* padding; for savegame compat */
110 	sread_16 (fp, &GroupPtr->group_counter);
111 	sread_8  (fp, &GroupPtr->race_id);
112 	sread_8  (fp, &tmpb); /* was var2 */
113 	GroupPtr->sys_loc = LONIBBLE (tmpb);
114 	GroupPtr->task = HINIBBLE (tmpb);
115 	sread_8  (fp, &GroupPtr->in_system); /* was crew_level */
116 	sread_8  (fp, NULL); /* unused; was max_crew */
117 	sread_8  (fp, &tmpb); /* was energy_level */
118 	GroupPtr->dest_loc = LONIBBLE (tmpb);
119 	GroupPtr->orbit_pos = HINIBBLE (tmpb);
120 	sread_8  (fp, &GroupPtr->group_id); /* was max_energy */
121 	sread_16s(fp, &GroupPtr->loc.x);
122 	sread_16s(fp, &GroupPtr->loc.y);
123 }
124 
125 void
WriteIpGroup(GAME_STATE_FILE * fp,const IP_GROUP * GroupPtr)126 WriteIpGroup (GAME_STATE_FILE *fp, const IP_GROUP *GroupPtr)
127 {
128 	swrite_16 (fp, 0); /* unused; was which_side */
129 	swrite_8  (fp, 0); /* unused; was captains_name_index */
130 	swrite_8  (fp, 0); /* padding; for savegame compat */
131 	swrite_16 (fp, GroupPtr->group_counter);
132 	swrite_8  (fp, GroupPtr->race_id);
133 	assert (GroupPtr->sys_loc < 0x10 && GroupPtr->task < 0x10);
134 	swrite_8  (fp, MAKE_BYTE (GroupPtr->sys_loc, GroupPtr->task));
135 			/* was var2 */
136 	swrite_8  (fp, GroupPtr->in_system); /* was crew_level */
137 	swrite_8  (fp, 0); /* unused; was max_crew */
138 	assert (GroupPtr->dest_loc < 0x10 && GroupPtr->orbit_pos < 0x10);
139 	swrite_8  (fp, MAKE_BYTE (GroupPtr->dest_loc, GroupPtr->orbit_pos));
140 			/* was energy_level */
141 	swrite_8  (fp, GroupPtr->group_id); /* was max_energy */
142 	swrite_16 (fp, GroupPtr->loc.x);
143 	swrite_16 (fp, GroupPtr->loc.y);
144 }
145 
146 void
InitGroupInfo(BOOLEAN FirstTime)147 InitGroupInfo (BOOLEAN FirstTime)
148 {
149 	GAME_STATE_FILE *fp;
150 
151 	assert (NUM_SAVED_BATTLE_GROUPS >= MAX_BATTLE_GROUPS);
152 
153 	fp = OpenStateFile (RANDGRPINFO_FILE, "wb");
154 	if (fp)
155 	{
156 		GROUP_HEADER GH;
157 
158 		memset (&GH, 0, sizeof (GH));
159 		GH.star_index = (COUNT)~0;
160 		WriteGroupHeader (fp, &GH);
161 		CloseStateFile (fp);
162 	}
163 
164 	if (FirstTime && (fp = OpenStateFile (DEFGRPINFO_FILE, "wb")))
165 	{
166 		// Group headers cannot start with offset 0 in 'defined' group
167 		// info file, so bump it (because offset 0 is reserved to
168 		// indicate the 'random' group info file).
169 		swrite_8 (fp, 0);
170 		CloseStateFile (fp);
171 	}
172 }
173 
174 void
UninitGroupInfo(void)175 UninitGroupInfo (void)
176 {
177 	DeleteStateFile (DEFGRPINFO_FILE);
178 	DeleteStateFile (RANDGRPINFO_FILE);
179 }
180 
181 HIPGROUP
BuildGroup(QUEUE * pDstQueue,BYTE race_id)182 BuildGroup (QUEUE *pDstQueue, BYTE race_id)
183 {
184 	HFLEETINFO hFleet;
185 	FLEET_INFO *TemplatePtr;
186 	HLINK hGroup;
187 	IP_GROUP *GroupPtr;
188 
189 	assert (GetLinkSize (pDstQueue) == sizeof (IP_GROUP));
190 
191 	hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race_id);
192 	if (!hFleet)
193 		return 0;
194 
195 	hGroup = AllocLink (pDstQueue);
196 	if (!hGroup)
197 		return 0;
198 
199 	TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
200 	GroupPtr = LockIpGroup (pDstQueue, hGroup);
201 	memset (GroupPtr, 0, GetLinkSize (pDstQueue));
202 	GroupPtr->race_id = race_id;
203 	GroupPtr->melee_icon = TemplatePtr->melee_icon;
204 	UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
205 	UnlockIpGroup (pDstQueue, hGroup);
206 	PutQueue (pDstQueue, hGroup);
207 
208 	return hGroup;
209 }
210 
211 void
BuildGroups(void)212 BuildGroups (void)
213 {
214 	BYTE Index;
215 	BYTE BestIndex = 0;
216 	COUNT BestPercent = 0;
217 	POINT universe;
218 	HFLEETINFO hFleet, hNextFleet;
219 	BYTE HomeWorld[] =
220 	{
221 		0,                /* ARILOU_SHIP */
222 		0,                /* CHMMR_SHIP */
223 		0,                /* HUMAN_SHIP */
224 		ORZ_DEFINED,      /* ORZ_SHIP */
225 		PKUNK_DEFINED,    /* PKUNK_SHIP */
226 		0,                /* SHOFIXTI_SHIP */
227 		SPATHI_DEFINED,   /* SPATHI_SHIP */
228 		SUPOX_DEFINED,    /* SUPOX_SHIP */
229 		THRADD_DEFINED,   /* THRADDASH_SHIP */
230 		UTWIG_DEFINED,    /* UTWIG_SHIP */
231 		VUX_DEFINED,      /* VUX_SHIP */
232 		YEHAT_DEFINED,    /* YEHAT_SHIP */
233 		0,                /* MELNORME_SHIP */
234 		DRUUGE_DEFINED,   /* DRUUGE_SHIP */
235 		ILWRATH_DEFINED,  /* ILWRATH_SHIP */
236 		MYCON_DEFINED,    /* MYCON_SHIP */
237 		0,                /* SLYLANDRO_SHIP */
238 		UMGAH_DEFINED,    /* UMGAH_SHIP */
239 		0,                /* URQUAN_SHIP */
240 		ZOQFOT_DEFINED,   /* ZOQFOTPIK_SHIP */
241 
242 		0,                /* SYREEN_SHIP */
243 		0,                /* BLACK_URQUAN_SHIP */
244 		0,                /* YEHAT_REBEL_SHIP */
245 	};
246 	BYTE EncounterPercent[] =
247 	{
248 		RACE_INTERPLANETARY_PERCENT
249 	};
250 
251 	EncounterPercent[SLYLANDRO_SHIP] *= GET_GAME_STATE (SLYLANDRO_MULTIPLIER);
252 	Index = GET_GAME_STATE (UTWIG_SUPOX_MISSION);
253 	if (Index > 1 && Index < 5)
254 	{
255 		// When the Utwig and Supox are on their mission, there won't be
256 		// new battle groups generated for the system.
257 		// Note that old groups may still exist (in which case this function
258 		// would not even be called), but those expire after spending a week
259 		// outside of the star system, or when a different star system is
260 		// entered.
261 		HomeWorld[UTWIG_SHIP] = 0;
262 		HomeWorld[SUPOX_SHIP] = 0;
263 	}
264 
265 	universe = CurStarDescPtr->star_pt;
266 	for (hFleet = GetHeadLink (&GLOBAL (avail_race_q)), Index = 0;
267 			hFleet; hFleet = hNextFleet, ++Index)
268 	{
269 		COUNT i, encounter_radius;
270 		FLEET_INFO *FleetPtr;
271 
272 		FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet);
273 		hNextFleet = _GetSuccLink (FleetPtr);
274 
275 		if ((encounter_radius = FleetPtr->actual_strength)
276 				&& (i = EncounterPercent[Index]))
277 		{
278 			SIZE dx, dy;
279 			DWORD d_squared;
280 			BYTE race_enc;
281 
282 			race_enc = HomeWorld[Index];
283 			if (race_enc && CurStarDescPtr->Index == race_enc)
284 			{	// In general, there are always ships at the Homeworld for
285 				// the races specified in HomeWorld[] array.
286 				BestIndex = Index;
287 				BestPercent = 70;
288 				if (race_enc == SPATHI_DEFINED || race_enc == SUPOX_DEFINED)
289 					BestPercent = 2;
290 				// Terminate the loop!
291 				hNextFleet = 0;
292 
293 				goto FoundHome;
294 			}
295 
296 			if (encounter_radius == INFINITE_RADIUS)
297 				encounter_radius = (MAX_X_UNIVERSE + 1) << 1;
298 			else
299 				encounter_radius =
300 						(encounter_radius * SPHERE_RADIUS_INCREMENT) >> 1;
301 			dx = universe.x - FleetPtr->loc.x;
302 			if (dx < 0)
303 				dx = -dx;
304 			dy = universe.y - FleetPtr->loc.y;
305 			if (dy < 0)
306 				dy = -dy;
307 			if ((COUNT)dx < encounter_radius
308 					&& (COUNT)dy < encounter_radius
309 					&& (d_squared = (DWORD)dx * dx + (DWORD)dy * dy) <
310 					(DWORD)encounter_radius * encounter_radius)
311 			{
312 				DWORD rand_val;
313 
314 				// EncounterPercent is only used in practice for the Slylandro
315 				// Probes, for the rest of races the chance of encounter is
316 				// calced directly below from the distance to the Homeworld
317 				if (FleetPtr->actual_strength != INFINITE_RADIUS)
318 				{
319 					i = 70 - (COUNT)((DWORD)square_root (d_squared)
320 							* 60L / encounter_radius);
321 				}
322 
323 				rand_val = TFB_Random ();
324 				if ((int)(LOWORD (rand_val) % 100) < (int)i
325 						&& (BestPercent == 0
326 						|| (HIWORD (rand_val) % (i + BestPercent)) < i))
327 				{
328 					if (FleetPtr->actual_strength == INFINITE_RADIUS)
329 					{	// The prevailing encounter chance is hereby limitted
330 						// to 4% for races with infinite SoI (currently, it
331 						// is only the Slylandro Probes)
332 						i = 4;
333 					}
334 
335 					BestPercent = i;
336 					BestIndex = Index;
337 				}
338 			}
339 		}
340 
341 FoundHome:
342 		UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet);
343 	}
344 
345 	if (BestPercent)
346 	{
347 		BYTE which_group, num_groups;
348 		BYTE EncounterMakeup[] =
349 		{
350 			RACE_ENCOUNTER_MAKEUP
351 		};
352 
353 		which_group = 0;
354 		num_groups = ((COUNT)TFB_Random () % (BestPercent >> 1)) + 1;
355 		if (num_groups > MAX_BATTLE_GROUPS)
356 			num_groups = MAX_BATTLE_GROUPS;
357 		else if (num_groups < 5
358 				&& (Index = HomeWorld[BestIndex])
359 				&& CurStarDescPtr->Index == Index)
360 			num_groups = 5;
361 		do
362 		{
363 			for (Index = HINIBBLE (EncounterMakeup[BestIndex]); Index;
364 					--Index)
365 			{
366 				if (Index <= LONIBBLE (EncounterMakeup[BestIndex])
367 						|| (COUNT)TFB_Random () % 100 < 50)
368 					CloneShipFragment (BestIndex,
369 							&GLOBAL (npc_built_ship_q), 0);
370 			}
371 
372 			PutGroupInfo (GROUPS_RANDOM, ++which_group);
373 			ReinitQueue (&GLOBAL (npc_built_ship_q));
374 		} while (--num_groups);
375 	}
376 
377 	GetGroupInfo (GROUPS_RANDOM, GROUP_INIT_IP);
378 }
379 
380 static void
FlushGroupInfo(GROUP_HEADER * pGH,DWORD offset,BYTE which_group,GAME_STATE_FILE * fp)381 FlushGroupInfo (GROUP_HEADER* pGH, DWORD offset, BYTE which_group, GAME_STATE_FILE *fp)
382 {
383 	if (which_group == GROUP_LIST)
384 	{
385 		HIPGROUP hGroup, hNextGroup;
386 
387 		/* If the group list was never written before, add it */
388 		if (pGH->GroupOffset[0] == 0)
389 			pGH->GroupOffset[0] = LengthStateFile (fp);
390 
391 		// XXX: npc_built_ship_q must be empty because the wipe-out
392 		//   procedure is actually the writing of the npc_built_ship_q
393 		//   out as the group in question
394 		assert (!GetHeadLink (&GLOBAL (npc_built_ship_q)));
395 
396 		/* Weed out the groups that left the system first */
397 		for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
398 				hGroup; hGroup = hNextGroup)
399 		{
400 			BYTE in_system;
401 			BYTE group_id;
402 			IP_GROUP *GroupPtr;
403 
404 			GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
405 			hNextGroup = _GetSuccLink (GroupPtr);
406 			in_system = GroupPtr->in_system;
407 			group_id = GroupPtr->group_id;
408 			UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
409 
410 			if (!in_system)
411 			{
412 				// The following 'if' is needed because GROUP_LIST is only
413 				// ever flushed to RANDGRPINFO_FILE, but the current group
414 				// may need to be updated in the DEFGRPINFO_FILE as well.
415 				// In that case, PutGroupInfo() will update the correct file.
416 				if (GLOBAL (BattleGroupRef))
417 					PutGroupInfo (GLOBAL (BattleGroupRef), group_id);
418 				else
419 					FlushGroupInfo (pGH, GROUPS_RANDOM, group_id, fp);
420 				// This will also wipe the group out in the RANDGRPINFO_FILE
421 				pGH->GroupOffset[group_id] = 0;
422 				RemoveQueue (&GLOBAL (ip_group_q), hGroup);
423 				FreeIpGroup (&GLOBAL (ip_group_q), hGroup);
424 			}
425 		}
426 	}
427 	else if (which_group > pGH->NumGroups)
428 	{	/* Group not present yet -- add it */
429 		pGH->NumGroups = which_group;
430 		pGH->GroupOffset[which_group] = LengthStateFile (fp);
431 	}
432 
433 	SeekStateFile (fp, offset, SEEK_SET);
434 	WriteGroupHeader (fp, pGH);
435 
436 #ifdef DEBUG_GROUPS
437 	log_add (log_Debug, "1)FlushGroupInfo(%lu): WG = %u(%lu), NG = %u, "
438 			"SI = %u", offset, which_group, pGH->GroupOffset[which_group],
439 			pGH->NumGroups, pGH->star_index);
440 #endif /* DEBUG_GROUPS */
441 
442 	if (which_group == GROUP_LIST)
443 	{
444 		/* Write out ip_group_q as group 0 */
445 		HIPGROUP hGroup, hNextGroup;
446 		BYTE NumGroups = CountLinks (&GLOBAL (ip_group_q));
447 
448 		SeekStateFile (fp, pGH->GroupOffset[0], SEEK_SET);
449 		swrite_8 (fp, LastEncGroup);
450 		swrite_8 (fp, NumGroups);
451 
452 		hGroup = GetHeadLink (&GLOBAL (ip_group_q));
453 		for ( ; NumGroups; --NumGroups, hGroup = hNextGroup)
454 		{
455 			IP_GROUP *GroupPtr;
456 
457 			GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
458 			hNextGroup = _GetSuccLink (GroupPtr);
459 
460 			swrite_8 (fp, GroupPtr->race_id);
461 
462 #ifdef DEBUG_GROUPS
463 			log_add (log_Debug, "F) type %u, loc %u<%d, %d>, task 0x%02x:%u",
464 					GroupPtr->race_id,
465 					GET_GROUP_LOC (GroupPtr),
466 					GroupPtr->loc.x,
467 					GroupPtr->loc.y,
468 					GET_GROUP_MISSION (GroupPtr),
469 					GET_GROUP_DEST (GroupPtr));
470 #endif /* DEBUG_GROUPS */
471 
472 			WriteIpGroup (fp, GroupPtr);
473 
474 			UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
475 		}
476 	}
477 	else
478 	{
479 		/* Write out npc_built_ship_q as 'which_group' group */
480 		HSHIPFRAG hStarShip, hNextShip;
481 		BYTE NumShips = CountLinks (&GLOBAL (npc_built_ship_q));
482 		BYTE RaceType = 0;
483 
484 		hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q));
485 		if (NumShips > 0)
486 		{
487 			SHIP_FRAGMENT *FragPtr;
488 
489 			/* The first ship in a group defines the alien race */
490 			FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
491 			RaceType = FragPtr->race_id;
492 			UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
493 		}
494 
495 		SeekStateFile (fp, pGH->GroupOffset[which_group], SEEK_SET);
496 		swrite_8 (fp, RaceType);
497 		swrite_8 (fp, NumShips);
498 
499 		for ( ; NumShips; --NumShips, hStarShip = hNextShip)
500 		{
501 			SHIP_FRAGMENT *FragPtr;
502 
503 			FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
504 			hNextShip = _GetSuccLink (FragPtr);
505 
506 			swrite_8 (fp, FragPtr->race_id);
507 			WriteShipFragment (fp, FragPtr);
508 
509 			UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
510 		}
511 	}
512 }
513 
514 BOOLEAN
GetGroupInfo(DWORD offset,BYTE which_group)515 GetGroupInfo (DWORD offset, BYTE which_group)
516 {
517 	GAME_STATE_FILE *fp;
518 	GROUP_HEADER GH;
519 
520 	if (offset != GROUPS_RANDOM && which_group != GROUP_LIST)
521 		fp = OpenStateFile (DEFGRPINFO_FILE, "r+b");
522 	else
523 		fp = OpenStateFile (RANDGRPINFO_FILE, "r+b");
524 
525 	if (!fp)
526 		return FALSE;
527 
528 	SeekStateFile (fp, offset, SEEK_SET);
529 	ReadGroupHeader (fp, &GH);
530 #ifdef DEBUG_GROUPS
531 	log_add (log_Debug, "GetGroupInfo(%lu): %u(%lu) out of %u", offset,
532 			which_group, GH.GroupOffset[which_group], GH.NumGroups);
533 #endif /* DEBUG_GROUPS */
534 
535 	if (which_group == GROUP_INIT_IP)
536 	{
537 		COUNT month_index, day_index, year_index;
538 
539 		ReinitQueue (&GLOBAL (ip_group_q));
540 #ifdef DEBUG_GROUPS
541 		log_add (log_Debug, "%u == %u", GH.star_index,
542 				(COUNT)(CurStarDescPtr - star_array));
543 #endif /* DEBUG_GROUPS */
544 
545 		/* Check if the requested groups are valid for this star system
546 		 * and if they are still current (not expired) */
547 		day_index = GH.day_index;
548 		month_index = GH.month_index;
549 		year_index = GH.year_index;
550 		if (offset == GROUPS_RANDOM
551 				&& (GH.star_index != (COUNT)(CurStarDescPtr - star_array)
552 				|| !ValidateEvent (ABSOLUTE_EVENT, &month_index, &day_index,
553 					&year_index)))
554 		{
555 #ifdef DEBUG_GROUPS
556 			if (GH.star_index == CurStarDescPtr - star_array)
557 				log_add (log_Debug, "GetGroupInfo: battle groups out of "
558 						"date %u/%u/%u!", month_index, day_index,
559 						year_index);
560 #endif /* DEBUG_GROUPS */
561 
562 			CloseStateFile (fp);
563 			/* Erase random groups (out of date) */
564 			fp = OpenStateFile (RANDGRPINFO_FILE, "wb");
565 			memset (&GH, 0, sizeof (GH));
566 			GH.star_index = (COUNT)~0;
567 			WriteGroupHeader (fp, &GH);
568 			CloseStateFile (fp);
569 
570 			return FALSE;
571 		}
572 
573 		/* Read IP groups into ip_group_q and send them on their missions */
574 		for (which_group = 1; which_group <= GH.NumGroups; ++which_group)
575 		{
576 			BYTE task, group_loc;
577 			DWORD rand_val;
578 			BYTE RaceType;
579 			BYTE NumShips;
580 			HIPGROUP hGroup;
581 			IP_GROUP *GroupPtr;
582 
583 			if (GH.GroupOffset[which_group] == 0)
584 				continue;
585 
586 			SeekStateFile (fp, GH.GroupOffset[which_group], SEEK_SET);
587 			sread_8 (fp, &RaceType);
588 			sread_8 (fp, &NumShips);
589 			if (!NumShips)
590 				continue; /* group is dead */
591 
592 			hGroup = BuildGroup (&GLOBAL (ip_group_q), RaceType);
593 			GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
594 			GroupPtr->group_id = which_group;
595 			GroupPtr->in_system = 1;
596 
597 			rand_val = TFB_Random ();
598 			task = (BYTE)(LOBYTE (LOWORD (rand_val)) % ON_STATION);
599 			if (task == FLEE)
600 				task = ON_STATION;
601 			GroupPtr->orbit_pos = NORMALIZE_FACING (
602 					LOBYTE (HIWORD (rand_val)));
603 
604 			group_loc = pSolarSysState->SunDesc[0].NumPlanets;
605 			if (group_loc == 1 && task == EXPLORE)
606 				task = IN_ORBIT;
607 			else
608 				group_loc = (BYTE)((HIBYTE (LOWORD (rand_val)) % group_loc) + 1);
609 			GroupPtr->dest_loc = group_loc;
610 			rand_val = TFB_Random ();
611 			GroupPtr->loc.x = (LOWORD (rand_val) % 10000) - 5000;
612 			GroupPtr->loc.y = (HIWORD (rand_val) % 10000) - 5000;
613 			GroupPtr->group_counter = 0;
614 			if (task == EXPLORE)
615 			{
616 				GroupPtr->group_counter = ((COUNT)TFB_Random () %
617 						MAX_REVOLUTIONS) << FACING_SHIFT;
618 			}
619 			else if (task == ON_STATION)
620 			{
621 				COUNT angle;
622 				POINT org;
623 
624 				org = planetOuterLocation (group_loc - 1);
625 				angle = FACING_TO_ANGLE (GroupPtr->orbit_pos + 1);
626 				GroupPtr->loc.x = org.x + COSINE (angle, STATION_RADIUS);
627 				GroupPtr->loc.y = org.y + SINE (angle, STATION_RADIUS);
628 				group_loc = 0;
629 			}
630 
631 			GroupPtr->task = task;
632 			GroupPtr->sys_loc = group_loc;
633 
634 #ifdef DEBUG_GROUPS
635 			log_add (log_Debug, "battle group %u(0x%04x) strength "
636 					"%u, type %u, loc %u<%d, %d>, task %u",
637 					which_group,
638 					hGroup,
639 					NumShips,
640 					RaceType,
641 					group_loc,
642 					GroupPtr->loc.x,
643 					GroupPtr->loc.y,
644 					task);
645 #endif /* DEBUG_GROUPS */
646 
647 			UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
648 		}
649 
650 		if (offset != GROUPS_RANDOM)
651 			InitGroupInfo (FALSE); 	/* Wipe out random battle groups */
652 		else if (ValidateEvent (ABSOLUTE_EVENT, /* still fresh */
653 					&month_index, &day_index, &year_index))
654 		{
655 			CloseStateFile (fp);
656 			return TRUE;
657 		}
658 
659 		CloseStateFile (fp);
660 		return (GetHeadLink (&GLOBAL (ip_group_q)) != 0);
661 	}
662 
663 	if (!GH.GroupOffset[which_group])
664 	{
665 		/* Group not present */
666 		CloseStateFile (fp);
667 		return FALSE;
668 	}
669 
670 
671 	if (which_group == GROUP_LIST)
672 	{
673 		BYTE NumGroups;
674 		COUNT ShipsLeftInLEG;
675 
676 		// XXX: Hack: First, save the state of last encountered group, if any.
677 		//   The assumption here is that we read the group list immediately
678 		//   after an IP encounter, and npc_built_ship_q contains whatever
679 		//   ships are left in the encountered group (can be none).
680 		ShipsLeftInLEG = CountLinks (&GLOBAL (npc_built_ship_q));
681 
682 		SeekStateFile (fp, GH.GroupOffset[0], SEEK_SET);
683 		sread_8 (fp, &LastEncGroup);
684 
685 		if (LastEncGroup)
686 		{
687 			// The following 'if' is needed because GROUP_LIST is only
688 			// ever read from RANDGRPINFO_FILE, but the LastEncGroup
689 			// may need to be updated in the DEFGRPINFO_FILE as well.
690 			// In that case, PutGroupInfo() will update the correct file.
691 			if (GLOBAL (BattleGroupRef))
692 				PutGroupInfo (GLOBAL (BattleGroupRef), LastEncGroup);
693 			else
694 				FlushGroupInfo (&GH, offset, LastEncGroup, fp);
695 		}
696 		ReinitQueue (&GLOBAL (npc_built_ship_q));
697 
698 		/* Read group 0 into ip_group_q */
699 		ReinitQueue (&GLOBAL (ip_group_q));
700 		/* Need a seek because Put/Flush has moved the file ptr */
701 		SeekStateFile (fp, GH.GroupOffset[0] + 1, SEEK_SET);
702 		sread_8 (fp, &NumGroups);
703 
704 		while (NumGroups--)
705 		{
706 			BYTE group_id;
707 			BYTE RaceType;
708 			HSHIPFRAG hGroup;
709 			IP_GROUP *GroupPtr;
710 
711 			sread_8 (fp, &RaceType);
712 
713 			hGroup = BuildGroup (&GLOBAL (ip_group_q), RaceType);
714 			GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
715 			ReadIpGroup (fp, GroupPtr);
716 			group_id = GroupPtr->group_id;
717 
718 #ifdef DEBUG_GROUPS
719 			log_add (log_Debug, "G) type %u, loc %u<%d, %d>, task 0x%02x:%u",
720 					RaceType,
721 					GroupPtr->sys_loc,
722 					GroupPtr->loc.x,
723 					GroupPtr->loc.y,
724 					GroupPtr->task,
725 					GroupPtr->dest_loc);
726 #endif /* DEBUG_GROUPS */
727 
728 			UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
729 
730 			if (group_id == LastEncGroup && !ShipsLeftInLEG)
731 			{
732 				/* No ships left in the last encountered group, remove it */
733 #ifdef DEBUG_GROUPS
734 				log_add (log_Debug, " -- REMOVING");
735 #endif /* DEBUG_GROUPS */
736 				RemoveQueue (&GLOBAL (ip_group_q), hGroup);
737 				FreeIpGroup (&GLOBAL (ip_group_q), hGroup);
738 			}
739 		}
740 
741 		CloseStateFile (fp);
742 		return (GetHeadLink (&GLOBAL (ip_group_q)) != 0);
743 	}
744 	else
745 	{
746 		/* Read 'which_group' group into npc_built_ship_q */
747 		BYTE NumShips;
748 
749 		// XXX: Hack: The assumption here is that we only read the makeup
750 		//   of a particular group when initializing an encounter, which
751 		//   makes this group 'last encountered'. Also the state of all
752 		//   groups is saved here. This may make working with savegames
753 		//   harder in the future, as special care will have to be taken
754 		//   when loading a game into an encounter.
755 		LastEncGroup = which_group;
756 		// The following 'if' is needed because GROUP_LIST is only
757 		// ever written to RANDGRPINFO_FILE, but the group we are reading
758 		// may be in the DEFGRPINFO_FILE as well.
759 		// In that case, PutGroupInfo() will update the correct file.
760 		// Always calling PutGroupInfo() here would also be acceptable now.
761 		if (offset != GROUPS_RANDOM)
762 			PutGroupInfo (GROUPS_RANDOM, GROUP_LIST);
763 		else
764 			FlushGroupInfo (&GH, GROUPS_RANDOM, GROUP_LIST, fp);
765 		ReinitQueue (&GLOBAL (ip_group_q));
766 
767 		ReinitQueue (&GLOBAL (npc_built_ship_q));
768 		// skip RaceType
769 		SeekStateFile (fp, GH.GroupOffset[which_group] + 1, SEEK_SET);
770 		sread_8 (fp, &NumShips);
771 
772 		while (NumShips--)
773 		{
774 			BYTE RaceType;
775 			HSHIPFRAG hStarShip;
776 			SHIP_FRAGMENT *FragPtr;
777 
778 			sread_8 (fp, &RaceType);
779 
780 			hStarShip = CloneShipFragment (RaceType,
781 					&GLOBAL (npc_built_ship_q), 0);
782 
783 			FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
784 			ReadShipFragment (fp, FragPtr);
785 			UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip);
786 		}
787 
788 		CloseStateFile (fp);
789 		return (GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0);
790 	}
791 }
792 
793 DWORD
PutGroupInfo(DWORD offset,BYTE which_group)794 PutGroupInfo (DWORD offset, BYTE which_group)
795 {
796 	GAME_STATE_FILE *fp;
797 	GROUP_HEADER GH;
798 
799 	if (offset != GROUPS_RANDOM && which_group != GROUP_LIST)
800 		fp = OpenStateFile (DEFGRPINFO_FILE, "r+b");
801 	else
802 		fp = OpenStateFile (RANDGRPINFO_FILE, "r+b");
803 
804 	if (!fp)
805 		return offset;
806 
807 	if (offset == GROUPS_ADD_NEW)
808 	{
809 		offset = LengthStateFile (fp);
810 		SeekStateFile (fp, offset, SEEK_SET);
811 		memset (&GH, 0, sizeof (GH));
812 		GH.star_index = (COUNT)~0;
813 		WriteGroupHeader (fp, &GH);
814 	}
815 
816 	// XXX: This is a bit dangerous. The assumption here is that we are
817 	//   only called to write GROUP_LIST in the GROUPS_RANDOM context,
818 	//   which is true right now and in which case we would seek to 0 anyway.
819 	//   The latter also makes guarding the seek with
820 	//   'if (which_group != GROUP_LIST)' moot.
821 	if (which_group != GROUP_LIST)
822 	{
823 		SeekStateFile (fp, offset, SEEK_SET);
824 		if (which_group == GROUP_SAVE_IP)
825 		{
826 			LastEncGroup = 0;
827 			which_group = GROUP_LIST;
828 		}
829 	}
830 	ReadGroupHeader (fp, &GH);
831 
832 #ifdef NEVER
833 	// XXX: this appears to be a remnant of a slightly different group info
834 	//   expiration mechanism. Nowadays, the 'defined' groups never expire,
835 	//   and the dead 'random' groups stay in the file with NumShips==0 until
836 	//   the entire 'random' group header expires.
837 	if (GetHeadLink (&GLOBAL (npc_built_ship_q)) || GH.GroupOffset[0] == 0)
838 #endif /* NEVER */
839 	{
840 		COUNT month_index, day_index, year_index;
841 
842 		/* The groups in this system are good for the next 7 days */
843 		month_index = 0;
844 		day_index = 7;
845 		year_index = 0;
846 		ValidateEvent (RELATIVE_EVENT, &month_index, &day_index, &year_index);
847 		GH.day_index = (BYTE)day_index;
848 		GH.month_index = (BYTE)month_index;
849 		GH.year_index = year_index;
850 	}
851 	GH.star_index = CurStarDescPtr - star_array;
852 
853 #ifdef DEBUG_GROUPS
854 	log_add (log_Debug, "PutGroupInfo(%lu): %u out of %u -- %u/%u/%u",
855 			offset, which_group, GH.NumGroups,
856 			GH.month_index, GH.day_index, GH.year_index);
857 #endif /* DEBUG_GROUPS */
858 
859 	FlushGroupInfo (&GH, offset, which_group, fp);
860 
861 	CloseStateFile (fp);
862 
863 	return (offset);
864 }
865 
866