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:
commit
b0f37748d6
1
.gitignore
vendored
1
.gitignore
vendored
@ -43,6 +43,7 @@ VCMI_VS11.sdf
|
||||
*.ipch
|
||||
VCMI_VS11.opensdf
|
||||
.DS_Store
|
||||
.directory
|
||||
CMakeUserPresets.json
|
||||
compile_commands.json
|
||||
fuzzylite.pc
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:",
|
||||
|
@ -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:",
|
||||
|
166
client/ArtifactsUIController.cpp
Normal file
166
client/ArtifactsUIController.cpp
Normal 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();
|
||||
}
|
42
client/ArtifactsUIController.h
Normal file
42
client/ArtifactsUIController.h
Normal 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();
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -196,11 +196,13 @@ MainWindow::~MainWindow()
|
||||
|
||||
void MainWindow::on_startGameButton_clicked()
|
||||
{
|
||||
hide();
|
||||
startGame({});
|
||||
}
|
||||
|
||||
void MainWindow::on_startEditorButton_clicked()
|
||||
{
|
||||
hide();
|
||||
startEditor({});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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){};
|
||||
|
@ -49,7 +49,7 @@ private:
|
||||
REPLACE_TEXTID_STRING,
|
||||
REPLACE_NUMBER,
|
||||
REPLACE_POSITIVE_NUMBER,
|
||||
APPEND_EOL
|
||||
APPEND_EOL
|
||||
};
|
||||
|
||||
std::vector<EMessage> message;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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) {}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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>();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user