From 7f6002caba3f0a6749820c2772161caf55b8d267 Mon Sep 17 00:00:00 2001 From: neonloop Date: Fri, 7 May 2021 20:00:12 +0000 Subject: Initial commit (uqm-0.8.0) --- src/uqm/grpinfo.c | 865 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 865 insertions(+) create mode 100644 src/uqm/grpinfo.c (limited to 'src/uqm/grpinfo.c') diff --git a/src/uqm/grpinfo.c b/src/uqm/grpinfo.c new file mode 100644 index 0000000..43f1b22 --- /dev/null +++ b/src/uqm/grpinfo.c @@ -0,0 +1,865 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "build.h" +#include "starmap.h" +#include "gendef.h" +#include "libs/file.h" +#include "globdata.h" +#include "intel.h" +#include "state.h" +#include "grpintrn.h" + +#include "libs/mathlib.h" +#include "libs/log.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +static BYTE LastEncGroup; + // Last encountered group, saved into state files + +void +ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH) +{ + sread_8 (fp, &pGH->NumGroups); + sread_8 (fp, &pGH->day_index); + sread_8 (fp, &pGH->month_index); + sread_8 (fp, NULL); /* padding */ + sread_16 (fp, &pGH->star_index); + sread_16 (fp, &pGH->year_index); + sread_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1); +} + +void +WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH) +{ + swrite_8 (fp, pGH->NumGroups); + swrite_8 (fp, pGH->day_index); + swrite_8 (fp, pGH->month_index); + swrite_8 (fp, 0); /* padding */ + swrite_16 (fp, pGH->star_index); + swrite_16 (fp, pGH->year_index); + swrite_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1); +} + +void +ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr) +{ + BYTE tmpb; + + sread_16 (fp, NULL); /* unused: was which_side */ + sread_8 (fp, &FragPtr->captains_name_index); + sread_8 (fp, NULL); /* padding; for savegame compat */ + sread_16 (fp, NULL); /* unused: was ship_flags */ + sread_8 (fp, &FragPtr->race_id); + sread_8 (fp, &FragPtr->index); + // XXX: reading crew as BYTE to maintain savegame compatibility + sread_8 (fp, &tmpb); + FragPtr->crew_level = tmpb; + sread_8 (fp, &tmpb); + FragPtr->max_crew = tmpb; + sread_8 (fp, &FragPtr->energy_level); + sread_8 (fp, &FragPtr->max_energy); + sread_16 (fp, NULL); /* unused; was loc.x */ + sread_16 (fp, NULL); /* unused; was loc.y */ +} + +void +WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr) +{ + swrite_16 (fp, 0); /* unused: was which_side */ + swrite_8 (fp, FragPtr->captains_name_index); + swrite_8 (fp, 0); /* padding; for savegame compat */ + swrite_16 (fp, 0); /* unused: was ship_flags */ + swrite_8 (fp, FragPtr->race_id); + swrite_8 (fp, FragPtr->index); + // XXX: writing crew as BYTE to maintain savegame compatibility + swrite_8 (fp, FragPtr->crew_level); + swrite_8 (fp, FragPtr->max_crew); + swrite_8 (fp, FragPtr->energy_level); + swrite_8 (fp, FragPtr->max_energy); + swrite_16 (fp, 0); /* unused; was loc.x */ + swrite_16 (fp, 0); /* unused; was loc.y */ +} + +void +ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr) +{ + BYTE tmpb; + + sread_16 (fp, NULL); /* unused; was which_side */ + sread_8 (fp, NULL); /* unused; was captains_name_index */ + sread_8 (fp, NULL); /* padding; for savegame compat */ + sread_16 (fp, &GroupPtr->group_counter); + sread_8 (fp, &GroupPtr->race_id); + sread_8 (fp, &tmpb); /* was var2 */ + GroupPtr->sys_loc = LONIBBLE (tmpb); + GroupPtr->task = HINIBBLE (tmpb); + sread_8 (fp, &GroupPtr->in_system); /* was crew_level */ + sread_8 (fp, NULL); /* unused; was max_crew */ + sread_8 (fp, &tmpb); /* was energy_level */ + GroupPtr->dest_loc = LONIBBLE (tmpb); + GroupPtr->orbit_pos = HINIBBLE (tmpb); + sread_8 (fp, &GroupPtr->group_id); /* was max_energy */ + sread_16s(fp, &GroupPtr->loc.x); + sread_16s(fp, &GroupPtr->loc.y); +} + +void +WriteIpGroup (GAME_STATE_FILE *fp, const IP_GROUP *GroupPtr) +{ + swrite_16 (fp, 0); /* unused; was which_side */ + swrite_8 (fp, 0); /* unused; was captains_name_index */ + swrite_8 (fp, 0); /* padding; for savegame compat */ + swrite_16 (fp, GroupPtr->group_counter); + swrite_8 (fp, GroupPtr->race_id); + assert (GroupPtr->sys_loc < 0x10 && GroupPtr->task < 0x10); + swrite_8 (fp, MAKE_BYTE (GroupPtr->sys_loc, GroupPtr->task)); + /* was var2 */ + swrite_8 (fp, GroupPtr->in_system); /* was crew_level */ + swrite_8 (fp, 0); /* unused; was max_crew */ + assert (GroupPtr->dest_loc < 0x10 && GroupPtr->orbit_pos < 0x10); + swrite_8 (fp, MAKE_BYTE (GroupPtr->dest_loc, GroupPtr->orbit_pos)); + /* was energy_level */ + swrite_8 (fp, GroupPtr->group_id); /* was max_energy */ + swrite_16 (fp, GroupPtr->loc.x); + swrite_16 (fp, GroupPtr->loc.y); +} + +void +InitGroupInfo (BOOLEAN FirstTime) +{ + GAME_STATE_FILE *fp; + + assert (NUM_SAVED_BATTLE_GROUPS >= MAX_BATTLE_GROUPS); + + fp = OpenStateFile (RANDGRPINFO_FILE, "wb"); + if (fp) + { + GROUP_HEADER GH; + + memset (&GH, 0, sizeof (GH)); + GH.star_index = (COUNT)~0; + WriteGroupHeader (fp, &GH); + CloseStateFile (fp); + } + + if (FirstTime && (fp = OpenStateFile (DEFGRPINFO_FILE, "wb"))) + { + // Group headers cannot start with offset 0 in 'defined' group + // info file, so bump it (because offset 0 is reserved to + // indicate the 'random' group info file). + swrite_8 (fp, 0); + CloseStateFile (fp); + } +} + +void +UninitGroupInfo (void) +{ + DeleteStateFile (DEFGRPINFO_FILE); + DeleteStateFile (RANDGRPINFO_FILE); +} + +HIPGROUP +BuildGroup (QUEUE *pDstQueue, BYTE race_id) +{ + HFLEETINFO hFleet; + FLEET_INFO *TemplatePtr; + HLINK hGroup; + IP_GROUP *GroupPtr; + + assert (GetLinkSize (pDstQueue) == sizeof (IP_GROUP)); + + hFleet = GetStarShipFromIndex (&GLOBAL (avail_race_q), race_id); + if (!hFleet) + return 0; + + hGroup = AllocLink (pDstQueue); + if (!hGroup) + return 0; + + TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet); + GroupPtr = LockIpGroup (pDstQueue, hGroup); + memset (GroupPtr, 0, GetLinkSize (pDstQueue)); + GroupPtr->race_id = race_id; + GroupPtr->melee_icon = TemplatePtr->melee_icon; + UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet); + UnlockIpGroup (pDstQueue, hGroup); + PutQueue (pDstQueue, hGroup); + + return hGroup; +} + +void +BuildGroups (void) +{ + BYTE Index; + BYTE BestIndex = 0; + COUNT BestPercent = 0; + POINT universe; + HFLEETINFO hFleet, hNextFleet; + BYTE HomeWorld[] = + { + 0, /* ARILOU_SHIP */ + 0, /* CHMMR_SHIP */ + 0, /* HUMAN_SHIP */ + ORZ_DEFINED, /* ORZ_SHIP */ + PKUNK_DEFINED, /* PKUNK_SHIP */ + 0, /* SHOFIXTI_SHIP */ + SPATHI_DEFINED, /* SPATHI_SHIP */ + SUPOX_DEFINED, /* SUPOX_SHIP */ + THRADD_DEFINED, /* THRADDASH_SHIP */ + UTWIG_DEFINED, /* UTWIG_SHIP */ + VUX_DEFINED, /* VUX_SHIP */ + YEHAT_DEFINED, /* YEHAT_SHIP */ + 0, /* MELNORME_SHIP */ + DRUUGE_DEFINED, /* DRUUGE_SHIP */ + ILWRATH_DEFINED, /* ILWRATH_SHIP */ + MYCON_DEFINED, /* MYCON_SHIP */ + 0, /* SLYLANDRO_SHIP */ + UMGAH_DEFINED, /* UMGAH_SHIP */ + 0, /* URQUAN_SHIP */ + ZOQFOT_DEFINED, /* ZOQFOTPIK_SHIP */ + + 0, /* SYREEN_SHIP */ + 0, /* BLACK_URQUAN_SHIP */ + 0, /* YEHAT_REBEL_SHIP */ + }; + BYTE EncounterPercent[] = + { + RACE_INTERPLANETARY_PERCENT + }; + + EncounterPercent[SLYLANDRO_SHIP] *= GET_GAME_STATE (SLYLANDRO_MULTIPLIER); + Index = GET_GAME_STATE (UTWIG_SUPOX_MISSION); + if (Index > 1 && Index < 5) + { + // When the Utwig and Supox are on their mission, there won't be + // new battle groups generated for the system. + // Note that old groups may still exist (in which case this function + // would not even be called), but those expire after spending a week + // outside of the star system, or when a different star system is + // entered. + HomeWorld[UTWIG_SHIP] = 0; + HomeWorld[SUPOX_SHIP] = 0; + } + + universe = CurStarDescPtr->star_pt; + for (hFleet = GetHeadLink (&GLOBAL (avail_race_q)), Index = 0; + hFleet; hFleet = hNextFleet, ++Index) + { + COUNT i, encounter_radius; + FLEET_INFO *FleetPtr; + + FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hFleet); + hNextFleet = _GetSuccLink (FleetPtr); + + if ((encounter_radius = FleetPtr->actual_strength) + && (i = EncounterPercent[Index])) + { + SIZE dx, dy; + DWORD d_squared; + BYTE race_enc; + + race_enc = HomeWorld[Index]; + if (race_enc && CurStarDescPtr->Index == race_enc) + { // In general, there are always ships at the Homeworld for + // the races specified in HomeWorld[] array. + BestIndex = Index; + BestPercent = 70; + if (race_enc == SPATHI_DEFINED || race_enc == SUPOX_DEFINED) + BestPercent = 2; + // Terminate the loop! + hNextFleet = 0; + + goto FoundHome; + } + + if (encounter_radius == INFINITE_RADIUS) + encounter_radius = (MAX_X_UNIVERSE + 1) << 1; + else + encounter_radius = + (encounter_radius * SPHERE_RADIUS_INCREMENT) >> 1; + dx = universe.x - FleetPtr->loc.x; + if (dx < 0) + dx = -dx; + dy = universe.y - FleetPtr->loc.y; + if (dy < 0) + dy = -dy; + if ((COUNT)dx < encounter_radius + && (COUNT)dy < encounter_radius + && (d_squared = (DWORD)dx * dx + (DWORD)dy * dy) < + (DWORD)encounter_radius * encounter_radius) + { + DWORD rand_val; + + // EncounterPercent is only used in practice for the Slylandro + // Probes, for the rest of races the chance of encounter is + // calced directly below from the distance to the Homeworld + if (FleetPtr->actual_strength != INFINITE_RADIUS) + { + i = 70 - (COUNT)((DWORD)square_root (d_squared) + * 60L / encounter_radius); + } + + rand_val = TFB_Random (); + if ((int)(LOWORD (rand_val) % 100) < (int)i + && (BestPercent == 0 + || (HIWORD (rand_val) % (i + BestPercent)) < i)) + { + if (FleetPtr->actual_strength == INFINITE_RADIUS) + { // The prevailing encounter chance is hereby limitted + // to 4% for races with infinite SoI (currently, it + // is only the Slylandro Probes) + i = 4; + } + + BestPercent = i; + BestIndex = Index; + } + } + } + +FoundHome: + UnlockFleetInfo (&GLOBAL (avail_race_q), hFleet); + } + + if (BestPercent) + { + BYTE which_group, num_groups; + BYTE EncounterMakeup[] = + { + RACE_ENCOUNTER_MAKEUP + }; + + which_group = 0; + num_groups = ((COUNT)TFB_Random () % (BestPercent >> 1)) + 1; + if (num_groups > MAX_BATTLE_GROUPS) + num_groups = MAX_BATTLE_GROUPS; + else if (num_groups < 5 + && (Index = HomeWorld[BestIndex]) + && CurStarDescPtr->Index == Index) + num_groups = 5; + do + { + for (Index = HINIBBLE (EncounterMakeup[BestIndex]); Index; + --Index) + { + if (Index <= LONIBBLE (EncounterMakeup[BestIndex]) + || (COUNT)TFB_Random () % 100 < 50) + CloneShipFragment (BestIndex, + &GLOBAL (npc_built_ship_q), 0); + } + + PutGroupInfo (GROUPS_RANDOM, ++which_group); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + } while (--num_groups); + } + + GetGroupInfo (GROUPS_RANDOM, GROUP_INIT_IP); +} + +static void +FlushGroupInfo (GROUP_HEADER* pGH, DWORD offset, BYTE which_group, GAME_STATE_FILE *fp) +{ + if (which_group == GROUP_LIST) + { + HIPGROUP hGroup, hNextGroup; + + /* If the group list was never written before, add it */ + if (pGH->GroupOffset[0] == 0) + pGH->GroupOffset[0] = LengthStateFile (fp); + + // XXX: npc_built_ship_q must be empty because the wipe-out + // procedure is actually the writing of the npc_built_ship_q + // out as the group in question + assert (!GetHeadLink (&GLOBAL (npc_built_ship_q))); + + /* Weed out the groups that left the system first */ + for (hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + hGroup; hGroup = hNextGroup) + { + BYTE in_system; + BYTE group_id; + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + hNextGroup = _GetSuccLink (GroupPtr); + in_system = GroupPtr->in_system; + group_id = GroupPtr->group_id; + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + + if (!in_system) + { + // The following 'if' is needed because GROUP_LIST is only + // ever flushed to RANDGRPINFO_FILE, but the current group + // may need to be updated in the DEFGRPINFO_FILE as well. + // In that case, PutGroupInfo() will update the correct file. + if (GLOBAL (BattleGroupRef)) + PutGroupInfo (GLOBAL (BattleGroupRef), group_id); + else + FlushGroupInfo (pGH, GROUPS_RANDOM, group_id, fp); + // This will also wipe the group out in the RANDGRPINFO_FILE + pGH->GroupOffset[group_id] = 0; + RemoveQueue (&GLOBAL (ip_group_q), hGroup); + FreeIpGroup (&GLOBAL (ip_group_q), hGroup); + } + } + } + else if (which_group > pGH->NumGroups) + { /* Group not present yet -- add it */ + pGH->NumGroups = which_group; + pGH->GroupOffset[which_group] = LengthStateFile (fp); + } + + SeekStateFile (fp, offset, SEEK_SET); + WriteGroupHeader (fp, pGH); + +#ifdef DEBUG_GROUPS + log_add (log_Debug, "1)FlushGroupInfo(%lu): WG = %u(%lu), NG = %u, " + "SI = %u", offset, which_group, pGH->GroupOffset[which_group], + pGH->NumGroups, pGH->star_index); +#endif /* DEBUG_GROUPS */ + + if (which_group == GROUP_LIST) + { + /* Write out ip_group_q as group 0 */ + HIPGROUP hGroup, hNextGroup; + BYTE NumGroups = CountLinks (&GLOBAL (ip_group_q)); + + SeekStateFile (fp, pGH->GroupOffset[0], SEEK_SET); + swrite_8 (fp, LastEncGroup); + swrite_8 (fp, NumGroups); + + hGroup = GetHeadLink (&GLOBAL (ip_group_q)); + for ( ; NumGroups; --NumGroups, hGroup = hNextGroup) + { + IP_GROUP *GroupPtr; + + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + hNextGroup = _GetSuccLink (GroupPtr); + + swrite_8 (fp, GroupPtr->race_id); + +#ifdef DEBUG_GROUPS + log_add (log_Debug, "F) type %u, loc %u<%d, %d>, task 0x%02x:%u", + GroupPtr->race_id, + GET_GROUP_LOC (GroupPtr), + GroupPtr->loc.x, + GroupPtr->loc.y, + GET_GROUP_MISSION (GroupPtr), + GET_GROUP_DEST (GroupPtr)); +#endif /* DEBUG_GROUPS */ + + WriteIpGroup (fp, GroupPtr); + + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + } + } + else + { + /* Write out npc_built_ship_q as 'which_group' group */ + HSHIPFRAG hStarShip, hNextShip; + BYTE NumShips = CountLinks (&GLOBAL (npc_built_ship_q)); + BYTE RaceType = 0; + + hStarShip = GetHeadLink (&GLOBAL (npc_built_ship_q)); + if (NumShips > 0) + { + SHIP_FRAGMENT *FragPtr; + + /* The first ship in a group defines the alien race */ + FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + RaceType = FragPtr->race_id; + UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + } + + SeekStateFile (fp, pGH->GroupOffset[which_group], SEEK_SET); + swrite_8 (fp, RaceType); + swrite_8 (fp, NumShips); + + for ( ; NumShips; --NumShips, hStarShip = hNextShip) + { + SHIP_FRAGMENT *FragPtr; + + FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + hNextShip = _GetSuccLink (FragPtr); + + swrite_8 (fp, FragPtr->race_id); + WriteShipFragment (fp, FragPtr); + + UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + } + } +} + +BOOLEAN +GetGroupInfo (DWORD offset, BYTE which_group) +{ + GAME_STATE_FILE *fp; + GROUP_HEADER GH; + + if (offset != GROUPS_RANDOM && which_group != GROUP_LIST) + fp = OpenStateFile (DEFGRPINFO_FILE, "r+b"); + else + fp = OpenStateFile (RANDGRPINFO_FILE, "r+b"); + + if (!fp) + return FALSE; + + SeekStateFile (fp, offset, SEEK_SET); + ReadGroupHeader (fp, &GH); +#ifdef DEBUG_GROUPS + log_add (log_Debug, "GetGroupInfo(%lu): %u(%lu) out of %u", offset, + which_group, GH.GroupOffset[which_group], GH.NumGroups); +#endif /* DEBUG_GROUPS */ + + if (which_group == GROUP_INIT_IP) + { + COUNT month_index, day_index, year_index; + + ReinitQueue (&GLOBAL (ip_group_q)); +#ifdef DEBUG_GROUPS + log_add (log_Debug, "%u == %u", GH.star_index, + (COUNT)(CurStarDescPtr - star_array)); +#endif /* DEBUG_GROUPS */ + + /* Check if the requested groups are valid for this star system + * and if they are still current (not expired) */ + day_index = GH.day_index; + month_index = GH.month_index; + year_index = GH.year_index; + if (offset == GROUPS_RANDOM + && (GH.star_index != (COUNT)(CurStarDescPtr - star_array) + || !ValidateEvent (ABSOLUTE_EVENT, &month_index, &day_index, + &year_index))) + { +#ifdef DEBUG_GROUPS + if (GH.star_index == CurStarDescPtr - star_array) + log_add (log_Debug, "GetGroupInfo: battle groups out of " + "date %u/%u/%u!", month_index, day_index, + year_index); +#endif /* DEBUG_GROUPS */ + + CloseStateFile (fp); + /* Erase random groups (out of date) */ + fp = OpenStateFile (RANDGRPINFO_FILE, "wb"); + memset (&GH, 0, sizeof (GH)); + GH.star_index = (COUNT)~0; + WriteGroupHeader (fp, &GH); + CloseStateFile (fp); + + return FALSE; + } + + /* Read IP groups into ip_group_q and send them on their missions */ + for (which_group = 1; which_group <= GH.NumGroups; ++which_group) + { + BYTE task, group_loc; + DWORD rand_val; + BYTE RaceType; + BYTE NumShips; + HIPGROUP hGroup; + IP_GROUP *GroupPtr; + + if (GH.GroupOffset[which_group] == 0) + continue; + + SeekStateFile (fp, GH.GroupOffset[which_group], SEEK_SET); + sread_8 (fp, &RaceType); + sread_8 (fp, &NumShips); + if (!NumShips) + continue; /* group is dead */ + + hGroup = BuildGroup (&GLOBAL (ip_group_q), RaceType); + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + GroupPtr->group_id = which_group; + GroupPtr->in_system = 1; + + rand_val = TFB_Random (); + task = (BYTE)(LOBYTE (LOWORD (rand_val)) % ON_STATION); + if (task == FLEE) + task = ON_STATION; + GroupPtr->orbit_pos = NORMALIZE_FACING ( + LOBYTE (HIWORD (rand_val))); + + group_loc = pSolarSysState->SunDesc[0].NumPlanets; + if (group_loc == 1 && task == EXPLORE) + task = IN_ORBIT; + else + group_loc = (BYTE)((HIBYTE (LOWORD (rand_val)) % group_loc) + 1); + GroupPtr->dest_loc = group_loc; + rand_val = TFB_Random (); + GroupPtr->loc.x = (LOWORD (rand_val) % 10000) - 5000; + GroupPtr->loc.y = (HIWORD (rand_val) % 10000) - 5000; + GroupPtr->group_counter = 0; + if (task == EXPLORE) + { + GroupPtr->group_counter = ((COUNT)TFB_Random () % + MAX_REVOLUTIONS) << FACING_SHIFT; + } + else if (task == ON_STATION) + { + COUNT angle; + POINT org; + + org = planetOuterLocation (group_loc - 1); + angle = FACING_TO_ANGLE (GroupPtr->orbit_pos + 1); + GroupPtr->loc.x = org.x + COSINE (angle, STATION_RADIUS); + GroupPtr->loc.y = org.y + SINE (angle, STATION_RADIUS); + group_loc = 0; + } + + GroupPtr->task = task; + GroupPtr->sys_loc = group_loc; + +#ifdef DEBUG_GROUPS + log_add (log_Debug, "battle group %u(0x%04x) strength " + "%u, type %u, loc %u<%d, %d>, task %u", + which_group, + hGroup, + NumShips, + RaceType, + group_loc, + GroupPtr->loc.x, + GroupPtr->loc.y, + task); +#endif /* DEBUG_GROUPS */ + + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + } + + if (offset != GROUPS_RANDOM) + InitGroupInfo (FALSE); /* Wipe out random battle groups */ + else if (ValidateEvent (ABSOLUTE_EVENT, /* still fresh */ + &month_index, &day_index, &year_index)) + { + CloseStateFile (fp); + return TRUE; + } + + CloseStateFile (fp); + return (GetHeadLink (&GLOBAL (ip_group_q)) != 0); + } + + if (!GH.GroupOffset[which_group]) + { + /* Group not present */ + CloseStateFile (fp); + return FALSE; + } + + + if (which_group == GROUP_LIST) + { + BYTE NumGroups; + COUNT ShipsLeftInLEG; + + // XXX: Hack: First, save the state of last encountered group, if any. + // The assumption here is that we read the group list immediately + // after an IP encounter, and npc_built_ship_q contains whatever + // ships are left in the encountered group (can be none). + ShipsLeftInLEG = CountLinks (&GLOBAL (npc_built_ship_q)); + + SeekStateFile (fp, GH.GroupOffset[0], SEEK_SET); + sread_8 (fp, &LastEncGroup); + + if (LastEncGroup) + { + // The following 'if' is needed because GROUP_LIST is only + // ever read from RANDGRPINFO_FILE, but the LastEncGroup + // may need to be updated in the DEFGRPINFO_FILE as well. + // In that case, PutGroupInfo() will update the correct file. + if (GLOBAL (BattleGroupRef)) + PutGroupInfo (GLOBAL (BattleGroupRef), LastEncGroup); + else + FlushGroupInfo (&GH, offset, LastEncGroup, fp); + } + ReinitQueue (&GLOBAL (npc_built_ship_q)); + + /* Read group 0 into ip_group_q */ + ReinitQueue (&GLOBAL (ip_group_q)); + /* Need a seek because Put/Flush has moved the file ptr */ + SeekStateFile (fp, GH.GroupOffset[0] + 1, SEEK_SET); + sread_8 (fp, &NumGroups); + + while (NumGroups--) + { + BYTE group_id; + BYTE RaceType; + HSHIPFRAG hGroup; + IP_GROUP *GroupPtr; + + sread_8 (fp, &RaceType); + + hGroup = BuildGroup (&GLOBAL (ip_group_q), RaceType); + GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup); + ReadIpGroup (fp, GroupPtr); + group_id = GroupPtr->group_id; + +#ifdef DEBUG_GROUPS + log_add (log_Debug, "G) type %u, loc %u<%d, %d>, task 0x%02x:%u", + RaceType, + GroupPtr->sys_loc, + GroupPtr->loc.x, + GroupPtr->loc.y, + GroupPtr->task, + GroupPtr->dest_loc); +#endif /* DEBUG_GROUPS */ + + UnlockIpGroup (&GLOBAL (ip_group_q), hGroup); + + if (group_id == LastEncGroup && !ShipsLeftInLEG) + { + /* No ships left in the last encountered group, remove it */ +#ifdef DEBUG_GROUPS + log_add (log_Debug, " -- REMOVING"); +#endif /* DEBUG_GROUPS */ + RemoveQueue (&GLOBAL (ip_group_q), hGroup); + FreeIpGroup (&GLOBAL (ip_group_q), hGroup); + } + } + + CloseStateFile (fp); + return (GetHeadLink (&GLOBAL (ip_group_q)) != 0); + } + else + { + /* Read 'which_group' group into npc_built_ship_q */ + BYTE NumShips; + + // XXX: Hack: The assumption here is that we only read the makeup + // of a particular group when initializing an encounter, which + // makes this group 'last encountered'. Also the state of all + // groups is saved here. This may make working with savegames + // harder in the future, as special care will have to be taken + // when loading a game into an encounter. + LastEncGroup = which_group; + // The following 'if' is needed because GROUP_LIST is only + // ever written to RANDGRPINFO_FILE, but the group we are reading + // may be in the DEFGRPINFO_FILE as well. + // In that case, PutGroupInfo() will update the correct file. + // Always calling PutGroupInfo() here would also be acceptable now. + if (offset != GROUPS_RANDOM) + PutGroupInfo (GROUPS_RANDOM, GROUP_LIST); + else + FlushGroupInfo (&GH, GROUPS_RANDOM, GROUP_LIST, fp); + ReinitQueue (&GLOBAL (ip_group_q)); + + ReinitQueue (&GLOBAL (npc_built_ship_q)); + // skip RaceType + SeekStateFile (fp, GH.GroupOffset[which_group] + 1, SEEK_SET); + sread_8 (fp, &NumShips); + + while (NumShips--) + { + BYTE RaceType; + HSHIPFRAG hStarShip; + SHIP_FRAGMENT *FragPtr; + + sread_8 (fp, &RaceType); + + hStarShip = CloneShipFragment (RaceType, + &GLOBAL (npc_built_ship_q), 0); + + FragPtr = LockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + ReadShipFragment (fp, FragPtr); + UnlockShipFrag (&GLOBAL (npc_built_ship_q), hStarShip); + } + + CloseStateFile (fp); + return (GetHeadLink (&GLOBAL (npc_built_ship_q)) != 0); + } +} + +DWORD +PutGroupInfo (DWORD offset, BYTE which_group) +{ + GAME_STATE_FILE *fp; + GROUP_HEADER GH; + + if (offset != GROUPS_RANDOM && which_group != GROUP_LIST) + fp = OpenStateFile (DEFGRPINFO_FILE, "r+b"); + else + fp = OpenStateFile (RANDGRPINFO_FILE, "r+b"); + + if (!fp) + return offset; + + if (offset == GROUPS_ADD_NEW) + { + offset = LengthStateFile (fp); + SeekStateFile (fp, offset, SEEK_SET); + memset (&GH, 0, sizeof (GH)); + GH.star_index = (COUNT)~0; + WriteGroupHeader (fp, &GH); + } + + // XXX: This is a bit dangerous. The assumption here is that we are + // only called to write GROUP_LIST in the GROUPS_RANDOM context, + // which is true right now and in which case we would seek to 0 anyway. + // The latter also makes guarding the seek with + // 'if (which_group != GROUP_LIST)' moot. + if (which_group != GROUP_LIST) + { + SeekStateFile (fp, offset, SEEK_SET); + if (which_group == GROUP_SAVE_IP) + { + LastEncGroup = 0; + which_group = GROUP_LIST; + } + } + ReadGroupHeader (fp, &GH); + +#ifdef NEVER + // XXX: this appears to be a remnant of a slightly different group info + // expiration mechanism. Nowadays, the 'defined' groups never expire, + // and the dead 'random' groups stay in the file with NumShips==0 until + // the entire 'random' group header expires. + if (GetHeadLink (&GLOBAL (npc_built_ship_q)) || GH.GroupOffset[0] == 0) +#endif /* NEVER */ + { + COUNT month_index, day_index, year_index; + + /* The groups in this system are good for the next 7 days */ + month_index = 0; + day_index = 7; + year_index = 0; + ValidateEvent (RELATIVE_EVENT, &month_index, &day_index, &year_index); + GH.day_index = (BYTE)day_index; + GH.month_index = (BYTE)month_index; + GH.year_index = year_index; + } + GH.star_index = CurStarDescPtr - star_array; + +#ifdef DEBUG_GROUPS + log_add (log_Debug, "PutGroupInfo(%lu): %u out of %u -- %u/%u/%u", + offset, which_group, GH.NumGroups, + GH.month_index, GH.day_index, GH.year_index); +#endif /* DEBUG_GROUPS */ + + FlushGroupInfo (&GH, offset, which_group, fp); + + CloseStateFile (fp); + + return (offset); +} + -- cgit v1.2.3