Author Topic: RubbleMaker class for OP2Helper  (Read 4089 times)

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
RubbleMaker class for OP2Helper
« on: March 26, 2016, 04:54:06 PM »
I'm looking at adding a static class to OP2Helper for automating the placement of rubble, blast marks, and destroyed buildings (the 2 tubes, rubble, and dozed area).

The code currently relies on HFL to pull the width and height of all the buildings. If the destroyed building code is well received, I would like to design a private std::map<map_id, BuildingInfo> to store the needed values to create a destroyed building. Of note, HFL also attempts to provide the amount of common and rare rubble that a building will 'drop' on destruction, but it didn't seem to work properly when I attempted pulling the values for a residence.

Private structure to store destroyed building data
Code: [Select]
struct BuildingInfo
{
bool IncludesTubes;
int CommonRubbleCount;
int RareRubbleCount;
int FootprintWidth;
int FootprintHeight;
};

I put a lot of time into cleaning this code, but it is my first static class in C++, so let me know if I'm not using best practices in some places.

RubbleMaker.h
Code: [Select]
#pragma once

#include "OP2Helper\Bulldozer.h"
#include "HFL\Source\HFL.h"
#include "Outpost2DLL\Outpost2DLL.h"
#include <vector>

/*Automates placing Rubble, Blast Marks (meteor strikes or destroyed vehicles) and Destroyed
Buildings. When attempting to place rubble or blast marks, LOCATIONs are checked to ensure
they are valid before placement. When placing multiple blast marks or rubble, if not enough
valid LOCATIONs are provided, the function will place as many as possible and then exit.

BLAST MARK - Will not be added to transition tiles (like mud/dirt), special objects (like
conestoga debris tiles), or special terrain features (like multi-tile craters and cliffs).

RUBBLE - Will only be placed on tiles with a unit passable CellType.

DESTROYED BUILDING - Destroyed buildings include the properly sized extended
dozed footprint, 2 tubes if appropriate, and proper number of common and rare rubble. No
error checking is done to ensure LOCATION of destroyed building is valid (IE no cliffs in
the middle).*/
class RubbleMaker
{
public:
// Attempts to place blasted tile. Returns true if placement successful.
static bool CreateBlastedTile(const LOCATION &loc);

//Attempts to randomly place blasted tiles within a MAP_RECT.
static void CreateBlastedTiles(const MAP_RECT &mapRect, int count);

//Attempts to place blasted tiles at each location.
template<typename LOCATION, size_t N>
static void CreateBlastedTiles(const std::array<LOCATION, N> &locs)
{
for (const LOCATION &loc : locs)
{
CreateBlastedTile(loc);
}
}

// Attempts to place blasted tiles at each location.
static void CreateBlastedTiles(const std::vector<LOCATION> &locs);

static bool RubbleAllowed(const LOCATION &loc);

// Attempts to place common rubble. Returns true if placement successful.
static bool CreateCommonRubble(const LOCATION &loc);

// Attempts to place rare rubble. Returns true if placement successful.
static bool CreateRareRubble(const LOCATION &loc);

// Attempts to place rubble randomly over provided MAP_RECT.
static void CreateRandomRubble(const MAP_RECT &mapRect, int commonRubbleAmount, int rareRubbleAmount);

// Places a destroyed building of given map_id type. LOCATION specifies the position
// where the North/South and East/West tubes would meet underneath the structure.
static void CreateDestroyedBuilding(map_id buildingType, const LOCATION &loc);

// Places a destroyed building with custom parameters.
static void CreateDestroyedBuilding(const LOCATION &loc, int buildingWidth, int buildingHeight,
bool includeTubes, int commonRubbleAmount, int rareRubbleAmount);

private:
static const int FirstRockTileIndex = 439;
static const int FirstSandTileIndex = 1206;

static const int FirstCommonRubbleMudIndex = 323;
static const int FirstCommonRubbleRockIndex = 830;
static const int FirstCommonRubbleSandIndex = 1585;

static const int FirstRareRubbleMudIndex = 327;
static const int FirstRareRubbleRockIndex = 834;
static const int FirstRareRubbleSandIndex = 1589;

static const int RubbleIndicesPerSet = 4;

static const int FirstDozedMudBlastTileIndex = 264;
static const int FirstDozedRockBlastTileIndex = 789;
static const int FirstDozedSandBlastTileIndex = 1547;

static const int FirstRoughMudBlastTileIndex = 267;
static const int FirstRoughRockBlastTileIndex = 792;
static const int FirstRoughSandBlastTileIndex = 1550;

static const int DozedBlastIndicesPerSet = 3; //Dozed blasted tile indices always occur before rough blasted tile indices

static const int RoughMudBlastIndicesPerSet = 3;
static const int RoughRockBlastIndicesPerSet = 6; //There are 3 extra rough rock blasted tiles compared to mud and sand. Possibly, two sets of 3 designed for two different looking rock fields. I cannot tell the difference in testing.
static const int RoughSandBlastIndicesPerSet = 3;

static void GetAllLocationsFromRect(std::vector<LOCATION> &locations, const MAP_RECT &mapRect);
static MAP_RECT GetBuildingDozedRect(const LOCATION &loc, int buildingWidth, int buildingHeight); //Returned MAP_RECT does not include dozed perimeter tiles of building.
static MAP_RECT GetBuildingFootprint(const MAP_RECT &buildingFootprint);

static void RubbleMaker::CreateRandomCommonRubble(int commonRubbleAmount, std::vector<LOCATION> &possRubbleLocs);
static void RubbleMaker::CreateRandomRareRubble(int rareRubbleAmount, std::vector<LOCATION> &possRubbleLocs);
static void RubbleMaker::CreateDestroyedBuildingTubes(const LOCATION &buildingLoc, const MAP_RECT &buildingFootprint);
};

RubbleMaker.cpp
Code: [Select]
#include "RubbleMaker.h"

bool RubbleMaker::RubbleAllowed(const LOCATION &loc)
{
int currentCellType = GameMap::GetCellType(loc);

return
currentCellType == CellTypes::cellDozedArea ||
currentCellType == CellTypes::cellFastPassible1 ||
currentCellType == CellTypes::cellFastPassible2 ||
currentCellType == CellTypes::cellMediumPassible1 ||
currentCellType == CellTypes::cellMediumPassible2 ||
currentCellType == CellTypes::cellSlowPassible1 ||
currentCellType == CellTypes::cellSlowPassible2;
}

bool RubbleMaker::CreateBlastedTile(const LOCATION &loc)
{
int originalTileIndex = GameMap::GetTile(loc);

if (originalTileIndex <= 32) //Place rough mud blast crater
{
//Mud blast crater tile indicies are 264-269
GameMap::SetTile(loc, FirstRoughMudBlastTileIndex + TethysGame::GetRand(RoughMudBlastIndicesPerSet));
}
else if (originalTileIndex >= 409 && originalTileIndex <= 416) //Place dozed mud blast crater
{
GameMap::SetTile(loc, FirstDozedMudBlastTileIndex + TethysGame::GetRand(DozedBlastIndicesPerSet));
}
else if (originalTileIndex >= 439 && originalTileIndex <= 504) //Place rough rock blast crater
{
//Rock blast crater tile indicies are 789-797
GameMap::SetTile(loc, FirstRoughRockBlastTileIndex + TethysGame::GetRand(RoughRockBlastIndicesPerSet));
}
else if (originalTileIndex >= 921 && originalTileIndex <= 928) //Place dozed rock blast crater
{
GameMap::SetTile(loc, FirstDozedRockBlastTileIndex + TethysGame::GetRand(DozedBlastIndicesPerSet));
}
else if (
(originalTileIndex >= FirstSandTileIndex && originalTileIndex <= 1269) || //Place rough sand blast crater
(originalTileIndex >= 1286 && originalTileIndex <= 1295)) // Blasted tiles are not allowed on a small set of terrain tiles before 1 tile meteor crater. Blasted tiles are allowed over 1 tile craters.
{
//Sand blast crater tile indicies are 1547-1552
GameMap::SetTile(loc, FirstRoughSandBlastTileIndex + TethysGame::GetRand(RoughSandBlastIndicesPerSet));
}
else if (originalTileIndex >= 1670 && originalTileIndex <= 1677) //Place dozed sand blast crater
{
GameMap::SetTile(loc, FirstDozedSandBlastTileIndex + TethysGame::GetRand(DozedBlastIndicesPerSet));
}

//Return true if tile at provided LOCATION has changed to a blast tile.
return originalTileIndex != GameMap::GetTile(loc);
}

void RubbleMaker::CreateBlastedTiles(const std::vector<LOCATION> &locs)
{
for (const LOCATION &loc : locs)
{
CreateBlastedTile(loc);
}
}

void RubbleMaker::CreateBlastedTiles(const MAP_RECT &mapRect, int count)
{
std::vector<LOCATION> possibleBlastLocs;
GetAllLocationsFromRect(possibleBlastLocs, mapRect);

int placedBlastCount = 0;
while (placedBlastCount < count)
{
if (possibleBlastLocs.size() == 0)
{
break;
}

int locIndex = TethysGame::GetRand(possibleBlastLocs.size());

if (CreateBlastedTile(possibleBlastLocs[locIndex]))
{
++placedBlastCount;
}

possibleBlastLocs.erase(possibleBlastLocs.begin() + locIndex);
}
}

bool RubbleMaker::CreateCommonRubble(const LOCATION &loc)
{
if (!RubbleAllowed(loc))
{
return false;
}

GameMap::SetCellType(loc, CellTypes::cellRubble);

int originalTileIndex = GameMap::GetTile(loc);

if (originalTileIndex < FirstRockTileIndex)
{
//Mud Common Rubble is 323 to 326
GameMap::SetTile(loc, FirstCommonRubbleMudIndex + TethysGame::GetRand(RubbleIndicesPerSet));
}
else if (originalTileIndex < FirstSandTileIndex)
{
//Rock Common Rubble is 830 to 833
GameMap::SetTile(loc, FirstCommonRubbleRockIndex + TethysGame::GetRand(RubbleIndicesPerSet));
}
else
{
//Dirt Common Rubble is 1585 to 1588
GameMap::SetTile(loc, FirstCommonRubbleSandIndex + TethysGame::GetRand(RubbleIndicesPerSet));
}

return true;
}

bool RubbleMaker::CreateRareRubble(const LOCATION &loc)
{
if (!RubbleAllowed(loc))
{
return false;
}

GameMap::SetCellType(loc, CellTypes::cellRubble);

int originalTileIndex = GameMap::GetTile(loc);

if (originalTileIndex < FirstRockTileIndex)
{
//Mud Rare Rubble is 327 to 330
GameMap::SetTile(loc, FirstRareRubbleMudIndex + TethysGame::GetRand(RubbleIndicesPerSet));
}
else if (originalTileIndex < FirstSandTileIndex)
{
//Rock Rare Rubble is 834 to 837
GameMap::SetTile(loc, FirstRareRubbleRockIndex + TethysGame::GetRand(RubbleIndicesPerSet));
}
else
{
//Dirt Rare Rubble is 1589 to 1592
GameMap::SetTile(loc, FirstRareRubbleSandIndex + TethysGame::GetRand(RubbleIndicesPerSet));
}

return true;
}

void RubbleMaker::CreateRandomRubble(const MAP_RECT &mapRect, int commonRubbleAmount, int rareRubbleAmount)
{
std::vector<LOCATION> possRubbleLocs;
GetAllLocationsFromRect(possRubbleLocs, mapRect);

CreateRandomCommonRubble(commonRubbleAmount, possRubbleLocs);
CreateRandomRareRubble(rareRubbleAmount, possRubbleLocs);
}

void RubbleMaker::CreateDestroyedBuilding(const LOCATION &loc, int buildingWidth, int buildingHeight,
bool includeTubes, int commonRubbleAmount, int rareRubbleAmount)
{
MAP_RECT buildingDozedRect = GetBuildingDozedRect(loc, buildingWidth, buildingHeight);
MAP_RECT buildingFootprint = GetBuildingFootprint(buildingDozedRect);

Bulldozer::Doze(buildingDozedRect);

if (includeTubes)
{
CreateDestroyedBuildingTubes(loc, buildingDozedRect);
}

CreateRandomRubble(buildingFootprint, commonRubbleAmount, rareRubbleAmount);
}

void RubbleMaker::CreateDestroyedBuilding(map_id buildingType, const LOCATION &loc)
{
UnitInfo unitInfo = UnitInfo(buildingType);

bool includeTubes = true;
int commonRubbleCount = 1;
int rareRubbleCount = 0;

switch (buildingType)
{
case map_id::mapLightTower:
includeTubes = false;
commonRubbleCount = 0;
break;
case map_id::mapResidence:
case map_id::mapCommonStorage:
case map_id::mapRareStorage:
case map_id::mapAgridome:
case map_id::mapRecreationFacility:
case map_id::mapNursery:
case map_id::mapGuardPost:
case map_id::mapForum:
break;
case map_id::mapCommonOreMine:
case map_id::mapRareOreMine:
case map_id::mapMagmaWell:
includeTubes = false;
break;
case map_id::mapSolarPowerArray:
includeTubes = false;
commonRubbleCount = 1;
rareRubbleCount = 1;
case map_id::mapUniversity:
case map_id::mapGORF:
case map_id::mapTradeCenter:
case map_id::mapAdvancedResidence:
case map_id::mapReinforcedResidence:
commonRubbleCount = 2;
break;
case map_id::mapGeothermalPlant:
commonRubbleCount = 2;
includeTubes = false;
case map_id::mapMHDGenerator:
includeTubes = false;
commonRubbleCount = 2;
rareRubbleCount = 1;
break;
case map_id::mapTokamak:
includeTubes = false;
commonRubbleCount = 3;
break;
case map_id::mapArachnidFactory:
case map_id::mapGarage:
case map_id::mapBasicLab:
case map_id::mapStandardLab:
case map_id::mapRobotCommand:
case map_id::mapConsumerFactory:
case map_id::mapDIRT:
commonRubbleCount = 3;
break;
case map_id::mapObservatory:
case map_id::mapMeteorDefense:
commonRubbleCount = 3;
rareRubbleCount = 1;
break;
case map_id::mapAdvancedLab:
commonRubbleCount = 4;
break;
case map_id::mapVehicleFactory:
case map_id::mapCommonOreSmelter:
commonRubbleCount = 5;
break;
case map_id::mapCommandCenter:
case map_id::mapStructureFactory:
case map_id::mapRareOreSmelter:
commonRubbleCount = 6;
break;
case map_id::mapSpaceport:
commonRubbleCount = 7;
rareRubbleCount = 1;
break;
}

CreateDestroyedBuilding(loc, unitInfo.GetXSize(), unitInfo.GetYSize(), includeTubes, commonRubbleCount, rareRubbleCount);
}

// PRIVATE FUNCTIONS

void RubbleMaker::GetAllLocationsFromRect(std::vector<LOCATION> &locations, const MAP_RECT &mapRect)
{
for (int y = 0; y < mapRect.Height(); ++y)
{
for (int x = 0; x < mapRect.Width(); ++x)
{
locations.push_back(LOCATION(mapRect.x1 + x, mapRect.y1 + y));
}
}
}

MAP_RECT RubbleMaker::GetBuildingDozedRect(const LOCATION &buildingLoc, int buildingWidth, int buildingHeight)
{
LOCATION topLeftDozedTile(
buildingLoc.x - buildingWidth / 2,
buildingLoc.y - buildingHeight / 2);

LOCATION bottomRightOfFootprint(topLeftDozedTile.x + buildingWidth, topLeftDozedTile.y + buildingHeight);

return MAP_RECT(LOCATION(topLeftDozedTile.x - 1, topLeftDozedTile.y - 1), bottomRightOfFootprint);
}

MAP_RECT RubbleMaker::GetBuildingFootprint(const MAP_RECT &buildingFootprint)
{
return MAP_RECT(
buildingFootprint.x1 + 1,
buildingFootprint.y1 + 1,
buildingFootprint.x2 - 1,
buildingFootprint.y2 - 1);
}

// Removes LOCATIONS from possRubbleLocs where rubble is now added.
void RubbleMaker::CreateRandomCommonRubble(int commonRubbleAmount, std::vector<LOCATION> &possRubbleLocs)
{
for (int i = 0; i < commonRubbleAmount; ++i)
{
if (possRubbleLocs.size() == 0)
{
return;
}

int rubbleLocIndex = TethysGame::GetRand(possRubbleLocs.size());
LOCATION rubbleLoc = possRubbleLocs[rubbleLocIndex];

CreateCommonRubble(rubbleLoc);

possRubbleLocs.erase(possRubbleLocs.begin() + rubbleLocIndex);
}
}

// Removes LOCATIONS from possRubbleLocs where rubble is now added.
void RubbleMaker::CreateRandomRareRubble(int rareRubbleAmount, std::vector<LOCATION> &possRubbleLocs)
{
for (int i = 0; i < rareRubbleAmount; ++i)
{
if (possRubbleLocs.size() == 0)
{
return;
}

int rubbleLocIndex = TethysGame::GetRand(possRubbleLocs.size());
LOCATION rubbleLoc = possRubbleLocs[rubbleLocIndex];

CreateRareRubble(rubbleLoc);

possRubbleLocs.erase(possRubbleLocs.begin() + rubbleLocIndex);
}
}

void RubbleMaker::CreateDestroyedBuildingTubes(const LOCATION &buildingLoc, const MAP_RECT &buildingFootprint)
{
LOCATION southTubeLoc(buildingLoc.x, buildingFootprint.y2);
LOCATION eastTubeLoc(buildingFootprint.x2, buildingLoc.y);

if (RubbleAllowed(southTubeLoc))
{
// The tile's celltype North of the Southern tube is temporarily set to tube to ensure the tube is orientied North/South.
LOCATION fauxTubeCellLoc(southTubeLoc.x, southTubeLoc.y - 1);
CellTypes fauxTubeCellOriginalType = (CellTypes)GameMap::GetCellType(fauxTubeCellLoc);

GameMap::SetCellType(fauxTubeCellLoc, CellTypes::cellTube0);
TethysGame::CreateWallOrTube(southTubeLoc.x, southTubeLoc.y, -1, map_id::mapTube);

GameMap::SetCellType(fauxTubeCellLoc, fauxTubeCellOriginalType);
}

if (RubbleAllowed(eastTubeLoc))
{
TethysGame::CreateWallOrTube(eastTubeLoc.x, eastTubeLoc.y, -1, map_id::mapTube);
}
}

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Re: RubbleMaker class for OP2Helper
« Reply #1 on: March 26, 2016, 10:20:47 PM »
Impressive, and large.

If all of the methods and data are static, is it really a class or a namespace?

It seems like you've encoded a lot of terrain information that would be useful outside of rubble placements. Maybe this can be extracted to a separate module of some sort.

Despite the large number of terrain constants, I still see plenty of hardcoded numbers. It would be easier to understand if the numbers were named constants.

Actually, it might be better if the information was extracted from the game rather than built up as a set of constants. The tile sets are changeable, so those hardcoded values are subject to change. Consider the Greenworld tile set. There's also the Dune 2000 import. There might be a way to read values from the game such that the result is always correct, even for new tile sets.

The HFL thing may be worth looking into.

I'm going to suggest keeping this code out of OP2Helper. I think it's a little farther reaching than OP2Helper was meant to be. In particular, the dependence on HFL is a sign this may be better off as a separate library.

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
Re: RubbleMaker class for OP2Helper
« Reply #2 on: March 30, 2016, 03:55:18 PM »
Okay,

Version 2 of the RubbleMaker is below. The class is no longer static and includes 2 constructors. If the default constructor is used, the RubbleMaker uses stock Outpost2 tiles. The second constructor allows passing the class TileIndices that contains the required tile index data to use the class. New instances of TileIndices can be created and modified to represent custom tile sets.

This is my first time using a C++ variadic function and my first time writing my own class constructors outside of a book exercise. I'm probably completely abusing the header file placing all the classes in it? I'm proud of the code, especially since it works as advertised, but I also recognize that it is my first time doing a couple of things, so there is a good chance it could be better. Definitely interested in header/cpp code separation discussion.

I'd like to move the Bulldozer code inside the RubbleMaker, which would require renaming RubbleMaker. It would fit nicely and allow defining the required Bulldozer TileSubSets inside the TileIndices Class where the rubble and blast indices are already located. This way Dozing terrain on custom tile sets could be done. RubbleMaker is calling Bulldozer anyways to doze the footprint of destroyed buildings.

Trying to think of a decent folder name to place the code inside the repo LevelsAndMods folder. OutpostSTL? I've got some other code to place next to it that is probably too 'heavy' for OP2Helper.

Still wouldn't mind eliminating HFL dependency. If someone wants to use the code without HFL, let me know and I'll make the modifications.

The code below is pretty long, so I understand if it doesn't really get read in its entirety. I'll try to keep the next code sample a little smaller!

RubbleMaker.h
Code: [Select]
#pragma once

#include "OP2Helper\Bulldozer.h"
#include "HFL\Source\HFL.h"
#include "Outpost2DLL\Outpost2DLL.h"
#include <vector>
 
class TileGroup;
class TileSubSet;
class TileChangeSet;
class TileIndices;

/*Automates placing Rubble, Blast Marks (meteor strikes or destroyed vehicles) and Destroyed
Buildings. When attempting to place rubble or blast marks, LOCATIONs are checked to ensure
they are valid before placement. When placing multiple blast marks or rubble, if not enough
valid LOCATIONs are provided, the function will place as many as possible and then exit.

BLAST MARK - Will not be added to transition tiles (like mud/dirt), special objects (like
conestoga debris tiles), or special terrain features (like multi-tile craters and cliffs).

RUBBLE - Will only be placed on tiles with a unit passable CellType.

DESTROYED BUILDING - Destroyed buildings include the properly sized extended
dozed footprint, 2 tubes if appropriate, and proper number of common and rare rubble. No
error checking is done to ensure LOCATION of destroyed building is valid (IE no cliffs in
the middle).*/
class RubbleMaker
{
public:
// Default constructor used stock Outpost 2 Tile Set.
RubbleMaker()
{
TileIndices = class TileIndices();
}

// Pass modified TileIndices class for custom Tile Sets.
RubbleMaker(const TileIndices &tileIndices)
{
TileIndices = tileIndices;
};

// Attempts to place blasted tile. Returns true if placement successful.
bool CreateBlastedTile(const LOCATION &loc);

//Attempts to randomly place blasted tiles within a MAP_RECT.
void CreateBlastedTiles(const MAP_RECT &mapRect, int count);

//Attempts to place blasted tiles at each location.
template<typename LOCATION, size_t N>
void CreateBlastedTiles(const std::array<LOCATION, N> &locs)
{
for (const LOCATION &loc : locs)
{
CreateBlastedTile(loc);
}
}

// Attempts to place blasted tiles at each location.
void CreateBlastedTiles(const std::vector<LOCATION> &locs);

bool RubbleAllowed(const LOCATION &loc);

// Attempts to place common rubble. Returns true if placement successful.
bool CreateCommonRubble(const LOCATION &loc);

// Attempts to place rare rubble. Returns true if placement successful.
bool CreateRareRubble(const LOCATION &loc);

// Attempts to place rubble randomly over provided MAP_RECT.
void CreateRandomRubble(const MAP_RECT &mapRect, int commonRubbleAmount, int rareRubbleAmount);

// Places a destroyed building of given map_id type. LOCATION specifies the position
// where the North/South and East/West tubes would meet underneath the structure.
void CreateDestroyedBuilding(map_id buildingType, const LOCATION &loc);

// Places a destroyed building with custom parameters.
void CreateDestroyedBuilding(const LOCATION &loc, int buildingWidth, int buildingHeight,
bool includeTubes, int commonRubbleAmount, int rareRubbleAmount);

private:
TileIndices TileIndices;

void GetAllLocationsFromRect(std::vector<LOCATION> &locations, const MAP_RECT &mapRect);
MAP_RECT GetBuildingDozedRect(const LOCATION &loc, int buildingWidth, int buildingHeight); //Returned MAP_RECT does not include dozed perimeter tiles of building.
MAP_RECT GetBuildingFootprint(const MAP_RECT &buildingFootprint);

void RubbleMaker::CreateRandomCommonRubble(int commonRubbleAmount, std::vector<LOCATION> &possRubbleLocs);
void RubbleMaker::CreateRandomRareRubble(int rareRubbleAmount, std::vector<LOCATION> &possRubbleLocs);
void RubbleMaker::CreateDestroyedBuildingTubes(const LOCATION &buildingLoc, const MAP_RECT &buildingFootprint);
};


// Defines noteorthy tile groupings for editing map tiles. The default TileGroups represent the stock Outpost 2 TileSet.
// Values may be replaced for custom tileSets.
class TileIndices
{
public:
// Common Rubble TileGroups and TileGroups that common rubble may replace.
std::vector<TileChangeSet> CommonRubbleSets = std::vector<TileChangeSet>{
TileChangeSet(TileSubSet("Common Mud Rubble", 323, 326), TileSubSet("Mud Replaceable By Rubble", 0, 438)),
TileChangeSet(TileSubSet("Common Rock Rubble", 830, 833), TileSubSet("Rock Replaceable By Rubble", 439, 1205)),
TileChangeSet(TileSubSet("Common Sand Rubble", 1585, 1588), TileSubSet("Sand Replaceable by Rubble", 1206, 2011)) };

// Rare Rubble TileGroups and TileGroups that rare rubble may replace.
std::vector<TileChangeSet> RareRubbleSets = std::vector<TileChangeSet>{
TileChangeSet(TileSubSet("Rare Mud Rubble", 327, 330), TileSubSet("Mud Replaceable By Rubble", 0, 438)),
TileChangeSet(TileSubSet("Rare Rock Rubble", 834, 837), TileSubSet("Rock Replaceable By Rubble", 439, 1205)),
TileChangeSet(TileSubSet("Rare Sand Rubble", 1589, 1592), TileSubSet("Sand Replaceable by Rubble", 1206, 2011)),
};

// Blast Mark TileGroups and TileGroups that blast marks may replace.
std::vector<TileChangeSet> BlastSets = std::vector<TileChangeSet>
{
TileChangeSet(TileSubSet("Rough Mud Blasts", 267, 269), TileSubSet("Rough Mud Replaceable By Blasts", 0, 32)),
TileChangeSet(TileSubSet("Dozed Mud Blasts", 264, 266), TileSubSet("Dozed Mud Replaceable By Blasts", 409, 416)),
TileChangeSet(TileSubSet("Rough Rock Blasts", 792, 794), TileSubSet("Rough Rock Replaceable By Blasts", 439, 504)),
TileChangeSet(TileSubSet("Dozed Rock Blasts", 789, 791), TileSubSet("Dozed Rock Replaceable By Blasts", 921, 928)),
TileChangeSet(TileSubSet("Rough Sand Blasts", 1550, 1552), TileSubSet("Rough Sand Replaceable By Blasts", 2, TileGroup{ 1206, 1269 }, TileGroup{ 1286, 1295 })),
TileChangeSet(TileSubSet("Dozed Sand Blasts", 1547, 1549), TileSubSet("Dozed Sand Replaceable By Blasts", 1670, 1677))
};
};

// Includes 2 TileGroups. A Replaceable TileGroup that indicates which tiles may be replaced and a
// Replacement TileGroup that indicates which tiles are valid replacement tiles.
class TileChangeSet
{
public:
TileChangeSet() {}

TileChangeSet(TileSubSet replacementTiles, TileSubSet replaceableTiles)
{
ReplacementTiles = replacementTiles;
ReplaceableTiles = replaceableTiles;
}

// Returns if the replaceable TileGroup contains the tileID.
bool IsTileReplaceable(int tileID)
{
return ReplaceableTiles.ContainsTile(tileID);
};

// Returns the total number of possible replacement tiles.
int ReplacementTileRange()
{
return ReplacementTiles.Range();
};

// Returns a replacement tileID based on it's range in the Replacement TileGroup.
int ReplacementTileID(int replacementRange)
{
return ReplacementTiles.GetTile(replacementRange);
};

private:
TileSubSet ReplacementTiles;
TileSubSet ReplaceableTiles;
};

// One or more TileGroups that should be treated the same.
class TileSubSet
{
public:
TileSubSet() {}

TileSubSet(char* name, int firstIndex, int lastIndex)
{
Name = name;

TileGroups.push_back(TileGroup(firstIndex, lastIndex));
}

TileSubSet(char* name, int numbGroups, ...)
{
Name = name;

va_list tileGroups;

va_start(tileGroups, numbGroups);
for (int i = 0; i < numbGroups; i++)
{
TileGroups.push_back(va_arg(tileGroups, TileGroup));
}
va_end(tileGroups);
}

char* GetName() { return Name; };

// Returns if any of the member TileGroups contains the TileID
bool ContainsTile(int tileID)
{
for (TileGroup tileGroup : TileGroups)
{
if (tileGroup.ContainsTile(tileID))
{
return true;
}
}

return false;
}

// The total number of tiles included in all member TileGroups.
int Range()
{
int range = 0;

for (TileGroup tileGroup : TileGroups)
{
range += tileGroup.Range();
}

return range;
};

// Returns a TileID based on the combined range of all member TileGroups.
int GetTile(int range)
{
int tileID = 0;

for (TileGroup tileGroup : TileGroups)
{
if (tileGroup.Range() < range)
{
range -= tileGroup.Range();
}
else
{
return tileGroup.GetTile(range);
}
}

return -1;
};

private:
char* Name;
std::vector<TileGroup> TileGroups;
};

// Inclusive group of consectutive tiles defined by mapping ID.
class TileGroup
{
public:
TileGroup() {}

TileGroup(int firstIndex, int lastIndex)
{
FirstIndex = firstIndex;
LastIndex = lastIndex;
}

bool ContainsTile(int tileIndex)
{
return tileIndex >= FirstIndex && tileIndex <= LastIndex;
};

int Range()
{
return LastIndex - FirstIndex + 1;
};

int GetTile(int range)
{
return FirstIndex + range;
};

private:
int FirstIndex;
int LastIndex;
};

RubbleMaker.cpp
Code: [Select]
#include "RubbleMaker.h"

bool RubbleMaker::RubbleAllowed(const LOCATION &loc)
{
int currentCellType = GameMap::GetCellType(loc);

return
currentCellType == CellTypes::cellDozedArea ||
currentCellType == CellTypes::cellFastPassible1 ||
currentCellType == CellTypes::cellFastPassible2 ||
currentCellType == CellTypes::cellMediumPassible1 ||
currentCellType == CellTypes::cellMediumPassible2 ||
currentCellType == CellTypes::cellSlowPassible1 ||
currentCellType == CellTypes::cellSlowPassible2;
}

bool RubbleMaker::CreateBlastedTile(const LOCATION &loc)
{
int originalTileIndex = GameMap::GetTile(loc);

for (TileChangeSet changeSet : TileIndices.BlastSets)
{
if (changeSet.IsTileReplaceable(originalTileIndex))
{
GameMap::SetTile(loc, changeSet.ReplacementTileID(TethysGame::GetRand(changeSet.ReplacementTileRange())));
return true;
}
}

return false;
}

void RubbleMaker::CreateBlastedTiles(const std::vector<LOCATION> &locs)
{
for (const LOCATION &loc : locs)
{
CreateBlastedTile(loc);
}
}

void RubbleMaker::CreateBlastedTiles(const MAP_RECT &mapRect, int count)
{
std::vector<LOCATION> possibleBlastLocs;
GetAllLocationsFromRect(possibleBlastLocs, mapRect);

int placedBlastCount = 0;
while (placedBlastCount < count)
{
if (possibleBlastLocs.size() == 0)
{
break;
}

int locIndex = TethysGame::GetRand(possibleBlastLocs.size());

if (CreateBlastedTile(possibleBlastLocs[locIndex]))
{
++placedBlastCount;
}

possibleBlastLocs.erase(possibleBlastLocs.begin() + locIndex);
}
}

bool RubbleMaker::CreateCommonRubble(const LOCATION &loc)
{
if (!RubbleAllowed(loc))
{
return false;
}

GameMap::SetCellType(loc, CellTypes::cellRubble);

int originalTileIndex = GameMap::GetTile(loc);

for (TileChangeSet changeSet : TileIndices.CommonRubbleSets)
{
if (changeSet.IsTileReplaceable(originalTileIndex))
{
GameMap::SetTile(loc, changeSet.ReplacementTileID(TethysGame::GetRand(changeSet.ReplacementTileRange())));
return true;
}
}

return false;
}

bool RubbleMaker::CreateRareRubble(const LOCATION &loc)
{
if (!RubbleAllowed(loc))
{
return false;
}

GameMap::SetCellType(loc, CellTypes::cellRubble);

int originalTileIndex = GameMap::GetTile(loc);

for (TileChangeSet changeSet : TileIndices.RareRubbleSets)
{
if (changeSet.IsTileReplaceable(originalTileIndex))
{
GameMap::SetTile(loc, changeSet.ReplacementTileID(TethysGame::GetRand(changeSet.ReplacementTileRange())));
return true;
}
}

return false;
}

void RubbleMaker::CreateRandomRubble(const MAP_RECT &mapRect, int commonRubbleAmount, int rareRubbleAmount)
{
std::vector<LOCATION> possRubbleLocs;
GetAllLocationsFromRect(possRubbleLocs, mapRect);

CreateRandomCommonRubble(commonRubbleAmount, possRubbleLocs);
CreateRandomRareRubble(rareRubbleAmount, possRubbleLocs);
}

void RubbleMaker::CreateDestroyedBuilding(const LOCATION &loc, int buildingWidth, int buildingHeight,
bool includeTubes, int commonRubbleAmount, int rareRubbleAmount)
{
MAP_RECT buildingDozedRect = GetBuildingDozedRect(loc, buildingWidth, buildingHeight);
MAP_RECT buildingFootprint = GetBuildingFootprint(buildingDozedRect);

Bulldozer::Doze(buildingDozedRect);

if (includeTubes)
{
CreateDestroyedBuildingTubes(loc, buildingDozedRect);
}

CreateRandomRubble(buildingFootprint, commonRubbleAmount, rareRubbleAmount);
}

void RubbleMaker::CreateDestroyedBuilding(map_id buildingType, const LOCATION &loc)
{
UnitInfo unitInfo = UnitInfo(buildingType);

bool includeTubes = true;
int commonRubbleCount = 1;
int rareRubbleCount = 0;

switch (buildingType)
{
case map_id::mapLightTower:
includeTubes = false;
commonRubbleCount = 0;
break;
case map_id::mapResidence:
case map_id::mapCommonStorage:
case map_id::mapRareStorage:
case map_id::mapAgridome:
case map_id::mapRecreationFacility:
case map_id::mapNursery:
case map_id::mapGuardPost:
case map_id::mapForum:
break;
case map_id::mapCommonOreMine:
case map_id::mapRareOreMine:
case map_id::mapMagmaWell:
includeTubes = false;
break;
case map_id::mapSolarPowerArray:
includeTubes = false;
commonRubbleCount = 1;
rareRubbleCount = 1;
case map_id::mapUniversity:
case map_id::mapGORF:
case map_id::mapTradeCenter:
case map_id::mapAdvancedResidence:
case map_id::mapReinforcedResidence:
commonRubbleCount = 2;
break;
case map_id::mapGeothermalPlant:
commonRubbleCount = 2;
includeTubes = false;
case map_id::mapMHDGenerator:
includeTubes = false;
commonRubbleCount = 2;
rareRubbleCount = 1;
break;
case map_id::mapTokamak:
includeTubes = false;
commonRubbleCount = 3;
break;
case map_id::mapArachnidFactory:
case map_id::mapGarage:
case map_id::mapBasicLab:
case map_id::mapStandardLab:
case map_id::mapRobotCommand:
case map_id::mapConsumerFactory:
case map_id::mapDIRT:
commonRubbleCount = 3;
break;
case map_id::mapObservatory:
case map_id::mapMeteorDefense:
commonRubbleCount = 3;
rareRubbleCount = 1;
break;
case map_id::mapAdvancedLab:
commonRubbleCount = 4;
break;
case map_id::mapVehicleFactory:
case map_id::mapCommonOreSmelter:
commonRubbleCount = 5;
break;
case map_id::mapCommandCenter:
case map_id::mapStructureFactory:
case map_id::mapRareOreSmelter:
commonRubbleCount = 6;
break;
case map_id::mapSpaceport:
commonRubbleCount = 7;
rareRubbleCount = 1;
break;
}

CreateDestroyedBuilding(loc, unitInfo.GetXSize(), unitInfo.GetYSize(), includeTubes, commonRubbleCount, rareRubbleCount);
}

// PRIVATE FUNCTIONS

void RubbleMaker::GetAllLocationsFromRect(std::vector<LOCATION> &locations, const MAP_RECT &mapRect)
{
for (int y = 0; y < mapRect.Height(); ++y)
{
for (int x = 0; x < mapRect.Width(); ++x)
{
locations.push_back(LOCATION(mapRect.x1 + x, mapRect.y1 + y));
}
}
}

MAP_RECT RubbleMaker::GetBuildingDozedRect(const LOCATION &buildingLoc, int buildingWidth, int buildingHeight)
{
LOCATION topLeftDozedTile(
buildingLoc.x - buildingWidth / 2,
buildingLoc.y - buildingHeight / 2);

LOCATION bottomRightOfFootprint(topLeftDozedTile.x + buildingWidth, topLeftDozedTile.y + buildingHeight);

return MAP_RECT(LOCATION(topLeftDozedTile.x - 1, topLeftDozedTile.y - 1), bottomRightOfFootprint);
}

MAP_RECT RubbleMaker::GetBuildingFootprint(const MAP_RECT &buildingFootprint)
{
return MAP_RECT(
buildingFootprint.x1 + 1,
buildingFootprint.y1 + 1,
buildingFootprint.x2 - 1,
buildingFootprint.y2 - 1);
}

// Removes LOCATIONS from possRubbleLocs where rubble is now added.
void RubbleMaker::CreateRandomCommonRubble(int commonRubbleAmount, std::vector<LOCATION> &possRubbleLocs)
{
for (int i = 0; i < commonRubbleAmount; ++i)
{
if (possRubbleLocs.size() == 0)
{
return;
}

int rubbleLocIndex = TethysGame::GetRand(possRubbleLocs.size());
LOCATION rubbleLoc = possRubbleLocs[rubbleLocIndex];

CreateCommonRubble(rubbleLoc);

possRubbleLocs.erase(possRubbleLocs.begin() + rubbleLocIndex);
}
}

// Removes LOCATIONS from possRubbleLocs where rubble is now added.
void RubbleMaker::CreateRandomRareRubble(int rareRubbleAmount, std::vector<LOCATION> &possRubbleLocs)
{
for (int i = 0; i < rareRubbleAmount; ++i)
{
if (possRubbleLocs.size() == 0)
{
return;
}

int rubbleLocIndex = TethysGame::GetRand(possRubbleLocs.size());
LOCATION rubbleLoc = possRubbleLocs[rubbleLocIndex];

CreateRareRubble(rubbleLoc);

possRubbleLocs.erase(possRubbleLocs.begin() + rubbleLocIndex);
}
}

void RubbleMaker::CreateDestroyedBuildingTubes(const LOCATION &buildingLoc, const MAP_RECT &buildingFootprint)
{
LOCATION southTubeLoc(buildingLoc.x, buildingFootprint.y2);
LOCATION eastTubeLoc(buildingFootprint.x2, buildingLoc.y);

if (RubbleAllowed(southTubeLoc))
{
// The tile's celltype North of the Southern tube is temporarily set to tube to ensure the tube is orientied North/South.
LOCATION fauxTubeCellLoc(southTubeLoc.x, southTubeLoc.y - 1);
CellTypes fauxTubeCellOriginalType = (CellTypes)GameMap::GetCellType(fauxTubeCellLoc);

GameMap::SetCellType(fauxTubeCellLoc, CellTypes::cellTube0);
TethysGame::CreateWallOrTube(southTubeLoc.x, southTubeLoc.y, -1, map_id::mapTube);

GameMap::SetCellType(fauxTubeCellLoc, fauxTubeCellOriginalType);
}

if (RubbleAllowed(eastTubeLoc))
{
TethysGame::CreateWallOrTube(eastTubeLoc.x, eastTubeLoc.y, -1, map_id::mapTube);
}
}

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Re: RubbleMaker class for OP2Helper
« Reply #3 on: March 31, 2016, 08:37:29 AM »
I took a quick glance over this. One thing that comes to mind is variable names are usually lowerCamelCase, while class and function names are usually UpperCamelCase. I noticed a number of member variables that used upper camel case.

Rather than remove the dependence on HFL, why not embrace this problem as needing a bit more internal info to do well. There's a lot of tile set information that could be read right out of the game. You could potentially make use of that internal data, rather than defining a copy of it in your library as hardcoded constants. The code could potentially use the same data and logic as the game.

Where to put it is not really clear right now. Libraries like HFL and IUnit give access to and extend internal game classes. They might be a suitable home. Your code seems to be an entirely new class though. I'm not really certain where the line is drawn. Perhaps we should get in contact with BlackBox (aka Hacker), or Eddy-B to see what they think. I may have had my own similar conflicting library too, that did sort of the same thing, with sort of the same name, but wasn't quite compatible. Perhaps some work is needed to merge these efforts.