1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #3409 from vcmi/fix-headless

Fix headless
This commit is contained in:
Andrii Danylchenko 2024-01-21 14:51:10 +02:00 committed by GitHub
commit 33f8f6dc53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 120 additions and 47 deletions

View File

@ -586,11 +586,18 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
requestActionASAP([=]()
{
int sel = 0;
if(hPtr.validAndSet())
{
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
nullkiller->heroManager->update();
answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hPtr, skills));
sel = nullkiller->heroManager->selectBestSkill(hPtr, skills);
}
answerQuery(queryID, sel);
});
}
@ -661,14 +668,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
if(selection) //select from multiple components -> take the last one (they're indexed [1-size])
sel = components.size();
// TODO: Find better way to understand it is Chest of Treasures
if(hero.validAndSet()
&& components.size() == 2
&& components.front().type == ComponentType::RESOURCE
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
{
sel = 1; // for now lets pick gold from a chest.
std::unique_lock<std::mutex> mxLock(nullkiller->aiStateMutex);
// TODO: Find better way to understand it is Chest of Treasures
if(hero.validAndSet()
&& components.size() == 2
&& components.front().type == ComponentType::RESOURCE
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
{
sel = 1; // for now lets pick gold from a chest.
}
}
answerQuery(askID, sel);
@ -859,6 +870,8 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
{
makePossibleUpgrades(h.get());
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
moveCreaturesToHero(h->visitedTown);

View File

@ -115,6 +115,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
void Nullkiller::resetAiState()
{
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
lockedResources = TResources();
scanDepth = ScanDepth::MAIN_FULL;
playerID = ai->playerID;
@ -127,6 +129,8 @@ void Nullkiller::updateAiState(int pass, bool fast)
{
boost::this_thread::interruption_point();
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
auto start = std::chrono::high_resolution_clock::now();
activeHero = nullptr;

View File

@ -73,6 +73,7 @@ public:
std::unique_ptr<ArmyFormation> armyFormation;
PlayerColor playerID;
std::shared_ptr<CCallback> cb;
std::mutex aiStateMutex;
Nullkiller();
void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);

View File

@ -137,6 +137,18 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
return sum > 1 ? result / sum : result;
}
uint64_t getResourcesGoldReward(const TResources & res)
{
int nonGoldResources = res[EGameResID::GEMS]
+ res[EGameResID::SULFUR]
+ res[EGameResID::WOOD]
+ res[EGameResID::ORE]
+ res[EGameResID::CRYSTAL]
+ res[EGameResID::MERCURY];
return res[EGameResID::GOLD] + 100 * nonGoldResources;
}
uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
{
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
@ -491,7 +503,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
//Evaluate resources used for construction. Gold is evaluated separately.
if (it->resType != EGameResID::GOLD)
{
sum += 0.1f * getResourceRequirementStrength(it->resType);
sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
}
}
return sum;
@ -529,6 +541,9 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
: 0;
case Obj::KEYMASTER:
return 0.6f;
default:
return 0;
}
@ -588,6 +603,8 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
case Obj::PANDORAS_BOX:
//Can contains experience, spells, or skills (only on custom maps)
return 2.5f;
case Obj::PYRAMID:
return 3.0f;
case Obj::HERO:
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
@ -660,7 +677,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
case Obj::WAGON:
return 100;
case Obj::CREATURE_BANK:
return getCreatureBankResources(target, hero)[EGameResID::GOLD];
return getResourcesGoldReward(getCreatureBankResources(target, hero));
case Obj::CRYPT:
case Obj::DERELICT_SHIP:
return 3000;

View File

@ -234,6 +234,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPath
heroNode.specialAction.reset();
heroNode.armyLoss = 0;
heroNode.chainOther = nullptr;
heroNode.dayFlags = DayFlags::NONE;
heroNode.update(coord, layer, accessibility);
}
}
@ -295,6 +296,11 @@ void AINodeStorage::commit(
{
commitedTiles.insert(destination->coord);
}
if(destination->turns == source->turns)
{
destination->dayFlags = source->dayFlags;
}
}
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(

View File

@ -41,11 +41,19 @@ namespace AIPathfinding
const int CHAIN_MAX_DEPTH = 4;
}
enum DayFlags : ui8
{
NONE = 0,
FLY_CAST = 1,
WATER_WALK_CAST = 2
};
struct AIPathNode : public CGPathNode
{
uint64_t danger;
uint64_t armyLoss;
int32_t manaCost;
int16_t manaCost;
DayFlags dayFlags;
const AIPathNode * chainOther;
std::shared_ptr<const SpecialAction> specialAction;
const ChainActor * actor;

View File

@ -22,18 +22,18 @@ namespace NKAI
namespace AIPathfinding
{
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero)
:spellToCast(spellToCast), hero(hero)
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd)
:spellToCast(spellToCast), hero(hero), flagsToAdd(flagsToAdd)
{
manaCost = hero->getSpellCost(spellToCast.toSpell());
}
WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
:AdventureCastAction(SpellID::WATER_WALK, hero)
:AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST)
{ }
AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
: AdventureCastAction(SpellID::FLY, hero)
: AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST)
{
}
@ -41,11 +41,12 @@ namespace AIPathfinding
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
AIPathNode * dstNode,
const AIPathNode * srcNode) const
{
dstMode->manaCost = srcNode->manaCost + manaCost;
dstMode->theNodeBefore = source.node;
dstNode->manaCost = srcNode->manaCost + manaCost;
dstNode->theNodeBefore = source.node;
dstNode->dayFlags = static_cast<DayFlags>(dstNode->dayFlags | flagsToAdd);
}
void AdventureCastAction::execute(const CGHeroInstance * hero) const

View File

@ -24,9 +24,10 @@ namespace AIPathfinding
SpellID spellToCast;
const CGHeroInstance * hero;
int manaCost;
DayFlags flagsToAdd;
public:
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero);
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
virtual void execute(const CGHeroInstance * hero) const override;

View File

@ -61,6 +61,12 @@ namespace AIPathfinding
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
{
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::WATER_WALK_CAST)
{
destination.blocked = false;
return;
}
auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
@ -73,6 +79,12 @@ namespace AIPathfinding
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
{
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::FLY_CAST)
{
destination.blocked = false;
return;
}
auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))

View File

@ -462,9 +462,9 @@ static void mainLoop()
{
if(CSH->client)
CSH->endGameplay();
}
GH.windows().clear();
GH.windows().clear();
}
CMM.reset();
@ -524,25 +524,24 @@ void handleQuit(bool ask)
// FIXME: avoids crash if player attempts to close game while opening is still playing
// use cursor handler as indicator that loading is not done yet
// proper solution would be to abort init thread (or wait for it to finish)
if(!ask)
{
quitApplication();
return;
}
if (!CCS->curh)
{
quitRequestedDuringOpeningPlayback = true;
return;
}
if(ask)
{
CCS->curh->set(Cursor::Map::POINTER);
CCS->curh->set(Cursor::Map::POINTER);
if (LOCPLINT)
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
else
CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
}
if (LOCPLINT)
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
else
{
quitApplication();
}
CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
}
void handleFatalError(const std::string & message, bool terminate)

View File

@ -617,7 +617,9 @@ bool CServerHandler::validateGameStart(bool allowOnlyAI) const
void CServerHandler::sendStartGame(bool allowOnlyAI) const
{
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
GH.windows().createAndPushWindow<CLoadingScreen>();
if(!settings["session"]["headless"].Bool())
GH.windows().createAndPushWindow<CLoadingScreen>();
LobbyStartGame lsg;
if(client)

View File

@ -411,7 +411,7 @@ void CClient::initPlayerEnvironments()
hasHumanPlayer = true;
}
if(!hasHumanPlayer)
if(!hasHumanPlayer && !settings["session"]["headless"].Bool())
{
Settings session = settings.write["session"];
session["spectate"].Bool() = true;
@ -436,7 +436,7 @@ void CClient::initPlayerInterfaces()
if(!vstd::contains(playerint, color))
{
logNetwork->info("Preparing interface for player %s", color.toString());
if(playerInfo.second.isControlledByAI())
if(playerInfo.second.isControlledByAI() || settings["session"]["onlyai"].Bool())
{
bool alliedToHuman = false;
for(auto & allyInfo : gs->scenarioOps->playerInfos)

View File

@ -157,6 +157,9 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack)
const CGHeroInstance *h = cl.getHero(pack.hid);
callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroManaPointsChanged, h);
if(settings["session"]["headless"].Bool())
return;
for (auto window : GH.windows().findWindows<BattleWindow>())
window->heroManaPointsChanged(h);
}
@ -467,7 +470,8 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
i->second->objectRemoved(o, pack.initiator);
}
CGI->mh->waitForOngoingAnimations();
if(CGI->mh)
CGI->mh->waitForOngoingAnimations();
}
void ApplyClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
@ -553,9 +557,11 @@ void ApplyClientNetPackVisitor::visitNewStructures(NewStructures & pack)
}
// invalidate section of map view with our object and force an update
CGI->mh->onObjectInstantRemove(town, town->getOwner());
CGI->mh->onObjectInstantAdd(town, town->getOwner());
if(CGI->mh)
{
CGI->mh->onObjectInstantRemove(town, town->getOwner());
CGI->mh->onObjectInstantAdd(town, town->getOwner());
}
}
void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
{
@ -566,8 +572,11 @@ void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
}
// invalidate section of map view with our object and force an update
CGI->mh->onObjectInstantRemove(town, town->getOwner());
CGI->mh->onObjectInstantAdd(town, town->getOwner());
if(CGI->mh)
{
CGI->mh->onObjectInstantRemove(town, town->getOwner());
CGI->mh->onObjectInstantAdd(town, town->getOwner());
}
}
void ApplyClientNetPackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack)
@ -651,7 +660,7 @@ void ApplyFirstClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty &
}
// invalidate section of map view with our object and force an update with new flag color
if (pack.what == ObjProperty::OWNER)
if (pack.what == ObjProperty::OWNER && CGI->mh)
{
auto object = gs.getObjInstance(pack.id);
CGI->mh->onObjectInstantRemove(object, object->getOwner());
@ -668,7 +677,7 @@ void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
}
// invalidate section of map view with our object and force an update with new flag color
if (pack.what == ObjProperty::OWNER)
if (pack.what == ObjProperty::OWNER && CGI->mh)
{
auto object = gs.getObjInstance(pack.id);
CGI->mh->onObjectInstantAdd(object, object->getOwner());
@ -1023,7 +1032,9 @@ void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
if(gs.isVisible(obj, i->first))
i->second->newObject(obj);
}
CGI->mh->waitForOngoingAnimations();
if(CGI->mh)
CGI->mh->waitForOngoingAnimations();
}
void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack)

View File

@ -249,9 +249,7 @@ PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlocking
}
case EPathNodeAction::BLOCKING_VISIT:
return destination.guarded
? BlockingReason::DESTINATION_GUARDED
: BlockingReason::DESTINATION_BLOCKVIS;
return BlockingReason::DESTINATION_BLOCKVIS;
case EPathNodeAction::NORMAL:
return BlockingReason::NONE;