1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge branch 'develop' into dev-documentation

This commit is contained in:
Alexander Wilms 2024-07-17 17:09:52 +02:00 committed by Alexander Wilms
commit e9f2907efc
140 changed files with 1344 additions and 887 deletions

1
.gitignore vendored
View File

@ -43,6 +43,7 @@ VCMI_VS11.sdf
*.ipch
VCMI_VS11.opensdf
.DS_Store
.directory
CMakeUserPresets.json
compile_commands.json
fuzzylite.pc

View File

@ -25,7 +25,7 @@ namespace NKAI
{
std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
uint64_t AISharedStorage::version = 0;
uint32_t AISharedStorage::version = 0;
boost::mutex AISharedStorage::locker;
std::set<int3> committedTiles;
std::set<int3> committedTilesInitial;
@ -224,7 +224,6 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
AIPathNode * initialNode = allocated.value();
initialNode->inPQ = false;
initialNode->pq = nullptr;
initialNode->turns = actor->initialTurn;
initialNode->moveRemains = actor->initialMovement;

View File

@ -44,14 +44,17 @@ enum DayFlags : ui8
struct AIPathNode : public CGPathNode
{
std::shared_ptr<const SpecialAction> specialAction;
const AIPathNode * chainOther;
const ChainActor * actor;
uint64_t danger;
uint64_t armyLoss;
uint32_t version;
int16_t manaCost;
DayFlags dayFlags;
const AIPathNode * chainOther;
std::shared_ptr<const SpecialAction> specialAction;
const ChainActor * actor;
uint64_t version;
void addSpecialAction(std::shared_ptr<const SpecialAction> action);
@ -152,7 +155,7 @@ class AISharedStorage
std::shared_ptr<boost::multi_array<AIPathNode, 4>> nodes;
public:
static boost::mutex locker;
static uint64_t version;
static uint32_t version;
AISharedStorage(int3 mapSize);
~AISharedStorage();

View File

@ -173,9 +173,9 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
* @param assembleTo If assemble is true, this represents the artifact ID of the combination
* artifact to assemble to. Otherwise it's not used.
*/
void CCallback::assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
void CCallback::assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
{
AssembleArtifacts aa(hero->id, artifactSlot, assemble, assembleTo);
AssembleArtifacts aa(heroID, artifactSlot, assemble, assembleTo);
sendRequest(&aa);
}

View File

@ -93,7 +93,7 @@ public:
virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
virtual void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
virtual void endTurn()=0;
@ -176,7 +176,7 @@ public:
int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) override;
bool dismissHero(const CGHeroInstance * hero) override;
bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;

View File

@ -72,6 +72,7 @@
"vcmi.lobby.noUnderground" : "no underground",
"vcmi.lobby.sortDate" : "Sorts maps by change date",
"vcmi.lobby.backToLobby" : "Return to lobby",
"vcmi.lobby.author" : "Author",
"vcmi.lobby.login.title" : "VCMI Online Lobby",
"vcmi.lobby.login.username" : "Username:",

View File

@ -72,6 +72,7 @@
"vcmi.lobby.noUnderground" : "Kein Untergrund",
"vcmi.lobby.sortDate" : "Ordnet Karten nach Änderungsdatum",
"vcmi.lobby.backToLobby" : "Zur Lobby zurückkehren",
"vcmi.lobby.author" : "Author",
"vcmi.lobby.login.title" : "VCMI Online Lobby",
"vcmi.lobby.login.username" : "Benutzername:",

View File

@ -0,0 +1,166 @@
/*
* ArtifactsUIController.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 "ArtifactsUIController.h"
#include "CGameInfo.h"
#include "CPlayerInterface.h"
#include "../CCallback.h"
#include "../lib/ArtifactUtils.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "widgets/CComponent.h"
#include "windows/CWindowWithArtifacts.h"
ArtifactsUIController::ArtifactsUIController()
{
numOfMovedArts = 0;
}
bool ArtifactsUIController::askToAssemble(const ArtifactLocation & al, const bool onlyEquipped, const bool checkIgnored)
{
if(auto hero = LOCPLINT->cb->getHero(al.artHolder))
{
if(hero->getArt(al.slot) == nullptr)
{
logGlobal->error("artifact location %d points to nothing", al.slot.num);
return false;
}
return askToAssemble(hero, al.slot, onlyEquipped, checkIgnored);
}
return false;
}
bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot,
const bool onlyEquipped, const bool checkIgnored)
{
assert(hero);
const auto art = hero->getArt(slot);
assert(art);
if(hero->tempOwner != LOCPLINT->playerID)
return false;
if(numOfArtsAskAssembleSession != 0)
numOfArtsAskAssembleSession--;
auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), onlyEquipped);
if(!assemblyPossibilities.empty())
{
auto askThread = new boost::thread([this, hero, art, slot, assemblyPossibilities, checkIgnored]() -> void
{
boost::mutex::scoped_lock askLock(askAssembleArtifactMutex);
for(const auto combinedArt : assemblyPossibilities)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if(checkIgnored)
{
if(vstd::contains(ignoredArtifacts, combinedArt->getId()))
continue;
ignoredArtifacts.emplace(combinedArt->getId());
}
bool assembleConfirmed = false;
MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
message.appendEOL();
message.appendEOL();
message.appendRawString(CGI->generaltexth->allTexts[732]); // You possess all of the components needed to assemble the
message.replaceName(ArtifactID(combinedArt->getId()));
LOCPLINT->showYesNoDialog(message.toString(), [&assembleConfirmed, hero, slot, combinedArt]()
{
assembleConfirmed = true;
LOCPLINT->cb.get()->assembleArtifacts(hero->id, slot, true, combinedArt->getId());
}, nullptr, {std::make_shared<CComponent>(ComponentType::ARTIFACT, combinedArt->getId())});
LOCPLINT->waitWhileDialog();
if(assembleConfirmed)
break;
}
});
askThread->detach();
return true;
}
return false;
}
bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
{
assert(hero);
const auto art = hero->getArt(slot);
assert(art);
if(hero->tempOwner != LOCPLINT->playerID)
return false;
if(art->isCombined())
{
if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1))
return false;
MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
message.appendEOL();
message.appendEOL();
message.appendRawString(CGI->generaltexth->allTexts[733]); // Do you wish to disassemble this artifact?
LOCPLINT->showYesNoDialog(message.toString(), [hero, slot]()
{
LOCPLINT->cb->assembleArtifacts(hero->id, slot, false, ArtifactID());
}, nullptr);
return true;
}
return false;
}
void ArtifactsUIController::artifactRemoved()
{
for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
artWin->update();
LOCPLINT->waitWhileDialog();
}
void ArtifactsUIController::artifactMoved()
{
// If a bulk transfer has arrived, then redrawing only the last art movement.
if(numOfMovedArts != 0)
numOfMovedArts--;
if(numOfMovedArts == 0)
for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
{
artWin->update();
}
LOCPLINT->waitWhileDialog();
}
void ArtifactsUIController::bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts)
{
assert(totalNumOfArts >= possibleAssemblyNumOfArts);
numOfMovedArts = totalNumOfArts;
if(numOfArtsAskAssembleSession == 0)
{
// Do not start the next session until the previous one is finished
numOfArtsAskAssembleSession = possibleAssemblyNumOfArts;
ignoredArtifacts.clear();
}
}
void ArtifactsUIController::artifactAssembled()
{
for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
artWin->update();
}
void ArtifactsUIController::artifactDisassembled()
{
for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
artWin->update();
}

View File

@ -0,0 +1,42 @@
/*
* ArtifactsUIController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/constants/EntityIdentifiers.h"
#include "../lib/networkPacks/ArtifactLocation.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
VCMI_LIB_NAMESPACE_END
class ArtifactsUIController
{
size_t numOfMovedArts;
size_t numOfArtsAskAssembleSession;
std::set<ArtifactID> ignoredArtifacts;
boost::mutex askAssembleArtifactMutex;
public:
ArtifactsUIController();
bool askToAssemble(const ArtifactLocation & al, const bool onlyEquipped = false, const bool checkIgnored = false);
bool askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot, const bool onlyEquipped = false,
const bool checkIgnored = false);
bool askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
void artifactRemoved();
void artifactMoved();
void bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts);
void artifactAssembled();
void artifactDisassembled();
};

View File

@ -168,6 +168,7 @@ set(client_SRCS
windows/settings/BattleOptionsTab.cpp
windows/settings/AdventureOptionsTab.cpp
ArtifactsUIController.cpp
CGameInfo.cpp
CMT.cpp
CPlayerInterface.cpp
@ -371,6 +372,7 @@ set(client_HEADERS
windows/settings/BattleOptionsTab.h
windows/settings/AdventureOptionsTab.h
ArtifactsUIController.h
CGameInfo.h
CMT.h
CPlayerInterface.h

View File

@ -66,7 +66,6 @@
#include "../CCallback.h"
#include "../lib/CArtHandler.h"
#include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
@ -132,7 +131,9 @@ struct HeroObjectRetriever
CPlayerInterface::CPlayerInterface(PlayerColor Player):
localState(std::make_unique<PlayerLocalState>(*this)),
movementController(std::make_unique<HeroMovementController>())
movementController(std::make_unique<HeroMovementController>()),
artifactController(std::make_unique<ArtifactsUIController>())
{
logGlobal->trace("\tHuman player interface for player %s being constructed", Player.toString());
GH.defActionsDef = 0;
@ -148,7 +149,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
isAutoFightOn = false;
isAutoFightEndBattle = false;
ignoreEvents = false;
numOfMovedArts = 0;
}
CPlayerInterface::~CPlayerInterface()
@ -1125,7 +1125,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
std::vector<int> tempList;
tempList.reserve(objectGuiOrdered.size());
for(auto item : objectGuiOrdered)
for(const auto & item : objectGuiOrdered)
tempList.push_back(item.getNum());
CComponent localIconC(icon);
@ -1134,7 +1134,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
localIconC.removeChild(localIcon.get(), false);
std::vector<std::shared_ptr<IImage>> images;
for(auto & obj : objectGuiOrdered)
for(const auto & obj : objectGuiOrdered)
{
if(!settings["general"]["enableUiEnhancements"].Bool())
break;
@ -1252,35 +1252,6 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
GH.windows().pushWindow(cgw);
}
/**
* Shows the dialog that appears when right-clicking an artifact that can be assembled
* into a combinational one on an artifact screen. Does not require the combination of
* artifacts to be legal.
*/
void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<void()> onYes)
{
std::string text = artifact->getDescriptionTranslated();
text += "\n\n";
std::vector<std::shared_ptr<CComponent>> scs;
if(assembledArtifact)
{
// You possess all of the components to...
text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated());
// Picture of assembled artifact at bottom.
auto sc = std::make_shared<CComponent>(ComponentType::ARTIFACT, assembledArtifact->getId());
scs.push_back(sc);
}
else
{
// Do you wish to disassemble this artifact?
text += CGI->generaltexth->allTexts[733];
}
showYesNoDialog(text, onYes, nullptr, scs);
}
void CPlayerInterface::requestRealized( PackageApplied *pa )
{
if(pa->packType == CTypeList::getInstance().getTypeID<MoveHero>(nullptr))
@ -1738,17 +1709,7 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
{
if(auto hero = cb->getHero(al.artHolder))
{
auto art = hero->getArt(al.slot);
if(art == nullptr)
{
logGlobal->error("artifact location %d points to nothing",
al.slot.num);
return;
}
ArtifactUtilsClient::askToAssemble(hero, al.slot);
}
artifactController->askToAssemble(al, true, true);
}
void CPlayerInterface::artifactPut(const ArtifactLocation &al)
@ -1761,55 +1722,33 @@ void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onHeroChanged(cb->getHero(al.artHolder));
for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
artWin->artifactRemoved(al);
waitWhileDialog();
artifactController->artifactRemoved();
}
void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onHeroChanged(cb->getHero(dst.artHolder));
// If a bulk transfer has arrived, then redrawing only the last art movement.
if(numOfMovedArts != 0)
numOfMovedArts--;
for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
{
artWin->artifactMoved(src, dst);
if(numOfMovedArts == 0)
{
artWin->update();
artWin->redraw();
}
}
waitWhileDialog();
artifactController->artifactMoved();
}
void CPlayerInterface::bulkArtMovementStart(size_t numOfArts)
void CPlayerInterface::bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts)
{
numOfMovedArts = numOfArts;
artifactController->bulkArtMovementStart(totalNumOfArts, possibleAssemblyNumOfArts);
}
void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onHeroChanged(cb->getHero(al.artHolder));
for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
artWin->artifactAssembled(al);
artifactController->artifactAssembled();
}
void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
adventureInt->onHeroChanged(cb->getHero(al.artHolder));
for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
artWin->artifactDisassembled(al);
artifactController->artifactDisassembled();
}
void CPlayerInterface::waitForAllDialogs()

View File

@ -9,6 +9,8 @@
*/
#pragma once
#include "ArtifactsUIController.h"
#include "../lib/FunctionList.h"
#include "../lib/CGameInterface.h"
#include "gui/CIntObject.h"
@ -16,9 +18,7 @@
VCMI_LIB_NAMESPACE_BEGIN
class Artifact;
struct TryMoveHero;
class CGHeroInstance;
class CStack;
class CCreature;
struct CGPath;
@ -59,14 +59,13 @@ namespace boost
class CPlayerInterface : public CGameInterface, public IUpdateable
{
bool ignoreEvents;
size_t numOfMovedArts;
int autosaveCount;
std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
std::unique_ptr<HeroMovementController> movementController;
public: // TODO: make private
std::unique_ptr<ArtifactsUIController> artifactController;
std::shared_ptr<Environment> env;
std::unique_ptr<PlayerLocalState> localState;
@ -98,7 +97,7 @@ protected: // Call-ins from server, should not be called directly, but only via
void artifactPut(const ArtifactLocation &al) override;
void artifactRemoved(const ArtifactLocation &al) override;
void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst) override;
void bulkArtMovementStart(size_t numOfArts) override;
void bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts) override;
void artifactAssembled(const ArtifactLocation &al) override;
void askToAssembleArtifact(const ArtifactLocation & dst) override;
void artifactDisassembled(const ArtifactLocation &al) override;
@ -180,7 +179,6 @@ public: // public interface for use by client via LOCPLINT access
void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard;
void showHeroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2);
void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<void()> onYes);
void waitWhileDialog();
void waitForAllDialogs();
void openTownWindow(const CGTownInstance * town); //shows townscreen

View File

@ -238,9 +238,9 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
si->difficulty = lastDifficulty.Integer();
logNetwork->trace("\tStarting local server");
serverRunner->start(getLocalPort(), connectToLobby, si);
uint16_t srvport = serverRunner->start(getLocalPort(), connectToLobby, si);
logNetwork->trace("\tConnecting to local server");
connectToServer(getLocalHostname(), getLocalPort());
connectToServer(getLocalHostname(), srvport);
logNetwork->trace("\tWaiting for connection");
}

View File

@ -48,7 +48,6 @@ public:
void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) override;
void visitPutArtifact(PutArtifact & pack) override;
void visitEraseArtifact(EraseArtifact & pack) override;
void visitMoveArtifact(MoveArtifact & pack) override;
void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
void visitAssembledArtifact(AssembledArtifact & pack) override;
void visitDisassembledArtifact(DisassembledArtifact & pack) override;

View File

@ -296,45 +296,45 @@ void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactRemoved, pack.al);
}
void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack)
{
auto moveArtifact = [this, &pack](PlayerColor player) -> void
{
callInterfaceIfPresent(cl, player, &IGameEventsReceiver::artifactMoved, pack.src, pack.dst);
if(pack.askAssemble)
callInterfaceIfPresent(cl, player, &IGameEventsReceiver::askToAssembleArtifact, pack.dst);
};
moveArtifact(pack.interfaceOwner);
if(pack.interfaceOwner != cl.getOwner(pack.dst.artHolder))
moveArtifact(cl.getOwner(pack.dst.artHolder));
cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
}
void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
{
auto applyMove = [this, &pack](std::vector<BulkMoveArtifacts::LinkedSlots> & artsPack) -> void
const auto dstOwner = cl.getOwner(pack.dstArtHolder);
const auto applyMove = [this, &pack, dstOwner](std::vector<BulkMoveArtifacts::LinkedSlots> & artsPack)
{
for(auto & slotToMove : artsPack)
for(const auto & slotToMove : artsPack)
{
auto srcLoc = ArtifactLocation(pack.srcArtHolder, slotToMove.srcPos);
auto dstLoc = ArtifactLocation(pack.dstArtHolder, slotToMove.dstPos);
MoveArtifact ma(pack.interfaceOwner, srcLoc, dstLoc, pack.askAssemble);
visitMoveArtifact(ma);
const auto srcLoc = ArtifactLocation(pack.srcArtHolder, slotToMove.srcPos);
const auto dstLoc = ArtifactLocation(pack.dstArtHolder, slotToMove.dstPos);
callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
if(slotToMove.askAssemble)
callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::askToAssembleArtifact, dstLoc);
if(pack.interfaceOwner != dstOwner)
callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
}
};
auto srcOwner = cl.getOwner(pack.srcArtHolder);
auto dstOwner = cl.getOwner(pack.dstArtHolder);
size_t possibleAssemblyNumOfArts = 0;
const auto calcPossibleAssemblyNumOfArts = [&possibleAssemblyNumOfArts](const auto & slotToMove)
{
if(slotToMove.askAssemble)
possibleAssemblyNumOfArts++;
};
std::for_each(pack.artsPack0.cbegin(), pack.artsPack0.cend(), calcPossibleAssemblyNumOfArts);
std::for_each(pack.artsPack1.cbegin(), pack.artsPack1.cend(), calcPossibleAssemblyNumOfArts);
// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
callInterfaceIfPresent(cl, srcOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
if(srcOwner != dstOwner)
callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::bulkArtMovementStart,
pack.artsPack0.size() + pack.artsPack1.size(), possibleAssemblyNumOfArts);
if(pack.interfaceOwner != dstOwner)
callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::bulkArtMovementStart,
pack.artsPack0.size() + pack.artsPack1.size(), possibleAssemblyNumOfArts);
applyMove(pack.artsPack0);
if(pack.swap)
if(!pack.artsPack1.empty())
applyMove(pack.artsPack1);
}

View File

@ -20,22 +20,35 @@
#include <boost/process/io.hpp>
#endif
#include <future>
ServerThreadRunner::ServerThreadRunner() = default;
ServerThreadRunner::~ServerThreadRunner() = default;
void ServerThreadRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
uint16_t ServerThreadRunner::start(uint16_t cfgport, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
{
server = std::make_unique<CVCMIServer>(port, connectToLobby, true);
// cfgport may be 0 -- the real port is returned after calling prepare()
server = std::make_unique<CVCMIServer>(cfgport, true);
if (startingInfo)
{
server->si = startingInfo; //Else use default
}
threadRunLocalServer = boost::thread([this]{
std::promise<uint16_t> promise;
threadRunLocalServer = boost::thread([this, connectToLobby, &promise]{
setThreadName("runServer");
uint16_t port = server->prepare(connectToLobby);
promise.set_value(port);
server->run();
});
logNetwork->trace("Waiting for server port...");
auto srvport = promise.get_future().get();
logNetwork->debug("Server port: %d", srvport);
return srvport;
}
void ServerThreadRunner::shutdown()
@ -73,7 +86,7 @@ int ServerProcessRunner::exitCode()
return child->exit_code();
}
void ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
uint16_t ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
{
boost::filesystem::path serverPath = VCMIDirs::get().serverPath();
boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt";
@ -88,6 +101,8 @@ void ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_
if (ec)
throw std::runtime_error("Failed to start server! Reason: " + ec.message());
return port;
}
#endif

View File

@ -20,7 +20,7 @@ class CVCMIServer;
class IServerRunner
{
public:
virtual void start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) = 0;
virtual uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) = 0;
virtual void shutdown() = 0;
virtual void wait() = 0;
virtual int exitCode() = 0;
@ -34,7 +34,7 @@ class ServerThreadRunner : public IServerRunner, boost::noncopyable
std::unique_ptr<CVCMIServer> server;
boost::thread threadRunLocalServer;
public:
void start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
void shutdown() override;
void wait() override;
int exitCode() override;
@ -56,7 +56,7 @@ class ServerProcessRunner : public IServerRunner, boost::noncopyable
std::unique_ptr<boost::process::child> child;
public:
void start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
void shutdown() override;
void wait() override;
int exitCode() override;

View File

@ -402,7 +402,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
}
OptionsTab::SelectionWindow::SelectionWindow(const PlayerColor & color, SelType _type)
OptionsTab::SelectionWindow::SelectionWindow(const PlayerColor & color, SelType _type, int sliderPos)
: CWindowObject(BORDERED), color(color)
{
addUsedEvents(LCLICK | SHOW_POPUP);
@ -434,15 +434,18 @@ OptionsTab::SelectionWindow::SelectionWindow(const PlayerColor & color, SelType
if(initialFaction.isValid())
allowedBonus.push_back(PlayerStartingBonus::RESOURCE);
recreate();
recreate(sliderPos);
}
int OptionsTab::SelectionWindow::calcLines(FactionID faction)
std::tuple<int, int> OptionsTab::SelectionWindow::calcLines(FactionID faction)
{
double additionalItems = 1; // random
int additionalItems = 1; // random
if(!faction.isValid())
return std::ceil(((double)allowedFactions.size() + additionalItems) / elementsPerLine);
return std::make_tuple(
std::ceil(((double)allowedFactions.size() + additionalItems) / MAX_ELEM_PER_LINES),
(allowedFactions.size() + additionalItems) % MAX_ELEM_PER_LINES
);
int count = 0;
for(auto & elemh : allowedHeroes)
@ -452,7 +455,10 @@ int OptionsTab::SelectionWindow::calcLines(FactionID faction)
count++;
}
return std::ceil(std::max((double)count + additionalItems, (double)allowedFactions.size() + additionalItems) / (double)elementsPerLine);
return std::make_tuple(
std::ceil(((double)count + additionalItems) / MAX_ELEM_PER_LINES),
(count + additionalItems) % MAX_ELEM_PER_LINES
);
}
void OptionsTab::SelectionWindow::apply()
@ -482,13 +488,17 @@ void OptionsTab::SelectionWindow::setSelection()
void OptionsTab::SelectionWindow::reopen()
{
auto window = std::shared_ptr<SelectionWindow>(new SelectionWindow(color, type));
close();
if(CSH->isMyColor(color) || CSH->isHost())
GH.windows().pushWindow(window);
if(type == SelType::HERO && SEL->getStartInfo()->playerInfos.find(color)->second.castle == FactionID::RANDOM)
close();
else{
auto window = std::shared_ptr<SelectionWindow>(new SelectionWindow(color, type, slider ? slider->getValue() : 0));
close();
if(CSH->isMyColor(color) || CSH->isHost())
GH.windows().pushWindow(window);
}
}
void OptionsTab::SelectionWindow::recreate()
void OptionsTab::SelectionWindow::recreate(int sliderPos)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@ -497,32 +507,19 @@ void OptionsTab::SelectionWindow::recreate()
elementsPerLine = allowedBonus.size();
else
{
// try to make squarish
if(type == SelType::TOWN)
elementsPerLine = floor(sqrt(allowedFactions.size()));
if(type == SelType::HERO)
{
int count = 0;
for(auto & elem : allowedHeroes)
{
const CHero * type = elem.toHeroType();
if(type->heroClass->faction == selectedFaction)
{
count++;
}
}
elementsPerLine = floor(sqrt(count));
}
amountLines = calcLines((type > SelType::TOWN) ? selectedFaction : FactionID::RANDOM);
std::tie(amountLines, elementsPerLine) = calcLines((type > SelType::TOWN) ? selectedFaction : FactionID::RANDOM);
if(amountLines > 1 || elementsPerLine == 0)
elementsPerLine = MAX_ELEM_PER_LINES;
}
int x = (elementsPerLine) * (ICON_BIG_WIDTH-1);
int y = (amountLines) * (ICON_BIG_HEIGHT-1);
int y = (std::min(amountLines, MAX_LINES)) * (ICON_BIG_HEIGHT-1);
pos = Rect(0, 0, x, y);
int sliderWidth = ((amountLines > MAX_LINES) ? 16 : 0);
backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), pos);
pos = Rect(pos.x, pos.y, x + sliderWidth, y);
backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w - sliderWidth, pos.h));
backgroundTexture->playerColored(PlayerColor(1));
updateShadow();
@ -532,7 +529,15 @@ void OptionsTab::SelectionWindow::recreate()
genContentHeroes();
if(type == SelType::BONUS)
genContentBonus();
genContentGrid(amountLines);
genContentGrid(std::min(amountLines, MAX_LINES));
if(!slider && amountLines > MAX_LINES)
{
slider = std::make_shared<CSlider>(Point(x, 0), y, std::bind(&OptionsTab::SelectionWindow::sliderMove, this, _1), MAX_LINES, amountLines, 0, Orientation::VERTICAL, CSlider::BLUE);
slider->setPanningStep(ICON_BIG_HEIGHT);
slider->setScrollBounds(Rect(-pos.w + slider->pos.w, 0, x + slider->pos.w, y));
slider->scrollTo(sliderPos);
}
center();
}
@ -570,22 +575,26 @@ void OptionsTab::SelectionWindow::genContentFactions()
if(selectedFaction == FactionID::RANDOM)
components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
factions.clear();
for(auto & elem : allowedFactions)
{
int x = i % elementsPerLine;
int y = i / elementsPerLine;
int y = (i / elementsPerLine) - (slider ? slider->getValue() : 0);
PlayerSettings set = PlayerSettings();
set.castle = elem;
CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
factions.push_back(elem);
i++;
if(y < 0 || y > MAX_LINES - 1)
continue;
components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
components.push_back(std::make_shared<CPicture>(ImagePath::builtin(selectedFaction == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig"), x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedFaction == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
factions.push_back(elem);
i++;
}
}
@ -602,33 +611,36 @@ void OptionsTab::SelectionWindow::genContentHeroes()
if(selectedHero == HeroTypeID::RANDOM)
components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
heroes.clear();
for(auto & elem : allowedHeroes)
{
const CHero * type = elem.toHeroType();
if(type->heroClass->faction == selectedFaction)
{
if(type->heroClass->faction != selectedFaction)
continue;
int x = i % elementsPerLine;
int y = i / elementsPerLine;
int x = i % elementsPerLine;
int y = (i / elementsPerLine) - (slider ? slider->getValue() : 0);
PlayerSettings set = PlayerSettings();
set.hero = elem;
PlayerSettings set = PlayerSettings();
set.hero = elem;
CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedHero == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
ImagePath image = ImagePath::builtin("lobby/townBorderBig");
if(selectedHero == elem)
image = ImagePath::builtin("lobby/townBorderBigActivated");
if(unusableHeroes.count(elem))
image = ImagePath::builtin("lobby/townBorderBigGrayedOut");
components.push_back(std::make_shared<CPicture>(image, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
heroes.push_back(elem);
heroes.push_back(elem);
i++;
i++;
}
if(y < 0 || y > MAX_LINES - 1)
continue;
components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedHero == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
ImagePath image = ImagePath::builtin("lobby/townBorderBig");
if(selectedHero == elem)
image = ImagePath::builtin("lobby/townBorderBigActivated");
if(unusableHeroes.count(elem))
image = ImagePath::builtin("lobby/townBorderBigGrayedOut");
components.push_back(std::make_shared<CPicture>(image, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
}
}
@ -659,7 +671,7 @@ void OptionsTab::SelectionWindow::genContentBonus()
int OptionsTab::SelectionWindow::getElement(const Point & cursorPosition)
{
int x = (cursorPosition.x - pos.x) / (ICON_BIG_WIDTH-1);
int y = (cursorPosition.y - pos.y) / (ICON_BIG_HEIGHT-1);
int y = (cursorPosition.y - pos.y) / (ICON_BIG_HEIGHT-1) + (slider ? slider->getValue() : 0);
return x + y * elementsPerLine;
}
@ -741,6 +753,14 @@ void OptionsTab::SelectionWindow::setElement(int elem, bool doApply)
apply();
}
void OptionsTab::SelectionWindow::sliderMove(int slidPos)
{
if(!slider)
return; // ignore spurious call when slider is being created
recreate();
redraw();
}
bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int eventType) const
{
return true; // capture click also outside of window
@ -748,6 +768,9 @@ bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int event
void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition)
{
if(slider && slider->pos.isInside(cursorPosition))
return;
if(!pos.isInside(cursorPosition))
{
close();
@ -761,7 +784,7 @@ void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition)
void OptionsTab::SelectionWindow::showPopupWindow(const Point & cursorPosition)
{
if(!pos.isInside(cursorPosition))
if(!pos.isInside(cursorPosition) || (slider && slider->pos.isInside(cursorPosition)))
return;
int elem = getElement(cursorPosition);

View File

@ -26,6 +26,7 @@ class CAnimImage;
class CComponentBox;
class CTextBox;
class CButton;
class CSlider;
class FilledTexturePlayerColored;
@ -105,8 +106,13 @@ private:
const int TEXT_POS_X = 29;
const int TEXT_POS_Y = 56;
const int MAX_LINES = 5;
const int MAX_ELEM_PER_LINES = 5;
int elementsPerLine;
std::shared_ptr<CSlider> slider;
PlayerColor color;
SelType type;
@ -134,13 +140,15 @@ private:
void genContentBonus();
void drawOutlinedText(int x, int y, ColorRGBA color, std::string text);
int calcLines(FactionID faction);
std::tuple<int, int> calcLines(FactionID faction);
void apply();
void recreate();
void recreate(int sliderPos = 0);
void setSelection();
int getElement(const Point & cursorPosition);
void setElement(int element, bool doApply);
void sliderMove(int slidPos);
bool receiveEvent(const Point & position, int eventType) const override;
void clickReleased(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
@ -148,7 +156,7 @@ private:
public:
void reopen();
SelectionWindow(const PlayerColor & color, SelType _type);
SelectionWindow(const PlayerColor & color, SelType _type, int sliderPos = 0);
};
/// Image with current town/hero/bonus

View File

@ -44,6 +44,7 @@
#include "../../lib/mapping/CMapHeader.h"
#include "../../lib/mapping/MapFormat.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/TextOperations.h"
bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb)
{
@ -391,7 +392,19 @@ void SelectionTab::showPopupWindow(const Point & cursorPosition)
return;
if(!curItems[py]->isFolder)
GH.windows().createAndPushWindow<CMapOverview>(curItems[py]->getNameTranslated(), curItems[py]->fullFileURI, curItems[py]->date, ResourcePath(curItems[py]->fileURI), tabType);
{
auto creationDateTime = tabType == ESelectionScreen::newGame && curItems[py]->mapHeader->creationDateTime ? TextOperations::getFormattedDateTimeLocal(curItems[py]->mapHeader->creationDateTime) : curItems[py]->date;
auto author = curItems[py]->mapHeader->author.toString() + (!curItems[py]->mapHeader->authorContact.toString().empty() ? (" <" + curItems[py]->mapHeader->authorContact.toString() + ">") : "");
GH.windows().createAndPushWindow<CMapOverview>(
curItems[py]->getNameTranslated(),
curItems[py]->fullFileURI,
creationDateTime,
author,
curItems[py]->mapHeader->mapVersion.toString(),
ResourcePath(curItems[py]->fileURI),
tabType
);
}
else
CRClickPopup::createAndPush(curItems[py]->folderName);
}

View File

@ -245,59 +245,3 @@ void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<
text += info.toString();
}
}
bool ArtifactUtilsClient::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
{
assert(hero);
const auto art = hero->getArt(slot);
assert(art);
if(hero->tempOwner != LOCPLINT->playerID)
return false;
auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId());
if(!assemblyPossibilities.empty())
{
auto askThread = new boost::thread([hero, art, slot, assemblyPossibilities]() -> void
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
for(const auto combinedArt : assemblyPossibilities)
{
bool assembleConfirmed = false;
CFunctionList<void()> onYesHandlers([&assembleConfirmed]() -> void {assembleConfirmed = true; });
onYesHandlers += std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combinedArt->getId());
LOCPLINT->showArtifactAssemblyDialog(art->artType, combinedArt, onYesHandlers);
LOCPLINT->waitWhileDialog();
if(assembleConfirmed)
break;
}
});
askThread->detach();
return true;
}
return false;
}
bool ArtifactUtilsClient::askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
{
assert(hero);
const auto art = hero->getArt(slot);
assert(art);
if(hero->tempOwner != LOCPLINT->playerID)
return false;
if(art->isCombined())
{
if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1))
return false;
LOCPLINT->showArtifactAssemblyDialog(
art->artType,
nullptr,
std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, false, ArtifactID()));
return true;
}
return false;
}

View File

@ -59,9 +59,3 @@ public:
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
};
namespace ArtifactUtilsClient
{
bool askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
bool askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
}

View File

@ -176,9 +176,9 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
initAOHbackpack(requiredSlots, false);
auto artPlace = backpack.begin();
for(auto & art : filteredArts)
setSlotData(*artPlace++, curHero->getSlotByInstance(art.second));
setSlotData(*artPlace++, curHero->getArtPos(art.second));
for(auto & art : filteredScrolls)
setSlotData(*artPlace++, curHero->getSlotByInstance(art.second));
setSlotData(*artPlace++, curHero->getArtPos(art.second));
}
}

View File

@ -146,7 +146,7 @@ void CAltarArtifacts::updateAltarSlots()
for(auto & tradeSlot : tradeSlotsMapNewArts)
{
assert(tradeSlot.first->id == -1);
assert(altarArtifacts->getSlotByInstance(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
assert(altarArtifacts->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
tradeSlot.first->setID(tradeSlot.second->getTypeId().num);
tradeSlot.first->subtitle->setText(std::to_string(calcExpCost(tradeSlot.second->getTypeId())));
}
@ -221,7 +221,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
else if(altarSlot->id != -1)
{
assert(tradeSlotsMap.at(altarSlot));
const auto slot = altarArtifacts->getSlotByInstance(tradeSlotsMap.at(altarSlot));
const auto slot = altarArtifacts->getArtPos(tradeSlotsMap.at(altarSlot));
assert(slot != ArtifactPosition::PRE_FIRST);
LOCPLINT->cb->swapArtifacts(ArtifactLocation(heroArts->altarId, slot),
ArtifactLocation(hero->id, GH.isKeyboardCtrlDown() ? ArtifactPosition::FIRST_AVAILABLE : ArtifactPosition::TRANSITION_POS));

View File

@ -43,8 +43,8 @@
#include "../../lib/rmg/CMapGenOptions.h"
#include "../../lib/Languages.h"
CMapOverview::CMapOverview(std::string mapName, std::string fileName, std::string date, ResourcePath resource, ESelectionScreen tabType)
: CWindowObject(BORDERED | RCLICK_POPUP), resource(resource), mapName(mapName), fileName(fileName), date(date), tabType(tabType)
CMapOverview::CMapOverview(std::string mapName, std::string fileName, std::string date, std::string author, std::string version, ResourcePath resource, ESelectionScreen tabType)
: CWindowObject(BORDERED | RCLICK_POPUP), resource(resource), mapName(mapName), fileName(fileName), date(date), author(author), version(version), tabType(tabType)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@ -204,6 +204,14 @@ CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent):
else
w->setText(p.date);
}
if(auto w = widget<CLabel>("author"))
{
w->setText(p.author.empty() ? "-" : p.author);
}
if(auto w = widget<CLabel>("version"))
{
w->setText(p.version);
}
if(auto w = widget<CLabel>("noUnderground"))
{
if(minimaps.size() == 0)

View File

@ -53,7 +53,9 @@ public:
const std::string mapName;
const std::string fileName;
const std::string date;
const std::string author;
const std::string version;
const ESelectionScreen tabType;
CMapOverview(std::string mapName, std::string fileName, std::string date, ResourcePath resource, ESelectionScreen tabType);
CMapOverview(std::string mapName, std::string fileName, std::string date, std::string author, std::string version, ResourcePath resource, ESelectionScreen tabType);
};

View File

@ -117,9 +117,9 @@ void CWindowWithArtifacts::showArtifactAssembling(const CArtifactsOfHeroBase & a
{
if(artsInst.getArt(artPlace.slot))
{
if(ArtifactUtilsClient::askToDisassemble(artsInst.getHero(), artPlace.slot))
if(LOCPLINT->artifactController->askToDisassemble(artsInst.getHero(), artPlace.slot))
return;
if(ArtifactUtilsClient::askToAssemble(artsInst.getHero(), artPlace.slot))
if(LOCPLINT->artifactController->askToAssemble(artsInst.getHero(), artPlace.slot))
return;
if(artPlace.text.size())
artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
@ -166,14 +166,13 @@ void CWindowWithArtifacts::enableKeyboardShortcuts() const
artSet->enableKeyboardShortcuts();
}
void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc)
{
update();
}
void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc)
void CWindowWithArtifacts::update()
{
for(const auto & artSet : artSets)
{
artSet->updateWornSlots();
artSet->updateBackpackSlots();
if(const auto pickedArtInst = getPickedArtifact())
{
markPossibleSlots();
@ -184,30 +183,12 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const
artSet->unmarkSlots();
CCS->curh->dragAndDropCursor(nullptr);
}
}
void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation & artLoc)
{
update();
}
void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation & artLoc)
{
markPossibleSlots();
update();
}
void CWindowWithArtifacts::update()
{
for(const auto & artSet : artSets)
{
artSet->updateWornSlots();
artSet->updateBackpackSlots();
// Make sure the status bar is updated so it does not display old text
if(auto artPlace = artSet->getArtPlace(GH.getCursorPosition()))
artPlace->hover(true);
}
redraw();
}
void CWindowWithArtifacts::markPossibleSlots() const

View File

@ -37,10 +37,6 @@ public:
void deactivate() override;
void enableKeyboardShortcuts() const;
virtual void artifactRemoved(const ArtifactLocation & artLoc);
virtual void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc);
virtual void artifactDisassembled(const ArtifactLocation & artLoc);
virtual void artifactAssembled(const ArtifactLocation & artLoc);
virtual void update();
protected:

View File

@ -669,15 +669,50 @@ CTavernWindow::HeroSelector::HeroSelector(std::map<HeroTypeID, CGHeroInstance*>
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos = Rect(0, 0, 16 * 48, (inviteableHeroes.size() / 16 + (inviteableHeroes.size() % 16 != 0)) * 32);
pos = Rect(
pos.x,
pos.y,
ELEM_PER_LINES * 48,
std::min((int)(inviteableHeroes.size() / ELEM_PER_LINES + (inviteableHeroes.size() % ELEM_PER_LINES != 0)), MAX_LINES) * 32
);
background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h));
if(inviteableHeroes.size() / ELEM_PER_LINES > MAX_LINES)
{
pos.w += 16;
slider = std::make_shared<CSlider>(Point(pos.w - 16, 0), pos.h, std::bind(&CTavernWindow::HeroSelector::sliderMove, this, _1), MAX_LINES, std::ceil((double)inviteableHeroes.size() / ELEM_PER_LINES), 0, Orientation::VERTICAL, CSlider::BROWN);
slider->setPanningStep(32);
slider->setScrollBounds(Rect(-pos.w + slider->pos.w, 0, pos.w, pos.h));
}
recreate();
center();
}
void CTavernWindow::HeroSelector::sliderMove(int slidPos)
{
if(!slider)
return; // ignore spurious call when slider is being created
recreate();
redraw();
}
void CTavernWindow::HeroSelector::recreate()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
int sliderLine = slider ? slider->getValue() : 0;
int x = 0;
int y = 0;
int y = -sliderLine;
portraits.clear();
portraitAreas.clear();
for(auto & h : inviteableHeroes)
{
portraits.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32));
portraitAreas.push_back(std::make_shared<LRClickableArea>(Rect(x * 48, y * 32, 48, 32), [this, h](){ close(); onChoose(inviteableHeroes[h.first]); }, [this, h](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(inviteableHeroes[h.first])); }));
if(y >= 0 && y <= MAX_LINES - 1)
{
portraits.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32));
portraitAreas.push_back(std::make_shared<LRClickableArea>(Rect(x * 48, y * 32, 48, 32), [this, h](){ close(); onChoose(inviteableHeroes[h.first]); }, [this, h](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(inviteableHeroes[h.first])); }));
}
if(x > 0 && x % 15 == 0)
{
@ -687,8 +722,6 @@ CTavernWindow::HeroSelector::HeroSelector(std::map<HeroTypeID, CGHeroInstance*>
else
x++;
}
center();
}
CShipyardWindow::CShipyardWindow(const TResources & cost, int state, BoatId boatType, const std::function<void()> & onBuy)

View File

@ -238,6 +238,10 @@ public:
{
public:
std::shared_ptr<CFilledTexture> background;
std::shared_ptr<CSlider> slider;
const int MAX_LINES = 18;
const int ELEM_PER_LINES = 16;
HeroSelector(std::map<HeroTypeID, CGHeroInstance*> InviteableHeroes, std::function<void(CGHeroInstance*)> OnChoose);
@ -247,6 +251,9 @@ public:
std::vector<std::shared_ptr<CAnimImage>> portraits;
std::vector<std::shared_ptr<LRClickableArea>> portraitAreas;
void recreate();
void sliderMove(int slidPos);
};
//recruitable heroes

View File

@ -7,7 +7,7 @@
"name": "background",
"type": "texture",
"image": "DIBOXBCK",
"rect": {"w": 428, "h": 379}
"rect": {"w": 428, "h": 429}
},
{
"type": "boxWithBackground",
@ -34,6 +34,15 @@
"text": "",
"position": {"x": 214, "y": 40}
},
{
"type": "label",
"name": "version",
"font": "small",
"alignment": "right",
"color": "green",
"text": "",
"position": {"x": 418, "y": 48}
},
{
"type": "boxWithBackground",
"rect": {"x": 5, "y": 55, "w": 418, "h": 20}
@ -115,12 +124,37 @@
"font": "medium",
"alignment": "center",
"color": "yellow",
"text": "vcmi.lobby.filepath",
"text": "vcmi.lobby.author",
"position": {"x": 214, "y": 314}
},
{
"type": "boxWithBackground",
"rect": {"x": 5, "y": 329, "w": 418, "h": 45}
"rect": {"x": 5, "y": 329, "w": 418, "h": 20}
},
{
"type": "label",
"name": "author",
"font": "small",
"alignment": "center",
"color": "white",
"text": "",
"position": {"x": 214, "y": 339}
},
{
"type": "boxWithBackground",
"rect": {"x": 5, "y": 354, "w": 418, "h": 20}
},
{
"type": "label",
"font": "medium",
"alignment": "center",
"color": "yellow",
"text": "vcmi.lobby.filepath",
"position": {"x": 214, "y": 364}
},
{
"type": "boxWithBackground",
"rect": {"x": 5, "y": 379, "w": 418, "h": 45}
},
{
"type": "textBox",
@ -129,7 +163,7 @@
"alignment": "center",
"color": "white",
"text": "",
"rect": {"x": 10, "y": 334, "w": 408, "h": 35}
"rect": {"x": 10, "y": 384, "w": 408, "h": 35}
}
],

View File

@ -1,3 +1,5 @@
# AI
There are two types of AI: adventure and battle.
**Adventure AIs** are responsible for moving heroes across the map and developing towns
@ -15,7 +17,7 @@ When you call unit->getAttack() it summarizes all these bonuses and returns the
One important class is HypotheticBattle. It is used to evaluate the effects of an action without changing the actual gamestate. It is a wrapper around CPlayerSpecificCallback or another HypotheticBattle so it can provide you data, Internally it has a set of modified unit states and intercepts some calls to underlying callback and returns these internal states instead. These states in turn are wrappers around original units and contain modified bonuses (CStackWithBonuses). So if you need to emulate an attack you can call hypotheticbattle.getforupdate() and it will return the CStackWithBonuses which you can safely change.
# BattleAI
## BattleAI
BattleAI's most important classes are the following:
@ -30,3 +32,68 @@ BattleAI's most important classes are the following:
- BattleEvaluator - is a top level logic layer which also adds spellcasts and movement to unreachable targets
BattleAI itself handles all the rest and issues actual commands
## Nullkiller AI
Adventure AI responsible for moving heroes on map, gathering things, developing town. Main idea is to gather all possible tasks on map, prioritize them and select the best one for each heroes. Initially was a fork of VCAI
### Parts
Gateway - a callback for server used to invoke AI actions when server thinks it is time to do something. Through this callback AI is informed about various events like hero level up, tile revialed, blocking dialogs and so on. In order to do this Gaateway implements specific interface. The interface is exactly the same for human and AI
Another important actor for server interaction is CCallback * cb. This one is used to retrieve gamestate information and ask server to do things like hero moving, spell casting and so on. Each AI has own instance of Gateway and it is a root object which holds all AI state. Gateway has an event method yourTurn which invokes makeTurn in another thread. The last passes control to Nullkiller engine.
Nullkiller engine - place where actual AI logic is organized. It contains a main loop for gathering and prioritizing things. Its algorithm:
* reset AI state, it avoids keeping some memory about the game in general to reduce amount of things serialized into savefile state. The only serialized things are in nullkiller->memory. This helps reducing save incompatibility. It should be mostly enough for AI to analyze data avaialble in CCallback
* main loop, loop iteration is called a pass
** update AI state, some state is lazy and updates once per day to avoid performance hit, some state is recalculated each loop iteration. At this stage analysers and pathfidner work
** gathering goals, prioritizing and decomposing them
** execute selected best goals
Analyzer - a module gathering data from CCallback *. Its goal to make some statistics and avoid making any significant decissions.
* HeroAnalyser - decides upong which hero suits better to be main (army carrier and fighter) and which is better to be a scout (gathering unguarded resources, exploring)
* BuildAnalyzer - prepares information on what we can build in our towns, and what resources we need to do this
* DangerHitMapAnalyser - checks if enemy hero can rich each tile, how fast and what is their army strangth
* Pathfinder - core thing used to calculate paths including bypassing monsters, quests, using advmap spells
* Graph - experimental thing connecting all objects into a network by paths (using common hero characteristics), does not work without maphack, allows simplified faster paths calculation. Possibly can be used for something else
* ArmyManager - for now only helps calculating best army from two CreatureSet objects. Later may be responsible for forming ideal army for each main hero so that we know what we need to build and buy.
* ObjectClusterizer - aggregates all objects into clusters depending on which object blocks way towards them.
* DeepDecomposer - sometimes pathfinder may return path through some object which canno be simply bypassed but instead it requires something to be done first. DeepDecomposer allows to detalizing such paths. Examples: building a boat requires capturing shipyard, bypassing bordergate requires visiting masterkey tent. See AbstractGoal
* FuzzyEngines - looks like some legacy from VCAI
* PriorityEvaluator - gathers information on task rewards, evaluates their priority using Fuzzy Light library (fuzzy logic)
### Goals
Units of activity in AI. Can be AbstractGoal, Task, Marker and Behavior
Task - simple thing which can be done right away in order to gain some reward. Or a composition of simple things in case if more than one action is needed to gain the reward.
* AdventureSpellCast - town portal, water walk, air walk, summon boat
* BuildBoat - builds a boat in a specific shipyard
* BuildThis - builds a building in a specified town
* BuyArmy - buys specific amount of army in AIValue in specific town
* DigAtTile - for grail, not implemented yet
* DismissHero - sometimes we may want to get rid of some scout
* ExchangeSwapTownHeroes - puts specifc hero in garrison or extracts hero from garrison. Also makes possible upgrades and buys army in town (used by defence and startup behaviors)
* ExecuteHeroChain - moves hero accross some path (or a few heroes forming a chain in order to move army to the target hero), can bypass simple obstacles like monsters, garrisons
* ExploreNeighborTile - after AI visits initial tile for exploration - makes a few sequential explorations of nearby tiles to save some performance
* RecruitHero - recruits specific hero in specifc town
* SaveResources - locks some resources for later use during next day
* StayAtTown - stay at town for the rest of the day (to regain mana)
Behavior - a core game activity
* CaptureObjectsBehavior - generally it is about visiting map objects which give reward. It can capture any object, even those which are behind monsters and so on. But due to performance considerations it is not allowed to handle monsters and quests now.
* ClusterBehavior - uses information of ObjectClusterizer to unblock objects hidden behind various blockers. It kills guards, completes quests, captures garrisons.
* BuildingBehavior - develops our towns
* BuyArmyBehavior - buys army in towns
* GatherArmyBehavior - picks army from towns and brings it to main hero by scout, or main itslef goes for it
* RecruitHeroBehavior - recruits hero it it is either stronger than any main or there is something to gather
* StartupBehavior - scripted behavior which helps a bit on the first day. It keeps main hero in town garrison and accumulates army from initial heroes bought in tavern
* StayAtTownBehavior - stay at town to gain mana from mage guild
* DefenceBehavior - defend towns by eliminating treatening heroes or hiding in town garrison
AbstractGoal - some goals can not be completed because it is not clear how to do this. They express desire to do something, not exact plan. DeepDecomposer is used to refine such goals until they are turned into such plan or discarded. Some examples:
* CaptureObject - you need to visit some object (flag a shipyard for instance) but do not know how
* CompleteQuest - you need to bypass bordergate or borderguard or questguard but do not know how
AbstractGoal usually comes in form of composition with some elementar task blocked by abstract objective. For instance CaptureObject(Shipyard), ExecuteHeroChain(visit x, build boat, visit enemy town). When such composition is decomposed it can turn into either a pair of herochains or into another abstract composition if path to shipyard is also blocked with something.
Sometimes such decomposition may form a loop of abstract goals and will be discarded in such case. Generally the current architecture attempts to avoid decomposition as quite a heavy operation.
Composition - a goal which can be both elementar (a set of tasks) or abstract (contains unresolved abstract goal at the end). Compositions express a chain of tasks in order to achieve some reward. They consist of sequences. Each sequence is a vector of goals. Only last sequence is actually executed or decomposed. All the rest adds value to reward evaluator.
Marker - a goal used to just add value (reward) into some composition. We want to capture some shipyard not just because but in order to capture a town (or something else) later. Thus when we are capturing a shipyard we should know that later we will unlock town so we contribute towards town reward as well.

View File

@ -1,3 +1,5 @@
# Bonus System
The bonus system of VCMI is a set of mechanisms that make handling of different bonuses for heroes, towns, players and units easier. The system consists of a set of nodes representing objects that can be a source or a subject of a bonus and two directed acyclic graphs (DAGs) representing inheritance and propagation of bonuses. Core of bonus system is defined in HeroBonus.h file.
## Propagation and inheritance

View File

@ -1,3 +1,5 @@
# Building Android
The following instructions apply to **v1.2 and later**. For earlier versions the best documentation is https://github.com/vcmi/vcmi-android/blob/master/building.txt (and reading scripts in that repo), however very limited to no support will be provided from our side if you wish to go down that rabbit hole.
*Note*: building has been tested only on Linux and macOS. It may or may not work on Windows out of the box.

View File

@ -5,10 +5,10 @@
Older distributions and compilers might work, but they aren't tested by Github CI (Actions)
# Prerequisites
## Installing dependencies
### Prerequisites
To compile, the following packages (and their development counterparts) are needed to build:
- CMake
@ -45,7 +45,7 @@ It can be found at https://aur.archlinux.org/packages/vcmi-git/
Information about building packages from the Arch User Repository (AUR) can be found at the Arch wiki.
# Getting the sources
## Getting the sources
We recommend the following directory structure:
@ -59,9 +59,9 @@ You can get the latest source code with:
`git clone -b develop --recursive https://github.com/vcmi/vcmi.git`
# Compilation
## Compilation
## Configuring Makefiles
### Configuring Makefiles
```sh
mkdir build
@ -74,8 +74,7 @@ cmake -S ../vcmi
See [CMake](CMake.md) for a list of options
## Building
### Trigger build
```
cmake --build . -j8
@ -85,9 +84,9 @@ cmake --build . -j8
This will generate `vcmiclient`, `vcmiserver`, `vcmilauncher` as well as .so libraries in the `build/bin/` directory.
# Packaging
## Packaging
## RPM package
### RPM package
The first step is to prepare a RPM build environment. On Fedora systems you can follow this guide: http://fedoraproject.org/wiki/How_to_create_an_RPM_package#SPEC_file_overview
@ -137,7 +136,7 @@ For other distributions that uses RPM, chances are there might be a spec file fo
Available root environments and their names are listed in /etc/mock.
## Debian/Ubuntu
### Debian/Ubuntu
1. Install debhelper and devscripts packages

View File

@ -1,9 +1,10 @@
# Building VCMI for Windows
# Preparations
## Preparations
Windows builds can be made in more than one way and with more than one tool. This guide focuses on the simplest building process using Microsoft Visual Studio 2022
# Prerequisites
## Prerequisites
- Windows Vista or newer.
- [Microsoft Visual Studio](https://visualstudio.microsoft.com/downloads/)
@ -14,7 +15,7 @@ Windows builds can be made in more than one way and with more than one tool. Thi
- To create installer: [NSIS](http://nsis.sourceforge.net/Main_Page)
- To speed up recompilation: [CCache](https://github.com/ccache/ccache/releases)
## Choose an installation directory
### Choose an installation directory
Create a directory for VCMI development, eg. `C:\VCMI` We will call this directory `%VCMI_DIR%`
@ -29,13 +30,13 @@ Bad locations:
- `C:\Users\Michał\VCMI (non-ascii character)`
- `C:\Program Files (x86)\VCMI (write protection)`
# Install VCMI dependencies
## Install VCMI dependencies
You have two options: to use pre-built libraries or build your own. We strongly recommend start with using pre-built ones.
## Option A. Use pre-built Vcpkg
### Option A. Use pre-built Vcpkg
### Download and unpack archive
#### Download and unpack archive
Vcpkg Archives are available at our GitHub: https://github.com/vcmi/vcmi-deps-windows/releases
@ -43,18 +44,18 @@ Vcpkg Archives are available at our GitHub: https://github.com/vcmi/vcmi-deps-wi
EG: v1.6 assets - [vcpkg-export-x64-windows-v143.7z](https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-x64-windows-v143.7z)
- Extract archive by right clicking on it and choosing "7-zip -> Extract Here".
### Move dependencies to target directory
#### Move dependencies to target directory
Once extracted, a `vcpkg` directory will appear with `installed` and `scripts` subfolders inside.
Move extracted `vcpkg` directory into your `%VCMI_DIR%`
## Option B. Build Vcpkg on your own
### Option B. Build Vcpkg on your own
Please be aware that if you're running 32-bit Windows version, then this is impossible due to <https://github.com/microsoft/vcpkg/issues/26036>
Be aware that building Vcpkg might take a lot of time depend on your CPU model and 10-20GB of disk space.
### Create initial directory
#### Create initial directory
### Clone vcpkg
#### Clone vcpkg
1. open SourceTree
2. File -\> Clone
@ -66,7 +67,7 @@ From command line use:
git clone https://github.com/microsoft/vcpkg.git %VCMI_DIR%/vcpkg
### Build vcpkg and dependencies
#### Build vcpkg and dependencies
- Run
`%VCMI_DIR%/vcpkg/bootstrap-vcpkg.bat`
@ -77,11 +78,11 @@ From command line use:
For the list of the packages used you can also consult [vcmi-deps-windows readme](https://github.com/vcmi/vcmi-deps-windows) in case this article gets outdated a bit.
# Install CCache
## Install CCache
Extract `ccache` to a folder of your choosing, add the folder to the `PATH` environment variable and log out and back in.
# Build VCMI
## Build VCMI
#### From GIT GUI
- Open SourceTree
@ -95,13 +96,13 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi
#### From command line
- `git clone --recursive https://github.com/vcmi/vcmi.git %VCMI_DIR%/source`
## Generate solution for VCMI
### Generate solution for VCMI
- Create `%VCMI_DIR%/build` folder
- Open a command line prompt at `%VCMI_DIR%/build`
- Execute `cd %VCMI_DIR%/build`
- Create solution (Visual Studio 2022 64-bit) `cmake %VCMI_DIR%/source -DCMAKE_TOOLCHAIN_FILE=%VCMI_DIR%/vcpkg/scripts/buildsystems/vcpkg.cmake -G "Visual Studio 17 2022" -A x64`
## Compile VCMI with Visual Studio
### Compile VCMI with Visual Studio
- Open `%VCMI_DIR%/build/VCMI.sln` in Visual Studio
- Select `Release` build type in the combobox
- If you want to use ccache:
@ -111,7 +112,7 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi
- Right click on `BUILD_ALL` project. This `BUILD_ALL` project should be in `CMakePredefinedTargets` tree in Solution Explorer.
- VCMI will be built in `%VCMI_DIR%/build/bin` folder!
## Compile VCMI with MinGW via MSYS2
### Compile VCMI with MinGW via MSYS2
- Install MSYS2 from https://www.msys2.org/
- Start the `MSYS MinGW x64`-shell
- Install dependencies: `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-boost mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-static`
@ -119,7 +120,7 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi
**NOTE:** This will link Qt5 statically to `VCMI_launcher.exe` and `VCMI_Mapeditor.exe`. See [PR #3421](https://github.com/vcmi/vcmi/pull/3421) for some background.
# Create VCMI installer (This step is not required for just building & development)
## Create VCMI installer (This step is not required for just building & development)
Make sure NSIS is installed to default directory or have registry entry so CMake can find it.
After you build VCMI execute following commands from `%VCMI_DIR%/build`.
@ -127,7 +128,7 @@ After you build VCMI execute following commands from `%VCMI_DIR%/build`.
- for release build: `cpack`
- for debug build: `cpack -C Debug`
# Troubleshooting and workarounds
## Troubleshooting and workarounds
Vcpkg might be very unstable due to limited popularity and fact of using bleeding edge packages (such as most recent Boost). Using latest version of dependencies could also expose both problems in VCMI code or library interface changes that developers not checked yet. So if you're built Vcpkg yourself and can't get it working please try to use binary package.

View File

@ -1,6 +1,8 @@
# Code Structure
The code of VCMI is divided into several main parts: client, server, lib and AIs, each one in a separate binary file.
# The big picture
## The big picture
VCMI contains three core projects: VCMI_lib (dll / so), VCMI_client (executable) and VCMI_server (executable). Server handles all game mechanics and events. Client presents game state and events to player and collects input from him.
@ -8,37 +10,37 @@ During the game, we have one (and only one) server and one or more (one for each
Important: State of the game and its mechanics are synchronized between clients and server. All changes to the game state or mechanics must be done by server which will send appropriate notices to clients.
## Game state
### Game state
It's basically CGameState class object and everything that's accessible from it: map (with objects), player statuses, game options, etc.
## Bonus system
### Bonus system
One of the more important pieces of VCMI is the [bonus system](Bonus_System.md). It's described in a separate article.
## Configuration
### Configuration
Most of VCMI configuration files uses Json format and located in "config" directory
### Json parser and writer
#### Json parser and writer
# Client
## Client
## Main purposes of client
### Main purposes of client
Client is responsible for:
- displaying state of game to human player
- capturing player's actions and sending requests to server
- displaying changes in state of game indicated by server
## Rendering of graphics
### Rendering of graphics
Rendering of graphics relies heavily on SDL. Currently we do not have any wrapper for SDL internal structures and most of rendering is about blitting surfaces using SDL_BlitSurface. We have a few function that make rendering easier or make specific parts of rendering (like printing text). They are places in client/SDL_Extensions and client/SDL_Framerate (the second one contains code responsible for keeping appropriate framerate, it should work more smart than just SDL_Delay(milliseconds)).
In rendering, Interface object system is quite helpful. Its base is CIntObject class that is basically a base class for our library of GUI components and other objects.
# Server
## Server
## Main purposes of server
### Main purposes of server
Server is responsible for:
@ -47,9 +49,9 @@ Server is responsible for:
- informing all clients about changes in state of the game that are
visible to them
# Lib
## Lib
## Main purposes of lib
### Main purposes of lib
VCMI_Lib is a library that contains code common to server and client, so we avoid it's duplication. Important: the library code is common for client and server and used by them, but the library instance (in opposition to the library as file) is not shared by them! Both client and server create their own "copies" of lib with all its class instances.
@ -63,16 +65,16 @@ Lib contains code responsible for:
- handling general game mechanics and related actions (only adventure map objects; it's an unwanted remnant of past development - all game mechanics should be handled by the server)
- networking and serialization
### Serialization
#### Serialization
The serialization framework can serialize basic types, several standard containers among smart pointers and custom objects. Its design is based on the [boost serialization libraries](http://www.boost.org/doc/libs/1_52_0/libs/serialization/doc/index.html).
In addition to the basic functionality it provides light-weight transfer of CGObjectInstance objects by sending only the index/id.
Serialization page for all the details.
### Wrapped namespace examples
#### Wrapped namespace examples
#### Inside the lib
##### Inside the lib
Both header and implementation of a new class inside the lib should have the following structure:
@ -81,7 +83,7 @@ Both header and implementation of a new class inside the lib should have the fol
`<code>`
`VCMI_LIB_NAMESPACE_END`
#### Headers outside the lib
##### Headers outside the lib
Forward declarations of the lib in headers of other parts of the project need to be wrapped in the macros:
@ -93,30 +95,30 @@ Forward declarations of the lib in headers of other parts of the project need to
`<classes>`
#### New project part
##### New project part
If you're creating new project part, place `VCMI_LIB_USING_NAMESPACE` in its `StdInc.h` to be able to use lib classes without explicit namespace in implementation files. Example: <https://github.com/vcmi/vcmi/blob/develop/launcher/StdInc.h>
# Artificial Intelligence
## Artificial Intelligence
## StupidAI
### StupidAI
Stupid AI is recent and used battle AI.
## Adventure AI
### Adventure AI
VCAI module is currently developed agent-based system driven by goals and heroes.
## Programming challenge
### Programming challenge
## Fuzzy logic
### Fuzzy logic
VCMI includes [FuzzyLite](http://code.google.com/p/fuzzy-lite/) library to make use of fuzzy rule-based algorithms. They are useful to handle uncertainty and resemble human behaviour who takes decisions based on rough observations. FuzzyLite is linked as separate static library in AI/FuzzyLite.lib file.
# Utilities
## Utilities
## Launcher
### Launcher
## Duels
### Duels
## ERM parser
### ERM parser

View File

@ -1,3 +1,5 @@
# Coding Guidelines
## C++ Standard
VCMI implementation bases on C++17 standard. Any feature is acceptable as long as it's will pass build on our CI, but there is list below on what is already being used.

View File

@ -1,4 +1,4 @@
# Using dependencies from Conan
# Conan Dependencies
[Conan](https://conan.io/) is a package manager for C/C++. We provide prebuilt binary dependencies for some platforms that are used by our CI, but they can also be consumed by users to build VCMI. However, it's not required to use only the prebuilt binaries: you can build them from source as well.

View File

@ -1,4 +1,4 @@
# Introduction to Qt Creator
# Development with Qt Creator
Qt Creator is the recommended IDE for VCMI development on Linux distributions, but it may be used on other operating systems as well. It has the following advantages compared to other IDEs:

View File

@ -1,4 +1,6 @@
# Features
# Logging API
## Features
- A logger belongs to a "domain", this enables us to change log level settings more selectively
- The log format can be customized
@ -9,7 +11,7 @@
- Macros for tracing the application flow
- Provides stream-like and function-like logging
# Class diagram
## Class diagram
![Logging_Class_Diagram](https://github.com/IvanSavenko/vcmi/assets/1576820/31c9b14e-a055-4b07-87fe-00da43430a2b)
@ -18,9 +20,9 @@ Some notes:
- There are two methods `configure` and `configureDefault` of the class `CBasicLogConfigurator` to initialize and setup the logging system. The latter one setups default logging and isn't dependent on VCMI's filesystem, whereas the first one setups logging based on the user's settings which can be configured in the settings.json.
- The methods `isDebugEnabled` and `isTraceEnabled` return true if a log record of level debug respectively trace will be logged. This can be useful if composing the log message is a expensive task and performance is important.
# Usage
## Usage
## Setup settings.json
### Setup settings.json
``` javascript
{
@ -51,7 +53,7 @@ Some notes:
The above code is an example on how to configure logging. It sets the log level to debug globally and the log level of the domain ai to trace. In addition, it tells the console to log debug messages as well with the threshold attribute. Finally, it configures the console so that it logs network trace messages in magenta.
## Configuration
### Configuration
The following code shows how the logging system can be configured:
@ -82,14 +84,14 @@ Format: %d %l %n \[%t\] - %m
global -\> info
## How to get a logger
### How to get a logger
There exist only one logger object per domain. A logger object cannot be copied. You can get access to a logger object by using the globally defined ones like `logGlobal` or `logAi`, etc... or by getting one manually:
```cpp
Logger * logger = CLogger::getLogger(CLoggerDomain("rmg"));
```
## Logging
### Logging
Logging can be done via two ways, stream-like or function-like.
@ -119,7 +121,7 @@ The following colors are available for console output:
- gray
- teal
## How to trace execution
### How to trace execution
The program execution can be traced by using the macros TRACE_BEGIN, TRACE_END and their \_PARAMS counterparts. This can be important if you want to analyze the operations/internal workings of the AI or the communication of the client-server. In addition, it can help you to find bugs on a foreign VCMI installation with a custom mod configuration.
@ -135,9 +137,9 @@ The program execution can be traced by using the macros TRACE_BEGIN, TRACE_END a
}
```
# Concepts
## Concepts
## Domain
### Domain
A domain is a specific part of the software. In VCMI there exist several domains:

View File

@ -1,4 +1,6 @@
# Configuration
# Lua Scripting System
## Configuration
``` javascript
{
@ -32,13 +34,13 @@
}
```
# Lua
## Lua
## API Reference
### API Reference
TODO **In near future Lua API may change drastically several times. Information here may be outdated**
### Globals
#### Globals
- DATA - persistent table
- - DATA.ERM contains ERM state, anything else is free to use.
@ -61,7 +63,7 @@ TODO **In near future Lua API may change drastically several times. Information
- -TODO require(":relative.path.to.module") - loads module from same mod
- logError(text) - backup error log function
### Low level events API
#### Low level events API
``` Lua
@ -79,7 +81,7 @@ sub2 = PlayerGotTurn.subscribeBefore(EVENT_BUS, function(event)
end)
```
### Lua standard library
#### Lua standard library
VCMI uses LuaJIT, which is Lua 5.1 API, see [upstream documentation](https://www.lua.org/manual/5.1/manual.html)
@ -91,19 +93,19 @@ Following libraries are supported
- math
- bit
# ERM
## ERM
## Features
### Features
- no strict limit on function/variable numbers (technical limit 32 bit integer except 0))
- TODO semi compare
- DONE macros
## Bugs
### Bugs
- TODO Broken XOR support (clashes with \`X\` option)
## Triggers
### Triggers
- TODO **!?AE** Equip/Unequip artifact
- WIP **!?BA** when any battle occurs
@ -134,22 +136,22 @@ Following libraries are supported
- TODO **!?TL** Real-Time Timer
- TODO **!?TM** timed events
## Receivers
### Receivers
### VCMI
#### VCMI
- **!!MC:S@varName@** - declare new "normal" variable (technically
v-var with string key)
- TODO Identifier resolver
- WIP Bonus system
### ERA
#### ERA
- DONE !!if !!el !!en
- TODO !!br !!co
- TODO !!SN:X
### WoG
#### WoG
- TODO !!AR Артефакт (ресурс) в определенной позиции
- TODO !!BA Битва
@ -199,4 +201,4 @@ Following libraries are supported
- *!#VC Контроль переменных*
- WIP !!VR Установка переменных
## Persistence
### Persistence

View File

@ -1,4 +1,6 @@
# The big picture
# Networking
## The big picture
For implementation details see files located at `lib/network` directory.
@ -19,11 +21,11 @@ Following connections can be established during game lifetime:
- game client -> lobby server: This connection is used to access global lobby, for multiplayer over Internet. Created when player logs into a lobby (Multiplayer -> Connect to global service)
- match server -> lobby server: This connection is established when player creates new multiplayer room via lobby. It is used by lobby server to send commands to match server
# Gameplay communication
## Gameplay communication
For gameplay, VCMI serializes data into a binary stream. See [Serialization](Serialization.md) for more information.
# Global lobby communication
## Global lobby communication
For implementation details see:
- game client: `client/globalLobby/GlobalLobbyClient.h
@ -38,29 +40,29 @@ char jsonString[messageSize];
Every message must be a struct (json object) that contains "type" field. Unlike rest of VCMI codebase, this message is validated as strict json, without any extensions, such as comments.
## Communication flow
### Communication flow
Notes:
- invalid message, such as corrupted json format or failure to validate message will result in no reply from server
- in addition to specified messages, match server will send `operationFailed` message on failure to apply player request
### New Account Creation
#### New Account Creation
- client -> lobby: `clientRegister`
- lobby -> client: `accountCreated`
### Login
#### Login
- client -> lobby: `clientLogin`
- lobby -> client: `loginSuccess`
- lobby -> client: `chatHistory`
- lobby -> client: `activeAccounts`
- lobby -> client: `activeGameRooms`
### Chat Message
#### Chat Message
- client -> lobby: `sendChatMessage`
- lobby -> every client: `chatMessage`
### New Game Room
#### New Game Room
- client starts match server instance
- match -> lobby: `serverLogin`
- lobby -> match: `loginSuccess`
@ -70,24 +72,24 @@ Notes:
- lobby -> every client: `activeAccounts`
- lobby -> every client: `activeGameRooms`
### Joining a game room
#### Joining a game room
See [#Proxy mode](proxy-mode)
### Leaving a game room
#### Leaving a game room
- client closes connection to match server
- match -> lobby: `leaveGameRoom`
### Sending an invite:
#### Sending an invite:
- client -> lobby: `sendInvite`
- lobby -> target client: `inviteReceived`
Note: there is no dedicated procedure to accept an invite. Instead, invited player will use same flow as when joining public game room
### Logout
#### Logout
- client closes connection
- lobby -> every client: `activeAccounts`
## Proxy mode
### Proxy mode
In order to connect players located behind NAT, VCMI lobby can operate in "proxy" mode. In this mode, connection will be act as proxy and will transmit gameplay data from client to a match server, without any data processing on lobby server.

View File

@ -1,46 +1,48 @@
# Fundamentals
# RMG Description
## Fundamentals
Random maps are represented by undirected graph of zones linked with connections.
On maps with water, a single extra water zone is created.
## Modifiers
### Modifiers
Zone filling process is split into multiple phases, each of them represented as a modifier. A modifier can require other modifiers to finish their job before launching. A modifier might be preceded by other modifier from every zone, or many modifiers from all zones. For instance, placing underground rock requires all underground zones to finish treasure placement first.
## Thread pool
### Thread pool
A queue of Modifiers jobs is created in roughly topological order, so that Modificators with no dependencies are placed first. The queue is iterated in a circular manner and if Modificator with no remaining preceders is found, it is picked for execution in a separate thread. After job completion, Modifier is erased from dependencies of Modifiers which depend on it.
# Placing Zones
## Placing Zones
## Generating distance graph
### Generating distance graph
Based on zone connections, a simple distance graph is created using Dijkstra algorithm.
## Initial zone placement
### Initial zone placement
Based on distance graph, zones are placed one by one on N x N grid of size just enough to fit all the zones (for instance, 5 zones are placed on a 3 x 3 grid and 24 zones on 5 x 5 grid). Adjacent zones are placed close while distant zones are placed as far away from each other as possible.
## Iterative optimization
### Iterative optimization
Finally, zones are moved from their initial positions using Fruchterman-Reingold algorithm. It assumes all the zones are soft spheres, which attract connected zones as springs but push back not overlapping zones crossing their borders. These forces are summend and determine the vector shift of the zone position. The algorithm uses classic "simulated annealing" approach - zones start with high "temperature" (are very soft and squishy) and then gradually become colder (harder) and push away overlapping zones with stronger force.
To prevent getting stuck in local minima, sometimes most misplaced zones swap placed manually.
## Penrose Tiling
### Penrose Tiling
Using iterative subdivision, a set of Penrose tiling vertices ic created at random orientation and centered over middle of the map. All the tiles on a map are assigned to the closes vertex. Then, every vertex is assigned to the closest zone, creating irregular shapes.
# Zone connections
## Zone connections
Directly adjacent zones are connected with a guard and a road, and overlapping zones on different levels are connected with Subterranean Gate. Zones which are not directly adjacent might be covered through water zone. If a zone shouldn't be connected with nearby zones adjacent to body of water, it's coast is sealed with obstacles.
## Water routes
### Water routes
**TODO**
## Fractalization
### Fractalization
Every zone starts with at least one free tile in the center. Now, from every tiles at a distant greater than some number, a random tile is chosen. From it, algorithm routes a free path connected to already existing free paths. Process is repetaed until no tiles distant from free paths are left. Tiles used for connections are marked `free` and nothing else can be placed on them.
@ -48,28 +50,28 @@ Zones with type `junction` are not fractalized.
The remaining tiles that are not obstacles (such as zone edges) are marked as `possible"` so they can be either filled with treasures or left `free.`
# Treasures
## Treasures
Every object or treasure pile in the zone is placed as far away from existing objects as possible. This includes towns and zone guards placed first. Zone keeps a priority queue of tiles sorted by their distance to closest object. Whenever an object is placed, these distances are updated.
## Treasure generation
### Treasure generation
Treasures are separated in value ranges. The highest range is picked first. A large number of treasure piles is generated, then RMG tries to fit each of treasure piles into a zone. Then, lower treasure ranges are generated
## Treasure placement
### Treasure placement
New treasure pile can't be closer to any previous object than some distance determined by total treasure density and value. Lower value piles and placed with lower minimal distance.
Any new treasure is placed so that it can't join two previously separated blocked islands, to prevent sealing a gap and ensuring entire zone is passable.
# Obstacles
## Obstacles
After all the treasures are placed, tiles marked as `possible` are iteratively stripped and cleared from lose appendages, leaving `free` space. Then remaining ones are marked as `blocked`, and covered with obstacles.
## Biomes
### Biomes
For every zone, a few random obstacle sets are selected. [Details](https://github.com/vcmi/vcmi/blob/develop/docs/modders/Entities_Format/Biome_Format.md)
## Filling space
### Filling space
Tiles which need to be `blocked` but are not `used` are filled with obstacles. Largest obstacles which cover the most tiles are picked first, other than that they are chosen randomly.

View File

@ -1,24 +1,26 @@
# Introduction
# Serialization
## Introduction
The serializer translates between objects living in our code (like int or CGameState\*) and stream of bytes. Having objects represented as a stream of bytes is useful. Such bytes can send through the network connection (so client and server can communicate) or written to the disk (savegames).
VCMI uses binary format. The primitive types are simply copied from memory, more complex structures are represented as a sequence of primitives.
## Typical tasks
### Typical tasks
### Bumping a version number
#### Bumping a version number
Different major version of VCMI likely change the format of the save game. Every save game needs a version identifier, that loading can work properly. Backward compatibility isn't supported for now. The version identifier is a constant named version in Connection.h and should be updated every major VCMI version or development version if the format has been changed. Do not change this constant if it's not required as it leads to full rebuilds. Why should the version be updated? If VCMI cannot detect "invalid" save games the program behaviour is random and undefined. It mostly results in a crash. The reason can be anything from null pointer exceptions, index out of bounds exceptions(ok, they aren't available in c++, but you know what I mean:) or invalid objects loading(too much elements in a vector, etc...) This should be avoided at least for public VCMI releases.
### Adding a new class
#### Adding a new class
If you want your class to be serializable (eg. being storable in a savegame) you need to define a serialize method template, as described in [#User types](#user-types)
Additionally, if your class is part of one of registered object hierarchies (basically: if it derives from CGObjectInstance, IPropagator, ILimiter, CBonusSystemNode, CPack) it needs to be registered. Just add an appropriate entry in the `RegisterTypes.h` file. See polymorphic serialization for more information.
# How does it work
## How does it work
## Primitive types
### Primitive types
They are simply stored in a binary form, as in memory. Compatibility is ensued through the following means:
@ -27,21 +29,21 @@ They are simply stored in a binary form, as in memory. Compatibility is ensued t
It's not "really" portable, yet it works properly across all platforms we currently support.
## Dependant types
### Dependant types
### Pointers
#### Pointers
Storing pointers mechanics can be and almost always is customized. See [#Additional features](additional-features).
In the most basic form storing pointer simply sends the object state and loading pointer allocates an object (using "new" operator) and fills its state with the stored data.
### Arrays
#### Arrays
Serializing array is simply serializing all its elements.
## Standard library types
### Standard library types
### STL Containers
#### STL Containers
First the container size is stored, then every single contained element.
@ -56,7 +58,7 @@ Supported STL types include:
`pair`
`map`
### Smart pointers
#### Smart pointers
Smart pointers at the moment are treated as the raw C-style pointers. This is very bad and dangerous for shared_ptr and is expected to be fixed somewhen in the future.
@ -65,14 +67,14 @@ The list of supported data types from standard library:
`shared_ptr (partial!!!)`
`unique_ptr`
### Boost
#### Boost
Additionally, a few types for Boost are supported as well:
`variant`
`optional`
## User types
### User types
To make the user-defined type serializable, it has to provide a template method serialize. The first argument (typed as template parameter) is a reference to serializer. The second one is version number.
@ -98,7 +100,7 @@ struct DLL_LINKAGE Rumor
};
```
## Backwards compatibility
### Backwards compatibility
Serializer, before sending any data, stores its version number. It is passed as the parameter to the serialize method, so conditional code ensuring backwards compatibility can be added.
@ -126,21 +128,21 @@ struct DLL_LINKAGE Rumor
};
```
## Serializer classes
### Serializer classes
### Common information
#### Common information
Serializer classes provide iostream-like interface with operator `<<` for serialization and operator `>>` for deserialization. Serializer upon creation will retrieve/store some metadata (version number, endianness), so even if no object is actually serialized, some data will be passed.
### Serialization to file
#### Serialization to file
CLoadFile/CSaveFile classes allow to read data to file and store data to file. They take filename as the first parameter in constructor and, optionally, the minimum supported version number (default to the current version). If the construction fails (no file or wrong file) the exception is thrown.
### Networking
#### Networking
See [Networking](Networking.md)
## Additional features
### Additional features
Here is the list of additional custom features serialzier provides. Most of them can be turned on and off.
@ -149,7 +151,7 @@ Here is the list of additional custom features serialzier provides. Most of them
- Stack instance serialization — enabled by sendStackInstanceByIds flag.
- Smart pointer serialization — enabled by smartPointerSerialization flag.
### Polymorphic serialization
#### Polymorphic serialization
Serializer is to recognize the true type of object under the pointer if classes of that hierarchy were previously registered.
@ -170,7 +172,7 @@ Class hierarchies that are now registered to benefit from this feature are mostl
It is crucial that classes are registered in the same order in the both serializers (storing and loading).
### Vectorized list member serialization
#### Vectorized list member serialization
Both client and server store their own copies of game state and VLC (handlers with data from config). Many game logic objects are stored in the vectors and possess a unique id number that represent also their position in such vector.
@ -219,7 +221,7 @@ Important: this means that the object state is not serialized.
This feature makes sense only for server-client network communication.
### Stack instance serialization
#### Stack instance serialization
This feature works very much like the vectorised object serialization. It is like its special case for stack instances that are not vectorised (each hero owns its map). When this option is turned on, sending CStackInstance\* will actually send an owning object (town, hero, garrison, etc) id and the stack slot position.
@ -227,7 +229,7 @@ For this to work, obviously, both sides of the connection need to have exactly t
This feature depends on vectorised member serialization being turned on. (Sending owning object by id.)
### Smart pointer serialization
#### Smart pointer serialization
Note: name is unfortunate, this feature is not about smart pointers (like shared-ptr and unique_ptr). It is for raw C-style pointers, that happen to point to the same object.

View File

@ -1,3 +1,5 @@
# Release Process
## Versioning
For releases VCMI uses version numbering in form "1.X.Y", where:
- 'X' indicates major release. Different major versions are generally not compatible with each other. Save format is different, network protocol is different, mod format likely different.

View File

@ -1,3 +1,5 @@
# Ubuntu PPA
## Main links
- [Team](https://launchpad.net/~vcmi)
- [Project](https://launchpad.net/vcmi)

View File

@ -1,3 +1,5 @@
# Animation Format
VCMI allows overriding HoMM3 .def files with .json replacement. Compared to .def this format allows:
- Overriding individual frames from json file (e.g. icons)

View File

@ -1,3 +1,5 @@
# Bonus Duration Types
Bonus may have any of these durations. They acts in disjunction.
## List of all bonus duration types

View File

@ -1,3 +1,5 @@
# Bonus Limiters
## Predefined Limiters
The limiters take no parameters:

View File

@ -1,3 +1,5 @@
# Bonus Propagators
## Available propagators
- BATTLE_WIDE: Affects both sides during battle

View File

@ -1,3 +1,5 @@
# Bonus Range Types
## List of all Bonus range types
- NO_LIMIT

View File

@ -1,3 +1,5 @@
# Bonus Sources
## List of all possible bonus sources
- ARTIFACT

View File

@ -1,3 +1,5 @@
# Bonus Types
The bonuses were grouped according to their original purpose. The bonus system allows them to propagate freely between the nodes, however they may not be recognized properly beyond the scope of original use.
## General-purpose bonuses

View File

@ -1,3 +1,5 @@
# Bonus Updaters
TODO: this page may be incorrect or outdated
Updaters come in two forms: simple and complex. Simple updaters take no

View File

@ -1,3 +1,5 @@
# Bonus Value Types
Total value of Bonus is calculated using the following:
- For each bonus source type we calculate new source value (for all bonus value types except PERCENT_TO_SOURCE and PERCENT_TO_TARGET_TYPE) using the following:

View File

@ -1,3 +1,5 @@
# Bonus Format
## Full format
All parameters but type are optional.

View File

@ -1,9 +1,11 @@
# Building Bonuses
Work-in-progress page do describe all bonuses provided by town buildings
for future configuration.
TODO: This page is outdated and may not represent VCMI 1.3 state
## unique buildings
### unique buildings
Hardcoded functionalities, selectable but not configurable. In future
should be moved to scripting.
@ -24,7 +26,7 @@ Function of all of these objects can be enabled by this:
"function" : "castleGates"
```
## trade-related
### trade-related
Hardcoded functionality for now due to complexity of these objects.
Temporary can be handles as unique buildings. Includes:
@ -37,7 +39,7 @@ Temporary can be handles as unique buildings. Includes:
- resource - skills
- creature - skeleton
## hero visitables
### hero visitables
Buildings that give one or another bonus to visiting hero. All should be
handled via configurable objects system.
@ -49,76 +51,76 @@ Includes:
- give bonus to visitor
- permanent bonus to hero
## generic functions
### generic functions
Generic town-specific functions that can be implemented as part of
CBuilding class.
### unlock guild level
#### unlock guild level
``` javascript
"guildLevels" : 1
```
### unlock hero recruitment
#### unlock hero recruitment
``` javascript
"allowsHeroPurchase" : true
```
### unlock ship purchase
#### unlock ship purchase
``` javascript
"allowsShipPurchase" : true
```
### unlock building purchase
#### unlock building purchase
``` javascript
"allowsBuildingPurchase" : true
```
### unlocks creatures
#### unlocks creatures
``` javascript
"dwelling" : { "level" : 1, "creature" : "archer" }
```
### creature growth bonus
#### creature growth bonus
Turn into town bonus? What about creature-specific bonuses from hordes?
### gives resources
#### gives resources
``` javascript
"provides" : { "gold" : 500 }
```
### gives guild spells
#### gives guild spells
``` javascript
"guildSpells" : [5, 0, 0, 0, 0]
```
### gives thieves guild
#### gives thieves guild
``` javascript
"thievesGuildLevels" : 1
```
### gives fortifications
#### gives fortifications
``` javascript
"fortificationLevels" : 1
```
### gives war machine
#### gives war machine
``` javascript
"warMachine" : "ballista"
```
## simple bonuses
### simple bonuses
Bonuses that can be made part of CBuilding. Note that due to how bonus
system works this bonuses won't be stackable.
@ -149,12 +151,12 @@ Includes:
}
```
## misc
### misc
Some other properties of town building that does not fall under "bonus"
category.
### unique building
#### unique building
Possible issue - with removing of fixed ID's buildings in different town
may no longer share same ID. However Capitol must be unique across all
@ -164,7 +166,7 @@ town. Should be fixed somehow.
"onePerPlayer" : true
```
### chance to be built on start
#### chance to be built on start
``` javascript
"prebuiltChance" : 75

View File

@ -1,4 +1,6 @@
# Introduction
# Campaign Format
## Introduction
Starting from version 1.3, VCMI supports its own campaign format.
Campaigns have *.vcmp file format and it consists from campaign json and set of scenarios (can be both *.vmap and *.h3m)
@ -27,7 +29,7 @@ Basic structure of this file is here, each section is described in details below
`"version"` defines version of campaign file. Larger versions should have more features and flexibility, but may not be supported by older VCMI engines. See [compatibility table](#compatibility-table)
# Header properties
## Header properties
In header are parameters describing campaign properties
```js
@ -35,15 +37,23 @@ In header are parameters describing campaign properties
"regions": {...},
"name": "Campaign name",
"description": "Campaign description",
"author": "Author",
"authorContact": "Author contact",
"campaignVersion": "Campaign version",
"creationDateTime": "Creation date and time",
"allowDifficultySelection": true,
```
- `"regions"` contains information about background and regions. See section [campaign regions](#regions-description) for more information
- `"name"` is a human readable title of campaign
- `"description"` is a human readable description of campaign
- `"author"` is the author of the campaign
- `"authorContact"` is a contact address for the author (e.g. email)
- `"campaignVersion"` is creator defined version
- `"creationDateTime"` unix time of campaign creation
- `"allowDifficultySelection"` is a boolean field (`true`/`false`) which allows or disallows to choose difficulty before scenario start
# Scenario description
## Scenario description
Scenario description looks like follow:
```js
@ -79,7 +89,7 @@ Scenario description looks like follow:
- `"playerColor"` defines color id of flag which player will play for. Possible values are `0: red, 1: blue, tan: 2, green: 3, orange: 4, purple: 5, teal: 6, pink: 7`
- "bonuses" array of possible bonus objects, format depends on `"startOptions"` parameter
## Prolog/Epilog
### Prolog/Epilog
Prolog and epilog properties are optional
```js
@ -91,13 +101,13 @@ Prolog and epilog properties are optional
}
```
## Start options and bonuses
### Start options and bonuses
### None start option
#### None start option
If `startOptions` is `none`, `bonuses` field will be ignored
### Bonus start option
#### Bonus start option
If `startOptions` is `bonus`, bonus format may vary depending on its type.
@ -140,7 +150,7 @@ If `startOptions` is `bonus`, bonus format may vary depending on its type.
- `"amount"`: amount of resources
- `"hero"` can be specified as explicit hero name and as one of keywords: `strongest`, `generated`
### Crossover start option
#### Crossover start option
If `startOptions` is `crossover`, heroes from specific scenario will be moved to this scenario. Bonus format is following
@ -153,7 +163,7 @@ If `startOptions` is `crossover`, heroes from specific scenario will be moved to
- `"playerColor"` from what player color heroes shall be taken. Possible values are `0: red, 1: blue, tan: 2, green: 3, orange: 4, purple: 5, teal: 6, pink: 7`
- `"scenario"` from which scenario heroes shall be taken. 0 means first scenario
### Hero start option
#### Hero start option
If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus format is following
```js
@ -166,7 +176,7 @@ If `startOptions` is `hero`, hero can be chosen as a starting bonus. Bonus forma
- `"playerColor"` from what player color heroes shall be taken. Possible values are `0: red, 1: blue, tan: 2, green: 3, orange: 4, purple: 5, teal: 6, pink: 7`
- `"hero"` can be specified as explicit hero name and as one of keywords: `random`
## Regions description
### Regions description
Predefined campaign regions are located in file `campaign_regions.json`
@ -186,7 +196,7 @@ Predefined campaign regions are located in file `campaign_regions.json`
- `"inflix"` ised to identify all images related to region. In this example, it will be pictures starting from `G3A_..., G3B_..., G3C_..."`
- `"color_suffix_length"` identifies suffix length for region colourful frames. 1 is used for `R, B, N, G, O, V, T, P`, value 2 is used for `Re, Bl, Br, Gr, Or, Vi, Te, Pi`
# Packing campaign
## Packing campaign
After campaign scenarios and campaign description are ready, you should pack them into *.vcmp file.
This file is basically headless gz archive.
@ -208,7 +218,7 @@ gzip -c -n ./* >> my-campaign.vcmp
If you are using Windows system, try this https://gnuwin32.sourceforge.net/packages/gzip.htm
# Compatibility table
## Compatibility table
| Version | Min VCMI | Max VCMI | Description |
|---------|----------|----------|-------------|
| 1 | 1.3 | | Initial release |

View File

@ -1,10 +1,12 @@
# Introduction
# Configurable Widgets
## Introduction
VCMI has capabilities to change some UI elements in your mods beyond only replacing one image with another. Not all UI elements are possible to modify currently, but development team is expanding them.
Elements possible to modify are located in `config/widgets`.
# Tutorial
## Tutorial
Let's take `extendedLobby` mod from `vcmi-extras` as an example for VCMI-1.4. [Example sources](https://github.com/vcmi-mods/vcmi-extras/tree/vcmi-1.4/Mods/extendedLobby).
@ -16,7 +18,7 @@ For options tab it introduces UI for chess timers.
In this tutorial we will recreate options tab to support chess timers UI.
## Creating mod structure
### Creating mod structure
To start making mod, create following folders structure;
```
@ -44,7 +46,7 @@ File `mod.json` is generic and could look like this:
After that you can copy `extendedLobby/ folder to `mods/` folder and your mod will immediately appear in launcher but it does nothing for now.
## Making layout for timer
### Making layout for timer
Let's copy `config/widgets/optionsTab.json` file from VCMI folder to `content/config/widgets/` folder from our mod.
It defines UI for options tab as it designed in original game, we will keep everything related to player settings and will modify only timer area.
@ -111,7 +113,7 @@ In order to make it work, add file `RmgTTBk.bmp` to `content/sprites/`
Elements named `labelTurnDurationValue` and `sliderTurnDuration` we will keep without change - they are needed to configure classic timer.
## Adding combo box
### Adding combo box
Now, let's add combo box.
@ -264,7 +266,7 @@ Now specify items inside `dropDown` field
Now we can press drop-down menu and even select elements.
## Switching timer modes
### Switching timer modes
After view part is done, let's make behavioural part.
Let's hide elements, related to classic timer when chess timer is selected and show them back if classic selected.
@ -322,7 +324,7 @@ This background must be visible for chess timer and hidden for classic timer. Ju
It works and can switch elements, the only missing part is chess timer configuration.
## Chess timer configuration
### Chess timer configuration
We should add text input fields, to specify different timers. We will use background for them `timerField.bmp`, copy it to `content/sprites/` folder of your mod.
@ -352,39 +354,39 @@ And what we want to do is to hide/show those fields when classic/chess times is
We are done! You can find more information about configurable UI elements in documentation section.
# Documentation
## Documentation
## Types
### Types
All fields have format `"key": value`
There are different basic types, which can be used as value.
### Primitive types
#### Primitive types
Read JSON documentation for primitive types description: https://www.json.org/json-en.html
### Text
#### Text
Load predefined text which can be localised, examples:
`"vcmi.otherOptions.availableCreaturesAsDwellingLabel"`
`"core.genrltxt.738"`
### Position
#### Position
Point with two coordinates, example:
`{ "x": 43, "y": -28 }`
### Rect
#### Rect
Rectangle ares, example:
`{ "x": 28, "y": 220, "w": 108, "h": 50 }`
### Text alignment
#### Text alignment
Defines text alignment, can be one of values:
`"center"`, `"left"`, `"right"`
### Color
#### Color
Predefined colors:
`"yellow"`, `"white"`, `"gold"`, `"green"`, `"orange"`, `"bright-yellow"`
@ -392,12 +394,12 @@ Predefined colors:
To have custom color make an array of four elements in RGBA notation:
`[255, 128, 0, 255]`
### Font
#### Font
Predefined fonts:
`"big"`, `"medium"`, `"small"`, `"tiny"`, `"calisto"`
### Hint text
#### Hint text
Hint text is a pair of strings, one is usually shown in status bar when cursor hovers element, another hint while right button pressed.
Each of elements is a [Text](#text)
@ -413,19 +415,19 @@ If one string specified, it will be applied for both hover and help.
`"text"`
### Shortcut
#### Shortcut
String value defines shortcut. Some examples of shortcuts:
`"globalAccept", "globalCancel", "globalReturn","globalFullscreen", "globalOptions", "globalBackspace", "globalMoveFocus"`
Full list is TBD
### [VCMI-1.4] Player color
#### [VCMI-1.4] Player color
One of predefined values:
`"red"`, `"blue"`, `"tan"`, `"green"`, `"orange"`, `"purple"`, `"teal"`, `"pink"`
## Configurable objects
### Configurable objects
Configurable object has following structure:
```json
@ -445,9 +447,9 @@ Configurable object has following structure:
`library` - same as above, but custom widgets are described in separate json, this parameter should contain path to library json is specified
## Basic widgets
### Basic widgets
### Label
#### Label
`"type": "label"`
@ -463,7 +465,7 @@ Configurable object has following structure:
`"position"`: [position](#position)
### [VCMI-1.4] Multi-line label
#### [VCMI-1.4] Multi-line label
`"type": "multiLineLabel"`
@ -483,7 +485,7 @@ Configurable object has following structure:
`"adoptHeight": bool` //if true, text area height will be adopted automatically based on content
### Label group
#### Label group
`"type": "labelGroup"`
@ -503,7 +505,7 @@ Configurable object has following structure:
`"text"`: [text](#text),
### TextBox
#### TextBox
`"type": "textBox"`
@ -519,7 +521,7 @@ Configurable object has following structure:
`"rect"`: [rect](#rect)
### Picture
#### Picture
`"type": "picture"`
@ -533,7 +535,7 @@ Configurable object has following structure:
`"playerColored", bool`, optional, if true will be colorised to current player
### Image
#### Image
Use to show single frame from animation
@ -549,7 +551,7 @@ Use to show single frame from animation
`"frame": integer` optional, specify animation frame
### Texture
#### Texture
Filling area with texture
@ -561,7 +563,7 @@ Filling area with texture
`"rect"`: [rect](#rect)
### TransparentFilledRectangle
#### TransparentFilledRectangle
`"type": "transparentFilledRectangle"`
@ -573,7 +575,7 @@ Filling area with texture
`"rect"`: [rect](#rect)
### Animation
#### Animation
`"type": "animation"`
@ -599,7 +601,7 @@ Filling area with texture
`"end": integer`, last frame
### [VCMI-1.4] Text input
#### [VCMI-1.4] Text input
`"type": "textInput"`
@ -625,7 +627,7 @@ Filling area with texture
`"callback": string` optional, callback to be called on text changed. Input text is passed to callback function as an argument.
### Button
#### Button
`"type": "button"`
@ -647,7 +649,7 @@ Filling area with texture
`"items": []` array of widgets to be shown as overlay (caption [label](#label), for example)
### Toggle button
#### Toggle button
`"type": "toggleButton"`
@ -667,7 +669,7 @@ Filling area with texture
`"items": []` array of widgets to be shown as overlay (caption [label](#label), for example)
### Toggle group
#### Toggle group
Group of [toggle buttons](#toggle-button), when one is selected, other will be de-selected
@ -681,7 +683,7 @@ Group of [toggle buttons](#toggle-button), when one is selected, other will be d
`"items": []` array of [toggle buttons](#toggle-button)
### Slider
#### Slider
`"type": "slider"`
@ -707,7 +709,7 @@ Group of [toggle buttons](#toggle-button), when one is selected, other will be d
`"panningStep": integer`, optional
### Combo box
#### Combo box
`"type": "comboBox"`
@ -729,7 +731,7 @@ Group of [toggle buttons](#toggle-button), when one is selected, other will be d
`"dropDown" : {}` description of [drop down](#drop-down) menu widget
### Drop down
#### Drop down
Used only as special object for [combo box](#combo-box)
@ -750,17 +752,17 @@ Used only as special object for [combo box](#combo-box)
**Callbacks**
- `sliderMove` connect to slider callback to correctly navigate over elements
### Layout
#### Layout
`"type": "layout"`
`"name": "string"` optional, object name
## High-level widgets
### High-level widgets
## Custom widgets
### Custom widgets
# For developers
## For developers
While designing a new element, you can make it configurable to reuse all functionality described above. It will provide flexibility to further changes as well as modding capabilities.
@ -798,9 +800,9 @@ MyYesNoDialog::MyYesNoDialog(const JsonNode & config):
}
```
## Callbacks
### Callbacks
## Custom widgets
### Custom widgets
You can build custom widgets, related to your UI element specifically. Like in example above, there is Item widget, which can be also used on JSON config.
@ -832,7 +834,7 @@ After that, if your JSON file has items with type "MyItem", the new Item element
}
```
## Variables
### Variables
After calling `build(config)` variables defined in config JSON file become available. You can interpret them and use in callbacks or in element code

View File

@ -1,3 +1,5 @@
# Difficulty
Since VCMI 1.4.0 there are more capabilities to configure difficulty parameters.
It means, that modders can give different bonuses to AI or human players depending on selected difficulty

View File

@ -1,3 +1,5 @@
# Artifact Format
Artifact bonuses use [Bonus Format](../Bonus_Format.md)
## Required data

View File

@ -1,3 +1,5 @@
# Battle Obstacle Format
```jsonc
// List of terrains on which this obstacle can be used
"allowedTerrains" : []

View File

@ -1,3 +1,5 @@
# Battlefield Format
```jsonc
// Human-readable name of the battlefield
"name" : ""

View File

@ -1,3 +1,5 @@
# Biome Format
## General description
Biome is a new entity type added in VCMI 1.5.0. It defines a set of random map obstacles which will be generated together. For each zone different obstacle sets is randomized and then only obstacles from that set will be used to fill this zone.

View File

@ -1,3 +1,5 @@
# Creature Format
## Required data
In order to make functional creature you also need:

View File

@ -1,3 +1,5 @@
# Faction Format
## Required data
In order to make functional town you also need:

View File

@ -1,3 +1,5 @@
# Hero Class Format
## Required data
In order to make functional hero class you also need:

View File

@ -1,3 +1,5 @@
# Hero Type Format
## Required data
In order to make functional hero you also need:

View File

@ -1,3 +1,5 @@
# River Format
## Format
```jsonc

View File

@ -1,3 +1,5 @@
# Road Format
## Format
```jsonc

View File

@ -1,3 +1,5 @@
# Secondary Skill Format
## Main format
```jsonc

View File

@ -1,3 +1,5 @@
# Spell Format
## Main format
``` javascript

View File

@ -1,3 +1,5 @@
# Terrain Format
## Format
```jsonc

View File

@ -1,3 +1,5 @@
# Game Identifiers
## List of all game identifiers
This is a list of all game identifiers available to modders. Note that only identifiers from base game have been included. For identifiers from mods please look up corresponding mod

View File

@ -1,16 +1,18 @@
# Interface
# Map Editor
## Interface
<img width="738" src="https://user-images.githubusercontent.com/9308612/188775648-8551107d-9f0b-4743-8980-56c2c1c58bbc.png">
# Create the map
## Create the map
<img width="145" src="https://user-images.githubusercontent.com/9308612/188782248-b7895eb1-86a9-4f06-9309-43c6b3a3a728.png">
## New map
### New map
Create the new map by pressing **New** button from the toolbar
### Empty map
#### Empty map
To create empty map, define its size by choosing option from drop-down list or enter required size manually in the text fields and press Ok button. Check **Two level map** option to create map with underground.
`Note: there are no limits on map size but be careful with sizes larger predefined XL size. It will be processed quite long to create even empty map. Also, it will be difficult to work with the huge maps because of possible performance issues`
@ -19,7 +21,7 @@ Other parameters won't be used for empty map.
<img width="413" src="https://user-images.githubusercontent.com/9308612/188782588-c9f1a164-df1e-46bc-a6d3-24ff1e5396c2.png">
### Random map
#### Random map
To generate random map, check the **Random map** option and configure map parameters. You can select template from the drop-down list.
@ -32,27 +34,27 @@ Templates are dynamically filtered depending on parameters you choose.
<img width="412" src="https://user-images.githubusercontent.com/9308612/188783360-1c042782-328d-4692-952f-58fa5110d0d0.png">
## Map load & save
### Map load & save
To load the map, press open and select map file from the browser.
You can load both *.h3m and *.vmap formats but for saving *.vmap is allowed only.
# Views
## Views
There are 3 buttons switching views <img width="131" alt="Снимок экрана 2022-09-07 в 06 48 08" src="https://user-images.githubusercontent.com/9308612/188777527-b7106146-0d8c-4f14-b78d-f13512bc7bad.png">
### Ground/underground
#### Ground/underground
**"U/G"** switches you between ground and underground
### Grid view
#### Grid view
**Grid** show/hide grid
<img width="153" src="https://user-images.githubusercontent.com/9308612/188777723-934d693b-247d-42a6-815c-aecd0d34f653.png">
### Passability view
#### Passability view
**Pass** show/hide passability map
@ -73,7 +75,7 @@ There are 3 buttons switching views <img width="131" alt="Снимок экра
<img width="116" src="https://user-images.githubusercontent.com/9308612/188777176-1167561a-f23c-400a-a57b-be7fc90accae.png">
### Drawing roads and rivers
#### Drawing roads and rivers
Actually, the process to draw rivers or roads is exactly the same as for terrains. You need to select tiles and then choose road/river type from the panel.
@ -85,13 +87,13 @@ To erase roads or rivers, you need to select tiles to be cleaned and press empty
_Erasing works either for roads or for rivers, e.g. empty button from the roads tab erases roads only, but not rivers. You also can safely select bigger area, because it won't erase anything on tiles without roads/rivers accordingly_
## About brushes
### About brushes
* Buttons "1", "2", "4" - 1x1, 2x2, 4x4 brush sizes accordingly
* Button "[]" - non-additive rectangle selection
* Button "O" - lasso brush (not implemented yet)
* Button "E" - object erase, not a brush
## Fill obstacles
### Fill obstacles
Map editor supports automatic obstacle placement.
Obstacle types are automatically selected for appropriate terrain types
@ -105,9 +107,9 @@ To do that, select area (see Setup terrains) and press **Fill** button from the
`Note: obstacle placer may occupy few neighbour tiles outside of selected area`
# Manipulating objects
## Manipulating objects
## Adding new objects
### Adding new objects
1. Find the object you'd like to place in the object browser
@ -125,7 +127,7 @@ To do that, select area (see Setup terrains) and press **Fill** button from the
**Right click over the scene - cancel object placement**
## Removing objects
### Removing objects
1. **Make sure that no one terrain brush is selected.** To de-select brush click on selected brush again.
@ -134,7 +136,7 @@ To do that, select area (see Setup terrains) and press **Fill** button from the
3. Press **"E"** button from the brush panel or press **delete** on keyboard
## Changing object's properties
### Changing object's properties
1. **Make sure that no one terrain brush is selected.** To de-select brush click on selected brush again.
@ -147,15 +149,15 @@ To do that, select area (see Setup terrains) and press **Fill** button from the
4. You are able to modify properties which are not gray
`Note: sometimes there are empty editable fields`
### Assigning player to the object
#### Assigning player to the object
Objects with flags can be assigned to the player. Find Owner property in the inspector for selected object, press twice to modify right cell. Type player number from **0 to 7 or type NEUTRAL** for neutral objects.
# Set up the map
## Set up the map
You can modify general properties of the map
## Map name and description
### Map name and description
1. Open **Map** menu on the top and select **General**
@ -167,7 +169,7 @@ You can modify general properties of the map
<img width="307" src="https://user-images.githubusercontent.com/9308612/188781063-50ce65f8-aab4-43c3-a308-22acafa7d0a2.png">
# Player settings
## Player settings
Open **Map** menu on the top and select **Player settings"
@ -179,29 +181,29 @@ You will see a window with player settings. Combobox players defines amount of p
<img width="641" src="https://user-images.githubusercontent.com/9308612/188781400-7d5ba463-4f82-4dba-83ff-56210995e2c7.png">
# Compatibility questions
## Compatibility questions
## Platform compatibility
### Platform compatibility
vcmieditor is a cross-platform application, so in general can support all platforms, supported by VCMI.
However, currently it doesn't support mobile platforms.
## Engine compatibility
### Engine compatibility
vcmieditor is independent application so potentially it can be installed just in the folder with existing stable vcmi. However, on the initial stages of development compatibility was not preserved because major changes were needed to introduce into vcmi library. So it's recommended to download full package to use editor.
## Map compatibility
### Map compatibility
vcmieditor haven't introduced any change into map format yet, so all maps made by vcmieditor can be easily played with any version of vcmi. At the same time, those maps can be open and read in the old map editor and vice verse - maps from old editor can be imported in the new editor. So, full compatibility is ensured here.
## Mod compatibility
### Mod compatibility
vcmieditor loads set of mods using exactly same mechanism as game uses and mod manipulations can be done using vcmilaucnher application, just enable or disable mods you want and open editor to use content from those mods. In regards on compatibility, of course you need to play maps with same set of mods as you used in the editor. Good part is that is maps don't use content from the mods (even mods were enabled), it can be played on vcmi without mods as well
# Working With Mods
## Working With Mods
## Enabling and disabling mods
### Enabling and disabling mods
The mods mechanism used in map editor is the same as in game.
@ -212,7 +214,7 @@ To enable or disable mods
There is no button to start map editor directly from launcher, however you may use this approach to control active mods from any version of vcmi.
## Placing objects from mods
### Placing objects from mods
* All objects from mods will be automatically added into objects Browser. You can type mod name into filter field to find them.
@ -222,13 +224,13 @@ There is no button to start map editor directly from launcher, however you may u
<img width="271" src="https://user-images.githubusercontent.com/9308612/189965836-de1fd196-2f6d-4996-a62a-e2fcb52b8d74.png">
## Playing maps with mods
### Playing maps with mods
If you place any kind of objects from the mods, obviously, you need those mods to be installed to play the map.
Also, you need to activate them.
You also may have other mods being activated in addition to what was used during map designing.
### Mod versions
#### Mod versions
In the future, the will be support of mods versioning so map will contain information about mods used and game can automatically search and activate required mods or let user know which are required. However, it's not implemented yet

View File

@ -1,3 +1,5 @@
# Map Object Format
## Description
Full object consists from 3 parts:

View File

@ -1,3 +1,5 @@
# Boat
``` javascript
{
// Layer on which this boat moves. Possible values:

View File

@ -1,3 +1,5 @@
# Creature Bank
Reward types for clearing creature bank are limited to resources, creatures, artifacts and spell.
Format of rewards is same as in [Rewardable Objects](Rewardable.md)

View File

@ -1,3 +1,5 @@
# Dwelling
``` javascript
{
/// List of creatures in this bank. Each list represents one "level" of bank

View File

@ -1,3 +1,5 @@
# Market
## Market schema
Since VCMI-1.3 it's possible to create customizable markets on adventure map.

View File

@ -1,3 +1,5 @@
# Rewardable
## Base object definition
Rewardable object is defined similarly to other objects, with key difference being `handler`. This field must be set to `"handler" : "configurable"` in order for vcmi to use this mode.
```jsonc

View File

@ -1,3 +1,5 @@
# Mod File Format
## Fields with description of mod
``` javascript

View File

@ -1,3 +1,5 @@
# Random Map Template
## Template format
``` javascript

View File

@ -1,3 +1,5 @@
# Modding Readme
## Creating mod
To make your own mod you need to create subdirectory in **<data dir>/Mods/** with name that will be used as identifier for your mod.

View File

@ -1,3 +1,5 @@
# Translations
## List of currently supported languages
This is list of all languages that are currently supported by VCMI. If your languages is missing from the list and you wish to translate VCMI - please contact our team and we'll add support for your language in next release.

View File

@ -1,3 +1,5 @@
# Bug Reporting Guidelines
First of all, thanks for your support! If you report a bug we can fix it. But keep in mind that reporting your bugs appropriately makes our (developers') lives easier. Here are a few guidelines that will help you write good bug reports.
## Github bugtracker

View File

@ -1,4 +1,6 @@
## Cheat Codes
# Cheat Codes
## Codes
Similar to H3, VCMI provides cheat codes to make testing game more convenient.

View File

@ -1,3 +1,5 @@
# Game Mechanics
## List of features added in VCMI
### High resolutions

View File

@ -1,3 +1,5 @@
# Installation Android
## Step 1: Download and install VCMI
**This app requires original heroes 3 sod / complete files to operate, they are not supplied with this installer. it is recommended to purchase version from gog.com. Heroes 3 "hd edition" (steam version) files are not supported !!!**

View File

@ -1,6 +1,8 @@
# Installation Linux
VCMI requires data from original Heroes 3: Shadow of Death or Complete editions. Data from native Linux version made by LOKI will not work.
# Step 1: Binaries installation
## Step 1: Binaries installation
### Ubuntu - Latest stable build from PPA (recommended)

View File

@ -1,3 +1,5 @@
# Installation Windows
## Prerequisites
As of VCMI 1.2 and newer Windows 10 or newer is required since our automated system uses elements incompatible with older Windows.

View File

@ -1,3 +1,5 @@
# Installation iOS
You can run VCMI on iOS 12.0 and later, all devices are supported. If you wish to run on iOS 10 or 11, you should build from source, see [How to build VCMI (iOS)](../developers/Building_iOS.md).
## Step 1: Download and install VCMI

View File

@ -1,3 +1,5 @@
# Installation macOS
For iOS installation look here: (Installation on iOS)[Installation_iOS.md]
## Step 1: Download and install VCMI

View File

@ -1,14 +1,16 @@
# Privacy Policy
**Last Updated: 24th December, 2022**
### Glossary
## Glossary
* VCMI team - a community of VCMI developers, mod makers and testers. It is not some officially registered organization.
* VCMI app - an application provided by VCMI team.
### Single player
## Single player
VCMI team does not collect any data produced by VCMI app. All game files, logs, saves, mods are stored in app's internal directory and will be removed upon app uninstallation. It should be possible to backup this data by standard ways provided by your device.
### Multiplayer
## Multiplayer
If you decide to play with other users via Internet there are two roles. The host is the one who provides the game server. The clients are the other players who connect to the host. The host provides to the client its IP address in order to establish connections. The clients and the host during the gameplay exchange their usernames, messages and other game activity. All this data is collected and stored by the host. VCMI team does not collect and store any multiplayer data.

View File

@ -368,8 +368,8 @@ void FirstLaunchView::extractGogData()
QString errorText{};
auto isGogGalaxyExe = [](QString fileExe) {
QFile file(fileExe);
auto isGogGalaxyExe = [](QString fileToTest) {
QFile file(fileToTest);
quint64 fileSize = file.size();
if(fileSize > 10 * 1024 * 1024)
@ -379,7 +379,7 @@ void FirstLaunchView::extractGogData()
return false;
QByteArray data = file.readAll();
const QByteArray magicId{(const char*)u"GOG Galaxy", 20};
const QByteArray magicId{reinterpret_cast<const char*>(u"GOG Galaxy"), 20};
return data.contains(magicId);
};

View File

@ -108,19 +108,20 @@ void startEditor(const QStringList & args)
#ifndef VCMI_MOBILE
void startExecutable(QString name, const QStringList & args)
{
// Start vcmiclient and vcmieditor with QProcess::start() instead of QProcess::startDetached()
// since startDetached() results in a missing terminal prompt after quitting vcmiclient.
// QProcess::start() causes the launcher window to freeze while the child process is running, so we hide it in
// MainWindow::on_startGameButton_clicked() and MainWindow::on_startEditorButton_clicked()
QProcess process;
// Start the executable
if(process.startDetached(name, args))
{
qApp->quit();
}
else
{
process.start(name, args);
process.waitForFinished(-1);
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
QMessageBox::critical(qApp->activeWindow(),
QObject::tr("Error starting executable"),
QObject::tr("Failed to start %1\nReason: %2").arg(name, process.errorString())
);
QObject::tr("Error starting executable"),
QObject::tr("Failed to start %1\nReason: %2").arg(name, process.errorString()));
}
qApp->quit();
}
#endif

View File

@ -196,11 +196,13 @@ MainWindow::~MainWindow()
void MainWindow::on_startGameButton_clicked()
{
hide();
startGame({});
}
void MainWindow::on_startEditorButton_clicked()
{
hide();
startEditor({});
}

View File

@ -134,6 +134,8 @@ CModListView::CModListView(QWidget * parent)
ui->updateButton->setIcon(QIcon{":/icons/mod-update.png"});
ui->installButton->setIcon(QIcon{":/icons/mod-download.png"});
ui->splitter->setStyleSheet("QSplitter::handle {background: palette('window');}");
setupModModel();
setupFilterModel();
setupModsView();

View File

@ -27,9 +27,7 @@
#include <SDL2/SDL.h>
#endif
namespace
{
QString resolutionToString(const QSize & resolution)
static QString resolutionToString(const QSize & resolution)
{
return QString{"%1x%2"}.arg(resolution.width()).arg(resolution.height());
}
@ -47,8 +45,6 @@ static constexpr std::array upscalingFilterTypes =
"best"
};
}
void CSettingsView::setDisplayList()
{
QStringList list;

Some files were not shown because too many files have changed in this diff Show More