mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-07 07:10:04 +02:00
Merge pull request #2868 from IvanSavenko/simultaneous_turns
Simultaneous turns
This commit is contained in:
commit
94dbde05a0
@ -41,12 +41,10 @@ bool CCallback::moveHero(const CGHeroInstance *h, int3 dst, bool transit)
|
||||
|
||||
int CCallback::selectionMade(int selection, QueryID queryID)
|
||||
{
|
||||
JsonNode reply(JsonNode::JsonType::DATA_INTEGER);
|
||||
reply.Integer() = selection;
|
||||
return sendQueryReply(reply, queryID);
|
||||
return sendQueryReply(selection, queryID);
|
||||
}
|
||||
|
||||
int CCallback::sendQueryReply(const JsonNode & reply, QueryID queryID)
|
||||
int CCallback::sendQueryReply(std::optional<int32_t> reply, QueryID queryID)
|
||||
{
|
||||
ASSERT_IF_CALLED_WITH_PLAYER
|
||||
if(queryID == QueryID(-1))
|
||||
|
@ -82,7 +82,7 @@ public:
|
||||
virtual void trade(const IMarket * market, EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
|
||||
|
||||
virtual int selectionMade(int selection, QueryID queryID) =0;
|
||||
virtual int sendQueryReply(const JsonNode & reply, QueryID queryID) =0;
|
||||
virtual int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) =0;
|
||||
virtual int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)=0;//swaps creatures between two possibly different garrisons // TODO: AI-unsafe code - fix it!
|
||||
virtual int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)=0;//joins first stack to the second (creatures must be same type)
|
||||
virtual int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) =0; //first goes to the second
|
||||
@ -159,7 +159,7 @@ public:
|
||||
bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false) override; //dst must be free, neighbouring tile (this function can move hero only by one tile)
|
||||
bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where);
|
||||
int selectionMade(int selection, QueryID queryID) override;
|
||||
int sendQueryReply(const JsonNode & reply, QueryID queryID) override;
|
||||
int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) override;
|
||||
int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override;
|
||||
int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second
|
||||
int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second
|
||||
|
@ -167,47 +167,61 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
|
||||
CCS->musich->loadTerrainMusicThemes();
|
||||
|
||||
initializeHeroTownList();
|
||||
|
||||
// always recreate advmap interface to avoid possible memory-corruption bugs
|
||||
adventureInt.reset(new AdventureMapInterface());
|
||||
}
|
||||
|
||||
void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
if (player == playerID)
|
||||
{
|
||||
makingTurn = false;
|
||||
|
||||
// remove all active dialogs that do not expect query answer
|
||||
for (;;)
|
||||
{
|
||||
auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>();
|
||||
auto infoWindow = GH.windows().topWindow<CInfoWindow>();
|
||||
|
||||
if(adventureWindow != nullptr)
|
||||
break;
|
||||
|
||||
if(infoWindow && infoWindow->ID != QueryID::NONE)
|
||||
break;
|
||||
|
||||
if (infoWindow)
|
||||
infoWindow->close();
|
||||
else
|
||||
GH.windows().popWindows(1);
|
||||
}
|
||||
|
||||
// remove all pending dialogs that do not expect query answer
|
||||
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
|
||||
return window->ID == QueryID::NONE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CPlayerInterface::playerStartsTurn(PlayerColor player)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
|
||||
movementController->onPlayerTurnStarted();
|
||||
|
||||
if(GH.windows().findWindows<AdventureMapInterface>().empty())
|
||||
{
|
||||
// after map load - remove all active windows and replace them with adventure map
|
||||
// always recreate advmap interface to avoid possible memory-corruption bugs
|
||||
adventureInt.reset(new AdventureMapInterface());
|
||||
|
||||
GH.windows().clear();
|
||||
GH.windows().pushWindow(adventureInt);
|
||||
}
|
||||
|
||||
// close window from another player
|
||||
if(auto w = GH.windows().topWindow<CInfoWindow>())
|
||||
if(w->ID == QueryID::NONE && player != playerID)
|
||||
w->close();
|
||||
|
||||
// remove all dialogs that do not expect query answer
|
||||
while (!GH.windows().topWindow<AdventureMapInterface>() && !GH.windows().topWindow<CInfoWindow>())
|
||||
GH.windows().popWindows(1);
|
||||
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
if (player != playerID && LOCPLINT == this)
|
||||
{
|
||||
waitWhileDialog();
|
||||
|
||||
bool isHuman = cb->getStartInfo()->playerInfos.count(player) && cb->getStartInfo()->playerInfos.at(player).isControlledByHuman();
|
||||
|
||||
adventureInt->onEnemyTurnStarted(player, isHuman);
|
||||
if (makingTurn == false)
|
||||
adventureInt->onEnemyTurnStarted(player, isHuman);
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,6 +349,7 @@ void CPlayerInterface::acceptTurn(QueryID queryID)
|
||||
}
|
||||
|
||||
cb->selectionMade(0, queryID);
|
||||
movementController->onPlayerTurnStarted();
|
||||
}
|
||||
|
||||
void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
|
||||
@ -1058,15 +1073,12 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
|
||||
|
||||
auto selectCallback = [=](int selection)
|
||||
{
|
||||
JsonNode reply(JsonNode::JsonType::DATA_INTEGER);
|
||||
reply.Integer() = selection;
|
||||
cb->sendQueryReply(reply, askID);
|
||||
cb->sendQueryReply(selection, askID);
|
||||
};
|
||||
|
||||
auto cancelCallback = [=]()
|
||||
{
|
||||
JsonNode reply(JsonNode::JsonType::DATA_NULL);
|
||||
cb->sendQueryReply(reply, askID);
|
||||
cb->sendQueryReply(std::nullopt, askID);
|
||||
};
|
||||
|
||||
const std::string localTitle = title.toString();
|
||||
|
@ -477,6 +477,13 @@ void CServerHandler::setDifficulty(int to) const
|
||||
sendLobbyPack(lsd);
|
||||
}
|
||||
|
||||
void CServerHandler::setSimturnsInfo(const SimturnsInfo & info) const
|
||||
{
|
||||
LobbySetSimturns pack;
|
||||
pack.simturnsInfo = info;
|
||||
sendLobbyPack(pack);
|
||||
}
|
||||
|
||||
void CServerHandler::setTurnTimerInfo(const TurnTimerInfo & info) const
|
||||
{
|
||||
LobbySetTurnTime lstt;
|
||||
|
@ -66,6 +66,7 @@ public:
|
||||
virtual void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const = 0;
|
||||
virtual void setDifficulty(int to) const = 0;
|
||||
virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0;
|
||||
virtual void setSimturnsInfo(const SimturnsInfo &) const = 0;
|
||||
virtual void sendMessage(const std::string & txt) const = 0;
|
||||
virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
|
||||
virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
|
||||
@ -148,6 +149,7 @@ public:
|
||||
void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const override;
|
||||
void setDifficulty(int to) const override;
|
||||
void setTurnTimerInfo(const TurnTimerInfo &) const override;
|
||||
void setSimturnsInfo(const SimturnsInfo &) const override;
|
||||
void sendMessage(const std::string & txt) const override;
|
||||
void sendGuiAction(ui8 action) const override;
|
||||
void sendRestartGame() const override;
|
||||
|
@ -71,7 +71,7 @@ std::vector<AdventureMapShortcutState> AdventureMapShortcuts::getShortcuts()
|
||||
{ EShortcut::ADVENTURE_GAME_OPTIONS, optionInMapView(), [this]() { this->adventureOptions(); } },
|
||||
{ EShortcut::GLOBAL_OPTIONS, optionInMapView(), [this]() { this->systemOptions(); } },
|
||||
{ EShortcut::ADVENTURE_NEXT_HERO, optionHasNextHero(), [this]() { this->nextHero(); } },
|
||||
{ EShortcut::GAME_END_TURN, optionInMapView(), [this]() { this->endTurn(); } },
|
||||
{ EShortcut::GAME_END_TURN, optionCanEndTurn(), [this]() { this->endTurn(); } },
|
||||
{ EShortcut::ADVENTURE_THIEVES_GUILD, optionInMapView(), [this]() { this->showThievesGuild(); } },
|
||||
{ EShortcut::ADVENTURE_VIEW_SCENARIO, optionInMapView(), [this]() { this->showScenarioInfo(); } },
|
||||
{ EShortcut::GAME_SAVE_GAME, optionInMapView(), [this]() { this->saveGame(); } },
|
||||
@ -453,6 +453,11 @@ bool AdventureMapShortcuts::optionHasNextHero()
|
||||
return optionInMapView() && nextSuitableHero != nullptr;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionCanEndTurn()
|
||||
{
|
||||
return optionInMapView() && LOCPLINT->makingTurn;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionSpellcasting()
|
||||
{
|
||||
return state == EAdventureState::CASTING_SPELL;
|
||||
|
@ -78,6 +78,7 @@ public:
|
||||
bool optionHeroCanMove();
|
||||
bool optionHasNextHero();
|
||||
bool optionCanVisitObject();
|
||||
bool optionCanEndTurn();
|
||||
bool optionSpellcasting();
|
||||
bool optionInMapView();
|
||||
bool optionInWorldView();
|
||||
|
@ -59,6 +59,7 @@ public:
|
||||
std::shared_ptr<CButton> buttonOptions;
|
||||
std::shared_ptr<CButton> buttonStart;
|
||||
std::shared_ptr<CButton> buttonBack;
|
||||
std::shared_ptr<CButton> buttonSimturns;
|
||||
|
||||
std::shared_ptr<SelectionTab> tabSel;
|
||||
std::shared_ptr<OptionsTab> tabOpt;
|
||||
|
@ -58,6 +58,12 @@ OptionsTab::OptionsTab() : humanPlayers(0)
|
||||
CSH->setTurnTimerInfo(tinfo);
|
||||
}
|
||||
});
|
||||
|
||||
addCallback("setSimturnDuration", [&](int index){
|
||||
SimturnsInfo info;
|
||||
info.optionalTurns = index;
|
||||
CSH->setSimturnsInfo(info);
|
||||
});
|
||||
|
||||
//helper function to parse string containing time to integer reflecting time in seconds
|
||||
//assumed that input string can be modified by user, function shall support user's intention
|
||||
@ -214,6 +220,18 @@ void OptionsTab::recreate()
|
||||
|
||||
entries.insert(std::make_pair(pInfo.first, std::make_shared<PlayerOptionsEntry>(pInfo.second, * this)));
|
||||
}
|
||||
|
||||
//Simultaneous turns
|
||||
if(auto turnSlider = widget<CSlider>("labelSimturnsDurationValue"))
|
||||
turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns);
|
||||
|
||||
if(auto w = widget<CLabel>("labelSimturnsDurationValue"))
|
||||
{
|
||||
MetaString message;
|
||||
message.appendRawString("Simturns: up to %d days");
|
||||
message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns);
|
||||
w->setText(message.toString());
|
||||
}
|
||||
|
||||
const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo;
|
||||
|
||||
|
@ -73,16 +73,41 @@
|
||||
"adoptHeight": true
|
||||
},
|
||||
|
||||
// timer
|
||||
{
|
||||
"name": "simturnsDuration",
|
||||
"type": "slider",
|
||||
"orientation": "horizontal",
|
||||
"position": {"x": 55, "y": 537},
|
||||
"size": 194,
|
||||
"callback": "setSimturnDuration",
|
||||
"itemsVisible": 1,
|
||||
"itemsTotal": 28,
|
||||
"selected": 0,
|
||||
"style": "blue",
|
||||
"scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32},
|
||||
"panningStep": 20
|
||||
},
|
||||
|
||||
{
|
||||
"name": "labelSimturnsDurationValue",
|
||||
"type": "label",
|
||||
"font": "small",
|
||||
"alignment": "center",
|
||||
"color": "yellow",
|
||||
"text": "core.genrltxt.521",
|
||||
"position": {"x": 222, "y": 544}
|
||||
"color": "white",
|
||||
"text": "",
|
||||
"position": {"x": 319, "y": 545}
|
||||
},
|
||||
|
||||
// timer
|
||||
//{
|
||||
// "type": "label",
|
||||
// "font": "small",
|
||||
// "alignment": "center",
|
||||
// "color": "yellow",
|
||||
// "text": "core.genrltxt.521",
|
||||
// "position": {"x": 222, "y": 544}
|
||||
//},
|
||||
|
||||
{
|
||||
"name": "labelTurnDurationValue",
|
||||
"type": "label",
|
||||
@ -104,7 +129,8 @@
|
||||
"itemsTotal": 11,
|
||||
"selected": 11,
|
||||
"style": "blue",
|
||||
"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43},
|
||||
"scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32},
|
||||
//"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43},
|
||||
"panningStep": 20
|
||||
},
|
||||
],
|
||||
|
@ -158,6 +158,7 @@ public:
|
||||
virtual void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) {}
|
||||
virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) {}
|
||||
virtual void visitLobbySetPlayer(LobbySetPlayer & pack) {}
|
||||
virtual void visitLobbySetSimturns(LobbySetSimturns & pack) {}
|
||||
virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) {}
|
||||
virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) {}
|
||||
virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {}
|
||||
|
@ -2609,14 +2609,14 @@ struct DLL_LINKAGE BuildBoat : public CPackForServer
|
||||
struct DLL_LINKAGE QueryReply : public CPackForServer
|
||||
{
|
||||
QueryReply() = default;
|
||||
QueryReply(const QueryID & QID, const JsonNode & Reply)
|
||||
QueryReply(const QueryID & QID, std::optional<int32_t> Reply)
|
||||
: qid(QID)
|
||||
, reply(Reply)
|
||||
{
|
||||
}
|
||||
QueryID qid;
|
||||
PlayerColor player;
|
||||
JsonNode reply;
|
||||
std::optional<int32_t> reply;
|
||||
|
||||
virtual void visitTyped(ICPackVisitor & visitor) override;
|
||||
|
||||
|
@ -750,6 +750,11 @@ void LobbySetPlayer::visitTyped(ICPackVisitor & visitor)
|
||||
visitor.visitLobbySetPlayer(*this);
|
||||
}
|
||||
|
||||
void LobbySetSimturns::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitLobbySetSimturns(*this);
|
||||
}
|
||||
|
||||
void LobbySetTurnTime::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitLobbySetTurnTime(*this);
|
||||
|
@ -250,6 +250,18 @@ struct DLL_LINKAGE LobbySetPlayer : public CLobbyPackToServer
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE LobbySetSimturns : public CLobbyPackToServer
|
||||
{
|
||||
SimturnsInfo simturnsInfo;
|
||||
|
||||
virtual void visitTyped(ICPackVisitor & visitor) override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & simturnsInfo;
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE LobbySetTurnTime : public CLobbyPackToServer
|
||||
{
|
||||
TurnTimerInfo turnTimerInfo;
|
||||
|
@ -23,6 +23,24 @@ class CMapInfo;
|
||||
struct PlayerInfo;
|
||||
class PlayerColor;
|
||||
|
||||
struct DLL_LINKAGE SimturnsInfo
|
||||
{
|
||||
/// Minimal number of turns that must be played simultaneously even if contact has been detected
|
||||
int requiredTurns = 0;
|
||||
/// Maximum number of turns that might be played simultaneously unless contact is detected
|
||||
int optionalTurns = 0;
|
||||
/// If set to true, human and 1 AI can act at the same time
|
||||
bool allowHumanWithAI = true;
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & requiredTurns;
|
||||
h & optionalTurns;
|
||||
h & allowHumanWithAI;
|
||||
}
|
||||
};
|
||||
|
||||
/// Struct which describes the name, the color, the starting bonus of a player
|
||||
struct DLL_LINKAGE PlayerSettings
|
||||
{
|
||||
@ -84,6 +102,7 @@ struct DLL_LINKAGE StartInfo
|
||||
ui32 seedPostInit; //so we know that game is correctly synced at the start; 0 if not known yet
|
||||
ui32 mapfileChecksum; //0 if not relevant
|
||||
std::string startTimeIso8601;
|
||||
SimturnsInfo simturnsInfo;
|
||||
TurnTimerInfo turnTimerInfo;
|
||||
std::string mapname; // empty for random map, otherwise name of the map or savegame
|
||||
bool createRandomMap() const { return mapGenOptions != nullptr; }
|
||||
@ -108,6 +127,7 @@ struct DLL_LINKAGE StartInfo
|
||||
h & seedPostInit;
|
||||
h & mapfileChecksum;
|
||||
h & startTimeIso8601;
|
||||
h & simturnsInfo;
|
||||
h & turnTimerInfo;
|
||||
h & mapname;
|
||||
h & mapGenOptions;
|
||||
|
@ -372,7 +372,7 @@ ObjectInstanceID CGTeleport::getRandomExit(const CGHeroInstance * h) const
|
||||
|
||||
bool CGTeleport::isTeleport(const CGObjectInstance * obj)
|
||||
{
|
||||
return ((dynamic_cast<const CGTeleport *>(obj)));
|
||||
return dynamic_cast<const CGTeleport *>(obj) != nullptr;
|
||||
}
|
||||
|
||||
bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst)
|
||||
|
@ -121,6 +121,8 @@ void CPathfinder::calculatePaths()
|
||||
movement = hlp->getMaxMovePoints(source.node->layer);
|
||||
if(!hlp->passOneTurnLimitCheck(source))
|
||||
continue;
|
||||
if(turn >= hlp->options.turnLimit)
|
||||
continue;
|
||||
}
|
||||
|
||||
source.isInitialPosition = source.nodeHero == hlp->hero;
|
||||
|
@ -19,7 +19,7 @@ class CGWhirlpool;
|
||||
struct TurnInfo;
|
||||
struct PathfinderOptions;
|
||||
|
||||
class CPathfinder
|
||||
class DLL_LINKAGE CPathfinder
|
||||
{
|
||||
public:
|
||||
friend class CPathfinderHelper;
|
||||
|
@ -30,6 +30,7 @@ PathfinderOptions::PathfinderOptions()
|
||||
, lightweightFlyingMode(false)
|
||||
, oneTurnSpecialLayersLimit(true)
|
||||
, originalMovementRules(false)
|
||||
, turnLimit(std::numeric_limits<uint8_t>::max())
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,9 @@ struct DLL_LINKAGE PathfinderOptions
|
||||
/// I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
|
||||
bool originalMovementRules;
|
||||
|
||||
/// Max number of turns to compute. Default = infinite
|
||||
uint8_t turnLimit;
|
||||
|
||||
PathfinderOptions();
|
||||
};
|
||||
|
||||
|
@ -399,6 +399,7 @@ void registerTypesLobbyPacks(Serializer &s)
|
||||
s.template registerType<CLobbyPackToServer, LobbySetCampaignBonus>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetPlayer>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetTurnTime>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetSimturns>();
|
||||
s.template registerType<CLobbyPackToServer, LobbySetDifficulty>();
|
||||
s.template registerType<CLobbyPackToServer, LobbyForceSetPlayer>();
|
||||
}
|
||||
|
@ -482,11 +482,11 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
|
||||
|
||||
if(!parameters.pos.valid() && parameters.caster->getSpellSchoolLevel(owner) >= 2)
|
||||
{
|
||||
auto queryCallback = [=](const JsonNode & reply) -> void
|
||||
auto queryCallback = [=](std::optional<int32_t> reply) -> void
|
||||
{
|
||||
if(reply.getType() == JsonNode::JsonType::DATA_INTEGER)
|
||||
if(reply.has_value())
|
||||
{
|
||||
ObjectInstanceID townId(static_cast<si32>(reply.Integer()));
|
||||
ObjectInstanceID townId(*reply);
|
||||
|
||||
const CGObjectInstance * o = env->getCb()->getObj(townId, true);
|
||||
if(o == nullptr)
|
||||
|
@ -56,7 +56,7 @@ public:
|
||||
|
||||
virtual bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) = 0; //TODO: remove
|
||||
|
||||
virtual void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented
|
||||
virtual void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented
|
||||
};
|
||||
|
||||
namespace spells
|
||||
|
@ -1086,8 +1086,18 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
|
||||
const TerrainTile t = *getTile(hmpos);
|
||||
const int3 guardPos = gs->guardingCreaturePosition(hmpos);
|
||||
CGObjectInstance * objectToVisit = nullptr;
|
||||
CGObjectInstance * guardian = nullptr;
|
||||
|
||||
const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT;
|
||||
if (!t.visitableObjects.empty())
|
||||
objectToVisit = t.visitableObjects.back();
|
||||
|
||||
if (isInTheMap(guardPos))
|
||||
guardian = getTile(guardPos)->visitableObjects.back();
|
||||
|
||||
assert(guardian == nullptr || dynamic_cast<CGCreature*>(guardian) != nullptr);
|
||||
|
||||
const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT;
|
||||
const bool disembarking = h->boat
|
||||
&& t.terType->isLand()
|
||||
&& (dst == h->pos
|
||||
@ -1110,7 +1120,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining());
|
||||
|
||||
const bool standAtObstacle = t.blocked && !t.visitable;
|
||||
const bool standAtWater = !h->boat && t.terType->isWater() && (t.visitableObjects.empty() || !t.visitableObjects.back()->isCoastVisitable());
|
||||
const bool standAtWater = !h->boat && t.terType->isWater() && (objectToVisit || !objectToVisit->isCoastVisitable());
|
||||
|
||||
const auto complainRet = [&](const std::string & message)
|
||||
{
|
||||
@ -1120,29 +1130,41 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
return false;
|
||||
};
|
||||
|
||||
if (guardian && getVisitingHero(guardian) != nullptr)
|
||||
return complainRet("Cannot move hero, destination monster is busy!");
|
||||
|
||||
if (objectToVisit && getVisitingHero(objectToVisit) != nullptr)
|
||||
return complainRet("Cannot move hero, destination object is busy!");
|
||||
|
||||
if (objectToVisit &&
|
||||
objectToVisit->getOwner().isValidPlayer() &&
|
||||
getPlayerRelations(objectToVisit->getOwner(), h->getOwner()) == PlayerRelations::ENEMIES &&
|
||||
!turnOrder->isContactAllowed(objectToVisit->getOwner(), h->getOwner()))
|
||||
return complainRet("Cannot move hero, destination player is busy!");
|
||||
|
||||
//it's a rock or blocked and not visitable tile
|
||||
//OR hero is on land and dest is water and (there is not present only one object - boat)
|
||||
if (!t.terType->isPassable() || (standAtObstacle && !canFly))
|
||||
complainRet("Cannot move hero, destination tile is blocked!");
|
||||
return complainRet("Cannot move hero, destination tile is blocked!");
|
||||
|
||||
//hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
|
||||
if(standAtWater && !canFly && !canWalkOnSea)
|
||||
complainRet("Cannot move hero, destination tile is on water!");
|
||||
return complainRet("Cannot move hero, destination tile is on water!");
|
||||
|
||||
if(h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.terType->isLand() && t.blocked)
|
||||
complainRet("Cannot disembark hero, tile is blocked!");
|
||||
return complainRet("Cannot disembark hero, tile is blocked!");
|
||||
|
||||
if(distance(h->pos, dst) >= 1.5 && !teleporting)
|
||||
complainRet("Tiles are not neighboring!");
|
||||
return complainRet("Tiles are not neighboring!");
|
||||
|
||||
if(h->inTownGarrison)
|
||||
complainRet("Can not move garrisoned hero!");
|
||||
return complainRet("Can not move garrisoned hero!");
|
||||
|
||||
if(h->movementPointsRemaining() < cost && dst != h->pos && !teleporting)
|
||||
complainRet("Hero doesn't have any movement points left!");
|
||||
return complainRet("Hero doesn't have any movement points left!");
|
||||
|
||||
if (transit && !canFly && !(canWalkOnSea && t.terType->isWater()))
|
||||
complainRet("Hero cannot transit over this tile!");
|
||||
if (transit && !canFly && !(canWalkOnSea && t.terType->isWater()) && !CGTeleport::isTeleport(objectToVisit))
|
||||
return complainRet("Hero cannot transit over this tile!");
|
||||
|
||||
//several generic blocks of code
|
||||
|
||||
@ -1173,14 +1195,13 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
tmh.result = result;
|
||||
sendAndApply(&tmh);
|
||||
|
||||
if (visitDest == VISIT_DEST && t.topVisitableObj() && t.topVisitableObj()->id == h->id)
|
||||
if (visitDest == VISIT_DEST && objectToVisit && objectToVisit->id == h->id)
|
||||
{ // Hero should be always able to visit any object he staying on even if there guards around
|
||||
visitObjectOnTile(t, h);
|
||||
}
|
||||
else if (lookForGuards == CHECK_FOR_GUARDS && isInTheMap(guardPos))
|
||||
{
|
||||
const TerrainTile &guardTile = *gs->getTile(guardPos);
|
||||
objectVisited(guardTile.visitableObjects.back(), h);
|
||||
objectVisited(guardian, h);
|
||||
|
||||
moveQuery->visitDestAfterVictory = visitDest==VISIT_DEST;
|
||||
}
|
||||
@ -1238,9 +1259,9 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
// visit town for town portal \ castle gates
|
||||
// do not use generic visitObjectOnTile to avoid double-teleporting
|
||||
// if this moveHero call was triggered by teleporter
|
||||
if (!t.visitableObjects.empty())
|
||||
if (objectToVisit)
|
||||
{
|
||||
if (CGTownInstance * town = dynamic_cast<CGTownInstance *>(t.visitableObjects.back()))
|
||||
if (CGTownInstance * town = dynamic_cast<CGTownInstance *>(objectToVisit))
|
||||
town->onHeroVisit(h);
|
||||
}
|
||||
|
||||
@ -1258,7 +1279,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
EVisitDest visitDest = VISIT_DEST;
|
||||
if (transit)
|
||||
{
|
||||
if (CGTeleport::isTeleport(t.topVisitableObj()))
|
||||
if (CGTeleport::isTeleport(objectToVisit))
|
||||
visitDest = DONT_VISIT_DEST;
|
||||
|
||||
if (canFly || (canWalkOnSea && t.terType->isWater()))
|
||||
@ -1271,7 +1292,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
return true;
|
||||
|
||||
if(h->boat && !h->boat->onboardAssaultAllowed)
|
||||
lookForGuards = IGNORE_GUARDS;
|
||||
lookForGuards = IGNORE_GUARDS;
|
||||
|
||||
turnTimerHandler.setEndTurnAllowed(h->getOwner(), !standAtWater && !standAtObstacle);
|
||||
doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE);
|
||||
@ -2197,6 +2218,11 @@ bool CGameHandler::hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection>
|
||||
return connections.at(player).count(c);
|
||||
}
|
||||
|
||||
bool CGameHandler::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
|
||||
{
|
||||
return connections.at(left) == connections.at(right);
|
||||
}
|
||||
|
||||
bool CGameHandler::disbandCreature(ObjectInstanceID id, SlotID pos)
|
||||
{
|
||||
const CArmedInstance * s1 = static_cast<const CArmedInstance *>(getObjInstance(id));
|
||||
@ -2422,6 +2448,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
|
||||
}
|
||||
else
|
||||
{
|
||||
COMPLAIN_RET_FALSE_IF(getVisitingHero(dwelling) != hero, "Cannot recruit: can only recruit by visiting hero!");
|
||||
COMPLAIN_RET_FALSE_IF(!hero || hero->getOwner() != player, "Cannot recruit: can only recruit to owned hero!");
|
||||
}
|
||||
|
||||
@ -3124,12 +3151,13 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player)
|
||||
bool CGameHandler::queryReply(QueryID qid, std::optional<int32_t> answer, PlayerColor player)
|
||||
{
|
||||
boost::unique_lock<boost::recursive_mutex> lock(gsm);
|
||||
|
||||
logGlobal->trace("Player %s attempts answering query %d with answer:", player, qid);
|
||||
logGlobal->trace(answer.toJson());
|
||||
if (answer)
|
||||
logGlobal->trace("%d", *answer);
|
||||
|
||||
auto topQuery = queries->topQuery(player);
|
||||
|
||||
@ -3388,6 +3416,12 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta
|
||||
|
||||
logGlobal->debug("%s visits %s (%d:%d)", h->nodeName(), obj->getObjectName(), obj->ID, obj->subID);
|
||||
|
||||
if (getVisitingHero(obj) != nullptr)
|
||||
{
|
||||
logGlobal->error("Attempt to visit object that is being visited by another hero!");
|
||||
throw std::runtime_error("Can not visit object that is being visited");
|
||||
}
|
||||
|
||||
std::shared_ptr<CObjectVisitQuery> visitQuery;
|
||||
|
||||
auto startVisit = [&](ObjectVisitStarted & event)
|
||||
@ -4087,8 +4121,28 @@ void CGameHandler::changeFogOfWar(std::unordered_set<int3> &tiles, PlayerColor p
|
||||
sendAndApply(&fow);
|
||||
}
|
||||
|
||||
const CGHeroInstance * CGameHandler::getVisitingHero(const CGObjectInstance *obj)
|
||||
{
|
||||
assert(obj);
|
||||
|
||||
for (auto const & query : queries->allQueries())
|
||||
{
|
||||
auto visit = std::dynamic_pointer_cast<const CObjectVisitQuery>(query);
|
||||
if (visit && visit->visitedObject == obj)
|
||||
return visit->visitingHero;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)
|
||||
{
|
||||
assert(obj);
|
||||
assert(hero);
|
||||
assert(getVisitingHero(obj) == hero);
|
||||
// Check top query of targeted player:
|
||||
// If top query is NOT visit to targeted object then we assume that
|
||||
// visitation query is covered by other query that must be answered first
|
||||
|
||||
if (auto topQuery = queries->topQuery(hero->getOwner()))
|
||||
if (auto visit = std::dynamic_pointer_cast<const CObjectVisitQuery>(topQuery))
|
||||
return !(visit->visitedObject == obj && visit->visitingHero == hero);
|
||||
|
@ -153,6 +153,8 @@ public:
|
||||
|
||||
void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override;
|
||||
|
||||
/// Returns hero that is currently visiting this object, or nullptr if no visit is active
|
||||
const CGHeroInstance * getVisitingHero(const CGObjectInstance *obj);
|
||||
bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) override;
|
||||
void setObjProperty(ObjectInstanceID objid, int prop, si64 val) override;
|
||||
void showInfoDialog(InfoWindow * iw) override;
|
||||
@ -176,8 +178,9 @@ public:
|
||||
void handleClientDisconnection(std::shared_ptr<CConnection> c);
|
||||
void handleReceivedPack(CPackForServer * pack);
|
||||
bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const;
|
||||
bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const;
|
||||
|
||||
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );
|
||||
bool queryReply( QueryID qid, std::optional<int32_t> reply, PlayerColor player );
|
||||
bool buildBoat( ObjectInstanceID objid, PlayerColor player );
|
||||
bool setFormation( ObjectInstanceID hid, ui8 formation );
|
||||
bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2);
|
||||
|
@ -87,6 +87,7 @@ public:
|
||||
virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override;
|
||||
virtual void visitLobbySetPlayer(LobbySetPlayer & pack) override;
|
||||
virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) override;
|
||||
virtual void visitLobbySetSimturns(LobbySetSimturns & pack) override;
|
||||
virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) override;
|
||||
virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override;
|
||||
};
|
||||
};
|
||||
|
@ -391,6 +391,12 @@ void ApplyOnServerNetPackVisitor::visitLobbySetPlayer(LobbySetPlayer & pack)
|
||||
result = true;
|
||||
}
|
||||
|
||||
void ApplyOnServerNetPackVisitor::visitLobbySetSimturns(LobbySetSimturns & pack)
|
||||
{
|
||||
srv.si->simturnsInfo = pack.simturnsInfo;
|
||||
result = true;
|
||||
}
|
||||
|
||||
void ApplyOnServerNetPackVisitor::visitLobbySetTurnTime(LobbySetTurnTime & pack)
|
||||
{
|
||||
srv.si->turnTimerInfo = pack.turnTimerInfo;
|
||||
|
@ -93,9 +93,9 @@ bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, bool t
|
||||
return gh->moveHero(hid, dst, teleporting, false);
|
||||
}
|
||||
|
||||
void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode&)> callback)
|
||||
void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback)
|
||||
{
|
||||
auto query = std::make_shared<CGenericQuery>(gh->queries.get(), color, callback);
|
||||
auto query = std::make_shared<CGenericQuery>(gh, color, callback);
|
||||
request->queryID = query->queryID;
|
||||
gh->queries->addQuery(query);
|
||||
gh->sendAndApply(request);
|
||||
|
@ -36,7 +36,7 @@ public:
|
||||
const CMap * getMap() const override;
|
||||
const CGameInfoCallback * getCb() const override;
|
||||
bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) override;
|
||||
void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) override;
|
||||
void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) override;
|
||||
private:
|
||||
CGameHandler * gh;
|
||||
};
|
||||
};
|
||||
|
@ -174,6 +174,14 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
|
||||
|
||||
if(mapObject->ID == Obj::TAVERN)
|
||||
{
|
||||
const CGHeroInstance * visitor = gameHandler->getVisitingHero(mapObject);
|
||||
|
||||
if (!visitor || visitor->getOwner() != player)
|
||||
{
|
||||
gameHandler->complain("Can't buy hero in tavern not being visited!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(gameHandler->getTile(mapObject->visitablePos())->visitableObjects.back() != mapObject && gameHandler->complain("Tavern entry must be unoccupied!"))
|
||||
return false;
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include "../../lib/CPlayerState.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/pathfinder/CPathfinder.h"
|
||||
#include "../../lib/pathfinder/PathfinderOptions.h"
|
||||
|
||||
TurnOrderProcessor::TurnOrderProcessor(CGameHandler * owner):
|
||||
gameHandler(owner)
|
||||
@ -24,11 +26,125 @@ TurnOrderProcessor::TurnOrderProcessor(CGameHandler * owner):
|
||||
|
||||
}
|
||||
|
||||
bool TurnOrderProcessor::canActSimultaneously(PlayerColor active, PlayerColor waiting) const
|
||||
int TurnOrderProcessor::simturnsTurnsMaxLimit() const
|
||||
{
|
||||
return gameHandler->getStartInfo()->simturnsInfo.optionalTurns;
|
||||
}
|
||||
|
||||
int TurnOrderProcessor::simturnsTurnsMinLimit() const
|
||||
{
|
||||
return gameHandler->getStartInfo()->simturnsInfo.requiredTurns;
|
||||
}
|
||||
|
||||
void TurnOrderProcessor::updateContactStatus()
|
||||
{
|
||||
blockedContacts.clear();
|
||||
|
||||
assert(actedPlayers.empty());
|
||||
assert(actingPlayers.empty());
|
||||
|
||||
for (auto left : awaitingPlayers)
|
||||
{
|
||||
for(auto right : awaitingPlayers)
|
||||
{
|
||||
if (left == right)
|
||||
continue;
|
||||
|
||||
if (computeCanActSimultaneously(left, right))
|
||||
blockedContacts.push_back({left, right});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TurnOrderProcessor::playersInContact(PlayerColor left, PlayerColor right) const
|
||||
{
|
||||
// TODO: refactor, cleanup and optimize
|
||||
|
||||
boost::multi_array<bool, 3> leftReachability;
|
||||
boost::multi_array<bool, 3> rightReachability;
|
||||
|
||||
int3 mapSize = gameHandler->getMapSize();
|
||||
|
||||
leftReachability.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
|
||||
rightReachability.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
|
||||
|
||||
const auto * leftInfo = gameHandler->getPlayerState(left, false);
|
||||
const auto * rightInfo = gameHandler->getPlayerState(right, false);
|
||||
|
||||
for(const auto & hero : leftInfo->heroes)
|
||||
{
|
||||
CPathsInfo out(mapSize, hero);
|
||||
auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
|
||||
CPathfinder pathfinder(gameHandler->gameState(), config);
|
||||
pathfinder.calculatePaths();
|
||||
|
||||
for (int z = 0; z < mapSize.z; ++z)
|
||||
for (int y = 0; y < mapSize.y; ++y)
|
||||
for (int x = 0; x < mapSize.x; ++x)
|
||||
if (out.getNode({x,y,z})->reachable())
|
||||
leftReachability[z][x][y] = true;
|
||||
}
|
||||
|
||||
for(const auto & hero : rightInfo->heroes)
|
||||
{
|
||||
CPathsInfo out(mapSize, hero);
|
||||
auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
|
||||
CPathfinder pathfinder(gameHandler->gameState(), config);
|
||||
pathfinder.calculatePaths();
|
||||
|
||||
for (int z = 0; z < mapSize.z; ++z)
|
||||
for (int y = 0; y < mapSize.y; ++y)
|
||||
for (int x = 0; x < mapSize.x; ++x)
|
||||
if (out.getNode({x,y,z})->reachable())
|
||||
rightReachability[z][x][y] = true;
|
||||
}
|
||||
|
||||
for (int z = 0; z < mapSize.z; ++z)
|
||||
for (int y = 0; y < mapSize.y; ++y)
|
||||
for (int x = 0; x < mapSize.x; ++x)
|
||||
if (leftReachability[z][x][y] && rightReachability[z][x][y])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TurnOrderProcessor::isContactAllowed(PlayerColor active, PlayerColor waiting) const
|
||||
{
|
||||
assert(active != waiting);
|
||||
return !vstd::contains(blockedContacts, PlayerPair{active, waiting});
|
||||
}
|
||||
|
||||
bool TurnOrderProcessor::computeCanActSimultaneously(PlayerColor active, PlayerColor waiting) const
|
||||
{
|
||||
const auto * activeInfo = gameHandler->getPlayerState(active, false);
|
||||
const auto * waitingInfo = gameHandler->getPlayerState(waiting, false);
|
||||
|
||||
assert(active != waiting);
|
||||
assert(activeInfo);
|
||||
assert(waitingInfo);
|
||||
|
||||
if (gameHandler->hasBothPlayersAtSameConnection(active, waiting))
|
||||
{
|
||||
if (!gameHandler->getStartInfo()->simturnsInfo.allowHumanWithAI)
|
||||
return false;
|
||||
|
||||
// only one AI and one human can play simultaneoulsy from single connection
|
||||
if (activeInfo->human == waitingInfo->human)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameHandler->getDate(Date::DAY) < simturnsTurnsMinLimit())
|
||||
return true;
|
||||
|
||||
if (gameHandler->getDate(Date::DAY) > simturnsTurnsMaxLimit())
|
||||
return false;
|
||||
|
||||
if (playersInContact(active, waiting))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TurnOrderProcessor::mustActBefore(PlayerColor left, PlayerColor right) const
|
||||
{
|
||||
const auto * leftInfo = gameHandler->getPlayerState(left, false);
|
||||
@ -61,7 +177,7 @@ bool TurnOrderProcessor::canStartTurn(PlayerColor which) const
|
||||
|
||||
for (auto player : actingPlayers)
|
||||
{
|
||||
if (!canActSimultaneously(player, which))
|
||||
if (player != which && isContactAllowed(player, which))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -86,6 +202,7 @@ void TurnOrderProcessor::doStartNewDay()
|
||||
std::swap(actedPlayers, awaitingPlayers);
|
||||
|
||||
gameHandler->onNewTurn();
|
||||
updateContactStatus();
|
||||
tryStartTurnsForPlayers();
|
||||
}
|
||||
|
||||
@ -107,8 +224,7 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which)
|
||||
pst.queryID = turnQuery->queryID;
|
||||
gameHandler->sendAndApply(&pst);
|
||||
|
||||
assert(actingPlayers.size() == 1); // No simturns yet :(
|
||||
assert(gameHandler->isPlayerMakingTurn(*actingPlayers.begin()));
|
||||
assert(!actingPlayers.empty());
|
||||
}
|
||||
|
||||
void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which)
|
||||
@ -130,8 +246,6 @@ void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which)
|
||||
doStartNewDay();
|
||||
|
||||
assert(!actingPlayers.empty());
|
||||
assert(actingPlayers.size() == 1); // No simturns yet :(
|
||||
assert(gameHandler->isPlayerMakingTurn(*actingPlayers.begin()));
|
||||
}
|
||||
|
||||
void TurnOrderProcessor::addPlayer(PlayerColor which)
|
||||
@ -152,8 +266,6 @@ void TurnOrderProcessor::onPlayerEndsGame(PlayerColor which)
|
||||
doStartNewDay();
|
||||
|
||||
assert(!actingPlayers.empty());
|
||||
assert(actingPlayers.size() == 1); // No simturns yet :(
|
||||
assert(gameHandler->isPlayerMakingTurn(*actingPlayers.begin()));
|
||||
}
|
||||
|
||||
bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which)
|
||||
@ -188,6 +300,9 @@ bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which)
|
||||
|
||||
void TurnOrderProcessor::onGameStarted()
|
||||
{
|
||||
if (actingPlayers.empty())
|
||||
updateContactStatus();
|
||||
|
||||
// this may be game load - send notification to players that they can act
|
||||
auto actingPlayersCopy = actingPlayers;
|
||||
for (auto player : actingPlayersCopy)
|
||||
|
@ -17,12 +17,41 @@ class TurnOrderProcessor : boost::noncopyable
|
||||
{
|
||||
CGameHandler * gameHandler;
|
||||
|
||||
struct PlayerPair
|
||||
{
|
||||
PlayerColor a;
|
||||
PlayerColor b;
|
||||
|
||||
bool operator == (const PlayerPair & other) const
|
||||
{
|
||||
return (a == other.a && b == other.b) || (a == other.b && b == other.a);
|
||||
}
|
||||
|
||||
template<typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & a;
|
||||
h & b;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<PlayerPair> blockedContacts;
|
||||
|
||||
std::set<PlayerColor> awaitingPlayers;
|
||||
std::set<PlayerColor> actingPlayers;
|
||||
std::set<PlayerColor> actedPlayers;
|
||||
|
||||
/// Returns date on which simturns must end unconditionally
|
||||
int simturnsTurnsMaxLimit() const;
|
||||
|
||||
/// Returns date until which simturns must play unconditionally
|
||||
int simturnsTurnsMinLimit() const;
|
||||
|
||||
/// Returns true if players are close enough to each other for their heroes to meet on this turn
|
||||
bool playersInContact(PlayerColor left, PlayerColor right) const;
|
||||
|
||||
/// Returns true if waiting player can act alongside with currently acting player
|
||||
bool canActSimultaneously(PlayerColor active, PlayerColor waiting) const;
|
||||
bool computeCanActSimultaneously(PlayerColor active, PlayerColor waiting) const;
|
||||
|
||||
/// Returns true if left player must act before right player
|
||||
bool mustActBefore(PlayerColor left, PlayerColor right) const;
|
||||
@ -33,6 +62,8 @@ class TurnOrderProcessor : boost::noncopyable
|
||||
/// Starts turn for all players that can start turn
|
||||
void tryStartTurnsForPlayers();
|
||||
|
||||
void updateContactStatus();
|
||||
|
||||
void doStartNewDay();
|
||||
void doStartPlayerTurn(PlayerColor which);
|
||||
void doEndPlayerTurn(PlayerColor which);
|
||||
@ -44,6 +75,8 @@ class TurnOrderProcessor : boost::noncopyable
|
||||
public:
|
||||
TurnOrderProcessor(CGameHandler * owner);
|
||||
|
||||
bool isContactAllowed(PlayerColor left, PlayerColor right) const;
|
||||
|
||||
/// Add new player to handle (e.g. on game start)
|
||||
void addPlayer(PlayerColor which);
|
||||
|
||||
@ -59,6 +92,7 @@ public:
|
||||
template<typename Handler>
|
||||
void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & blockedContacts;
|
||||
h & awaitingPlayers;
|
||||
h & actingPlayers;
|
||||
h & actedPlayers;
|
||||
|
@ -26,7 +26,7 @@ void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisi
|
||||
}
|
||||
|
||||
CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi):
|
||||
CGhQuery(owner),
|
||||
CQuery(owner),
|
||||
battleID(bi->getBattleID())
|
||||
{
|
||||
belligerents[0] = bi->getSideArmy(0);
|
||||
@ -37,7 +37,7 @@ CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi):
|
||||
}
|
||||
|
||||
CBattleQuery::CBattleQuery(CGameHandler * owner):
|
||||
CGhQuery(owner)
|
||||
CQuery(owner)
|
||||
{
|
||||
belligerents[0] = belligerents[1] = nullptr;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
class IBattleInfo;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CBattleQuery : public CGhQuery
|
||||
class CBattleQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
std::array<const CArmedInstance *,2> belligerents;
|
||||
|
@ -45,8 +45,9 @@ std::ostream & operator<<(std::ostream & out, QueryPtr query)
|
||||
return out << "[" << query.get() << "] " << query->toString();
|
||||
}
|
||||
|
||||
CQuery::CQuery(QueriesProcessor * Owner):
|
||||
owner(Owner)
|
||||
CQuery::CQuery(CGameHandler * gameHandler)
|
||||
: owner(gameHandler->queries.get())
|
||||
, gh(gameHandler)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> l(QueriesProcessor::mx);
|
||||
|
||||
@ -127,7 +128,7 @@ void CQuery::onAdded(PlayerColor color)
|
||||
|
||||
}
|
||||
|
||||
void CQuery::setReply(const JsonNode & reply)
|
||||
void CQuery::setReply(std::optional<int32_t> reply)
|
||||
{
|
||||
|
||||
}
|
||||
@ -141,14 +142,8 @@ bool CQuery::blockAllButReply(const CPack * pack) const
|
||||
return true;
|
||||
}
|
||||
|
||||
CGhQuery::CGhQuery(CGameHandler * owner):
|
||||
CQuery(owner->queries.get()), gh(owner)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CDialogQuery::CDialogQuery(CGameHandler * owner):
|
||||
CGhQuery(owner)
|
||||
CQuery(owner)
|
||||
{
|
||||
|
||||
}
|
||||
@ -163,14 +158,14 @@ bool CDialogQuery::blocksPack(const CPack * pack) const
|
||||
return blockAllButReply(pack);
|
||||
}
|
||||
|
||||
void CDialogQuery::setReply(const JsonNode & reply)
|
||||
void CDialogQuery::setReply(std::optional<int32_t> reply)
|
||||
{
|
||||
if(reply.getType() == JsonNode::JsonType::DATA_INTEGER)
|
||||
answer = reply.Integer();
|
||||
if(reply.has_value())
|
||||
answer = *reply;
|
||||
}
|
||||
|
||||
CGenericQuery::CGenericQuery(QueriesProcessor * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback):
|
||||
CQuery(Owner), callback(Callback)
|
||||
CGenericQuery::CGenericQuery(CGameHandler * gh, PlayerColor color, std::function<void(std::optional<int32_t>)> Callback):
|
||||
CQuery(gh), callback(Callback)
|
||||
{
|
||||
addPlayer(color);
|
||||
}
|
||||
@ -190,7 +185,7 @@ void CGenericQuery::onExposure(QueryPtr topQuery)
|
||||
//do nothing
|
||||
}
|
||||
|
||||
void CGenericQuery::setReply(const JsonNode & reply)
|
||||
void CGenericQuery::setReply(std::optional<int32_t> reply)
|
||||
{
|
||||
this->reply = reply;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/JsonNode.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -39,8 +38,7 @@ public:
|
||||
std::vector<PlayerColor> players; //players that are affected (often "blocked") by query
|
||||
QueryID queryID;
|
||||
|
||||
CQuery(QueriesProcessor * Owner);
|
||||
|
||||
CQuery(CGameHandler * gh);
|
||||
|
||||
virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle.
|
||||
|
||||
@ -53,11 +51,12 @@ public:
|
||||
|
||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const;
|
||||
|
||||
virtual void setReply(const JsonNode & reply);
|
||||
virtual void setReply(std::optional<int32_t> reply);
|
||||
|
||||
virtual ~CQuery();
|
||||
protected:
|
||||
QueriesProcessor * owner;
|
||||
CGameHandler * gh;
|
||||
void addPlayer(PlayerColor color);
|
||||
bool blockAllButReply(const CPack * pack) const;
|
||||
};
|
||||
@ -65,21 +64,13 @@ protected:
|
||||
std::ostream &operator<<(std::ostream &out, const CQuery &query);
|
||||
std::ostream &operator<<(std::ostream &out, QueryPtr query);
|
||||
|
||||
class CGhQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
CGhQuery(CGameHandler * owner);
|
||||
protected:
|
||||
CGameHandler * gh;
|
||||
};
|
||||
|
||||
class CDialogQuery : public CGhQuery
|
||||
class CDialogQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
CDialogQuery(CGameHandler * owner);
|
||||
virtual bool endsByPlayerAnswer() const override;
|
||||
virtual bool blocksPack(const CPack *pack) const override;
|
||||
void setReply(const JsonNode & reply) override;
|
||||
void setReply(std::optional<int32_t> reply) override;
|
||||
protected:
|
||||
std::optional<ui32> answer;
|
||||
};
|
||||
@ -87,14 +78,14 @@ protected:
|
||||
class CGenericQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
CGenericQuery(QueriesProcessor * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback);
|
||||
CGenericQuery(CGameHandler * gh, PlayerColor color, std::function<void(std::optional<int32_t>)> Callback);
|
||||
|
||||
bool blocksPack(const CPack * pack) const override;
|
||||
bool endsByPlayerAnswer() const override;
|
||||
void onExposure(QueryPtr topQuery) override;
|
||||
void setReply(const JsonNode & reply) override;
|
||||
void setReply(std::optional<int32_t> reply) override;
|
||||
void onRemoval(PlayerColor color) override;
|
||||
private:
|
||||
std::function<void(const JsonNode &)> callback;
|
||||
JsonNode reply;
|
||||
std::function<void(std::optional<int32_t>)> callback;
|
||||
std::optional<int32_t> reply;
|
||||
};
|
||||
|
@ -16,7 +16,7 @@
|
||||
#include "../../lib/serializer/Cast.h"
|
||||
|
||||
PlayerStartsTurnQuery::PlayerStartsTurnQuery(CGameHandler * owner, PlayerColor player):
|
||||
CGhQuery(owner)
|
||||
CQuery(owner)
|
||||
{
|
||||
addPlayer(player);
|
||||
}
|
||||
@ -42,7 +42,7 @@ bool PlayerStartsTurnQuery::endsByPlayerAnswer() const
|
||||
}
|
||||
|
||||
CObjectVisitQuery::CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, int3 Tile):
|
||||
CGhQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
|
||||
CQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
|
||||
{
|
||||
addPlayer(Hero->tempOwner);
|
||||
}
|
||||
@ -213,7 +213,7 @@ void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQu
|
||||
}
|
||||
|
||||
CHeroMovementQuery::CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory):
|
||||
CGhQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero)
|
||||
CQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero)
|
||||
{
|
||||
players.push_back(hero->tempOwner);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class TurnTimerHandler;
|
||||
|
||||
//Created when player starts turn
|
||||
//Removed when player accepts a turn
|
||||
class PlayerStartsTurnQuery : public CGhQuery
|
||||
class PlayerStartsTurnQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
PlayerStartsTurnQuery(CGameHandler * owner, PlayerColor player);
|
||||
@ -30,7 +30,7 @@ public:
|
||||
|
||||
//Created when hero visits object.
|
||||
//Removed when query above is resolved (or immediately after visit if no queries were created)
|
||||
class CObjectVisitQuery : public CGhQuery
|
||||
class CObjectVisitQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
const CGObjectInstance *visitedObject;
|
||||
@ -47,7 +47,7 @@ public:
|
||||
|
||||
//Created when hero attempts move and something happens
|
||||
//(not necessarily position change, could be just an object interaction).
|
||||
class CHeroMovementQuery : public CGhQuery
|
||||
class CHeroMovementQuery : public CQuery
|
||||
{
|
||||
public:
|
||||
TryMoveHero tmh;
|
||||
|
@ -126,7 +126,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) override
|
||||
void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) override
|
||||
{
|
||||
//todo:
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user