1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-18 03:21:27 +02:00

Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Xilmi 2024-07-16 15:47:59 +02:00
commit b0f37748d6
73 changed files with 951 additions and 617 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

@ -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

@ -30,3 +30,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

@ -35,12 +35,20 @@ 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

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

@ -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;

View File

@ -196,7 +196,7 @@ DLL_LINKAGE bool ArtifactUtils::isBackpackFreeSlots(const CArtifactSet * target,
}
DLL_LINKAGE std::vector<const CArtifact*> ArtifactUtils::assemblyPossibilities(
const CArtifactSet * artSet, const ArtifactID & aid)
const CArtifactSet * artSet, const ArtifactID & aid, const bool onlyEquiped)
{
std::vector<const CArtifact*> arts;
const auto * art = aid.toArtifact();
@ -210,7 +210,7 @@ DLL_LINKAGE std::vector<const CArtifact*> ArtifactUtils::assemblyPossibilities(
for(const auto constituent : artifact->getConstituents()) //check if all constituents are available
{
if(!artSet->hasArt(constituent->getId(), false, false, false))
if(!artSet->hasArt(constituent->getId(), onlyEquiped, false, false))
{
possible = false;
break;

View File

@ -39,7 +39,7 @@ namespace ArtifactUtils
DLL_LINKAGE bool isSlotBackpack(const ArtifactPosition & slot);
DLL_LINKAGE bool isSlotEquipment(const ArtifactPosition & slot);
DLL_LINKAGE bool isBackpackFreeSlots(const CArtifactSet * target, const size_t reqSlots = 1);
DLL_LINKAGE std::vector<const CArtifact*> assemblyPossibilities(const CArtifactSet * artSet, const ArtifactID & aid);
DLL_LINKAGE std::vector<const CArtifact*> assemblyPossibilities(const CArtifactSet * artSet, const ArtifactID & aid, const bool onlyEquiped = false);
DLL_LINKAGE CArtifactInstance * createScroll(const SpellID & sid);
DLL_LINKAGE CArtifactInstance * createNewArtifactInstance(const CArtifact * art);
DLL_LINKAGE CArtifactInstance * createNewArtifactInstance(const ArtifactID & aid);

View File

@ -741,19 +741,6 @@ std::vector<ArtifactPosition> CArtifactSet::getBackpackArtPositions(const Artifa
return result;
}
ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance *art) const
{
for(auto i : artifactsWorn)
if(i.second.artifact == art)
return i.first;
for(int i = 0; i < artifactsInBackpack.size(); i++)
if(artifactsInBackpack[i].artifact == art)
return ArtifactPosition::BACKPACK_START + i;
return ArtifactPosition::PRE_FIRST;
}
const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanceID & artInstId) const
{
for(auto i : artifactsWorn)
@ -767,7 +754,7 @@ const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanc
return nullptr;
}
const ArtifactPosition CArtifactSet::getSlotByInstance(const CArtifactInstance * artInst) const
const ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance * artInst) const
{
if(artInst)
{
@ -1079,8 +1066,8 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa
}
}
CArtifactFittingSet::CArtifactFittingSet(ArtBearer::ArtBearer Bearer):
Bearer(Bearer)
CArtifactFittingSet::CArtifactFittingSet(ArtBearer::ArtBearer bearer)
: bearer(bearer)
{
}
@ -1094,7 +1081,7 @@ CArtifactFittingSet::CArtifactFittingSet(const CArtifactSet & artSet)
ArtBearer::ArtBearer CArtifactFittingSet::bearerType() const
{
return this->Bearer;
return this->bearer;
}
VCMI_LIB_NAMESPACE_END

View File

@ -207,11 +207,10 @@ public:
/// Looks for equipped artifact with given ID and returns its slot ID or -1 if none
/// (if more than one such artifact lower ID is returned)
ArtifactPosition getArtPos(const ArtifactID & aid, bool onlyWorn = true, bool allowLocked = true) const;
ArtifactPosition getArtPos(const CArtifactInstance *art) const;
const ArtifactPosition getArtPos(const CArtifactInstance * art) const;
std::vector<ArtifactPosition> getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const;
std::vector<ArtifactPosition> getBackpackArtPositions(const ArtifactID & aid) const;
const CArtifactInstance * getArtByInstanceId(const ArtifactInstanceID & artInstId) const;
const ArtifactPosition getSlotByInstance(const CArtifactInstance * artInst) const;
/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
const CArtifactInstance * getHiddenArt(const ArtifactID & aid) const;
const CArtifactInstance * getAssemblyByConstituent(const ArtifactID & aid) const;
@ -255,7 +254,7 @@ public:
ArtBearer::ArtBearer bearerType() const override;
protected:
ArtBearer::ArtBearer Bearer;
ArtBearer::ArtBearer bearer;
};
VCMI_LIB_NAMESPACE_END

View File

@ -138,11 +138,11 @@ static void createMemoryDump(MINIDUMP_EXCEPTION_INFORMATION * meinfo)
| MiniDumpWithThreadInfo);
}
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dfile, dumpType, meinfo, 0, 0);
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dfile, dumpType, meinfo, nullptr, nullptr);
MessageBoxA(0, "VCMI has crashed. We are sorry. File with information about encountered problem has been created.", "VCMI Crashhandler", MB_OK | MB_ICONERROR);
}
static void onTerminate()
[[noreturn]] static void onTerminate()
{
logGlobal->error("Disaster happened.");
try

View File

@ -284,6 +284,10 @@ CArtifactSet * CNonConstInfoCallback::getArtSet(const ArtifactLocation & loc)
return hero;
}
}
else if(auto army = getArmyInstance(loc.artHolder))
{
return army->getStackPtr(loc.creature.value());
}
else if(auto market = dynamic_cast<CGArtifactsAltar*>(getObjInstance(loc.artHolder)))
{
return market;

View File

@ -90,7 +90,7 @@ public:
virtual void artifactAssembled(const ArtifactLocation &al){};
virtual void artifactDisassembled(const ArtifactLocation &al){};
virtual void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst){};
virtual void bulkArtMovementStart(size_t numOfArts) {};
virtual void bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts) {};
virtual void askToAssembleArtifact(const ArtifactLocation & dst) {};
virtual void heroVisit(const CGHeroInstance *visitor, const CGObjectInstance *visitedObj, bool start){};

View File

@ -49,7 +49,7 @@ private:
REPLACE_TEXTID_STRING,
REPLACE_NUMBER,
REPLACE_POSITIVE_NUMBER,
APPEND_EOL
APPEND_EOL
};
std::vector<EMessage> message;

View File

@ -66,9 +66,25 @@ uint32_t ReachabilityInfo::distToNearestNeighbour(
if(attacker->doubleWide())
{
vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->occupiedHex(), true, attacker->unitSide()));
if(defender->doubleWide())
{
// It can be back to back attack o==o or head to head =oo=.
// In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles
vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide()));
}
else
{
vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide()));
}
}
vstd::removeDuplicates(attackableHexes);
vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool
{
return h.getY() != defender->getPosition().getY() || !h.isAvailable();
});
return distToNearestNeighbour(attackableHexes, chosenHex);
}

View File

@ -99,7 +99,7 @@ int BonusList::totalValue() const
int indepMax = std::numeric_limits<int>::min();
};
auto percent = [](int base, int percent) -> int {
auto applyPercentage = [](int base, int percent) -> int {
return (static_cast<int64_t>(base) * (100 + percent)) / 100;
};
@ -125,7 +125,7 @@ int BonusList::totalValue() const
for(const auto & b : bonuses)
{
int sourceIndex = vstd::to_underlying(b->source);
int valModified = percent(b->val, percentToSource[sourceIndex]);
int valModified = applyPercentage(b->val, percentToSource[sourceIndex]);
switch(b->valType)
{
@ -152,9 +152,9 @@ int BonusList::totalValue() const
}
}
accumulated.base = percent(accumulated.base, accumulated.percentToBase);
accumulated.base = applyPercentage(accumulated.base, accumulated.percentToBase);
accumulated.base += accumulated.additive;
auto valFirst = percent(accumulated.base ,accumulated.percentToAll);
auto valFirst = applyPercentage(accumulated.base ,accumulated.percentToAll);
if(hasIndepMin && hasIndepMax && accumulated.indepMin < accumulated.indepMax)
accumulated.indepMax = accumulated.indepMin;
@ -167,7 +167,13 @@ int BonusList::totalValue() const
if(notIndepBonuses)
return std::clamp(valFirst, accumulated.indepMax, accumulated.indepMin);
return hasIndepMin ? accumulated.indepMin : hasIndepMax ? accumulated.indepMax : 0;
if (hasIndepMin)
return accumulated.indepMin;
if (hasIndepMax)
return accumulated.indepMax;
return 0;
}
std::shared_ptr<Bonus> BonusList::getFirst(const CSelector &select)

View File

@ -151,6 +151,10 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
ret.numberOfScenarios = reader["scenarios"].Vector().size();
ret.name.appendTextID(reader["name"].String());
ret.description.appendTextID(reader["description"].String());
ret.author.appendRawString(reader["author"].String());
ret.authorContact.appendRawString(reader["authorContact"].String());
ret.campaignVersion.appendRawString(reader["campaignVersion"].String());
ret.creationDateTime = reader["creationDateTime"].Integer();
ret.difficultyChosenByPlayer = reader["allowDifficultySelection"].Bool();
ret.music = AudioPath::fromJson(reader["music"]);
ret.filename = filename;
@ -385,6 +389,10 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
ret.loadLegacyData(campId);
ret.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
ret.author.appendRawString("");
ret.authorContact.appendRawString("");
ret.campaignVersion.appendRawString("");
ret.creationDateTime = 0;
if (ret.version > CampaignVersion::RoE)
ret.difficultyChosenByPlayer = reader.readInt8();
else

View File

@ -81,6 +81,10 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
CampaignRegions campaignRegions;
MetaString name;
MetaString description;
MetaString author;
MetaString authorContact;
MetaString campaignVersion;
std::time_t creationDateTime;
AudioPath music;
std::string filename;
std::string modName;
@ -114,6 +118,13 @@ public:
h & numberOfScenarios;
h & name;
h & description;
if (h.version >= Handler::Version::MAP_FORMAT_ADDITIONAL_INFOS)
{
h & author;
h & authorContact;
h & campaignVersion;
h & creationDateTime;
}
h & difficultyChosenByPlayer;
h & filename;
h & modName;

View File

@ -54,6 +54,7 @@ namespace GameConstants
constexpr int ALTAR_ARTIFACTS_SLOTS = 22;
constexpr int TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD = 144*144*2; //map tiles count threshold for 2 dimension door casts with tournament rules
constexpr int KINGDOM_WINDOW_HEROES_SLOTS = 4;
constexpr int INFO_WINDOW_ARTIFACTS_MAX_ITEMS = 14;
}
VCMI_LIB_NAMESPACE_END

View File

@ -587,7 +587,12 @@ bool JsonParser::error(const std::string & message, bool warning)
std::ostringstream stream;
std::string type(warning ? " warning: " : " error: ");
stream << "At line " << lineCount << ", position " << pos - lineStart << type << message << "\n";
if(!errors.empty())
{
// only add the line breaks between error messages so we don't have a trailing line break
stream << "\n";
}
stream << "At line " << lineCount << ", position " << pos - lineStart << type << message;
errors += stream.str();
return warning;

View File

@ -101,10 +101,10 @@ void CCastleEvent::serializeJson(JsonSerializeFormat & handler)
TerrainTile::TerrainTile():
terType(nullptr),
terView(0),
riverType(VLC->riverTypeHandler->getById(River::NO_RIVER)),
riverDir(0),
roadType(VLC->roadTypeHandler->getById(Road::NO_ROAD)),
terView(0),
riverDir(0),
roadDir(0),
extTileFlags(0),
visitable(false),

View File

@ -230,6 +230,10 @@ public:
MetaString name;
MetaString description;
EMapDifficulty difficulty;
MetaString author;
MetaString authorContact;
MetaString mapVersion;
std::time_t creationDateTime;
/// Specifies the maximum level to reach for a hero. A value of 0 states that there is no
/// maximum level for heroes. This is the default value.
ui8 levelLimit;
@ -263,6 +267,13 @@ public:
h & mods;
h & name;
h & description;
if (h.version >= Handler::Version::MAP_FORMAT_ADDITIONAL_INFOS)
{
h & author;
h & authorContact;
h & mapVersion;
h & creationDateTime;
}
h & width;
h & height;
h & twoLevel;

View File

@ -219,6 +219,10 @@ void CMapLoaderH3M::readHeader()
mapHeader->twoLevel = reader->readBool();
mapHeader->name.appendTextID(readLocalizedString("header.name"));
mapHeader->description.appendTextID(readLocalizedString("header.description"));
mapHeader->author.appendRawString("");
mapHeader->authorContact.appendRawString("");
mapHeader->mapVersion.appendRawString("");
mapHeader->creationDateTime = 0;
mapHeader->difficulty = static_cast<EMapDifficulty>(reader->readInt8Checked(0, 4));
if(features.levelAB)

View File

@ -311,6 +311,10 @@ void CMapFormatJson::serializeHeader(JsonSerializeFormat & handler)
{
handler.serializeStruct("name", mapHeader->name);
handler.serializeStruct("description", mapHeader->description);
handler.serializeStruct("author", mapHeader->author);
handler.serializeStruct("authorContact", mapHeader->authorContact);
handler.serializeStruct("mapVersion", mapHeader->mapVersion);
handler.serializeInt("creationDateTime", mapHeader->creationDateTime, 0);
handler.serializeInt("heroLevelLimit", mapHeader->levelLimit, 0);
//todo: support arbitrary percentage
@ -855,7 +859,6 @@ void CMapLoaderJson::readHeader(const bool complete)
//todo: multilevel map load support
{
auto levels = handler.enterStruct("mapLevels");
{
auto surface = handler.enterStruct("surface");
handler.serializeInt("height", mapHeader->height);

View File

@ -40,7 +40,7 @@ class DLL_LINKAGE INetworkServer : boost::noncopyable
public:
virtual ~INetworkServer() = default;
virtual void start(uint16_t port) = 0;
virtual uint16_t start(uint16_t port) = 0;
};
/// Base interface that must be implemented by user of networking API to handle any connection callbacks

View File

@ -19,16 +19,17 @@ NetworkServer::NetworkServer(INetworkServerListener & listener, const std::share
{
}
void NetworkServer::start(uint16_t port)
uint16_t NetworkServer::start(uint16_t port)
{
acceptor = std::make_shared<NetworkAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
startAsyncAccept();
return startAsyncAccept();
}
void NetworkServer::startAsyncAccept()
uint16_t NetworkServer::startAsyncAccept()
{
auto upcomingConnection = std::make_shared<NetworkSocket>(*io);
acceptor->async_accept(*upcomingConnection, [this, upcomingConnection](const auto & ec) { connectionAccepted(upcomingConnection, ec); });
return acceptor->local_endpoint().port();
}
void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingConnection, const boost::system::error_code & ec)

View File

@ -22,14 +22,14 @@ class NetworkServer : public INetworkConnectionListener, public INetworkServer
INetworkServerListener & listener;
void connectionAccepted(std::shared_ptr<NetworkSocket>, const boost::system::error_code & ec);
void startAsyncAccept();
uint16_t startAsyncAccept();
void onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage) override;
void onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message) override;
public:
NetworkServer(INetworkServerListener & listener, const std::shared_ptr<NetworkContext> & context);
void start(uint16_t port) override;
uint16_t start(uint16_t port) override;
};
VCMI_LIB_NAMESPACE_END

View File

@ -37,6 +37,12 @@ struct ArtifactLocation
, creature(creatureSlot)
{
}
ArtifactLocation(const ObjectInstanceID id, const std::optional<SlotID> creatureSlot, const ArtifactPosition & slot)
: artHolder(id)
, slot(slot)
, creature(creatureSlot)
{
}
template <typename Handler> void serialize(Handler & h)
{

View File

@ -78,7 +78,6 @@ public:
virtual void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) {}
virtual void visitPutArtifact(PutArtifact & pack) {}
virtual void visitEraseArtifact(EraseArtifact & pack) {}
virtual void visitMoveArtifact(MoveArtifact & pack) {}
virtual void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) {}
virtual void visitAssembledArtifact(AssembledArtifact & pack) {}
virtual void visitDisassembledArtifact(DisassembledArtifact & pack) {}

View File

@ -338,11 +338,6 @@ void EraseArtifact::visitTyped(ICPackVisitor & visitor)
visitor.visitEraseArtifact(*this);
}
void MoveArtifact::visitTyped(ICPackVisitor & visitor)
{
visitor.visitMoveArtifact(*this);
}
void BulkMoveArtifacts::visitTyped(ICPackVisitor & visitor)
{
visitor.visitBulkMoveArtifacts(*this);
@ -1794,17 +1789,6 @@ void EraseArtifact::applyGs(CGameState *gs)
art->removeFrom(*artSet, al.slot);
}
void MoveArtifact::applyGs(CGameState * gs)
{
auto srcHero = gs->getArtSet(src);
auto dstHero = gs->getArtSet(dst);
assert(srcHero);
assert(dstHero);
auto art = srcHero->getArt(src.slot);
assert(art && art->canBePutAt(dstHero, dst.slot));
art->move(*srcHero, src.slot, *dstHero, dst.slot);
}
void BulkMoveArtifacts::applyGs(CGameState * gs)
{
const auto bulkArtsRemove = [](std::vector<LinkedSlots> & artsPack, CArtifactSet & artSet)
@ -1861,7 +1845,7 @@ void AssembledArtifact::applyGs(CGameState *gs)
return art->getId() == builtArt->getId();
}));
const auto transformedArtSlot = hero->getSlotByInstance(transformedArt);
const auto transformedArtSlot = hero->getArtPos(transformedArt);
auto * combinedArt = new CArtifactInstance(builtArt);
gs->map->addNewArtifactInstance(combinedArt);

View File

@ -1025,47 +1025,26 @@ struct DLL_LINKAGE EraseArtifact : CArtifactOperationPack
}
};
struct DLL_LINKAGE MoveArtifact : CArtifactOperationPack
{
MoveArtifact() = default;
MoveArtifact(const PlayerColor & interfaceOwner, const ArtifactLocation & src, const ArtifactLocation & dst, bool askAssemble = true)
: interfaceOwner(interfaceOwner), src(src), dst(dst), askAssemble(askAssemble)
{
}
PlayerColor interfaceOwner;
ArtifactLocation src;
ArtifactLocation dst;
bool askAssemble = true;
void applyGs(CGameState * gs);
void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler & h)
{
h & interfaceOwner;
h & src;
h & dst;
h & askAssemble;
}
};
struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
{
struct LinkedSlots
{
ArtifactPosition srcPos;
ArtifactPosition dstPos;
bool askAssemble;
LinkedSlots() = default;
LinkedSlots(const ArtifactPosition & srcPos, const ArtifactPosition & dstPos)
LinkedSlots(const ArtifactPosition & srcPos, const ArtifactPosition & dstPos, bool askAssemble = false)
: srcPos(srcPos)
, dstPos(dstPos)
, askAssemble(askAssemble)
{
}
template <typename Handler> void serialize(Handler & h)
{
h & srcPos;
h & dstPos;
h & askAssemble;
}
};
@ -1079,8 +1058,6 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
: interfaceOwner(PlayerColor::NEUTRAL)
, srcArtHolder(ObjectInstanceID::NONE)
, dstArtHolder(ObjectInstanceID::NONE)
, swap(false)
, askAssemble(false)
, srcCreature(std::nullopt)
, dstCreature(std::nullopt)
{
@ -1089,8 +1066,6 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
: interfaceOwner(interfaceOwner)
, srcArtHolder(srcArtHolder)
, dstArtHolder(dstArtHolder)
, swap(swap)
, askAssemble(false)
, srcCreature(std::nullopt)
, dstCreature(std::nullopt)
{
@ -1100,8 +1075,6 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
std::vector<LinkedSlots> artsPack0;
std::vector<LinkedSlots> artsPack1;
bool swap;
bool askAssemble;
void visitTyped(ICPackVisitor & visitor) override;
@ -1114,8 +1087,6 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
h & dstArtHolder;
h & srcCreature;
h & dstCreature;
h & swap;
h & askAssemble;
}
};

View File

@ -112,7 +112,6 @@ void registerTypesClientPacks(Serializer &s)
s.template registerType<CPackForClient, CArtifactOperationPack>();
s.template registerType<CArtifactOperationPack, PutArtifact>();
s.template registerType<CArtifactOperationPack, EraseArtifact>();
s.template registerType<CArtifactOperationPack, MoveArtifact>();
s.template registerType<CArtifactOperationPack, AssembledArtifact>();
s.template registerType<CArtifactOperationPack, DisassembledArtifact>();
s.template registerType<CArtifactOperationPack, BulkMoveArtifacts>();

View File

@ -137,6 +137,13 @@ std::unique_ptr<CMap> CMapGenerator::generate()
throw;
}
Load::Progress::finish();
map->mapInstance->creationDateTime = std::time(nullptr);
map->mapInstance->author = MetaString::createFromTextID("core.genrltxt.740");
const auto * mapTemplate = mapGenOptions.getMapTemplate();
if(mapTemplate)
map->mapInstance->mapVersion = MetaString::createFromRawString(mapTemplate->getName());
return std::move(map->mapInstance);
}

View File

@ -55,6 +55,7 @@ enum class ESerializationVersion : int32_t
COMPACT_INTEGER_SERIALIZATION, // 845 - serialize integers in forms similar to protobuf
REMOVE_FOG_OF_WAR_POINTER, // 846 - fog of war is serialized as reference instead of pointer
SIMPLE_TEXT_CONTAINER_SERIALIZATION, // 847 - text container is serialized using common routine instead of custom approach
MAP_FORMAT_ADDITIONAL_INFOS, // 848 - serialize new infos in map format
CURRENT = SIMPLE_TEXT_CONTAINER_SERIALIZATION
CURRENT = MAP_FORMAT_ADDITIONAL_INFOS
};

View File

@ -29,6 +29,10 @@ void GeneralSettings::initialize(MapController & c)
AbstractSettings::initialize(c);
ui->mapNameEdit->setText(QString::fromStdString(controller->map()->name.toString()));
ui->mapDescriptionEdit->setPlainText(QString::fromStdString(controller->map()->description.toString()));
ui->authorEdit->setText(QString::fromStdString(controller->map()->author.toString()));
ui->authorContactEdit->setText(QString::fromStdString(controller->map()->authorContact.toString()));
ui->mapCreationDateTimeEdit->setDateTime(QDateTime::fromSecsSinceEpoch(controller->map()->creationDateTime));
ui->mapVersionEdit->setText(QString::fromStdString(controller->map()->mapVersion.toString()));
ui->heroLevelLimit->setValue(controller->map()->levelLimit);
ui->heroLevelLimitCheck->setChecked(controller->map()->levelLimit);
@ -61,6 +65,10 @@ void GeneralSettings::update()
{
controller->map()->name = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "name"), ui->mapNameEdit->text().toStdString()));
controller->map()->description = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "description"), ui->mapDescriptionEdit->toPlainText().toStdString()));
controller->map()->author = MetaString::createFromRawString(ui->authorEdit->text().toStdString());
controller->map()->authorContact = MetaString::createFromRawString(ui->authorContactEdit->text().toStdString());
controller->map()->creationDateTime = ui->mapCreationDateTimeEdit->dateTime().toSecsSinceEpoch();
controller->map()->mapVersion = MetaString::createFromRawString(ui->mapVersionEdit->text().toStdString());
if(ui->heroLevelLimitCheck->isChecked())
controller->map()->levelLimit = ui->heroLevelLimit->value();
else

View File

@ -46,6 +46,50 @@
<item>
<widget class="QPlainTextEdit" name="mapDescriptionEdit"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Author</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="authorEdit"/>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Author contact (e.g. email)</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="authorContactEdit"/>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Map Creation Time</string>
</property>
</widget>
</item>
<item>
<widget class="QDateTimeEdit" name="mapCreationDateTimeEdit">
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Map Version</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mapVersionEdit"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="topMargin">

View File

@ -201,6 +201,7 @@ std::unique_ptr<CMap> generateEmptyMap(CMapGenOptions & options)
{
auto map = std::make_unique<CMap>(nullptr);
map->version = EMapFormat::VCMI;
map->creationDateTime = std::time(nullptr);
map->width = options.getWidth();
map->height = options.getHeight();
map->twoLevel = options.getHasTwoLevels();

View File

@ -2779,7 +2779,6 @@ bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocati
// Previous artifact must be swapped
COMPLAIN_RET_FALSE_IF(!dstArtifact->canBePutAt(srcArtSet, src.slot, true), "Cannot swap artifacts!");
ma.artsPack1.push_back(BulkMoveArtifacts::LinkedSlots(dstSlot, src.slot));
ma.swap = true;
}
auto hero = getHero(dst.artHolder);
@ -2788,7 +2787,7 @@ bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocati
ma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(src.slot, dstSlot));
if(src.artHolder != dst.artHolder)
ma.askAssemble = true;
ma.artsPack0.back().askAssemble = true;
sendAndApply(&ma);
return true;
}
@ -2896,7 +2895,7 @@ bool CGameHandler::bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceI
bool CGameHandler::scrollBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, bool left)
{
auto artSet = getArtSet(heroID);
const auto artSet = getArtSet(heroID);
COMPLAIN_RET_FALSE_IF(artSet == nullptr, "scrollBackpackArtifacts: wrong hero's ID");
BulkMoveArtifacts bma(player, heroID, heroID, false);
@ -2961,7 +2960,7 @@ bool CGameHandler::switchArtifactsCostume(const PlayerColor & player, const Obje
{
bma.artsPack0.emplace_back(BulkMoveArtifacts::LinkedSlots
{
artSet->getSlotByInstance(artFittingSet.getArt(availableArts.front())),
artSet->getArtPos(artFittingSet.getArt(availableArts.front())),
artPos.first
});
artFittingSet.removeArtifact(availableArts.front());
@ -3926,7 +3925,7 @@ bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * h
int expToGive;
m->getOffer(art->getTypeId(), 0, dmp, expToGive, EMarketMode::ARTIFACT_EXP);
expSum += expToGive;
removeArtifact(ArtifactLocation(altarObj->id, altarObj->getSlotByInstance(art)));
removeArtifact(ArtifactLocation(altarObj->id, altarObj->getArtPos(art)));
}
else
{

View File

@ -118,7 +118,7 @@ public:
}
};
CVCMIServer::CVCMIServer(uint16_t port, bool connectToLobby, bool runByClient)
CVCMIServer::CVCMIServer(uint16_t port, bool runByClient)
: currentClientId(1)
, currentPlayerId(1)
, port(port)
@ -130,22 +130,30 @@ CVCMIServer::CVCMIServer(uint16_t port, bool connectToLobby, bool runByClient)
registerTypesLobbyPacks(*applier);
networkHandler = INetworkHandler::createHandler();
if(connectToLobby)
lobbyProcessor = std::make_unique<GlobalLobbyProcessor>(*this);
else
startAcceptingIncomingConnections();
}
CVCMIServer::~CVCMIServer() = default;
void CVCMIServer::startAcceptingIncomingConnections()
{
logNetwork->info("Port %d will be used", port);
uint16_t CVCMIServer::prepare(bool connectToLobby) {
if(connectToLobby) {
lobbyProcessor = std::make_unique<GlobalLobbyProcessor>(*this);
return 0;
} else {
return startAcceptingIncomingConnections();
}
}
uint16_t CVCMIServer::startAcceptingIncomingConnections()
{
port
? logNetwork->info("Port %d will be used", port)
: logNetwork->info("Randomly assigned port will be used");
// config port may be 0 => srvport will contain the OS-assigned port value
networkServer = networkHandler->createServerTCP(*this);
networkServer->start(port);
logNetwork->info("Listening for connections at port %d", port);
auto srvport = networkServer->start(port);
logNetwork->info("Listening for connections at port %d", srvport);
return srvport;
}
void CVCMIServer::onNewConnection(const std::shared_ptr<INetworkConnection> & connection)

View File

@ -66,6 +66,8 @@ public:
/// List of all active connections
std::vector<std::shared_ptr<CConnection>> activeConnections;
uint16_t prepare(bool connectToLobby);
// INetworkListener impl
void onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage) override;
void onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message) override;
@ -74,7 +76,7 @@ public:
std::shared_ptr<CGameHandler> gh;
CVCMIServer(uint16_t port, bool connectToLobby, bool runByClient);
CVCMIServer(uint16_t port, bool runByClient);
~CVCMIServer();
void run();
@ -83,7 +85,7 @@ public:
bool prepareToStartGame();
void prepareToRestart();
void startGameImmediately();
void startAcceptingIncomingConnections();
uint16_t startAcceptingIncomingConnections();
void threadHandleClient(std::shared_ptr<CConnection> c);

View File

@ -323,176 +323,9 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
CasualtiesAfterBattle cab1(battle, BattleSide::ATTACKER);
CasualtiesAfterBattle cab2(battle, BattleSide::DEFENDER);
ChangeSpells cs; //for Eagle Eye
if(!finishingBattle->isDraw() && finishingBattle->winnerHero)
{
if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT))
{
double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE);
for(auto & spellId : battle.getBattle()->getUsedSpells(battle.otherSide(battleResult->winner)))
{
auto spell = spellId.toEntity(VLC->spells());
if(spell && spell->getLevel() <= eagleEyeLevel && !finishingBattle->winnerHero->spellbookContainsSpell(spell->getId()) && gameHandler->getRandomGenerator().nextInt(99) < eagleEyeChance)
cs.spells.insert(spell->getId());
}
}
}
std::vector<const CArtifactInstance *> arts; //display them in window
if(result == EBattleResult::NORMAL && !finishingBattle->isDraw() && finishingBattle->winnerHero)
{
auto sendMoveArtifact = [&](const CArtifactInstance *art, MoveArtifact *ma)
{
const auto slot = ArtifactUtils::getArtAnyPosition(finishingBattle->winnerHero, art->getTypeId());
if(slot != ArtifactPosition::PRE_FIRST)
{
arts.push_back(art);
ma->dst = ArtifactLocation(finishingBattle->winnerHero->id, slot);
if(ArtifactUtils::isSlotBackpack(slot))
ma->askAssemble = false;
gameHandler->sendAndApply(ma);
}
};
if (finishingBattle->loserHero)
{
//TODO: wrap it into a function, somehow (std::variant -_-)
auto artifactsWorn = finishingBattle->loserHero->artifactsWorn;
for (auto artSlot : artifactsWorn)
{
MoveArtifact ma;
ma.src = ArtifactLocation(finishingBattle->loserHero->id, artSlot.first);
const CArtifactInstance * art = finishingBattle->loserHero->getArt(artSlot.first);
if (art && !art->artType->isBig() &&
art->artType->getId() != ArtifactID::SPELLBOOK)
// don't move war machines or locked arts (spellbook)
{
sendMoveArtifact(art, &ma);
}
}
for(int slotNumber = finishingBattle->loserHero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--)
{
//we assume that no big artifacts can be found
MoveArtifact ma;
ma.src = ArtifactLocation(finishingBattle->loserHero->id,
ArtifactPosition(ArtifactPosition::BACKPACK_START + slotNumber)); //backpack automatically shifts arts to beginning
const CArtifactInstance * art = finishingBattle->loserHero->getArt(ArtifactPosition::BACKPACK_START + slotNumber);
if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won
{
sendMoveArtifact(art, &ma);
}
}
if (finishingBattle->loserHero->commander) //TODO: what if commanders belong to no hero?
{
artifactsWorn = finishingBattle->loserHero->commander->artifactsWorn;
for (auto artSlot : artifactsWorn)
{
MoveArtifact ma;
ma.src = ArtifactLocation(finishingBattle->loserHero->id, artSlot.first);
ma.src.creature = finishingBattle->loserHero->findStack(finishingBattle->loserHero->commander);
const auto art = finishingBattle->loserHero->commander->getArt(artSlot.first);
if (art && !art->artType->isBig())
{
sendMoveArtifact(art, &ma);
}
}
}
}
auto loser = battle.otherSide(battleResult->winner);
for (auto armySlot : battle.battleGetArmyObject(loser)->stacks)
{
auto artifactsWorn = armySlot.second->artifactsWorn;
for(const auto & artSlot : artifactsWorn)
{
MoveArtifact ma;
ma.src = ArtifactLocation(finishingBattle->loserHero->id, artSlot.first);
ma.src.creature = finishingBattle->loserHero->findStack(finishingBattle->loserHero->commander);
const auto art = finishingBattle->loserHero->commander->getArt(artSlot.first);
if (art && !art->artType->isBig())
{
sendMoveArtifact(art, &ma);
}
}
}
}
if (arts.size()) //display loot
{
InfoWindow iw;
iw.player = finishingBattle->winnerHero->tempOwner;
iw.text.appendLocalString (EMetaText::GENERAL_TXT, 30); //You have captured enemy artifact
for (auto art : arts) //TODO; separate function to display loot for various objects?
{
if (art->artType->getId() == ArtifactID::SPELL_SCROLL)
iw.components.emplace_back(ComponentType::SPELL_SCROLL, art->getScrollSpellID());
else
iw.components.emplace_back(ComponentType::ARTIFACT, art->artType->getId());
if (iw.components.size() >= 14)
{
gameHandler->sendAndApply(&iw);
iw.components.clear();
}
}
if (iw.components.size())
{
gameHandler->sendAndApply(&iw);
}
}
//Eagle Eye secondary skill handling
if (!cs.spells.empty())
{
cs.learn = 1;
cs.hid = finishingBattle->winnerHero->id;
InfoWindow iw;
iw.player = finishingBattle->winnerHero->tempOwner;
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
iw.text.replaceRawString(finishingBattle->winnerHero->getNameTranslated());
std::ostringstream names;
for (int i = 0; i < cs.spells.size(); i++)
{
names << "%s";
if (i < cs.spells.size() - 2)
names << ", ";
else if (i < cs.spells.size() - 1)
names << "%s";
}
names << ".";
iw.text.replaceRawString(names.str());
auto it = cs.spells.begin();
for (int i = 0; i < cs.spells.size(); i++, it++)
{
iw.text.replaceName(*it);
if (i == cs.spells.size() - 2) //we just added pre-last name
iw.text.replaceLocalString(EMetaText::GENERAL_TXT, 141); // " and "
iw.components.emplace_back(ComponentType::SPELL, *it);
}
gameHandler->sendAndApply(&iw);
gameHandler->sendAndApply(&cs);
}
cab1.updateArmy(gameHandler);
cab2.updateArmy(gameHandler); //take casualties after battle is deleted
if(finishingBattle->loserHero) //remove beaten hero
{
RemoveObject ro(finishingBattle->loserHero->id, finishingBattle->victor);
gameHandler->sendAndApply(&ro);
}
if(finishingBattle->isDraw() && finishingBattle->winnerHero) //for draw case both heroes should be removed
{
RemoveObject ro(finishingBattle->winnerHero->id, finishingBattle->loser);
gameHandler->sendAndApply(&ro);
}
if(battleResult->winner == BattleSide::DEFENDER
&& finishingBattle->winnerHero
&& finishingBattle->winnerHero->visitedTown
@ -505,6 +338,160 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
if(!finishingBattle->isDraw() && battleResult->exp[finishingBattle->winnerSide] && finishingBattle->winnerHero)
gameHandler->giveExperience(finishingBattle->winnerHero, battleResult->exp[finishingBattle->winnerSide]);
// Eagle Eye handling
if(!finishingBattle->isDraw() && finishingBattle->winnerHero)
{
ChangeSpells spells;
if(auto eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT))
{
auto eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE);
for(auto & spellId : battle.getBattle()->getUsedSpells(battle.otherSide(battleResult->winner)))
{
auto spell = spellId.toEntity(VLC->spells());
if(spell
&& spell->getLevel() <= eagleEyeLevel
&& !finishingBattle->winnerHero->spellbookContainsSpell(spell->getId())
&& gameHandler->getRandomGenerator().nextInt(99) < eagleEyeChance)
{
spells.spells.insert(spell->getId());
}
}
}
if(!spells.spells.empty())
{
spells.learn = 1;
spells.hid = finishingBattle->winnerHero->id;
InfoWindow iw;
iw.player = finishingBattle->winnerHero->tempOwner;
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
iw.text.replaceRawString(finishingBattle->winnerHero->getNameTranslated());
std::ostringstream names;
for(int i = 0; i < spells.spells.size(); i++)
{
names << "%s";
if(i < spells.spells.size() - 2)
names << ", ";
else if(i < spells.spells.size() - 1)
names << "%s";
}
names << ".";
iw.text.replaceRawString(names.str());
auto it = spells.spells.begin();
for(int i = 0; i < spells.spells.size(); i++, it++)
{
iw.text.replaceName(*it);
if(i == spells.spells.size() - 2) //we just added pre-last name
iw.text.replaceLocalString(EMetaText::GENERAL_TXT, 141); // " and "
iw.components.emplace_back(ComponentType::SPELL, *it);
}
gameHandler->sendAndApply(&iw);
gameHandler->sendAndApply(&spells);
}
}
// Artifacts handling
if(result == EBattleResult::NORMAL && !finishingBattle->isDraw() && finishingBattle->winnerHero)
{
std::vector<const CArtifactInstance*> arts; // display them in window
CArtifactFittingSet artFittingSet(*finishingBattle->winnerHero);
const auto addArtifactToTransfer = [&artFittingSet, &arts](BulkMoveArtifacts & pack, const ArtifactPosition & srcSlot, const CArtifactInstance * art)
{
assert(art);
const auto dstSlot = ArtifactUtils::getArtAnyPosition(&artFittingSet, art->getTypeId());
if(dstSlot != ArtifactPosition::PRE_FIRST)
{
pack.artsPack0.emplace_back(BulkMoveArtifacts::LinkedSlots(srcSlot, dstSlot));
if(ArtifactUtils::isSlotEquipment(dstSlot))
pack.artsPack0.back().askAssemble = true;
arts.emplace_back(art);
artFittingSet.putArtifact(dstSlot, const_cast<CArtifactInstance*>(art));
}
};
const auto sendArtifacts = [this](BulkMoveArtifacts & bma)
{
if(!bma.artsPack0.empty())
gameHandler->sendAndApply(&bma);
};
BulkMoveArtifacts packHero(finishingBattle->winnerHero->getOwner(), ObjectInstanceID::NONE, finishingBattle->winnerHero->id, false);
if(finishingBattle->loserHero)
{
packHero.srcArtHolder = finishingBattle->loserHero->id;
for(const auto & artSlot : finishingBattle->loserHero->artifactsWorn)
{
if(ArtifactUtils::isArtRemovable(artSlot))
addArtifactToTransfer(packHero, artSlot.first, artSlot.second.getArt());
}
for(const auto & artSlot : finishingBattle->loserHero->artifactsInBackpack)
{
if(const auto art = artSlot.getArt(); art->getTypeId() != ArtifactID::GRAIL)
addArtifactToTransfer(packHero, finishingBattle->loserHero->getArtPos(art), art);
}
if(finishingBattle->loserHero->commander)
{
BulkMoveArtifacts packCommander(finishingBattle->winnerHero->getOwner(), finishingBattle->loserHero->id, finishingBattle->winnerHero->id, false);
packCommander.srcCreature = finishingBattle->loserHero->findStack(finishingBattle->loserHero->commander);
for(const auto & artSlot : finishingBattle->loserHero->commander->artifactsWorn)
addArtifactToTransfer(packCommander, artSlot.first, artSlot.second.getArt());
sendArtifacts(packCommander);
}
}
auto armyObj = battle.battleGetArmyObject(battle.otherSide(battleResult->winner));
for(const auto & armySlot : armyObj->stacks)
{
BulkMoveArtifacts packsArmy(finishingBattle->winnerHero->getOwner(), finishingBattle->loserHero->id, finishingBattle->winnerHero->id, false);
packsArmy.srcArtHolder = armyObj->id;
packsArmy.srcCreature = armySlot.first;
for(const auto & artSlot : armySlot.second->artifactsWorn)
addArtifactToTransfer(packsArmy, artSlot.first, armySlot.second->getArt(artSlot.first));
sendArtifacts(packsArmy);
}
// Display loot
if(!arts.empty())
{
InfoWindow iw;
iw.player = finishingBattle->winnerHero->tempOwner;
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 30); //You have captured enemy artifact
for(const auto art : arts) //TODO; separate function to display loot for various objects?
{
if(art->isScroll())
iw.components.emplace_back(ComponentType::SPELL_SCROLL, art->getScrollSpellID());
else
iw.components.emplace_back(ComponentType::ARTIFACT, art->getTypeId());
if(iw.components.size() >= GameConstants::INFO_WINDOW_ARTIFACTS_MAX_ITEMS)
{
gameHandler->sendAndApply(&iw);
iw.components.clear();
}
}
gameHandler->sendAndApply(&iw);
}
if(!packHero.artsPack0.empty())
sendArtifacts(packHero);
}
// Remove beaten hero
if(finishingBattle->loserHero)
{
RemoveObject ro(finishingBattle->loserHero->id, finishingBattle->victor);
gameHandler->sendAndApply(&ro);
}
// For draw case both heroes should be removed
if(finishingBattle->isDraw() && finishingBattle->winnerHero)
{
RemoveObject ro(finishingBattle->winnerHero->id, finishingBattle->loser);
gameHandler->sendAndApply(&ro);
}
BattleResultAccepted raccepted;
raccepted.battleID = battle.getBattle()->getBattleID();
raccepted.heroResult[0].army = const_cast<CArmedInstance*>(battle.battleGetArmyObject(BattleSide::ATTACKER));
@ -513,7 +500,7 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
raccepted.heroResult[1].hero = const_cast<CGHeroInstance*>(battle.battleGetFightingHero(BattleSide::DEFENDER));
raccepted.heroResult[0].exp = battleResult->exp[0];
raccepted.heroResult[1].exp = battleResult->exp[1];
raccepted.winnerSide = finishingBattle->winnerSide;
raccepted.winnerSide = finishingBattle->winnerSide;
gameHandler->sendAndApply(&raccepted);
gameHandler->queries->popIfTop(battleQuery);

View File

@ -37,10 +37,10 @@ public:
class CBattleDialogQuery : public CDialogQuery
{
bool resultProcessed = false;
public:
CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * Bi, std::optional<BattleResult> Br);
const IBattleInfo * bi;
std::optional<BattleResult> result;
public:
CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * Bi, std::optional<BattleResult> Br);
void onRemoval(PlayerColor color) override;
};

View File

@ -17,24 +17,6 @@
#include "../../lib/serializer/Cast.h"
#include "../../lib/networkPacks/PacksForServer.h"
template <typename Container>
std::string formatContainer(const Container & c, std::string delimiter = ", ", std::string opener = "(", std::string closer=")")
{
std::string ret = opener;
auto itr = std::begin(c);
if(itr != std::end(c))
{
ret += std::to_string(*itr);
while(++itr != std::end(c))
{
ret += delimiter;
ret += std::to_string(*itr);
}
}
ret += closer;
return ret;
}
std::ostream & operator<<(std::ostream & out, const CQuery & query)
{
return out << query.toString();

View File

@ -15,6 +15,7 @@
#include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/VCMIDirs.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/CConfigHandler.h"
#include <boost/program_options.hpp>
@ -86,12 +87,12 @@ int main(int argc, const char * argv[])
{
bool connectToLobby = opts.count("lobby");
bool runByClient = opts.count("runByClient");
uint16_t port = 3030;
uint16_t port = settings["server"]["localPort"].Integer();
if(opts.count("port"))
port = opts["port"].as<uint16_t>();
CVCMIServer server(port, connectToLobby, runByClient);
CVCMIServer server(port, runByClient);
server.prepare(connectToLobby);
server.run();
// CVCMIServer destructor must be called here - before VLC cleanup