1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-14 10:12:59 +02:00
vcmi/lib/mapping/CMap.cpp
K 3a27725fcb Optimize CMap::isInTheMap
replace x >= 0 && x < size by (unsigned)x < size

By converting signed coordinate to unsigned number, negative values became
very large positive ones, larger than every positive signed number and
therefore also bigger than the map size. As a result check against size
also implicitly checks if coordinate is negative.

Compiler cannot do this transformation automatically because it doesn't
know that map dimensions are always positive.

The change shrinks isInTheMap from 19 instructions to 11 on x86.
2024-08-22 11:39:24 +02:00

778 lines
19 KiB
C++

/*
* CMap.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CMap.h"
#include "../CArtHandler.h"
#include "../VCMI_Lib.h"
#include "../CCreatureHandler.h"
#include "../CHeroHandler.h"
#include "../RiverHandler.h"
#include "../RoadHandler.h"
#include "../TerrainHandler.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../mapObjects/CGTownInstance.h"
#include "../mapObjects/CQuest.h"
#include "../mapObjects/ObjectTemplate.h"
#include "../texts/CGeneralTextHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CSkillHandler.h"
#include "CMapEditManager.h"
#include "CMapOperation.h"
#include "../serializer/JsonSerializeFormat.h"
#include <vstd/RNG.h>
VCMI_LIB_NAMESPACE_BEGIN
void Rumor::serializeJson(JsonSerializeFormat & handler)
{
handler.serializeString("name", name);
handler.serializeStruct("text", text);
}
DisposedHero::DisposedHero() : heroId(0), portrait(255)
{
}
CMapEvent::CMapEvent()
: humanAffected(false)
, computerAffected(false)
, firstOccurrence(0)
, nextOccurrence(0)
{
}
bool CMapEvent::occursToday(int currentDay) const
{
if (currentDay == firstOccurrence + 1)
return true;
if (nextOccurrence == 0)
return false;
if (currentDay < firstOccurrence)
return false;
return (currentDay - firstOccurrence - 1) % nextOccurrence == 0;
}
bool CMapEvent::affectsPlayer(PlayerColor color, bool isHuman) const
{
if (players.count(color) == 0)
return false;
if (!isHuman && !computerAffected)
return false;
if (isHuman && !humanAffected)
return false;
return true;
}
void CMapEvent::serializeJson(JsonSerializeFormat & handler)
{
handler.serializeString("name", name);
handler.serializeStruct("message", message);
if (!handler.saving && handler.getCurrent()["players"].isNumber())
{
// compatibility for old maps
int playersMask = 0;
handler.serializeInt("players", playersMask);
for (int i = 0; i < 8; ++i)
if ((playersMask & (1 << i)) != 0)
players.insert(PlayerColor(i));
}
else
{
handler.serializeIdArray("players", players);
}
handler.serializeInt("humanAffected", humanAffected);
handler.serializeInt("computerAffected", computerAffected);
handler.serializeInt("firstOccurrence", firstOccurrence);
handler.serializeInt("nextOccurrence", nextOccurrence);
resources.serializeJson(handler, "resources");
}
void CCastleEvent::serializeJson(JsonSerializeFormat & handler)
{
CMapEvent::serializeJson(handler);
// TODO: handler.serializeIdArray("buildings", buildings);
{
std::vector<BuildingID> temp(buildings.begin(), buildings.end());
auto a = handler.enterArray("buildings");
a.syncSize(temp);
for(int i = 0; i < temp.size(); ++i)
{
int buildingID = temp[i].getNum();
a.serializeInt(i, buildingID);
buildings.insert(buildingID);
}
}
{
auto a = handler.enterArray("creatures");
a.syncSize(creatures);
for(int i = 0; i < creatures.size(); ++i)
a.serializeInt(i, creatures[i]);
}
}
TerrainTile::TerrainTile():
terType(nullptr),
riverType(VLC->riverTypeHandler->getById(River::NO_RIVER)),
roadType(VLC->roadTypeHandler->getById(Road::NO_ROAD)),
terView(0),
riverDir(0),
roadDir(0),
extTileFlags(0),
visitable(false),
blocked(false)
{
}
bool TerrainTile::entrableTerrain(const TerrainTile * from) const
{
return entrableTerrain(from ? from->terType->isLand() : true, from ? from->terType->isWater() : true);
}
bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const
{
return terType->isPassable()
&& ((allowSea && terType->isWater()) || (allowLand && terType->isLand()));
}
bool TerrainTile::isClear(const TerrainTile * from) const
{
return entrableTerrain(from) && !blocked;
}
Obj TerrainTile::topVisitableId(bool excludeTop) const
{
return topVisitableObj(excludeTop) ? topVisitableObj(excludeTop)->ID : Obj(Obj::NO_OBJ);
}
CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
{
if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1))
return nullptr;
if(excludeTop)
return visitableObjects[visitableObjects.size()-2];
return visitableObjects.back();
}
EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const
{
if(terType->isWater() || !terType->isPassable())
return EDiggingStatus::WRONG_TERRAIN;
int allowedBlocked = excludeTop ? 1 : 0;
if(blockingObjects.size() > allowedBlocked || topVisitableObj(excludeTop))
return EDiggingStatus::TILE_OCCUPIED;
else
return EDiggingStatus::CAN_DIG;
}
bool TerrainTile::hasFavorableWinds() const
{
return extTileFlags & 128;
}
bool TerrainTile::isWater() const
{
return terType->isWater();
}
CMap::CMap(IGameCallback * cb)
: GameCallbackHolder(cb)
, checksum(0)
, grailPos(-1, -1, -1)
, grailRadius(0)
, waterMap(false)
, uidCounter(0)
{
allHeroes.resize(VLC->heroh->size());
allowedAbilities = VLC->skillh->getDefaultAllowed();
allowedArtifact = VLC->arth->getDefaultAllowed();
allowedSpells = VLC->spellh->getDefaultAllowed();
}
CMap::~CMap()
{
getEditManager()->getUndoManager().clearAll();
for(auto obj : objects)
obj.dellNull();
for(auto quest : quests)
quest.dellNull();
for(auto artInstance : artInstances)
artInstance.dellNull();
resetStaticData();
}
void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
{
const int zVal = obj->pos.z;
for(int fx = 0; fx < obj->getWidth(); ++fx)
{
int xVal = obj->pos.x - fx;
for(int fy = 0; fy < obj->getHeight(); ++fy)
{
int yVal = obj->pos.y - fy;
if(xVal>=0 && xVal < width && yVal>=0 && yVal < height)
{
TerrainTile & curt = terrain[zVal][xVal][yVal];
if(total || obj->visitableAt(xVal, yVal))
{
curt.visitableObjects -= obj;
curt.visitable = curt.visitableObjects.size();
}
if(total || obj->blockingAt(xVal, yVal))
{
curt.blockingObjects -= obj;
curt.blocked = curt.blockingObjects.size();
}
}
}
}
}
void CMap::addBlockVisTiles(CGObjectInstance * obj)
{
const int zVal = obj->pos.z;
for(int fx = 0; fx < obj->getWidth(); ++fx)
{
int xVal = obj->pos.x - fx;
for(int fy = 0; fy < obj->getHeight(); ++fy)
{
int yVal = obj->pos.y - fy;
if(xVal>=0 && xVal < width && yVal >= 0 && yVal < height)
{
TerrainTile & curt = terrain[zVal][xVal][yVal];
if(obj->visitableAt(xVal, yVal))
{
curt.visitableObjects.push_back(obj);
curt.visitable = true;
}
if(obj->blockingAt(xVal, yVal))
{
curt.blockingObjects.push_back(obj);
curt.blocked = true;
}
}
}
}
}
void CMap::calculateGuardingGreaturePositions()
{
int levels = twoLevel ? 2 : 1;
for(int z = 0; z < levels; z++)
{
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
guardingCreaturePositions[z][x][y] = guardingCreaturePosition(int3(x, y, z));
}
}
}
}
CGHeroInstance * CMap::getHero(HeroTypeID heroID)
{
for(auto & elem : heroesOnMap)
if(elem->getHeroType() == heroID)
return elem;
return nullptr;
}
bool CMap::isCoastalTile(const int3 & pos) const
{
//todo: refactoring: extract neighbor tile iterator and use it in GameState
static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
if(!isInTheMap(pos))
{
logGlobal->error("Coastal check outside of map: %s", pos.toString());
return false;
}
if(isWaterTile(pos))
return false;
for(const auto & dir : dirs)
{
const int3 hlp = pos + dir;
if(!isInTheMap(hlp))
continue;
const TerrainTile &hlpt = getTile(hlp);
if(hlpt.isWater())
return true;
}
return false;
}
bool CMap::isInTheMap(const int3 & pos) const
{
// Check whether coord < 0 is done implicitly. Negative signed int overflows to unsigned number larger than all signed ints.
return
static_cast<uint32_t>(pos.x) < static_cast<uint32_t>(width) &&
static_cast<uint32_t>(pos.y) < static_cast<uint32_t>(height) &&
static_cast<uint32_t>(pos.z) <= (twoLevel ? 1 : 0);
}
TerrainTile & CMap::getTile(const int3 & tile)
{
assert(isInTheMap(tile));
return terrain[tile.z][tile.x][tile.y];
}
const TerrainTile & CMap::getTile(const int3 & tile) const
{
assert(isInTheMap(tile));
return terrain[tile.z][tile.x][tile.y];
}
bool CMap::isWaterTile(const int3 &pos) const
{
return isInTheMap(pos) && getTile(pos).isWater();
}
bool CMap::canMoveBetween(const int3 &src, const int3 &dst) const
{
const TerrainTile * dstTile = &getTile(dst);
const TerrainTile * srcTile = &getTile(src);
return checkForVisitableDir(src, dstTile, dst) && checkForVisitableDir(dst, srcTile, src);
}
bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile * pom, const int3 & dst) const
{
if (!pom->entrableTerrain()) //rock is never accessible
return false;
for(auto * obj : pom->visitableObjects) //checking destination tile
{
if(!vstd::contains(pom->blockingObjects, obj)) //this visitable object is not blocking, ignore
continue;
if (!obj->appearance->isVisitableFrom(src.x - dst.x, src.y - dst.y))
return false;
}
return true;
}
int3 CMap::guardingCreaturePosition (int3 pos) const
{
const int3 originalPos = pos;
// Give monster at position priority.
if (!isInTheMap(pos))
return int3(-1, -1, -1);
const TerrainTile &posTile = getTile(pos);
if (posTile.visitable)
{
for (CGObjectInstance* obj : posTile.visitableObjects)
{
if (obj->ID == Obj::MONSTER)
return pos;
}
}
// See if there are any monsters adjacent.
bool water = posTile.isWater();
pos -= int3(1, 1, 0); // Start with top left.
for (int dx = 0; dx < 3; dx++)
{
for (int dy = 0; dy < 3; dy++)
{
if (isInTheMap(pos))
{
const auto & tile = getTile(pos);
if (tile.visitable && (tile.isWater() == water))
{
for (CGObjectInstance* obj : tile.visitableObjects)
{
if (obj->ID == Obj::MONSTER && checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile
{
return pos;
}
}
}
}
pos.y++;
}
pos.y -= 3;
pos.x++;
}
return int3(-1, -1, -1);
}
const CGObjectInstance * CMap::getObjectiveObjectFrom(const int3 & pos, Obj type)
{
for (CGObjectInstance * object : getTile(pos).visitableObjects)
{
if (object->ID == type)
return object;
}
// There is weird bug because of which sometimes heroes will not be found properly despite having correct position
// Try to workaround that and find closest object that we can use
logGlobal->error("Failed to find object of type %d at %s", type.getNum(), pos.toString());
logGlobal->error("Will try to find closest matching object");
CGObjectInstance * bestMatch = nullptr;
for (CGObjectInstance * object : objects)
{
if (object && object->ID == type)
{
if (bestMatch == nullptr)
bestMatch = object;
else
{
if (object->pos.dist2dSQ(pos) < bestMatch->pos.dist2dSQ(pos))
bestMatch = object;// closer than one we already found
}
}
}
assert(bestMatch != nullptr); // if this happens - victory conditions or map itself is very, very broken
logGlobal->error("Will use %s from %s", bestMatch->getObjectName(), bestMatch->pos.toString());
return bestMatch;
}
void CMap::checkForObjectives()
{
// NOTE: probably should be moved to MapFormatH3M.cpp
for (TriggeredEvent & event : triggeredEvents)
{
auto patcher = [&](EventCondition cond) -> EventExpression::Variant
{
switch (cond.condition)
{
case EventCondition::HAVE_ARTIFACT:
event.onFulfill.replaceTextID(cond.objectType.as<ArtifactID>().toEntity(VLC)->getNameTextID());
break;
case EventCondition::HAVE_CREATURES:
event.onFulfill.replaceTextID(cond.objectType.as<CreatureID>().toEntity(VLC)->getNameSingularTextID());
event.onFulfill.replaceNumber(cond.value);
break;
case EventCondition::HAVE_RESOURCES:
event.onFulfill.replaceName(cond.objectType.as<GameResID>());
event.onFulfill.replaceNumber(cond.value);
break;
case EventCondition::HAVE_BUILDING:
if (isInTheMap(cond.position))
cond.objectID = getObjectiveObjectFrom(cond.position, Obj::TOWN)->id;
break;
case EventCondition::CONTROL:
if (isInTheMap(cond.position))
cond.objectID = getObjectiveObjectFrom(cond.position, cond.objectType.as<MapObjectID>())->id;
if (cond.objectID != ObjectInstanceID::NONE)
{
const auto * town = dynamic_cast<const CGTownInstance *>(objects[cond.objectID].get());
if (town)
event.onFulfill.replaceRawString(town->getNameTranslated());
const auto * hero = dynamic_cast<const CGHeroInstance *>(objects[cond.objectID].get());
if (hero)
event.onFulfill.replaceRawString(hero->getNameTranslated());
}
break;
case EventCondition::DESTROY:
if (isInTheMap(cond.position))
cond.objectID = getObjectiveObjectFrom(cond.position, cond.objectType.as<MapObjectID>())->id;
if (cond.objectID != ObjectInstanceID::NONE)
{
const auto * hero = dynamic_cast<const CGHeroInstance *>(objects[cond.objectID].get());
if (hero)
event.onFulfill.replaceRawString(hero->getNameTranslated());
}
break;
case EventCondition::TRANSPORT:
cond.objectID = getObjectiveObjectFrom(cond.position, Obj::TOWN)->id;
break;
//break; case EventCondition::DAYS_PASSED:
//break; case EventCondition::IS_HUMAN:
//break; case EventCondition::DAYS_WITHOUT_TOWN:
//break; case EventCondition::STANDARD_WIN:
}
return cond;
};
event.trigger = event.trigger.morph(patcher);
}
}
void CMap::addNewArtifactInstance(ConstTransitivePtr<CArtifactInstance> art)
{
art->setId(static_cast<ArtifactInstanceID>(artInstances.size()));
artInstances.emplace_back(art);
}
void CMap::eraseArtifactInstance(CArtifactInstance * art)
{
//TODO: handle for artifacts removed in map editor
assert(artInstances[art->getId().getNum()] == art);
artInstances[art->getId().getNum()].dellNull();
}
void CMap::addNewQuestInstance(CQuest* quest)
{
quest->qid = static_cast<si32>(quests.size());
quests.emplace_back(quest);
}
void CMap::removeQuestInstance(CQuest * quest)
{
//TODO: should be called only by map editor.
//During game, completed quests or quests from removed objects stay forever
//Shift indexes
auto iter = std::next(quests.begin(), quest->qid);
iter = quests.erase(iter);
for (int i = quest->qid; iter != quests.end(); ++i, ++iter)
{
(*iter)->qid = i;
}
}
void CMap::setUniqueInstanceName(CGObjectInstance * obj)
{
//this gives object unique name even if objects are removed later
auto uid = uidCounter++;
boost::format fmt("%s_%d");
fmt % obj->typeName % uid;
obj->instanceName = fmt.str();
}
void CMap::addNewObject(CGObjectInstance * obj)
{
if(obj->id != ObjectInstanceID(static_cast<si32>(objects.size())))
throw std::runtime_error("Invalid object instance id");
if(obj->instanceName.empty())
throw std::runtime_error("Object instance name missing");
if (vstd::contains(instanceNames, obj->instanceName))
throw std::runtime_error("Object instance name duplicated: "+obj->instanceName);
objects.emplace_back(obj);
instanceNames[obj->instanceName] = obj;
addBlockVisTiles(obj);
//TODO: how about defeated heroes recruited again?
obj->afterAddToMap(this);
}
void CMap::moveObject(CGObjectInstance * obj, const int3 & pos)
{
removeBlockVisTiles(obj);
obj->pos = pos;
addBlockVisTiles(obj);
}
void CMap::removeObject(CGObjectInstance * obj)
{
removeBlockVisTiles(obj);
instanceNames.erase(obj->instanceName);
//update indices
auto iter = std::next(objects.begin(), obj->id.getNum());
iter = objects.erase(iter);
for(int i = obj->id.getNum(); iter != objects.end(); ++i, ++iter)
{
(*iter)->id = ObjectInstanceID(i);
}
obj->afterRemoveFromMap(this);
//TODO: Clean artifact instances (mostly worn by hero?) and quests related to this object
//This causes crash with undo/redo in editor
}
bool CMap::isWaterMap() const
{
return waterMap;
}
bool CMap::calculateWaterContent()
{
size_t totalTiles = height * width * levels();
size_t waterTiles = 0;
for(auto tile = terrain.origin(); tile < (terrain.origin() + terrain.num_elements()); ++tile)
{
if (tile->isWater())
{
waterTiles++;
}
}
if (waterTiles >= totalTiles / 100) //At least 1% of area is water
{
waterMap = true;
}
else
{
waterMap = false;
}
return waterMap;
}
void CMap::banWaterContent()
{
banWaterHeroes();
banWaterArtifacts();
banWaterSpells();
banWaterSkills();
}
void CMap::banWaterSpells()
{
vstd::erase_if(allowedSpells, [&](SpellID spell)
{
return spell.toSpell()->onlyOnWaterMap && !isWaterMap();
});
}
void CMap::banWaterArtifacts()
{
vstd::erase_if(allowedArtifact, [&](ArtifactID artifact)
{
return artifact.toArtifact()->onlyOnWaterMap && !isWaterMap();
});
}
void CMap::banWaterSkills()
{
vstd::erase_if(allowedAbilities, [&](SecondarySkill skill)
{
return skill.toSkill()->onlyOnWaterMap && !isWaterMap();
});
}
void CMap::banWaterHeroes()
{
vstd::erase_if(allowedHeroes, [&](HeroTypeID hero)
{
return hero.toHeroType()->onlyOnWaterMap && !isWaterMap();
});
vstd::erase_if(allowedHeroes, [&](HeroTypeID hero)
{
return hero.toHeroType()->onlyOnMapWithoutWater && isWaterMap();
});
}
void CMap::banHero(const HeroTypeID & id)
{
if (!vstd::contains(allowedHeroes, id))
logGlobal->warn("Attempt to ban hero %s, who is already not allowed", id.encode(id));
allowedHeroes.erase(id);
}
void CMap::unbanHero(const HeroTypeID & id)
{
if (vstd::contains(allowedHeroes, id))
logGlobal->warn("Attempt to unban hero %s, who is already allowed", id.encode(id));
allowedHeroes.insert(id);
}
void CMap::initTerrain()
{
terrain.resize(boost::extents[levels()][width][height]);
guardingCreaturePositions.resize(boost::extents[levels()][width][height]);
}
CMapEditManager * CMap::getEditManager()
{
if(!editManager) editManager = std::make_unique<CMapEditManager>(this);
return editManager.get();
}
void CMap::resetStaticData()
{
obeliskCount = 0;
obelisksVisited.clear();
townMerchantArtifacts.clear();
townUniversitySkills.clear();
}
void CMap::resolveQuestIdentifiers()
{
//FIXME: move to CMapLoaderH3M
for (auto & quest : quests)
{
if (quest && quest->killTarget != ObjectInstanceID::NONE)
quest->killTarget = questIdentifierToId[quest->killTarget.getNum()];
}
questIdentifierToId.clear();
}
void CMap::reindexObjects()
{
// Only reindex at editor / RMG operations
std::sort(objects.begin(), objects.end(), [](const CGObjectInstance * lhs, const CGObjectInstance * rhs)
{
// Obstacles first, then visitable, at the end - removable
if (!lhs->isVisitable() && rhs->isVisitable())
return true;
if (lhs->isVisitable() && !rhs->isVisitable())
return false;
// Special case for Windomill - draw on top of other objects
if (lhs->ID != Obj::WINDMILL && rhs->ID == Obj::WINDMILL)
return true;
if (lhs->ID == Obj::WINDMILL && rhs->ID != Obj::WINDMILL)
return false;
if (!lhs->isRemovable() && rhs->isRemovable())
return true;
if (lhs->isRemovable() && !rhs->isRemovable())
return false;
return lhs->pos.y < rhs->pos.y;
});
// instanceNames don't change
for (size_t i = 0; i < objects.size(); ++i)
{
objects[i]->id = ObjectInstanceID(i);
}
}
VCMI_LIB_NAMESPACE_END