CnC_Remastered_Collection/TIBERIANDAWN/INI.CPP

2384 lines
84 KiB
C++
Raw Normal View History

//
// 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;
}
}
}
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
/*
** 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));
}