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:
commit
e09b13c00a
BIN
Mods/vcmi/Sounds/we5.wav
Normal file
BIN
Mods/vcmi/Sounds/we5.wav
Normal file
Binary file not shown.
@ -190,6 +190,10 @@
|
||||
"MAPS/":
|
||||
[
|
||||
{"type" : "dir", "path" : "/Maps"}
|
||||
],
|
||||
"SOUNDS/":
|
||||
[
|
||||
{"type" : "dir", "path" : "/Sounds"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -170,6 +170,9 @@ void CPlayerInterface::playerStartsTurn(PlayerColor player)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
|
||||
makingTurn = false;
|
||||
stillMoveHero.setn(STOP_MOVE);
|
||||
|
||||
if(GH.windows().findWindows<AdventureMapInterface>().empty())
|
||||
{
|
||||
// after map load - remove all active windows and replace them with adventure map
|
||||
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
129
client/adventureMap/TurnTimerWidget.cpp
Normal file
129
client/adventureMap/TurnTimerWidget.cpp
Normal 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);
|
||||
}
|
||||
}
|
51
client/adventureMap/TurnTimerWidget.h
Normal file
51
client/adventureMap/TurnTimerWidget.h
Normal 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();
|
||||
};
|
@ -189,6 +189,8 @@ ColorRGBA InterfaceObjectConfigurable::readColor(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Reading color");
|
||||
if(!config.isNull())
|
||||
{
|
||||
if(config.isString())
|
||||
{
|
||||
if(config.String() == "yellow")
|
||||
return Colors::YELLOW;
|
||||
@ -203,6 +205,15 @@ ColorRGBA InterfaceObjectConfigurable::readColor(const JsonNode & config) const
|
||||
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;
|
||||
|
||||
|
@ -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()]);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
34
config/widgets/turnTimer.json
Normal file
34
config/widgets/turnTimer.json
Normal 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
|
||||
}
|
||||
}
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,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;
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
25
lib/TurnTimerInfo.cpp
Normal 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
35
lib/TurnTimerInfo.h
Normal 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
|
@ -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>();
|
||||
|
@ -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;
|
||||
@ -993,6 +999,10 @@ void CGameHandler::run(bool resume)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../lib/IGameCallback.h"
|
||||
#include "../lib/battle/CBattleInfoCallback.h"
|
||||
#include "../lib/ScriptHandler.h"
|
||||
#include "TurnTimerHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -106,6 +107,8 @@ public:
|
||||
|
||||
SpellCastEnvironment * spellEnv;
|
||||
|
||||
TurnTimerHandler turnTimerHandler;
|
||||
|
||||
const Services * services() const override;
|
||||
const BattleCb * battle() const override;
|
||||
const GameCb * game() 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();
|
||||
|
||||
|
@ -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})
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
195
server/TurnTimerHandler.cpp
Normal 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
39
server/TurnTimerHandler.h
Normal 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);
|
||||
};
|
@ -131,6 +131,8 @@ void BattleFlowProcessor::onBattleStarted()
|
||||
|
||||
tryPlaceMoats();
|
||||
|
||||
gameHandler->turnTimerHandler.onBattleStart();
|
||||
|
||||
if (gameHandler->gameState()->curB->tacticDistance == 0)
|
||||
onTacticsEnded();
|
||||
}
|
||||
@ -316,6 +318,8 @@ void BattleFlowProcessor::activateNextStack()
|
||||
if(!removeGhosts.changedStacks.empty())
|
||||
gameHandler->sendAndApply(&removeGhosts);
|
||||
|
||||
gameHandler->turnTimerHandler.onBattleNextStack(*next);
|
||||
|
||||
if (!tryMakeAutomaticAction(next))
|
||||
{
|
||||
setActiveStack(next);
|
||||
|
Loading…
Reference in New Issue
Block a user