1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Merge pull request #2576 from Nordsoft91/turn-timer

Implement turn timer feature
This commit is contained in:
Nordsoft91 2023-08-23 01:48:12 +04:00 committed by GitHub
commit e09b13c00a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 658 additions and 27 deletions

BIN
Mods/vcmi/Sounds/we5.wav Normal file

Binary file not shown.

View File

@ -190,6 +190,10 @@
"MAPS/":
[
{"type" : "dir", "path" : "/Maps"}
],
"SOUNDS/":
[
{"type" : "dir", "path" : "/Sounds"}
]
}
}

View File

@ -12,6 +12,7 @@ set(client_SRCS
adventureMap/CMinimap.cpp
adventureMap/CResDataBar.cpp
adventureMap/MapAudioPlayer.cpp
adventureMap/TurnTimerWidget.cpp
battle/BattleActionsController.cpp
battle/BattleAnimationClasses.cpp
@ -157,6 +158,7 @@ set(client_HEADERS
adventureMap/CMinimap.h
adventureMap/CResDataBar.h
adventureMap/MapAudioPlayer.h
adventureMap/TurnTimerWidget.h
battle/BattleActionsController.h
battle/BattleAnimationClasses.h

View File

@ -169,6 +169,9 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
void CPlayerInterface::playerStartsTurn(PlayerColor player)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
makingTurn = false;
stillMoveHero.setn(STOP_MOVE);
if(GH.windows().findWindows<AdventureMapInterface>().empty())
{
@ -177,6 +180,11 @@ void CPlayerInterface::playerStartsTurn(PlayerColor player)
GH.windows().pushWindow(adventureInt);
}
//close window from another player
if(auto w = GH.windows().topWindow<CInfoWindow>())
if(w->ID == -1 && 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);

View File

@ -479,7 +479,7 @@ void CServerHandler::setTurnLength(int npos) const
{
vstd::amin(npos, GameConstants::POSSIBLE_TURNTIME.size() - 1);
LobbySetTurnTime lstt;
lstt.turnTime = GameConstants::POSSIBLE_TURNTIME[npos];
lstt.turnTimerInfo.turnTimer = GameConstants::POSSIBLE_TURNTIME[npos] * 60 * 1000;
sendLobbyPack(lstt);
}

View File

@ -94,6 +94,7 @@ public:
void visitSystemMessage(SystemMessage & pack) override;
void visitPlayerBlocked(PlayerBlocked & pack) override;
void visitYourTurn(YourTurn & pack) override;
void visitTurnTimeUpdate(TurnTimeUpdate & pack) override;
void visitPlayerMessageClient(PlayerMessageClient & pack) override;
void visitAdvmapSpellCast(AdvmapSpellCast & pack) override;
void visitShowWorldViewEx(ShowWorldViewEx & pack) override;

View File

@ -864,6 +864,11 @@ void ApplyClientNetPackVisitor::visitYourTurn(YourTurn & pack)
callOnlyThatInterface(cl, pack.player, &CGameInterface::yourTurn);
}
void ApplyClientNetPackVisitor::visitTurnTimeUpdate(TurnTimeUpdate & pack)
{
logNetwork->debug("Server sets turn timer {turn: %d, base: %d, battle: %d, creature: %d} for %s", pack.turnTimer.turnTimer, pack.turnTimer.baseTimer, pack.turnTimer.battleTimer, pack.turnTimer.creatureTimer, pack.player.getStr());
}
void ApplyClientNetPackVisitor::visitPlayerMessageClient(PlayerMessageClient & pack)
{
logNetwork->debug("pack.player %s sends a message: %s", pack.player.getStr(), pack.text);

View File

@ -17,6 +17,7 @@
#include "CList.h"
#include "CInfoBar.h"
#include "MapAudioPlayer.h"
#include "TurnTimerWidget.h"
#include "AdventureMapWidget.h"
#include "AdventureMapShortcuts.h"
@ -35,6 +36,7 @@
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/StartInfo.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
@ -61,6 +63,9 @@ AdventureMapInterface::AdventureMapInterface():
shortcuts->setState(EAdventureState::MAKING_TURN);
widget->getMapView()->onViewMapActivated();
if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.isEnabled())
watches = std::make_shared<TurnTimerWidget>();
addUsedEvents(KEYBOARD | TIME);
}

View File

@ -39,6 +39,7 @@ class CTownList;
class CInfoBar;
class CMinimap;
class MapAudioPlayer;
class TurnTimerWidget;
enum class EAdventureState;
struct MapDrawingInfo;
@ -64,6 +65,7 @@ private:
std::shared_ptr<MapAudioPlayer> mapAudio;
std::shared_ptr<AdventureMapWidget> widget;
std::shared_ptr<AdventureMapShortcuts> shortcuts;
std::shared_ptr<TurnTimerWidget> watches;
private:
void setState(EAdventureState state);

View File

@ -314,7 +314,7 @@ void AdventureMapShortcuts::visitObject()
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if(h)
LOCPLINT->cb->moveHero(h,h->pos);
LOCPLINT->cb->moveHero(h, h->pos);
}
void AdventureMapShortcuts::openObject()

View File

@ -0,0 +1,129 @@
/*
* TurnTimerWidget.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 "TurnTimerWidget.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../render/EFont.h"
#include "../render/Graphics.h"
#include "../gui/CGuiHandler.h"
#include "../gui/TextAlignment.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../../CCallback.h"
#include "../../lib/CPlayerState.h"
#include "../../lib/filesystem/ResourceID.h"
TurnTimerWidget::DrawRect::DrawRect(const Rect & r, const ColorRGBA & c):
CIntObject(), rect(r), color(c)
{
}
void TurnTimerWidget::DrawRect::showAll(Canvas & to)
{
to.drawColor(rect, color);
CIntObject::showAll(to);
}
TurnTimerWidget::TurnTimerWidget():
InterfaceObjectConfigurable(TIME),
turnTime(0), lastTurnTime(0), cachedTurnTime(0)
{
REGISTER_BUILDER("drawRect", &TurnTimerWidget::buildDrawRect);
recActions &= ~DEACTIVATE;
const JsonNode config(ResourceID("config/widgets/turnTimer.json"));
build(config);
std::transform(variables["notificationTime"].Vector().begin(),
variables["notificationTime"].Vector().end(),
std::inserter(notifications, notifications.begin()),
[](const JsonNode & node){ return node.Integer(); });
}
std::shared_ptr<TurnTimerWidget::DrawRect> TurnTimerWidget::buildDrawRect(const JsonNode & config) const
{
logGlobal->debug("Building widget TurnTimerWidget::DrawRect");
auto rect = readRect(config["rect"]);
auto color = readColor(config["color"]);
return std::make_shared<TurnTimerWidget::DrawRect>(rect, color);
}
void TurnTimerWidget::show(Canvas & to)
{
showAll(to);
}
void TurnTimerWidget::setTime(int time)
{
int newTime = time / 1000;
if((LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID)
&& (newTime != turnTime)
&& notifications.count(newTime))
CCS->soundh->playSound(variables["notificationSound"].String());
turnTime = newTime;
if(auto w = widget<CLabel>("timer"))
{
std::ostringstream oss;
oss << turnTime / 60 << ":" << std::setw(2) << std::setfill('0') << turnTime % 60;
w->setText(oss.str());
if(graphics && LOCPLINT && LOCPLINT->cb
&& variables["textColorFromPlayerColor"].Bool()
&& LOCPLINT->cb->getCurrentPlayer().isValidPlayer())
{
w->setColor(graphics->playerColors[LOCPLINT->cb->getCurrentPlayer()]);
}
}
}
void TurnTimerWidget::tick(uint32_t msPassed)
{
if(LOCPLINT && LOCPLINT->cb)
{
auto player = LOCPLINT->cb->getCurrentPlayer();
auto time = LOCPLINT->cb->getPlayerTurnTime(player);
cachedTurnTime -= msPassed;
if(cachedTurnTime < 0) cachedTurnTime = 0; //do not go below zero
auto timeCheckAndUpdate = [&](int time)
{
if(time / 1000 != lastTurnTime / 1000)
{
//do not update timer on this tick
lastTurnTime = time;
cachedTurnTime = time;
}
else setTime(cachedTurnTime);
};
auto * playerInfo = LOCPLINT->cb->getPlayer(player);
if(playerInfo && playerInfo->isHuman())
{
if(LOCPLINT->battleInt)
{
if(time.isBattleEnabled())
timeCheckAndUpdate(time.creatureTimer);
}
else
{
timeCheckAndUpdate(time.turnTimer);
}
}
else
timeCheckAndUpdate(0);
}
}

View File

@ -0,0 +1,51 @@
/*
* TurnTimerWidget.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 "../gui/CIntObject.h"
#include "../gui/InterfaceObjectConfigurable.h"
#include "../render/Canvas.h"
#include "../render/Colors.h"
class CAnimImage;
class CLabel;
class TurnTimerWidget : public InterfaceObjectConfigurable
{
private:
class DrawRect : public CIntObject
{
const Rect rect;
const ColorRGBA color;
public:
DrawRect(const Rect &, const ColorRGBA &);
void showAll(Canvas & to) override;
};
int turnTime;
int lastTurnTime;
int cachedTurnTime;
std::set<int> notifications;
std::shared_ptr<DrawRect> buildDrawRect(const JsonNode & config) const;
public:
void show(Canvas & to) override;
void tick(uint32_t msPassed) override;
void setTime(int time);
TurnTimerWidget();
};

View File

@ -190,18 +190,29 @@ ColorRGBA InterfaceObjectConfigurable::readColor(const JsonNode & config) const
logGlobal->debug("Reading color");
if(!config.isNull())
{
if(config.String() == "yellow")
return Colors::YELLOW;
if(config.String() == "white")
return Colors::WHITE;
if(config.String() == "gold")
return Colors::METALLIC_GOLD;
if(config.String() == "green")
return Colors::GREEN;
if(config.String() == "orange")
return Colors::ORANGE;
if(config.String() == "bright-yellow")
return Colors::BRIGHT_YELLOW;
if(config.isString())
{
if(config.String() == "yellow")
return Colors::YELLOW;
if(config.String() == "white")
return Colors::WHITE;
if(config.String() == "gold")
return Colors::METALLIC_GOLD;
if(config.String() == "green")
return Colors::GREEN;
if(config.String() == "orange")
return Colors::ORANGE;
if(config.String() == "bright-yellow")
return Colors::BRIGHT_YELLOW;
}
if(config.isVector())
{
const auto & asVector = config.Vector();
if(asVector.size() == 4)
return ColorRGBA(asVector[0].Integer(), asVector[1].Integer(), asVector[2].Integer(), asVector[3].Integer());
if(asVector.size() == 3)
return ColorRGBA(asVector[0].Integer(), asVector[1].Integer(), asVector[2].Integer());
}
}
logGlobal->debug("Uknown color attribute");
return Colors::DEFAULT_KEY_COLOR;

View File

@ -78,7 +78,7 @@ void OptionsTab::recreate()
if(sliderTurnDuration)
{
sliderTurnDuration->scrollTo(vstd::find_pos(GameConstants::POSSIBLE_TURNTIME, SEL->getStartInfo()->turnTime));
sliderTurnDuration->scrollTo(vstd::find_pos(GameConstants::POSSIBLE_TURNTIME, SEL->getStartInfo()->turnTimerInfo.turnTimer / (60 * 1000)));
labelTurnDurationValue->setText(CGI->generaltexth->turnDurations[sliderTurnDuration->getValue()]);
}
}

View File

@ -265,6 +265,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/ScriptHandler.cpp
${MAIN_LIB_DIR}/TerrainHandler.cpp
${MAIN_LIB_DIR}/TextOperations.cpp
${MAIN_LIB_DIR}/TurnTimerInfo.cpp
${MAIN_LIB_DIR}/VCMIDirs.cpp
${MAIN_LIB_DIR}/VCMI_Lib.cpp
)
@ -623,6 +624,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/StringConstants.h
${MAIN_LIB_DIR}/TerrainHandler.h
${MAIN_LIB_DIR}/TextOperations.h
${MAIN_LIB_DIR}/TurnTimerInfo.h
${MAIN_LIB_DIR}/UnlockGuard.h
${MAIN_LIB_DIR}/VCMIDirs.h
${MAIN_LIB_DIR}/vcmi_endian.h

View File

@ -0,0 +1,34 @@
{
"items":
[
{ //backgound color
"type": "drawRect",
"rect": {"x": 4, "y": 4, "w": 68, "h": 24},
"color": [10, 10, 10, 255]
},
{ //clocks icon
"type": "image",
"image": "VCMI/BATTLEQUEUE/STATESSMALL",
"frame": 1,
"position": {"x": 4, "y": 6}
},
{ //timer field label
"name": "timer",
"type": "label",
"font": "big",
"alignment": "left",
"color": "yellow",
"text": "",
"position": {"x": 24, "y": 2}
},
],
"variables":
{
"notificationTime": [0, 1, 2, 3, 4, 5, 20],
"notificationSound": "WE5",
"textColorFromPlayerColor": true
}
}

View File

@ -99,6 +99,22 @@ const PlayerState * CGameInfoCallback::getPlayerState(PlayerColor color, bool ve
}
}
TurnTimerInfo CGameInfoCallback::getPlayerTurnTime(PlayerColor color) const
{
if(!color.isValidPlayer())
{
return TurnTimerInfo{};
}
auto player = gs->players.find(color);
if(player != gs->players.end())
{
return player->second.turnTimer;
}
return TurnTimerInfo{};
}
const CGObjectInstance * CGameInfoCallback::getObjByQuestIdentifier(int identifier) const
{
if(gs->map->questIdentifierToId.empty())

View File

@ -37,6 +37,7 @@ struct TeamState;
struct QuestInfo;
class CGameState;
class PathfinderConfig;
struct TurnTimerInfo;
class CArmedInstance;
class CGObjectInstance;
@ -153,6 +154,7 @@ public:
virtual PlayerColor getCurrentPlayer() const; //player that currently makes move // TODO synchronous turns
PlayerColor getLocalPlayer() const override; //player that is currently owning given client (if not a client, then returns current player)
virtual const PlayerSettings * getPlayerSettings(PlayerColor color) const;
virtual TurnTimerInfo getPlayerTurnTime(PlayerColor color) const;
//map
virtual bool isVisible(int3 pos, const std::optional<PlayerColor> & Player) const;

View File

@ -15,6 +15,7 @@
#include "bonuses/Bonus.h"
#include "bonuses/CBonusSystemNode.h"
#include "ResourceSet.h"
#include "TurnTimerInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -39,6 +40,7 @@ public:
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
EPlayerStatus::EStatus status;
std::optional<ui8> daysWithoutCastle;
TurnTimerInfo turnTimer;
PlayerState();
PlayerState(PlayerState && other) noexcept;
@ -71,6 +73,7 @@ public:
h & team;
h & resources;
h & status;
h & turnTimer;
h & heroes;
h & towns;
h & dwellings;

View File

@ -27,6 +27,7 @@ public:
virtual void visitPlayerBlocked(PlayerBlocked & pack) {}
virtual void visitPlayerCheated(PlayerCheated & pack) {}
virtual void visitYourTurn(YourTurn & pack) {}
virtual void visitTurnTimeUpdate(TurnTimeUpdate & pack) {}
virtual void visitEntitiesChanged(EntitiesChanged & pack) {}
virtual void visitSetResources(SetResources & pack) {}
virtual void visitSetPrimSkill(SetPrimSkill & pack) {}

View File

@ -14,6 +14,7 @@
#include "ConstTransitivePtr.h"
#include "MetaString.h"
#include "ResourceSet.h"
#include "TurnTimerInfo.h"
#include "int3.h"
#include "battle/BattleAction.h"
@ -147,6 +148,20 @@ struct DLL_LINKAGE PlayerCheated : public CPackForClient
}
};
struct DLL_LINKAGE TurnTimeUpdate : public CPackForClient
{
void applyGs(CGameState * gs) const;
PlayerColor player;
TurnTimerInfo turnTimer;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & player;
h & turnTimer;
}
};
struct DLL_LINKAGE YourTurn : public CPackForClient
{
void applyGs(CGameState * gs) const;

View File

@ -2509,6 +2509,12 @@ void YourTurn::applyGs(CGameState * gs) const
playerState.daysWithoutCastle = daysWithoutCastle;
}
void TurnTimeUpdate::applyGs(CGameState *gs) const
{
auto & playerState = gs->players[player];
playerState.turnTimer = turnTimer;
}
Component::Component(const CStackBasicDescriptor & stack)
: id(EComponentType::CREATURE)
, subtype(stack.type->getId())

View File

@ -240,13 +240,13 @@ struct DLL_LINKAGE LobbySetPlayer : public CLobbyPackToServer
struct DLL_LINKAGE LobbySetTurnTime : public CLobbyPackToServer
{
ui8 turnTime = 0;
TurnTimerInfo turnTimerInfo;
virtual void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & turnTime;
h & turnTimerInfo;
}
};

View File

@ -10,6 +10,7 @@
#pragma once
#include "GameConstants.h"
#include "TurnTimerInfo.h"
#include "campaign/CampaignConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -80,7 +81,7 @@ struct DLL_LINKAGE StartInfo
ui32 seedToBeUsed; //0 if not sure (client requests server to decide, will be send in reply pack)
ui32 seedPostInit; //so we know that game is correctly synced at the start; 0 if not known yet
ui32 mapfileChecksum; //0 if not relevant
ui8 turnTime; //in minutes, 0=unlimited
TurnTimerInfo turnTimerInfo;
std::string mapname; // empty for random map, otherwise name of the map or savegame
bool createRandomMap() const { return mapGenOptions != nullptr; }
std::shared_ptr<CMapGenOptions> mapGenOptions;
@ -103,14 +104,14 @@ struct DLL_LINKAGE StartInfo
h & seedToBeUsed;
h & seedPostInit;
h & mapfileChecksum;
h & turnTime;
h & turnTimerInfo;
h & mapname;
h & mapGenOptions;
h & campState;
}
StartInfo() : mode(INVALID), difficulty(1), seedToBeUsed(0), seedPostInit(0),
mapfileChecksum(0), turnTime(0)
mapfileChecksum(0)
{
}

25
lib/TurnTimerInfo.cpp Normal file
View File

@ -0,0 +1,25 @@
/*
* TurnTimerInfo.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 "TurnTimerInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
bool TurnTimerInfo::isEnabled() const
{
return turnTimer > 0;
}
bool TurnTimerInfo::isBattleEnabled() const
{
return creatureTimer > 0;
}
VCMI_LIB_NAMESPACE_END

35
lib/TurnTimerInfo.h Normal file
View File

@ -0,0 +1,35 @@
/*
* TurnTimerInfo.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
VCMI_LIB_NAMESPACE_BEGIN
struct DLL_LINKAGE TurnTimerInfo
{
int turnTimer = 0; //in ms, counts down when player is making his turn on adventure map
int baseTimer = 0; //in ms, counts down only when turn timer runs out
int battleTimer = 0; //in ms, counts down during battles when creature timer runs out
int creatureTimer = 0; //in ms, counts down when player is choosing action in battle
bool isEnabled() const;
bool isBattleEnabled() const;
template <typename Handler>
void serialize(Handler &h, const int version)
{
h & turnTimer;
h & baseTimer;
h & battleTimer;
h & creatureTimer;
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -232,6 +232,7 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, PlayerBlocked>();
s.template registerType<CPackForClient, PlayerCheated>();
s.template registerType<CPackForClient, YourTurn>();
s.template registerType<CPackForClient, TurnTimeUpdate>();
s.template registerType<CPackForClient, SetResources>();
s.template registerType<CPackForClient, SetPrimSkill>();
s.template registerType<CPackForClient, SetSecSkill>();

View File

@ -541,6 +541,11 @@ void CGameHandler::handleReceivedPack(CPackForServer * pack)
vstd::clear_pointer(pack);
}
CGameHandler::CGameHandler()
: turnTimerHandler(*this)
{}
CGameHandler::CGameHandler(CVCMIServer * lobby)
: lobby(lobby)
, heroPool(std::make_unique<HeroPoolProcessor>(this))
@ -550,6 +555,7 @@ CGameHandler::CGameHandler(CVCMIServer * lobby)
, complainNoCreatures("No creatures to split")
, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
, complainInvalidSlot("Invalid slot accessed!")
, turnTimerHandler(*this)
{
QID = 1;
IObjectInterface::cb = this;
@ -992,7 +998,11 @@ void CGameHandler::run(bool resume)
events::GameResumed::defaultExecute(serverEventBus.get());
auto playerTurnOrder = generatePlayerTurnOrder();
if(!resume)
for(auto & playerColor : playerTurnOrder)
turnTimerHandler.onGameplayStart(gs->players[playerColor]);
while(lobby->state == EServerState::GAMEPLAY)
{
if(!resume)
@ -1040,6 +1050,8 @@ void CGameHandler::run(bool resume)
//Change local daysWithoutCastle counter for local interface message //TODO: needed?
yt.daysWithoutCastle = playerState->daysWithoutCastle;
applyAndSend(&yt);
turnTimerHandler.onPlayerGetTurn(gs->players[player]);
}
};
@ -1048,10 +1060,15 @@ void CGameHandler::run(bool resume)
if(playerColor != PlayerColor::CANNOT_DETERMINE)
{
//wait till turn is done
const int waitTime = 100; //ms
boost::unique_lock<boost::mutex> lock(states.mx);
while(states.players.at(playerColor).makingTurn && lobby->state == EServerState::GAMEPLAY)
{
static time_duration p = milliseconds(100);
turnTimerHandler.onPlayerMakingTurn(gs->players[playerColor], waitTime);
if(gs->curB)
turnTimerHandler.onBattleLoop(waitTime);
static time_duration p = milliseconds(waitTime);
states.cv.timed_wait(lock, p);
}
}
@ -1141,6 +1158,9 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
// not turn of that hero or player can't simply teleport hero (at least not with this function)
if (!h || (asker != PlayerColor::NEUTRAL && (teleporting || h->getOwner() != gs->currentPlayer)))
{
if(h && getStartInfo()->turnTimerInfo.isEnabled() && gs->players[h->getOwner()].turnTimer.turnTimer == 0)
return true; //timer expired, no error
logGlobal->error("Illegal call to move hero!");
return false;
}
@ -1248,7 +1268,17 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
visitObjectOnTile(t, h);
}
queries->popIfTop(moveQuery);
if(!transit)
{
for(auto topQuery = queries->topQuery(h->tempOwner); true; topQuery = queries->topQuery(h->tempOwner))
{
moveQuery = std::dynamic_pointer_cast<CHeroMovementQuery>(topQuery);
if(moveQuery)
queries->popIfTop(moveQuery);
else
break;
}
}
logGlobal->trace("Hero %s ends movement", h->getNameTranslated());
return result != TryMoveHero::FAILED;
};

View File

@ -14,6 +14,7 @@
#include "../lib/IGameCallback.h"
#include "../lib/battle/CBattleInfoCallback.h"
#include "../lib/ScriptHandler.h"
#include "TurnTimerHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -105,6 +106,8 @@ public:
SpellCastEnvironment * spellEnv;
TurnTimerHandler turnTimerHandler;
const Services * services() const override;
const BattleCb * battle() const override;
@ -118,7 +121,7 @@ public:
bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2);
void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
CGameHandler() = default;
CGameHandler();
CGameHandler(CVCMIServer * lobby);
~CGameHandler();

View File

@ -19,6 +19,7 @@ set(server_SRCS
CVCMIServer.cpp
NetPacksServer.cpp
NetPacksLobbyServer.cpp
TurnTimerHandler.cpp
)
set(server_HEADERS
@ -42,6 +43,7 @@ set(server_HEADERS
CVCMIServer.h
LobbyNetPackVisitors.h
ServerNetPackVisitors.h
TurnTimerHandler.h
)
assign_source_group(${server_SRCS} ${server_HEADERS})

View File

@ -214,7 +214,7 @@ void ApplyOnServerNetPackVisitor::visitLobbySetCampaign(LobbySetCampaign & pack)
srv.si->mapname = pack.ourCampaign->getFilename();
srv.si->mode = StartInfo::CAMPAIGN;
srv.si->campState = pack.ourCampaign;
srv.si->turnTime = 0;
srv.si->turnTimerInfo = TurnTimerInfo{};
bool isCurrentMapConquerable = pack.ourCampaign->currentScenario() && pack.ourCampaign->isAvailable(*pack.ourCampaign->currentScenario());
@ -391,7 +391,7 @@ void ApplyOnServerNetPackVisitor::visitLobbySetPlayer(LobbySetPlayer & pack)
void ApplyOnServerNetPackVisitor::visitLobbySetTurnTime(LobbySetTurnTime & pack)
{
srv.si->turnTime = pack.turnTime;
srv.si->turnTimerInfo = pack.turnTimerInfo;
result = true;
}

View File

@ -64,7 +64,6 @@ void ApplyGhNetPackVisitor::visitDismissHero(DismissHero & pack)
void ApplyGhNetPackVisitor::visitMoveHero(MoveHero & pack)
{
gh.throwOnWrongOwner(&pack, pack.hid);
result = gh.moveHero(pack.hid, pack.dest, 0, pack.transit, gh.getPlayerAt(pack.c));
}

195
server/TurnTimerHandler.cpp Normal file
View File

@ -0,0 +1,195 @@
/*
* TurnTimerHandler.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 "TurnTimerHandler.h"
#include "CGameHandler.h"
#include "battles/BattleProcessor.h"
#include "queries/QueriesProcessor.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/CPlayerState.h"
#include "../lib/CStack.h"
#include "../lib/StartInfo.h"
#include "../lib/NetPacks.h"
TurnTimerHandler::TurnTimerHandler(CGameHandler & gh):
gameHandler(gh)
{
}
void TurnTimerHandler::onGameplayStart(PlayerState & state)
{
if(const auto * si = gameHandler.getStartInfo())
{
if(si->turnTimerInfo.isEnabled())
{
state.turnTimer = si->turnTimerInfo;
state.turnTimer.turnTimer = 0;
}
}
}
void TurnTimerHandler::onPlayerGetTurn(PlayerState & state)
{
if(const auto * si = gameHandler.getStartInfo())
{
if(si->turnTimerInfo.isEnabled())
{
state.turnTimer.baseTimer += state.turnTimer.turnTimer;
state.turnTimer.turnTimer = si->turnTimerInfo.turnTimer;
TurnTimeUpdate ttu;
ttu.player = state.color;
ttu.turnTimer = state.turnTimer;
gameHandler.sendAndApply(&ttu);
}
}
}
void TurnTimerHandler::onPlayerMakingTurn(PlayerState & state, int waitTime)
{
const auto * gs = gameHandler.gameState();
const auto * si = gameHandler.getStartInfo();
if(!si || !gs)
return;
if(state.human && si->turnTimerInfo.isEnabled() && !gs->curB)
{
if(state.turnTimer.turnTimer > 0)
{
state.turnTimer.turnTimer -= waitTime;
int frequency = (state.turnTimer.creatureTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit);
if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already
&& state.turnTimer.turnTimer % frequency == 0)
{
TurnTimeUpdate ttu;
ttu.player = state.color;
ttu.turnTimer = state.turnTimer;
gameHandler.sendAndApply(&ttu);
}
}
else if(state.turnTimer.baseTimer > 0)
{
state.turnTimer.turnTimer = state.turnTimer.baseTimer;
state.turnTimer.baseTimer = 0;
onPlayerMakingTurn(state, waitTime);
}
else if(!gameHandler.queries->topQuery(state.color)) //wait for replies to avoid pending queries
gameHandler.states.players.at(state.color).makingTurn = false; //force end turn
}
}
void TurnTimerHandler::onBattleStart()
{
const auto * gs = gameHandler.gameState();
const auto * si = gameHandler.getStartInfo();
if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled())
return;
auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER);
auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER);
for(auto i : {attacker, defender})
{
if(i.isValidPlayer())
{
const auto & state = gs->players.at(i);
TurnTimeUpdate ttu;
ttu.player = state.color;
ttu.turnTimer = state.turnTimer;
ttu.turnTimer.battleTimer = si->turnTimerInfo.battleTimer;
ttu.turnTimer.creatureTimer = si->turnTimerInfo.creatureTimer;
gameHandler.sendAndApply(&ttu);
}
}
}
void TurnTimerHandler::onBattleNextStack(const CStack & stack)
{
const auto * gs = gameHandler.gameState();
const auto * si = gameHandler.getStartInfo();
if(!si || !gs || !gs->curB)
return;
if(!stack.getOwner().isValidPlayer())
return;
const auto & state = gs->players.at(stack.getOwner());
if(si->turnTimerInfo.isBattleEnabled())
{
TurnTimeUpdate ttu;
ttu.player = state.color;
ttu.turnTimer = state.turnTimer;
if(state.turnTimer.battleTimer < si->turnTimerInfo.battleTimer)
ttu.turnTimer.battleTimer = ttu.turnTimer.creatureTimer;
ttu.turnTimer.creatureTimer = si->turnTimerInfo.creatureTimer;
gameHandler.sendAndApply(&ttu);
}
}
void TurnTimerHandler::onBattleLoop(int waitTime)
{
const auto * gs = gameHandler.gameState();
const auto * si = gameHandler.getStartInfo();
if(!si || !gs || !gs->curB)
return;
const auto * stack = gs->curB.get()->battleGetStackByID(gs->curB->getActiveStackID());
if(!stack || !stack->getOwner().isValidPlayer())
return;
auto & state = gs->players.at(gs->curB->getSidePlayer(stack->unitSide()));
auto turnTimerUpdateApplier = [&](const TurnTimerInfo & tTimer)
{
TurnTimerInfo turnTimerUpdate = tTimer;
if(tTimer.creatureTimer > 0)
{
turnTimerUpdate.creatureTimer -= waitTime;
int frequency = (turnTimerUpdate.creatureTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit);
if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already
&& turnTimerUpdate.creatureTimer % frequency == 0)
{
TurnTimeUpdate ttu;
ttu.player = state.color;
ttu.turnTimer = turnTimerUpdate;
gameHandler.sendAndApply(&ttu);
}
return true;
}
return false;
};
if(state.human && si->turnTimerInfo.isBattleEnabled())
{
TurnTimerInfo turnTimer = state.turnTimer;
if(!turnTimerUpdateApplier(turnTimer))
{
if(turnTimer.battleTimer > 0)
{
turnTimer.creatureTimer = turnTimer.battleTimer;
turnTimer.battleTimer = 0;
turnTimerUpdateApplier(turnTimer);
}
else
{
BattleAction doNothing;
doNothing.actionType = EActionType::DEFEND;
doNothing.side = stack->unitSide();
doNothing.stackNumber = stack->unitId();
gameHandler.battles->makePlayerBattleAction(state.color, doNothing);
}
}
}
}

39
server/TurnTimerHandler.h Normal file
View File

@ -0,0 +1,39 @@
/*
* TurnTimerHandler.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
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
class PlayerColor;
struct PlayerState;
VCMI_LIB_NAMESPACE_END
class CGameHandler;
class TurnTimerHandler
{
CGameHandler & gameHandler;
const int turnTimePropagateFrequency = 5000;
const int turnTimePropagateFrequencyCrit = 1000;
const int turnTimePropagateThreshold = 3000;
public:
TurnTimerHandler(CGameHandler &);
void onGameplayStart(PlayerState & state);
void onPlayerGetTurn(PlayerState & state);
void onPlayerMakingTurn(PlayerState & state, int waitTime);
void onBattleStart();
void onBattleNextStack(const CStack & stack);
void onBattleLoop(int waitTime);
};

View File

@ -130,6 +130,8 @@ void BattleFlowProcessor::onBattleStarted()
assert(gameHandler->gameState()->curB);
tryPlaceMoats();
gameHandler->turnTimerHandler.onBattleStart();
if (gameHandler->gameState()->curB->tacticDistance == 0)
onTacticsEnded();
@ -315,6 +317,8 @@ void BattleFlowProcessor::activateNextStack()
if(!removeGhosts.changedStacks.empty())
gameHandler->sendAndApply(&removeGhosts);
gameHandler->turnTimerHandler.onBattleNextStack(*next);
if (!tryMakeAutomaticAction(next))
{