diff --git a/Global.h b/Global.h index 3a53469e9..27f479e61 100644 --- a/Global.h +++ b/Global.h @@ -467,6 +467,11 @@ namespace vstd { return std::unique_ptr(new T(std::forward(arg1), std::forward(arg2), std::forward(arg3))); } + template + std::unique_ptr make_unique(Arg1 &&arg1, Arg2 &&arg2, Arg3 &&arg3, Arg4 &&arg4) + { + return std::unique_ptr(new T(std::forward(arg1), std::forward(arg2), std::forward(arg3), std::forward(arg4))); + } template typename Container::const_reference circularAt(const Container &r, size_t index) diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index eee5f40e1..4622ddada 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -478,7 +478,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std:: continue; const TerrainTile2 & tile = ttiles[pos.x][pos.y][pos.z]; - const TerrainTile &tinfo = map->terrain[pos.x][pos.y][pos.z]; + const TerrainTile &tinfo = map->getTile(int3(pos.x, pos.y, pos.z)); SDL_Rect sr; sr.x=srx; @@ -501,9 +501,9 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std:: } //Roads are shifted by 16 pixels to bottom. We have to draw both parts separately - if (pos.y > 0 && map->terrain[pos.x][pos.y-1][pos.z].roadType != ERoadType::NO_ROAD) + if (pos.y > 0 && map->getTile(int3(pos.x, pos.y-1, pos.z)).roadType != ERoadType::NO_ROAD) { //part from top tile - const TerrainTile &topTile = map->terrain[pos.x][pos.y-1][pos.z]; + const TerrainTile &topTile = map->getTile(int3(pos.x, pos.y-1, pos.z)); Rect source(0, 16, 32, 16); Rect dest(sr.x, sr.y, sr.w, sr.h/2); blitterWithRotationAndAlpha(roadDefs[topTile.roadType - 1]->ourImages[topTile.roadDir].bitmap, source, extSurf, dest, (topTile.extTileFlags>>4)%4); @@ -720,7 +720,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std:: // TODO: these should be activable by the console #ifdef MARK_BLOCKED_POSITIONS - if(map->terrain[pos.x][pos.y][top_tile.z].blocked) //temporary hiding blocked positions + if(map->getTile(int3(pos.x, pos.y, top_tile.z)).blocked) //temporary hiding blocked positions { SDL_Rect sr; @@ -733,7 +733,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std:: } #endif #ifdef MARK_VISITABLE_POSITIONS - if(map->terrain[pos.x][pos.y][top_tile.z].visitable) //temporary hiding visitable positions + if(map->getTile(int3(pos.x, pos.y, top_tile.z)).visitable) //temporary hiding visitable positions { SDL_Rect sr; @@ -1085,7 +1085,7 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN { out.clear(); TerrainTile2 & tt = ttiles[pos.x][pos.y][pos.z]; - const TerrainTile &t = map->terrain[pos.x][pos.y][pos.z]; + const TerrainTile &t = map->getTile(pos); for(std::vector < std::pair >::const_iterator i = tt.objects.begin(); i != tt.objects.end(); i++) { if(i->first->ID == Obj::HOLE) //Hole diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 9aa2d2abb..5d5940c58 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -959,7 +959,7 @@ void CGameState::init(StartInfo * si) { for (int k = 0; k < (map->twoLevel ? 2 : 1); k++) { - const TerrainTile &t = map->terrain[i][j][k]; + const TerrainTile &t = map->getTile(int3(i, j, k)); if(!t.blocked && !t.visitable && t.terType != ETerrainType::WATER @@ -1914,8 +1914,8 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const if(src == dest) //same tile return 0; - TerrainTile &s = map->terrain[src.x][src.y][src.z], - &d = map->terrain[dest.x][dest.y][dest.z]; + TerrainTile &s = map->getTile(src), + &d = map->getTile(dest); //get basic cost int ret = h->getTileCost(d,s); @@ -1993,7 +1993,7 @@ std::vector CGameState::guardingCreatures (int3 pos) const if (!map->isInTheMap(pos)) return guards; - const TerrainTile &posTile = map->terrain[pos.x][pos.y][pos.z]; + const TerrainTile &posTile = map->getTile(pos); if (posTile.visitable) { BOOST_FOREACH (CGObjectInstance* obj, posTile.visitableObjects) @@ -2012,7 +2012,7 @@ std::vector CGameState::guardingCreatures (int3 pos) const { if (map->isInTheMap(pos)) { - TerrainTile &tile = map->terrain[pos.x][pos.y][pos.z]; + const auto & tile = map->getTile(pos); if (tile.visitable && (tile.isWater() == posTile.isWater())) { BOOST_FOREACH (CGObjectInstance* obj, tile.visitableObjects) @@ -2040,7 +2040,7 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const // Give monster at position priority. if (!map->isInTheMap(pos)) return int3(-1, -1, -1); - const TerrainTile &posTile = map->terrain[pos.x][pos.y][pos.z]; + const TerrainTile &posTile = map->getTile(pos); if (posTile.visitable) { BOOST_FOREACH (CGObjectInstance* obj, posTile.visitableObjects) @@ -2063,7 +2063,7 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const { if (map->isInTheMap(pos)) { - TerrainTile &tile = map->terrain[pos.x][pos.y][pos.z]; + const auto & tile = map->getTile(pos); if (tile.visitable && (tile.isWater() == posTile.isWater())) { BOOST_FOREACH (CGObjectInstance* obj, tile.visitableObjects) @@ -3009,7 +3009,7 @@ void CPathfinder::initializeGraph() for(size_t k=0; k < out.sizes.z; ++k) { curPos = int3(i,j,k); - const TerrainTile *tinfo = &gs->map->terrain[i][j][k]; + const TerrainTile *tinfo = &gs->map->getTile(int3(i, j, k)); CGPathNode &node = graph[i][j][k]; node.accessible = evaluateAccessibility(tinfo); node.turns = 0xff; diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h index b813152e6..81c540755 100644 --- a/lib/CRandomGenerator.h +++ b/lib/CRandomGenerator.h @@ -34,101 +34,49 @@ typedef boost::uniform_real TRealDist; typedef boost::variate_generator TRandI; typedef boost::variate_generator TRand; -/** - * The random generator randomly generates integers and real numbers("doubles") between - * a given range. This is a header only class and mainly a wrapper for - * convenient usage of the boost random API. - */ +/// The random generator randomly generates integers and real numbers("doubles") between +/// a given range. This is a header only class and mainly a wrapper for +/// convenient usage of the boost random API. class CRandomGenerator { public: - /** - * Constructor. Seeds the generator with the current time by default. - */ + /// Seeds the generator with the current time by default. CRandomGenerator() { - gen.seed(std::time(nullptr)); + gen.seed(std::time(nullptr)); } - /** - * Seeds the generator with the given value. - * - * @param value the random seed - */ void seed(int value) { gen.seed(value); } - /** - * Gets a generator which generates integers in the given range. - * - * Example how to use: - * @code - * TRandI rand = getRangeI(0, 10); - * int a = rand(); // with the operator() the next value can be obtained - * int b = rand(); // you can generate more values - * @endcode - * - * @param lower the lower boundary - * @param upper the upper boundary - * @return the generator which can be used to generate integer numbers - */ + /// Generate several integer numbers within the same range. + /// e.g.: auto a = gen.getRangeI(0,10); a(); a(); a(); TRandI getRangeI(int lower, int upper) { TIntDist range(lower, upper); return TRandI(gen, range); } - /** - * Gets a integer in the given range. In comparison to getRangeI it's - * a convenient method if you want to generate only one value in a given - * range. - * - * @param lower the lower boundary - * @param upper the upper boundary - * @return the generated integer - */ int getInteger(int lower, int upper) { return getRangeI(lower, upper)(); } - /** - * Gets a generator which generates doubles in the given range. - * - * Example how to use: - * @code - * TRand rand = getRange(23.56, 32.10); - * double a = rand(); // with the operator() the next value can be obtained - * double b = rand(); // you can generate more values - * @endcode - * - * @param lower the lower boundary - * @param upper the upper boundary - * @return the generated double - */ + /// Generate several double/real numbers within the same range. + /// e.g.: auto a = gen.getRangeI(0,10); a(); a(); a(); TRand getRange(double lower, double upper) { TRealDist range(lower, upper); return TRand(gen, range); } - /** - * Gets a double in the given range. In comparison to getRange it's - * a convenient method if you want to generate only one value in a given - * range. - * - * @param lower the lower boundary - * @param upper the upper boundary - * @return the generated double - */ double getDouble(double lower, double upper) { return getRange(lower, upper)(); } private: - /** The actual boost random generator. */ TGenerator gen; }; diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 5d93c40b5..881bdfa4a 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -7,6 +7,7 @@ #include "../CHeroHandler.h" #include "../CDefObjInfoHandler.h" #include "../CSpellHandler.h" +#include "CMapEditManager.h" SHeroName::SHeroName() : heroId(-1) { @@ -144,7 +145,7 @@ CMapHeader::~CMapHeader() } -CMap::CMap() : checksum(0), terrain(nullptr), grailRadious(0) +CMap::CMap() : checksum(0), grailRadious(0), terrain(nullptr) { allowedAbilities = VLC->heroh->getDefaultAllowedAbilities(); allowedArtifact = VLC->arth->getDefaultAllowedArtifacts(); @@ -227,7 +228,7 @@ CGHeroInstance * CMap::getHero(int heroID) return nullptr; } -bool CMap::isInTheMap(const int3 &pos) const +bool CMap::isInTheMap(const int3 & pos) const { if(pos.x < 0 || pos.y < 0 || pos.z < 0 || pos.x >= width || pos.y >= height || pos.z > (twoLevel ? 1 : 0)) @@ -240,13 +241,23 @@ bool CMap::isInTheMap(const int3 &pos) const } } -TerrainTile & CMap::getTile( const int3 & tile ) +void CMap::getTileRangeCheck(const int3 & tile) const { + if(!isInTheMap(tile)) + { + throw std::runtime_error(boost::str(boost::format("Cannot get map tile for position x '%d', y '%d', z '%d'.") % tile.x % tile.y % tile.z)); + } +} + +TerrainTile & CMap::getTile(const int3 & tile) +{ + getTileRangeCheck(tile); return terrain[tile.x][tile.y][tile.z]; } -const TerrainTile & CMap::getTile( const int3 & tile ) const +const TerrainTile & CMap::getTile(const int3 & tile) const { + getTileRangeCheck(tile); return terrain[tile.x][tile.y][tile.z]; } @@ -312,3 +323,9 @@ void CMap::initTerrain() } } } + +CMapEditManager * CMap::getEditManager() +{ + if(!editManager) editManager = make_unique(this); + return editManager.get(); +} diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 432e8d202..ceecafdf4 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -28,6 +28,7 @@ class CGTownInstance; class IModableArt; class IQuestObject; class CInputStream; +class CMapEditManager; /// The hero name struct consists of the hero id and the hero name. struct DLL_LINKAGE SHeroName @@ -326,6 +327,7 @@ public: ~CMap(); void initTerrain(); + CMapEditManager * getEditManager(); TerrainTile & getTile(const int3 & tile); const TerrainTile & getTile(const int3 & tile) const; bool isInTheMap(const int3 & pos) const; @@ -347,7 +349,6 @@ public: ui32 checksum; /// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground - TerrainTile*** terrain; std::vector rumors; std::vector disposedHeroes; std::vector > predefinedHeroes; @@ -367,6 +368,14 @@ public: /// associative list to identify which hero/creature id belongs to which object id(index for objects) bmap questIdentifierToId; + unique_ptr editManager; + +private: + void getTileRangeCheck(const int3 & tile) const; + + TerrainTile*** terrain; + +public: template void serialize(Handler &h, const int formatVersion) { diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index 5c6d7ff73..89e20551f 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -5,321 +5,141 @@ #include "../filesystem/CResourceLoader.h" #include "../CDefObjInfoHandler.h" -CMapEditManager::CMapEditManager(CMap * map, int randomSeed /*= std::time(nullptr)*/) - : map(map) +CMapOperation::CMapOperation(CMap * map) : map(map) { - gen.seed(randomSeed); + } -void CMapEditManager::clearTerrain() +std::string CMapOperation::getLabel() const +{ + return ""; +} + +CMapUndoManager::CMapUndoManager() : undoRedoLimit(10) +{ + +} + +void CMapUndoManager::undo() +{ + doOperation(undoStack, redoStack, true); +} + +void CMapUndoManager::redo() +{ + doOperation(redoStack, undoStack, false); +} + +void CMapUndoManager::clearAll() +{ + undoStack.clear(); + redoStack.clear(); +} + +int CMapUndoManager::getUndoRedoLimit() const +{ + return undoRedoLimit; +} + +void CMapUndoManager::setUndoRedoLimit(int value) +{ + if(value < 0) throw std::runtime_error("Cannot set a negative value for the undo redo limit."); + undoStack.resize(std::min(undoStack.size(), static_cast(value))); + redoStack.resize(std::min(redoStack.size(), static_cast(value))); +} + +const CMapOperation * CMapUndoManager::peekRedo() const +{ + return peek(redoStack); +} + +const CMapOperation * CMapUndoManager::peekUndo() const +{ + return peek(undoStack); +} + +void CMapUndoManager::addOperation(unique_ptr && operation) +{ + undoStack.push_front(std::move(operation)); + if(undoStack.size() > undoRedoLimit) undoStack.pop_back(); + redoStack.clear(); +} + +void CMapUndoManager::doOperation(TStack & fromStack, TStack & toStack, bool doUndo) +{ + if(fromStack.empty()) return; + auto & operation = fromStack.front(); + if(doUndo) + { + operation->undo(); + } + else + { + operation->redo(); + } + toStack.push_front(std::move(operation)); + fromStack.pop_front(); +} + +const CMapOperation * CMapUndoManager::peek(const TStack & stack) const +{ + if(stack.empty()) return nullptr; + return stack.front().get(); +} + +CMapEditManager::CMapEditManager(CMap * map) + : map(map) +{ + +} + +void CMapEditManager::clearTerrain(CRandomGenerator * gen) { for(int i = 0; i < map->width; ++i) { for(int j = 0; j < map->height; ++j) { - map->terrain[i][j][0].terType = ETerrainType::WATER; - map->terrain[i][j][0].terView = gen.getInteger(20, 32); + map->getTile(int3(i, j, 0)).terType = ETerrainType::WATER; + map->getTile(int3(i, j, 0)).terView = gen->getInteger(20, 32); if(map->twoLevel) { - map->terrain[i][j][1].terType = ETerrainType::ROCK; - map->terrain[i][j][1].terView = 0; + map->getTile(int3(i, j, 1)).terType = ETerrainType::ROCK; + map->getTile(int3(i, j, 1)).terView = 0; } } } } -void CMapEditManager::drawTerrain(ETerrainType terType, int posx, int posy, int width, int height, bool underground) +void CMapEditManager::drawTerrain(const MapRect & rect, ETerrainType terType, CRandomGenerator * gen) { - bool mapLevel = underground ? 1 : 0; - for(int i = posx; i < posx + width; ++i) - { - for(int j = posy; j < posy + height; ++j) - { - map->terrain[i][j][mapLevel].terType = terType; - } - } - - //TODO there are situations where more tiles are affected implicitely - //TODO add coastal bit to extTileFlags appropriately - - updateTerrainViews(posx - 1, posy - 1, width + 2, height + 2, mapLevel); + execute(make_unique(map, rect, terType, gen)); } -void CMapEditManager::updateTerrainViews(int posx, int posy, int width, int height, int mapLevel) +void CMapEditManager::insertObject(const int3 & pos, CGObjectInstance * obj) { - for(int i = posx; i < posx + width; ++i) - { - for(int j = posy; j < posy + height; ++j) - { - const std::vector & patterns = - CTerrainViewPatternConfig::get().getPatternsForGroup(getTerrainGroup(map->terrain[i][j][mapLevel].terType)); - - // Detect a pattern which fits best - int bestPattern = -1, bestFlip = -1; - std::string transitionReplacement; - for(int k = 0; k < patterns.size(); ++k) - { - const TerrainViewPattern & pattern = patterns[k]; - - for(int flip = 0; flip < 4; ++flip) - { - ValidationResult valRslt = validateTerrainView(i, j, mapLevel, flip > 0 ? getFlippedPattern(pattern, flip) : pattern); - if(valRslt.result) - { - logGlobal->debugStream() << "Pattern detected at pos " << i << "x" << j << "x" << mapLevel << ": P-Nr. " << k - << ", Flip " << flip << ", Repl. " << valRslt.transitionReplacement; - - bestPattern = k; - bestFlip = flip; - transitionReplacement = valRslt.transitionReplacement; - break; - } - } - } - if(bestPattern == -1) - { - // This shouldn't be the case - logGlobal->warnStream() << "No pattern detected at pos " << i << "x" << j << "x" << mapLevel; - continue; - } - - // Get mapping - const TerrainViewPattern & pattern = patterns[bestPattern]; - std::pair mapping; - if(transitionReplacement.empty()) - { - mapping = pattern.mapping[0]; - } - else - { - mapping = transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1]; - } - - // Set terrain view - if(pattern.flipMode == TerrainViewPattern::FLIP_MODE_SAME_IMAGE) - { - map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first, mapping.second); - map->terrain[i][j][mapLevel].extTileFlags = bestFlip; - } - else - { - int range = (mapping.second - mapping.first) / 4; - map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first + bestFlip * range, - mapping.first + (bestFlip + 1) * range - 1); - map->terrain[i][j][mapLevel].extTileFlags = 0; - } - } - } + execute(make_unique(map, pos, obj)); } -ETerrainGroup::ETerrainGroup CMapEditManager::getTerrainGroup(ETerrainType terType) const +void CMapEditManager::execute(unique_ptr && operation) { - switch(terType) - { - case ETerrainType::DIRT: - return ETerrainGroup::DIRT; - case ETerrainType::SAND: - return ETerrainGroup::SAND; - case ETerrainType::WATER: - return ETerrainGroup::WATER; - case ETerrainType::ROCK: - return ETerrainGroup::ROCK; - default: - return ETerrainGroup::NORMAL; - } + operation->execute(); + undoManager.addOperation(std::move(operation)); } -CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const +void CMapEditManager::undo() { - ETerrainType centerTerType = map->terrain[posx][posy][mapLevel].terType; - int totalPoints = 0; - std::string transitionReplacement; - - for(int i = 0; i < 9; ++i) - { - // The center, middle cell can be skipped - if(i == 4) - { - continue; - } - - // Get terrain group of the current cell - int cx = posx + (i % 3) - 1; - int cy = posy + (i / 3) - 1; - bool isAlien = false; - ETerrainType terType; - if(cx < 0 || cx >= map->width || cy < 0 || cy >= map->height) - { - terType = centerTerType; - } - else - { - terType = map->terrain[cx][cy][mapLevel].terType; - if(terType != centerTerType) - { - isAlien = true; - } - } - - // Validate all rules per cell - int topPoints = -1; - for(int j = 0; j < pattern.data[i].size(); ++j) - { - TerrainViewPattern::WeightedRule rule = pattern.data[i][j]; - if(!rule.isStandardRule()) - { - if(recDepth == 0) - { - const TerrainViewPattern & patternForRule = CTerrainViewPatternConfig::get().getPatternById(pattern.terGroup, rule.name); - ValidationResult rslt = validateTerrainView(cx, cy, mapLevel, patternForRule, 1); - if(!rslt.result) - { - return ValidationResult(false); - } - else - { - topPoints = std::max(topPoints, rule.points); - continue; - } - } - else - { - rule.name = TerrainViewPattern::RULE_NATIVE; - } - } - - bool nativeTestOk = (rule.name == TerrainViewPattern::RULE_NATIVE || rule.name == TerrainViewPattern::RULE_ANY) && !isAlien; - auto applyValidationRslt = [&](bool rslt) - { - if(rslt) - { - topPoints = std::max(topPoints, rule.points); - } - }; - - // Validate cell with the ruleset of the pattern - if(pattern.terGroup == ETerrainGroup::NORMAL) - { - bool dirtTestOk = (rule.name == TerrainViewPattern::RULE_DIRT - || rule.name == TerrainViewPattern::RULE_TRANSITION || rule.name == TerrainViewPattern::RULE_ANY) - && isAlien && !isSandType(terType); - bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION - || rule.name == TerrainViewPattern::RULE_ANY) - && isSandType(terType); - - if(transitionReplacement.empty() && (rule.name == TerrainViewPattern::RULE_TRANSITION - || rule.name == TerrainViewPattern::RULE_ANY) && (dirtTestOk || sandTestOk)) - { - transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND; - } - applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) - || (sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT) - || nativeTestOk); - } - else if(pattern.terGroup == ETerrainGroup::DIRT) - { - bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isSandType(terType); - bool dirtTestOk = rule.name == TerrainViewPattern::RULE_DIRT && !isSandType(terType) && !nativeTestOk; - applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || dirtTestOk || nativeTestOk); - } - else if(pattern.terGroup == ETerrainGroup::SAND) - { - bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isAlien; - applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); - } - else if(pattern.terGroup == ETerrainGroup::WATER) - { - bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT - && terType != ETerrainType::WATER; - applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); - } - else if(pattern.terGroup == ETerrainGroup::ROCK) - { - bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT - && terType != ETerrainType::ROCK; - applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); - } - } - - if(topPoints == -1) - { - return ValidationResult(false); - } - else - { - totalPoints += topPoints; - } - } - - if(pattern.minPoints > totalPoints) - { - return ValidationResult(false); - } - - return ValidationResult(true, transitionReplacement); + undoManager.undo(); } -bool CMapEditManager::isSandType(ETerrainType terType) const +void CMapEditManager::redo() { - switch(terType) - { - case ETerrainType::WATER: - case ETerrainType::SAND: - case ETerrainType::ROCK: - return true; - default: - return false; - } + undoManager.redo(); } -TerrainViewPattern CMapEditManager::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const +CMapUndoManager & CMapEditManager::getUndoManager() { - if(flip == 0) - { - return pattern; - } - - TerrainViewPattern ret = pattern; - if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH) - { - for(int i = 0; i < 3; ++i) - { - int y = i * 3; - std::swap(ret.data[y], ret.data[y + 2]); - } - } - if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH) - { - for(int i = 0; i < 3; ++i) - { - std::swap(ret.data[i], ret.data[6 + i]); - } - } - - return ret; -} - -void CMapEditManager::insertObject(CGObjectInstance * obj, int posx, int posy, bool underground) -{ - obj->pos = int3(posx, posy, underground ? 1 : 0); - obj->id = ObjectInstanceID(map->objects.size()); - map->objects.push_back(obj); - if(obj->ID == Obj::TOWN) - { - map->towns.push_back(static_cast(obj)); - } - if(obj->ID == Obj::HERO) - { - map->heroes.push_back(static_cast(obj)); - } - map->addBlockVisTiles(obj); -} - -CMapEditManager::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/) - : result(result), transitionReplacement(transitionReplacement) -{ - + return undoManager; } const std::string TerrainViewPattern::FLIP_MODE_SAME_IMAGE = "sameImage"; @@ -446,3 +266,338 @@ const TerrainViewPattern & CTerrainViewPatternConfig::getPatternById(ETerrainGro } throw std::runtime_error("Pattern with ID not found: " + id); } + +DrawTerrainOperation::DrawTerrainOperation(CMap * map, const MapRect & rect, ETerrainType terType, CRandomGenerator * gen) + : CMapOperation(map), rect(rect), terType(terType), gen(gen) +{ + +} + +void DrawTerrainOperation::execute() +{ + for(int i = rect.pos.x; i < rect.pos.x + rect.width; ++i) + { + for(int j = rect.pos.y; j < rect.pos.y + rect.height; ++j) + { + map->getTile(int3(i, j, rect.pos.z)).terType = terType; + } + } + + //TODO there are situations where more tiles are affected implicitely + //TODO add coastal bit to extTileFlags appropriately + + updateTerrainViews(MapRect(int3(rect.pos.x - 1, rect.pos.y - 1, rect.pos.z), rect.width + 2, rect.height + 2)); +} + +void DrawTerrainOperation::undo() +{ + //TODO +} + +void DrawTerrainOperation::redo() +{ + //TODO +} + +std::string DrawTerrainOperation::getLabel() const +{ + return "Draw Terrain"; +} + +void DrawTerrainOperation::updateTerrainViews(const MapRect & rect) +{ + for(int i = rect.pos.x; i < rect.pos.x + rect.width; ++i) + { + for(int j = rect.pos.y; j < rect.pos.y + rect.height; ++j) + { + const auto & patterns = + CTerrainViewPatternConfig::get().getPatternsForGroup(getTerrainGroup(map->getTile(int3(i, j, rect.pos.z)).terType)); + + // Detect a pattern which fits best + int bestPattern = -1, bestFlip = -1; + std::string transitionReplacement; + for(int k = 0; k < patterns.size(); ++k) + { + const auto & pattern = patterns[k]; + + for(int flip = 0; flip < 4; ++flip) + { + auto valRslt = validateTerrainView(int3(i, j, rect.pos.z), flip > 0 ? getFlippedPattern(pattern, flip) : pattern); + if(valRslt.result) + { + logGlobal->debugStream() << "Pattern detected at pos " << i << "x" << j << "x" << rect.pos.z << ": P-Nr. " << k + << ", Flip " << flip << ", Repl. " << valRslt.transitionReplacement; + + bestPattern = k; + bestFlip = flip; + transitionReplacement = valRslt.transitionReplacement; + break; + } + } + } + if(bestPattern == -1) + { + // This shouldn't be the case + logGlobal->warnStream() << "No pattern detected at pos " << i << "x" << j << "x" << rect.pos.z; + continue; + } + + // Get mapping + const TerrainViewPattern & pattern = patterns[bestPattern]; + std::pair mapping; + if(transitionReplacement.empty()) + { + mapping = pattern.mapping[0]; + } + else + { + mapping = transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1]; + } + + // Set terrain view + auto & tile = map->getTile(int3(i, j, rect.pos.z)); + if(pattern.flipMode == TerrainViewPattern::FLIP_MODE_SAME_IMAGE) + { + tile.terView = gen->getInteger(mapping.first, mapping.second); + tile.extTileFlags = bestFlip; + } + else + { + int range = (mapping.second - mapping.first) / 4; + tile.terView = gen->getInteger(mapping.first + bestFlip * range, + mapping.first + (bestFlip + 1) * range - 1); + tile.extTileFlags = 0; + } + } + } +} + +ETerrainGroup::ETerrainGroup DrawTerrainOperation::getTerrainGroup(ETerrainType terType) const +{ + switch(terType) + { + case ETerrainType::DIRT: + return ETerrainGroup::DIRT; + case ETerrainType::SAND: + return ETerrainGroup::SAND; + case ETerrainType::WATER: + return ETerrainGroup::WATER; + case ETerrainType::ROCK: + return ETerrainGroup::ROCK; + default: + return ETerrainGroup::NORMAL; + } +} + +DrawTerrainOperation::ValidationResult DrawTerrainOperation::validateTerrainView(const int3 & pos, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const +{ + ETerrainType centerTerType = map->getTile(pos).terType; + int totalPoints = 0; + std::string transitionReplacement; + + for(int i = 0; i < 9; ++i) + { + // The center, middle cell can be skipped + if(i == 4) + { + continue; + } + + // Get terrain group of the current cell + int cx = pos.x + (i % 3) - 1; + int cy = pos.y + (i / 3) - 1; + bool isAlien = false; + ETerrainType terType; + if(cx < 0 || cx >= map->width || cy < 0 || cy >= map->height) + { + terType = centerTerType; + } + else + { + terType = map->getTile(int3(cx, cy, pos.z)).terType; + if(terType != centerTerType) + { + isAlien = true; + } + } + + // Validate all rules per cell + int topPoints = -1; + for(int j = 0; j < pattern.data[i].size(); ++j) + { + TerrainViewPattern::WeightedRule rule = pattern.data[i][j]; + if(!rule.isStandardRule()) + { + if(recDepth == 0) + { + const auto & patternForRule = CTerrainViewPatternConfig::get().getPatternById(pattern.terGroup, rule.name); + auto rslt = validateTerrainView(int3(cx, cy, pos.z), patternForRule, 1); + if(!rslt.result) + { + return ValidationResult(false); + } + else + { + topPoints = std::max(topPoints, rule.points); + continue; + } + } + else + { + rule.name = TerrainViewPattern::RULE_NATIVE; + } + } + + bool nativeTestOk = (rule.name == TerrainViewPattern::RULE_NATIVE || rule.name == TerrainViewPattern::RULE_ANY) && !isAlien; + auto applyValidationRslt = [&](bool rslt) + { + if(rslt) + { + topPoints = std::max(topPoints, rule.points); + } + }; + + // Validate cell with the ruleset of the pattern + if(pattern.terGroup == ETerrainGroup::NORMAL) + { + bool dirtTestOk = (rule.name == TerrainViewPattern::RULE_DIRT + || rule.name == TerrainViewPattern::RULE_TRANSITION || rule.name == TerrainViewPattern::RULE_ANY) + && isAlien && !isSandType(terType); + bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION + || rule.name == TerrainViewPattern::RULE_ANY) + && isSandType(terType); + + if(transitionReplacement.empty() && (rule.name == TerrainViewPattern::RULE_TRANSITION + || rule.name == TerrainViewPattern::RULE_ANY) && (dirtTestOk || sandTestOk)) + { + transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND; + } + applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) + || (sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT) + || nativeTestOk); + } + else if(pattern.terGroup == ETerrainGroup::DIRT) + { + bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isSandType(terType); + bool dirtTestOk = rule.name == TerrainViewPattern::RULE_DIRT && !isSandType(terType) && !nativeTestOk; + applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || dirtTestOk || nativeTestOk); + } + else if(pattern.terGroup == ETerrainGroup::SAND) + { + bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isAlien; + applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); + } + else if(pattern.terGroup == ETerrainGroup::WATER) + { + bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT + && terType != ETerrainType::WATER; + applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); + } + else if(pattern.terGroup == ETerrainGroup::ROCK) + { + bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT + && terType != ETerrainType::ROCK; + applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); + } + } + + if(topPoints == -1) + { + return ValidationResult(false); + } + else + { + totalPoints += topPoints; + } + } + + if(pattern.minPoints > totalPoints) + { + return ValidationResult(false); + } + + return ValidationResult(true, transitionReplacement); +} + +bool DrawTerrainOperation::isSandType(ETerrainType terType) const +{ + switch(terType) + { + case ETerrainType::WATER: + case ETerrainType::SAND: + case ETerrainType::ROCK: + return true; + default: + return false; + } +} + +TerrainViewPattern DrawTerrainOperation::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const +{ + if(flip == 0) + { + return pattern; + } + + TerrainViewPattern ret = pattern; + if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH) + { + for(int i = 0; i < 3; ++i) + { + int y = i * 3; + std::swap(ret.data[y], ret.data[y + 2]); + } + } + if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH) + { + for(int i = 0; i < 3; ++i) + { + std::swap(ret.data[i], ret.data[6 + i]); + } + } + + return ret; +} + +DrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/) + : result(result), transitionReplacement(transitionReplacement) +{ + +} + +InsertObjectOperation::InsertObjectOperation(CMap * map, const int3 & pos, CGObjectInstance * obj) + : CMapOperation(map), pos(pos), obj(obj) +{ + +} + +void InsertObjectOperation::execute() +{ + obj->pos = pos; + obj->id = ObjectInstanceID(map->objects.size()); + map->objects.push_back(obj); + if(obj->ID == Obj::TOWN) + { + map->towns.push_back(static_cast(obj)); + } + if(obj->ID == Obj::HERO) + { + map->heroes.push_back(static_cast(obj)); + } + map->addBlockVisTiles(obj); +} + +void InsertObjectOperation::undo() +{ + //TODO +} + +void InsertObjectOperation::redo() +{ + execute(); +} + +std::string InsertObjectOperation::getLabel() const +{ + return "Insert Object"; +} diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 207b7af1d..a2698e227 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -30,43 +30,83 @@ namespace ETerrainGroup }; } -/// The map edit manager provides functionality for drawing terrain and placing -/// objects on the map. -class CMapEditManager +/// Represents a map rectangle. +struct DLL_LINKAGE MapRect +{ + MapRect(int3 pos, si32 width, si32 height) : pos(pos), width(width), height(height) { }; + int3 pos; + si32 width, height; +}; + +/// The abstract base class CMapOperation defines an operation that can be executed, undone and redone. +class CMapOperation { public: - CMapEditManager(CMap * map, int randomSeed = std::time(nullptr)); + CMapOperation(CMap * map); + virtual ~CMapOperation() { }; - /// Clears the terrain. The free level is filled with water and the underground level with rock. - void clearTerrain(); + virtual void execute() = 0; + virtual void undo() = 0; + virtual void redo() = 0; + virtual std::string getLabel() const; /// Returns a display-able name of the operation. - void drawTerrain(ETerrainType terType, int posx, int posy, int width, int height, bool underground); - void insertObject(CGObjectInstance * obj, int posx, int posy, bool underground); +protected: + CMap * map; +}; + +/// The CMapUndoManager provides the functionality to save operations and undo/redo them. +class CMapUndoManager +{ +public: + CMapUndoManager(); + + void undo(); + void redo(); + void clearAll(); + + /// The undo redo limit is a number which says how many undo/redo items can be saved. The default + /// value is 10. If the value is 0, no undo/redo history will be maintained. + int getUndoRedoLimit() const; + void setUndoRedoLimit(int value); + + const CMapOperation * peekRedo() const; + const CMapOperation * peekUndo() const; + + void addOperation(unique_ptr && operation); /// Client code does not need to call this method. private: - struct ValidationResult - { - ValidationResult(bool result, const std::string & transitionReplacement = ""); + typedef std::list > TStack; - bool result; - /// The replacement of a T rule, either D or S. - std::string transitionReplacement; - }; + inline void doOperation(TStack & fromStack, TStack & toStack, bool doUndo); + inline const CMapOperation * peek(const TStack & stack) const; - void updateTerrainViews(int posx, int posy, int width, int height, int mapLevel); - ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const; - /// Validates the terrain view of the given position and with the given pattern. - ValidationResult validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern, int recDepth = 0) const; - /// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock - bool isSandType(ETerrainType terType) const; - TerrainViewPattern getFlippedPattern(const TerrainViewPattern & pattern, int flip) const; + TStack undoStack; + TStack redoStack; + int undoRedoLimit; +}; - static const int FLIP_PATTERN_HORIZONTAL = 1; - static const int FLIP_PATTERN_VERTICAL = 2; - static const int FLIP_PATTERN_BOTH = 3; +/// The map edit manager provides functionality for drawing terrain and placing +/// objects on the map. +class DLL_LINKAGE CMapEditManager +{ +public: + CMapEditManager(CMap * map); + + /// Clears the terrain. The free level is filled with water and the underground level with rock. + void clearTerrain(CRandomGenerator * gen); + + void drawTerrain(const MapRect & rect, ETerrainType terType, CRandomGenerator * gen); + void insertObject(const int3 & pos, CGObjectInstance * obj); + + CMapUndoManager & getUndoManager(); + void undo(); + void redo(); + +private: + void execute(unique_ptr && operation); CMap * map; - CRandomGenerator gen; + CMapUndoManager undoManager; }; /* ---------------------------------------------------------------------------- */ @@ -146,7 +186,7 @@ struct TerrainViewPattern }; /// The terrain view pattern config loads pattern data from the filesystem. -class CTerrainViewPatternConfig +class CTerrainViewPatternConfig : public boost::noncopyable { public: static CTerrainViewPatternConfig & get(); @@ -161,3 +201,57 @@ private: std::map > patterns; static boost::mutex smx; }; + +/// The DrawTerrainOperation class draws a terrain area on the map. +class DrawTerrainOperation : public CMapOperation +{ +public: + DrawTerrainOperation(CMap * map, const MapRect & rect, ETerrainType terType, CRandomGenerator * gen); + + void execute(); + void undo(); + void redo(); + std::string getLabel() const; + +private: + struct ValidationResult + { + ValidationResult(bool result, const std::string & transitionReplacement = ""); + + bool result; + /// The replacement of a T rule, either D or S. + std::string transitionReplacement; + }; + + void updateTerrainViews(const MapRect & rect); + ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const; + /// Validates the terrain view of the given position and with the given pattern. + ValidationResult validateTerrainView(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const; + /// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock + bool isSandType(ETerrainType terType) const; + TerrainViewPattern getFlippedPattern(const TerrainViewPattern & pattern, int flip) const; + + static const int FLIP_PATTERN_HORIZONTAL = 1; + static const int FLIP_PATTERN_VERTICAL = 2; + static const int FLIP_PATTERN_BOTH = 3; + + MapRect rect; + ETerrainType terType; + CRandomGenerator * gen; +}; + +/// The InsertObjectOperation class inserts an object to the map. +class InsertObjectOperation : public CMapOperation +{ +public: + InsertObjectOperation(CMap * map, const int3 & pos, CGObjectInstance * obj); + + void execute(); + void undo(); + void redo(); + std::string getLabel() const; + +private: + int3 pos; + CGObjectInstance * obj; +}; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index a3ef9532b..72ec6c63d 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -711,15 +711,16 @@ void CMapLoaderH3M::readTerrain() { for(int z = 0; z < map->height; z++) { - map->terrain[z][c][a].terType = ETerrainType(reader.readUInt8()); - map->terrain[z][c][a].terView = reader.readUInt8(); - map->terrain[z][c][a].riverType = static_cast(reader.readUInt8()); - map->terrain[z][c][a].riverDir = reader.readUInt8(); - map->terrain[z][c][a].roadType = static_cast(reader.readUInt8()); - map->terrain[z][c][a].roadDir = reader.readUInt8(); - map->terrain[z][c][a].extTileFlags = reader.readUInt8(); - map->terrain[z][c][a].blocked = (map->terrain[z][c][a].terType == ETerrainType::ROCK ? 1 : 0); //underground tiles are always blocked - map->terrain[z][c][a].visitable = 0; + auto & tile = map->getTile(int3(z, c, a)); + tile.terType = ETerrainType(reader.readUInt8()); + tile.terView = reader.readUInt8(); + tile.riverType = static_cast(reader.readUInt8()); + tile.riverDir = reader.readUInt8(); + tile.roadType = static_cast(reader.readUInt8()); + tile.roadDir = reader.readUInt8(); + tile.extTileFlags = reader.readUInt8(); + tile.blocked = (tile.terType == ETerrainType::ROCK ? 1 : 0); //underground tiles are always blocked + tile.visitable = 0; } } } diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 44ce1861b..05eb76eb1 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -179,14 +179,14 @@ const std::map & CMapGenOptions::g void CMapGenOptions::setStartingTownForPlayer(PlayerColor color, si32 town) { auto it = players.find(color); - if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set starting town for the player with the color '%s'.") % std::to_string(color.getNum()))); + if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set starting town for the player with the color '%s'.") % color)); it->second.setStartingTown(town); } void CMapGenOptions::setPlayerTypeForStandardPlayer(PlayerColor color, EPlayerType::EPlayerType playerType) { auto it = players.find(color); - if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set player type for the player with the color '%s'.") % std::to_string(color.getNum()))); + if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set player type for the player with the color '%s'.") % color)); if(playerType == EPlayerType::COMP_ONLY) throw std::runtime_error("Cannot set player type computer only to a standard player."); it->second.setPlayerType(playerType); } @@ -375,9 +375,9 @@ std::unique_ptr CMapGenerator::generate() //TODO select a template based on the map gen options or adapt it if necessary map = make_unique(); + editManager = map->getEditManager(); addHeaderInfo(); - mapMgr = make_unique(map.get(), randomSeed); genTerrain(); genTowns(); @@ -473,8 +473,8 @@ void CMapGenerator::addPlayerInfo() void CMapGenerator::genTerrain() { map->initTerrain(); //FIXME nicer solution - mapMgr->clearTerrain(); - mapMgr->drawTerrain(ETerrainType::GRASS, 10, 10, 20, 30, false); + editManager->clearTerrain(&gen); + editManager->drawTerrain(MapRect(int3(10, 10, 0), 20, 30), ETerrainType::GRASS, &gen); } void CMapGenerator::genTowns() @@ -497,7 +497,7 @@ void CMapGenerator::genTowns() town->defInfo = VLC->dobjinfo->gobjs[town->ID][town->subID]; town->builtBuildings.insert(BuildingID::FORT); town->builtBuildings.insert(BuildingID::DEFAULT); - mapMgr->insertObject(town, townPos[side].x, townPos[side].y + (i / 2) * 5, false); + editManager->insertObject(int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0), town); // Update player info playerInfo.allowedFactions.clear(); diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index 84be545a4..3a832b3f0 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -186,5 +186,5 @@ private: std::unique_ptr map; CRandomGenerator gen; int randomSeed; - std::unique_ptr mapMgr; + CMapEditManager * editManager; }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ec4162e2d..d43e62e20 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1699,7 +1699,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player return false; } - TerrainTile t = gs->map->terrain[hmpos.x][hmpos.y][hmpos.z]; + TerrainTile t = gs->map->getTile(hmpos); int cost = gs->getMovementCost(h, h->getPosition(false), CGHeroInstance::convertPosition(dst,false),h->movement); int3 guardPos = gs->guardingCreaturePosition(hmpos); @@ -1740,7 +1740,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player // should be called if hero changes tile but before applying TryMoveHero package auto leaveTile = [&]() { - BOOST_FOREACH(CGObjectInstance *obj, gs->map->terrain[h->pos.x-1][h->pos.y][h->pos.z].visitableObjects) + BOOST_FOREACH(CGObjectInstance *obj, gs->map->getTile(int3(h->pos.x-1, h->pos.y, h->pos.z)).visitableObjects) { obj->onHeroLeave(h); } @@ -5637,7 +5637,7 @@ bool CGameHandler::tryAttackingGuard(const int3 &guardPos, const CGHeroInstance if(!gs->map->isInTheMap(guardPos)) return false; - const TerrainTile &guardTile = gs->map->terrain[guardPos.x][guardPos.y][guardPos.z]; + const TerrainTile &guardTile = gs->map->getTile(guardPos); objectVisited(guardTile.visitableObjects.back(), h); visitObjectAfterVictory = true; return true;