Bläddra i källkod

Implement turn timer feature

nordsoft 2 år sedan
förälder
incheckning
4b1224ec8c

+ 2 - 0
client/CMakeLists.txt

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

+ 1 - 0
client/ClientNetPackVisitors.h

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

+ 5 - 0
client/NetPacksClient.cpp

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

+ 5 - 0
client/adventureMap/AdventureMapInterface.cpp

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

+ 2 - 0
client/adventureMap/AdventureMapInterface.h

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

+ 1 - 1
client/adventureMap/AdventureMapShortcuts.cpp

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

+ 73 - 0
client/adventureMap/TurnTimerWidget.cpp

@@ -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);
+	}
+}

+ 39 - 0
client/adventureMap/TurnTimerWidget.h

@@ -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();
+};

+ 16 - 0
lib/CGameInfoCallback.cpp

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

+ 1 - 0
lib/CGameInfoCallback.h

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

+ 2 - 0
lib/CPlayerState.h

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

+ 1 - 0
lib/NetPackVisitor.h

@@ -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) {}

+ 14 - 0
lib/NetPacks.h

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

+ 6 - 0
lib/NetPacksLib.cpp

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

+ 1 - 0
lib/registerTypes/RegisterTypes.h

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

+ 30 - 1
server/CGameHandler.cpp

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