#include "CUnitTable.h" #include #include #include #include "CAI.h" #include "CUnit.h" #include "CConfigParser.h" #include "Util.hpp" #include "ReusableObjectFactory.hpp" std::map CUnitTable::str2cat; CUnitTable::UnitCategory2StrMap CUnitTable::cat2str; std::vector CUnitTable::cats; CUnitTable::CUnitTable(AIClasses *ai): ARegistrar(100) { this->ai = ai; if (cat2str.empty()) { /* techlevels */ cat2str[TECH1] = "TECH1"; cat2str[TECH2] = "TECH2"; cat2str[TECH3] = "TECH3"; cat2str[TECH4] = "TECH4"; cat2str[TECH5] = "TECH5"; /* main categories */ cat2str[AIR] = "AIR"; cat2str[SEA] = "SEA"; cat2str[LAND] = "LAND"; cat2str[SUB] = "SUB"; cat2str[STATIC] = "STATIC"; cat2str[MOBILE] = "MOBILE"; /* builders */ cat2str[FACTORY] = "FACTORY"; cat2str[BUILDER] = "BUILDER"; cat2str[ASSISTER] = "ASSISTER"; cat2str[RESURRECTOR] = "RESURRECTOR"; /* offensives */ cat2str[COMMANDER] = "COMMANDER"; cat2str[ATTACKER] = "ATTACKER"; cat2str[ANTIAIR] = "ANTIAIR"; cat2str[SCOUTER] = "SCOUTER"; cat2str[ARTILLERY] = "ARTILLERY"; cat2str[SNIPER] = "SNIPER"; cat2str[ASSAULT] = "ASSAULT"; /* economic */ cat2str[MEXTRACTOR] = "MEXTRACTOR"; cat2str[MMAKER] = "MMAKER"; cat2str[EMAKER] = "EMAKER"; cat2str[MSTORAGE] = "MSTORAGE"; cat2str[ESTORAGE] = "ESTORAGE"; /* factory types */ cat2str[KBOT] = "KBOT"; cat2str[VEHICLE] = "VEHICLE"; cat2str[HOVER] = "HOVER"; cat2str[AIRCRAFT] = "AIRCRAFT"; cat2str[NAVAL] = "NAVAL"; cat2str[DEFENSE] = "DEFENSE"; cat2str[JAMMER] = "JAMMER"; cat2str[NUKE] = "NUKE"; cat2str[ANTINUKE] = "ANTINUKE"; cat2str[PARALYZER] = "PARALYZER"; cat2str[TORPEDO] = "TORPEDO"; cat2str[TRANSPORT] = "TRANSPORT"; cat2str[EBOOSTER] = "EBOOSTER"; cat2str[MBOOSTER] = "MBOOSTER"; cat2str[SHIELD] = "SHIELD"; cat2str[NANOTOWER] = "NANOTOWER"; cat2str[REPAIRPAD] = "REPAIRPAD"; cat2str[WIND] = "WIND"; cat2str[TIDAL] = "TIDAL"; assert(cat2str.size() == MAX_CATEGORIES); } if (str2cat.empty()) { /* Create the str2cat table and cats vector */ UnitCategory2StrMap::iterator i; for (i = cat2str.begin(); i != cat2str.end(); ++i) { cats.push_back(i->first); str2cat[i->second] = i->first; } } maxUnitPower = 0.0f; numUnits = ai->cb->GetNumUnitDefs(); /* Build the techtree, note that this is actually a graph in XTA */ buildTechTree(); bool success = false; unsigned int lastFlags = GET_CAT; unsigned int flagsOrder[] = { GET_CAT|GET_VER|GET_TEAM, GET_CAT|GET_VER, GET_CAT|GET_TEAM, GET_CAT }; std::string filename; for (int i = 0; i < sizeof(flagsOrder) / sizeof(unsigned int); i++) { lastFlags = flagsOrder[i]; filename = ai->cfgparser->getFilename(lastFlags); if (ai->cfgparser->fileExists(filename)) { success = ai->cfgparser->parseCategories(filename, units); if (success) break; } else LOG_WW("CUnitTable::CUnitTable Categorization file not found: " << filename) } if (!success) { filename = util::GetAbsFileName(ai->cb, std::string(CFG_FOLDER) + filename, false); generateCategorizationFile(filename); } filename = ai->cfgparser->getFilename(lastFlags|GET_PATCH); if (ai->cfgparser->fileExists(filename)) ai->cfgparser->parseCategories(filename, units, true); else LOG_WW("CUnitTable::CUnitTable Categorization patch-file not found: " << filename) /* Generate the buildBy and canBuild lists per UnitType */ /* std::map::iterator l; std::string buildBy, canBuild; std::map::iterator j; for (j = units.begin(); j != units.end(); j++) { UnitType *utParent = &(j->second); debugCategories(utParent); debugUnitDefs(utParent); debugWeapons(utParent); canBuild = buildBy = ""; for (l = utParent->buildBy.begin(); l != utParent->buildBy.end(); l++) { std::stringstream out; out << l->first; buildBy += l->second->def->name + "(" + out.str() + "), "; } buildBy = buildBy.substr(0, buildBy.length() - 2); for (l = utParent->canBuild.begin(); l != utParent->canBuild.end(); l++) { std::stringstream out; out << l->first; canBuild += l->second->def->name + "(" + out.str() + "), "; } canBuild = canBuild.substr(0, canBuild.length() - 2); } */ LOG_II("CUnitTable::CUnitTable Number of unit types: " << numUnits); LOG_II("CUnitTable::CUnitTable Max unit power: " << maxUnitPower); } CUnitTable::~CUnitTable() { } void CUnitTable::generateCategorizationFile(std::string& fileName) { const std::string modShortName(ai->cb->GetModShortName()); const std::string modVersion(ai->cb->GetModVersion()); std::ofstream file(fileName.c_str(), std::ios::trunc); file << "# Unit basic categorization file for " << AI_NAME << "\n\n"; file << "# Based on game " << modShortName << "-" << modVersion << "\n\n"; file << "# Autogenerated by " << AI_VERSION << "\n# DO NOT MODIFY!\n\n"; file << "# Available categories:\n"; for (UnitCategory2StrMap::iterator i = cat2str.begin(); i != cat2str.end(); ++i) { file << "# " << i->second << "\n"; } file << "\n\n# Total number of unit definitions: " << numUnits << "\n\n"; for (std::map::iterator j = units.begin(); j != units.end(); ++j) { UnitType* utParent = &(j->second); file << "# " << utParent->def->humanName << "\n"; file << utParent->def->name; for (unsigned int i = 0; i < cats.size(); i++) if ((cats[i]&utParent->cats).any()) file << "," << cat2str[cats[i]]; file << "\n\n"; } file.close(); LOG_II("CUnitTable::generateCategorizationFile " << fileName) } void CUnitTable::remove(ARegistrar& object) { CUnit *unit = dynamic_cast(&object); LOG_II("CUnitTable::remove " << (*unit)) builders.erase(unit->key); idle.erase(unit->key); metalMakers.erase(unit->key); activeUnits.erase(unit->key); factories.erase(unit->key); defenses.erase(unit->key); energyStorages.erase(unit->key); unitsUnderPlayerControl.erase(unit->key); unitsUnderConstruction.erase(unit->key); unitsBuilding.erase(unit->key); staticUnits.erase(unit->key); staticWaterUnits.erase(unit->key); staticEconomyUnits.erase(unit->key); unit->unreg(*this); ReusableObjectFactory::Release(unit); } CUnit* CUnitTable::getUnit(int uid) { std::map::iterator u = activeUnits.find(uid); if (u == activeUnits.end()) return NULL; else return u->second; } CUnit* CUnitTable::requestUnit(int uid, int bid) { CUnit *unit = ReusableObjectFactory::Instance(); unit->ai = ai; unit->reset(uid, bid); unit->reg(*this); const unitCategory cats = unit->type->cats; if (bid > 0) builders[bid] = false; activeUnits[uid] = unit; idle[bid] = false; idle[uid] = false; if ((cats&MOBILE).any() && bid >= 0) { const unitCategory bcats = activeUnits[bid]->type->cats; unit->techlvl = (bcats&TECH1).any() ? TECH1 : unit->techlvl; unit->techlvl = (bcats&TECH2).any() ? TECH2 : unit->techlvl; unit->techlvl = (bcats&TECH3).any() ? TECH3 : unit->techlvl; unit->techlvl = (bcats&TECH4).any() ? TECH4 : unit->techlvl; unit->techlvl = (bcats&TECH5).any() ? TECH5 : unit->techlvl; } // NOTE: remember that NOTA has mobile defenses if (((cats&STATIC).any() && (cats&ATTACKER).any()) || (cats&DEFENSE).any()) defenses[unit->key] = unit; if ((cats&ESTORAGE).any()) energyStorages[unit->key] = unit; if ((cats&FACTORY).any()) factories[unit->key] = unit; if ((cats&MMAKER).any()) metalMakers[unit->key] = unit; if ((cats&STATIC).any()) { staticUnits[unit->key] = unit; if (unit->isEconomy()) staticEconomyUnits[unit->key] = unit; if ((cats&(SEA|SUB)).any()) staticWaterUnits[unit->key] = unit; } return unit; } void CUnitTable::update() { CUnit* unit; std::map::iterator i; for (i = activeUnits.begin(); i != activeUnits.end(); ++i) { unit = i->second; if (unit->isMicroing()) unit->microingFrames += MULTIPLEXER; else unit->aliveFrames += MULTIPLEXER; } } void CUnitTable::buildTechTree() { if (!units.empty()) return; // alreay initialized std::map buildOptions; std::map::iterator j; std::vector unitdefs(numUnits); ai->cb->GetUnitDefList(&unitdefs[0]); // NOTE: -1 movetype means a graph for aircraft moveTypes[-1] = NULL; for (int i = 0; i < numUnits; i++) { const UnitDef *ud = unitdefs[i]; if (ud == NULL) continue; std::map::iterator u = units.find(ud->id); UnitType *utParent, *utChild; if (u == units.end()) utParent = insertUnit(ud); else utParent = &(u->second); buildOptions = ud->buildOptions; for (j = buildOptions.begin(); j != buildOptions.end(); ++j) { ud = ai->cb->GetUnitDef(j->second.c_str()); u = units.find(ud->id); if (u == units.end()) utChild = insertUnit(ud); else utChild = &(u->second); utChild->buildBy[utParent->def->id] = utParent; utParent->canBuild[utChild->def->id] = utChild; } } for (int i = 0; i < numUnits; i++) { const UnitDef *ud = unitdefs[i]; if (ud == NULL) continue; units[ud->id].cats = categorizeUnit(&units[ud->id]); } } UnitType* CUnitTable::insertUnit(const UnitDef *ud) { UnitType ut; ut.def = ud; ut.cost = ud->metalCost*METAL2ENERGY + ud->energyCost; ut.costMetal = ud->metalCost; ut.energyMake = ud->energyMake - ud->energyUpkeep; ut.metalMake = ud->metalMake - ud->metalUpkeep; ut.dps = calcUnitDps(&ut); units[ud->id] = ut; // also register pathtype... MoveData* md = ud->movedata; if (md) moveTypes[md->pathType] = md; if (maxUnitPower < ut.dps) maxUnitPower = ut.dps; return &units[ud->id]; } unitCategory CUnitTable::categorizeUnit(UnitType *ut) { const UnitDef* ud = ut->def; unitCategory cats = 0; //assert(ud->name != "armsonar"); //assert(ud->humanName != "Shark"); if (ud->isCommander) cats |= COMMANDER; if (ud->speed > EPS) cats |= MOBILE; else cats |= STATIC; if (ud->canfly) cats |= AIR; if (ud->canhover) cats |= SEA; else if (ud->canSubmerge) cats |= SUB; else if (ud->movedata) { if (ud->movedata->subMarine) cats |= SUB; // submarine else if (ud->movedata->moveType == MoveData::Ship_Move) { float heightAboveWater = ai->cb->GetUnitDefHeight(ud->id) - ud->waterline; if (heightAboveWater < EPS) cats |= SUB; else cats |= SEA; } else if (ud->movedata->depth > 100.0f) cats |= SUB; // amphibious unit } else if (ud->floater || ud->waterline > 0.0f || ud->minWaterDepth > 0.0f) { float heightAboveWater; if (ud->waterline > 0.0f || ud->floater) //heightAboveWater = ai->cb->GetUnitDefHeight(ud->id) - ud->waterline; heightAboveWater = ai->cb->GetUnitDefRadius(ud->id) / 2.0f - ud->waterline; else heightAboveWater = -1.0f; // force to SUB if (heightAboveWater < EPS) cats |= SUB; else cats |= SEA; } if ((ud->canhover || ud->minWaterDepth < 0.0f) && !ud->canfly) cats |= LAND; if (ud->canAssist) cats |= ASSISTER; if (ud->metalStorage / ut->cost > 0.1f) cats |= MSTORAGE; if (ud->energyStorage / ut->cost > 0.2f) cats |= ESTORAGE; if (ud->makesMetal >= 0.5f && (ud->energyUpkeep > (ud->makesMetal * 40.0f))) cats |= MMAKER; if ((ud->energyMake - ud->energyUpkeep) / ut->cost > 0.002 || ud->tidalGenerator || ud->windGenerator) { cats |= EMAKER; if (ud->tidalGenerator) cats |= TIDAL; if (ud->windGenerator) cats |= WIND; } if (ud->extractsMetal) cats |= MEXTRACTOR; /* if (ud->radarRadius > 0) cats |= RADAR; if (ud->sonarRadius > 0) cats |= SONAR; */ // NOTE: BA's Dragonfly (transport aircraft) jammer radius = 75 if (ud->jammerRadius > 100) { cats |= JAMMER; if ((cats&STATIC).any()) cats |= DEFENSE; } if (!ud->weapons.empty()) { cats |= ATTACKER; if (CUnit::hasTorpedoWeapon(ud->weapons)) cats |= TORPEDO; if (CUnit::hasParalyzerWeapon(ud->weapons)) cats |= PARALYZER; else if ((cats&AIR).any() && ud->hoverAttack) cats |= ASSAULT; /* 0 = only low, 1 = only high, 2 both */ if ((cats&AIR).none() && ud->highTrajectoryType >= 1) cats |= ARTILLERY; if (CUnit::hasAntiAirWeapon(ud->weapons)) cats |= ANTIAIR; else if (CUnit::hasNukeWeapon(ud->weapons)) cats |= NUKE; else if (CUnit::hasInterceptorWeapon(ud->weapons)) cats |= ANTINUKE; // TODO: distinguish from EMP else if (CUnit::hasShield(ud->weapons)) cats |= SHIELD; if ((cats&STATIC).any() && (cats&NUKE).none()) cats |= DEFENSE; } if (ud->canResurrect) cats |= RESURRECTOR; // NOTE: we aren't checking for "canMove" because it is usually used // to set rally point for factory if (!ud->buildOptions.empty()) { int kamikazeUnitCount = 0; std::map::const_iterator j; cats |= BUILDER; if ((cats&STATIC).any()) cats |= FACTORY; // preprocessing stage... for (j = ud->buildOptions.begin(); j != ud->buildOptions.end(); ++j) { const UnitDef* canbuild = ai->cb->GetUnitDef(j->second.c_str()); if (canbuild == NULL) continue; if (canbuild->canKamikaze) kamikazeUnitCount++; if (canbuild->speed < EPS && (cats&FACTORY).any()) // this is a static builder, not a factory cats &= ~FACTORY; } if (kamikazeUnitCount > 4) cats &= ~(FACTORY|BUILDER); if ((cats&FACTORY).any()) { // precise factory type... for (j = ud->buildOptions.begin(); j != ud->buildOptions.end(); ++j) { const UnitDef* canbuild = ai->cb->GetUnitDef(j->second.c_str()); if (canbuild == NULL) continue; if (canbuild->canfly) { cats |= AIRCRAFT; break; } if (canbuild->movedata == NULL) continue; if (canbuild->movedata->moveFamily == MoveData::KBot && ud->minWaterDepth < 0.0f) { cats |= KBOT; break; } if (canbuild->movedata->moveFamily == MoveData::Tank && ud->minWaterDepth < 0.0f) { cats |= VEHICLE; break; } if (canbuild->movedata->moveFamily == MoveData::Hover) { cats |= HOVER; break; } if (canbuild->movedata->moveFamily == MoveData::Ship) { cats |= NAVAL; break; } } } /* // TODO: improve heuristic estimator then uncomment the code below if ((cats&BUILDER).any()) { if (ud->metalCost < 2000.0f) cats |= TECH1; else cats |= TECH2; } */ } if ((cats&ASSISTER).any() && (cats&(BUILDER|FACTORY)).none()) { // NOTE: default value for "buildDistance" = 128 if (ud->buildDistance < 130.0f) cats |= REPAIRPAD; else cats |= NANOTOWER; } if (ud->loadingRadius > 0.0f && ud->transportCapacity > 0) cats |= TRANSPORT; /* // TODO: improve heuristic estimator then uncomment the code below if ((cats&ATTACKER).any() && (cats&MOBILE).any() && (cats&BUILDER).none() && ud->speed >= 50.0f) { std::map::iterator i,j; for (i = ut->buildBy.begin(); i != ut->buildBy.end(); ++i) { bool isCheapest = true; UnitType *bb = i->second; for (j = bb->canBuild.begin(); j != bb->canBuild.end(); ++j) { if (ut->cost > j->second->cost && !j->second->def->weapons.empty()) { isCheapest = false; break; } } if (isCheapest) { cats |= SCOUTER; break; } } } */ return cats; } float CUnitTable::calcUnitDps(UnitType *ut) { // FIXME: make our own *briljant* dps calc here return ut->def->power; } int CUnitTable::unitCount(unitCategory c) { int result = 0; std::map::iterator i; for (i = activeUnits.begin(); i != activeUnits.end(); ++i) { if ((c&i->second->type->cats) == c) result++; } return result; } int CUnitTable::factoryCount(unitCategory c) { int result = 0; std::map::iterator i; for (i = factories.begin(); i != factories.end(); ++i) { if ((c&i->second->type->cats) == c) result++; } return result; } bool CUnitTable::gotFactory(unitCategory c) { return factoryCount(c) > 0; } void CUnitTable::getBuildables(UnitType* ut, unitCategory include, unitCategory exclude, std::multimap& candidates) { if (include.none()) return; unitCategory incEnvCats = (CATS_ENV&include); std::vector incCats, excCats; // split categories... for (unsigned int i = 0; i < cats.size(); i++) { // NOTE: excluding tags have priority over including tags if ((exclude&cats[i]).any()) excCats.push_back(cats[i]); else if ((include&cats[i]).any()) incCats.push_back(cats[i]); } std::map::iterator j; for (j = ut->canBuild.begin(); j != ut->canBuild.end(); ++j) { bool valid = true; unitCategory cat = j->second->cats; for (unsigned int i = 0; i < incCats.size(); i++) { // NOTE: evironment tags are handled differently: if requested // AIR, LAND, SEA & SUB in any combination that means having // at least one match automatically qualifies unit as valid if ((incCats[i]&CATS_ENV).any()) { if (incEnvCats.any()) { // filter by environment tags is active if ((incEnvCats&cat).none()) { valid = false; break; } } } else if ((incCats[i]&cat).none()) { valid = false; break; } } if (valid) { /* Filter out excludes */ for (unsigned int i = 0; i < excCats.size(); i++) { if ((excCats[i]&cat).any()) { valid = false; break; } } if (valid) { float cost = j->second->cost; candidates.insert(std::pair(cost, j->second)); } } } if (candidates.empty()) LOG_WW("CUnitTable::getBuildables no candidates found INCLUDE(" << debugCategories(include) << ") EXCLUDE("<def->humanName << ")") } UnitType* CUnitTable::canBuild(UnitType *ut, unitCategory c) { std::map::iterator it; // TODO: make it compatible with environment tags for (it = ut->canBuild.begin(); it != ut->canBuild.end(); it++) { if ((it->second->cats & c) == c) return it->second; } //LOG_WW("CUnitTable::canBuild failed to build " << debugCategories(c)) return NULL; } CUnit* CUnitTable::getUnitByDef(std::map& dic, const UnitDef *udef) { return CUnitTable::getUnitByDef(dic, udef->id); } CUnit* CUnitTable::getUnitByDef(std::map& dic, int did) { CUnit* unit; std::map::const_iterator i; for(i = dic.begin(); i != dic.end(); i++) { unit = i->second; if(unit->def->id == did) { return unit; } } return NULL; } UnitType* CUnitTable::getUnitTypeByCats(unitCategory c) { std::map::iterator it; for (it = units.begin(); it != units.end(); ++it) { if ((it->second.cats&c) == c) return &(it->second); } return NULL; } int CUnitTable::setOnOff(std::map& list, bool value) { int result = 0; std::map::iterator i; for (i = list.begin(); i != list.end(); ++i) { CUnit* unit = i->second; if (value != unit->isOn()) { unit->setOnOff(value); result++; } } return result; } std::string CUnitTable::debugCategories(const unitCategory& categories) { std::string cats(""); UnitCategory2StrMap::iterator i; for (i = cat2str.begin(); i != cat2str.end(); ++i) { unitCategory v = categories & i->first; if (v == i->first) cats += i->second + " | "; } cats = cats.substr(0, cats.length() - 3); return cats; } std::string CUnitTable::debugCategories(UnitType *ut) { std::string cats(""); UnitCategory2StrMap::iterator i; for (i = cat2str.begin(); i != cat2str.end(); ++i) { unitCategory v = ut->cats & i->first; if (v == i->first) cats += i->second + " | "; } cats = cats.substr(0, cats.length() - 3); return cats; } void CUnitTable::debugUnitDefs(UnitType *ut) { const UnitDef *ud = ut->def; sprintf(buf, "metalUpKeep(%0.2f), metalMake(%0.2f), makesMetal(%0.2f), energyUpkeep(%0.2f), energyMake(%0.2f)\n", ud->metalUpkeep, ud->metalMake, ud->makesMetal, ud->energyUpkeep, ud->energyMake); sprintf(buf, "buildTime(%0.2f), mCost(%0.2f), eCost(%0.2f)\n", ud->buildTime, ud->metalCost, ud->energyCost); } void CUnitTable::debugWeapons(UnitType *ut) { const UnitDef *ud = ut->def; for (unsigned int i = 0; i < ud->weapons.size(); i++) { const UnitDef::UnitDefWeapon *w = &(ud->weapons[i]); sprintf(buf, "Weapon name = %s\n", w->def->type.c_str()); } }