1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-10-31 00:07:39 +02:00

Merge pull request #3820 from IvanSavenko/bugfixing

Bugfixing
This commit is contained in:
Ivan Savenko
2024-04-23 13:03:36 +03:00
committed by GitHub
21 changed files with 149 additions and 56 deletions

View File

@@ -619,7 +619,7 @@ void AIGateway::commanderGotLevel(const CCommanderInstance * commander, std::vec
void AIGateway::showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel, bool safeToAutoaccept)
{
LOG_TRACE_PARAMS(logAi, "text '%s', askID '%i', soundID '%i', selection '%i', cancel '%i'", text % askID % soundID % selection % cancel % safeToAutoaccept);
LOG_TRACE_PARAMS(logAi, "text '%s', askID '%i', soundID '%i', selection '%i', cancel '%i', autoaccept '%i'", text % askID % soundID % selection % cancel % safeToAutoaccept);
NET_EVENT_HANDLER;
status.addQuery(askID, boost::str(boost::format("Blocking dialog query with %d components - %s")
% components.size() % text));

View File

@@ -94,7 +94,7 @@ std::string CompleteQuest::questToString() const
return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
}
if(q.quest->questName == CQuest::missionName(0))
if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
return "inactive quest";
MetaString ms;

View File

@@ -146,7 +146,10 @@ TSubgoal CollectRes::whatToDoToTrade()
markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool
{
auto * o = dynamic_cast<const CGObjectInstance *>(market);
if(o && !(o->ID == Obj::TOWN && o->tempOwner == ai->playerID))
// FIXME: disabled broken visitation of external markets
//if(o && !(o->ID == Obj::TOWN && o->tempOwner == ai->playerID))
if(o && o->ID == Obj::TOWN)
{
if(!ai->isAccessible(o->visitablePos()))
return true;

View File

@@ -99,7 +99,7 @@ std::string CompleteQuest::completeMessage() const
std::string CompleteQuest::questToString() const
{
if(q.quest->questName == CQuest::missionName(0))
if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
return "inactive quest";
MetaString ms;

View File

@@ -657,7 +657,7 @@ void VCAI::commanderGotLevel(const CCommanderInstance * commander, std::vector<u
void VCAI::showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel, bool safeToAutoaccept)
{
LOG_TRACE_PARAMS(logAi, "text '%s', askID '%i', soundID '%i', selection '%i', cancel '%i'", text % askID % soundID % selection % cancel % safeToAutoaccept);
LOG_TRACE_PARAMS(logAi, "text '%s', askID '%i', soundID '%i', selection '%i', cancel '%i', autoaccept '%i'", text % askID % soundID % selection % cancel % safeToAutoaccept);
NET_EVENT_HANDLER;
int sel = 0;
status.addQuery(askID, boost::str(boost::format("Blocking dialog query with %d components - %s")

View File

@@ -386,6 +386,69 @@
"vcmi.stackExperience.rank.8" : "Elite",
"vcmi.stackExperience.rank.9" : "Master",
"vcmi.stackExperience.rank.10" : "Ace",
// Strings for HotA Seer Hut / Quest Guards
"core.seerhut.quest.heroClass.complete.0" : "Ah, you are %s. Here's a gift for you. Do you accept?",
"core.seerhut.quest.heroClass.complete.1" : "Ah, you are %s. Here's a gift for you. Do you accept?",
"core.seerhut.quest.heroClass.complete.2" : "Ah, you are %s. Here's a gift for you. Do you accept?",
"core.seerhut.quest.heroClass.complete.3" : "The guards note that you are %s and offer to let you pass. Do you accept?",
"core.seerhut.quest.heroClass.complete.4" : "The guards note that you are %s and offer to let you pass. Do you accept?",
"core.seerhut.quest.heroClass.complete.5" : "The guards note that you are %s and offer to let you pass. Do you accept?",
"core.seerhut.quest.heroClass.description.0" : "Send %s to %s",
"core.seerhut.quest.heroClass.description.1" : "Send %s to %s",
"core.seerhut.quest.heroClass.description.2" : "Send %s to %s",
"core.seerhut.quest.heroClass.description.3" : "Send %s to open gate",
"core.seerhut.quest.heroClass.description.4" : "Send %s to open gate",
"core.seerhut.quest.heroClass.description.5" : "Send %s to open gate",
"core.seerhut.quest.heroClass.hover.0" : "(seeks hero of %s class)",
"core.seerhut.quest.heroClass.hover.1" : "(seeks hero of %s class)",
"core.seerhut.quest.heroClass.hover.2" : "(seeks hero of %s class)",
"core.seerhut.quest.heroClass.hover.3" : "(seeks hero of %s class)",
"core.seerhut.quest.heroClass.hover.4" : "(seeks hero of %s class)",
"core.seerhut.quest.heroClass.hover.5" : "(seeks hero of %s class)",
"core.seerhut.quest.heroClass.receive.0" : "I've got a gift for %s.",
"core.seerhut.quest.heroClass.receive.1" : "I've got a gift for %s.",
"core.seerhut.quest.heroClass.receive.2" : "I've got a gift for %s.",
"core.seerhut.quest.heroClass.receive.3" : "The guards here say they will only let %s pass.",
"core.seerhut.quest.heroClass.receive.4" : "The guards here say they will only let %s pass.",
"core.seerhut.quest.heroClass.receive.5" : "The guards here say they will only let %s pass.",
"core.seerhut.quest.heroClass.visit.0" : "You are not %s. I've got nothing for you. Begone!",
"core.seerhut.quest.heroClass.visit.1" : "You are not %s. I've got nothing for you. Begone!",
"core.seerhut.quest.heroClass.visit.2" : "You are not %s. I've got nothing for you. Begone!",
"core.seerhut.quest.heroClass.visit.3" : "The guards here will only let %s pass.",
"core.seerhut.quest.heroClass.visit.4" : "The guards here will only let %s pass.",
"core.seerhut.quest.heroClass.visit.5" : "The guards here will only let %s pass.",
"core.seerhut.quest.reachDate.complete.0" : "I'm free now. Here's what I've got for you. Do you accept?",
"core.seerhut.quest.reachDate.complete.1" : "I'm free now. Here's what I've got for you. Do you accept?",
"core.seerhut.quest.reachDate.complete.2" : "I'm free now. Here's what I've got for you. Do you accept?",
"core.seerhut.quest.reachDate.complete.3" : "You are free to go through now. Do you wish to pass?",
"core.seerhut.quest.reachDate.complete.4" : "You are free to go through now. Do you wish to pass?",
"core.seerhut.quest.reachDate.complete.5" : "You are free to go through now. Do you wish to pass?",
"core.seerhut.quest.reachDate.description.0" : "Wait till %s for %s",
"core.seerhut.quest.reachDate.description.1" : "Wait till %s for %s",
"core.seerhut.quest.reachDate.description.2" : "Wait till %s for %s",
"core.seerhut.quest.reachDate.description.3" : "Wait till %s to open gate",
"core.seerhut.quest.reachDate.description.4" : "Wait till %s to open gate",
"core.seerhut.quest.reachDate.description.5" : "Wait till %s to open gate",
"core.seerhut.quest.reachDate.hover.0" : "(Return not before %s)",
"core.seerhut.quest.reachDate.hover.1" : "(Return not before %s)",
"core.seerhut.quest.reachDate.hover.2" : "(Return not before %s)",
"core.seerhut.quest.reachDate.hover.3" : "(Return not before %s)",
"core.seerhut.quest.reachDate.hover.4" : "(Return not before %s)",
"core.seerhut.quest.reachDate.hover.5" : "(Return not before %s)",
"core.seerhut.quest.reachDate.receive.0" : "I'm busy. Come back not before %s",
"core.seerhut.quest.reachDate.receive.1" : "I'm busy. Come back not before %s",
"core.seerhut.quest.reachDate.receive.2" : "I'm busy. Come back not before %s",
"core.seerhut.quest.reachDate.receive.3" : "Closed till %s.",
"core.seerhut.quest.reachDate.receive.4" : "Closed till %s.",
"core.seerhut.quest.reachDate.receive.5" : "Closed till %s.",
"core.seerhut.quest.reachDate.visit.0" : "I'm busy. Come back not before %s.",
"core.seerhut.quest.reachDate.visit.1" : "I'm busy. Come back not before %s.",
"core.seerhut.quest.reachDate.visit.2" : "I'm busy. Come back not before %s.",
"core.seerhut.quest.reachDate.visit.3" : "Closed till %s.",
"core.seerhut.quest.reachDate.visit.4" : "Closed till %s.",
"core.seerhut.quest.reachDate.visit.5" : "Closed till %s.",
"core.bonus.ADDITIONAL_ATTACK.name": "Double Strike",
"core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice",

View File

@@ -240,7 +240,7 @@ void CBonusSelection::createBonusesIcons()
}
case CampaignBonusType::SECONDARY_SKILL:
desc.appendLocalString(EMetaText::GENERAL_TXT, 718);
desc.replaceTextID(TextIdentifier("core", "genrltxt", "levels", bonDescs[i].info3 - 1).get());
desc.replaceTextID(TextIdentifier("core", "skilllev", bonDescs[i].info3 - 1).get());
desc.replaceName(SecondarySkill(bonDescs[i].info2));
picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;

View File

@@ -65,6 +65,7 @@
},
"airConflux": {
"index": 7,
"bannedForRandomDwelling" : true,
"creatures": [["airElemental"]],
"sounds": {
"ambient": ["LOOPAIR"]
@@ -107,6 +108,7 @@
},
"earthConflux": {
"index": 13,
"bannedForRandomDwelling" : true,
"creatures": [["earthElemental"]],
"sounds": {
"ambient": ["LOOPEART"]
@@ -128,6 +130,7 @@
},
"fireConflux": {
"index": 16,
"bannedForRandomDwelling" : true,
"creatures": [["fireElemental"]],
"sounds": {
"ambient": ["LOOPFIRE"]
@@ -345,6 +348,7 @@
},
"waterConflux": {
"index": 47,
"bannedForRandomDwelling" : true,
"creatures": [["waterElemental"]],
"sounds": {
"ambient": ["LOOPFOUN"]
@@ -538,6 +542,7 @@
"types" : {
"elementalConflux" : {
"index" : 0,
"bannedForRandomDwelling" : true,
"creatures" : [ // 4 separate "levels" to give them separate growth
[ "airElemental" ],
[ "waterElemental" ],

View File

@@ -810,7 +810,8 @@ void CModListView::installFiles(QStringList files)
images.push_back(filename);
}
manager->loadRepositories(repositories);
if (!repositories.empty())
manager->loadRepositories(repositories);
if(!mods.empty())
installMods(mods);

View File

@@ -569,7 +569,9 @@ CGeneralTextHandler::CGeneralTextHandler():
for (size_t i = 0; i < 9; ++i) //9 types of quests
{
std::string questName = CQuest::missionName(1+i);
EQuestMission missionID = static_cast<EQuestMission>(i+1);
std::string questName = CQuest::missionName(missionID);
for (size_t j = 0; j < 5; ++j)
{

View File

@@ -351,12 +351,12 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObj
}
catch (std::out_of_range & e)
{
// Leave catch block silently
// Leave catch block silently and handle error in block outside of try ... catch - all valid values should use 'return' in try block
}
std::string errorString = "Failed to find object of type " + std::to_string(type.getNum()) + "::" + std::to_string(subtype.getNum());
logGlobal->error(errorString);
throw std::runtime_error(errorString);
throw std::out_of_range(errorString);
}
TObjectTypeHandler CObjectClassesHandler::getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const

View File

@@ -51,6 +51,12 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input)
assert(!availableCreatures[currentLevel].empty());
}
guards = input["guards"];
bannedForRandomDwelling = input["bannedForRandomDwelling"].Bool();
}
bool DwellingInstanceConstructor::isBannedForRandomDwelling() const
{
return bannedForRandomDwelling;
}
bool DwellingInstanceConstructor::objectFilter(const CGObjectInstance * obj, std::shared_ptr<const ObjectTemplate> tmpl) const

View File

@@ -23,6 +23,7 @@ class DwellingInstanceConstructor : public CDefaultObjectTypeHandler<CGDwelling>
std::vector<std::vector<const CCreature *>> availableCreatures;
JsonNode guards;
bool bannedForRandomDwelling = false;
protected:
bool objectFilter(const CGObjectInstance * obj, std::shared_ptr<const ObjectTemplate> tmpl) const override;
@@ -34,6 +35,7 @@ public:
void initializeObject(CGDwelling * object) const override;
void randomizeObject(CGDwelling * object, CRandomGenerator & rng) const override;
bool isBannedForRandomDwelling() const;
bool producesCreature(const CCreature * crea) const;
std::vector<const CCreature *> getProducedCreatures() const;
};

View File

@@ -16,6 +16,7 @@
#include "../CConfigHandler.h"
#include "../GameSettings.h"
#include "../IGameCallback.h"
#include "../mapObjectConstructors/CObjectClassesHandler.h"
#include "../networkPacks/PacksForClient.h"
#include "../networkPacks/PacksForClientBattle.h"
#include "../networkPacks/StackLocation.h"
@@ -217,6 +218,18 @@ void CGCreature::pickRandomObject(CRandomGenerator & rand)
subID = VLC->creh->pickRandomMonster(rand, 7);
break;
}
try {
// sanity check
VLC->objtypeh->getHandlerFor(ID, subID);
}
catch (const std::out_of_range & )
{
// Try to generate some debug information if sanity check failed
CreatureID creatureID(subID.getNum());
throw std::out_of_range("Failed to find handler for creature " + std::to_string(creatureID.getNum()) + ", identifer:" + creatureID.toEntity(VLC)->getJsonKey());
}
ID = MapObjectID::MONSTER;
setType(ID, subID);
}

View File

@@ -146,7 +146,7 @@ void CGDwelling::pickRandomObject(CRandomGenerator & rand)
{
const auto * handler = dynamic_cast<const DwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(primaryID, entry).get());
if (handler->producesCreature(cid.toCreature()))
if (!handler->isBannedForRandomDwelling() && handler->producesCreature(cid.toCreature()))
return MapObjectSubID(entry);
}
return MapObjectSubID();

View File

@@ -133,11 +133,7 @@ void CGObjectInstance::setType(MapObjectID newID, MapObjectSubID newSubID)
//recalculate blockvis tiles - new appearance might have different blockmap than before
cb->gameState()->map->removeBlockVisTiles(this, true);
auto handler = VLC->objtypeh->getHandlerFor(newID, newSubID);
if(!handler)
{
logGlobal->error("Unknown object type %d:%d at %s", newID, newSubID, visitablePos().toString());
return;
}
if(!handler->getTemplates(tile.terType->getId()).empty())
{
appearance = handler->getTemplates(tile.terType->getId())[0];

View File

@@ -49,7 +49,7 @@ CQuest::CQuest():
isCustomNext(false),
isCustomComplete(false),
repeatedQuest(false),
questName(CQuest::missionName(0))
questName(CQuest::missionName(EQuestMission::NONE))
{
}
@@ -59,9 +59,9 @@ static std::string visitedTxt(const bool visited)
return VLC->generaltexth->allTexts[id];
}
const std::string & CQuest::missionName(int mission)
const std::string & CQuest::missionName(EQuestMission mission)
{
static const std::array<std::string, 13> names = {
static const std::array<std::string, 14> names = {
"empty",
"heroLevel",
"primarySkill",
@@ -72,9 +72,10 @@ const std::string & CQuest::missionName(int mission)
"bringResources",
"bringHero",
"bringPlayer",
"hotaINVALID", // only used for h3m parsing
"keymaster",
"hota",
"other"
"heroClass",
"reachDate"
};
if(static_cast<size_t>(mission) < names.size())
@@ -307,20 +308,18 @@ void CQuest::getCompletionText(IGameCallback * cb, MetaString &iwText) const
void CQuest::defineQuestName()
{
//standard quests
questName = CQuest::missionName(0);
if(mission != Rewardable::Limiter{}) questName = CQuest::missionName(12);
if(mission.heroLevel > 0) questName = CQuest::missionName(1);
for(auto & s : mission.primary) if(s) questName = CQuest::missionName(2);
if(!mission.spells.empty()) questName = CQuest::missionName(2);
if(!mission.secondary.empty()) questName = CQuest::missionName(2);
if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) questName = CQuest::missionName(3);
if(killTarget != ObjectInstanceID::NONE && stackToKill != CreatureID::NONE) questName = CQuest::missionName(4);
if(!mission.artifacts.empty()) questName = CQuest::missionName(5);
if(!mission.creatures.empty()) questName = CQuest::missionName(6);
if(mission.resources.nonZero()) questName = CQuest::missionName(7);
if(!mission.heroes.empty()) questName = CQuest::missionName(8);
if(!mission.players.empty()) questName = CQuest::missionName(9);
if(mission.daysPassed > 0 || !mission.heroClasses.empty()) questName = CQuest::missionName(11);
questName = CQuest::missionName(EQuestMission::NONE);
if(mission.heroLevel > 0) questName = CQuest::missionName(EQuestMission::LEVEL);
for(auto & s : mission.primary) if(s) questName = CQuest::missionName(EQuestMission::PRIMARY_SKILL);
if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) questName = CQuest::missionName(EQuestMission::KILL_HERO);
if(killTarget != ObjectInstanceID::NONE && stackToKill != CreatureID::NONE) questName = CQuest::missionName(EQuestMission::KILL_CREATURE);
if(!mission.artifacts.empty()) questName = CQuest::missionName(EQuestMission::ARTIFACT);
if(!mission.creatures.empty()) questName = CQuest::missionName(EQuestMission::ARMY);
if(mission.resources.nonZero()) questName = CQuest::missionName(EQuestMission::RESOURCES);
if(!mission.heroes.empty()) questName = CQuest::missionName(EQuestMission::HERO);
if(!mission.players.empty()) questName = CQuest::missionName(EQuestMission::PLAYER);
if(mission.daysPassed > 0) questName = CQuest::missionName(EQuestMission::HOTA_REACH_DATE);
if(!mission.heroClasses.empty()) questName = CQuest::missionName(EQuestMission::HOTA_HERO_CLASS);
}
void CQuest::addKillTargetReplacements(MetaString &out) const
@@ -466,7 +465,7 @@ void CGSeerHut::initObj(CRandomGenerator & rand)
if(quest->mission == Rewardable::Limiter{} && quest->killTarget == ObjectInstanceID::NONE)
quest->isCompleted = true;
if(quest->questName == quest->missionName(0))
if(quest->questName == quest->missionName(EQuestMission::NONE))
{
quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
}

View File

@@ -17,11 +17,30 @@ VCMI_LIB_NAMESPACE_BEGIN
class CGCreature;
enum class EQuestMission {
NONE = 0,
LEVEL = 1,
PRIMARY_SKILL = 2,
KILL_HERO = 3,
KILL_CREATURE = 4,
ARTIFACT = 5,
ARMY = 6,
RESOURCES = 7,
HERO = 8,
PLAYER = 9,
HOTA_MULTI = 10,
// end of H3 missions
KEYMASTER = 11,
HOTA_HERO_CLASS = 12,
HOTA_REACH_DATE = 13,
};
class DLL_LINKAGE CQuest final
{
public:
static const std::string & missionName(int index);
static const std::string & missionName(EQuestMission index);
static const std::string & missionState(int index);
std::string questName;

View File

@@ -1257,7 +1257,7 @@ void CGObelisk::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
if(progress > cb->gameState()->map->obeliskCount)
{
logGlobal->error("Visited %d of %d", static_cast<int>(progress), cb->gameState()->map->obeliskCount);
throw std::runtime_error("Player visited more obelisks than present on map!");
throw std::runtime_error("Player visited " + std::to_string(progress) + " obelisks out of " + std::to_string(cb->gameState()->map->obeliskCount) + " present on map!");
}
break;

View File

@@ -35,23 +35,7 @@ class SpellID;
class PlayerColor;
class int3;
enum class EQuestMission {
NONE = 0,
LEVEL = 1,
PRIMARY_SKILL = 2,
KILL_HERO = 3,
KILL_CREATURE = 4,
ARTIFACT = 5,
ARMY = 6,
RESOURCES = 7,
HERO = 8,
PLAYER = 9,
HOTA_MULTI = 10,
// end of H3 missions
KEYMASTER = 100,
HOTA_HERO_CLASS = 101,
HOTA_REACH_DATE = 102
};
enum class EQuestMission;
enum class EVictoryConditionType : int8_t
{

View File

@@ -760,7 +760,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
//TODO we need find batter way to handle double-wide stacks
//currently if only second occupied stack part is standing on gate / bridge hex then stack will start to wait for bridge to lower before it's needed. Though this is just a visual bug.
if (curStack->doubleWide())
if (curStack->doubleWide() && i + 2 < path.first.size())
{
BattleHex otherHex = curStack->occupiedHex(hex);
if (otherHex.isValid() && needOpenGates(otherHex))