diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index 6a50f6f84..8eacd7221 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -21,23 +21,43 @@ class HypotheticBattle; ///Fake random generator, used by AI to evaluate random server behavior -class RNGStub : public vstd::RNG +class RNGStub final : public vstd::RNG { public: - vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override + virtual int nextInt() override { - return [=]()->int64_t - { - return (lower + upper)/2; - }; + return 0; } - vstd::TRand getDoubleRange(double lower, double upper) override + int nextBinomialInt(int coinsCount, double coinChance) override { - return [=]()->double - { - return (lower + upper)/2; - }; + return coinsCount * coinChance; + } + + int nextInt(int lower, int upper) override + { + return (lower + upper) / 2; + } + int64_t nextInt64(int64_t lower, int64_t upper) override + { + return (lower + upper) / 2; + } + double nextDouble(double lower, double upper) override + { + return (lower + upper) / 2; + } + + int nextInt(int upper) override + { + return upper / 2; + } + int64_t nextInt64(int64_t upper) override + { + return upper / 2; + } + double nextDouble(double upper) override + { + return upper / 2; } }; diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index f3feeda6b..ec1135ee0 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -28,6 +28,7 @@ #include "../../lib/networkPacks/StackLocation.h" #include "../../lib/battle/BattleStateInfoForRetreat.h" #include "../../lib/battle/BattleInfo.h" +#include "../../lib/CPlayerState.h" #include "AIGateway.h" #include "Goals/Goals.h" @@ -1166,6 +1167,17 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu battlename.clear(); CAdventureAI::battleEnd(battleID, br, queryID); + + // gosolo + if(queryID != QueryID::NONE && myCb->getPlayerState(playerID)->isHuman()) + { + status.addQuery(queryID, "Confirm battle query"); + + requestActionASAP([=]() + { + answerQuery(queryID, 0); + }); + } } void AIGateway::waitTillFree() diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index d69b529c7..95b5ca725 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -26,7 +26,12 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * if(obj) { objid = obj->id.getNum(); + +#if NKAI_TRACE_LEVEL >= 1 + targetName = obj->getObjectName() + tile.toString(); +#else targetName = obj->typeName + tile.toString(); +#endif } else { @@ -260,7 +265,11 @@ void ExecuteHeroChain::accept(AIGateway * ai) std::string ExecuteHeroChain::toString() const { +#if NKAI_TRACE_LEVEL >= 1 + return "ExecuteHeroChain " + targetName + " by " + chainPath.toString(); +#else return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated(); +#endif } bool ExecuteHeroChain::moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index b5cc485c3..39bc8f9f1 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -25,7 +25,7 @@ namespace NKAI { std::shared_ptr> AISharedStorage::shared; -uint64_t AISharedStorage::version = 0; +uint32_t AISharedStorage::version = 0; boost::mutex AISharedStorage::locker; std::set committedTiles; std::set committedTilesInitial; @@ -224,7 +224,6 @@ std::vector AINodeStorage::getInitialNodes() AIPathNode * initialNode = allocated.value(); - initialNode->inPQ = false; initialNode->pq = nullptr; initialNode->turns = actor->initialTurn; initialNode->moveRemains = actor->initialMovement; @@ -1386,6 +1385,28 @@ void AINodeStorage::calculateChainInfo(std::vector & paths, const int3 & path.armyLoss = node.armyLoss; path.targetObjectDanger = evaluateDanger(pos, path.targetHero, !node.actor->allowBattle); + if(path.targetObjectDanger > 0) + { + if(node.theNodeBefore) + { + auto prevNode = getAINode(node.theNodeBefore); + + if(node.coord == prevNode->coord && node.actor->hero == prevNode->actor->hero) + { + paths.pop_back(); + continue; + } + else + { + path.armyLoss = prevNode->armyLoss; + } + } + else + { + path.armyLoss = 0; + } + } + path.targetObjectArmyLoss = evaluateArmyLoss( path.targetHero, getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy), diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 979f19a50..2f823f2de 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -44,14 +44,17 @@ enum DayFlags : ui8 struct AIPathNode : public CGPathNode { + std::shared_ptr specialAction; + + const AIPathNode * chainOther; + const ChainActor * actor; + uint64_t danger; uint64_t armyLoss; + uint32_t version; + int16_t manaCost; DayFlags dayFlags; - const AIPathNode * chainOther; - std::shared_ptr specialAction; - const ChainActor * actor; - uint64_t version; void addSpecialAction(std::shared_ptr action); @@ -152,7 +155,7 @@ class AISharedStorage std::shared_ptr> nodes; public: static boost::mutex locker; - static uint64_t version; + static uint32_t version; AISharedStorage(int3 mapSize); ~AISharedStorage(); diff --git a/AI/Nullkiller/Pathfinding/GraphPaths.cpp b/AI/Nullkiller/Pathfinding/GraphPaths.cpp index 150d37c1a..a3dba0696 100644 --- a/AI/Nullkiller/Pathfinding/GraphPaths.cpp +++ b/AI/Nullkiller/Pathfinding/GraphPaths.cpp @@ -160,7 +160,7 @@ void GraphPaths::dumpToLog() const node.previous.coord.toString(), tile.first.toString(), node.cost, - node.danger); + node.linkDanger); } logBuilder.addLine(node.previous.coord, tile.first); @@ -169,14 +169,17 @@ void GraphPaths::dumpToLog() const }); } -bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link) +bool GraphPathNode::tryUpdate( + const GraphPathNodePointer & pos, + const GraphPathNode & prev, + const ObjectLink & link) { auto newCost = prev.cost + link.cost; if(newCost < cost) { previous = pos; - danger = prev.danger + link.danger; + linkDanger = link.danger; cost = newCost; return true; @@ -199,7 +202,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe std::vector tilesToPass; - uint64_t danger = node.danger; + uint64_t danger = node.linkDanger; float cost = node.cost; bool allowBattle = false; @@ -212,13 +215,13 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe if(currentTile == pathNodes.end()) break; - auto currentNode = currentTile->second[current.nodeType]; + auto & currentNode = currentTile->second[current.nodeType]; if(!currentNode.previous.valid()) break; allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE; - vstd::amax(danger, currentNode.danger); + vstd::amax(danger, currentNode.linkDanger); vstd::amax(cost, currentNode.cost); tilesToPass.push_back(current); @@ -239,9 +242,13 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe if(path.targetHero != hero) continue; - for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++) + uint64_t loss = 0; + uint64_t strength = getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy); + + for(auto graphTile = ++tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++) { AIPathNodeInfo n; + auto & node = getNode(*graphTile); n.coord = graphTile->coord; n.cost = cost; @@ -249,7 +256,21 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe n.danger = danger; n.targetHero = hero; n.parentIndex = -1; - n.specialAction = getNode(*graphTile).specialAction; + n.specialAction = node.specialAction; + + if(node.linkDanger > 0) + { + auto additionalLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, strength, node.linkDanger); + loss += additionalLoss; + + if(strength > additionalLoss) + strength -= additionalLoss; + else + { + strength = 0; + break; + } + } if(n.specialAction) { @@ -264,7 +285,12 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe path.nodes.insert(path.nodes.begin(), n); } - path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger); + if(strength == 0) + { + continue; + } + + path.armyLoss += loss; path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle); path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger); @@ -287,7 +313,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector & paths, int3 std::vector tilesToPass; - uint64_t danger = targetNode.danger; + uint64_t danger = targetNode.linkDanger; float cost = targetNode.cost; bool allowBattle = false; @@ -303,7 +329,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector & paths, int3 auto currentNode = currentTile->second[current.nodeType]; allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE; - vstd::amax(danger, currentNode.danger); + vstd::amax(danger, currentNode.linkDanger); vstd::amax(cost, currentNode.cost); tilesToPass.push_back(current); @@ -341,7 +367,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector & paths, int3 // final node n.coord = tile; n.cost = targetNode.cost; - n.danger = targetNode.danger; + n.danger = danger; n.parentIndex = path.nodes.size(); path.nodes.push_back(n); @@ -368,7 +394,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector & paths, int3 n.coord = graphTile->coord; n.cost = node.cost; n.turns = static_cast(node.cost); - n.danger = node.danger; + n.danger = danger; n.specialAction = node.specialAction; n.parentIndex = path.nodes.size(); diff --git a/AI/Nullkiller/Pathfinding/GraphPaths.h b/AI/Nullkiller/Pathfinding/GraphPaths.h index 83167eabe..a41781f68 100644 --- a/AI/Nullkiller/Pathfinding/GraphPaths.h +++ b/AI/Nullkiller/Pathfinding/GraphPaths.h @@ -67,7 +67,7 @@ struct GraphPathNode GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL; GraphPathNodePointer previous; float cost = BAD_COST; - uint64_t danger = 0; + uint64_t linkDanger = 0; const CGObjectInstance * obj = nullptr; std::shared_ptr specialAction; diff --git a/ChangeLog.md b/ChangeLog.md index bf895d923..7c7a93454 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,13 @@ +# 1.5.4 -> 1.5.5 + +* Fixed crash when advancing to the next scenario in campaigns when the hero not transferring has a combination artefact that can be transferred to the next scenario. +* Fixed game not updating information such as hero path and current music on new day +* Changed default battle queue hotkey from Q to Z to match HD Mod / HotA +* Changed default hotkey for finishing battle with quick combat from E to Z to match HD Mod / HotA +* Creature casting now uses both F and G keyboard hotkeys +* Shift+left click now directly opens the hero window when two heroes are in town +* Fixed handling of alternative actions for creatures that have more than two potential actions, such as move, shoot, and cast spells. + # 1.5.3 -> 1.5.4 ### Stability diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json b/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json index 1c6b60d36..fff27bb58 100644 --- a/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json +++ b/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json @@ -2,33 +2,33 @@ "basepath" : "battle/rangeHighlights/green/", "images" : [ - { "frame" : 0, "file" : "empty.png"}, // 000001 -> 00 empty frame + { "frame" : 0, "file" : "empty.png"}, // 000001 -> 00 empty frame // load single edges - { "frame" : 1, "file" : "topLeft.png"}, //000001 -> 01 topLeft - { "frame" : 2, "file" : "topLeft.png"}, //000010 -> 02 topRight - { "frame" : 3, "file" : "left.png"}, //000100 -> 04 right - { "frame" : 4, "file" : "topLeft.png"}, //001000 -> 08 bottomRight - { "frame" : 5, "file" : "topLeft.png"}, //010000 -> 16 bottomLeft - { "frame" : 6, "file" : "left.png"}, //100000 -> 32 left + { "frame" : 1, "file" : "topLeft.png"}, //000001 -> 01 topLeft + { "frame" : 2, "file" : "topLeft.png", "verticalFlip" : true }, //000010 -> 02 topRight + { "frame" : 3, "file" : "left.png", "verticalFlip" : true }, //000100 -> 04 right + { "frame" : 4, "file" : "topLeft.png", "verticalFlip" : true, "horizontalFlip" : true }, //001000 -> 08 bottomRight + { "frame" : 5, "file" : "topLeft.png", "horizontalFlip" : true }, //010000 -> 16 bottomLeft + { "frame" : 6, "file" : "left.png"}, //100000 -> 32 left // load double edges - { "frame" : 7, "file" : "top.png"}, //000011 -> 03 top - { "frame" : 8, "file" : "top.png"}, //011000 -> 24 bottom - { "frame" : 9, "file" : "topLeftHalfCorner.png"}, //000110 -> 06 topRightHalfCorner - { "frame" : 10, "file" : "topLeftHalfCorner.png"}, //001100 -> 12 bottomRightHalfCorner - { "frame" : 11, "file" : "topLeftHalfCorner.png"}, //110000 -> 48 bottomLeftHalfCorner - { "frame" : 12, "file" : "topLeftHalfCorner.png"}, //100001 -> 33 topLeftHalfCorner + { "frame" : 7, "file" : "top.png"}, //000011 -> 03 top + { "frame" : 8, "file" : "top.png", "horizontalFlip" : true }, //011000 -> 24 bottom + { "frame" : 9, "file" : "topLeftHalfCorner.png", "verticalFlip" : true }, //000110 -> 06 topRightHalfCorner + { "frame" : 10, "file" : "topLeftHalfCorner.png", "verticalFlip" : true, "horizontalFlip" : true }, //001100 -> 12 bottomRightHalfCorner + { "frame" : 11, "file" : "topLeftHalfCorner.png", "horizontalFlip" : true }, //110000 -> 48 bottomLeftHalfCorner + { "frame" : 12, "file" : "topLeftHalfCorner.png"}, //100001 -> 33 topLeftHalfCorner // load halves - { "frame" : 13, "file" : "leftHalf.png"}, //001110 -> 14 rightHalf - { "frame" : 14, "file" : "leftHalf.png"}, //110001 -> 49 leftHalf + { "frame" : 13, "file" : "leftHalf.png", "verticalFlip" : true}, //001110 -> 14 rightHalf + { "frame" : 14, "file" : "leftHalf.png"}, //110001 -> 49 leftHalf // load corners - { "frame" : 15, "file" : "topLeftCorner.png"}, //000111 -> 07 topRightCorner - { "frame" : 16, "file" : "topLeftCorner.png"}, //011100 -> 28 bottomRightCorner - { "frame" : 17, "file" : "topLeftCorner.png"}, //111000 -> 56 bottomLeftCorner - { "frame" : 18, "file" : "topLeftCorner.png"} //100011 -> 35 topLeftCorner + { "frame" : 15, "file" : "topLeftCorner.png", "verticalFlip" : true }, //000111 -> 07 topRightCorner + { "frame" : 16, "file" : "topLeftCorner.png", "verticalFlip" : true, "horizontalFlip" : true }, //011100 -> 28 bottomRightCorner + { "frame" : 17, "file" : "topLeftCorner.png", "horizontalFlip" : true }, //111000 -> 56 bottomLeftCorner + { "frame" : 18, "file" : "topLeftCorner.png"} //100011 -> 35 topLeftCorner ] } diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json b/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json index 42350c073..68908f0ce 100644 --- a/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json +++ b/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json @@ -2,33 +2,33 @@ "basepath" : "battle/rangeHighlights/red/", "images" : [ - { "frame" : 0, "file" : "empty.png"}, // 000001 -> 00 empty frame + { "frame" : 0, "file" : "empty.png"}, // 000001 -> 00 empty frame // load single edges - { "frame" : 1, "file" : "topLeft.png"}, //000001 -> 01 topLeft - { "frame" : 2, "file" : "topLeft.png"}, //000010 -> 02 topRight - { "frame" : 3, "file" : "left.png"}, //000100 -> 04 right - { "frame" : 4, "file" : "topLeft.png"}, //001000 -> 08 bottomRight - { "frame" : 5, "file" : "topLeft.png"}, //010000 -> 16 bottomLeft - { "frame" : 6, "file" : "left.png"}, //100000 -> 32 left + { "frame" : 1, "file" : "topLeft.png"}, //000001 -> 01 topLeft + { "frame" : 2, "file" : "topLeft.png", "verticalFlip" : true }, //000010 -> 02 topRight + { "frame" : 3, "file" : "left.png", "verticalFlip" : true }, //000100 -> 04 right + { "frame" : 4, "file" : "topLeft.png", "verticalFlip" : true, "horizontalFlip" : true }, //001000 -> 08 bottomRight + { "frame" : 5, "file" : "topLeft.png", "horizontalFlip" : true }, //010000 -> 16 bottomLeft + { "frame" : 6, "file" : "left.png"}, //100000 -> 32 left // load double edges - { "frame" : 7, "file" : "top.png"}, //000011 -> 03 top - { "frame" : 8, "file" : "top.png"}, //011000 -> 24 bottom - { "frame" : 9, "file" : "topLeftHalfCorner.png"}, //000110 -> 06 topRightHalfCorner - { "frame" : 10, "file" : "topLeftHalfCorner.png"}, //001100 -> 12 bottomRightHalfCorner - { "frame" : 11, "file" : "topLeftHalfCorner.png"}, //110000 -> 48 bottomLeftHalfCorner - { "frame" : 12, "file" : "topLeftHalfCorner.png"}, //100001 -> 33 topLeftHalfCorner + { "frame" : 7, "file" : "top.png"}, //000011 -> 03 top + { "frame" : 8, "file" : "top.png", "horizontalFlip" : true }, //011000 -> 24 bottom + { "frame" : 9, "file" : "topLeftHalfCorner.png", "verticalFlip" : true }, //000110 -> 06 topRightHalfCorner + { "frame" : 10, "file" : "topLeftHalfCorner.png", "verticalFlip" : true, "horizontalFlip" : true }, //001100 -> 12 bottomRightHalfCorner + { "frame" : 11, "file" : "topLeftHalfCorner.png", "horizontalFlip" : true }, //110000 -> 48 bottomLeftHalfCorner + { "frame" : 12, "file" : "topLeftHalfCorner.png"}, //100001 -> 33 topLeftHalfCorner // load halves - { "frame" : 13, "file" : "leftHalf.png"}, //001110 -> 14 rightHalf - { "frame" : 14, "file" : "leftHalf.png"}, //110001 -> 49 leftHalf + { "frame" : 13, "file" : "leftHalf.png", "verticalFlip" : true}, //001110 -> 14 rightHalf + { "frame" : 14, "file" : "leftHalf.png"}, //110001 -> 49 leftHalf // load corners - { "frame" : 15, "file" : "topLeftCorner.png"}, //000111 -> 07 topRightCorner - { "frame" : 16, "file" : "topLeftCorner.png"}, //011100 -> 28 bottomRightCorner - { "frame" : 17, "file" : "topLeftCorner.png"}, //111000 -> 56 bottomLeftCorner - { "frame" : 18, "file" : "topLeftCorner.png"} //100011 -> 35 topLeftCorner + { "frame" : 15, "file" : "topLeftCorner.png", "verticalFlip" : true }, //000111 -> 07 topRightCorner + { "frame" : 16, "file" : "topLeftCorner.png", "verticalFlip" : true, "horizontalFlip" : true }, //011100 -> 28 bottomRightCorner + { "frame" : 17, "file" : "topLeftCorner.png", "horizontalFlip" : true }, //111000 -> 56 bottomLeftCorner + { "frame" : 18, "file" : "topLeftCorner.png"} //100011 -> 35 topLeftCorner ] } diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 4405870fc..0e5391e17 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -33,7 +33,7 @@ "vcmi.heroOverview.startingArmy" : "Unidades Iniciais", "vcmi.heroOverview.warMachine" : "Máquinas de Guerra", - "vcmi.heroOverview.secondarySkills" : "Habilidades Secundárias", + "vcmi.heroOverview.secondarySkills" : "Habilid. Secundárias", "vcmi.heroOverview.spells" : "Feitiços", "vcmi.radialWheel.mergeSameUnit" : "Mesclar criaturas iguais", @@ -259,7 +259,7 @@ "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", - "vcmi.battleWindow.killed" : "Eliminados", + "vcmi.battleWindow.killed" : "Mortos", "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s morreram por tiros precisos!", "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s morreu com um tiro preciso!", "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s morreram por tiros precisos!", @@ -612,7 +612,7 @@ "core.bonus.SPELL_LIKE_ATTACK.description" : "Ataques com ${subtype.spell}", "core.bonus.SPELL_RESISTANCE_AURA.name" : "Aura de Resistência a Feitiços", "core.bonus.SPELL_RESISTANCE_AURA.description" : "Pilhas próximas ganham ${val}% de resistência a magia", - "core.bonus.SUMMON_GUARDIANS.name" : "Invocar Guardiões", + "core.bonus.SUMMON_GUARDIANS.name" : "Invocar Guardas", "core.bonus.SUMMON_GUARDIANS.description" : "No início da batalha, invoca ${subtype.creature} (${val}%)", "core.bonus.SYNERGY_TARGET.name" : "Alvo Sinergizável", "core.bonus.SYNERGY_TARGET.description" : "Esta criatura é vulnerável ao efeito de sinergia", diff --git a/client/CMT.cpp b/client/CMT.cpp index b9862ef91..8109081fa 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -28,6 +28,7 @@ #include "windows/CMessage.h" #include "windows/InfoWindows.h" #include "render/IScreenHandler.h" +#include "render/IRenderHandler.h" #include "render/Graphics.h" #include "../lib/CConfigHandler.h" @@ -347,6 +348,7 @@ int main(int argc, char * argv[]) { pomtime.getDiff(); graphics = new Graphics(); // should be before curh + GH.renderHandler().onLibraryLoadingFinished(CGI); CCS->curh = new CursorHandler(); logGlobal->info("Screen handler: %d ms", pomtime.getDiff()); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 65f156973..9630a478c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -88,6 +88,7 @@ set(client_SRCS render/Colors.cpp render/Graphics.cpp render/IFont.cpp + render/ImageLocator.cpp renderSDL/CBitmapFont.cpp renderSDL/CBitmapHanFont.cpp @@ -287,6 +288,7 @@ set(client_HEADERS render/IFont.h render/IImage.h render/IImageLoader.h + render/ImageLocator.h render/IRenderHandler.h render/IScreenHandler.h diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 55efb470a..dab02d6dd 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -70,6 +70,7 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" +#include "../lib/CRandomGenerator.h" #include "../lib/CStack.h" #include "../lib/CStopWatch.h" #include "../lib/CThreadHelper.h" @@ -413,8 +414,9 @@ void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectI EVENT_HANDLER_CALLED_BY_CLIENT; if(start && visitedObj) { - if(visitedObj->getVisitSound()) - CCS->soundh->playSound(visitedObj->getVisitSound().value()); + auto visitSound = visitedObj->getVisitSound(CRandomGenerator::getDefault()); + if (visitSound) + CCS->soundh->playSound(visitSound.value()); } } @@ -440,18 +442,20 @@ void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, Prim EVENT_HANDLER_CALLED_BY_CLIENT; if (which == PrimarySkill::EXPERIENCE) { - for(auto ctw : GH.windows().findWindows()) - ctw->updateHero(); + for(auto ctw : GH.windows().findWindows()) + ctw->updateExperience(); } else + { adventureInt->onHeroChanged(hero); + } } void CPlayerInterface::heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) { EVENT_HANDLER_CALLED_BY_CLIENT; - for (auto cuw : GH.windows().findWindows()) - cuw->redraw(); + for (auto cuw : GH.windows().findWindows()) + cuw->updateSecondarySkills(); } void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero) @@ -470,8 +474,8 @@ void CPlayerInterface::heroMovePointsChanged(const CGHeroInstance * hero) void CPlayerInterface::receivedResource() { EVENT_HANDLER_CALLED_BY_CLIENT; - for (auto mw : GH.windows().findWindows()) - mw->updateResource(); + for (auto mw : GH.windows().findWindows()) + mw->updateResources(); GH.windows().totalRedraw(); } @@ -1141,9 +1145,9 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component const CGTownInstance * t = dynamic_cast(cb->getObj(obj)); if(t) { - std::shared_ptr a = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITPA")); - a->preload(); - images.push_back(a->getImage(t->town->clientInfo.icons[t->hasFort()][false] + 2)->scaleFast(Point(35, 23))); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); + image->scaleFast(Point(35, 23)); + images.push_back(image); } } @@ -1410,10 +1414,14 @@ void CPlayerInterface::centerView (int3 pos, int focusTime) void CPlayerInterface::objectRemoved(const CGObjectInstance * obj, const PlayerColor & initiator) { EVENT_HANDLER_CALLED_BY_CLIENT; - if(playerID == initiator && obj->getRemovalSound()) + if(playerID == initiator) { - waitWhileDialog(); - CCS->soundh->playSound(obj->getRemovalSound().value()); + auto removalSound = obj->getRemovalSound(CRandomGenerator::getDefault()); + if (removalSound) + { + waitWhileDialog(); + CCS->soundh->playSound(removalSound.value()); + } } CGI->mh->waitForOngoingAnimations(); @@ -1669,7 +1677,7 @@ void CPlayerInterface::showHillFortWindow(const CGObjectInstance *object, const void CPlayerInterface::availableArtifactsChanged(const CGBlackMarket * bm) { EVENT_HANDLER_CALLED_BY_CLIENT; - for (auto cmw : GH.windows().findWindows()) + for (auto cmw : GH.windows().findWindows()) cmw->updateArtifacts(); } diff --git a/client/Client.cpp b/client/Client.cpp index 15f7c47a0..174acdb39 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -556,6 +556,13 @@ void CClient::invalidatePaths() pathCache.clear(); } +vstd::RNG & CClient::getRandomGenerator() +{ + // Client should use CRandomGenerator::getDefault() for UI logic + // Gamestate should never call this method on client! + throw std::runtime_error("Illegal access to random number generator from client code!"); +} + std::shared_ptr CClient::getPathsInfo(const CGHeroInstance * h) { assert(h); diff --git a/client/Client.h b/client/Client.h index a775ad732..276aafb51 100644 --- a/client/Client.h +++ b/client/Client.h @@ -25,6 +25,7 @@ class BinaryDeserializer; class BinarySerializer; class BattleAction; class BattleInfo; +struct BankConfig; template class CApplier; @@ -161,7 +162,7 @@ public: void changeSpells(const CGHeroInstance * hero, bool give, const std::set & spells) override {}; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; - void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override {}; + void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; void giveExperience(const CGHeroInstance * hero, TExpType val) override {}; void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs = false) override {}; @@ -214,10 +215,15 @@ public: void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override {}; void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {}; + void setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) override {}; + void setRewardableObjectConfiguration(ObjectInstanceID objid, const Rewardable::Configuration & configuration) override {}; + void setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) override{}; void showInfoDialog(InfoWindow * iw) override {}; void removeGUI() const; + vstd::RNG & getRandomGenerator() override; + #if SCRIPTING_ENABLED scripting::Pool * getGlobalContextPool() const override; #endif diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 5bba85706..96e150713 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -39,6 +39,7 @@ #include "../lib/VCMIDirs.h" #include "../lib/logging/VisualLogger.h" #include "CMT.h" +#include "../lib/serializer/Connection.h" #ifdef SCRIPTING_ENABLED #include "../lib/ScriptHandler.h" @@ -82,31 +83,37 @@ void ClientCommandManager::handleGoSoloCommand() printCommandMessage("Game is not in playing state"); return; } - PlayerColor color; + if(session["aiSolo"].Bool()) { - for(auto & elem : CSH->client->gameState()->players) + // unlikely it will work but just in case to be consistent + for(auto & color : CSH->getAllClientPlayers(CSH->logicConnection->connectionID)) { - if(elem.second.human) - CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); + if(color.isValidPlayer() && CSH->client->getStartInfo()->playerInfos.at(color).isControlledByHuman()) + { + CSH->client->installNewPlayerInterface(std::make_shared(color), color); + } } } else { - color = LOCPLINT->playerID; + PlayerColor currentColor = LOCPLINT->playerID; CSH->client->removeGUI(); - for(auto & elem : CSH->client->gameState()->players) + + for(auto & color : CSH->getAllClientPlayers(CSH->logicConnection->connectionID)) { - if(elem.second.human) + if(color.isValidPlayer() && CSH->client->getStartInfo()->playerInfos.at(color).isControlledByHuman()) { - auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false, false); - printCommandMessage("Player " + elem.first.toString() + " will be lead by " + AiToGive, ELogLevel::INFO); - CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first); + auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(color), false, false); + printCommandMessage("Player " + color.toString() + " will be lead by " + AiToGive, ELogLevel::INFO); + CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color); } } + GH.windows().totalRedraw(); - giveTurn(color); + giveTurn(currentColor); } + session["aiSolo"].Bool() = !session["aiSolo"].Bool(); } @@ -381,8 +388,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu { std::string URI; singleWordBuffer >> URI; - auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI)); - anim->preload(); + auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::ALPHA); anim->exportBitmaps(VCMIDirs::get().userExtractedPath()); } diff --git a/client/HeroMovementController.cpp b/client/HeroMovementController.cpp index 2d0efe2eb..d402413e5 100644 --- a/client/HeroMovementController.cpp +++ b/client/HeroMovementController.cpp @@ -24,6 +24,7 @@ #include "ConditionalWait.h" #include "../lib/CConfigHandler.h" +#include "../lib/CRandomGenerator.h" #include "../lib/pathfinder/CGPathNode.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/networkPacks/PacksForClient.h" @@ -153,8 +154,12 @@ void HeroMovementController::onTryMoveHero(const CGHeroInstance * hero, const Tr if(details.result == TryMoveHero::EMBARK || details.result == TryMoveHero::DISEMBARK) { - if(hero->getRemovalSound() && hero->tempOwner == LOCPLINT->playerID) - CCS->soundh->playSound(hero->getRemovalSound().value()); + if (hero->tempOwner == LOCPLINT->playerID) + { + auto removalSound = hero->getRemovalSound(CRandomGenerator::getDefault()); + if (removalSound) + CCS->soundh->playSound(removalSound.value()); + } } bool directlyAttackingCreature = diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 3b1f31beb..c7755dd82 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -1048,7 +1048,7 @@ void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack) { cl.invalidatePaths(); - const CGObjectInstance *obj = cl.getObj(pack.createdObjectID); + const CGObjectInstance *obj = pack.newObject; if(CGI->mh) CGI->mh->onObjectFadeIn(obj, pack.initiator); diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 2a4dbe0be..255dc2884 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -31,6 +31,7 @@ #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" #include "../render/Canvas.h" +#include "../render/IImage.h" #include "../render/IRenderHandler.h" #include "../CMT.h" #include "../PlayerLocalState.h" @@ -178,7 +179,7 @@ void AdventureMapInterface::dim(Canvas & to) { if(!std::dynamic_pointer_cast(window) && std::dynamic_pointer_cast(window) && isBigWindow(window)) { - to.fillTexture(GH.renderHandler().loadImage(ImagePath::builtin("DiBoxBck"))); + to.fillTexture(GH.renderHandler().loadImage(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE)); return; } } @@ -399,7 +400,7 @@ void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID) return; currentPlayerID = playerID; - widget->setPlayer(playerID); + widget->setPlayerColor(playerID); } void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID) @@ -914,7 +915,7 @@ void AdventureMapInterface::onScreenResize() widget = std::make_shared(shortcuts); widget->getMapView()->onViewMapActivated(); - widget->setPlayer(currentPlayerID); + widget->setPlayerColor(currentPlayerID); widget->updateActiveState(); widget->getMinimap()->update(); widget->getInfoBar()->showSelection(); diff --git a/client/adventureMap/AdventureMapInterface.h b/client/adventureMap/AdventureMapInterface.h index b0509a77c..32ff1df89 100644 --- a/client/adventureMap/AdventureMapInterface.h +++ b/client/adventureMap/AdventureMapInterface.h @@ -31,7 +31,6 @@ class CAnimImage; class CGStatusBar; class AdventureMapWidget; class AdventureMapShortcuts; -class CAnimation; class MapView; class CResDataBar; class CHeroList; diff --git a/client/adventureMap/AdventureMapWidget.cpp b/client/adventureMap/AdventureMapWidget.cpp index 7333f1d37..80b6399fc 100644 --- a/client/adventureMap/AdventureMapWidget.cpp +++ b/client/adventureMap/AdventureMapWidget.cpp @@ -20,7 +20,6 @@ #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "../mapView/MapView.h" -#include "../render/CAnimation.h" #include "../render/IImage.h" #include "../render/IRenderHandler.h" #include "../widgets/Buttons.h" @@ -60,7 +59,7 @@ AdventureMapWidget::AdventureMapWidget( std::shared_ptr s const JsonNode config(JsonPath::builtin("config/widgets/adventureMap.json")); for(const auto & entry : config["options"]["imagesPlayerColored"].Vector()) - playerColorerImages.push_back(ImagePath::fromJson(entry)); + playerColoredImages.push_back(ImagePath::fromJson(entry)); build(config); addUsedEvents(KEYBOARD); @@ -125,26 +124,6 @@ Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & bounding return Rect(topLeft + boundingBox.topLeft(), dimensions); } -std::shared_ptr AdventureMapWidget::loadImage(const JsonNode & name) -{ - ImagePath resource = ImagePath::fromJson(name); - - if(images.count(resource) == 0) - images[resource] = GH.renderHandler().loadImage(resource); - - return images[resource]; -} - -std::shared_ptr AdventureMapWidget::loadAnimation(const JsonNode & name) -{ - AnimationPath resource = AnimationPath::fromJson(name); - - if(animations.count(resource) == 0) - animations[resource] = GH.renderHandler().loadAnimation(resource); - - return animations[resource]; -} - std::shared_ptr AdventureMapWidget::buildInfobox(const JsonNode & input) { Rect area = readTargetArea(input["area"]); @@ -156,8 +135,12 @@ std::shared_ptr AdventureMapWidget::buildMapImage(const JsonNode & i { Rect targetArea = readTargetArea(input["area"]); Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]); + ImagePath path = ImagePath::fromJson(input["image"]); - return std::make_shared(loadImage(input["image"]), targetArea, sourceArea); + if (vstd::contains(playerColoredImages, path)) + return std::make_shared(path, targetArea, sourceArea); + else + return std::make_shared(path, targetArea, sourceArea); } std::shared_ptr AdventureMapWidget::buildMapButton(const JsonNode & input) @@ -257,7 +240,7 @@ std::shared_ptr AdventureMapWidget::buildMapIcon(const JsonNode & in size_t index = input["index"].Integer(); size_t perPlayer = input["perPlayer"].Integer(); - return std::make_shared(area.topLeft(), loadAnimation(input["image"]), index, perPlayer); + return std::make_shared(area.topLeft(), AnimationPath::fromJson(input["image"]), index, perPlayer); } std::shared_ptr AdventureMapWidget::buildMapTownList(const JsonNode & input) @@ -356,7 +339,7 @@ std::shared_ptr AdventureMapWidget::getInfoBar() return infoBar; } -void AdventureMapWidget::setPlayer(const PlayerColor & player) +void AdventureMapWidget::setPlayerColor(const PlayerColor & player) { setPlayerChildren(this, player); } @@ -369,34 +352,32 @@ void AdventureMapWidget::setPlayerChildren(CIntObject * widget, const PlayerColo auto icon = dynamic_cast(entry); auto button = dynamic_cast(entry); auto resDataBar = dynamic_cast(entry); - auto texture = dynamic_cast(entry); + auto textureColored = dynamic_cast(entry); + auto textureIndexed = dynamic_cast(entry); if(button) button->setPlayerColor(player); if(resDataBar) - resDataBar->colorize(player); + resDataBar->setPlayerColor(player); if(icon) - icon->setPlayer(player); + icon->setPlayerColor(player); if(container) setPlayerChildren(container, player); - if (texture) - texture->playerColored(player); - } + if (textureColored) + textureColored->setPlayerColor(player); - for(const auto & entry : playerColorerImages) - { - if(images.count(entry)) - images[entry]->playerColored(player); + if (textureIndexed) + textureIndexed->setPlayerColor(player); } redraw(); } -CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr animation, size_t index, size_t iconsPerPlayer) +CAdventureMapIcon::CAdventureMapIcon(const Point & position, const AnimationPath & animation, size_t index, size_t iconsPerPlayer) : index(index) , iconsPerPlayer(iconsPerPlayer) { @@ -405,7 +386,7 @@ CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr(animation, index); } -void CAdventureMapIcon::setPlayer(const PlayerColor & player) +void CAdventureMapIcon::setPlayerColor(const PlayerColor & player) { image->setFrame(index + player.getNum() * iconsPerPlayer); } diff --git a/client/adventureMap/AdventureMapWidget.h b/client/adventureMap/AdventureMapWidget.h index 93a19eeb6..742e7d683 100644 --- a/client/adventureMap/AdventureMapWidget.h +++ b/client/adventureMap/AdventureMapWidget.h @@ -11,7 +11,6 @@ #include "../gui/InterfaceObjectConfigurable.h" -class CAnimation; class CHeroList; class CTownList; class CMinimap; @@ -29,11 +28,7 @@ class AdventureMapWidget : public InterfaceObjectConfigurable std::vector subwidgetSizes; /// list of images on which player-colored palette will be applied - std::vector playerColorerImages; - - /// list of named images shared between widgets - std::map> images; - std::map> animations; + std::vector playerColoredImages; /// Widgets that require access from adventure map std::shared_ptr heroList; @@ -48,9 +43,6 @@ class AdventureMapWidget : public InterfaceObjectConfigurable Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon); Rect readArea(const JsonNode & source, const Rect & boundingBox); - std::shared_ptr loadImage(const JsonNode & name); - std::shared_ptr loadAnimation(const JsonNode & name); - std::shared_ptr buildInfobox(const JsonNode & input); std::shared_ptr buildMapImage(const JsonNode & input); std::shared_ptr buildMapButton(const JsonNode & input); @@ -64,7 +56,6 @@ class AdventureMapWidget : public InterfaceObjectConfigurable std::shared_ptr buildStatusBar(const JsonNode & input); std::shared_ptr buildTexturePlayerColored(const JsonNode &); - void setPlayerChildren(CIntObject * widget, const PlayerColor & player); void updateActiveStateChildden(CIntObject * widget); public: @@ -76,7 +67,7 @@ public: std::shared_ptr getMapView(); std::shared_ptr getInfoBar(); - void setPlayer(const PlayerColor & player); + void setPlayerColor(const PlayerColor & player); void onMapViewMoved(const Rect & visibleArea, int mapLevel); void updateActiveState(); @@ -104,7 +95,7 @@ class CAdventureMapIcon : public CIntObject size_t index; size_t iconsPerPlayer; public: - CAdventureMapIcon(const Point & position, std::shared_ptr image, size_t index, size_t iconsPerPlayer); + CAdventureMapIcon(const Point & position, const AnimationPath & image, size_t index, size_t iconsPerPlayer); - void setPlayer(const PlayerColor & player); + void setPlayerColor(const PlayerColor & player); }; diff --git a/client/adventureMap/CResDataBar.cpp b/client/adventureMap/CResDataBar.cpp index 7096b47bc..d15fe230f 100644 --- a/client/adventureMap/CResDataBar.cpp +++ b/client/adventureMap/CResDataBar.cpp @@ -31,7 +31,7 @@ CResDataBar::CResDataBar(const ImagePath & imageName, const Point & position) OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); background = std::make_shared(imageName, 0, 0); - background->colorize(LOCPLINT->playerID); + background->setPlayerColor(LOCPLINT->playerID); pos.w = background->pos.w; pos.h = background->pos.h; @@ -84,7 +84,7 @@ void CResDataBar::showAll(Canvas & to) to.drawText(pos.topLeft() + *datePosition, FONT_SMALL, Colors::WHITE, ETextAlignment::TOPLEFT, buildDateString()); } -void CResDataBar::colorize(PlayerColor player) +void CResDataBar::setPlayerColor(PlayerColor player) { - background->colorize(player); + background->setPlayerColor(player); } diff --git a/client/adventureMap/CResDataBar.h b/client/adventureMap/CResDataBar.h index 3bf294ad4..3363802c4 100644 --- a/client/adventureMap/CResDataBar.h +++ b/client/adventureMap/CResDataBar.h @@ -34,7 +34,7 @@ public: void setDatePosition(const Point & position); void setResourcePosition(const GameResID & resource, const Point & position); - void colorize(PlayerColor player); + void setPlayerColor(PlayerColor player); void showAll(Canvas & to) override; }; diff --git a/client/adventureMap/MapAudioPlayer.cpp b/client/adventureMap/MapAudioPlayer.cpp index bca944448..bac234103 100644 --- a/client/adventureMap/MapAudioPlayer.cpp +++ b/client/adventureMap/MapAudioPlayer.cpp @@ -17,6 +17,7 @@ #include "../media/IMusicPlayer.h" #include "../media/ISoundPlayer.h" +#include "../../lib/CRandomGenerator.h" #include "../../lib/TerrainHandler.h" #include "../../lib/mapObjects/CArmedInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h" @@ -136,8 +137,12 @@ std::vector MapAudioPlayer::getAmbientSounds(const int3 & tile) if (!object) logGlobal->warn("Already removed object %d found on tile! (%d %d %d)", objectID.getNum(), tile.x, tile.y, tile.z); - if(object && object->getAmbientSound()) - result.push_back(object->getAmbientSound().value()); + if(object) + { + auto ambientSound = object->getAmbientSound(CRandomGenerator::getDefault()); + if (ambientSound) + result.push_back(ambientSound.value()); + } } if(CGI->mh->getMap()->isCoastalTile(tile)) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 0228334ec..a4fe689a1 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -985,26 +985,27 @@ void BattleActionsController::activateStack() std::list actionsToSelect; if(!possibleActions.empty()) { - switch(possibleActions.front().get()) + auto primaryAction = possibleActions.front().get(); + + if(primaryAction == PossiblePlayerBattleAction::SHOOT || primaryAction == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE + || primaryAction == PossiblePlayerBattleAction::ANY_LOCATION || primaryAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN) { - case PossiblePlayerBattleAction::SHOOT: - actionsToSelect.push_back(possibleActions.front()); - actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK); - break; - - case PossiblePlayerBattleAction::ATTACK_AND_RETURN: - actionsToSelect.push_back(possibleActions.front()); - actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK); - break; - - case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: - actionsToSelect.push_back(possibleActions.front()); - actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK); - break; - case PossiblePlayerBattleAction::ANY_LOCATION: - actionsToSelect.push_back(possibleActions.front()); - actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK); - break; + actionsToSelect.push_back(possibleActions.front()); + + auto shootActionPredicate = [](const PossiblePlayerBattleAction& action) + { + return action.get() == PossiblePlayerBattleAction::SHOOT; + }; + bool hasShootSecondaryAction = std::any_of(possibleActions.begin() + 1, possibleActions.end(), shootActionPredicate); + + if(hasShootSecondaryAction) //casters may have shooting capabilities, for example storm elementals + actionsToSelect.emplace_back(PossiblePlayerBattleAction::SHOOT); + + /* TODO: maybe it would also make sense to check spellcast as non-top priority action ("NO_SPELLCAST_BY_DEFAULT" bonus)? + * it would require going beyond this "if" block for melee casters + * F button helps, but some mod creatures may have that bonus and more than 1 castable spell */ + + actionsToSelect.emplace_back(PossiblePlayerBattleAction::ATTACK); //always allow melee attack as last option } } owner.windowObject->setAlternativeActions(actionsToSelect); @@ -1071,3 +1072,8 @@ void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction { possibleActions.insert(possibleActions.begin(), action); } + +void BattleActionsController::resetCurrentStackPossibleActions() +{ + possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); +} diff --git a/client/battle/BattleActionsController.h b/client/battle/BattleActionsController.h index 5b46b0bcb..409f3172a 100644 --- a/client/battle/BattleActionsController.h +++ b/client/battle/BattleActionsController.h @@ -122,4 +122,7 @@ public: /// inserts possible action in the beginning in order to prioritize it void pushFrontPossibleAction(PossiblePlayerBattleAction); + + /// resets possible actions to original state + void resetCurrentStackPossibleActions(); }; diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index c54a1d202..434f6c4d7 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -24,6 +24,7 @@ #include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../media/ISoundPlayer.h" +#include "../render/CAnimation.h" #include "../render/IRenderHandler.h" #include "../../CCallback.h" @@ -882,7 +883,7 @@ uint32_t CastAnimation::getAttackClimaxFrame() const EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed): BattleAnimation(owner), - animation(GH.renderHandler().loadAnimation(animationName)), + animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::ALPHA)), effectFlags(effects), effectFinished(false), reversed(reversed) @@ -925,8 +926,6 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & bool EffectAnimation::init() { - animation->preload(); - auto first = animation->getImage(0, 0, true); if(!first) { diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index aeb4e2817..439e5ab71 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -82,39 +82,30 @@ namespace HexMasks }; } -std::map hexEdgeMaskToFrameIndex; - -// Maps HexEdgesMask to "Frame" indexes for range highlight images -void initializeHexEdgeMaskToFrameIndex() +static const std::map hexEdgeMaskToFrameIndex = { - hexEdgeMaskToFrameIndex[HexMasks::empty] = 0; - - hexEdgeMaskToFrameIndex[HexMasks::topLeft] = 1; - hexEdgeMaskToFrameIndex[HexMasks::topRight] = 2; - hexEdgeMaskToFrameIndex[HexMasks::right] = 3; - hexEdgeMaskToFrameIndex[HexMasks::bottomRight] = 4; - hexEdgeMaskToFrameIndex[HexMasks::bottomLeft] = 5; - hexEdgeMaskToFrameIndex[HexMasks::left] = 6; - - hexEdgeMaskToFrameIndex[HexMasks::top] = 7; - hexEdgeMaskToFrameIndex[HexMasks::bottom] = 8; - - hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner] = 9; - hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner] = 10; - hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner] = 11; - hexEdgeMaskToFrameIndex[HexMasks::topLeftHalfCorner] = 12; - - hexEdgeMaskToFrameIndex[HexMasks::rightTopAndBottom] = 13; - hexEdgeMaskToFrameIndex[HexMasks::leftTopAndBottom] = 14; - - hexEdgeMaskToFrameIndex[HexMasks::rightHalf] = 13; - hexEdgeMaskToFrameIndex[HexMasks::leftHalf] = 14; - - hexEdgeMaskToFrameIndex[HexMasks::topRightCorner] = 15; - hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner] = 16; - hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner] = 17; - hexEdgeMaskToFrameIndex[HexMasks::topLeftCorner] = 18; -} + { HexMasks::empty, 0 }, + { HexMasks::topLeft, 1 }, + { HexMasks::topRight, 2 }, + { HexMasks::right, 3 }, + { HexMasks::bottomRight, 4 }, + { HexMasks::bottomLeft, 5 }, + { HexMasks::left, 6 }, + { HexMasks::top, 7 }, + { HexMasks::bottom, 8 }, + { HexMasks::topRightHalfCorner, 9 }, + { HexMasks::bottomRightHalfCorner, 10 }, + { HexMasks::bottomLeftHalfCorner, 11 }, + { HexMasks::topLeftHalfCorner, 12 }, + { HexMasks::rightTopAndBottom, 13 }, + { HexMasks::leftTopAndBottom, 14 }, + { HexMasks::rightHalf, 13 }, + { HexMasks::leftHalf, 14 }, + { HexMasks::topRightCorner, 15 }, + { HexMasks::bottomRightCorner, 16 }, + { HexMasks::bottomLeftCorner, 17 }, + { HexMasks::topLeftCorner, 18 } +}; BattleFieldController::BattleFieldController(BattleInterface & owner): owner(owner) @@ -123,26 +114,15 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): //preparing cells and hexes cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY); - cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP")); + cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"), EImageBlitMode::ALPHA); cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY); cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY); - attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT")); - attackCursors->preload(); + attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"), EImageBlitMode::COLORKEY); + spellCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"), EImageBlitMode::COLORKEY); - spellCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL")); - spellCursors->preload(); - - initializeHexEdgeMaskToFrameIndex(); - - rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json")); - rangedFullDamageLimitImages->preload(); - - shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json")); - shootingRangeLimitImages->preload(); - - flipRangeLimitImagesIntoPositions(rangedFullDamageLimitImages); - flipRangeLimitImagesIntoPositions(shootingRangeLimitImages); + rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"), EImageBlitMode::COLORKEY); + shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY); if(!owner.siegeController) { @@ -542,7 +522,7 @@ std::vector> BattleFieldController::calculateRangeLimitH mask.set(direction); uint8_t imageKey = static_cast(mask.to_ulong()); - output.push_back(limitImages->getImage(hexEdgeMaskToFrameIndex[imageKey])); + output.push_back(limitImages->getImage(hexEdgeMaskToFrameIndex.at(imageKey))); } return output; @@ -556,26 +536,6 @@ void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distan rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages); } -void BattleFieldController::flipRangeLimitImagesIntoPositions(std::shared_ptr images) -{ - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRight])->verticalFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::right])->verticalFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRight])->doubleFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeft])->horizontalFlip(); - - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottom])->horizontalFlip(); - - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip(); - - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip(); - - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip(); - images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip(); -} - void BattleFieldController::showHighlightedHexes(Canvas & canvas) { std::vector rangedFullDamageLimitHexes; diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index 359c82ad8..4500e95ab 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -84,9 +84,6 @@ class BattleFieldController : public CIntObject /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, std::vector & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); - /// to reduce the number of source images used, some images will be used as flipped versions of preloaded ones - void flipRangeLimitImagesIntoPositions(std::shared_ptr images); - void showBackground(Canvas & canvas); void showBackgroundImage(Canvas & canvas); void showBackgroundImageWithHexes(Canvas & canvas); diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index f9411a9fb..8309add13 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -35,6 +35,7 @@ #include "../adventureMap/AdventureMapInterface.h" #include "../../CCallback.h" +#include "../../lib/BattleFieldHandler.h" #include "../../lib/CStack.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" @@ -113,6 +114,9 @@ void BattleInterface::playIntroSoundAndUnlockInterface() onIntroSoundPlayed(); }; + auto bfieldType = getBattle()->battleGetBattlefieldType(); + const auto & battlefieldSound = bfieldType.getInfo()->musicFilename; + std::vector battleIntroSounds = { soundBase::battle00, soundBase::battle01, @@ -120,7 +124,13 @@ void BattleInterface::playIntroSoundAndUnlockInterface() soundBase::battle05, soundBase::battle06, soundBase::battle07 }; - int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(battleIntroSounds); + int battleIntroSoundChannel = -1; + + if (!battlefieldSound.empty()) + battleIntroSoundChannel = CCS->soundh->playSound(battlefieldSound); + else + battleIntroSoundChannel = CCS->soundh->playSoundFromSet(battleIntroSounds); + if (battleIntroSoundChannel != -1) { CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed); @@ -144,7 +154,13 @@ void BattleInterface::onIntroSoundPlayed() if (openingPlaying()) openingEnd(); - CCS->musich->playMusicFromSet("battle", true, true); + auto bfieldType = getBattle()->battleGetBattlefieldType(); + const auto & battlefieldMusic = bfieldType.getInfo()->musicFilename; + + if (!battlefieldMusic.empty()) + CCS->musich->playMusic(battlefieldMusic, true, true); + else + CCS->musich->playMusicFromSet("battle", true, true); } void BattleInterface::openingEnd() diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 49e63ebbb..6055cdf5e 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -39,7 +39,6 @@ class Canvas; class BattleResultWindow; class StackQueue; class CPlayerInterface; -class CAnimation; struct BattleEffect; class IImage; class StackQueue; diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index d6e2ca161..3f4ab4838 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -31,6 +31,7 @@ #include "../render/IFont.h" #include "../render/Graphics.h" #include "../widgets/Buttons.h" +#include "../widgets/CComponent.h" #include "../widgets/Images.h" #include "../widgets/Slider.h" #include "../widgets/TextControls.h" @@ -39,9 +40,11 @@ #include "../windows/CMessage.h" #include "../windows/CCreatureWindow.h" #include "../windows/CSpellWindow.h" +#include "../windows/InfoWindows.h" #include "../render/CAnimation.h" #include "../render/IRenderHandler.h" #include "../adventureMap/CInGameConsole.h" +#include "../eventsSDL/InputHandler.h" #include "../../CCallback.h" #include "../../lib/CStack.h" @@ -55,6 +58,8 @@ #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/TextOperations.h" +#include "../../lib/json/JsonUtils.h" + void BattleConsole::showAll(Canvas & to) { @@ -392,8 +397,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her else animationPath = hero->type->heroClass->imageBattleMale; - animation = GH.renderHandler().loadAnimation(animationPath); - animation->preload(); + animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA); pos.w = 64; pos.h = 136; @@ -404,11 +408,10 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her animation->verticalFlip(); if(defender) - flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGR")); + flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGR"), EImageBlitMode::COLORKEY); else - flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGL")); + flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGL"), EImageBlitMode::COLORKEY); - flagAnimation->preload(); flagAnimation->playerColored(hero->tempOwner); switchToNextPhase(); @@ -417,6 +420,79 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her addUsedEvents(TIME); } +QuickSpellPanel::QuickSpellPanel(BattleInterface & owner) + : CIntObject(0), owner(owner) +{ + OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + addUsedEvents(LCLICK | SHOW_POPUP | MOVE); + + pos = Rect(0, 0, 52, 600); + background = std::make_shared(ImagePath::builtin("DIBOXBCK"), pos); + rect = std::make_shared(Rect(0, 0, pos.w + 1, pos.h + 1), ColorRGBA(0, 0, 0, 0), ColorRGBA(241, 216, 120, 255)); + + create(); +} + +void QuickSpellPanel::create() +{ + OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig"); + + labels.clear(); + buttons.clear(); + buttonsDisabled.clear(); + + auto hero = owner.getBattle()->battleGetMyHero(); + if(!hero) + return; + + for(int i = 0; i < 12; i++) { + std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String(); + + SpellID id; + try + { + id = SpellID::decode(spellIdentifier); + } + catch(const IdentifierResolutionException& e) + { + id = SpellID::NONE; + } + + auto button = std::make_shared(Point(2, 7 + 50 * i), AnimationPath::builtin("spellint"), CButton::tooltip(), [this, id, hero](){ + if(id.hasValue() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero)) + { + owner.castThisSpell(id); + } + }); + button->setOverlay(std::make_shared(AnimationPath::builtin("spellint"), !spellIdentifier.empty() ? id.num + 1 : 0)); + button->addPopupCallback([this, i, hero](){ + GH.input().hapticFeedback(); + GH.windows().createAndPushWindow(hero, owner.curInt.get(), true, [this, i](SpellID spell){ + Settings configID = persistentStorage.write["quickSpell"][std::to_string(i)]; + configID->String() = spell.toSpell()->identifier; + create(); + }); + }); + + if(!id.hasValue() || !id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero)) + { + buttonsDisabled.push_back(std::make_shared(Rect(2, 7 + 50 * i, 48, 36), ColorRGBA(0, 0, 0, 172))); + } + labels.push_back(std::make_shared(7, 10 + 50 * i, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, config["keyboard"]["battleSpellShortcut" + std::to_string(i)].String())); + + buttons.push_back(button); + } +} + +void QuickSpellPanel::show(Canvas & to) +{ + showAll(to); + CIntObject::show(to); +} + HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * position, bool initializeBackground) : CIntObject(0) { @@ -427,8 +503,7 @@ HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * posit if(initializeBackground) { background = std::make_shared(ImagePath::builtin("CHRPOP")); - background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); - background->colorize(hero.owner); + background->setPlayerColor(hero.owner); } initializeData(hero); @@ -495,11 +570,9 @@ StackInfoBasicPanel::StackInfoBasicPanel(const CStack * stack, bool initializeBa { background = std::make_shared(ImagePath::builtin("CCRPOP")); background->pos.y += 37; - background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); - background->colorize(stack->getOwner()); + background->setPlayerColor(stack->getOwner()); background2 = std::make_shared(ImagePath::builtin("CHRPOP")); - background2->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); - background2->colorize(stack->getOwner()); + background2->setPlayerColor(stack->getOwner()); } initializeData(stack); @@ -603,7 +676,7 @@ HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position) if (position != nullptr) moveTo(*position); - background->colorize(hero.owner); //maybe add this functionality to base class? + background->setPlayerColor(hero.owner); //maybe add this functionality to base class? content = std::make_shared(hero, nullptr, false); } @@ -614,7 +687,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); background = std::make_shared(ImagePath::builtin("CPRESULT")); - background->colorize(owner.playerID); + background->setPlayerColor(owner.playerID); pos = center(background->pos); exit = std::make_shared(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT); @@ -741,7 +814,7 @@ BattleResultResources BattleResultWindow::getResources(const BattleResult & br) else { resources.musicName = AudioPath::builtin("Music/Win Battle"); - resources.prologueVideo = VideoPath(); + resources.prologueVideo = VideoPath::builtin("WIN3.BIK"); resources.loopedVideo = VideoPath::builtin("WIN3.BIK"); } @@ -856,9 +929,6 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner) pos.h = 49; pos.x += parent->pos.w/2 - pos.w/2; pos.y += queueSmallOutside ? -queueSmallOutsideYOffset : 10; - - icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL")); - stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL")); } else { @@ -868,13 +938,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner) pos.y -= pos.h; background = std::make_shared(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h)); - - icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("TWCRPORT")); - stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL")); - //TODO: where use big icons? - //stateIcons = GH.renderHandler().loadAnimation("VCMI/BATTLEQUEUE/STATESBIG"); } - stateIcons->preload(); stackBoxes.resize(queueSize); for (int i = 0; i < stackBoxes.size(); i++) @@ -943,13 +1007,13 @@ StackQueue::StackBox::StackBox(StackQueue * owner): if(owner->embedded) { - icon = std::make_shared(owner->icons, 0, 0, 5, 2); + icon = std::make_shared(AnimationPath::builtin("CPRSMALL"), 0, 0, 5, 2); amount = std::make_shared(pos.w/2, pos.h - 7, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); roundRect = std::make_shared(Rect(0, 0, 2, 48), ColorRGBA(0, 0, 0, 255), ColorRGBA(0, 255, 0, 255)); } else { - icon = std::make_shared(owner->icons, 0, 0, 9, 1); + icon = std::make_shared(AnimationPath::builtin("TWCRPORT"), 0, 0, 9, 1); amount = std::make_shared(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE); roundRect = std::make_shared(Rect(0, 0, 15, 18), ColorRGBA(0, 0, 0, 255), ColorRGBA(241, 216, 120, 255)); round = std::make_shared(4, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); @@ -957,7 +1021,7 @@ StackQueue::StackBox::StackBox(StackQueue * owner): int icon_x = pos.w - 17; int icon_y = pos.h - 18; - stateIcon = std::make_shared(owner->stateIcons, 0, 0, icon_x, icon_y); + stateIcon = std::make_shared(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"), 0, 0, icon_x, icon_y); stateIcon->visible = false; } roundRect->disable(); @@ -968,7 +1032,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std:: if(unit) { boundUnitID = unit->unitId(); - background->colorize(unit->unitOwner()); + background->setPlayerColor(unit->unitOwner()); icon->visible = true; // temporary code for mod compatibility: @@ -1014,7 +1078,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std:: else { boundUnitID = std::nullopt; - background->colorize(PlayerColor::NEUTRAL); + background->setPlayerColor(PlayerColor::NEUTRAL); icon->visible = false; icon->setFrame(0); amount->setText(""); diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index c385db754..634c66567 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -22,6 +22,7 @@ class CGHeroInstance; struct BattleResult; struct InfoAboutHero; class CStack; +class CPlayerBattleCallback; namespace battle { @@ -44,6 +45,7 @@ class TransparentFilledRectangle; class CPlayerInterface; class BattleRenderer; class VideoWidget; +class QuickSpellPanel; /// Class which shows the console at the bottom of the battle screen and manages the text of the console class BattleConsole : public CIntObject, public IStatusBar @@ -147,6 +149,26 @@ public: BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender); }; +class QuickSpellPanel : public CIntObject +{ +private: + std::shared_ptr background; + std::shared_ptr rect; + std::vector> buttons; + std::vector> buttonsDisabled; + std::vector> labels; + + BattleInterface & owner; +public: + bool isEnabled; // isActive() is not working on multiple conditions, because of this we need a seperate flag + + QuickSpellPanel(BattleInterface & owner); + + void create(); + + void show(Canvas & to) override; +}; + class HeroInfoBasicPanel : public CIntObject //extracted from InfoWindow to fit better as non-popup embed element { private: @@ -252,9 +274,6 @@ class StackQueue : public CIntObject std::vector> stackBoxes; BattleInterface & owner; - std::shared_ptr icons; - std::shared_ptr stateIcons; - int32_t getSiegeShooterIconID(); public: const bool embedded; diff --git a/client/battle/BattleObstacleController.cpp b/client/battle/BattleObstacleController.cpp index 2d7fbdbb7..031d40ee9 100644 --- a/client/battle/BattleObstacleController.cpp +++ b/client/battle/BattleObstacleController.cpp @@ -21,6 +21,7 @@ #include "../CPlayerInterface.h" #include "../gui/CGuiHandler.h" #include "../media/ISoundPlayer.h" +#include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/IRenderHandler.h" @@ -46,24 +47,16 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi) { AnimationPath animationName = oi.getAnimation(); - if (animationsCache.count(animationName) == 0) + if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) { - if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) - { - // obstacle uses single bitmap image for animations - auto animation = GH.renderHandler().createAnimation(); - animation->setCustom(animationName.getName(), 0, 0); - animationsCache[animationName] = animation; - animation->preload(); - } - else - { - auto animation = GH.renderHandler().loadAnimation(animationName); - animationsCache[animationName] = animation; - animation->preload(); - } + // obstacle uses single bitmap image for animations + obstacleImages[oi.uniqueID] = GH.renderHandler().loadImage(animationName.toType(), EImageBlitMode::COLORKEY); + } + else + { + obstacleAnimations[oi.uniqueID] = GH.renderHandler().loadAnimation(animationName, EImageBlitMode::COLORKEY); + obstacleImages[oi.uniqueID] = obstacleAnimations[oi.uniqueID]->getImage(0); } - obstacleAnimations[oi.uniqueID] = animationsCache[animationName]; } void BattleObstacleController::obstacleRemoved(const std::vector & obstacles) @@ -85,9 +78,7 @@ void BattleObstacleController::obstacleRemoved(const std::vectorpreload(); - + auto animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::COLORKEY); auto first = animation->getImage(0, 0); if(!first) continue; @@ -99,6 +90,7 @@ void BattleObstacleController::obstacleRemoved(const std::vectoraddNewAnim(new EffectAnimation(owner, animationPath, whereTo, obstacle["position"].Integer(), 0, true)); obstacleAnimations.erase(oi.id); + obstacleImages.erase(oi.id); //so when multiple obstacles are removed, they show up one after another owner.waitForAnimations(); } @@ -113,9 +105,7 @@ void BattleObstacleController::obstaclePlaced(const std::vectorvisibleForSide(side.value(), owner.getBattle()->battleHasNativeStack(side.value()))) continue; - auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation()); - animation->preload(); - + auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation(), EImageBlitMode::ALPHA); auto first = animation->getImage(0, 0); if(!first) continue; @@ -187,26 +177,22 @@ void BattleObstacleController::collectRenderableObjects(BattleRenderer & rendere void BattleObstacleController::tick(uint32_t msPassed) { timePassed += msPassed / 1000.f; + int framesCount = timePassed * AnimationControls::getObstaclesSpeed(); + + for(auto & animation : obstacleAnimations) + { + int frameIndex = framesCount % animation.second->size(0); + obstacleImages[animation.first] = animation.second->getImage(frameIndex, 0); + } } std::shared_ptr BattleObstacleController::getObstacleImage(const CObstacleInstance & oi) { - int framesCount = timePassed * AnimationControls::getObstaclesSpeed(); - std::shared_ptr animation; - // obstacle is not loaded yet, don't show anything - if (obstacleAnimations.count(oi.uniqueID) == 0) + if (obstacleImages.count(oi.uniqueID) == 0) return nullptr; - animation = obstacleAnimations[oi.uniqueID]; - assert(animation); - - if(animation) - { - int frameIndex = framesCount % animation->size(0); - return animation->getImage(frameIndex, 0); - } - return nullptr; + return obstacleImages[oi.uniqueID]; } Point BattleObstacleController::getObstaclePosition(std::shared_ptr image, const CObstacleInstance & obstacle) diff --git a/client/battle/BattleObstacleController.h b/client/battle/BattleObstacleController.h index c4a7467a4..39119cf32 100644 --- a/client/battle/BattleObstacleController.h +++ b/client/battle/BattleObstacleController.h @@ -36,12 +36,12 @@ class BattleObstacleController /// total time, in seconds, since start of battle. Used for animating obstacles float timePassed; - /// cached animations of all obstacles in current battle - std::map> animationsCache; - /// list of all obstacles that are currently being rendered std::map> obstacleAnimations; + /// Current images for all present obstacles + std::map> obstacleImages; + void loadObstacleImage(const CObstacleInstance & oi); std::shared_ptr getObstacleImage(const CObstacleInstance & oi); diff --git a/client/battle/BattleProjectileController.cpp b/client/battle/BattleProjectileController.cpp index 1a8117baa..694ab4e0f 100644 --- a/client/battle/BattleProjectileController.cpp +++ b/client/battle/BattleProjectileController.cpp @@ -15,6 +15,7 @@ #include "BattleStacksController.h" #include "CreatureAnimation.h" +#include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/IRenderHandler.h" #include "../gui/CGuiHandler.h" @@ -191,8 +192,7 @@ void BattleProjectileController::initStackProjectile(const CStack * stack) std::shared_ptr BattleProjectileController::createProjectileImage(const AnimationPath & path ) { - std::shared_ptr projectile = GH.renderHandler().loadAnimation(path); - projectile->preload(); + std::shared_ptr projectile = GH.renderHandler().loadAnimation(path, EImageBlitMode::COLORKEY); if(projectile->size(1) != 0) logAnim->error("Expected empty group 1 in stack projectile"); diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 0d1321b0d..05923b2e9 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -182,7 +182,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo if ( !getWallPieceExistence(EWallVisual::EWallVisual(g)) ) continue; - wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED)); + wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED), EImageBlitMode::COLORKEY); } } @@ -248,7 +248,7 @@ void BattleSiegeController::gateStateChanged(const EGateState state) wallPieceImages[EWallVisual::GATE] = nullptr; if (stateId != EWallState::NONE) - wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE, stateId)); + wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE, stateId), EImageBlitMode::COLORKEY); if (playSound) CCS->soundh->playSound(soundBase::DRAWBRG); @@ -357,7 +357,7 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) auto wallState = EWallState(owner.getBattle()->battleGetWallState(attackInfo.attackedPart)); - wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState)); + wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState), EImageBlitMode::COLORKEY); } } diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index c1a21e044..0f90f357b 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -45,9 +45,9 @@ #include "../../lib/CPlayerState.h" #include "../windows/settings/SettingsMainWindow.h" -BattleWindow::BattleWindow(BattleInterface & owner): - owner(owner), - defaultAction(PossiblePlayerBattleAction::INVALID) +BattleWindow::BattleWindow(BattleInterface & Owner): + owner(Owner), + lastAlternativeAction(PossiblePlayerBattleAction::INVALID) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; pos.w = 800; @@ -64,6 +64,20 @@ BattleWindow::BattleWindow(BattleInterface & owner): const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json")); + addShortcut(EShortcut::BATTLE_TOGGLE_QUICKSPELL, [this](){ this->toggleStickyQuickSpellVisibility();}); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_0, [this](){ useSpellIfPossible(0); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_1, [this](){ useSpellIfPossible(1); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_2, [this](){ useSpellIfPossible(2); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_3, [this](){ useSpellIfPossible(3); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_4, [this](){ useSpellIfPossible(4); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_5, [this](){ useSpellIfPossible(5); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_6, [this](){ useSpellIfPossible(6); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_7, [this](){ useSpellIfPossible(7); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_8, [this](){ useSpellIfPossible(8); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_9, [this](){ useSpellIfPossible(9); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_10, [this](){ useSpellIfPossible(10); }); + addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_11, [this](){ useSpellIfPossible(11); }); + addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this)); addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this)); addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this)); @@ -95,6 +109,7 @@ BattleWindow::BattleWindow(BattleInterface & owner): owner.fieldController->createHeroes(); createQueue(); + createQuickSpellWindow(); createStickyHeroInfoWindows(); createTimerInfoWindows(); @@ -164,10 +179,67 @@ void BattleWindow::createStickyHeroInfoWindows() setPositionInfoWindow(); } +void BattleWindow::createQuickSpellWindow() +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + quickSpellWindow = std::make_shared(owner); + quickSpellWindow->moveTo(Point(pos.x - 67, pos.y)); + + if(settings["battle"]["enableQuickSpellPanel"].Bool()) + showStickyQuickSpellWindow(); + else + hideStickyQuickSpellWindow(); +} + +void BattleWindow::toggleStickyQuickSpellVisibility() +{ + if(settings["battle"]["enableQuickSpellPanel"].Bool()) + hideStickyQuickSpellWindow(); + else + showStickyQuickSpellWindow(); +} + +void BattleWindow::hideStickyQuickSpellWindow() +{ + Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"]; + showStickyQuickSpellWindow->Bool() = false; + + quickSpellWindow->disable(); + quickSpellWindow->isEnabled = false; + + setPositionInfoWindow(); + createTimerInfoWindows(); + GH.windows().totalRedraw(); +} + +void BattleWindow::showStickyQuickSpellWindow() +{ + Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"]; + showStickyQuickSpellWindow->Bool() = true; + + if(GH.screenDimensions().x >= 1050) + { + quickSpellWindow->enable(); + quickSpellWindow->isEnabled = true; + } + else + { + quickSpellWindow->disable(); + quickSpellWindow->isEnabled = false; + } + + setPositionInfoWindow(); + createTimerInfoWindows(); + GH.windows().totalRedraw(); +} + void BattleWindow::createTimerInfoWindows() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0; + if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || LOCPLINT->cb->getStartInfo()->turnTimerInfo.unitTimer != 0) { PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER); @@ -176,7 +248,7 @@ void BattleWindow::createTimerInfoWindows() if (attacker.isValidPlayer()) { if (GH.screenDimensions().x >= 1000) - attackerTimerWidget = std::make_shared(Point(-92, 1), attacker); + attackerTimerWidget = std::make_shared(Point(-92 + xOffsetAttacker, 1), attacker); else attackerTimerWidget = std::make_shared(Point(1, 135), attacker); } @@ -199,6 +271,24 @@ std::shared_ptr BattleWindow::buildBattleConsole(const JsonNode & return std::make_shared(owner, background, rect.topLeft(), offset, rect.dimensions() ); } +void BattleWindow::useSpellIfPossible(int slot) +{ + std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(slot)].String(); + SpellID id; + try + { + id = SpellID::decode(spellIdentifier); + } + catch(const IdentifierResolutionException& e) + { + return; + } + if(id.hasValue() && owner.getBattle()->battleGetMyHero() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, owner.getBattle()->battleGetMyHero())) + { + owner.castThisSpell(id); + } +}; + void BattleWindow::toggleQueueVisibility() { if(settings["battle"]["showQueue"].Bool()) @@ -283,10 +373,12 @@ void BattleWindow::showStickyHeroWindows() void BattleWindow::updateQueue() { queue->update(); + createQuickSpellWindow(); } void BattleWindow::setPositionInfoWindow() { + int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0; if(defenderHeroWindow) { Point position = (GH.screenDimensions().x >= 1000) @@ -297,7 +389,7 @@ void BattleWindow::setPositionInfoWindow() if(attackerHeroWindow) { Point position = (GH.screenDimensions().x >= 1000) - ? Point(pos.x - 93, pos.y + 60) + ? Point(pos.x - 93 + xOffsetAttacker, pos.y + 60) : Point(pos.x + 1, pos.y + 195); attackerHeroWindow->moveTo(position); } @@ -311,7 +403,7 @@ void BattleWindow::setPositionInfoWindow() if(attackerStackWindow) { Point position = (GH.screenDimensions().x >= 1000) - ? Point(pos.x - 93, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60) + ? Point(pos.x - 93 + xOffsetAttacker, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60) : Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195); attackerStackWindow->moveTo(position); } @@ -346,6 +438,7 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack) attackerStackWindow = nullptr; setPositionInfoWindow(); + createTimerInfoWindows(); } void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero) @@ -567,14 +660,18 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action) void BattleWindow::setAlternativeActions(const std::list & actions) { + assert(actions.size() != 1); + alternativeActions = actions; - defaultAction = PossiblePlayerBattleAction::INVALID; + lastAlternativeAction = PossiblePlayerBattleAction::INVALID; + if(alternativeActions.size() > 1) - defaultAction = alternativeActions.back(); - if(!alternativeActions.empty()) + { + lastAlternativeAction = alternativeActions.back(); showAlternativeActionIcon(alternativeActions.front()); + } else - showAlternativeActionIcon(defaultAction); + showAlternativeActionIcon(PossiblePlayerBattleAction::INVALID); } void BattleWindow::bAutofightf() @@ -669,23 +766,18 @@ void BattleWindow::bSwitchActionf() { if(alternativeActions.empty()) return; - - if(alternativeActions.front() == defaultAction) - { - alternativeActions.push_back(alternativeActions.front()); - alternativeActions.pop_front(); - } - + auto actions = owner.actionsController->getPossibleActions(); - if(!actions.empty() && actions.front() == alternativeActions.front()) + + if(!actions.empty() && actions.front() != lastAlternativeAction) { owner.actionsController->removePossibleAction(alternativeActions.front()); - showAlternativeActionIcon(defaultAction); + showAlternativeActionIcon(*std::next(alternativeActions.begin())); } else { - owner.actionsController->pushFrontPossibleAction(alternativeActions.front()); - showAlternativeActionIcon(alternativeActions.front()); + owner.actionsController->resetCurrentStackPossibleActions(); + showAlternativeActionIcon(owner.actionsController->getPossibleActions().front()); } alternativeActions.push_back(alternativeActions.front()); @@ -766,6 +858,8 @@ void BattleWindow::blockUI(bool on) setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode); setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode); setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode); + + quickSpellWindow->setInputEnabled(!on); } void BattleWindow::bOpenActiveUnit() diff --git a/client/battle/BattleWindow.h b/client/battle/BattleWindow.h index 5369cca9d..e9246179b 100644 --- a/client/battle/BattleWindow.h +++ b/client/battle/BattleWindow.h @@ -27,6 +27,7 @@ class StackQueue; class TurnTimerWidget; class HeroInfoBasicPanel; class StackInfoBasicPanel; +class QuickSpellPanel; /// GUI object that handles functionality of panel at the bottom of combat screen class BattleWindow : public InterfaceObjectConfigurable @@ -40,6 +41,8 @@ class BattleWindow : public InterfaceObjectConfigurable std::shared_ptr attackerStackWindow; std::shared_ptr defenderStackWindow; + std::shared_ptr quickSpellWindow; + std::shared_ptr attackerTimerWidget; std::shared_ptr defenderTimerWidget; @@ -65,15 +68,19 @@ class BattleWindow : public InterfaceObjectConfigurable /// management of alternative actions std::list alternativeActions; - PossiblePlayerBattleAction defaultAction; + PossiblePlayerBattleAction lastAlternativeAction; void showAlternativeActionIcon(PossiblePlayerBattleAction); + void useSpellIfPossible(int slot); + /// flip battle queue visibility to opposite void toggleQueueVisibility(); void createQueue(); void toggleStickyHeroWindowsVisibility(); + void toggleStickyQuickSpellVisibility(); void createStickyHeroInfoWindows(); + void createQuickSpellWindow(); void createTimerInfoWindows(); std::shared_ptr buildBattleConsole(const JsonNode &) const; @@ -94,6 +101,10 @@ public: void hideStickyHeroWindows(); void showStickyHeroWindows(); + /// Toggle permanent quickspell windows visibility + void hideStickyQuickSpellWindow(); + void showStickyQuickSpellWindow(); + /// Event handler for netpack changing hero mana points void heroManaPointsChanged(const CGHeroInstance * hero); diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index 53bca89a4..8fcfd1aeb 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -14,6 +14,7 @@ #include "../../lib/CCreatureHandler.h" #include "../gui/CGuiHandler.h" +#include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/ColorFilter.h" #include "../render/IRenderHandler.h" @@ -198,12 +199,8 @@ CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedControll speedController(controller), once(false) { - forward = GH.renderHandler().loadAnimation(name_); - reverse = GH.renderHandler().loadAnimation(name_); - - //todo: optimize - forward->preload(); - reverse->preload(); + forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA); + reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA); // if necessary, add one frame into vcmi-only group DEAD if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0) diff --git a/client/battle/CreatureAnimation.h b/client/battle/CreatureAnimation.h index 686e941b5..46d4d87da 100644 --- a/client/battle/CreatureAnimation.h +++ b/client/battle/CreatureAnimation.h @@ -12,7 +12,6 @@ #include "../../lib/FunctionList.h" #include "../../lib/Color.h" #include "../widgets/Images.h" -#include "../render/CAnimation.h" #include "../render/IImage.h" class CIntObject; diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 9b58d782f..018af2334 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -43,7 +43,7 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr"); if(json["type"].String() == "accountCreated") return receiveAccountCreated(json); diff --git a/client/globalLobby/GlobalLobbyInviteWindow.cpp b/client/globalLobby/GlobalLobbyInviteWindow.cpp index ec436fea7..fb63d5b5f 100644 --- a/client/globalLobby/GlobalLobbyInviteWindow.cpp +++ b/client/globalLobby/GlobalLobbyInviteWindow.cpp @@ -79,7 +79,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow() pos.h = 420; filledBackground = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); - filledBackground->playerColored(PlayerColor(1)); + filledBackground->setPlayerColor(PlayerColor(1)); labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.invite.header").toString() ); diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index 48748548e..06718961e 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -70,7 +70,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() else toggleMode->setSelected(1); - filledBackground->playerColored(PlayerColor(1)); + filledBackground->setPlayerColor(PlayerColor(1)); inputUsername->setCallback([this](const std::string & text) { this->buttonLogin->block(text.empty()); diff --git a/client/globalLobby/GlobalLobbyRoomWindow.cpp b/client/globalLobby/GlobalLobbyRoomWindow.cpp index 06b98d5bb..b1803f0a4 100644 --- a/client/globalLobby/GlobalLobbyRoomWindow.cpp +++ b/client/globalLobby/GlobalLobbyRoomWindow.cpp @@ -188,7 +188,7 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s modListTitle = std::make_shared( 182, 59, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.mods").toString()); buttonJoin->block(!errorMessage.empty()); - filledBackground->playerColored(PlayerColor(1)); + filledBackground->setPlayerColor(PlayerColor(1)); center(); } diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp index b23ff2a9a..ad55222d5 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.cpp +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -78,7 +78,7 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup() buttonCreate = std::make_shared(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); }, EShortcut::GLOBAL_ACCEPT); buttonClose = std::make_shared(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }, EShortcut::GLOBAL_CANCEL); - filledBackground->playerColored(PlayerColor(1)); + filledBackground->setPlayerColor(PlayerColor(1)); updateDescription(); center(); diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 06b484376..a2afaea54 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -165,6 +165,15 @@ public: virtual void updateGarrisons() = 0; }; +class IMarketHolder +{ +public: + virtual void updateResources() {}; + virtual void updateExperience() {}; + virtual void updateSecondarySkills() {}; + virtual void updateArtifacts() {}; +}; + class ITownHolder { public: diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 2052ea2c9..146f27b9c 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -47,15 +47,12 @@ CursorHandler::CursorHandler() cursors = { - GH.renderHandler().loadAnimation(AnimationPath::builtin("CRADVNTR")), - GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT")), - GH.renderHandler().loadAnimation(AnimationPath::builtin("CRDEFLT")), - GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL")) + GH.renderHandler().loadAnimation(AnimationPath::builtin("CRADVNTR"), EImageBlitMode::COLORKEY), + GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"), EImageBlitMode::COLORKEY), + GH.renderHandler().loadAnimation(AnimationPath::builtin("CRDEFLT"), EImageBlitMode::COLORKEY), + GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"), EImageBlitMode::COLORKEY) }; - for (auto & cursor : cursors) - cursor->preload(); - set(Cursor::Map::POINTER); showType = dynamic_cast(cursor.get()) ? Cursor::ShowType::SOFTWARE : Cursor::ShowType::HARDWARE; } @@ -104,8 +101,7 @@ void CursorHandler::dragAndDropCursor(std::shared_ptr image) void CursorHandler::dragAndDropCursor (const AnimationPath & path, size_t index) { - auto anim = GH.renderHandler().loadAnimation(path); - anim->load(index); + auto anim = GH.renderHandler().loadAnimation(path, EImageBlitMode::COLORKEY); dragAndDropCursor(anim->getImage(index)); } diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 5bcf45e17..bf9736cde 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -333,7 +333,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildPicture(const JsonNo auto pic = std::make_shared(image, position.x, position.y); if ( config["playerColored"].Bool() && LOCPLINT) - pic->colorize(LOCPLINT->playerID); + pic->setPlayerColor(LOCPLINT->playerID); return pic; } @@ -571,7 +571,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildTexture(const if(playerColor.isValidPlayer()) { auto result = std::make_shared(image, rect); - result->playerColored(playerColor); + result->setPlayerColor(playerColor); return result; } return std::make_shared(image, rect); diff --git a/client/gui/Shortcut.h b/client/gui/Shortcut.h index ebe89e4e2..aedf695d9 100644 --- a/client/gui/Shortcut.h +++ b/client/gui/Shortcut.h @@ -186,6 +186,19 @@ enum class EShortcut BATTLE_TOGGLE_HEROES_STATS, BATTLE_OPEN_ACTIVE_UNIT, BATTLE_OPEN_HOVERED_UNIT, + BATTLE_TOGGLE_QUICKSPELL, + BATTLE_SPELL_SHORTCUT_0, + BATTLE_SPELL_SHORTCUT_1, + BATTLE_SPELL_SHORTCUT_2, + BATTLE_SPELL_SHORTCUT_3, + BATTLE_SPELL_SHORTCUT_4, + BATTLE_SPELL_SHORTCUT_5, + BATTLE_SPELL_SHORTCUT_6, + BATTLE_SPELL_SHORTCUT_7, + BATTLE_SPELL_SHORTCUT_8, + BATTLE_SPELL_SHORTCUT_9, + BATTLE_SPELL_SHORTCUT_10, + BATTLE_SPELL_SHORTCUT_11, MARKET_DEAL, MARKET_MAX_AMOUNT, diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index 3a5033882..259a1c6cb 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -222,6 +222,19 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT }, {"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END }, {"battleSelectAction", EShortcut::BATTLE_SELECT_ACTION }, + {"battleToggleQuickSpell", EShortcut::BATTLE_TOGGLE_QUICKSPELL }, + {"battleSpellShortcut0", EShortcut::BATTLE_SPELL_SHORTCUT_0 }, + {"battleSpellShortcut1", EShortcut::BATTLE_SPELL_SHORTCUT_1 }, + {"battleSpellShortcut2", EShortcut::BATTLE_SPELL_SHORTCUT_2 }, + {"battleSpellShortcut3", EShortcut::BATTLE_SPELL_SHORTCUT_3 }, + {"battleSpellShortcut4", EShortcut::BATTLE_SPELL_SHORTCUT_4 }, + {"battleSpellShortcut5", EShortcut::BATTLE_SPELL_SHORTCUT_5 }, + {"battleSpellShortcut6", EShortcut::BATTLE_SPELL_SHORTCUT_6 }, + {"battleSpellShortcut7", EShortcut::BATTLE_SPELL_SHORTCUT_7 }, + {"battleSpellShortcut8", EShortcut::BATTLE_SPELL_SHORTCUT_8 }, + {"battleSpellShortcut9", EShortcut::BATTLE_SPELL_SHORTCUT_9 }, + {"battleSpellShortcut10", EShortcut::BATTLE_SPELL_SHORTCUT_10 }, + {"battleSpellShortcut11", EShortcut::BATTLE_SPELL_SHORTCUT_11 }, {"spectateTrackHero", EShortcut::SPECTATE_TRACK_HERO }, {"spectateSkipBattle", EShortcut::SPECTATE_SKIP_BATTLE }, {"spectateSkipBattleResult", EShortcut::SPECTATE_SKIP_BATTLE_RESULT }, diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index b7ea0a076..04a77fc7c 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -401,7 +401,7 @@ PvPBox::PvPBox(const Rect & rect) setRedrawParent(true); backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, rect.w, rect.h)); - backgroundTexture->playerColored(PlayerColor(1)); + backgroundTexture->setPlayerColor(PlayerColor(1)); backgroundBorder = std::make_shared(Rect(0, 0, rect.w, rect.h), ColorRGBA(0, 0, 0, 64), ColorRGBA(96, 96, 96, 255), 1); townSelector = std::make_shared(Point(5, 3)); @@ -515,9 +515,6 @@ CFlagBox::CFlagBox(const Rect & rect) labelAllies = std::make_shared(0, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":"); labelEnemies = std::make_shared(133, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":"); - - iconsTeamFlags = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITGFLAGS.DEF")); - iconsTeamFlags->preload(); } void CFlagBox::recreate() @@ -529,7 +526,7 @@ void CFlagBox::recreate() const int enemiesX = 5 + 133 + (int)labelEnemies->getWidth(); for(auto i = CSH->si->playerInfos.cbegin(); i != CSH->si->playerInfos.cend(); i++) { - auto flag = std::make_shared(iconsTeamFlags, i->first.getNum(), 0); + auto flag = std::make_shared(AnimationPath::builtin("ITGFLAGS.DEF"), i->first.getNum(), 0); if(i->first == CSH->myFirstColor() || CSH->getPlayerTeamId(i->first) == CSH->getPlayerTeamId(CSH->myFirstColor())) { flag->moveTo(Point(pos.x + alliesX + (int)flagsAllies.size()*flag->pos.w, pos.y)); @@ -546,10 +543,10 @@ void CFlagBox::recreate() void CFlagBox::showPopupWindow(const Point & cursorPosition) { if(SEL->getMapInfo()) - GH.windows().createAndPushWindow(iconsTeamFlags); + GH.windows().createAndPushWindow(); } -CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr icons) +CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox() : CWindowObject(BORDERED | RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("DIBOXBCK")) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; @@ -577,7 +574,7 @@ CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr ico int curx = 128 - 9 * team.size(); for(const auto & player : team) { - iconsFlags.push_back(std::make_shared(icons, player, 0, curx, 75 + 50 * curIdx)); + iconsFlags.push_back(std::make_shared(AnimationPath::builtin("ITGFLAGS.DEF"), player, 0, curx, 75 + 50 * curIdx)); curx += 18; } ++curIdx; diff --git a/client/lobby/CSelectionBase.h b/client/lobby/CSelectionBase.h index 3f545892f..2e0e870e2 100644 --- a/client/lobby/CSelectionBase.h +++ b/client/lobby/CSelectionBase.h @@ -174,7 +174,6 @@ public: class CFlagBox : public CIntObject { - std::shared_ptr iconsTeamFlags; std::shared_ptr labelAllies; std::shared_ptr labelEnemies; std::vector> flagsAllies; @@ -192,7 +191,7 @@ public: std::shared_ptr labelGroupTeams; std::vector> iconsFlags; public: - CFlagBoxTooltipBox(std::shared_ptr icons); + CFlagBoxTooltipBox(); }; }; diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 49633bdd1..d8cd1f273 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -518,9 +518,8 @@ void OptionsTab::SelectionWindow::recreate(int sliderPos) int sliderWidth = ((amountLines > MAX_LINES) ? 16 : 0); pos = Rect(pos.x, pos.y, x + sliderWidth, y); - backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w - sliderWidth, pos.h)); - backgroundTexture->playerColored(PlayerColor(1)); + backgroundTexture->setPlayerColor(PlayerColor(1)); updateShadow(); if(type == SelType::TOWN) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index fdf0807f9..c65944237 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -227,11 +227,8 @@ SelectionTab::SelectionTab(ESelectionScreen Type) buttonsSortBy.push_back(sortByDate); } - iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF")); - iconsVictoryCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRVICT.DEF")); - iconsLossCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRLOSS.DEF")); for(int i = 0; i < positionsToShow; i++) - listItems.push_back(std::make_shared(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition)); + listItems.push_back(std::make_shared(Point(30, 129 + i * 25))); labelTabTitle = std::make_shared(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle); slider = std::make_shared(Point(372, 86 + (enableUiEnhancements ? 30 : 0)), (tabType != ESelectionScreen::saveGame ? 480 : 430) - (enableUiEnhancements ? 30 : 0), std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, Orientation::VERTICAL, CSlider::BLUE); @@ -883,11 +880,11 @@ std::unordered_set SelectionTab::getFiles(std::string dirURI, ERes return ret; } -SelectionTab::ListItem::ListItem(Point position, std::shared_ptr iconsFormats, std::shared_ptr iconsVictory, std::shared_ptr iconsLoss) +SelectionTab::ListItem::ListItem(Point position) : CIntObject(LCLICK, position) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - pictureEmptyLine = std::make_shared(GH.renderHandler().loadImage(ImagePath::builtin("camcust")), Rect(25, 121, 349, 26), -8, -14); + pictureEmptyLine = std::make_shared(ImagePath::builtin("camcust"), Rect(25, 121, 349, 26), -8, -14); labelName = std::make_shared(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "", 185); labelName->setAutoRedraw(false); labelAmountOfPlayers = std::make_shared(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); @@ -898,9 +895,9 @@ SelectionTab::ListItem::ListItem(Point position, std::shared_ptr ico labelMapSizeLetter->setAutoRedraw(false); // FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise iconFolder = std::make_shared(ImagePath::builtin("lobby/iconFolder.png"), -8, -12); - iconFormat = std::make_shared(iconsFormats, 0, 0, 59, -12); - iconVictoryCondition = std::make_shared(iconsVictory, 0, 0, 277, -12); - iconLossCondition = std::make_shared(iconsLoss, 0, 0, 310, -12); + iconFormat = std::make_shared(AnimationPath::builtin("SCSELC.DEF"), 0, 0, 59, -12); + iconVictoryCondition = std::make_shared(AnimationPath::builtin("SCNRVICT.DEF"), 0, 0, 277, -12); + iconLossCondition = std::make_shared(AnimationPath::builtin("SCNRLOSS.DEF"), 0, 0, 310, -12); } void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool selected) diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index e19fa055f..67e39047c 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -20,6 +20,7 @@ class CSlider; class CLabel; class CPicture; class IImage; +class CAnimation; enum ESortBy { @@ -58,7 +59,7 @@ class SelectionTab : public CIntObject std::shared_ptr pictureEmptyLine; std::shared_ptr labelName; - ListItem(Point position, std::shared_ptr iconsFormats, std::shared_ptr iconsVictory, std::shared_ptr iconsLoss); + ListItem(Point position); void updateItem(std::shared_ptr info = {}, bool selected = false); }; std::vector> listItems; diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index 442f92afb..32e421330 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -24,7 +24,6 @@ class CGStatusBar; class CTextBox; class CTabbedInt; class CAnimImage; -class CAnimation; class CButton; class CFilledTexture; class CLabel; diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index b541b30e3..bbd3b6001 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -104,31 +104,29 @@ void MapTileStorage::load(size_t index, const AnimationPath & filename, EImageBl for(auto & entry : terrainAnimations) { if (!filename.empty()) - { - entry = GH.renderHandler().loadAnimation(filename); - entry->preload(); - } - else - entry = GH.renderHandler().createAnimation(); - - for(size_t i = 0; i < entry->size(); ++i) - entry->getImage(i)->setBlitMode(blitMode); + entry = GH.renderHandler().loadAnimation(filename, blitMode); } - for(size_t i = 0; i < terrainAnimations[0]->size(); ++i) - { - terrainAnimations[1]->getImage(i)->verticalFlip(); - terrainAnimations[3]->getImage(i)->verticalFlip(); + if (terrainAnimations[1]) + terrainAnimations[1]->verticalFlip(); - terrainAnimations[2]->getImage(i)->horizontalFlip(); - terrainAnimations[3]->getImage(i)->horizontalFlip(); - } + if (terrainAnimations[3]) + terrainAnimations[3]->verticalFlip(); + + if (terrainAnimations[2]) + terrainAnimations[2]->horizontalFlip(); + + if (terrainAnimations[3]) + terrainAnimations[3]->horizontalFlip(); } std::shared_ptr MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex) { const auto & animation = animations[fileIndex][rotationIndex]; - return animation->getImage(imageIndex); + if (animation) + return animation->getImage(imageIndex); + else + return nullptr; } MapRendererTerrain::MapRendererTerrain() @@ -255,8 +253,7 @@ uint8_t MapRendererRoad::checksum(IMapRendererContext & context, const int3 & co MapRendererBorder::MapRendererBorder() { - animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("EDG")); - animation->preload(); + animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("EDG"), EImageBlitMode::OPAQUE); } size_t MapRendererBorder::getIndexForTile(IMapRendererContext & context, const int3 & tile) @@ -317,13 +314,8 @@ uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 & MapRendererFow::MapRendererFow() { - fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC")); - fogOfWarFullHide->preload(); - fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE")); - fogOfWarPartialHide->preload(); - - for(size_t i = 0; i < fogOfWarFullHide->size(); ++i) - fogOfWarFullHide->getImage(i)->setBlitMode(EImageBlitMode::OPAQUE); + fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"), EImageBlitMode::OPAQUE); + fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::ALPHA); static const std::vector rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27}; @@ -332,8 +324,7 @@ MapRendererFow::MapRendererFow() for(const int rotation : rotations) { fogOfWarPartialHide->duplicateImage(0, rotation, 0); - auto image = fogOfWarPartialHide->getImage(size, 0); - image->verticalFlip(); + fogOfWarPartialHide->verticalFlip(size, 0); size++; } } @@ -408,9 +399,8 @@ std::shared_ptr MapRendererObjects::getAnimation(const AnimationPath if(it != animations.end()) return it->second; - auto ret = GH.renderHandler().loadAnimation(filename); + auto ret = GH.renderHandler().loadAnimation(filename, EImageBlitMode::ALPHA); animations[filename] = ret; - ret->preload(); if(generateMovementGroups) { @@ -630,9 +620,8 @@ uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 & } MapRendererPath::MapRendererPath() - : pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"))) + : pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::ALPHA)) { - pathNodes->preload(); } size_t MapRendererPath::selectImageReachability(bool reachableToday, size_t imageIndex) diff --git a/client/mapView/MapViewCache.cpp b/client/mapView/MapViewCache.cpp index 2bd21ce8d..cb9ff8d81 100644 --- a/client/mapView/MapViewCache.cpp +++ b/client/mapView/MapViewCache.cpp @@ -31,15 +31,11 @@ MapViewCache::MapViewCache(const std::shared_ptr & model) : model(model) , cachedLevel(0) , mapRenderer(new MapRenderer()) - , iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"))) + , iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"), EImageBlitMode::COLORKEY)) , intermediate(new Canvas(Point(32, 32))) , terrain(new Canvas(model->getCacheDimensionsPixels())) , terrainTransition(new Canvas(model->getPixelsVisibleDimensions())) { - iconsStorage->preload(); - for(size_t i = 0; i < iconsStorage->size(); ++i) - iconsStorage->getImage(i)->setBlitMode(EImageBlitMode::COLORKEY); - Point visibleSize = model->getTilesVisibleDimensions(); terrainChecksum.resize(boost::extents[visibleSize.x][visibleSize.y]); tilesUpToDate.resize(boost::extents[visibleSize.x][visibleSize.y]); diff --git a/client/media/CMusicHandler.cpp b/client/media/CMusicHandler.cpp index 0721b9354..913a95ef6 100644 --- a/client/media/CMusicHandler.cpp +++ b/client/media/CMusicHandler.cpp @@ -16,6 +16,7 @@ #include "../renderSDL/SDLRWwrapper.h" #include "../../lib/CRandomGenerator.h" +#include "../../lib/CTownHandler.h" #include "../../lib/TerrainHandler.h" #include "../../lib/filesystem/Filesystem.h" @@ -64,7 +65,17 @@ void CMusicHandler::loadTerrainMusicThemes() { for(const auto & terrain : CGI->terrainTypeHandler->objects) { - addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename); + for(const auto & filename : terrain->musicFilename) + addEntryToSet("terrain_" + terrain->getJsonKey(), filename); + } + + for(const auto & faction : CGI->townh->objects) + { + if (!faction || !faction->hasTown()) + continue; + + for(const auto & filename : faction->town->clientInfo.musicTheme) + addEntryToSet("faction_" + faction->getJsonKey(), filename); } } diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index e8537cf14..d32da36bd 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -494,7 +494,11 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide if (!openInput(videoToOpen)) return { nullptr, 0}; openContext(); - openCodec(findAudioStream()); + + int audioStreamIndex = findAudioStream(); + if (audioStreamIndex == -1) + return { nullptr, 0}; + openCodec(audioStreamIndex); const auto * codecpar = getCodecParameters(); @@ -547,7 +551,7 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide ui16 NumOfChan = 2; ui32 SamplesPerSec = 22050; ui32 bytesPerSec = 22050 * 2; - ui16 blockAlign = 2; + ui16 blockAlign = 1; ui16 bitsPerSample = 32; ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'}; ui32 Subchunk2Size; @@ -582,7 +586,7 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po return true; instance.openVideo(); - instance.prepareOutput(scale, useOverlay); + instance.prepareOutput(scale, true); auto lastTimePoint = boost::chrono::steady_clock::now(); @@ -604,10 +608,7 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po rect.w = instance.dimensions.x; rect.h = instance.dimensions.y; - if(useOverlay) - SDL_RenderFillRect(mainRenderer, &rect); - else - SDL_RenderClear(mainRenderer); + SDL_RenderFillRect(mainRenderer, &rect); if(instance.textureYUV) SDL_RenderCopy(mainRenderer, instance.textureYUV, nullptr, &rect); @@ -623,7 +624,6 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po auto timePointAfterPresent = boost::chrono::steady_clock::now(); auto timeSpentBusy = boost::chrono::duration_cast(timePointAfterPresent - lastTimePoint); - logGlobal->info("Sleeping for %d", (targetFrameTime - timeSpentBusy).count()); if(targetFrameTime > timeSpentBusy) boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); diff --git a/client/render/CAnimation.cpp b/client/render/CAnimation.cpp index 93549f333..514540d79 100644 --- a/client/render/CAnimation.cpp +++ b/client/render/CAnimation.cpp @@ -10,34 +10,12 @@ #include "StdInc.h" #include "CAnimation.h" -#include "CDefFile.h" +#include "../gui/CGuiHandler.h" +#include "../render/IImage.h" +#include "../render/IRenderHandler.h" -#include "Graphics.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/json/JsonUtils.h" -#include "../renderSDL/SDLImage.h" - -std::shared_ptr CAnimation::getFromExtraDef(std::string filename) -{ - size_t pos = filename.find(':'); - if (pos == -1) - return nullptr; - CAnimation anim(AnimationPath::builtinTODO(filename.substr(0, pos))); - pos++; - size_t frame = atoi(filename.c_str()+pos); - size_t group = 0; - pos = filename.find(':', pos); - if (pos != -1) - { - pos++; - group = frame; - frame = atoi(filename.c_str()+pos); - } - anim.load(frame ,group); - auto ret = anim.images[group][frame]; - anim.images.clear(); - return ret; -} bool CAnimation::loadFrame(size_t frame, size_t group) { @@ -47,40 +25,23 @@ bool CAnimation::loadFrame(size_t frame, size_t group) return false; } - auto image = getImage(frame, group, false); + if(auto image = getImageImpl(frame, group, false)) + return true; + + std::shared_ptr image = GH.renderHandler().loadImage(getImageLocator(frame, group), mode); + if(image) { + images[group][frame] = image; return true; } - - //try to get image from def - if(source[group][frame].getType() == JsonNode::JsonType::DATA_NULL) + else { - if(defFile) - { - auto frameList = defFile->getEntries(); - - if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present - { - images[group][frame] = std::make_shared(defFile.get(), frame, group); - return true; - } - } - // still here? image is missing - + // image is missing printError(frame, group, "LoadFrame"); - images[group][frame] = std::make_shared(ImagePath::builtin("DEFAULT"), EImageBlitMode::ALPHA); + images[group][frame] = GH.renderHandler().loadImage(ImagePath::builtin("DEFAULT"), EImageBlitMode::OPAQUE); + return false; } - else //load from separate file - { - auto img = getFromExtraDef(source[group][frame]["file"].String()); - if(!img) - img = std::make_shared(source[group][frame], EImageBlitMode::ALPHA); - - images[group][frame] = img; - return true; - } - return false; } bool CAnimation::unloadFrame(size_t frame, size_t group) @@ -97,45 +58,6 @@ bool CAnimation::unloadFrame(size_t frame, size_t group) return false; } -void CAnimation::initFromJson(const JsonNode & config) -{ - std::string basepath; - basepath = config["basepath"].String(); - - JsonNode base; - base["margins"] = config["margins"]; - base["width"] = config["width"]; - base["height"] = config["height"]; - - for(const JsonNode & group : config["sequences"].Vector()) - { - size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING) - source[groupID].clear(); - - for(const JsonNode & frame : group["frames"].Vector()) - { - JsonNode toAdd; - JsonUtils::inherit(toAdd, base); - toAdd["file"].String() = basepath + frame.String(); - source[groupID].push_back(toAdd); - } - } - - for(const JsonNode & node : config["images"].Vector()) - { - size_t group = node["group"].Integer(); - size_t frame = node["frame"].Integer(); - - if (source[group].size() <= frame) - source[group].resize(frame+1); - - JsonNode toAdd; - JsonUtils::inherit(toAdd, base); - toAdd["file"].String() = basepath + node["file"].String(); - source[group][frame] = toAdd; - } -} - void CAnimation::exportBitmaps(const boost::filesystem::path& path) const { if(images.empty()) @@ -169,109 +91,36 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const logGlobal->info("Exported %d frames to %s", counter, actualPath.string()); } -void CAnimation::init() -{ - if(defFile) - { - const std::map defEntries = defFile->getEntries(); - - for (auto & defEntry : defEntries) - source[defEntry.first].resize(defEntry.second); - } - - if (vstd::contains(graphics->imageLists, name.getName())) - initFromJson(graphics->imageLists[name.getName()]); - - auto jsonResource = name.toType(); - auto configList = CResourceHandler::get()->getResourcesWithName(jsonResource); - - for(auto & loader : configList) - { - auto stream = loader->load(jsonResource); - std::unique_ptr textData(new ui8[stream->getSize()]); - stream->read(textData.get(), stream->getSize()); - - const JsonNode config(reinterpret_cast(textData.get()), stream->getSize()); - - initFromJson(config); - } -} - void CAnimation::printError(size_t frame, size_t group, std::string type) const { logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name.getOriginalName(), group, frame); } -CAnimation::CAnimation(const AnimationPath & Name): +CAnimation::CAnimation(const AnimationPath & Name, std::map > layout, EImageBlitMode mode): name(boost::starts_with(Name.getName(), "SPRITES") ? Name : Name.addPrefix("SPRITES/")), - preloaded(false) + source(layout), + mode(mode) { - if(CResourceHandler::get()->existsResource(name)) - { - try - { - defFile = std::make_shared(name); - } - catch ( const std::runtime_error & e) - { - logAnim->error("Def file %s failed to load! Reason: %s", Name.getOriginalName(), e.what()); - } - } - - init(); - if(source.empty()) logAnim->error("Animation %s failed to load", Name.getOriginalName()); } -CAnimation::CAnimation(): - preloaded(false) -{ - init(); -} - CAnimation::~CAnimation() = default; void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup) { - if(!source.count(sourceGroup)) - { - logAnim->error("Group %d missing in %s", sourceGroup, name.getName()); - return; - } - - if(source[sourceGroup].size() <= sourceFrame) - { - logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name.getName()); - return; - } - - //todo: clone actual loaded Image object - JsonNode clone(source[sourceGroup][sourceFrame]); - - if(clone.getType() == JsonNode::JsonType::DATA_NULL) - { - std::string temp = name.getName()+":"+std::to_string(sourceGroup)+":"+std::to_string(sourceFrame); - clone["file"].String() = temp; - } - + ImageLocator clone(getImageLocator(sourceFrame, sourceGroup)); source[targetGroup].push_back(clone); - - size_t index = source[targetGroup].size() - 1; - - if(preloaded) - load(index, targetGroup); } -void CAnimation::setCustom(std::string filename, size_t frame, size_t group) +std::shared_ptr CAnimation::getImage(size_t frame, size_t group, bool verbose) { - if (source[group].size() <= frame) - source[group].resize(frame+1); - source[group][frame]["file"].String() = filename; - //FIXME: update image if already loaded + if (!loadFrame(frame, group)) + return nullptr; + return getImageImpl(frame, group, verbose); } -std::shared_ptr CAnimation::getImage(size_t frame, size_t group, bool verbose) const +std::shared_ptr CAnimation::getImageImpl(size_t frame, size_t group, bool verbose) { auto groupIter = images.find(group); if (groupIter != images.end()) @@ -285,54 +134,6 @@ std::shared_ptr CAnimation::getImage(size_t frame, size_t group, bool ve return nullptr; } -void CAnimation::load() -{ - for (auto & elem : source) - for (size_t image=0; image < elem.second.size(); image++) - loadFrame(image, elem.first); -} - -void CAnimation::unload() -{ - for (auto & elem : source) - for (size_t image=0; image < elem.second.size(); image++) - unloadFrame(image, elem.first); - -} - -void CAnimation::preload() -{ - if(!preloaded) - { - preloaded = true; - load(); - } -} - -void CAnimation::loadGroup(size_t group) -{ - if (vstd::contains(source, group)) - for (size_t image=0; image < source[group].size(); image++) - loadFrame(image, group); -} - -void CAnimation::unloadGroup(size_t group) -{ - if (vstd::contains(source, group)) - for (size_t image=0; image < source[group].size(); image++) - unloadFrame(image, group); -} - -void CAnimation::load(size_t frame, size_t group) -{ - loadFrame(frame, group); -} - -void CAnimation::unload(size_t frame, size_t group) -{ - unloadFrame(frame, group); -} - size_t CAnimation::size(size_t group) const { auto iter = source.find(group); @@ -343,16 +144,48 @@ size_t CAnimation::size(size_t group) const void CAnimation::horizontalFlip() { - for(auto & group : images) - for(auto & image : group.second) - image.second->horizontalFlip(); + for(auto & group : source) + for(size_t i = 0; i < group.second.size(); ++i) + horizontalFlip(i, group.first); } void CAnimation::verticalFlip() { - for(auto & group : images) - for(auto & image : group.second) - image.second->verticalFlip(); + for(auto & group : source) + for(size_t i = 0; i < group.second.size(); ++i) + verticalFlip(i, group.first); +} + +void CAnimation::horizontalFlip(size_t frame, size_t group) +{ + try + { + images.at(group).at(frame) = nullptr; + } + catch (const std::out_of_range &) + { + // ignore - image not loaded + } + + auto locator = getImageLocator(frame, group); + locator.horizontalFlip = !locator.horizontalFlip; + source[group][frame] = locator; +} + +void CAnimation::verticalFlip(size_t frame, size_t group) +{ + try + { + images.at(group).at(frame) = nullptr; + } + catch (const std::out_of_range &) + { + // ignore - image not loaded + } + + auto locator = getImageLocator(frame, group); + locator.verticalFlip = !locator.verticalFlip; + source[group][frame] = locator; } void CAnimation::playerColored(PlayerColor player) @@ -367,9 +200,16 @@ void CAnimation::createFlippedGroup(const size_t sourceGroup, const size_t targe for(size_t frame = 0; frame < size(sourceGroup); ++frame) { duplicateImage(sourceGroup, frame, targetGroup); - - auto image = getImage(frame, targetGroup); - image->verticalFlip(); + verticalFlip(frame, targetGroup); } } +ImageLocator CAnimation::getImageLocator(size_t frame, size_t group) const +{ + const ImageLocator & locator = source.at(group).at(frame); + + if (!locator.empty()) + return locator; + + return ImageLocator(name, frame, group); +} diff --git a/client/render/CAnimation.h b/client/render/CAnimation.h index efb66f602..75c8d1566 100644 --- a/client/render/CAnimation.h +++ b/client/render/CAnimation.h @@ -9,6 +9,9 @@ */ #pragma once +#include "IImage.h" +#include "ImageLocator.h" + #include "../../lib/GameConstants.h" #include "../../lib/filesystem/ResourcePath.h" @@ -17,15 +20,14 @@ class JsonNode; VCMI_LIB_NAMESPACE_END class CDefFile; -class IImage; class RenderHandler; /// Class for handling animation class CAnimation { private: - //source[group][position] - file with this frame, if string is empty - image located in def file - std::map > source; + //source[group][position] - location of this frame + std::map > source; //bitmap[group][position], store objects with loaded bitmaps std::map > > images; @@ -33,9 +35,10 @@ private: //animation file name AnimationPath name; - bool preloaded; + EImageBlitMode mode; - std::shared_ptr defFile; + // current player color, if any + PlayerColor player = PlayerColor::CANNOT_DETERMINE; //loader, will be called by load(), require opened def file for loading from it. Returns true if image is loaded bool loadFrame(size_t frame, size_t group); @@ -43,49 +46,29 @@ private: //unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount) bool unloadFrame(size_t frame, size_t group); - //initialize animation from file - void initFromJson(const JsonNode & input); - void init(); - //to get rid of copy-pasting error message :] void printError(size_t frame, size_t group, std::string type) const; - //not a very nice method to get image from another def file - //TODO: remove after implementing resource manager - std::shared_ptr getFromExtraDef(std::string filename); + std::shared_ptr getImageImpl(size_t frame, size_t group=0, bool verbose=true); + ImageLocator getImageLocator(size_t frame, size_t group) const; public: - CAnimation(const AnimationPath & Name); - CAnimation(); + CAnimation(const AnimationPath & Name, std::map > layout, EImageBlitMode mode); ~CAnimation(); //duplicates frame at [sourceGroup, sourceFrame] as last frame in targetGroup //and loads it if animation is preloaded void duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup); - //add custom surface to the selected position. - void setCustom(std::string filename, size_t frame, size_t group=0); - - std::shared_ptr getImage(size_t frame, size_t group=0, bool verbose=true) const; + std::shared_ptr getImage(size_t frame, size_t group=0, bool verbose=true); void exportBitmaps(const boost::filesystem::path & path) const; - //all available frames - void load (); - void unload(); - void preload(); - - //all frames from group - void loadGroup (size_t group); - void unloadGroup(size_t group); - - //single image - void load (size_t frame, size_t group=0); - void unload(size_t frame, size_t group=0); - //total count of frames in group (including not loaded) size_t size(size_t group=0) const; + void horizontalFlip(size_t frame, size_t group=0); + void verticalFlip(size_t frame, size_t group=0); void horizontalFlip(); void verticalFlip(); void playerColored(PlayerColor player); diff --git a/client/render/CDefFile.cpp b/client/render/CDefFile.cpp index 23377c4e5..1c8670a09 100644 --- a/client/render/CDefFile.cpp +++ b/client/render/CDefFile.cpp @@ -18,50 +18,6 @@ #include -// Extremely simple file cache. TODO: smarter, more general solution -class CFileCache -{ - static const int cacheSize = 50; //Max number of cached files - struct FileData - { - AnimationPath name; - size_t size; - std::unique_ptr data; - - std::unique_ptr getCopy() - { - auto ret = std::unique_ptr(new ui8[size]); - std::copy(data.get(), data.get() + size, ret.get()); - return ret; - } - FileData(AnimationPath name_, size_t size_, std::unique_ptr data_): - name{std::move(name_)}, - size{size_}, - data{std::move(data_)} - {} - }; - - std::deque cache; -public: - std::unique_ptr getCachedFile(AnimationPath rid) - { - for(auto & file : cache) - { - if (file.name == rid) - return file.getCopy(); - } - // Still here? Cache miss - if (cache.size() > cacheSize) - cache.pop_front(); - - auto data = CResourceHandler::get()->load(rid)->readAll(); - - cache.emplace_back(std::move(rid), data.second, std::move(data.first)); - - return cache.back().getCopy(); - } -}; - enum class DefType : uint32_t { SPELL = 0x40, @@ -76,8 +32,6 @@ enum class DefType : uint32_t BATTLE_HERO = 0x49 }; -static CFileCache animationCache; - /************************************************************************* * DefFile, class used for def loading * *************************************************************************/ @@ -124,7 +78,7 @@ CDefFile::CDefFile(const AnimationPath & Name): {0, 0, 0, 64 } // shadow border below selection ( used in battle def's ) }; - data = animationCache.getCachedFile(Name); + data = CResourceHandler::get()->load(Name)->readAll().first; palette = std::unique_ptr(new SDL_Color[256]); int it = 0; diff --git a/client/render/Canvas.cpp b/client/render/Canvas.cpp index 1e6bd290d..dbb46b9f0 100644 --- a/client/render/Canvas.cpp +++ b/client/render/Canvas.cpp @@ -81,14 +81,14 @@ void Canvas::draw(const std::shared_ptr& image, const Point & pos) { assert(image); if (image) - image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y); + image->draw(surface, pos + renderArea.topLeft()); } void Canvas::draw(const std::shared_ptr& image, const Point & pos, const Rect & sourceRect) { assert(image); if (image) - image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y, &sourceRect); + image->draw(surface, pos + renderArea.topLeft(), &sourceRect); } void Canvas::draw(const Canvas & image, const Point & pos) @@ -192,7 +192,7 @@ void Canvas::fillTexture(const std::shared_ptr& image) for (int y=0; y < surface->h; y+= imageArea.h) { for (int x=0; x < surface->w; x+= imageArea.w) - image->draw(surface, renderArea.x + x, renderArea.y + y); + image->draw(surface, Point(renderArea.x + x, renderArea.y + y)); } } diff --git a/client/render/Graphics.cpp b/client/render/Graphics.cpp index ad8f90d92..a42a90c5b 100644 --- a/client/render/Graphics.cpp +++ b/client/render/Graphics.cpp @@ -131,76 +131,38 @@ Graphics::Graphics() loadPaletteAndColors(); initializeBattleGraphics(); loadErmuToPicture(); - initializeImageLists(); //(!) do not load any CAnimation here } -void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player) +void Graphics::setPlayerPalette(SDL_Palette * targetPalette, PlayerColor player) { - if(sur->format->palette) + SDL_Color palette[32]; + if(player.isValidPlayer()) { - SDL_Color palette[32]; - if(player.isValidPlayer()) - { - for(int i=0; i<32; ++i) - palette[i] = CSDL_Ext::toSDL(playerColorPalette[player][i]); - } - else if(player == PlayerColor::NEUTRAL) - { - for(int i=0; i<32; ++i) - palette[i] = CSDL_Ext::toSDL(neutralColorPalette[i]); - } - else - { - logGlobal->error("Wrong player id in blueToPlayersAdv (%s)!", player.toString()); - return; - } -//FIXME: not all player colored images have player palette at last 32 indexes -//NOTE: following code is much more correct but still not perfect (bugged with status bar) - CSDL_Ext::setColors(sur, palette, 224, 32); - - -#if 0 - - SDL_Color * bluePalette = playerColorPalette + 32; - - SDL_Palette * oldPalette = sur->format->palette; - - SDL_Palette * newPalette = SDL_AllocPalette(256); - - for(size_t destIndex = 0; destIndex < 256; destIndex++) - { - SDL_Color old = oldPalette->colors[destIndex]; - - bool found = false; - - for(size_t srcIndex = 0; srcIndex < 32; srcIndex++) - { - if(old.b == bluePalette[srcIndex].b && old.g == bluePalette[srcIndex].g && old.r == bluePalette[srcIndex].r) - { - found = true; - newPalette->colors[destIndex] = palette[srcIndex]; - break; - } - } - if(!found) - newPalette->colors[destIndex] = old; - } - - SDL_SetSurfacePalette(sur, newPalette); - - SDL_FreePalette(newPalette); - -#endif // 0 + for(int i=0; i<32; ++i) + palette[i] = CSDL_Ext::toSDL(playerColorPalette[player][i]); } else { - //TODO: implement. H3 method works only for images with palettes. - // Add some kind of player-colored overlay? - // Or keep palette approach here and replace only colors of specific value(s) - // Or just wait for OpenGL support? - logGlobal->warn("Image must have palette to be player-colored!"); + for(int i=0; i<32; ++i) + palette[i] = CSDL_Ext::toSDL(neutralColorPalette[i]); + } + + SDL_SetPaletteColors(targetPalette, palette, 224, 32); +} + +void Graphics::setPlayerFlagColor(SDL_Palette * targetPalette, PlayerColor player) +{ + if(player.isValidPlayer()) + { + SDL_Color color = CSDL_Ext::toSDL(playerColors[player.getNum()]); + SDL_SetPaletteColors(targetPalette, &color, 5, 1); + } + else + { + SDL_Color color = CSDL_Ext::toSDL(neutralColor); + SDL_SetPaletteColors(targetPalette, &color, 5, 1); } } @@ -244,51 +206,3 @@ void Graphics::loadErmuToPicture() } assert (etp_idx == 44); } - -void Graphics::addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName) -{ - if (!imageName.empty()) - { - JsonNode entry; - if (group != 0) - entry["group"].Integer() = group; - entry["frame"].Integer() = index; - entry["file"].String() = imageName; - - imageLists["SPRITES/" + listName]["images"].Vector().push_back(entry); - } -} - -void Graphics::addImageListEntries(const EntityService * service) -{ - auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3, _4); - - auto loopCb = [&](const Entity * entity, bool & stop) - { - entity->registerIcons(cb); - }; - - service->forEachBase(loopCb); -} - -void Graphics::initializeImageLists() -{ - addImageListEntries(CGI->creatures()); - addImageListEntries(CGI->heroTypes()); - addImageListEntries(CGI->artifacts()); - addImageListEntries(CGI->factions()); - addImageListEntries(CGI->spells()); - addImageListEntries(CGI->skills()); -} - -std::shared_ptr Graphics::getAnimation(const AnimationPath & path) -{ - if (cachedAnimations.count(path) != 0) - return cachedAnimations.at(path); - - auto newAnimation = GH.renderHandler().loadAnimation(path); - - newAnimation->preload(); - cachedAnimations[path] = newAnimation; - return newAnimation; -} diff --git a/client/render/Graphics.h b/client/render/Graphics.h index 4170f8e1a..8b4941b40 100644 --- a/client/render/Graphics.h +++ b/client/render/Graphics.h @@ -27,27 +27,18 @@ class JsonNode; VCMI_LIB_NAMESPACE_END -struct SDL_Surface; -class CAnimation; +struct SDL_Palette; class IFont; /// Handles fonts, hero images, town images, various graphics class Graphics { - void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName); - void addImageListEntries(const EntityService * service); - void initializeBattleGraphics(); void loadPaletteAndColors(); void loadErmuToPicture(); void loadFonts(); - void initializeImageLists(); - - std::map> cachedAnimations; public: - std::shared_ptr getAnimation(const AnimationPath & path); - //Fonts static const int FONTS_NUMBER = 9; std::array< std::shared_ptr, FONTS_NUMBER> fonts; @@ -61,8 +52,6 @@ public: PlayerPalette neutralColorPalette; ColorRGBA neutralColor; - std::map imageLists; - //towns std::map ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type //for battles @@ -71,7 +60,8 @@ public: //functions Graphics(); - void blueToPlayersAdv(SDL_Surface * sur, PlayerColor player); //replaces blue interface colour with a color of player + void setPlayerPalette(SDL_Palette * sur, PlayerColor player); + void setPlayerFlagColor(SDL_Palette * sur, PlayerColor player); }; extern Graphics * graphics; diff --git a/client/render/IImage.h b/client/render/IImage.h index 2b8bf1d8d..6247c1269 100644 --- a/client/render/IImage.h +++ b/client/render/IImage.h @@ -26,13 +26,17 @@ class ColorFilter; /// Defines which blit method will be selected when image is used for rendering enum class EImageBlitMode { - /// Image can have no transparency and can be only used as background + /// Preferred for images that don't need any background + /// Indexed or RGBA: Image can have no transparency and can be only used as background OPAQUE, - /// Image can have only a single color as transparency and has no semi-transparent areas + /// Preferred for images that may need transparency + /// Indexed: Image can have only a single color as transparency and has no semi-transparent areas + /// RGBA: full alpha transparency range, e.g. shadows COLORKEY, - /// Image might have full alpha transparency range, e.g. shadows + /// Should be avoided if possible, use only for images that use def's with semi-transparency + /// Indexed or RGBA: Image might have full alpha transparency range, e.g. shadows ALPHA }; @@ -46,18 +50,17 @@ public: static constexpr int32_t SPECIAL_PALETTE_MASK_CREATURES = 0b11110011; //draws image on surface "where" at position - virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0; - virtual void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const = 0; + virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src = nullptr) const = 0; - virtual std::shared_ptr scaleFast(const Point & size) const = 0; + virtual void scaleFast(const Point & size) = 0; virtual void exportBitmap(const boost::filesystem::path & path) const = 0; //Change palette to specific player - virtual void playerColored(PlayerColor player)=0; + virtual void playerColored(PlayerColor player) = 0; //set special color for flag - virtual void setFlagColor(PlayerColor player)=0; + virtual void setFlagColor(PlayerColor player) = 0; //test transparency of specific pixel virtual bool isTransparent(const Point & coords) const = 0; @@ -69,8 +72,6 @@ public: //only indexed bitmaps, 16 colors maximum virtual void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) = 0; virtual void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) = 0; - virtual void resetPalette(int colorID) = 0; - virtual void resetPalette() = 0; virtual void setAlpha(uint8_t value) = 0; virtual void setBlitMode(EImageBlitMode mode) = 0; @@ -78,11 +79,21 @@ public: //only indexed bitmaps with 7 special colors virtual void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) = 0; - virtual void horizontalFlip() = 0; - virtual void verticalFlip() = 0; - virtual void doubleFlip() = 0; - - IImage() = default; virtual ~IImage() = default; }; +class IConstImage +{ +public: + virtual Point dimensions() const = 0; + virtual void exportBitmap(const boost::filesystem::path & path) const = 0; + virtual bool isTransparent(const Point & coords) const = 0; + + virtual std::shared_ptr createImageReference(EImageBlitMode mode) = 0; + + virtual std::shared_ptr horizontalFlip() const = 0; + virtual std::shared_ptr verticalFlip() const = 0; + + + virtual ~IConstImage() = default; +}; diff --git a/client/render/IImageLoader.h b/client/render/IImageLoader.h index 7cd950091..b4368e5f5 100644 --- a/client/render/IImageLoader.h +++ b/client/render/IImageLoader.h @@ -14,8 +14,6 @@ VCMI_LIB_NAMESPACE_BEGIN class Point; VCMI_LIB_NAMESPACE_END -class SDLImage; - struct SDL_Color; class IImageLoader diff --git a/client/render/IRenderHandler.h b/client/render/IRenderHandler.h index 880295170..aa16e4acf 100644 --- a/client/render/IRenderHandler.h +++ b/client/render/IRenderHandler.h @@ -9,7 +9,11 @@ */ #pragma once -#include "../../lib/filesystem/ResourcePath.h" +#include "ImageLocator.h" + +VCMI_LIB_NAMESPACE_BEGIN +class Services; +VCMI_LIB_NAMESPACE_END struct SDL_Surface; @@ -22,17 +26,18 @@ class IRenderHandler : public boost::noncopyable public: virtual ~IRenderHandler() = default; + /// Must be called once VLC loading is over to initialize icons + virtual void onLibraryLoadingFinished(const Services * services) = 0; + /// Loads image using given path - virtual std::shared_ptr loadImage(const ImagePath & path) = 0; + virtual std::shared_ptr loadImage(const ImageLocator & locator, EImageBlitMode mode) = 0; virtual std::shared_ptr loadImage(const ImagePath & path, EImageBlitMode mode) = 0; + virtual std::shared_ptr loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) = 0; /// temporary compatibility method. Creates IImage from existing SDL_Surface /// Surface will be shared, caller must still free it with SDL_FreeSurface virtual std::shared_ptr createImage(SDL_Surface * source) = 0; /// Loads animation using given path - virtual std::shared_ptr loadAnimation(const AnimationPath & path) = 0; - - /// Creates empty CAnimation - virtual std::shared_ptr createAnimation() = 0; + virtual std::shared_ptr loadAnimation(const AnimationPath & path, EImageBlitMode mode) = 0; }; diff --git a/client/render/ImageLocator.cpp b/client/render/ImageLocator.cpp new file mode 100644 index 000000000..250fd2f47 --- /dev/null +++ b/client/render/ImageLocator.cpp @@ -0,0 +1,56 @@ +/* + * ImageLocator.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ImageLocator.h" + +#include "../../lib/json/JsonNode.h" + + +ImageLocator::ImageLocator(const JsonNode & config) + : image(ImagePath::fromJson(config["file"])) + , defFile(AnimationPath::fromJson(config["defFile"])) + , defFrame(config["defFrame"].Integer()) + , defGroup(config["defGroup"].Integer()) + , verticalFlip(config["verticalFlip"].Bool()) + , horizontalFlip(config["horizontalFlip"].Bool()) +{ +} + +ImageLocator::ImageLocator(const ImagePath & path) + : image(path) +{ +} + +ImageLocator::ImageLocator(const AnimationPath & path, int frame, int group) + : defFile(path) + , defFrame(frame) + , defGroup(group) +{ +} + +bool ImageLocator::operator<(const ImageLocator & other) const +{ + if(image != other.image) + return image < other.image; + if(defFile != other.defFile) + return defFile < other.defFile; + if(defGroup != other.defGroup) + return defGroup < other.defGroup; + if(defFrame != other.defFrame) + return defFrame < other.defFrame; + if(verticalFlip != other.verticalFlip) + return verticalFlip < other.verticalFlip; + return horizontalFlip < other.horizontalFlip; +} + +bool ImageLocator::empty() const +{ + return !image.has_value() && !defFile.has_value(); +} diff --git a/client/render/ImageLocator.h b/client/render/ImageLocator.h new file mode 100644 index 000000000..875b537c1 --- /dev/null +++ b/client/render/ImageLocator.h @@ -0,0 +1,31 @@ +/* + * ImageLocator.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/filesystem/ResourcePath.h" + +struct ImageLocator +{ + std::optional image; + std::optional defFile; + int defFrame = -1; + int defGroup = -1; + + bool verticalFlip = false; + bool horizontalFlip = false; + + ImageLocator() = default; + ImageLocator(const AnimationPath & path, int frame, int group); + explicit ImageLocator(const JsonNode & config); + explicit ImageLocator(const ImagePath & path); + + bool operator < (const ImageLocator & other) const; + bool empty() const; +}; diff --git a/client/renderSDL/CBitmapFont.cpp b/client/renderSDL/CBitmapFont.cpp index 36a29d174..9b755680d 100644 --- a/client/renderSDL/CBitmapFont.cpp +++ b/client/renderSDL/CBitmapFont.cpp @@ -125,7 +125,7 @@ void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & char posX += character.leftOffset; - CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); + CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface); uint8_t bpp = surface->format->BytesPerPixel; diff --git a/client/renderSDL/CBitmapHanFont.cpp b/client/renderSDL/CBitmapHanFont.cpp index ac1684185..5476d823f 100644 --- a/client/renderSDL/CBitmapHanFont.cpp +++ b/client/renderSDL/CBitmapHanFont.cpp @@ -41,7 +41,7 @@ void CBitmapHanFont::renderCharacter(SDL_Surface * surface, int characterIndex, Rect clipRect; CSDL_Ext::getClipRect(surface, clipRect); - CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); + CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface); uint8_t bpp = surface->format->BytesPerPixel; // start of line, may differ from 0 due to end of surface or clipped surface diff --git a/client/renderSDL/CursorHardware.cpp b/client/renderSDL/CursorHardware.cpp index a4a10f2f8..dc42f9e38 100644 --- a/client/renderSDL/CursorHardware.cpp +++ b/client/renderSDL/CursorHardware.cpp @@ -49,7 +49,7 @@ void CursorHardware::setImage(std::shared_ptr image, const Point & pivot CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); - image->draw(cursorSurface); + image->draw(cursorSurface, Point(0,0)); auto oldCursor = cursor; cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y); diff --git a/client/renderSDL/CursorHardware.h b/client/renderSDL/CursorHardware.h index c4e311778..02b75fca5 100644 --- a/client/renderSDL/CursorHardware.h +++ b/client/renderSDL/CursorHardware.h @@ -9,7 +9,6 @@ */ #pragma once -class CAnimation; class IImage; struct SDL_Surface; struct SDL_Texture; diff --git a/client/renderSDL/CursorSoftware.cpp b/client/renderSDL/CursorSoftware.cpp index 78b9e1250..29b606b96 100644 --- a/client/renderSDL/CursorSoftware.cpp +++ b/client/renderSDL/CursorSoftware.cpp @@ -58,7 +58,7 @@ void CursorSoftware::updateTexture() CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); - cursorImage->draw(cursorSurface); + cursorImage->draw(cursorSurface, Point(0,0)); SDL_UpdateTexture(cursorTexture, nullptr, cursorSurface->pixels, cursorSurface->pitch); needUpdate = false; } diff --git a/client/renderSDL/CursorSoftware.h b/client/renderSDL/CursorSoftware.h index 44080e3e2..cb43bb1e5 100644 --- a/client/renderSDL/CursorSoftware.h +++ b/client/renderSDL/CursorSoftware.h @@ -9,7 +9,6 @@ */ #pragma once -class CAnimation; class IImage; struct SDL_Surface; struct SDL_Texture; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 87eb99091..ec4a0bdc3 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -10,31 +10,235 @@ #include "StdInc.h" #include "RenderHandler.h" -#include "../render/CAnimation.h" #include "SDLImage.h" +#include "../render/CAnimation.h" +#include "../render/CDefFile.h" -std::shared_ptr RenderHandler::loadImage(const ImagePath & path) +#include "../../lib/json/JsonUtils.h" +#include "../../lib/filesystem/Filesystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & path) { - return loadImage(path, EImageBlitMode::ALPHA); + AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/"); + + auto it = animationFiles.find(actualPath); + + if (it != animationFiles.end()) + return it->second; + + if (!CResourceHandler::get()->existsResource(actualPath)) + { + animationFiles[actualPath] = nullptr; + return nullptr; + } + + auto result = std::make_shared(actualPath); + + animationFiles[actualPath] = result; + return result; +} + +void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config) +{ + std::string basepath; + basepath = config["basepath"].String(); + + JsonNode base; + base["margins"] = config["margins"]; + base["width"] = config["width"]; + base["height"] = config["height"]; + + for(const JsonNode & group : config["sequences"].Vector()) + { + size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING) + source[groupID].clear(); + + for(const JsonNode & frame : group["frames"].Vector()) + { + JsonNode toAdd = frame; + JsonUtils::inherit(toAdd, base); + toAdd["file"].String() = basepath + frame.String(); + source[groupID].emplace_back(toAdd); + } + } + + for(const JsonNode & node : config["images"].Vector()) + { + size_t group = node["group"].Integer(); + size_t frame = node["frame"].Integer(); + + if (source[group].size() <= frame) + source[group].resize(frame+1); + + JsonNode toAdd = node; + JsonUtils::inherit(toAdd, base); + toAdd["file"].String() = basepath + node["file"].String(); + source[group][frame] = ImageLocator(toAdd); + } +} + +RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path) +{ + AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/"); + + auto it = animationLayouts.find(actualPath); + + if (it != animationLayouts.end()) + return it->second; + + AnimationLayoutMap result; + + auto defFile = getAnimationFile(actualPath); + if(defFile) + { + const std::map defEntries = defFile->getEntries(); + + for (const auto & defEntry : defEntries) + result[defEntry.first].resize(defEntry.second); + } + + auto jsonResource = actualPath.toType(); + auto configList = CResourceHandler::get()->getResourcesWithName(jsonResource); + + for(auto & loader : configList) + { + auto stream = loader->load(jsonResource); + std::unique_ptr textData(new ui8[stream->getSize()]); + stream->read(textData.get(), stream->getSize()); + + const JsonNode config(reinterpret_cast(textData.get()), stream->getSize(), path.getOriginalName()); + + initFromJson(result, config); + } + + animationLayouts[actualPath] = result; + return animationLayouts[actualPath]; +} + +std::shared_ptr RenderHandler::loadImageFromSingleFile(const ImagePath & path) +{ + auto result = std::make_shared(path); + imageFiles[ImageLocator(path)] = result; + return result; +} + +std::shared_ptr RenderHandler::loadImageFromAnimationFileUncached(const AnimationPath & path, int frame, int group) +{ + const auto & layout = getAnimationLayout(path); + if (!layout.count(group)) + return loadImageFromSingleFile(ImagePath::builtin("DEFAULT")); + + if (frame >= layout.at(group).size()) + return loadImageFromSingleFile(ImagePath::builtin("DEFAULT")); + + const auto & locator = layout.at(group).at(frame); + if (locator.image) + { + return loadImageImpl(locator); + } + else + { + auto defFile = getAnimationFile(path); + return std::make_shared(defFile.get(), frame, group); + } +} + +std::shared_ptr RenderHandler::loadImageFromAnimationFile(const AnimationPath & path, int frame, int group) +{ + auto result = loadImageFromAnimationFileUncached(path, frame, group); + imageFiles[ImageLocator(path, frame, group)] = result; + return result; +} + +std::shared_ptr RenderHandler::loadImageImpl(const ImageLocator & locator) +{ + auto it = imageFiles.find(locator); + if (it != imageFiles.end()) + return it->second; + + std::shared_ptr result; + + if (locator.image) + result = loadImageFromSingleFile(*locator.image); + else if (locator.defFile) + result = loadImageFromAnimationFile(*locator.defFile, locator.defFrame, locator.defGroup); + + if (!result) + result = loadImageFromSingleFile(ImagePath::builtin("DEFAULT")); + + if (locator.verticalFlip) + result = result->verticalFlip(); + + if (locator.horizontalFlip) + result = result->horizontalFlip(); + + imageFiles[locator] = result; + return result; +} + +std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode) +{ + return loadImageImpl(locator)->createImageReference(mode); +} + +std::shared_ptr RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) +{ + return loadImageFromAnimationFile(path, frame, group)->createImageReference(mode); } std::shared_ptr RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode) { - return std::make_shared(path, mode); + return loadImageImpl(ImageLocator(path))->createImageReference(mode); } std::shared_ptr RenderHandler::createImage(SDL_Surface * source) { - return std::make_shared(source, EImageBlitMode::ALPHA); + return std::make_shared(source)->createImageReference(EImageBlitMode::ALPHA); } -std::shared_ptr RenderHandler::loadAnimation(const AnimationPath & path) +std::shared_ptr RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode) { - return std::make_shared(path); + return std::make_shared(path, getAnimationLayout(path), mode); } -std::shared_ptr RenderHandler::createAnimation() +void RenderHandler::addImageListEntries(const EntityService * service) { - return std::make_shared(); + service->forEachBase([this](const Entity * entity, bool & stop) + { + entity->registerIcons([this](size_t index, size_t group, const std::string & listName, const std::string & imageName) + { + if (imageName.empty()) + return; + + auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName)); + + JsonNode entry; + entry["file"].String() = imageName; + + if (index >= layout[group].size()) + layout[group].resize(index + 1); + + layout[group][index] = ImageLocator(entry); + }); + }); +} + +void RenderHandler::onLibraryLoadingFinished(const Services * services) +{ + addImageListEntries(services->creatures()); + addImageListEntries(services->heroTypes()); + addImageListEntries(services->artifacts()); + addImageListEntries(services->factions()); + addImageListEntries(services->spells()); + addImageListEntries(services->skills()); } diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index 76e30ee9c..1d1d72755 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -11,15 +11,42 @@ #include "../render/IRenderHandler.h" +VCMI_LIB_NAMESPACE_BEGIN +class EntityService; +VCMI_LIB_NAMESPACE_END + +class CDefFile; +class IConstImage; + class RenderHandler : public IRenderHandler { + using AnimationLayoutMap = std::map>; + + std::map> animationFiles; + std::map animationLayouts; + std::map> imageFiles; + + std::shared_ptr getAnimationFile(const AnimationPath & path); + AnimationLayoutMap & getAnimationLayout(const AnimationPath & path); + void initFromJson(AnimationLayoutMap & layout, const JsonNode & config); + + void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName); + void addImageListEntries(const EntityService * service); + + std::shared_ptr loadImageFromSingleFile(const ImagePath & path); + std::shared_ptr loadImageFromAnimationFileUncached(const AnimationPath & path, int frame, int group); + std::shared_ptr loadImageFromAnimationFile(const AnimationPath & path, int frame, int group); + std::shared_ptr loadImageImpl(const ImageLocator & config); public: - std::shared_ptr loadImage(const ImagePath & path) override; + + // IRenderHandler implementation + void onLibraryLoadingFinished(const Services * services) override; + + std::shared_ptr loadImage(const ImageLocator & locator, EImageBlitMode mode) override; std::shared_ptr loadImage(const ImagePath & path, EImageBlitMode mode) override; + std::shared_ptr loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) override; + + std::shared_ptr loadAnimation(const AnimationPath & path, EImageBlitMode mode) override; std::shared_ptr createImage(SDL_Surface * source) override; - - std::shared_ptr loadAnimation(const AnimationPath & path) override; - - std::shared_ptr createAnimation() override; }; diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 2a1291c67..c7e47cd97 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -1,4 +1,4 @@ -/* +/* * SDLImage.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -18,8 +18,6 @@ #include "../render/CDefFile.h" #include "../render/Graphics.h" -#include "../../lib/json/JsonNode.h" - #include class SDLImageLoader; @@ -34,7 +32,7 @@ int IImage::height() const return dimensions().y; } -SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group) +SDLImageConst::SDLImageConst(CDefFile * data, size_t frame, size_t group) : surf(nullptr), margins(0, 0), fullSize(0, 0), @@ -44,10 +42,9 @@ SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group) data->loadFrame(frame, group, loader); savePalette(); - setBlitMode(EImageBlitMode::ALPHA); } -SDLImage::SDLImage(SDL_Surface * from, EImageBlitMode mode) +SDLImageConst::SDLImageConst(SDL_Surface * from) : surf(nullptr), margins(0, 0), fullSize(0, 0), @@ -58,47 +55,13 @@ SDLImage::SDLImage(SDL_Surface * from, EImageBlitMode mode) return; savePalette(); - setBlitMode(mode); surf->refcount++; fullSize.x = surf->w; fullSize.y = surf->h; } -SDLImage::SDLImage(const JsonNode & conf, EImageBlitMode mode) - : surf(nullptr), - margins(0, 0), - fullSize(0, 0), - originalPalette(nullptr) -{ - surf = BitmapHandler::loadBitmap(ImagePath::fromJson(conf["file"])); - - if(surf == nullptr) - return; - - savePalette(); - setBlitMode(mode); - - const JsonNode & jsonMargins = conf["margins"]; - - margins.x = static_cast(jsonMargins["left"].Integer()); - margins.y = static_cast(jsonMargins["top"].Integer()); - - fullSize.x = static_cast(conf["width"].Integer()); - fullSize.y = static_cast(conf["height"].Integer()); - - if(fullSize.x == 0) - { - fullSize.x = margins.x + surf->w + (int)jsonMargins["right"].Integer(); - } - - if(fullSize.y == 0) - { - fullSize.y = margins.y + surf->h + (int)jsonMargins["bottom"].Integer(); - } -} - -SDLImage::SDLImage(const ImagePath & filename, EImageBlitMode mode) +SDLImageConst::SDLImageConst(const ImagePath & filename) : surf(nullptr), margins(0, 0), fullSize(0, 0), @@ -114,22 +77,13 @@ SDLImage::SDLImage(const ImagePath & filename, EImageBlitMode mode) else { savePalette(); - setBlitMode(mode); fullSize.x = surf->w; fullSize.y = surf->h; } } -void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const -{ - if(!surf) - return; - Rect destRect(posX, posY, surf->w, surf->h); - draw(where, &destRect, src); -} - -void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) const +void SDLImageConst::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const { if (!surf) return; @@ -153,16 +107,21 @@ void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) cons else destShift = margins; - if(dest) - destShift += dest->topLeft(); + destShift += dest; - uint8_t perSurfaceAlpha; - if (SDL_GetSurfaceAlphaMod(surf, &perSurfaceAlpha) != 0) - logGlobal->error("SDL_GetSurfaceAlphaMod faied! %s", SDL_GetError()); + SDL_SetSurfaceAlphaMod(surf, alpha); - if(surf->format->BitsPerPixel == 8 && perSurfaceAlpha == SDL_ALPHA_OPAQUE && blitMode == EImageBlitMode::ALPHA) + if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0)) + SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND); + else + SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); + + if (palette && surf->format->palette) + SDL_SetSurfacePalette(surf, palette); + + if(surf->format->palette && mode == EImageBlitMode::ALPHA) { - CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift); + CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift, alpha); } else { @@ -170,12 +129,20 @@ void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) cons } } -std::shared_ptr SDLImage::scaleFast(const Point & size) const +const SDL_Palette * SDLImageConst::getPalette() const { - float scaleX = float(size.x) / width(); - float scaleY = float(size.y) / height(); + if (originalPalette == nullptr) + throw std::runtime_error("Palette not found!"); - auto scaled = CSDL_Ext::scaleSurfaceFast(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY)); + return originalPalette; +} + +std::shared_ptr SDLImageConst::scaleFast(const Point & size) const +{ + float scaleX = float(size.x) / dimensions().x; + float scaleY = float(size.y) / dimensions().y; + + auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY)); if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]); @@ -184,7 +151,7 @@ std::shared_ptr SDLImage::scaleFast(const Point & size) const else CSDL_Ext::setDefaultColorKey(scaled);//just in case - auto * ret = new SDLImage(scaled, EImageBlitMode::ALPHA); + auto ret = std::make_shared(scaled); ret->fullSize.x = (int) round((float)fullSize.x * scaleX); ret->fullSize.y = (int) round((float)fullSize.y * scaleY); @@ -195,45 +162,26 @@ std::shared_ptr SDLImage::scaleFast(const Point & size) const // erase our own reference SDL_FreeSurface(scaled); - return std::shared_ptr(ret); + return ret; } -void SDLImage::exportBitmap(const boost::filesystem::path& path) const +void SDLImageConst::exportBitmap(const boost::filesystem::path& path) const { SDL_SaveBMP(surf, path.string().c_str()); } -void SDLImage::playerColored(PlayerColor player) +void SDLImageIndexed::playerColored(PlayerColor player) { - if (!surf) - return; - graphics->blueToPlayersAdv(surf, player); + graphics->setPlayerPalette(currentPalette, player); } -void SDLImage::setAlpha(uint8_t value) -{ - CSDL_Ext::setAlpha (surf, value); - if (value != 255) - SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND); -} - -void SDLImage::setBlitMode(EImageBlitMode mode) -{ - blitMode = mode; - - if (blitMode != EImageBlitMode::OPAQUE && surf->format->Amask != 0) - SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND); - else - SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); -} - -void SDLImage::setFlagColor(PlayerColor player) +void SDLImageIndexed::setFlagColor(PlayerColor player) { if(player.isValidPlayer() || player==PlayerColor::NEUTRAL) - CSDL_Ext::setPlayerColor(surf, player); + graphics->setPlayerFlagColor(currentPalette, player); } -bool SDLImage::isTransparent(const Point & coords) const +bool SDLImageConst::isTransparent(const Point & coords) const { if (surf) return CSDL_Ext::isTransparent(surf, coords.x, coords.y); @@ -241,121 +189,172 @@ bool SDLImage::isTransparent(const Point & coords) const return true; } -Point SDLImage::dimensions() const +Point SDLImageConst::dimensions() const { return fullSize; } -void SDLImage::horizontalFlip() +std::shared_ptr SDLImageConst::createImageReference(EImageBlitMode mode) { - margins.y = fullSize.y - surf->h - margins.y; + if (surf->format->palette) + return std::make_shared(shared_from_this(), mode); + else + return std::make_shared(shared_from_this(), mode); +} - //todo: modify in-place +std::shared_ptr SDLImageConst::horizontalFlip() const +{ SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf); - SDL_FreeSurface(surf); - surf = flipped; + auto ret = std::make_shared(flipped); + ret->fullSize = fullSize; + ret->margins.x = margins.x; + ret->margins.y = fullSize.y - surf->h - margins.y; + ret->fullSize = fullSize; + + return ret; } -void SDLImage::verticalFlip() +std::shared_ptr SDLImageConst::verticalFlip() const { - margins.x = fullSize.x - surf->w - margins.x; - - //todo: modify in-place SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf); - SDL_FreeSurface(surf); - surf = flipped; -} + auto ret = std::make_shared(flipped); + ret->fullSize = fullSize; + ret->margins.x = fullSize.x - surf->w - margins.x; + ret->margins.y = margins.y; + ret->fullSize = fullSize; -void SDLImage::doubleFlip() -{ - horizontalFlip(); - verticalFlip(); + return ret; } // Keep the original palette, in order to do color switching operation -void SDLImage::savePalette() +void SDLImageConst::savePalette() { // For some images that don't have palette, skip this if(surf->format->palette == nullptr) return; if(originalPalette == nullptr) - originalPalette = SDL_AllocPalette(DEFAULT_PALETTE_COLORS); + originalPalette = SDL_AllocPalette(surf->format->palette->ncolors); - SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, DEFAULT_PALETTE_COLORS); + SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, surf->format->palette->ncolors); } -void SDLImage::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) +void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) { - if(surf->format->palette) - { - std::vector shifterColors(colorsToMove); + const SDL_Palette * originalPalette = image->getPalette(); - for(uint32_t i=0; icolors[firstColorID + i]; - } - CSDL_Ext::setColors(surf, shifterColors.data(), firstColorID, colorsToMove); - } + std::vector shifterColors(colorsToMove); + + for(uint32_t i=0; icolors[firstColorID + i]; + + SDL_SetPaletteColors(currentPalette, shifterColors.data(), firstColorID, colorsToMove); } -void SDLImage::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) +void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) { - if(originalPalette == nullptr) - return; - - SDL_Palette* palette = surf->format->palette; + const SDL_Palette * originalPalette = image->getPalette(); // Note: here we skip first colors in the palette that are predefined in H3 images - for(int i = 0; i < palette->ncolors; i++) + for(int i = 0; i < currentPalette->ncolors; i++) { if(i < std::numeric_limits::digits && ((colorsToSkipMask >> i) & 1) == 1) continue; - palette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i]))); + currentPalette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i]))); } } -void SDLImage::resetPalette() +SDLImageIndexed::SDLImageIndexed(const std::shared_ptr & image, EImageBlitMode mode) + :SDLImageBase::SDLImageBase(image, mode) { - if(originalPalette == nullptr) - return; - - // Always keep the original palette not changed, copy a new palette to assign to surface - SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors); + auto originalPalette = image->getPalette(); + + currentPalette = SDL_AllocPalette(originalPalette->ncolors); + SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors); } -void SDLImage::resetPalette( int colorID ) +SDLImageIndexed::~SDLImageIndexed() { - if(originalPalette == nullptr) - return; - - // Always keep the original palette not changed, copy a new palette to assign to surface - SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1); + SDL_FreePalette(currentPalette); } -void SDLImage::setSpecialPalette(const IImage::SpecialPalette & specialPalette, uint32_t colorsToSkipMask) +void SDLImageIndexed::setSpecialPalette(const IImage::SpecialPalette & specialPalette, uint32_t colorsToSkipMask) { - if(surf->format->palette) + size_t last = std::min(specialPalette.size(), currentPalette->ncolors); + + for (size_t i = 0; i < last; ++i) { - size_t last = std::min(specialPalette.size(), surf->format->palette->ncolors); - - for (size_t i = 0; i < last; ++i) - { - if(i < std::numeric_limits::digits && ((colorsToSkipMask >> i) & 1) == 1) - surf->format->palette->colors[i] = CSDL_Ext::toSDL(specialPalette[i]); - } + if(i < std::numeric_limits::digits && ((colorsToSkipMask >> i) & 1) == 1) + currentPalette->colors[i] = CSDL_Ext::toSDL(specialPalette[i]); } } -SDLImage::~SDLImage() +SDLImageConst::~SDLImageConst() { SDL_FreeSurface(surf); - - if(originalPalette != nullptr) - { - SDL_FreePalette(originalPalette); - originalPalette = nullptr; - } + SDL_FreePalette(originalPalette); } +SDLImageBase::SDLImageBase(const std::shared_ptr & image, EImageBlitMode mode) + :image(image) + , alphaValue(SDL_ALPHA_OPAQUE) + , blitMode(mode) +{} + +void SDLImageRGB::draw(SDL_Surface * where, const Point & pos, const Rect * src) const +{ + image->draw(where, nullptr, pos, src, alphaValue, blitMode); +} + +void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect * src) const +{ + image->draw(where, currentPalette, pos, src, alphaValue, blitMode); +} + +void SDLImageBase::scaleFast(const Point & size) +{ + image = image->scaleFast(size); +} + +void SDLImageBase::exportBitmap(const boost::filesystem::path & path) const +{ + image->exportBitmap(path); +} + +bool SDLImageBase::isTransparent(const Point & coords) const +{ + return image->isTransparent(coords); +} + +Point SDLImageBase::dimensions() const +{ + return image->dimensions(); +} + +void SDLImageBase::setAlpha(uint8_t value) +{ + alphaValue = value; +} + +void SDLImageBase::setBlitMode(EImageBlitMode mode) +{ + blitMode = mode; +} + +void SDLImageRGB::setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) +{} + +void SDLImageRGB::playerColored(PlayerColor player) +{} + +void SDLImageRGB::setFlagColor(PlayerColor player) +{} + +void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) +{} + +void SDLImageRGB::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) +{} + + diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index 59487ff35..155eb173b 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -24,60 +24,88 @@ struct SDL_Palette; /* * Wrapper around SDL_Surface */ -class SDLImage : public IImage +class SDLImageConst final : public IConstImage, public std::enable_shared_from_this, boost::noncopyable { -public: - - const static int DEFAULT_PALETTE_COLORS = 256; - //Surface without empty borders SDL_Surface * surf; + + SDL_Palette * originalPalette; //size of left and top borders Point margins; //total size including borders Point fullSize; - EImageBlitMode blitMode; - -public: - //Load image from def file - SDLImage(CDefFile *data, size_t frame, size_t group=0); - //Load from bitmap file - SDLImage(const ImagePath & filename, EImageBlitMode blitMode); - - SDLImage(const JsonNode & conf, EImageBlitMode blitMode); - //Create using existing surface, extraRef will increase refcount on SDL_Surface - SDLImage(SDL_Surface * from, EImageBlitMode blitMode); - ~SDLImage(); - // Keep the original palette, in order to do color switching operation void savePalette(); - void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override; - void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const override; - std::shared_ptr scaleFast(const Point & size) const override; +public: + //Load image from def file + SDLImageConst(CDefFile *data, size_t frame, size_t group=0); + //Load from bitmap file + SDLImageConst(const ImagePath & filename); + //Create using existing surface, extraRef will increase refcount on SDL_Surface + SDLImageConst(SDL_Surface * from); + ~SDLImageConst(); + + void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const; + void exportBitmap(const boost::filesystem::path & path) const override; - void playerColored(PlayerColor player) override; - void setFlagColor(PlayerColor player) override; - bool isTransparent(const Point & coords) const override; Point dimensions() const override; + bool isTransparent(const Point & coords) const override; + std::shared_ptr createImageReference(EImageBlitMode mode) override; + std::shared_ptr horizontalFlip() const override; + std::shared_ptr verticalFlip() const override; + std::shared_ptr scaleFast(const Point & size) const; - void horizontalFlip() override; - void verticalFlip() override; - void doubleFlip() override; - - void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; - void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; - void resetPalette(int colorID) override; - void resetPalette() override; - - void setAlpha(uint8_t value) override; - void setBlitMode(EImageBlitMode mode) override; - - void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override; + const SDL_Palette * getPalette() const; friend class SDLImageLoader; - -private: - SDL_Palette * originalPalette; +}; + +class SDLImageBase : public IImage, boost::noncopyable +{ +protected: + std::shared_ptr image; + + uint8_t alphaValue; + EImageBlitMode blitMode; + +public: + SDLImageBase(const std::shared_ptr & image, EImageBlitMode mode); + + void scaleFast(const Point & size) override; + void exportBitmap(const boost::filesystem::path & path) const override; + bool isTransparent(const Point & coords) const override; + Point dimensions() const override; + void setAlpha(uint8_t value) override; + void setBlitMode(EImageBlitMode mode) override; +}; + +class SDLImageIndexed final : public SDLImageBase +{ + SDL_Palette * currentPalette = nullptr; + +public: + SDLImageIndexed(const std::shared_ptr & image, EImageBlitMode mode); + ~SDLImageIndexed(); + + void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override; + void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override; + void playerColored(PlayerColor player) override; + void setFlagColor(PlayerColor player) override; + void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; + void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; +}; + +class SDLImageRGB final : public SDLImageBase +{ +public: + using SDLImageBase::SDLImageBase; + + void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override; + void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override; + void playerColored(PlayerColor player) override; + void setFlagColor(PlayerColor player) override; + void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; + void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; }; diff --git a/client/renderSDL/SDLImageLoader.cpp b/client/renderSDL/SDLImageLoader.cpp index 30e288f38..87b87c6ec 100644 --- a/client/renderSDL/SDLImageLoader.cpp +++ b/client/renderSDL/SDLImageLoader.cpp @@ -17,7 +17,7 @@ #include -SDLImageLoader::SDLImageLoader(SDLImage * Img): +SDLImageLoader::SDLImageLoader(SDLImageConst * Img): image(Img), lineStart(nullptr), position(nullptr) @@ -32,8 +32,8 @@ void SDLImageLoader::init(Point SpriteSize, Point Margins, Point FullSize, SDL_C image->fullSize = FullSize; //Prepare surface - SDL_Palette * p = SDL_AllocPalette(SDLImage::DEFAULT_PALETTE_COLORS); - SDL_SetPaletteColors(p, pal, 0, SDLImage::DEFAULT_PALETTE_COLORS); + SDL_Palette * p = SDL_AllocPalette(DEFAULT_PALETTE_COLORS); + SDL_SetPaletteColors(p, pal, 0, DEFAULT_PALETTE_COLORS); SDL_SetSurfacePalette(image->surf, p); SDL_FreePalette(p); diff --git a/client/renderSDL/SDLImageLoader.h b/client/renderSDL/SDLImageLoader.h index 6e4cca11b..ada6c0230 100644 --- a/client/renderSDL/SDLImageLoader.h +++ b/client/renderSDL/SDLImageLoader.h @@ -11,9 +11,13 @@ #include "../render/IImageLoader.h" +class SDLImageConst; + class SDLImageLoader : public IImageLoader { - SDLImage * image; + static constexpr int DEFAULT_PALETTE_COLORS = 256; + + SDLImageConst * image; ui8 * lineStart; ui8 * position; public: @@ -25,7 +29,7 @@ public: //init image with these sizes and palette void init(Point SpriteSize, Point Margins, Point FullSize, SDL_Color *pal); - SDLImageLoader(SDLImage * Img); + SDLImageLoader(SDLImageConst * Img); ~SDLImageLoader(); }; diff --git a/client/renderSDL/SDL_Extensions.cpp b/client/renderSDL/SDL_Extensions.cpp index 9000473c4..5987e7d6c 100644 --- a/client/renderSDL/SDL_Extensions.cpp +++ b/client/renderSDL/SDL_Extensions.cpp @@ -19,6 +19,8 @@ #include "../../lib/GameConstants.h" #include +#include +#include Rect CSDL_Ext::fromSDL(const SDL_Rect & rect) { @@ -61,19 +63,6 @@ void CSDL_Ext::setAlpha(SDL_Surface * bg, int value) SDL_SetSurfaceAlphaMod(bg, value); } -void CSDL_Ext::updateRect(SDL_Surface *surface, const Rect & rect ) -{ - SDL_Rect rectSDL = CSDL_Ext::toSDL(rect); - if(0 !=SDL_UpdateTexture(screenTexture, &rectSDL, surface->pixels, surface->pitch)) - logGlobal->error("%sSDL_UpdateTexture %s", __FUNCTION__, SDL_GetError()); - - SDL_RenderClear(mainRenderer); - if(0 != SDL_RenderCopy(mainRenderer, screenTexture, nullptr, nullptr)) - logGlobal->error("%sSDL_RenderCopy %s", __FUNCTION__, SDL_GetError()); - SDL_RenderPresent(mainRenderer); - -} - SDL_Surface * CSDL_Ext::newSurface(int w, int h) { return newSurface(w, h, screen); @@ -221,8 +210,8 @@ uint32_t CSDL_Ext::getPixel(SDL_Surface *surface, const int & x, const int & y, } } -template -int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPointInput) +template +int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPointInput, [[maybe_unused]] uint8_t alpha) { SDL_Rect srcRectInstance = CSDL_Ext::toSDL(srcRectInput); SDL_Rect dstRectInstance = CSDL_Ext::toSDL(Rect(dstPointInput, srcRectInput.dimensions())); @@ -337,15 +326,20 @@ int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRec uint8_t *colory = (uint8_t*)src->pixels + srcy*src->pitch + srcx; uint8_t *py = (uint8_t*)dst->pixels + dstRect->y*dst->pitch + dstRect->x*bpp; - for(int y=h; y; y--, colory+=src->pitch, py+=dst->pitch) + for(int y=0; ypitch, py+=dst->pitch) { uint8_t *color = colory; uint8_t *p = py; - for(int x = w; x; x--) + for(int x = 0; x < w; ++x) { const SDL_Color &tbc = colors[*color++]; //color to blit - ColorPutter::PutColorAlphaSwitch(p, tbc.r, tbc.g, tbc.b, tbc.a); + if constexpr (useAlpha) + ColorPutter::PutColorAlphaSwitch(p, tbc.r, tbc.g, tbc.b, int(alpha) * tbc.a / 255 ); + else + ColorPutter::PutColorAlphaSwitch(p, tbc.r, tbc.g, tbc.b, tbc.a); + + p += bpp; } } SDL_UnlockSurface(dst); @@ -354,17 +348,27 @@ int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRec return 0; } -int CSDL_Ext::blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint) +int CSDL_Ext::blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint, uint8_t alpha) { - switch(dst->format->BytesPerPixel) + if (alpha == SDL_ALPHA_OPAQUE) { - case 2: return blit8bppAlphaTo24bppT<2>(src, srcRect, dst, dstPoint); - case 3: return blit8bppAlphaTo24bppT<3>(src, srcRect, dst, dstPoint); - case 4: return blit8bppAlphaTo24bppT<4>(src, srcRect, dst, dstPoint); - default: - logGlobal->error("%d bpp is not supported!", (int)dst->format->BitsPerPixel); - return -1; + switch(dst->format->BytesPerPixel) + { + case 3: return blit8bppAlphaTo24bppT<3, false>(src, srcRect, dst, dstPoint, alpha); + case 4: return blit8bppAlphaTo24bppT<4, false>(src, srcRect, dst, dstPoint, alpha); + } } + else + { + switch(dst->format->BytesPerPixel) + { + case 3: return blit8bppAlphaTo24bppT<3, true>(src, srcRect, dst, dstPoint, alpha); + case 4: return blit8bppAlphaTo24bppT<4, true>(src, srcRect, dst, dstPoint, alpha); + } + } + + logGlobal->error("%d bpp is not supported!", (int)dst->format->BitsPerPixel); + return -1; } uint32_t CSDL_Ext::colorTouint32_t(const SDL_Color * color) @@ -422,7 +426,7 @@ static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S uint8_t a = vstd::lerp(color1.a, color2.a, f); uint8_t *p = CSDL_Ext::getPxPtr(sur, x, y); - ColorPutter<4, 0>::PutColor(p, r,g,b,a); + ColorPutter<4>::PutColor(p, r,g,b,a); } } @@ -440,7 +444,7 @@ static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S uint8_t a = vstd::lerp(color1.a, color2.a, f); uint8_t *p = CSDL_Ext::getPxPtr(sur, x, y); - ColorPutter<4, 0>::PutColor(p, r,g,b,a); + ColorPutter<4>::PutColor(p, r,g,b,a); } } @@ -453,7 +457,7 @@ void CSDL_Ext::drawLine(SDL_Surface * sur, const Point & from, const Point & des if ( width == 0 && height == 0) { uint8_t *p = CSDL_Ext::getPxPtr(sur, from.x, from.y); - ColorPutter<4, 0>::PutColorAlpha(p, color1); + ColorPutter<4>::PutColorAlpha(p, color1); return; } @@ -524,59 +528,18 @@ void CSDL_Ext::drawBorder( SDL_Surface * sur, const Rect &r, const SDL_Color &co drawBorder(sur, r.x, r.y, r.w, r.h, color, depth); } -void CSDL_Ext::setPlayerColor(SDL_Surface * sur, const PlayerColor & player) +CSDL_Ext::TColorPutter CSDL_Ext::getPutterFor(SDL_Surface * const &dest) { - if(player==PlayerColor::UNFLAGGABLE) - return; - if(sur->format->BitsPerPixel==8) - { - ColorRGBA color = (player == PlayerColor::NEUTRAL - ? graphics->neutralColor - : graphics->playerColors[player.getNum()]); - - SDL_Color colorSDL = toSDL(color); - CSDL_Ext::setColors(sur, &colorSDL, 5, 1); - } - else - logGlobal->warn("Warning, setPlayerColor called on not 8bpp surface!"); -} - -CSDL_Ext::TColorPutter CSDL_Ext::getPutterFor(SDL_Surface * const &dest, int incrementing) -{ -#define CASE_BPP(BytesPerPixel) \ -case BytesPerPixel: \ - if(incrementing > 0) \ - return ColorPutter::PutColor; \ - else if(incrementing == 0) \ - return ColorPutter::PutColor; \ - else \ - return ColorPutter::PutColor;\ - break; - switch(dest->format->BytesPerPixel) { - CASE_BPP(2) - CASE_BPP(3) - CASE_BPP(4) + case 3: + return ColorPutter<3>::PutColor; + case 4: + return ColorPutter<4>::PutColor; default: logGlobal->error("%d bpp is not supported!", (int)dest->format->BitsPerPixel); return nullptr; } - -} - -CSDL_Ext::TColorPutterAlpha CSDL_Ext::getPutterAlphaFor(SDL_Surface * const &dest, int incrementing) -{ - switch(dest->format->BytesPerPixel) - { - CASE_BPP(2) - CASE_BPP(3) - CASE_BPP(4) - default: - logGlobal->error("%d bpp is not supported!", (int)dest->format->BitsPerPixel); - return nullptr; - } -#undef CASE_BPP } uint8_t * CSDL_Ext::getPxPtr(const SDL_Surface * const &srf, const int x, const int y) @@ -607,11 +570,10 @@ bool CSDL_Ext::isTransparent( SDL_Surface * srf, int x, int y ) void CSDL_Ext::putPixelWithoutRefresh(SDL_Surface *ekran, const int & x, const int & y, const uint8_t & R, const uint8_t & G, const uint8_t & B, uint8_t A) { uint8_t *p = getPxPtr(ekran, x, y); - getPutterFor(ekran, false)(p, R, G, B); + getPutterFor(ekran)(p, R, G, B); switch(ekran->format->BytesPerPixel) { - case 2: Channels::px<2>::a.set(p, A); break; case 3: Channels::px<3>::a.set(p, A); break; case 4: Channels::px<4>::a.set(p, A); break; } @@ -655,126 +617,28 @@ void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect ) { switch(surf->format->BytesPerPixel) { - case 2: convertToGrayscaleBpp<2>(surf, rect); break; case 3: convertToGrayscaleBpp<3>(surf, rect); break; case 4: convertToGrayscaleBpp<4>(surf, rect); break; } } -template -void scaleSurfaceFastInternal(SDL_Surface *surf, SDL_Surface *ret) -{ - const float factorX = static_cast(surf->w) / static_cast(ret->w); - const float factorY = static_cast(surf->h) / static_cast(ret->h); - - for(int y = 0; y < ret->h; y++) - { - for(int x = 0; x < ret->w; x++) - { - //coordinates we want to calculate - auto origX = static_cast(floor(factorX * x)); - auto origY = static_cast(floor(factorY * y)); - - // Get pointers to source pixels - uint8_t *srcPtr = (uint8_t*)surf->pixels + origY * surf->pitch + origX * bpp; - uint8_t *destPtr = (uint8_t*)ret->pixels + y * ret->pitch + x * bpp; - - memcpy(destPtr, srcPtr, bpp); - } - } -} - -SDL_Surface * CSDL_Ext::scaleSurfaceFast(SDL_Surface *surf, int width, int height) -{ - if (!surf || !width || !height) - return nullptr; - - //Same size? return copy - this should more be faster - if (width == surf->w && height == surf->h) - return copySurface(surf); - - SDL_Surface *ret = newSurface(width, height, surf); - - switch(surf->format->BytesPerPixel) - { - case 1: scaleSurfaceFastInternal<1>(surf, ret); break; - case 2: scaleSurfaceFastInternal<2>(surf, ret); break; - case 3: scaleSurfaceFastInternal<3>(surf, ret); break; - case 4: scaleSurfaceFastInternal<4>(surf, ret); break; - } - return ret; -} - -template -void scaleSurfaceInternal(SDL_Surface *surf, SDL_Surface *ret) -{ - const float factorX = float(surf->w - 1) / float(ret->w), - factorY = float(surf->h - 1) / float(ret->h); - - for(int y = 0; y < ret->h; y++) - { - for(int x = 0; x < ret->w; x++) - { - //coordinates we want to interpolate - float origX = factorX * x, - origY = factorY * y; - - float x1 = floor(origX), x2 = floor(origX+1), - y1 = floor(origY), y2 = floor(origY+1); - //assert( x1 >= 0 && y1 >= 0 && x2 < surf->w && y2 < surf->h);//All pixels are in range - - // Calculate weights of each source pixel - float w11 = ((origX - x1) * (origY - y1)); - float w12 = ((origX - x1) * (y2 - origY)); - float w21 = ((x2 - origX) * (origY - y1)); - float w22 = ((x2 - origX) * (y2 - origY)); - //assert( w11 + w12 + w21 + w22 > 0.99 && w11 + w12 + w21 + w22 < 1.01);//total weight is ~1.0 - - // Get pointers to source pixels - uint8_t *p11 = (uint8_t*)surf->pixels + int(y1) * surf->pitch + int(x1) * bpp; - uint8_t *p12 = p11 + bpp; - uint8_t *p21 = p11 + surf->pitch; - uint8_t *p22 = p21 + bpp; - // Calculate resulting channels -#define PX(X, PTR) Channels::px::X.get(PTR) - int resR = static_cast(PX(r, p11) * w11 + PX(r, p12) * w12 + PX(r, p21) * w21 + PX(r, p22) * w22); - int resG = static_cast(PX(g, p11) * w11 + PX(g, p12) * w12 + PX(g, p21) * w21 + PX(g, p22) * w22); - int resB = static_cast(PX(b, p11) * w11 + PX(b, p12) * w12 + PX(b, p21) * w21 + PX(b, p22) * w22); - int resA = static_cast(PX(a, p11) * w11 + PX(a, p12) * w12 + PX(a, p21) * w21 + PX(a, p22) * w22); - //assert(resR < 256 && resG < 256 && resB < 256 && resA < 256); -#undef PX - uint8_t *dest = (uint8_t*)ret->pixels + y * ret->pitch + x * bpp; - Channels::px::r.set(dest, resR); - Channels::px::g.set(dest, resG); - Channels::px::b.set(dest, resB); - Channels::px::a.set(dest, resA); - } - } -} - // scaling via bilinear interpolation algorithm. // NOTE: best results are for scaling in range 50%...200%. // And upscaling looks awful right now - should be fixed somehow -SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface *surf, int width, int height) +SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height) { - if (!surf || !width || !height) + if(!surf || !width || !height) return nullptr; - if (surf->format->palette) - return scaleSurfaceFast(surf, width, height); + SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0); + SDL_Surface * ret = newSurface(width, height, intermediate); - //Same size? return copy - this should more be faster - if (width == surf->w && height == surf->h) - return copySurface(surf); - - SDL_Surface *ret = newSurface(width, height, surf); - - switch(surf->format->BytesPerPixel) - { - case 2: scaleSurfaceInternal<2>(surf, ret); break; - case 3: scaleSurfaceInternal<3>(surf, ret); break; - case 4: scaleSurfaceInternal<4>(surf, ret); break; - } +#if SDL_VERSION_ATLEAST(2,0,16) + SDL_SoftStretchLinear(intermediate, nullptr, ret, nullptr); +#else + SDL_SoftStretch(intermediate, nullptr, ret, nullptr); +#endif + SDL_FreeSurface(intermediate); return ret; } @@ -868,7 +732,5 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other) other = CSDL_Ext::fromSDL(rect); } -template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int); - diff --git a/client/renderSDL/SDL_Extensions.h b/client/renderSDL/SDL_Extensions.h index c85458c09..9b1dec1aa 100644 --- a/client/renderSDL/SDL_Extensions.h +++ b/client/renderSDL/SDL_Extensions.h @@ -61,8 +61,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &, void fillRect(SDL_Surface * dst, const Rect & dstrect, const SDL_Color & color); void fillRectBlended(SDL_Surface * dst, const Rect & dstrect, const SDL_Color & color); - void updateRect(SDL_Surface * surface, const Rect & rect); - void putPixelWithoutRefresh(SDL_Surface * ekran, const int & x, const int & y, const uint8_t & R, const uint8_t & G, const uint8_t & B, uint8_t A = 255); void putPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x, const int & y, const uint8_t & R, const uint8_t & G, const uint8_t & B, uint8_t A = 255); @@ -73,12 +71,11 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &, bool isTransparent(SDL_Surface * srf, const Point & position); //checks if surface is transparent at given position uint8_t * getPxPtr(const SDL_Surface * const & srf, const int x, const int y); - TColorPutter getPutterFor(SDL_Surface * const & dest, int incrementing); //incrementing: -1, 0, 1 - TColorPutterAlpha getPutterAlphaFor(SDL_Surface * const & dest, int incrementing); //incrementing: -1, 0, 1 + TColorPutter getPutterFor(SDL_Surface * const & dest); - template - int blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint); //blits 8 bpp surface with alpha channel to 24 bpp surface - int blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint); //blits 8 bpp surface with alpha channel to 24 bpp surface + template + int blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint, uint8_t alpha); //blits 8 bpp surface with alpha channel to 24 bpp surface + int blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint, uint8_t alpha); //blits 8 bpp surface with alpha channel to 24 bpp surface uint32_t colorTouint32_t(const SDL_Color * color); //little endian only void drawLine(SDL_Surface * sur, const Point & from, const Point & dest, const SDL_Color & color1, const SDL_Color & color2); @@ -86,7 +83,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &, void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color & color, int depth = 1); void drawBorder(SDL_Surface * sur, const Rect & r, const SDL_Color & color, int depth = 1); - void setPlayerColor(SDL_Surface * sur, const PlayerColor & player); //sets correct color of flags; -1 for neutral SDL_Surface * newSurface(int w, int h, SDL_Surface * mod); //creates new surface, with flags/format same as in surface given SDL_Surface * newSurface(int w, int h); //creates new surface, with flags/format same as in screen surface @@ -94,10 +90,7 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &, template SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value - //scale surface to required size. - //nearest neighbour algorithm - SDL_Surface * scaleSurfaceFast(SDL_Surface * surf, int width, int height); - // bilinear filtering. Uses fallback to scaleSurfaceFast in case of indexed surfaces + // bilinear filtering. Always returns rgba surface SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height); template diff --git a/client/renderSDL/SDL_PixelAccess.h b/client/renderSDL/SDL_PixelAccess.h index ddd91a663..2cf41ff28 100644 --- a/client/renderSDL/SDL_PixelAccess.h +++ b/client/renderSDL/SDL_PixelAccess.h @@ -109,58 +109,29 @@ namespace Channels }; #endif - - template<> - struct px<2> - { - static channel_subpx<5, 0xF800, 11> r; - static channel_subpx<6, 0x07E0, 5 > g; - static channel_subpx<5, 0x001F, 0 > b; - static channel_empty a; - }; } -template +template struct ColorPutter { static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B); static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A); static STRONG_INLINE void PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A); - static STRONG_INLINE void PutColor(uint8_t *&ptr, const SDL_Color & Color); static STRONG_INLINE void PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color); - static STRONG_INLINE void PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count); }; -template -struct ColorPutter<2, incrementPtr> -{ - static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B); - static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A); - static STRONG_INLINE void PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A); - static STRONG_INLINE void PutColor(uint8_t *&ptr, const SDL_Color & Color); - static STRONG_INLINE void PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color); - static STRONG_INLINE void PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count); -}; - -template -STRONG_INLINE void ColorPutter::PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color) +template +STRONG_INLINE void ColorPutter::PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color) { PutColor(ptr, Color.r, Color.g, Color.b, Color.a); } -template -STRONG_INLINE void ColorPutter::PutColor(uint8_t *&ptr, const SDL_Color & Color) -{ - PutColor(ptr, Color.r, Color.g, Color.b); -} - -template -STRONG_INLINE void ColorPutter::PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A) +template +STRONG_INLINE void ColorPutter::PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A) { switch (A) { case 0: - ptr += bpp * incrementPtr; return; case 255: PutColor(ptr, R, G, B); @@ -177,124 +148,19 @@ STRONG_INLINE void ColorPutter::PutColorAlphaSwitch(uint8_t * } } -template -STRONG_INLINE void ColorPutter::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A) +template +STRONG_INLINE void ColorPutter::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A) { PutColor(ptr, ((((uint32_t)R - (uint32_t)Channels::px::r.get(ptr))*(uint32_t)A) >> 8 ) + (uint32_t)Channels::px::r.get(ptr), ((((uint32_t)G - (uint32_t)Channels::px::g.get(ptr))*(uint32_t)A) >> 8 ) + (uint32_t)Channels::px::g.get(ptr), ((((uint32_t)B - (uint32_t)Channels::px::b.get(ptr))*(uint32_t)A) >> 8 ) + (uint32_t)Channels::px::b.get(ptr)); } - -template -STRONG_INLINE void ColorPutter::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B) +template +STRONG_INLINE void ColorPutter::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B) { - static_assert(incrementPtr >= -1 && incrementPtr <= +1, "Invalid incrementPtr value!"); - - if (incrementPtr < 0) - ptr -= bpp; - Channels::px::r.set(ptr, R); Channels::px::g.set(ptr, G); Channels::px::b.set(ptr, B); Channels::px::a.set(ptr, 255); - - if (incrementPtr > 0) - ptr += bpp; - -} - -template -STRONG_INLINE void ColorPutter::PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count) -{ - if (count) - { - uint8_t *pixel = ptr; - PutColor(ptr, Color.r, Color.g, Color.b); - - for (size_t i=0; i -STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B) -{ - if(incrementPtr == -1) - ptr -= 2; - - auto * const px = (uint16_t *)ptr; - *px = (B>>3) + ((G>>2) << 5) + ((R>>3) << 11); //drop least significant bits of 24 bpp encoded color - - if(incrementPtr == 1) - ptr += 2; //bpp -} - -template -STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A) -{ - switch (A) - { - case 0: - ptr += 2 * incrementPtr; - return; - case 255: - PutColor(ptr, R, G, B); - return; - default: - PutColor(ptr, R, G, B, A); - return; - } -} - -template -STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A) -{ - const int rbit = 5, gbit = 6, bbit = 5; //bits per color - const int rmask = 0xF800, gmask = 0x7E0, bmask = 0x1F; - const int rshift = 11, gshift = 5, bshift = 0; - - const uint8_t r5 = (*((uint16_t *)ptr) & rmask) >> rshift, - b5 = (*((uint16_t *)ptr) & bmask) >> bshift, - g5 = (*((uint16_t *)ptr) & gmask) >> gshift; - - const uint32_t r8 = (r5 << (8 - rbit)) | (r5 >> (2*rbit - 8)), - g8 = (g5 << (8 - gbit)) | (g5 >> (2*gbit - 8)), - b8 = (b5 << (8 - bbit)) | (b5 >> (2*bbit - 8)); - - PutColor(ptr, - (((R-r8)*A) >> 8) + r8, - (((G-g8)*A) >> 8) + g8, - (((B-b8)*A) >> 8) + b8); -} - -template -STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color) -{ - PutColor(ptr, Color.r, Color.g, Color.b, Color.a); -} - -template -STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColor(uint8_t *&ptr, const SDL_Color & Color) -{ - PutColor(ptr, Color.r, Color.g, Color.b); -} - -template -STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count) -{ - //drop least significant bits of 24 bpp encoded color - uint16_t pixel = (Color.b>>3) + ((Color.g>>2) << 5) + ((Color.r>>3) << 11); - - for (size_t i=0; i & callback) this->callback += callback; } +void CButton::addPopupCallback(const std::function & callback) +{ + this->callbackPopup += callback; +} + void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color) { OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); @@ -95,7 +99,7 @@ void ButtonBase::setImage(const AnimationPath & defName, bool playerColoredButto pos = image->pos; if (playerColoredButton) - image->playerColored(LOCPLINT->playerID); + image->setPlayerColor(LOCPLINT->playerID); } const JsonNode & ButtonBase::getCurrentConfig() const @@ -130,7 +134,7 @@ void ButtonBase::setConfigurable(const JsonPath & jsonName, bool playerColoredBu pos = configurable->pos; if (playerColoredButton) - image->playerColored(LOCPLINT->playerID); + image->setPlayerColor(LOCPLINT->playerID); } void CButton::addHoverText(EButtonState state, const std::string & text) @@ -289,6 +293,8 @@ void CButton::clickCancel(const Point & cursorPosition) void CButton::showPopupWindow(const Point & cursorPosition) { + callbackPopup(); + if(!helpBox.empty()) //there is no point to show window with nothing inside... CRClickPopup::createAndPush(helpBox); } @@ -358,7 +364,7 @@ CButton::CButton(Point position, const AnimationPath &defName, const std::pairisPlayerColored()) - image->playerColored(player); + image->setPlayerColor(player); } void CButton::showAll(Canvas & to) diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index e5bad6aab..34d97ec83 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -69,6 +69,7 @@ public: class CButton : public ButtonBase { CFunctionList callback; + CFunctionList callbackPopup; std::array hoverTexts; //texts for statusbar, if empty - first entry will be used std::optional borderColor; // mapping of button state to border color @@ -90,6 +91,7 @@ public: /// adds one more callback to on-click actions void addCallback(const std::function & callback); + void addPopupCallback(const std::function & callback); void addHoverText(EButtonState state, const std::string & text); diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp index 6acf43b61..667cfdc22 100644 --- a/client/widgets/CGarrisonInt.cpp +++ b/client/widgets/CGarrisonInt.cpp @@ -17,7 +17,6 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../render/IImage.h" -#include "../render/Graphics.h" #include "../windows/CCreatureWindow.h" #include "../windows/CWindowWithArtifacts.h" #include "../windows/GUIClasses.h" @@ -437,10 +436,10 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa AnimationPath imgName = AnimationPath::builtin(owner->smallIcons ? "cprsmall" : "TWCRPORT"); - creatureImage = std::make_shared(graphics->getAnimation(imgName), 0); + creatureImage = std::make_shared(imgName, 0); creatureImage->disable(); - selectionImage = std::make_shared(graphics->getAnimation(imgName), 1); + selectionImage = std::make_shared(imgName, 1); selectionImage->disable(); selectionImage->center(creatureImage->pos.center()); diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 58f50dcda..35218477e 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -19,7 +19,6 @@ #include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/ColorFilter.h" -#include "../render/Graphics.h" #include "../battle/BattleInterface.h" #include "../battle/BattleInterfaceClasses.h" @@ -51,7 +50,7 @@ CPicture::CPicture( const ImagePath & bmpname ) {} CPicture::CPicture( const ImagePath & bmpname, const Point & position ) - : bg(GH.renderHandler().loadImage(bmpname)) + : bg(GH.renderHandler().loadImage(bmpname, EImageBlitMode::COLORKEY)) , needRefresh(false) { pos.x += position.x; @@ -69,6 +68,14 @@ CPicture::CPicture( const ImagePath & bmpname, const Point & position ) } } +CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y) + : CPicture(bmpname, Point(x,y)) +{ + srcRect = SrcRect; + pos.w = srcRect->w; + pos.h = srcRect->h; +} + CPicture::CPicture(std::shared_ptr image, const Rect &SrcRect, int x, int y) : CPicture(image, Point(x,y)) { @@ -101,29 +108,29 @@ void CPicture::setAlpha(uint8_t value) void CPicture::scaleTo(Point size) { - bg = bg->scaleFast(size); + bg->scaleFast(size); pos.w = bg->width(); pos.h = bg->height(); } -void CPicture::colorize(PlayerColor player) +void CPicture::setPlayerColor(PlayerColor player) { bg->playerColored(player); } -CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position): - CIntObject(0, position.topLeft()), - texture(GH.renderHandler().loadImage(imageName)) +CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position) + : CIntObject(0, position.topLeft()) + , texture(GH.renderHandler().loadImage(imageName, EImageBlitMode::COLORKEY)) { pos.w = position.w; pos.h = position.h; imageArea = Rect(Point(), texture->dimensions()); } -CFilledTexture::CFilledTexture(std::shared_ptr image, Rect position, Rect imageArea) +CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position, Rect imageArea) : CIntObject(0, position.topLeft()) - , texture(image) + , texture(GH.renderHandler().loadImage(imageName, EImageBlitMode::COLORKEY)) , imageArea(imageArea) { pos.w = position.w; @@ -141,12 +148,12 @@ void CFilledTexture::showAll(Canvas & to) } } -FilledTexturePlayerColored::FilledTexturePlayerColored(const ImagePath & imageName, Rect position) - : CFilledTexture(imageName, position) +void FilledTexturePlayerIndexed::setPlayerColor(PlayerColor player) { + texture->playerColored(player); } -void FilledTexturePlayerColored::playerColored(PlayerColor player) +void FilledTexturePlayerColored::setPlayerColor(PlayerColor player) { // Color transform to make color of brown DIBOX.PCX texture match color of specified player std::array filters = { @@ -177,23 +184,23 @@ CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, size_t Group, i { pos.x += x; pos.y += y; - anim = graphics->getAnimation(name); + anim = GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY); init(); } -CAnimImage::CAnimImage(std::shared_ptr Anim, size_t Frame, size_t Group, int x, int y, ui8 Flags): - anim(Anim), - frame(Frame), - group(Group), - flags(Flags) -{ - pos.x += x; - pos.y += y; - init(); -} +//CAnimImage::CAnimImage(std::shared_ptr Anim, size_t Frame, size_t Group, int x, int y, ui8 Flags): +// anim(Anim), +// frame(Frame), +// group(Group), +// flags(Flags) +//{ +// pos.x += x; +// pos.y += y; +// init(); +//} -CAnimImage::CAnimImage(std::shared_ptr Anim, size_t Frame, Rect targetPos, size_t Group, ui8 Flags): - anim(Anim), +CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags): + anim(GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY)), frame(Frame), group(Group), flags(Flags), @@ -233,10 +240,6 @@ void CAnimImage::setSizeFromImage(const IImage &img) void CAnimImage::init() { visible = true; - anim->load(frame, group); - if (flags & CShowableAnim::BASE) - anim->load(0,group); - auto img = anim->getImage(frame, group); if (img) setSizeFromImage(*img); @@ -263,12 +266,9 @@ void CAnimImage::showAll(Canvas & to) if(auto img = anim->getImage(targetFrame, group)) { if(isScaled()) - { - auto scaled = img->scaleFast(scaledSize); - to.draw(scaled, pos.topLeft()); - } - else - to.draw(img, pos.topLeft()); + img->scaleFast(scaledSize); + + to.draw(img, pos.topLeft()); } } } @@ -276,7 +276,7 @@ void CAnimImage::showAll(Canvas & to) void CAnimImage::setAnimationPath(const AnimationPath & name, size_t frame) { this->frame = frame; - anim = GH.renderHandler().loadAnimation(name); + anim = GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY); init(); } @@ -291,7 +291,6 @@ void CAnimImage::setFrame(size_t Frame, size_t Group) return; if (anim->size(Group) > Frame) { - anim->load(Frame, Group); frame = Frame; group = Group; if(auto img = anim->getImage(frame, group)) @@ -305,7 +304,7 @@ void CAnimImage::setFrame(size_t Frame, size_t Group) logGlobal->error("Error: accessing unavailable frame %d:%d in CAnimation!", Group, Frame); } -void CAnimImage::playerColored(PlayerColor currPlayer) +void CAnimImage::setPlayerColor(PlayerColor currPlayer) { player = currPlayer; anim->getImage(frame, group)->playerColored(*player); @@ -319,7 +318,7 @@ bool CAnimImage::isPlayerColored() const } CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha): - anim(GH.renderHandler().loadAnimation(name)), + anim(GH.renderHandler().loadAnimation(name, (Flags & PALETTE_ALPHA) ? EImageBlitMode::ALPHA : EImageBlitMode::COLORKEY)), group(Group), frame(0), first(0), @@ -330,7 +329,6 @@ CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags yOffset(0), alpha(alpha) { - anim->loadGroup(group); last = anim->size(group); auto image = anim->getImage(0, group); @@ -345,11 +343,6 @@ CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags addUsedEvents(TIME); } -CShowableAnim::~CShowableAnim() -{ - anim->unloadGroup(group); -} - void CShowableAnim::setAlpha(ui32 alphaValue) { alpha = std::min(alphaValue, 255); @@ -365,9 +358,6 @@ bool CShowableAnim::set(size_t Group, size_t from, size_t to) if (max < from || max == 0) return false; - anim->unloadGroup(group); - anim->loadGroup(Group); - group = Group; frame = first = from; last = max; @@ -381,9 +371,6 @@ bool CShowableAnim::set(size_t Group) return false; if (group != Group) { - anim->unloadGroup(group); - anim->loadGroup(Group); - first = 0; group = Group; last = anim->size(Group); @@ -464,7 +451,7 @@ void CShowableAnim::setDuration(int durationMs) } CCreatureAnim::CCreatureAnim(int x, int y, const AnimationPath & name, ui8 flags, ECreatureAnimType type): - CShowableAnim(x, y, name, flags, 100, size_t(type)) // H3 uses 100 ms per frame, irregardless of battle speed settings + CShowableAnim(x, y, name, flags | PALETTE_ALPHA, 100, size_t(type)) // H3 uses 100 ms per frame, irregardless of battle speed settings { xOffset = 0; yOffset = 0; diff --git a/client/widgets/Images.h b/client/widgets/Images.h index 004f7916a..5e66276d1 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -44,6 +44,7 @@ public: /// wrap section of an existing Image CPicture(std::shared_ptr image, const Rect &SrcRext, int x = 0, int y = 0); //wrap subrect of given surface + CPicture(const ImagePath & bmpname, const Rect &SrcRext, int x = 0, int y = 0); //wrap subrect of given surface /// Loads image from specified file name CPicture(const ImagePath & bmpname); @@ -54,7 +55,7 @@ public: /// 0=transparent, 255=opaque void setAlpha(uint8_t value); void scaleTo(Point size); - void colorize(PlayerColor player); + void setPlayerColor(PlayerColor player); void show(Canvas & to) override; void showAll(Canvas & to) override; @@ -68,18 +69,28 @@ protected: Rect imageArea; public: + CFilledTexture(const ImagePath & imageName, Rect position, Rect imageArea); CFilledTexture(const ImagePath & imageName, Rect position); - CFilledTexture(std::shared_ptr image, Rect position, Rect imageArea); void showAll(Canvas & to) override; }; +/// area filled with specific texture, colorized to player color if image is indexed +class FilledTexturePlayerIndexed : public CFilledTexture +{ +public: + using CFilledTexture::CFilledTexture; + + void setPlayerColor(PlayerColor player); +}; + +/// area filled with specific texture, with applied color filter to colorize it to specific player class FilledTexturePlayerColored : public CFilledTexture { public: - FilledTexturePlayerColored(const ImagePath & imageName, Rect position); + using CFilledTexture::CFilledTexture; - void playerColored(PlayerColor player); + void setPlayerColor(PlayerColor player); }; /// Class for displaying one image from animation @@ -103,8 +114,8 @@ public: bool visible; CAnimImage(const AnimationPath & name, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); - CAnimImage(std::shared_ptr Anim, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); - CAnimImage(std::shared_ptr Anim, size_t Frame, Rect targetPos, size_t Group=0, ui8 Flags=0); +// CAnimImage(std::shared_ptr Anim, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0); + CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group=0, ui8 Flags=0); ~CAnimImage(); /// size of animation @@ -114,7 +125,7 @@ public: void setFrame(size_t Frame, size_t Group=0); /// makes image player-colored to specific player - void playerColored(PlayerColor player); + void setPlayerColor(PlayerColor player); /// returns true if image has player-colored effect applied bool isPlayerColored() const; @@ -135,6 +146,7 @@ public: BASE=1, //base frame will be blitted before current one HORIZONTAL_FLIP=2, //TODO: will be displayed rotated VERTICAL_FLIP=4, //TODO: will be displayed rotated + PALETTE_ALPHA=8, // use alpha channel for images with palette. Required for creatures in battle and map objects PLAY_ONCE=32 //play animation only once and stop at last frame }; protected: @@ -168,7 +180,6 @@ public: void setAlpha(ui32 alphaValue); CShowableAnim(int x, int y, const AnimationPath & name, ui8 flags, ui32 frameTime, size_t Group=0, uint8_t alpha = UINT8_MAX); - ~CShowableAnim(); //set animation to group or part of group bool set(size_t Group); diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index 1bdba2a75..81f1da476 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -28,7 +28,6 @@ #include "../windows/CCastleInterface.h" #include "../windows/InfoWindows.h" #include "../render/Canvas.h" -#include "../render/Graphics.h" #include "../../CCallback.h" @@ -249,7 +248,7 @@ CMinorResDataBar::CMinorResDataBar() pos.y = 575; background = std::make_shared(ImagePath::builtin("KRESBAR.bmp")); - background->colorize(LOCPLINT->playerID); + background->setPlayerColor(LOCPLINT->playerID); pos.w = background->pos.w; pos.h = background->pos.h; @@ -535,7 +534,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature) auto creatureID = creature->getCreature(); int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex(); - creatureImage = std::make_shared(graphics->getAnimation(AnimationPath::builtin("TWCRPORT")), creatureIconIndex); + creatureImage = std::make_shared(AnimationPath::builtin("TWCRPORT"), creatureIconIndex); creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11)); bool isHeroSelected = LOCPLINT->localState->getCurrentHero() != nullptr; diff --git a/client/widgets/ObjectLists.h b/client/widgets/ObjectLists.h index d4e07de08..11703130c 100644 --- a/client/widgets/ObjectLists.h +++ b/client/widgets/ObjectLists.h @@ -18,7 +18,6 @@ VCMI_LIB_NAMESPACE_END class CAnimImage; class CSlider; class CLabel; -class CAnimation; /// Used as base for Tabs and List classes class CObjectList : public CIntObject diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 3b805687f..bb2fe18c0 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -95,7 +95,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town } if(!str->borderName.empty()) - border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::ALPHA); + border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::COLORKEY); if(!str->areaName.empty()) area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::ALPHA); @@ -272,7 +272,7 @@ CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstanc : CWindowObject(RCLICK_POPUP, ImagePath::builtin("CRTOINFO"), Point(centerX, centerY)) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - background->colorize(Town->tempOwner); + background->setPlayerColor(Town->tempOwner); const CCreature * creature = Town->creatures.at(level).second.back().toCreature(); @@ -430,7 +430,7 @@ void CHeroGSlot::clickPressed(const Point & cursorPosition) { setHighlight(false); - if(other->hero) + if(other->hero && !GH.isKeyboardShiftDown()) LOCPLINT->showHeroExchange(hero->id, other->hero->id); else LOCPLINT->openHeroWindow(hero); @@ -885,9 +885,9 @@ void CCastleBuildings::enterCastleGate() availableTowns.push_back(t->id.getNum());//add to the list if(settings["general"]["enableUiEnhancements"].Bool()) { - std::shared_ptr a = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITPA")); - a->preload(); - images.push_back(a->getImage(t->town->clientInfo.icons[t->hasFort()][false] + 2)->scaleFast(Point(35, 23))); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); + image->scaleFast(Point(35, 23)); + images.push_back(image); } } } @@ -1245,7 +1245,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst builds = std::make_shared(town); panel = std::make_shared(ImagePath::builtin("TOWNSCRN"), 0, builds->pos.h); - panel->colorize(LOCPLINT->playerID); + panel->setPlayerColor(LOCPLINT->playerID); pos.w = panel->pos.w; pos.h = builds->pos.h + panel->pos.h; center(); @@ -1283,7 +1283,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst recreateIcons(); if (!from) adventureInt->onAudioPaused(); - CCS->musich->playMusic(town->town->clientInfo.musicTheme, true, false); + CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false); } CCastleInterface::~CCastleInterface() diff --git a/client/windows/CExchangeWindow.cpp b/client/windows/CExchangeWindow.cpp index e447deb70..6d0fca66e 100644 --- a/client/windows/CExchangeWindow.cpp +++ b/client/windows/CExchangeWindow.cpp @@ -26,7 +26,6 @@ #include "../widgets/TextControls.h" #include "../render/IRenderHandler.h" -#include "../render/CAnimation.h" #include "../../CCallback.h" @@ -66,17 +65,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, titles[0] = std::make_shared(147, 25, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, genTitle(heroInst[0])); titles[1] = std::make_shared(653, 25, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, genTitle(heroInst[1])); - auto PSKIL32 = GH.renderHandler().loadAnimation(AnimationPath::builtin("PSKIL32")); - PSKIL32->preload(); - - auto SECSK32 = GH.renderHandler().loadAnimation(AnimationPath::builtin("SECSK32")); - for(int g = 0; g < 4; ++g) { if (qeLayout) - primSkillImages.push_back(std::make_shared(PSKIL32, g, Rect(389, 12 + 26 * g, 22, 22))); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL32"), g, Rect(389, 12 + 26 * g, 22, 22))); else - primSkillImages.push_back(std::make_shared(PSKIL32, g, 0, 385, 19 + 36 * g)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL32"), g, 0, 385, 19 + 36 * g)); } for(int leftRight : {0, 1}) @@ -88,14 +82,14 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, for(int m=0; m < hero->secSkills.size(); ++m) - secSkillIcons[leftRight].push_back(std::make_shared(SECSK32, 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88)); + secSkillIcons[leftRight].push_back(std::make_shared(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88)); specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); - expImages[leftRight] = std::make_shared(PSKIL32, 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45); + expImages[leftRight] = std::make_shared(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45); expValues[leftRight] = std::make_shared(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); - manaImages[leftRight] = std::make_shared(PSKIL32, 5, 0, 139 + 490 * leftRight, qeLayout ? 41 : 45); + manaImages[leftRight] = std::make_shared(AnimationPath::builtin("PSKIL32"), 5, 0, 139 + 490 * leftRight, qeLayout ? 41 : 45); manaValues[leftRight] = std::make_shared(155 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); } diff --git a/client/windows/CHeroOverview.cpp b/client/windows/CHeroOverview.cpp index 390d94674..563bf9644 100644 --- a/client/windows/CHeroOverview.cpp +++ b/client/windows/CHeroOverview.cpp @@ -14,7 +14,6 @@ #include "../gui/CGuiHandler.h" #include "../render/Canvas.h" #include "../render/Colors.h" -#include "../render/Graphics.h" #include "../render/IImage.h" #include "../renderSDL/RenderHandler.h" #include "../widgets/CComponent.h" @@ -225,12 +224,12 @@ void CHeroOverview::genControls() { if((*CGI->heroh)[heroIdx]->haveSpellBook) { - imageSpells.push_back(std::make_shared(GH.renderHandler().loadAnimation(AnimationPath::builtin("ARTIFACT")), 0, Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0)); + imageSpells.push_back(std::make_shared(AnimationPath::builtin("ARTIFACT"), 0, Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0)); } i++; } - imageSpells.push_back(std::make_shared(GH.renderHandler().loadAnimation(AnimationPath::builtin("SPELLBON")), (*CGI->spellh)[spell]->getIconIndex(), Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0)); + imageSpells.push_back(std::make_shared(AnimationPath::builtin("SPELLBON"), (*CGI->spellh)[spell]->getIconIndex(), Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0)); labelSpellsNames.push_back(std::make_shared(302 + (292 / 2) + 3 * borderOffset + 32 + borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 3, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->spellh)[spell]->getNameTranslated())); i++; } diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 469c3add4..36701a63a 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -28,7 +28,6 @@ #include "../widgets/CGarrisonInt.h" #include "../widgets/TextControls.h" #include "../widgets/Buttons.h" -#include "../render/CAnimation.h" #include "../render/IRenderHandler.h" #include "../../CCallback.h" @@ -131,14 +130,12 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) primSkillValues.push_back(value); } - auto primSkills = GH.renderHandler().loadAnimation(AnimationPath::builtin("PSKIL42")); - primSkills->preload(); - primSkillImages.push_back(std::make_shared(primSkills, 0, 0, 32, 111)); - primSkillImages.push_back(std::make_shared(primSkills, 1, 0, 102, 111)); - primSkillImages.push_back(std::make_shared(primSkills, 2, 0, 172, 111)); - primSkillImages.push_back(std::make_shared(primSkills, 3, 0, 162, 230)); - primSkillImages.push_back(std::make_shared(primSkills, 4, 0, 20, 230)); - primSkillImages.push_back(std::make_shared(primSkills, 5, 0, 242, 111)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL42"), 0, 0, 32, 111)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL42"), 1, 0, 102, 111)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL42"), 2, 0, 172, 111)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL42"), 3, 0, 162, 230)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL42"), 4, 0, 20, 230)); + primSkillImages.push_back(std::make_shared(AnimationPath::builtin("PSKIL42"), 5, 0, 242, 111)); specImage = std::make_shared(AnimationPath::builtin("UN44"), 0, 0, 18, 180); specArea = std::make_shared(Rect(18, 180, 136, 42), CGI->generaltexth->heroscrn[27]); @@ -152,12 +149,11 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) expValue = std::make_shared(68, 252); manaValue = std::make_shared(211, 252); - auto secSkills = GH.renderHandler().loadAnimation(AnimationPath::builtin("SECSKILL")); for(int i = 0; i < std::min(hero->secSkills.size(), 8u); ++i) { Rect r = Rect(i%2 == 0 ? 18 : 162, 276 + 48 * (i/2), 136, 42); secSkillAreas.push_back(std::make_shared(r, ComponentType::SEC_SKILL)); - secSkillImages.push_back(std::make_shared(secSkills, 0, 0, r.x, r.y)); + secSkillImages.push_back(std::make_shared(AnimationPath::builtin("SECSKILL"), 0, 0, r.x, r.y)); int x = (i % 2) ? 212 : 68; int y = 280 + 48 * (i/2); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 7b57bd51e..46feda7ef 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -692,7 +692,7 @@ CKingdHeroList::CKingdHeroList(size_t maxSize, const CreateHeroItemFunctor & onC { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); title = std::make_shared(ImagePath::builtin("OVTITLE"),16,0); - title->colorize(LOCPLINT->playerID); + title->setPlayerColor(LOCPLINT->playerID); heroLabel = std::make_shared(150, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[0]); skillsLabel = std::make_shared(500, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[1]); @@ -736,7 +736,7 @@ CKingdTownList::CKingdTownList(size_t maxSize) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); title = std::make_shared(ImagePath::builtin("OVTITLE"), 16, 0); - title->colorize(LOCPLINT->playerID); + title->setPlayerColor(LOCPLINT->playerID); townLabel = std::make_shared(146, 10,FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[3]); garrHeroLabel = std::make_shared(375, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[4]); visitHeroLabel = std::make_shared(608, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[5]); diff --git a/client/windows/CMapOverview.cpp b/client/windows/CMapOverview.cpp index d2fae75df..eb9a2c6f5 100644 --- a/client/windows/CMapOverview.cpp +++ b/client/windows/CMapOverview.cpp @@ -20,7 +20,6 @@ #include "../widgets/TextControls.h" #include "../windows/GUIClasses.h" #include "../windows/InfoWindows.h" -#include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/IImage.h" #include "../render/IRenderHandler.h" diff --git a/client/windows/CMarketWindow.cpp b/client/windows/CMarketWindow.cpp index de09e748b..2789f3ca8 100644 --- a/client/windows/CMarketWindow.cpp +++ b/client/windows/CMarketWindow.cpp @@ -70,12 +70,12 @@ void CMarketWindow::updateGarrisons() update(); } -void CMarketWindow::updateResource() +void CMarketWindow::updateResources() { update(); } -void CMarketWindow::updateHero() +void CMarketWindow::updateExperience() { update(); } @@ -251,7 +251,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns }; addSet(heroArts); initWidgetInternals(EMarketMode::ARTIFACT_EXP, CGI->generaltexth->zelp[568]); - updateHero(); + updateExperience(); quitButton->addCallback([altarArtifacts](){altarArtifacts->putBackArtifacts();}); } @@ -262,5 +262,5 @@ void CMarketWindow::createAltarCreatures(const IMarket * market, const CGHeroIns background = createBg(ImagePath::builtin("ALTARMON.bmp"), PLAYER_COLORED); marketWidget = std::make_shared(market, hero); initWidgetInternals(EMarketMode::CREATURE_EXP, CGI->generaltexth->zelp[568]); - updateHero(); + updateExperience(); } diff --git a/client/windows/CMarketWindow.h b/client/windows/CMarketWindow.h index 59403b84d..1f7d2179c 100644 --- a/client/windows/CMarketWindow.h +++ b/client/windows/CMarketWindow.h @@ -12,14 +12,14 @@ #include "../widgets/markets/CMarketBase.h" #include "CWindowWithArtifacts.h" -class CMarketWindow : public CStatusbarWindow, public CWindowWithArtifacts, public IGarrisonHolder +class CMarketWindow final : public CStatusbarWindow, public CWindowWithArtifacts, public IGarrisonHolder, public IMarketHolder { public: CMarketWindow(const IMarket * market, const CGHeroInstance * hero, const std::function & onWindowClosed, EMarketMode mode); - void updateResource(); - void updateArtifacts(); + void updateResources() override; + void updateArtifacts() override; void updateGarrisons() override; - void updateHero(); + void updateExperience() override; void update() override; void close() override; bool holdsGarrison(const CArmedInstance * army) override; diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index 68351ba08..5cf0dd3a7 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -41,8 +41,7 @@ void CMessage::init() { for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) { - dialogBorders[i] = GH.renderHandler().loadAnimation(AnimationPath::builtin("DIALGBOX")); - dialogBorders[i]->preload(); + dialogBorders[i] = GH.renderHandler().loadAnimation(AnimationPath::builtin("DIALGBOX"), EImageBlitMode::OPAQUE); for(int j = 0; j < dialogBorders[i]->size(0); j++) { diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 5c89602c3..d3274c304 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -30,7 +30,6 @@ #include "../widgets/CTextInput.h" #include "../widgets/TextControls.h" #include "../adventureMap/AdventureMapInterface.h" -#include "../render/CAnimation.h" #include "../render/IRenderHandler.h" #include "../render/IImage.h" #include "../render/IImageLoader.h" @@ -98,13 +97,15 @@ public: } }; -CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells): +CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells, std::function onSpellSelect): CWindowObject(PLAYER_COLORED | (settings["gameTweaks"]["enableLargeSpellbook"].Bool() ? BORDERED : 0)), battleSpellsOnly(openOnBattleSpells), selectedTab(4), currentPage(0), myHero(_myHero), myInt(_myInt), + openOnBattleSpells(openOnBattleSpells), + onSpellSelect(onSpellSelect), isBigSpellbook(settings["gameTweaks"]["enableLargeSpellbook"].Bool()), spellsPerPage(24), offL(-11), @@ -149,18 +150,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m leftCorner = std::make_shared(ImagePath::builtin("SpelTrnL.bmp"), 97 + offL, 77 + offT); rightCorner = std::make_shared(ImagePath::builtin("SpelTrnR.bmp"), 487 + offR, 72 + offT); - spellIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("Spells")); - schoolTab = std::make_shared(AnimationPath::builtin("SpelTab"), selectedTab, 0, 524 + offR, 88); schoolPicture = std::make_shared(AnimationPath::builtin("Schools"), 0, 0, 117 + offL, 74 + offT); - schoolBorders[0] = GH.renderHandler().loadAnimation(AnimationPath::builtin("SplevA.def")); - schoolBorders[1] = GH.renderHandler().loadAnimation(AnimationPath::builtin("SplevF.def")); - schoolBorders[2] = GH.renderHandler().loadAnimation(AnimationPath::builtin("SplevW.def")); - schoolBorders[3] = GH.renderHandler().loadAnimation(AnimationPath::builtin("SplevE.def")); - - for(auto item : schoolBorders) - item->preload(); mana = std::make_shared(435 + (isBigSpellbook ? 159 : 0), 426 + offB, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, std::to_string(myHero->mana)); if(isBigSpellbook) @@ -223,7 +215,7 @@ CSpellWindow::~CSpellWindow() std::shared_ptr CSpellWindow::createBigSpellBook() { - std::shared_ptr img = GH.renderHandler().loadImage(ImagePath::builtin("SpelBack")); + std::shared_ptr img = GH.renderHandler().loadImage(ImagePath::builtin("SpelBack"), EImageBlitMode::OPAQUE); Canvas canvas = Canvas(Point(800, 600)); // edges canvas.draw(img, Point(0, 0), Rect(15, 38, 90, 45)); @@ -293,6 +285,14 @@ void CSpellWindow::processSpells() for(auto const & spell : CGI->spellh->objects) { bool searchTextFound = !searchBox || boost::algorithm::contains(boost::algorithm::to_lower_copy(spell->getNameTranslated()), boost::algorithm::to_lower_copy(searchBox->getText())); + + if(onSpellSelect) + { + if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility()) + mySpells.push_back(spell.get()); + continue; + } + if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell.get()) && searchTextFound) mySpells.push_back(spell.get()); } @@ -585,7 +585,7 @@ CSpellWindow::SpellArea::SpellArea(Rect pos, CSpellWindow * owner) OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - image = std::make_shared(owner->spellIcons, 0, 0); + image = std::make_shared(AnimationPath::builtin("Spells"), 0, 0); image->visible = false; name = std::make_shared(39, 70, FONT_TINY, ETextAlignment::CENTER); @@ -602,6 +602,13 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition) { if(mySpell) { + if(owner->onSpellSelect) + { + owner->onSpellSelect(mySpell->id); + owner->fexitb(); + return; + } + auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero); if(spellCost > owner->myHero->mana) //insufficient mana { @@ -727,18 +734,26 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + static const std::array schoolBorders = { + AnimationPath::builtin("SplevA.def"), + AnimationPath::builtin("SplevF.def"), + AnimationPath::builtin("SplevW.def"), + AnimationPath::builtin("SplevE.def") + }; + schoolBorder.reset(); if (owner->selectedTab >= 4) { if (whichSchool.getNum() != SpellSchool()) - schoolBorder = std::make_shared(owner->schoolBorders.at(whichSchool.getNum()), schoolLevel); + schoolBorder = std::make_shared(schoolBorders.at(whichSchool.getNum()), schoolLevel); } else - schoolBorder = std::make_shared(owner->schoolBorders.at(owner->selectedTab), schoolLevel); + schoolBorder = std::make_shared(schoolBorders.at(owner->selectedTab), schoolLevel); } ColorRGBA firstLineColor, secondLineColor; - if(spellCost > owner->myHero->mana) //hero cannot cast this spell + if(spellCost > owner->myHero->mana && !owner->onSpellSelect) //hero cannot cast this spell { firstLineColor = Colors::WHITE; secondLineColor = Colors::ORANGE; diff --git a/client/windows/CSpellWindow.h b/client/windows/CSpellWindow.h index 16401a752..73dee4e4c 100644 --- a/client/windows/CSpellWindow.h +++ b/client/windows/CSpellWindow.h @@ -19,7 +19,6 @@ class CSpell; VCMI_LIB_NAMESPACE_END class IImage; -class CAnimation; class CAnimImage; class CPicture; class CLabel; @@ -67,9 +66,6 @@ class CSpellWindow : public CWindowObject InteractiveArea(const Rect &myRect, std::function funcL, int helpTextId, CSpellWindow * _owner); }; - std::shared_ptr spellIcons; - std::array, 4> schoolBorders; //[0]: air, [1]: fire, [2]: water, [3]: earth - std::shared_ptr leftCorner; std::shared_ptr rightCorner; @@ -115,8 +111,11 @@ class CSpellWindow : public CWindowObject std::shared_ptr createBigSpellBook(); + bool openOnBattleSpells; + std::function onSpellSelect; //external processing of selected spell + public: - CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells = true); + CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells = true, std::function onSpellSelect = nullptr); ~CSpellWindow(); void fexitb(); diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index 30f70432b..3909b0d45 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -93,7 +93,7 @@ std::shared_ptr CWindowObject::createBg(const ImagePath & imageName, b auto image = std::make_shared(imageName); image->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); if(playerColored) - image->colorize(LOCPLINT->playerID); + image->setPlayerColor(LOCPLINT->playerID); return image; } @@ -202,8 +202,8 @@ void CWindowObject::setShadow(bool on) //create base 8x8 piece of shadow SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl); - SDL_Surface * shadowBottom = CSDL_Ext::scaleSurfaceFast(shadowBottomTempl, fullsize.x - size, size); - SDL_Surface * shadowRight = CSDL_Ext::scaleSurfaceFast(shadowRightTempl, size, fullsize.y - size); + SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, fullsize.x - size, size); + SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, fullsize.y - size); blitAlphaCol(shadowBottom, 0); blitAlphaRow(shadowRight, 0); diff --git a/client/windows/CWindowWithArtifacts.cpp b/client/windows/CWindowWithArtifacts.cpp index 86ca47e11..ec9949a5c 100644 --- a/client/windows/CWindowWithArtifacts.cpp +++ b/client/windows/CWindowWithArtifacts.cpp @@ -237,9 +237,10 @@ void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst) if(artInst.isScroll() && settings["general"]["enableUiEnhancements"].Bool()) { assert(artInst.getScrollSpellID().num >= 0); - const auto animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("spellscr")); - animation->load(artInst.getScrollSpellID().num); - CCS->curh->dragAndDropCursor(animation->getImage(artInst.getScrollSpellID().num)->scaleFast(Point(44, 34))); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("spellscr"), artInst.getScrollSpellID().num, 0, EImageBlitMode::COLORKEY); + image->scaleFast(Point(44,34)); + + CCS->curh->dragAndDropCursor(image); } else { diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 3b2fb85d9..17f73ef91 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -36,7 +36,6 @@ #include "../widgets/VideoWidget.h" #include "../render/Canvas.h" -#include "../render/CAnimation.h" #include "../render/IRenderHandler.h" #include "../render/IImage.h" @@ -54,6 +53,7 @@ #include "../lib/CHeroHandler.h" #include "../lib/GameSettings.h" #include "ConditionalWait.h" +#include "../lib/CRandomGenerator.h" #include "../lib/CSkillHandler.h" #include "../lib/CSoundBase.h" @@ -891,21 +891,20 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int pos.x += X; pos.y += Y; - topBar = std::make_shared(parent->bars, 0, 0, -28, -22); - bottomBar = std::make_shared(parent->bars, 0, 0, -28, 48); - icon = std::make_shared(AnimationPath::builtin("SECSKILL"), _ID * 3 + 3, 0); - name = std::make_shared(22, -13, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->skillh->getByIndex(ID)->getNameTranslated()); - level = std::make_shared(22, 57, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[0]); - pos.h = icon->pos.h; pos.w = icon->pos.w; + + update(); } void CUniversityWindow::CItem::clickPressed(const Point & cursorPosition) { - if(state() == 2) + bool skillKnown = parent->hero->getSecSkillLevel(ID); + bool canLearn = parent->hero->canLearnSkill(ID); + + if (!skillKnown && canLearn) GH.windows().createAndPushWindow(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000); } @@ -914,33 +913,37 @@ void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition) CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared(ComponentType::SEC_SKILL, ID, 1)); } +void CUniversityWindow::CItem::update() +{ + bool skillKnown = parent->hero->getSecSkillLevel(ID); + bool canLearn = parent->hero->canLearnSkill(ID); + + ImagePath image; + + if (skillKnown) + image = ImagePath::builtin("UNIVGOLD"); + else if (canLearn) + image = ImagePath::builtin("UNIVGREN"); + else + image = ImagePath::builtin("UNIVRED"); + + OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + topBar = std::make_shared(image, Point(-28, -22)); + bottomBar = std::make_shared(image, Point(-28, 48)); + + // needs to be on top of background bars + name = std::make_shared(22, -13, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, ID.toEntity(VLC)->getNameTranslated()); + level = std::make_shared(22, 57, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[0]); +} + void CUniversityWindow::CItem::hover(bool on) { if(on) - GH.statusbar()->write(CGI->skillh->getByIndex(ID)->getNameTranslated()); + GH.statusbar()->write(ID.toEntity(VLC)->getNameTranslated()); else GH.statusbar()->clear(); } -int CUniversityWindow::CItem::state() -{ - if(parent->hero->getSecSkillLevel(SecondarySkill(ID)))//hero know this skill - return 1; - if(!parent->hero->canLearnSkill(SecondarySkill(ID)))//can't learn more skills - return 0; - return 2; -} - -void CUniversityWindow::CItem::showAll(Canvas & to) -{ - //TODO: update when state actually changes - auto stateIndex = state(); - topBar->setFrame(stateIndex); - bottomBar->setFrame(stateIndex); - - CIntObject::showAll(to); -} - CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market, const std::function & onWindowClosed) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("UNIVERS1")), hero(_hero), @@ -949,12 +952,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - bars = GH.renderHandler().createAnimation(); - bars->setCustom("UNIVRED", 0, 0); - bars->setCustom("UNIVGOLD", 1, 0); - bars->setCustom("UNIVGREN", 2, 0); - bars->preload(); - + std::string titleStr = CGI->generaltexth->allTexts[602]; std::string speechStr = CGI->generaltexth->allTexts[603]; @@ -997,12 +995,17 @@ void CUniversityWindow::close() CStatusbarWindow::close(); } +void CUniversityWindow::updateSecondarySkills() +{ + for (auto const & item : items) + item->update(); +} + void CUniversityWindow::makeDeal(SecondarySkill skill) { LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero); } - CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, SecondarySkill SKILL, bool available) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("UNIVERS2.PCX")), owner(owner_) @@ -1330,18 +1333,12 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): rowHeaders.push_back(std::make_shared(135, y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, text)); } - auto PRSTRIPS = GH.renderHandler().loadAnimation(AnimationPath::builtin("PRSTRIPS")); - PRSTRIPS->preload(); - for(int g=1; g(PRSTRIPS, g-1, 0, 250 + 66*g, 7)); + columnBackgrounds.push_back(std::make_shared(AnimationPath::builtin("PRSTRIPS"), g-1, 0, 250 + 66*g, 7)); for(int g=0; g(283 + 66*g, 24, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[16+g])); - auto itgflags = GH.renderHandler().loadAnimation(AnimationPath::builtin("itgflags")); - itgflags->preload(); - //printing flags for(int g = 0; g < std::size(fields); ++g) //by lines { @@ -1365,7 +1362,7 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): int rowStartY = ypos + (j ? 4 : 0); for(size_t i=0; i < rowLength[j]; i++) - cells.push_back(std::make_shared(itgflags, players[i + j*4].getNum(), 0, rowStartX + (int)i*12, rowStartY)); + cells.push_back(std::make_shared(AnimationPath::builtin("itgflags"), players[i + j*4].getNum(), 0, rowStartX + (int)i*12, rowStartY)); } } } diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 550ff7a0c..00bf4c8fa 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -358,32 +358,29 @@ public: CTransformerWindow(const IMarket * _market, const CGHeroInstance * _hero, const std::function & onWindowClosed); }; -class CUniversityWindow : public CStatusbarWindow +class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder { - class CItem : public CIntObject + class CItem final : public CIntObject { std::shared_ptr icon; - std::shared_ptr topBar; - std::shared_ptr bottomBar; + std::shared_ptr topBar; + std::shared_ptr bottomBar; std::shared_ptr name; std::shared_ptr level; public: SecondarySkill ID;//id of selected skill CUniversityWindow * parent; - void showAll(Canvas & to) override; void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void hover(bool on) override; - int state();//0=can't learn, 1=learned, 2=can learn + void update(); CItem(CUniversityWindow * _parent, int _ID, int X, int Y); }; const CGHeroInstance * hero; const IMarket * market; - std::shared_ptr bars; - std::vector> items; std::shared_ptr cancel; @@ -397,11 +394,14 @@ public: CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market, const std::function & onWindowClosed); void makeDeal(SecondarySkill skill); - void close(); + void close() override; + + // IMarketHolder impl + void updateSecondarySkills() override; }; /// Confirmation window for University -class CUnivConfirmWindow : public CStatusbarWindow +class CUnivConfirmWindow final : public CStatusbarWindow { std::shared_ptr clerkSpeech; std::shared_ptr name; diff --git a/config/factions/castle.json b/config/factions/castle.json index 5026120a1..1d8c02d65 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -119,7 +119,7 @@ "dwellingUpLvl7": { "animation" : "TBCSUP_6.def", "x" : 303, "y" : 0, "z" : -1, "border" : "TOCSANG2.bmp", "area" : "TZCSANG2.bmp" } }, - "musicTheme" : "music/CstleTown", + "musicTheme" : [ "music/CstleTown" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/conflux.json b/config/factions/conflux.json index fd431f6b1..69307372c 100644 --- a/config/factions/conflux.json +++ b/config/factions/conflux.json @@ -123,7 +123,7 @@ "dwellingUpLvl7": { "animation" : "TBELUP_6.def", "x" : 43, "y" : 0, "z" : -2, "border" : "TOELUP_6.bmp", "area" : "TZELUP_6.bmp" } }, - "musicTheme" : "music/ElemTown", + "musicTheme" : [ "music/ElemTown" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index f864b282e..84a4d1b13 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -119,7 +119,7 @@ "dwellingUpLvl7": { "animation" : "TBDNUP_6.def", "x" : 550, "y" : 0, "z" : -1, "border" : "TODDRA2A.bmp", "area" : "TZDDRA2A.bmp" } }, - "musicTheme" : "music/Dungeon", + "musicTheme" : [ "music/Dungeon" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 38002253a..082dfd38f 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -119,7 +119,7 @@ "dwellingUpLvl7": { "animation" : "TBFRUP_6.def", "x" : 587, "y" : 263, "z" : 5, "border" : "TOFHYD2A.bmp", "area" : "TZFHYD2A.bmp" } }, - "musicTheme" : "music/FortressTown", + "musicTheme" : [ "music/FortressTown" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 9a8c86912..db1b7675a 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -120,7 +120,7 @@ "dwellingUpLvl7": { "animation" : "TBINUP_6.def", "x" : 420, "y" : 105, "z" : -1, "border" : "TOIDVL2.bmp", "area" : "TZIDVL2.bmp" } }, - "musicTheme" : "music/InfernoTown", + "musicTheme" : [ "music/InfernoTown" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 29951540d..425a3a550 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -124,7 +124,7 @@ "dwellingUpLvl7": { "animation" : "TBNCUP_6.def", "x" : 662, "y" : 23, "border" : "TONBON2.bmp", "area" : "TZNBON2.bmp" } }, - "musicTheme" : "music/NecroTown", + "musicTheme" : [ "music/NecroTown" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 8893fe56a..4c2ba145d 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -123,7 +123,7 @@ "dwellingUpLvl7": { "animation" : "TBRMUP_6.def", "x" : 502, "y" : 5, "z" : -5, "border" : "TORDR2AA.bmp", "area" : "TZRDR2AA.bmp" } }, - "musicTheme" : "music/Rampart", + "musicTheme" : [ "music/Rampart" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index 5e8afda84..c1eeb8397 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -117,7 +117,7 @@ "dwellingUpLvl7": { "animation" : "TBSTUP_6.def", "x" : 604, "y" : 0, "border" : "TOSBEH2A.bmp", "area" : "TZSBEH2A.bmp" } }, - "musicTheme" : "music/Stronghold", + "musicTheme" : [ "music/Stronghold" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/factions/tower.json b/config/factions/tower.json index 488759b91..edc3ab29f 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -118,7 +118,7 @@ "dwellingUpLvl7": { "animation" : "TBTWUP_6.def", "x" : 75, "y" : 91, "z" : -1, "border" : "TOTTIT2.bmp", "area" : "TZTTIT2.bmp" } }, - "musicTheme" : "music/TowerTown", + "musicTheme" : [ "music/TowerTown" ], "defaultTavern" : 5, "tavernVideo" : "TAVERN.BIK", "guildBackground" : "TPMAGE.bmp", diff --git a/config/schemas/battlefield.json b/config/schemas/battlefield.json index 3135c2fb4..0a19116ae 100644 --- a/config/schemas/battlefield.json +++ b/config/schemas/battlefield.json @@ -24,6 +24,18 @@ "format" : "imageFile", "description" : "Background image for this battlefield" }, + "music" : + { + "description" : "Optional, filename for custom music to play during combat on this terrain", + "type" : "string", + "format" : "musicFile" + }, + "openingSound" : + { + "description" : "Optional, filename for custom sound to play during combat opening on this terrain", + "type" : "string", + "format" : "musicFile" + }, "impassableHexes" : { "type" : "array", "description" : "List of battle hexes that will be always blocked on this battlefield (e.g. ship to ship battles)", diff --git a/config/schemas/faction.json b/config/schemas/faction.json index 38f9f9e85..18bfabcd1 100644 --- a/config/schemas/faction.json +++ b/config/schemas/faction.json @@ -151,9 +151,13 @@ "$ref" : "townSiege.json" }, "musicTheme" : { - "type" : "string", - "description" : "Path to town music theme", - "format" : "musicFile" + "type" : "array", + "description" : "Path to town music themes", + "minItems" : 1, + "items" : { + "type" : "string", + "format" : "musicFile" + } }, "tavernVideo" : { "type" : "string", diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index 272e5d266..b470bc1cc 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -40,6 +40,12 @@ "$ref" : "objectTemplate.json" } }, + + "battleground" : { + "description" : "Battleground that will be used for combats in this object. Overrides terrain this object was placed on", + "type" : "string" + }, + "sounds" : { "type" : "object", "additionalProperties" : false, diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 002500054..4143ddf39 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -372,7 +372,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots", "queueSmallOutside" ], + "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots", "queueSmallOutside", "enableQuickSpellPanel" ], "properties" : { "speedFactor" : { "type" : "number", @@ -430,6 +430,10 @@ "queueSmallOutside" : { "type": "boolean", "default": false + }, + "enableQuickSpellPanel" : { + "type": "boolean", + "default": true } } }, @@ -437,7 +441,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "localHostname", "localPort", "remoteHostname", "remotePort", "playerAI", "alliedAI", "friendlyAI", "neutralAI", "enemyAI" ], + "required" : [ "localHostname", "localPort", "remoteHostname", "remotePort", "seed", "playerAI", "alliedAI", "friendlyAI", "neutralAI", "enemyAI" ], "properties" : { "localHostname" : { "type" : "string", @@ -455,6 +459,10 @@ "type" : "number", "default" : 3030 }, + "seed" : { + "type" : "number", + "default" : 0 + }, "playerAI" : { "type" : "string", "default" : "Nullkiller" @@ -537,7 +545,7 @@ }, "loggers" : { "type" : "array", - "default" : [ { "domain" : "global", "level" : "trace" } ], + "default" : [ { "domain" : "global", "level" : "trace" }, { "domain" : "rng", "level" : "info" } ], "items" : { "type" : "object", "additionalProperties" : false, diff --git a/config/schemas/terrain.json b/config/schemas/terrain.json index 23aa598a3..980193db9 100644 --- a/config/schemas/terrain.json +++ b/config/schemas/terrain.json @@ -97,9 +97,13 @@ }, "music" : { - "type" : "string", - "description" : "Music filename to play on this terrain on adventure map", - "format" : "musicFile" + "description" : "Music filenames to play on this terrain on adventure map", + "type" : "array", + "minItems" : 1, + "items" : { + "type" : "string", + "format" : "musicFile" + } }, "sounds" : { diff --git a/config/shortcutsConfig.json b/config/shortcutsConfig.json index 4359730be..817d89b96 100644 --- a/config/shortcutsConfig.json +++ b/config/shortcutsConfig.json @@ -55,7 +55,7 @@ "adventureZoomOut": "Keypad -", "adventureZoomReset": "Backspace", "battleAutocombat": "A", - "battleAutocombatEnd": "E", + "battleAutocombatEnd": "Q", "battleCastSpell": "C", "battleConsoleDown": "Down", "battleConsoleUp": "Up", @@ -64,12 +64,25 @@ "battleOpenHoveredUnit": "V", "battleRetreat": "R", "battleSelectAction": "S", + "battleToggleQuickSpell": "T", + "battleSpellShortcut0": "1", + "battleSpellShortcut1": "2", + "battleSpellShortcut2": "3", + "battleSpellShortcut3": "4", + "battleSpellShortcut4": "5", + "battleSpellShortcut5": "6", + "battleSpellShortcut6": "7", + "battleSpellShortcut7": "8", + "battleSpellShortcut8": "9", + "battleSpellShortcut9": "0", + "battleSpellShortcut10": "N", + "battleSpellShortcut11": "M", "battleSurrender": "S", "battleTacticsEnd": [ "Return", "Keypad Enter"], "battleTacticsNext": "Space", "battleToggleHeroesStats": [], - "battleToggleQueue": "Q", - "battleUseCreatureSpell": "F", + "battleToggleQueue": "Z", + "battleUseCreatureSpell": ["F", "G"], "battleWait": "W", "exchangeArmySwap": "F10", "exchangeArmyToLeft": [], diff --git a/config/terrains.json b/config/terrains.json index e649fcdb2..4bbf8f5ad 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -5,7 +5,7 @@ "moveCost" : 100, "minimapUnblocked" : [ 82, 56, 8 ], "minimapBlocked" : [ 57, 40, 8 ], - "music" : "Dirt.mp3", + "music" : [ "Dirt.mp3" ], "tiles" : "DIRTTL", "type" : ["SURFACE"], "shortIdentifier" : "dt", @@ -21,7 +21,7 @@ "moveCost" : 150, "minimapUnblocked" : [ 222, 207, 140 ], "minimapBlocked" : [ 165, 158, 107 ], - "music" : "Sand.mp3", + "music" : [ "Sand.mp3" ], "tiles" : "SANDTL", "type" : ["SURFACE"], "shortIdentifier" : "sa", @@ -38,7 +38,7 @@ "moveCost" : 100, "minimapUnblocked" : [ 0, 65, 0 ], "minimapBlocked" : [ 0, 48, 0 ], - "music" : "Grass.mp3", + "music" : [ "Grass.mp3" ], "tiles" : "GRASTL", "type" : ["SURFACE"], "shortIdentifier" : "gr", @@ -53,7 +53,7 @@ "moveCost" : 150, "minimapUnblocked" : [ 181, 199, 198 ], "minimapBlocked" : [ 140, 158, 156 ], - "music" : "Snow.mp3", + "music" : [ "Snow.mp3" ], "tiles" : "SNOWTL", "type" : ["SURFACE"], "shortIdentifier" : "sn", @@ -68,7 +68,7 @@ "moveCost" : 175, "minimapUnblocked" : [ 74, 134, 107 ], "minimapBlocked" : [ 33, 89, 66 ], - "music" : "Swamp.mp3", + "music" : [ "Swamp.mp3" ], "tiles" : "SWMPTL", "type" : ["SURFACE"], "shortIdentifier" : "sw", @@ -83,7 +83,7 @@ "moveCost" : 125, "minimapUnblocked" : [ 132, 113, 49 ], "minimapBlocked" : [ 99, 81, 33 ], - "music" : "Rough.mp3", + "music" : [ "Rough.mp3" ], "tiles" : "ROUGTL", "type" : ["SURFACE"], "shortIdentifier" : "rg", @@ -98,7 +98,7 @@ "moveCost" : 100, "minimapUnblocked" : [ 132, 48, 0 ], "minimapBlocked" : [ 90, 8, 0 ], - "music" : "Underground.mp3", + "music" : [ "Underground.mp3" ], "tiles" : "SUBBTL", "type" : [ "SUB" ], "shortIdentifier" : "sb", @@ -114,7 +114,7 @@ "moveCost" : 100, "minimapUnblocked" : [ 74, 73, 74 ], "minimapBlocked" : [ 41, 40, 41 ], - "music" : "Lava.mp3", + "music" : [ "Lava.mp3" ], "tiles" : "LAVATL", "type" : ["SUB", "SURFACE"], "shortIdentifier" : "lv", @@ -133,7 +133,7 @@ "moveCost" : 100, "minimapUnblocked" : [ 8, 81, 148 ], "minimapBlocked" : [ 8, 81, 148 ], - "music" : "Water.mp3", + "music" : [ "Water.mp3" ], "tiles" : "WATRTL", "type" : [ "WATER" ], "shortIdentifier" : "wt", @@ -156,7 +156,7 @@ "moveCost" : -1, "minimapUnblocked" : [ 0, 0, 0 ], "minimapBlocked" : [ 0, 0, 0 ], - "music" : "Underground.mp3", // Impossible in H3 + "music" : [ "Underground.mp3" ], // Impossible in H3 "tiles" : "ROCKTL", "type" : [ "ROCK" ], "shortIdentifier" : "rc", diff --git a/debian/changelog b/debian/changelog index e2c64b577..5998a8c0a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -4,6 +4,12 @@ vcmi (1.6.0) jammy; urgency=medium -- Ivan Savenko Fri, 30 Aug 2024 12:00:00 +0200 +vcmi (1.5.5) jammy; urgency=medium + + * New upstream release + + -- Ivan Savenko Wed, 17 Jul 2024 12:00:00 +0200 + vcmi (1.5.4) jammy; urgency=medium * New upstream release diff --git a/docs/Readme.md b/docs/Readme.md index 2344f8a09..8ceb7a55d 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,7 +1,7 @@ [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0) -[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.3/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.3) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.4/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.4) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5) [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases) # VCMI Project diff --git a/docs/developers/AI.md b/docs/developers/AI.md index ebcd30924..b75ba539a 100644 --- a/docs/developers/AI.md +++ b/docs/developers/AI.md @@ -1,3 +1,5 @@ +# AI + There are two types of AI: adventure and battle. **Adventure AIs** are responsible for moving heroes across the map and developing towns @@ -15,7 +17,7 @@ When you call unit->getAttack() it summarizes all these bonuses and returns the One important class is HypotheticBattle. It is used to evaluate the effects of an action without changing the actual gamestate. It is a wrapper around CPlayerSpecificCallback or another HypotheticBattle so it can provide you data, Internally it has a set of modified unit states and intercepts some calls to underlying callback and returns these internal states instead. These states in turn are wrappers around original units and contain modified bonuses (CStackWithBonuses). So if you need to emulate an attack you can call hypotheticbattle.getforupdate() and it will return the CStackWithBonuses which you can safely change. -# BattleAI +## BattleAI BattleAI's most important classes are the following: @@ -31,11 +33,11 @@ BattleAI's most important classes are the following: BattleAI itself handles all the rest and issues actual commands -# Nullkiller AI +## Nullkiller AI Adventure AI responsible for moving heroes on map, gathering things, developing town. Main idea is to gather all possible tasks on map, prioritize them and select the best one for each heroes. Initially was a fork of VCAI -## Parts +### Parts Gateway - a callback for server used to invoke AI actions when server thinks it is time to do something. Through this callback AI is informed about various events like hero level up, tile revialed, blocking dialogs and so on. In order to do this Gaateway implements specific interface. The interface is exactly the same for human and AI Another important actor for server interaction is CCallback * cb. This one is used to retrieve gamestate information and ask server to do things like hero moving, spell casting and so on. Each AI has own instance of Gateway and it is a root object which holds all AI state. Gateway has an event method yourTurn which invokes makeTurn in another thread. The last passes control to Nullkiller engine. @@ -58,7 +60,7 @@ Analyzer - a module gathering data from CCallback *. Its goal to make some stati * FuzzyEngines - looks like some legacy from VCAI * PriorityEvaluator - gathers information on task rewards, evaluates their priority using Fuzzy Light library (fuzzy logic) -## Goals +### Goals Units of activity in AI. Can be AbstractGoal, Task, Marker and Behavior Task - simple thing which can be done right away in order to gain some reward. Or a composition of simple things in case if more than one action is needed to gain the reward. diff --git a/docs/developers/Bonus_System.md b/docs/developers/Bonus_System.md index 370bf9ea4..e594fff8e 100644 --- a/docs/developers/Bonus_System.md +++ b/docs/developers/Bonus_System.md @@ -1,3 +1,5 @@ +# Bonus System + The bonus system of VCMI is a set of mechanisms that make handling of different bonuses for heroes, towns, players and units easier. The system consists of a set of nodes representing objects that can be a source or a subject of a bonus and two directed acyclic graphs (DAGs) representing inheritance and propagation of bonuses. Core of bonus system is defined in HeroBonus.h file. ## Propagation and inheritance diff --git a/docs/developers/Building_Android.md b/docs/developers/Building_Android.md index 13db490aa..5ce865f7b 100644 --- a/docs/developers/Building_Android.md +++ b/docs/developers/Building_Android.md @@ -1,3 +1,5 @@ +# Building Android + The following instructions apply to **v1.2 and later**. For earlier versions the best documentation is https://github.com/vcmi/vcmi-android/blob/master/building.txt (and reading scripts in that repo), however very limited to no support will be provided from our side if you wish to go down that rabbit hole. *Note*: building has been tested only on Linux and macOS. It may or may not work on Windows out of the box. diff --git a/docs/developers/Building_Linux.md b/docs/developers/Building_Linux.md index 68284a98a..b64fc1541 100644 --- a/docs/developers/Building_Linux.md +++ b/docs/developers/Building_Linux.md @@ -1,13 +1,13 @@ -# Compiling VCMI +# Building VCMI for Linux - Current baseline requirement for building is Ubuntu 20.04 - Supported C++ compilers for UNIX-like systems are GCC 9+ and Clang 13+ Older distributions and compilers might work, but they aren't tested by Github CI (Actions) -# Installing dependencies +## Installing dependencies -## Prerequisites +### Prerequisites To compile, the following packages (and their development counterparts) are needed to build: @@ -21,7 +21,7 @@ To compile, the following packages (and their development counterparts) are need - if you want to build scripting modules: LuaJIT - to speed up recompilation: Ccache -## On Debian-based systems (e.g. Ubuntu) +### On Debian-based systems (e.g. Ubuntu) For Ubuntu and Debian you need to install this list of packages: @@ -31,63 +31,62 @@ Alternatively if you have VCMI installed from repository or PPA you can use: `sudo apt-get build-dep vcmi` -## On RPM-based distributions (e.g. Fedora) +### On RPM-based distributions (e.g. Fedora) `sudo yum install cmake gcc-c++ SDL2-devel SDL2_image-devel SDL2_ttf-devel SDL2_mixer-devel boost boost-devel boost-filesystem boost-system boost-thread boost-program-options boost-locale boost-iostreams zlib-devel ffmpeg-devel ffmpeg-libs qt5-qtbase-devel tbb-devel luajit-devel liblzma-devel libsqlite3-devel fuzzylite-devel ccache` NOTE: `fuzzylite-devel` package is no longer available in recent version of Fedora, for example Fedora 38. It's not a blocker because VCMI bundles fuzzylite lib in its source code. -## On Arch-based distributions +### On Arch-based distributions On Arch-based distributions, there is a development package available for VCMI on the AUR. -It can be found at: +It can be found at https://aur.archlinux.org/packages/vcmi-git/ Information about building packages from the Arch User Repository (AUR) can be found at the Arch wiki. -# Getting the sources +## Getting the sources We recommend the following directory structure: - . - ├── vcmi -> contains sources and is under git control - └── build -> contains build output, makefiles, object files,... +``` +. +├── vcmi -> contains sources and is under git control +└── build -> contains build output, makefiles, object files,... +``` -Out-of-source builds keep the local repository clean so one doesn't have to manually exclude files generated during the build from commits. - -You can get latest sources with: +You can get the latest source code with: `git clone -b develop --recursive https://github.com/vcmi/vcmi.git` -# Compilation +## Compilation -## Configuring Makefiles +### Configuring Makefiles ```sh -mkdir build && cd build +mkdir build +cd build cmake -S ../vcmi ``` -# Additional options that you may want to use: +> [!NOTE] +> The `../vcmi` is not a typo, it will place Makefiles into the build dir as the build dir is your working dir when calling CMake. -## To enable debugging: -`cmake -S ../vcmi -D CMAKE_BUILD_TYPE=Debug` +See [CMake](CMake.md) for a list of options -**Notice**: The ../vcmi/ is not a typo, it will place makefile scripts into the build dir as the build dir is your working dir when calling CMake. +### Trigger build -## To use ccache: -`cmake -S ../vcmi -D ENABLE_CCACHE:BOOL=ON` +``` +cmake --build . -j8 +``` -## Trigger build +(-j8 = compile with 8 threads, you can specify any value. ) -`cmake --build . -- -j2` -(-j2 = compile with 2 threads, you can specify any value) +This will generate `vcmiclient`, `vcmiserver`, `vcmilauncher` as well as .so libraries in the `build/bin/` directory. -That will generate vcmiclient, vcmiserver, vcmilauncher as well as .so libraries in the **build/bin/** directory. +## Packaging -# Package building - -## RPM package +### RPM package The first step is to prepare a RPM build environment. On Fedora systems you can follow this guide: http://fedoraproject.org/wiki/How_to_create_an_RPM_package#SPEC_file_overview @@ -97,7 +96,8 @@ The first step is to prepare a RPM build environment. On Fedora systems you can sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm ``` -NOTE: the stock ffmpeg from Fedora repo is no good as it has stripped lots of codecs +> [!NOTE] +> The stock ffmpeg from Fedora repo is no good as it lacks a lots of codecs 1. Perform a git clone from a tagged branch for the right Fedora version from https://github.com/rpmfusion/vcmi; for example for Fedora 38:
git clone -b f38 --single-branch https://github.com/rpmfusion/vcmi.git
@@ -136,7 +136,7 @@ For other distributions that uses RPM, chances are there might be a spec file fo Available root environments and their names are listed in /etc/mock. -## Debian/Ubuntu +### Debian/Ubuntu 1. Install debhelper and devscripts packages diff --git a/docs/developers/Building_Windows.md b/docs/developers/Building_Windows.md index 257ef14ba..91f7a1fd2 100644 --- a/docs/developers/Building_Windows.md +++ b/docs/developers/Building_Windows.md @@ -1,7 +1,10 @@ -# Preparations +# Building VCMI for Windows + +## Preparations + Windows builds can be made in more than one way and with more than one tool. This guide focuses on the simplest building process using Microsoft Visual Studio 2022 -# Prerequisites +## Prerequisites - Windows Vista or newer. - [Microsoft Visual Studio](https://visualstudio.microsoft.com/downloads/) @@ -12,7 +15,7 @@ Windows builds can be made in more than one way and with more than one tool. Thi - To create installer: [NSIS](http://nsis.sourceforge.net/Main_Page) - To speed up recompilation: [CCache](https://github.com/ccache/ccache/releases) -## Choose an installation directory +### Choose an installation directory Create a directory for VCMI development, eg. `C:\VCMI` We will call this directory `%VCMI_DIR%` @@ -27,13 +30,13 @@ Bad locations: - `C:\Users\Michał\VCMI (non-ascii character)` - `C:\Program Files (x86)\VCMI (write protection)` -# Install VCMI dependencies +## Install VCMI dependencies You have two options: to use pre-built libraries or build your own. We strongly recommend start with using pre-built ones. -## Option A. Use pre-built Vcpkg +### Option A. Use pre-built Vcpkg -### Download and unpack archive +#### Download and unpack archive Vcpkg Archives are available at our GitHub: https://github.com/vcmi/vcmi-deps-windows/releases @@ -41,18 +44,18 @@ Vcpkg Archives are available at our GitHub: https://github.com/vcmi/vcmi-deps-wi EG: v1.6 assets - [vcpkg-export-x64-windows-v143.7z](https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-x64-windows-v143.7z) - Extract archive by right clicking on it and choosing "7-zip -> Extract Here". -### Move dependencies to target directory +#### Move dependencies to target directory Once extracted, a `vcpkg` directory will appear with `installed` and `scripts` subfolders inside. Move extracted `vcpkg` directory into your `%VCMI_DIR%` -## Option B. Build Vcpkg on your own +### Option B. Build Vcpkg on your own Please be aware that if you're running 32-bit Windows version, then this is impossible due to Be aware that building Vcpkg might take a lot of time depend on your CPU model and 10-20GB of disk space. -### Create initial directory +#### Create initial directory -### Clone vcpkg +#### Clone vcpkg 1. open SourceTree 2. File -\> Clone @@ -64,7 +67,7 @@ From command line use: git clone https://github.com/microsoft/vcpkg.git %VCMI_DIR%/vcpkg -### Build vcpkg and dependencies +#### Build vcpkg and dependencies - Run `%VCMI_DIR%/vcpkg/bootstrap-vcpkg.bat` @@ -75,11 +78,11 @@ From command line use: For the list of the packages used you can also consult [vcmi-deps-windows readme](https://github.com/vcmi/vcmi-deps-windows) in case this article gets outdated a bit. -# Install CCache +## Install CCache Extract `ccache` to a folder of your choosing, add the folder to the `PATH` environment variable and log out and back in. -# Build VCMI +## Build VCMI #### From GIT GUI - Open SourceTree @@ -93,13 +96,13 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi #### From command line - `git clone --recursive https://github.com/vcmi/vcmi.git %VCMI_DIR%/source` -## Generate solution for VCMI +### Generate solution for VCMI - Create `%VCMI_DIR%/build` folder - Open a command line prompt at `%VCMI_DIR%/build` - Execute `cd %VCMI_DIR%/build` - Create solution (Visual Studio 2022 64-bit) `cmake %VCMI_DIR%/source -DCMAKE_TOOLCHAIN_FILE=%VCMI_DIR%/vcpkg/scripts/buildsystems/vcpkg.cmake -G "Visual Studio 17 2022" -A x64` -## Compile VCMI with Visual Studio +### Compile VCMI with Visual Studio - Open `%VCMI_DIR%/build/VCMI.sln` in Visual Studio - Select `Release` build type in the combobox - If you want to use ccache: @@ -109,7 +112,7 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi - Right click on `BUILD_ALL` project. This `BUILD_ALL` project should be in `CMakePredefinedTargets` tree in Solution Explorer. - VCMI will be built in `%VCMI_DIR%/build/bin` folder! -## Compile VCMI with MinGW via MSYS2 +### Compile VCMI with MinGW via MSYS2 - Install MSYS2 from https://www.msys2.org/ - Start the `MSYS MinGW x64`-shell - Install dependencies: `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-boost mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-static` @@ -117,7 +120,7 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi **NOTE:** This will link Qt5 statically to `VCMI_launcher.exe` and `VCMI_Mapeditor.exe`. See [PR #3421](https://github.com/vcmi/vcmi/pull/3421) for some background. -# Create VCMI installer (This step is not required for just building & development) +## Create VCMI installer (This step is not required for just building & development) Make sure NSIS is installed to default directory or have registry entry so CMake can find it. After you build VCMI execute following commands from `%VCMI_DIR%/build`. @@ -125,7 +128,7 @@ After you build VCMI execute following commands from `%VCMI_DIR%/build`. - for release build: `cpack` - for debug build: `cpack -C Debug` -# Troubleshooting and workarounds +## Troubleshooting and workarounds Vcpkg might be very unstable due to limited popularity and fact of using bleeding edge packages (such as most recent Boost). Using latest version of dependencies could also expose both problems in VCMI code or library interface changes that developers not checked yet. So if you're built Vcpkg yourself and can't get it working please try to use binary package. diff --git a/docs/developers/Building_iOS.md b/docs/developers/Building_iOS.md index 8383f59ae..a922e44a1 100644 --- a/docs/developers/Building_iOS.md +++ b/docs/developers/Building_iOS.md @@ -1,3 +1,5 @@ +# Building VCMI for iOS + ## Requirements 1. **macOS** diff --git a/docs/developers/Building_macOS.md b/docs/developers/Building_macOS.md index 67571f74d..71a1ae644 100644 --- a/docs/developers/Building_macOS.md +++ b/docs/developers/Building_macOS.md @@ -1,3 +1,5 @@ +# Building VCMI for macOS + ## Requirements 1. C++ toolchain, either of: diff --git a/docs/developers/CMake.md b/docs/developers/CMake.md new file mode 100644 index 000000000..140d7ef89 --- /dev/null +++ b/docs/developers/CMake.md @@ -0,0 +1,23 @@ +# CMake options + +* `-D CMAKE_BUILD_TYPE=Debug` + * Enables debug info and disables optimizations +* `-D CMAKE_EXPORT_COMPILE_COMMANDS=ON` + * Creates `compile_commands.json` for [clangd](https://clangd.llvm.org/) language server. + + For clangd to find the JSON, create a file named `.clangd` with this content + ``` + CompileFlags: + CompilationDatabase: build + ``` + and place it here: + ``` + . + ├── vcmi -> contains sources and is under git control + ├── build -> contains build output, makefiles, object files,... + └── .clangd + ``` +* `-D ENABLE_CCACHE:BOOL=ON` + * Speeds up recompilation +* `-G Ninja` + * Use Ninja build system instead of Make, which speeds up the build and doesn't require a `-j` flag \ No newline at end of file diff --git a/docs/developers/Code_Structure.md b/docs/developers/Code_Structure.md index cae75b71d..9f3e2c802 100644 --- a/docs/developers/Code_Structure.md +++ b/docs/developers/Code_Structure.md @@ -1,6 +1,8 @@ +# Code Structure + The code of VCMI is divided into several main parts: client, server, lib and AIs, each one in a separate binary file. -# The big picture +## The big picture VCMI contains three core projects: VCMI_lib (dll / so), VCMI_client (executable) and VCMI_server (executable). Server handles all game mechanics and events. Client presents game state and events to player and collects input from him. @@ -8,37 +10,37 @@ During the game, we have one (and only one) server and one or more (one for each Important: State of the game and its mechanics are synchronized between clients and server. All changes to the game state or mechanics must be done by server which will send appropriate notices to clients. -## Game state +### Game state It's basically CGameState class object and everything that's accessible from it: map (with objects), player statuses, game options, etc. -## Bonus system +### Bonus system One of the more important pieces of VCMI is the [bonus system](Bonus_System.md). It's described in a separate article. -## Configuration +### Configuration Most of VCMI configuration files uses Json format and located in "config" directory -### Json parser and writer +#### Json parser and writer -# Client +## Client -## Main purposes of client +### Main purposes of client Client is responsible for: - displaying state of game to human player - capturing player's actions and sending requests to server - displaying changes in state of game indicated by server -## Rendering of graphics +### Rendering of graphics Rendering of graphics relies heavily on SDL. Currently we do not have any wrapper for SDL internal structures and most of rendering is about blitting surfaces using SDL_BlitSurface. We have a few function that make rendering easier or make specific parts of rendering (like printing text). They are places in client/SDL_Extensions and client/SDL_Framerate (the second one contains code responsible for keeping appropriate framerate, it should work more smart than just SDL_Delay(milliseconds)). In rendering, Interface object system is quite helpful. Its base is CIntObject class that is basically a base class for our library of GUI components and other objects. -# Server +## Server -## Main purposes of server +### Main purposes of server Server is responsible for: @@ -47,9 +49,9 @@ Server is responsible for: - informing all clients about changes in state of the game that are visible to them -# Lib +## Lib -## Main purposes of lib +### Main purposes of lib VCMI_Lib is a library that contains code common to server and client, so we avoid it's duplication. Important: the library code is common for client and server and used by them, but the library instance (in opposition to the library as file) is not shared by them! Both client and server create their own "copies" of lib with all its class instances. @@ -63,16 +65,16 @@ Lib contains code responsible for: - handling general game mechanics and related actions (only adventure map objects; it's an unwanted remnant of past development - all game mechanics should be handled by the server) - networking and serialization -### Serialization +#### Serialization The serialization framework can serialize basic types, several standard containers among smart pointers and custom objects. Its design is based on the [boost serialization libraries](http://www.boost.org/doc/libs/1_52_0/libs/serialization/doc/index.html). In addition to the basic functionality it provides light-weight transfer of CGObjectInstance objects by sending only the index/id. Serialization page for all the details. -### Wrapped namespace examples +#### Wrapped namespace examples -#### Inside the lib +##### Inside the lib Both header and implementation of a new class inside the lib should have the following structure: @@ -81,7 +83,7 @@ Both header and implementation of a new class inside the lib should have the fol `` `VCMI_LIB_NAMESPACE_END` -#### Headers outside the lib +##### Headers outside the lib Forward declarations of the lib in headers of other parts of the project need to be wrapped in the macros: @@ -93,30 +95,30 @@ Forward declarations of the lib in headers of other parts of the project need to `` -#### New project part +##### New project part If you're creating new project part, place `VCMI_LIB_USING_NAMESPACE` in its `StdInc.h` to be able to use lib classes without explicit namespace in implementation files. Example: -# Artificial Intelligence +## Artificial Intelligence -## StupidAI +### StupidAI Stupid AI is recent and used battle AI. -## Adventure AI +### Adventure AI VCAI module is currently developed agent-based system driven by goals and heroes. -## Programming challenge +### Programming challenge -## Fuzzy logic +### Fuzzy logic VCMI includes [FuzzyLite](http://code.google.com/p/fuzzy-lite/) library to make use of fuzzy rule-based algorithms. They are useful to handle uncertainty and resemble human behaviour who takes decisions based on rough observations. FuzzyLite is linked as separate static library in AI/FuzzyLite.lib file. -# Utilities +## Utilities -## Launcher +### Launcher -## Duels +### Duels -## ERM parser \ No newline at end of file +### ERM parser \ No newline at end of file diff --git a/docs/developers/Coding_Guidelines.md b/docs/developers/Coding_Guidelines.md index 4f10d9864..89bad05a6 100644 --- a/docs/developers/Coding_Guidelines.md +++ b/docs/developers/Coding_Guidelines.md @@ -1,3 +1,5 @@ +# Coding Guidelines + ## C++ Standard VCMI implementation bases on C++17 standard. Any feature is acceptable as long as it's will pass build on our CI, but there is list below on what is already being used. diff --git a/docs/developers/Conan.md b/docs/developers/Conan.md index 185c85af5..af99c5495 100644 --- a/docs/developers/Conan.md +++ b/docs/developers/Conan.md @@ -1,4 +1,4 @@ -# Using dependencies from Conan +# Conan Dependencies [Conan](https://conan.io/) is a package manager for C/C++. We provide prebuilt binary dependencies for some platforms that are used by our CI, but they can also be consumed by users to build VCMI. However, it's not required to use only the prebuilt binaries: you can build them from source as well. diff --git a/docs/developers/Development_with_Qt_Creator.md b/docs/developers/Development_with_Qt_Creator.md index 1153ae016..4e3c96f8e 100644 --- a/docs/developers/Development_with_Qt_Creator.md +++ b/docs/developers/Development_with_Qt_Creator.md @@ -1,4 +1,4 @@ -# Introduction to Qt Creator +# Development with Qt Creator Qt Creator is the recommended IDE for VCMI development on Linux distributions, but it may be used on other operating systems as well. It has the following advantages compared to other IDEs: diff --git a/docs/developers/Logging_API.md b/docs/developers/Logging_API.md index 6b53234b4..a3ce23b76 100644 --- a/docs/developers/Logging_API.md +++ b/docs/developers/Logging_API.md @@ -1,4 +1,6 @@ -# Features +# Logging API + +## Features - A logger belongs to a "domain", this enables us to change log level settings more selectively - The log format can be customized @@ -9,7 +11,7 @@ - Macros for tracing the application flow - Provides stream-like and function-like logging -# Class diagram +## Class diagram ![Logging_Class_Diagram](https://github.com/IvanSavenko/vcmi/assets/1576820/31c9b14e-a055-4b07-87fe-00da43430a2b) @@ -18,9 +20,9 @@ Some notes: - There are two methods `configure` and `configureDefault` of the class `CBasicLogConfigurator` to initialize and setup the logging system. The latter one setups default logging and isn't dependent on VCMI's filesystem, whereas the first one setups logging based on the user's settings which can be configured in the settings.json. - The methods `isDebugEnabled` and `isTraceEnabled` return true if a log record of level debug respectively trace will be logged. This can be useful if composing the log message is a expensive task and performance is important. -# Usage +## Usage -## Setup settings.json +### Setup settings.json ``` javascript { @@ -51,7 +53,7 @@ Some notes: The above code is an example on how to configure logging. It sets the log level to debug globally and the log level of the domain ai to trace. In addition, it tells the console to log debug messages as well with the threshold attribute. Finally, it configures the console so that it logs network trace messages in magenta. -## Configuration +### Configuration The following code shows how the logging system can be configured: @@ -82,14 +84,14 @@ Format: %d %l %n \[%t\] - %m global -\> info -## How to get a logger +### How to get a logger There exist only one logger object per domain. A logger object cannot be copied. You can get access to a logger object by using the globally defined ones like `logGlobal` or `logAi`, etc... or by getting one manually: ```cpp Logger * logger = CLogger::getLogger(CLoggerDomain("rmg")); ``` -## Logging +### Logging Logging can be done via two ways, stream-like or function-like. @@ -119,7 +121,7 @@ The following colors are available for console output: - gray - teal -## How to trace execution +### How to trace execution The program execution can be traced by using the macros TRACE_BEGIN, TRACE_END and their \_PARAMS counterparts. This can be important if you want to analyze the operations/internal workings of the AI or the communication of the client-server. In addition, it can help you to find bugs on a foreign VCMI installation with a custom mod configuration. @@ -135,9 +137,9 @@ The program execution can be traced by using the macros TRACE_BEGIN, TRACE_END a } ``` -# Concepts +## Concepts -## Domain +### Domain A domain is a specific part of the software. In VCMI there exist several domains: diff --git a/docs/developers/Lua_Scripting_System.md b/docs/developers/Lua_Scripting_System.md index 2c4be7387..16124c800 100644 --- a/docs/developers/Lua_Scripting_System.md +++ b/docs/developers/Lua_Scripting_System.md @@ -1,4 +1,6 @@ -# Configuration +# Lua Scripting System + +## Configuration ``` javascript { @@ -32,13 +34,13 @@ } ``` -# Lua +## Lua -## API Reference +### API Reference TODO **In near future Lua API may change drastically several times. Information here may be outdated** -### Globals +#### Globals - DATA - persistent table - - DATA.ERM contains ERM state, anything else is free to use. @@ -61,7 +63,7 @@ TODO **In near future Lua API may change drastically several times. Information - -TODO require(":relative.path.to.module") - loads module from same mod - logError(text) - backup error log function -### Low level events API +#### Low level events API ``` Lua @@ -79,7 +81,7 @@ sub2 = PlayerGotTurn.subscribeBefore(EVENT_BUS, function(event) end) ``` -### Lua standard library +#### Lua standard library VCMI uses LuaJIT, which is Lua 5.1 API, see [upstream documentation](https://www.lua.org/manual/5.1/manual.html) @@ -91,19 +93,19 @@ Following libraries are supported - math - bit -# ERM +## ERM -## Features +### Features - no strict limit on function/variable numbers (technical limit 32 bit integer except 0)) - TODO semi compare - DONE macros -## Bugs +### Bugs - TODO Broken XOR support (clashes with \`X\` option) -## Triggers +### Triggers - TODO **!?AE** Equip/Unequip artifact - WIP **!?BA** when any battle occurs @@ -134,22 +136,22 @@ Following libraries are supported - TODO **!?TL** Real-Time Timer - TODO **!?TM** timed events -## Receivers +### Receivers -### VCMI +#### VCMI - **!!MC:S@varName@** - declare new "normal" variable (technically v-var with string key) - TODO Identifier resolver - WIP Bonus system -### ERA +#### ERA - DONE !!if !!el !!en - TODO !!br !!co - TODO !!SN:X -### WoG +#### WoG - TODO !!AR Артефакт (ресурс) в определенной позиции - TODO !!BA Битва @@ -199,4 +201,4 @@ Following libraries are supported - *!#VC Контроль переменных* - WIP !!VR Установка переменных -## Persistence \ No newline at end of file +### Persistence \ No newline at end of file diff --git a/docs/developers/Networking.md b/docs/developers/Networking.md index 7bcadc437..5c3736abe 100644 --- a/docs/developers/Networking.md +++ b/docs/developers/Networking.md @@ -1,4 +1,6 @@ -# The big picture +# Networking + +## The big picture For implementation details see files located at `lib/network` directory. @@ -19,11 +21,11 @@ Following connections can be established during game lifetime: - game client -> lobby server: This connection is used to access global lobby, for multiplayer over Internet. Created when player logs into a lobby (Multiplayer -> Connect to global service) - match server -> lobby server: This connection is established when player creates new multiplayer room via lobby. It is used by lobby server to send commands to match server -# Gameplay communication +## Gameplay communication For gameplay, VCMI serializes data into a binary stream. See [Serialization](Serialization.md) for more information. -# Global lobby communication +## Global lobby communication For implementation details see: - game client: `client/globalLobby/GlobalLobbyClient.h @@ -38,29 +40,29 @@ char jsonString[messageSize]; Every message must be a struct (json object) that contains "type" field. Unlike rest of VCMI codebase, this message is validated as strict json, without any extensions, such as comments. -## Communication flow +### Communication flow Notes: - invalid message, such as corrupted json format or failure to validate message will result in no reply from server - in addition to specified messages, match server will send `operationFailed` message on failure to apply player request -### New Account Creation +#### New Account Creation - client -> lobby: `clientRegister` - lobby -> client: `accountCreated` -### Login +#### Login - client -> lobby: `clientLogin` - lobby -> client: `loginSuccess` - lobby -> client: `chatHistory` - lobby -> client: `activeAccounts` - lobby -> client: `activeGameRooms` -### Chat Message +#### Chat Message - client -> lobby: `sendChatMessage` - lobby -> every client: `chatMessage` -### New Game Room +#### New Game Room - client starts match server instance - match -> lobby: `serverLogin` - lobby -> match: `loginSuccess` @@ -70,24 +72,24 @@ Notes: - lobby -> every client: `activeAccounts` - lobby -> every client: `activeGameRooms` -### Joining a game room +#### Joining a game room See [#Proxy mode](proxy-mode) -### Leaving a game room +#### Leaving a game room - client closes connection to match server - match -> lobby: `leaveGameRoom` -### Sending an invite: +#### Sending an invite: - client -> lobby: `sendInvite` - lobby -> target client: `inviteReceived` Note: there is no dedicated procedure to accept an invite. Instead, invited player will use same flow as when joining public game room -### Logout +#### Logout - client closes connection - lobby -> every client: `activeAccounts` -## Proxy mode +### Proxy mode In order to connect players located behind NAT, VCMI lobby can operate in "proxy" mode. In this mode, connection will be act as proxy and will transmit gameplay data from client to a match server, without any data processing on lobby server. diff --git a/docs/developers/RMG_Description.md b/docs/developers/RMG_Description.md index 7a9d7810c..59a6b683a 100644 --- a/docs/developers/RMG_Description.md +++ b/docs/developers/RMG_Description.md @@ -1,46 +1,48 @@ -# Fundamentals +# RMG Description + +## Fundamentals Random maps are represented by undirected graph of zones linked with connections. On maps with water, a single extra water zone is created. -## Modifiers +### Modifiers Zone filling process is split into multiple phases, each of them represented as a modifier. A modifier can require other modifiers to finish their job before launching. A modifier might be preceded by other modifier from every zone, or many modifiers from all zones. For instance, placing underground rock requires all underground zones to finish treasure placement first. -## Thread pool +### Thread pool A queue of Modifiers jobs is created in roughly topological order, so that Modificators with no dependencies are placed first. The queue is iterated in a circular manner and if Modificator with no remaining preceders is found, it is picked for execution in a separate thread. After job completion, Modifier is erased from dependencies of Modifiers which depend on it. -# Placing Zones +## Placing Zones -## Generating distance graph +### Generating distance graph Based on zone connections, a simple distance graph is created using Dijkstra algorithm. -## Initial zone placement +### Initial zone placement Based on distance graph, zones are placed one by one on N x N grid of size just enough to fit all the zones (for instance, 5 zones are placed on a 3 x 3 grid and 24 zones on 5 x 5 grid). Adjacent zones are placed close while distant zones are placed as far away from each other as possible. -## Iterative optimization +### Iterative optimization Finally, zones are moved from their initial positions using Fruchterman-Reingold algorithm. It assumes all the zones are soft spheres, which attract connected zones as springs but push back not overlapping zones crossing their borders. These forces are summend and determine the vector shift of the zone position. The algorithm uses classic "simulated annealing" approach - zones start with high "temperature" (are very soft and squishy) and then gradually become colder (harder) and push away overlapping zones with stronger force. To prevent getting stuck in local minima, sometimes most misplaced zones swap placed manually. -## Penrose Tiling +### Penrose Tiling Using iterative subdivision, a set of Penrose tiling vertices ic created at random orientation and centered over middle of the map. All the tiles on a map are assigned to the closes vertex. Then, every vertex is assigned to the closest zone, creating irregular shapes. -# Zone connections +## Zone connections Directly adjacent zones are connected with a guard and a road, and overlapping zones on different levels are connected with Subterranean Gate. Zones which are not directly adjacent might be covered through water zone. If a zone shouldn't be connected with nearby zones adjacent to body of water, it's coast is sealed with obstacles. -## Water routes +### Water routes **TODO** -## Fractalization +### Fractalization Every zone starts with at least one free tile in the center. Now, from every tiles at a distant greater than some number, a random tile is chosen. From it, algorithm routes a free path connected to already existing free paths. Process is repetaed until no tiles distant from free paths are left. Tiles used for connections are marked `free` and nothing else can be placed on them. @@ -48,28 +50,28 @@ Zones with type `junction` are not fractalized. The remaining tiles that are not obstacles (such as zone edges) are marked as `possible"` so they can be either filled with treasures or left `free.` -# Treasures +## Treasures Every object or treasure pile in the zone is placed as far away from existing objects as possible. This includes towns and zone guards placed first. Zone keeps a priority queue of tiles sorted by their distance to closest object. Whenever an object is placed, these distances are updated. -## Treasure generation +### Treasure generation Treasures are separated in value ranges. The highest range is picked first. A large number of treasure piles is generated, then RMG tries to fit each of treasure piles into a zone. Then, lower treasure ranges are generated -## Treasure placement +### Treasure placement New treasure pile can't be closer to any previous object than some distance determined by total treasure density and value. Lower value piles and placed with lower minimal distance. Any new treasure is placed so that it can't join two previously separated blocked islands, to prevent sealing a gap and ensuring entire zone is passable. -# Obstacles +## Obstacles After all the treasures are placed, tiles marked as `possible` are iteratively stripped and cleared from lose appendages, leaving `free` space. Then remaining ones are marked as `blocked`, and covered with obstacles. -## Biomes +### Biomes For every zone, a few random obstacle sets are selected. [Details](https://github.com/vcmi/vcmi/blob/develop/docs/modders/Entities_Format/Biome_Format.md) -## Filling space +### Filling space Tiles which need to be `blocked` but are not `used` are filled with obstacles. Largest obstacles which cover the most tiles are picked first, other than that they are chosen randomly. \ No newline at end of file diff --git a/docs/developers/Serialization.md b/docs/developers/Serialization.md index 2b582f227..143eaf0ad 100644 --- a/docs/developers/Serialization.md +++ b/docs/developers/Serialization.md @@ -1,24 +1,26 @@ -# Introduction +# Serialization + +## Introduction The serializer translates between objects living in our code (like int or CGameState\*) and stream of bytes. Having objects represented as a stream of bytes is useful. Such bytes can send through the network connection (so client and server can communicate) or written to the disk (savegames). VCMI uses binary format. The primitive types are simply copied from memory, more complex structures are represented as a sequence of primitives. -## Typical tasks +### Typical tasks -### Bumping a version number +#### Bumping a version number Different major version of VCMI likely change the format of the save game. Every save game needs a version identifier, that loading can work properly. Backward compatibility isn't supported for now. The version identifier is a constant named version in Connection.h and should be updated every major VCMI version or development version if the format has been changed. Do not change this constant if it's not required as it leads to full rebuilds. Why should the version be updated? If VCMI cannot detect "invalid" save games the program behaviour is random and undefined. It mostly results in a crash. The reason can be anything from null pointer exceptions, index out of bounds exceptions(ok, they aren't available in c++, but you know what I mean:) or invalid objects loading(too much elements in a vector, etc...) This should be avoided at least for public VCMI releases. -### Adding a new class +#### Adding a new class If you want your class to be serializable (eg. being storable in a savegame) you need to define a serialize method template, as described in [#User types](#user-types) Additionally, if your class is part of one of registered object hierarchies (basically: if it derives from CGObjectInstance, IPropagator, ILimiter, CBonusSystemNode, CPack) it needs to be registered. Just add an appropriate entry in the `RegisterTypes.h` file. See polymorphic serialization for more information. -# How does it work +## How does it work -## Primitive types +### Primitive types They are simply stored in a binary form, as in memory. Compatibility is ensued through the following means: @@ -27,21 +29,21 @@ They are simply stored in a binary form, as in memory. Compatibility is ensued t It's not "really" portable, yet it works properly across all platforms we currently support. -## Dependant types +### Dependant types -### Pointers +#### Pointers Storing pointers mechanics can be and almost always is customized. See [#Additional features](additional-features). In the most basic form storing pointer simply sends the object state and loading pointer allocates an object (using "new" operator) and fills its state with the stored data. -### Arrays +#### Arrays Serializing array is simply serializing all its elements. -## Standard library types +### Standard library types -### STL Containers +#### STL Containers First the container size is stored, then every single contained element. @@ -56,7 +58,7 @@ Supported STL types include: `pair` `map` -### Smart pointers +#### Smart pointers Smart pointers at the moment are treated as the raw C-style pointers. This is very bad and dangerous for shared_ptr and is expected to be fixed somewhen in the future. @@ -65,14 +67,14 @@ The list of supported data types from standard library: `shared_ptr (partial!!!)` `unique_ptr` -### Boost +#### Boost Additionally, a few types for Boost are supported as well: `variant` `optional` -## User types +### User types To make the user-defined type serializable, it has to provide a template method serialize. The first argument (typed as template parameter) is a reference to serializer. The second one is version number. @@ -98,7 +100,7 @@ struct DLL_LINKAGE Rumor }; ``` -## Backwards compatibility +### Backwards compatibility Serializer, before sending any data, stores its version number. It is passed as the parameter to the serialize method, so conditional code ensuring backwards compatibility can be added. @@ -126,21 +128,21 @@ struct DLL_LINKAGE Rumor }; ``` -## Serializer classes +### Serializer classes -### Common information +#### Common information Serializer classes provide iostream-like interface with operator `<<` for serialization and operator `>>` for deserialization. Serializer upon creation will retrieve/store some metadata (version number, endianness), so even if no object is actually serialized, some data will be passed. -### Serialization to file +#### Serialization to file CLoadFile/CSaveFile classes allow to read data to file and store data to file. They take filename as the first parameter in constructor and, optionally, the minimum supported version number (default to the current version). If the construction fails (no file or wrong file) the exception is thrown. -### Networking +#### Networking See [Networking](Networking.md) -## Additional features +### Additional features Here is the list of additional custom features serialzier provides. Most of them can be turned on and off. @@ -149,7 +151,7 @@ Here is the list of additional custom features serialzier provides. Most of them - Stack instance serialization — enabled by sendStackInstanceByIds flag. - Smart pointer serialization — enabled by smartPointerSerialization flag. -### Polymorphic serialization +#### Polymorphic serialization Serializer is to recognize the true type of object under the pointer if classes of that hierarchy were previously registered. @@ -170,7 +172,7 @@ Class hierarchies that are now registered to benefit from this feature are mostl It is crucial that classes are registered in the same order in the both serializers (storing and loading). -### Vectorized list member serialization +#### Vectorized list member serialization Both client and server store their own copies of game state and VLC (handlers with data from config). Many game logic objects are stored in the vectors and possess a unique id number that represent also their position in such vector. @@ -219,7 +221,7 @@ Important: this means that the object state is not serialized. This feature makes sense only for server-client network communication. -### Stack instance serialization +#### Stack instance serialization This feature works very much like the vectorised object serialization. It is like its special case for stack instances that are not vectorised (each hero owns its map). When this option is turned on, sending CStackInstance\* will actually send an owning object (town, hero, garrison, etc) id and the stack slot position. @@ -227,7 +229,7 @@ For this to work, obviously, both sides of the connection need to have exactly t This feature depends on vectorised member serialization being turned on. (Sending owning object by id.) -### Smart pointer serialization +#### Smart pointer serialization Note: name is unfortunate, this feature is not about smart pointers (like shared-ptr and unique_ptr). It is for raw C-style pointers, that happen to point to the same object. diff --git a/docs/maintainers/Release_Process.md b/docs/maintainers/Release_Process.md index eaa6108e0..51d7a8e1a 100644 --- a/docs/maintainers/Release_Process.md +++ b/docs/maintainers/Release_Process.md @@ -1,3 +1,5 @@ +# Release Process + ## Versioning For releases VCMI uses version numbering in form "1.X.Y", where: - 'X' indicates major release. Different major versions are generally not compatible with each other. Save format is different, network protocol is different, mod format likely different. diff --git a/docs/maintainers/Ubuntu_PPA.md b/docs/maintainers/Ubuntu_PPA.md index 220df1bff..8ada53d86 100644 --- a/docs/maintainers/Ubuntu_PPA.md +++ b/docs/maintainers/Ubuntu_PPA.md @@ -1,3 +1,5 @@ +# Ubuntu PPA + ## Main links - [Team](https://launchpad.net/~vcmi) - [Project](https://launchpad.net/vcmi) diff --git a/docs/modders/Animation_Format.md b/docs/modders/Animation_Format.md index a4a0d9896..2aece5165 100644 --- a/docs/modders/Animation_Format.md +++ b/docs/modders/Animation_Format.md @@ -1,3 +1,5 @@ +# Animation Format + VCMI allows overriding HoMM3 .def files with .json replacement. Compared to .def this format allows: - Overriding individual frames from json file (e.g. icons) diff --git a/docs/modders/Bonus/Bonus_Duration_Types.md b/docs/modders/Bonus/Bonus_Duration_Types.md index cf276ed7a..3c20d0e7c 100644 --- a/docs/modders/Bonus/Bonus_Duration_Types.md +++ b/docs/modders/Bonus/Bonus_Duration_Types.md @@ -1,3 +1,5 @@ +# Bonus Duration Types + Bonus may have any of these durations. They acts in disjunction. ## List of all bonus duration types diff --git a/docs/modders/Bonus/Bonus_Limiters.md b/docs/modders/Bonus/Bonus_Limiters.md index 6c422acb8..5dc9ee46a 100644 --- a/docs/modders/Bonus/Bonus_Limiters.md +++ b/docs/modders/Bonus/Bonus_Limiters.md @@ -1,3 +1,5 @@ +# Bonus Limiters + ## Predefined Limiters The limiters take no parameters: diff --git a/docs/modders/Bonus/Bonus_Propagators.md b/docs/modders/Bonus/Bonus_Propagators.md index 73b8b3004..90f09cf84 100644 --- a/docs/modders/Bonus/Bonus_Propagators.md +++ b/docs/modders/Bonus/Bonus_Propagators.md @@ -1,3 +1,5 @@ +# Bonus Propagators + ## Available propagators - BATTLE_WIDE: Affects both sides during battle diff --git a/docs/modders/Bonus/Bonus_Range_Types.md b/docs/modders/Bonus/Bonus_Range_Types.md index 447632847..5be67c077 100644 --- a/docs/modders/Bonus/Bonus_Range_Types.md +++ b/docs/modders/Bonus/Bonus_Range_Types.md @@ -1,3 +1,5 @@ +# Bonus Range Types + ## List of all Bonus range types - NO_LIMIT diff --git a/docs/modders/Bonus/Bonus_Sources.md b/docs/modders/Bonus/Bonus_Sources.md index 44fd6ed10..86754e407 100644 --- a/docs/modders/Bonus/Bonus_Sources.md +++ b/docs/modders/Bonus/Bonus_Sources.md @@ -1,3 +1,5 @@ +# Bonus Sources + ## List of all possible bonus sources - ARTIFACT diff --git a/docs/modders/Bonus/Bonus_Types.md b/docs/modders/Bonus/Bonus_Types.md index d897d7a7f..82f389103 100644 --- a/docs/modders/Bonus/Bonus_Types.md +++ b/docs/modders/Bonus/Bonus_Types.md @@ -1,3 +1,5 @@ +# Bonus Types + The bonuses were grouped according to their original purpose. The bonus system allows them to propagate freely between the nodes, however they may not be recognized properly beyond the scope of original use. ## General-purpose bonuses diff --git a/docs/modders/Bonus/Bonus_Updaters.md b/docs/modders/Bonus/Bonus_Updaters.md index d8a33b6de..f0d95f940 100644 --- a/docs/modders/Bonus/Bonus_Updaters.md +++ b/docs/modders/Bonus/Bonus_Updaters.md @@ -1,3 +1,5 @@ +# Bonus Updaters + TODO: this page may be incorrect or outdated Updaters come in two forms: simple and complex. Simple updaters take no diff --git a/docs/modders/Bonus/Bonus_Value_Types.md b/docs/modders/Bonus/Bonus_Value_Types.md index f33be82c4..aca624516 100644 --- a/docs/modders/Bonus/Bonus_Value_Types.md +++ b/docs/modders/Bonus/Bonus_Value_Types.md @@ -1,3 +1,5 @@ +# Bonus Value Types + Total value of Bonus is calculated using the following: - For each bonus source type we calculate new source value (for all bonus value types except PERCENT_TO_SOURCE and PERCENT_TO_TARGET_TYPE) using the following: diff --git a/docs/modders/Bonus_Format.md b/docs/modders/Bonus_Format.md index f3a7c671c..5dce1c1d0 100644 --- a/docs/modders/Bonus_Format.md +++ b/docs/modders/Bonus_Format.md @@ -1,3 +1,5 @@ +# Bonus Format + ## Full format All parameters but type are optional. diff --git a/docs/modders/Building_Bonuses.md b/docs/modders/Building_Bonuses.md index cee6a9cb5..ac4e0c7d9 100644 --- a/docs/modders/Building_Bonuses.md +++ b/docs/modders/Building_Bonuses.md @@ -1,9 +1,11 @@ +# Building Bonuses + Work-in-progress page do describe all bonuses provided by town buildings for future configuration. TODO: This page is outdated and may not represent VCMI 1.3 state -## unique buildings +### unique buildings Hardcoded functionalities, selectable but not configurable. In future should be moved to scripting. @@ -24,7 +26,7 @@ Function of all of these objects can be enabled by this: "function" : "castleGates" ``` -## trade-related +### trade-related Hardcoded functionality for now due to complexity of these objects. Temporary can be handles as unique buildings. Includes: @@ -37,7 +39,7 @@ Temporary can be handles as unique buildings. Includes: - resource - skills - creature - skeleton -## hero visitables +### hero visitables Buildings that give one or another bonus to visiting hero. All should be handled via configurable objects system. @@ -49,76 +51,76 @@ Includes: - give bonus to visitor - permanent bonus to hero -## generic functions +### generic functions Generic town-specific functions that can be implemented as part of CBuilding class. -### unlock guild level +#### unlock guild level ``` javascript "guildLevels" : 1 ``` -### unlock hero recruitment +#### unlock hero recruitment ``` javascript "allowsHeroPurchase" : true ``` -### unlock ship purchase +#### unlock ship purchase ``` javascript "allowsShipPurchase" : true ``` -### unlock building purchase +#### unlock building purchase ``` javascript "allowsBuildingPurchase" : true ``` -### unlocks creatures +#### unlocks creatures ``` javascript "dwelling" : { "level" : 1, "creature" : "archer" } ``` -### creature growth bonus +#### creature growth bonus Turn into town bonus? What about creature-specific bonuses from hordes? -### gives resources +#### gives resources ``` javascript "provides" : { "gold" : 500 } ``` -### gives guild spells +#### gives guild spells ``` javascript "guildSpells" : [5, 0, 0, 0, 0] ``` -### gives thieves guild +#### gives thieves guild ``` javascript "thievesGuildLevels" : 1 ``` -### gives fortifications +#### gives fortifications ``` javascript "fortificationLevels" : 1 ``` -### gives war machine +#### gives war machine ``` javascript "warMachine" : "ballista" ``` -## simple bonuses +### simple bonuses Bonuses that can be made part of CBuilding. Note that due to how bonus system works this bonuses won't be stackable. @@ -149,12 +151,12 @@ Includes: } ``` -## misc +### misc Some other properties of town building that does not fall under "bonus" category. -### unique building +#### unique building Possible issue - with removing of fixed ID's buildings in different town may no longer share same ID. However Capitol must be unique across all @@ -164,7 +166,7 @@ town. Should be fixed somehow. "onePerPlayer" : true ``` -### chance to be built on start +#### chance to be built on start ``` javascript "prebuiltChance" : 75 diff --git a/docs/modders/Campaign_Format.md b/docs/modders/Campaign_Format.md index e020531cd..554e98566 100644 --- a/docs/modders/Campaign_Format.md +++ b/docs/modders/Campaign_Format.md @@ -1,4 +1,6 @@ -# Introduction +# Campaign Format + +## Introduction Starting from version 1.3, VCMI supports its own campaign format. Campaigns have *.vcmp file format and it consists from campaign json and set of scenarios (can be both *.vmap and *.h3m) @@ -27,7 +29,7 @@ Basic structure of this file is here, each section is described in details below `"version"` defines version of campaign file. Larger versions should have more features and flexibility, but may not be supported by older VCMI engines. See [compatibility table](#compatibility-table) -# Header properties +## Header properties In header are parameters describing campaign properties ```js @@ -51,7 +53,7 @@ In header are parameters describing campaign properties - `"creationDateTime"` unix time of campaign creation - `"allowDifficultySelection"` is a boolean field (`true`/`false`) which allows or disallows to choose difficulty before scenario start -# Scenario description +## Scenario description Scenario description looks like follow: ```js @@ -87,7 +89,7 @@ Scenario description looks like follow: - `"playerColor"` defines color id of flag which player will play for. Possible values are `0: red, 1: blue, tan: 2, green: 3, orange: 4, purple: 5, teal: 6, pink: 7` - "bonuses" array of possible bonus objects, format depends on `"startOptions"` parameter -## Prolog/Epilog +### Prolog/Epilog Prolog and epilog properties are optional ```js @@ -99,13 +101,13 @@ Prolog and epilog properties are optional } ``` -## Start options and bonuses +### Start options and bonuses -### None start option +#### None start option If `startOptions` is `none`, `bonuses` field will be ignored -### Bonus start option +#### Bonus start option If `startOptions` is `bonus`, bonus format may vary depending on its type. @@ -148,7 +150,7 @@ If `startOptions` is `bonus`, bonus format may vary depending on its type. - `"amount"`: amount of resources - `"hero"` can be specified as explicit hero name and as one of keywords: `strongest`, `generated` -### Crossover start option +#### Crossover start option If `startOptions` is `crossover`, heroes from specific scenario will be moved to this scenario. Bonus format is following @@ -161,7 +163,7 @@ If `startOptions` is `crossover`, heroes from specific scenario will be moved to - `"playerColor"` from what player color heroes shall be taken. Possible values are `0: red, 1: blue, tan: 2, green: 3, orange: 4, purple: 5, teal: 6, pink: 7` - `"scenario"` from which scenario heroes shall be taken. 0 means first scenario -### Hero start option +#### Hero start option If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus format is following ```js @@ -174,7 +176,7 @@ If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus forma - `"playerColor"` from what player color heroes shall be taken. Possible values are `0: red, 1: blue, tan: 2, green: 3, orange: 4, purple: 5, teal: 6, pink: 7` - `"hero"` can be specified as explicit hero name and as one of keywords: `random` -## Regions description +### Regions description Predefined campaign regions are located in file `campaign_regions.json` @@ -194,7 +196,7 @@ Predefined campaign regions are located in file `campaign_regions.json` - `"inflix"` ised to identify all images related to region. In this example, it will be pictures starting from `G3A_..., G3B_..., G3C_..."` - `"color_suffix_length"` identifies suffix length for region colourful frames. 1 is used for `R, B, N, G, O, V, T, P`, value 2 is used for `Re, Bl, Br, Gr, Or, Vi, Te, Pi` -# Packing campaign +## Packing campaign After campaign scenarios and campaign description are ready, you should pack them into *.vcmp file. This file is basically headless gz archive. @@ -216,7 +218,7 @@ gzip -c -n ./* >> my-campaign.vcmp If you are using Windows system, try this https://gnuwin32.sourceforge.net/packages/gzip.htm -# Compatibility table +## Compatibility table | Version | Min VCMI | Max VCMI | Description | |---------|----------|----------|-------------| | 1 | 1.3 | | Initial release | \ No newline at end of file diff --git a/docs/modders/Configurable_Widgets.md b/docs/modders/Configurable_Widgets.md index 0649b866b..7a1ea901a 100644 --- a/docs/modders/Configurable_Widgets.md +++ b/docs/modders/Configurable_Widgets.md @@ -1,10 +1,12 @@ -# Introduction +# Configurable Widgets + +## Introduction VCMI has capabilities to change some UI elements in your mods beyond only replacing one image with another. Not all UI elements are possible to modify currently, but development team is expanding them. Elements possible to modify are located in `config/widgets`. -# Tutorial +## Tutorial Let's take `extendedLobby` mod from `vcmi-extras` as an example for VCMI-1.4. [Example sources](https://github.com/vcmi-mods/vcmi-extras/tree/vcmi-1.4/Mods/extendedLobby). @@ -16,7 +18,7 @@ For options tab it introduces UI for chess timers. In this tutorial we will recreate options tab to support chess timers UI. -## Creating mod structure +### Creating mod structure To start making mod, create following folders structure; ``` @@ -44,7 +46,7 @@ File `mod.json` is generic and could look like this: After that you can copy `extendedLobby/ folder to `mods/` folder and your mod will immediately appear in launcher but it does nothing for now. -## Making layout for timer +### Making layout for timer Let's copy `config/widgets/optionsTab.json` file from VCMI folder to `content/config/widgets/` folder from our mod. It defines UI for options tab as it designed in original game, we will keep everything related to player settings and will modify only timer area. @@ -111,7 +113,7 @@ In order to make it work, add file `RmgTTBk.bmp` to `content/sprites/` Elements named `labelTurnDurationValue` and `sliderTurnDuration` we will keep without change - they are needed to configure classic timer. -## Adding combo box +### Adding combo box Now, let's add combo box. @@ -264,7 +266,7 @@ Now specify items inside `dropDown` field Now we can press drop-down menu and even select elements. -## Switching timer modes +### Switching timer modes After view part is done, let's make behavioural part. Let's hide elements, related to classic timer when chess timer is selected and show them back if classic selected. @@ -322,7 +324,7 @@ This background must be visible for chess timer and hidden for classic timer. Ju It works and can switch elements, the only missing part is chess timer configuration. -## Chess timer configuration +### Chess timer configuration We should add text input fields, to specify different timers. We will use background for them `timerField.bmp`, copy it to `content/sprites/` folder of your mod. @@ -352,39 +354,39 @@ And what we want to do is to hide/show those fields when classic/chess times is We are done! You can find more information about configurable UI elements in documentation section. -# Documentation +## Documentation -## Types +### Types All fields have format `"key": value` There are different basic types, which can be used as value. -### Primitive types +#### Primitive types Read JSON documentation for primitive types description: https://www.json.org/json-en.html -### Text +#### Text Load predefined text which can be localised, examples: `"vcmi.otherOptions.availableCreaturesAsDwellingLabel"` `"core.genrltxt.738"` -### Position +#### Position Point with two coordinates, example: `{ "x": 43, "y": -28 }` -### Rect +#### Rect Rectangle ares, example: `{ "x": 28, "y": 220, "w": 108, "h": 50 }` -### Text alignment +#### Text alignment Defines text alignment, can be one of values: `"center"`, `"left"`, `"right"` -### Color +#### Color Predefined colors: `"yellow"`, `"white"`, `"gold"`, `"green"`, `"orange"`, `"bright-yellow"` @@ -392,12 +394,12 @@ Predefined colors: To have custom color make an array of four elements in RGBA notation: `[255, 128, 0, 255]` -### Font +#### Font Predefined fonts: `"big"`, `"medium"`, `"small"`, `"tiny"`, `"calisto"` -### Hint text +#### Hint text Hint text is a pair of strings, one is usually shown in status bar when cursor hovers element, another hint while right button pressed. Each of elements is a [Text](#text) @@ -413,19 +415,19 @@ If one string specified, it will be applied for both hover and help. `"text"` -### Shortcut +#### Shortcut String value defines shortcut. Some examples of shortcuts: `"globalAccept", "globalCancel", "globalReturn","globalFullscreen", "globalOptions", "globalBackspace", "globalMoveFocus"` Full list is TBD -### [VCMI-1.4] Player color +#### [VCMI-1.4] Player color One of predefined values: `"red"`, `"blue"`, `"tan"`, `"green"`, `"orange"`, `"purple"`, `"teal"`, `"pink"` -## Configurable objects +### Configurable objects Configurable object has following structure: ```json @@ -445,9 +447,9 @@ Configurable object has following structure: `library` - same as above, but custom widgets are described in separate json, this parameter should contain path to library json is specified -## Basic widgets +### Basic widgets -### Label +#### Label `"type": "label"` @@ -463,7 +465,7 @@ Configurable object has following structure: `"position"`: [position](#position) -### [VCMI-1.4] Multi-line label +#### [VCMI-1.4] Multi-line label `"type": "multiLineLabel"` @@ -483,7 +485,7 @@ Configurable object has following structure: `"adoptHeight": bool` //if true, text area height will be adopted automatically based on content -### Label group +#### Label group `"type": "labelGroup"` @@ -503,7 +505,7 @@ Configurable object has following structure: `"text"`: [text](#text), -### TextBox +#### TextBox `"type": "textBox"` @@ -519,7 +521,7 @@ Configurable object has following structure: `"rect"`: [rect](#rect) -### Picture +#### Picture `"type": "picture"` @@ -533,7 +535,7 @@ Configurable object has following structure: `"playerColored", bool`, optional, if true will be colorised to current player -### Image +#### Image Use to show single frame from animation @@ -549,7 +551,7 @@ Use to show single frame from animation `"frame": integer` optional, specify animation frame -### Texture +#### Texture Filling area with texture @@ -561,7 +563,7 @@ Filling area with texture `"rect"`: [rect](#rect) -### TransparentFilledRectangle +#### TransparentFilledRectangle `"type": "transparentFilledRectangle"` @@ -573,7 +575,7 @@ Filling area with texture `"rect"`: [rect](#rect) -### Animation +#### Animation `"type": "animation"` @@ -599,7 +601,7 @@ Filling area with texture `"end": integer`, last frame -### [VCMI-1.4] Text input +#### [VCMI-1.4] Text input `"type": "textInput"` @@ -625,7 +627,7 @@ Filling area with texture `"callback": string` optional, callback to be called on text changed. Input text is passed to callback function as an argument. -### Button +#### Button `"type": "button"` @@ -647,7 +649,7 @@ Filling area with texture `"items": []` array of widgets to be shown as overlay (caption [label](#label), for example) -### Toggle button +#### Toggle button `"type": "toggleButton"` @@ -667,7 +669,7 @@ Filling area with texture `"items": []` array of widgets to be shown as overlay (caption [label](#label), for example) -### Toggle group +#### Toggle group Group of [toggle buttons](#toggle-button), when one is selected, other will be de-selected @@ -681,7 +683,7 @@ Group of [toggle buttons](#toggle-button), when one is selected, other will be d `"items": []` array of [toggle buttons](#toggle-button) -### Slider +#### Slider `"type": "slider"` @@ -707,7 +709,7 @@ Group of [toggle buttons](#toggle-button), when one is selected, other will be d `"panningStep": integer`, optional -### Combo box +#### Combo box `"type": "comboBox"` @@ -729,7 +731,7 @@ Group of [toggle buttons](#toggle-button), when one is selected, other will be d `"dropDown" : {}` description of [drop down](#drop-down) menu widget -### Drop down +#### Drop down Used only as special object for [combo box](#combo-box) @@ -750,17 +752,17 @@ Used only as special object for [combo box](#combo-box) **Callbacks** - `sliderMove` connect to slider callback to correctly navigate over elements -### Layout +#### Layout `"type": "layout"` `"name": "string"` optional, object name -## High-level widgets +### High-level widgets -## Custom widgets +### Custom widgets -# For developers +## For developers While designing a new element, you can make it configurable to reuse all functionality described above. It will provide flexibility to further changes as well as modding capabilities. @@ -798,9 +800,9 @@ MyYesNoDialog::MyYesNoDialog(const JsonNode & config): } ``` -## Callbacks +### Callbacks -## Custom widgets +### Custom widgets You can build custom widgets, related to your UI element specifically. Like in example above, there is Item widget, which can be also used on JSON config. @@ -832,7 +834,7 @@ After that, if your JSON file has items with type "MyItem", the new Item element } ``` -## Variables +### Variables After calling `build(config)` variables defined in config JSON file become available. You can interpret them and use in callbacks or in element code diff --git a/docs/modders/Difficulty.md b/docs/modders/Difficulty.md index e70a09ca3..91f78af9c 100644 --- a/docs/modders/Difficulty.md +++ b/docs/modders/Difficulty.md @@ -1,3 +1,5 @@ +# Difficulty + Since VCMI 1.4.0 there are more capabilities to configure difficulty parameters. It means, that modders can give different bonuses to AI or human players depending on selected difficulty diff --git a/docs/modders/Entities_Format/Artifact_Format.md b/docs/modders/Entities_Format/Artifact_Format.md index 72299d2b9..928496dc7 100644 --- a/docs/modders/Entities_Format/Artifact_Format.md +++ b/docs/modders/Entities_Format/Artifact_Format.md @@ -1,3 +1,5 @@ +# Artifact Format + Artifact bonuses use [Bonus Format](../Bonus_Format.md) ## Required data diff --git a/docs/modders/Entities_Format/Battle_Obstacle_Format.md b/docs/modders/Entities_Format/Battle_Obstacle_Format.md index 9d769ec29..e9b00d3d6 100644 --- a/docs/modders/Entities_Format/Battle_Obstacle_Format.md +++ b/docs/modders/Entities_Format/Battle_Obstacle_Format.md @@ -1,3 +1,5 @@ +# Battle Obstacle Format + ```jsonc // List of terrains on which this obstacle can be used "allowedTerrains" : [] diff --git a/docs/modders/Entities_Format/Battlefield_Format.md b/docs/modders/Entities_Format/Battlefield_Format.md index 2d8164228..21c219273 100644 --- a/docs/modders/Entities_Format/Battlefield_Format.md +++ b/docs/modders/Entities_Format/Battlefield_Format.md @@ -1,17 +1,25 @@ +# Battlefield Format + ```jsonc // Human-readable name of the battlefield - "name" : "" + "name" : "", // If set to true, obstacles will be taken from "specialBattlefields" property of an obstacle // If set to false, obstacles will be taken from "allowedTerrains" instead - "isSpecial" : false + "isSpecial" : false, // List of bonuses that will affect all battles on this battlefield - "bonuses" : { BONUS_FORMAT } + "bonuses" : { BONUS_FORMAT }, // Background image for this battlefield - "graphics" : "" + "graphics" : "", + + // Optional, filename for custom music to play during combat on this terrain + "music" : "", + + // Optional, filename for custom sound to play during combat opening on this terrain + "openingSound" : "", // List of battle hexes that will be always blocked on this battlefield (e.g. ship to ship battles) - "impassableHexes" : [ 10, 20, 50 ] + "impassableHexes" : [ 10, 20, 50 ], ``` \ No newline at end of file diff --git a/docs/modders/Entities_Format/Biome_Format.md b/docs/modders/Entities_Format/Biome_Format.md index da50664a9..a0d2a3689 100644 --- a/docs/modders/Entities_Format/Biome_Format.md +++ b/docs/modders/Entities_Format/Biome_Format.md @@ -1,3 +1,5 @@ +# Biome Format + ## General description Biome is a new entity type added in VCMI 1.5.0. It defines a set of random map obstacles which will be generated together. For each zone different obstacle sets is randomized and then only obstacles from that set will be used to fill this zone. diff --git a/docs/modders/Entities_Format/Creature_Format.md b/docs/modders/Entities_Format/Creature_Format.md index ad58c66ad..ecbb2b78e 100644 --- a/docs/modders/Entities_Format/Creature_Format.md +++ b/docs/modders/Entities_Format/Creature_Format.md @@ -1,3 +1,5 @@ +# Creature Format + ## Required data In order to make functional creature you also need: diff --git a/docs/modders/Entities_Format/Faction_Format.md b/docs/modders/Entities_Format/Faction_Format.md index 6fe6c4236..a7c9935be 100644 --- a/docs/modders/Entities_Format/Faction_Format.md +++ b/docs/modders/Entities_Format/Faction_Format.md @@ -1,3 +1,5 @@ +# Faction Format + ## Required data In order to make functional town you also need: @@ -30,7 +32,7 @@ In order to make functional town you also need: ### Music -- Town theme music track (1 music file) +- Town theme music track (at least 1 music file) ### Buildings @@ -150,8 +152,9 @@ Each town requires a set of buildings (Around 30-45 buildings) } } }, - // Path to town music theme, e.g. "music/castleTheme" - "musicTheme" : "", + // List of town music themes, e.g. [ "music/castleTheme" ] + // At least one music file is required + "musicTheme" : [ "" ], // List of structures which represents visible graphical objects on town screen. // See detailed description below diff --git a/docs/modders/Entities_Format/Hero_Class_Format.md b/docs/modders/Entities_Format/Hero_Class_Format.md index 6bfb54efe..68833fa5d 100644 --- a/docs/modders/Entities_Format/Hero_Class_Format.md +++ b/docs/modders/Entities_Format/Hero_Class_Format.md @@ -1,3 +1,5 @@ +# Hero Class Format + ## Required data In order to make functional hero class you also need: diff --git a/docs/modders/Entities_Format/Hero_Type_Format.md b/docs/modders/Entities_Format/Hero_Type_Format.md index 54c2f9b8f..b646c0e89 100644 --- a/docs/modders/Entities_Format/Hero_Type_Format.md +++ b/docs/modders/Entities_Format/Hero_Type_Format.md @@ -1,3 +1,5 @@ +# Hero Type Format + ## Required data In order to make functional hero you also need: diff --git a/docs/modders/Entities_Format/River_Format.md b/docs/modders/Entities_Format/River_Format.md index 48f29e889..35635768d 100644 --- a/docs/modders/Entities_Format/River_Format.md +++ b/docs/modders/Entities_Format/River_Format.md @@ -1,3 +1,5 @@ +# River Format + ## Format ```jsonc diff --git a/docs/modders/Entities_Format/Road_Format.md b/docs/modders/Entities_Format/Road_Format.md index 564264fc0..d78de9b10 100644 --- a/docs/modders/Entities_Format/Road_Format.md +++ b/docs/modders/Entities_Format/Road_Format.md @@ -1,3 +1,5 @@ +# Road Format + ## Format ```jsonc diff --git a/docs/modders/Entities_Format/Secondary_Skill_Format.md b/docs/modders/Entities_Format/Secondary_Skill_Format.md index 90bbf757c..367ca775f 100644 --- a/docs/modders/Entities_Format/Secondary_Skill_Format.md +++ b/docs/modders/Entities_Format/Secondary_Skill_Format.md @@ -1,3 +1,5 @@ +# Secondary Skill Format + ## Main format ```jsonc diff --git a/docs/modders/Entities_Format/Spell_Format.md b/docs/modders/Entities_Format/Spell_Format.md index 32d68afa0..5e17e817e 100644 --- a/docs/modders/Entities_Format/Spell_Format.md +++ b/docs/modders/Entities_Format/Spell_Format.md @@ -1,3 +1,5 @@ +# Spell Format + ## Main format ``` javascript diff --git a/docs/modders/Entities_Format/Terrain_Format.md b/docs/modders/Entities_Format/Terrain_Format.md index 287ff4386..d12a7f7cf 100644 --- a/docs/modders/Entities_Format/Terrain_Format.md +++ b/docs/modders/Entities_Format/Terrain_Format.md @@ -1,3 +1,5 @@ +# Terrain Format + ## Format ```jsonc @@ -48,8 +50,8 @@ // Color of terrain on minimap with unpassable objects. RGB triplet, 0-255 range "minimapBlocked" : [ 150, 100, 50 ], - // Music filename to play on this terrain on adventure map - "music" : "", + // List of music files to play on this terrain on adventure map. At least one file is required + "music" : [ "" ], "sounds" : { // List of ambient sounds for this terrain diff --git a/docs/modders/Game_Identifiers.md b/docs/modders/Game_Identifiers.md index 6317339d1..4e212fe01 100644 --- a/docs/modders/Game_Identifiers.md +++ b/docs/modders/Game_Identifiers.md @@ -1,3 +1,5 @@ +# Game Identifiers + ## List of all game identifiers This is a list of all game identifiers available to modders. Note that only identifiers from base game have been included. For identifiers from mods please look up corresponding mod diff --git a/docs/modders/Map_Editor.md b/docs/modders/Map_Editor.md index 5373adca0..3c472d8a3 100644 --- a/docs/modders/Map_Editor.md +++ b/docs/modders/Map_Editor.md @@ -1,16 +1,18 @@ -# Interface +# Map Editor + +## Interface -# Create the map +## Create the map -## New map +### New map Create the new map by pressing **New** button from the toolbar -### Empty map +#### Empty map To create empty map, define its size by choosing option from drop-down list or enter required size manually in the text fields and press Ok button. Check **Two level map** option to create map with underground. `Note: there are no limits on map size but be careful with sizes larger predefined XL size. It will be processed quite long to create even empty map. Also, it will be difficult to work with the huge maps because of possible performance issues` @@ -19,7 +21,7 @@ Other parameters won't be used for empty map. -### Random map +#### Random map To generate random map, check the **Random map** option and configure map parameters. You can select template from the drop-down list. @@ -32,27 +34,27 @@ Templates are dynamically filtered depending on parameters you choose. -## Map load & save +### Map load & save To load the map, press open and select map file from the browser. You can load both *.h3m and *.vmap formats but for saving *.vmap is allowed only. -# Views +## Views There are 3 buttons switching views Снимок экрана 2022-09-07 в 06 48 08 -### Ground/underground +#### Ground/underground **"U/G"** switches you between ground and underground -### Grid view +#### Grid view **Grid** show/hide grid -### Passability view +#### Passability view **Pass** show/hide passability map @@ -73,7 +75,7 @@ There are 3 buttons switching views Снимок экра
 
 <img width= -### Drawing roads and rivers +#### Drawing roads and rivers Actually, the process to draw rivers or roads is exactly the same as for terrains. You need to select tiles and then choose road/river type from the panel. @@ -85,13 +87,13 @@ To erase roads or rivers, you need to select tiles to be cleaned and press empty _Erasing works either for roads or for rivers, e.g. empty button from the roads tab erases roads only, but not rivers. You also can safely select bigger area, because it won't erase anything on tiles without roads/rivers accordingly_ -## About brushes +### About brushes * Buttons "1", "2", "4" - 1x1, 2x2, 4x4 brush sizes accordingly * Button "[]" - non-additive rectangle selection * Button "O" - lasso brush (not implemented yet) * Button "E" - object erase, not a brush -## Fill obstacles +### Fill obstacles Map editor supports automatic obstacle placement. Obstacle types are automatically selected for appropriate terrain types @@ -105,9 +107,9 @@ To do that, select area (see Setup terrains) and press **Fill** button from the `Note: obstacle placer may occupy few neighbour tiles outside of selected area` -# Manipulating objects +## Manipulating objects -## Adding new objects +### Adding new objects 1. Find the object you'd like to place in the object browser @@ -125,7 +127,7 @@ To do that, select area (see Setup terrains) and press **Fill** button from the **Right click over the scene - cancel object placement** -## Removing objects +### Removing objects 1. **Make sure that no one terrain brush is selected.** To de-select brush click on selected brush again. @@ -134,7 +136,7 @@ To do that, select area (see Setup terrains) and press **Fill** button from the 3. Press **"E"** button from the brush panel or press **delete** on keyboard -## Changing object's properties +### Changing object's properties 1. **Make sure that no one terrain brush is selected.** To de-select brush click on selected brush again. @@ -147,15 +149,15 @@ To do that, select area (see Setup terrains) and press **Fill** button from the 4. You are able to modify properties which are not gray `Note: sometimes there are empty editable fields` -### Assigning player to the object +#### Assigning player to the object Objects with flags can be assigned to the player. Find Owner property in the inspector for selected object, press twice to modify right cell. Type player number from **0 to 7 or type NEUTRAL** for neutral objects. -# Set up the map +## Set up the map You can modify general properties of the map -## Map name and description +### Map name and description 1. Open **Map** menu on the top and select **General** @@ -167,7 +169,7 @@ You can modify general properties of the map -# Player settings +## Player settings Open **Map** menu on the top and select **Player settings" @@ -179,29 +181,29 @@ You will see a window with player settings. Combobox players defines amount of p -# Compatibility questions +## Compatibility questions -## Platform compatibility +### Platform compatibility vcmieditor is a cross-platform application, so in general can support all platforms, supported by VCMI. However, currently it doesn't support mobile platforms. -## Engine compatibility +### Engine compatibility vcmieditor is independent application so potentially it can be installed just in the folder with existing stable vcmi. However, on the initial stages of development compatibility was not preserved because major changes were needed to introduce into vcmi library. So it's recommended to download full package to use editor. -## Map compatibility +### Map compatibility vcmieditor haven't introduced any change into map format yet, so all maps made by vcmieditor can be easily played with any version of vcmi. At the same time, those maps can be open and read in the old map editor and vice verse - maps from old editor can be imported in the new editor. So, full compatibility is ensured here. -## Mod compatibility +### Mod compatibility vcmieditor loads set of mods using exactly same mechanism as game uses and mod manipulations can be done using vcmilaucnher application, just enable or disable mods you want and open editor to use content from those mods. In regards on compatibility, of course you need to play maps with same set of mods as you used in the editor. Good part is that is maps don't use content from the mods (even mods were enabled), it can be played on vcmi without mods as well -# Working With Mods +## Working With Mods -## Enabling and disabling mods +### Enabling and disabling mods The mods mechanism used in map editor is the same as in game. @@ -212,7 +214,7 @@ To enable or disable mods There is no button to start map editor directly from launcher, however you may use this approach to control active mods from any version of vcmi. -## Placing objects from mods +### Placing objects from mods * All objects from mods will be automatically added into objects Browser. You can type mod name into filter field to find them. @@ -222,13 +224,13 @@ There is no button to start map editor directly from launcher, however you may u -## Playing maps with mods +### Playing maps with mods If you place any kind of objects from the mods, obviously, you need those mods to be installed to play the map. Also, you need to activate them. You also may have other mods being activated in addition to what was used during map designing. -### Mod versions +#### Mod versions In the future, the will be support of mods versioning so map will contain information about mods used and game can automatically search and activate required mods or let user know which are required. However, it's not implemented yet \ No newline at end of file diff --git a/docs/modders/Map_Object_Format.md b/docs/modders/Map_Object_Format.md index b2daf6288..887af1965 100644 --- a/docs/modders/Map_Object_Format.md +++ b/docs/modders/Map_Object_Format.md @@ -1,3 +1,5 @@ +# Map Object Format + ## Description Full object consists from 3 parts: @@ -58,6 +60,9 @@ Full object consists from 3 parts: // How valuable this object is to AI "aiValue" : 1000, + // Battleground that will be used for combats in this object. Overrides terrain this object was placed on + "battleground" : "cursed_ground", + // Sounds assigned to this object "sounds" : { // Ambient sounds that plays when current hero is near this object diff --git a/docs/modders/Map_Objects/Boat.md b/docs/modders/Map_Objects/Boat.md index 1f17090f3..8d67581a5 100644 --- a/docs/modders/Map_Objects/Boat.md +++ b/docs/modders/Map_Objects/Boat.md @@ -1,3 +1,5 @@ +# Boat + ``` javascript { // Layer on which this boat moves. Possible values: diff --git a/docs/modders/Map_Objects/Creature_Bank.md b/docs/modders/Map_Objects/Creature_Bank.md index 4a583f270..36a3b6a3d 100644 --- a/docs/modders/Map_Objects/Creature_Bank.md +++ b/docs/modders/Map_Objects/Creature_Bank.md @@ -1,3 +1,5 @@ +# Creature Bank + Reward types for clearing creature bank are limited to resources, creatures, artifacts and spell. Format of rewards is same as in [Rewardable Objects](Rewardable.md) diff --git a/docs/modders/Map_Objects/Dwelling.md b/docs/modders/Map_Objects/Dwelling.md index 84823d0b1..df369c502 100644 --- a/docs/modders/Map_Objects/Dwelling.md +++ b/docs/modders/Map_Objects/Dwelling.md @@ -1,3 +1,5 @@ +# Dwelling + ``` javascript { /// List of creatures in this bank. Each list represents one "level" of bank diff --git a/docs/modders/Map_Objects/Market.md b/docs/modders/Map_Objects/Market.md index 3861db879..2b411dfb8 100644 --- a/docs/modders/Map_Objects/Market.md +++ b/docs/modders/Map_Objects/Market.md @@ -1,3 +1,5 @@ +# Market + ## Market schema Since VCMI-1.3 it's possible to create customizable markets on adventure map. diff --git a/docs/modders/Map_Objects/Rewardable.md b/docs/modders/Map_Objects/Rewardable.md index 6be3e3abe..48426cf00 100644 --- a/docs/modders/Map_Objects/Rewardable.md +++ b/docs/modders/Map_Objects/Rewardable.md @@ -1,3 +1,5 @@ +# Rewardable + ## Base object definition Rewardable object is defined similarly to other objects, with key difference being `handler`. This field must be set to `"handler" : "configurable"` in order for vcmi to use this mode. ```jsonc diff --git a/docs/modders/Mod_File_Format.md b/docs/modders/Mod_File_Format.md index 29e04f2b9..f0ac58070 100644 --- a/docs/modders/Mod_File_Format.md +++ b/docs/modders/Mod_File_Format.md @@ -1,3 +1,5 @@ +# Mod File Format + ## Fields with description of mod ``` javascript diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index 10c4eb141..c133b80d8 100644 --- a/docs/modders/Random_Map_Template.md +++ b/docs/modders/Random_Map_Template.md @@ -1,3 +1,5 @@ +# Random Map Template + ## Template format ``` javascript diff --git a/docs/modders/Readme.md b/docs/modders/Readme.md index 33dee4330..5c788b539 100644 --- a/docs/modders/Readme.md +++ b/docs/modders/Readme.md @@ -1,3 +1,5 @@ +# Modding Readme + ## Creating mod To make your own mod you need to create subdirectory in **/Mods/** with name that will be used as identifier for your mod. diff --git a/docs/modders/Translations.md b/docs/modders/Translations.md index 170e4fa54..3915986c5 100644 --- a/docs/modders/Translations.md +++ b/docs/modders/Translations.md @@ -1,3 +1,5 @@ +# Translations + ## List of currently supported languages This is list of all languages that are currently supported by VCMI. If your languages is missing from the list and you wish to translate VCMI - please contact our team and we'll add support for your language in next release. diff --git a/docs/players/Bug_Reporting_Guidelines.md b/docs/players/Bug_Reporting_Guidelines.md index 6ed39fd52..eabe6b6af 100644 --- a/docs/players/Bug_Reporting_Guidelines.md +++ b/docs/players/Bug_Reporting_Guidelines.md @@ -1,3 +1,5 @@ +# Bug Reporting Guidelines + First of all, thanks for your support! If you report a bug we can fix it. But keep in mind that reporting your bugs appropriately makes our (developers') lives easier. Here are a few guidelines that will help you write good bug reports. ## Github bugtracker diff --git a/docs/players/Cheat_Codes.md b/docs/players/Cheat_Codes.md index b8d8c6387..925a9fdef 100644 --- a/docs/players/Cheat_Codes.md +++ b/docs/players/Cheat_Codes.md @@ -1,4 +1,6 @@ -## Cheat Codes +# Cheat Codes + +## Codes Similar to H3, VCMI provides cheat codes to make testing game more convenient. diff --git a/docs/players/Game_Mechanics.md b/docs/players/Game_Mechanics.md index 561d3fdfc..e64f7e94e 100644 --- a/docs/players/Game_Mechanics.md +++ b/docs/players/Game_Mechanics.md @@ -1,3 +1,5 @@ +# Game Mechanics + ## List of features added in VCMI ### High resolutions diff --git a/docs/players/Installation_Android.md b/docs/players/Installation_Android.md index cbbff127d..a28290a1b 100644 --- a/docs/players/Installation_Android.md +++ b/docs/players/Installation_Android.md @@ -1,3 +1,5 @@ +# Installation Android + ## Step 1: Download and install VCMI **This app requires original heroes 3 sod / complete files to operate, they are not supplied with this installer. it is recommended to purchase version from gog.com. Heroes 3 "hd edition" (steam version) files are not supported !!!** diff --git a/docs/players/Installation_Linux.md b/docs/players/Installation_Linux.md index 73ea09077..9440246b3 100644 --- a/docs/players/Installation_Linux.md +++ b/docs/players/Installation_Linux.md @@ -1,6 +1,8 @@ +# Installation Linux + VCMI requires data from original Heroes 3: Shadow of Death or Complete editions. Data from native Linux version made by LOKI will not work. -# Step 1: Binaries installation +## Step 1: Binaries installation ### Ubuntu - Latest stable build from PPA (recommended) @@ -41,6 +43,15 @@ To install VCMI from repository: sudo apt-get update sudo apt-get install vcmi ``` + +### Fedora (40 or newer) + +Stable VCMI version is available in RPM Fusion repository. Learn how to enable it in [wiki](https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/). To install VCMI from repository: + +``` + sudo dnf update + sudo dnf install vcmi +``` ### Flatpak (distribution-agnostic) Latest public release build can be installed via Flatpak. diff --git a/docs/players/Installation_Windows.md b/docs/players/Installation_Windows.md index 06c0e86ea..b7fbd66d1 100644 --- a/docs/players/Installation_Windows.md +++ b/docs/players/Installation_Windows.md @@ -1,3 +1,5 @@ +# Installation Windows + ## Prerequisites As of VCMI 1.2 and newer Windows 10 or newer is required since our automated system uses elements incompatible with older Windows. diff --git a/docs/players/Installation_iOS.md b/docs/players/Installation_iOS.md index 2c2b9ddf7..c30cfd421 100644 --- a/docs/players/Installation_iOS.md +++ b/docs/players/Installation_iOS.md @@ -1,3 +1,5 @@ +# Installation iOS + You can run VCMI on iOS 12.0 and later, all devices are supported. If you wish to run on iOS 10 or 11, you should build from source, see [How to build VCMI (iOS)](../developers/Building_iOS.md). ## Step 1: Download and install VCMI diff --git a/docs/players/Installation_macOS.md b/docs/players/Installation_macOS.md index f550d6e94..1ce876d59 100644 --- a/docs/players/Installation_macOS.md +++ b/docs/players/Installation_macOS.md @@ -1,3 +1,5 @@ +# Installation macOS + For iOS installation look here: (Installation on iOS)[Installation_iOS.md] ## Step 1: Download and install VCMI diff --git a/docs/players/Privacy_Policy.md b/docs/players/Privacy_Policy.md index 25a884435..5bd841505 100644 --- a/docs/players/Privacy_Policy.md +++ b/docs/players/Privacy_Policy.md @@ -1,14 +1,16 @@ +# Privacy Policy + **Last Updated: 24th December, 2022** -### Glossary +## Glossary * VCMI team - a community of VCMI developers, mod makers and testers. It is not some officially registered organization. * VCMI app - an application provided by VCMI team. -### Single player +## Single player VCMI team does not collect any data produced by VCMI app. All game files, logs, saves, mods are stored in app's internal directory and will be removed upon app uninstallation. It should be possible to backup this data by standard ways provided by your device. -### Multiplayer +## Multiplayer If you decide to play with other users via Internet there are two roles. The host is the one who provides the game server. The clients are the other players who connect to the host. The host provides to the client its IP address in order to establish connections. The clients and the host during the gameplay exchange their usernames, messages and other game activity. All this data is collected and stored by the host. VCMI team does not collect and store any multiplayer data. \ No newline at end of file diff --git a/include/vstd/CLoggerBase.h b/include/vstd/CLoggerBase.h index 546b3d352..a1cf6edba 100644 --- a/include/vstd/CLoggerBase.h +++ b/include/vstd/CLoggerBase.h @@ -193,5 +193,6 @@ extern DLL_LINKAGE vstd::CLoggerBase * logNetwork; extern DLL_LINKAGE vstd::CLoggerBase * logAi; extern DLL_LINKAGE vstd::CLoggerBase * logAnim; extern DLL_LINKAGE vstd::CLoggerBase * logMod; +extern DLL_LINKAGE vstd::CLoggerBase * logRng; VCMI_LIB_NAMESPACE_END diff --git a/include/vstd/RNG.h b/include/vstd/RNG.h index 60e1dedc0..9a4e54a06 100644 --- a/include/vstd/RNG.h +++ b/include/vstd/RNG.h @@ -15,18 +15,36 @@ VCMI_LIB_NAMESPACE_BEGIN namespace vstd { -using TRandI64 = std::function; -using TRand = std::function; - class DLL_LINKAGE RNG { public: - virtual ~RNG() = default; - virtual TRandI64 getInt64Range(int64_t lower, int64_t upper) = 0; + /// Returns random number in range [lower, upper] + virtual int nextInt(int lower, int upper) = 0; - virtual TRand getDoubleRange(double lower, double upper) = 0; + /// Returns random number in range [lower, upper] + virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; + + /// Returns random number in range [lower, upper] + virtual double nextDouble(double lower, double upper) = 0; + + /// Returns random number in range [0, upper] + virtual int nextInt(int upper) = 0; + + /// Returns random number in range [0, upper] + virtual int64_t nextInt64(int64_t upper) = 0; + + /// Returns random number in range [0, upper] + virtual double nextDouble(double upper) = 0; + + /// Generates an integer between 0 and the maximum value it can hold. + /// Should be only used for seeding other generators + virtual int nextInt() = 0; + + /// Returns integer using binomial distribution + /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts + virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; }; } @@ -39,7 +57,7 @@ namespace RandomGeneratorUtil if(container.empty()) throw std::runtime_error("Unable to select random item from empty container!"); - return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)()); + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); } template @@ -48,7 +66,7 @@ namespace RandomGeneratorUtil if(container.empty()) throw std::runtime_error("Unable to select random item from empty container!"); - return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)()); + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); } template @@ -59,7 +77,7 @@ namespace RandomGeneratorUtil int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); assert(totalWeight > 0); - int64_t roll = rand.getInt64Range(0, totalWeight - 1)(); + int64_t roll = rand.nextInt64(0, totalWeight - 1); for (size_t i = 0; i < container.size(); ++i) { @@ -77,7 +95,7 @@ namespace RandomGeneratorUtil for(int64_t i = n-1; i>0; --i) { - std::swap(container.begin()[i],container.begin()[rand.getInt64Range(0, i)()]); + std::swap(container.begin()[i],container.begin()[rand.nextInt64(0, i)]); } } } diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index 16aaace12..4ab10bb28 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -91,6 +91,7 @@ vcmilauncher.desktop + diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index 4635e9f50..e4eb884d6 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -89,7 +89,7 @@ QVariant JsonFromFile(QString filename) } const auto data = file.readAll(); - JsonNode node(reinterpret_cast(data.data()), data.size()); + JsonNode node(reinterpret_cast(data.data()), data.size(), filename.toStdString()); return toVariant(node); } diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 2c6292c5b..88b86be46 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -236,16 +236,32 @@ void MainWindow::on_aboutButton_clicked() void MainWindow::updateTranslation() { #ifdef ENABLE_QT_TRANSLATIONS - const std::string translationFile = settings["general"]["language"].String() + ".qm"; - logGlobal->info("Loading translation '%s'", translationFile); + const std::string translationFile = settings["general"]["language"].String()+ ".qm"; + QString translationFileResourcePath = QString{":/translation/%1"}.arg(translationFile.c_str()); - if (!translator.load(QString{":/translation/%1"}.arg(translationFile.c_str()))) + logGlobal->info("Loading translation %s", translationFile); + + if(!QFile::exists(translationFileResourcePath)) { - logGlobal->error("Failed to load translation"); + logGlobal->debug("Translation file %s does not exist", translationFileResourcePath.toStdString()); + return; + } + + if (!translator.load(translationFileResourcePath)) + { + logGlobal->error("Failed to load translation file %s", translationFileResourcePath.toStdString()); + return; + } + + if(translationFile == "english.qm") + { + // translator doesn't need to be installed for English return; } if (!qApp->installTranslator(&translator)) - logGlobal->error("Failed to install translator"); + { + logGlobal->error("Failed to install translator for translation file %s", translationFileResourcePath.toStdString()); + } #endif } diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 1a4aa2348..0fa415c5e 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -134,6 +134,8 @@ CModListView::CModListView(QWidget * parent) ui->updateButton->setIcon(QIcon{":/icons/mod-update.png"}); ui->installButton->setIcon(QIcon{":/icons/mod-download.png"}); + ui->splitter->setStyleSheet("QSplitter::handle {background: palette('window');}"); + setupModModel(); setupFilterModel(); setupModsView(); @@ -960,6 +962,12 @@ void CModListView::loadScreenshots() { if(ui->tabWidget->currentIndex() == 2) { + if(!ui->allModsView->currentIndex().isValid()) + { + // select the first mod, so we can access its data + ui->allModsView->setCurrentIndex(filterModel->index(0, 0)); + } + ui->screenshotsList->clear(); QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); assert(modModel->hasMod(modName)); //should be filtered out by check above diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index 2b245d6cd..26518f982 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -465,11 +465,11 @@ Downloading %1. %p% (%v MB out of %m MB) finished - + 正在下载 %1. %p% (%v MB 共 %m MB) 已完成 Downloading %s%. %p% (%v MB out of %m MB) finished - 下载进度 %s%. %p% (%v MB 共 %m MB) 已完成 + 正在下载 %s%. %p% (%v MB 共 %m MB) 已完成 @@ -738,12 +738,12 @@ Install successfully downloaded? Show Tutorial again - + 重新显示教程 Reset - + 重置 @@ -1275,7 +1275,7 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b File cannot opened - + 打开文件失败 @@ -1324,23 +1324,24 @@ Please select directory with installed Heroes III data. You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer! - + 您提供的是GOG Galaxy安装器!这个文件不包含游戏内容,请下载离线游戏安装器! Stream error while extracting files! error reason: - + 提取文件时遭遇文件流错误! +错误原因: Not a supported Inno Setup installer! - + 这不是一个支持的Inno Setup安装器! Extracting error! - + 提取错误! diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index ce306c2e8..364c674e7 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -461,7 +461,7 @@ Downloading %1. %p% (%v MB out of %m MB) finished - + Baixando %1. %p% (%v MB de %m MB) concluído Downloading %s%. %p% (%v MB out of %m MB) finished @@ -733,12 +733,12 @@ Instalar o download realizado com sucesso? Show Tutorial again - + Mostrar o Tutorial novamente Reset - + Redefinir @@ -940,7 +940,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu Autosave - Salvar automaticamente + Salvamento automático @@ -1268,7 +1268,7 @@ O instalador offline consiste em duas partes, .exe e .bin. Certifique-se de baix File cannot opened - + O arquivo não pode ser aberto @@ -1317,23 +1317,24 @@ Por favor, selecione o diretório com os dados do Heroes III instalados. You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer! - + Você forneceu o instalador do GOG Galaxy! Este arquivo não contém o jogo. Por favor, faça o download do instalador offline de backup do jogo! Stream error while extracting files! error reason: - + Erro de fluxo ao extrair arquivos! +Motivo do erro: Not a supported Inno Setup installer! - + Instalador do Inno Setup não suportado! Extracting error! - + Erro ao extrair! diff --git a/launcher/updatedialog_moc.cpp b/launcher/updatedialog_moc.cpp index 6c9825868..2de3aba7d 100644 --- a/launcher/updatedialog_moc.cpp +++ b/launcher/updatedialog_moc.cpp @@ -67,7 +67,7 @@ UpdateDialog::UpdateDialog(bool calledManually, QWidget *parent): } auto byteArray = response->readAll(); - JsonNode node(reinterpret_cast(byteArray.constData()), byteArray.size()); + JsonNode node(reinterpret_cast(byteArray.constData()), byteArray.size(), ""); loadFromJson(node); }); } diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index 7482eff5a..98a6a9a58 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -40,6 +40,9 @@ std::shared_ptr BattleFieldHandler::loadFromJson(const std::str for(auto node : json["impassableHexes"].Vector()) info->impassableHexes.emplace_back(node.Integer()); + info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); + info->musicFilename = AudioPath::fromJson(json["music"]); + return info; } diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index ca2ddc8ce..f8b814f50 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -32,6 +32,8 @@ public: std::string icon; si32 iconIndex; std::vector impassableHexes; + AudioPath openingSoundFilename; + AudioPath musicFilename; BattleFieldInfo() : BattleFieldInfo(BattleField::NONE, "") diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index bd57aa59e..127afe112 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -25,7 +25,6 @@ class CArtHandler; class CGHeroInstance; class CArtifactSet; class CArtifactInstance; -class CRandomGenerator; class CMap; class JsonSerializeFormat; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 0f259f563..7b29e1f6f 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -14,7 +14,6 @@ #include "ResourceSet.h" #include "filesystem/Filesystem.h" #include "VCMI_Lib.h" -#include "CRandomGenerator.h" #include "CTownHandler.h" #include "GameSettings.h" #include "constants/StringConstants.h" @@ -28,6 +27,8 @@ #include "modding/CModHandler.h" #include "ExceptionsCommon.h" +#include + VCMI_LIB_NAMESPACE_BEGIN const std::map CCreature::creatureQuantityRanges = @@ -1362,7 +1363,7 @@ CCreatureHandler::~CCreatureHandler() p.first = nullptr; } -CreatureID CCreatureHandler::pickRandomMonster(CRandomGenerator & rand, int tier) const +CreatureID CCreatureHandler::pickRandomMonster(vstd::RNG & rand, int tier) const { std::vector allowed; for(const auto & creature : objects) diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 782f3adf8..5347855f0 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -23,11 +23,15 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + class CLegacyConfigParser; class CCreatureHandler; class CCreature; class JsonSerializeFormat; -class CRandomGenerator; class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode { @@ -225,7 +229,7 @@ public: std::vector< std::vector > skillLevels; //how much of a bonus will be given to commander with every level. SPELL_POWER also gives CASTS and RESISTANCE std::vector , std::pair > > skillRequirements; // first - Bonus, second - which two skills are needed to use it - CreatureID pickRandomMonster(CRandomGenerator & rand, int tier = -1) const; //tier <1 - CREATURES_PER_TOWN> or -1 for any + CreatureID pickRandomMonster(vstd::RNG & rand, int tier = -1) const; //tier <1 - CREATURES_PER_TOWN> or -1 for any CCreatureHandler(); ~CCreatureHandler(); diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 0d2833539..379db73fd 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -658,11 +658,11 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav text.appendLocalString(EMetaText::GENERAL_TXT, 216); std::string extraText; - if(gs->rumor.type == RumorState::TYPE_NONE) + if(gs->currentRumor.type == RumorState::TYPE_NONE) return text.toString(); - auto rumor = gs->rumor.last[gs->rumor.type]; - switch(gs->rumor.type) + auto rumor = gs->currentRumor.last[gs->currentRumor.type]; + switch(gs->currentRumor.type) { case RumorState::TYPE_SPECIAL: text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first); diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 91a4a5c5a..b2bdc41b9 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -17,7 +17,6 @@ #include "battle/BattleHex.h" #include "CCreatureHandler.h" #include "GameSettings.h" -#include "CRandomGenerator.h" #include "CTownHandler.h" #include "CSkillHandler.h" #include "BattleFieldHandler.h" @@ -29,6 +28,8 @@ #include "mapObjectConstructors/CObjectClassesHandler.h" #include "modding/IdentifierStorage.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CHero::CHero() = default; @@ -123,7 +124,7 @@ void CHero::serializeJson(JsonSerializeFormat & handler) } -SecondarySkill CHeroClass::chooseSecSkill(const std::set & possibles, CRandomGenerator & rand) const //picks secondary skill out from given possibilities +SecondarySkill CHeroClass::chooseSecSkill(const std::set & possibles, vstd::RNG & rand) const //picks secondary skill out from given possibilities { assert(!possibles.empty()); diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index e3a477964..323b978e6 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -23,11 +23,15 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + class CHeroClass; class CGHeroInstance; struct BattleHex; class JsonNode; -class CRandomGenerator; class JsonSerializeFormat; class BattleField; @@ -148,7 +152,7 @@ public: std::string getNameTextID() const override; bool isMagicHero() const; - SecondarySkill chooseSecSkill(const std::set & possibles, CRandomGenerator & rand) const; //picks secondary skill out from given possibilities + SecondarySkill chooseSecSkill(const std::set & possibles, vstd::RNG & rand) const; //picks secondary skill out from given possibilities void updateFrom(const JsonNode & data); void serializeJson(JsonSerializeFormat & handler); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6eff0035c..4b84c8411 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -93,6 +93,7 @@ set(lib_MAIN_SRCS gameState/CGameState.cpp gameState/CGameStateCampaign.cpp gameState/InfoAboutArmy.cpp + gameState/RumorState.cpp gameState/TavernHeroesPool.cpp mapObjectConstructors/AObjectTypeHandler.cpp @@ -448,6 +449,7 @@ set(lib_MAIN_HEADERS gameState/CGameStateCampaign.h gameState/EVictoryLossCheckResult.h gameState/InfoAboutArmy.h + gameState/RumorState.h gameState/SThievesGuildInfo.h gameState/TavernHeroesPool.h gameState/TavernSlot.h @@ -527,6 +529,7 @@ set(lib_MAIN_HEADERS networkPacks/PacksForClientBattle.h networkPacks/PacksForLobby.h networkPacks/PacksForServer.h + networkPacks/SetRewardableConfiguration.h networkPacks/SetStackEffect.h networkPacks/StackLocation.h networkPacks/TradeItem.h diff --git a/lib/CRandomGenerator.cpp b/lib/CRandomGenerator.cpp index 22ff8c78e..0cec4fa31 100644 --- a/lib/CRandomGenerator.cpp +++ b/lib/CRandomGenerator.cpp @@ -15,76 +15,87 @@ VCMI_LIB_NAMESPACE_BEGIN CRandomGenerator::CRandomGenerator() { + logRng->trace("CRandomGenerator constructed"); resetSeed(); } CRandomGenerator::CRandomGenerator(int seed) { + logRng->trace("CRandomGenerator constructed (%d)", seed); setSeed(seed); } void CRandomGenerator::setSeed(int seed) { + logRng->trace("CRandomGenerator::setSeed (%d)", seed); rand.seed(seed); } void CRandomGenerator::resetSeed() { + logRng->trace("CRandomGenerator::resetSeed"); boost::hash stringHash; auto threadIdHash = stringHash(boost::lexical_cast(boost::this_thread::get_id())); setSeed(static_cast(threadIdHash * std::time(nullptr))); } -TRandI CRandomGenerator::getIntRange(int lower, int upper) -{ - if (lower <= upper) - return std::bind(TIntDist(lower, upper), std::ref(rand)); - throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); -} - -vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper) -{ - if(lower <= upper) - return std::bind(TInt64Dist(lower, upper), std::ref(rand)); - throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); -} - int CRandomGenerator::nextInt(int upper) { - return getIntRange(0, upper)(); + logRng->trace("CRandomGenerator::nextInt (%d)", upper); + return nextInt(0, upper); +} + +int64_t CRandomGenerator::nextInt64(int64_t upper) +{ + logRng->trace("CRandomGenerator::nextInt64 (%d)", upper); + return nextInt64(0, upper); } int CRandomGenerator::nextInt(int lower, int upper) { - return getIntRange(lower, upper)(); + logRng->trace("CRandomGenerator::nextInt64 (%d, %d)", lower, upper); + + if (lower > upper) + throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); + + return TIntDist(lower, upper)(rand); } int CRandomGenerator::nextInt() { + logRng->trace("CRandomGenerator::nextInt64"); return TIntDist()(rand); } -vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper) +int CRandomGenerator::nextBinomialInt(int coinsCount, double coinChance) { - if(lower <= upper) - return std::bind(TRealDist(lower, upper), std::ref(rand)); - throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); + logRng->trace("CRandomGenerator::nextBinomialInt (%d, %f)", coinsCount, coinChance); + std::binomial_distribution<> distribution(coinsCount, coinChance); + return distribution(rand); +} +int64_t CRandomGenerator::nextInt64(int64_t lower, int64_t upper) +{ + logRng->trace("CRandomGenerator::nextInt64 (%d, %d)", lower, upper); + if (lower > upper) + throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); + + return TInt64Dist(lower, upper)(rand); } double CRandomGenerator::nextDouble(double upper) { - return getDoubleRange(0, upper)(); + logRng->trace("CRandomGenerator::nextDouble (%f)", upper); + return nextDouble(0, upper); } double CRandomGenerator::nextDouble(double lower, double upper) { - return getDoubleRange(lower, upper)(); -} + logRng->trace("CRandomGenerator::nextDouble (%f, %f)", lower, upper); + if(lower > upper) + throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); -double CRandomGenerator::nextDouble() -{ - return TRealDist()(rand); + return TRealDist(lower, upper)(rand); } CRandomGenerator & CRandomGenerator::getDefault() @@ -93,9 +104,5 @@ CRandomGenerator & CRandomGenerator::getDefault() return defaultRand; } -TGenerator & CRandomGenerator::getStdGenerator() -{ - return rand; -} VCMI_LIB_NAMESPACE_END diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h index fe0dd2070..c24daaf14 100644 --- a/lib/CRandomGenerator.h +++ b/lib/CRandomGenerator.h @@ -15,6 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN + /// Generator to use for all randomization in game /// minstd_rand is selected due to following reasons: /// 1. Its randomization quality is below mt_19937 however this is unlikely to be noticeable in game @@ -23,12 +24,11 @@ using TGenerator = std::minstd_rand; using TIntDist = std::uniform_int_distribution; using TInt64Dist = std::uniform_int_distribution; using TRealDist = std::uniform_real_distribution; -using TRandI = std::function; /// The random generator randomly generates integers and real numbers("doubles") between /// a given range. This is a header only class and mainly a wrapper for /// convenient usage of the standard random API. An instance of this RNG is not thread safe. -class DLL_LINKAGE CRandomGenerator : public vstd::RNG, boost::noncopyable, public Serializeable +class DLL_LINKAGE CRandomGenerator final : public vstd::RNG, boost::noncopyable, public Serializeable { public: /// Seeds the generator by default with the product of the current time in milliseconds and the @@ -44,45 +44,33 @@ public: /// current thread ID. void resetSeed(); - /// Generate several integer numbers within the same range. - /// e.g.: auto a = gen.getIntRange(0,10); a(); a(); a(); - /// requires: lower <= upper - TRandI getIntRange(int lower, int upper); - - vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override; - /// Generates an integer between 0 and upper. /// requires: 0 <= upper - int nextInt(int upper); + int nextInt(int upper) override; + int64_t nextInt64(int64_t upper) override; /// requires: lower <= upper - int nextInt(int lower, int upper); + int nextInt(int lower, int upper) override; + int64_t nextInt64(int64_t lower, int64_t upper) override; /// Generates an integer between 0 and the maximum value it can hold. - int nextInt(); + int nextInt() override; + + /// + int nextBinomialInt(int coinsCount, double coinChance) override; - /// Generate several double/real numbers within the same range. - /// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a(); - /// requires: lower <= upper - vstd::TRand getDoubleRange(double lower, double upper) override; /// Generates a double between 0 and upper. /// requires: 0 <= upper - double nextDouble(double upper); + double nextDouble(double upper) override; /// requires: lower <= upper - double nextDouble(double lower, double upper); - - /// Generates a double between 0.0 and 1.0. - double nextDouble(); + double nextDouble(double lower, double upper) override; /// Gets a globally accessible RNG which will be constructed once per thread. For the /// seed a combination of the thread ID and current time in milliseconds will be used. static CRandomGenerator & getDefault(); - /// Provide method so that this RNG can be used with legacy std:: API - TGenerator & getStdGenerator(); - private: TGenerator rand; diff --git a/lib/CStack.cpp b/lib/CStack.cpp index a84f8cd0f..21747bfd2 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -212,11 +212,9 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const auto resurrectedAdd = static_cast(baseAmount - (resurrectedCount / resurrectFactor)); - auto rangeGen = rand.getInt64Range(0, 99); - for(int32_t i = 0; i < resurrectedAdd; i++) { - if(resurrectValue > rangeGen()) + if(resurrectValue > rand.nextInt(0, 99)) resurrectedCount += 1; } diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index e1469c526..2c118d1f2 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -902,8 +902,17 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source) const readIcon(source["icons"]["fort"]["normal"], info.iconSmall[1][0], info.iconLarge[1][0]); readIcon(source["icons"]["fort"]["built"], info.iconSmall[1][1], info.iconLarge[1][1]); + if (source["musicTheme"].isVector()) + { + for (auto const & entry : source["musicTheme"].Vector()) + info.musicTheme.push_back(AudioPath::fromJson(entry)); + } + else + { + info.musicTheme.push_back(AudioPath::fromJson(source["musicTheme"])); + } + info.hallBackground = ImagePath::fromJson(source["hallBackground"]); - info.musicTheme = AudioPath::fromJson(source["musicTheme"]); info.townBackground = ImagePath::fromJson(source["townBackground"]); info.guildWindow = ImagePath::fromJson(source["guildWindow"]); info.buildingsIcons = AnimationPath::fromJson(source["buildingsIcons"]); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 758eac6fe..12c8200b7 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -253,7 +253,7 @@ public: std::string iconSmall[2][2]; /// icon names used during loading std::string iconLarge[2][2]; VideoPath tavernVideo; - AudioPath musicTheme; + std::vector musicTheme; ImagePath townBackground; ImagePath guildBackground; ImagePath guildWindow; diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index acef3ca0f..ce3a3fa98 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -51,6 +51,8 @@ #include "RiverHandler.h" #include "TerrainHandler.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void CPrivilegedInfoCallback::getFreeTiles(std::vector & tiles) const @@ -146,7 +148,7 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set & tiles, std: } } -void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector & out, CRandomGenerator & rand) +void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector & out, vstd::RNG & rand) { for (int j = 0; j < 3 ; j++) out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_TREASURE).toArtifact()); diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index b3d121294..e1a32b505 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -16,13 +16,18 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + struct SetMovePoints; struct GiveBonus; struct BlockingDialog; struct TeleportDialog; struct StackLocation; struct ArtifactLocation; -class CRandomGenerator; +struct BankConfig; class CCreatureSet; class CStackBasicDescriptor; class CGCreature; @@ -33,6 +38,11 @@ namespace spells class Caster; } +namespace Rewardable +{ + struct Configuration; +} + #if SCRIPTING_ENABLED namespace scripting { @@ -61,7 +71,7 @@ public: void getAllTiles(std::unordered_set &tiles, std::optional player, int level, std::function filter) const; //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant - void pickAllowedArtsSet(std::vector & out, CRandomGenerator & rand); + void pickAllowedArtsSet(std::vector & out, vstd::RNG & rand); void getAllowedSpells(std::vector &out, std::optional level = std::nullopt); template @@ -75,13 +85,16 @@ class DLL_LINKAGE IGameEventCallback { public: virtual void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value = 0) = 0; + virtual void setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) = 0; + virtual void setRewardableObjectConfiguration(ObjectInstanceID mapObjectID, const Rewardable::Configuration & configuration) = 0; + virtual void setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) = 0; virtual void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) = 0; virtual void showInfoDialog(InfoWindow * iw) = 0; virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells)=0; virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; - virtual void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) = 0; + virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; virtual void giveExperience(const CGHeroInstance * hero, TExpType val) =0; virtual void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false)=0; @@ -131,6 +144,9 @@ public: virtual void changeFogOfWar(std::unordered_set &tiles, PlayerColor player, ETileVisibility mode) = 0; virtual void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) = 0; + + virtual vstd::RNG & getRandomGenerator() = 0; + }; class DLL_LINKAGE CNonConstInfoCallback : public CPrivilegedInfoCallback diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 0bcd64972..bfef5b344 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -123,9 +123,6 @@ struct DLL_LINKAGE StartInfo : public Serializeable using TPlayerInfos = std::map; TPlayerInfos playerInfos; //color indexed - ui32 seedToBeUsed; //0 if not sure (client requests server to decide, will be send in reply pack) - ui32 seedPostInit; //so we know that game is correctly synced at the start; 0 if not known yet - ui32 mapfileChecksum; //0 if not relevant std::string startTimeIso8601; std::string fileURI; SimturnsInfo simturnsInfo; @@ -153,9 +150,13 @@ struct DLL_LINKAGE StartInfo : public Serializeable h & mode; h & difficulty; h & playerInfos; - h & seedToBeUsed; - h & seedPostInit; - h & mapfileChecksum; + if (h.version < Handler::Version::REMOVE_LIB_RNG) + { + uint32_t oldSeeds = 0; + h & oldSeeds; + h & oldSeeds; + h & oldSeeds; + } h & startTimeIso8601; h & fileURI; h & simturnsInfo; @@ -169,8 +170,10 @@ struct DLL_LINKAGE StartInfo : public Serializeable h & campState; } - StartInfo() : mode(EStartMode::INVALID), difficulty(1), seedToBeUsed(0), seedPostInit(0), - mapfileChecksum(0), startTimeIso8601(vstd::getDateTimeISO8601Basic(std::time(nullptr))), fileURI("") + StartInfo() + : mode(EStartMode::INVALID) + , difficulty(1) + , startTimeIso8601(vstd::getDateTimeISO8601Basic(std::time(nullptr))) { } diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index d66bf521d..13789cacf 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -28,7 +28,16 @@ std::shared_ptr TerrainTypeHandler::loadFromJson( const std::string info->identifier = identifier; info->modScope = scope; info->moveCost = static_cast(json["moveCost"].Integer()); - info->musicFilename = AudioPath::fromJson(json["music"]); + if (json["music"].isVector()) + { + for (auto const & entry : json["music"].Vector()) + info->musicFilename.push_back(AudioPath::fromJson(entry)); + } + else + { + info->musicFilename.push_back(AudioPath::fromJson(json["music"])); + } + info->tilesFilename = AnimationPath::fromJson(json["tiles"]); info->horseSound = AudioPath::fromJson(json["horseSound"]); info->horseSoundPenalty = AudioPath::fromJson(json["horseSoundPenalty"]); diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index ae2bed46d..9d2e71d11 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -67,7 +67,7 @@ public: ColorRGBA minimapBlocked; ColorRGBA minimapUnblocked; std::string shortIdentifier; - AudioPath musicFilename; + std::vector musicFilename; AnimationPath tilesFilename; std::string terrainViewPatterns; AudioPath horseSound; diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 946499a4a..3ecb0bb83 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -12,7 +12,6 @@ #include "CObstacleInstance.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" -#include "../CRandomGenerator.h" #include "../CStack.h" #include "../CHeroHandler.h" #include "../filesystem/Filesystem.h" @@ -21,6 +20,7 @@ #include "../BattleFieldHandler.h" #include "../ObstacleHandler.h" +#include //TODO: remove #include "../IGameCallback.h" @@ -662,10 +662,9 @@ int64_t BattleInfo::getActualDamage(const DamageRange & damage, int32_t attacker int64_t sum = 0; auto howManyToAv = std::min(10, attackerCount); - auto rangeGen = rng.getInt64Range(damage.min, damage.max); for(int32_t g = 0; g < howManyToAv; ++g) - sum += rangeGen(); + sum += rng.nextInt64(damage.min, damage.max); return sum / howManyToAv; } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index dd090d7d2..be1e6cc1d 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -11,6 +11,7 @@ #include "CBattleInfoCallback.h" #include +#include #include "../CStack.h" #include "BattleInfo.h" @@ -25,7 +26,6 @@ #include "../networkPacks/PacksForClientBattle.h" #include "../BattleFieldHandler.h" #include "../Rect.h" -#include "../CRandomGenerator.h" VCMI_LIB_NAMESPACE_BEGIN @@ -1602,7 +1602,7 @@ std::set CBattleInfoCallback::battleAdjacentUnits(const ba return ret; } -SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, const battle::Unit * caster, const battle::Unit * subject) const +SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const battle::Unit * caster, const battle::Unit * subject) const { RETURN_IF_NOT_BATTLE(SpellID::NONE); //This is complete list. No spells from mods. @@ -1748,7 +1748,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c } } -SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const CStack * caster) const +SpellID CBattleInfoCallback::getRandomCastedSpell(vstd::RNG & rand,const CStack * caster) const { RETURN_IF_NOT_BATTLE(SpellID::NONE); diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index f9fe241a7..1ee127522 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -23,9 +23,13 @@ class SpellCastEnvironment; class CSpell; struct CObstacleInstance; class IBonusBearer; -class CRandomGenerator; class PossiblePlayerBattleAction; +namespace vstd +{ +class RNG; +} + namespace spells { class Caster; @@ -116,8 +120,8 @@ public: int32_t battleGetSpellCost(const spells::Spell * sp, const CGHeroInstance * caster) const; //returns cost of given spell ESpellCastProblem battleCanCastSpell(const spells::Caster * caster, spells::Mode mode) const; //returns true if there are no general issues preventing from casting a spell - SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const battle::Unit * caster, const battle::Unit * target) const; - SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon + SpellID getRandomBeneficialSpell(vstd::RNG & rand, const battle::Unit * caster, const battle::Unit * target) const; + SpellID getRandomCastedSpell(vstd::RNG & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon std::vector getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data); PossiblePlayerBattleAction getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const; diff --git a/lib/battle/PossiblePlayerBattleAction.h b/lib/battle/PossiblePlayerBattleAction.h index 401c18d00..402980ded 100644 --- a/lib/battle/PossiblePlayerBattleAction.h +++ b/lib/battle/PossiblePlayerBattleAction.h @@ -74,6 +74,11 @@ public: { return action == other.action && spellToCast == other.spellToCast; } + + bool operator != (const PossiblePlayerBattleAction & other) const + { + return action != other.action || spellToCast != other.spellToCast; + } }; VCMI_LIB_NAMESPACE_END diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index f4099a827..5de4f9c7b 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -46,7 +46,7 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector & inpu } else // text format (json) { - JsonNode jsonCampaign(reinterpret_cast(input.data()), input.size()); + JsonNode jsonCampaign(reinterpret_cast(input.data()), input.size(), filename); readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding); for(auto & scenario : jsonCampaign["scenarios"].Vector()) diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index 7b4821c5c..b873e0718 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -117,7 +117,7 @@ void CFilesystemGenerator::loadJsonMap(const std::string &mountPoint, const Json if (filename) { auto configData = CResourceHandler::get("initial")->load(JsonPath::builtin(URI))->readAll(); - const JsonNode configInitial(reinterpret_cast(configData.first.get()), configData.second); + const JsonNode configInitial(reinterpret_cast(configData.first.get()), configData.second, URI); filesystem->addLoader(new CMappedFileLoader(mountPoint, configInitial), false); } } @@ -212,7 +212,7 @@ void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives { auto fsConfigData = get("initial")->load(JsonPath::builtin(fsConfigURI))->readAll(); - const JsonNode fsConfig(reinterpret_cast(fsConfigData.first.get()), fsConfigData.second); + const JsonNode fsConfig(reinterpret_cast(fsConfigData.first.get()), fsConfigData.second, fsConfigURI); addFilesystem("data", ModScope::scopeBuiltin(), createFileSystem("", fsConfig["filesystem"], extractArchives)); } diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index a6c7f4591..cc48de519 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -53,6 +53,8 @@ #include "../serializer/CTypeList.h" #include "../spells/CSpellHandler.h" +#include + VCMI_LIB_NAMESPACE_BEGIN boost::shared_mutex CGameState::mutex; @@ -182,8 +184,6 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog { assert(services); assert(callback); - logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed); - getRandomGenerator().setSeed(si->seedToBeUsed); scenarioOps = CMemorySerializer::deepCopy(*si).release(); initialOpts = CMemorySerializer::deepCopy(*si).release(); si = nullptr; @@ -202,8 +202,6 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog } logGlobal->info("Map loaded!"); - checkMapChecksum(); - day = 0; logGlobal->debug("Initialization:"); @@ -235,18 +233,6 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog logGlobal->debug("\tChecking objectives"); map->checkForObjectives(); //needs to be run when all objects are properly placed - - auto seedAfterInit = getRandomGenerator().nextInt(); - logGlobal->info("Seed after init is %d (before was %d)", seedAfterInit, scenarioOps->seedToBeUsed); - if(scenarioOps->seedPostInit > 0) - { - //RNG must be in the same state on all machines when initialization is done (otherwise we have desync) - assert(scenarioOps->seedPostInit == seedAfterInit); - } - else - { - scenarioOps->seedPostInit = seedAfterInit; //store the post init "seed" - } } void CGameState::updateEntity(Metatype metatype, int32_t index, const JsonNode & data) @@ -306,7 +292,7 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan CStopWatch sw; // Gen map - CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, callback, scenarioOps->seedToBeUsed); + CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, callback, getRandomGenerator().nextInt()); progressTracking.include(mapGenerator); std::unique_ptr randomMap = mapGenerator.generate(); @@ -322,10 +308,9 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan std::shared_ptr options = scenarioOps->mapGenOptions; const std::string templateName = options->getMapTemplate()->getName(); - const ui32 seed = scenarioOps->seedToBeUsed; const std::string dt = vstd::getDateTimeISO8601Basic(std::time(nullptr)); - const std::string fileName = boost::str(boost::format("%s_%s_%d.vmap") % dt % templateName % seed ); + const std::string fileName = boost::str(boost::format("%s_%s.vmap") % dt % templateName ); const auto fullPath = path / fileName; randomMap->name.appendRawString(boost::str(boost::format(" %s") % dt)); @@ -379,24 +364,6 @@ void CGameState::initCampaign() map = campaign->getCurrentMap().release(); } -void CGameState::checkMapChecksum() -{ - logGlobal->info("\tOur checksum for the map: %d", map->checksum); - if(scenarioOps->mapfileChecksum) - { - logGlobal->info("\tServer checksum for %s: %d", scenarioOps->mapname, scenarioOps->mapfileChecksum); - if(map->checksum != scenarioOps->mapfileChecksum) - { - logGlobal->error("Wrong map checksum!!!"); - throw std::runtime_error("Wrong checksum"); - } - } - else - { - scenarioOps->mapfileChecksum = map->checksum; - } -} - void CGameState::initGlobalBonuses() { const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_GLOBAL); @@ -1072,7 +1039,7 @@ BattleInfo * CGameState::getBattle(const BattleID & battle) return nullptr; } -BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand) +BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand) { assert(tile.valid()); @@ -1245,8 +1212,10 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const return gs->map->guardingCreaturePositions[pos.z][pos.x][pos.y]; } -void CGameState::updateRumor() +RumorState CGameState::pickNewRumor() { + RumorState newRumor; + static const std::vector rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND}; std::vector sRumorTypes = { RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME}; @@ -1256,11 +1225,11 @@ void CGameState::updateRumor() int rumorId = -1; int rumorExtra = -1; auto & rand = getRandomGenerator(); - rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); + newRumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); do { - switch(rumor.type) + switch(newRumor.type) { case RumorState::TYPE_SPECIAL: { @@ -1298,13 +1267,13 @@ void CGameState::updateRumor() } case RumorState::TYPE_MAP: // Makes sure that map rumors only used if there enough rumors too choose from - if(!map->rumors.empty() && (map->rumors.size() > 1 || !rumor.last.count(RumorState::TYPE_MAP))) + if(!map->rumors.empty() && (map->rumors.size() > 1 || !currentRumor.last.count(RumorState::TYPE_MAP))) { rumorId = rand.nextInt((int)map->rumors.size() - 1); break; } else - rumor.type = RumorState::TYPE_RAND; + newRumor.type = RumorState::TYPE_RAND; [[fallthrough]]; case RumorState::TYPE_RAND: @@ -1314,7 +1283,9 @@ void CGameState::updateRumor() break; } } - while(!rumor.update(rumorId, rumorExtra)); + while(!newRumor.update(rumorId, rumorExtra)); + + return newRumor; } bool CGameState::isVisible(int3 pos, const std::optional & player) const @@ -1938,35 +1909,19 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const return nullptr; } -bool RumorState::update(int id, int extra) -{ - if(vstd::contains(last, type)) - { - if(last[type].first != id) - { - last[type].first = id; - last[type].second = extra; - } - else - return false; - } - else - last[type] = std::make_pair(id, extra); - return true; -} TeamState::TeamState() { setNodeType(TEAM); } -CRandomGenerator & CGameState::getRandomGenerator() +vstd::RNG & CGameState::getRandomGenerator() { - return rand; + return callback->getRandomGenerator(); } -ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts) +ArtifactID CGameState::pickRandomArtifact(vstd::RNG & rand, int flags, std::function accepts) { std::set potentialPicks; @@ -2001,7 +1956,7 @@ ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, int flags, st return pickRandomArtifact(rand, potentialPicks); } -ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, std::set potentialPicks) +ArtifactID CGameState::pickRandomArtifact(vstd::RNG & rand, std::set potentialPicks) { // No allowed artifacts at all - give Grail - this can't be banned (hopefully) // FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior @@ -2030,12 +1985,12 @@ ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, std::set accepts) +ArtifactID CGameState::pickRandomArtifact(vstd::RNG & rand, std::function accepts) { return pickRandomArtifact(rand, 0xff, std::move(accepts)); } -ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, int flags) +ArtifactID CGameState::pickRandomArtifact(vstd::RNG & rand, int flags) { return pickRandomArtifact(rand, flags, [](const ArtifactID &) { return true; }); } diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index 9cd3fc312..f7814fde9 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -9,11 +9,12 @@ */ #pragma once -#include "bonuses/CBonusSystemNode.h" -#include "IGameCallback.h" -#include "LoadProgress.h" -#include "ConstTransitivePtr.h" -#include "../CRandomGenerator.h" +#include "../bonuses/CBonusSystemNode.h" +#include "../IGameCallback.h" +#include "../LoadProgress.h" +#include "../ConstTransitivePtr.h" + +#include "RumorState.h" namespace boost { @@ -34,39 +35,11 @@ class CStackInstance; class CGameStateCampaign; class TavernHeroesPool; struct SThievesGuildInfo; +class CRandomGenerator; template class CApplier; class CBaseForGSApply; -struct DLL_LINKAGE RumorState -{ - enum ERumorType : ui8 - { - TYPE_NONE = 0, TYPE_RAND, TYPE_SPECIAL, TYPE_MAP - }; - - enum ERumorTypeSpecial : ui8 - { - RUMOR_OBELISKS = 208, - RUMOR_ARTIFACTS = 209, - RUMOR_ARMY = 210, - RUMOR_INCOME = 211, - RUMOR_GRAIL = 212 - }; - - ERumorType type; - std::map> last; - - RumorState(){type = TYPE_NONE;}; - bool update(int id, int extra); - - template void serialize(Handler &h) - { - h & type; - h & last; - } -}; - struct UpgradeInfo { CreatureID oldID; //creature to be upgraded @@ -115,7 +88,7 @@ public: std::map players; std::map teams; CBonusSystemNode globalEffects; - RumorState rumor; + RumorState currentRumor; static boost::shared_mutex mutex; @@ -126,7 +99,7 @@ public: HeroTypeID pickNextHeroType(const PlayerColor & owner); void apply(CPack *pack); - BattleField battleGetBattlefieldType(int3 tile, CRandomGenerator & rand); + BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand); void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override; PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2) const override; @@ -135,13 +108,13 @@ public: void calculatePaths(const std::shared_ptr & config) override; int3 guardingCreaturePosition (int3 pos) const override; std::vector guardingCreatures (int3 pos) const; - void updateRumor(); + RumorState pickNewRumor(); /// Gets a artifact ID randomly and removes the selected artifact from this handler. - ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags); - ArtifactID pickRandomArtifact(CRandomGenerator & rand, std::function accepts); - ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts); - ArtifactID pickRandomArtifact(CRandomGenerator & rand, std::set filtered); + ArtifactID pickRandomArtifact(vstd::RNG & rand, int flags); + ArtifactID pickRandomArtifact(vstd::RNG & rand, std::function accepts); + ArtifactID pickRandomArtifact(vstd::RNG & rand, int flags, std::function accepts); + ArtifactID pickRandomArtifact(vstd::RNG & rand, std::set filtered); /// Returns battle in which selected player is engaged, or nullptr if none. /// Can NOT be used with neutral player, use battle by ID instead @@ -169,11 +142,11 @@ public: /// This RNG should only be used inside GS or CPackForClient-derived applyGs /// If this doesn't work for your code that mean you need a new netpack /// - /// Client-side must use CRandomGenerator::getDefault which is not serialized + /// Client-side must use vstd::RNG::getDefault which is not serialized /// - /// CGameHandler have it's own getter for CRandomGenerator::getDefault - /// Any server-side code outside of GH must use CRandomGenerator::getDefault - CRandomGenerator & getRandomGenerator(); + /// CGameHandler have it's own getter for vstd::RNG::getDefault + /// Any server-side code outside of GH must use vstd::RNG::getDefault + vstd::RNG & getRandomGenerator(); template void serialize(Handler &h) { @@ -186,8 +159,12 @@ public: h & teams; h & heroesPool; h & globalEffects; - h & rand; - h & rumor; + if (h.version < Handler::Version::REMOVE_LIB_RNG) + { + std::string oldStateOfRNG; + h & oldStateOfRNG; + } + h & currentRumor; h & campaign; h & allocatedArtifacts; @@ -197,7 +174,6 @@ public: private: // ----- initialization ----- void initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking); - void checkMapChecksum(); void initGlobalBonuses(); void initGrailPosition(); void initRandomFactionsForPlayers(); @@ -234,7 +210,6 @@ private: // ---- data ----- std::shared_ptr> applier; - CRandomGenerator rand; Services * services; /// Pointer to campaign state manager. Nullptr for single scenarios diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index c496db471..69f8981e3 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -28,6 +28,8 @@ #include "../CPlayerState.h" #include "../serializer/CMemorySerializer.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId): @@ -120,6 +122,11 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr if(!info) return false; + // FIXME: double-check how H3 handles case of transferring components of a combined artifact if entire combined artifact is not transferrable + // For example, what happens if hero has assembled Angelic Alliance, AA is not marked is transferrable, but Sandals can be transferred? Should artifact be disassembled? + if (info->locked) + return false; + // TODO: why would there be nullptr artifacts? const CArtifactInstance *art = info->artifact; if(!art) diff --git a/lib/gameState/RumorState.cpp b/lib/gameState/RumorState.cpp new file mode 100644 index 000000000..b4c1caeb2 --- /dev/null +++ b/lib/gameState/RumorState.cpp @@ -0,0 +1,33 @@ +/* + * RumorState.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "RumorState.h" + +VCMI_LIB_NAMESPACE_BEGIN + +bool RumorState::update(int id, int extra) +{ + if(vstd::contains(last, type)) + { + if(last[type].first != id) + { + last[type].first = id; + last[type].second = extra; + } + else + return false; + } + else + last[type] = std::make_pair(id, extra); + + return true; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/RumorState.h b/lib/gameState/RumorState.h new file mode 100644 index 000000000..571bd49c2 --- /dev/null +++ b/lib/gameState/RumorState.h @@ -0,0 +1,43 @@ +/* + * RumorState.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +struct DLL_LINKAGE RumorState +{ + enum ERumorType : ui8 + { + TYPE_NONE = 0, TYPE_RAND, TYPE_SPECIAL, TYPE_MAP + }; + + enum ERumorTypeSpecial : ui8 + { + RUMOR_OBELISKS = 208, + RUMOR_ARTIFACTS = 209, + RUMOR_ARMY = 210, + RUMOR_INCOME = 211, + RUMOR_GRAIL = 212 + }; + + ERumorType type; + std::map> last; + + RumorState(){type = TYPE_NONE;}; + bool update(int id, int extra); + + template void serialize(Handler &h) + { + h & type; + h & last; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/TavernHeroesPool.h b/lib/gameState/TavernHeroesPool.h index ec92dfe52..250cfb0a8 100644 --- a/lib/gameState/TavernHeroesPool.h +++ b/lib/gameState/TavernHeroesPool.h @@ -18,7 +18,6 @@ VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; class CTown; -class CRandomGenerator; class CHeroClass; class CGameState; class CSimpleArmy; diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 32712c85a..4d0e7ddd5 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -86,15 +86,15 @@ JsonNode::JsonNode(const std::string & string) { } -JsonNode::JsonNode(const std::byte * data, size_t datasize) - : JsonNode(data, datasize, JsonParsingSettings()) +JsonNode::JsonNode(const std::byte * data, size_t datasize, const std::string & fileName) + : JsonNode(data, datasize, JsonParsingSettings(), fileName) { } -JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings) +JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings, const std::string & fileName) { JsonParser parser(data, datasize, parserSettings); - *this = parser.parse(""); + *this = parser.parse(fileName); } JsonNode::JsonNode(const JsonPath & fileURI) diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index c4afbcae1..7ed8ae9ae 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -72,8 +72,8 @@ public: explicit JsonNode(const std::string & string); /// Create tree from Json-formatted input - explicit JsonNode(const std::byte * data, size_t datasize); - explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings); + explicit JsonNode(const std::byte * data, size_t datasize, const std::string & fileName); + explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings, const std::string & fileName); /// Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index 320005b25..f3ad1ab9b 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -55,7 +55,7 @@ JsonNode JsonParser::parse(const std::string & fileName) if(!errors.empty()) { - logMod->warn("File %s is not a valid JSON file!", fileName); + logMod->warn("%s is not valid JSON!", fileName); logMod->warn(errors); } return root; diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index df71283b2..57f7a97a2 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -12,10 +12,10 @@ #include "JsonRandom.h" #include +#include #include "JsonBonus.h" -#include "../CRandomGenerator.h" #include "../constants/StringConstants.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -50,7 +50,7 @@ VCMI_LIB_NAMESPACE_BEGIN return variables.at(variableID); } - si32 JsonRandom::loadValue(const JsonNode & value, CRandomGenerator & rng, const Variables & variables, si32 defaultValue) + si32 JsonRandom::loadValue(const JsonNode & value, vstd::RNG & rng, const Variables & variables, si32 defaultValue) { if(value.isNull()) return defaultValue; @@ -63,7 +63,7 @@ VCMI_LIB_NAMESPACE_BEGIN { const auto & vector = value.Vector(); - size_t index= rng.getIntRange(0, vector.size()-1)(); + size_t index= rng.nextInt64(0, vector.size()-1); return loadValue(vector[index], rng, variables, 0); } if(value.isStruct()) @@ -72,7 +72,7 @@ VCMI_LIB_NAMESPACE_BEGIN return loadValue(value["amount"], rng, variables, defaultValue); si32 min = loadValue(value["min"], rng, variables, 0); si32 max = loadValue(value["max"], rng, variables, 0); - return rng.getIntRange(min, max)(); + return rng.nextInt64(min, max); } return defaultValue; } @@ -256,7 +256,7 @@ VCMI_LIB_NAMESPACE_BEGIN return valuesSet; } - TResources JsonRandom::loadResources(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + TResources JsonRandom::loadResources(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { TResources ret; @@ -274,7 +274,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - TResources JsonRandom::loadResource(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + TResources JsonRandom::loadResource(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::set defaultResources{ GameResID::WOOD, @@ -295,7 +295,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - PrimarySkill JsonRandom::loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + PrimarySkill JsonRandom::loadPrimary(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::set defaultSkills{ PrimarySkill::ATTACK, @@ -307,7 +307,7 @@ VCMI_LIB_NAMESPACE_BEGIN return *RandomGeneratorUtil::nextItem(potentialPicks, rng); } - std::vector JsonRandom::loadPrimaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + std::vector JsonRandom::loadPrimaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::vector ret(GameConstants::PRIMARY_SKILLS, 0); std::set defaultSkills{ @@ -339,7 +339,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - SecondarySkill JsonRandom::loadSecondary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + SecondarySkill JsonRandom::loadSecondary(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::set defaultSkills; for(const auto & skill : VLC->skillh->objects) @@ -350,7 +350,7 @@ VCMI_LIB_NAMESPACE_BEGIN return *RandomGeneratorUtil::nextItem(potentialPicks, rng); } - std::map JsonRandom::loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + std::map JsonRandom::loadSecondaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::map ret; if(value.isStruct()) @@ -380,7 +380,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - ArtifactID JsonRandom::loadArtifact(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + ArtifactID JsonRandom::loadArtifact(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::set allowedArts; for(const auto & artifact : VLC->arth->objects) @@ -392,7 +392,7 @@ VCMI_LIB_NAMESPACE_BEGIN return cb->gameState()->pickRandomArtifact(rng, potentialPicks); } - std::vector JsonRandom::loadArtifacts(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + std::vector JsonRandom::loadArtifacts(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::vector ret; for (const JsonNode & entry : value.Vector()) @@ -402,7 +402,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - SpellID JsonRandom::loadSpell(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + SpellID JsonRandom::loadSpell(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::set defaultSpells; for(const auto & spell : VLC->spellh->objects) @@ -419,7 +419,7 @@ VCMI_LIB_NAMESPACE_BEGIN return *RandomGeneratorUtil::nextItem(potentialPicks, rng); } - std::vector JsonRandom::loadSpells(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + std::vector JsonRandom::loadSpells(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::vector ret; for (const JsonNode & entry : value.Vector()) @@ -429,7 +429,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - std::vector JsonRandom::loadColors(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + std::vector JsonRandom::loadColors(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::vector ret; std::set defaultPlayers; @@ -445,7 +445,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - std::vector JsonRandom::loadHeroes(const JsonNode & value, CRandomGenerator & rng) + std::vector JsonRandom::loadHeroes(const JsonNode & value, vstd::RNG & rng) { std::vector ret; for(auto & entry : value.Vector()) @@ -455,7 +455,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - std::vector JsonRandom::loadHeroClasses(const JsonNode & value, CRandomGenerator & rng) + std::vector JsonRandom::loadHeroClasses(const JsonNode & value, vstd::RNG & rng) { std::vector ret; for(auto & entry : value.Vector()) @@ -465,7 +465,7 @@ VCMI_LIB_NAMESPACE_BEGIN return ret; } - CStackBasicDescriptor JsonRandom::loadCreature(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + CStackBasicDescriptor JsonRandom::loadCreature(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { CStackBasicDescriptor stack; @@ -494,7 +494,7 @@ VCMI_LIB_NAMESPACE_BEGIN return stack; } - std::vector JsonRandom::loadCreatures(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) + std::vector JsonRandom::loadCreatures(const JsonNode & value, vstd::RNG & rng, const Variables & variables) { std::vector ret; for (const JsonNode & node : value.Vector()) diff --git a/lib/json/JsonRandom.h b/lib/json/JsonRandom.h index 8129c11bb..5098dc306 100644 --- a/lib/json/JsonRandom.h +++ b/lib/json/JsonRandom.h @@ -15,9 +15,13 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + class JsonNode; using JsonVector = std::vector; -class CRandomGenerator; struct Bonus; struct Component; @@ -53,28 +57,28 @@ public: si32 maxAmount; }; - si32 loadValue(const JsonNode & value, CRandomGenerator & rng, const Variables & variables, si32 defaultValue = 0); + si32 loadValue(const JsonNode & value, vstd::RNG & rng, const Variables & variables, si32 defaultValue = 0); - TResources loadResources(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - TResources loadResource(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - PrimarySkill loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - std::vector loadPrimaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - SecondarySkill loadSecondary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - std::map loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); + TResources loadResources(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + TResources loadResource(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + PrimarySkill loadPrimary(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + std::vector loadPrimaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + SecondarySkill loadSecondary(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + std::map loadSecondaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables); - ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); + ArtifactID loadArtifact(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + std::vector loadArtifacts(const JsonNode & value, vstd::RNG & rng, const Variables & variables); - SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); + SpellID loadSpell(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + std::vector loadSpells(const JsonNode & value, vstd::RNG & rng, const Variables & variables); - CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); + CStackBasicDescriptor loadCreature(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + std::vector loadCreatures(const JsonNode & value, vstd::RNG & rng, const Variables & variables); std::vector evaluateCreatures(const JsonNode & value, const Variables & variables); - std::vector loadColors(const JsonNode & value, CRandomGenerator & rng, const Variables & variables); - std::vector loadHeroes(const JsonNode & value, CRandomGenerator & rng); - std::vector loadHeroClasses(const JsonNode & value, CRandomGenerator & rng); + std::vector loadColors(const JsonNode & value, vstd::RNG & rng, const Variables & variables); + std::vector loadHeroes(const JsonNode & value, vstd::RNG & rng); + std::vector loadHeroClasses(const JsonNode & value, vstd::RNG & rng); static std::vector loadBonuses(const JsonNode & value); }; diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index f2ea7a78e..f1680e11f 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -269,7 +269,7 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename) for(auto & loader : CResourceHandler::get()->getResourcesWithName(resID)) { auto textData = loader->load(resID)->readAll(); - JsonNode section(reinterpret_cast(textData.first.get()), textData.second); + JsonNode section(reinterpret_cast(textData.first.get()), textData.second, resID.getName()); merge(result, section); } return result; diff --git a/lib/logging/CLogger.cpp b/lib/logging/CLogger.cpp index 6d7c43ed3..e33c6425e 100644 --- a/lib/logging/CLogger.cpp +++ b/lib/logging/CLogger.cpp @@ -96,6 +96,7 @@ DLL_LINKAGE vstd::CLoggerBase * logNetwork = CLogger::getLogger(CLoggerDomain("n DLL_LINKAGE vstd::CLoggerBase * logAi = CLogger::getLogger(CLoggerDomain("ai")); DLL_LINKAGE vstd::CLoggerBase * logAnim = CLogger::getLogger(CLoggerDomain("animation")); DLL_LINKAGE vstd::CLoggerBase * logMod = CLogger::getLogger(CLoggerDomain("mod")); +DLL_LINKAGE vstd::CLoggerBase * logRng = CLogger::getLogger(CLoggerDomain("rng")); CLogger * CLogger::getLogger(const CLoggerDomain & domain) { diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.h b/lib/mapObjectConstructors/AObjectTypeHandler.h index ba298d13f..d555244b2 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.h +++ b/lib/mapObjectConstructors/AObjectTypeHandler.h @@ -15,9 +15,13 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + class ObjectTemplate; class CGObjectInstance; -class CRandomGenerator; class IObjectInfo; class IGameCallback; @@ -114,7 +118,7 @@ public: /// Configures object properties. Should be re-entrable, resetting state of the object if necessarily /// This should set remaining properties, including randomized or depending on map - virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; + virtual void configureObject(CGObjectInstance * object, vstd::RNG & rng) const = 0; /// Returns object configuration, if available. Otherwise returns NULL virtual std::unique_ptr getObjectInfo(std::shared_ptr tmpl) const; diff --git a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp index f1f6a5c84..94e186058 100644 --- a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp @@ -13,7 +13,8 @@ #include "../json/JsonRandom.h" #include "../CGeneralTextHandler.h" #include "../IGameCallback.h" -#include "../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -36,7 +37,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input) regularUnitPlacement = input["regularUnitPlacement"].Bool(); } -BankConfig CBankInstanceConstructor::generateConfig(IGameCallback * cb, const JsonNode & level, CRandomGenerator & rng) const +BankConfig CBankInstanceConstructor::generateLevelConfiguration(IGameCallback * cb, const JsonNode & level, vstd::RNG & rng) const { BankConfig bc; JsonRandom randomizer(cb); @@ -53,13 +54,17 @@ BankConfig CBankInstanceConstructor::generateConfig(IGameCallback * cb, const Js return bc; } -void CBankInstanceConstructor::randomizeObject(CBank * bank, CRandomGenerator & rng) const +void CBankInstanceConstructor::randomizeObject(CBank * bank, vstd::RNG & rng) const { bank->resetDuration = bankResetDuration; bank->blockVisit = blockVisit; bank->coastVisitable = coastVisitable; bank->regularUnitPlacement = regularUnitPlacement; + bank->setConfig(generateConfiguration(bank->cb, rng, bank->ID)); +} +BankConfig CBankInstanceConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rng, MapObjectID objectID) const +{ si32 totalChance = 0; for(const auto & node : levels) totalChance += static_cast(node["chance"].Float()); @@ -73,11 +78,10 @@ void CBankInstanceConstructor::randomizeObject(CBank * bank, CRandomGenerator & { cumulativeChance += static_cast(node["chance"].Float()); if(selectedChance < cumulativeChance) - { - bank->setConfig(generateConfig(bank->cb, node, rng)); - break; - } + return generateLevelConfiguration(cb, node, rng); } + + throw std::runtime_error("Failed to select bank configuration"); } CBankInfo::CBankInfo(const JsonVector & Config) : diff --git a/lib/mapObjectConstructors/CBankInstanceConstructor.h b/lib/mapObjectConstructors/CBankInstanceConstructor.h index 246da89b6..8a30d4bcd 100644 --- a/lib/mapObjectConstructors/CBankInstanceConstructor.h +++ b/lib/mapObjectConstructors/CBankInstanceConstructor.h @@ -69,7 +69,7 @@ public: class CBankInstanceConstructor : public CDefaultObjectTypeHandler { - BankConfig generateConfig(IGameCallback * cb, const JsonNode & conf, CRandomGenerator & rng) const; + BankConfig generateLevelConfiguration(IGameCallback * cb, const JsonNode & conf, vstd::RNG & rng) const; JsonVector levels; @@ -87,11 +87,13 @@ protected: public: - void randomizeObject(CBank * object, CRandomGenerator & rng) const override; + void randomizeObject(CBank * object, vstd::RNG & rng) const override; bool hasNameTextID() const override; std::unique_ptr getObjectInfo(std::shared_ptr tmpl) const override; + + BankConfig generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/CDefaultObjectTypeHandler.h b/lib/mapObjectConstructors/CDefaultObjectTypeHandler.h index c56e5eee5..e0d37469d 100644 --- a/lib/mapObjectConstructors/CDefaultObjectTypeHandler.h +++ b/lib/mapObjectConstructors/CDefaultObjectTypeHandler.h @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN template class CDefaultObjectTypeHandler : public AObjectTypeHandler { - void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const final + void configureObject(CGObjectInstance * object, vstd::RNG & rng) const final { ObjectType * castedObject = dynamic_cast(object); @@ -43,7 +43,7 @@ class CDefaultObjectTypeHandler : public AObjectTypeHandler protected: virtual void initializeObject(ObjectType * object) const {} - virtual void randomizeObject(ObjectType * object, CRandomGenerator & rng) const {} + virtual void randomizeObject(ObjectType * object, vstd::RNG & rng) const {} virtual ObjectType * createObject(IGameCallback * cb) const { return new ObjectType(cb); diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.h b/lib/mapObjectConstructors/CObjectClassesHandler.h index d65d2b38c..97521688c 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.h +++ b/lib/mapObjectConstructors/CObjectClassesHandler.h @@ -15,7 +15,6 @@ VCMI_LIB_NAMESPACE_BEGIN -class CRandomGenerator; class AObjectTypeHandler; class ObjectTemplate; struct SObjectSounds; diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index abe3552b5..28d98d50a 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -40,19 +40,29 @@ CGObjectInstance * CRewardableConstructor::create(IGameCallback * cb, std::share return ret; } -void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const +{ + Rewardable::Configuration result; + objectInfo.configureObject(result, rand, cb); + + for(auto & rewardInfo : result.info) + { + for (auto & bonus : rewardInfo.reward.bonuses) + { + bonus.source = BonusSource::OBJECT_TYPE; + bonus.sid = BonusSourceID(objectID); + } + } + + return result; +} + +void CRewardableConstructor::configureObject(CGObjectInstance * object, vstd::RNG & rng) const { if(auto * rewardableObject = dynamic_cast(object)) { - objectInfo.configureObject(rewardableObject->configuration, rng, object->cb); - for(auto & rewardInfo : rewardableObject->configuration.info) - { - for (auto & bonus : rewardInfo.reward.bonuses) - { - bonus.source = BonusSource::OBJECT_TYPE; - bonus.sid = BonusSourceID(rewardableObject->ID); - } - } + rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID); + if (rewardableObject->configuration.info.empty()) { if (objectInfo.getParameters()["rewards"].isNull()) diff --git a/lib/mapObjectConstructors/CRewardableConstructor.h b/lib/mapObjectConstructors/CRewardableConstructor.h index 81fd286e6..eab0e500a 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.h +++ b/lib/mapObjectConstructors/CRewardableConstructor.h @@ -27,9 +27,11 @@ public: CGObjectInstance * create(IGameCallback * cb, std::shared_ptr tmpl = nullptr) const override; - void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; + void configureObject(CGObjectInstance * object, vstd::RNG & rng) const override; std::unique_ptr getObjectInfo(std::shared_ptr tmpl) const override; + + Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 806b32194..94aade233 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -100,7 +100,7 @@ void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const obj->tempOwner = PlayerColor::NEUTRAL; } -void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, CRandomGenerator & rng) const +void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, vstd::RNG & rng) const { auto templ = getOverride(object->cb->getTile(object->pos)->terType->getId(), object); if(templ) @@ -159,7 +159,7 @@ void CHeroInstanceConstructor::initializeObject(CGHeroInstance * obj) const obj->type = nullptr; //FIXME: set to valid value. somehow. } -void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, CRandomGenerator & rng) const +void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const { } @@ -259,7 +259,7 @@ void MarketInstanceConstructor::initializeObject(CGMarket * market) const market->speech = VLC->generaltexth->translate(speech); } -void MarketInstanceConstructor::randomizeObject(CGMarket * object, CRandomGenerator & rng) const +void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & rng) const { JsonRandom randomizer(object->cb); JsonRandom::Variables emptyVariables; diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index ea5ef3d72..81cc4e3a0 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -63,7 +63,7 @@ public: std::map> filters; void initializeObject(CGTownInstance * object) const override; - void randomizeObject(CGTownInstance * object, CRandomGenerator & rng) const override; + void randomizeObject(CGTownInstance * object, vstd::RNG & rng) const override; void afterLoadFinalization() override; bool hasNameTextID() const override; @@ -82,7 +82,7 @@ public: std::map> filters; void initializeObject(CGHeroInstance * object) const override; - void randomizeObject(CGHeroInstance * object, CRandomGenerator & rng) const override; + void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; void afterLoadFinalization() override; bool hasNameTextID() const override; @@ -125,7 +125,7 @@ protected: public: CGMarket * createObject(IGameCallback * cb) const override; void initializeObject(CGMarket * object) const override; - void randomizeObject(CGMarket * object, CRandomGenerator & rng) const override; + void randomizeObject(CGMarket * object, vstd::RNG & rng) const override; }; diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index b6fe21715..12c2a0d93 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -74,7 +74,7 @@ void DwellingInstanceConstructor::initializeObject(CGDwelling * obj) const } } -void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, CRandomGenerator &rng) const +void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::RNG &rng) const { JsonRandom randomizer(dwelling->cb); diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.h b/lib/mapObjectConstructors/DwellingInstanceConstructor.h index ac82e2e87..99b61093c 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.h +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.h @@ -33,7 +33,7 @@ public: bool hasNameTextID() const override; void initializeObject(CGDwelling * object) const override; - void randomizeObject(CGDwelling * object, CRandomGenerator & rng) const override; + void randomizeObject(CGDwelling * object, vstd::RNG & rng) const override; bool isBannedForRandomDwelling() const; bool producesCreature(const CCreature * crea) const; diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 75ac1bcaa..cceca0a6f 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -44,7 +44,7 @@ CBank::CBank(IGameCallback *cb) //must be instantiated in .cpp file for access to complete types of all member fields CBank::~CBank() = default; -void CBank::initObj(CRandomGenerator & rand) +void CBank::initObj(vstd::RNG & rand) { daycounter = 0; resetDuration = 0; @@ -97,6 +97,8 @@ void CBank::setConfig(const BankConfig & config) for(const auto & stack : config.guards) setCreature (SlotID(stacksCount()), stack.type->getId(), stack.count); + + daycounter = 1; //yes, 1 since "today" daycounter won't be incremented } void CBank::setPropertyDer (ObjProperty what, ObjPropertyID identifier) @@ -106,25 +108,24 @@ void CBank::setPropertyDer (ObjProperty what, ObjPropertyID identifier) case ObjProperty::BANK_DAYCOUNTER: //daycounter daycounter+= identifier.getNum(); break; - case ObjProperty::BANK_RESET: - // FIXME: Object reset must be done by separate netpack from server - initObj(cb->gameState()->getRandomGenerator()); - daycounter = 1; //yes, 1 since "today" daycounter won't be incremented - break; case ObjProperty::BANK_CLEAR: bankConfig.reset(); break; } } -void CBank::newTurn(CRandomGenerator & rand) const +void CBank::newTurn(vstd::RNG & rand) const { if (bankConfig == nullptr) { if (resetDuration != 0) { if (daycounter >= resetDuration) - cb->setObjPropertyValue(id, ObjProperty::BANK_RESET); //daycounter 0 + { + auto handler = std::dynamic_pointer_cast(getObjectHandler()); + auto config = handler->generateConfiguration(cb, rand, ID); + cb->setBankObjectConfiguration(id, config); + } else cb->setObjPropertyValue(id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ } diff --git a/lib/mapObjects/CBank.h b/lib/mapObjects/CBank.h index 9c9dd5059..d2fe226f1 100644 --- a/lib/mapObjects/CBank.h +++ b/lib/mapObjects/CBank.h @@ -33,9 +33,9 @@ public: void setConfig(const BankConfig & bc); - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; std::string getHoverText(PlayerColor player) const override; - void newTurn(CRandomGenerator & rand) const override; + void newTurn(vstd::RNG & rand) const override; bool wasVisited (PlayerColor player) const override; bool isCoastVisitable() const override; void onHeroVisit(const CGHeroInstance * h) const override; diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index a2da0ef3c..12e6ef77c 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -16,12 +16,14 @@ #include "../CConfigHandler.h" #include "../GameSettings.h" #include "../IGameCallback.h" +#include "../gameState/CGameState.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../networkPacks/PacksForClient.h" #include "../networkPacks/PacksForClientBattle.h" #include "../networkPacks/StackLocation.h" #include "../serializer/JsonSerializeFormat.h" -#include "../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -189,7 +191,7 @@ CreatureID CGCreature::getCreature() const return CreatureID(getObjTypeIndex().getNum()); } -void CGCreature::pickRandomObject(CRandomGenerator & rand) +void CGCreature::pickRandomObject(vstd::RNG & rand) { switch(ID.toEnum()) { @@ -234,7 +236,7 @@ void CGCreature::pickRandomObject(CRandomGenerator & rand) setType(ID, subID); } -void CGCreature::initObj(CRandomGenerator & rand) +void CGCreature::initObj(vstd::RNG & rand) { blockVisit = true; switch(character) @@ -274,7 +276,7 @@ void CGCreature::initObj(CRandomGenerator & rand) refusedJoining = false; } -void CGCreature::newTurn(CRandomGenerator & rand) const +void CGCreature::newTurn(vstd::RNG & rand) const {//Works only for stacks of single type of size up to 2 millions if (!notGrowingTeam) { @@ -457,7 +459,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const const auto & upgrades = getStack(slotID).type->upgrades; if(!upgrades.empty()) { - auto it = RandomGeneratorUtil::nextItem(upgrades, CRandomGenerator::getDefault()); + auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); cb->changeStackType(StackLocation(this, slotID), it->toCreature()); } } diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index 90a75cd77..34374ff49 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -45,9 +45,9 @@ public: std::string getPopupText(PlayerColor player) const override; std::string getPopupText(const CGHeroInstance * hero) const override; std::vector getPopupComponents(PlayerColor player) const override; - void initObj(CRandomGenerator & rand) override; - void pickRandomObject(CRandomGenerator & rand) override; - void newTurn(CRandomGenerator & rand) const override; + void initObj(vstd::RNG & rand) override; + void pickRandomObject(vstd::RNG & rand) override; + void newTurn(vstd::RNG & rand) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; CreatureID getCreature() const; diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 4b67b2c76..aa0d85452 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -27,6 +27,8 @@ #include "../GameSettings.h" #include "../CConfigHandler.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void CGDwellingRandomizationInfo::serializeJson(JsonSerializeFormat & handler) @@ -50,7 +52,7 @@ CGDwelling::CGDwelling(IGameCallback *cb): CGDwelling::~CGDwelling() = default; -FactionID CGDwelling::randomizeFaction(CRandomGenerator & rand) +FactionID CGDwelling::randomizeFaction(vstd::RNG & rand) { if (ID == Obj::RANDOM_DWELLING_FACTION) return FactionID(subID.getNum()); @@ -108,7 +110,7 @@ FactionID CGDwelling::randomizeFaction(CRandomGenerator & rand) return *RandomGeneratorUtil::nextItem(potentialPicks, rand); } -int CGDwelling::randomizeLevel(CRandomGenerator & rand) +int CGDwelling::randomizeLevel(vstd::RNG & rand) { if (ID == Obj::RANDOM_DWELLING_LVL) return subID.getNum(); @@ -125,7 +127,7 @@ int CGDwelling::randomizeLevel(CRandomGenerator & rand) return rand.nextInt(randomizationInfo->minLevel, randomizationInfo->maxLevel) - 1; } -void CGDwelling::pickRandomObject(CRandomGenerator & rand) +void CGDwelling::pickRandomObject(vstd::RNG & rand) { if (ID == Obj::RANDOM_DWELLING || ID == Obj::RANDOM_DWELLING_LVL || ID == Obj::RANDOM_DWELLING_FACTION) { @@ -172,7 +174,7 @@ void CGDwelling::pickRandomObject(CRandomGenerator & rand) } } -void CGDwelling::initObj(CRandomGenerator & rand) +void CGDwelling::initObj(vstd::RNG & rand) { switch(ID.toEnum()) { @@ -298,7 +300,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const cb->showBlockingDialog(&bd); } -void CGDwelling::newTurn(CRandomGenerator & rand) const +void CGDwelling::newTurn(vstd::RNG & rand) const { if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week return; diff --git a/lib/mapObjects/CGDwelling.h b/lib/mapObjects/CGDwelling.h index da85542d2..4afa3907e 100644 --- a/lib/mapObjects/CGDwelling.h +++ b/lib/mapObjects/CGDwelling.h @@ -45,13 +45,13 @@ protected: void serializeJsonOptions(JsonSerializeFormat & handler) override; private: - FactionID randomizeFaction(CRandomGenerator & rand); - int randomizeLevel(CRandomGenerator & rand); + FactionID randomizeFaction(vstd::RNG & rand); + int randomizeLevel(vstd::RNG & rand); - void pickRandomObject(CRandomGenerator & rand) override; - void initObj(CRandomGenerator & rand) override; + void pickRandomObject(vstd::RNG & rand) override; + void initObj(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; - void newTurn(CRandomGenerator & rand) const override; + void newTurn(vstd::RNG & rand) const override; void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index b4119f891..a7dbed71e 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -313,13 +313,13 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType) subID = heroType; } -void CGHeroInstance::initHero(CRandomGenerator & rand, const HeroTypeID & SUBID) +void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) { subID = SUBID.getNum(); initHero(rand); } -void CGHeroInstance::initHero(CRandomGenerator & rand) +void CGHeroInstance::initHero(vstd::RNG & rand) { assert(validTypes(true)); if(!type) @@ -422,7 +422,7 @@ void CGHeroInstance::initHero(CRandomGenerator & rand) mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one } -void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst) +void CGHeroInstance::initArmy(vstd::RNG & rand, IArmyDescriptor * dst) { if(!dst) dst = this; @@ -532,7 +532,7 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const if (!boat) { //Create a new boat for hero - cb->createObject(boatPos, h->getOwner(), Obj::BOAT, getBoatType().getNum()); + cb->createBoat(boatPos, getBoatType(), h->getOwner()); boatId = cb->getTopObj(boatPos)->id; } } @@ -589,7 +589,7 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter() wisdomCounter = 0; } -void CGHeroInstance::pickRandomObject(CRandomGenerator & rand) +void CGHeroInstance::pickRandomObject(vstd::RNG & rand) { assert(ID == Obj::HERO || ID == Obj::PRISON || ID == Obj::RANDOM_HERO); @@ -614,11 +614,6 @@ void CGHeroInstance::pickRandomObject(CRandomGenerator & rand) this->subID = oldSubID; } -void CGHeroInstance::initObj(CRandomGenerator & rand) -{ - -} - void CGHeroInstance::recreateSecondarySkillsBonuses() { auto secondarySkillsBonuses = getBonuses(Selector::sourceType()(BonusSource::SECONDARY_SKILL)); @@ -972,7 +967,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b * @param raisedStack Pair where the first element represents ID of the raised creature * and the second element the amount. */ -void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack, CRandomGenerator & rand) const +void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack, vstd::RNG & rand) const { InfoWindow iw; iw.type = EInfoWindowMode::AUTO; @@ -1081,7 +1076,7 @@ EAlignment CGHeroInstance::getAlignment() const return type->heroClass->getAlignment(); } -void CGHeroInstance::initExp(CRandomGenerator & rand) +void CGHeroInstance::initExp(vstd::RNG & rand) { exp = rand.nextInt(40, 89); } @@ -1299,7 +1294,7 @@ ArtBearer::ArtBearer CGHeroInstance::bearerType() const return ArtBearer::HERO; } -std::vector CGHeroInstance::getLevelUpProposedSecondarySkills(CRandomGenerator & rand) const +std::vector CGHeroInstance::getLevelUpProposedSecondarySkills(vstd::RNG & rand) const { auto getObligatorySkills = [](CSkill::Obligatory obl){ std::set obligatory; @@ -1378,7 +1373,7 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills(CR return skills; } -PrimarySkill CGHeroInstance::nextPrimarySkill(CRandomGenerator & rand) const +PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const { assert(gainsLevel()); const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL; @@ -1394,7 +1389,7 @@ PrimarySkill CGHeroInstance::nextPrimarySkill(CRandomGenerator & rand) const return static_cast(RandomGeneratorUtil::nextItemWeighted(skillChances, rand)); } -std::optional CGHeroInstance::nextSecondarySkill(CRandomGenerator & rand) const +std::optional CGHeroInstance::nextSecondarySkill(vstd::RNG & rand) const { assert(gainsLevel()); @@ -1482,7 +1477,7 @@ void CGHeroInstance::levelUp(const std::vector & skills) treeHasChanged(); } -void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand) +void CGHeroInstance::levelUpAutomatically(vstd::RNG & rand) { while(gainsLevel()) { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index b929ef7e0..ff2ea4281 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -187,13 +187,13 @@ public: bool gainsLevel() const; /// Returns the next primary skill on level up. Can only be called if hero can gain a level up. - PrimarySkill nextPrimarySkill(CRandomGenerator & rand) const; + PrimarySkill nextPrimarySkill(vstd::RNG & rand) const; /// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up. - std::optional nextSecondarySkill(CRandomGenerator & rand) const; + std::optional nextSecondarySkill(vstd::RNG & rand) const; /// Gets 0, 1 or 2 secondary skills which are proposed on hero level up. - std::vector getLevelUpProposedSecondarySkills(CRandomGenerator & rand) const; + std::vector getLevelUpProposedSecondarySkills(vstd::RNG & rand) const; ui8 getSecSkillLevel(const SecondarySkill & skill) const; //0 - no skill @@ -225,7 +225,7 @@ public: TExpType calculateXp(TExpType exp) const; //apply learning skill CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; - void showNecromancyDialog(const CStackBasicDescriptor &raisedStack, CRandomGenerator & rand) const; + void showNecromancyDialog(const CStackBasicDescriptor &raisedStack, vstd::RNG & rand) const; EDiggingStatus diggingStatus() const; ////////////////////////////////////////////////////////////////////////// @@ -233,13 +233,13 @@ public: HeroTypeID getHeroType() const; void setHeroType(HeroTypeID type); - void initHero(CRandomGenerator & rand); - void initHero(CRandomGenerator & rand, const HeroTypeID & SUBID); + void initHero(vstd::RNG & rand); + void initHero(vstd::RNG & rand, const HeroTypeID & SUBID); ArtPlacementMap putArtifact(ArtifactPosition pos, CArtifactInstance * art) override; void removeArtifact(ArtifactPosition pos) override; - void initExp(CRandomGenerator & rand); - void initArmy(CRandomGenerator & rand, IArmyDescriptor *dst = nullptr); + void initExp(vstd::RNG & rand); + void initArmy(vstd::RNG & rand, IArmyDescriptor *dst = nullptr); void pushPrimSkill(PrimarySkill which, int val); ui8 maxlevelsToMagicSchool() const; ui8 maxlevelsToWisdom() const; @@ -293,8 +293,7 @@ public: void boatDeserializationFix(); void deserializationFix(); - void initObj(CRandomGenerator & rand) override; - void pickRandomObject(CRandomGenerator & rand) override; + void pickRandomObject(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; std::string getObjectName() const override; @@ -318,7 +317,7 @@ protected: void serializeJsonOptions(JsonSerializeFormat & handler) override; private: - void levelUpAutomatically(CRandomGenerator & rand); + void levelUpAutomatically(vstd::RNG & rand); public: std::string getHeroTypeName() const; diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index dfc072366..3bca00646 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -23,7 +23,7 @@ VCMI_LIB_NAMESPACE_BEGIN -void CGMarket::initObj(CRandomGenerator & rand) +void CGMarket::initObj(vstd::RNG & rand) { getObjectHandler()->configureObject(this, rand); } @@ -80,7 +80,7 @@ std::vector CGBlackMarket::availableItemsIds(EMarketMode mode) con } } -void CGBlackMarket::newTurn(CRandomGenerator & rand) const +void CGBlackMarket::newTurn(vstd::RNG & rand) const { int resetPeriod = VLC->settings()->getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD); diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index c0a589f3b..956d15daa 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -29,7 +29,7 @@ public: CGMarket(IGameCallback *cb); ///IObjectInterface void onHeroVisit(const CGHeroInstance * h) const override; //open trading window - void initObj(CRandomGenerator & rand) override;//set skills for trade + void initObj(vstd::RNG & rand) override;//set skills for trade ///IMarket int getMarketEfficiency() const override; @@ -54,7 +54,7 @@ public: std::vector artifacts; //available artifacts - void newTurn(CRandomGenerator & rand) const override; //reset artifacts for black market every month + void newTurn(vstd::RNG & rand) const override; //reset artifacts for black market every month std::vector availableItemsIds(EMarketMode mode) const override; template void serialize(Handler &h) diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index f23f3a44a..b659c34d1 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -25,6 +25,8 @@ #include "../networkPacks/PacksForClient.h" #include "../serializer/JsonSerializeFormat.h" +#include + VCMI_LIB_NAMESPACE_BEGIN //TODO: remove constructor @@ -164,12 +166,12 @@ void CGObjectInstance::setType(MapObjectID newID, MapObjectSubID newSubID) cb->gameState()->map->addBlockVisTiles(this); } -void CGObjectInstance::pickRandomObject(CRandomGenerator & rand) +void CGObjectInstance::pickRandomObject(vstd::RNG & rand) { // no-op } -void CGObjectInstance::initObj(CRandomGenerator & rand) +void CGObjectInstance::initObj(vstd::RNG & rand) { // no-op } @@ -232,7 +234,7 @@ std::string CGObjectInstance::getObjectName() const return VLC->objtypeh->getObjectName(ID, subID); } -std::optional CGObjectInstance::getAmbientSound() const +std::optional CGObjectInstance::getAmbientSound(vstd::RNG & rng) const { const auto & sounds = VLC->objtypeh->getObjectSounds(ID, subID).ambient; if(!sounds.empty()) @@ -241,20 +243,20 @@ std::optional CGObjectInstance::getAmbientSound() const return std::nullopt; } -std::optional CGObjectInstance::getVisitSound() const +std::optional CGObjectInstance::getVisitSound(vstd::RNG & rng) const { const auto & sounds = VLC->objtypeh->getObjectSounds(ID, subID).visit; if(!sounds.empty()) - return *RandomGeneratorUtil::nextItem(sounds, CRandomGenerator::getDefault()); + return *RandomGeneratorUtil::nextItem(sounds, rng); return std::nullopt; } -std::optional CGObjectInstance::getRemovalSound() const +std::optional CGObjectInstance::getRemovalSound(vstd::RNG & rng) const { const auto & sounds = VLC->objtypeh->getObjectSounds(ID, subID).removal; if(!sounds.empty()) - return *RandomGeneratorUtil::nextItem(sounds, CRandomGenerator::getDefault()); + return *RandomGeneratorUtil::nextItem(sounds, rng); return std::nullopt; } diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index 143118727..36a330142 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -97,9 +97,9 @@ public: virtual bool isTile2Terrain() const { return false; } - std::optional getAmbientSound() const; - std::optional getVisitSound() const; - std::optional getRemovalSound() const; + std::optional getAmbientSound(vstd::RNG & rng) const; + std::optional getVisitSound(vstd::RNG & rng) const; + std::optional getRemovalSound(vstd::RNG & rng) const; TObjectTypeHandler getObjectHandler() const; @@ -128,8 +128,8 @@ public: /** OVERRIDES OF IObjectInterface **/ - void initObj(CRandomGenerator & rand) override; - void pickRandomObject(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; + void pickRandomObject(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; /// method for synchronous update. Note: For new properties classes should override setPropertyDer instead void setProperty(ObjProperty what, ObjPropertyID identifier) final; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index b79077948..faa49360b 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -41,7 +41,7 @@ void CGPandoraBox::init() } } -void CGPandoraBox::initObj(CRandomGenerator & rand) +void CGPandoraBox::initObj(vstd::RNG & rand) { init(); diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index f739f1fb9..a1e5e8394 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -23,7 +23,7 @@ public: MetaString message; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; diff --git a/lib/mapObjects/CGTownBuilding.cpp b/lib/mapObjects/CGTownBuilding.cpp index e87988df4..8e58f1a57 100644 --- a/lib/mapObjects/CGTownBuilding.cpp +++ b/lib/mapObjects/CGTownBuilding.cpp @@ -17,6 +17,8 @@ #include "../mapObjects/CGHeroInstance.h" #include "../networkPacks/PacksForClient.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CGTownBuilding::CGTownBuilding(IGameCallback * cb) @@ -314,7 +316,7 @@ CTownRewardableBuilding::CTownRewardableBuilding(IGameCallback *cb) : CGTownBuilding(cb) {} -CTownRewardableBuilding::CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown, CRandomGenerator & rand) +CTownRewardableBuilding::CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown, vstd::RNG & rand) : CGTownBuilding(cgTown) { bID = index; @@ -323,14 +325,19 @@ CTownRewardableBuilding::CTownRewardableBuilding(const BuildingID & index, Build initObj(rand); } -void CTownRewardableBuilding::initObj(CRandomGenerator & rand) +void CTownRewardableBuilding::initObj(vstd::RNG & rand) { assert(town && town->town); + configuration = generateConfiguration(rand); +} +Rewardable::Configuration CTownRewardableBuilding::generateConfiguration(vstd::RNG & rand) const +{ + Rewardable::Configuration result; auto building = town->town->buildings.at(bID); - building->rewardableObjectInfo.configureObject(configuration, rand, cb); - for(auto & rewardInfo : configuration.info) + building->rewardableObjectInfo.configureObject(result, rand, cb); + for(auto & rewardInfo : result.info) { for (auto & bonus : rewardInfo.reward.bonuses) { @@ -338,16 +345,16 @@ void CTownRewardableBuilding::initObj(CRandomGenerator & rand) bonus.sid = BonusSourceID(building->getUniqueTypeID()); } } + return result; } -void CTownRewardableBuilding::newTurn(CRandomGenerator & rand) const +void CTownRewardableBuilding::newTurn(vstd::RNG & rand) const { if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0) { - if(configuration.resetParameters.rewards) - { - cb->setObjPropertyValue(town->id, ObjProperty::REWARD_RANDOMIZE, indexOnTV); - } + auto newConfiguration = generateConfiguration(rand); + cb->setRewardableObjectConfiguration(town->id, bID, newConfiguration); + if(configuration.resetParameters.visitors) { cb->setObjPropertyValue(town->id, ObjProperty::STRUCTURE_CLEAR_VISITORS, indexOnTV); @@ -365,9 +372,6 @@ void CTownRewardableBuilding::setProperty(ObjProperty what, ObjPropertyID identi case ObjProperty::STRUCTURE_CLEAR_VISITORS: visitors.clear(); break; - case ObjProperty::REWARD_RANDOMIZE: - initObj(cb->gameState()->getRandomGenerator()); - break; case ObjProperty::REWARD_SELECT: selectedReward = identifier.getNum(); break; diff --git a/lib/mapObjects/CGTownBuilding.h b/lib/mapObjects/CGTownBuilding.h index 9b620a6ac..9ddf4e616 100644 --- a/lib/mapObjects/CGTownBuilding.h +++ b/lib/mapObjects/CGTownBuilding.h @@ -118,22 +118,24 @@ class DLL_LINKAGE CTownRewardableBuilding : public CGTownBuilding, public Reward bool wasVisitedBefore(const CGHeroInstance * contextHero) const; void grantReward(ui32 rewardID, const CGHeroInstance * hero) const; + + Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const; public: void setProperty(ObjProperty what, ObjPropertyID identifier) override; void onHeroVisit(const CGHeroInstance * h) const override; - void newTurn(CRandomGenerator & rand) const override; + void newTurn(vstd::RNG & rand) const override; /// gives second part of reward after hero level-ups for proper granting of spells/mana void heroLevelUpDone(const CGHeroInstance *hero) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; /// applies player selection of reward void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * town, CRandomGenerator & rand); + CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * town, vstd::RNG & rand); CTownRewardableBuilding(IGameCallback *cb); template void serialize(Handler &h) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index f61e84abc..35eeecd9f 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -31,6 +31,8 @@ #include "../networkPacks/PacksForClientBattle.h" #include "../serializer/JsonSerializeFormat.h" +#include + VCMI_LIB_NAMESPACE_BEGIN int CGTownInstance::getSightRadius() const //returns sight distance @@ -69,9 +71,6 @@ void CGTownInstance::setPropertyDer(ObjProperty what, ObjPropertyID identifier) case ObjProperty::BONUS_VALUE_SECOND: bonusValue.second = identifier.getNum(); break; - case ObjProperty::REWARD_RANDOMIZE: - bonusingBuildings[identifier.getNum()]->setProperty(ObjProperty::REWARD_RANDOMIZE, NumericID(0)); - break; } } CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle @@ -379,7 +378,7 @@ bool CGTownInstance::isBonusingBuildingAdded(BuildingID bid) const return present != bonusingBuildings.end(); } -void CGTownInstance::addTownBonuses(CRandomGenerator & rand) +void CGTownInstance::addTownBonuses(vstd::RNG & rand) { for(const auto & kvp : town->buildings) { @@ -461,7 +460,7 @@ void CGTownInstance::deleteTownBonus(BuildingID bid) delete freeIt; } -FactionID CGTownInstance::randomizeFaction(CRandomGenerator & rand) +FactionID CGTownInstance::randomizeFaction(vstd::RNG & rand) { if(getOwner().isValidPlayer()) return cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner()).castle; @@ -479,7 +478,7 @@ FactionID CGTownInstance::randomizeFaction(CRandomGenerator & rand) return *RandomGeneratorUtil::nextItem(potentialPicks, rand); } -void CGTownInstance::pickRandomObject(CRandomGenerator & rand) +void CGTownInstance::pickRandomObject(vstd::RNG & rand) { assert(ID == MapObjectID::TOWN || ID == MapObjectID::RANDOM_TOWN); if (ID == MapObjectID::RANDOM_TOWN) @@ -495,7 +494,7 @@ void CGTownInstance::pickRandomObject(CRandomGenerator & rand) updateAppearance(); } -void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures +void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures { blockVisit = true; @@ -521,7 +520,7 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu updateAppearance(); } -void CGTownInstance::newTurn(CRandomGenerator & rand) const +void CGTownInstance::newTurn(vstd::RNG & rand) const { if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week { diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 4eeb5a069..ce0ee5e0a 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -201,11 +201,11 @@ public: virtual ~CGTownInstance(); ///IObjectInterface overrides - void newTurn(CRandomGenerator & rand) const override; + void newTurn(vstd::RNG & rand) const override; void onHeroVisit(const CGHeroInstance * h) const override; void onHeroLeave(const CGHeroInstance * h) const override; - void initObj(CRandomGenerator & rand) override; - void pickRandomObject(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; + void pickRandomObject(vstd::RNG & rand) override; void battleFinished(const CGHeroInstance * hero, const BattleResult & result) const override; std::string getObjectName() const override; @@ -224,14 +224,14 @@ protected: void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; private: - FactionID randomizeFaction(CRandomGenerator & rand); + FactionID randomizeFaction(vstd::RNG & rand); void setOwner(const PlayerColor & owner) const; void onTownCaptured(const PlayerColor & winner) const; int getDwellingBonus(const std::vector& creatureIds, const std::vector >& dwellings) const; bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const; bool isBonusingBuildingAdded(BuildingID bid) const; void initOverriddenBids(); - void addTownBonuses(CRandomGenerator & rand); + void addTownBonuses(vstd::RNG & rand); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index dfb97696b..af7fc5bf3 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -31,7 +31,8 @@ #include "../modding/ModUtility.h" #include "../networkPacks/PacksForClient.h" #include "../spells/CSpellHandler.h" -#include "../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -441,7 +442,7 @@ void CGSeerHut::setObjToKill() } } -void CGSeerHut::init(CRandomGenerator & rand) +void CGSeerHut::init(vstd::RNG & rand) { auto names = VLC->generaltexth->findStringsWithPrefix("core.seerhut.names"); @@ -455,7 +456,7 @@ void CGSeerHut::init(CRandomGenerator & rand) configuration.selectMode = Rewardable::ESelectMode::SELECT_PLAYER; } -void CGSeerHut::initObj(CRandomGenerator & rand) +void CGSeerHut::initObj(vstd::RNG & rand) { init(rand); @@ -562,7 +563,7 @@ void CGSeerHut::setPropertyDer(ObjProperty what, ObjPropertyID identifier) } } -void CGSeerHut::newTurn(CRandomGenerator & rand) const +void CGSeerHut::newTurn(vstd::RNG & rand) const { CRewardableObject::newTurn(rand); if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up @@ -750,7 +751,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) } } -void CGQuestGuard::init(CRandomGenerator & rand) +void CGQuestGuard::init(vstd::RNG & rand) { blockVisit = true; quest->textOption = rand.nextInt(3, 5); @@ -818,7 +819,7 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const h->showInfoDialog(txt_id); } -void CGBorderGuard::initObj(CRandomGenerator & rand) +void CGBorderGuard::initObj(vstd::RNG & rand) { blockVisit = true; } diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 22748ecb0..caebee439 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -141,19 +141,19 @@ public: std::string seerName; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; std::string getHoverText(PlayerColor player) const override; std::string getHoverText(const CGHeroInstance * hero) const override; std::string getPopupText(PlayerColor player) const override; std::string getPopupText(const CGHeroInstance * hero) const override; std::vector getPopupComponents(PlayerColor player) const override; std::vector getPopupComponents(const CGHeroInstance * hero) const override; - void newTurn(CRandomGenerator & rand) const override; + void newTurn(vstd::RNG & rand) const override; void onHeroVisit(const CGHeroInstance * h) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; void getVisitText (MetaString &text, std::vector &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override; - virtual void init(CRandomGenerator & rand); + virtual void init(vstd::RNG & rand); int checkDirection() const; //calculates the region of map where monster is placed void setObjToKill(); //remember creatures / heroes to kill after they are initialized const CGHeroInstance *getHeroToKill(bool allowNull) const; @@ -179,7 +179,7 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut public: using CGSeerHut::CGSeerHut; - void init(CRandomGenerator & rand) override; + void init(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; bool passableFor(PlayerColor color) const override; @@ -227,7 +227,7 @@ class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject public: using CGKeys::CGKeys; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index ab05a36de..80f852c08 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -16,10 +16,13 @@ #include "../IGameCallback.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" +#include "../mapObjectConstructors/CRewardableConstructor.h" #include "../mapObjects/CGHeroInstance.h" #include "../networkPacks/PacksForClient.h" #include "../serializer/JsonSerializeFormat.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const @@ -375,9 +378,6 @@ void CRewardableObject::setPropertyDer(ObjProperty what, ObjPropertyID identifie { switch (what) { - case ObjProperty::REWARD_RANDOMIZE: - initObj(cb->gameState()->getRandomGenerator()); - break; case ObjProperty::REWARD_SELECT: selectedReward = identifier.getNum(); break; @@ -387,13 +387,15 @@ void CRewardableObject::setPropertyDer(ObjProperty what, ObjPropertyID identifie } } -void CRewardableObject::newTurn(CRandomGenerator & rand) const +void CRewardableObject::newTurn(vstd::RNG & rand) const { if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0) { if (configuration.resetParameters.rewards) { - cb->setObjPropertyValue(id, ObjProperty::REWARD_RANDOMIZE, 0); + auto handler = std::dynamic_pointer_cast(getObjectHandler()); + auto newConfiguration = handler->generateConfiguration(cb, rand, ID); + cb->setRewardableObjectConfiguration(id, newConfiguration); } if (configuration.resetParameters.visitors) { @@ -404,7 +406,7 @@ void CRewardableObject::newTurn(CRandomGenerator & rand) const } } -void CRewardableObject::initObj(CRandomGenerator & rand) +void CRewardableObject::initObj(vstd::RNG & rand) { getObjectHandler()->configureObject(this, rand); } diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 0b0ddf5ff..a3fb7c2e2 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -57,7 +57,7 @@ public: void onHeroVisit(const CGHeroInstance *h) const override; ///possibly resets object state - void newTurn(CRandomGenerator & rand) const override; + void newTurn(vstd::RNG & rand) const override; /// gives second part of reward after hero level-ups for proper granting of spells/mana void heroLevelUpDone(const CGHeroInstance *hero) const override; @@ -65,7 +65,7 @@ public: /// applies player selection of reward void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override; diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index e7dac1ca0..b3101155d 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -38,13 +38,13 @@ void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const void IObjectInterface::onHeroLeave(const CGHeroInstance * h) const {} -void IObjectInterface::newTurn(CRandomGenerator & rand) const +void IObjectInterface::newTurn(vstd::RNG & rand) const {} -void IObjectInterface::initObj(CRandomGenerator & rand) +void IObjectInterface::initObj(vstd::RNG & rand) {} -void IObjectInterface::pickRandomObject(CRandomGenerator & rand) +void IObjectInterface::pickRandomObject(vstd::RNG & rand) {} void IObjectInterface::setProperty(ObjProperty what, ObjPropertyID identifier) diff --git a/lib/mapObjects/IObjectInterface.h b/lib/mapObjects/IObjectInterface.h index 9a4e365b6..82a798e72 100644 --- a/lib/mapObjects/IObjectInterface.h +++ b/lib/mapObjects/IObjectInterface.h @@ -17,11 +17,15 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + struct BattleResult; struct UpgradeInfo; class BoatId; class CGObjectInstance; -class CRandomGenerator; class CStackInstance; class CGHeroInstance; class IGameCallback; @@ -46,9 +50,12 @@ public: virtual void onHeroVisit(const CGHeroInstance * h) const; virtual void onHeroLeave(const CGHeroInstance * h) const; - virtual void newTurn(CRandomGenerator & rand) const; - virtual void initObj(CRandomGenerator & rand); //synchr - virtual void pickRandomObject(CRandomGenerator & rand); + + /// Called on new turn by server. This method can not modify object state on its own + /// Instead all changes must be propagated via netpacks + virtual void newTurn(vstd::RNG & rand) const; + virtual void initObj(vstd::RNG & rand); //synchr + virtual void pickRandomObject(vstd::RNG & rand); virtual void setProperty(ObjProperty what, ObjPropertyID identifier);//synchr //Called when queries created DURING HERO VISIT are resolved diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index a7cef0720..03e2e1079 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -32,6 +32,8 @@ #include "../networkPacks/PacksForClientBattle.h" #include "../networkPacks/StackLocation.h" +#include + VCMI_LIB_NAMESPACE_BEGIN ///helpers @@ -93,7 +95,7 @@ void CGMine::onHeroVisit( const CGHeroInstance * h ) const } -void CGMine::newTurn(CRandomGenerator & rand) const +void CGMine::newTurn(vstd::RNG & rand) const { if(cb->getDate() == 1) return; @@ -104,7 +106,7 @@ void CGMine::newTurn(CRandomGenerator & rand) const cb->giveResource(tempOwner, producedResource, producedQuantity); } -void CGMine::initObj(CRandomGenerator & rand) +void CGMine::initObj(vstd::RNG & rand) { if(isAbandoned()) { @@ -254,7 +256,7 @@ std::string CGResource::getHoverText(PlayerColor player) const return VLC->generaltexth->restypes[resourceID().getNum()]; } -void CGResource::pickRandomObject(CRandomGenerator & rand) +void CGResource::pickRandomObject(vstd::RNG & rand) { assert(ID == Obj::RESOURCE || ID == Obj::RANDOM_RESOURCE); @@ -269,7 +271,7 @@ void CGResource::pickRandomObject(CRandomGenerator & rand) } } -void CGResource::initObj(CRandomGenerator & rand) +void CGResource::initObj(vstd::RNG & rand) { blockVisit = true; @@ -327,7 +329,7 @@ void CGResource::collectRes(const PlayerColor & player) const sii.text.replaceName(resourceID()); } sii.components.emplace_back(ComponentType::RESOURCE, resourceID(), amount); - sii.soundID = soundBase::pickup01 + CRandomGenerator::getDefault().nextInt(6); + sii.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6); cb->showInfoDialog(&sii); cb->removeObject(this, player); } @@ -395,7 +397,7 @@ ObjectInstanceID CGTeleport::getRandomExit(const CGHeroInstance * h) const { auto passableExits = getPassableExits(cb->gameState(), h, getAllExits(true)); if(!passableExits.empty()) - return *RandomGeneratorUtil::nextItem(passableExits, CRandomGenerator::getDefault()); + return *RandomGeneratorUtil::nextItem(passableExits, cb->gameState()->getRandomGenerator()); return ObjectInstanceID(); } @@ -530,7 +532,7 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, cb->moveHero(hero->id, hero->convertFromVisitablePos(dPos), EMovementMode::MONOLITH); } -void CGMonolith::initObj(CRandomGenerator & rand) +void CGMonolith::initObj(vstd::RNG & rand) { std::vector IDs; IDs.push_back(ID); @@ -575,7 +577,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const cb->showTeleportDialog(&td); } -void CGSubterraneanGate::initObj(CRandomGenerator & rand) +void CGSubterraneanGate::initObj(vstd::RNG & rand) { type = BOTH; } @@ -703,7 +705,7 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer const auto * obj = cb->getObj(exit); std::set tiles = obj->getBlockedPos(); - dPos = *RandomGeneratorUtil::nextItem(tiles, CRandomGenerator::getDefault()); + dPos = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); } cb->moveHero(hero->id, hero->convertFromVisitablePos(dPos), EMovementMode::MONOLITH); @@ -724,7 +726,7 @@ ArtifactID CGArtifact::getArtifact() const return getObjTypeIndex().getNum(); } -void CGArtifact::pickRandomObject(CRandomGenerator & rand) +void CGArtifact::pickRandomObject(vstd::RNG & rand) { switch(ID.toEnum()) { @@ -754,7 +756,7 @@ void CGArtifact::pickRandomObject(CRandomGenerator & rand) ID = MapObjectID::ARTIFACT; } -void CGArtifact::initObj(CRandomGenerator & rand) +void CGArtifact::initObj(vstd::RNG & rand) { blockVisit = true; if(ID == Obj::ARTIFACT) @@ -936,7 +938,7 @@ void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler) } } -void CGSignBottle::initObj(CRandomGenerator & rand) +void CGSignBottle::initObj(vstd::RNG & rand) { //if no text is set than we pick random from the predefined ones if(message.empty()) @@ -1011,7 +1013,7 @@ void CGGarrison::serializeJsonOptions(JsonSerializeFormat& handler) CArmedInstance::serializeJsonOptions(handler); } -void CGGarrison::initObj(CRandomGenerator &rand) +void CGGarrison::initObj(vstd::RNG &rand) { if(this->subID == MapObjectSubID::decode(this->ID, "antiMagic")) addAntimagicGarrisonBonus(); @@ -1028,7 +1030,7 @@ void CGGarrison::addAntimagicGarrisonBonus() this->addNewBonus(bonus); } -void CGMagi::initObj(CRandomGenerator & rand) +void CGMagi::initObj(vstd::RNG & rand) { if (ID == Obj::EYE_OF_MAGI) blockVisit = true; @@ -1091,7 +1093,7 @@ bool CGBoat::isCoastVisitable() const return true; } -void CGSirens::initObj(CRandomGenerator & rand) +void CGSirens::initObj(vstd::RNG & rand) { blockVisit = true; } @@ -1238,7 +1240,7 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const } -void CGObelisk::initObj(CRandomGenerator & rand) +void CGObelisk::initObj(vstd::RNG & rand) { cb->gameState()->map->obeliskCount++; } @@ -1291,7 +1293,7 @@ void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const } } -void CGLighthouse::initObj(CRandomGenerator & rand) +void CGLighthouse::initObj(vstd::RNG & rand) { if(tempOwner.isValidPlayer()) { diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 173735499..0ab01cb18 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -48,7 +48,7 @@ public: MetaString message; void onHeroVisit(const CGHeroInstance * h) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; template void serialize(Handler &h) { @@ -66,7 +66,7 @@ public: bool removableUnits; - void initObj(CRandomGenerator &rand) override; + void initObj(vstd::RNG &rand) override; bool passableFor(PlayerColor color) const override; void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; @@ -99,8 +99,8 @@ public: std::vector getPopupComponents(PlayerColor player) const override; void pick( const CGHeroInstance * h ) const; - void initObj(CRandomGenerator & rand) override; - void pickRandomObject(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; + void pickRandomObject(vstd::RNG & rand) override; void afterAddToMap(CMap * map) override; BattleField getBattlefield() const override; @@ -129,8 +129,8 @@ public: MetaString message; void onHeroVisit(const CGHeroInstance * h) const override; - void initObj(CRandomGenerator & rand) override; - void pickRandomObject(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; + void pickRandomObject(vstd::RNG & rand) override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; std::string getHoverText(PlayerColor player) const override; @@ -166,8 +166,8 @@ private: void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; void flagMine(const PlayerColor & player) const; - void newTurn(CRandomGenerator & rand) const override; - void initObj(CRandomGenerator & rand) override; + void newTurn(vstd::RNG & rand) const override; + void initObj(vstd::RNG & rand) override; std::string getObjectName() const override; std::string getHoverText(PlayerColor player) const override; @@ -248,7 +248,7 @@ class DLL_LINKAGE CGMonolith : public CGTeleport protected: void onHeroVisit(const CGHeroInstance * h) const override; void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; public: using CGTeleport::CGTeleport; @@ -262,7 +262,7 @@ public: class DLL_LINKAGE CGSubterraneanGate : public CGMonolith { void onHeroVisit(const CGHeroInstance * h) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; public: using CGMonolith::CGMonolith; @@ -297,7 +297,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; std::string getHoverText(const CGHeroInstance * hero) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; template void serialize(Handler &h) { @@ -369,7 +369,7 @@ class DLL_LINKAGE CGMagi : public CGObjectInstance public: using CGObjectInstance::CGObjectInstance; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; template void serialize(Handler &h) @@ -391,7 +391,7 @@ public: using CTeamVisited::CTeamVisited; void onHeroVisit(const CGHeroInstance * h) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; std::string getHoverText(PlayerColor player) const override; template void serialize(Handler &h) @@ -408,7 +408,7 @@ public: using CGObjectInstance::CGObjectInstance; void onHeroVisit(const CGHeroInstance * h) const override; - void initObj(CRandomGenerator & rand) override; + void initObj(vstd::RNG & rand) override; template void serialize(Handler &h) { diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index 2cf1bbc94..c67684ceb 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -12,11 +12,12 @@ #include "CDrawRoadsOperation.h" #include "CMap.h" -#include "../CRandomGenerator.h" #include "../RoadHandler.h" #include "../RiverHandler.h" #include "../VCMI_Lib.h" +#include + VCMI_LIB_NAMESPACE_BEGIN const std::vector CDrawLinesOperation::patterns = @@ -155,7 +156,7 @@ static bool ruleIsAny(const std::string & rule) #endif ///CDrawLinesOperation -CDrawLinesOperation::CDrawLinesOperation(CMap * map, CTerrainSelection terrainSel, CRandomGenerator * gen): +CDrawLinesOperation::CDrawLinesOperation(CMap * map, CTerrainSelection terrainSel, vstd::RNG * gen): CMapOperation(map), terrainSel(std::move(terrainSel)), gen(gen) @@ -163,14 +164,14 @@ CDrawLinesOperation::CDrawLinesOperation(CMap * map, CTerrainSelection terrainSe } ///CDrawRoadsOperation -CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, CRandomGenerator * gen): +CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, vstd::RNG * gen): CDrawLinesOperation(map, terrainSel,gen), roadType(roadType) { } ///CDrawRiversOperation -CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RiverId riverType, CRandomGenerator * gen): +CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RiverId riverType, vstd::RNG * gen): CDrawLinesOperation(map, terrainSel, gen), riverType(riverType) { diff --git a/lib/mapping/CDrawRoadsOperation.h b/lib/mapping/CDrawRoadsOperation.h index 16e2ad070..7e9ea754d 100644 --- a/lib/mapping/CDrawRoadsOperation.h +++ b/lib/mapping/CDrawRoadsOperation.h @@ -41,7 +41,7 @@ protected: int flip; }; - CDrawLinesOperation(CMap * map, CTerrainSelection terrainSel, CRandomGenerator * gen); + CDrawLinesOperation(CMap * map, CTerrainSelection terrainSel, vstd::RNG * gen); virtual void executeTile(TerrainTile & tile) = 0; virtual bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const = 0; @@ -58,13 +58,13 @@ protected: ValidationResult validateTile(const LinePattern & pattern, const int3 & pos); CTerrainSelection terrainSel; - CRandomGenerator * gen; + vstd::RNG * gen; }; class CDrawRoadsOperation : public CDrawLinesOperation { public: - CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, CRandomGenerator * gen); + CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, vstd::RNG * gen); std::string getLabel() const override; protected: @@ -81,7 +81,7 @@ private: class CDrawRiversOperation : public CDrawLinesOperation { public: - CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RiverId roadType, CRandomGenerator * gen); + CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RiverId roadType, vstd::RNG * gen); std::string getLabel() const override; protected: diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index cb0f5dbf1..b91e335d3 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -29,6 +29,8 @@ #include "CMapOperation.h" #include "../serializer/JsonSerializeFormat.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void Rumor::serializeJson(JsonSerializeFormat & handler) diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index f97b11125..2884dbb12 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -15,6 +15,8 @@ #include "CDrawRoadsOperation.h" #include "CMapOperation.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CMapUndoManager::CMapUndoManager() : @@ -113,34 +115,35 @@ void CMapUndoManager::setUndoCallback(std::function functor) CMapEditManager::CMapEditManager(CMap * map) : map(map), terrainSel(map), objectSel(map) { - } +CMapEditManager::~CMapEditManager() = default; + CMap * CMapEditManager::getMap() { return map; } -void CMapEditManager::clearTerrain(CRandomGenerator * gen) +void CMapEditManager::clearTerrain(vstd::RNG * gen) { - execute(std::make_unique(map, gen ? gen : &(this->gen))); + execute(std::make_unique(map, gen ? gen : this->gen.get())); } -void CMapEditManager::drawTerrain(TerrainId terType, int decorationsPercentage, CRandomGenerator * gen) +void CMapEditManager::drawTerrain(TerrainId terType, int decorationsPercentage, vstd::RNG * gen) { - execute(std::make_unique(map, terrainSel, terType, decorationsPercentage, gen ? gen : &(this->gen))); + execute(std::make_unique(map, terrainSel, terType, decorationsPercentage, gen ? gen : this->gen.get())); terrainSel.clearSelection(); } -void CMapEditManager::drawRoad(RoadId roadType, CRandomGenerator* gen) +void CMapEditManager::drawRoad(RoadId roadType, vstd::RNG* gen) { - execute(std::make_unique(map, terrainSel, roadType, gen ? gen : &(this->gen))); + execute(std::make_unique(map, terrainSel, roadType, gen ? gen : this->gen.get())); terrainSel.clearSelection(); } -void CMapEditManager::drawRiver(RiverId riverType, CRandomGenerator* gen) +void CMapEditManager::drawRiver(RiverId riverType, vstd::RNG* gen) { - execute(std::make_unique(map, terrainSel, riverType, gen ? gen : &(this->gen))); + execute(std::make_unique(map, terrainSel, riverType, gen ? gen : this->gen.get())); terrainSel.clearSelection(); } diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index e380db826..10f68a8ae 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -11,13 +11,17 @@ #pragma once #include "../GameConstants.h" -#include "../CRandomGenerator.h" #include "MapEditUtils.h" VCMI_LIB_NAMESPACE_BEGIN class CMapOperation; +namespace vstd +{ +class RNG; +} + /// The CMapUndoManager provides the functionality to save operations and undo/redo them. class DLL_LINKAGE CMapUndoManager : boost::noncopyable { @@ -64,19 +68,20 @@ class DLL_LINKAGE CMapEditManager : boost::noncopyable { public: CMapEditManager(CMap * map); + ~CMapEditManager(); CMap * getMap(); /// Clears the terrain. The free level is filled with water and the underground level with rock. - void clearTerrain(CRandomGenerator * gen = nullptr); + void clearTerrain(vstd::RNG * gen); /// Draws terrain at the current terrain selection. The selection will be cleared automatically. - void drawTerrain(TerrainId terType, int decorationsPercentage, CRandomGenerator * gen = nullptr); + void drawTerrain(TerrainId terType, int decorationsPercentage, vstd::RNG * gen); /// Draws roads at the current terrain selection. The selection will be cleared automatically. - void drawRoad(RoadId roadType, CRandomGenerator * gen = nullptr); + void drawRoad(RoadId roadType, vstd::RNG * gen); /// Draws rivers at the current terrain selection. The selection will be cleared automatically. - void drawRiver(RiverId riverType, CRandomGenerator * gen = nullptr); + void drawRiver(RiverId riverType, vstd::RNG * gen); void insertObject(CGObjectInstance * obj); void insertObjects(std::set & objects); @@ -94,7 +99,7 @@ private: CMap * map; CMapUndoManager undoManager; - CRandomGenerator gen; + std::unique_ptr gen; CTerrainSelection terrainSel; CObjectSelection objectSel; }; diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 7c8e76c7c..7aaf268fc 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -12,12 +12,13 @@ #include "CMapOperation.h" #include "../VCMI_Lib.h" -#include "../CRandomGenerator.h" #include "../TerrainHandler.h" #include "../mapObjects/CGObjectInstance.h" #include "CMap.h" #include "MapEditUtils.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CMapOperation::CMapOperation(CMap* map) : map(map) @@ -87,7 +88,7 @@ void CComposedOperation::addOperation(std::unique_ptr&& operation operations.push_back(std::move(operation)); } -CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, CTerrainSelection terrainSel, TerrainId terType, int decorationsPercentage, CRandomGenerator * gen): +CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, CTerrainSelection terrainSel, TerrainId terType, int decorationsPercentage, vstd::RNG * gen): CMapOperation(map), terrainSel(std::move(terrainSel)), terType(terType), @@ -560,7 +561,7 @@ CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, std::stri } -CClearTerrainOperation::CClearTerrainOperation(CMap* map, CRandomGenerator* gen) : CComposedOperation(map) +CClearTerrainOperation::CClearTerrainOperation(CMap* map, vstd::RNG* gen) : CComposedOperation(map) { CTerrainSelection terrainSel(map); terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); diff --git a/lib/mapping/CMapOperation.h b/lib/mapping/CMapOperation.h index a9c36fe27..b081c8b58 100644 --- a/lib/mapping/CMapOperation.h +++ b/lib/mapping/CMapOperation.h @@ -17,7 +17,11 @@ VCMI_LIB_NAMESPACE_BEGIN class CGObjectInstance; class CMap; -class CRandomGenerator; + +namespace vstd +{ +class RNG; +} /// The abstract base class CMapOperation defines an operation that can be executed, undone and redone. class DLL_LINKAGE CMapOperation : public boost::noncopyable @@ -63,7 +67,7 @@ private: class CDrawTerrainOperation : public CMapOperation { public: - CDrawTerrainOperation(CMap * map, CTerrainSelection terrainSel, TerrainId terType, int decorationsPercentage, CRandomGenerator * gen); + CDrawTerrainOperation(CMap * map, CTerrainSelection terrainSel, TerrainId terType, int decorationsPercentage, vstd::RNG * gen); void execute() override; void undo() override; @@ -103,7 +107,7 @@ private: CTerrainSelection terrainSel; TerrainId terType; int decorationsPercentage; - CRandomGenerator* gen; + vstd::RNG* gen; std::set invalidatedTerViews; }; @@ -111,7 +115,7 @@ private: class CClearTerrainOperation : public CComposedOperation { public: - CClearTerrainOperation(CMap * map, CRandomGenerator * gen); + CClearTerrainOperation(CMap * map, vstd::RNG * gen); std::string getLabel() const override; }; diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index eaa966959..cc6ba42f5 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -813,7 +813,7 @@ JsonNode CMapLoaderJson::getFromArchive(const std::string & archiveFilename) auto data = loader.load(resource)->readAll(); - JsonNode res(reinterpret_cast(data.first.get()), data.second); + JsonNode res(reinterpret_cast(data.first.get()), data.second, archiveFilename); return res; } diff --git a/lib/mapping/ObstacleProxy.cpp b/lib/mapping/ObstacleProxy.cpp index e1f9fe024..10a22d508 100644 --- a/lib/mapping/ObstacleProxy.cpp +++ b/lib/mapping/ObstacleProxy.cpp @@ -18,6 +18,8 @@ #include "../mapObjects/ObstacleSetHandler.h" #include "../VCMI_Lib.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void ObstacleProxy::collectPossibleObstacles(TerrainId terrain) @@ -53,7 +55,7 @@ void ObstacleProxy::sortObstacles() }); } -bool ObstacleProxy::prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand) +bool ObstacleProxy::prepareBiome(const ObstacleSetFilter & filter, vstd::RNG & rand) { possibleObstacles.clear(); @@ -228,7 +230,7 @@ bool ObstacleProxy::isProhibited(const rmg::Area& objArea) const return false; }; -int ObstacleProxy::getWeightedObjects(const int3 & tile, CRandomGenerator & rand, IGameCallback * cb, std::list & allObjects, std::vector> & weightedObjects) +int ObstacleProxy::getWeightedObjects(const int3 & tile, vstd::RNG & rand, IGameCallback * cb, std::list & allObjects, std::vector> & weightedObjects) { int maxWeight = std::numeric_limits::min(); for(auto & possibleObstacle : possibleObstacles) @@ -309,7 +311,7 @@ int ObstacleProxy::getWeightedObjects(const int3 & tile, CRandomGenerator & rand return maxWeight; } -std::set ObstacleProxy::createObstacles(CRandomGenerator & rand, IGameCallback * cb) +std::set ObstacleProxy::createObstacles(vstd::RNG & rand, IGameCallback * cb) { //reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left auto blockedTiles = blockedArea.getTilesVector(); @@ -382,7 +384,7 @@ bool EditorObstaclePlacer::isInTheMap(const int3& tile) return map->isInTheMap(tile); } -std::set EditorObstaclePlacer::placeObstacles(CRandomGenerator & rand) +std::set EditorObstaclePlacer::placeObstacles(vstd::RNG & rand) { auto obstacles = createObstacles(rand, map->cb); finalInsertion(map->getEditManager(), obstacles); diff --git a/lib/mapping/ObstacleProxy.h b/lib/mapping/ObstacleProxy.h index 1bf4e4b72..b3dd2f35e 100644 --- a/lib/mapping/ObstacleProxy.h +++ b/lib/mapping/ObstacleProxy.h @@ -18,7 +18,6 @@ VCMI_LIB_NAMESPACE_BEGIN class CMapEditManager; class CGObjectInstance; class ObjectTemplate; -class CRandomGenerator; class IGameCallback; class ObstacleSetFilter; @@ -30,7 +29,7 @@ public: virtual ~ObstacleProxy() = default; void collectPossibleObstacles(TerrainId terrain); - bool prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand); + bool prepareBiome(const ObstacleSetFilter & filter, vstd::RNG & rand); void addBlockedTile(const int3 & tile); @@ -44,7 +43,7 @@ public: virtual void placeObject(rmg::Object & object, std::set & instances); - virtual std::set createObstacles(CRandomGenerator & rand, IGameCallback * cb); + virtual std::set createObstacles(vstd::RNG & rand, IGameCallback * cb); virtual bool isInTheMap(const int3& tile) = 0; @@ -53,7 +52,7 @@ public: virtual void postProcess(const rmg::Object& object) {}; protected: - int getWeightedObjects(const int3& tile, CRandomGenerator& rand, IGameCallback * cb, std::list& allObjects, std::vector>& weightedObjects); + int getWeightedObjects(const int3& tile, vstd::RNG& rand, IGameCallback * cb, std::list& allObjects, std::vector>& weightedObjects); void sortObstacles(); rmg::Area blockedArea; @@ -71,7 +70,7 @@ public: bool isInTheMap(const int3& tile) override; - std::set placeObstacles(CRandomGenerator& rand); + std::set placeObstacles(vstd::RNG& rand); private: CMap* map; diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 73d3d8d04..442a470bb 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -13,6 +13,7 @@ #include "PacksForClientBattle.h" #include "PacksForServer.h" #include "PacksForLobby.h" +#include "SetRewardableConfiguration.h" #include "SetStackEffect.h" VCMI_LIB_NAMESPACE_BEGIN @@ -34,6 +35,8 @@ public: virtual void visitTurnTimeUpdate(TurnTimeUpdate & pack) {} virtual void visitGamePause(GamePause & pack) {} virtual void visitEntitiesChanged(EntitiesChanged & pack) {} + virtual void visitSetRewardableConfiguration(SetRewardableConfiguration & pack) {} + virtual void visitSetBankConfiguration(SetBankConfiguration & pack) {} virtual void visitSetResources(SetResources & pack) {} virtual void visitSetPrimSkill(SetPrimSkill & pack) {} virtual void visitSetSecSkill(SetSecSkill & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 4b0557d28..8fcc759d7 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -12,6 +12,7 @@ #include "PacksForClient.h" #include "PacksForClientBattle.h" #include "PacksForServer.h" +#include "SetRewardableConfiguration.h" #include "StackLocation.h" #include "PacksForLobby.h" #include "SetStackEffect.h" @@ -32,6 +33,7 @@ #include "StartInfo.h" #include "CPlayerState.h" #include "TerrainHandler.h" +#include "mapObjects/CBank.h" #include "mapObjects/CGCreature.h" #include "mapObjects/CGMarket.h" #include "mapObjects/CGTownInstance.h" @@ -123,6 +125,16 @@ void EntitiesChanged::visitTyped(ICPackVisitor & visitor) visitor.visitEntitiesChanged(*this); } +void SetRewardableConfiguration::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSetRewardableConfiguration(*this); +} + +void SetBankConfiguration::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSetBankConfiguration(*this); +} + void SetResources::visitTyped(ICPackVisitor & visitor) { visitor.visitSetResources(*this); @@ -1415,7 +1427,6 @@ void HeroRecruited::applyGs(CGameState * gs) const h->setOwner(player); h->pos = tile; - h->initObj(gs->getRandomGenerator()); if(h->id == ObjectInstanceID()) { @@ -1469,59 +1480,13 @@ void GiveHero::applyGs(CGameState * gs) const void NewObject::applyGs(CGameState *gs) { - TerrainId terrainType = ETerrainId::NONE; + newObject->id = ObjectInstanceID(static_cast(gs->map->objects.size())); - if (!gs->isInTheMap(targetPos)) - { - logGlobal->error("Attempt to create object outside map at %s!", targetPos.toString()); - return; - } - - const TerrainTile & t = gs->map->getTile(targetPos); - terrainType = t.terType->getId(); - - auto handler = VLC->objtypeh->getHandlerFor(ID, subID); - - CGObjectInstance * o = handler->create(gs->callback, nullptr); - handler->configureObject(o, gs->getRandomGenerator()); - assert(o->ID == this->ID); - - if (ID == Obj::MONSTER) //probably more options will be needed - { - //CStackInstance hlp; - auto * cre = dynamic_cast(o); - //cre->slots[0] = hlp; - assert(cre); - cre->notGrowingTeam = cre->neverFlees = false; - cre->character = 2; - cre->gainedArtifact = ArtifactID::NONE; - cre->identifier = -1; - cre->addToSlot(SlotID(0), new CStackInstance(subID.getNum(), -1)); //add placeholder stack - } - - assert(!handler->getTemplates(terrainType).empty()); - if (handler->getTemplates().empty()) - { - logGlobal->error("Attempt to create object (%d %d) with no templates!", ID, subID.getNum()); - return; - } - - if (!handler->getTemplates(terrainType).empty()) - o->appearance = handler->getTemplates(terrainType).front(); - else - o->appearance = handler->getTemplates().front(); - - o->id = ObjectInstanceID(static_cast(gs->map->objects.size())); - o->pos = targetPos + o->getVisitableOffset(); - - gs->map->objects.emplace_back(o); - gs->map->addBlockVisTiles(o); - o->initObj(gs->getRandomGenerator()); + gs->map->objects.emplace_back(newObject); + gs->map->addBlockVisTiles(newObject); gs->map->calculateGuardingGreaturePositions(); - createdObjectID = o->id; - - logGlobal->debug("Added object id=%d; address=%x; name=%s", o->id, (intptr_t)o, o->getObjectName()); + logGlobal->debug("Added object id=%d; address=%x; name=%s", newObject->id, (intptr_t)newObject, newObject->getObjectName()); } void NewArtifact::applyGs(CGameState *gs) @@ -1984,8 +1949,8 @@ void NewTurn::applyGs(CGameState *gs) for(CGTownInstance* t : gs->map->towns) t->built = 0; - if(gs->getDate(Date::DAY_OF_WEEK) == 1) - gs->updateRumor(); + if(newRumor) + gs->currentRumor = *newRumor; } void SetObjectProperty::applyGs(CGameState * gs) const @@ -2479,6 +2444,26 @@ void EntitiesChanged::applyGs(CGameState * gs) gs->updateEntity(change.metatype, change.entityIndex, change.data); } +void SetRewardableConfiguration::applyGs(CGameState * gs) +{ + auto * objectPtr = gs->getObjInstance(objectID); + auto * rewardablePtr = dynamic_cast(objectPtr); + + assert(rewardablePtr); + + rewardablePtr->configuration = configuration; +} + +void SetBankConfiguration::applyGs(CGameState * gs) +{ + auto * objectPtr = gs->getObjInstance(objectID); + auto * bankPtr = dynamic_cast(objectPtr); + + assert(bankPtr); + + bankPtr->setConfig(configuration); +} + const CArtifactInstance * ArtSlotInfo::getArt() const { if(locked) diff --git a/lib/networkPacks/ObjProperty.h b/lib/networkPacks/ObjProperty.h index 67029d375..1e94c24c6 100644 --- a/lib/networkPacks/ObjProperty.h +++ b/lib/networkPacks/ObjProperty.h @@ -43,11 +43,9 @@ enum class ObjProperty : int8_t //creature-bank specific BANK_DAYCOUNTER, - BANK_RESET, BANK_CLEAR, //object with reward - REWARD_RANDOMIZE, REWARD_SELECT, REWARD_CLEARED }; diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index d01ac40a7..7ad75413b 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -22,6 +22,7 @@ #include "../ResourceSet.h" #include "../TurnTimerInfo.h" #include "../gameState/EVictoryLossCheckResult.h" +#include "../gameState/RumorState.h" #include "../gameState/QuestInfo.h" #include "../gameState/TavernSlot.h" #include "../int3.h" @@ -786,23 +787,15 @@ struct DLL_LINKAGE NewObject : public CPackForClient void applyGs(CGameState * gs); /// Object ID to create - MapObjectID ID; - /// Object secondary ID to create - MapObjectSubID subID; - /// Position of visitable tile of created object - int3 targetPos; + CGObjectInstance * newObject; /// Which player initiated creation of this object PlayerColor initiator; - ObjectInstanceID createdObjectID; //used locally, filled during applyGs - void visitTyped(ICPackVisitor & visitor) override; template void serialize(Handler & h) { - h & ID; - subID.serializeIdentifier(h, ID); - h & targetPos; + h & newObject; h & initiator; } }; @@ -1169,6 +1162,7 @@ struct DLL_LINKAGE NewTurn : public CPackForClient ui32 day = 0; ui8 specialWeek = 0; //weekType CreatureID creatureid; //for creature weeks + std::optional newRumor; // only on new weeks NewTurn() = default; @@ -1180,6 +1174,7 @@ struct DLL_LINKAGE NewTurn : public CPackForClient h & day; h & specialWeek; h & creatureid; + h & newRumor; } }; diff --git a/lib/networkPacks/SetRewardableConfiguration.h b/lib/networkPacks/SetRewardableConfiguration.h new file mode 100644 index 000000000..1749e6f66 --- /dev/null +++ b/lib/networkPacks/SetRewardableConfiguration.h @@ -0,0 +1,51 @@ +/* + * SetRewardableConfiguration.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetPacksBase.h" + +#include "../rewardable/Configuration.h" +#include "../mapObjectConstructors/CBankInstanceConstructor.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct DLL_LINKAGE SetRewardableConfiguration : public CPackForClient +{ + void applyGs(CGameState * gs); + void visitTyped(ICPackVisitor & visitor) override; + + ObjectInstanceID objectID; + BuildingID buildingID; + Rewardable::Configuration configuration; + + template void serialize(Handler & h) + { + h & objectID; + h & buildingID; + h & configuration; + } +}; + +struct DLL_LINKAGE SetBankConfiguration : public CPackForClient +{ + void applyGs(CGameState * gs); + void visitTyped(ICPackVisitor & visitor) override; + + ObjectInstanceID objectID; + BankConfig configuration; + + template void serialize(Handler & h) + { + h & objectID; + h & configuration; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h index a850761b1..2598633cc 100644 --- a/lib/pathfinder/CGPathNode.h +++ b/lib/pathfinder/CGPathNode.h @@ -59,18 +59,22 @@ enum class EPathNodeAction : ui8 struct DLL_LINKAGE CGPathNode { + using TFibHeap = boost::heap::fibonacci_heap>>; using ELayer = EPathfindingLayer; + TFibHeap::handle_type pqHandle; + TFibHeap * pq; CGPathNode * theNodeBefore; + int3 coord; //coordinates ELayer layer; + + float cost; //total cost of the path to this tile measured in turns with fractions int moveRemains; //remaining movement points after hero reaches the tile ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn - EPathAccessibility accessible; EPathNodeAction action; bool locked; - bool inPQ; CGPathNode() : coord(-1), @@ -89,9 +93,14 @@ struct DLL_LINKAGE CGPathNode cost = std::numeric_limits::max(); turns = 255; theNodeBefore = nullptr; - action = EPathNodeAction::UNKNOWN; - inPQ = false; pq = nullptr; + action = EPathNodeAction::UNKNOWN; + } + + STRONG_INLINE + bool inPQ() const + { + return pq != nullptr; } STRONG_INLINE @@ -109,7 +118,7 @@ struct DLL_LINKAGE CGPathNode bool getUpNode = value < cost; cost = value; // If the node is in the heap, update the heap. - if(inPQ && pq != nullptr) + if(inPQ()) { if(getUpNode) { @@ -155,14 +164,6 @@ struct DLL_LINKAGE CGPathNode return true; } - - using TFibHeap = boost::heap::fibonacci_heap>>; - - TFibHeap::handle_type pqHandle; - TFibHeap* pq; - -private: - float cost; //total cost of the path to this tile measured in turns with fractions }; struct DLL_LINKAGE CGPath diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index a4a725d35..cefb55673 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -82,9 +82,8 @@ CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr con void CPathfinder::push(CGPathNode * node) { - if(node && !node->inPQ) + if(node && !node->inPQ()) { - node->inPQ = true; node->pq = &this->pq; auto handle = pq.push(node); node->pqHandle = handle; @@ -96,7 +95,6 @@ CGPathNode * CPathfinder::topAndPop() auto * node = pq.top(); pq.pop(); - node->inPQ = false; node->pq = nullptr; return node; } diff --git a/lib/registerTypes/RegisterTypesClientPacks.h b/lib/registerTypes/RegisterTypesClientPacks.h index d989facd6..af64194ab 100644 --- a/lib/registerTypes/RegisterTypesClientPacks.h +++ b/lib/registerTypes/RegisterTypesClientPacks.h @@ -12,6 +12,7 @@ #include "../networkPacks/PacksForClient.h" #include "../networkPacks/PacksForClientBattle.h" #include "../networkPacks/SetStackEffect.h" +#include "../networkPacks/SetRewardableConfiguration.h" VCMI_LIB_NAMESPACE_BEGIN @@ -119,6 +120,9 @@ void registerTypesClientPacks(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + + s.template registerType(); + s.template registerType(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index 14b73fa6c..4e5a40d2a 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -20,7 +20,8 @@ #include "../json/JsonRandom.h" #include "../mapObjects/IObjectInterface.h" #include "../modding/IdentifierStorage.h" -#include "../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -106,7 +107,7 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o loadString(parameters["onEmptyMessage"], TextIdentifier(objectName, "onEmpty")); } -Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, const JsonNode & source) const +Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, const JsonNode & source) const { Rewardable::LimitersList result; for (const auto & input : source.Vector()) @@ -121,7 +122,7 @@ Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Conf return result; } -void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const +void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const { auto const & variables = object.variables.values; JsonRandom randomizer(cb); @@ -153,7 +154,7 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan limiter.noneOf = configureSublimiters(object, rng, cb, source["noneOf"] ); } -void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, Rewardable::Reward & reward, const JsonNode & source) const +void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, Rewardable::Reward & reward, const JsonNode & source) const { auto const & variables = object.variables.values; JsonRandom randomizer(cb); @@ -210,14 +211,14 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRand } } -void Rewardable::Info::configureResetInfo(Rewardable::Configuration & object, CRandomGenerator & rng, Rewardable::ResetInfo & resetParameters, const JsonNode & source) const +void Rewardable::Info::configureResetInfo(Rewardable::Configuration & object, vstd::RNG & rng, Rewardable::ResetInfo & resetParameters, const JsonNode & source) const { resetParameters.period = static_cast(source["period"].Float()); resetParameters.visitors = source["visitors"].Bool(); resetParameters.rewards = source["rewards"].Bool(); } -void Rewardable::Info::configureVariables(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, const JsonNode & source) const +void Rewardable::Info::configureVariables(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, const JsonNode & source) const { JsonRandom randomizer(cb); @@ -277,7 +278,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab void Rewardable::Info::configureRewards( Rewardable::Configuration & object, - CRandomGenerator & rng, + vstd::RNG & rng, IGameCallback * cb, const JsonNode & source, Rewardable::EEventType event, @@ -298,7 +299,7 @@ void Rewardable::Info::configureRewards( { const JsonNode & preset = object.getPresetVariable("dice", diceID); if (preset.isNull()) - object.initVariable("dice", diceID, rng.getIntRange(0, 99)()); + object.initVariable("dice", diceID, rng.nextInt(0, 99)); else object.initVariable("dice", diceID, preset.Integer()); @@ -335,7 +336,7 @@ void Rewardable::Info::configureRewards( } } -void Rewardable::Info::configureObject(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb) const +void Rewardable::Info::configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb) const { object.info.clear(); diff --git a/lib/rewardable/Info.h b/lib/rewardable/Info.h index e2bb4322f..bad4f5031 100644 --- a/lib/rewardable/Info.h +++ b/lib/rewardable/Info.h @@ -15,13 +15,16 @@ VCMI_LIB_NAMESPACE_BEGIN -class CRandomGenerator; +namespace vstd +{ +class RNG; +} + class MetaString; class IGameCallback; namespace Rewardable { - struct Limiter; using LimitersList = std::vector>; struct Reward; @@ -39,14 +42,14 @@ class DLL_LINKAGE Info : public IObjectInfo void replaceTextPlaceholders(MetaString & target, const Variables & variables) const; void replaceTextPlaceholders(MetaString & target, const Variables & variables, const VisitInfo & info) const; - void configureVariables(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, const JsonNode & source) const; - void configureRewards(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, const JsonNode & source, Rewardable::EEventType mode, const std::string & textPrefix) const; + void configureVariables(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, const JsonNode & source) const; + void configureRewards(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, const JsonNode & source, Rewardable::EEventType mode, const std::string & textPrefix) const; - void configureLimiter(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const; - Rewardable::LimitersList configureSublimiters(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, const JsonNode & source) const; + void configureLimiter(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const; + Rewardable::LimitersList configureSublimiters(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, const JsonNode & source) const; - void configureReward(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb, Rewardable::Reward & info, const JsonNode & source) const; - void configureResetInfo(Rewardable::Configuration & object, CRandomGenerator & rng, Rewardable::ResetInfo & info, const JsonNode & source) const; + void configureReward(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb, Rewardable::Reward & info, const JsonNode & source) const; + void configureResetInfo(Rewardable::Configuration & object, vstd::RNG & rng, Rewardable::ResetInfo & info, const JsonNode & source) const; public: const JsonNode & getParameters() const; @@ -65,7 +68,7 @@ public: bool givesBonuses() const override; - void configureObject(Rewardable::Configuration & object, CRandomGenerator & rng, IGameCallback * cb) const; + void configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb) const; void init(const JsonNode & objectConfig, const std::string & objectTextID); diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 622bbdc3f..a12178d3b 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -14,11 +14,12 @@ #include "../mapping/CMapHeader.h" #include "CRmgTemplateStorage.h" #include "CRmgTemplate.h" -#include "CRandomGenerator.h" #include "../VCMI_Lib.h" #include "../CTownHandler.h" #include "serializer/JsonSerializeFormat.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CMapGenOptions::CMapGenOptions() @@ -487,7 +488,7 @@ void CMapGenOptions::setPlayerTeam(const PlayerColor & color, const TeamID & tea customizedPlayers = true; } -void CMapGenOptions::finalize(CRandomGenerator & rand) +void CMapGenOptions::finalize(vstd::RNG & rand) { logGlobal->info("RMG map: %dx%d, %s underground", getWidth(), getHeight(), getHasTwoLevels() ? "WITH" : "NO"); logGlobal->info("RMG settings: players %d, teams %d, computer players %d, computer teams %d, water %d, monsters %d", @@ -690,8 +691,7 @@ bool CMapGenOptions::checkOptions() const } else { - CRandomGenerator gen; - return getPossibleTemplate(gen) != nullptr; + return !getPossibleTemplates().empty(); } } @@ -750,7 +750,7 @@ std::vector CMapGenOptions::getPossibleTemplates() const return templates; } -const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand) const +const CRmgTemplate * CMapGenOptions::getPossibleTemplate(vstd::RNG & rand) const { auto templates = getPossibleTemplates(); diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index b4c613b56..d82083553 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -16,7 +16,10 @@ VCMI_LIB_NAMESPACE_BEGIN -class CRandomGenerator; +namespace vstd +{ +class RNG; +} enum class EPlayerType { @@ -148,7 +151,7 @@ public: /// Finalizes the options. All random sizes for various properties will be overwritten by numbers from /// a random number generator by keeping the options in a valid state. Check options should return true, otherwise /// this function fails. - void finalize(CRandomGenerator & rand); + void finalize(vstd::RNG & rand); /// Returns false if there is no template available which fits to the currently selected options. bool checkOptions() const; @@ -166,7 +169,7 @@ private: PlayerColor getNextPlayerColor() const; void updateCompOnlyPlayers(); void updatePlayers(); - const CRmgTemplate * getPossibleTemplate(CRandomGenerator & rand) const; + const CRmgTemplate * getPossibleTemplate(vstd::RNG & rand) const; si32 width; si32 height; diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index d7d41aa2e..9807d60d6 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -14,6 +14,7 @@ #include "../mapping/MapFormat.h" #include "../VCMI_Lib.h" #include "../CGeneralTextHandler.h" +#include "../CRandomGenerator.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../mapping/CMapEditManager.h" @@ -32,15 +33,17 @@ #include "modificators/TreasurePlacer.h" #include "modificators/RoadPlacer.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, IGameCallback * cb, int RandomSeed) : mapGenOptions(mapGenOptions), randomSeed(RandomSeed), - monolithIndex(0) + monolithIndex(0), + rand(std::make_unique(RandomSeed)) { loadConfig(); - rand.setSeed(this->randomSeed); - mapGenOptions.finalize(rand); + mapGenOptions.finalize(*rand); map = std::make_unique(mapGenOptions, cb); placer = std::make_shared(*map); } @@ -116,7 +119,7 @@ std::unique_ptr CMapGenerator::generate() try { addHeaderInfo(); - map->initTiles(*this, rand); + map->initTiles(*this, *rand); Load::Progress::step(); initQuestArtsRemaining(); genZones(); @@ -286,7 +289,7 @@ void CMapGenerator::addPlayerInfo() logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human")); assert (teamNumbers[j].size()); } - auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand); + auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], *rand); player.team = TeamID(*itTeam); teamNumbers[j].erase(itTeam); } @@ -306,8 +309,8 @@ void CMapGenerator::addPlayerInfo() void CMapGenerator::genZones() { - placer->placeZones(&rand); - placer->assignZones(&rand); + placer->placeZones(rand.get()); + placer->assignZones(rand.get()); logGlobal->info("Zones generated successfully"); } @@ -428,9 +431,9 @@ void CMapGenerator::fillZones() if (it.second->getType() != ETemplateZoneType::WATER) treasureZones.push_back(it.second); } - auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand); + auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, *rand); - map->getMap(this).grailPos = *RandomGeneratorUtil::nextItem(grailZone->freePaths()->getTiles(), rand); + map->getMap(this).grailPos = *RandomGeneratorUtil::nextItem(grailZone->freePaths()->getTiles(), *rand); map->getMap(this).reindexObjects(); logGlobal->info("Zones filled successfully"); diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index e500016fa..c1971bdba 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -11,7 +11,6 @@ #pragma once #include "../GameConstants.h" -#include "../CRandomGenerator.h" #include "CMapGenOptions.h" #include "../int3.h" #include "CRmgTemplate.h" @@ -79,7 +78,7 @@ public: int getRandomSeed() const; private: - CRandomGenerator rand; + std::unique_ptr rand; int randomSeed; CMapGenOptions& mapGenOptions; Config config; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index e2af8582a..645a87276 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -11,7 +11,6 @@ #include "StdInc.h" #include "CZonePlacer.h" -#include "../CRandomGenerator.h" #include "../CTownHandler.h" #include "../TerrainHandler.h" #include "../mapping/CMap.h" @@ -23,12 +22,12 @@ #include "Functions.h" #include "PenroseTiling.h" +#include + VCMI_LIB_NAMESPACE_BEGIN //#define ZONE_PLACEMENT_LOG true -class CRandomGenerator; - CZonePlacer::CZonePlacer(RmgMap & map) : width(0), height(0), mapSize(0), gravityConstant(1e-3f), @@ -97,7 +96,7 @@ void CZonePlacer::findPathsBetweenZones() } } -void CZonePlacer::placeOnGrid(CRandomGenerator* rand) +void CZonePlacer::placeOnGrid(vstd::RNG* rand) { auto zones = map.getZones(); assert(zones.size()); @@ -118,7 +117,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) { - switch (rand->nextInt() % 4) + switch (rand->nextInt(0, 3) % 4) { case 0: x = 0; @@ -150,7 +149,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) else { //Random corner - if (rand->nextInt() % 2) + if (rand->nextInt(0, 1) == 1) { x = 0; } @@ -158,7 +157,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) { x = gridSize - 1; } - if (rand->nextInt() % 2) + if (rand->nextInt(0, 1) == 1) { y = 0; } @@ -176,8 +175,8 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) else { //One of 4 squares in the middle - x = (gridSize / 2) - 1 + rand->nextInt() % 2; - y = (gridSize / 2) - 1 + rand->nextInt() % 2; + x = (gridSize / 2) - 1 + rand->nextInt(0, 1); + y = (gridSize / 2) - 1 + rand->nextInt(0, 1); } break; case ETemplateZoneType::JUNCTION: @@ -308,7 +307,7 @@ float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, con } } -void CZonePlacer::placeZones(CRandomGenerator * rand) +void CZonePlacer::placeZones(vstd::RNG * rand) { logGlobal->info("Starting zone placement"); @@ -432,7 +431,7 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) } } -void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand) +void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) { std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map @@ -824,7 +823,7 @@ float CZonePlacer::metric (const int3 &A, const int3 &B) const } -void CZonePlacer::assignZones(CRandomGenerator * rand) +void CZonePlacer::assignZones(vstd::RNG * rand) { logGlobal->info("Starting zone colouring"); diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 414f79b3c..43e3479df 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -16,9 +16,13 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + class CZoneGraph; class CMap; -class CRandomGenerator; class RmgMap; class Zone; @@ -37,16 +41,16 @@ public: float getDistance(float distance) const; //additional scaling without 0 division ~CZonePlacer() = default; - void placeZones(CRandomGenerator * rand); + void placeZones(vstd::RNG * rand); void findPathsBetweenZones(); - void placeOnGrid(CRandomGenerator* rand); + void placeOnGrid(vstd::RNG* rand); float scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const; - void assignZones(CRandomGenerator * rand); + void assignZones(vstd::RNG * rand); const TDistanceMap & getDistanceMap(); private: - void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand); + void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand); void attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const; void separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps); void moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDistanceVector & distances, TDistanceVector & overlaps); diff --git a/lib/rmg/Functions.cpp b/lib/rmg/Functions.cpp index 4147aafb6..208877109 100644 --- a/lib/rmg/Functions.cpp +++ b/lib/rmg/Functions.cpp @@ -21,6 +21,8 @@ #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../VCMI_Lib.h" +#include + VCMI_LIB_NAMESPACE_BEGIN rmg::Tileset collectDistantTiles(const Zone& zone, int distance) @@ -34,7 +36,7 @@ rmg::Tileset collectDistantTiles(const Zone& zone, int distance) return subarea.getTiles(); } -int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, TerrainId terrain) +int chooseRandomAppearance(vstd::RNG & generator, si32 ObjID, TerrainId terrain) { auto factories = VLC->objtypeh->knownSubObjects(ObjID); vstd::erase_if(factories, [ObjID, &terrain](si32 f) diff --git a/lib/rmg/Functions.h b/lib/rmg/Functions.h index 0bcaee0d7..2756d0897 100644 --- a/lib/rmg/Functions.h +++ b/lib/rmg/Functions.h @@ -19,7 +19,6 @@ class RmgMap; class ObjectManager; class ObjectTemplate; class CMapGenerator; -class CRandomGenerator; class rmgException : public std::exception { @@ -37,7 +36,7 @@ public: rmg::Tileset collectDistantTiles(const Zone & zone, int distance); -int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, TerrainId terrain); +int chooseRandomAppearance(vstd::RNG & generator, si32 ObjID, TerrainId terrain); VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 8f4091cd2..9df43f866 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -13,6 +13,8 @@ #include "StdInc.h" #include "PenroseTiling.h" +#include + VCMI_LIB_NAMESPACE_BEGIN @@ -143,7 +145,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, return; } -std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) +std::set PenroseTiling::generatePenroseTiling(size_t numZones, vstd::RNG * rand) { float scale = 173.2f / (numZones * 1.5f + 20); float polyAngle = (2 * PI_CONSTANT) / POLY; diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index 8b6ace8ed..18a8a3f40 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -11,12 +11,16 @@ #pragma once #include "../GameConstants.h" -#include "../CRandomGenerator.h" #include #include VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + using namespace boost::geometry; typedef std::array TIndices; @@ -66,11 +70,11 @@ public: const bool P2 = false; // Tiling type - std::set generatePenroseTiling(size_t numZones, CRandomGenerator * rand); + std::set generatePenroseTiling(size_t numZones, vstd::RNG * rand); private: void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp index 790a09a24..f83655f51 100644 --- a/lib/rmg/RmgMap.cpp +++ b/lib/rmg/RmgMap.cpp @@ -84,7 +84,7 @@ void RmgMap::foreachDiagonalNeighbour(const int3 & pos, const std::functioninitTerrain(); diff --git a/lib/rmg/RmgMap.h b/lib/rmg/RmgMap.h index 6caff8236..24510dee5 100644 --- a/lib/rmg/RmgMap.h +++ b/lib/rmg/RmgMap.h @@ -17,7 +17,6 @@ VCMI_LIB_NAMESPACE_BEGIN class CMap; class CMapEditManager; -class CRandomGenerator; class TileInfo; class CMapGenOptions; class Zone; @@ -25,6 +24,11 @@ class CMapGenerator; class MapProxy; class playerInfo; +namespace vstd +{ +class RNG; +} + class RmgMap { public: @@ -79,7 +83,7 @@ public: void registerZone(FactionID faction); ui32 getZoneCount(FactionID faction); ui32 getTotalZoneCount() const; - void initTiles(CMapGenerator & generator, CRandomGenerator & rand); + void initTiles(CMapGenerator & generator, vstd::RNG & rand); void addModificators(); bool isAllowedSpell(const SpellID & sid) const; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 0d89e1b74..1004072cb 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -21,6 +21,8 @@ #include "Functions.h" #include "../TerrainHandler.h" +#include + VCMI_LIB_NAMESPACE_BEGIN using namespace rmg; @@ -111,7 +113,7 @@ void Object::Instance::setPositionRaw(const int3 & position) dObject.pos = dPosition + dParent.getPosition(); } -void Object::Instance::setAnyTemplate(CRandomGenerator & rng) +void Object::Instance::setAnyTemplate(vstd::RNG & rng) { auto templates = dObject.getObjectHandler()->getTemplates(); if(templates.empty()) @@ -122,7 +124,7 @@ void Object::Instance::setAnyTemplate(CRandomGenerator & rng) setPosition(getPosition(false)); } -void Object::Instance::setTemplate(TerrainId terrain, CRandomGenerator & rng) +void Object::Instance::setTemplate(TerrainId terrain, vstd::RNG & rng) { auto templates = dObject.getObjectHandler()->getMostSpecificTemplates(terrain); @@ -366,7 +368,7 @@ void Object::setPosition(const int3 & position) i.setPositionRaw(i.getPosition()); } -void Object::setTemplate(const TerrainId & terrain, CRandomGenerator & rng) +void Object::setTemplate(const TerrainId & terrain, vstd::RNG & rng) { for(auto& i : dInstances) i.setTemplate(terrain, rng); @@ -474,7 +476,7 @@ rmg::Area Object::Instance::getBorderAbove() const return borderAbove; } -void Object::Instance::finalize(RmgMap & map, CRandomGenerator & rng) +void Object::Instance::finalize(RmgMap & map, vstd::RNG & rng) { if(!map.isOnMap(getPosition(true))) throw rmgException(boost::str(boost::format("Position of object %d at %s is outside the map") % dObject.id % getPosition(true).toString())); @@ -511,7 +513,7 @@ void Object::Instance::finalize(RmgMap & map, CRandomGenerator & rng) map.getMapProxy()->insertObject(&dObject); } -void Object::finalize(RmgMap & map, CRandomGenerator & rng) +void Object::finalize(RmgMap & map, vstd::RNG & rng) { if(dInstances.empty()) throw rmgException("Cannot finalize object without instances"); diff --git a/lib/rmg/RmgObject.h b/lib/rmg/RmgObject.h index ffdfb0a89..c15256fb2 100644 --- a/lib/rmg/RmgObject.h +++ b/lib/rmg/RmgObject.h @@ -16,8 +16,12 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace vstd +{ +class RNG; +} + class CGObjectInstance; -class CRandomGenerator; class RmgMap; namespace rmg { @@ -39,8 +43,8 @@ public: bool isRemovable() const; const Area & getAccessibleArea() const; Area getBorderAbove() const; - void setTemplate(TerrainId terrain, CRandomGenerator &); //cache invalidation - void setAnyTemplate(CRandomGenerator &); //cache invalidation + void setTemplate(TerrainId terrain, vstd::RNG &); //cache invalidation + void setAnyTemplate(vstd::RNG &); //cache invalidation int3 getTopTile() const; int3 getPosition(bool isAbsolute = false) const; @@ -49,7 +53,7 @@ public: const CGObjectInstance & object() const; CGObjectInstance & object(); - void finalize(RmgMap & map, CRandomGenerator &); //cache invalidation + void finalize(RmgMap & map, vstd::RNG &); //cache invalidation void clear(); std::function onCleared; @@ -83,7 +87,7 @@ public: const int3 & getPosition() const; void setPosition(const int3 & position); - void setTemplate(const TerrainId & terrain, CRandomGenerator &); + void setTemplate(const TerrainId & terrain, vstd::RNG &); const Area & getArea() const; //lazy cache invalidation const int3 getVisibleTop() const; @@ -94,7 +98,7 @@ public: void setValue(uint32_t value); uint32_t getValue() const; - void finalize(RmgMap & map, CRandomGenerator &); + void finalize(RmgMap & map, vstd::RNG &); void clearCachedArea() const; void clear(); diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 5bc88a8d7..273dc226e 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -17,6 +17,10 @@ #include "RmgPath.h" #include "modificators/ObjectManager.h" +#include "../CRandomGenerator.h" + +#include + VCMI_LIB_NAMESPACE_BEGIN const std::function AREA_NO_FILTER = [](const int3 & t) @@ -24,16 +28,18 @@ const std::function AREA_NO_FILTER = [](const int3 & t) return true; }; -Zone::Zone(RmgMap & map, CMapGenerator & generator, CRandomGenerator & r) +Zone::Zone(RmgMap & map, CMapGenerator & generator, vstd::RNG & r) : finished(false) , townType(ETownType::NEUTRAL) , terrainType(ETerrainId::GRASS) , map(map) + , rand(std::make_unique(r.nextInt())) , generator(generator) { - rand.setSeed(r.nextInt()); } +Zone::~Zone() = default; + bool Zone::isUnderground() const { return getPos().z; @@ -401,9 +407,9 @@ void Zone::initModificators() } } -CRandomGenerator& Zone::getRand() +vstd::RNG& Zone::getRand() { - return rand; + return *rand; } VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/Zone.h b/lib/rmg/Zone.h index 73a985363..4ae2c7a13 100644 --- a/lib/rmg/Zone.h +++ b/lib/rmg/Zone.h @@ -13,7 +13,6 @@ #include "../GameConstants.h" #include "float3.h" #include "../int3.h" -#include "../CRandomGenerator.h" #include "CRmgTemplate.h" #include "RmgArea.h" #include "RmgPath.h" @@ -28,7 +27,6 @@ VCMI_LIB_NAMESPACE_BEGIN class RmgMap; class CMapGenerator; class Modificator; -class CRandomGenerator; extern const std::function AREA_NO_FILTER; @@ -74,8 +72,9 @@ private: class Zone : public rmg::ZoneOptions { public: - Zone(RmgMap & map, CMapGenerator & generator, CRandomGenerator & rand); + Zone(RmgMap & map, CMapGenerator & generator, vstd::RNG & rand); Zone(const Zone &) = delete; + ~Zone(); void setOptions(const rmg::ZoneOptions & options); bool isUnderground() const; @@ -127,14 +126,14 @@ public: void initModificators(); - CRandomGenerator & getRand(); + vstd::RNG & getRand(); public: mutable boost::recursive_mutex areaMutex; using Lock = boost::unique_lock; protected: CMapGenerator & generator; - CRandomGenerator rand; + std::unique_ptr rand; RmgMap & map; TModificators modificators; bool finished; diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index e6692e260..d6bc3c2c5 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -26,6 +26,8 @@ #include "WaterProxy.h" #include "TownPlacer.h" +#include + VCMI_LIB_NAMESPACE_BEGIN std::pair ConnectionsPlacer::lockZones(std::shared_ptr otherZone) @@ -444,7 +446,7 @@ void ConnectionsPlacer::collectNeighbourZones() bool ConnectionsPlacer::shouldGenerateRoad(const rmg::ZoneConnection& connection) const { return connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE || - (connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM && zone.getRand().nextDouble() >= 0.5f); + (connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM && zone.getRand().nextDouble(0, 1) >= 0.5f); } void ConnectionsPlacer::createBorder() diff --git a/lib/rmg/modificators/MinePlacer.cpp b/lib/rmg/modificators/MinePlacer.cpp index b0dc77a9e..4dcc2b41a 100644 --- a/lib/rmg/modificators/MinePlacer.cpp +++ b/lib/rmg/modificators/MinePlacer.cpp @@ -22,6 +22,8 @@ #include "WaterAdopter.h" #include "../TileInfo.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void MinePlacer::process() diff --git a/lib/rmg/modificators/ObjectDistributor.cpp b/lib/rmg/modificators/ObjectDistributor.cpp index df4723cd3..ae6c42f65 100644 --- a/lib/rmg/modificators/ObjectDistributor.cpp +++ b/lib/rmg/modificators/ObjectDistributor.cpp @@ -25,6 +25,8 @@ #include "../Functions.h" #include "../RmgObject.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void ObjectDistributor::process() diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index c55bd60b5..a15ef0937 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -29,6 +29,8 @@ #include "../Functions.h" #include "../RmgObject.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void ObjectManager::process() diff --git a/lib/rmg/modificators/ObstaclePlacer.cpp b/lib/rmg/modificators/ObstaclePlacer.cpp index 37afc1477..621b985e7 100644 --- a/lib/rmg/modificators/ObstaclePlacer.cpp +++ b/lib/rmg/modificators/ObstaclePlacer.cpp @@ -19,7 +19,6 @@ #include "RiverPlacer.h" #include "../RmgMap.h" #include "../CMapGenerator.h" -#include "../../CRandomGenerator.h" #include "../Functions.h" #include "../../mapping/CMapEditManager.h" #include "../../mapping/CMap.h" diff --git a/lib/rmg/modificators/PrisonHeroPlacer.cpp b/lib/rmg/modificators/PrisonHeroPlacer.cpp index 3aafc0eda..9d801e098 100644 --- a/lib/rmg/modificators/PrisonHeroPlacer.cpp +++ b/lib/rmg/modificators/PrisonHeroPlacer.cpp @@ -17,7 +17,9 @@ #include "../../VCMI_Lib.h" #include "../../mapObjectConstructors/AObjectTypeHandler.h" #include "../../mapObjectConstructors/CObjectClassesHandler.h" -#include "../../mapObjects/MapObjects.h" +#include "../../mapObjects/MapObjects.h" + +#include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/modificators/PrisonHeroPlacer.h b/lib/rmg/modificators/PrisonHeroPlacer.h index c302daa4e..6fc379da6 100644 --- a/lib/rmg/modificators/PrisonHeroPlacer.h +++ b/lib/rmg/modificators/PrisonHeroPlacer.h @@ -15,8 +15,6 @@ VCMI_LIB_NAMESPACE_BEGIN -class CRandomGenerator; - class PrisonHeroPlacer : public Modificator { public: diff --git a/lib/rmg/modificators/QuestArtifactPlacer.cpp b/lib/rmg/modificators/QuestArtifactPlacer.cpp index 912ee58c9..9117ab34c 100644 --- a/lib/rmg/modificators/QuestArtifactPlacer.cpp +++ b/lib/rmg/modificators/QuestArtifactPlacer.cpp @@ -17,7 +17,9 @@ #include "../../VCMI_Lib.h" #include "../../mapObjectConstructors/AObjectTypeHandler.h" #include "../../mapObjectConstructors/CObjectClassesHandler.h" -#include "../../mapObjects/MapObjects.h" +#include "../../mapObjects/MapObjects.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -95,7 +97,7 @@ void QuestArtifactPlacer::findZonesForQuestArts() logGlobal->trace("Number of nearby zones suitable for quest artifacts: %d", questArtZones.size()); } -void QuestArtifactPlacer::placeQuestArtifacts(CRandomGenerator & rand) +void QuestArtifactPlacer::placeQuestArtifacts(vstd::RNG & rand) { for (const auto & artifactToPlace : questArtifactsToPlace) { diff --git a/lib/rmg/modificators/QuestArtifactPlacer.h b/lib/rmg/modificators/QuestArtifactPlacer.h index 28896b711..6800bcb48 100644 --- a/lib/rmg/modificators/QuestArtifactPlacer.h +++ b/lib/rmg/modificators/QuestArtifactPlacer.h @@ -15,8 +15,6 @@ VCMI_LIB_NAMESPACE_BEGIN -class CRandomGenerator; - class QuestArtifactPlacer : public Modificator { public: @@ -33,7 +31,7 @@ public: void rememberPotentialArtifactToReplace(CGObjectInstance* obj); CGObjectInstance * drawObjectToReplace(); std::vector getPossibleArtifactsToReplace() const; - void placeQuestArtifacts(CRandomGenerator & rand); + void placeQuestArtifacts(vstd::RNG & rand); void dropReplacedArtifact(CGObjectInstance* obj); size_t getMaxQuestArtifactCount() const; @@ -50,4 +48,4 @@ protected: std::vector questArtifacts; }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/modificators/RiverPlacer.cpp b/lib/rmg/modificators/RiverPlacer.cpp index 303613803..dd4150627 100644 --- a/lib/rmg/modificators/RiverPlacer.cpp +++ b/lib/rmg/modificators/RiverPlacer.cpp @@ -27,6 +27,8 @@ #include "WaterProxy.h" #include "RoadPlacer.h" +#include + VCMI_LIB_NAMESPACE_BEGIN const int RIVER_DELTA_ID = 143; diff --git a/lib/rmg/modificators/RockFiller.cpp b/lib/rmg/modificators/RockFiller.cpp index db422ec00..116885706 100644 --- a/lib/rmg/modificators/RockFiller.cpp +++ b/lib/rmg/modificators/RockFiller.cpp @@ -19,7 +19,6 @@ #include "../CMapGenerator.h" #include "../Functions.h" #include "../../TerrainHandler.h" -#include "../../CRandomGenerator.h" #include "../lib/mapping/CMapEditManager.h" #include "../TileInfo.h" #include "../threadpool/MapProxy.h" diff --git a/lib/rmg/modificators/RockPlacer.cpp b/lib/rmg/modificators/RockPlacer.cpp index 6798156bf..3fefee83b 100644 --- a/lib/rmg/modificators/RockPlacer.cpp +++ b/lib/rmg/modificators/RockPlacer.cpp @@ -18,7 +18,6 @@ #include "../CMapGenerator.h" #include "../Functions.h" #include "../../TerrainHandler.h" -#include "../../CRandomGenerator.h" #include "../../mapping/CMapEditManager.h" #include "../../VCMI_Lib.h" #include "../TileInfo.h" diff --git a/lib/rmg/modificators/TerrainPainter.cpp b/lib/rmg/modificators/TerrainPainter.cpp index 3648fb8a8..b12fb4586 100644 --- a/lib/rmg/modificators/TerrainPainter.cpp +++ b/lib/rmg/modificators/TerrainPainter.cpp @@ -22,6 +22,8 @@ #include "../../TerrainHandler.h" #include "../../CTownHandler.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void TerrainPainter::process() diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index cb26f2c89..c4d3d1803 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -27,6 +27,8 @@ #include "WaterAdopter.h" #include "../TileInfo.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void TownPlacer::process() diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 96a093f61..13de65b39 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -33,6 +33,8 @@ #include "../../mapping/CMap.h" #include "../../mapping/CMapEditManager.h" +#include + VCMI_LIB_NAMESPACE_BEGIN ObjectInfo::ObjectInfo(): @@ -649,7 +651,7 @@ std::vector TreasurePlacer::prepareTreasurePile(const CTreasureInfo if (currentValue >= minValue) { // 50% chance to end right here - if (zone.getRand().nextInt() & 1) + if (zone.getRand().nextInt(0, 1) == 1) break; } } diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index 8c6a6c316..450c812b7 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -18,7 +18,6 @@ class CGObjectInstance; class ObjectManager; class RmgMap; class CMapGenerator; -class CRandomGenerator; struct ObjectInfo { diff --git a/lib/rmg/modificators/WaterAdopter.cpp b/lib/rmg/modificators/WaterAdopter.cpp index af3fb68bf..642a2c5aa 100644 --- a/lib/rmg/modificators/WaterAdopter.cpp +++ b/lib/rmg/modificators/WaterAdopter.cpp @@ -23,6 +23,8 @@ #include "ConnectionsPlacer.h" #include "../TileInfo.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void WaterAdopter::process() diff --git a/lib/rmg/modificators/WaterProxy.cpp b/lib/rmg/modificators/WaterProxy.cpp index c9678f406..f50c1c555 100644 --- a/lib/rmg/modificators/WaterProxy.cpp +++ b/lib/rmg/modificators/WaterProxy.cpp @@ -30,6 +30,8 @@ #include "WaterAdopter.h" #include "../RmgArea.h" +#include + VCMI_LIB_NAMESPACE_BEGIN void WaterProxy::process() diff --git a/lib/rmg/threadpool/MapProxy.cpp b/lib/rmg/threadpool/MapProxy.cpp index 87cb42455..1f29b1bd3 100644 --- a/lib/rmg/threadpool/MapProxy.cpp +++ b/lib/rmg/threadpool/MapProxy.cpp @@ -37,21 +37,21 @@ void MapProxy::removeObject(CGObjectInstance * obj) map.getEditManager()->removeObject(obj); } -void MapProxy::drawTerrain(CRandomGenerator & generator, std::vector & tiles, TerrainId terrain) +void MapProxy::drawTerrain(vstd::RNG & generator, std::vector & tiles, TerrainId terrain) { Lock lock(mx); map.getEditManager()->getTerrainSelection().setSelection(tiles); map.getEditManager()->drawTerrain(terrain, map.getDecorationsPercentage(), &generator); } -void MapProxy::drawRivers(CRandomGenerator & generator, std::vector & tiles, TerrainId terrain) +void MapProxy::drawRivers(vstd::RNG & generator, std::vector & tiles, TerrainId terrain) { Lock lock(mx); map.getEditManager()->getTerrainSelection().setSelection(tiles); map.getEditManager()->drawRiver(VLC->terrainTypeHandler->getById(terrain)->river, &generator); } -void MapProxy::drawRoads(CRandomGenerator & generator, std::vector & tiles, RoadId roadType) +void MapProxy::drawRoads(vstd::RNG & generator, std::vector & tiles, RoadId roadType) { Lock lock(mx); map.getEditManager()->getTerrainSelection().setSelection(tiles); diff --git a/lib/rmg/threadpool/MapProxy.h b/lib/rmg/threadpool/MapProxy.h index 9cb265f8f..5158a97fa 100644 --- a/lib/rmg/threadpool/MapProxy.h +++ b/lib/rmg/threadpool/MapProxy.h @@ -28,9 +28,9 @@ public: void insertObjects(std::set& objects); void removeObject(CGObjectInstance* obj); - void drawTerrain(CRandomGenerator & generator, std::vector & tiles, TerrainId terrain); - void drawRivers(CRandomGenerator & generator, std::vector & tiles, TerrainId terrain); - void drawRoads(CRandomGenerator & generator, std::vector & tiles, RoadId roadType); + void drawTerrain(vstd::RNG & generator, std::vector & tiles, TerrainId terrain); + void drawRivers(vstd::RNG & generator, std::vector & tiles, TerrainId terrain); + void drawRoads(vstd::RNG & generator, std::vector & tiles, RoadId roadType); private: mutable boost::shared_mutex mx; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index d7d830afc..68e3e1f5a 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -56,6 +56,7 @@ enum class ESerializationVersion : int32_t REMOVE_FOG_OF_WAR_POINTER, // 846 - fog of war is serialized as reference instead of pointer SIMPLE_TEXT_CONTAINER_SERIALIZATION, // 847 - text container is serialized using common routine instead of custom approach MAP_FORMAT_ADDITIONAL_INFOS, // 848 - serialize new infos in map format + REMOVE_LIB_RNG, // 849 - removed random number generators from library classes - CURRENT = MAP_FORMAT_ADDITIONAL_INFOS + CURRENT = REMOVE_LIB_RNG }; diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index db3e97ab1..1d559f0d9 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -17,7 +17,6 @@ #include "../CGameInfoCallback.h" #include "../CPlayerState.h" -#include "../CRandomGenerator.h" #include "../GameSettings.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" @@ -25,6 +24,8 @@ #include "../mapping/CMap.h" #include "../networkPacks/PacksForClient.h" +#include + VCMI_LIB_NAMESPACE_BEGIN ///AdventureSpellMechanics @@ -187,7 +188,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner); //check if spell works at all - if(env->getRNG()->getInt64Range(0, 99)() >= owner->getLevelPower(schoolLevel)) //power is % chance of success + if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success { InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); @@ -237,12 +238,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment } else //create boat { - NewObject no; - no.ID = Obj::BOAT; - no.subID = BoatId::NECROPOLIS; - no.targetPos = summonPos; - no.initiator = parameters.caster->getCasterOwner(); - env->apply(&no); + env->createBoat(summonPos, BoatId::NECROPOLIS, parameters.caster->getCasterOwner()); } return ESpellCastResult::OK; } @@ -280,7 +276,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen { const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner); //check if spell works at all - if(env->getRNG()->getInt64Range(0, 99)() >= owner->getLevelPower(schoolLevel)) //power is % chance of success + if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success { InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 8ad548bfa..c9d6b70b1 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -19,7 +19,8 @@ #include "../networkPacks/PacksForClientBattle.h" #include "../networkPacks/SetStackEffect.h" #include "../CStack.h" -#include "../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -377,15 +378,13 @@ void BattleSpellMechanics::beforeCast(BattleSpellCast & sc, vstd::RNG & rng, con std::vector resisted; - auto rangeGen = rng.getInt64Range(0, 99); - auto filterResisted = [&, this](const battle::Unit * unit) -> bool { if(isNegativeSpell() && isMagicalEffect()) { //magic resistance const int prob = std::min(unit->magicResistance(), 100); //probability of resistance in % - if(rangeGen() < prob) + if(rng.nextInt(0, 99) < prob) return true; } return false; diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index 6e260347f..8a1593a73 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -11,7 +11,6 @@ #include "StdInc.h" #include "ISpellMechanics.h" -#include "../CRandomGenerator.h" #include "../VCMI_Lib.h" #include "../bonuses/Bonus.h" @@ -41,6 +40,8 @@ #include "../IGameCallback.h"//todo: remove #include "../BattleFieldHandler.h" +#include + VCMI_LIB_NAMESPACE_BEGIN namespace spells @@ -268,11 +269,9 @@ void BattleCast::cast(ServerCallback * server, Target target) const std::string magicMirrorCacheStr = "type_MAGIC_MIRROR"; static const auto magicMirrorSelector = Selector::type()(BonusType::MAGIC_MIRROR); - auto rangeGen = server->getRNG()->getInt64Range(0, 99); - const int mirrorChance = mainTarget->valOfBonuses(magicMirrorSelector, magicMirrorCacheStr); - if(rangeGen() < mirrorChance) + if(server->getRNG()->nextInt(0, 99) < mirrorChance) { auto mirrorTargets = cb->battleGetUnitsIf([this](const battle::Unit * unit) { diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 0f9dc9646..ea150497d 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -22,7 +22,6 @@ VCMI_LIB_NAMESPACE_BEGIN struct Query; class IBattleState; -class CRandomGenerator; class CreatureService; class CMap; class CGameInfoCallback; @@ -59,6 +58,7 @@ public: virtual const CMap * getMap() const = 0; virtual const CGameInfoCallback * getCb() const = 0; + virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 167aaabd6..4fa46b2b7 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -20,7 +20,8 @@ #include "../../mapObjects/CGTownInstance.h" #include "../../networkPacks/PacksForClientBattle.h" #include "../../serializer/JsonSerializeFormat.h" -#include "../../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -118,7 +119,7 @@ void Catapult::applyTargeted(ServerCallback * server, const Mechanics * m, const auto actualTarget = EWallPart::INVALID; if ( m->battle()->isWallPartAttackable(desiredTarget) && - server->getRNG()->getInt64Range(0, 99)() < getCatapultHitChance(desiredTarget)) + server->getRNG()->nextInt(0, 99) < getCatapultHitChance(desiredTarget)) { actualTarget = desiredTarget; } @@ -172,7 +173,7 @@ int Catapult::getRandomDamage (ServerCallback * server) const { std::array damageChances = { noDmg, hit, crit }; //dmgChance[i] - chance for doing i dmg when hit is successful int totalChance = std::accumulate(damageChances.begin(), damageChances.end(), 0); - int damageRandom = server->getRNG()->getInt64Range(0, totalChance - 1)(); + int damageRandom = server->getRNG()->nextInt(0, totalChance - 1); int dealtDamage = 0; //calculating dealt damage diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index eb9fa5be7..e75052684 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -18,7 +18,8 @@ #include "../../battle/CBattleInfoCallback.h" #include "../../networkPacks/PacksForClientBattle.h" #include "../../serializer/JsonSerializeFormat.h" -#include "../../CRandomGenerator.h" + +#include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 6ee3afba1..7f1a08635 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -213,7 +213,7 @@ static JsonNode loadLobbyGameRoomToJson(const LobbyGameRoom & gameRoom) jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit; jsonEntry["ageSeconds"].Integer() = gameRoom.age.count(); if (!gameRoom.modsJson.empty()) // not present in match history - jsonEntry["mods"] = JsonNode(reinterpret_cast(gameRoom.modsJson.data()), gameRoom.modsJson.size()); + jsonEntry["mods"] = JsonNode(reinterpret_cast(gameRoom.modsJson.data()), gameRoom.modsJson.size(), ""); for(const auto & account : gameRoom.participants) jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account)); @@ -348,7 +348,7 @@ JsonNode LobbyServer::parseAndValidateMessage(const std::vector & mes JsonNode json; try { - JsonNode jsonTemp(message.data(), message.size()); + JsonNode jsonTemp(message.data(), message.size(), ""); json = std::move(jsonTemp); } catch (const JsonFormatException & e) diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 16019a65a..0b1c89161 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -599,7 +599,7 @@ void Animation::init() std::unique_ptr textData(new ui8[stream->getSize()]); stream->read(textData.get(), stream->getSize()); - const JsonNode config(reinterpret_cast(textData.get()), stream->getSize()); + const JsonNode config(reinterpret_cast(textData.get()), stream->getSize(), resID.getName()); initFromJson(config); } @@ -711,14 +711,6 @@ void Animation::duplicateImage(const size_t sourceGroup, const size_t sourceFram load(index, targetGroup); } -void Animation::setCustom(std::string filename, size_t frame, size_t group) -{ - if(source[group].size() <= frame) - source[group].resize(frame+1); - source[group][frame]["file"].String() = filename; - //FIXME: update image if already loaded -} - std::shared_ptr Animation::getImage(size_t frame, size_t group, bool verbose) const { auto groupIter = images.find(group); diff --git a/mapeditor/Animation.h b/mapeditor/Animation.h index 2e4556413..253e68981 100644 --- a/mapeditor/Animation.h +++ b/mapeditor/Animation.h @@ -68,9 +68,6 @@ public: // adjust the color of the animation, used in battle spell effects, e.g. Cloned objects - //add custom surface to the selected position. - void setCustom(std::string filename, size_t frame, size_t group = 0); - std::shared_ptr getImage(size_t frame, size_t group = 0, bool verbose = true) const; //all available frames diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index f7ce677bb..5ab2335af 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -97,7 +97,7 @@ QVariant JsonFromFile(QString filename) } else { - JsonNode node(reinterpret_cast(data.data()), data.size()); + JsonNode node(reinterpret_cast(data.data()), data.size(), filename.toStdString()); return toVariant(node); } } diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index a0084b1ef..98ef59470 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -133,17 +133,33 @@ void MainWindow::parseCommandLine(ExtractionOptions & extractionOptions) void MainWindow::loadTranslation() { #ifdef ENABLE_QT_TRANSLATIONS - const std::string translationFile = settings["general"]["language"].String() + ".qm"; - logGlobal->info("Loading translation '%s'", translationFile); + const std::string translationFile = settings["general"]["language"].String()+ ".qm"; + QString translationFileResourcePath = QString{":/translation/%1"}.arg(translationFile.c_str()); - if (!translator.load(QString{":/translation/%1"}.arg(translationFile.c_str()))) + logGlobal->info("Loading translation %s", translationFile); + + if(!QFile::exists(translationFileResourcePath)) { - logGlobal->error("Failed to load translation"); + logGlobal->debug("Translation file %s does not exist", translationFileResourcePath.toStdString()); + return; + } + + if (!translator.load(translationFileResourcePath)) + { + logGlobal->error("Failed to load translation file %s", translationFileResourcePath.toStdString()); + return; + } + + if(translationFile == "english.qm") + { + // translator doesn't need to be installed for English return; } if (!qApp->installTranslator(&translator)) - logGlobal->error("Failed to install translator"); + { + logGlobal->error("Failed to install translator for translation file %s", translationFileResourcePath.toStdString()); + } #endif } diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index a44b2d1af..9e869ff75 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -26,6 +26,7 @@ #include "../lib/CSkillHandler.h" #include "../lib/spells/CSpellHandler.h" #include "../lib/CHeroHandler.h" +#include "../lib/CRandomGenerator.h" #include "../lib/serializer/CMemorySerializer.h" #include "mapview.h" #include "scenelayer.h" diff --git a/mapeditor/translation/portuguese.ts b/mapeditor/translation/portuguese.ts index 5bc267e5a..e6ca56dd1 100644 --- a/mapeditor/translation/portuguese.ts +++ b/mapeditor/translation/portuguese.ts @@ -6,17 +6,17 @@ Army settings - Configurações do Exército + Configurações do exército Wide formation - Formação Aberta + Formação aberta Tight formation - Formação Compacta + Formação compacta @@ -29,7 +29,7 @@ Timed events - Eventos Temporizados + Eventos temporizados @@ -44,7 +44,7 @@ New event - Novo Evento + Novo evento @@ -57,12 +57,12 @@ Map name - Nome do Mapa + Nome do mapa Map description - Descrição do Mapa + Descrição do mapa @@ -88,7 +88,7 @@ Hero skills - Habilidades do Herói + Habilidades do herói @@ -172,7 +172,7 @@ Defeat message - Mensagem de Derrota + Mensagem de derrota diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 75aad118a..92e6a19fa 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -17,6 +17,7 @@ #include "../lib/mapping/CMapEditManager.h" #include "../lib/mapping/MapFormat.h" #include "../lib/CGeneralTextHandler.h" +#include "../lib/CRandomGenerator.h" #include "../lib/serializer/JsonSerializer.h" #include "../lib/serializer/JsonDeserializer.h" diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 8221db459..c4e0264b3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -24,11 +24,13 @@ #include "../lib/ArtifactUtils.h" #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" +#include "../lib/CConfigHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/CCreatureSet.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" +#include "../lib/CRandomGenerator.h" #include "../lib/CSoundBase.h" #include "../lib/CThreadHelper.h" #include "../lib/CTownHandler.h" @@ -49,9 +51,12 @@ #include "../lib/mapping/CMap.h" #include "../lib/mapping/CMapService.h" +#include "../lib/mapObjects/CGCreature.h" #include "../lib/mapObjects/CGMarket.h" #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/MiscObjects.h" +#include "../lib/mapObjectConstructors/AObjectTypeHandler.h" +#include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/modding/ModIncompatibility.h" #include "../lib/networkPacks/StackLocation.h" #include "../lib/pathfinder/CPathfinder.h" @@ -68,7 +73,8 @@ #include "../lib/spells/CSpellHandler.h" -#include "vstd/CLoggerBase.h" +#include +#include #include #include #include @@ -546,10 +552,12 @@ void CGameHandler::reinitScripting() void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTracking) { - if (si->seedToBeUsed == 0) - { - si->seedToBeUsed = CRandomGenerator::getDefault().nextInt(); - } + randomNumberGenerator = std::make_unique(); + int requestedSeed = settings["server"]["seed"].Integer(); + if (requestedSeed != 0) + randomNumberGenerator->setSeed(requestedSeed); + logGlobal->info("Using random seed: %d", randomNumberGenerator->nextInt()); + CMapService mapService; gs = new CGameState(); gs->preInit(VLC, this); @@ -557,9 +565,6 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack gs->init(&mapService, si, progressTracking); logGlobal->info("Gamestate initialized!"); - // reset seed, so that clients can't predict any following random values - getRandomGenerator().resetSeed(); - for (auto & elem : gs->players) turnOrder->addPlayer(elem.first); @@ -910,6 +915,9 @@ void CGameHandler::onNewTurn() } } + if (newWeek) + n.newRumor = gameState()->pickNewRumor(); + if (newMonth) { SetAvailableArtifacts saa; @@ -3683,7 +3691,7 @@ bool CGameHandler::buildBoat(ObjectInstanceID objid, PlayerColor playerID) } giveResources(playerID, -boatCost); - createObject(tile, playerID, Obj::BOAT, obj->getBoatType().getNum()); + createBoat(tile, obj->getBoatType(), playerID); return true; } @@ -3808,7 +3816,7 @@ bool CGameHandler::dig(const CGHeroInstance *h) if (h->diggingStatus() != EDiggingStatus::CAN_DIG) //checks for terrain and movement COMPLAIN_RETF("Hero cannot dig (error code %d)!", static_cast(h->diggingStatus())); - createObject(h->visitablePos(), h->getOwner(), Obj::HOLE, 0 ); + createHole(h->visitablePos(), h->getOwner()); //take MPs SetMovePoints smp; @@ -4213,7 +4221,7 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID) { auto count = cre->getRandomAmount(std::rand); - createObject(*tile, PlayerColor::NEUTRAL, Obj::MONSTER, creatureID); + createWanderingMonster(*tile, creatureID); auto monsterId = getTopObj(*tile)->id; setObjPropertyValue(monsterId, ObjProperty::MONSTER_COUNT, count); @@ -4373,14 +4381,39 @@ void CGameHandler::setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, Ob sendAndApply(&sob); } +void CGameHandler::setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) +{ + SetBankConfiguration srb; + srb.objectID = objid; + srb.configuration = configuration; + sendAndApply(&srb); +} + +void CGameHandler::setRewardableObjectConfiguration(ObjectInstanceID objid, const Rewardable::Configuration & configuration) +{ + SetRewardableConfiguration srb; + srb.objectID = objid; + srb.configuration = configuration; + sendAndApply(&srb); +} + +void CGameHandler::setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) +{ + SetRewardableConfiguration srb; + srb.objectID = townInstanceID; + srb.buildingID = buildingID; + srb.configuration = configuration; + sendAndApply(&srb); +} + void CGameHandler::showInfoDialog(InfoWindow * iw) { sendAndApply(iw); } -CRandomGenerator & CGameHandler::getRandomGenerator() +vstd::RNG & CGameHandler::getRandomGenerator() { - return CRandomGenerator::getDefault(); + return *randomNumberGenerator; } #if SCRIPTING_ENABLED @@ -4395,13 +4428,71 @@ scripting::Pool * CGameHandler::getGlobalContextPool() const //} #endif -void CGameHandler::createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) + +CGObjectInstance * CGameHandler::createNewObject(const int3 & visitablePosition, MapObjectID objectID, MapObjectSubID subID) { + TerrainId terrainType = ETerrainId::NONE; + + if (!gs->isInTheMap(visitablePosition)) + throw std::runtime_error("Attempt to create object outside map at " + visitablePosition.toString()); + + const TerrainTile & t = gs->map->getTile(visitablePosition); + terrainType = t.terType->getId(); + + auto handler = VLC->objtypeh->getHandlerFor(objectID, subID); + + CGObjectInstance * o = handler->create(gs->callback, nullptr); + handler->configureObject(o, getRandomGenerator()); + assert(o->ID == objectID); + + assert(!handler->getTemplates(terrainType).empty()); + if (handler->getTemplates().empty()) + throw std::runtime_error("Attempt to create object (" + std::to_string(objectID) + ", " + std::to_string(subID.getNum()) + ") with no templates!"); + + if (!handler->getTemplates(terrainType).empty()) + o->appearance = handler->getTemplates(terrainType).front(); + else + o->appearance = handler->getTemplates().front(); + + + o->pos = visitablePosition + o->getVisitableOffset(); + return o; +} + +void CGameHandler::createWanderingMonster(const int3 & visitablePosition, CreatureID creature) +{ + auto createdObject = createNewObject(visitablePosition, Obj::MONSTER, creature); + + auto * cre = dynamic_cast(createdObject); + assert(cre); + cre->notGrowingTeam = cre->neverFlees = false; + cre->character = 2; + cre->gainedArtifact = ArtifactID::NONE; + cre->identifier = -1; + cre->addToSlot(SlotID(0), new CStackInstance(creature, -1)); //add placeholder stack + + newObject(createdObject, PlayerColor::NEUTRAL); +} + +void CGameHandler::createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) +{ + auto createdObject = createNewObject(visitablePosition, Obj::BOAT, type); + newObject(createdObject, initiator); +} + +void CGameHandler::createHole(const int3 & visitablePosition, PlayerColor initiator) +{ + auto createdObject = createNewObject(visitablePosition, Obj::HOLE, 0); + newObject(createdObject, initiator); +} + +void CGameHandler::newObject(CGObjectInstance * object, PlayerColor initiator) +{ + object->initObj(gs->getRandomGenerator()); + NewObject no; - no.ID = type; - no.subID = subtype; + no.newObject = object; no.initiator = initiator; - no.targetPos = visitablePosition; sendAndApply(&no); } diff --git a/server/CGameHandler.h b/server/CGameHandler.h index eebb21f68..60e7d8be0 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -23,6 +23,7 @@ class SpellCastEnvironment; class CConnection; class CCommanderInstance; class EVictoryLossCheckResult; +class CRandomGenerator; struct CPack; struct CPackForServer; @@ -63,6 +64,7 @@ public: std::unique_ptr queries; std::unique_ptr turnOrder; std::unique_ptr turnTimerHandler; + std::unique_ptr randomNumberGenerator; //use enums as parameters, because doMove(sth, true, false, true) is not readable enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS}; @@ -90,6 +92,14 @@ public: bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2); void giveSpells(const CGTownInstance *t, const CGHeroInstance *h); + // Helpers to create new object of specified type + + CGObjectInstance * createNewObject(const int3 & visitablePosition, MapObjectID objectID, MapObjectSubID subID); + void createWanderingMonster(const int3 & visitablePosition, CreatureID creature); + void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override; + void createHole(const int3 & visitablePosition, PlayerColor initiator); + void newObject(CGObjectInstance * object, PlayerColor initiator); + explicit CGameHandler(CVCMIServer * lobby); ~CGameHandler(); @@ -98,7 +108,6 @@ public: //do sth void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; - void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; void giveExperience(const CGHeroInstance * hero, TExpType val) override; void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false) override; @@ -160,6 +169,9 @@ public: bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) override; void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override; void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override; + void setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) override; + void setRewardableObjectConfiguration(ObjectInstanceID objid, const Rewardable::Configuration & configuration) override; + void setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) override; void showInfoDialog(InfoWindow * iw) override; ////////////////////////////////////////////////////////////////////////// @@ -226,7 +238,7 @@ public: template void serialize(Handler &h) { h & QID; - h & getRandomGenerator(); + h & randomNumberGenerator; h & *battles; h & *heroPool; h & *playerMessages; @@ -275,7 +287,7 @@ public: void checkVictoryLossConditions(const std::set & playerColors); void checkVictoryLossConditionsForAll(); - CRandomGenerator & getRandomGenerator(); + vstd::RNG & getRandomGenerator() override; #if SCRIPTING_ENABLED scripting::Pool * getGlobalContextPool() const override; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 9ce49099d..375568880 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -253,7 +253,6 @@ void CVCMIServer::prepareToRestart() } * si = * gh->gs->initialOpts; - si->seedToBeUsed = si->seedPostInit = 0; setState(EServerState::LOBBY); if (si->campState) { diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 7effca617..15fd376f7 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -67,7 +67,7 @@ void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptr"); if(json["type"].String() == "operationFailed") return receiveOperationFailed(json); diff --git a/server/ServerSpellCastEnvironment.cpp b/server/ServerSpellCastEnvironment.cpp index 035617d6e..9213aef7b 100644 --- a/server/ServerSpellCastEnvironment.cpp +++ b/server/ServerSpellCastEnvironment.cpp @@ -94,6 +94,11 @@ bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, EMovem return gh->moveHero(hid, dst, mode, false); } +void ServerSpellCastEnvironment::createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) +{ + return gh->createBoat(visitablePosition, type, initiator); +} + void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function)> callback) { auto query = std::make_shared(gh, color, callback); diff --git a/server/ServerSpellCastEnvironment.h b/server/ServerSpellCastEnvironment.h index 307f858ea..e2aa9b796 100644 --- a/server/ServerSpellCastEnvironment.h +++ b/server/ServerSpellCastEnvironment.h @@ -37,6 +37,7 @@ public: const CMap * getMap() const override; const CGameInfoCallback * getCb() const override; bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) override; + void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override; void genericQuery(Query * request, PlayerColor color, std::function)> callback) override; private: CGameHandler * gh; diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 61e13c26a..98d16493a 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -29,6 +29,8 @@ #include "../../lib/spells/ISpellMechanics.h" #include "../../lib/spells/Problem.h" +#include + BattleActionProcessor::BattleActionProcessor(BattleProcessor * owner, CGameHandler * newGameHandler) : owner(owner) , gameHandler(newGameHandler) @@ -1263,8 +1265,7 @@ void BattleActionProcessor::handleDeathStare(const CBattleInfoCallback & battle, int singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::DEATH_STARE, subtype); double chanceToKill = singleCreatureKillChancePercent / 100.0; vstd::amin(chanceToKill, 1); //cap at 100% - std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill); - int killedCreatures = distribution(gameHandler->getRandomGenerator().getStdGenerator()); + int killedCreatures = gameHandler->getRandomGenerator().nextBinomialInt(attacker->getCount(), chanceToKill); int maxToKill = (attacker->getCount() * singleCreatureKillChancePercent + 99) / 100; vstd::amin(killedCreatures, maxToKill); @@ -1341,7 +1342,7 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback & double chanceToTrigger = attacker->valOfBonuses(BonusType::TRANSMUTATION) / 100.0f; vstd::amin(chanceToTrigger, 1); //cap at 100% - if(gameHandler->getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger) + if(gameHandler->getRandomGenerator().nextDouble(0, 1) > chanceToTrigger) return; int bonusAdditionalInfo = attacker->getBonus(Selector::type()(BonusType::TRANSMUTATION))->additionalInfo[0]; @@ -1405,7 +1406,7 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback & vstd::amin(chanceToTrigger, 1); //cap trigger chance at 100% - if(gameHandler->getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger) + if(gameHandler->getRandomGenerator().nextDouble(0, 1) > chanceToTrigger) return; BattleStackAttacked bsa; diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index eadf4e65e..920ee1a4a 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -26,6 +26,8 @@ #include "../../lib/spells/ISpellMechanics.h" #include "../../lib/spells/ObstacleCasterProxy.h" +#include + BattleFlowProcessor::BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler) : owner(owner) , gameHandler(newGameHandler) diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 6daf68a47..90ce8fbc7 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -30,6 +30,8 @@ #include "../../lib/serializer/Cast.h" #include "../../lib/spells/CSpellHandler.h" +#include + BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner, CGameHandler * newGameHandler) // : owner(owner) : gameHandler(newGameHandler) diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 32bedb747..7e8d327ba 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -13,6 +13,7 @@ #include "TurnOrderProcessor.h" #include "../CGameHandler.h" +#include "../../lib/CRandomGenerator.h" #include "../../lib/CHeroHandler.h" #include "../../lib/CPlayerState.h" #include "../../lib/GameSettings.h" @@ -226,8 +227,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat) { //Create a new boat for hero - gameHandler->createObject(targetPos, player, Obj::BOAT, recruitedHero->getBoatType().getNum()); - + gameHandler->createBoat(targetPos, recruitedHero->getBoatType(), player); hr.boatId = gameHandler->getTopObj(targetPos)->id; } @@ -368,7 +368,7 @@ CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, const PlayerColor return *RandomGeneratorUtil::nextItem(possibleHeroes, getRandomGenerator(player)); } -CRandomGenerator & HeroPoolProcessor::getHeroSkillsRandomGenerator(const HeroTypeID & hero) +vstd::RNG & HeroPoolProcessor::getHeroSkillsRandomGenerator(const HeroTypeID & hero) { if (heroSeed.count(hero) == 0) { @@ -379,7 +379,7 @@ CRandomGenerator & HeroPoolProcessor::getHeroSkillsRandomGenerator(const HeroTyp return *heroSeed.at(hero); } -CRandomGenerator & HeroPoolProcessor::getRandomGenerator(const PlayerColor & player) +vstd::RNG & HeroPoolProcessor::getRandomGenerator(const PlayerColor & player) { if (playerSeed.count(player) == 0) { diff --git a/server/processors/HeroPoolProcessor.h b/server/processors/HeroPoolProcessor.h index 8d0d8552d..0a2159d58 100644 --- a/server/processors/HeroPoolProcessor.h +++ b/server/processors/HeroPoolProcessor.h @@ -19,8 +19,13 @@ class PlayerColor; class CGHeroInstance; class HeroTypeID; class ObjectInstanceID; -class CRandomGenerator; class CHeroClass; +class CRandomGenerator; + +namespace vstd +{ +class RNG; +} VCMI_LIB_NAMESPACE_END @@ -46,7 +51,7 @@ class HeroPoolProcessor : boost::noncopyable CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player); - CRandomGenerator & getRandomGenerator(const PlayerColor & player); + vstd::RNG & getRandomGenerator(const PlayerColor & player); TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID); @@ -58,7 +63,7 @@ public: void onNewWeek(const PlayerColor & color); - CRandomGenerator & getHeroSkillsRandomGenerator(const HeroTypeID & hero); + vstd::RNG & getHeroSkillsRandomGenerator(const HeroTypeID & hero); /// Incoming net pack handling bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player, const HeroTypeID & nextHero); diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 00da8456f..c5f774ed1 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -121,6 +121,10 @@ public: return gameState.get(); } + void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override + { + } + bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode) override { return false; @@ -142,9 +146,7 @@ public: StartInfo si; si.mapname = "anything";//does not matter, map service mocked si.difficulty = 0; - si.mapfileChecksum = 0; si.mode = EStartMode::NEW_GAME; - si.seedToBeUsed = 42; std::unique_ptr header = mapService.loadMapHeader(ResourcePath(si.mapname)); diff --git a/test/map/CMapEditManagerTest.cpp b/test/map/CMapEditManagerTest.cpp index 0020a6e93..b1e7f56e6 100644 --- a/test/map/CMapEditManagerTest.cpp +++ b/test/map/CMapEditManagerTest.cpp @@ -25,15 +25,16 @@ TEST(MapManager, DrawTerrain_Type) try { auto map = std::make_unique(nullptr); + CRandomGenerator rand; map->width = 52; map->height = 52; map->initTerrain(); auto editManager = map->getEditManager(); - editManager->clearTerrain(); + editManager->clearTerrain(&rand); // 1x1 Blow up editManager->getTerrainSelection().select(int3(5, 5, 0)); - editManager->drawTerrain(ETerrainId::GRASS, 10); + editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); static const int3 squareCheck[] = { int3(5,5,0), int3(5,4,0), int3(4,4,0), int3(4,5,0) }; for(const auto & tile : squareCheck) { @@ -42,20 +43,20 @@ TEST(MapManager, DrawTerrain_Type) // Concat to square editManager->getTerrainSelection().select(int3(6, 5, 0)); - editManager->drawTerrain(ETerrainId::GRASS, 10); + editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); EXPECT_EQ(map->getTile(int3(6, 4, 0)).terType->getId(), ETerrainId::GRASS); editManager->getTerrainSelection().select(int3(6, 5, 0)); - editManager->drawTerrain(ETerrainId::LAVA, 10); + editManager->drawTerrain(ETerrainId::LAVA, 10, &rand); EXPECT_EQ(map->getTile(int3(4, 4, 0)).terType->getId(), ETerrainId::GRASS); EXPECT_EQ(map->getTile(int3(7, 4, 0)).terType->getId(), ETerrainId::LAVA); // Special case water,rock editManager->getTerrainSelection().selectRange(MapRect(int3(10, 10, 0), 10, 5)); - editManager->drawTerrain(ETerrainId::GRASS, 10); + editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); editManager->getTerrainSelection().selectRange(MapRect(int3(15, 17, 0), 10, 5)); - editManager->drawTerrain(ETerrainId::GRASS, 10); + editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); editManager->getTerrainSelection().select(int3(21, 16, 0)); - editManager->drawTerrain(ETerrainId::GRASS, 10); + editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); EXPECT_EQ(map->getTile(int3(20, 15, 0)).terType->getId(), ETerrainId::GRASS); // Special case non water,rock @@ -66,16 +67,16 @@ TEST(MapManager, DrawTerrain_Type) { editManager->getTerrainSelection().select(tile); } - editManager->drawTerrain(ETerrainId::GRASS, 10); + editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); EXPECT_EQ(map->getTile(int3(35, 44, 0)).terType->getId(), ETerrainId::WATER); // Rock case editManager->getTerrainSelection().selectRange(MapRect(int3(1, 1, 1), 15, 15)); - editManager->drawTerrain(ETerrainId::SUBTERRANEAN, 10); + editManager->drawTerrain(ETerrainId::SUBTERRANEAN, 10, &rand); std::vector vec({ int3(6, 6, 1), int3(7, 6, 1), int3(8, 6, 1), int3(5, 7, 1), int3(6, 7, 1), int3(7, 7, 1), int3(8, 7, 1), int3(4, 8, 1), int3(5, 8, 1), int3(6, 8, 1)}); editManager->getTerrainSelection().setSelection(vec); - editManager->drawTerrain(ETerrainId::ROCK, 10); + editManager->drawTerrain(ETerrainId::ROCK, 10, &rand); EXPECT_TRUE(!map->getTile(int3(5, 6, 1)).terType->isPassable() || !map->getTile(int3(7, 8, 1)).terType->isPassable()); //todo: add checks here and enable, also use smaller size diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index 176d5d916..21dec61b0 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -95,7 +95,7 @@ static JsonNode getFromArchive(CZipLoader & archive, const std::string & archive auto data = archive.load(resource)->readAll(); - JsonNode res(reinterpret_cast(data.first.get()), data.second); + JsonNode res(reinterpret_cast(data.first.get()), data.second, resource.getName()); return res; } diff --git a/test/mock/mock_IGameCallback.cpp b/test/mock/mock_IGameCallback.cpp index 13a6ea0ff..43428b698 100644 --- a/test/mock/mock_IGameCallback.cpp +++ b/test/mock/mock_IGameCallback.cpp @@ -31,3 +31,8 @@ void GameCallbackMock::sendAndApply(CPackForClient * pack) { upperCallback->apply(pack); } + +vstd::RNG & GameCallbackMock::getRandomGenerator() +{ + throw std::runtime_error("Not implemented!"); +} diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 9ab8675f1..81f84d3c3 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -37,11 +37,15 @@ public: void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value = 0) override {} void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {} + void setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) override {} + void setRewardableObjectConfiguration(ObjectInstanceID mapObjectID, const Rewardable::Configuration & configuration) override {} + void setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) override {} + void showInfoDialog(InfoWindow * iw) override {} void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override {} bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} - void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override {}; + void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {} void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} void giveExperience(const CGHeroInstance * hero, TExpType val) override {} void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false) override {} @@ -93,6 +97,8 @@ public: ///useful callback methods void sendAndApply(CPackForClient * pack) override; + vstd::RNG & getRandomGenerator() override; + #if SCRIPTING_ENABLED MOCK_CONST_METHOD0(getGlobalContextPool, scripting::Pool *()); #endif diff --git a/test/mock/mock_vstd_RNG.h b/test/mock/mock_vstd_RNG.h index 2fe2b04e1..f3723eb19 100644 --- a/test/mock/mock_vstd_RNG.h +++ b/test/mock/mock_vstd_RNG.h @@ -18,8 +18,16 @@ namespace vstd class RNGMock : public RNG { public: - MOCK_METHOD2(getInt64Range, TRandI64(int64_t, int64_t)); - MOCK_METHOD2(getDoubleRange, TRand(double, double)); + MOCK_METHOD2(nextInt, int(int lower, int upper)); + MOCK_METHOD2(nextInt64, int64_t(int64_t lower, int64_t upper)); + MOCK_METHOD2(nextDouble, double(double lower, double upper)); + + MOCK_METHOD1(nextInt, int(int upper)); + MOCK_METHOD1(nextInt64, int64_t(int64_t upper)); + MOCK_METHOD1(nextDouble, double(double upper)); + + MOCK_METHOD0(nextInt, int()); + MOCK_METHOD2(nextBinomialInt, int(int coinsCount, double coinChance)); }; } diff --git a/test/spells/effects/EffectFixture.cpp b/test/spells/effects/EffectFixture.cpp index 5cf558a44..cf4478786 100644 --- a/test/spells/effects/EffectFixture.cpp +++ b/test/spells/effects/EffectFixture.cpp @@ -101,27 +101,21 @@ void EffectFixture::setUp() ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); } -static vstd::TRandI64 getInt64RangeDef(int64_t lower, int64_t upper) +static int64_t getInt64Range(int64_t lower, int64_t upper) { - return [=]()->int64_t - { - return (lower + upper)/2; - }; + return (lower + upper)/2; } -static vstd::TRand getDoubleRangeDef(double lower, double upper) +static double getDoubleRange(double lower, double upper) { - return [=]()->double - { - return (lower + upper)/2; - }; + return (lower + upper)/2; } void EffectFixture::setupDefaultRNG() { EXPECT_CALL(serverMock, getRNG()).Times(AtLeast(0)); - EXPECT_CALL(rngMock, getInt64Range(_,_)).WillRepeatedly(Invoke(&getInt64RangeDef)); - EXPECT_CALL(rngMock, getDoubleRange(_,_)).WillRepeatedly(Invoke(&getDoubleRangeDef)); + EXPECT_CALL(rngMock, nextInt64(_,_)).WillRepeatedly(Invoke(&getInt64Range)); + EXPECT_CALL(rngMock, nextDouble(_,_)).WillRepeatedly(Invoke(&getDoubleRange)); } }