// // Copyright 2020 Electronic Arts Inc. // // TiberianDawn.DLL and RedAlert.dll and corresponding source code 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 3 of the License, or (at your option) any later version. // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed // in the hope that it will be useful, but with permitted additional restrictions // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection /* $Header: F:\projects\c&c\vcs\code\ini.cpv 2.18 16 Oct 1995 16:48:50 JOE_BOSTIC $ */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Command & Conquer * * * * File Name : INI.CPP * * * * Programmer : Joe L. Bostic * * * * Start Date : September 10, 1993 * * * * Last Update : July 30, 1995 [BRR] * * * *---------------------------------------------------------------------------------------------* * Functions: * * Assign_Houses -- Assigns multiplayer houses to various players * * Clear_Flag_Spots -- Clears flag overlays off the map * * Clip_Move -- moves in given direction from given cell; clips to map * * Clip_Scatter -- randomly scatters from given cell; won't fall off map * * Create_Units -- Creates infantry & units, for non-base multiplayer * * Furthest_Cell -- Finds cell furthest from a group of cells * * Place_Flags -- Places flags for multiplayer games * * Read_Scenario_Ini -- Read specified scenario INI file. * * Remove_AI_Players -- Removes the computer AI houses & their units * * Scan_Place_Object -- places an object >near< the given cell * * Set_Scenario_Name -- Creates the INI scenario name string. * * Sort_Cells -- sorts an array of cells by distance * * Write_Scenario_Ini -- Write the scenario INI file. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" /************************************* Prototypes *********************************************/ static void Assign_Houses(void); static void Remove_AI_Players(void); static void Create_Units(void); static void Sort_Cells(CELL *cells, int numcells, CELL *outcells); static int Furthest_Cell(CELL *cells, int numcells, CELL *tcells, int numtcells); static CELL Clip_Scatter(CELL cell, int maxdist); static CELL Clip_Move(CELL cell, FacingType facing, int dist); /*********************************************************************************************** * Set_Scenario_Name -- Creates the INI scenario name string. * * * * This routine is used by the scenario loading and saving code. It generates the scenario * * INI root file name for the specified scenario parameters. * * * * INPUT: * * buf buffer to store filename in; must be long enough for root.ext * * scenario scenario number * * player player type for this game (GDI, NOD, multi-player, ...) * * dir directional parameter for this game (East/West) * * var variation of this game (Lose, A/B/C/D, etc) * * * * OUTPUT: none. * * * * WARNINGS: none. * * * * HISTORY: * * 05/28/1994 JLB : Created. * * 05/01/1995 BRR : 2-player scenarios use same names as multiplayer * *=============================================================================================*/ void Set_Scenario_Name(char *buf, int scenario, ScenarioPlayerType player, ScenarioDirType dir, ScenarioVarType var) { char c_player; // character representing player type char c_dir; // character representing direction type char c_var; // character representing variation type ScenarioVarType i; char fname[_MAX_FNAME+_MAX_EXT]; /* ** Set the player-type value. */ switch (player) { case SCEN_PLAYER_GDI: c_player = HouseTypeClass::As_Reference(HOUSE_GOOD).Prefix; // c_player = 'G'; break; case SCEN_PLAYER_NOD: c_player = HouseTypeClass::As_Reference(HOUSE_BAD).Prefix; // c_player = 'B'; break; case SCEN_PLAYER_JP: c_player = HouseTypeClass::As_Reference(HOUSE_JP).Prefix; // c_player = 'J'; break; /* ** Multi player scenario. */ default: c_player = HouseTypeClass::As_Reference(HOUSE_MULTI1).Prefix; // c_player = 'M'; break; } /* ** Set the directional character value. ** If SCEN_DIR_NONE is specified, randomly pick a direction; otherwise, use 'E' or 'W' */ switch (dir) { case SCEN_DIR_EAST: c_dir = 'E'; break; case SCEN_DIR_WEST: c_dir = 'W'; break; default: case SCEN_DIR_NONE: c_dir = (Random_Pick(0, 1) == 0) ? 'W' : 'E'; break; } /* ** Set the variation value. */ if (var == SCEN_VAR_NONE) { /* ** Find which variations are available for this scenario */ for (i = SCEN_VAR_FIRST; i < SCEN_VAR_COUNT; i++) { sprintf(fname, "SC%c%02d%c%c.INI", c_player, scenario, c_dir, 'A' + i); if (!CCFileClass(fname).Is_Available()) { break; } } if (i==SCEN_VAR_FIRST) { c_var = 'X'; // indicates an error } else { c_var = 'A' + Random_Pick(0, i-1); } } else { switch (var) { case SCEN_VAR_A: c_var = 'A'; break; case SCEN_VAR_B: c_var = 'B'; break; case SCEN_VAR_C: c_var = 'C'; break; case SCEN_VAR_D: c_var = 'D'; break; default: c_var = 'L'; break; } } /* ** generate the filename */ sprintf(buf, "SC%c%02d%c%c", c_player, scenario, c_dir, c_var); } extern void GlyphX_Assign_Houses(void); //ST - 6/25/2019 11:08AM /*********************************************************************************************** * Read_Scenario_Ini -- Read specified scenario INI file. * * * * Read in the scenario INI file. This routine only sets the game * * globals with that data that is explicitly defined in the INI file. * * The remaining necessary interpolated data is generated elsewhere. * * * * INPUT: * * root root filename for scenario file to read * * * * fresh true = should the current scenario be cleared? * * * * OUTPUT: bool; Was the scenario read successful? * * * * WARNINGS: none * * * * HISTORY: * * 10/07/1992 JLB : Created. * *=============================================================================================*/ bool Read_Scenario_Ini(char *root, bool fresh) { char *buffer; // Scenario.ini staging buffer pointer. char fname[_MAX_FNAME+_MAX_EXT]; // full INI filename char buf[128]; // Working string staging buffer. #ifndef USE_RA_AI int rndmax; int rndmin; #endif //USE_RA_AI int len; unsigned char val; ScenarioInit++; /* ** Fetch working pointer to the INI staging buffer. Make sure that the buffer ** is cleared out before proceeding. (Don't use the HidPage for this, since ** the HidPage may be needed for various uncompressions during the INI ** parsing.) */ buffer = (char *)_ShapeBuffer; memset(buffer, '\0', _ShapeBufferSize); if (fresh) { Clear_Scenario(); } /* ** If we are not dealing with scenario 1, or a multi player scenario ** then make sure the correct disk is in the drive. */ if (RequiredCD != -2) { if (Scenario >= 20 && Scenario <60 && GameToPlay == GAME_NORMAL) { RequiredCD = 2; } else { if (Scenario != 1) { if (Scenario >=60){ RequiredCD = -1; }else{ switch (ScenPlayer) { case SCEN_PLAYER_GDI: RequiredCD = 0; break; case SCEN_PLAYER_NOD: RequiredCD = 1; break; default: RequiredCD = -1; break; } } } else { RequiredCD = -1; } } } if (!Force_CD_Available(RequiredCD)) { Prog_End("Read_Scenario_Ini - CD not found", true); if (!RunningAsDLL) { exit(EXIT_FAILURE); } } /* ** Create scenario filename and read the file. */ sprintf(fname,"%s.INI",root); CCFileClass file(fname); if (!file.Is_Available()) { GlyphX_Debug_Print("Failed to find scenario file"); GlyphX_Debug_Print(fname); return(false); } else { GlyphX_Debug_Print("Opened scenario file"); GlyphX_Debug_Print(fname); file.Read(buffer, _ShapeBufferSize-1); } /* ** Init the Scenario CRC value */ ScenarioCRC = 0; len = strlen(buffer); for (int i = 0; i < len; i++) { val = (unsigned char)buffer[i]; #ifndef DEMO Add_CRC(&ScenarioCRC, (unsigned long)val); #endif } /* ** Fetch the appropriate movie names from the INI file. */ WWGetPrivateProfileString("Basic", "Intro", "x", IntroMovie, sizeof(IntroMovie), buffer); WWGetPrivateProfileString("Basic", "Brief", "x", BriefMovie, sizeof(BriefMovie), buffer); WWGetPrivateProfileString("Basic", "Win", "x", WinMovie, sizeof(WinMovie), buffer); WWGetPrivateProfileString("Basic", "Win2", "x", WinMovie2, sizeof(WinMovie2), buffer); WWGetPrivateProfileString("Basic", "Win3", "x", WinMovie3, sizeof(WinMovie3), buffer); WWGetPrivateProfileString("Basic", "Win4", "x", WinMovie4, sizeof(WinMovie4), buffer); WWGetPrivateProfileString("Basic", "Lose", "x", LoseMovie, sizeof(LoseMovie), buffer); WWGetPrivateProfileString("Basic", "Action", "x", ActionMovie, sizeof(ActionMovie), buffer); /* ** For single-player scenarios, 'BuildLevel' is the scenario number. ** This must be set before any buildings are created (if a factory is created, ** it needs to know the BuildLevel for the sidebar.) */ if (GameToPlay == GAME_NORMAL) { #ifdef NEWMENU if (Scenario <= 15) { BuildLevel = Scenario; } else if (_stricmp(ScenarioName, "scg30ea") == 0 || _stricmp(ScenarioName, "scg90ea") == 0 || _stricmp(ScenarioName, "scb22ea") == 0) { // N64 missions require build level 15 BuildLevel = 15; } else { BuildLevel = WWGetPrivateProfileInt("Basic", "BuildLevel", Scenario, buffer); } #else BuildLevel = Scenario; #endif } /* ** Jurassic scenarios are allowed to build the full multiplayer set ** of objects. */ if (Special.IsJurassic && AreThingiesEnabled) { BuildLevel = 98; } /* ** Fetch the transition theme for this scenario. */ TransitTheme = THEME_NONE; WWGetPrivateProfileString("Basic", "Theme", "No Theme", buf, sizeof(buf), buffer); TransitTheme = Theme.From_Name(buf); /* ** Read in the team-type data. The team types must be created before any ** triggers can be created. */ TeamTypeClass::Read_INI(buffer); Call_Back(); /* ** Read in the specific information for each of the house types. This creates ** the houses of different types. */ HouseClass::Read_INI(buffer); Call_Back(); /* ** Read in the trigger data. The triggers must be created before any other ** objects can be initialized. */ TriggerClass::Read_INI(buffer); Call_Back(); /* ** Read in the map control values. This includes dimensions ** as well as theater information. */ Map.Read_INI(buffer); Call_Back(); /* ** Assign PlayerPtr by reading the player's house from the INI; ** Must be done before any TechnoClass objects are created. */ // if (GameToPlay == GAME_NORMAL && (ScenPlayer == SCEN_PLAYER_GDI || ScenPlayer == SCEN_PLAYER_NOD)) { if (GameToPlay == GAME_NORMAL) { WWGetPrivateProfileString("Basic", "Player", "GoodGuy", buf, 127, buffer); CarryOverPercent = WWGetPrivateProfileInt("Basic", "CarryOverMoney", 100, buffer); CarryOverPercent = Cardinal_To_Fixed(100, CarryOverPercent); CarryOverCap = WWGetPrivateProfileInt("Basic", "CarryOverCap", -1, buffer); PlayerPtr = HouseClass::As_Pointer(HouseTypeClass::From_Name(buf)); PlayerPtr->IsHuman = true; int carryover; if (CarryOverCap != -1) { carryover = MIN((int)Fixed_To_Cardinal(CarryOverMoney, CarryOverPercent), CarryOverCap); } else { carryover = Fixed_To_Cardinal(CarryOverMoney, CarryOverPercent); } PlayerPtr->Credits += carryover; PlayerPtr->InitialCredits += carryover; if (Special.IsJurassic) { PlayerPtr->ActLike = Whom; } if (Special.IsEasy) { PlayerPtr->Assign_Handicap(DIFF_EASY); } else if (Special.IsDifficult) { PlayerPtr->Assign_Handicap(DIFF_HARD); } } else { #ifdef OBSOLETE if (GameToPlay==GAME_NORMAL && ScenPlayer==SCEN_PLAYER_JP) { PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI4); PlayerPtr->IsHuman = true; PlayerPtr->Credits += CarryOverMoney; PlayerPtr->InitialCredits += CarryOverMoney; PlayerPtr->ActLike = Whom; } else { Assign_Houses(); } #endif //Call new Assign_Houses function. ST - 6/25/2019 11:07AM //Assign_Houses(); GlyphX_Assign_Houses(); } /* ** Attempt to read the map's binary image file; if fails, read the ** template data from the INI, for backward compatibility */ if (fresh) { if (!Map.Read_Binary(root, &ScenarioCRC)) { TemplateClass::Read_INI(buffer); } } Call_Back(); /* ** Read in and place the 3D terrain objects. */ TerrainClass::Read_INI(buffer); Call_Back(); /* ** Read in and place the units (all sides). */ UnitClass::Read_INI(buffer); Call_Back(); AircraftClass::Read_INI(buffer); Call_Back(); /* ** Read in and place the infantry units (all sides). */ InfantryClass::Read_INI(buffer); Call_Back(); /* ** Read in and place all the buildings on the map. */ BuildingClass::Read_INI(buffer); Call_Back(); /* ** Read in the AI's base information. */ Base.Read_INI(buffer); Call_Back(); /* ** Read in any normal overlay objects. */ OverlayClass::Read_INI(buffer); Call_Back(); /* ** Read in any smudge overlays. */ SmudgeClass::Read_INI(buffer); Call_Back(); /* ** Read in any briefing text. */ char * stage = &BriefingText[0]; *stage = '\0'; int index = 1; /* ** Build the full text of the mission objective. */ for (;;) { int len = (sizeof(BriefingText)-strlen(BriefingText))-1; if (len <= 0) { break; } char buff[16]; sprintf(buff, "%d", index++); *stage = '\0'; WWGetPrivateProfileString("Briefing", buff, "", stage, len, buffer); if (strlen(stage) == 0) break; strcat(stage, " "); stage += strlen(stage); } /* ** If the briefing text could not be found in the INI file, then search ** the mission.ini file. */ if (BriefingText[0] == '\0') { memset(_ShapeBuffer, '\0', _ShapeBufferSize); CCFileClass("MISSION.INI").Read(_ShapeBuffer, _ShapeBufferSize); char * buffer = (char *)Add_Long_To_Pointer(_ShapeBuffer, strlen(_ShapeBuffer)); char * work = &BriefingText[0]; int index = 1; /* ** Build the full text of the mission objective. */ for (;;) { char buff[16]; sprintf(buff, "%d", index++); *work = '\0'; WWGetPrivateProfileString(root, buff, "", work, (sizeof(BriefingText)-strlen(BriefingText))-1, _ShapeBuffer); if (strlen(work) == 0) break; strcat(work, " "); work += strlen(work); } } /* ** Perform a final overpass of the map. This handles smoothing of certain ** types of terrain (tiberium). */ Map.Overpass(); Call_Back(); /* ** Special cases: ** NOD7A cell 2795 - LAND_ROCK ** NOD09A - delete airstrike trigger when radar destroyed ** NOD10B cell 2015 - LAND_ROCK ** NOD13B - trigger AI production when the player reaches the transports - fix repeating airstrike trigger ** NOD13C - delete airstrike trigger when radar destroyed */ if (_stricmp(ScenarioName, "scb07ea") == 0) { Map[(CELL)2795].Override_Land_Type(LAND_ROCK); } if (_stricmp(ScenarioName, "scb09ea") == 0) { for (int index = 0; index < Buildings.Count(); ++index) { BuildingClass* building = Buildings.Ptr(index); if (building != NULL && building->Owner() == HOUSE_GOOD && *building == STRUCT_RADAR) { building->Trigger = TriggerClass::As_Pointer("dely"); if (building->Trigger) { building->Trigger->AttachCount++; } break; } } } if (_stricmp(ScenarioName, "scb10eb") == 0) { Map[(CELL)2015].Override_Land_Type(LAND_ROCK); } if (_stricmp(ScenarioName, "scb13eb") == 0) { TriggerClass* prod = new TriggerClass(); prod->Set_Name("prod"); prod->Event = EVENT_PLAYER_ENTERED; prod->Action = TriggerClass::ACTION_BEGIN_PRODUCTION; prod->House = HOUSE_BAD; CellTriggers[276] = prod; prod->AttachCount++; CellTriggers[340] = prod; prod->AttachCount++; CellTriggers[404] = prod; prod->AttachCount++; CellTriggers[468] = prod; prod->AttachCount++; TriggerClass* xxxx = TriggerClass::As_Pointer("xxxx"); assert(xxxx != NULL); xxxx->IsPersistant = TriggerClass::PERSISTANT; } if (_stricmp(ScenarioName, "scb13ec") == 0) { for (int index = 0; index < Buildings.Count(); ++index) { BuildingClass* building = Buildings.Ptr(index); if (building != NULL && building->Owner() == HOUSE_GOOD && *building == STRUCT_RADAR && building->Trigger == NULL) { building->Trigger = TriggerClass::As_Pointer("delx"); if (building->Trigger) { building->Trigger->AttachCount++; } break; } } } /* ** Scenario fix-up (applied on loaded games as well) */ Fixup_Scenario(); /* ** Multi-player last-minute fixups: ** - If computer players are disabled, remove all computer-owned houses ** - Otherwise, set MPlayerBlitz to 0 or 1, randomly ** - If bases are disabled, create the scenario dynamically ** - Remove any flag spot overlays lying around ** - If capture-the-flag is enabled, assign flags to cells. */ if (GameToPlay != GAME_NORMAL || ScenPlayer == SCEN_PLAYER_2PLAYER || ScenPlayer == SCEN_PLAYER_MPLAYER) { /* ** If Ghosts are disabled and we're not editing, remove computer players ** (Must be done after all objects are read in from the INI) */ if (!MPlayerGhosts && !Debug_Map) { //Remove_AI_Players(); // Done elsewhere now. ST - 6/25/2019 12:33PM } else { /* ** If Ghosts are on, set up their houses for blitzing the humans */ #ifndef USE_RA_AI MPlayerBlitz = IRandom (0,1); // 1 = computer will blitz if (MPlayerBlitz) { if (MPlayerBases) { rndmax = 14000; rndmin = 10000; } else { rndmax = 8000; rndmin = 4000; } for (int i = 0; i < MPlayerMax; i++) { HousesType house = (HousesType)(i + (int)HOUSE_MULTI1); HouseClass *housep = HouseClass::As_Pointer (house); if (housep) { //Added. ST - 6/25/2019 11:37AM housep->BlitzTime = IRandom (rndmin,rndmax); } } } #else // USE_RA_AI MPlayerBlitz = 0; #endif // USE_RA_AI } /* ** Units must be created for each house. If bases are ON, this routine ** will create an MCV along with the units; otherwise, it will just create ** a whole bunch of units. MPlayerUnitCount is the total # of units ** to create. */ if (!Debug_Map) { int save_init = ScenarioInit; // turn ScenarioInit off ScenarioInit = 0; Create_Units(); ScenarioInit = save_init; // turn ScenarioInit back on } /* ** Place crates if MPlayerGoodies is on. */ if (MPlayerGoodies) { for (int index = 0; index < MPlayerCount; index++) { //for (int index = 0; index < 200; index++) { // Lots of crates for test Map.Place_Random_Crate(); } } } Call_Back(); /* ** Return with flag saying that the scenario file was read. */ ScenarioInit--; return(true); } /*********************************************************************************************** * Read_Scenario_Ini_File -- Read specified scenario INI file. * * * * Read in the scenario INI file. This routine only sets the game * * globals with that data that is explicitly defined in the INI file. * * The remaining necessary interpolated data is generated elsewhere. * * * * INPUT: * * scenario_file_name path to the ini for the scenario * * * * bin_file_name path to the bin for the scenario * * * * root root filename for scenario file to read * * * * fresh true = should the current scenario be cleared? * * * * OUTPUT: bool; Was the scenario read successful? * * * * WARNINGS: none * * * * HISTORY: * * 10/28/2019 JAS : Created. * *=============================================================================================*/ bool Read_Scenario_Ini_File(char *scenario_file_name, char* bin_file_name, const char* root, bool fresh) { ScenarioInit++; char *buffer = (char *)_ShapeBuffer; memset(buffer, '\0', _ShapeBufferSize); char buf[128]; int len; unsigned char val; CCFileClass file(scenario_file_name); if (!file.Is_Available()) { GlyphX_Debug_Print("Failed to find scenario file"); GlyphX_Debug_Print(scenario_file_name); return(false); } else { GlyphX_Debug_Print("Opened scenario file"); GlyphX_Debug_Print(scenario_file_name); file.Read(buffer, _ShapeBufferSize - 1); } /* ** Init the Scenario CRC value */ ScenarioCRC = 0; len = strlen(buffer); for (int i = 0; i < len; i++) { val = (unsigned char)buffer[i]; #ifndef DEMO Add_CRC(&ScenarioCRC, (unsigned long)val); #endif } /* ** Fetch the appropriate movie names from the INI file. */ WWGetPrivateProfileString("Basic", "Intro", "x", IntroMovie, sizeof(IntroMovie), buffer); WWGetPrivateProfileString("Basic", "Brief", "x", BriefMovie, sizeof(BriefMovie), buffer); WWGetPrivateProfileString("Basic", "Win", "x", WinMovie, sizeof(WinMovie), buffer); WWGetPrivateProfileString("Basic", "Win2", "x", WinMovie2, sizeof(WinMovie2), buffer); WWGetPrivateProfileString("Basic", "Win3", "x", WinMovie3, sizeof(WinMovie3), buffer); WWGetPrivateProfileString("Basic", "Win4", "x", WinMovie4, sizeof(WinMovie4), buffer); WWGetPrivateProfileString("Basic", "Lose", "x", LoseMovie, sizeof(LoseMovie), buffer); WWGetPrivateProfileString("Basic", "Action", "x", ActionMovie, sizeof(ActionMovie), buffer); /* ** For single-player scenarios, 'BuildLevel' is the scenario number. ** This must be set before any buildings are created (if a factory is created, ** it needs to know the BuildLevel for the sidebar.) */ if (GameToPlay == GAME_NORMAL) { /* ** In this function we are only dealing with custom maps, so set based on the BuildLevel from the map, or 98 if none. ** ST - 4/22/2020 5:14PM */ BuildLevel = WWGetPrivateProfileInt("Basic", "BuildLevel", 98, buffer); } /* ** Jurassic scenarios are allowed to build the full multiplayer set ** of objects. */ if (Special.IsJurassic && AreThingiesEnabled) { BuildLevel = 98; } /* ** Fetch the transition theme for this scenario. */ TransitTheme = THEME_NONE; WWGetPrivateProfileString("Basic", "Theme", "No Theme", buf, sizeof(buf), buffer); TransitTheme = Theme.From_Name(buf); /* ** Read in the team-type data. The team types must be created before any ** triggers can be created. */ TeamTypeClass::Read_INI(buffer); Call_Back(); /* ** Read in the specific information for each of the house types. This creates ** the houses of different types. */ HouseClass::Read_INI(buffer); Call_Back(); /* ** Read in the trigger data. The triggers must be created before any other ** objects can be initialized. */ TriggerClass::Read_INI(buffer); Call_Back(); /* ** Read in the map control values. This includes dimensions ** as well as theater information. */ Map.Read_INI(buffer); Call_Back(); /* ** Assign PlayerPtr by reading the player's house from the INI; ** Must be done before any TechnoClass objects are created. */ // if (GameToPlay == GAME_NORMAL && (ScenPlayer == SCEN_PLAYER_GDI || ScenPlayer == SCEN_PLAYER_NOD)) { if (GameToPlay == GAME_NORMAL) { WWGetPrivateProfileString("Basic", "Player", "GoodGuy", buf, 127, buffer); CarryOverPercent = WWGetPrivateProfileInt("Basic", "CarryOverMoney", 100, buffer); CarryOverPercent = Cardinal_To_Fixed(100, CarryOverPercent); CarryOverCap = WWGetPrivateProfileInt("Basic", "CarryOverCap", -1, buffer); PlayerPtr = HouseClass::As_Pointer(HouseTypeClass::From_Name(buf)); PlayerPtr->IsHuman = true; int carryover; if (CarryOverCap != -1) { carryover = MIN((int)Fixed_To_Cardinal(CarryOverMoney, CarryOverPercent), CarryOverCap); } else { carryover = Fixed_To_Cardinal(CarryOverMoney, CarryOverPercent); } PlayerPtr->Credits += carryover; PlayerPtr->InitialCredits += carryover; if (Special.IsJurassic) { PlayerPtr->ActLike = Whom; } if (Special.IsEasy) { PlayerPtr->Assign_Handicap(DIFF_EASY); } else if (Special.IsDifficult) { PlayerPtr->Assign_Handicap(DIFF_HARD); } } else { #ifdef OBSOLETE if (GameToPlay == GAME_NORMAL && ScenPlayer == SCEN_PLAYER_JP) { PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI4); PlayerPtr->IsHuman = true; PlayerPtr->Credits += CarryOverMoney; PlayerPtr->InitialCredits += CarryOverMoney; PlayerPtr->ActLike = Whom; } else { Assign_Houses(); } #endif //Call new Assign_Houses function. ST - 6/25/2019 11:07AM //Assign_Houses(); GlyphX_Assign_Houses(); } /* ** Attempt to read the map's binary image file; if fails, read the ** template data from the INI, for backward compatibility */ if (fresh) { if (!Map.Read_Binary_File(bin_file_name, &ScenarioCRC)) { TemplateClass::Read_INI(buffer); } } Call_Back(); /* ** Read in and place the 3D terrain objects. */ TerrainClass::Read_INI(buffer); Call_Back(); /* ** Read in and place the units (all sides). */ UnitClass::Read_INI(buffer); Call_Back(); AircraftClass::Read_INI(buffer); Call_Back(); /* ** Read in and place the infantry units (all sides). */ InfantryClass::Read_INI(buffer); Call_Back(); /* ** Read in and place all the buildings on the map. */ BuildingClass::Read_INI(buffer); Call_Back(); /* ** Read in the AI's base information. */ Base.Read_INI(buffer); Call_Back(); /* ** Read in any normal overlay objects. */ OverlayClass::Read_INI(buffer); Call_Back(); /* ** Read in any smudge overlays. */ SmudgeClass::Read_INI(buffer); Call_Back(); /* ** Read in any briefing text. */ char * stage = &BriefingText[0]; *stage = '\0'; int index = 1; /* ** Build the full text of the mission objective. */ for (;;) { int len = (sizeof(BriefingText) - strlen(BriefingText)) - 1; if (len <= 0) { break; } char buff[16]; sprintf(buff, "%d", index++); *stage = '\0'; WWGetPrivateProfileString("Briefing", buff, "", stage, len, buffer); if (strlen(stage) == 0) break; strcat(stage, " "); stage += strlen(stage); } /* ** If the briefing text could not be found in the INI file, then search ** the mission.ini file. */ if (BriefingText[0] == '\0') { memset(_ShapeBuffer, '\0', _ShapeBufferSize); CCFileClass("MISSION.INI").Read(_ShapeBuffer, _ShapeBufferSize); char * buffer = (char *)Add_Long_To_Pointer(_ShapeBuffer, strlen(_ShapeBuffer)); char * work = &BriefingText[0]; int index = 1; /* ** Build the full text of the mission objective. */ for (;;) { char buff[16]; sprintf(buff, "%d", index++); *work = '\0'; WWGetPrivateProfileString(root, buff, "", work, (sizeof(BriefingText) - strlen(BriefingText)) - 1, _ShapeBuffer); if (strlen(work) == 0) break; strcat(work, " "); work += strlen(work); } } /* ** Perform a final overpass of the map. This handles smoothing of certain ** types of terrain (tiberium). */ Map.Overpass(); Call_Back(); /* ** Scenario fix-up (applied on loaded games as well) */ Fixup_Scenario(); /* ** Multi-player last-minute fixups: ** - If computer players are disabled, remove all computer-owned houses ** - Otherwise, set MPlayerBlitz to 0 or 1, randomly ** - If bases are disabled, create the scenario dynamically ** - Remove any flag spot overlays lying around ** - If capture-the-flag is enabled, assign flags to cells. */ if (GameToPlay != GAME_NORMAL || ScenPlayer == SCEN_PLAYER_2PLAYER || ScenPlayer == SCEN_PLAYER_MPLAYER) { /* ** If Ghosts are disabled and we're not editing, remove computer players ** (Must be done after all objects are read in from the INI) */ if (!MPlayerGhosts && !Debug_Map) { //Remove_AI_Players(); // Done elsewhere now. ST - 6/25/2019 12:33PM } else { /* ** If Ghosts are on, set up their houses for blitzing the humans */ #ifndef USE_RA_AI MPlayerBlitz = IRandom(0, 1); // 1 = computer will blitz if (MPlayerBlitz) { if (MPlayerBases) { rndmax = 14000; rndmin = 10000; } else { rndmax = 8000; rndmin = 4000; } for (int i = 0; i < MPlayerMax; i++) { HousesType house = (HousesType)(i + (int)HOUSE_MULTI1); HouseClass *housep = HouseClass::As_Pointer(house); if (housep) { //Added. ST - 6/25/2019 11:37AM housep->BlitzTime = IRandom(rndmin, rndmax); } } } #else // USE_RA_AI MPlayerBlitz = 0; #endif // USE_RA_AI } /* ** Units must be created for each house. If bases are ON, this routine ** will create an MCV along with the units; otherwise, it will just create ** a whole bunch of units. MPlayerUnitCount is the total # of units ** to create. */ if (!Debug_Map) { int save_init = ScenarioInit; // turn ScenarioInit off ScenarioInit = 0; Create_Units(); ScenarioInit = save_init; // turn ScenarioInit back on } /* ** Place crates if MPlayerGoodies is on. */ if (MPlayerGoodies) { for (int index = 0; index < MPlayerCount; index++) { //for (int index = 0; index < 200; index++) { // Lots of crates for test Map.Place_Random_Crate(); } } } Call_Back(); /* ** Return with flag saying that the scenario file was read. */ ScenarioInit--; return(true); } /*********************************************************************************************** * Read_Movies_From_Scenario_Ini -- Reads just the movie files from the scenario. * * * * * * INPUT: * * root root filename for scenario file to read * * * * fresh true = should the current scenario be cleared? * * * * OUTPUT: bool; Was the scenario read successful? * * * * WARNINGS: none * * * * HISTORY: * * 10/14/2019 JAS : Created. * *=============================================================================================*/ bool Read_Movies_From_Scenario_Ini(char *root, bool fresh) { char *buffer; // Scenario.ini staging buffer pointer. char fname[_MAX_FNAME + _MAX_EXT]; // full INI filename // char buf[128]; // Working string staging buffer. #ifndef USE_RA_AI int rndmax; int rndmin; #endif //USE_RA_AI int len; unsigned char val; ScenarioInit++; /* ** Fetch working pointer to the INI staging buffer. Make sure that the buffer ** is cleared out before proceeding. (Don't use the HidPage for this, since ** the HidPage may be needed for various uncompressions during the INI ** parsing.) */ buffer = (char *)_ShapeBuffer; memset(buffer, '\0', _ShapeBufferSize); if (fresh) { Clear_Scenario(); } /* ** If we are not dealing with scenario 1, or a multi player scenario ** then make sure the correct disk is in the drive. */ if (RequiredCD != -2) { if (Scenario >= 20 && Scenario < 60 && GameToPlay == GAME_NORMAL) { RequiredCD = 2; } else { if (Scenario != 1) { if (Scenario >= 60) { RequiredCD = -1; } else { switch (ScenPlayer) { case SCEN_PLAYER_GDI: RequiredCD = 0; break; case SCEN_PLAYER_NOD: RequiredCD = 1; break; default: RequiredCD = -1; break; } } } else { RequiredCD = -1; } } } if (!Force_CD_Available(RequiredCD)) { Prog_End("Read_Scenario_Ini - CD not found", true); if (!RunningAsDLL) { exit(EXIT_FAILURE); } } /* ** Create scenario filename and read the file. */ sprintf(fname, "%s.INI", root); CCFileClass file(fname); if (!file.Is_Available()) { GlyphX_Debug_Print("Failed to find scenario file"); GlyphX_Debug_Print(fname); return(false); } else { GlyphX_Debug_Print("Opened scenario file"); GlyphX_Debug_Print(fname); file.Read(buffer, _ShapeBufferSize - 1); } /* ** Init the Scenario CRC value */ ScenarioCRC = 0; len = strlen(buffer); for (int i = 0; i < len; i++) { val = (unsigned char)buffer[i]; #ifndef DEMO Add_CRC(&ScenarioCRC, (unsigned long)val); #endif } /* ** Fetch the appropriate movie names from the INI file. */ WWGetPrivateProfileString("Basic", "Intro", "x", IntroMovie, sizeof(IntroMovie), buffer); WWGetPrivateProfileString("Basic", "Brief", "x", BriefMovie, sizeof(BriefMovie), buffer); WWGetPrivateProfileString("Basic", "Win", "x", WinMovie, sizeof(WinMovie), buffer); WWGetPrivateProfileString("Basic", "Win2", "x", WinMovie2, sizeof(WinMovie2), buffer); WWGetPrivateProfileString("Basic", "Win3", "x", WinMovie3, sizeof(WinMovie3), buffer); WWGetPrivateProfileString("Basic", "Win4", "x", WinMovie4, sizeof(WinMovie4), buffer); WWGetPrivateProfileString("Basic", "Lose", "x", LoseMovie, sizeof(LoseMovie), buffer); WWGetPrivateProfileString("Basic", "Action", "x", ActionMovie, sizeof(ActionMovie), buffer); /* ** Fetch the transition theme for this scenario. */ TransitTheme = THEME_NONE; WWGetPrivateProfileString("Basic", "Theme", "No Theme", MovieThemeName, sizeof(MovieThemeName), buffer); //TransitTheme = Theme.From_Name(buf); /* ** Return with flag saying that the scenario file was read. */ ScenarioInit--; return(true); } /*********************************************************************************************** * Write_Scenario_Ini -- Write the scenario INI file. * * * * INPUT: * * root root filename for the scenario * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 10/07/1992 JLB : Created. * * 05/11/1995 JLB : Updates movie data. * *=============================================================================================*/ void Write_Scenario_Ini(char *root) { #ifndef CHEAT_KEYS root = root; #else char * buffer; // Scenario.ini staging buffer pointer. char fname[_MAX_FNAME+_MAX_EXT]; // full scenario name HousesType house; CCFileClass file; /* ** Get a working pointer to the INI staging buffer. Make sure that the buffer ** starts cleared out of any data. */ buffer = (char *)_ShapeBuffer; memset(buffer, '\0', _ShapeBufferSize); switch (ScenPlayer) { case SCEN_PLAYER_GDI: house = HOUSE_GOOD; break; case SCEN_PLAYER_NOD: house = HOUSE_BAD; break; case SCEN_PLAYER_JP: house = HOUSE_JP; break; default: house = HOUSE_MULTI1; break; } /* ** Create scenario filename and clear the buffer to empty. */ sprintf(fname,"%s.INI",root); file.Set_Name(fname); if (file.Is_Available()) { // file.Open(READ); file.Read(buffer, _ShapeBufferSize-1); // file.Close(); } else { sprintf(buffer, "; Scenario %d control for house %s.\r\n", Scenario, HouseTypeClass::As_Reference(house).IniName); } WWWritePrivateProfileString("Basic", "Intro", IntroMovie, buffer); WWWritePrivateProfileString("Basic", "Brief", BriefMovie, buffer); WWWritePrivateProfileString("Basic", "Win", WinMovie, buffer); WWWritePrivateProfileString("Basic", "Win2", WinMovie, buffer); WWWritePrivateProfileString("Basic", "Win3", WinMovie, buffer); WWWritePrivateProfileString("Basic", "Win4", WinMovie, buffer); WWWritePrivateProfileString("Basic", "Lose", LoseMovie, buffer); WWWritePrivateProfileString("Basic", "Action", ActionMovie, buffer); WWWritePrivateProfileString("Basic", "Player", PlayerPtr->Class->IniName, buffer); WWWritePrivateProfileString("Basic", "Theme", Theme.Base_Name(TransitTheme), buffer); WWWritePrivateProfileInt("Basic", "BuildLevel", BuildLevel, buffer); WWWritePrivateProfileInt("Basic", "CarryOverMoney", Fixed_To_Cardinal(100, CarryOverPercent), buffer); WWWritePrivateProfileInt("Basic", "CarryOverCap", CarryOverCap, buffer); TeamTypeClass::Write_INI(buffer, true); TriggerClass::Write_INI(buffer, true); Map.Write_INI(buffer); Map.Write_Binary(root); HouseClass::Write_INI(buffer); UnitClass::Write_INI(buffer); InfantryClass::Write_INI(buffer); BuildingClass::Write_INI(buffer); TerrainClass::Write_INI(buffer); OverlayClass::Write_INI(buffer); SmudgeClass::Write_INI(buffer); Base.Write_INI(buffer); /* ** Write the scenario data out to a file. */ // file.Open(WRITE); file.Write(buffer, strlen(buffer)); // file.Close(); /* ** Now update the Master INI file, containing the master list of triggers & teams */ memset(buffer, '\0', _ShapeBufferSize); file.Set_Name("MASTER.INI"); if (file.Is_Available()) { // file.Open(READ); file.Read(buffer, _ShapeBufferSize-1); // file.Close(); } else { sprintf(buffer, "; Master Trigger & Team List.\r\n"); } TeamTypeClass::Write_INI(buffer, false); TriggerClass::Write_INI(buffer, false); // file.Open(WRITE); file.Write(buffer,strlen(buffer)); // file.Close(); #endif } /*********************************************************************************************** * Assign_Houses -- Assigns multiplayer houses to various players * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/09/1995 BRR : Created. * * 07/14/1995 JLB : Records name of player in house structure. * *=============================================================================================*/ static void Assign_Houses(void) { HousesType house; HousesType pref_house; HouseClass *housep; bool house_used[MAX_PLAYERS]; // true = this house is in use bool color_used[16]; // true = this color is in use. We have more than 6 color options now, so bumped this to 16. ST - 6/19/2019 5:18PM int i,j; PlayerColorType color; HousesType house2; HouseClass *housep2; /* ** Init the 'used' flag for all houses & colors to 0 */ for (i = 0; i < MAX_PLAYERS; i++) { house_used[i] = false; } for (i = 0; i < 16; i++) { color_used[i] = false; } /* ** For each player, randomly pick a house */ for (i = 0; i < MPlayerCount; i++) { j = Random_Pick(0, MPlayerMax-1); /* ** If this house was already selected, decrement 'i' & keep looping. */ if (house_used[j]) { i--; continue; } /* ** Set the house, preferred house (GDI/NOD), color, and actual house; ** get a pointer to the house instance */ house = (HousesType)(j + (int)HOUSE_MULTI1); pref_house = MPlayerID_To_HousesType(MPlayerID[i]); color = MPlayerID_To_ColorIndex(MPlayerID[i]); housep = HouseClass::As_Pointer(house); MPlayerHouses[i] = house; /* ** Mark this house & color as used */ house_used[j] = true; color_used[color] = true; /* ** Set the house's IsHuman, Credits, ActLike, & RemapTable */ memset((char *)housep->Name, 0, MPLAYER_NAME_MAX); strncpy((char *)housep->Name, MPlayerNames[i], MPLAYER_NAME_MAX-1); housep->IsHuman = true; housep->Init_Data(color, pref_house, MPlayerCredits); /* ** If this ID is for myself, set up PlayerPtr */ if (MPlayerID[i] == MPlayerLocalID) { PlayerPtr = housep; } } /* ** For all houses not assigned to a player, set them up for computer use */ for (i = 0; i < MPlayerMax; i++) { if (house_used[i] == false) { /* ** Set the house, preferred house (GDI/NOD), and color; get a pointer ** to the house instance */ house = (HousesType)(i + (int)HOUSE_MULTI1); pref_house = (HousesType)(IRandom(0, 1) + (int)HOUSE_GOOD); for (;;) { color = Random_Pick(REMAP_FIRST, REMAP_LAST); if (color_used[color] == false) { break; } } housep = HouseClass::As_Pointer (house); /* ** Mark this house & color as used */ house_used[i] = true; color_used[color] = true; /* ** Set the house's IsHuman, Credits, ActLike, & RemapTable */ housep->IsHuman = false; housep->Init_Data(color, pref_house, MPlayerCredits); } } /* ** Now make all computer-owned houses allies of each other. */ for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MPlayerMax); house++) { housep = HouseClass::As_Pointer(house); if (housep->IsHuman) continue; for (house2 = HOUSE_MULTI1; house2 < (HOUSE_MULTI1 + MPlayerMax); house2++) { housep2 = HouseClass::As_Pointer (house2); if (housep2->IsHuman) continue; housep->Make_Ally(house2); } } } /*********************************************************************************************** * Remove_AI_Players -- Removes the computer AI houses & their units * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/09/1995 BRR : Created. * *=============================================================================================*/ static void Remove_AI_Players(void) { int i; HousesType house; HouseClass *housep; for (i = 0; i < MAX_PLAYERS; i++) { house = (HousesType)(i + (int)HOUSE_MULTI1); housep = HouseClass::As_Pointer (house); if (housep->IsHuman == false) { housep->Clobber_All(); } } } #define USE_GLYPHX_START_LOCATIONS 1 /*********************************************************************************************** * Create_Units -- Creates infantry & units, for non-base multiplayer * * * * This routine uses data tables to determine which units to create for either * * a GDI or NOD house, and how many of each. * * * * It also sets each house's FlagHome & FlagLocation to the Waypoint selected * * as that house's "home" cell. * * * * ------------------ Unit Summary: ------------------------------- * * UNIT_MTANK Medium tank (M1). GDI 7 * * UNIT_JEEP 4x4 jeep replacement. GDI 5 * * UNIT_MLRS MLRS rocket launcher. GDI 99 * * UNIT_APC APC. GDI 10 * * UNIT_HTANK Heavy tank (Mammoth). GDI 13 * * * * UNIT_LTANK Light tank ('Bradly'). NOD 5 * * UNIT_BUGGY Rat patrol dune buggy type NOD 5 * * UNIT_ARTY Artillery unit. NOD 10 * * UNIT_FTANK Flame thrower tank. NOD 11 * * UNIT_STANK Stealth tank (Romulan). NOD 13 * * UNIT_BIKE Nod recon motor-bike. NOD 99 * * * * ~1/3 chance of getting: {UNIT_MHQ, Mobile Head Quarters. * * * * ------------------ Infantry Summary: ------------------------------- * * INFANTRY_E1, Mini-gun armed. GDI/NOD * * INFANTRY_E2, Grenade thrower. GDI * * INFANTRY_E3, Rocket launcher. NOD * * INFANTRY_E6, Rocket launcher GDI * * INFANTRY_E4, Flame thrower equipped. NOD * * INFANTRY_RAMBO, Commando. GDI/NOD * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/09/1995 BRR : Created. * *=============================================================================================*/ static int ReserveInfantryIndex = 0; static void Reserve_Infantry() { if (Infantry.Count() == Infantry.Length()) { delete Infantry.Ptr(ReserveInfantryIndex); ReserveInfantryIndex = (ReserveInfantryIndex + 1) % Infantry.Length(); } } static int ReserveUnitIndex = 0; static void Reserve_Unit() { if (Units.Count() == Units.Length()) { delete Units.Ptr(ReserveUnitIndex); ReserveUnitIndex = (ReserveUnitIndex + 1) % Units.Length(); } } static void Create_Units(void) { enum { NUM_UNIT_CATEGORIES = 8, NUM_INFANTRY_CATEGORIES = 5, }; static struct { int MinLevel; int GDICount; UnitType GDIType; int NODCount; UnitType NODType; } utable[] = { {0, 1,UNIT_MTANK, 2,UNIT_LTANK}, {2, 1,UNIT_JEEP, 1,UNIT_BUGGY}, {3, 1,UNIT_MLRS, 1,UNIT_ARTY}, {4, 1,UNIT_APC, 2,UNIT_BUGGY}, {5, 1,UNIT_JEEP, 1,UNIT_BIKE}, {5, 2,UNIT_JEEP, 1,UNIT_FTANK}, {6, 1,UNIT_MSAM, 1,UNIT_MSAM}, {7, 1,UNIT_HTANK, 2,UNIT_STANK}, }; static int num_units[NUM_UNIT_CATEGORIES]; // # of each type of unit to create int tot_units; // total # units to create static struct { int MinLevel; int GDICount; InfantryType GDIType; int NODCount; InfantryType NODType; } itable[] = { {0, 1,INFANTRY_E1, 1,INFANTRY_E1}, {1, 1,INFANTRY_E2, 1,INFANTRY_E3}, {3, 1,INFANTRY_E3, 1,INFANTRY_E3}, {5, 1,INFANTRY_E3, 1,INFANTRY_E4}, {7, 1,INFANTRY_RAMBO, 1,INFANTRY_RAMBO}, }; static int num_infantry[NUM_INFANTRY_CATEGORIES];// # of each type of infantry to create int tot_infantry; // total # infantry to create CELL waypts[26]; // CELL sorted_waypts[26]; int num_waypts; HousesType h; // house loop counter HouseClass *hptr; // ptr to house being processed CELL centroid; // centroid of this house's stuff // int try_count; // # times we've tried to select a centroid CELL centerpt; // centroid for a category of objects, as a CELL int u_limit; // last allowable index of units for this BuildLevel int i_limit; // last allowable index of infantry for this BuildLevel TechnoClass *obj; // newly-created object int i,j,k; // loop counters int scaleval; // value to scale # units or infantry ReserveInfantryIndex = ReserveUnitIndex = 0; /*------------------------------------------------------------------------ For the current BuildLevel, find the max allowable index into the tables ------------------------------------------------------------------------*/ for (i = 0; i < NUM_UNIT_CATEGORIES; i++) { if (BuildLevel >= (unsigned)utable[i].MinLevel) u_limit = i; } for (i = 0; i < NUM_INFANTRY_CATEGORIES; i++) { if (BuildLevel >= (unsigned)utable[i].MinLevel) i_limit = i; } /*------------------------------------------------------------------------ Compute how many of each buildable category to create ------------------------------------------------------------------------*/ /*........................................................................ Compute allowed # units ........................................................................*/ tot_units = (MPlayerUnitCount * 2) / 3; // tot_units = MAX(tot_units, 1); /*........................................................................ Init # of each category to 0 ........................................................................*/ for (i = 0; i <= u_limit; i++) num_units[i] = 0; /*........................................................................ Increment # of each category, until we've used up all units ........................................................................*/ j = 0; for (i = 0; i < tot_units; i++) { num_units[j]++; j++; if (j > u_limit) j = 0; } /*........................................................................ Compute allowed # infantry ........................................................................*/ tot_infantry = MPlayerUnitCount - tot_units; /*........................................................................ Init # of each category to 0 ........................................................................*/ for (i = 0; i <= i_limit; i++) num_infantry[i] = 0; /*........................................................................ Increment # of each category, until we've used up all infantry ........................................................................*/ j = 0; for (i = 0; i < tot_infantry; i++) { num_infantry[j]++; j++; if (j > i_limit) j = 0; } /*------------------------------------------------------------------------ Now sort all the Waypoints on the map by distance. ------------------------------------------------------------------------*/ num_waypts = 0; // counts # waypoints /*........................................................................ First, copy all valid waytpoints into my 'waypts' array ........................................................................*/ for (i = 0; i < 26; i++) { if (Waypoint[i] != -1) { waypts[num_waypts] = Waypoint[i]; num_waypts++; } } /*........................................................................ Now sort the 'waypts' array ........................................................................*/ #ifndef USE_GLYPHX_START_LOCATIONS //Sort_Cells (waypts, num_waypts, sorted_waypts); #endif /*------------------------------------------------------------------------ Loop through all houses. Computer-controlled houses, with MPlayerBases ON, are treated as though bases are OFF (since we have no base-building AI logic.) ------------------------------------------------------------------------*/ for (h = HOUSE_MULTI1; h < (HOUSE_MULTI1 + MPlayerMax); h++) { /*..................................................................... Get a pointer to this house; if there is none, go to the next house .....................................................................*/ hptr = HouseClass::As_Pointer(h); if (!hptr) continue; #ifdef USE_GLYPHX_START_LOCATIONS /* ** New code that respects the start locations passed in from GlyphX. ** ** ST - 1/8/2020 3:39PM */ centroid = waypts[hptr->StartLocationOverride]; #else // USE_GLYPHX_START_LOCATIONS /* ** Original start position logic. */ /*..................................................................... Pick a random waypoint; if the chosen waypoint isn't valid, try again. 'centroid' will be the centroid of all this house's stuff. .....................................................................*/ try_count = 0; while (1) { j = IRandom(0,MPlayerMax - 1); if (sorted_waypts[j] != -1) { centroid = sorted_waypts[j]; sorted_waypts[j] = -1; break; } try_count++; /*.................................................................. OK, we've tried enough; just pick any old cell at random, as long as it's mappable. ..................................................................*/ if (try_count > 200) { while (1) { centroid = IRandom(0,MAP_CELL_TOTAL - 1); if (Map.In_Radar(centroid)) break; } break; } } #endif // USE_GLYPHX_START_LOCATIONS /*--------------------------------------------------------------------- If Bases are ON, human & computer houses are treated differently ---------------------------------------------------------------------*/ if (MPlayerBases) { /*.................................................................. - For a human-controlled house: - Set 'scaleval' to 1 - Create an MCV - Attach a flag to it for capture-the-flag mode ..................................................................*/ if (hptr->IsHuman) { scaleval = 1; #ifndef USE_RA_AI // Moved to below. ST - 7/25/2019 11:21AM obj = new UnitClass (UNIT_MCV, h); if (!obj->Unlimbo(Cell_Coord(centroid),DIR_N)) { if (!Scan_Place_Object(obj, centroid)) { delete obj; obj = NULL; } } if (obj) { hptr->FlagHome = 0; hptr->FlagLocation = 0; if (Special.IsCaptureTheFlag) { hptr->Flag_Attach((UnitClass *)obj,true); } } #endif //USE_RA_AI } else { /*.................................................................. - For computer-controlled house: - Set 'scaleval' to 3 - Create a Mobile HQ for capture-the-flag mode ..................................................................*/ // Added fix for divide by zero. ST - 6/26/2019 10:40AM int ai_player_count = MPlayerMax - MPlayerCount; //scaleval = 3 / (MPlayerMax - MPlayerCount); //scaleval = max(ai_player_count, 1); scaleval = 1; //Set to 1 since EA QA can't beat skirmish with scaleval set higher. //if (scaleval==0) { // scaleval = 1; //} #ifndef USE_RA_AI // Give the AI an MCV below. ST - 7/25/2019 11:22AM if (Special.IsCaptureTheFlag) { obj = new UnitClass (UNIT_MHQ, h); if (!obj->Unlimbo(Cell_Coord(centroid),DIR_N)) { if (!Scan_Place_Object(obj, centroid)) { delete obj; obj = NULL; } } hptr->FlagHome = 0; // turn house's flag off hptr->FlagLocation = 0; } #endif //USE_RA_AI } #ifdef USE_RA_AI /* ** Moved HQ code down here, so the AI player gets one too. ST - 7/25/2019 11:21AM */ Reserve_Unit(); obj = new UnitClass (UNIT_MCV, h); if (!obj->Unlimbo(Cell_Coord(centroid),DIR_N)) { if (!Scan_Place_Object(obj, centroid)) { delete obj; obj = NULL; } } if (obj) { hptr->FlagHome = 0; hptr->FlagLocation = 0; if (Special.IsCaptureTheFlag) { hptr->Flag_Attach((UnitClass *)obj,true); } } #endif //USE_RA_AI } else { /*--------------------------------------------------------------------- If bases are OFF, set 'scaleval' to 1 & create a Mobile HQ for capture-the-flag mode. ---------------------------------------------------------------------*/ scaleval = 1; if (Special.IsCaptureTheFlag) { Reserve_Unit(); obj = new UnitClass (UNIT_MHQ, h); obj->Unlimbo(Cell_Coord(centroid),DIR_N); hptr->FlagHome = 0; // turn house's flag off hptr->FlagLocation = 0; } } /*--------------------------------------------------------------------- Set the house's max # units (this is used in the Mission_Timed_Hunt()) ---------------------------------------------------------------------*/ hptr->MaxUnit = MPlayerUnitCount * scaleval; /*--------------------------------------------------------------------- Create units for this house ---------------------------------------------------------------------*/ for (i = 0; i <= u_limit; i++) { /*.................................................................. Find the center point for this category. ..................................................................*/ centerpt = Clip_Scatter(centroid,4); /*.................................................................. Place objects; loop through all unit in this category ..................................................................*/ for (j = 0; j < num_units[i] * scaleval; j++) { /*............................................................... Create a GDI unit ...............................................................*/ if (hptr->ActLike == HOUSE_GOOD) { for (k = 0; k < utable[i].GDICount; k++) { Reserve_Unit(); obj = new UnitClass (utable[i].GDIType, h); if (!Scan_Place_Object(obj, centerpt)) { delete obj; } else { /* ** Don't use MISSION_TIMED_HUNT since it can trigger blitz behavior. ST - 2/28/2020 10:51AM */ //if (!hptr->IsHuman) { // obj->Set_Mission(MISSION_TIMED_HUNT); //} if (!hptr->IsHuman) { obj->Set_Mission(MISSION_GUARD_AREA); } } } } else { /*............................................................... Create a NOD unit ...............................................................*/ for (k = 0; k < utable[i].NODCount; k++) { Reserve_Unit(); obj = new UnitClass (utable[i].NODType, h); if (!Scan_Place_Object(obj, centerpt)) { delete obj; } else { /* ** Don't use MISSION_TIMED_HUNT since it can trigger blitz behavior. ST - 2/28/2020 10:51AM */ //if (!hptr->IsHuman) { // obj->Set_Mission(MISSION_TIMED_HUNT); //} if (!hptr->IsHuman) { obj->Set_Mission(MISSION_GUARD_AREA); } } } } } } /*--------------------------------------------------------------------- Create infantry ---------------------------------------------------------------------*/ for (i = 0; i <= i_limit; i++) { /*.................................................................. Find the center point for this category. ..................................................................*/ centerpt = Clip_Scatter(centroid,4); /*.................................................................. Place objects; loop through all unit in this category ..................................................................*/ for (j = 0; j < num_infantry[i] * scaleval; j++) { /*............................................................... Create GDI infantry (Note: Unlimbo calls Enter_Idle_Mode(), which assigns the infantry to HUNT; we must use Set_Mission() to override this state.) ...............................................................*/ if (hptr->ActLike == HOUSE_GOOD) { for (k = 0; k < itable[i].GDICount; k++) { Reserve_Infantry(); obj = new InfantryClass (itable[i].GDIType, h); if (!Scan_Place_Object(obj, centerpt)) { delete obj; } else { /* ** Don't use MISSION_TIMED_HUNT since it can trigger blitz behavior. ST - 2/28/2020 10:51AM */ //if (!hptr->IsHuman) { // obj->Set_Mission(MISSION_TIMED_HUNT); //} if (!hptr->IsHuman) { obj->Set_Mission(MISSION_GUARD_AREA); } } } } else { /*............................................................... Create NOD infantry ...............................................................*/ for (k = 0; k < itable[i].NODCount; k++) { Reserve_Infantry(); obj = new InfantryClass (itable[i].NODType, h); if (!Scan_Place_Object(obj, centerpt)) { delete obj; } else { /* ** Don't use MISSION_TIMED_HUNT since it can trigger blitz behavior. ST - 2/28/2020 10:51AM */ //if (!hptr->IsHuman) { // obj->Set_Mission(MISSION_TIMED_HUNT); //} if (!hptr->IsHuman) { obj->Set_Mission(MISSION_GUARD_AREA); } } } } } } } } /*********************************************************************************************** * Scan_Place_Object -- places an object >near< the given cell * * * * INPUT: * * obj ptr to object to Unlimbo * * cell center of search area * * * * OUTPUT: * * true = object was placed; false = it wasn't * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/09/1995 BRR : Created. * *=============================================================================================*/ int Scan_Place_Object(ObjectClass *obj, CELL cell) { int dist; // for object placement FacingType rot; // for object placement FacingType fcounter; // for object placement int tryval; CELL newcell; TechnoClass *techno; int skipit; /*------------------------------------------------------------------------ First try to unlimbo the object in the given cell. ------------------------------------------------------------------------*/ if (Map.In_Radar(cell)) { techno = Map[cell].Cell_Techno(); if (!techno || (techno->What_Am_I()==RTTI_INFANTRY && obj->What_Am_I()==RTTI_INFANTRY)) { if (obj->Unlimbo(Cell_Coord(cell),DIR_N)) { return(true); } } } /*------------------------------------------------------------------------ Loop through distances from the given center cell; skip the center cell. For each distance, try placing the object along each rotational direction; if none are available, try each direction with a random scatter value. If that fails, go to the next distance. This ensures that the closest coordinates are filled first. ------------------------------------------------------------------------*/ for (dist = 1; dist < 32; dist++) { /*..................................................................... Pick a random starting direction .....................................................................*/ rot = (FacingType)IRandom (FACING_N, FACING_NW); /*..................................................................... Try all directions twice .....................................................................*/ for (tryval = 0 ; tryval < 2; tryval++) { /*.................................................................. Loop through all directions, at this distance. ..................................................................*/ for (fcounter = FACING_N; fcounter <= FACING_NW; fcounter++) { skipit = false; /*............................................................... Pick a coordinate along this directional axis ...............................................................*/ newcell = Clip_Move(cell, rot, dist); /*............................................................... If this is our second try at this distance, add a random scatter to the desired cell, so our units aren't all aligned along spokes. ...............................................................*/ if (tryval > 0) newcell = Clip_Scatter (newcell, 1); /*............................................................... If, by randomly scattering, we've chosen the exact center, skip it & try another direction. ...............................................................*/ if (newcell==cell) skipit = true; if (!skipit) { /*............................................................ Only attempt to Unlimbo the object if: - there is no techno in the cell - the techno in the cell & the object are both infantry ............................................................*/ techno = Map[newcell].Cell_Techno(); if (!techno || (techno->What_Am_I()==RTTI_INFANTRY && obj->What_Am_I()==RTTI_INFANTRY)) { if (obj->Unlimbo(Cell_Coord(newcell),DIR_N)) { return(true); } } } rot++; if (rot > FACING_NW) rot = FACING_N; } } } return(false); } /*********************************************************************************************** * Sort_Cells -- sorts an array of cells by distance * * * * INPUT: * * cells array to sort * * numcells # entries in 'cells' * * outcells array to store sorted values in * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 07/19/1995 BRR : Created. * *=============================================================================================*/ static void Sort_Cells(CELL *cells, int numcells, CELL *outcells) { int i,j,k; int num_sorted = 0; int num_unsorted = numcells; /*------------------------------------------------------------------------ Pick the first cell at random ------------------------------------------------------------------------*/ j = Random_Pick(0,numcells - 1); outcells[0] = cells[j]; num_sorted++; for (k = j; k < num_unsorted - 1; k++) { cells[k] = cells[k + 1]; } num_unsorted--; /*------------------------------------------------------------------------ After the first cell, assign the other cells based on who's furthest away from the chosen ones. ------------------------------------------------------------------------*/ for (i = 0; i < numcells; i++) { j = Furthest_Cell (outcells, num_sorted, cells, num_unsorted); outcells[num_sorted] = cells[j]; num_sorted++; for (k = j; k < num_unsorted - 1; k++) { cells[k] = cells[k + 1]; } num_unsorted--; } } /*********************************************************************************************** * Furthest_Cell -- Finds cell furthest from a group of cells * * * * INPUT: * * cells array of cells to find furthest-cell-away-from * * numcells # entries in 'cells' * * tcells array of cells to test; one of these will be selected as being * * "furthest" from all the cells in 'cells' * * numtcells # entries in 'tcells' * * * * OUTPUT: * * index of 'tcell' that's furthest away from 'cells' * * * * WARNINGS: * * none. * * * * HISTORY: * * 07/19/1995 BRR : Created. * *=============================================================================================*/ static int Furthest_Cell(CELL *cells, int numcells, CELL *tcells, int numtcells) { int i; int j; int mindist; // minimum distance a 'tcell' is from a 'cell' int maxmindist; // the highest mindist value of all tcells int maxmin_idx; // index of the tcell with largest mindist int dist; // working distance measure /*------------------------------------------------------------------------ Initialize ------------------------------------------------------------------------*/ maxmindist = 0; maxmin_idx = 0; /*------------------------------------------------------------------------ Loop through all test cells, finding the furthest one from all entries in the 'cells' array ------------------------------------------------------------------------*/ for (i = 0; i < numtcells; i++) { /*..................................................................... Find the 'cell' closest to this 'tcell' .....................................................................*/ mindist = 0xffff; for (j = 0; j < numcells; j++) { dist = Distance (tcells[i],cells[j]); if (dist <= mindist) { mindist = dist; } } /*..................................................................... If this tcell is further away than the others, save its distance & index value .....................................................................*/ if (mindist >= maxmindist) { maxmindist = mindist; maxmin_idx = i; } } return (maxmin_idx); } /*********************************************************************************************** * Clip_Scatter -- randomly scatters from given cell; won't fall off map * * * * INPUT: * * cell cell to scatter from * * maxdist max distance to scatter * * * * OUTPUT: * * new cell number * * * * WARNINGS: * * none. * * * * HISTORY: * * 07/30/1995 BRR : Created. * *=============================================================================================*/ static CELL Clip_Scatter(CELL cell, int maxdist) { int x,y; int xdist; int ydist; int xmin,xmax; int ymin,ymax; /*------------------------------------------------------------------------ Get X & Y coords of given starting cell ------------------------------------------------------------------------*/ x = Cell_X(cell); y = Cell_Y(cell); /*------------------------------------------------------------------------ Compute our x & y limits ------------------------------------------------------------------------*/ xmin = Map.MapCellX; xmax = xmin + Map.MapCellWidth - 1; ymin = Map.MapCellY; ymax = ymin + Map.MapCellHeight - 1; /*------------------------------------------------------------------------ Adjust the x-coordinate ------------------------------------------------------------------------*/ xdist = IRandom(0,maxdist); if (IRandom(0,1)==0) { x += xdist; if (x > xmax) { x = xmax; } } else { x -= xdist; if (x < xmin) { x = xmin; } } /*------------------------------------------------------------------------ Adjust the y-coordinate ------------------------------------------------------------------------*/ ydist = IRandom(0,maxdist); if (IRandom(0,1)==0) { y += ydist; if (y > ymax) { y = ymax; } } else { y -= ydist; if (y < ymin) { y = ymin; } } return (XY_Cell(x,y)); } /*********************************************************************************************** * Clip_Move -- moves in given direction from given cell; clips to map * * * * INPUT: * * cell cell to start from * * facing direction to move * * dist distance to move * * * * OUTPUT: * * new cell number * * * * WARNINGS: * * none. * * * * HISTORY: * * 07/30/1995 BRR : Created. * *=============================================================================================*/ static CELL Clip_Move(CELL cell, FacingType facing, int dist) { int x,y; int xmin,xmax; int ymin,ymax; /*------------------------------------------------------------------------ Get X & Y coords of given starting cell ------------------------------------------------------------------------*/ x = Cell_X(cell); y = Cell_Y(cell); /*------------------------------------------------------------------------ Compute our x & y limits ------------------------------------------------------------------------*/ xmin = Map.MapCellX; xmax = xmin + Map.MapCellWidth - 1; ymin = Map.MapCellY; ymax = ymin + Map.MapCellHeight - 1; /*------------------------------------------------------------------------ Adjust the x-coordinate ------------------------------------------------------------------------*/ switch (facing) { case FACING_N: y -= dist; break; case FACING_NE: x += dist; y -= dist; break; case FACING_E: x += dist; break; case FACING_SE: x += dist; y += dist; break; case FACING_S: y += dist; break; case FACING_SW: x -= dist; y += dist; break; case FACING_W: x -= dist; break; case FACING_NW: x -= dist; y -= dist; break; } /*------------------------------------------------------------------------ Clip to the map ------------------------------------------------------------------------*/ if (x > xmax) x = xmax; if (x < xmin) x = xmin; if (y > ymax) y = ymax; if (y < ymin) y = ymin; return (XY_Cell(x,y)); }