1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-17 01:32:21 +02:00

Fix regressions

This commit is contained in:
Ivan Savenko
2025-04-10 12:27:18 +03:00
parent e6a8e5d4bd
commit 912c2eae94
17 changed files with 94 additions and 98 deletions

View File

@ -1332,7 +1332,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
{
auto tile = cb->getTile(coord, false);
assert(tile);
return cb->getObj(tile->topVisitableObj(ignoreHero));
return cb->getObj(tile->topVisitableObj(ignoreHero), false);
};
auto isTeleportAction = [&](EPathNodeAction action) -> bool

View File

@ -214,6 +214,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
CRandomGenerator rng;
auto visitablePos = town->visitablePos();
townHero->id = town->id;
townHero->setOwner(ai->playerID); // lets avoid having multiple colors
townHero->initHero(rng, static_cast<HeroTypeID>(0));
townHero->pos = townHero->convertFromVisitablePos(visitablePos);

View File

@ -31,8 +31,8 @@ namespace AIPathfinding
bool QuestAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero) const
{
auto object = questInfo.getObject(cb);
auto quest = questInfo.getQuest(cb);
auto object = questInfo.getObject(ai->cb.get());
auto quest = questInfo.getQuest(ai->cb.get());
if(object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD)
{
return dynamic_cast<const IQuestObject *>(object)->checkQuest(hero);

View File

@ -371,8 +371,10 @@ int CClient::sendRequest(const CPackForServer & request, PlayerColor player)
return requestID;
}
void CClient::battleStarted(const BattleInfo * info)
void CClient::battleStarted(const BattleID & battleID)
{
const BattleInfo * info = gameState()->getBattle(battleID);
std::shared_ptr<CPlayerInterface> att;
std::shared_ptr<CPlayerInterface> def;
const auto & leftSide = info->getSide(BattleSide::LEFT_SIDE);

View File

@ -164,7 +164,7 @@ public:
void handlePack(CPackForClient & pack); //applies the given pack and deletes it
int sendRequest(const CPackForServer & request, PlayerColor player); //returns ID given to that request
void battleStarted(const BattleInfo * info);
void battleStarted(const BattleID & battle);
void battleFinished(const BattleID & battleID);
void startPlayerBattleAction(const BattleID & battleID, PlayerColor color);

View File

@ -754,7 +754,7 @@ void ApplyFirstClientNetPackVisitor::visitBattleStart(BattleStart & pack)
void ApplyClientNetPackVisitor::visitBattleStart(BattleStart & pack)
{
cl.battleStarted(pack.info.get());
cl.battleStarted(pack.battleID);
}
void ApplyFirstClientNetPackVisitor::visitBattleNextRound(BattleNextRound & pack)

View File

@ -516,6 +516,7 @@ void CCreatureSet::putStack(const SlotID & slot, std::unique_ptr<CStackInstance>
{
assert(slot.getNum() < GameConstants::ARMY_SIZE);
assert(!hasStackAtSlot(slot));
stacks[slot] = std::move(stack);
stacks[slot]->setArmy(getArmy());
armyChanged();

View File

@ -443,16 +443,6 @@ bool CGameInfoCallback::isVisible(const CGObjectInstance *obj) const
{
return isVisible(obj, getPlayerID());
}
// const CCreatureSet* CInfoCallback::getGarrison(const CGObjectInstance *obj) const
// {
// //std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
// if()
// const CArmedInstance *armi = dynamic_cast<const CArmedInstance*>(obj);
// if(!armi)
// return nullptr;
// else
// return armi;
// }
std::vector <const CGObjectInstance *> CGameInfoCallback::getBlockingObjs( int3 pos ) const
{
@ -473,7 +463,9 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getVisitableObjs(int3
for(const auto & objID : t->visitableObjects)
{
const auto & object = getObj(objID);
const auto & object = getObj(objID, false);
if (!object)
continue; // event - visitable, but not visible
if(!getPlayerID().has_value() || object->ID != Obj::EVENT) //hide events from players
ret.push_back(object);

View File

@ -577,7 +577,6 @@ void CGameState::removeHeroPlaceholders()
for(auto obj : map->getObjects<CGHeroPlaceholder>())
{
map->removeBlockVisTiles(obj, true);
map->instanceNames.erase(obj->instanceName);
map->eraseObject(obj->id);
}
}
@ -623,20 +622,18 @@ void CGameState::initHeroes()
for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
{
auto vhi = map->tryTakeFromHeroPool(htype);
CGHeroInstance * heroInPool = map->tryGetFromHeroPool(htype);
if (vhi)
// some heroes are created as part of map loading (sod+ h3m maps)
// instances for h3 heroes from roe/ab h3m maps and heroes from mods at this point don't exist -> create them
if (!heroInPool)
{
vhi->initHero(getRandomGenerator());
auto newHeroPtr = std::make_shared<CGHeroInstance>(cb);
newHeroPtr->subID = htype.getNum();
map->addToHeroPool(newHeroPtr);
heroInPool = newHeroPtr.get();
}
else
{
vhi = std::make_shared<CGHeroInstance>(cb);
vhi->initHero(getRandomGenerator(), htype);
}
map->addToHeroPool(vhi);
heroesPool->addHeroToPool(vhi->getHeroTypeID());
heroInPool->initHero(getRandomGenerator());
}
for(auto & elem : map->disposedHeroes)

View File

@ -155,13 +155,12 @@ CBonusSystemNode & CArmedInstance::whatShouldBeAttached()
void CArmedInstance::attachToBonusSystem(CGameState * gs)
{
CArmedInstance::restoreBonusSystem(gs);
whatShouldBeAttached().attachTo(whereShouldBeAttached(gs));
}
void CArmedInstance::restoreBonusSystem(CGameState * gs)
{
whatShouldBeAttached().attachTo(whereShouldBeAttached(gs));
for(const auto & elem : stacks)
elem.second->artDeserializationFix(gs, elem.second.get());
}
@ -169,14 +168,12 @@ void CArmedInstance::restoreBonusSystem(CGameState * gs)
void CArmedInstance::detachFromBonusSystem(CGameState * gs)
{
whatShouldBeAttached().detachFrom(whereShouldBeAttached(gs));
// TODO: the opposite
// for(const auto & elem : stacks)
// elem.second->artDeserializationFix(elem.second.get());
}
void CArmedInstance::attachUnitsToArmy()
{
assert(getArmy() != nullptr);
for(const auto & elem : stacks)
elem.second->attachTo(*getArmy());
}

View File

@ -1317,10 +1317,19 @@ CGBoat * CGHeroInstance::getBoat()
void CGHeroInstance::setBoat(CGBoat* newBoat)
{
assert(newBoat);
if (newBoat)
{
boardedBoat = newBoat->id;
attachTo(*newBoat);
newBoat->setBoardedHero(this);
}
else if (boardedBoat.hasValue())
{
auto oldBoat = getBoat();
boardedBoat = {};
detachFrom(*oldBoat);
oldBoat->setBoardedHero(nullptr);
}
}
void CGHeroInstance::restoreBonusSystem(CGameState * gs)
@ -1348,7 +1357,7 @@ void CGHeroInstance::attachToBonusSystem(CGameState * gs)
void CGHeroInstance::detachFromBonusSystem(CGameState * gs)
{
CArmedInstance::attachToBonusSystem(gs);
CArmedInstance::detachFromBonusSystem(gs);
if (boardedBoat.hasValue())
{
auto boat = gs->getObjInstance(boardedBoat);

View File

@ -769,20 +769,17 @@ void CGTownInstance::setVisitingHero(CGHeroInstance *h)
if(h)
{
PlayerState *p = cb->gameState()->getPlayerState(h->tempOwner);
assert(p);
h->detachFrom(*p);
h->attachTo(townAndVis);
h->detachFromBonusSystem(cb->gameState());
h->setVisitedTown(this, false);
h->attachToBonusSystem(cb->gameState());
visitingHero = h->id;
}
else
else if (visitingHero.hasValue())
{
auto oldVisitor = dynamic_cast<CGHeroInstance*>(cb->gameState()->getObjInstance(visitingHero));
PlayerState *p = cb->gameState()->getPlayerState(getVisitingHero()->tempOwner);
oldVisitor->detachFromBonusSystem(cb->gameState());
oldVisitor->setVisitedTown(nullptr, false);
oldVisitor->detachFrom(townAndVis);
oldVisitor->attachTo(*p);
oldVisitor->attachToBonusSystem(cb->gameState());
visitingHero = {};
}
}
@ -794,20 +791,17 @@ void CGTownInstance::setGarrisonedHero(CGHeroInstance *h)
if(h)
{
PlayerState *p = cb->gameState()->getPlayerState(h->tempOwner);
assert(p);
h->detachFrom(*p);
h->attachTo(*this);
h->detachFromBonusSystem(cb->gameState());
h->setVisitedTown(this, true);
h->attachToBonusSystem(cb->gameState());
garrisonHero = h->id;
}
else
else if (garrisonHero.hasValue())
{
PlayerState *p = cb->gameState()->getPlayerState(getGarrisonHero()->tempOwner);
auto oldVisitor = dynamic_cast<CGHeroInstance*>(cb->gameState()->getObjInstance(visitingHero));
auto oldVisitor = dynamic_cast<CGHeroInstance*>(cb->gameState()->getObjInstance(garrisonHero));
oldVisitor->detachFromBonusSystem(cb->gameState());
oldVisitor->setVisitedTown(nullptr, false);
oldVisitor->detachFrom(*this);
oldVisitor->attachTo(*p);
oldVisitor->attachToBonusSystem(cb->gameState());
garrisonHero = {};
}
updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice

View File

@ -581,6 +581,7 @@ std::shared_ptr<CGObjectInstance> CMap::eraseObject(ObjectInstanceID oldObjectID
{
auto oldObject = objects.at(oldObjectID.getNum());
instanceNames.erase(oldObject->instanceName);
objects.at(oldObjectID) = nullptr;
removeBlockVisTiles(oldObject.get(), true);
oldObject->afterRemoveFromMap(this);
@ -849,6 +850,13 @@ void CMap::addToHeroPool(std::shared_ptr<CGHeroInstance> hero)
assert(heroesPool.at(hero->getHeroTypeID().getNum()) == nullptr);
heroesPool.at(hero->getHeroTypeID().getNum()) = hero;
if (!hero->id.hasValue())
{
// reserve ID for this new hero, if needed (but don't actually add it since hero is not present on map)
hero->id = ObjectInstanceID(objects.size());
objects.push_back(nullptr);
}
}
CGHeroInstance * CMap::tryGetFromHeroPool(HeroTypeID hero)

View File

@ -1104,7 +1104,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readPandora(const int3 & mapPos
void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition, const ObjectInstanceID & idToBeGiven)
{
readMessageAndGuards(object->message, object, mapPosition);
readMessageAndGuards(object->message, object, mapPosition, idToBeGiven);
Rewardable::VisitInfo vinfo;
auto & reward = vinfo.reward;
@ -1182,6 +1182,7 @@ void CMapLoaderH3M::readBoxHotaContent(CGPandoraBox * object, const int3 & mapPo
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readMonster(const int3 & mapPosition, const ObjectInstanceID & objectInstanceID)
{
auto object = std::make_shared<CGCreature>(map->cb);
object->id = objectInstanceID;
if(features.levelAB)
{
@ -1369,12 +1370,12 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readScholar(const int3 & positi
return object;
}
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readGarrison(const int3 & mapPosition)
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readGarrison(const int3 & mapPosition, const ObjectInstanceID & idToBeGiven)
{
auto object = std::make_shared<CGGarrison>(map->cb);
setOwnerAndValidate(mapPosition, object.get(), reader->readPlayer32());
readCreatureSet(object.get(), 7);
readCreatureSet(object.get(), idToBeGiven);
if(features.levelAB)
object->removableUnits = reader->readBool();
else
@ -1384,12 +1385,12 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readGarrison(const int3 & mapPo
return object;
}
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readArtifact(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate)
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readArtifact(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate, const ObjectInstanceID & idToBeGiven)
{
ArtifactID artID = ArtifactID::NONE; //random, set later
auto object = std::make_shared<CGArtifact>(map->cb);
readMessageAndGuards(object->message, object.get(), mapPosition);
readMessageAndGuards(object->message, object.get(), mapPosition, idToBeGiven);
//specific artifact
if(objectTemplate->id == Obj::ARTIFACT)
@ -1410,21 +1411,21 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readArtifact(const int3 & mapPo
return object;
}
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readScroll(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate)
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readScroll(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate, const ObjectInstanceID & idToBeGiven)
{
auto object = std::make_shared<CGArtifact>(map->cb);
readMessageAndGuards(object->message, object.get(), mapPosition);
readMessageAndGuards(object->message, object.get(), mapPosition, idToBeGiven);
SpellID spellID = reader->readSpell32();
object->setArtifactInstance(map->createArtifact(ArtifactID::SPELL_SCROLL, spellID.getNum()));
return object;
}
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readResource(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate)
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readResource(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate, const ObjectInstanceID & idToBeGiven)
{
auto object = std::make_shared<CGResource>(map->cb);
readMessageAndGuards(object->message, object.get(), mapPosition);
readMessageAndGuards(object->message, object.get(), mapPosition, idToBeGiven);
object->amount = reader->readUInt32();
@ -1798,7 +1799,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readObject(MapObjectID id, MapO
case Obj::GARRISON:
case Obj::GARRISON2:
return readGarrison(mapPosition);
return readGarrison(mapPosition, objectInstanceID);
case Obj::ARTIFACT:
case Obj::RANDOM_ART:
@ -1806,16 +1807,16 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readObject(MapObjectID id, MapO
case Obj::RANDOM_MINOR_ART:
case Obj::RANDOM_MAJOR_ART:
case Obj::RANDOM_RELIC_ART:
return readArtifact(mapPosition, objectTemplate);
return readArtifact(mapPosition, objectTemplate, objectInstanceID);
case Obj::SPELL_SCROLL:
return readScroll(mapPosition, objectTemplate);
return readScroll(mapPosition, objectTemplate, objectInstanceID);
case Obj::RANDOM_RESOURCE:
case Obj::RESOURCE:
return readResource(mapPosition, objectTemplate);
return readResource(mapPosition, objectTemplate, objectInstanceID);
case Obj::RANDOM_TOWN:
case Obj::TOWN:
return readTown(mapPosition, objectTemplate);
return readTown(mapPosition, objectTemplate, objectInstanceID);
case Obj::MINE:
case Obj::ABANDONED_MINE:
@ -1952,14 +1953,7 @@ void CMapLoaderH3M::readObjects()
if (newObject->isVisitable() && !map->isInTheMap(newObject->visitablePos()))
logGlobal->error("Map '%s': Object at %s - outside of map borders!", mapName, mapPosition.toString());
{
//TODO: define valid typeName and subtypeName for H3M maps
//boost::format fmt("%s_%d");
//fmt % nobj->typeName % nobj->id.getNum();
boost::format fmt("obj_%d");
fmt % newObject->id.getNum();
newObject->instanceName = fmt.str();
}
map->generateUniqueInstanceName(newObject.get());
map->addNewObject(newObject);
nextObjectID.num += 1;
}
@ -1967,9 +1961,12 @@ void CMapLoaderH3M::readObjects()
map->postInitialize();
}
void CMapLoaderH3M::readCreatureSet(CCreatureSet * out, int number)
void CMapLoaderH3M::readCreatureSet(CArmedInstance * out, const ObjectInstanceID & idToBeGiven)
{
for(int index = 0; index < number; ++index)
constexpr int unitsToRead = 7;
out->id = idToBeGiven;
for(int index = 0; index < unitsToRead; ++index)
{
CreatureID creatureID = reader->readCreature();
int count = reader->readUInt16();
@ -2106,7 +2103,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readHero(const int3 & mapPositi
bool hasGarison = reader->readBool();
if(hasGarison)
readCreatureSet(object.get(), 7);
readCreatureSet(object.get(), objectInstanceID);
object->formation = static_cast<EArmyFormation>(reader->readInt8Checked(0, 1));
assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT);
@ -2494,7 +2491,7 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
return missionId;
}
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)
std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate, const ObjectInstanceID & idToBeGiven)
{
auto object = std::make_shared<CGTownInstance>(map->cb);
if(features.levelAB)
@ -2512,7 +2509,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readTown(const int3 & position,
bool hasGarrison = reader->readBool();
if(hasGarrison)
readCreatureSet(object.get(), 7);
readCreatureSet(object.get(), idToBeGiven);
object->formation = static_cast<EArmyFormation>(reader->readInt8Checked(0, 1));
assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT);
@ -2698,7 +2695,7 @@ void CMapLoaderH3M::readEvents()
}
}
void CMapLoaderH3M::readMessageAndGuards(MetaString & message, CCreatureSet * guards, const int3 & position)
void CMapLoaderH3M::readMessageAndGuards(MetaString & message, CArmedInstance * guards, const int3 & position, const ObjectInstanceID & idToBeGiven)
{
bool hasMessage = reader->readBool();
if(hasMessage)
@ -2706,7 +2703,7 @@ void CMapLoaderH3M::readMessageAndGuards(MetaString & message, CCreatureSet * gu
message.appendTextID(readLocalizedString(TextIdentifier("guards", position.x, position.y, position.z, "message")));
bool hasGuards = reader->readBool();
if(hasGuards)
readCreatureSet(guards, 7);
readCreatureSet(guards, idToBeGiven);
reader->skipZero(4);
}

View File

@ -20,6 +20,7 @@ class CGHeroInstance;
class MapReaderH3M;
class MetaString;
class CArtifactInstance;
class CArmedInstance;
class CGObjectInstance;
class CGSeerHut;
class IQuestObject;
@ -192,14 +193,14 @@ private:
std::shared_ptr<CGObjectInstance> readMonster(const int3 & objectPosition, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readHero(const int3 & initialPos, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readSeerHut(const int3 & initialPos, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
std::shared_ptr<CGObjectInstance> readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readSign(const int3 & position);
std::shared_ptr<CGObjectInstance> readWitchHut(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
std::shared_ptr<CGObjectInstance> readScholar(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
std::shared_ptr<CGObjectInstance> readGarrison(const int3 & mapPosition);
std::shared_ptr<CGObjectInstance> readArtifact(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
std::shared_ptr<CGObjectInstance> readScroll(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
std::shared_ptr<CGObjectInstance> readResource(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
std::shared_ptr<CGObjectInstance> readGarrison(const int3 & mapPosition, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readArtifact(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readScroll(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readResource(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl, const ObjectInstanceID & idToBeGiven);
std::shared_ptr<CGObjectInstance> readMine(const int3 & position);
std::shared_ptr<CGObjectInstance> readAbandonedMine(const int3 & position);
std::shared_ptr<CGObjectInstance> readPandora(const int3 & position, const ObjectInstanceID & idToBeGiven);
@ -225,7 +226,7 @@ private:
* @param out the loaded creature set
* @param number the count of creatures to read
*/
void readCreatureSet(CCreatureSet * out, int number);
void readCreatureSet(CArmedInstance * out, const ObjectInstanceID & idToBeGiven);
void readBoxContent(CGPandoraBox * object, const int3 & position, const ObjectInstanceID & idToBeGiven);
void readBoxHotaContent(CGPandoraBox * object, const int3 & position, const ObjectInstanceID & idToBeGiven);
@ -249,7 +250,7 @@ private:
/**
* read optional message and optional guards
*/
void readMessageAndGuards(MetaString & message, CCreatureSet * guards, const int3 & position);
void readMessageAndGuards(MetaString & message, CArmedInstance * guards, const int3 & position, const ObjectInstanceID & idToBeGiven);
/// reads string from input stream and converts it to unicode
std::string readBasicString();

View File

@ -1246,7 +1246,7 @@ void RemoveObject::applyGs(CGameState *gs)
}
}
gs->getMap().instanceNames.erase(obj->instanceName);
gs->getMap().eraseObject(objectID);
gs->getMap().calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
}
@ -1322,17 +1322,13 @@ void TryMoveHero::applyGs(CGameState *gs)
gs->getMap().removeBlockVisTiles(boat); //hero blockvis mask will be used, we don't need to duplicate it with boat
h->setBoat(boat);
h->attachTo(*boat);
boat->setBoardedHero(h);
}
else if(result == DISEMBARK) //hero leaves boat to destination tile
{
auto * b = h->getBoat();
b->direction = h->moveDir;
b->pos = start;
b->setBoardedHero(nullptr);
gs->getMap().addBlockVisTiles(b);
h->detachFrom(*b);
h->setBoat(nullptr);
}

View File

@ -4263,6 +4263,7 @@ std::shared_ptr<CGObjectInstance> CGameHandler::createNewObject(const int3 & vis
auto o = handler->create(gameState()->cb, nullptr);
handler->configureObject(o.get(), getRandomGenerator());
assert(o->ID == objectID);
gameState()->getMap().generateUniqueInstanceName(o.get());
assert(!handler->getTemplates(terrainType).empty());
if (handler->getTemplates().empty())