<commit_before>#include <algorithm>
#include <iomanip>
#include <sstream>

#include "ExteriorLevelData.h"
#include "WorldType.h"
#include "../Assets/RMDFile.h"
#include "../Math/Random.h"
#include "../Utilities/Debug.h"
#include "../Utilities/String.h"
#include "../World/Location.h"
#include "../World/LocationType.h"
#include "../World/VoxelDataType.h"

ExteriorLevelData::ExteriorLevelData(int gridWidth, int gridHeight, int gridDepth,
	const std::string &infName, const std::string &name)
	: LevelData(gridWidth, gridHeight, gridDepth, infName, name) { }

ExteriorLevelData::~ExteriorLevelData()
{

}

void ExteriorLevelData::generateBuildingNames(int localCityID, int provinceID, uint32_t citySeed,
	ArenaRandom &random, bool isCoastal, bool isCity, int gridWidth, int gridDepth,
	const MiscAssets &miscAssets)
{
	const auto &exeData = miscAssets.getExeData();
	const int globalCityID = CityDataFile::getGlobalCityID(localCityID, provinceID);

	std::vector<int> seen;
	auto hashInSeen = [&seen](int hash)
	{
		return std::find(seen.begin(), seen.end(), hash) != seen.end();
	};

	auto generateName = [this, localCityID, provinceID, &citySeed, &random, isCoastal, isCity,
		gridWidth, gridDepth, &miscAssets, &exeData, globalCityID, &seen, &hashInSeen](
			int x, int z, VoxelData::WallData::MenuType menuType)
	{
		if ((menuType == VoxelData::WallData::MenuType::Equipment) ||
			(menuType == VoxelData::WallData::MenuType::Temple))
		{
			// X and Y coordinates are swapped and reversed because the modern coordinate system
			// is being used here since it's operating on the resulting voxel grid to avoid
			// decoding voxel bits twice.
			citySeed = (((gridWidth - 1) - x) << 16) + ((gridDepth - 1) - z);
			random.srand(citySeed);
		}

		// Lambdas for creating tavern, equipment store, and temple building names.
		auto createTavernName = [isCoastal, &exeData](int m, int n)
		{
			const auto &tavernPrefixes = exeData.cityGen.tavernPrefixes;
			const auto &tavernSuffixes = isCoastal ?
				exeData.cityGen.tavernMarineSuffixes : exeData.cityGen.tavernSuffixes;
			return tavernPrefixes.at(m) + ' ' + tavernSuffixes.at(n);
		};

		auto createEquipmentName = [localCityID, provinceID, &random, &miscAssets,
			&exeData, x, z](int m, int n)
		{
			const auto &equipmentPrefixes = exeData.cityGen.equipmentPrefixes;
			const auto &equipmentSuffixes = exeData.cityGen.equipmentSuffixes;

			// Equipment store names can have variables in them.
			std::string str = equipmentPrefixes.at(m) + ' ' + equipmentSuffixes.at(n);

			// Replace %ct with city type string.
			const std::string &cityTypeStr = [localCityID, &exeData]() -> const std::string&
			{
				const int index = [localCityID]()
				{
					const LocationType locationType = Location::getCityType(localCityID);
					if (locationType == LocationType::CityState)
					{
						return 0;
					}
					else if (locationType == LocationType::Town)
					{
						return 1;
					}
					else if (locationType == LocationType::Village)
					{
						return 2;
					}
					else
					{
						throw DebugException("Invalid local city ID \"" +
							std::to_string(localCityID) + "\".");
					}
				}();

				return exeData.locations.locationTypes.at(index);
			}();

			size_t index = str.find("%ct");
			if (index != std::string::npos)
			{
				str.replace(index, 3, cityTypeStr);
			}

			// Replace %ef with generated male first name from (y<<16)+x seed.
			random.srand((x << 16) + z);
			const bool isMale = true;
			const std::string maleFirstName = [&miscAssets, provinceID, isMale, &random]()
			{
				const std::string name = miscAssets.generateNpcName(provinceID, isMale, random);
				const std::string firstName = String::split(name).front();
				return firstName;
			}();

			index = str.find("%ef");
			if (index != std::string::npos)
			{
				str.replace(index, 3, maleFirstName);
			}

			// Replace %n with generated male name from (x<<16)+y seed.
			random.srand((z << 16) + x);
			const std::string maleName = miscAssets.generateNpcName(provinceID, isMale, random);
			index = str.find("%n");
			if (index != std::string::npos)
			{
				str.replace(index, 2, maleName);
			}

			return str;
		};

		auto createTempleName = [&exeData](int model, int n)
		{
			const auto &templePrefixes = exeData.cityGen.templePrefixes;
			const auto &temple1Suffixes = exeData.cityGen.temple1Suffixes;
			const auto &temple2Suffixes = exeData.cityGen.temple2Suffixes;
			const auto &temple3Suffixes = exeData.cityGen.temple3Suffixes;

			const std::string &templeSuffix = [&temple1Suffixes, &temple2Suffixes,
				&temple3Suffixes, model, n]() -> const std::string&
			{
				if (model == 0)
				{
					return temple1Suffixes.at(n);
				}
				else if (model == 1)
				{
					return temple2Suffixes.at(n);
				}
				else
				{
					return temple3Suffixes.at(n);
				}
			}();

			// No extra whitespace needed, I think?
			return templePrefixes.at(model) + templeSuffix;
		};

		// See if the current voxel is a *MENU block and matches the target menu type.
		const bool matchesTargetType = [this, isCity, x, z, menuType]()
		{
			const auto &voxelGrid = this->getVoxelGrid();
			const uint16_t voxelID = voxelGrid.getVoxel(x, 1, z);
			const VoxelData &voxelData = voxelGrid.getVoxelData(voxelID);
			return (voxelData.dataType == VoxelDataType::Wall) && voxelData.wall.isMenu() &&
				(VoxelData::WallData::getMenuType(voxelData.wall.menuID, isCity) == menuType);
		}();

		if (matchesTargetType)
		{
			// Get the *MENU block's display name.
			int hash;
			std::string name;

			if (menuType == VoxelData::WallData::MenuType::Tavern)
			{
				// Tavern.
				int m, n;
				do
				{
					m = random.next() % 23;
					n = random.next() % 23;
					hash = (m << 8) + n;
				} while (hashInSeen(hash));

				name = createTavernName(m, n);
			}
			else if (menuType == VoxelData::WallData::MenuType::Equipment)
			{
				// Equipment store.
				int m, n;
				do
				{
					m = random.next() % 20;
					n = random.next() % 10;
					hash = (m << 8) + n;
				} while (hashInSeen(hash));

				name = createEquipmentName(m, n);
			}
			else
			{
				// Temple.
				int model, n;
				do
				{
					model = random.next() % 3;
					const std::array<int, 3> ModelVars = { 5, 9, 10 };
					const int vars = ModelVars.at(model);
					n = random.next() % vars;
					hash = (model << 8) + n;
				} while (hashInSeen(hash));

				name = createTempleName(model, n);
			}

			this->menuNames.push_back(std::make_pair(Int2(x, z), std::move(name)));
			seen.push_back(hash);
		}

		// Fix some edge cases presumably caught during Arena's play-testing.
		if ((menuType == VoxelData::WallData::MenuType::Temple) &&
			(globalCityID == 2) || (globalCityID == 0xE0))
		{
			int model, n;
			if (globalCityID == 2)
			{
				model = 1;
				n = 7;
			}
			else<commit_msg>Fixed parenthesis warning on GCC and Clang.<commit_after>#include <algorithm>
#include <iomanip>
#include <sstream>

#include "ExteriorLevelData.h"
#include "WorldType.h"
#include "../Assets/RMDFile.h"
#include "../Math/Random.h"
#include "../Utilities/Debug.h"
#include "../Utilities/String.h"
#include "../World/Location.h"
#include "../World/LocationType.h"
#include "../World/VoxelDataType.h"

ExteriorLevelData::ExteriorLevelData(int gridWidth, int gridHeight, int gridDepth,
	const std::string &infName, const std::string &name)
	: LevelData(gridWidth, gridHeight, gridDepth, infName, name) { }

ExteriorLevelData::~ExteriorLevelData()
{

}

void ExteriorLevelData::generateBuildingNames(int localCityID, int provinceID, uint32_t citySeed,
	ArenaRandom &random, bool isCoastal, bool isCity, int gridWidth, int gridDepth,
	const MiscAssets &miscAssets)
{
	const auto &exeData = miscAssets.getExeData();
	const int globalCityID = CityDataFile::getGlobalCityID(localCityID, provinceID);

	std::vector<int> seen;
	auto hashInSeen = [&seen](int hash)
	{
		return std::find(seen.begin(), seen.end(), hash) != seen.end();
	};

	auto generateName = [this, localCityID, provinceID, &citySeed, &random, isCoastal, isCity,
		gridWidth, gridDepth, &miscAssets, &exeData, globalCityID, &seen, &hashInSeen](
			int x, int z, VoxelData::WallData::MenuType menuType)
	{
		if ((menuType == VoxelData::WallData::MenuType::Equipment) ||
			(menuType == VoxelData::WallData::MenuType::Temple))
		{
			// X and Y coordinates are swapped and reversed because the modern coordinate system
			// is being used here since it's operating on the resulting voxel grid to avoid
			// decoding voxel bits twice.
			citySeed = (((gridWidth - 1) - x) << 16) + ((gridDepth - 1) - z);
			random.srand(citySeed);
		}

		// Lambdas for creating tavern, equipment store, and temple building names.
		auto createTavernName = [isCoastal, &exeData](int m, int n)
		{
			const auto &tavernPrefixes = exeData.cityGen.tavernPrefixes;
			const auto &tavernSuffixes = isCoastal ?
				exeData.cityGen.tavernMarineSuffixes : exeData.cityGen.tavernSuffixes;
			return tavernPrefixes.at(m) + ' ' + tavernSuffixes.at(n);
		};

		auto createEquipmentName = [localCityID, provinceID, &random, &miscAssets,
			&exeData, x, z](int m, int n)
		{
			const auto &equipmentPrefixes = exeData.cityGen.equipmentPrefixes;
			const auto &equipmentSuffixes = exeData.cityGen.equipmentSuffixes;

			// Equipment store names can have variables in them.
			std::string str = equipmentPrefixes.at(m) + ' ' + equipmentSuffixes.at(n);

			// Replace %ct with city type string.
			const std::string &cityTypeStr = [localCityID, &exeData]() -> const std::string&
			{
				const int index = [localCityID]()
				{
					const LocationType locationType = Location::getCityType(localCityID);
					if (locationType == LocationType::CityState)
					{
						return 0;
					}
					else if (locationType == LocationType::Town)
					{
						return 1;
					}
					else if (locationType == LocationType::Village)
					{
						return 2;
					}
					else
					{
						throw DebugException("Invalid local city ID \"" +
							std::to_string(localCityID) + "\".");
					}
				}();

				return exeData.locations.locationTypes.at(index);
			}();

			size_t index = str.find("%ct");
			if (index != std::string::npos)
			{
				str.replace(index, 3, cityTypeStr);
			}

			// Replace %ef with generated male first name from (y<<16)+x seed.
			random.srand((x << 16) + z);
			const bool isMale = true;
			const std::string maleFirstName = [&miscAssets, provinceID, isMale, &random]()
			{
				const std::string name = miscAssets.generateNpcName(provinceID, isMale, random);
				const std::string firstName = String::split(name).front();
				return firstName;
			}();

			index = str.find("%ef");
			if (index != std::string::npos)
			{
				str.replace(index, 3, maleFirstName);
			}

			// Replace %n with generated male name from (x<<16)+y seed.
			random.srand((z << 16) + x);
			const std::string maleName = miscAssets.generateNpcName(provinceID, isMale, random);
			index = str.find("%n");
			if (index != std::string::npos)
			{
				str.replace(index, 2, maleName);
			}

			return str;
		};

		auto createTempleName = [&exeData](int model, int n)
		{
			const auto &templePrefixes = exeData.cityGen.templePrefixes;
			const auto &temple1Suffixes = exeData.cityGen.temple1Suffixes;
			const auto &temple2Suffixes = exeData.cityGen.temple2Suffixes;
			const auto &temple3Suffixes = exeData.cityGen.temple3Suffixes;

			const std::string &templeSuffix = [&temple1Suffixes, &temple2Suffixes,
				&temple3Suffixes, model, n]() -> const std::string&
			{
				if (model == 0)
				{
					return temple1Suffixes.at(n);
				}
				else if (model == 1)
				{
					return temple2Suffixes.at(n);
				}
				else
				{
					return temple3Suffixes.at(n);
				}
			}();

			// No extra whitespace needed, I think?
			return templePrefixes.at(model) + templeSuffix;
		};

		// See if the current voxel is a *MENU block and matches the target menu type.
		const bool matchesTargetType = [this, isCity, x, z, menuType]()
		{
			const auto &voxelGrid = this->getVoxelGrid();
			const uint16_t voxelID = voxelGrid.getVoxel(x, 1, z);
			const VoxelData &voxelData = voxelGrid.getVoxelData(voxelID);
			return (voxelData.dataType == VoxelDataType::Wall) && voxelData.wall.isMenu() &&
				(VoxelData::WallData::getMenuType(voxelData.wall.menuID, isCity) == menuType);
		}();

		if (matchesTargetType)
		{
			// Get the *MENU block's display name.
			int hash;
			std::string name;

			if (menuType == VoxelData::WallData::MenuType::Tavern)
			{
				// Tavern.
				int m, n;
				do
				{
					m = random.next() % 23;
					n = random.next() % 23;
					hash = (m << 8) + n;
				} while (hashInSeen(hash));

				name = createTavernName(m, n);
			}
			else if (menuType == VoxelData::WallData::MenuType::Equipment)
			{
				// Equipment store.
				int m, n;
				do
				{
					m = random.next() % 20;
					n = random.next() % 10;
					hash = (m << 8) + n;
				} while (hashInSeen(hash));

				name = createEquipmentName(m, n);
			}
			else
			{
				// Temple.
				int model, n;
				do
				{
					model = random.next() % 3;
					const std::array<int, 3> ModelVars = { 5, 9, 10 };
					const int vars = ModelVars.at(model);
					n = random.next() % vars;
					hash = (model << 8) + n;
				} while (hashInSeen(hash));

				name = createTempleName(model, n);
			}

			this->menuNames.push_back(std::make_pair(Int2(x, z), std::move(name)));
			seen.push_back(hash);
		}

		// Fix some edge cases presumably caught during Arena's play-testing.
		if ((menuType == VoxelData::WallData::MenuType::Temple) &&
			((globalCityID == 2) || (globalCityID == 0xE0)))
		{
			int model, n;
			if (globalCityID == 2)
			{
				model = 1;
				n = 7;
			}
			else
{'fraction_non_alphanumeric': 0.14334997064004698, 'fraction_numerical': 0.008954785672342924, 'mean_word_length': 3.3810289389067525, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5553437', 'n_tokens_mistral': 5864, 'n_tokens_neox': 4748, 'n_words': 1127}