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