diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 6a6dfd299..92be1822a 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -774,8 +774,10 @@ void AIGateway::makeTurn() retrieveVisitableObjs(); } +#if NKAI_TRACE_LEVEL == 0 try { +#endif nullkiller->makeTurn(); //for debug purpose @@ -784,6 +786,7 @@ void AIGateway::makeTurn() if (h->movement) logAi->warn("Hero %s has %d MP left", h->name, h->movement); } +#if NKAI_TRACE_LEVEL == 0 } catch (boost::thread_interrupted & e) { @@ -795,6 +798,7 @@ void AIGateway::makeTurn() { logAi->debug("Making turn thread has caught an exception: %s", e.what()); } +#endif endTurn(); } diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 314b4174f..d719c03f4 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -334,7 +334,7 @@ BuildingInfo::BuildingInfo() buildCost = 0; buildCostWithPrerequisits = 0; prerequisitesCount = 0; - name = ""; + name.clear(); armyStrength = 0; } diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index c4e96bb54..366f5f076 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -57,8 +57,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const continue; } - if(ai->nullkiller->heroManager->getHeroRole(targetHero) == HeroRole::MAIN - && targetHero->getArmyStrength() >= 300) + if(ai->nullkiller->heroManager->getHeroRole(targetHero) == HeroRole::MAIN) { auto reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanGet( targetHero, diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 8b3e56f21..9c0dcd132 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -307,7 +307,7 @@ void Nullkiller::executeTask(Goals::TTask task) { logAi->trace("Task %s completed", task->toString()); } - catch(std::exception & e) + catch(cannotFulfillGoalException & e) { logAi->debug("Failed to realize subgoal of type %s, I will stop.", taskDescr); logAi->debug("The error message was: %s", e.what()); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index ee91b7b0c..916ff8240 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1086,7 +1086,7 @@ void AINodeStorage::calculateTownPortal( for(const CGTownInstance * targetTown : towns) { // TODO: allow to hide visiting hero in garrison - if(targetTown->visitingHero) + if(targetTown->visitingHero && maskMap.find(targetTown->visitingHero.get()) != maskMap.end()) { auto basicMask = maskMap.at(targetTown->visitingHero.get()); bool heroIsInChain = (actor->chainMask & basicMask) != 0; diff --git a/CCallback.cpp b/CCallback.cpp index b9af639f7..6545c9729 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -181,6 +181,12 @@ bool CCallback::assembleArtifacts (const CGHeroInstance * hero, ArtifactPosition return true; } +void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) +{ + BulkExchangeArtifacts bma(srcHero, dstHero, swap); + sendRequest(&bma); +} + bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) { if(town->tempOwner!=player) diff --git a/CCallback.h b/CCallback.h index 49e1a9970..d96d9dd83 100644 --- a/CCallback.h +++ b/CCallback.h @@ -94,6 +94,10 @@ public: virtual int bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany = 1) = 0; virtual int bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) = 0; virtual int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) = 0; + + + // Moves all artifacts from one hero to another + virtual void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) = 0; }; class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback @@ -151,6 +155,7 @@ public: bool dismissHero(const CGHeroInstance * hero) override; bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override; bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override; + void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) override; bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override; void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override; bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; diff --git a/README.md b/README.md index bfb351d87..a6c760796 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/github/vcmi/vcmi?branch=develop&svg=true)](https://ci.appveyor.com/project/vcmi/vcmi) [![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.0.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.0.0) # VCMI Project VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities. diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 97160bded..b879dddf6 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include +#include #include "CMusicHandler.h" #include "CGameInfo.h" @@ -410,15 +411,15 @@ void CMusicHandler::release() CAudioBase::release(); } -void CMusicHandler::playMusic(const std::string & musicURI, bool loop) +void CMusicHandler::playMusic(const std::string & musicURI, bool loop, bool fromStart) { if (current && current->isTrack(musicURI)) return; - queueNext(this, "", musicURI, loop); + queueNext(this, "", musicURI, loop, fromStart); } -void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop) +void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart) { auto selectedSet = musicsSet.find(whichSet); if (selectedSet == musicsSet.end()) @@ -431,10 +432,10 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop) return; // in this mode - play random track from set - queueNext(this, whichSet, "", loop); + queueNext(this, whichSet, "", loop, fromStart); } -void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop) +void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop, bool fromStart) { auto selectedSet = musicsSet.find(whichSet); if (selectedSet == musicsSet.end()) @@ -454,7 +455,7 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::st return; // in this mode - play specific track from set - queueNext(this, "", selectedEntry->second, loop); + queueNext(this, "", selectedEntry->second, loop, fromStart); } void CMusicHandler::queueNext(std::unique_ptr queued) @@ -473,11 +474,11 @@ void CMusicHandler::queueNext(std::unique_ptr queued) } } -void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped) +void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart) { try { - queueNext(make_unique(owner, setName, musicURI, looped)); + queueNext(make_unique(owner, setName, musicURI, looped, fromStart)); } catch(std::exception &e) { @@ -526,10 +527,13 @@ void CMusicHandler::musicFinishedCallback() } } -MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped): +MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart): owner(owner), music(nullptr), + startTime(uint32_t(-1)), + startPosition(0), loop(looped ? -1 : 1), + fromStart(fromStart), setName(std::move(setName)) { if (!musicURI.empty()) @@ -578,11 +582,25 @@ bool MusicEntry::play() } logGlobal->trace("Playing music file %s", currentName); - if(Mix_PlayMusic(music, 1) == -1) + + if ( !fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0) + { + float timeToStart = owner->trackPositions[currentName]; + startPosition = std::round(timeToStart * 1000); + + if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1) + { + logGlobal->error("Unable to play music (%s)", Mix_GetError()); + return false; + } + } + else if(Mix_PlayMusic(music, 1) == -1) { logGlobal->error("Unable to play music (%s)", Mix_GetError()); return false; } + + startTime = SDL_GetTicks(); return true; } @@ -590,8 +608,13 @@ bool MusicEntry::stop(int fade_ms) { if (Mix_PlayingMusic()) { - logGlobal->trace("Stopping music file %s", currentName); loop = 0; + uint32_t endTime = SDL_GetTicks(); + assert(startTime != uint32_t(-1)); + float playDuration = (endTime - startTime + startPosition) / 1000.f; + owner->trackPositions[currentName] = playDuration; + logGlobal->info("Stopping music file %s at %f", currentName, playDuration); + Mix_FadeOutMusic(fade_ms); return true; } diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index fa69d8a28..0c40566ab 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -99,6 +99,9 @@ class MusicEntry Mix_Music *music; int loop; // -1 = indefinite + bool fromStart; + uint32_t startTime; + uint32_t startPosition; //if not null - set from which music will be randomly selected std::string setName; std::string currentName; @@ -110,7 +113,7 @@ public: bool isSet(std::string setName); bool isTrack(std::string trackName); - MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped); + MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart); ~MusicEntry(); bool play(); @@ -128,10 +131,11 @@ private: std::unique_ptr current; std::unique_ptr next; - void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped); + void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart); void queueNext(std::unique_ptr queued); std::map> musicsSet; + std::map trackPositions; public: CMusicHandler(); @@ -145,11 +149,11 @@ public: void setVolume(ui32 percent) override; /// play track by URI, if loop = true music will be looped - void playMusic(const std::string & musicURI, bool loop); + void playMusic(const std::string & musicURI, bool loop, bool fromStart); /// play random track from this set - void playMusicFromSet(const std::string & musicSet, bool loop); + void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart); /// play specific track from set - void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop); + void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart); void stopMusic(int fade_ms=1000); void musicFinishedCallback(); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index af40aa43f..d7a37d8dd 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -276,7 +276,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) { updateAmbientSounds(); //We may need to change music - select new track, music handler will change it if needed - CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true); + CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true, false); if(details.result == TryMoveHero::TELEPORTATION) { @@ -2338,10 +2338,13 @@ void CPlayerInterface::acceptTurn() while(CInfoWindow *iw = dynamic_cast(GH.topInt().get())) iw->close(); } - waitWhileDialog(); if(CSH->howManyPlayerInterfaces() > 1) + { + waitWhileDialog(); // wait for player to accept turn in hot-seat mode + adventureInt->startTurn(); + } adventureInt->heroList.update(); adventureInt->townList.update(); @@ -2591,6 +2594,12 @@ void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const Artifact if (artWin) artWin->artifactMoved(src, dst); } + if(!GH.objsToBlit.empty()) + GH.objsToBlit.back()->redraw(); +} + +void CPlayerInterface::artifactPossibleAssembling(const ArtifactLocation & dst) +{ askToAssembleArtifact(dst); } diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index f7907594c..14a81895f 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -133,6 +133,7 @@ public: void artifactRemoved(const ArtifactLocation &al) override; void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst) override; void artifactAssembled(const ArtifactLocation &al) override; + void artifactPossibleAssembling(const ArtifactLocation & dst) override; void artifactDisassembled(const ArtifactLocation &al) override; void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override; diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index e6c6c3687..d1e8a3a41 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -519,7 +519,7 @@ void CServerHandler::sendMessage(const std::string & txt) const std::string connectedId, playerColorId; readed >> connectedId; readed >> playerColorId; - if(connectedId.length(), playerColorId.length()) // BUG https://bugs.vcmi.eu/view.php?id=3144 + if(connectedId.length() && playerColorId.length()) { ui8 connected = boost::lexical_cast(connectedId); auto color = PlayerColor(boost::lexical_cast(playerColorId)); diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index afe18c924..31c2cebf6 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -381,7 +381,7 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo void CVideoPlayer::close() { - fname = ""; + fname.clear(); if (sws) { sws_freeContext(sws); diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index d44434b18..f2d27ffdc 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -275,8 +275,31 @@ void EraseArtifact::applyCl(CClient *cl) void MoveArtifact::applyCl(CClient *cl) { callInterfaceIfPresent(cl, src.owningPlayer(), &IGameEventsReceiver::artifactMoved, src, dst); + callInterfaceIfPresent(cl, src.owningPlayer(), &IGameEventsReceiver::artifactPossibleAssembling, dst); if(src.owningPlayer() != dst.owningPlayer()) + { callInterfaceIfPresent(cl, dst.owningPlayer(), &IGameEventsReceiver::artifactMoved, src, dst); + callInterfaceIfPresent(cl, dst.owningPlayer(), &IGameEventsReceiver::artifactPossibleAssembling, dst); + } +} + +void BulkMoveArtifacts::applyCl(CClient * cl) +{ + auto applyMove = [this, cl](std::vector & artsPack) -> void + { + for(auto & slotToMove : artsPack) + { + auto srcLoc = ArtifactLocation(srcArtHolder, slotToMove.srcPos); + auto dstLoc = ArtifactLocation(dstArtHolder, slotToMove.dstPos); + callInterfaceIfPresent(cl, srcLoc.owningPlayer(), &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc); + if(srcLoc.owningPlayer() != dstLoc.owningPlayer()) + callInterfaceIfPresent(cl, dstLoc.owningPlayer(), &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc); + } + }; + + applyMove(artsPack0); + if(swap) + applyMove(artsPack1); } void AssembledArtifact::applyCl(CClient *cl) diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 6606e6dd8..b02d7153c 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -411,7 +411,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet { if(LOCPLINT->battleInt) { - CCS->musich->playMusicFromSet("battle", true); + CCS->musich->playMusicFromSet("battle", true, true); battleActionsStarted = true; blockUI(settings["session"]["spectate"].Bool()); battleIntroSoundChannel = -1; @@ -457,7 +457,7 @@ CBattleInterface::~CBattleInterface() if (adventureInt && adventureInt->selection) { const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType); - CCS->musich->playMusicFromSet("terrain", terrain.name, true); + CCS->musich->playMusicFromSet("terrain", terrain.name, true, false); } animsAreDisplayed.setn(false); } @@ -859,17 +859,17 @@ void CBattleInterface::reallySurrender() void CBattleInterface::bAutofightf() { - if (spellDestSelectMode) //we are casting a spell + if(spellDestSelectMode) //we are casting a spell return; //Stop auto-fight mode - if (curInt->isAutoFightOn) + if(curInt->isAutoFightOn) { assert(curInt->autofightingAI); curInt->isAutoFightOn = false; logGlobal->trace("Stopping the autofight..."); } - else + else if(!curInt->autofightingAI) { curInt->isAutoFightOn = true; blockUI(true); @@ -1619,7 +1619,9 @@ void CBattleInterface::activateStack() setActiveStack(stackToActivate); stackToActivate = nullptr; - const CStack *s = activeStack; + const CStack * s = activeStack; + if(!s) + return; queue->update(); redrawBackgroundWithHexes(activeStack); diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index f9902989f..7bf3d8f1d 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -506,7 +506,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa break; } - CCS->musich->playMusic("Music/Win Battle", false); + CCS->musich->playMusic("Music/Win Battle", false, true); CCS->videoh->open("WIN3.BIK"); std::string str = CGI->generaltexth->allTexts[text]; @@ -543,7 +543,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast(br.result)); break; } - CCS->musich->playMusic(musicName, false); + CCS->musich->playMusic(musicName, false, true); CCS->videoh->open(videoName); labels.push_back(std::make_shared(235, 235, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text])); diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index e611f790b..02879d9d8 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -115,7 +115,7 @@ void CMenuScreen::show(SDL_Surface * to) void CMenuScreen::activate() { - CCS->musich->playMusic("Music/MainMenu", true); + CCS->musich->playMusic("Music/MainMenu", true, true); if(!config["video"].isNull()) CCS->videoh->open(config["video"]["name"].String()); CIntObject::activate(); diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index 5f2e5d92b..f84e2038a 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -29,7 +29,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog updateShadow(); CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo)); - CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true); + CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true, true); // MPTODO: Custom campaign crashing on this? // voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo)); diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index d24a720eb..667dafd48 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -536,6 +536,8 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf for(auto & object : objects) { const CGObjectInstance * obj = object.obj; + if(!obj) + continue; const bool sameLevel = obj->pos.z == pos.z; diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index b0d351a2b..1a4be8d59 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1158,7 +1158,7 @@ void CInGameConsole::endEnteringText(bool printEnteredText) previouslyEntered.push_back(txt); //print(txt); } - enteredText = ""; + enteredText.clear(); if(GH.topInt() == adventureInt) { GH.statusbar->alignment = CENTER; diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 746b2fc0c..2800b579e 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -367,7 +367,7 @@ void CHeroArtPlace::setArtifact(const CArtifactInstance *art) if(!art) { image->disable(); - text = std::string(); + text.clear(); hoverText = CGI->generaltexth->allTexts[507]; return; } @@ -1034,7 +1034,7 @@ void CCommanderArtPlace::setArtifact(const CArtifactInstance * art) if (!art) { image->disable(); - text = std::string(); + text.clear(); return; } diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index b7854f127..96a0ec71c 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -577,7 +577,7 @@ void CTextInput::textInputed(const SDL_TextInputEvent & event) redraw(); cb(text); } - newText = ""; + newText.clear(); #ifdef VCMI_ANDROID notifyAndroidTextInputChanged(text); diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 538fc554d..92d9da39c 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -573,7 +573,7 @@ CAdvMapInt::CAdvMapInt(): strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode townList.onSelect = std::bind(&CAdvMapInt::selectionChanged,this); bg = BitmapHandler::loadBitmap(ADVOPT.mainGraphic); - if (ADVOPT.worldViewGraphic != "") + if(!ADVOPT.worldViewGraphic.empty()) { bgWorldView = BitmapHandler::loadBitmap(ADVOPT.worldViewGraphic); } @@ -1413,7 +1413,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView) auto pos = sel->visitablePos(); auto tile = LOCPLINT->cb->getTile(pos); if(tile) - CCS->musich->playMusicFromSet("terrain", tile->terType->name, true); + CCS->musich->playMusicFromSet("terrain", tile->terType->name, true, false); } if(centerView) centerOn(sel); @@ -1863,7 +1863,7 @@ void CAdvMapInt::aiTurnStarted() return; adjustActiveness(true); - CCS->musich->playMusicFromSet("enemy-turn", true); + CCS->musich->playMusicFromSet("enemy-turn", true, false); adventureInt->minimap.setAIRadar(true); adventureInt->infoBar.startEnemyTurn(LOCPLINT->cb->getCurrentPlayer()); adventureInt->infoBar.showAll(screen);//force refresh on inactive object diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 9cfd8f991..a656dad68 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1171,7 +1171,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst townlist->onSelect = std::bind(&CCastleInterface::townChange, this); recreateIcons(); - CCS->musich->playMusic(town->town->clientInfo.musicTheme, true); + CCS->musich->playMusic(town->town->clientInfo.musicTheme, true, false); } CCastleInterface::~CCastleInterface() diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index ad48699e2..b5dcd5e93 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -593,7 +593,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState) std::string dmgInfo; auto causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero); if(causedDmg == 0 || mySpell->id == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included - dmgInfo = ""; + dmgInfo.clear(); else { dmgInfo = CGI->generaltexth->allTexts[343]; diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index 05091a70d..63381e08e 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -192,7 +192,7 @@ void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState) aw->arts->artifactsOnAltar.erase(art); setID(-1); - subtitle = ""; + subtitle.clear(); aw->deal->block(!aw->arts->artifactsOnAltar.size()); } diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 4bce0f2d0..3989e7ae0 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -872,41 +872,6 @@ std::function CExchangeController::onMoveArmyToRight() return [&]() { moveArmy(true); }; } -void CExchangeController::swapArtifacts(ArtifactPosition slot) -{ - bool leftHasArt = !left->isPositionFree(slot); - bool rightHasArt = !right->isPositionFree(slot); - - if(!leftHasArt && !rightHasArt) - return; - - ArtifactLocation leftLocation = ArtifactLocation(left, slot); - ArtifactLocation rightLocation = ArtifactLocation(right, slot); - - if(leftHasArt && !left->artifactsWorn.at(slot).artifact->canBePutAt(rightLocation, true)) - return; - - if(rightHasArt && !right->artifactsWorn.at(slot).artifact->canBePutAt(leftLocation, true)) - return; - - if(leftHasArt) - { - if(rightHasArt) - { - auto art = right->getArt(slot); - - cb->swapArtifacts(leftLocation, rightLocation); - cb->swapArtifacts(ArtifactLocation(right, right->getArtPos(art)), leftLocation); - } - else - cb->swapArtifacts(leftLocation, rightLocation); - } - else - { - cb->swapArtifacts(rightLocation, leftLocation); - } -} - std::vector getBackpackArts(const CGHeroInstance * hero) { std::vector result; @@ -919,92 +884,13 @@ std::vector getBackpackArts(const CGHeroInstance * hero) return result; } -const std::vector unmovablePositions = {ArtifactPosition::SPELLBOOK, ArtifactPosition::MACH4}; - -bool isArtRemovable(const std::pair & slot) -{ - return slot.second.artifact - && !slot.second.locked - && !vstd::contains(unmovablePositions, slot.first); -} - -// Puts all composite arts to backpack and returns their previous location -std::vector CExchangeController::moveCompositeArtsToBackpack() -{ - std::vector sides = {left, right}; - std::vector artPositions; - - for(auto hero : sides) - { - for(int i = ArtifactPosition::HEAD; i < ArtifactPosition::AFTER_LAST; i++) - { - auto artPosition = ArtifactPosition(i); - auto art = hero->getArt(artPosition); - - if(art && art->canBeDisassembled()) - { - cb->swapArtifacts( - ArtifactLocation(hero, artPosition), - ArtifactLocation(hero, ArtifactPosition(GameConstants::BACKPACK_START))); - - artPositions.push_back(HeroArtifact(hero, art, artPosition)); - } - } - } - - return artPositions; -} - -void CExchangeController::swapArtifacts() -{ - for(int i = ArtifactPosition::HEAD; i < ArtifactPosition::AFTER_LAST; i++) - { - if(vstd::contains(unmovablePositions, i)) - continue; - - swapArtifacts(ArtifactPosition(i)); - } - - auto leftHeroBackpack = getBackpackArts(left); - auto rightHeroBackpack = getBackpackArts(right); - - for(auto leftArt : leftHeroBackpack) - { - cb->swapArtifacts( - ArtifactLocation(left, left->getArtPos(leftArt)), - ArtifactLocation(right, ArtifactPosition(GameConstants::BACKPACK_START))); - } - - for(auto rightArt : rightHeroBackpack) - { - cb->swapArtifacts( - ArtifactLocation(right, right->getArtPos(rightArt)), - ArtifactLocation(left, ArtifactPosition(GameConstants::BACKPACK_START))); - } -} - std::function CExchangeController::onSwapArtifacts() { return [&]() { GsThread::run([=] { - // it is not possible directly exchange composite artifacts like Angelic Alliance and Armor of Damned - auto compositeArtLocations = moveCompositeArtsToBackpack(); - - swapArtifacts(); - - for(HeroArtifact artLocation : compositeArtLocations) - { - auto target = artLocation.hero == left ? right : left; - auto currentPos = target->getArtPos(artLocation.artifact); - - cb->swapArtifacts( - ArtifactLocation(target, currentPos), - ArtifactLocation(target, artLocation.artPosition)); - } - - view->redraw(); + cb->bulkMoveArtifacts(left->id, right->id, true); }); }; } @@ -1160,20 +1046,8 @@ void CExchangeController::moveArtifacts(bool leftToRight) } GsThread::run([=] - { - while(vstd::contains_if(source->artifactsWorn, isArtRemovable)) - { - auto art = std::find_if(source->artifactsWorn.begin(), source->artifactsWorn.end(), isArtRemovable); - - moveArtifact(source, target, art->first); - } - - while(!source->artifactsInBackpack.empty()) - { - moveArtifact(source, target, source->getArtPos(source->artifactsInBackpack.begin()->artifact)); - } - - view->redraw(); + { + cb->bulkMoveArtifacts(source->id, target->id, false); }); } @@ -1182,26 +1056,11 @@ void CExchangeController::moveArtifact( const CGHeroInstance * target, ArtifactPosition srcPosition) { - auto artifact = source->getArt(srcPosition); auto srcLocation = ArtifactLocation(source, srcPosition); + auto dstLocation = ArtifactLocation(target, + ArtifactUtils::getArtifactDstPosition(source->getArt(srcPosition), target, target->bearerType())); - for(auto slot : artifact->artType->possibleSlots.at(target->bearerType())) - { - auto existingArtifact = target->getArt(slot); - auto existingArtInfo = target->getSlot(slot); - ArtifactLocation destLocation(target, slot); - - if(!existingArtifact - && (!existingArtInfo || !existingArtInfo->locked) - && artifact->canBePutAt(destLocation)) - { - cb->swapArtifacts(srcLocation, ArtifactLocation(target, slot)); - - return; - } - } - - cb->swapArtifacts(srcLocation, ArtifactLocation(target, ArtifactPosition(GameConstants::BACKPACK_START))); + cb->swapArtifacts(srcLocation, dstLocation); } CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID) @@ -1700,9 +1559,7 @@ int CUniversityWindow::CItem::state() { if(parent->hero->getSecSkillLevel(SecondarySkill(ID)))//hero know this skill return 1; - if(!parent->hero->canLearnSkill())//can't learn more skills - return 0; - if(parent->hero->type->heroClass->secSkillProbability[ID]==0)//can't learn this skill (like necromancy for most of non-necros) + if(!parent->hero->canLearnSkill(SecondarySkill(ID)))//can't learn more skills return 0; return 2; } diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index a56f87d35..56097d0b4 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -324,9 +324,6 @@ private: void moveArtifacts(bool leftToRight); void moveArtifact(const CGHeroInstance * source, const CGHeroInstance * target, ArtifactPosition srcPosition); void moveStack(const CGHeroInstance * source, const CGHeroInstance * target, SlotID sourceSlot); - void swapArtifacts(ArtifactPosition artPosition); - std::vector moveCompositeArtsToBackpack(); - void swapArtifacts(); }; class CExchangeWindow : public CStatusbarWindow, public CGarrisonHolder, public CWindowWithArtifacts diff --git a/config/obstacles.json b/config/obstacles.json index e5105febf..a5b94e3ea 100644 --- a/config/obstacles.json +++ b/config/obstacles.json @@ -3,7 +3,7 @@ // * "absoluteObstacles" are a little special: there can be only one such obstacle in the battlefield and its position is always the same. // // Their properties: -// * "allowedTerrain" vector of terrain types (TT format) where obstacle is appropriate +// * "allowedTerrains" vector of terrain types (TT format) where obstacle is appropriate // * "specialBattlefields" vector of battlefield images (BI format) where obstacle is appropriate. If there is a special battlefield image, then only this list is checked. Otherwise it's ignored. // * "blockedTiles": for absolute obstacles contains absolute coordinates. For usual obstacles contains offsets relative to the obstacle position (that is bottom left corner). If obstacle is placed in an odd row (counting from 0) and the blocked tile is in an even row, position will be shifted one tile to the left. Thanks to that ie. -16 is always top-right hex, no matter where the obstale will get placed. // * "width" for usual obstacles it's count of tiles that must be free to the right for obstacle to be placed. For absolute obstacles, it's x offset for the graphics. @@ -13,7 +13,7 @@ { "0": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -24,7 +24,7 @@ }, "1": { - "allowedTerrain" : ["dirt", "sand", "rough", "subterra"], + "allowedTerrains" : ["dirt", "sand", "rough", "subterra"], "specialBattlefields" : ["sand_shore"], "width" : 3, "height" : 2, @@ -35,7 +35,7 @@ }, "2": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -46,7 +46,7 @@ }, "3": { - "allowedTerrain" : ["dirt", "rough"], + "allowedTerrains" : ["dirt", "rough"], "specialBattlefields" : ["cursed_ground"], "width" : 2, "height" : 1, @@ -57,7 +57,7 @@ }, "4": { - "allowedTerrain" : ["dirt", "rough", "subterra"], + "allowedTerrains" : ["dirt", "rough", "subterra"], "specialBattlefields" : ["sand_shore", "cursed_ground"], "width" : 2, "height" : 1, @@ -68,7 +68,7 @@ }, "5": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -79,7 +79,7 @@ }, "6": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -90,7 +90,7 @@ }, "7": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -101,7 +101,7 @@ }, "8": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -112,7 +112,7 @@ }, "9": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -123,7 +123,7 @@ }, "10": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -134,7 +134,7 @@ }, "11": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -145,7 +145,7 @@ }, "12": { - "allowedTerrain" : ["dirt", "rough"], + "allowedTerrains" : ["dirt", "rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, @@ -156,7 +156,7 @@ }, "13": { - "allowedTerrain" : ["dirt", "rough"], + "allowedTerrains" : ["dirt", "rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 2, @@ -167,7 +167,7 @@ }, "14": { - "allowedTerrain" : ["dirt", "rough"], + "allowedTerrains" : ["dirt", "rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 2, @@ -178,7 +178,7 @@ }, "15": { - "allowedTerrain" : ["dirt", "rough"], + "allowedTerrains" : ["dirt", "rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, @@ -189,7 +189,7 @@ }, "16": { - "allowedTerrain" : ["sand"], + "allowedTerrains" : ["sand"], "specialBattlefields" : [], "width" : 4, "height" : 4, @@ -200,7 +200,7 @@ }, "17": { - "allowedTerrain" : ["sand"], + "allowedTerrains" : ["sand"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -211,7 +211,7 @@ }, "18": { - "allowedTerrain" : ["sand"], + "allowedTerrains" : ["sand"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -222,7 +222,7 @@ }, "19": { - "allowedTerrain" : ["grass", "swamp"], + "allowedTerrains" : ["grass", "swamp"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -233,7 +233,7 @@ }, "20": { - "allowedTerrain" : ["grass", "swamp"], + "allowedTerrains" : ["grass", "swamp"], "specialBattlefields" : ["magic_plains"], "width" : 2, "height" : 2, @@ -244,7 +244,7 @@ }, "21": { - "allowedTerrain" : ["grass", "swamp"], + "allowedTerrains" : ["grass", "swamp"], "specialBattlefields" : [], "width" : 1, "height" : 1, @@ -255,7 +255,7 @@ }, "22": { - "allowedTerrain" : ["grass"], + "allowedTerrains" : ["grass"], "specialBattlefields" : ["magic_plains"], "width" : 6, "height" : 2, @@ -266,7 +266,7 @@ }, "23": { - "allowedTerrain" : ["grass"], + "allowedTerrains" : ["grass"], "specialBattlefields" : [], "width" : 7, "height" : 1, @@ -277,7 +277,7 @@ }, "24": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -288,7 +288,7 @@ }, "25": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 5, "height" : 1, @@ -299,7 +299,7 @@ }, "26": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 3, @@ -310,7 +310,7 @@ }, "27": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -321,7 +321,7 @@ }, "28": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -332,7 +332,7 @@ }, "29": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -343,7 +343,7 @@ }, "30": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -354,7 +354,7 @@ }, "31": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -365,7 +365,7 @@ }, "32": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 7, "height" : 2, @@ -376,7 +376,7 @@ }, "33": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 5, "height" : 5, @@ -387,7 +387,7 @@ }, "34": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -398,7 +398,7 @@ }, "35": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 8, "height" : 3, @@ -409,7 +409,7 @@ }, "36": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -420,7 +420,7 @@ }, "37": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -431,7 +431,7 @@ }, "38": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 5, "height" : 4, @@ -442,7 +442,7 @@ }, "39": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 4, "height" : 3, @@ -453,7 +453,7 @@ }, "40": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 2, "height" : 2, @@ -464,7 +464,7 @@ }, "41": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 4, "height" : 3, @@ -475,7 +475,7 @@ }, "42": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 2, @@ -486,7 +486,7 @@ }, "43": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, @@ -497,7 +497,7 @@ }, "44": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, @@ -508,7 +508,7 @@ }, "45": { - "allowedTerrain" : ["subterra"], + "allowedTerrains" : ["subterra"], "specialBattlefields" : [], "width" : 3, "height" : 3, @@ -519,7 +519,7 @@ }, "46": { - "allowedTerrain" : ["subterra"], + "allowedTerrains" : ["subterra"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -530,7 +530,7 @@ }, "47": { - "allowedTerrain" : ["subterra"], + "allowedTerrains" : ["subterra"], "specialBattlefields" : [], "width" : 4, "height" : 3, @@ -541,7 +541,7 @@ }, "48": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 4, "height" : 3, @@ -552,7 +552,7 @@ }, "49": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -563,7 +563,7 @@ }, "50": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 5, "height" : 3, @@ -574,7 +574,7 @@ }, "51": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -585,7 +585,7 @@ }, "52": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 4, "height" : 4, @@ -596,7 +596,7 @@ }, "53": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 5, "height" : 3, @@ -607,7 +607,7 @@ }, "54": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 5, "height" : 3, @@ -618,7 +618,7 @@ }, "55": { - "allowedTerrain" : ["water"], + "allowedTerrains" : ["water"], "specialBattlefields" : [], "width" : 3, "height" : 3, @@ -629,7 +629,7 @@ }, "56": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 3, "height" : 2, @@ -640,7 +640,7 @@ }, "57": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 3, "height" : 2, @@ -651,7 +651,7 @@ }, "58": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 5, "height" : 2, @@ -662,7 +662,7 @@ }, "59": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 4, "height" : 2, @@ -673,7 +673,7 @@ }, "60": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 2, "height" : 2, @@ -684,7 +684,7 @@ }, "61": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["holy_ground"], "width" : 1, "height" : 1, @@ -695,7 +695,7 @@ }, "62": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["holy_ground"], "width" : 2, "height" : 1, @@ -706,7 +706,7 @@ }, "63": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["holy_ground"], "width" : 3, "height" : 3, @@ -717,7 +717,7 @@ }, "64": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["holy_ground"], "width" : 3, "height" : 2, @@ -728,7 +728,7 @@ }, "65": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["holy_ground"], "width" : 4, "height" : 3, @@ -739,7 +739,7 @@ }, "66": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["evil_fog"], "width" : 1, "height" : 1, @@ -750,7 +750,7 @@ }, "67": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["evil_fog"], "width" : 2, "height" : 1, @@ -761,7 +761,7 @@ }, "68": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["evil_fog"], "width" : 3, "height" : 2, @@ -772,7 +772,7 @@ }, "69": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["evil_fog"], "width" : 4, "height" : 2, @@ -783,7 +783,7 @@ }, "70": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["evil_fog"], "width" : 6, "height" : 2, @@ -794,7 +794,7 @@ }, "71": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["clover_field"], "width" : 1, "height" : 1, @@ -805,7 +805,7 @@ }, "72": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["clover_field"], "width" : 3, "height" : 1, @@ -816,7 +816,7 @@ }, "73": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["clover_field"], "width" : 3, "height" : 2, @@ -827,7 +827,7 @@ }, "74": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["clover_field"], "width" : 4, "height" : 2, @@ -838,7 +838,7 @@ }, "75": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["lucid_pools"], "width" : 1, "height" : 1, @@ -849,7 +849,7 @@ }, "76": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["lucid_pools"], "width" : 2, "height" : 1, @@ -860,7 +860,7 @@ }, "77": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["lucid_pools"], "width" : 3, "height" : 2, @@ -871,7 +871,7 @@ }, "78": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["lucid_pools"], "width" : 5, "height" : 2, @@ -882,7 +882,7 @@ }, "79": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["fiery_fields"], "width" : 1, "height" : 1, @@ -893,7 +893,7 @@ }, "80": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["fiery_fields"], "width" : 2, "height" : 1, @@ -904,7 +904,7 @@ }, "81": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["fiery_fields"], "width" : 3, "height" : 2, @@ -915,7 +915,7 @@ }, "82": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["fiery_fields"], "width" : 4, "height" : 2, @@ -926,7 +926,7 @@ }, "83": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["fiery_fields"], "width" : 3, "height" : 3, @@ -937,7 +937,7 @@ }, "84": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["rocklands"], "width" : 1, "height" : 1, @@ -948,7 +948,7 @@ }, "85": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["rocklands"], "width" : 2, "height" : 1, @@ -959,7 +959,7 @@ }, "86": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["rocklands"], "width" : 3, "height" : 1, @@ -970,7 +970,7 @@ }, "87": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["rocklands"], "width" : 4, "height" : 2, @@ -981,7 +981,7 @@ }, "88": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["magic_clouds"], "width" : 1, "height" : 1, @@ -992,7 +992,7 @@ }, "89": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["magic_clouds"], "width" : 2, "height" : 2, @@ -1003,7 +1003,7 @@ }, "90": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["magic_clouds"], "width" : 4, "height" : 2, @@ -1015,7 +1015,7 @@ "100": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 124, "height" : 254, @@ -1025,7 +1025,7 @@ }, "101": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 256, "height" : 254, @@ -1035,7 +1035,7 @@ }, "102": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 168, "height" : 212, @@ -1045,7 +1045,7 @@ }, "103": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 124, "height" : 254, @@ -1055,7 +1055,7 @@ }, "104": { - "allowedTerrain" : ["dirt"], + "allowedTerrains" : ["dirt"], "specialBattlefields" : [], "width" : 146, "height" : 254, @@ -1065,7 +1065,7 @@ }, "105": { - "allowedTerrain" : ["grass"], + "allowedTerrains" : ["grass"], "specialBattlefields" : [], "width" : 173, "height" : 221, @@ -1075,7 +1075,7 @@ }, "106": { - "allowedTerrain" : ["grass"], + "allowedTerrains" : ["grass"], "specialBattlefields" : [], "width" : 180, "height" : 264, @@ -1085,7 +1085,7 @@ }, "107": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 166, "height" : 255, @@ -1095,7 +1095,7 @@ }, "108": { - "allowedTerrain" : ["snow"], + "allowedTerrains" : ["snow"], "specialBattlefields" : [], "width" : 302, "height" : 172, @@ -1105,7 +1105,7 @@ }, "109": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 300, "height" : 170, @@ -1115,7 +1115,7 @@ }, "110": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 278, "height" : 171, @@ -1125,7 +1125,7 @@ }, "111": { - "allowedTerrain" : ["swamp"], + "allowedTerrains" : ["swamp"], "specialBattlefields" : [], "width" : 256, "height" : 254, @@ -1135,7 +1135,7 @@ }, "112": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 124, "height" : 254, @@ -1145,7 +1145,7 @@ }, "113": { - "allowedTerrain" : ["lava"], + "allowedTerrains" : ["lava"], "specialBattlefields" : [], "width" : 256, "height" : 128, @@ -1155,7 +1155,7 @@ }, "114": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 186, "height" : 212, @@ -1165,7 +1165,7 @@ }, "115": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 347, "height" : 174, @@ -1175,7 +1175,7 @@ }, "116": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 294, "height" : 169, @@ -1185,7 +1185,7 @@ }, "117": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 165, "height" : 257, @@ -1195,7 +1195,7 @@ }, "118": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 208, "height" : 268, @@ -1205,7 +1205,7 @@ }, "119": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 252, "height" : 254, @@ -1215,7 +1215,7 @@ }, "120": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 278, "height" : 128, @@ -1225,7 +1225,7 @@ }, "121": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 208, "height" : 268, @@ -1235,7 +1235,7 @@ }, "122": { - "allowedTerrain" : ["rough"], + "allowedTerrains" : ["rough"], "specialBattlefields" : ["cursed_ground"], "width" : 168, "height" : 212, @@ -1245,7 +1245,7 @@ }, "123": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 147, "height" : 264, @@ -1255,7 +1255,7 @@ }, "124": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 178, "height" : 262, @@ -1265,7 +1265,7 @@ }, "125": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 173, "height" : 257, @@ -1275,7 +1275,7 @@ }, "126": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 241, "height" : 272, @@ -1285,7 +1285,7 @@ }, "127": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 261, "height" : 129, @@ -1295,7 +1295,7 @@ }, "128": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["sand_shore"], "width" : 180, "height" : 154, @@ -1305,7 +1305,7 @@ }, "129": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["clover_field"], "width" : 304, "height" : 264, @@ -1315,7 +1315,7 @@ }, "130": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["lucid_pools"], "width" : 256, "height" : 257, @@ -1325,7 +1325,7 @@ }, "131": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["fiery_fields"], "width" : 257, "height" : 255, @@ -1335,7 +1335,7 @@ }, "132": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["rocklands"], "width" : 277, "height" : 218, @@ -1345,7 +1345,7 @@ }, "133": { - "allowedTerrain" : [], + "allowedTerrains" : [], "specialBattlefields" : ["magic_clouds"], "width" : 300, "height" : 214, diff --git a/config/schemas/obstacle.json b/config/schemas/obstacle.json index 18cc76c10..08e7a2e8c 100644 --- a/config/schemas/obstacle.json +++ b/config/schemas/obstacle.json @@ -15,7 +15,7 @@ "specialBattlefields": { "type": "array", "description": "Obstacles can be placed on specified specified battlefields", - "items": { "$ref" : "battlefield.json" } + "items": { "type" : "string" } }, "width": { "type": "number", diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index ee57823f9..0f62c029d 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -352,7 +352,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode } const JsonNode & warMachine = node["warMachine"]; - if(warMachine.getType() == JsonNode::JsonType::DATA_STRING && warMachine.String() != "") + if(warMachine.getType() == JsonNode::JsonType::DATA_STRING && !warMachine.String().empty()) { VLC->modh->identifiers.requestIdentifier("creature", warMachine, [=](si32 id) { @@ -729,7 +729,7 @@ CArtifactInstance::CArtifactInstance( CArtifact *Art) void CArtifactInstance::setType( CArtifact *Art ) { artType = Art; - attachTo(Art); + attachTo(*Art); } std::string CArtifactInstance::nodeName() const @@ -852,7 +852,7 @@ void CArtifactInstance::putAt(ArtifactLocation al) al.getHolderArtSet()->setNewArtSlot(al.slot, this, false); if(al.slot < GameConstants::BACKPACK_START) - al.getHolderNode()->attachTo(this); + al.getHolderNode()->attachTo(*this); } void CArtifactInstance::removeFrom(ArtifactLocation al) @@ -860,7 +860,7 @@ void CArtifactInstance::removeFrom(ArtifactLocation al) assert(al.getHolderArtSet()->getArt(al.slot) == this); al.getHolderArtSet()->eraseArtSlot(al.slot); if(al.slot < GameConstants::BACKPACK_START) - al.getHolderNode()->detachFrom(this); + al.getHolderNode()->detachFrom(*this); //TODO delete me? } @@ -1054,7 +1054,7 @@ void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, Artifac assert(vstd::contains(*artType->constituents, art->artType.get())); assert(art->getParentNodes().size() == 1 && art->getParentNodes().front() == art->artType); constituentsInfo.push_back(ConstituentInfo(art, slot)); - attachTo(art); + attachTo(*art); } void CCombinedArtifactInstance::putAt(ArtifactLocation al) @@ -1143,7 +1143,7 @@ CArtifactInstance * CCombinedArtifactInstance::figureMainConstituent(const Artif void CCombinedArtifactInstance::deserializationFix() { for(ConstituentInfo &ci : constituentsInfo) - attachTo(ci.art); + attachTo(*ci.art); } bool CCombinedArtifactInstance::isPart(const CArtifactInstance *supposedPart) const @@ -1350,7 +1350,7 @@ void CArtifactSet::artDeserializationFix(CBonusSystemNode *node) { for(auto & elem : artifactsWorn) if(elem.second.artifact && !elem.second.locked) - node->attachTo(elem.second.artifact); + node->attachTo(*elem.second.artifact); } void CArtifactSet::serializeJsonArtifacts(JsonSerializeFormat & handler, const std::string & fieldName, CMap * map) @@ -1457,4 +1457,83 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa } } +CArtifactFittingSet::CArtifactFittingSet(ArtBearer::ArtBearer Bearer) +{ + this->Bearer = Bearer; +} + +void CArtifactFittingSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance * art, bool locked) +{ + ArtSlotInfo & asi = retrieveNewArtSlot(slot); + asi.artifact = art; + asi.locked = locked; +} + +void CArtifactFittingSet::putArtifact(ArtifactPosition pos, CArtifactInstance * art) +{ + if(art && art->canBeDisassembled() && (pos < ArtifactPosition::AFTER_LAST)) + { + for(auto & part : dynamic_cast(art)->constituentsInfo) + { + // For the ArtFittingSet is no needed to do figureMainConstituent, just lock slots + this->setNewArtSlot(part.art->firstAvailableSlot(this), part.art, true); + } + } + else + { + this->setNewArtSlot(pos, art, false); + } +} + +ArtBearer::ArtBearer CArtifactFittingSet::bearerType() const +{ + return this->Bearer; +} + +DLL_LINKAGE ArtifactPosition ArtifactUtils::getArtifactDstPosition( const CArtifactInstance * artifact, + const CArtifactSet * target, + ArtBearer::ArtBearer bearer) +{ + for(auto slot : artifact->artType->possibleSlots.at(bearer)) + { + auto existingArtifact = target->getArt(slot); + auto existingArtInfo = target->getSlot(slot); + + if(!existingArtifact + && (!existingArtInfo || !existingArtInfo->locked) + && artifact->canBePutAt(target, slot)) + { + return slot; + } + } + return ArtifactPosition(GameConstants::BACKPACK_START); +} + +DLL_LINKAGE std::vector ArtifactUtils::unmovablePositions() +{ + return { ArtifactPosition::SPELLBOOK, ArtifactPosition::MACH4 }; +} + +DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair & slot) +{ + return slot.second.artifact + && !slot.second.locked + && !vstd::contains(unmovablePositions(), slot.first); +} + +DLL_LINKAGE bool ArtifactUtils::checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot) +{ + // TODO what'll happen if Titan's thunder is equipped by pickin git up or the start of game? + // Titan's Thunder creates new spellbook on equip + if(artID == ArtifactID::TITANS_THUNDER && slot == ArtifactPosition::RIGHT_HAND) + { + if(heroPtr) + { + if(heroPtr && !heroPtr->hasSpellbook()) + return true; + } + } + return false; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index a933b70a3..46f14d490 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -303,8 +303,9 @@ struct DLL_LINKAGE ArtSlotInfo ui8 locked; //if locked, then artifact points to the combined artifact ArtSlotInfo() : locked(false) {} + const CArtifactInstance * getArt() const; - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { h & artifact; h & locked; @@ -363,4 +364,28 @@ private: void serializeJsonSlot(JsonSerializeFormat & handler, const ArtifactPosition & slot, CMap * map);//normal slots }; +// Used to try on artifacts before the claimed changes have been applied +class DLL_LINKAGE CArtifactFittingSet : public CArtifactSet +{ +public: + CArtifactFittingSet(ArtBearer::ArtBearer Bearer); + void setNewArtSlot(ArtifactPosition slot, CArtifactInstance * art, bool locked); + void putArtifact(ArtifactPosition pos, CArtifactInstance * art) override; + ArtBearer::ArtBearer bearerType() const override; + +protected: + ArtBearer::ArtBearer Bearer; +}; + +namespace ArtifactUtils +{ + // Calculates where an artifact gets placed when it gets transferred from one hero to another. + DLL_LINKAGE ArtifactPosition getArtifactDstPosition( const CArtifactInstance * artifact, + const CArtifactSet * target, + ArtBearer::ArtBearer bearer); + DLL_LINKAGE std::vector unmovablePositions(); // TODO: Make this constexpr when the toolset is upgraded + DLL_LINKAGE bool isArtRemovable(const std::pair & slot); + DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 00342ec9f..7edd984b4 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -94,7 +94,9 @@ std::string MacroString::build(const GetValue & getValue) const CBonusType::CBonusType() { hidden = true; - icon = nameTemplate = descriptionTemplate = ""; + icon.clear(); + nameTemplate.clear(); + descriptionTemplate.clear(); } CBonusType::~CBonusType() diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index dd2587066..bf1f43bc7 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1345,12 +1345,12 @@ void CCreatureHandler::buildBonusTreeForTiers() for(CCreature * c : objects) { if(vstd::isbetween(c->level, 0, ARRAY_COUNT(creaturesOfLevel))) - c->attachTo(&creaturesOfLevel[c->level]); + c->attachTo(creaturesOfLevel[c->level]); else - c->attachTo(&creaturesOfLevel[0]); + c->attachTo(creaturesOfLevel[0]); } for(CBonusSystemNode &b : creaturesOfLevel) - b.attachTo(&allCreatures); + b.attachTo(allCreatures); } void CCreatureHandler::afterLoadFinalization() diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 412316449..d75b0f7f3 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -773,7 +773,7 @@ void CStackInstance::setType(const CCreature *c) { if(type) { - detachFrom(const_cast(type)); + detachFrom(const_cast(*type)); if (type->isMyUpgrade(c) && VLC->modh->modules.STACK_EXP) experience = static_cast(experience * VLC->creh->expAfterUpgrade / 100.0); } @@ -781,7 +781,7 @@ void CStackInstance::setType(const CCreature *c) CStackBasicDescriptor::setType(c); if(type) - attachTo(const_cast(type)); + attachTo(const_cast(*type)); } std::string CStackInstance::bonusToString(const std::shared_ptr& bonus, bool description) const { @@ -804,12 +804,12 @@ std::string CStackInstance::bonusToGraphics(const std::shared_ptr& bonus) void CStackInstance::setArmyObj(const CArmedInstance * ArmyObj) { if(_armyObj) - detachFrom(const_cast(_armyObj)); + detachFrom(const_cast(*_armyObj)); _armyObj = ArmyObj; if(ArmyObj) - attachTo(const_cast(_armyObj)); + attachTo(const_cast(*_armyObj)); } std::string CStackInstance::getQuantityTXT(bool capitalized) const @@ -1052,7 +1052,7 @@ void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler) { std::string typeName(""); handler.serializeString("type", typeName); - if(typeName != "") + if(!typeName.empty()) setType(VLC->creh->getCreature("core", typeName)); } } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index d2482bb99..eed1adc6d 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -532,7 +532,7 @@ std::pair CGameState::pickObject (CGObjectInstance *obj) if (auto info = dynamic_cast(dwl->info)) { faction = getRandomGenerator().nextInt((int)VLC->townh->size() - 1); - if(info->asCastle && info->instanceId != "") + if(info->asCastle && !info->instanceId.empty()) { auto iter = map->instanceNames.find(info->instanceId); @@ -2724,13 +2724,13 @@ void CGameState::buildGlobalTeamPlayerTree() for(auto k=teams.begin(); k!=teams.end(); ++k) { TeamState *t = &k->second; - t->attachTo(&globalEffects); + t->attachTo(globalEffects); for(PlayerColor teamMember : k->second.players) { PlayerState *p = getPlayerState(teamMember); assert(p); - p->attachTo(t); + p->attachTo(*t); } } } @@ -2740,7 +2740,9 @@ void CGameState::attachArmedObjects() for(CGObjectInstance *obj : map->objects) { if(CArmedInstance *armed = dynamic_cast(obj)) - armed->whatShouldBeAttached()->attachTo(armed->whereShouldBeAttached(this)); + { + armed->whatShouldBeAttached().attachTo(armed->whereShouldBeAttached(this)); + } } } diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 3d112f0ed..fdf5229ab 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -226,7 +226,7 @@ std::vector CIdentifierStorage::getPossibleIdent //for map format support core mod has access to any mod //TODO: better solution for access from map? - if(request.localScope == "core" || request.localScope == "") + if(request.localScope == "core" || request.localScope.empty()) { allowedScopes.insert(request.remoteScope); } @@ -1116,13 +1116,13 @@ void CModHandler::parseIdentifier(const std::string & fullIdentifier, std::strin else { type = p.second; - identifier = ""; + identifier.clear(); } } std::string CModHandler::makeFullIdentifier(const std::string & scope, const std::string & type, const std::string & identifier) { - if(type == "") + if(type.empty()) logGlobal->error("Full identifier (%s %s) requires type name", scope, identifier); std::string actualScope = scope; @@ -1137,13 +1137,13 @@ std::string CModHandler::makeFullIdentifier(const std::string & scope, const std actualName = scopeAndName.second; } - if(actualScope == "") + if(actualScope.empty()) { - return actualName == "" ? type : type + "." + actualName; + return actualName.empty() ? type : type + "." + actualName; } else { - return actualName == "" ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName; + return actualName.empty() ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName; } } diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index bc2fca93d..0d131f59e 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -339,7 +339,7 @@ void CPathfinder::calculatePaths() auto hlp = config->getOrCreatePathfinderHelper(source, gs); if(hlp->isHeroPatrolLocked()) - break; + continue; pq.push(initialNode); } @@ -1252,7 +1252,7 @@ int CPathfinderHelper::getMovementCost( if(src.x != dst.x && src.y != dst.y) //it's diagonal move { int old = ret; - ret = static_cast < int>(ret * 1.414213); + ret = static_cast(ret * M_SQRT2); //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points if(ret > remainingMovePoints && remainingMovePoints >= old) { diff --git a/lib/CStack.cpp b/lib/CStack.cpp index d71474d98..fb28549b8 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -82,13 +82,14 @@ void CStack::localInit(BattleInfo * battleInfo) exportBonuses(); if(base) //stack originating from "real" stack in garrison -> attach to it { - attachTo(const_cast(base)); + attachTo(const_cast(*base)); } else //attach directly to obj to which stack belongs and creature type { CArmedInstance * army = battle->battleGetArmyObject(side); - attachTo(army); - attachTo(const_cast(type)); + assert(army); + attachTo(*army); + attachTo(const_cast(*type)); } nativeTerrain = type->getNativeTerrain(); //save nativeTerrain in the variable on the battle start to avoid dead lock CUnitState::localInit(this); //it causes execution of the CStack::isOnNativeTerrain where nativeTerrain will be considered diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index c50d097d4..ced8fdf0b 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -822,11 +822,11 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source) info.buildingsIcons = source["buildingsIcons"].String(); //left for back compatibility - will be removed later - if (source["guildBackground"].String() != "") + if(!source["guildBackground"].String().empty()) info.guildBackground = source["guildBackground"].String(); else info.guildBackground = "TPMAGE.bmp"; - if (source["tavernVideo"].String() != "") + if(!source["tavernVideo"].String().empty()) info.tavernVideo = source["tavernVideo"].String(); else info.tavernVideo = "TAVERN.BIK"; @@ -963,7 +963,6 @@ TerrainId CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment ali CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode & source, const std::string & identifier, size_t index) { auto faction = new CFaction(); - faction->index = index; faction->index = static_cast(index); faction->name = source["name"].String(); diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index b43346658..34e26ecc4 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1016,7 +1016,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co // If a bonus system request comes with a caching string then look up in the map if there are any // pre-calculated bonus results. Limiters can't be cached so they have to be calculated. - if (cachingStr != "") + if(!cachingStr.empty()) { auto it = cachedRequests.find(cachingStr); if(it != cachedRequests.end()) @@ -1032,7 +1032,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co cachedBonuses.getBonuses(*ret, selector, limit); // Save the results in the cache - if(cachingStr != "") + if(!cachingStr.empty()) cachedRequests[cachingStr] = ret; return ret; @@ -1150,53 +1150,53 @@ CBonusSystemNode::~CBonusSystemNode() if(children.size()) { while(children.size()) - children.front()->detachFrom(this); + children.front()->detachFrom(*this); } } -void CBonusSystemNode::attachTo(CBonusSystemNode *parent) +void CBonusSystemNode::attachTo(CBonusSystemNode & parent) { - assert(!vstd::contains(parents, parent)); - parents.push_back(parent); + assert(!vstd::contains(parents, &parent)); + parents.push_back(&parent); if(!isHypothetic()) { - if(parent->actsAsBonusSourceOnly()) - parent->newRedDescendant(this); + if(parent.actsAsBonusSourceOnly()) + parent.newRedDescendant(*this); else newRedDescendant(parent); - parent->newChildAttached(this); + parent.newChildAttached(*this); } CBonusSystemNode::treeHasChanged(); } -void CBonusSystemNode::detachFrom(CBonusSystemNode *parent) +void CBonusSystemNode::detachFrom(CBonusSystemNode & parent) { - assert(vstd::contains(parents, parent)); + assert(vstd::contains(parents, &parent)); if(!isHypothetic()) { - if(parent->actsAsBonusSourceOnly()) - parent->removedRedDescendant(this); + if(parent.actsAsBonusSourceOnly()) + parent.removedRedDescendant(*this); else removedRedDescendant(parent); } - if (vstd::contains(parents, parent)) + if (vstd::contains(parents, &parent)) { - parents -= parent; + parents -= &parent; } else { logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)" - , nodeShortInfo(), nodeType, parent->nodeShortInfo(), parent->nodeType); + , nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType); } if(!isHypothetic()) { - parent->childDetached(this); + parent.childDetached(*this); } CBonusSystemNode::treeHasChanged(); } @@ -1304,27 +1304,27 @@ void CBonusSystemNode::unpropagateBonus(std::shared_ptr b) child->unpropagateBonus(b); } -void CBonusSystemNode::newChildAttached(CBonusSystemNode *child) +void CBonusSystemNode::newChildAttached(CBonusSystemNode & child) { - assert(!vstd::contains(children, child)); - children.push_back(child); + assert(!vstd::contains(children, &child)); + children.push_back(&child); } -void CBonusSystemNode::childDetached(CBonusSystemNode *child) +void CBonusSystemNode::childDetached(CBonusSystemNode & child) { - if(vstd::contains(children, child)) - children -= child; + if(vstd::contains(children, &child)) + children -= &child; else { logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)" - , child->nodeShortInfo(), child->nodeType, nodeShortInfo(), nodeType); + , child.nodeShortInfo(), child.nodeType, nodeShortInfo(), nodeType); } } void CBonusSystemNode::detachFromAll() { while(parents.size()) - detachFrom(parents.front()); + detachFrom(*parents.front()); } bool CBonusSystemNode::isIndependentNode() const @@ -1393,12 +1393,12 @@ void CBonusSystemNode::getRedChildren(TNodes &out) } } -void CBonusSystemNode::newRedDescendant(CBonusSystemNode * descendant) +void CBonusSystemNode::newRedDescendant(CBonusSystemNode & descendant) { for(auto b : exportedBonuses) { if(b->propagator) - descendant->propagateBonus(b, *this); + descendant.propagateBonus(b, *this); } TNodes redParents; getRedAncestors(redParents); //get all red parents recursively @@ -1408,16 +1408,16 @@ void CBonusSystemNode::newRedDescendant(CBonusSystemNode * descendant) for(auto b : parent->exportedBonuses) { if(b->propagator) - descendant->propagateBonus(b, *this); + descendant.propagateBonus(b, *this); } } } -void CBonusSystemNode::removedRedDescendant(CBonusSystemNode *descendant) +void CBonusSystemNode::removedRedDescendant(CBonusSystemNode & descendant) { for(auto b : exportedBonuses) if(b->propagator) - descendant->unpropagateBonus(b); + descendant.unpropagateBonus(b); TNodes redParents; getRedAncestors(redParents); //get all red parents recursively @@ -1426,7 +1426,7 @@ void CBonusSystemNode::removedRedDescendant(CBonusSystemNode *descendant) { for(auto b : parent->exportedBonuses) if(b->propagator) - descendant->unpropagateBonus(b); + descendant.unpropagateBonus(b); } } @@ -1708,9 +1708,9 @@ JsonNode Bonus::toJsonNode() const root["val"].Integer() = val; if(valType != ADDITIVE_VALUE) root["valueType"].String() = vstd::findKey(bonusValueMap, valType); - if(stacking != "") + if(!stacking.empty()) root["stacking"].String() = stacking; - if(description != "") + if(!description.empty()) root["description"].String() = description; if(effectRange != NO_LIMIT) root["effectRange"].String() = vstd::findKey(bonusLimitEffect, effectRange); diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 482671e5f..ef256cc77 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -791,21 +791,21 @@ public: static PlayerColor retrieveNodeOwner(const CBonusSystemNode * node); std::shared_ptr getBonusLocalFirst(const CSelector & selector); - void attachTo(CBonusSystemNode *parent); - void detachFrom(CBonusSystemNode *parent); + void attachTo(CBonusSystemNode & parent); + void detachFrom(CBonusSystemNode & parent); void detachFromAll(); virtual void addNewBonus(const std::shared_ptr& b); void accumulateBonus(const std::shared_ptr& b); //add value of bonus with same type/subtype or create new - void newChildAttached(CBonusSystemNode *child); - void childDetached(CBonusSystemNode *child); + void newChildAttached(CBonusSystemNode & child); + void childDetached(CBonusSystemNode & child); void propagateBonus(std::shared_ptr b, const CBonusSystemNode & source); void unpropagateBonus(std::shared_ptr b); void removeBonus(const std::shared_ptr& b); void removeBonuses(const CSelector & selector); void removeBonusesRecursive(const CSelector & s); - void newRedDescendant(CBonusSystemNode *descendant); //propagation needed - void removedRedDescendant(CBonusSystemNode *descendant); //de-propagation needed + void newRedDescendant(CBonusSystemNode & descendant); //propagation needed + void removedRedDescendant(CBonusSystemNode & descendant); //de-propagation needed bool isIndependentNode() const; //node is independent when it has no parents nor children bool actsAsBonusSourceOnly() const; diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index 632489c76..9970b2c39 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -91,6 +91,7 @@ public: virtual void artifactAssembled(const ArtifactLocation &al){}; virtual void artifactDisassembled(const ArtifactLocation &al){}; virtual void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst){}; + virtual void artifactPossibleAssembling(const ArtifactLocation & dst) {}; virtual void heroVisit(const CGHeroInstance *visitor, const CGObjectInstance *visitedObj, bool start){}; virtual void heroCreated(const CGHeroInstance*){}; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 171ebdace..115f1647f 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -994,18 +994,64 @@ struct EraseArtifact : CArtifactOperationPack struct MoveArtifact : CArtifactOperationPack { + MoveArtifact() {} + MoveArtifact(ArtifactLocation * src, ArtifactLocation * dst) + : src(*src), dst(*dst) {} ArtifactLocation src, dst; void applyCl(CClient *cl); DLL_LINKAGE void applyGs(CGameState *gs); - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { h & src; h & dst; } }; +struct BulkMoveArtifacts : CArtifactOperationPack +{ + struct LinkedSlots + { + ArtifactPosition srcPos; + ArtifactPosition dstPos; + + LinkedSlots() {} + LinkedSlots(ArtifactPosition srcPos, ArtifactPosition dstPos) + : srcPos(srcPos), dstPos(dstPos) {} + template void serialize(Handler & h, const int version) + { + h & srcPos; + h & dstPos; + } + }; + + TArtHolder srcArtHolder; + TArtHolder dstArtHolder; + + BulkMoveArtifacts() + : swap(false) {} + BulkMoveArtifacts(TArtHolder srcArtHolder, TArtHolder dstArtHolder, bool swap) + : srcArtHolder(srcArtHolder), dstArtHolder(dstArtHolder), swap(swap) {} + + void applyCl(CClient * cl); + DLL_LINKAGE void applyGs(CGameState * gs); + + std::vector artsPack0; + std::vector artsPack1; + bool swap; + CArtifactSet * getSrcHolderArtSet(); + CArtifactSet * getDstHolderArtSet(); + template void serialize(Handler & h, const int version) + { + h & artsPack0; + h & artsPack1; + h & srcArtHolder; + h & dstArtHolder; + h & swap; + } +}; + struct AssembledArtifact : CArtifactOperationPack { ArtifactLocation al; //where assembly will be put @@ -2197,6 +2243,27 @@ struct ExchangeArtifacts : public CPackForServer } }; +struct BulkExchangeArtifacts : public CPackForServer +{ + ObjectInstanceID srcHero; + ObjectInstanceID dstHero; + bool swap; + + BulkExchangeArtifacts() + : swap(false) {} + BulkExchangeArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) + : srcHero(srcHero), dstHero(dstHero), swap(swap) {} + + bool applyGh(CGameHandler * gh); + template void serialize(Handler & h, const int version) + { + h & static_cast(*this); + h & srcHero; + h & dstHero; + h & swap; + } +}; + struct AssembleArtifacts : public CPackForServer { AssembleArtifacts():assemble(false){}; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 6339d77ee..ca8ae4c8b 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -414,7 +414,7 @@ DLL_LINKAGE void RemoveObject::applyGs(CGameState *gs) PlayerState * p = gs->getPlayerState(beatenHero->tempOwner); gs->map->heroesOnMap -= beatenHero; p->heroes -= beatenHero; - beatenHero->detachFrom(beatenHero->whereShouldBeAttachedOnSiege(gs)); + beatenHero->detachFrom(*beatenHero->whereShouldBeAttachedOnSiege(gs)); beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi) { @@ -683,7 +683,7 @@ DLL_LINKAGE void HeroRecruited::applyGs(CGameState *gs) gs->map->heroesOnMap.push_back(h); p->heroes.push_back(h); - h->attachTo(p); + h->attachTo(*p); if(fresh) { h->initObj(gs->getRandomGenerator()); @@ -701,8 +701,8 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs) CGHeroInstance *h = gs->getHero(id); //bonus system - h->detachFrom(&gs->globalEffects); - h->attachTo(gs->getPlayerState(player)); + h->detachFrom(gs->globalEffects); + h->attachTo(*gs->getPlayerState(player)); h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front(); gs->map->removeBlockVisTiles(h,true); @@ -845,18 +845,11 @@ DLL_LINKAGE CBonusSystemNode *ArtifactLocation::getHolderNode() DLL_LINKAGE const CArtifactInstance *ArtifactLocation::getArt() const { - const ArtSlotInfo *s = getSlot(); - if(s && s->artifact) - { - if(!s->locked) - return s->artifact; - else - { - logNetwork->warn("ArtifactLocation::getArt: This location is locked!"); - return nullptr; - } - } - return nullptr; + auto s = getSlot(); + if(s) + return s->getArt(); + else + return nullptr; } DLL_LINKAGE const CArtifactSet * ArtifactLocation::getHolderArtSet() const @@ -1093,24 +1086,82 @@ DLL_LINKAGE void EraseArtifact::applyGs(CGameState *gs) al.removeArtifact(); } -DLL_LINKAGE void MoveArtifact::applyGs(CGameState *gs) +DLL_LINKAGE void MoveArtifact::applyGs(CGameState * gs) { - CArtifactInstance *a = src.getArt(); + CArtifactInstance * art = src.getArt(); if(dst.slot < GameConstants::BACKPACK_START) assert(!dst.getArt()); - a->move(src, dst); + art->move(src, dst); +} - //TODO what'll happen if Titan's thunder is equipped by pickin git up or the start of game? - if (a->artType->id == ArtifactID::TITANS_THUNDER && dst.slot == ArtifactPosition::RIGHT_HAND) //Titan's Thunder creates new spellbook on equip +DLL_LINKAGE void BulkMoveArtifacts::applyGs(CGameState * gs) +{ + enum class EBulkArtsOp { - auto hPtr = boost::get >(&dst.artHolder); - if(hPtr) + BULK_MOVE, + BULK_REMOVE, + BULK_PUT + }; + + auto bulkArtsOperation = [this](std::vector & artsPack, + CArtifactSet * artSet, EBulkArtsOp operation) -> void + { + int numBackpackArtifactsMoved = 0; + for(auto & slot : artsPack) { - CGHeroInstance *h = *hPtr; - if(h && !h->hasSpellbook()) - gs->giveHeroArtifact(h, ArtifactID::SPELLBOOK); + // When an object gets removed from the backpack, the backpack shrinks + // so all the following indices will be affected. Thus, we need to update + // the subsequent artifact slots to account for that + auto srcPos = slot.srcPos; + if((srcPos >= GameConstants::BACKPACK_START) && (operation != EBulkArtsOp::BULK_PUT)) + { + srcPos = ArtifactPosition(srcPos.num - numBackpackArtifactsMoved); + } + auto slotInfo = artSet->getSlot(srcPos); + assert(slotInfo); + auto art = const_cast(slotInfo->getArt()); + assert(art); + switch(operation) + { + case EBulkArtsOp::BULK_MOVE: + const_cast(art)->move( + ArtifactLocation(srcArtHolder, srcPos), ArtifactLocation(dstArtHolder, slot.dstPos)); + break; + case EBulkArtsOp::BULK_REMOVE: + art->removeFrom(ArtifactLocation(dstArtHolder, srcPos)); + break; + case EBulkArtsOp::BULK_PUT: + art->putAt(ArtifactLocation(srcArtHolder, slot.dstPos)); + break; + default: + break; + } + + if(srcPos >= GameConstants::BACKPACK_START) + { + numBackpackArtifactsMoved++; + } } + }; + + if(swap) + { + // Swap + auto leftSet = getSrcHolderArtSet(); + auto rightSet = getDstHolderArtSet(); + CArtifactFittingSet artFittingSet(leftSet->bearerType()); + + artFittingSet.artifactsWorn = rightSet->artifactsWorn; + artFittingSet.artifactsInBackpack = rightSet->artifactsInBackpack; + + bulkArtsOperation(artsPack1, rightSet, EBulkArtsOp::BULK_REMOVE); + bulkArtsOperation(artsPack0, leftSet, EBulkArtsOp::BULK_MOVE); + bulkArtsOperation(artsPack1, &artFittingSet, EBulkArtsOp::BULK_PUT); + } + else + { + bulkArtsOperation(artsPack0, getSrcHolderArtSet(), EBulkArtsOp::BULK_MOVE); } } @@ -1153,7 +1204,7 @@ DLL_LINKAGE void DisassembledArtifact::applyGs(CGameState *gs) { ArtifactLocation constituentLoc = al; constituentLoc.slot = (ci.slot >= 0 ? ci.slot : al.slot); //-1 is slot of main constituent -> it'll replace combined artifact in its pos - disassembled->detachFrom(ci.art); + disassembled->detachFrom(*ci.art); ci.art->putAt(constituentLoc); } @@ -1281,10 +1332,10 @@ DLL_LINKAGE void SetObjectProperty::applyGs(CGameState *gs) } } - CBonusSystemNode *nodeToMove = cai->whatShouldBeAttached(); - nodeToMove->detachFrom(cai->whereShouldBeAttached(gs)); + CBonusSystemNode & nodeToMove = cai->whatShouldBeAttached(); + nodeToMove.detachFrom(cai->whereShouldBeAttached(gs)); obj->setProperty(what,val); - nodeToMove->attachTo(cai->whereShouldBeAttached(gs)); + nodeToMove.attachTo(cai->whereShouldBeAttached(gs)); } else //not an armed instance { @@ -1712,4 +1763,24 @@ DLL_LINKAGE void EntitiesChanged::applyGs(CGameState * gs) gs->updateEntity(change.metatype, change.entityIndex, change.data); } +const CArtifactInstance * ArtSlotInfo::getArt() const +{ + if(locked) + { + logNetwork->warn("ArtifactLocation::getArt: This location is locked!"); + return nullptr; + } + return artifact; +} + +CArtifactSet * BulkMoveArtifacts::getSrcHolderArtSet() +{ + return boost::apply_visitor(GetBase(), srcArtHolder); +} + +CArtifactSet * BulkMoveArtifacts::getDstHolderArtSet() +{ + return boost::apply_visitor(GetBase(), dstArtHolder); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index 37019cb2a..36b316881 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -85,7 +85,7 @@ ObstacleInfo * ObstacleHandler::loadFromJson(const std::string & scope, const Js info->animation = json["animation"].String(); info->width = json["width"].Integer(); info->height = json["height"].Integer(); - for(auto & t : json["allowedTerrain"].Vector()) + for(auto & t : json["allowedTerrains"].Vector()) info->allowedTerrains.emplace_back(VLC->terrainTypeHandler->getInfoByName(t.String())->id); for(auto & t : json["specialBattlefields"].Vector()) info->allowedSpecialBfields.emplace_back(t.String()); diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index ea5e7e776..72842f944 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -84,7 +84,7 @@ void BattleInfo::localInit() { auto armyObj = battleGetArmyObject(i); armyObj->battle = this; - armyObj->attachTo(this); + armyObj->attachTo(*this); } for(CStack * s : stacks) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 8d891a8f6..7f309e876 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -55,7 +55,7 @@ static void retrieveTurretDamageRange(const CGTownInstance * town, const battle: const int baseDamage = 15; outMinDmg = multiplier * (baseDamage + town->getTownLevel() * 3); - outMaxDmg = multiplier * (baseDamage + town->getTownLevel() * 3); + outMaxDmg = outMinDmg; } static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate) diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index e98b4b719..46c8c824a 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -142,17 +142,18 @@ void CArmedInstance::armyChanged() updateMoraleBonusFromArmy(); } -CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs) +CBonusSystemNode & CArmedInstance::whereShouldBeAttached(CGameState * gs) { if(tempOwner < PlayerColor::PLAYER_LIMIT) - return gs->getPlayerState(tempOwner); - else - return &gs->globalEffects; + if(auto * where = gs->getPlayerState(tempOwner)) + return *where; + + return gs->globalEffects; } -CBonusSystemNode * CArmedInstance::whatShouldBeAttached() +CBonusSystemNode & CArmedInstance::whatShouldBeAttached() { - return this; + return *this; } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CArmedInstance.h b/lib/mapObjects/CArmedInstance.h index 559e94e51..e7f6077e9 100644 --- a/lib/mapObjects/CArmedInstance.h +++ b/lib/mapObjects/CArmedInstance.h @@ -33,8 +33,8 @@ public: ////////////////////////////////////////////////////////////////////////// // int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface ??? - virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs); - virtual CBonusSystemNode *whatShouldBeAttached(); + virtual CBonusSystemNode & whereShouldBeAttached(CGameState * gs); + virtual CBonusSystemNode & whatShouldBeAttached(); ////////////////////////////////////////////////////////////////////////// CArmedInstance(); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 6866c8ffd..b14473ddd 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -195,6 +195,23 @@ bool CGHeroInstance::canLearnSkill() const return secSkills.size() < GameConstants::SKILL_PER_HERO; } +bool CGHeroInstance::canLearnSkill(SecondarySkill which) const +{ + if ( !canLearnSkill()) + return false; + + if (!cb->isAllowed(2, which)) + return false; + + if (getSecSkillLevel(which) > 0) + return false; + + if (type->heroClass->secSkillProbability[which] == 0) + return false; + + return true; +} + int CGHeroInstance::maxMovePoints(bool onLand) const { TurnInfo ti(this); @@ -1062,17 +1079,17 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttachedOnSiege(CGameState * gs) if(visitedTown) return whereShouldBeAttachedOnSiege(visitedTown->isBattleOutsideTown(this)); - return CArmedInstance::whereShouldBeAttached(gs); + return &CArmedInstance::whereShouldBeAttached(gs); } -CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState * gs) +CBonusSystemNode & CGHeroInstance::whereShouldBeAttached(CGameState * gs) { if(visitedTown) { if(inTownGarrison) - return visitedTown; + return *visitedTown; else - return &visitedTown->townAndVis; + return visitedTown->townAndVis; } else return CArmedInstance::whereShouldBeAttached(gs); @@ -1117,7 +1134,7 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() std::vector obligatorySkills; //hero is offered magic school or wisdom if possible if (!skillsInfo.wisdomCounter) { - if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM)) + if (canLearnSkill(SecondarySkill::WISDOM)) obligatorySkills.push_back(SecondarySkill::WISDOM); } if (!skillsInfo.magicSchoolCounter) @@ -1131,7 +1148,7 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() for (auto skill : ss) { - if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet + if (canLearnSkill(skill)) //only schools hero doesn't know yet { obligatorySkills.push_back(skill); break; //only one @@ -1143,7 +1160,7 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() //picking sec. skills for choice std::set basicAndAdv, expert, none; for(int i = 0; i < VLC->skillh->size(); i++) - if (cb->isAllowed(2,i)) + if (canLearnSkill(SecondarySkill(i))) none.insert(SecondarySkill(i)); for(auto & elem : secSkills) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 7cd11583e..5e05eafe5 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -187,6 +187,7 @@ public: /// Returns true if hero has free secondary skill slot. bool canLearnSkill() const; + bool canLearnSkill(SecondarySkill which) const; void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs); void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value @@ -240,7 +241,7 @@ public: ArtBearer::ArtBearer bearerType() const override; ///IBonusBearer - CBonusSystemNode * whereShouldBeAttached(CGameState *gs) override; + CBonusSystemNode & whereShouldBeAttached(CGameState * gs) override; std::string nodeName() const override; CBonusSystemNode * whereShouldBeAttachedOnSiege(const bool isBattleOutsideTown) const; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 9afffeaee..7fd19f76e 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -46,7 +46,7 @@ void CCreGenAsCastleInfo::serializeJson(JsonSerializeFormat & handler) if(!handler.saving) { - asCastle = (instanceId != ""); + asCastle = !instanceId.empty(); allowedFactions.clear(); } @@ -1146,7 +1146,7 @@ std::string CGTownInstance::nodeName() const void CGTownInstance::deserializationFix() { - attachTo(&townAndVis); + attachTo(townAndVis); //Hero is already handled by CGameState::attachArmedObjects @@ -1216,8 +1216,8 @@ void CGTownInstance::setVisitingHero(CGHeroInstance *h) { PlayerState *p = cb->gameState()->getPlayerState(h->tempOwner); assert(p); - h->detachFrom(p); - h->attachTo(&townAndVis); + h->detachFrom(*p); + h->attachTo(townAndVis); visitingHero = h; h->visitedTown = this; h->inTownGarrison = false; @@ -1226,8 +1226,8 @@ void CGTownInstance::setVisitingHero(CGHeroInstance *h) { PlayerState *p = cb->gameState()->getPlayerState(visitingHero->tempOwner); visitingHero->visitedTown = nullptr; - visitingHero->detachFrom(&townAndVis); - visitingHero->attachTo(p); + visitingHero->detachFrom(townAndVis); + visitingHero->attachTo(*p); visitingHero = nullptr; } } @@ -1239,8 +1239,8 @@ void CGTownInstance::setGarrisonedHero(CGHeroInstance *h) { PlayerState *p = cb->gameState()->getPlayerState(h->tempOwner); assert(p); - h->detachFrom(p); - h->attachTo(this); + h->detachFrom(*p); + h->attachTo(*this); garrisonHero = h; h->visitedTown = this; h->inTownGarrison = true; @@ -1250,8 +1250,8 @@ void CGTownInstance::setGarrisonedHero(CGHeroInstance *h) PlayerState *p = cb->gameState()->getPlayerState(garrisonHero->tempOwner); garrisonHero->visitedTown = nullptr; garrisonHero->inTownGarrison = false; - garrisonHero->detachFrom(this); - garrisonHero->attachTo(p); + garrisonHero->detachFrom(*this); + garrisonHero->attachTo(*p); garrisonHero = nullptr; } updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice @@ -1290,9 +1290,9 @@ int CGTownInstance::getTownLevel() const return level; } -CBonusSystemNode * CGTownInstance::whatShouldBeAttached() +CBonusSystemNode & CGTownInstance::whatShouldBeAttached() { - return &townAndVis; + return townAndVis; } const CArmedInstance * CGTownInstance::getUpperArmy() const diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 0d261f2af..032dfc1e2 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -274,7 +274,7 @@ public: } ////////////////////////////////////////////////////////////////////////// - CBonusSystemNode *whatShouldBeAttached() override; + CBonusSystemNode & whatShouldBeAttached() override; std::string nodeName() const override; void updateMoraleBonusFromArmy() override; void deserializationFix(); diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 2b9d87392..5f2810ed5 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -572,7 +572,7 @@ void AObjectTypeHandler::addTemplate(JsonNode config) auto tmpl = new ObjectTemplate; tmpl->id = Obj(type); tmpl->subid = subtype; - tmpl->stringID = ""; // TODO? + tmpl->stringID.clear(); // TODO? tmpl->readJson(config); templates.emplace_back(tmpl); } diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 04aa4efac..a63ff4eca 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -938,7 +938,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) case MANA_POINTS: case MORALE_BONUS: case LUCK_BONUS: - identifier = ""; + identifier.clear(); break; case RESOURCES: identifier = GameConstants::RESOURCE_NAMES[rID]; @@ -976,7 +976,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) const JsonNode & rewardsJson = handler.getCurrent(); - fullIdentifier = ""; + fullIdentifier.clear(); if(rewardsJson.Struct().empty()) return; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index bb51fadb7..bedd9b7c1 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1802,7 +1802,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler) //TODO: unify const JsonNode & json = handler.getCurrent(); bonusType = RANDOM; - if(json["rewardPrimSkill"].String() != "") + if(!json["rewardPrimSkill"].String().empty()) { auto raw = VLC->modh->identifiers.getIdentifier("core", "primSkill", json["rewardPrimSkill"].String()); if(raw) @@ -1811,7 +1811,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler) bonusID = raw.get(); } } - else if(json["rewardSkill"].String() != "") + else if(!json["rewardSkill"].String().empty()) { auto raw = VLC->modh->identifiers.getIdentifier("core", "skill", json["rewardSkill"].String()); if(raw) @@ -1820,7 +1820,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler) bonusID = raw.get(); } } - else if(json["rewardSpell"].String() != "") + else if(!json["rewardSpell"].String().empty()) { auto raw = VLC->modh->identifiers.getIdentifier("core", "spell", json["rewardSpell"].String()); if(raw) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index b2583721b..d5f3fee82 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -203,7 +203,7 @@ void CMapHeader::setupEvents() standardVictory.effect.type = EventEffect::VICTORY; standardVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[5]; standardVictory.identifier = "standardVictory"; - standardVictory.description = ""; // TODO: display in quest window + standardVictory.description.clear(); // TODO: display in quest window standardVictory.onFulfill = VLC->generaltexth->allTexts[659]; standardVictory.trigger = EventExpression(victoryCondition); @@ -212,7 +212,7 @@ void CMapHeader::setupEvents() standardDefeat.effect.type = EventEffect::DEFEAT; standardDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[8]; standardDefeat.identifier = "standardDefeat"; - standardDefeat.description = ""; // TODO: display in quest window + standardDefeat.description.clear(); // TODO: display in quest window standardDefeat.onFulfill = VLC->generaltexth->allTexts[7]; standardDefeat.trigger = EventExpression(defeatCondition); @@ -651,7 +651,7 @@ void CMap::addNewObject(CGObjectInstance * obj) if(obj->id != ObjectInstanceID((si32)objects.size())) throw std::runtime_error("Invalid object instance id"); - if(obj->instanceName == "") + if(obj->instanceName.empty()) throw std::runtime_error("Object instance name missing"); if (vstd::contains(instanceNames, obj->instanceName)) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 59e8d845d..5ca1087d6 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -313,7 +313,7 @@ void CMapLoaderH3M::readVictoryLossConditions() standardVictory.effect.type = EventEffect::VICTORY; standardVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[5]; standardVictory.identifier = "standardVictory"; - standardVictory.description = ""; // TODO: display in quest window + standardVictory.description.clear(); // TODO: display in quest window standardVictory.onFulfill = VLC->generaltexth->allTexts[659]; standardVictory.trigger = EventExpression(victoryCondition); @@ -321,7 +321,7 @@ void CMapLoaderH3M::readVictoryLossConditions() standardDefeat.effect.type = EventEffect::DEFEAT; standardDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[8]; standardDefeat.identifier = "standardDefeat"; - standardDefeat.description = ""; // TODO: display in quest window + standardDefeat.description.clear(); // TODO: display in quest window standardDefeat.onFulfill = VLC->generaltexth->allTexts[7]; standardDefeat.trigger = EventExpression(defeatCondition); @@ -338,7 +338,7 @@ void CMapLoaderH3M::readVictoryLossConditions() TriggeredEvent specialVictory; specialVictory.effect.type = EventEffect::VICTORY; specialVictory.identifier = "specialVictory"; - specialVictory.description = ""; // TODO: display in quest window + specialVictory.description.clear(); // TODO: display in quest window mapHeader->victoryIconIndex = ui16(vicCondition); mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1]; @@ -526,7 +526,7 @@ void CMapLoaderH3M::readVictoryLossConditions() specialDefeat.effect.type = EventEffect::DEFEAT; specialDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[5]; specialDefeat.identifier = "specialDefeat"; - specialDefeat.description = ""; // TODO: display in quest window + specialDefeat.description.clear(); // TODO: display in quest window mapHeader->defeatIconIndex = ui16(lossCond); mapHeader->defeatMessage = VLC->generaltexth->lossCondtions[size_t(lossCond) + 1]; diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index e1efb5b2c..4e26cdfe9 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -134,7 +134,7 @@ namespace TriggeredEventsDetail static EMetaclass decodeMetaclass(const std::string & source) { - if(source == "") + if(source.empty()) return EMetaclass::INVALID; auto rawId = vstd::find_pos(NMetaclass::names, source); @@ -286,7 +286,7 @@ namespace TriggeredEventsDetail if(event.value > 0) data["value"].Integer() = event.value; - if(event.objectInstanceName != "") + if(!event.objectInstanceName.empty()) data["object"].String() = event.objectInstanceName; } break; diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 519643e33..d2c67e8c1 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -319,6 +319,7 @@ void registerTypesClientPacks2(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); @@ -358,6 +359,7 @@ void registerTypesServerPacks(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); } template diff --git a/lib/serializer/JsonDeserializer.cpp b/lib/serializer/JsonDeserializer.cpp index 85cb80797..a5378dada 100644 --- a/lib/serializer/JsonDeserializer.cpp +++ b/lib/serializer/JsonDeserializer.cpp @@ -38,7 +38,7 @@ void JsonDeserializer::serializeInternal(const std::string & fieldName, si32 & v value = defaultValue ? defaultValue.get() : 0; - if(identifier != "") + if(!identifier.empty()) { si32 rawId = decoder(identifier); diff --git a/lib/serializer/JsonSerializer.cpp b/lib/serializer/JsonSerializer.cpp index d01ca0e4c..69253b986 100644 --- a/lib/serializer/JsonSerializer.cpp +++ b/lib/serializer/JsonSerializer.cpp @@ -108,7 +108,7 @@ void JsonSerializer::serializeLIC(const std::string & fieldName, LICSet & value) void JsonSerializer::serializeString(const std::string & fieldName, std::string & value) { - if(value != "") + if(!value.empty()) currentObject->operator[](fieldName).String() = value; } diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index ff0f049ec..91b0798fd 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -1011,7 +1011,7 @@ void MainWindow::onSelectionMade(int level, bool anythingSelected) { if (level == mapLevel) { - auto info = QString::asprintf("Selection on layer %d: %b", level, anythingSelected ? "true" : "false"); + auto info = QString::asprintf("Selection on layer %d: %s", level, anythingSelected ? "true" : "false"); setStatusMessage(info); ui->actionErase->setEnabled(anythingSelected); diff --git a/mapeditor/objectbrowser.cpp b/mapeditor/objectbrowser.cpp index 960fde550..416b8abfa 100644 --- a/mapeditor/objectbrowser.cpp +++ b/mapeditor/objectbrowser.cpp @@ -51,7 +51,7 @@ bool ObjectBrowser::filterAcceptsRow(int source_row, const QModelIndex & source_ auto factory = VLC->objtypeh->getHandlerFor(objId, objSubId); auto templ = factory->getTemplates()[templateId]; - result = result & templ->canBePlacedAt(terrain); + result = result && templ->canBePlacedAt(terrain); //if we are here, just text filter will be applied return result; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a4f8dedad..064dfb7c1 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3916,10 +3916,101 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition( (si32)dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START))); } + auto hero = boost::get>(dst.artHolder); + if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) + giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); - MoveArtifact ma; - ma.src = src; - ma.dst = dst; + MoveArtifact ma(&src, &dst); + sendAndApply(&ma); + return true; +} + +bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) +{ + // Make sure exchange is even possible between the two heroes. + if(!isAllowedExchange(srcHero, dstHero)) + COMPLAIN_RET("That heroes cannot make any exchange!"); + + auto psrcHero = getHero(srcHero); + auto pdstHero = getHero(dstHero); + if((!psrcHero) || (!pdstHero)) + COMPLAIN_RET("bulkMoveArtifacts: wrong hero's ID"); + + BulkMoveArtifacts ma(static_cast>(psrcHero), + static_cast>(pdstHero), swap); + auto & slotsSrcDst = ma.artsPack0; + auto & slotsDstSrc = ma.artsPack1; + + if(swap) + { + auto moveArtsWorn = [this](const CGHeroInstance * srcHero, const CGHeroInstance * dstHero, + std::vector & slots) -> void + { + for(auto & artifact : srcHero->artifactsWorn) + { + if(artifact.second.locked) + continue; + if(!ArtifactUtils::isArtRemovable(artifact)) + continue; + slots.push_back(BulkMoveArtifacts::LinkedSlots(artifact.first, artifact.first)); + + auto art = artifact.second.getArt(); + assert(art); + if(ArtifactUtils::checkSpellbookIsNeeded(dstHero, art->artType->id, artifact.first)) + giveHeroNewArtifact(dstHero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + } + }; + auto moveArtsInBackpack = [](const CGHeroInstance * pHero, + std::vector & slots) -> void + { + for(auto & slotInfo : pHero->artifactsInBackpack) + { + auto slot = pHero->getArtPos(slotInfo.artifact); + slots.push_back(BulkMoveArtifacts::LinkedSlots(slot, slot)); + } + }; + // Move over artifacts that are worn srcHero -> dstHero + moveArtsWorn(psrcHero, pdstHero, slotsSrcDst); + // Move over artifacts that are worn dstHero -> srcHero + moveArtsWorn(pdstHero, psrcHero, slotsDstSrc); + // Move over artifacts that are in backpack srcHero -> dstHero + moveArtsInBackpack(psrcHero, slotsSrcDst); + // Move over artifacts that are in backpack dstHero -> srcHero + moveArtsInBackpack(pdstHero, slotsDstSrc); + } + else + { + // Temporary fitting set for artifacts. Used to select available slots before sending data. + CArtifactFittingSet artFittingSet(pdstHero->bearerType()); + artFittingSet.artifactsInBackpack = pdstHero->artifactsInBackpack; + artFittingSet.artifactsWorn = pdstHero->artifactsWorn; + + auto moveArtifact = [this, &artFittingSet, &slotsSrcDst](const CArtifactInstance * artifact, + ArtifactPosition srcSlot, const CGHeroInstance * pdstHero) -> void + { + assert(artifact); + auto dstSlot = ArtifactUtils::getArtifactDstPosition(artifact, &artFittingSet, pdstHero->bearerType()); + artFittingSet.putArtifact(dstSlot, static_cast>(artifact)); + slotsSrcDst.push_back(BulkMoveArtifacts::LinkedSlots(srcSlot, dstSlot)); + + if(ArtifactUtils::checkSpellbookIsNeeded(pdstHero, artifact->artType->id, dstSlot)) + giveHeroNewArtifact(pdstHero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + }; + + // Move over artifacts that are worn + for(auto & artInfo : psrcHero->artifactsWorn) + { + if(ArtifactUtils::isArtRemovable(artInfo)) + { + moveArtifact(psrcHero->getArt(artInfo.first), artInfo.first, pdstHero); + } + } + // Move over artifacts that are in backpack + for(auto & slotInfo : psrcHero->artifactsInBackpack) + { + moveArtifact(psrcHero->getArt(psrcHero->getArtPos(slotInfo.artifact)), psrcHero->getArtPos(slotInfo.artifact), pdstHero); + } + } sendAndApply(&ma); return true; } @@ -3947,6 +4038,9 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition COMPLAIN_RET("assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts!"); if (!vstd::contains(destArtifact->assemblyPossibilities(hero), combinedArt)) COMPLAIN_RET("assembleArtifacts: It's impossible to assemble requested artifact!"); + + if(ArtifactUtils::checkSpellbookIsNeeded(hero, assembleTo, artifactSlot)) + giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); AssembledArtifact aa; aa.al = ArtifactLocation(hero, artifactSlot); @@ -4083,7 +4177,7 @@ bool CGameHandler::buySecSkill(const IMarket *m, const CGHeroInstance *h, Second if (!h->canLearnSkill()) COMPLAIN_RET("Hero can't learn any more skills"); - if (h->type->heroClass->secSkillProbability.at(skill)==0)//can't learn this skill (like necromancy for most of non-necros) + if (!h->canLearnSkill(skill)) COMPLAIN_RET("The hero can't learn this skill!"); if (!vstd::contains(m->availableItemsIds(EMarketMode::RESOURCE_SKILL), skill)) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e2b68c312..5d9800e49 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -180,7 +180,8 @@ public: void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override; void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override; void removeArtifact(const ArtifactLocation &al) override; - bool moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2) override; + bool moveArtifact(const ArtifactLocation & al1, const ArtifactLocation & al2) override; + bool bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap); void synchronizeArtifactHandlerLists(); void showCompInfo(ShowInInfobox * comp) override; diff --git a/server/CQuery.cpp b/server/CQuery.cpp index 4f8270a6f..7c1df47be 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -370,6 +370,9 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const } if(auto dismiss = dynamic_ptr_cast(pack)) return !vstd::contains(ourIds, dismiss->id); + + if(auto arts = dynamic_ptr_cast(pack)) + return !vstd::contains(ourIds, arts->srcHero) || !vstd::contains(ourIds, arts->dstHero); if(auto dismiss = dynamic_ptr_cast(pack)) return !vstd::contains(ourIds, dismiss->heroID); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 907a0e470..bc80920a3 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -180,6 +180,13 @@ bool ExchangeArtifacts::applyGh(CGameHandler * gh) return gh->moveArtifact(src, dst); } +bool BulkExchangeArtifacts::applyGh(CGameHandler * gh) +{ + const CGHeroInstance * pSrcHero = gh->getHero(srcHero); + throwOnWrongPlayer(gh, pSrcHero->getOwner()); + return gh->bulkMoveArtifacts(srcHero, dstHero, swap); +} + bool AssembleArtifacts::applyGh(CGameHandler * gh) { throwOnWrongOwner(gh, heroID);