1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Implement turn timer feature

This commit is contained in:
nordsoft 2023-08-13 14:06:35 +04:00
parent f13a53c1d9
commit 4b1224ec8c
16 changed files with 199 additions and 2 deletions

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

@ -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 %d turn time for %s", pack.turnTime, 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()->turnTime > 0)
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,73 @@
/*
* 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 "TurnTimerWidget.h"
#include "../CPlayerInterface.h"
#include "../render/Canvas.h"
#include "../render/Colors.h"
#include "../render/EFont.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 <SDL_render.h>
TurnTimerWidget::TurnTimerWidget():
CIntObject(TIME),
turnTime(0), lastTurnTime(0)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
watches = std::make_shared<CAnimImage>("VCMI/BATTLEQUEUE/STATESSMALL", 1, 0, 4, 6);
label = std::make_shared<CLabel>(24, 2, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, std::to_string(turnTime));
recActions &= ~DEACTIVATE;
}
void TurnTimerWidget::showAll(Canvas & to)
{
to.drawColor(Rect(4, 4, 68, 24), ColorRGBA(10, 10, 10, 255));
CIntObject::showAll(to);
}
void TurnTimerWidget::show(Canvas & to)
{
showAll(to);
}
void TurnTimerWidget::setTime(int time)
{
turnTime = time / 1000;
std::ostringstream oss;
oss << turnTime / 60 << ":" << std::setw(2) << std::setfill('0') << turnTime % 60;
label->setText(oss.str());
}
void TurnTimerWidget::tick(uint32_t msPassed)
{
if(LOCPLINT && LOCPLINT->cb && !LOCPLINT->battleInt)
{
auto player = LOCPLINT->cb->getCurrentPlayer();
auto time = LOCPLINT->cb->getPlayerTurnTime(player);
cachedTurnTime -= msPassed;
if(time / 1000 != lastTurnTime / 1000)
{
//do not update timer on this tick
lastTurnTime = time;
cachedTurnTime = time;
}
else setTime(cachedTurnTime);
}
}

View File

@ -0,0 +1,39 @@
/*
* TurnTimerWidget.hpp, 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"
class CAnimImage;
class CLabel;
class TurnTimerWidget : public CIntObject
{
private:
int turnTime;
int lastTurnTime;
int cachedTurnTime;
std::shared_ptr<CAnimImage> watches;
std::shared_ptr<CLabel> label;
public:
//void tick(uint32_t msPassed) override;
void show(Canvas & to) override;
void showAll(Canvas & to) override;
void tick(uint32_t msPassed) override;
void setTime(int time);
TurnTimerWidget();
};

View File

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

View File

@ -153,6 +153,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 int getPlayerTurnTime(PlayerColor color) const;
//map
virtual bool isVisible(int3 pos, const std::optional<PlayerColor> & Player) const;

View File

@ -39,6 +39,7 @@ public:
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
EPlayerStatus::EStatus status;
std::optional<ui8> daysWithoutCastle;
int turnTime = 0;
PlayerState();
PlayerState(PlayerState && other) noexcept;
@ -71,6 +72,7 @@ public:
h & team;
h & resources;
h & status;
h & turnTime;
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

@ -147,6 +147,20 @@ struct DLL_LINKAGE PlayerCheated : public CPackForClient
}
};
struct DLL_LINKAGE TurnTimeUpdate : public CPackForClient
{
void applyGs(CGameState * gs) const;
PlayerColor player;
int turnTime = 0;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & player;
h & turnTime;
}
};
struct DLL_LINKAGE YourTurn : public CPackForClient
{
void applyGs(CGameState * gs) const;

View File

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

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

@ -2067,6 +2067,14 @@ void CGameHandler::run(bool resume)
//Change local daysWithoutCastle counter for local interface message //TODO: needed?
yt.daysWithoutCastle = playerState->daysWithoutCastle;
applyAndSend(&yt);
if(gs->getStartInfo()->turnTime > 0) //turn timer check
{
TurnTimeUpdate ttu;
ttu.player = player;
ttu.turnTime = gs->getStartInfo()->turnTime * 60 * 1000; //ms
applyAndSend(&ttu);
}
}
};
@ -2075,10 +2083,31 @@ void CGameHandler::run(bool resume)
if(playerColor != PlayerColor::CANNOT_DETERMINE)
{
//wait till turn is done
const int waitTime = 100; //ms
int turnTimePropagateFrequency = 5000; //do not send updates too frequently
boost::unique_lock<boost::mutex> lock(states.mx);
while(states.players.at(playerColor).makingTurn && lobby->state == EServerState::GAMEPLAY)
{
static time_duration p = milliseconds(100);
if(gs->getStartInfo()->turnTime > 0 && !gs->curB) //turn timer check
{
if(gs->players[playerColor].turnTime > 0)
{
gs->players[playerColor].turnTime -= waitTime;
if(gs->players[playerColor].status == EPlayerStatus::INGAME //do not send message if player is not active already
&& gs->players[playerColor].turnTime % turnTimePropagateFrequency == 0)
{
TurnTimeUpdate ttu;
ttu.player = playerColor;
ttu.turnTime = gs->players[playerColor].turnTime;
applyAndSend(&ttu);
}
}
else if(!queries.topQuery(playerColor)) //wait for replies to avoid pending queries
states.players.at(playerColor).makingTurn = false; //force end turn
}
static time_duration p = milliseconds(waitTime);
states.cv.timed_wait(lock, p);
}
}