/** * moved the fix for #917 here - check a house's ability to handle base plan before it actually tries to generate a base plan, not at game start * (we have no idea what houses at game start are supposed to be able to do base planning, so mission maps f**k up) */ bool HouseExt::ExtData::CheckBasePlanSanity() { auto House = this->AttachedToObject; // this shouldn't happen, but you never know if(House->ControlledByHuman() || House->IsNeutral()) { return true; } const char *errorMsg = "AI House of country [%s] cannot build any object in %s. The AI ain't smart enough for that.\n"; bool AllIsWell(true); // if you don't have a base unit buildable, how did you get to base planning? // only through crates or map actions, so have to validate base unit in other situations auto pArray = &RulesClass::Instance->BaseUnit; bool canBuild = false; for(int i = 0; i < pArray->Count; ++i) { auto Item = pArray->GetItem(i); if(House->CanExpectToBuild(Item)) { canBuild = true; break; } } if(!canBuild) { AllIsWell = false; Debug::DevLog(Debug::Error, errorMsg, House->Type->ID, "BaseUnit"); } auto CheckList = [House, errorMsg, &AllIsWell] (DynamicVectorClass<BuildingTypeClass *> *const List, char * const ListName) -> void { if(!House->FirstBuildableFromArray(List)) { AllIsWell = false; Debug::DevLog(Debug::Error, errorMsg, House->Type->ID, ListName); } }; // commented out lists that do not cause a crash, according to testers // CheckList(&RulesClass::Instance->Shipyard, "Shipyard"); CheckList(&RulesClass::Instance->BuildPower, "BuildPower"); CheckList(&RulesClass::Instance->BuildRefinery, "BuildRefinery"); CheckList(&RulesClass::Instance->BuildWeapons, "BuildWeapons"); // CheckList(&RulesClass::Instance->BuildConst, "BuildConst"); // CheckList(&RulesClass::Instance->BuildBarracks, "BuildBarracks"); // CheckList(&RulesClass::Instance->BuildTech, "BuildTech"); // CheckList(&RulesClass::Instance->BuildRadar, "BuildRadar"); // CheckList(&RulesClass::Instance->ConcreteWalls, "ConcreteWalls"); // CheckList(&RulesClass::Instance->BuildDummy, "BuildDummy"); // CheckList(&RulesClass::Instance->BuildNavalYard, "BuildNavalYard"); auto pCountryData = HouseTypeExt::ExtMap.Find(House->Type); auto Powerplants = pCountryData->GetPowerplants(); DynamicVectorClass<BuildingTypeClass*> Dummy(Powerplants.size(), const_cast<BuildingTypeClass**>(Powerplants.begin())); CheckList(&Dummy, "Powerplants"); // auto pSide = SideClass::Array->GetItem(curHouse->Type->SideIndex); // auto pSideData = SideExt::ExtMap.Find(pSide); // CheckList(&pSideData->BaseDefenses, "Base Defenses"); return AllIsWell; }
void HouseExt::ExtData::UpdateTogglePower() { const auto pThis = this->OwnerObject(); auto pRulesExt = RulesExt::Global(); if(!pRulesExt->TogglePowerAllowed || pRulesExt->TogglePowerDelay <= 0 || pRulesExt->TogglePowerIQ < 0 || pRulesExt->TogglePowerIQ > pThis->IQLevel2 || pThis->Buildings.Count == 0 || pThis->IsBeingDrained || pThis->ControlledByHuman() || pThis->PowerBlackoutTimer.InProgress()) { return; } if(Unsorted::CurrentFrame % pRulesExt->TogglePowerDelay == 0) { struct ExpendabilityStruct { private: std::tuple<const int&, BuildingClass&> Tie() const { // compare with tie breaker to prevent desyncs return std::tie(this->Value, *this->Building); } public: bool operator < (const ExpendabilityStruct& rhs) const { return this->Tie() < rhs.Tie(); } bool operator > (const ExpendabilityStruct& rhs) const { return this->Tie() > rhs.Tie(); } BuildingClass* Building; int Value; }; // properties: the higher this value is, the more likely // this building is turned off (expendability) auto GetExpendability = [](BuildingClass* pBld) -> int { auto pType = pBld->Type; // disable super weapons, because a defenseless base is // worse than one without super weapons if(pType->HasSuperWeapon()) { return pType->PowerDrain * 20 / 10; } // non-base defenses should be disabled before going // to the base defenses. but power intensive defenses // might still evaluate worse if(!pType->IsBaseDefense) { return pType->PowerDrain * 15 / 10; } // default case, use power return pType->PowerDrain; }; // create a list of all buildings that can be powered down // and give each building an expendability value std::vector<ExpendabilityStruct> Buildings; Buildings.reserve(pThis->Buildings.Count); const auto HasLowPower = pThis->HasLowPower(); for(auto pBld : pThis->Buildings) { auto pType = pBld->Type; if(pType->CanTogglePower() && pType->PowerDrain > 0) { // if low power, we get buildings with StuffEnabled, if enough // power, we look for builidings that are disabled if(pBld->StuffEnabled == HasLowPower) { Buildings.emplace_back(ExpendabilityStruct{pBld, GetExpendability(pBld)}); } } } int Surplus = pThis->PowerOutput - pThis->PowerDrain; if(HasLowPower) { // most expendable building first std::sort(Buildings.begin(), Buildings.end(), std::greater<>()); // turn off the expendable buildings until power is restored for(const auto& item : Buildings) { auto Drain = item.Building->Type->PowerDrain; item.Building->GoOffline(); Surplus += Drain; if(Surplus >= 0) { break; } } } else { // least expendable building first std::sort(Buildings.begin(), Buildings.end(), std::less<>()); // turn on as many of them as possible for(const auto& item : Buildings) { auto Drain = item.Building->Type->PowerDrain; if(Surplus - Drain >= 0) { item.Building->GoOnline(); Surplus -= Drain; } } } } }
/** * moved the fix for #917 here - check a house's ability to handle base plan * before it actually tries to generate a base plan, not at game start (we have * no idea what houses at game start are supposed to be able to do base * planning, so mission maps f**k up) */ bool HouseExt::ExtData::CheckBasePlanSanity() { auto const pThis = this->OwnerObject(); // this shouldn't happen, but you never know if(pThis->ControlledByHuman() || pThis->IsNeutral()) { return true; } auto AllIsWell = true; auto const pRules = RulesClass::Instance; auto const pType = pThis->Type; auto const errorMsg = "AI House of country [%s] cannot build any object in " "%s. The AI ain't smart enough for that.\n"; // if you don't have a base unit buildable, how did you get to base // planning? only through crates or map actions, so have to validate base // unit in other situations auto const idxParent = pType->FindParentCountryIndex(); auto const canBuild = std::any_of( pRules->BaseUnit.begin(), pRules->BaseUnit.end(), [pThis, idxParent] (UnitTypeClass const* const pItem) { return pThis->CanExpectToBuild(pItem, idxParent); }); if(!canBuild) { AllIsWell = false; Debug::Log(Debug::Severity::Error, errorMsg, pType->ID, "BaseUnit"); } auto CheckList = [pThis, pType, idxParent, errorMsg, &AllIsWell] ( Iterator<BuildingTypeClass const*> const list, const char* const ListName) -> void { if(!HouseExt::FindBuildable(pThis, idxParent, list)) { AllIsWell = false; Debug::Log(Debug::Severity::Error, errorMsg, pType->ID, ListName); } }; // commented out lists that do not cause a crash, according to testers //CheckList(make_iterator(pRules->Shipyard), "Shipyard"); CheckList(make_iterator(pRules->BuildPower), "BuildPower"); CheckList(make_iterator(pRules->BuildRefinery), "BuildRefinery"); CheckList(make_iterator(pRules->BuildWeapons), "BuildWeapons"); //CheckList(make_iterator(pRules->BuildConst), "BuildConst"); //CheckList(make_iterator(pRules->BuildBarracks), "BuildBarracks"); //CheckList(make_iterator(pRules->BuildTech), "BuildTech"); //CheckList(make_iterator(pRules->BuildRadar), "BuildRadar"); //CheckList(make_iterator(pRules->ConcreteWalls), "ConcreteWalls"); //CheckList(make_iterator(pRules->BuildDummy), "BuildDummy"); //CheckList(make_iterator(pRules->BuildNavalYard), "BuildNavalYard"); auto const pCountryData = HouseTypeExt::ExtMap.Find(pType); auto const Powerplants = pCountryData->GetPowerplants(); CheckList(Powerplants, "Powerplants"); //auto const pSide = SideClass::Array->GetItemOrDefault(pType->SideIndex); //if(auto const pSideExt = SideExt::ExtMap.Find(pSide)) { // CheckList(make_iterator(pSideExt->BaseDefenses), "Base Defenses"); //} return AllIsWell; }