mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-19 21:10:12 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
df069bf14f
@ -281,6 +281,9 @@ void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
|
|||||||
for(const CGObjectInstance * obj : myCb->getVisitableObjs(tile))
|
for(const CGObjectInstance * obj : myCb->getVisitableObjs(tile))
|
||||||
addVisitableObj(obj);
|
addVisitableObj(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nullkiller->settings->isUpdateHitmapOnTileReveal())
|
||||||
|
nullkiller->dangerHitMap->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query)
|
void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query)
|
||||||
|
@ -348,6 +348,7 @@ std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObj
|
|||||||
void DangerHitMapAnalyzer::reset()
|
void DangerHitMapAnalyzer::reset()
|
||||||
{
|
{
|
||||||
hitMapUpToDate = false;
|
hitMapUpToDate = false;
|
||||||
|
tileOwnersUpToDate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1357,7 +1357,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
float score = 0;
|
float score = 0;
|
||||||
float maxWillingToLose = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0) ? 1 : 0.25;
|
const bool amIInDanger = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0);
|
||||||
|
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
||||||
|
|
||||||
bool arriveNextWeek = false;
|
bool arriveNextWeek = false;
|
||||||
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7)
|
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7)
|
||||||
|
@ -37,6 +37,7 @@ namespace NKAI
|
|||||||
pathfinderBucketSize(32),
|
pathfinderBucketSize(32),
|
||||||
allowObjectGraph(true),
|
allowObjectGraph(true),
|
||||||
useTroopsFromGarrisons(false),
|
useTroopsFromGarrisons(false),
|
||||||
|
updateHitmapOnTileReveal(false),
|
||||||
openMap(true),
|
openMap(true),
|
||||||
useFuzzy(false)
|
useFuzzy(false)
|
||||||
{
|
{
|
||||||
@ -53,8 +54,10 @@ namespace NKAI
|
|||||||
maxGoldPressure = node["maxGoldPressure"].Float();
|
maxGoldPressure = node["maxGoldPressure"].Float();
|
||||||
retreatThresholdRelative = node["retreatThresholdRelative"].Float();
|
retreatThresholdRelative = node["retreatThresholdRelative"].Float();
|
||||||
retreatThresholdAbsolute = node["retreatThresholdAbsolute"].Float();
|
retreatThresholdAbsolute = node["retreatThresholdAbsolute"].Float();
|
||||||
|
maxArmyLossTarget = node["maxArmyLossTarget"].Float();
|
||||||
safeAttackRatio = node["safeAttackRatio"].Float();
|
safeAttackRatio = node["safeAttackRatio"].Float();
|
||||||
allowObjectGraph = node["allowObjectGraph"].Bool();
|
allowObjectGraph = node["allowObjectGraph"].Bool();
|
||||||
|
updateHitmapOnTileReveal = node["updateHitmapOnTileReveal"].Bool();
|
||||||
openMap = node["openMap"].Bool();
|
openMap = node["openMap"].Bool();
|
||||||
useFuzzy = node["useFuzzy"].Bool();
|
useFuzzy = node["useFuzzy"].Bool();
|
||||||
useTroopsFromGarrisons = node["useTroopsFromGarrisons"].Bool();
|
useTroopsFromGarrisons = node["useTroopsFromGarrisons"].Bool();
|
||||||
|
@ -31,8 +31,10 @@ namespace NKAI
|
|||||||
float retreatThresholdRelative;
|
float retreatThresholdRelative;
|
||||||
float retreatThresholdAbsolute;
|
float retreatThresholdAbsolute;
|
||||||
float safeAttackRatio;
|
float safeAttackRatio;
|
||||||
|
float maxArmyLossTarget;
|
||||||
bool allowObjectGraph;
|
bool allowObjectGraph;
|
||||||
bool useTroopsFromGarrisons;
|
bool useTroopsFromGarrisons;
|
||||||
|
bool updateHitmapOnTileReveal;
|
||||||
bool openMap;
|
bool openMap;
|
||||||
bool useFuzzy;
|
bool useFuzzy;
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ namespace NKAI
|
|||||||
float getRetreatThresholdRelative() const { return retreatThresholdRelative; }
|
float getRetreatThresholdRelative() const { return retreatThresholdRelative; }
|
||||||
float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; }
|
float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; }
|
||||||
float getSafeAttackRatio() const { return safeAttackRatio; }
|
float getSafeAttackRatio() const { return safeAttackRatio; }
|
||||||
|
float getMaxArmyLossTarget() const { return maxArmyLossTarget; }
|
||||||
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
||||||
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
||||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||||
@ -51,6 +54,7 @@ namespace NKAI
|
|||||||
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
|
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
|
||||||
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
||||||
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
|
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
|
||||||
|
bool isUpdateHitmapOnTileReveal() const { return updateHitmapOnTileReveal; }
|
||||||
bool isOpenMap() const { return openMap; }
|
bool isOpenMap() const { return openMap; }
|
||||||
bool isUseFuzzy() const { return useFuzzy; }
|
bool isUseFuzzy() const { return useFuzzy; }
|
||||||
};
|
};
|
||||||
|
@ -182,14 +182,12 @@
|
|||||||
"MD039": true,
|
"MD039": true,
|
||||||
|
|
||||||
// MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md040.md
|
// MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md040.md
|
||||||
"MD040": false,
|
"MD040": {
|
||||||
// FIXME: enable and consider fixing
|
// List of languages
|
||||||
//{
|
"allowed_languages": [ "cpp", "json", "sh", "text", "nix", "powershell", "lua" ],
|
||||||
//// List of languages
|
// Require language only
|
||||||
// "allowed_languages": [ "cpp", "json5", "sh" ],
|
"language_only": true
|
||||||
//// Require language only
|
},
|
||||||
// "language_only": true
|
|
||||||
//},
|
|
||||||
|
|
||||||
// MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md041.md
|
// MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md041.md
|
||||||
"MD041": {
|
"MD041": {
|
||||||
|
@ -121,6 +121,44 @@
|
|||||||
"vcmi.lobby.deleteFolder" : "Do you want to delete following folder?",
|
"vcmi.lobby.deleteFolder" : "Do you want to delete following folder?",
|
||||||
"vcmi.lobby.deleteMode" : "Switch to delete mode and back",
|
"vcmi.lobby.deleteMode" : "Switch to delete mode and back",
|
||||||
|
|
||||||
|
"vcmi.broadcast.failedLoadGame" : "Failed to load game",
|
||||||
|
"vcmi.broadcast.command" : "Use '!help' to list available commands",
|
||||||
|
"vcmi.broadcast.simturn.end" : "Simultaneous turns have ended",
|
||||||
|
"vcmi.broadcast.simturn.endBetween" : "Simultaneous turns between players %s and %s have ended",
|
||||||
|
"vcmi.broadcast.serverProblem" : "Server encountered a problem",
|
||||||
|
"vcmi.broadcast.gameTerminated" : "game was terminated",
|
||||||
|
"vcmi.broadcast.gameSavedAs" : "game saved as",
|
||||||
|
"vcmi.broadcast.noCheater" : "No cheaters registered!",
|
||||||
|
"vcmi.broadcast.playerCheater" : "Player %s is cheater!",
|
||||||
|
"vcmi.broadcast.statisticFile" : "Statistic files can be found in %s directory",
|
||||||
|
"vcmi.broadcast.help.commands" : "Available commands to host:",
|
||||||
|
"vcmi.broadcast.help.exit" : "'!exit' - immediately ends current game",
|
||||||
|
"vcmi.broadcast.help.kick" : "'!kick <player>' - kick specified player from the game",
|
||||||
|
"vcmi.broadcast.help.save" : "'!save <filename>' - save game under specified filename",
|
||||||
|
"vcmi.broadcast.help.statistic" : "'!statistic' - save game statistics as csv file",
|
||||||
|
"vcmi.broadcast.help.commandsAll" : "Available commands to all players:",
|
||||||
|
"vcmi.broadcast.help.help" : "'!help' - display this help",
|
||||||
|
"vcmi.broadcast.help.cheaters" : "'!cheaters' - list players that entered cheat command during game",
|
||||||
|
"vcmi.broadcast.help.vote" : "'!vote' - allows to change some game settings if all players vote for it",
|
||||||
|
"vcmi.broadcast.vote.allow" : "'!vote simturns allow X' - allow simultaneous turns for specified number of days, or until contact",
|
||||||
|
"vcmi.broadcast.vote.force" : "'!vote simturns force X' - force simultaneous turns for specified number of days, blocking player contacts",
|
||||||
|
"vcmi.broadcast.vote.abort" : "'!vote simturns abort' - abort simultaneous turns once this turn ends",
|
||||||
|
"vcmi.broadcast.vote.timer" : "'!vote timer prolong X' - prolong base timer for all players by specified number of seconds",
|
||||||
|
"vcmi.broadcast.vote.noActive" : "No active voting!",
|
||||||
|
"vcmi.broadcast.vote.yes" : "yes",
|
||||||
|
"vcmi.broadcast.vote.no" : "no",
|
||||||
|
"vcmi.broadcast.vote.notRecognized" : "Voting command not recognized!",
|
||||||
|
"vcmi.broadcast.vote.success.untilContacts" : "Voting successful. Simultaneous turns will run for %s more days, or until contact",
|
||||||
|
"vcmi.broadcast.vote.success.contactsBlocked" : "Voting successful. Simultaneous turns will run for %s more days. Contacts are blocked",
|
||||||
|
"vcmi.broadcast.vote.success.nextDay" : "Voting successful. Simultaneous turns will end on next day",
|
||||||
|
"vcmi.broadcast.vote.success.timer" : "Voting successful. Timer for all players has been prolonger for %s seconds",
|
||||||
|
"vcmi.broadcast.vote.aborted" : "Player voted against change. Voting aborted",
|
||||||
|
"vcmi.broadcast.vote.start.untilContacts" : "Started voting to allow simultaneous turns for %s more days",
|
||||||
|
"vcmi.broadcast.vote.start.contactsBlocked" : "Started voting to force simultaneous turns for %s more days",
|
||||||
|
"vcmi.broadcast.vote.start.nextDay" : "Started voting to end simultaneous turns starting from next day",
|
||||||
|
"vcmi.broadcast.vote.start.timer" : "Started voting to prolong timer for all players by %s seconds",
|
||||||
|
"vcmi.broadcast.vote.hint" : "Type '!vote yes' to agree to this change or '!vote no' to vote against it",
|
||||||
|
|
||||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||||
"vcmi.lobby.login.username" : "Username:",
|
"vcmi.lobby.login.username" : "Username:",
|
||||||
"vcmi.lobby.login.connecting" : "Connecting...",
|
"vcmi.lobby.login.connecting" : "Connecting...",
|
||||||
@ -128,6 +166,7 @@
|
|||||||
"vcmi.lobby.login.create" : "New Account",
|
"vcmi.lobby.login.create" : "New Account",
|
||||||
"vcmi.lobby.login.login" : "Login",
|
"vcmi.lobby.login.login" : "Login",
|
||||||
"vcmi.lobby.login.as" : "Login as %s",
|
"vcmi.lobby.login.as" : "Login as %s",
|
||||||
|
"vcmi.lobby.login.spectator" : "Spectator",
|
||||||
"vcmi.lobby.header.rooms" : "Game Rooms - %d",
|
"vcmi.lobby.header.rooms" : "Game Rooms - %d",
|
||||||
"vcmi.lobby.header.channels" : "Chat Channels",
|
"vcmi.lobby.header.channels" : "Chat Channels",
|
||||||
"vcmi.lobby.header.chat.global" : "Global Game Chat - %s", // %s -> language name
|
"vcmi.lobby.header.chat.global" : "Global Game Chat - %s", // %s -> language name
|
||||||
@ -189,6 +228,8 @@
|
|||||||
"vcmi.server.errors.modsToEnable" : "{Following mods are required}",
|
"vcmi.server.errors.modsToEnable" : "{Following mods are required}",
|
||||||
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",
|
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",
|
||||||
"vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!",
|
"vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!",
|
||||||
|
"vcmi.server.errors.wrongIdentified" : "You were identified as player %s while expecting %s",
|
||||||
|
"vcmi.server.errors.notAllowed" : "You are not allowed to perform this action!",
|
||||||
|
|
||||||
"vcmi.dimensionDoor.seaToLandError" : "It's not possible to teleport from sea to land or vice versa with a Dimension Door.",
|
"vcmi.dimensionDoor.seaToLandError" : "It's not possible to teleport from sea to land or vice versa with a Dimension Door.",
|
||||||
|
|
||||||
|
@ -121,6 +121,8 @@
|
|||||||
"vcmi.lobby.deleteFolder" : "Möchtet Ihr folgenden Ordner löschen?",
|
"vcmi.lobby.deleteFolder" : "Möchtet Ihr folgenden Ordner löschen?",
|
||||||
"vcmi.lobby.deleteMode" : "In den Löschmodus wechseln und zurück",
|
"vcmi.lobby.deleteMode" : "In den Löschmodus wechseln und zurück",
|
||||||
|
|
||||||
|
"vcmi.broadcast.command" : "Benutze '!help' um alle verfügbaren Befehle aufzulisten",
|
||||||
|
|
||||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||||
"vcmi.lobby.login.username" : "Benutzername:",
|
"vcmi.lobby.login.username" : "Benutzername:",
|
||||||
"vcmi.lobby.login.connecting" : "Verbinde...",
|
"vcmi.lobby.login.connecting" : "Verbinde...",
|
||||||
|
@ -403,6 +403,18 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
|
|||||||
localState->erasePath(hero);
|
localState->erasePath(hero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPlayerInterface::townRemoved(const CGTownInstance* town)
|
||||||
|
{
|
||||||
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
|
|
||||||
|
if(town->tempOwner == playerID)
|
||||||
|
{
|
||||||
|
localState->removeOwnedTown(town);
|
||||||
|
adventureInt->onTownChanged(town);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
|
void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
|
||||||
{
|
{
|
||||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
@ -1424,6 +1436,12 @@ void CPlayerInterface::objectRemoved(const CGObjectInstance * obj, const PlayerC
|
|||||||
const CGHeroInstance * h = static_cast<const CGHeroInstance *>(obj);
|
const CGHeroInstance * h = static_cast<const CGHeroInstance *>(obj);
|
||||||
heroKilled(h);
|
heroKilled(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(obj->ID == Obj::TOWN && obj->tempOwner == playerID)
|
||||||
|
{
|
||||||
|
const CGTownInstance * t = static_cast<const CGTownInstance *>(obj);
|
||||||
|
townRemoved(t);
|
||||||
|
}
|
||||||
GH.fakeMouseMove();
|
GH.fakeMouseMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +220,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
void heroKilled(const CGHeroInstance* hero);
|
void heroKilled(const CGHeroInstance* hero);
|
||||||
|
void townRemoved(const CGTownInstance* town);
|
||||||
void garrisonsChanged(std::vector<const CArmedInstance *> objs);
|
void garrisonsChanged(std::vector<const CArmedInstance *> objs);
|
||||||
void requestReturningToMainMenu(bool won);
|
void requestReturningToMainMenu(bool won);
|
||||||
void acceptTurn(QueryID queryID, bool hotseatWait); //used during hot seat after your turn message is close
|
void acceptTurn(QueryID queryID, bool hotseatWait); //used during hot seat after your turn message is close
|
||||||
|
@ -93,7 +93,7 @@ void GameChatHandler::onNewGameMessageReceived(PlayerColor sender, const std::st
|
|||||||
playerName = LOCPLINT->cb->getStartInfo()->playerInfos.at(sender).name;
|
playerName = LOCPLINT->cb->getStartInfo()->playerInfos.at(sender).name;
|
||||||
|
|
||||||
if (sender.isSpectator())
|
if (sender.isSpectator())
|
||||||
playerName = "Spectator"; // FIXME: translate? Provide nickname somewhere?
|
playerName = VLC->generaltexth->translate("vcmi.lobby.login.spectator");
|
||||||
|
|
||||||
chatHistory.push_back({playerName, messageText, timeFormatted});
|
chatHistory.push_back({playerName, messageText, timeFormatted});
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include "../render/IImage.h"
|
#include "../render/IImage.h"
|
||||||
#include "../render/IRenderHandler.h"
|
#include "../render/IRenderHandler.h"
|
||||||
#include "../render/IScreenHandler.h"
|
#include "../render/IScreenHandler.h"
|
||||||
|
#include "../render/AssetGenerator.h"
|
||||||
#include "../CMT.h"
|
#include "../CMT.h"
|
||||||
#include "../PlayerLocalState.h"
|
#include "../PlayerLocalState.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
@ -64,6 +65,8 @@ AdventureMapInterface::AdventureMapInterface():
|
|||||||
pos.w = GH.screenDimensions().x;
|
pos.w = GH.screenDimensions().x;
|
||||||
pos.h = GH.screenDimensions().y;
|
pos.h = GH.screenDimensions().y;
|
||||||
|
|
||||||
|
AssetGenerator::createPaletteShiftedSprites();
|
||||||
|
|
||||||
shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
|
shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
|
||||||
|
|
||||||
widget = std::make_shared<AdventureMapWidget>(shortcuts);
|
widget = std::make_shared<AdventureMapWidget>(shortcuts);
|
||||||
|
@ -275,7 +275,7 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
|
|||||||
return 2;
|
return 2;
|
||||||
break;
|
break;
|
||||||
case PossiblePlayerBattleAction::SHOOT:
|
case PossiblePlayerBattleAction::SHOOT:
|
||||||
if(targetStack == nullptr || targetStack->unitSide() == stack->unitSide())
|
if(targetStack == nullptr || targetStack->unitSide() == stack->unitSide() || !targetStack->alive())
|
||||||
return 100; //bottom priority
|
return 100; //bottom priority
|
||||||
|
|
||||||
return 4;
|
return 4;
|
||||||
|
@ -121,21 +121,31 @@ void MapTileStorage::load(size_t index, const AnimationPath & filename, EImageBl
|
|||||||
terrainAnimations[3]->horizontalFlip();
|
terrainAnimations[3]->horizontalFlip();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
|
std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex, size_t groupIndex)
|
||||||
{
|
{
|
||||||
const auto & animation = animations[fileIndex][rotationIndex];
|
const auto & animation = animations[fileIndex][rotationIndex];
|
||||||
if (animation)
|
if (animation)
|
||||||
return animation->getImage(imageIndex);
|
return animation->getImage(imageIndex, groupIndex);
|
||||||
else
|
else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MapTileStorage::groupCount(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
|
||||||
|
{
|
||||||
|
const auto & animation = animations[fileIndex][rotationIndex];
|
||||||
|
if (animation)
|
||||||
|
for(int i = 0;; i++)
|
||||||
|
if(!animation->getImage(imageIndex, i, false))
|
||||||
|
return i;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
MapRendererTerrain::MapRendererTerrain()
|
MapRendererTerrain::MapRendererTerrain()
|
||||||
: storage(VLC->terrainTypeHandler->objects.size())
|
: storage(VLC->terrainTypeHandler->objects.size())
|
||||||
{
|
{
|
||||||
logGlobal->debug("Loading map terrains");
|
logGlobal->debug("Loading map terrains");
|
||||||
for(const auto & terrain : VLC->terrainTypeHandler->objects)
|
for(const auto & terrain : VLC->terrainTypeHandler->objects)
|
||||||
storage.load(terrain->getIndex(), terrain->tilesFilename, EImageBlitMode::OPAQUE);
|
storage.load(terrain->getIndex(), AnimationPath::builtin(terrain->tilesFilename.getName() + (terrain->paletteAnimation.size() ? "_Shifted": "")), EImageBlitMode::OPAQUE);
|
||||||
logGlobal->debug("Done loading map terrains");
|
logGlobal->debug("Done loading map terrains");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +157,8 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ
|
|||||||
int32_t imageIndex = mapTile.terView;
|
int32_t imageIndex = mapTile.terView;
|
||||||
int32_t rotationIndex = mapTile.extTileFlags % 4;
|
int32_t rotationIndex = mapTile.extTileFlags % 4;
|
||||||
|
|
||||||
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
|
int groupCount = storage.groupCount(terrainIndex, rotationIndex, imageIndex);
|
||||||
|
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex, groupCount > 1 ? context.terrainImageIndex(groupCount) : 0);
|
||||||
|
|
||||||
assert(image);
|
assert(image);
|
||||||
if (!image)
|
if (!image)
|
||||||
@ -156,9 +167,6 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for( auto const & element : mapTile.getTerrain()->paletteAnimation)
|
|
||||||
image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length));
|
|
||||||
|
|
||||||
target.draw(image, Point(0, 0));
|
target.draw(image, Point(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +184,7 @@ MapRendererRiver::MapRendererRiver()
|
|||||||
{
|
{
|
||||||
logGlobal->debug("Loading map rivers");
|
logGlobal->debug("Loading map rivers");
|
||||||
for(const auto & river : VLC->riverTypeHandler->objects)
|
for(const auto & river : VLC->riverTypeHandler->objects)
|
||||||
storage.load(river->getIndex(), river->tilesFilename, EImageBlitMode::COLORKEY);
|
storage.load(river->getIndex(), AnimationPath::builtin(river->tilesFilename.getName() + (river->paletteAnimation.size() ? "_Shifted": "")), EImageBlitMode::COLORKEY);
|
||||||
logGlobal->debug("Done loading map rivers");
|
logGlobal->debug("Done loading map rivers");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,10 +199,8 @@ void MapRendererRiver::renderTile(IMapRendererContext & context, Canvas & target
|
|||||||
int32_t imageIndex = mapTile.riverDir;
|
int32_t imageIndex = mapTile.riverDir;
|
||||||
int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
|
int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
|
||||||
|
|
||||||
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
|
int groupCount = storage.groupCount(terrainIndex, rotationIndex, imageIndex);
|
||||||
|
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex, groupCount > 1 ? context.terrainImageIndex(groupCount) : 0);
|
||||||
for( auto const & element : mapTile.getRiver()->paletteAnimation)
|
|
||||||
image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length));
|
|
||||||
|
|
||||||
target.draw(image, Point(0, 0));
|
target.draw(image, Point(0, 0));
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,8 @@ class MapTileStorage
|
|||||||
public:
|
public:
|
||||||
explicit MapTileStorage(size_t capacity);
|
explicit MapTileStorage(size_t capacity);
|
||||||
void load(size_t index, const AnimationPath & filename, EImageBlitMode blitMode);
|
void load(size_t index, const AnimationPath & filename, EImageBlitMode blitMode);
|
||||||
std::shared_ptr<IImage> find(size_t fileIndex, size_t rotationIndex, size_t imageIndex);
|
std::shared_ptr<IImage> find(size_t fileIndex, size_t rotationIndex, size_t imageIndex, size_t groupIndex = 0);
|
||||||
|
int groupCount(size_t fileIndex, size_t rotationIndex, size_t imageIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
class MapRendererTerrain
|
class MapRendererTerrain
|
||||||
|
@ -16,12 +16,16 @@
|
|||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/ColorFilter.h"
|
#include "../render/ColorFilter.h"
|
||||||
#include "../render/IRenderHandler.h"
|
#include "../render/IRenderHandler.h"
|
||||||
|
#include "../render/CAnimation.h"
|
||||||
|
|
||||||
#include "../lib/filesystem/Filesystem.h"
|
#include "../lib/filesystem/Filesystem.h"
|
||||||
#include "../lib/GameSettings.h"
|
#include "../lib/GameSettings.h"
|
||||||
#include "../lib/IGameSettings.h"
|
#include "../lib/IGameSettings.h"
|
||||||
#include "../lib/json/JsonNode.h"
|
#include "../lib/json/JsonNode.h"
|
||||||
#include "../lib/VCMI_Lib.h"
|
#include "../lib/VCMI_Lib.h"
|
||||||
|
#include "../lib/RiverHandler.h"
|
||||||
|
#include "../lib/RoadHandler.h"
|
||||||
|
#include "../lib/TerrainHandler.h"
|
||||||
|
|
||||||
void AssetGenerator::generateAll()
|
void AssetGenerator::generateAll()
|
||||||
{
|
{
|
||||||
@ -32,6 +36,7 @@ void AssetGenerator::generateAll()
|
|||||||
createCombatUnitNumberWindow();
|
createCombatUnitNumberWindow();
|
||||||
createCampaignBackground();
|
createCampaignBackground();
|
||||||
createChroniclesCampaignImages();
|
createChroniclesCampaignImages();
|
||||||
|
createPaletteShiftedSprites();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetGenerator::createAdventureOptionsCleanBackground()
|
void AssetGenerator::createAdventureOptionsCleanBackground()
|
||||||
@ -326,3 +331,106 @@ void AssetGenerator::createChroniclesCampaignImages()
|
|||||||
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
|
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssetGenerator::createPaletteShiftedSprites()
|
||||||
|
{
|
||||||
|
std::vector<std::string> tiles;
|
||||||
|
std::vector<std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>>> paletteAnimations;
|
||||||
|
for(auto entity : VLC->terrainTypeHandler->objects)
|
||||||
|
{
|
||||||
|
if(entity->paletteAnimation.size())
|
||||||
|
{
|
||||||
|
tiles.push_back(entity->tilesFilename.getName());
|
||||||
|
std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>> tmpAnim;
|
||||||
|
for(auto & animEntity : entity->paletteAnimation)
|
||||||
|
tmpAnim.push_back(animEntity);
|
||||||
|
paletteAnimations.push_back(tmpAnim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(auto entity : VLC->riverTypeHandler->objects)
|
||||||
|
{
|
||||||
|
if(entity->paletteAnimation.size())
|
||||||
|
{
|
||||||
|
tiles.push_back(entity->tilesFilename.getName());
|
||||||
|
std::vector<std::variant<TerrainPaletteAnimation, RiverPaletteAnimation>> tmpAnim;
|
||||||
|
for(auto & animEntity : entity->paletteAnimation)
|
||||||
|
tmpAnim.push_back(animEntity);
|
||||||
|
paletteAnimations.push_back(tmpAnim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < tiles.size(); i++)
|
||||||
|
{
|
||||||
|
auto sprite = tiles[i];
|
||||||
|
|
||||||
|
JsonNode config;
|
||||||
|
config["basepath"].String() = sprite + "_Shifted/";
|
||||||
|
config["images"].Vector();
|
||||||
|
|
||||||
|
auto filename = AnimationPath::builtin(sprite).addPrefix("SPRITES/");
|
||||||
|
auto filenameNew = AnimationPath::builtin(sprite + "_Shifted").addPrefix("SPRITES/");
|
||||||
|
|
||||||
|
if(CResourceHandler::get()->existsResource(ResourcePath(filenameNew.getName(), EResType::JSON))) // overridden by mod, no generation
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto anim = GH.renderHandler().loadAnimation(filename, EImageBlitMode::COLORKEY);
|
||||||
|
for(int j = 0; j < anim->size(); j++)
|
||||||
|
{
|
||||||
|
int maxLen = 1;
|
||||||
|
for(int k = 0; k < paletteAnimations[i].size(); k++)
|
||||||
|
{
|
||||||
|
auto element = paletteAnimations[i][k];
|
||||||
|
if(std::holds_alternative<TerrainPaletteAnimation>(element))
|
||||||
|
maxLen = std::lcm(maxLen, std::get<TerrainPaletteAnimation>(element).length);
|
||||||
|
else
|
||||||
|
maxLen = std::lcm(maxLen, std::get<RiverPaletteAnimation>(element).length);
|
||||||
|
}
|
||||||
|
for(int l = 0; l < maxLen; l++)
|
||||||
|
{
|
||||||
|
std::string spriteName = sprite + boost::str(boost::format("%02d") % j) + "_" + std::to_string(l) + ".png";
|
||||||
|
std::string filenameNewImg = "sprites/" + sprite + "_Shifted" + "/" + spriteName;
|
||||||
|
ResourcePath savePath(filenameNewImg, EResType::IMAGE);
|
||||||
|
|
||||||
|
if(!CResourceHandler::get("local")->createResource(filenameNewImg))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto imgLoc = anim->getImageLocator(j, 0);
|
||||||
|
imgLoc.scalingFactor = 1;
|
||||||
|
auto img = GH.renderHandler().loadImage(imgLoc, EImageBlitMode::COLORKEY);
|
||||||
|
for(int k = 0; k < paletteAnimations[i].size(); k++)
|
||||||
|
{
|
||||||
|
auto element = paletteAnimations[i][k];
|
||||||
|
if(std::holds_alternative<TerrainPaletteAnimation>(element))
|
||||||
|
{
|
||||||
|
auto tmp = std::get<TerrainPaletteAnimation>(element);
|
||||||
|
img->shiftPalette(tmp.start, tmp.length, l % tmp.length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto tmp = std::get<RiverPaletteAnimation>(element);
|
||||||
|
img->shiftPalette(tmp.start, tmp.length, l % tmp.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas canvas = Canvas(Point(32, 32), CanvasScalingPolicy::IGNORE);
|
||||||
|
canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2));
|
||||||
|
std::shared_ptr<IImage> image = GH.renderHandler().createImage(canvas.getInternalSurface());
|
||||||
|
image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath));
|
||||||
|
|
||||||
|
JsonNode node(JsonMap{
|
||||||
|
{ "group", JsonNode(l) },
|
||||||
|
{ "frame", JsonNode(j) },
|
||||||
|
{ "file", JsonNode(spriteName) }
|
||||||
|
});
|
||||||
|
config["images"].Vector().push_back(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourcePath savePath(filenameNew.getOriginalName(), EResType::JSON);
|
||||||
|
if(!CResourceHandler::get("local")->createResource(filenameNew.getOriginalName() + ".json"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::fstream file(CResourceHandler::get("local")->getResourceName(savePath)->c_str(), std::ofstream::out | std::ofstream::trunc);
|
||||||
|
file << config.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,4 +23,5 @@ public:
|
|||||||
static void createCombatUnitNumberWindow();
|
static void createCombatUnitNumberWindow();
|
||||||
static void createCampaignBackground();
|
static void createCampaignBackground();
|
||||||
static void createChroniclesCampaignImages();
|
static void createChroniclesCampaignImages();
|
||||||
|
static void createPaletteShiftedSprites();
|
||||||
};
|
};
|
||||||
|
@ -18,11 +18,12 @@
|
|||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
#include "../../lib/json/JsonUtils.h"
|
#include "../../lib/json/JsonUtils.h"
|
||||||
|
|
||||||
bool CAnimation::loadFrame(size_t frame, size_t group)
|
bool CAnimation::loadFrame(size_t frame, size_t group, bool verbose)
|
||||||
{
|
{
|
||||||
if(size(group) <= frame)
|
if(size(group) <= frame)
|
||||||
{
|
{
|
||||||
printError(frame, group, "LoadFrame");
|
if(verbose)
|
||||||
|
printError(frame, group, "LoadFrame");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +120,7 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra
|
|||||||
|
|
||||||
std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool verbose)
|
std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool verbose)
|
||||||
{
|
{
|
||||||
if (!loadFrame(frame, group))
|
if (!loadFrame(frame, group, verbose))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return getImageImpl(frame, group, verbose);
|
return getImageImpl(frame, group, verbose);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ private:
|
|||||||
PlayerColor player = PlayerColor::CANNOT_DETERMINE;
|
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
|
//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);
|
bool loadFrame(size_t frame, size_t group, bool verbose = true);
|
||||||
|
|
||||||
//unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount)
|
//unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount)
|
||||||
bool unloadFrame(size_t frame, size_t group);
|
bool unloadFrame(size_t frame, size_t group);
|
||||||
@ -50,8 +50,6 @@ private:
|
|||||||
void printError(size_t frame, size_t group, std::string type) const;
|
void printError(size_t frame, size_t group, std::string type) const;
|
||||||
|
|
||||||
std::shared_ptr<IImage> getImageImpl(size_t frame, size_t group=0, bool verbose=true);
|
std::shared_ptr<IImage> getImageImpl(size_t frame, size_t group=0, bool verbose=true);
|
||||||
|
|
||||||
ImageLocator getImageLocator(size_t frame, size_t group) const;
|
|
||||||
public:
|
public:
|
||||||
CAnimation(const AnimationPath & Name, std::map<size_t, std::vector <ImageLocator> > layout, EImageBlitMode mode);
|
CAnimation(const AnimationPath & Name, std::map<size_t, std::vector <ImageLocator> > layout, EImageBlitMode mode);
|
||||||
~CAnimation();
|
~CAnimation();
|
||||||
@ -74,5 +72,7 @@ public:
|
|||||||
void playerColored(PlayerColor player);
|
void playerColored(PlayerColor player);
|
||||||
|
|
||||||
void createFlippedGroup(const size_t sourceGroup, const size_t targetGroup);
|
void createFlippedGroup(const size_t sourceGroup, const size_t targetGroup);
|
||||||
|
|
||||||
|
ImageLocator getImageLocator(size_t frame, size_t group) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,6 +79,10 @@ void CExchangeController::moveArmy(bool leftToRight, std::optional<SlotID> heldS
|
|||||||
});
|
});
|
||||||
heldSlot = weakestSlot->first;
|
heldSlot = weakestSlot->first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source->getCreature(heldSlot.value()) == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
LOCPLINT->cb->bulkMoveArmy(source->id, target->id, heldSlot.value());
|
LOCPLINT->cb->bulkMoveArmy(source->id, target->id, heldSlot.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
|
|||||||
Point(484 + 35 * i, 154),
|
Point(484 + 35 * i, 154),
|
||||||
AnimationPath::builtin("quick-exchange/unitLeft.DEF"),
|
AnimationPath::builtin("quick-exchange/unitLeft.DEF"),
|
||||||
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")),
|
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")),
|
||||||
std::bind(&CExchangeController::moveStack, &controller, false, SlotID(i))));
|
[this, i]() { creatureArrowButtonCallback(false, SlotID(i)); }));
|
||||||
moveUnitFromRightToLeftButtons.back()->block(leftHeroBlock);
|
moveUnitFromRightToLeftButtons.back()->block(leftHeroBlock);
|
||||||
|
|
||||||
moveUnitFromLeftToRightButtons.push_back(
|
moveUnitFromLeftToRightButtons.push_back(
|
||||||
@ -256,7 +256,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
|
|||||||
Point(66 + 35 * i, 154),
|
Point(66 + 35 * i, 154),
|
||||||
AnimationPath::builtin("quick-exchange/unitRight.DEF"),
|
AnimationPath::builtin("quick-exchange/unitRight.DEF"),
|
||||||
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")),
|
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")),
|
||||||
std::bind(&CExchangeController::moveStack, &controller, true, SlotID(i))));
|
[this, i]() { creatureArrowButtonCallback(true, SlotID(i)); }));
|
||||||
moveUnitFromLeftToRightButtons.back()->block(rightHeroBlock);
|
moveUnitFromLeftToRightButtons.back()->block(rightHeroBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,6 +264,14 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
|
|||||||
CExchangeWindow::update();
|
CExchangeWindow::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CExchangeWindow::creatureArrowButtonCallback(bool leftToRight, SlotID slotId)
|
||||||
|
{
|
||||||
|
if (GH.isKeyboardAltDown())
|
||||||
|
controller.moveArmy(leftToRight, slotId);
|
||||||
|
else
|
||||||
|
controller.moveStack(leftToRight, slotId);
|
||||||
|
}
|
||||||
|
|
||||||
void CExchangeWindow::moveArtifactsCallback(bool leftToRight)
|
void CExchangeWindow::moveArtifactsCallback(bool leftToRight)
|
||||||
{
|
{
|
||||||
bool moveEquipped = !GH.isKeyboardShiftDown();
|
bool moveEquipped = !GH.isKeyboardShiftDown();
|
||||||
|
@ -54,6 +54,7 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
|
|||||||
std::shared_ptr<CButton> backpackButtonRight;
|
std::shared_ptr<CButton> backpackButtonRight;
|
||||||
CExchangeController controller;
|
CExchangeController controller;
|
||||||
|
|
||||||
|
void creatureArrowButtonCallback(bool leftToRight, SlotID slotID);
|
||||||
void moveArtifactsCallback(bool leftToRight);
|
void moveArtifactsCallback(bool leftToRight);
|
||||||
void swapArtifactsCallback();
|
void swapArtifactsCallback();
|
||||||
void moveUnitsShortcut(bool leftToRight);
|
void moveUnitsShortcut(bool leftToRight);
|
||||||
|
@ -117,14 +117,7 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
|
|||||||
color = "";
|
color = "";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
printableString.append(text.data() + currPos, symbolSize);
|
||||||
std::string character = "";
|
|
||||||
character.append(text.data() + currPos, symbolSize);
|
|
||||||
if(fontPtr->getStringWidth(printableString + character) > maxLineWidth)
|
|
||||||
break;
|
|
||||||
printableString += character;
|
|
||||||
}
|
|
||||||
|
|
||||||
currPos += symbolSize;
|
currPos += symbolSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,61 +1,101 @@
|
|||||||
{
|
{
|
||||||
|
// "maxRoamingHeroes" - AI will never recruit new heroes above this value.
|
||||||
|
// Note that AI might end up with more heroes - due to prisons or if he has large number of heroes on start
|
||||||
|
//
|
||||||
|
// "maxpass" - ???
|
||||||
|
//
|
||||||
|
// "mainHeroTurnDistanceLimit" - AI will only run pathfinding for specified number of turns for his main hero.
|
||||||
|
// "scoutHeroTurnDistanceLimit" - AI will only run pathfinding for specified number of turns for his secondary (scout) heroes
|
||||||
|
// Limiting this will make AI faster, but may result in AI being unable to discover objects outside of this range
|
||||||
|
//
|
||||||
|
// "maxGoldPressure" - ???
|
||||||
|
//
|
||||||
|
// "useTroopsFromGarrisons" - AI can take troops from garrisons on map.
|
||||||
|
// Note that at the moment AI will not deliberately seek out such garrisons, he can only take troops from them when passing through.
|
||||||
|
// This option is always disabled on H3 RoE campaign maps to be in line with H3 AI
|
||||||
|
//
|
||||||
|
// "openMap" - AI will use map reveal cheat if cheats are enabled and AI is not allied with human player
|
||||||
|
// This improves AI decision making, but may lead AI to deliberately targeting targets that he should not be able to see at the moment
|
||||||
|
//
|
||||||
|
// "allowObjectGraph" - if used, AI will build "cache" for pathfinder on first turn, which should make AI faster. Requires openMap.
|
||||||
|
//
|
||||||
|
// "pathfinderBucketsCount" - ???
|
||||||
|
// "pathfinderBucketSize" - ???
|
||||||
|
//
|
||||||
|
// "retreatThresholdRelative" - AI will consider retreating from battle only if his troops are less than specified ratio compated to enemy
|
||||||
|
// "retreatThresholdAbsolute" - AI will consider retreating from battle only if total fight value of his troops are less than specified value
|
||||||
|
//
|
||||||
|
// "maxArmyLossTarget" - AI will try keep army loss below specified target
|
||||||
|
//
|
||||||
|
// "safeAttackRatio" - TODO: figure out how exactly it affects AI decision making
|
||||||
|
//
|
||||||
|
// "useFuzzy" - allow using of fuzzy logic. TODO: better description
|
||||||
|
|
||||||
|
|
||||||
"pawn" : {
|
"pawn" : {
|
||||||
"maxRoamingHeroes" : 8,
|
"maxRoamingHeroes" : 4, //H3 value: 3,
|
||||||
"maxpass" : 30,
|
"maxpass" : 30,
|
||||||
"mainHeroTurnDistanceLimit" : 10,
|
"mainHeroTurnDistanceLimit" : 10,
|
||||||
"scoutHeroTurnDistanceLimit" : 5,
|
"scoutHeroTurnDistanceLimit" : 5,
|
||||||
"maxGoldPressure" : 0.3,
|
"maxGoldPressure" : 0.3,
|
||||||
|
"updateHitmapOnTileReveal" : false,
|
||||||
"useTroopsFromGarrisons" : true,
|
"useTroopsFromGarrisons" : true,
|
||||||
"openMap": false,
|
"openMap": true,
|
||||||
"allowObjectGraph": false,
|
"allowObjectGraph": false,
|
||||||
"pathfinderBucketsCount" : 1, // old value: 3,
|
"pathfinderBucketsCount" : 1, // old value: 3,
|
||||||
"pathfinderBucketSize" : 32, // old value: 7,
|
"pathfinderBucketSize" : 32, // old value: 7,
|
||||||
"retreatThresholdRelative" : 0.3,
|
"retreatThresholdRelative" : 0,
|
||||||
"retreatThresholdAbsolute" : 10000,
|
"retreatThresholdAbsolute" : 0,
|
||||||
"safeAttackRatio" : 1.1,
|
"safeAttackRatio" : 1.1,
|
||||||
|
"maxArmyLossTarget" : 0.5,
|
||||||
"useFuzzy" : false
|
"useFuzzy" : false
|
||||||
},
|
},
|
||||||
|
|
||||||
"knight" : {
|
"knight" : {
|
||||||
"maxRoamingHeroes" : 8,
|
"maxRoamingHeroes" : 6, //H3 value: 3,
|
||||||
"maxpass" : 30,
|
"maxpass" : 30,
|
||||||
"mainHeroTurnDistanceLimit" : 10,
|
"mainHeroTurnDistanceLimit" : 10,
|
||||||
"scoutHeroTurnDistanceLimit" : 5,
|
"scoutHeroTurnDistanceLimit" : 5,
|
||||||
"maxGoldPressure" : 0.3,
|
"maxGoldPressure" : 0.3,
|
||||||
|
"updateHitmapOnTileReveal" : false,
|
||||||
"useTroopsFromGarrisons" : true,
|
"useTroopsFromGarrisons" : true,
|
||||||
"openMap": false,
|
"openMap": true,
|
||||||
"allowObjectGraph": false,
|
"allowObjectGraph": false,
|
||||||
"pathfinderBucketsCount" : 1, // old value: 3,
|
"pathfinderBucketsCount" : 1, // old value: 3,
|
||||||
"pathfinderBucketSize" : 32, // old value: 7,
|
"pathfinderBucketSize" : 32, // old value: 7,
|
||||||
"retreatThresholdRelative" : 0.3,
|
"retreatThresholdRelative" : 0.1,
|
||||||
"retreatThresholdAbsolute" : 10000,
|
"retreatThresholdAbsolute" : 5000,
|
||||||
"safeAttackRatio" : 1.1,
|
"safeAttackRatio" : 1.1,
|
||||||
|
"maxArmyLossTarget" : 0.35,
|
||||||
"useFuzzy" : false
|
"useFuzzy" : false
|
||||||
},
|
},
|
||||||
|
|
||||||
"rook" : {
|
"rook" : {
|
||||||
"maxRoamingHeroes" : 8,
|
"maxRoamingHeroes" : 8, //H3 value: 4
|
||||||
"maxpass" : 30,
|
"maxpass" : 30,
|
||||||
"mainHeroTurnDistanceLimit" : 10,
|
"mainHeroTurnDistanceLimit" : 10,
|
||||||
"scoutHeroTurnDistanceLimit" : 5,
|
"scoutHeroTurnDistanceLimit" : 5,
|
||||||
"maxGoldPressure" : 0.3,
|
"maxGoldPressure" : 0.3,
|
||||||
|
"updateHitmapOnTileReveal" : false,
|
||||||
"useTroopsFromGarrisons" : true,
|
"useTroopsFromGarrisons" : true,
|
||||||
"openMap": false,
|
"openMap": true,
|
||||||
"allowObjectGraph": false,
|
"allowObjectGraph": false,
|
||||||
"pathfinderBucketsCount" : 1, // old value: 3,
|
"pathfinderBucketsCount" : 1, // old value: 3,
|
||||||
"pathfinderBucketSize" : 32, // old value: 7,
|
"pathfinderBucketSize" : 32, // old value: 7,
|
||||||
"retreatThresholdRelative" : 0.3,
|
"retreatThresholdRelative" : 0.3,
|
||||||
"retreatThresholdAbsolute" : 10000,
|
"retreatThresholdAbsolute" : 10000,
|
||||||
"safeAttackRatio" : 1.1,
|
"safeAttackRatio" : 1.1,
|
||||||
|
"maxArmyLossTarget" : 0.25,
|
||||||
"useFuzzy" : false
|
"useFuzzy" : false
|
||||||
},
|
},
|
||||||
|
|
||||||
"queen" : {
|
"queen" : {
|
||||||
"maxRoamingHeroes" : 8,
|
"maxRoamingHeroes" : 8, //H3 value: 5
|
||||||
"maxpass" : 30,
|
"maxpass" : 30,
|
||||||
"mainHeroTurnDistanceLimit" : 10,
|
"mainHeroTurnDistanceLimit" : 10,
|
||||||
"scoutHeroTurnDistanceLimit" : 5,
|
"scoutHeroTurnDistanceLimit" : 5,
|
||||||
"maxGoldPressure" : 0.3,
|
"maxGoldPressure" : 0.3,
|
||||||
|
"updateHitmapOnTileReveal" : false,
|
||||||
"useTroopsFromGarrisons" : true,
|
"useTroopsFromGarrisons" : true,
|
||||||
"openMap": true,
|
"openMap": true,
|
||||||
"allowObjectGraph": false,
|
"allowObjectGraph": false,
|
||||||
@ -64,15 +104,17 @@
|
|||||||
"retreatThresholdRelative" : 0.3,
|
"retreatThresholdRelative" : 0.3,
|
||||||
"retreatThresholdAbsolute" : 10000,
|
"retreatThresholdAbsolute" : 10000,
|
||||||
"safeAttackRatio" : 1.1,
|
"safeAttackRatio" : 1.1,
|
||||||
|
"maxArmyLossTarget" : 0.25,
|
||||||
"useFuzzy" : false
|
"useFuzzy" : false
|
||||||
},
|
},
|
||||||
|
|
||||||
"king" : {
|
"king" : {
|
||||||
"maxRoamingHeroes" : 8,
|
"maxRoamingHeroes" : 8, //H3 value: 6
|
||||||
"maxpass" : 30,
|
"maxpass" : 30,
|
||||||
"mainHeroTurnDistanceLimit" : 10,
|
"mainHeroTurnDistanceLimit" : 10,
|
||||||
"scoutHeroTurnDistanceLimit" : 5,
|
"scoutHeroTurnDistanceLimit" : 5,
|
||||||
"maxGoldPressure" : 0.3,
|
"maxGoldPressure" : 0.3,
|
||||||
|
"updateHitmapOnTileReveal" : false,
|
||||||
"useTroopsFromGarrisons" : true,
|
"useTroopsFromGarrisons" : true,
|
||||||
"openMap": true,
|
"openMap": true,
|
||||||
"allowObjectGraph": false,
|
"allowObjectGraph": false,
|
||||||
@ -81,6 +123,7 @@
|
|||||||
"retreatThresholdRelative" : 0.3,
|
"retreatThresholdRelative" : 0.3,
|
||||||
"retreatThresholdAbsolute" : 10000,
|
"retreatThresholdAbsolute" : 10000,
|
||||||
"safeAttackRatio" : 1.1,
|
"safeAttackRatio" : 1.1,
|
||||||
|
"maxArmyLossTarget" : 0.25,
|
||||||
"useFuzzy" : false
|
"useFuzzy" : false
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -55,7 +55,7 @@ Updaters are objects attached to bonuses. They can modify a bonus (typically by
|
|||||||
|
|
||||||
The following example shows an artifact providing a bonus based on the level of the hero that wears it:
|
The following example shows an artifact providing a bonus based on the level of the hero that wears it:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"core:greaterGnollsFlail":
|
"core:greaterGnollsFlail":
|
||||||
{
|
{
|
||||||
"text" : { "description" : "This mighty flail increases the attack of all gnolls under the hero's command by twice the hero's level." },
|
"text" : { "description" : "This mighty flail increases the attack of all gnolls under the hero's command by twice the hero's level." },
|
||||||
|
@ -22,7 +22,7 @@ The following instructions apply to **v1.2 and later**. For earlier versions the
|
|||||||
|
|
||||||
Clone <https://github.com/vcmi/vcmi> with submodules. Example for command line:
|
Clone <https://github.com/vcmi/vcmi> with submodules. Example for command line:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
git clone --recurse-submodules https://github.com/vcmi/vcmi.git
|
git clone --recurse-submodules https://github.com/vcmi/vcmi.git
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ Conan must be aware of the NDK location when you execute `conan install`. There'
|
|||||||
- the easiest is to download NDK from Conan (option 1 in the docs), then all the magic happens automatically. On the step where you need to replace **PROFILE**, choose *android-**X**-ndk* where ***X*** is either `32` or `64`.
|
- the easiest is to download NDK from Conan (option 1 in the docs), then all the magic happens automatically. On the step where you need to replace **PROFILE**, choose *android-**X**-ndk* where ***X*** is either `32` or `64`.
|
||||||
- to use an already installed NDK, you can simply pass it on the command line to `conan install`: (note that this will work only when consuming the pre-built binaries)
|
- to use an already installed NDK, you can simply pass it on the command line to `conan install`: (note that this will work only when consuming the pre-built binaries)
|
||||||
|
|
||||||
```
|
```sh
|
||||||
conan install -c tools.android:ndk_path=/path/to/ndk ...
|
conan install -c tools.android:ndk_path=/path/to/ndk ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ And put it into build directory. Then run `nix-shell` before running any build c
|
|||||||
|
|
||||||
We recommend the following directory structure:
|
We recommend the following directory structure:
|
||||||
|
|
||||||
```
|
```text
|
||||||
.
|
.
|
||||||
├── vcmi -> contains sources and is under git control
|
├── vcmi -> contains sources and is under git control
|
||||||
└── build -> contains build output, makefiles, object files,...
|
└── build -> contains build output, makefiles, object files,...
|
||||||
@ -97,7 +97,7 @@ See [CMake](CMake.md) for a list of options
|
|||||||
|
|
||||||
### Trigger build
|
### Trigger build
|
||||||
|
|
||||||
```
|
```sh
|
||||||
cmake --build . -j8
|
cmake --build . -j8
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
Clone <https://github.com/vcmi/vcmi> with submodules. Example for command line:
|
Clone <https://github.com/vcmi/vcmi> with submodules. Example for command line:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
git clone --recurse-submodules https://github.com/vcmi/vcmi.git
|
git clone --recurse-submodules https://github.com/vcmi/vcmi.git
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -36,9 +36,8 @@ There're a few [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-
|
|||||||
|
|
||||||
Open terminal and `cd` to the directory with source code. Configuration example for device with Conan:
|
Open terminal and `cd` to the directory with source code. Configuration example for device with Conan:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
cmake --preset ios-device-conan \
|
cmake --preset ios-device-conan -D BUNDLE_IDENTIFIER_PREFIX=com.MY-NAME
|
||||||
-D BUNDLE_IDENTIFIER_PREFIX=com.MY-NAME
|
|
||||||
```
|
```
|
||||||
|
|
||||||
By default build directory containing Xcode project will appear at `../build-ios-device-conan`, but you can change it with `-B` option.
|
By default build directory containing Xcode project will appear at `../build-ios-device-conan`, but you can change it with `-B` option.
|
||||||
@ -61,7 +60,7 @@ You must also install game files, see [Installation on iOS](../players/Installat
|
|||||||
|
|
||||||
### From command line
|
### From command line
|
||||||
|
|
||||||
```
|
```sh
|
||||||
cmake --build <path to build directory> --target vcmiclient -- -quiet
|
cmake --build <path to build directory> --target vcmiclient -- -quiet
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
Clone <https://github.com/vcmi/vcmi> with submodules. Example for command line:
|
Clone <https://github.com/vcmi/vcmi> with submodules. Example for command line:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
git clone --recurse-submodules https://github.com/vcmi/vcmi.git
|
git clone --recurse-submodules https://github.com/vcmi/vcmi.git
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
* Enables debug info and disables optimizations
|
* Enables debug info and disables optimizations
|
||||||
* `-D CMAKE_EXPORT_COMPILE_COMMANDS=ON`
|
* `-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
|
* 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
|
||||||
```
|
```text
|
||||||
CompileFlags:
|
CompileFlags:
|
||||||
CompilationDatabase: build
|
CompilationDatabase: build
|
||||||
```
|
```
|
||||||
and place it here:
|
and place it here:
|
||||||
```
|
```text
|
||||||
.
|
.
|
||||||
├── vcmi -> contains sources and is under git control
|
├── vcmi -> contains sources and is under git control
|
||||||
├── build -> contains build output, makefiles, object files,...
|
├── build -> contains build output, makefiles, object files,...
|
||||||
|
@ -126,7 +126,7 @@ In these examples only the minimum required amount of options is passed to `cmak
|
|||||||
|
|
||||||
### Use our prebuilt binaries to build for macOS x86_64 with Xcode
|
### Use our prebuilt binaries to build for macOS x86_64 with Xcode
|
||||||
|
|
||||||
```
|
```sh
|
||||||
conan install . \
|
conan install . \
|
||||||
--install-folder=conan-generated \
|
--install-folder=conan-generated \
|
||||||
--no-imports \
|
--no-imports \
|
||||||
@ -143,7 +143,7 @@ cmake -S . -B build -G Xcode \
|
|||||||
|
|
||||||
If you also want to build the missing binaries from source, use `--build=missing` instead of `--build=never`.
|
If you also want to build the missing binaries from source, use `--build=missing` instead of `--build=never`.
|
||||||
|
|
||||||
```
|
```sh
|
||||||
conan install . \
|
conan install . \
|
||||||
--install-folder=~/my-dir \
|
--install-folder=~/my-dir \
|
||||||
--no-imports \
|
--no-imports \
|
||||||
@ -158,7 +158,7 @@ cmake -S . -B build \
|
|||||||
|
|
||||||
### Use our prebuilt binaries to build for iOS arm64 device with custom preset
|
### Use our prebuilt binaries to build for iOS arm64 device with custom preset
|
||||||
|
|
||||||
```
|
```sh
|
||||||
conan install . \
|
conan install . \
|
||||||
--install-folder=~/my-dir \
|
--install-folder=~/my-dir \
|
||||||
--no-imports \
|
--no-imports \
|
||||||
@ -172,7 +172,7 @@ cmake --preset ios-conan
|
|||||||
|
|
||||||
`CMakeUserPresets.json` file:
|
`CMakeUserPresets.json` file:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"cmakeMinimumRequired": {
|
"cmakeMinimumRequired": {
|
||||||
@ -205,7 +205,7 @@ ubuntu
|
|||||||
|
|
||||||
Next steps are identical both in WSL and in real Ubuntu 22.04
|
Next steps are identical both in WSL and in real Ubuntu 22.04
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
sudo pip3 install conan
|
sudo pip3 install conan
|
||||||
sudo apt install cmake build-essential
|
sudo apt install cmake build-essential
|
||||||
sed -i 's/x86_64-w64-mingw32/i686-w64-mingw32/g' CI/mingw-ubuntu/before-install.sh
|
sed -i 's/x86_64-w64-mingw32/i686-w64-mingw32/g' CI/mingw-ubuntu/before-install.sh
|
||||||
|
@ -24,7 +24,7 @@ Some notes:
|
|||||||
|
|
||||||
### Setup settings.json
|
### Setup settings.json
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"logging" : {
|
"logging" : {
|
||||||
"console" : {
|
"console" : {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
//general purpose script, Lua or ERM, runs on server
|
//general purpose script, Lua or ERM, runs on server
|
||||||
"myScript":
|
"myScript":
|
||||||
@ -65,7 +65,7 @@ TODO **In near future Lua API may change drastically several times. Information
|
|||||||
|
|
||||||
#### Low level events API
|
#### Low level events API
|
||||||
|
|
||||||
``` Lua
|
```lua
|
||||||
|
|
||||||
-- Each event type must be loaded first
|
-- Each event type must be loaded first
|
||||||
local PlayerGotTurn = require("events.PlayerGotTurn")
|
local PlayerGotTurn = require("events.PlayerGotTurn")
|
||||||
|
@ -6,7 +6,7 @@ For implementation details see files located at `lib/network` directory.
|
|||||||
|
|
||||||
VCMI uses connection using TCP to communicate with server, even in single-player games. However, even though TCP is stream-based protocol, VCMI uses atomic messages for communication. Each message is a serialized stream of bytes, preceded by 4-byte message size:
|
VCMI uses connection using TCP to communicate with server, even in single-player games. However, even though TCP is stream-based protocol, VCMI uses atomic messages for communication. Each message is a serialized stream of bytes, preceded by 4-byte message size:
|
||||||
|
|
||||||
```
|
```cpp
|
||||||
int32_t messageSize;
|
int32_t messageSize;
|
||||||
byte messagePayload[messageSize];
|
byte messagePayload[messageSize];
|
||||||
```
|
```
|
||||||
@ -37,7 +37,7 @@ For implementation details see:
|
|||||||
|
|
||||||
In case of global lobby, message payload uses plaintext json format - utf-8 encoded string:
|
In case of global lobby, message payload uses plaintext json format - utf-8 encoded string:
|
||||||
|
|
||||||
```
|
```cpp
|
||||||
int32_t messageSize;
|
int32_t messageSize;
|
||||||
char jsonString[messageSize];
|
char jsonString[messageSize];
|
||||||
```
|
```
|
||||||
|
@ -8,7 +8,7 @@ VCMI allows overriding HoMM3 .def files with .json replacement. Compared to .def
|
|||||||
|
|
||||||
## Format description
|
## Format description
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Base path of all images in animation. Optional.
|
// Base path of all images in animation. Optional.
|
||||||
// Can be used to avoid using long path to images
|
// Can be used to avoid using long path to images
|
||||||
@ -64,7 +64,7 @@ This json file will allow replacing .def file for a button with png images. Butt
|
|||||||
3. Blocked state. Button is blocked and can not be interacted with. Note that some buttons are never blocked and can be used without this image
|
3. Blocked state. Button is blocked and can not be interacted with. Note that some buttons are never blocked and can be used without this image
|
||||||
4. Highlighted state. This state is used by only some buttons and only in some cases. For example, in main menu buttons will appear highlighted when mouse cursor is on top of the image. Another example is buttons that can be selected, such as settings that can be toggled on or off
|
4. Highlighted state. This state is used by only some buttons and only in some cases. For example, in main menu buttons will appear highlighted when mouse cursor is on top of the image. Another example is buttons that can be selected, such as settings that can be toggled on or off
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"basepath" : "interface/MyButton", // all images are located in this directory
|
"basepath" : "interface/MyButton", // all images are located in this directory
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ This json file will allow replacing .def file for a button with png images. Butt
|
|||||||
|
|
||||||
This json file allows defining one animation sequence, for example for adventure map objects or for town buildings.
|
This json file allows defining one animation sequence, for example for adventure map objects or for town buildings.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"basepath" : "myTown/myBuilding", // all images are located in this directory
|
"basepath" : "myTown/myBuilding", // all images are located in this directory
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ The limiters take no parameters:
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"limiters" : [ "SHOOTER_ONLY" ]
|
"limiters" : [ "SHOOTER_ONLY" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ Parameters:
|
|||||||
- (optional) bonus sourceType and sourceId in struct
|
- (optional) bonus sourceType and sourceId in struct
|
||||||
- example: (from Adele's bless):
|
- example: (from Adele's bless):
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"limiters" : [
|
"limiters" : [
|
||||||
{
|
{
|
||||||
"type" : "HAS_ANOTHER_BONUS_LIMITER",
|
"type" : "HAS_ANOTHER_BONUS_LIMITER",
|
||||||
@ -82,14 +82,14 @@ Parameters:
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"limiters": [ {
|
"limiters": [ {
|
||||||
"type":"CREATURE_TYPE_LIMITER",
|
"type":"CREATURE_TYPE_LIMITER",
|
||||||
"parameters": [ "angel", true ]
|
"parameters": [ "angel", true ]
|
||||||
} ],
|
} ],
|
||||||
```
|
```
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"limiters" : [ {
|
"limiters" : [ {
|
||||||
"type" : "CREATURE_TERRAIN_LIMITER",
|
"type" : "CREATURE_TERRAIN_LIMITER",
|
||||||
"parameters" : ["sand"]
|
"parameters" : ["sand"]
|
||||||
@ -113,7 +113,7 @@ and operate on the remaining limiters in that list:
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"limiters" : [
|
"limiters" : [
|
||||||
"noneOf",
|
"noneOf",
|
||||||
"IS_UNDEAD",
|
"IS_UNDEAD",
|
||||||
|
@ -10,7 +10,7 @@ TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT range types work ONLY with creature
|
|||||||
|
|
||||||
For replacing ONLY_ENEMY_ARMY alias, you should use the following parameters of bonus:
|
For replacing ONLY_ENEMY_ARMY alias, you should use the following parameters of bonus:
|
||||||
|
|
||||||
```
|
```text
|
||||||
"propagator": "BATTLE_WIDE",
|
"propagator": "BATTLE_WIDE",
|
||||||
"propagationUpdater" : "BONUS_OWNER_UPDATER",
|
"propagationUpdater" : "BONUS_OWNER_UPDATER",
|
||||||
"limiters" : [ "OPPOSITE_SIDE" ]
|
"limiters" : [ "OPPOSITE_SIDE" ]
|
||||||
|
@ -128,7 +128,7 @@ Allows to raise different creatures than Skeletons after battle.
|
|||||||
- addInfo: Level of Necromancy secondary skill (1 - Basic, 3 - Expert)
|
- addInfo: Level of Necromancy secondary skill (1 - Basic, 3 - Expert)
|
||||||
- Example (from Cloak Of The Undead King):
|
- Example (from Cloak Of The Undead King):
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"type" : "IMPROVED_NECROMANCY",
|
"type" : "IMPROVED_NECROMANCY",
|
||||||
"subtype" : "creature.walkingDead",
|
"subtype" : "creature.walkingDead",
|
||||||
@ -256,7 +256,7 @@ Gives creature under effect of this spell additional bonus, which is hardcoded a
|
|||||||
|
|
||||||
Modifies 'val' parameter of spell effects that give bonuses by specified value. For example, Aenain makes Disrupting Ray decrease target's defense by additional 2 points:
|
Modifies 'val' parameter of spell effects that give bonuses by specified value. For example, Aenain makes Disrupting Ray decrease target's defense by additional 2 points:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"disruptingRay" : {
|
"disruptingRay" : {
|
||||||
"addInfo" : -2,
|
"addInfo" : -2,
|
||||||
"subtype" : "spell.disruptingRay",
|
"subtype" : "spell.disruptingRay",
|
||||||
@ -271,7 +271,7 @@ Modifies 'val' parameter of spell effects that give bonuses by specified value.
|
|||||||
|
|
||||||
Changes 'val' parameter of spell effects that give bonuses to a specified value. For example, Fortune cast by Melody always modifies luck by +3:
|
Changes 'val' parameter of spell effects that give bonuses to a specified value. For example, Fortune cast by Melody always modifies luck by +3:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"fortune" : {
|
"fortune" : {
|
||||||
"addInfo" : 3,
|
"addInfo" : 3,
|
||||||
"subtype" : "spell.fortune",
|
"subtype" : "spell.fortune",
|
||||||
|
@ -17,7 +17,7 @@ Check the files in *config/heroes/* for additional usage examples.
|
|||||||
Example: The following updater will cause a bonus to grow by 6 for every
|
Example: The following updater will cause a bonus to grow by 6 for every
|
||||||
40 levels. At first level, rounding will cause the bonus to be 0.
|
40 levels. At first level, rounding will cause the bonus to be 0.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"updater" : {
|
"updater" : {
|
||||||
"parameters" : [ 6, 2 ],
|
"parameters" : [ 6, 2 ],
|
||||||
"type" : "GROWS_WITH_LEVEL"
|
"type" : "GROWS_WITH_LEVEL"
|
||||||
@ -27,7 +27,7 @@ Example: The following updater will cause a bonus to grow by 6 for every
|
|||||||
Example: The following updater will cause a bonus to grow by 3 for every
|
Example: The following updater will cause a bonus to grow by 3 for every
|
||||||
20 levels. At first level, rounding will cause the bonus to be 1.
|
20 levels. At first level, rounding will cause the bonus to be 1.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"updater" : {
|
"updater" : {
|
||||||
"parameters" : [ 3 ],
|
"parameters" : [ 3 ],
|
||||||
"type" : "GROWS_WITH_LEVEL"
|
"type" : "GROWS_WITH_LEVEL"
|
||||||
@ -71,7 +71,7 @@ Remark: The stack level for war machines is 0.
|
|||||||
- Remark: this updater is designed for MOVEMENT bonus to match H3 army movement rules (in the example - actual movement updater, which produces values same as in default movement.txt).
|
- Remark: this updater is designed for MOVEMENT bonus to match H3 army movement rules (in the example - actual movement updater, which produces values same as in default movement.txt).
|
||||||
- Example:
|
- Example:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"updater" : {
|
"updater" : {
|
||||||
"parameters" : [ 20, 3, 10, 700 ],
|
"parameters" : [ 20, 3, 10, 700 ],
|
||||||
"type" : "ARMY_MOVEMENT"
|
"type" : "ARMY_MOVEMENT"
|
||||||
|
@ -4,7 +4,7 @@ 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:
|
- 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:
|
||||||
|
|
||||||
```
|
```text
|
||||||
newVal = (val * (100 + PERCENT_TO_SOURCE) / 100))
|
newVal = (val * (100 + PERCENT_TO_SOURCE) / 100))
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ newVal = (val * (100 + PERCENT_TO_SOURCE) / 100))
|
|||||||
|
|
||||||
- All bonus value types summarized and then used as subject of the following formula:
|
- All bonus value types summarized and then used as subject of the following formula:
|
||||||
|
|
||||||
```
|
```text
|
||||||
clamp(((BASE_NUMBER * (100 + PERCENT_TO_BASE) / 100) + ADDITIVE_VALUE) * (100 + PERCENT_TO_ALL) / 100), INDEPENDENT_MAX, INDEPENDENT_MIN)
|
clamp(((BASE_NUMBER * (100 + PERCENT_TO_BASE) / 100) + ADDITIVE_VALUE) * (100 + PERCENT_TO_ALL) / 100), INDEPENDENT_MAX, INDEPENDENT_MIN)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
All parameters but type are optional.
|
All parameters but type are optional.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Type of the bonus. See Bonus Types for full list
|
// Type of the bonus. See Bonus Types for full list
|
||||||
"type": "BONUS_TYPE",
|
"type": "BONUS_TYPE",
|
||||||
@ -81,7 +81,7 @@ See [Game Identifiers](Game_Identifiers.md) for full list of available identifie
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"bonus" :
|
"bonus" :
|
||||||
{
|
{
|
||||||
"type" : "HATE",
|
"type" : "HATE",
|
||||||
|
@ -22,7 +22,7 @@ Includes:
|
|||||||
|
|
||||||
Function of all of these objects can be enabled by this:
|
Function of all of these objects can be enabled by this:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"function" : "castleGates"
|
"function" : "castleGates"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -58,31 +58,31 @@ CBuilding class.
|
|||||||
|
|
||||||
#### unlock guild level
|
#### unlock guild level
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"guildLevels" : 1
|
"guildLevels" : 1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### unlock hero recruitment
|
#### unlock hero recruitment
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"allowsHeroPurchase" : true
|
"allowsHeroPurchase" : true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### unlock ship purchase
|
#### unlock ship purchase
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"allowsShipPurchase" : true
|
"allowsShipPurchase" : true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### unlock building purchase
|
#### unlock building purchase
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"allowsBuildingPurchase" : true
|
"allowsBuildingPurchase" : true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### unlocks creatures
|
#### unlocks creatures
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"dwelling" : { "level" : 1, "creature" : "archer" }
|
"dwelling" : { "level" : 1, "creature" : "archer" }
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -92,31 +92,31 @@ Turn into town bonus? What about creature-specific bonuses from hordes?
|
|||||||
|
|
||||||
#### gives resources
|
#### gives resources
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"provides" : { "gold" : 500 }
|
"provides" : { "gold" : 500 }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### gives guild spells
|
#### gives guild spells
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"guildSpells" : [5, 0, 0, 0, 0]
|
"guildSpells" : [5, 0, 0, 0, 0]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### gives thieves guild
|
#### gives thieves guild
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"thievesGuildLevels" : 1
|
"thievesGuildLevels" : 1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### gives fortifications
|
#### gives fortifications
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"fortificationLevels" : 1
|
"fortificationLevels" : 1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### gives war machine
|
#### gives war machine
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"warMachine" : "ballista"
|
"warMachine" : "ballista"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ Includes:
|
|||||||
- bonus to scouting range
|
- bonus to scouting range
|
||||||
- bonus to player
|
- bonus to player
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"bonuses" :
|
"bonuses" :
|
||||||
{
|
{
|
||||||
"moraleToDefenders" :
|
"moraleToDefenders" :
|
||||||
@ -162,12 +162,12 @@ 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
|
may no longer share same ID. However Capitol must be unique across all
|
||||||
town. Should be fixed somehow.
|
town. Should be fixed somehow.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"onePerPlayer" : true
|
"onePerPlayer" : true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### chance to be built on start
|
#### chance to be built on start
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"prebuiltChance" : 75
|
"prebuiltChance" : 75
|
||||||
```
|
```
|
||||||
|
@ -9,7 +9,7 @@ To start making campaign, create file named `header.json`. See also [Packing cam
|
|||||||
|
|
||||||
Basic structure of this file is here, each section is described in details below
|
Basic structure of this file is here, each section is described in details below
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"version" : 1,
|
"version" : 1,
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ Basic structure of this file is here, each section is described in details below
|
|||||||
|
|
||||||
In header are parameters describing campaign properties
|
In header are parameters describing campaign properties
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
...
|
...
|
||||||
"regions": {...},
|
"regions": {...},
|
||||||
"name": "Campaign name",
|
"name": "Campaign name",
|
||||||
@ -63,7 +63,7 @@ In header are parameters describing campaign properties
|
|||||||
|
|
||||||
Scenario description looks like follow:
|
Scenario description looks like follow:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"map": "maps/SomeMap",
|
"map": "maps/SomeMap",
|
||||||
"preconditions": [],
|
"preconditions": [],
|
||||||
@ -100,7 +100,7 @@ Scenario description looks like follow:
|
|||||||
|
|
||||||
Prolog and epilog properties are optional
|
Prolog and epilog properties are optional
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"video": "NEUTRALA.smk", //video to show
|
"video": "NEUTRALA.smk", //video to show
|
||||||
"music": "musicFile.ogg", //music to play, should be located in music directory
|
"music": "musicFile.ogg", //music to play, should be located in music directory
|
||||||
@ -119,7 +119,7 @@ If `startOptions` is `none`, `bonuses` field will be ignored
|
|||||||
|
|
||||||
If `startOptions` is `bonus`, bonus format may vary depending on its type.
|
If `startOptions` is `bonus`, bonus format may vary depending on its type.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"what": "",
|
"what": "",
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ If `startOptions` is `bonus`, bonus format may vary depending on its type.
|
|||||||
|
|
||||||
If `startOptions` is `crossover`, heroes from specific scenario will be moved to this scenario. Bonus format is following
|
If `startOptions` is `crossover`, heroes from specific scenario will be moved to this scenario. Bonus format is following
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"playerColor": 0,
|
"playerColor": 0,
|
||||||
"scenario": 0
|
"scenario": 0
|
||||||
@ -176,7 +176,7 @@ If `startOptions` is `crossover`, heroes from specific scenario will be moved to
|
|||||||
|
|
||||||
If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus format is following
|
If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus format is following
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"playerColor": 0,
|
"playerColor": 0,
|
||||||
"hero": "random"
|
"hero": "random"
|
||||||
@ -190,7 +190,7 @@ If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus forma
|
|||||||
|
|
||||||
Predefined campaign regions are located in file `campaign_regions.json`
|
Predefined campaign regions are located in file `campaign_regions.json`
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"background": "ownRegionBackground.png",
|
"background": "ownRegionBackground.png",
|
||||||
"suffix": ["Enabled", "Selected", "Conquered"],
|
"suffix": ["Enabled", "Selected", "Conquered"],
|
||||||
|
@ -22,7 +22,7 @@ In this tutorial we will recreate options tab to support chess timers UI.
|
|||||||
|
|
||||||
To start making mod, create following folders structure;
|
To start making mod, create following folders structure;
|
||||||
|
|
||||||
```
|
```text
|
||||||
extendedLobby/
|
extendedLobby/
|
||||||
|- content/
|
|- content/
|
||||||
| |- sprites/
|
| |- sprites/
|
||||||
@ -33,7 +33,7 @@ extendedLobby/
|
|||||||
|
|
||||||
File `mod.json` is generic and could look like this:
|
File `mod.json` is generic and could look like this:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"name" : "Configurable UI tutorial mod",
|
"name" : "Configurable UI tutorial mod",
|
||||||
"description" : "See tutorial here https://github.com/vcmi/vcmi/wiki/Configurable-UI-widgets",
|
"description" : "See tutorial here https://github.com/vcmi/vcmi/wiki/Configurable-UI-widgets",
|
||||||
@ -67,7 +67,7 @@ Open `optionsTab.json` and scroll it until you see comment `timer`. Three elemen
|
|||||||
|
|
||||||
Let's find first element, which is label
|
Let's find first element, which is label
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"items"
|
"items"
|
||||||
[
|
[
|
||||||
@ -89,7 +89,7 @@ Let's find first element, which is label
|
|||||||
|
|
||||||
And modify it a bit
|
And modify it a bit
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"name": "labelTimer", //add name, only for convenience
|
"name": "labelTimer", //add name, only for convenience
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -103,7 +103,7 @@ And modify it a bit
|
|||||||
|
|
||||||
But we also need proper background image for this label. Add image widget BEFORE labelTimer widget:
|
But we also need proper background image for this label. Add image widget BEFORE labelTimer widget:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"type": "picture",
|
"type": "picture",
|
||||||
"image": "RmgTTBk",
|
"image": "RmgTTBk",
|
||||||
@ -127,7 +127,7 @@ Copy image `DrDoCoBk.bmp` to `content/sprites/`. Button objects use animated ima
|
|||||||
For normal, pressed, blocked and highlighted. Our combo box inherits this behavior, so let's convert image to animation.
|
For normal, pressed, blocked and highlighted. Our combo box inherits this behavior, so let's convert image to animation.
|
||||||
In order to do it, we need to create file `DrDoCoBk.json` in same folder `content/sprites/` with following content:
|
In order to do it, we need to create file `DrDoCoBk.json` in same folder `content/sprites/` with following content:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"sequences" :
|
"sequences" :
|
||||||
[
|
[
|
||||||
@ -146,7 +146,7 @@ Thus we created file with animation, containing single frame which can be used f
|
|||||||
|
|
||||||
Let's add one more element after `//timer` comment:
|
Let's add one more element after `//timer` comment:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
...
|
...
|
||||||
//timer
|
//timer
|
||||||
{
|
{
|
||||||
@ -163,7 +163,7 @@ Let's add one more element after `//timer` comment:
|
|||||||
|
|
||||||
We also want to have label on the top of this combo box showing which element is selected. You need to add `items` array, where additional elements can be specified, label in our case:
|
We also want to have label on the top of this combo box showing which element is selected. You need to add `items` array, where additional elements can be specified, label in our case:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
...
|
...
|
||||||
//timer
|
//timer
|
||||||
{
|
{
|
||||||
@ -195,7 +195,7 @@ First of all, add images to `content/sprites/` folder: `List2Bk.bmp` for drop-do
|
|||||||
|
|
||||||
Now specify items inside `dropDown` field
|
Now specify items inside `dropDown` field
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"dropDown":
|
"dropDown":
|
||||||
{
|
{
|
||||||
"items":
|
"items":
|
||||||
@ -279,7 +279,7 @@ Let's hide elements, related to classic timer when chess timer is selected and s
|
|||||||
|
|
||||||
To do that, find `"variables"` part inside `optionsTab.json` and add there `"timers"` array, containing 2 elements:
|
To do that, find `"variables"` part inside `optionsTab.json` and add there `"timers"` array, containing 2 elements:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"variables":
|
"variables":
|
||||||
{
|
{
|
||||||
"timers":
|
"timers":
|
||||||
@ -310,7 +310,7 @@ Now we show and hide elements, but visually you still can some "artifacts":
|
|||||||
It's because options tab background image we use has those elements drawn. Let's hide them with overlay image `timchebk.bmp`.
|
It's because options tab background image we use has those elements drawn. Let's hide them with overlay image `timchebk.bmp`.
|
||||||
It should be drawn before all other timer elements:
|
It should be drawn before all other timer elements:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
...
|
...
|
||||||
// timer
|
// timer
|
||||||
{
|
{
|
||||||
@ -338,7 +338,7 @@ We should add text input fields, to specify different timers. We will use backgr
|
|||||||
There are 4 different timers: base, turn, battle and creature. Read about them here: <https://github.com/vcmi/vcmi/issues/1364>
|
There are 4 different timers: base, turn, battle and creature. Read about them here: <https://github.com/vcmi/vcmi/issues/1364>
|
||||||
We can add editors for them into items list, their format will be following:
|
We can add editors for them into items list, their format will be following:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"name": "chessFieldBase",
|
"name": "chessFieldBase",
|
||||||
"type": "textInput",
|
"type": "textInput",
|
||||||
@ -413,7 +413,7 @@ Predefined fonts:
|
|||||||
Hint text is a pair of strings, one is usually shown in status bar when cursor hovers element, another hint while right button pressed.
|
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)
|
Each of elements is a [Text](#text)
|
||||||
|
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"hover": "Text",
|
"hover": "Text",
|
||||||
"help": "Text
|
"help": "Text
|
||||||
@ -440,7 +440,7 @@ One of predefined values:
|
|||||||
|
|
||||||
Configurable object has following structure:
|
Configurable object has following structure:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"items": [],
|
"items": [],
|
||||||
"variables": {}, //optional
|
"variables": {}, //optional
|
||||||
@ -837,7 +837,7 @@ std::shared_ptr<MyYesNoDialog::Item> MyYesNoDialog::buildMyItem(const JsonNode &
|
|||||||
|
|
||||||
After that, if your JSON file has items with type "MyItem", the new Item element will be constructed.
|
After that, if your JSON file has items with type "MyItem", the new Item element will be constructed.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"items":
|
"items":
|
||||||
[
|
[
|
||||||
|
@ -7,7 +7,7 @@ Difficulty configuration is located in [config/difficulty.json](../config/diffic
|
|||||||
|
|
||||||
## Format summary
|
## Format summary
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"human": //parameters impacting human players only
|
"human": //parameters impacting human players only
|
||||||
{
|
{
|
||||||
@ -50,7 +50,7 @@ For both types of bonuses, `source` should be specified as `OTHER`.
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{ //will give 150% extra health to all players' creatures if specified in "battleBonuses" array
|
{ //will give 150% extra health to all players' creatures if specified in "battleBonuses" array
|
||||||
"type" : "STACK_HEALTH",
|
"type" : "STACK_HEALTH",
|
||||||
"val" : 150,
|
"val" : 150,
|
||||||
|
@ -12,7 +12,7 @@ In order to make functional artifact you also need:
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Type of this artifact - creature, hero or commander
|
// Type of this artifact - creature, hero or commander
|
||||||
"type": ["HERO", "CREATURE", "COMMANDER"]
|
"type": ["HERO", "CREATURE", "COMMANDER"]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Battle Obstacle Format
|
# Battle Obstacle Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// List of terrains on which this obstacle can be used
|
// List of terrains on which this obstacle can be used
|
||||||
"allowedTerrains" : []
|
"allowedTerrains" : []
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Battlefield Format
|
# Battlefield Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// Human-readable name of the battlefield
|
// Human-readable name of the battlefield
|
||||||
"name" : "",
|
"name" : "",
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ The purpose is to create visually attractive and consistent maps, which will als
|
|||||||
|
|
||||||
If not enough biomes are defined for [terrain type](Terrain_Format.md), map generator will fall back to using all available templates that match this terrain, which was original behavior before 1.5.0.
|
If not enough biomes are defined for [terrain type](Terrain_Format.md), map generator will fall back to using all available templates that match this terrain, which was original behavior before 1.5.0.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"obstacleSetId" : {
|
"obstacleSetId" : {
|
||||||
"biome" : {
|
"biome" : {
|
||||||
"terrain" : "grass", // Id or vector of Ids this obstacle set can spawn at
|
"terrain" : "grass", // Id or vector of Ids this obstacle set can spawn at
|
||||||
|
@ -23,7 +23,7 @@ In order to make functional creature you also need:
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// camelCase unique creature identifier
|
// camelCase unique creature identifier
|
||||||
"creatureName" :
|
"creatureName" :
|
||||||
{
|
{
|
||||||
|
@ -47,7 +47,7 @@ Each town requires a set of buildings (Around 30-45 buildings)
|
|||||||
|
|
||||||
## Faction node (root entry for town configuration)
|
## Faction node (root entry for town configuration)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// Unique faction identifier.
|
// Unique faction identifier.
|
||||||
"myFaction" :
|
"myFaction" :
|
||||||
{
|
{
|
||||||
@ -108,7 +108,7 @@ Each town requires a set of buildings (Around 30-45 buildings)
|
|||||||
|
|
||||||
## Town node
|
## Town node
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Field that describes behavior of map object part of town. Town-specific part of object format
|
// Field that describes behavior of map object part of town. Town-specific part of object format
|
||||||
"mapObject" :
|
"mapObject" :
|
||||||
@ -256,7 +256,7 @@ Each town requires a set of buildings (Around 30-45 buildings)
|
|||||||
|
|
||||||
## Siege node
|
## Siege node
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// Describes town siege screen
|
// Describes town siege screen
|
||||||
// Comments in the end of each graphic position indicate specify required suffix for image
|
// Comments in the end of each graphic position indicate specify required suffix for image
|
||||||
// Note: one not included image is battlefield background with suffix "BACK"
|
// Note: one not included image is battlefield background with suffix "BACK"
|
||||||
|
@ -9,7 +9,7 @@ In order to make functional hero class you also need:
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// Unique identifier of hero class, camelCase
|
// Unique identifier of hero class, camelCase
|
||||||
"myClassName" :
|
"myClassName" :
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ In order to make functional hero you also need:
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"myHeroName" :
|
"myHeroName" :
|
||||||
{
|
{
|
||||||
// Identifier of class this hero belongs to. Such as knight or battleMage
|
// Identifier of class this hero belongs to. Such as knight or battleMage
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"newRiver" :
|
"newRiver" :
|
||||||
{
|
{
|
||||||
// Two-letters unique identifier for this river. Used in map format
|
// Two-letters unique identifier for this river. Used in map format
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"newRoad" :
|
"newRoad" :
|
||||||
{
|
{
|
||||||
// Two-letters unique identifier for this road. Used in map format
|
// Two-letters unique identifier for this road. Used in map format
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Main format
|
## Main format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Skill be only be available on maps with water
|
// Skill be only be available on maps with water
|
||||||
"onlyOnWaterMap" : false,
|
"onlyOnWaterMap" : false,
|
||||||
@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"skillName":
|
"skillName":
|
||||||
{
|
{
|
||||||
@ -55,7 +55,7 @@ level fields become optional if they equal "base" configuration.
|
|||||||
|
|
||||||
## Skill level format
|
## Skill level format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Localizable description
|
// Localizable description
|
||||||
// Use {xxx} for formatting
|
// Use {xxx} for formatting
|
||||||
@ -87,7 +87,7 @@ level fields become optional if they equal "base" configuration.
|
|||||||
The following modifies the tactics skill to grant an additional speed
|
The following modifies the tactics skill to grant an additional speed
|
||||||
boost at advanced and expert levels.
|
boost at advanced and expert levels.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"core:tactics" : {
|
"core:tactics" : {
|
||||||
"base" : {
|
"base" : {
|
||||||
"effects" : {
|
"effects" : {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Main format
|
## Main format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"spellName":
|
"spellName":
|
||||||
{
|
{
|
||||||
@ -156,7 +156,7 @@
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"projectile": [
|
"projectile": [
|
||||||
{"minimumAngle": 0 ,"defName":"C20SPX4"},
|
{"minimumAngle": 0 ,"defName":"C20SPX4"},
|
||||||
@ -179,7 +179,7 @@ Json object with data common for all levels can be put here. These configuration
|
|||||||
|
|
||||||
This will make spell affect single target on all levels except expert, where it is massive spell.
|
This will make spell affect single target on all levels except expert, where it is massive spell.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"base":{
|
"base":{
|
||||||
"range": 0
|
"range": 0
|
||||||
},
|
},
|
||||||
@ -192,7 +192,7 @@ This will make spell affect single target on all levels except expert, where it
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
//Mandatory, localizable description. Use {xxx} for formatting
|
//Mandatory, localizable description. Use {xxx} for formatting
|
||||||
@ -262,7 +262,7 @@ Configurable spells ignore *offensive* flag, *effects* and *cumulativeEffects*.
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
"mod:effectId":{
|
"mod:effectId":{
|
||||||
|
|
||||||
@ -283,7 +283,7 @@ TODO
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
"mod:effectId":{
|
"mod:effectId":{
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ TODO
|
|||||||
|
|
||||||
Configurable version of Clone spell.
|
Configurable version of Clone spell.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
"mod:effectId":{
|
"mod:effectId":{
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ TODO
|
|||||||
|
|
||||||
If effect is automatic, spell behave like offensive spell (uses power, levelPower etc)
|
If effect is automatic, spell behave like offensive spell (uses power, levelPower etc)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
"mod:effectId":{
|
"mod:effectId":{
|
||||||
|
|
||||||
@ -368,7 +368,7 @@ TODO
|
|||||||
If effect is automatic, spell behave like \[de\]buff spell (effect and
|
If effect is automatic, spell behave like \[de\]buff spell (effect and
|
||||||
cumulativeEffects ignored)
|
cumulativeEffects ignored)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
"mod:effectId":{
|
"mod:effectId":{
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"newTerrain" :
|
"newTerrain" :
|
||||||
{
|
{
|
||||||
// Two-letters unique identifier for this terrain. Used in map format
|
// Two-letters unique identifier for this terrain. Used in map format
|
||||||
|
@ -17,7 +17,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
##### Order of Fire from Inferno
|
##### Order of Fire from Inferno
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"special4": {
|
"special4": {
|
||||||
"requires" : [ "mageGuild1" ],
|
"requires" : [ "mageGuild1" ],
|
||||||
"name" : "Order of Fire",
|
"name" : "Order of Fire",
|
||||||
@ -41,7 +41,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
##### Mana Vortex from Dungeon
|
##### Mana Vortex from Dungeon
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"special2": {
|
"special2": {
|
||||||
"requires" : [ "mageGuild1" ],
|
"requires" : [ "mageGuild1" ],
|
||||||
"name" : "Mana Vortex",
|
"name" : "Mana Vortex",
|
||||||
@ -70,7 +70,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
#### Resource Silo with custom production
|
#### Resource Silo with custom production
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"resourceSilo": {
|
"resourceSilo": {
|
||||||
"name" : "Wood Resource Silo",
|
"name" : "Wood Resource Silo",
|
||||||
"description" : "Produces 2 wood every day",
|
"description" : "Produces 2 wood every day",
|
||||||
@ -86,7 +86,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
#### Brotherhood of Sword - bonuses in siege
|
#### Brotherhood of Sword - bonuses in siege
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"special3": {
|
"special3": {
|
||||||
// replaces +1 Morale bonus from Tavern
|
// replaces +1 Morale bonus from Tavern
|
||||||
"upgradeReplacesBonuses" : true,
|
"upgradeReplacesBonuses" : true,
|
||||||
@ -103,7 +103,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
#### Lighthouse - bonus to all heroes under player control
|
#### Lighthouse - bonus to all heroes under player control
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"special1": {
|
"special1": {
|
||||||
"bonuses": [
|
"bonuses": [
|
||||||
{
|
{
|
||||||
@ -119,7 +119,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
## Town Building node
|
## Town Building node
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Numeric identifier of this building
|
// Numeric identifier of this building
|
||||||
"id" : 0,
|
"id" : 0,
|
||||||
@ -218,7 +218,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
|||||||
|
|
||||||
Building requirements can be described using logical expressions:
|
Building requirements can be described using logical expressions:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"requires" :
|
"requires" :
|
||||||
[
|
[
|
||||||
"allOf", // Normal H3 "build all" mode
|
"allOf", // Normal H3 "build all" mode
|
||||||
@ -265,7 +265,7 @@ In addition to above, it is possible to use same format as [Rewardable](../Map_O
|
|||||||
|
|
||||||
### Town Structure node
|
### Town Structure node
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Main animation file for this building
|
// Main animation file for this building
|
||||||
"animation" : "",
|
"animation" : "",
|
||||||
@ -299,12 +299,12 @@ Market buildings require list of available [modes](../Map_Objects/Market.md)
|
|||||||
|
|
||||||
##### Marketplace
|
##### Marketplace
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Artifact merchant
|
##### Artifact merchant
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
|
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
|
||||||
```
|
```
|
||||||
|
@ -16,7 +16,7 @@ Full object consists from 3 parts:
|
|||||||
|
|
||||||
## Object group format
|
## Object group format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
"myCoolObjectGroup":
|
"myCoolObjectGroup":
|
||||||
@ -99,7 +99,7 @@ These are internal types that are generally not available for modding and are ha
|
|||||||
|
|
||||||
## Object type format
|
## Object type format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"myCoolObject":
|
"myCoolObject":
|
||||||
{
|
{
|
||||||
@ -153,7 +153,7 @@ These are internal types that are generally not available for modding and are ha
|
|||||||
|
|
||||||
## Object template format
|
## Object template format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"myCoolObjectTemplate" :
|
"myCoolObjectTemplate" :
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Boat
|
# Boat
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Layer on which this boat moves. Possible values:
|
// Layer on which this boat moves. Possible values:
|
||||||
// "land" - same rules as movement of hero on land
|
// "land" - same rules as movement of hero on land
|
||||||
|
@ -10,7 +10,7 @@ Deprecated in 1.6. Please use [Rewardable Objects](Rewardable.md) instead. See C
|
|||||||
This example defines a rewardable object with functionality similar of H3 creature bank.
|
This example defines a rewardable object with functionality similar of H3 creature bank.
|
||||||
See [Rewardable Objects](Rewardable.md) for detailed documentation of these properties.
|
See [Rewardable Objects](Rewardable.md) for detailed documentation of these properties.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"name" : "Cyclops Stockpile",
|
"name" : "Cyclops Stockpile",
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ This is a list of changes that needs to be done to bank config to migrate it to
|
|||||||
|
|
||||||
### Old format (1.5 or earlier)
|
### Old format (1.5 or earlier)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
/// If true, battle setup will be like normal - Attacking player on the left, enemy on the right
|
/// If true, battle setup will be like normal - Attacking player on the left, enemy on the right
|
||||||
"regularUnitPlacement" : true,
|
"regularUnitPlacement" : true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Dwelling
|
# Dwelling
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
/// List of creatures in this bank. Each list represents one "level" of bank
|
/// List of creatures in this bank. Each list represents one "level" of bank
|
||||||
/// Creatures on the same level will have shared growth and available number (similar to towns)
|
/// Creatures on the same level will have shared growth and available number (similar to towns)
|
||||||
|
@ -9,7 +9,7 @@ Currently, it is possible to make flaggable objects that provide player with:
|
|||||||
|
|
||||||
## Format description
|
## Format description
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"baseObjectName" : {
|
"baseObjectName" : {
|
||||||
"name" : "Object name",
|
"name" : "Object name",
|
||||||
|
@ -7,7 +7,7 @@ Markets can be added as any other object with special handler called "market".
|
|||||||
|
|
||||||
Here is schema describing such object
|
Here is schema describing such object
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"seafaringAcademy" : //object name
|
"seafaringAcademy" : //object name
|
||||||
{
|
{
|
||||||
"handler" : "market", //market handler
|
"handler" : "market", //market handler
|
||||||
@ -51,19 +51,19 @@ Following options are supported:
|
|||||||
|
|
||||||
Trading post allows to exchange resources and send resources to another player, so it shall be configured this way:
|
Trading post allows to exchange resources and send resources to another player, so it shall be configured this way:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"modes" : ["resource-resource", "resource-player"]
|
"modes" : ["resource-resource", "resource-player"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Black market
|
### Black market
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"modes" : ["resource-artifact"]
|
"modes" : ["resource-artifact"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Freelance guild
|
### Freelance guild
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"modes" : ["creature-resource"]
|
"modes" : ["creature-resource"]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ Altar of sacrifice allows exchange creatures for experience for evil factions an
|
|||||||
So both modes shall be available in the market.
|
So both modes shall be available in the market.
|
||||||
Game logic prohibits using modes unavailable for faction
|
Game logic prohibits using modes unavailable for faction
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"modes" : ["creature-experience", "artifact-experience"]
|
"modes" : ["creature-experience", "artifact-experience"]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -85,14 +85,14 @@ See [Secondary skills](Rewardable.md#secondary-skills) description for more deta
|
|||||||
|
|
||||||
### Example for University of magic (e.g conflux building)
|
### Example for University of magic (e.g conflux building)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"modes" : ["resource-skill"],
|
"modes" : ["resource-skill"],
|
||||||
"offer" : ["airMagic", "waterMagic", "earthMagic", "fireMagic"]
|
"offer" : ["airMagic", "waterMagic", "earthMagic", "fireMagic"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example for regular University
|
### Example for regular University
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"modes" : ["resource-skill"],
|
"modes" : ["resource-skill"],
|
||||||
"offer" : [ //4 random skills except necromancy
|
"offer" : [ //4 random skills except necromancy
|
||||||
{ "noneOf" : ["necromancy"] },
|
{ "noneOf" : ["necromancy"] },
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"baseObjectName" : {
|
"baseObjectName" : {
|
||||||
"name" : "Object name",
|
"name" : "Object name",
|
||||||
@ -37,7 +37,7 @@ Rewardable object is defined similarly to other objects, with key difference bei
|
|||||||
|
|
||||||
## Configurable object definition
|
## Configurable object definition
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
// List of potential rewards
|
// List of potential rewards
|
||||||
"rewards" : [
|
"rewards" : [
|
||||||
{
|
{
|
||||||
@ -177,7 +177,7 @@ Variables are randomized only once, so you can use them multiple times for examp
|
|||||||
|
|
||||||
Example of creation of a variable named "gainedSkill" of type "secondarySkill":
|
Example of creation of a variable named "gainedSkill" of type "secondarySkill":
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"variables" : {
|
"variables" : {
|
||||||
"secondarySkill" : {
|
"secondarySkill" : {
|
||||||
"gainedSkill" : {
|
"gainedSkill" : {
|
||||||
@ -200,7 +200,7 @@ Possible variable types:
|
|||||||
|
|
||||||
To reference variable in limiter prepend variable name with '@' symbol:
|
To reference variable in limiter prepend variable name with '@' symbol:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"secondary" : {
|
"secondary" : {
|
||||||
"@gainedSkill" : 1
|
"@gainedSkill" : 1
|
||||||
},
|
},
|
||||||
@ -214,7 +214,7 @@ This property describes how object state should be reset. Objects without this f
|
|||||||
- If `visitors` is set to true, game will reset list of visitors (heroes and players) on start of new period, allowing revisits of objects with `visitMode` set to `once`, `hero`, or `player`. Objects with visit mode set to `bonus` are not affected. In order to allow revisit such objects use appropriate bonus duration (e.g. `ONE_DAY` or `ONE_WEEK`) instead.
|
- If `visitors` is set to true, game will reset list of visitors (heroes and players) on start of new period, allowing revisits of objects with `visitMode` set to `once`, `hero`, or `player`. Objects with visit mode set to `bonus` are not affected. In order to allow revisit such objects use appropriate bonus duration (e.g. `ONE_DAY` or `ONE_WEEK`) instead.
|
||||||
- If `rewards` is set to true, object will re-randomize its provided rewards, similar to such H3 objects as "Fountain of Fortune" or "Windmill"
|
- If `rewards` is set to true, object will re-randomize its provided rewards, similar to such H3 objects as "Fountain of Fortune" or "Windmill"
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"resetParameters" : {
|
"resetParameters" : {
|
||||||
"period" : 7,
|
"period" : 7,
|
||||||
"visitors" : true,
|
"visitors" : true,
|
||||||
@ -233,7 +233,7 @@ Note that object that uses appearChance MUST have continuous range for every val
|
|||||||
- `"min" : 66, "max" : 100`
|
- `"min" : 66, "max" : 100`
|
||||||
In other words, min chance of second reward must be equal to max chance of previous reward
|
In other words, min chance of second reward must be equal to max chance of previous reward
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"appearChance":
|
"appearChance":
|
||||||
{
|
{
|
||||||
// (Advanced) rewards with different dice number will get different dice number
|
// (Advanced) rewards with different dice number will get different dice number
|
||||||
@ -253,7 +253,7 @@ In other words, min chance of second reward must be equal to max chance of previ
|
|||||||
|
|
||||||
Unless stated othervice, all numbers in this section can be replaced with random values, e.g.
|
Unless stated othervice, all numbers in this section can be replaced with random values, e.g.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"minLevel" : { "min" : 5, "max" : 10 } // select random number between 5-10, including both 5 & 10
|
"minLevel" : { "min" : 5, "max" : 10 } // select random number between 5-10, including both 5 & 10
|
||||||
"minLevel" : [ 2, 4, 6, 8, 10] // (VCMI 1.2) select random number out of provided list, with equal chance for each
|
"minLevel" : [ 2, 4, 6, 8, 10] // (VCMI 1.2) select random number out of provided list, with equal chance for each
|
||||||
```
|
```
|
||||||
@ -265,13 +265,13 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
|
|
||||||
- Can only be used as limiter. To pass, current day of week should be equal to this value. 1 = first day of the week, 7 = last day
|
- Can only be used as limiter. To pass, current day of week should be equal to this value. 1 = first day of the week, 7 = last day
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"dayOfWeek" : 0
|
"dayOfWeek" : 0
|
||||||
```
|
```
|
||||||
|
|
||||||
- Can only be used as limiter. To pass, number of days since game started must be at equal or greater than this value
|
- Can only be used as limiter. To pass, number of days since game started must be at equal or greater than this value
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"daysPassed" : 8
|
"daysPassed" : 8
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as reward to grant resources to player
|
- Can be used as reward to grant resources to player
|
||||||
- If negative value is used as reward, it will be used as cost and take resources from player
|
- If negative value is used as reward, it will be used as cost and take resources from player
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"resources": {
|
"resources": {
|
||||||
"crystal" : 6,
|
"crystal" : 6,
|
||||||
"gold" : -1000,
|
"gold" : -1000,
|
||||||
@ -290,7 +290,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
|
|
||||||
- Alternative format that allows random selection of a resource type
|
- Alternative format that allows random selection of a resource type
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
"anyOf" : [ "wood", "ore" ],
|
"anyOf" : [ "wood", "ore" ],
|
||||||
@ -308,7 +308,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as limiter
|
- Can be used as limiter
|
||||||
- Can be used as reward to grant experience to hero
|
- Can be used as reward to grant experience to hero
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"heroExperience" : 1000,
|
"heroExperience" : 1000,
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -317,7 +317,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as limiter. Hero requires to have at least specified level
|
- Can be used as limiter. Hero requires to have at least specified level
|
||||||
- Can be used as reward, will grant hero experience amount equal to the difference between the hero's next level and current level (Tree of Knowledge)
|
- Can be used as reward, will grant hero experience amount equal to the difference between the hero's next level and current level (Tree of Knowledge)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"heroLevel" : 1,
|
"heroLevel" : 1,
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -327,13 +327,13 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as reward, to give mana points to hero. Mana points may go above mana pool limit.
|
- Can be used as reward, to give mana points to hero. Mana points may go above mana pool limit.
|
||||||
- If negative value is used as reward, it will be used as cost and take mana from player
|
- If negative value is used as reward, it will be used as cost and take mana from player
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"manaPoints": -10,
|
"manaPoints": -10,
|
||||||
```
|
```
|
||||||
|
|
||||||
- If giving mana points puts hero above mana pool limit, any overflow will be multiplied by specified percentage. If set to 0, mana will not go above mana pool limit.
|
- If giving mana points puts hero above mana pool limit, any overflow will be multiplied by specified percentage. If set to 0, mana will not go above mana pool limit.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"manaOverflowFactor" : 50,
|
"manaOverflowFactor" : 50,
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -342,7 +342,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as limiter. Hero must have at least specific mana percentage
|
- Can be used as limiter. Hero must have at least specific mana percentage
|
||||||
- Can be used to set hero mana level to specified percentage value, not restricted to mana pool limit (Magic Well, Mana Spring)
|
- Can be used to set hero mana level to specified percentage value, not restricted to mana pool limit (Magic Well, Mana Spring)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"manaPercentage": 200,
|
"manaPercentage": 200,
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -351,7 +351,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can NOT be used as limiter
|
- Can NOT be used as limiter
|
||||||
- Can be used as reward, to give movement points to hero. Movement points may go above mana pool limit.
|
- Can be used as reward, to give movement points to hero. Movement points may go above mana pool limit.
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"movePoints": 200,
|
"movePoints": 200,
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can NOT be used as limiter
|
- Can NOT be used as limiter
|
||||||
- Can be used to set hero movement points level to specified percentage value. Value of 0 will take away any remaining movement points
|
- Can be used to set hero movement points level to specified percentage value. Value of 0 will take away any remaining movement points
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"movePercentage": 50,
|
"movePercentage": 50,
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Each primary skill can be explicitly specified or randomly selected
|
- Each primary skill can be explicitly specified or randomly selected
|
||||||
- Possible values: `"attack", "defence", "spellpower", "knowledge"`
|
- Possible values: `"attack", "defence", "spellpower", "knowledge"`
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"primary": [
|
"primary": [
|
||||||
{
|
{
|
||||||
// Specific primary skill
|
// Specific primary skill
|
||||||
@ -406,7 +406,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Possible values: 1 (basic), 2 (advanced), 3 (expert)
|
- Possible values: 1 (basic), 2 (advanced), 3 (expert)
|
||||||
- Each secondary skill can be explicitly specified or randomly selected
|
- Each secondary skill can be explicitly specified or randomly selected
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"secondary": [
|
"secondary": [
|
||||||
{
|
{
|
||||||
// Specific skill
|
// Specific skill
|
||||||
@ -435,7 +435,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
|
|
||||||
- Can be used as limiter. Hero must have free skill slot to pass limiter
|
- Can be used as limiter. Hero must have free skill slot to pass limiter
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"canLearnSkills" : true
|
"canLearnSkills" : true
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -446,7 +446,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Note that unlike most values, parameter of bonuses can NOT be randomized
|
- Note that unlike most values, parameter of bonuses can NOT be randomized
|
||||||
- Description can be string or number of corresponding string from `arraytxt.txt`
|
- Description can be string or number of corresponding string from `arraytxt.txt`
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"bonuses" : [
|
"bonuses" : [
|
||||||
{
|
{
|
||||||
"type" : "MORALE",
|
"type" : "MORALE",
|
||||||
@ -463,7 +463,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as reward, to give new artifact to a hero
|
- Can be used as reward, to give new artifact to a hero
|
||||||
- Artifacts added as reward will be used for text substitution. First `%s` in text string will be replaced with name of an artifact
|
- Artifacts added as reward will be used for text substitution. First `%s` in text string will be replaced with name of an artifact
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
"ribCage"
|
"ribCage"
|
||||||
],
|
],
|
||||||
@ -473,7 +473,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- For artifact class possible values are "TREASURE", "MINOR", "MAJOR", "RELIC"
|
- For artifact class possible values are "TREASURE", "MINOR", "MAJOR", "RELIC"
|
||||||
- Artifact value range can be specified with min value and max value
|
- Artifact value range can be specified with min value and max value
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
{
|
{
|
||||||
"class" : "TREASURE",
|
"class" : "TREASURE",
|
||||||
@ -489,7 +489,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as reward, to give new spell to a hero
|
- Can be used as reward, to give new spell to a hero
|
||||||
- Spells added as reward will be used for text substitution. First `%s` in text string will be replaced with spell name
|
- Spells added as reward will be used for text substitution. First `%s` in text string will be replaced with spell name
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"spells": [
|
"spells": [
|
||||||
"magicArrow"
|
"magicArrow"
|
||||||
],
|
],
|
||||||
@ -498,7 +498,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Alternative format, random spell selection
|
- Alternative format, random spell selection
|
||||||
- Spell can be selected from specifically selected school
|
- Spell can be selected from specifically selected school
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"spells": [
|
"spells": [
|
||||||
{
|
{
|
||||||
"level" : 1,
|
"level" : 1,
|
||||||
@ -515,7 +515,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- - he does not have a spellbook
|
- - he does not have a spellbook
|
||||||
- - he does not have sufficient Wisdom level for this spell
|
- - he does not have sufficient Wisdom level for this spell
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"canLearnSpells" : [
|
"canLearnSpells" : [
|
||||||
"magicArrow"
|
"magicArrow"
|
||||||
],
|
],
|
||||||
@ -528,7 +528,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- If hero does not have enough free slots, game will show selection dialog to pick troops to keep
|
- If hero does not have enough free slots, game will show selection dialog to pick troops to keep
|
||||||
- It is possible to specify probability to receive upgraded creature
|
- It is possible to specify probability to receive upgraded creature
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"creatures" : [
|
"creatures" : [
|
||||||
{
|
{
|
||||||
"type" : "archer",
|
"type" : "archer",
|
||||||
@ -547,7 +547,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- It is possible to add up to 7 slots of creatures
|
- It is possible to add up to 7 slots of creatures
|
||||||
- Guards of the same creature type will never merge or rearrange their stacks
|
- Guards of the same creature type will never merge or rearrange their stacks
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"guards" : [
|
"guards" : [
|
||||||
{ "type" : "archer", "amount" : 20 },
|
{ "type" : "archer", "amount" : 20 },
|
||||||
{ "type" : "archer", "amount" : 20, "upgradeChance" : 30 },
|
{ "type" : "archer", "amount" : 20, "upgradeChance" : 30 },
|
||||||
@ -561,7 +561,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can be used as reward, to replace creatures in hero army. It is possible to use this parameter both for upgrades of creatures as well as for changing them into completely unrelated creature, e.g. similar to Skeleton Transformer
|
- Can be used as reward, to replace creatures in hero army. It is possible to use this parameter both for upgrades of creatures as well as for changing them into completely unrelated creature, e.g. similar to Skeleton Transformer
|
||||||
- This parameter will not change creatures given by `creatures` parameter on the same visit
|
- This parameter will not change creatures given by `creatures` parameter on the same visit
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"changeCreatures" : {
|
"changeCreatures" : {
|
||||||
"cavalier" : "champion"
|
"cavalier" : "champion"
|
||||||
}
|
}
|
||||||
@ -573,7 +573,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- As reward, instantly casts adventure map spell for visiting hero. All checks for spell book, wisdom or presence of mana will be ignored. It's possible to specify school level at which spell will be casted. If it's necessary to reduce player's mana or do some checks, they shall be introduced as limiters and other rewards
|
- As reward, instantly casts adventure map spell for visiting hero. All checks for spell book, wisdom or presence of mana will be ignored. It's possible to specify school level at which spell will be casted. If it's necessary to reduce player's mana or do some checks, they shall be introduced as limiters and other rewards
|
||||||
- School level possible values: 1 (basic), 2 (advanced), 3 (expert)
|
- School level possible values: 1 (basic), 2 (advanced), 3 (expert)
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"spellCast" : {
|
"spellCast" : {
|
||||||
"spell" : "townPortal",
|
"spell" : "townPortal",
|
||||||
"schoolLevel": 3
|
"schoolLevel": 3
|
||||||
@ -588,7 +588,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- It is possible to specify which terrain classes should be affected. Tile will be affected if sum of values its classes is positive. For example, `"water" : 1` will affect all water tiles, while `"surface" : 1, "subterra" : -1` will include terrains that have "surface" flag but do not have "subterra" flag
|
- It is possible to specify which terrain classes should be affected. Tile will be affected if sum of values its classes is positive. For example, `"water" : 1` will affect all water tiles, while `"surface" : 1, "subterra" : -1` will include terrains that have "surface" flag but do not have "subterra" flag
|
||||||
- If 'hide' is set to true, then instead of revealing terrain, game will hide affected tiles for all other players
|
- If 'hide' is set to true, then instead of revealing terrain, game will hide affected tiles for all other players
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"revealTiles" : {
|
"revealTiles" : {
|
||||||
"radius" : 20,
|
"radius" : 20,
|
||||||
"surface" : 1,
|
"surface" : 1,
|
||||||
@ -605,7 +605,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can NOT be used as reward
|
- Can NOT be used as reward
|
||||||
- Only players with specific color can pass the limiter
|
- Only players with specific color can pass the limiter
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"colors" : [ "red", "blue", "tan", "green", "orange", "purple", "teal", "pink" ]
|
"colors" : [ "red", "blue", "tan", "green", "orange", "purple", "teal", "pink" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -615,7 +615,7 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can NOT be used as reward
|
- Can NOT be used as reward
|
||||||
- Only specific heroes can pass the limiter
|
- Only specific heroes can pass the limiter
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"heroes" : [ "orrin" ]
|
"heroes" : [ "orrin" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -625,6 +625,6 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
|||||||
- Can NOT be used as reward
|
- Can NOT be used as reward
|
||||||
- Only heroes belonging to specific classes can pass the limiter
|
- Only heroes belonging to specific classes can pass the limiter
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"heroClasses" : [ "battlemage" ]
|
"heroClasses" : [ "battlemage" ]
|
||||||
```
|
```
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Fields with description of mod
|
## Fields with description of mod
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Name of your mod. While it does not have hard length limit
|
// Name of your mod. While it does not have hard length limit
|
||||||
// it should not be longer than ~30 symbols to fit into allowed space
|
// it should not be longer than ~30 symbols to fit into allowed space
|
||||||
@ -91,7 +91,7 @@
|
|||||||
|
|
||||||
These are fields that are present only in local mod.json file
|
These are fields that are present only in local mod.json file
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
// Following section describes configuration files with content added by mod
|
// Following section describes configuration files with content added by mod
|
||||||
@ -195,7 +195,7 @@ These are fields that are present only in local mod.json file
|
|||||||
In addition to field listed above, it is possible to add following block for any language supported by VCMI. If such block is present, Launcher will use this information for displaying translated mod information and game will use provided json files to translate mod to specified language.
|
In addition to field listed above, it is possible to add following block for any language supported by VCMI. If such block is present, Launcher will use this information for displaying translated mod information and game will use provided json files to translate mod to specified language.
|
||||||
See [Translations](Translations.md) for more information
|
See [Translations](Translations.md) for more information
|
||||||
|
|
||||||
```
|
```json
|
||||||
"<language>" : {
|
"<language>" : {
|
||||||
"name" : "<translated name>",
|
"name" : "<translated name>",
|
||||||
"description" : "<translated description>",
|
"description" : "<translated description>",
|
||||||
@ -210,7 +210,7 @@ See [Translations](Translations.md) for more information
|
|||||||
|
|
||||||
These are fields that are present only in remote repository and are generally not used in mod.json
|
These are fields that are present only in remote repository and are generally not used in mod.json
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// URL to mod.json that describes this mod
|
// URL to mod.json that describes this mod
|
||||||
"mod" : "https://raw.githubusercontent.com/vcmi-mods/vcmi-extras/vcmi-1.4/mod.json",
|
"mod" : "https://raw.githubusercontent.com/vcmi-mods/vcmi-extras/vcmi-1.4/mod.json",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Template format
|
## Template format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
/// Unique template name
|
/// Unique template name
|
||||||
"Triangle" :
|
"Triangle" :
|
||||||
{
|
{
|
||||||
@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
## Zone format
|
## Zone format
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// Type of this zone. Possible values are:
|
// Type of this zone. Possible values are:
|
||||||
// "playerStart" - Starting zone for a "human or CPU" players
|
// "playerStart" - Starting zone for a "human or CPU" players
|
||||||
|
@ -8,7 +8,7 @@ All content of your mod should go into **Content** directory, e.g. **Mods/myMod/
|
|||||||
|
|
||||||
Example of how directory structure of your mod may look like:
|
Example of how directory structure of your mod may look like:
|
||||||
|
|
||||||
```
|
```text
|
||||||
Mods/
|
Mods/
|
||||||
myMod/
|
myMod/
|
||||||
mod.json
|
mod.json
|
||||||
@ -30,7 +30,7 @@ All VCMI configuration files use [JSON format](http://en.wikipedia.org/wiki/Json
|
|||||||
Mod.json is main file in your mod and must be present in any mod. This file contains basic description of your mod, dependencies or conflicting mods (if present), list of new content and so on.
|
Mod.json is main file in your mod and must be present in any mod. This file contains basic description of your mod, dependencies or conflicting mods (if present), list of new content and so on.
|
||||||
Minimalistic version of this file:
|
Minimalistic version of this file:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"name" : "My test mod",
|
"name" : "My test mod",
|
||||||
"description" : "My test mod that add a lot of useless stuff into the game",
|
"description" : "My test mod that add a lot of useless stuff into the game",
|
||||||
@ -102,7 +102,7 @@ VCMI uses strings to reference objects. Examples:
|
|||||||
|
|
||||||
Alternatively to creating new objects, you can edit existing objects. Normally, when creating new objects you specify object name as:
|
Alternatively to creating new objects, you can edit existing objects. Normally, when creating new objects you specify object name as:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"newCreature" : {
|
"newCreature" : {
|
||||||
// creature parameters
|
// creature parameters
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ Alternatively to creating new objects, you can edit existing objects. Normally,
|
|||||||
|
|
||||||
In order to access and modify existing object you need to specify mod that you wish to edit:
|
In order to access and modify existing object you need to specify mod that you wish to edit:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
/// "core" specifier refers to objects that exist in H3
|
/// "core" specifier refers to objects that exist in H3
|
||||||
"core:archer" : {
|
"core:archer" : {
|
||||||
/// This will set health of Archer to 10
|
/// This will set health of Archer to 10
|
||||||
@ -159,7 +159,7 @@ These files can be replaced by another def file but in some cases original forma
|
|||||||
In VCMI these animation files can also be replaced by json description of their content. See [Animation Format](Animation_Format.md) for full description of this format.
|
In VCMI these animation files can also be replaced by json description of their content. See [Animation Format](Animation_Format.md) for full description of this format.
|
||||||
Example: replacing single icon
|
Example: replacing single icon
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
// List of replaced images
|
// List of replaced images
|
||||||
"images" :
|
"images" :
|
||||||
@ -208,7 +208,7 @@ Link to our mod will looks like that: <https://github.com/vcmi-mods/adventure-ai
|
|||||||
|
|
||||||
For sanity reasons mod identifier must only contain lower-case English characters, numbers and hyphens.
|
For sanity reasons mod identifier must only contain lower-case English characters, numbers and hyphens.
|
||||||
|
|
||||||
```
|
```text
|
||||||
my-mod-name
|
my-mod-name
|
||||||
2000-new-maps
|
2000-new-maps
|
||||||
```
|
```
|
||||||
|
@ -10,7 +10,7 @@ Up-to-date releases can be found in our PPA here: <https://launchpad.net/~vcmi/+
|
|||||||
|
|
||||||
To install VCMI from PPA use:
|
To install VCMI from PPA use:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
sudo apt-add-repository ppa:vcmi/ppa
|
sudo apt-add-repository ppa:vcmi/ppa
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install vcmi
|
sudo apt install vcmi
|
||||||
@ -22,7 +22,7 @@ We also provide latest, unstable builds mostly suitable for testing here: <https
|
|||||||
|
|
||||||
In order to install from this PPA use:
|
In order to install from this PPA use:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
sudo add-apt-repository ppa:vcmi/vcmi-latest
|
sudo add-apt-repository ppa:vcmi/vcmi-latest
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install vcmi
|
sudo apt install vcmi
|
||||||
@ -33,7 +33,7 @@ In order to install from this PPA use:
|
|||||||
VCMI stable builds available in "multiverse" repository. Learn how to enable it in [Ubuntu wiki](https://help.ubuntu.com/community/Repositories/Ubuntu).
|
VCMI stable builds available in "multiverse" repository. Learn how to enable it in [Ubuntu wiki](https://help.ubuntu.com/community/Repositories/Ubuntu).
|
||||||
Once enabled, you can install VCMI using Ubuntu Store or in terminal using following commands:
|
Once enabled, you can install VCMI using Ubuntu Store or in terminal using following commands:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install vcmi
|
sudo apt install vcmi
|
||||||
```
|
```
|
||||||
@ -45,7 +45,7 @@ Note that version available in Ubuntu is outdated. Install via PPA is preferred.
|
|||||||
Stable VCMI version is available in "contrib" repository. Learn how to enable it in [Debian wiki](https://wiki.debian.org/SourcesList).
|
Stable VCMI version is available in "contrib" repository. Learn how to enable it in [Debian wiki](https://wiki.debian.org/SourcesList).
|
||||||
To install VCMI from repository:
|
To install VCMI from repository:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install vcmi
|
sudo apt-get install vcmi
|
||||||
```
|
```
|
||||||
@ -54,7 +54,7 @@ To install VCMI from repository:
|
|||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
sudo dnf update
|
sudo dnf update
|
||||||
sudo dnf install vcmi
|
sudo dnf install vcmi
|
||||||
```
|
```
|
||||||
@ -95,7 +95,7 @@ To install Heroes 3 data using automated script you need any of:
|
|||||||
|
|
||||||
Run the script using options appropriate to your input files:
|
Run the script using options appropriate to your input files:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
vcmibuilder --cd1 /path/to/iso/or/cd --cd2 /path/to/second/cd
|
vcmibuilder --cd1 /path/to/iso/or/cd --cd2 /path/to/second/cd
|
||||||
vcmibuilder --gog /path/to/gog.com/installer.exe
|
vcmibuilder --gog /path/to/gog.com/installer.exe
|
||||||
vcmibuilder --data /path/to/h3/data
|
vcmibuilder --data /path/to/h3/data
|
||||||
@ -105,7 +105,7 @@ You should use only one of these commands.
|
|||||||
|
|
||||||
On flatpak install, it's also possible to run the script, but any path seems to be interpreted from within the Flatpak sandbox:
|
On flatpak install, it's also possible to run the script, but any path seems to be interpreted from within the Flatpak sandbox:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
flatpak run --command=vcmibuilder eu.vcmi.VCMI --data /path/to/h3/data`
|
flatpak run --command=vcmibuilder eu.vcmi.VCMI --data /path/to/h3/data`
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ You can select both downloaded files in launcher to extract automatically.
|
|||||||
|
|
||||||
Alternatively you can use the classic way:
|
Alternatively you can use the classic way:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
innoextract --output-dir=~/Downloads/HoMM3 "setup_heroes_of_might_and_magic_3_complete_4.0_(28740).exe"
|
innoextract --output-dir=~/Downloads/HoMM3 "setup_heroes_of_might_and_magic_3_complete_4.0_(28740).exe"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ The easiest way to install the ipa on your device is to do one of the following:
|
|||||||
|
|
||||||
Alternatively, to install the signed ipa on your device, you can use Xcode or Apple Configurator (available on the Mac App Store for free). The latter also allows installing ipa from the command line, here's an example that assumes you have only 1 device connected to your Mac and the signed ipa is on your desktop:
|
Alternatively, to install the signed ipa on your device, you can use Xcode or Apple Configurator (available on the Mac App Store for free). The latter also allows installing ipa from the command line, here's an example that assumes you have only 1 device connected to your Mac and the signed ipa is on your desktop:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
/Applications/Apple\ Configurator.app/Contents/MacOS/cfgutil install-app ~/Desktop/vcmi.ipa
|
/Applications/Apple\ Configurator.app/Contents/MacOS/cfgutil install-app ~/Desktop/vcmi.ipa
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ To export maps and campaigns, use '/translate maps' command instead.
|
|||||||
|
|
||||||
It's possible to add video subtitles. Create a JSON file in `video` folder of translation mod with the name of the video (e.g. `H3Intro.json`):
|
It's possible to add video subtitles. Create a JSON file in `video` folder of translation mod with the name of the video (e.g. `H3Intro.json`):
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"timeStart" : 5.640, // start time, seconds
|
"timeStart" : 5.640, // start time, seconds
|
||||||
@ -164,7 +164,7 @@ If you want to update existing translation, you can use '/translate missing' com
|
|||||||
|
|
||||||
In order to display information in Launcher in language selected by user add following block into your `mod.json`:
|
In order to display information in Launcher in language selected by user add following block into your `mod.json`:
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
"<language>" : {
|
"<language>" : {
|
||||||
"name" : "<translated name>",
|
"name" : "<translated name>",
|
||||||
"description" : "<translated description>",
|
"description" : "<translated description>",
|
||||||
|
@ -359,8 +359,9 @@ void FirstLaunchView::extractGogData()
|
|||||||
return; // should not happen - but avoid deleting wrong folder in any case
|
return; // should not happen - but avoid deleting wrong folder in any case
|
||||||
|
|
||||||
QString tmpFileExe = tempDir.filePath("h3_gog.exe");
|
QString tmpFileExe = tempDir.filePath("h3_gog.exe");
|
||||||
|
QString tmpFileBin = tempDir.filePath("h3_gog-1.bin");
|
||||||
QFile(fileExe).copy(tmpFileExe);
|
QFile(fileExe).copy(tmpFileExe);
|
||||||
QFile(fileBin).copy(tempDir.filePath("h3_gog-1.bin"));
|
QFile(fileBin).copy(tmpFileBin);
|
||||||
|
|
||||||
QString errorText{};
|
QString errorText{};
|
||||||
|
|
||||||
@ -388,6 +389,10 @@ void FirstLaunchView::extractGogData()
|
|||||||
qApp->processEvents();
|
qApp->processEvents();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QString hashError;
|
||||||
|
if(!errorText.isEmpty())
|
||||||
|
hashError = Innoextract::getHashError(tmpFileExe, tmpFileBin, fileExe, fileBin);
|
||||||
|
|
||||||
ui->progressBarGog->setVisible(false);
|
ui->progressBarGog->setVisible(false);
|
||||||
ui->pushButtonGogInstall->setVisible(true);
|
ui->pushButtonGogInstall->setVisible(true);
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
@ -396,7 +401,11 @@ void FirstLaunchView::extractGogData()
|
|||||||
if(!errorText.isEmpty() || dirData.empty() || QDir(tempDir.filePath(dirData.front())).entryList({"*.lod"}, QDir::Filter::Files).empty())
|
if(!errorText.isEmpty() || dirData.empty() || QDir(tempDir.filePath(dirData.front())).entryList({"*.lod"}, QDir::Filter::Files).empty())
|
||||||
{
|
{
|
||||||
if(!errorText.isEmpty())
|
if(!errorText.isEmpty())
|
||||||
|
{
|
||||||
QMessageBox::critical(this, tr("Extracting error!"), errorText, QMessageBox::Ok, QMessageBox::Ok);
|
QMessageBox::critical(this, tr("Extracting error!"), errorText, QMessageBox::Ok, QMessageBox::Ok);
|
||||||
|
if(!hashError.isEmpty())
|
||||||
|
QMessageBox::critical(this, tr("Hash error!"), hashError, QMessageBox::Ok, QMessageBox::Ok);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
QMessageBox::critical(this, tr("No Heroes III data!"), tr("Selected files do not contain Heroes III data!"), QMessageBox::Ok, QMessageBox::Ok);
|
QMessageBox::critical(this, tr("No Heroes III data!"), tr("Selected files do not contain Heroes III data!"), QMessageBox::Ok, QMessageBox::Ok);
|
||||||
tempDir.removeRecursively();
|
tempDir.removeRecursively();
|
||||||
|
@ -60,3 +60,105 @@ QString Innoextract::extract(QString installer, QString outDir, std::function<vo
|
|||||||
|
|
||||||
return errorText;
|
return errorText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Innoextract::getHashError(QString exeFile, QString binFile, QString exeFileOriginal, QString binFileOriginal)
|
||||||
|
{
|
||||||
|
enum filetype
|
||||||
|
{
|
||||||
|
H3_COMPLETE, CHR
|
||||||
|
};
|
||||||
|
struct data
|
||||||
|
{
|
||||||
|
filetype type;
|
||||||
|
std::string language;
|
||||||
|
int exeSize;
|
||||||
|
int binSize;
|
||||||
|
std::string exe;
|
||||||
|
std::string bin;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fileinfo {
|
||||||
|
std::string hash;
|
||||||
|
int size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<data> knownHashes = {
|
||||||
|
{ H3_COMPLETE, "english", 822520, 1005040617, "66646a353b06417fa12c6384405688c84a315cc1", "c624e2071f4e35386765ab044ad5860ac245b7f4" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(28740).exe
|
||||||
|
{ H3_COMPLETE, "french", 824960, 997305870, "072f1d4466ff16444d8c7949c6530448a9c53cfa", "9b6b451d2bd2f8b4be159e62fa6d32e87ee10455" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(french)_(28740).exe
|
||||||
|
{ H3_COMPLETE, "polish", 822288, 849286313, "74ffde00156dd5a8e237668f87213387f0dd9c7c", "2523cf9943043ae100186f89e4ebf7c28be09804" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(polish)_(28740).exe
|
||||||
|
{ H3_COMPLETE, "russian", 821608, 980398466, "88ccae41e66da58ba4ad62024d97dfe69084f825", "58f1b3c813a1953992bba1f9855c47d01c897db8" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(russian)_(28740).exe
|
||||||
|
{ H3_COMPLETE, "english", 820288, 1006275333, "ca68adb8c2d8c6b3afa17a595ad70c2cec062b5a", "2715e10e91919d05377d39fd879d43f9f0cb9f87" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(3.2)_gog_0.1_(77075).exe
|
||||||
|
{ H3_COMPLETE, "french", 822688, 998540653, "fbb300eeef52f5d81a571a178723b19313e3856d", "4f4d90ff2f60968616766237664744bc54754500" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(3.2)_gog_0.1_(french)_(77075).exe
|
||||||
|
{ H3_COMPLETE, "polish", 819904, 851750601, "a413b0b9f3d5ca3e1a57e84a42de28c67d77b1a7", "fd9fe58bcbb8b442e8cfc299d90f1d503f281d40" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(3.2)_gog_0.1_(polish)_(77075).exe
|
||||||
|
{ H3_COMPLETE, "russian", 819416, 981633128, "e84eedf62fe2e5f9171a7e1ce6e99315a09ce41f", "49cc683395c0cf80830bfa66e42bb5dfdb7aa124" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(3.2)_gog_0.1_(russian)_(77075).exe
|
||||||
|
{ CHR, "english", 485694752, 0, "44e4fc2c38261a1c2a57d5198f44493210e8fc1a", "" }, // setup_heroes_chronicles_chapter1_2.1.0.42.exe
|
||||||
|
{ CHR, "english", 493102840, 0, "b479a3272cf4b57a6b7fc499df5eafb624dcd6de", "" }, // setup_heroes_chronicles_chapter2_2.1.0.43.exe
|
||||||
|
{ CHR, "english", 470364128, 0, "5ad36d822e1700c9ecf93b78652900a52518146b", "" }, // setup_heroes_chronicles_chapter3_2.1.0.41.exe
|
||||||
|
{ CHR, "english", 469211296, 0, "5deb374a2e188ed14e8f74ad1284c45e46adf760", "" }, // setup_heroes_chronicles_chapter4_2.1.0.42.exe
|
||||||
|
{ CHR, "english", 447497560, 0, "a6daa6ed56c840f3be7ad6ad920a2f9f2439acc8", "" }, // setup_heroes_chronicles_chapter5_2.1.0.42.exe
|
||||||
|
{ CHR, "english", 447430456, 0, "93a42dd24453f36e7020afc61bca05b8461a3f04", "" }, // setup_heroes_chronicles_chapter6_2.1.0.42.exe
|
||||||
|
{ CHR, "english", 481583720, 0, "d74b042015f3c5b667821c5d721ac3d2fdbf43fc", "" }, // setup_heroes_chronicles_chapter7_2.1.0.42.exe
|
||||||
|
{ CHR, "english", 462976008, 0, "9039050e88b9dabcdb3ffa74b33e6aa86a20b7d9", "" }, // setup_heroes_chronicles_chapter8_2.1.0.42.exe
|
||||||
|
};
|
||||||
|
|
||||||
|
auto doHash = [](QFile f){
|
||||||
|
fileinfo tmp;
|
||||||
|
|
||||||
|
if(f.open(QFile::ReadOnly)) {
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Algorithm::Sha1);
|
||||||
|
if(hash.addData(&f))
|
||||||
|
tmp.hash = hash.result().toHex().toLower().toStdString();
|
||||||
|
tmp.size = f.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
fileinfo exeInfo;
|
||||||
|
fileinfo binInfo;
|
||||||
|
fileinfo exeInfoOriginal;
|
||||||
|
fileinfo binInfoOriginal;
|
||||||
|
|
||||||
|
exeInfo = doHash(QFile(exeFile));
|
||||||
|
if(!binFile.isEmpty())
|
||||||
|
binInfo = doHash(QFile(binFile));
|
||||||
|
exeInfoOriginal = doHash(QFile(exeFileOriginal));
|
||||||
|
if(!binFileOriginal.isEmpty())
|
||||||
|
binInfoOriginal = doHash(QFile(binFileOriginal));
|
||||||
|
|
||||||
|
if(exeInfo.hash.empty() || (!binFile.isEmpty() && binInfo.hash.empty()))
|
||||||
|
return QString{}; // hashing not possible -> previous error is enough
|
||||||
|
|
||||||
|
QString hashOutput = tr("SHA1 hash of provided files:\nExe (%1 bytes):\n%2").arg(QString::number(exeInfo.size), QString::fromStdString(exeInfo.hash));
|
||||||
|
if(!binInfo.hash.empty())
|
||||||
|
hashOutput += tr("\nBin (%1 bytes):\n%2").arg(QString::number(binInfo.size), QString::fromStdString(binInfo.hash));
|
||||||
|
|
||||||
|
if((!exeInfoOriginal.hash.empty() && exeInfo.hash != exeInfoOriginal.hash) || (!binInfoOriginal.hash.empty() && !binFile.isEmpty() && !binFileOriginal.isEmpty() && binInfo.hash != binInfoOriginal.hash))
|
||||||
|
return tr("Internal copy process failed. Enough space on device?\n\n%1").arg(hashOutput);
|
||||||
|
|
||||||
|
QString foundKnown;
|
||||||
|
QString exeLang;
|
||||||
|
QString binLang;
|
||||||
|
auto find = [exeInfo, binInfo](const data & d) { return (!d.exe.empty() && d.exe == exeInfo.hash) || (!d.bin.empty() && d.bin == binInfo.hash);};
|
||||||
|
auto it = std::find_if(knownHashes.begin(), knownHashes.end(), find);
|
||||||
|
while(it != knownHashes.end()){
|
||||||
|
auto lang = QString::fromStdString((*it).language);
|
||||||
|
foundKnown += "\n" + (exeInfo.hash == (*it).exe ? tr("Exe") : tr("Bin")) + " - " + lang;
|
||||||
|
if(exeInfo.hash == (*it).exe)
|
||||||
|
exeLang = lang;
|
||||||
|
else
|
||||||
|
binLang = lang;
|
||||||
|
it = std::find_if(++it, knownHashes.end(), find);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!exeLang.isEmpty() && !binLang.isEmpty() && exeLang != binLang && !binFile.isEmpty())
|
||||||
|
return tr("Language mismatch!\n%1\n\n%2").arg(foundKnown, hashOutput);
|
||||||
|
else if((!exeLang.isEmpty() || !binLang.isEmpty()) && !binFile.isEmpty())
|
||||||
|
return tr("Only one file known! Maybe files are corrupted? Please download again.\n%1\n\n%2").arg(foundKnown, hashOutput);
|
||||||
|
else if(!exeLang.isEmpty() && binFile.isEmpty())
|
||||||
|
return QString{};
|
||||||
|
else if(!exeLang.isEmpty() && !binFile.isEmpty() && exeLang == binLang)
|
||||||
|
return QString{};
|
||||||
|
|
||||||
|
return tr("Unknown files! Maybe files are corrupted? Please download again.\n\n%1").arg(hashOutput);
|
||||||
|
}
|
||||||
|
@ -13,4 +13,5 @@ class Innoextract : public QObject
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static QString extract(QString installer, QString outDir, std::function<void (float percent)> cb = nullptr);
|
static QString extract(QString installer, QString outDir, std::function<void (float percent)> cb = nullptr);
|
||||||
|
static QString getHashError(QString exeFile, QString binFile, QString exeFileOriginal, QString binFileOriginal);
|
||||||
};
|
};
|
||||||
|
@ -84,7 +84,10 @@ bool ChroniclesExtractor::extractGogInstaller(QString file)
|
|||||||
|
|
||||||
if(!errorText.isEmpty())
|
if(!errorText.isEmpty())
|
||||||
{
|
{
|
||||||
|
QString hashError = Innoextract::getHashError(file, {}, {}, {});
|
||||||
QMessageBox::critical(parent, tr("Extracting error!"), errorText);
|
QMessageBox::critical(parent, tr("Extracting error!"), errorText);
|
||||||
|
if(!hashError.isEmpty())
|
||||||
|
QMessageBox::critical(parent, tr("Hash error!"), hashError, QMessageBox::Ok, QMessageBox::Ok);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +144,7 @@ CModListView::CModListView(QWidget * parent)
|
|||||||
|
|
||||||
ui->splitter->setStyleSheet("QSplitter::handle {background: palette('window');}");
|
ui->splitter->setStyleSheet("QSplitter::handle {background: palette('window');}");
|
||||||
|
|
||||||
|
disableModInfo();
|
||||||
setupModModel();
|
setupModModel();
|
||||||
setupFilterModel();
|
setupFilterModel();
|
||||||
setupModsView();
|
setupModsView();
|
||||||
@ -151,6 +152,7 @@ CModListView::CModListView(QWidget * parent)
|
|||||||
ui->progressWidget->setVisible(false);
|
ui->progressWidget->setVisible(false);
|
||||||
dlManager = nullptr;
|
dlManager = nullptr;
|
||||||
|
|
||||||
|
modModel->reloadRepositories();
|
||||||
if(settings["launcher"]["autoCheckRepositories"].Bool())
|
if(settings["launcher"]["autoCheckRepositories"].Bool())
|
||||||
loadRepositories();
|
loadRepositories();
|
||||||
|
|
||||||
@ -597,6 +599,10 @@ QStringList CModListView::getModsToInstall(QString mod)
|
|||||||
void CModListView::on_updateButton_clicked()
|
void CModListView::on_updateButton_clicked()
|
||||||
{
|
{
|
||||||
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
|
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
|
||||||
|
auto targetMod = modStateModel->getMod(modName);
|
||||||
|
|
||||||
|
if(targetMod.isUpdateAvailable())
|
||||||
|
downloadFile(modName + ".zip", targetMod.getDownloadUrl(), modName, targetMod.getDownloadSizeBytes());
|
||||||
|
|
||||||
for(const auto & name : getModsToInstall(modName))
|
for(const auto & name : getModsToInstall(modName))
|
||||||
{
|
{
|
||||||
@ -928,7 +934,8 @@ void CModListView::installMods(QStringList archives)
|
|||||||
manager->installMod(modNames[i], archives[i]);
|
manager->installMod(modNames[i], archives[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
manager->enableMods(modsToEnable);
|
if (!modsToEnable.empty())
|
||||||
|
manager->enableMods(modsToEnable);
|
||||||
|
|
||||||
checkManagerErrors();
|
checkManagerErrors();
|
||||||
|
|
||||||
|
@ -87,89 +87,6 @@
|
|||||||
<translation>Nahlásit chybu</translation>
|
<translation>Nahlásit chybu</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
|
||||||
<name>CModListModel</name>
|
|
||||||
<message>
|
|
||||||
<source>Translation</source>
|
|
||||||
<translation type="vanished">Překlad</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Town</source>
|
|
||||||
<translation type="vanished">Město</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Test</source>
|
|
||||||
<translation type="vanished">Zkouška</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Templates</source>
|
|
||||||
<translation type="vanished">Šablony</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Spells</source>
|
|
||||||
<translation type="vanished">Kouzla</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Music</source>
|
|
||||||
<translation type="vanished">Hudba</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Maps</source>
|
|
||||||
<translation type="vanished">Mapy</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Sounds</source>
|
|
||||||
<translation type="vanished">Zvuky</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Skills</source>
|
|
||||||
<translation type="vanished">Schopnosti</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Other</source>
|
|
||||||
<translation type="vanished">Ostatní</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Objects</source>
|
|
||||||
<translation type="vanished">Objekty</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mechanics</source>
|
|
||||||
<translation type="vanished">Mechaniky</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Interface</source>
|
|
||||||
<translation type="vanished">Rozhraní</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Heroes</source>
|
|
||||||
<translation type="vanished">Hrdinové</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Graphical</source>
|
|
||||||
<translation type="vanished">Grafika</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Expansion</source>
|
|
||||||
<translation type="vanished">Rozšíření</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Creatures</source>
|
|
||||||
<translation type="vanished">Jednotky</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Compatibility</source>
|
|
||||||
<translation type="vanished">Kompabilita</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Artifacts</source>
|
|
||||||
<translation type="vanished">Artefakty</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>AI</source>
|
|
||||||
<translation type="vanished">AI</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
<context>
|
||||||
<name>CModListView</name>
|
<name>CModListView</name>
|
||||||
<message>
|
<message>
|
||||||
@ -362,22 +279,6 @@
|
|||||||
<source>Conflicting mods</source>
|
<source>Conflicting mods</source>
|
||||||
<translation>Modifikace v kolizi</translation>
|
<translation>Modifikace v kolizi</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>This mod can not be installed or enabled because the following dependencies are not present</source>
|
|
||||||
<translation type="vanished">Tato modifikace nelze nainstalovat ani povolit, protože nejsou přítomny následující závislosti</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>This mod can not be enabled because the following mods are incompatible with it</source>
|
|
||||||
<translation type="vanished">Tato modifikace nemůže být povolena, protože není kompatibilní s následujícími modifikacemi</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>This mod cannot be disabled because it is required by the following mods</source>
|
|
||||||
<translation type="vanished">Tato modifikace nemůže být zakázána, protože je vyžadována následujícími modifikacemi</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>This mod cannot be uninstalled or updated because it is required by the following mods</source>
|
|
||||||
<translation type="vanished">Tato modifikace nemůže být odinstalována nebo aktualizována, protože je vyžadována následujícími modifikacemi</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="399"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="399"/>
|
||||||
<source>This mod cannot be enabled because it translates into a different language.</source>
|
<source>This mod cannot be enabled because it translates into a different language.</source>
|
||||||
@ -523,83 +424,6 @@ Nainstalovat úspěšně stažené?</translation>
|
|||||||
<translation>Modifikace není kompatibilní</translation>
|
<translation>Modifikace není kompatibilní</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
|
||||||
<name>CModManager</name>
|
|
||||||
<message>
|
|
||||||
<source>Can not install submod</source>
|
|
||||||
<translation type="vanished">Nelze nainstalovat podmodifikaci</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod is already installed</source>
|
|
||||||
<translation type="vanished">Modifikace je již nainstalována</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Can not uninstall submod</source>
|
|
||||||
<translation type="vanished">Nelze odinstalovat podmodifikaci</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod is not installed</source>
|
|
||||||
<translation type="vanished">Modifikace není nainstalována</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod is already enabled</source>
|
|
||||||
<translation type="vanished">Modifikace je již povolena</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod must be installed first</source>
|
|
||||||
<translation type="vanished">Nejprve musí být nainstalována modifikace</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod is not compatible, please update VCMI and checkout latest mod revisions</source>
|
|
||||||
<translation type="vanished">Modifikace není kompatibilní, prosíme aktualizujte VCMI a použijte nejnovější verzi modifikace</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Required mod %1 is missing</source>
|
|
||||||
<translation type="vanished">Vyžadovaná modifkace %1 chybí</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Required mod %1 is not enabled</source>
|
|
||||||
<translation type="vanished">Vyžadovaná modifikace %1 není povolena</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>This mod conflicts with %1</source>
|
|
||||||
<translation type="vanished">Tato modifikace koliduje s %1</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod is already disabled</source>
|
|
||||||
<translation type="vanished">Modifikace je již povolena</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>This mod is needed to run %1</source>
|
|
||||||
<translation type="vanished">Modifikace %1 je vyžadována pro běh</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod archive is missing</source>
|
|
||||||
<translation type="vanished">Archiv modifikace chybí</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod with such name is already installed</source>
|
|
||||||
<translation type="vanished">Modifikace s tímto názvem je již nainstalována</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod archive is invalid or corrupted</source>
|
|
||||||
<translation type="vanished">Archiv modifikace je neplatný nebo poškozený</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Failed to extract mod data</source>
|
|
||||||
<translation type="vanished">Extrakce dat modifikace selhala</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Data with this mod was not found</source>
|
|
||||||
<translation type="vanished">Data s touto modifikací nebyla nalezena</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Mod is located in protected directory, please remove it manually:
|
|
||||||
</source>
|
|
||||||
<translation type="vanished">Modifikace se nachází v zabezpečené složce, prosíme odstraňte ji ručně:
|
|
||||||
</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
<context>
|
||||||
<name>CSettingsView</name>
|
<name>CSettingsView</name>
|
||||||
<message>
|
<message>
|
||||||
@ -633,11 +457,6 @@ Nainstalovat úspěšně stažené?</translation>
|
|||||||
<source>Additional repository</source>
|
<source>Additional repository</source>
|
||||||
<translation>Další repozitáře</translation>
|
<translation>Další repozitáře</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../settingsView/csettingsview_moc.ui" line="484"/>
|
|
||||||
<source>Downscaling Filter</source>
|
|
||||||
<translation>Filtr pro zmenšování</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../settingsView/csettingsview_moc.ui" line="597"/>
|
<location filename="../settingsView/csettingsview_moc.ui" line="597"/>
|
||||||
<source>Adventure Map Allies</source>
|
<source>Adventure Map Allies</source>
|
||||||
@ -870,6 +689,11 @@ Nainstalovat úspěšně stažené?</translation>
|
|||||||
<source>Autosave limit (0 = off)</source>
|
<source>Autosave limit (0 = off)</source>
|
||||||
<translation>Limit aut. uložení (0=vypnuto)</translation>
|
<translation>Limit aut. uložení (0=vypnuto)</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../settingsView/csettingsview_moc.ui" line="484"/>
|
||||||
|
<source>Downscaling Filter</source>
|
||||||
|
<translation>Filtr pro zmenšování</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../settingsView/csettingsview_moc.ui" line="781"/>
|
<location filename="../settingsView/csettingsview_moc.ui" line="781"/>
|
||||||
<source>Framerate Limit</source>
|
<source>Framerate Limit</source>
|
||||||
@ -1061,28 +885,12 @@ Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybra
|
|||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>File size</name>
|
<name>File size</name>
|
||||||
<message>
|
|
||||||
<source>%1 B</source>
|
|
||||||
<translation type="vanished">%1 B</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>%1 KiB</source>
|
|
||||||
<translation type="vanished">%1 KiB</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/modstate.cpp" line="140"/>
|
<location filename="../modManager/modstate.cpp" line="140"/>
|
||||||
<location filename="../modManager/modstatemodel.cpp" line="95"/>
|
<location filename="../modManager/modstatemodel.cpp" line="95"/>
|
||||||
<source>%1 MiB</source>
|
<source>%1 MiB</source>
|
||||||
<translation>%1 MiB</translation>
|
<translation>%1 MiB</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>%1 GiB</source>
|
|
||||||
<translation type="vanished">%1 GiB</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>%1 TiB</source>
|
|
||||||
<translation type="vanished">%1 TiB</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>FirstLaunchView</name>
|
<name>FirstLaunchView</name>
|
||||||
@ -1126,7 +934,7 @@ Než začnete hrát, je třeba dokončit několik kroků.
|
|||||||
|
|
||||||
Pamatujte, že pro používání VCMI musíte vlastnit originální herní soubory pro Heroes® of Might and Magic® III: Complete nebo The Shadow of Death.
|
Pamatujte, že pro používání VCMI musíte vlastnit originální herní soubory pro Heroes® of Might and Magic® III: Complete nebo The Shadow of Death.
|
||||||
|
|
||||||
Heroes® of Might and Magic® III HD momentálně není podporováno!</translation>
|
Heroes® of Might and Magic® III HD momentálně není podporována!</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="248"/>
|
<location filename="../firstLaunch/firstlaunch_moc.ui" line="248"/>
|
||||||
|
@ -86,6 +86,11 @@ JsonNode::JsonNode(const std::string & string)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonNode::JsonNode(const JsonMap & map)
|
||||||
|
: data(map)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
JsonNode::JsonNode(const std::byte * data, size_t datasize, const std::string & fileName)
|
JsonNode::JsonNode(const std::byte * data, size_t datasize, const std::string & fileName)
|
||||||
: JsonNode(data, datasize, JsonParsingSettings(), fileName)
|
: JsonNode(data, datasize, JsonParsingSettings(), fileName)
|
||||||
{
|
{
|
||||||
|
@ -71,6 +71,9 @@ public:
|
|||||||
explicit JsonNode(const char * string);
|
explicit JsonNode(const char * string);
|
||||||
explicit JsonNode(const std::string & string);
|
explicit JsonNode(const std::string & string);
|
||||||
|
|
||||||
|
/// Create tree from map
|
||||||
|
explicit JsonNode(const JsonMap & map);
|
||||||
|
|
||||||
/// Create tree from Json-formatted input
|
/// Create tree from Json-formatted input
|
||||||
explicit JsonNode(const std::byte * data, size_t datasize, const std::string & fileName);
|
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);
|
explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings, const std::string & fileName);
|
||||||
|
@ -378,6 +378,9 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
|
|||||||
|
|
||||||
std::string CGTownInstance::getObjectName() const
|
std::string CGTownInstance::getObjectName() const
|
||||||
{
|
{
|
||||||
|
if(ID == Obj::RANDOM_TOWN )
|
||||||
|
return CGObjectInstance::getObjectName();
|
||||||
|
|
||||||
return getNameTranslated() + ", " + getTown()->faction->getNameTranslated();
|
return getNameTranslated() + ", " + getTown()->faction->getNameTranslated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,12 @@ void FlaggableMapObject::onHeroVisit( const CGHeroInstance * h ) const
|
|||||||
giveBonusTo(h->getOwner());
|
giveBonusTo(h->getOwner());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlaggableMapObject::markAsDeleted() const
|
||||||
|
{
|
||||||
|
if(getOwner().isValidPlayer())
|
||||||
|
takeBonusFrom(getOwner());
|
||||||
|
}
|
||||||
|
|
||||||
void FlaggableMapObject::initObj(vstd::RNG & rand)
|
void FlaggableMapObject::initObj(vstd::RNG & rand)
|
||||||
{
|
{
|
||||||
if(getOwner().isValidPlayer())
|
if(getOwner().isValidPlayer())
|
||||||
|
@ -28,6 +28,7 @@ public:
|
|||||||
using CGObjectInstance::CGObjectInstance;
|
using CGObjectInstance::CGObjectInstance;
|
||||||
|
|
||||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||||
|
void markAsDeleted() const;
|
||||||
void initObj(vstd::RNG & rand) override;
|
void initObj(vstd::RNG & rand) override;
|
||||||
|
|
||||||
const IOwnableObject * asOwnable() const final;
|
const IOwnableObject * asOwnable() const final;
|
||||||
|
@ -103,6 +103,9 @@ void CMapEvent::serializeJson(JsonSerializeFormat & handler)
|
|||||||
handler.serializeInt("firstOccurrence", firstOccurrence);
|
handler.serializeInt("firstOccurrence", firstOccurrence);
|
||||||
handler.serializeInt("nextOccurrence", nextOccurrence);
|
handler.serializeInt("nextOccurrence", nextOccurrence);
|
||||||
resources.serializeJson(handler, "resources");
|
resources.serializeJson(handler, "resources");
|
||||||
|
|
||||||
|
auto deletedObjects = handler.enterArray("deletedObjectsInstances");
|
||||||
|
deletedObjects.serializeArray(deletedObjectsInstances);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCastleEvent::serializeJson(JsonSerializeFormat & handler)
|
void CCastleEvent::serializeJson(JsonSerializeFormat & handler)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "../ResourceSet.h"
|
#include "../ResourceSet.h"
|
||||||
#include "../texts/MetaString.h"
|
#include "../texts/MetaString.h"
|
||||||
|
#include "../int3.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ public:
|
|||||||
ui32 firstOccurrence;
|
ui32 firstOccurrence;
|
||||||
ui32 nextOccurrence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time
|
ui32 nextOccurrence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time
|
||||||
|
|
||||||
|
std::vector<ObjectInstanceID> deletedObjectsInstances;
|
||||||
|
|
||||||
template <typename Handler>
|
template <typename Handler>
|
||||||
void serialize(Handler & h)
|
void serialize(Handler & h)
|
||||||
{
|
{
|
||||||
@ -64,6 +67,10 @@ public:
|
|||||||
h & computerAffected;
|
h & computerAffected;
|
||||||
h & firstOccurrence;
|
h & firstOccurrence;
|
||||||
h & nextOccurrence;
|
h & nextOccurrence;
|
||||||
|
if(h.version >= Handler::Version::EVENT_OBJECTS_DELETION)
|
||||||
|
{
|
||||||
|
h & deletedObjectsInstances;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void serializeJson(JsonSerializeFormat & handler);
|
virtual void serializeJson(JsonSerializeFormat & handler);
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
#include "mapObjectConstructors/CObjectClassesHandler.h"
|
#include "mapObjectConstructors/CObjectClassesHandler.h"
|
||||||
#include "campaign/CampaignState.h"
|
#include "campaign/CampaignState.h"
|
||||||
#include "IGameSettings.h"
|
#include "IGameSettings.h"
|
||||||
|
#include "mapObjects/FlaggableMapObject.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -1184,7 +1185,6 @@ void RemoveBonus::applyGs(CGameState *gs)
|
|||||||
|
|
||||||
void RemoveObject::applyGs(CGameState *gs)
|
void RemoveObject::applyGs(CGameState *gs)
|
||||||
{
|
{
|
||||||
|
|
||||||
CGObjectInstance *obj = gs->getObjInstance(objectID);
|
CGObjectInstance *obj = gs->getObjInstance(objectID);
|
||||||
logGlobal->debug("removing object id=%d; address=%x; name=%s", objectID, (intptr_t)obj, obj->getObjectName());
|
logGlobal->debug("removing object id=%d; address=%x; name=%s", objectID, (intptr_t)obj, obj->getObjectName());
|
||||||
//unblock tiles
|
//unblock tiles
|
||||||
@ -1197,10 +1197,7 @@ void RemoveObject::applyGs(CGameState *gs)
|
|||||||
{
|
{
|
||||||
auto * beatenHero = dynamic_cast<CGHeroInstance *>(obj);
|
auto * beatenHero = dynamic_cast<CGHeroInstance *>(obj);
|
||||||
assert(beatenHero);
|
assert(beatenHero);
|
||||||
PlayerState * p = gs->getPlayerState(beatenHero->tempOwner);
|
|
||||||
gs->map->heroesOnMap -= beatenHero;
|
gs->map->heroesOnMap -= beatenHero;
|
||||||
p->removeOwnedObject(beatenHero);
|
|
||||||
|
|
||||||
|
|
||||||
auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs);
|
auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs);
|
||||||
|
|
||||||
@ -1254,6 +1251,18 @@ void RemoveObject::applyGs(CGameState *gs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(obj->getOwner().isValidPlayer())
|
||||||
|
{
|
||||||
|
gs->getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten
|
||||||
|
|
||||||
|
FlaggableMapObject* flaggableObject = dynamic_cast<FlaggableMapObject*>(obj);
|
||||||
|
if(flaggableObject)
|
||||||
|
{
|
||||||
|
flaggableObject->markAsDeleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
gs->map->instanceNames.erase(obj->instanceName);
|
gs->map->instanceNames.erase(obj->instanceName);
|
||||||
gs->map->objects[objectID.getNum()].dellNull();
|
gs->map->objects[objectID.getNum()].dellNull();
|
||||||
gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
|
gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
|
||||||
|
@ -69,6 +69,7 @@ enum class ESerializationVersion : int32_t
|
|||||||
FOLDER_NAME_REWORK, // 870 - rework foldername
|
FOLDER_NAME_REWORK, // 870 - rework foldername
|
||||||
REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects
|
REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects
|
||||||
MARKET_TRANSLATION_FIX, // 872 - remove serialization of markets translateable strings
|
MARKET_TRANSLATION_FIX, // 872 - remove serialization of markets translateable strings
|
||||||
|
EVENT_OBJECTS_DELETION, //873 - allow events to remove map objects
|
||||||
|
|
||||||
CURRENT = MARKET_TRANSLATION_FIX
|
CURRENT = EVENT_OBJECTS_DELETION
|
||||||
};
|
};
|
||||||
|
@ -77,23 +77,23 @@ inline const auto & getLanguageList()
|
|||||||
{
|
{
|
||||||
static const std::array<Options, 17> languages
|
static const std::array<Options, 17> languages
|
||||||
{ {
|
{ {
|
||||||
{ "czech", "Czech", "Čeština", "CP1250", "cs", "cze", "%d.%m.%Y %H:%M:%S", EPluralForms::CZ_3 },
|
{ "czech", "Czech", "Čeština", "CP1250", "cs", "cze", "%d.%m.%Y %H:%M", EPluralForms::CZ_3 },
|
||||||
{ "chinese", "Chinese", "简体中文", "GBK", "zh", "chi", "%Y-%m-%d %H:%M:%S", EPluralForms::VI_1 }, // Note: actually Simplified Chinese
|
{ "chinese", "Chinese", "简体中文", "GBK", "zh", "chi", "%Y-%m-%d %H:%M", EPluralForms::VI_1 }, // Note: actually Simplified Chinese
|
||||||
{ "english", "English", "English", "CP1252", "en", "eng", "%Y-%m-%d %H:%M:%S", EPluralForms::EN_2 }, // English uses international date/time format here
|
{ "english", "English", "English", "CP1252", "en", "eng", "%Y-%m-%d %H:%M", EPluralForms::EN_2 }, // English uses international date/time format here
|
||||||
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", "fin", "%d.%m.%Y %H:%M:%S", EPluralForms::EN_2, },
|
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", "fin", "%d.%m.%Y %H:%M", EPluralForms::EN_2, },
|
||||||
{ "french", "French", "Français", "CP1252", "fr", "fre", "%d/%m/%Y %H:%M:%S", EPluralForms::FR_2, },
|
{ "french", "French", "Français", "CP1252", "fr", "fre", "%d/%m/%Y %H:%M", EPluralForms::FR_2, },
|
||||||
{ "german", "German", "Deutsch", "CP1252", "de", "ger", "%d.%m.%Y %H:%M:%S", EPluralForms::EN_2, },
|
{ "german", "German", "Deutsch", "CP1252", "de", "ger", "%d.%m.%Y %H:%M", EPluralForms::EN_2, },
|
||||||
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "hun", "%Y. %m. %d. %H:%M:%S", EPluralForms::EN_2 },
|
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "hun", "%Y. %m. %d. %H:%M", EPluralForms::EN_2 },
|
||||||
{ "italian", "Italian", "Italiano", "CP1250", "it", "ita", "%d/%m/%Y %H:%M:%S", EPluralForms::EN_2 },
|
{ "italian", "Italian", "Italiano", "CP1250", "it", "ita", "%d/%m/%Y %H:%M", EPluralForms::EN_2 },
|
||||||
{ "korean", "Korean", "한국어", "CP949", "ko", "kor", "%Y-%m-%d %H:%M:%S", EPluralForms::VI_1 },
|
{ "korean", "Korean", "한국어", "CP949", "ko", "kor", "%Y-%m-%d %H:%M", EPluralForms::VI_1 },
|
||||||
{ "polish", "Polish", "Polski", "CP1250", "pl", "pol", "%d.%m.%Y %H:%M:%S", EPluralForms::PL_3 },
|
{ "polish", "Polish", "Polski", "CP1250", "pl", "pol", "%d.%m.%Y %H:%M", EPluralForms::PL_3 },
|
||||||
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", "por", "%d/%m/%Y %H:%M:%S", EPluralForms::EN_2 }, // Note: actually Brazilian Portuguese
|
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", "por", "%d/%m/%Y %H:%M", EPluralForms::EN_2 }, // Note: actually Brazilian Portuguese
|
||||||
{ "russian", "Russian", "Русский", "CP1251", "ru", "rus", "%d.%m.%Y %H:%M:%S", EPluralForms::UK_3 },
|
{ "russian", "Russian", "Русский", "CP1251", "ru", "rus", "%d.%m.%Y %H:%M", EPluralForms::UK_3 },
|
||||||
{ "spanish", "Spanish", "Español", "CP1252", "es", "spa", "%d/%m/%Y %H:%M:%S", EPluralForms::EN_2 },
|
{ "spanish", "Spanish", "Español", "CP1252", "es", "spa", "%d/%m/%Y %H:%M", EPluralForms::EN_2 },
|
||||||
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", "swe", "%Y-%m-%d %H:%M:%S", EPluralForms::EN_2 },
|
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", "swe", "%Y-%m-%d %H:%M", EPluralForms::EN_2 },
|
||||||
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", "tur", "%d.%m.%Y %H:%M:%S", EPluralForms::EN_2 },
|
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", "tur", "%d.%m.%Y %H:%M", EPluralForms::EN_2 },
|
||||||
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "ukr", "%d.%m.%Y %H:%M:%S", EPluralForms::UK_3 },
|
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "ukr", "%d.%m.%Y %H:%M", EPluralForms::UK_3 },
|
||||||
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "vie", "%d/%m/%Y %H:%M:%S", EPluralForms::VI_1 }, // Fan translation uses special encoding
|
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "vie", "%d/%m/%Y %H:%M", EPluralForms::VI_1 }, // Fan translation uses special encoding
|
||||||
} };
|
} };
|
||||||
static_assert(languages.size() == static_cast<size_t>(ELanguages::COUNT), "Languages array is missing a value!");
|
static_assert(languages.size() == static_cast<size_t>(ELanguages::COUNT), "Languages array is missing a value!");
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ set(editor_SRCS
|
|||||||
inspector/messagewidget.cpp
|
inspector/messagewidget.cpp
|
||||||
inspector/rewardswidget.cpp
|
inspector/rewardswidget.cpp
|
||||||
inspector/questwidget.cpp
|
inspector/questwidget.cpp
|
||||||
|
inspector/heroartifactswidget.cpp
|
||||||
|
inspector/artifactwidget.cpp
|
||||||
inspector/heroskillswidget.cpp
|
inspector/heroskillswidget.cpp
|
||||||
inspector/herospellwidget.cpp
|
inspector/herospellwidget.cpp
|
||||||
inspector/PickObjectDelegate.cpp
|
inspector/PickObjectDelegate.cpp
|
||||||
@ -76,6 +78,8 @@ set(editor_HEADERS
|
|||||||
inspector/messagewidget.h
|
inspector/messagewidget.h
|
||||||
inspector/rewardswidget.h
|
inspector/rewardswidget.h
|
||||||
inspector/questwidget.h
|
inspector/questwidget.h
|
||||||
|
inspector/heroartifactswidget.h
|
||||||
|
inspector/artifactwidget.h
|
||||||
inspector/heroskillswidget.h
|
inspector/heroskillswidget.h
|
||||||
inspector/herospellwidget.h
|
inspector/herospellwidget.h
|
||||||
inspector/PickObjectDelegate.h
|
inspector/PickObjectDelegate.h
|
||||||
@ -108,6 +112,8 @@ set(editor_FORMS
|
|||||||
inspector/messagewidget.ui
|
inspector/messagewidget.ui
|
||||||
inspector/rewardswidget.ui
|
inspector/rewardswidget.ui
|
||||||
inspector/questwidget.ui
|
inspector/questwidget.ui
|
||||||
|
inspector/heroartifactswidget.ui
|
||||||
|
inspector/artifactwidget.ui
|
||||||
inspector/heroskillswidget.ui
|
inspector/heroskillswidget.ui
|
||||||
inspector/herospellwidget.ui
|
inspector/herospellwidget.ui
|
||||||
inspector/portraitwidget.ui
|
inspector/portraitwidget.ui
|
||||||
|
63
mapeditor/inspector/artifactwidget.cpp
Normal file
63
mapeditor/inspector/artifactwidget.cpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* herosspellwidget.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 "artifactwidget.h"
|
||||||
|
#include "ui_artifactwidget.h"
|
||||||
|
#include "inspector.h"
|
||||||
|
#include "../../lib/ArtifactUtils.h"
|
||||||
|
#include "../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
ArtifactWidget::ArtifactWidget(CArtifactFittingSet & fittingSet, QWidget * parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::ArtifactWidget),
|
||||||
|
fittingSet(fittingSet)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui->saveButton, &QPushButton::clicked, this, [this]()
|
||||||
|
{
|
||||||
|
emit saveArtifact(ui->artifact->currentData().toInt(), ArtifactPosition(ui->possiblePositions->currentData().toInt()));
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
connect(ui->cancelButton, &QPushButton::clicked, this, &ArtifactWidget::close);
|
||||||
|
connect(ui->possiblePositions, static_cast<void(QComboBox::*) (int)> (&QComboBox::currentIndexChanged), this, &ArtifactWidget::fillArtifacts);
|
||||||
|
|
||||||
|
std::vector<ArtifactPosition> possiblePositions;
|
||||||
|
for(const auto & slot : ArtifactUtils::allWornSlots())
|
||||||
|
{
|
||||||
|
if(fittingSet.isPositionFree(slot))
|
||||||
|
{
|
||||||
|
ui->possiblePositions->addItem(QString::fromStdString(NArtifactPosition::namesHero[slot.num]), slot.num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui->possiblePositions->addItem(QString::fromStdString(NArtifactPosition::backpack), ArtifactPosition::BACKPACK_START);
|
||||||
|
fillArtifacts();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArtifactWidget::fillArtifacts()
|
||||||
|
{
|
||||||
|
ui->artifact->clear();
|
||||||
|
auto currentSlot = ui->possiblePositions->currentData().toInt();
|
||||||
|
for (const auto& art : VLC->arth->getDefaultAllowed())
|
||||||
|
{
|
||||||
|
auto artifact = art.toArtifact();
|
||||||
|
// forbid spell scroll for now as require special handling
|
||||||
|
if (artifact->canBePutAt(&fittingSet, currentSlot, true) && artifact->getId() != ArtifactID::SPELL_SCROLL) {
|
||||||
|
ui->artifact->addItem(QString::fromStdString(artifact->getNameTranslated()), QVariant::fromValue(artifact->getIndex()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtifactWidget::~ArtifactWidget()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
35
mapeditor/inspector/artifactwidget.h
Normal file
35
mapeditor/inspector/artifactwidget.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* ArtifactWidget.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 <QDialog>
|
||||||
|
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ArtifactWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArtifactWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ArtifactWidget(CArtifactFittingSet & fittingSet, QWidget * parent = nullptr);
|
||||||
|
~ArtifactWidget();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void saveArtifact(int32_t artifactIndex, ArtifactPosition slot);
|
||||||
|
private slots:
|
||||||
|
void fillArtifacts();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::ArtifactWidget * ui;
|
||||||
|
CArtifactFittingSet & fittingSet;
|
||||||
|
};
|
92
mapeditor/inspector/artifactwidget.ui
Normal file
92
mapeditor/inspector/artifactwidget.ui
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ArtifactWidget</class>
|
||||||
|
<widget class="QDialog" name="ArtifactWidget">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::WindowModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>150</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>150</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>150</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Artifact</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="gridLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>10</y>
|
||||||
|
<width>381</width>
|
||||||
|
<height>80</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout" columnstretch="1,5">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Artifact</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="artifact"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="possiblePositions"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Equip where:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPushButton" name="saveButton">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>190</x>
|
||||||
|
<y>100</y>
|
||||||
|
<width>93</width>
|
||||||
|
<height>28</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPushButton" name="cancelButton">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>290</x>
|
||||||
|
<y>100</y>
|
||||||
|
<width>93</width>
|
||||||
|
<height>28</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
144
mapeditor/inspector/heroartifactswidget.cpp
Normal file
144
mapeditor/inspector/heroartifactswidget.cpp
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* herosspellwidget.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 "artifactwidget.h"
|
||||||
|
#include "heroartifactswidget.h"
|
||||||
|
#include "ui_heroartifactswidget.h"
|
||||||
|
#include "inspector.h"
|
||||||
|
#include "mapeditorroles.h"
|
||||||
|
#include "../../lib/ArtifactUtils.h"
|
||||||
|
#include "../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
HeroArtifactsWidget::HeroArtifactsWidget(CGHeroInstance & h, QWidget * parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::HeroArtifactsWidget),
|
||||||
|
hero(h),
|
||||||
|
fittingSet(CArtifactFittingSet(h))
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeroArtifactsWidget::~HeroArtifactsWidget()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsWidget::on_addButton_clicked()
|
||||||
|
{
|
||||||
|
ArtifactWidget artifactWidget{ fittingSet, this };
|
||||||
|
connect(&artifactWidget, &ArtifactWidget::saveArtifact, this, &HeroArtifactsWidget::onSaveArtifact);
|
||||||
|
artifactWidget.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsWidget::on_removeButton_clicked()
|
||||||
|
{
|
||||||
|
auto row = ui->artifacts->currentRow();
|
||||||
|
if (row == -1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slot = ui->artifacts->item(row, Column::SLOT)->data(MapEditorRoles::ArtifactSlotRole).toInt();
|
||||||
|
fittingSet.removeArtifact(ArtifactPosition(slot));
|
||||||
|
ui->artifacts->removeRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsWidget::onSaveArtifact(int32_t artifactIndex, ArtifactPosition slot)
|
||||||
|
{
|
||||||
|
auto artifact = ArtifactUtils::createArtifact(VLC->arth->getByIndex(artifactIndex)->getId());
|
||||||
|
fittingSet.putArtifact(slot, artifact);
|
||||||
|
addArtifactToTable(artifactIndex, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsWidget::addArtifactToTable(int32_t artifactIndex, ArtifactPosition slot)
|
||||||
|
{
|
||||||
|
auto artifact = VLC->arth->getByIndex(artifactIndex);
|
||||||
|
auto * itemArtifact = new QTableWidgetItem;
|
||||||
|
itemArtifact->setText(QString::fromStdString(artifact->getNameTranslated()));
|
||||||
|
itemArtifact->setData(MapEditorRoles::ArtifactIDRole, QVariant::fromValue(artifact->getIndex()));
|
||||||
|
|
||||||
|
auto * itemSlot = new QTableWidgetItem;
|
||||||
|
auto slotText = ArtifactUtils::isSlotBackpack(slot) ? NArtifactPosition::backpack : NArtifactPosition::namesHero[slot.num];
|
||||||
|
itemSlot->setData(MapEditorRoles::ArtifactSlotRole, QVariant::fromValue(slot.num));
|
||||||
|
itemSlot->setText(QString::fromStdString(slotText));
|
||||||
|
|
||||||
|
ui->artifacts->insertRow(ui->artifacts->rowCount());
|
||||||
|
ui->artifacts->setItem(ui->artifacts->rowCount() - 1, Column::ARTIFACT, itemArtifact);
|
||||||
|
ui->artifacts->setItem(ui->artifacts->rowCount() - 1, Column::SLOT, itemSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsWidget::obtainData()
|
||||||
|
{
|
||||||
|
std::vector<const CArtifact *> combinedArtifactsParts;
|
||||||
|
for (const auto & [artPosition, artSlotInfo] : fittingSet.artifactsWorn)
|
||||||
|
{
|
||||||
|
addArtifactToTable(VLC->arth->getById(artSlotInfo.artifact->getTypeId())->getIndex(), artPosition);
|
||||||
|
}
|
||||||
|
for (const auto & art : hero.artifactsInBackpack)
|
||||||
|
{
|
||||||
|
addArtifactToTable(VLC->arth->getById(art.artifact->getTypeId())->getIndex(), ArtifactPosition::BACKPACK_START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsWidget::commitChanges()
|
||||||
|
{
|
||||||
|
while(!hero.artifactsWorn.empty())
|
||||||
|
{
|
||||||
|
hero.removeArtifact(hero.artifactsWorn.begin()->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(!hero.artifactsInBackpack.empty())
|
||||||
|
{
|
||||||
|
hero.removeArtifact(ArtifactPosition::BACKPACK_START + static_cast<int>(hero.artifactsInBackpack.size()) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto & [artPosition, artSlotInfo] : fittingSet.artifactsWorn)
|
||||||
|
{
|
||||||
|
hero.putArtifact(artPosition, artSlotInfo.artifact);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto & art : fittingSet.artifactsInBackpack)
|
||||||
|
{
|
||||||
|
hero.putArtifact(ArtifactPosition::BACKPACK_START + static_cast<int>(hero.artifactsInBackpack.size()), art.artifact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeroArtifactsDelegate::HeroArtifactsDelegate(CGHeroInstance & h) : QStyledItemDelegate(), hero(h)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget * HeroArtifactsDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
|
||||||
|
{
|
||||||
|
return new HeroArtifactsWidget(hero, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
|
||||||
|
{
|
||||||
|
if (auto * ed = qobject_cast<HeroArtifactsWidget *>(editor))
|
||||||
|
{
|
||||||
|
ed->obtainData();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QStyledItemDelegate::setEditorData(editor, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeroArtifactsDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const
|
||||||
|
{
|
||||||
|
if (auto * ed = qobject_cast<HeroArtifactsWidget *>(editor))
|
||||||
|
{
|
||||||
|
ed->commitChanges();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QStyledItemDelegate::setModelData(editor, model, index);
|
||||||
|
}
|
||||||
|
}
|
65
mapeditor/inspector/heroartifactswidget.h
Normal file
65
mapeditor/inspector/heroartifactswidget.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* heroartifactswidget.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 <QDialog>
|
||||||
|
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class HeroArtifactsWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeroArtifactsWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit HeroArtifactsWidget(CGHeroInstance &, QWidget *parent = nullptr);
|
||||||
|
~HeroArtifactsWidget();
|
||||||
|
|
||||||
|
void obtainData();
|
||||||
|
void commitChanges();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSaveArtifact(int32_t artifactIndex, ArtifactPosition slot);
|
||||||
|
|
||||||
|
void on_addButton_clicked();
|
||||||
|
|
||||||
|
void on_removeButton_clicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum Column
|
||||||
|
{
|
||||||
|
SLOT, ARTIFACT
|
||||||
|
};
|
||||||
|
Ui::HeroArtifactsWidget * ui;
|
||||||
|
|
||||||
|
CGHeroInstance & hero;
|
||||||
|
CArtifactFittingSet fittingSet;
|
||||||
|
|
||||||
|
void addArtifactToTable(int32_t artifactIndex, ArtifactPosition slot);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeroArtifactsDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using QStyledItemDelegate::QStyledItemDelegate;
|
||||||
|
|
||||||
|
HeroArtifactsDelegate(CGHeroInstance &);
|
||||||
|
|
||||||
|
QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
|
||||||
|
void setEditorData(QWidget * editor, const QModelIndex & index) const override;
|
||||||
|
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CGHeroInstance & hero;
|
||||||
|
};
|
144
mapeditor/inspector/heroartifactswidget.ui
Normal file
144
mapeditor/inspector/heroartifactswidget.ui
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>HeroArtifactsWidget</class>
|
||||||
|
<widget class="QDialog" name="HeroArtifactsWidget">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::NonModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>480</width>
|
||||||
|
<height>635</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>480</width>
|
||||||
|
<height>480</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Artifacts</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="addButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>90</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Add</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="removeButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>90</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="artifacts">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<attribute name="horizontalHeaderVisible">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||||
|
<number>120</number>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="horizontalHeaderStretchLastSection">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="verticalHeaderVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="verticalHeaderDefaultSectionSize">
|
||||||
|
<number>26</number>
|
||||||
|
</attribute>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Slot</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Artifact</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -28,6 +28,7 @@
|
|||||||
#include "messagewidget.h"
|
#include "messagewidget.h"
|
||||||
#include "rewardswidget.h"
|
#include "rewardswidget.h"
|
||||||
#include "questwidget.h"
|
#include "questwidget.h"
|
||||||
|
#include "heroartifactswidget.h"
|
||||||
#include "heroskillswidget.h"
|
#include "heroskillswidget.h"
|
||||||
#include "herospellwidget.h"
|
#include "herospellwidget.h"
|
||||||
#include "portraitwidget.h"
|
#include "portraitwidget.h"
|
||||||
@ -333,6 +334,7 @@ void Inspector::updateProperties(CGHeroInstance * o)
|
|||||||
auto * delegate = new HeroSkillsDelegate(*o);
|
auto * delegate = new HeroSkillsDelegate(*o);
|
||||||
addProperty("Skills", PropertyEditorPlaceholder(), delegate, false);
|
addProperty("Skills", PropertyEditorPlaceholder(), delegate, false);
|
||||||
addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false);
|
addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false);
|
||||||
|
addProperty("Artifacts", PropertyEditorPlaceholder(), new HeroArtifactsDelegate(*o), false);
|
||||||
|
|
||||||
if(o->getHeroTypeID().hasValue() || o->ID == Obj::PRISON)
|
if(o->getHeroTypeID().hasValue() || o->ID == Obj::PRISON)
|
||||||
{ //Hero type
|
{ //Hero type
|
||||||
|
@ -640,9 +640,19 @@ ModCompatibilityInfo MapController::modAssessmentMap(const CMap & map)
|
|||||||
continue;
|
continue;
|
||||||
extractEntityMod(spellID.toEntity(VLC));
|
extractEntityMod(spellID.toEntity(VLC));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(const auto & [_, slotInfo] : hero->artifactsWorn)
|
||||||
|
{
|
||||||
|
extractEntityMod(slotInfo.artifact->getTypeId().toEntity(VLC));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto & art : hero->artifactsInBackpack)
|
||||||
|
{
|
||||||
|
extractEntityMod(art.artifact->getTypeId().toEntity(VLC));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: terrains, artifacts?
|
//TODO: terrains?
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -16,5 +16,8 @@ enum MapEditorRoles
|
|||||||
TownEventRole = Qt::UserRole + 1,
|
TownEventRole = Qt::UserRole + 1,
|
||||||
PlayerIDRole,
|
PlayerIDRole,
|
||||||
BuildingIDRole,
|
BuildingIDRole,
|
||||||
SpellIDRole
|
SpellIDRole,
|
||||||
|
ObjectInstanceIDRole,
|
||||||
|
ArtifactIDRole,
|
||||||
|
ArtifactSlotRole,
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(int3)
|
||||||
|
|
||||||
//parses date for lose condition (1m 1w 1d)
|
//parses date for lose condition (1m 1w 1d)
|
||||||
int expiredDate(const QString & date);
|
int expiredDate(const QString & date);
|
||||||
QString expiredDate(int date);
|
QString expiredDate(int date);
|
||||||
|
@ -55,6 +55,28 @@ TResources resourcesFromVariant(const QVariant & v)
|
|||||||
return TResources(vJson);
|
return TResources(vJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariant toVariant(std::vector<ObjectInstanceID> objects)
|
||||||
|
{
|
||||||
|
QVariantList result;
|
||||||
|
for(auto obj : objects)
|
||||||
|
{
|
||||||
|
result.push_back(QVariant::fromValue(obj.num));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ObjectInstanceID> deletedObjectsIdsFromVariant(const QVariant & v)
|
||||||
|
{
|
||||||
|
std::vector<ObjectInstanceID> result;
|
||||||
|
for(auto idAsVariant : v.toList())
|
||||||
|
{
|
||||||
|
auto id = idAsVariant.value<int>();
|
||||||
|
result.push_back(ObjectInstanceID(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QVariant toVariant(const CMapEvent & event)
|
QVariant toVariant(const CMapEvent & event)
|
||||||
{
|
{
|
||||||
QVariantMap result;
|
QVariantMap result;
|
||||||
@ -66,6 +88,7 @@ QVariant toVariant(const CMapEvent & event)
|
|||||||
result["firstOccurrence"] = QVariant::fromValue(event.firstOccurrence);
|
result["firstOccurrence"] = QVariant::fromValue(event.firstOccurrence);
|
||||||
result["nextOccurrence"] = QVariant::fromValue(event.nextOccurrence);
|
result["nextOccurrence"] = QVariant::fromValue(event.nextOccurrence);
|
||||||
result["resources"] = toVariant(event.resources);
|
result["resources"] = toVariant(event.resources);
|
||||||
|
result["deletedObjectsInstances"] = toVariant(event.deletedObjectsInstances);
|
||||||
return QVariant(result);
|
return QVariant(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +104,7 @@ CMapEvent eventFromVariant(CMapHeader & mapHeader, const QVariant & variant)
|
|||||||
result.firstOccurrence = v.value("firstOccurrence").toInt();
|
result.firstOccurrence = v.value("firstOccurrence").toInt();
|
||||||
result.nextOccurrence = v.value("nextOccurrence").toInt();
|
result.nextOccurrence = v.value("nextOccurrence").toInt();
|
||||||
result.resources = resourcesFromVariant(v.value("resources"));
|
result.resources = resourcesFromVariant(v.value("resources"));
|
||||||
|
result.deletedObjectsInstances = deletedObjectsIdsFromVariant(v.value("deletedObjectsInstances"));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +161,6 @@ void EventSettings::on_timedEventRemove_clicked()
|
|||||||
|
|
||||||
void EventSettings::on_eventsList_itemActivated(QListWidgetItem *item)
|
void EventSettings::on_eventsList_itemActivated(QListWidgetItem *item)
|
||||||
{
|
{
|
||||||
new TimedEvent(item, parentWidget());
|
new TimedEvent(*controller, item, parentWidget());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user