/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ #include "SkirmishAIHandler.h" #include "ExternalAI/SkirmishAIKey.h" #include "ExternalAI/AILibraryManager.h" #include "ExternalAI/EngineOutHandler.h" #include "ExternalAI/LuaAIImplHandler.h" #include "ExternalAI/Interface/SSkirmishAILibrary.h" #include "Game/GameSetup.h" #include "Game/GlobalUnsynced.h" #include "Net/Protocol/NetProtocol.h" #include "System/Option.h" #include "System/creg/STL_Map.h" #include "System/creg/STL_Set.h" #include #include CR_BIND(CSkirmishAIHandler,) CR_REG_METADATA(CSkirmishAIHandler, ( CR_MEMBER(aiInstanceData), CR_MEMBER(localTeamAIs), CR_MEMBER(aiKillFlags), CR_MEMBER(aiLibraryKeys), CR_MEMBER(skirmishAIDataMap), CR_MEMBER(luaAIShortNames), CR_IGNORED(currentAIId), CR_IGNORED(numSkirmishAIs), CR_MEMBER(gameInitialized) )) CSkirmishAIHandler skirmishAIHandler; void CSkirmishAIHandler::ResetState() { aiInstanceData.fill({}); aiLibraryKeys.fill({}); localTeamAIs.fill({}); aiKillFlags.fill(-1); skirmishAIDataMap.clear(); luaAIShortNames.clear(); numSkirmishAIs = 0; currentAIId = MAX_AIS; gameInitialized = false; } void CSkirmishAIHandler::LoadFromSetup(const CGameSetup& setup) { const auto& aiDataCont = setup.GetAIStartingDataCont(); for (const SkirmishAIData& aiData: aiDataCont) { AddSkirmishAI(aiData, &aiData - &aiDataCont[0]); } } void CSkirmishAIHandler::LoadPreGame() { if (gameInitialized) return; // extract all Lua AI implementation short names const CLuaAIImplHandler::InfoItemVector& luaAIImpls = luaAIImplHandler.LoadInfoItems(); for (const auto& impl: luaAIImpls) { for (const auto& info: impl) { if (info.key != SKIRMISH_AI_PROPERTY_SHORT_NAME) continue; assert(info.valueType == INFO_VALUE_TYPE_STRING); luaAIShortNames.insert(info.valueTypeString); } } gameInitialized = true; // finalize already added SkirmishAIData's for (size_t i = 0; i < aiInstanceData.size(); i++) { CompleteSkirmishAI(i); } } bool CSkirmishAIHandler::IsActiveSkirmishAI(const size_t skirmishAIId) const { return IsValidSkirmishAI(aiInstanceData[skirmishAIId]); } SkirmishAIData* CSkirmishAIHandler::GetSkirmishAI(const size_t skirmishAIId) { if (!IsActiveSkirmishAI(skirmishAIId)) return nullptr; return &aiInstanceData[skirmishAIId]; } size_t CSkirmishAIHandler::GetSkirmishAI(const std::string& name) const { const auto pred = [&name](const SkirmishAIData& aiData) { return (name == aiData.name); }; const auto iter = std::find_if(aiInstanceData.begin(), aiInstanceData.end(), pred); if (iter == aiInstanceData.end()) return -1; return (iter - aiInstanceData.begin()); } std::vector CSkirmishAIHandler::GetSkirmishAIsInTeam(const int teamId, const int hostPlayerId) const { std::vector ids; if (!skirmishAIDataMap.empty()) { ids.reserve(skirmishAIDataMap.size()); for (const auto& p: skirmishAIDataMap) { const SkirmishAIData& aiData = *(p.second); if (aiData.team != teamId) continue; if ((hostPlayerId >= 0) && (aiData.hostPlayer != hostPlayerId)) continue; ids.push_back(p.first); } std::sort(ids.begin(), ids.end()); } return ids; } std::vector CSkirmishAIHandler::GetSkirmishAIsByPlayer(const int hostPlayerId) const { std::vector skirmishAIs; for (const auto& p: skirmishAIDataMap) { const SkirmishAIData& aiData = *(p.second); if (aiData.hostPlayer != hostPlayerId) continue; skirmishAIs.push_back(p.first); } return skirmishAIs; } bool CSkirmishAIHandler::AddSkirmishAI(const SkirmishAIData& data, const size_t skirmishAIId) { // if the ID is already taken, something went very wrong if (IsValidSkirmishAI(aiInstanceData[skirmishAIId])) return false; assert(IsValidSkirmishAI(data)); aiInstanceData[skirmishAIId] = data; localTeamAIs[data.team] = {}; skirmishAIDataMap.emplace(skirmishAIId, &aiInstanceData[skirmishAIId]); CompleteSkirmishAI(skirmishAIId); numSkirmishAIs += 1; return true; } bool CSkirmishAIHandler::RemoveSkirmishAI(const size_t skirmishAIId) { if (!IsValidSkirmishAI(aiInstanceData[skirmishAIId])) return false; localTeamAIs[ aiInstanceData[skirmishAIId].team ] = {}; aiKillFlags[skirmishAIId] = -1; aiLibraryKeys[skirmishAIId] = {}; aiInstanceData[skirmishAIId] = {}; skirmishAIDataMap.erase(skirmishAIId); numSkirmishAIs -= 1; return true; } void CSkirmishAIHandler::CreateLocalSkirmishAI(const size_t skirmishAIId) { SkirmishAIData* aiData = GetSkirmishAI(skirmishAIId); // fail, if a local AI is already in line for this team assert(!IsValidSkirmishAI(localTeamAIs[aiData->team])); // fail, if the specified AI is not a local one assert(IsLocalSkirmishAI(*aiData)); localTeamAIs[aiData->team] = *aiData; localTeamAIs[aiData->team].isLuaAI = (aiData->isLuaAI = IsLuaAI(*aiData)); // create instantly eoh->CreateSkirmishAI(skirmishAIId); } void CSkirmishAIHandler::CreateLocalSkirmishAI(const SkirmishAIData& aiData) { // fail if a local AI is already in line for this team assert(!IsValidSkirmishAI(localTeamAIs[aiData.team])); // fail, if the specified AI is not a local one assert(IsLocalSkirmishAI(aiData)); localTeamAIs[aiData.team] = aiData; localTeamAIs[aiData.team].isLuaAI = IsLuaAI(aiData); // send to server, as the AI was not specified in the start script // (0 is bogus but will be ignored, server generates AI's real ID) clientNet->Send(CBaseNetProtocol::Get().SendAICreated(aiData.hostPlayer, 0, aiData.team, aiData.name)); } const SkirmishAIData* CSkirmishAIHandler::GetLocalSkirmishAIInCreation(const int teamId) const { if (IsValidSkirmishAI(localTeamAIs[teamId])) return &localTeamAIs[teamId]; return nullptr; } void CSkirmishAIHandler::SetLocalKillFlag(const size_t skirmishAIId, const int reason) { const SkirmishAIData& aiData = aiInstanceData[skirmishAIId]; assert(IsValidSkirmishAI(aiData)); // is valid id? assert(IsLocalSkirmishAI(aiData)); // is local AI? aiKillFlags[skirmishAIId] = reason; if (!aiData.isLuaAI) eoh->BlockSkirmishAIEvents(skirmishAIId); clientNet->Send(CBaseNetProtocol::Get().SendAIStateChanged(gu->myPlayerNum, skirmishAIId, SKIRMAISTATE_DIEING)); } const SkirmishAIKey* CSkirmishAIHandler::GetLocalSkirmishAILibraryKey(const size_t skirmishAIId) { // fail if the specified AI is not local assert(IsLocalSkirmishAI(skirmishAIId)); const SkirmishAIKey& libKey = aiLibraryKeys[skirmishAIId]; // already resolved if (!libKey.IsUnspecified()) return &libKey; // resolve it const SkirmishAIData* aiData = GetSkirmishAI(skirmishAIId); const SkirmishAIKey& resKey = aiLibManager->ResolveSkirmishAIKey(SkirmishAIKey(aiData->shortName, aiData->version)); assert(!resKey.IsUnspecified()); aiLibraryKeys[skirmishAIId] = resKey; return &aiLibraryKeys[skirmishAIId]; } bool CSkirmishAIHandler::IsLocalSkirmishAI(const size_t skirmishAIId) const { return (IsValidSkirmishAI(aiInstanceData[skirmishAIId]) && IsLocalSkirmishAI(aiInstanceData[skirmishAIId])); } bool CSkirmishAIHandler::IsLocalSkirmishAI(const SkirmishAIData& aiData) { return (aiData.hostPlayer == gu->myPlayerNum); } void CSkirmishAIHandler::CompleteWithDefaultOptionValues(const size_t skirmishAIId) { if (!gameInitialized) return; if (!IsLocalSkirmishAI(skirmishAIId)) return; const AILibraryManager* aiLibMan = AILibraryManager::GetInstance(); const AILibraryManager::T_skirmishAIInfos& aiInfos = aiLibMan->GetSkirmishAIInfos(); const SkirmishAIKey* aiKey = GetLocalSkirmishAILibraryKey(skirmishAIId); if (aiKey == nullptr) return; const auto infoIt = aiInfos.find(*aiKey); if (infoIt == aiInfos.end()) return; const CSkirmishAILibraryInfo& aiInfo = infoIt->second; const std::vector