1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge branch 'develop' into random_template_description

This commit is contained in:
Ivan Savenko
2024-02-11 17:54:18 +02:00
committed by GitHub
268 changed files with 6195 additions and 5878 deletions

7
.gitignore vendored
View File

@@ -1,12 +1,15 @@
/client/vcmiclient /client/vcmiclient
/server/vcmiserver /server/vcmiserver
/launcher/.lupdate
/launcher/vcmilauncher /launcher/vcmilauncher
/mapeditor/.lupdate
/launcher/vcmilauncher_automoc.cpp /launcher/vcmilauncher_automoc.cpp
/conan-* /conan-*
build/ build/
.cache/* .cache/*
out/ out/
/.qt
*.dll *.dll
*.exe *.exe
*.depend *.depend
@@ -42,6 +45,7 @@ VCMI_VS11.opensdf
.DS_Store .DS_Store
CMakeUserPresets.json CMakeUserPresets.json
compile_commands.json compile_commands.json
fuzzylite.pc
# Visual Studio # Visual Studio
*.suo *.suo
@@ -62,5 +66,8 @@ compile_commands.json
/deps /deps
.vs/ .vs/
# Visual Studio Code
/.vscode/
# CLion # CLion
.idea/ .idea/

View File

@@ -843,7 +843,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
} }
} }
if(!reachable && vstd::contains(reachabilityMap[hex], unit)) if(!reachable && std::count(reachabilityMap[hex].begin(), reachabilityMap[hex].end(), unit) > 1)
{ {
blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY);
} }

View File

@@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
std::shared_ptr<CCallback> cb; std::shared_ptr<CCallback> cb;
public: public:
virtual void saveGame(BinarySerializer & h) override; void saveGame(BinarySerializer & h) override;
virtual void loadGame(BinaryDeserializer & h) override; void loadGame(BinaryDeserializer & h) override;
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override; void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
void yourTurn(QueryID queryID) override; void yourTurn(QueryID queryID) override;

View File

@@ -27,7 +27,7 @@ namespace Goals
virtual Goals::TGoalVec decompose() const override; virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const BuildingBehavior & other) const override bool operator==(const BuildingBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -26,7 +26,7 @@ namespace Goals
virtual Goals::TGoalVec decompose() const override; virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const BuyArmyBehavior & other) const override bool operator==(const BuyArmyBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -65,7 +65,7 @@ namespace Goals
return *this; return *this;
} }
virtual bool operator==(const CaptureObjectsBehavior & other) const override; bool operator==(const CaptureObjectsBehavior & other) const override;
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr); static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);

View File

@@ -28,10 +28,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const ClusterBehavior & other) const override bool operator==(const ClusterBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -32,7 +32,7 @@ namespace Goals
virtual Goals::TGoalVec decompose() const override; virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const DefenceBehavior & other) const override bool operator==(const DefenceBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const GatherArmyBehavior & other) const override bool operator==(const GatherArmyBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const RecruitHeroBehavior & other) const override bool operator==(const RecruitHeroBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const StartupBehavior & other) const override bool operator==(const StartupBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool operator==(const StayAtTownBehavior & other) const override bool operator==(const StayAtTownBehavior & other) const override
{ {
return true; return true;
} }

View File

@@ -35,7 +35,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const AdventureSpellCast & other) const override; bool operator==(const AdventureSpellCast & other) const override;
}; };
} }

View File

@@ -32,7 +32,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override bool operator==(const Build & other) const override
{ {
return true; return true;
} }

View File

@@ -29,7 +29,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const BuildBoat & other) const override; bool operator==(const BuildBoat & other) const override;
}; };
} }

View File

@@ -39,7 +39,7 @@ namespace Goals
} }
BuildThis(BuildingID Bid, const CGTownInstance * tid); BuildThis(BuildingID Bid, const CGTownInstance * tid);
virtual bool operator==(const BuildThis & other) const override; bool operator==(const BuildThis & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
}; };

View File

@@ -36,11 +36,11 @@ namespace Goals
priority = 3;//TODO: evaluate? priority = 3;//TODO: evaluate?
} }
virtual bool operator==(const BuyArmy & other) const override; bool operator==(const BuyArmy & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
}; };
} }

View File

@@ -44,7 +44,7 @@ namespace Goals
//h & value & resID & objid & aid & tile & hero & town & bid; //h & value & resID & objid & aid & tile & hero & town & bid;
} }
virtual bool operator==(const AbstractGoal & g) const override bool operator==(const AbstractGoal & g) const override
{ {
if(goalType != g.goalType) if(goalType != g.goalType)
return false; return false;
@@ -54,7 +54,7 @@ namespace Goals
virtual bool operator==(const T & other) const = 0; virtual bool operator==(const T & other) const = 0;
virtual TGoalVec decompose() const override TGoalVec decompose() const override
{ {
TSubgoal single = decomposeSingle(); TSubgoal single = decomposeSingle();
@@ -90,11 +90,11 @@ namespace Goals
return *((T *)this); return *((T *)this);
} }
virtual bool isElementar() const override { return true; } bool isElementar() const override { return true; }
virtual HeroPtr getHero() const override { return AbstractGoal::hero; } virtual HeroPtr getHero() const override { return AbstractGoal::hero; }
virtual int getHeroExchangeCount() const override { return 0; } int getHeroExchangeCount() const override { return 0; }
}; };
} }

View File

@@ -34,11 +34,11 @@ namespace Goals
name = obj->getObjectName(); name = obj->getObjectName();
} }
virtual bool operator==(const CaptureObject & other) const override; bool operator==(const CaptureObject & other) const override;
virtual Goals::TGoalVec decompose() const override; virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool hasHash() const override { return true; } bool hasHash() const override { return true; }
virtual uint64_t getHash() const override; uint64_t getHash() const override;
}; };
} }

View File

@@ -31,10 +31,10 @@ namespace Goals
virtual Goals::TGoalVec decompose() const override; virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool hasHash() const override { return true; } bool hasHash() const override { return true; }
virtual uint64_t getHash() const override; uint64_t getHash() const override;
virtual bool operator==(const CompleteQuest & other) const override; bool operator==(const CompleteQuest & other) const override;
private: private:
TGoalVec tryCompleteQuest() const; TGoalVec tryCompleteQuest() const;

View File

@@ -26,15 +26,15 @@ namespace Goals
{ {
} }
virtual bool operator==(const Composition & other) const override; bool operator==(const Composition & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
Composition & addNext(const AbstractGoal & goal); Composition & addNext(const AbstractGoal & goal);
Composition & addNext(TSubgoal goal); Composition & addNext(TSubgoal goal);
Composition & addNextSequence(const TGoalVec & taskSequence); Composition & addNextSequence(const TGoalVec & taskSequence);
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual bool isElementar() const override; bool isElementar() const override;
virtual int getHeroExchangeCount() const override; int getHeroExchangeCount() const override;
}; };
} }

View File

@@ -33,7 +33,7 @@ namespace Goals
{ {
tile = Tile; tile = Tile;
} }
virtual bool operator==(const DigAtTile & other) const override; bool operator==(const DigAtTile & other) const override;
private: private:
//TSubgoal decomposeSingle() const override; //TSubgoal decomposeSingle() const override;

View File

@@ -26,7 +26,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const DismissHero & other) const override; bool operator==(const DismissHero & other) const override;
}; };
} }

View File

@@ -31,7 +31,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override; bool operator==(const ExchangeSwapTownHeroes & other) const override;
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; } const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
HeroLockedReason getLockingReason() const { return lockingReason; } HeroLockedReason getLockingReason() const { return lockingReason; }

View File

@@ -30,10 +30,10 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const ExecuteHeroChain & other) const override; bool operator==(const ExecuteHeroChain & other) const override;
const AIPath & getPath() const { return chainPath; } const AIPath & getPath() const { return chainPath; }
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; } int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
private: private:
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile); bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);

View File

@@ -36,7 +36,7 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const GatherArmy & other) const override; bool operator==(const GatherArmy & other) const override;
}; };
} }

View File

@@ -32,7 +32,7 @@ namespace Goals
return TGoalVec(); return TGoalVec();
} }
virtual bool operator==(const Invalid & other) const override bool operator==(const Invalid & other) const override
{ {
return true; return true;
} }
@@ -42,7 +42,7 @@ namespace Goals
return "Invalid"; return "Invalid";
} }
virtual void accept(AIGateway * ai) override void accept(AIGateway * ai) override
{ {
throw cannotFulfillGoalException("Can not fulfill Invalid goal!"); throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
} }

View File

@@ -38,7 +38,7 @@ namespace Goals
{ {
} }
virtual bool operator==(const RecruitHero & other) const override bool operator==(const RecruitHero & other) const override
{ {
return true; return true;
} }

View File

@@ -28,7 +28,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const SaveResources & other) const override; bool operator==(const SaveResources & other) const override;
}; };
} }

View File

@@ -26,7 +26,7 @@ namespace Goals
public: public:
StayAtTown(const CGTownInstance * town, AIPath & path); StayAtTown(const CGTownInstance * town, AIPath & path);
virtual bool operator==(const StayAtTown & other) const override; bool operator==(const StayAtTown & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
float getMovementWasted() const { return movementWasted; } float getMovementWasted() const { return movementWasted; }

View File

@@ -34,7 +34,7 @@ namespace Goals
value = val; value = val;
objid = Objid; objid = Objid;
} }
virtual bool operator==(const Trade & other) const override; bool operator==(const Trade & other) const override;
}; };
} }

View File

@@ -29,7 +29,7 @@ namespace Goals
ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
virtual bool operator==(const ArmyUpgrade & other) const override; bool operator==(const ArmyUpgrade & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
uint64_t getUpgradeValue() const { return upgradeValue; } uint64_t getUpgradeValue() const { return upgradeValue; }

View File

@@ -30,7 +30,7 @@ namespace Goals
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false); DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false);
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender); DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
virtual bool operator==(const DefendTown & other) const override; bool operator==(const DefendTown & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
const HitMapInfo & getTreat() const { return treat; } const HitMapInfo & getTreat() const { return treat; }

View File

@@ -28,7 +28,7 @@ namespace Goals
sethero(targetHero); sethero(targetHero);
} }
virtual bool operator==(const HeroExchange & other) const override; bool operator==(const HeroExchange & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
uint64_t getReinforcementArmyStrength() const; uint64_t getReinforcementArmyStrength() const;

View File

@@ -36,7 +36,7 @@ namespace Goals
sethero(pathToCenter.targetHero); sethero(pathToCenter.targetHero);
} }
virtual bool operator==(const UnlockCluster & other) const override; bool operator==(const UnlockCluster & other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
std::shared_ptr<ObjectCluster> getCluster() const { return cluster; } std::shared_ptr<ObjectCluster> getCluster() const { return cluster; }
const AIPath & getPathToCenter() { return pathToCenter; } const AIPath & getPathToCenter() { return pathToCenter; }

View File

@@ -200,7 +200,7 @@ public:
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override; const CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
void commit( void commit(
AIPathNode * destination, AIPathNode * destination,

View File

@@ -34,7 +34,7 @@ namespace AIPathfinding
~AIPathfinderConfig(); ~AIPathfinderConfig();
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
}; };
} }

View File

@@ -29,7 +29,7 @@ namespace AIPathfinding
public: public:
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE); AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination( virtual void applyOnDestination(
const CGHeroInstance * hero, const CGHeroInstance * hero,
@@ -38,7 +38,7 @@ namespace AIPathfinding
AIPathNode * dstMode, AIPathNode * dstMode,
const AIPathNode * srcNode) const override; const AIPathNode * srcNode) const override;
virtual bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
}; };

View File

@@ -28,7 +28,7 @@ namespace AIPathfinding
{ {
} }
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
}; };

View File

@@ -25,7 +25,7 @@ namespace AIPathfinding
class SummonBoatAction : public VirtualBoatAction class SummonBoatAction : public VirtualBoatAction
{ {
public: public:
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination( virtual void applyOnDestination(
const CGHeroInstance * hero, const CGHeroInstance * hero,
@@ -34,9 +34,9 @@ namespace AIPathfinding
AIPathNode * dstMode, AIPathNode * dstMode,
const AIPathNode * srcNode) const override; const AIPathNode * srcNode) const override;
virtual bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
@@ -56,17 +56,17 @@ namespace AIPathfinding
{ {
} }
virtual bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual const CGObjectInstance * targetObject() const override; const CGObjectInstance * targetObject() const override;
}; };
} }

View File

@@ -28,11 +28,11 @@ namespace AIPathfinding
{ {
} }
virtual bool canAct(const AIPathNode * node) const override; bool canAct(const AIPathNode * node) const override;
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
}; };

View File

@@ -29,7 +29,7 @@ namespace AIPathfinding
{ {
} }
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
}; };

View File

@@ -28,7 +28,7 @@ class HeroExchangeArmy : public CArmedInstance
public: public:
TResources armyCost; TResources armyCost;
bool requireBuyArmy; bool requireBuyArmy;
virtual bool needsLastStack() const override; bool needsLastStack() const override;
std::shared_ptr<SpecialAction> getActorAction() const; std::shared_ptr<SpecialAction> getActorAction() const;
HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {} HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
@@ -126,7 +126,7 @@ public:
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai); HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
protected: protected:
virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override; ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;
}; };
class ObjectActor : public ChainActor class ObjectActor : public ChainActor

View File

@@ -39,6 +39,6 @@ namespace Goals
void accept(VCAI * ai) override; void accept(VCAI * ai) override;
std::string name() const override; std::string name() const override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const AdventureSpellCast & other) const override; bool operator==(const AdventureSpellCast & other) const override;
}; };
} }

View File

@@ -29,7 +29,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override bool operator==(const Build & other) const override
{ {
return true; return true;
} }

View File

@@ -32,6 +32,6 @@ namespace Goals
void accept(VCAI * ai) override; void accept(VCAI * ai) override;
std::string name() const override; std::string name() const override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const BuildBoat & other) const override; bool operator==(const BuildBoat & other) const override;
}; };
} }

View File

@@ -43,6 +43,6 @@ namespace Goals
} }
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
//bool fulfillsMe(TSubgoal goal) override; //bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const BuildThis & other) const override; bool operator==(const BuildThis & other) const override;
}; };
} }

View File

@@ -36,6 +36,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const BuyArmy & other) const override; bool operator==(const BuyArmy & other) const override;
}; };
} }

View File

@@ -76,7 +76,7 @@ namespace Goals
//h & value & resID & objid & aid & tile & hero & town & bid; //h & value & resID & objid & aid & tile & hero & town & bid;
} }
virtual bool operator==(const AbstractGoal & g) const override bool operator==(const AbstractGoal & g) const override
{ {
if(goalType != g.goalType) if(goalType != g.goalType)
return false; return false;

View File

@@ -40,6 +40,6 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const ClearWayTo & other) const override; bool operator==(const ClearWayTo & other) const override;
}; };
} }

View File

@@ -35,6 +35,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
TSubgoal whatToDoToTrade(); TSubgoal whatToDoToTrade();
bool fulfillsMe(TSubgoal goal) override; //TODO: Trade bool fulfillsMe(TSubgoal goal) override; //TODO: Trade
virtual bool operator==(const CollectRes & other) const override; bool operator==(const CollectRes & other) const override;
}; };
} }

View File

@@ -30,7 +30,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string name() const override; std::string name() const override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const CompleteQuest & other) const override; bool operator==(const CompleteQuest & other) const override;
private: private:
TGoalVec tryCompleteQuest() const; TGoalVec tryCompleteQuest() const;

View File

@@ -27,6 +27,6 @@ namespace Goals
} }
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Conquer & other) const override; bool operator==(const Conquer & other) const override;
}; };
} }

View File

@@ -36,6 +36,6 @@ namespace Goals
return TGoalVec(); return TGoalVec();
} }
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const DigAtTile & other) const override; bool operator==(const DigAtTile & other) const override;
}; };
} }

View File

@@ -46,7 +46,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Explore & other) const override; bool operator==(const Explore & other) const override;
private: private:
TSubgoal exploreNearestNeighbour(HeroPtr h) const; TSubgoal exploreNearestNeighbour(HeroPtr h) const;

View File

@@ -42,6 +42,6 @@ namespace Goals
} }
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const FindObj & other) const override; bool operator==(const FindObj & other) const override;
}; };
} }

View File

@@ -33,6 +33,6 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const GatherArmy & other) const override; bool operator==(const GatherArmy & other) const override;
}; };
} }

View File

@@ -35,7 +35,7 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const GatherTroops & other) const override; bool operator==(const GatherTroops & other) const override;
private: private:
int getCreaturesCount(const CArmedInstance * army); int getCreaturesCount(const CArmedInstance * army);

View File

@@ -35,6 +35,6 @@ namespace Goals
return TGoalVec(); return TGoalVec();
} }
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const GetArtOfType & other) const override; bool operator==(const GetArtOfType & other) const override;
}; };
} }

View File

@@ -33,7 +33,7 @@ namespace Goals
return iAmElementar(); return iAmElementar();
} }
virtual bool operator==(const Invalid & other) const override bool operator==(const Invalid & other) const override
{ {
return true; return true;
} }

View File

@@ -33,7 +33,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const RecruitHero & other) const override bool operator==(const RecruitHero & other) const override
{ {
return true; return true;
} }

View File

@@ -33,6 +33,6 @@ namespace Goals
priority = 3; //trading is instant, but picking resources is free priority = 3; //trading is instant, but picking resources is free
} }
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Trade & other) const override; bool operator==(const Trade & other) const override;
}; };
} }

View File

@@ -37,6 +37,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const VisitHero & other) const override; bool operator==(const VisitHero & other) const override;
}; };
} }

View File

@@ -27,6 +27,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const VisitObj & other) const override; bool operator==(const VisitObj & other) const override;
}; };
} }

View File

@@ -32,6 +32,6 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const VisitTile & other) const override; bool operator==(const VisitTile & other) const override;
}; };
} }

View File

@@ -31,7 +31,7 @@ namespace Goals
} }
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Win & other) const override bool operator==(const Win & other) const override
{ {
return true; return true;
} }

View File

@@ -99,7 +99,7 @@ public:
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override; const CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
const AIPathNode * getAINode(const CGPathNode * node) const; const AIPathNode * getAINode(const CGPathNode * node) const;
void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater); void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);

View File

@@ -30,6 +30,6 @@ namespace AIPathfinding
~AIPathfinderConfig(); ~AIPathfinderConfig();
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
}; };
} }

View File

@@ -7,4 +7,4 @@ sudo apt-get install libboost-all-dev \
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \ qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \
ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \
libminizip-dev libfuzzylite-dev # Optional dependencies libminizip-dev libfuzzylite-dev libsqlite3-dev # Optional dependencies

View File

@@ -7,4 +7,4 @@ sudo apt-get install libboost-all-dev \
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
qtbase5-dev \ qtbase5-dev \
ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \
libminizip-dev libfuzzylite-dev qttools5-dev # Optional dependencies libminizip-dev libfuzzylite-dev qttools5-dev libsqlite3-dev # Optional dependencies

View File

@@ -41,6 +41,7 @@ if(NOT CMAKE_BUILD_TYPE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo)
endif() endif()
set(buildLobby OFF)
set(singleProcess OFF) set(singleProcess OFF)
set(staticAI OFF) set(staticAI OFF)
if(ANDROID) if(ANDROID)
@@ -89,6 +90,14 @@ if(NOT APPLE_IOS AND NOT ANDROID)
option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF)
endif() endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(buildLobby ON)
endif()
if(NOT APPLE_IOS AND NOT ANDROID)
option(ENABLE_LOBBY "Enable compilation of lobby server" ${buildLobby})
endif()
option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF) option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF)
if(ENABLE_CCACHE) if(ENABLE_CCACHE)
find_program(CCACHE ccache REQUIRED) find_program(CCACHE ccache REQUIRED)
@@ -475,6 +484,10 @@ if(TARGET SDL2_ttf::SDL2_ttf)
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf) add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
endif() endif()
if(ENABLE_LOBBY)
find_package(SQLite3 REQUIRED)
endif()
if(ENABLE_LAUNCHER OR ENABLE_EDITOR) if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
# Widgets finds its own dependencies (QtGui and QtCore). # Widgets finds its own dependencies (QtGui and QtCore).
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
@@ -622,6 +635,9 @@ endif()
if(ENABLE_EDITOR) if(ENABLE_EDITOR)
add_subdirectory(mapeditor) add_subdirectory(mapeditor)
endif() endif()
if(ENABLE_LOBBY)
add_subdirectory(lobby)
endif()
add_subdirectory(client) add_subdirectory(client)
add_subdirectory(server) add_subdirectory(server)
if(ENABLE_TEST) if(ENABLE_TEST)

View File

@@ -20,6 +20,7 @@
"vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s", "vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s",
"vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING", "vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉,重放对手行动功能目前暂未实现!",
"vcmi.capitalColors.0" : "红色", "vcmi.capitalColors.0" : "红色",
"vcmi.capitalColors.1" : "蓝色", "vcmi.capitalColors.1" : "蓝色",
@@ -70,7 +71,9 @@
"vcmi.lobby.mapPreview" : "地图预览", "vcmi.lobby.mapPreview" : "地图预览",
"vcmi.lobby.noPreview" : "无地上部分", "vcmi.lobby.noPreview" : "无地上部分",
"vcmi.lobby.noUnderground" : "无地下部分", "vcmi.lobby.noUnderground" : "无地下部分",
"vcmi.lobby.sortDate" : "以修改时间排序地图",
"vcmi.client.errors.invalidMap" : "{非法地图或战役}\n\n启动游戏失败,选择的地图或者战役,无效或被污染。原因:\n%s",
"vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。", "vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。",
"vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。", "vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。",
"vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}", "vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}",
@@ -81,9 +84,9 @@
"vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!", "vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!",
"vcmi.settingsMainWindow.generalTab.hover" : "常规", "vcmi.settingsMainWindow.generalTab.hover" : "常规",
"vcmi.settingsMainWindow.generalTab.help" : "切换到“常规”选项卡 - 设置游戏客户端呈现", "vcmi.settingsMainWindow.generalTab.help" : "切换到“常规”选项卡 - 配置客户端常规内容",
"vcmi.settingsMainWindow.battleTab.hover" : "战斗", "vcmi.settingsMainWindow.battleTab.hover" : "战斗",
"vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗”选项卡 - 这些设置允许配置战斗界面和相关内容", "vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗”选项卡 - 配置游戏战斗界面内容",
"vcmi.settingsMainWindow.adventureTab.hover" : "冒险地图", "vcmi.settingsMainWindow.adventureTab.hover" : "冒险地图",
"vcmi.settingsMainWindow.adventureTab.help" : "切换到“冒险地图”选项卡 - 冒险地图即玩家能操作英雄移动的界面", "vcmi.settingsMainWindow.adventureTab.help" : "切换到“冒险地图”选项卡 - 冒险地图即玩家能操作英雄移动的界面",
@@ -93,43 +96,43 @@
"vcmi.systemOptions.townsGroup" : "城镇画面", "vcmi.systemOptions.townsGroup" : "城镇画面",
"vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)", "vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)",
"vcmi.systemOptions.fullscreenBorderless.help" : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。在这种模式下,游戏会使用和桌面一致的分辨率而非设置的分辨率。 ", "vcmi.systemOptions.fullscreenBorderless.help" : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。模式下,游戏会始终和桌面分辨率保持一致,无视设置的分辨率。 ",
"vcmi.systemOptions.fullscreenExclusive.hover" : "全屏 (独占)", "vcmi.systemOptions.fullscreenExclusive.hover" : "全屏 (独占)",
"vcmi.systemOptions.fullscreenExclusive.help" : "{全屏}\n\n选中时,VCMI将以独占全屏模式运行。在这种模式下,游戏会将显示器分辨率改变为设置值。", "vcmi.systemOptions.fullscreenExclusive.help" : "{全屏}\n\n选中时,VCMI将以独占全屏模式运行。模式下,游戏会将显示器分辨率改变为设置值。",
"vcmi.systemOptions.resolutionButton.hover" : "分辨率", "vcmi.systemOptions.resolutionButton.hover" : "分辨率: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{分辨率选择}\n\n改变游戏内的分辨率,更改后需要重启游戏使其生效。", "vcmi.systemOptions.resolutionButton.help" : "{分辨率选择}\n\n改变游戏内的分辨率。",
"vcmi.systemOptions.resolutionMenu.hover" : "分辨率选择", "vcmi.systemOptions.resolutionMenu.hover" : "选择分辨率",
"vcmi.systemOptions.resolutionMenu.help" : "修改游戏运行时的分辨率。", "vcmi.systemOptions.resolutionMenu.help" : "修改游戏运行时的分辨率。",
"vcmi.systemOptions.scalingButton.hover" : "界面大小: %p%", "vcmi.systemOptions.scalingButton.hover" : "界面缩放: %p%",
"vcmi.systemOptions.scalingButton.help" : "{界面大小}\n\n改变界面的大小", "vcmi.systemOptions.scalingButton.help" : "{界面缩放}\n\n改变用户界面的缩放比例。",
"vcmi.systemOptions.scalingMenu.hover" : "选择界面大小", "vcmi.systemOptions.scalingMenu.hover" : "选择界面缩放",
"vcmi.systemOptions.scalingMenu.help" : "改变游戏界面大小。", "vcmi.systemOptions.scalingMenu.help" : "改变游戏界面缩放。",
"vcmi.systemOptions.longTouchButton.hover" : "触控间距: %d 毫秒", // Translation note: "ms" = "milliseconds" "vcmi.systemOptions.longTouchButton.hover" : "长触延迟: %d 毫秒", // Translation note: "ms" = "milliseconds"
"vcmi.systemOptions.longTouchButton.help" : "{触控间距}\n\n使用触摸屏时,触摸屏幕指定持续时间(以毫秒为单位)后将出现弹出窗口。", "vcmi.systemOptions.longTouchButton.help" : "{长触延迟}\n\n使用触摸屏时,长触屏幕一定时间后出现弹出窗口(单位:毫秒)。",
"vcmi.systemOptions.longTouchMenu.hover" : "选择触控间距", "vcmi.systemOptions.longTouchMenu.hover" : "选择长触延迟",
"vcmi.systemOptions.longTouchMenu.help" : "改变触控间距。", "vcmi.systemOptions.longTouchMenu.help" : "改变长触延迟。",
"vcmi.systemOptions.longTouchMenu.entry" : "%d 毫秒", "vcmi.systemOptions.longTouchMenu.entry" : "%d 毫秒",
"vcmi.systemOptions.framerateButton.hover" : "显示FPS", "vcmi.systemOptions.framerateButton.hover" : "显示FPS",
"vcmi.systemOptions.framerateButton.help" : "{显示FPS}\n\n打开/关闭在游戏窗口角落FPS指示器。", "vcmi.systemOptions.framerateButton.help" : "{显示FPS}\n\n切换在游戏窗口角落FPS指示器。",
"vcmi.systemOptions.hapticFeedbackButton.hover" : "触觉反馈", "vcmi.systemOptions.hapticFeedbackButton.hover" : "触觉反馈",
"vcmi.systemOptions.hapticFeedbackButton.help" : "{触觉反馈}\n\n切换触摸输入的触觉反馈。", "vcmi.systemOptions.hapticFeedbackButton.help" : "{触觉反馈}\n\n切换触摸输入的触觉反馈。",
"vcmi.systemOptions.enableUiEnhancementsButton.hover" : "界面增强", "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "界面增强",
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{界面增强}\n\n显示所有界面增强内容,如背包和魔法书等。", "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{界面增强}\n\n显示所有界面优化增强,如背包按钮等。关闭该项以贴近经典模式。",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "增大魔法书界面", "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "扩展魔法书",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{增大魔法书界面}\n\n可以在魔法书单页中显示更多魔法,从而获得更好的视觉效果。", "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{扩展魔法书}\n\n启用更大的魔法书界面,每页展示更多魔法,但魔法书翻页特效在该模式下无法呈现。",
"vcmi.systemOptions.audioMuteFocus.hover" : "切换窗口时静音", "vcmi.systemOptions.audioMuteFocus.hover" : "失去焦点时静音",
"vcmi.systemOptions.audioMuteFocus.help" : "{切换窗口时静音}\n\n快速切换窗口时将静音,在工作时,切换游戏窗口不会有声音。", "vcmi.systemOptions.audioMuteFocus.help" : "{失去焦点时静音}\n\n当窗口失去焦点时静音,游戏内消息提示和新回合提示除外。",
"vcmi.adventureOptions.infoBarPick.hover" : "在信息面板显示消息", "vcmi.adventureOptions.infoBarPick.hover" : "在信息面板显示消息",
"vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。", "vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n尽可能将来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。",
"vcmi.adventureOptions.numericQuantities.hover" : "生物数量显示", "vcmi.adventureOptions.numericQuantities.hover" : "生物数量显示",
"vcmi.adventureOptions.numericQuantities.help" : "{生物数量显示}\n\n以数字 A-B 格式显示不准确的敌方生物数量。", "vcmi.adventureOptions.numericQuantities.help" : "{生物数量显示}\n\n以数字 A-B 格式显示不准确的敌方生物数量。",
"vcmi.adventureOptions.forceMovementInfo.hover" : "在状态栏中显示移动", "vcmi.adventureOptions.forceMovementInfo.hover" : "总是显示移动花费",
"vcmi.adventureOptions.forceMovementInfo.help" : "{在状态栏中显示动力}\n\n不需要按ALT就可以显示移动力。", "vcmi.adventureOptions.forceMovementInfo.help" : "{总是显示移动花费}\n\n总是在状态栏中显示动力花费(否则仅在按下ALT时显示)。",
"vcmi.adventureOptions.showGrid.hover" : "显示网格", "vcmi.adventureOptions.showGrid.hover" : "显示网格",
"vcmi.adventureOptions.showGrid.help" : "{显示网格}\n\n显示网格覆盖层,高亮冒险地图物件的边沿。", "vcmi.adventureOptions.showGrid.help" : "{显示网格}\n\n显示网格覆盖层,高亮冒险地图物件的边沿。",
"vcmi.adventureOptions.borderScroll.hover" : "滚动边界", "vcmi.adventureOptions.borderScroll.hover" : "边缘滚动",
"vcmi.adventureOptions.borderScroll.help" : "{滚动边界}\n\n当光标靠近窗口边缘时滚动冒险地图。 可以通过按住 CTRL 键来禁用。", "vcmi.adventureOptions.borderScroll.help" : "{边缘滚动}\n\n当光标靠近窗口边缘时滚动冒险地图。 可以通过按住 CTRL 键来禁用。",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "信息面板生物管理", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "信息面板生物管理",
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{信息面板生物管理}\n\n允许在信息面板中重新排列生物,而不是在默认组件之间循环。", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{信息面板生物管理}\n\n允许在信息面板中重新排列生物,而不是在默认组件之间循环。",
"vcmi.adventureOptions.leftButtonDrag.hover" : "左键拖动地图", "vcmi.adventureOptions.leftButtonDrag.hover" : "左键拖动地图",
@@ -137,13 +140,15 @@
"vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动", "vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动",
"vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。", "vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。",
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "关闭淡入淡出特效", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "关闭淡入淡出特效",
"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{关闭淡入淡出特效}\n\n启用后,跳过物体淡出或类似特效资源收集,登船等。设置此项能在渲染开销重时能够加快UI的响应,尤其是在PvP对战中。当移动速度被设置为最大时忽略此项设置。", "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{关闭淡入淡出特效}\n\n启用后,跳过物体淡出或类似特效(资源收集,登船等)。设置此项能在渲染开销重时能够加快UI的响应,尤其是在PvP对战中。当移动速度被设置为最大时忽略此项设置。",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "",
"vcmi.adventureOptions.mapScrollSpeed1.help": "将地图卷动速度设置为非常慢", "vcmi.adventureOptions.mapScrollSpeed1.help": "将地图卷动速度设置为非常慢",
"vcmi.adventureOptions.mapScrollSpeed5.help": "将地图卷动速度设置为非常快", "vcmi.adventureOptions.mapScrollSpeed5.help": "将地图卷动速度设置为非常快",
"vcmi.adventureOptions.mapScrollSpeed6.help": "将地图卷动速度设置为即刻。", "vcmi.adventureOptions.mapScrollSpeed6.help": "将地图卷动速度设置为即刻。",
"vcmi.adventureOptions.hideBackground.hover" : "隐藏背景",
"vcmi.adventureOptions.hideBackground.help" : "{隐藏背景}\n\n隐藏冒险地图背景,以显示贴图代替。",
"vcmi.battleOptions.queueSizeLabel.hover": "回合顺序指示器", "vcmi.battleOptions.queueSizeLabel.hover": "回合顺序指示器",
"vcmi.battleOptions.queueSizeNoneButton.hover": "关闭", "vcmi.battleOptions.queueSizeNoneButton.hover": "关闭",
@@ -168,6 +173,8 @@
"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{显示英雄统计数据窗口}\n\n永久切换并显示主要统计数据和法术点的英雄统计数据窗口。", "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{显示英雄统计数据窗口}\n\n永久切换并显示主要统计数据和法术点的英雄统计数据窗口。",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐", "vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。", "vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
"vcmi.battleOptions.endWithAutocombat.hover": "结束战斗",
"vcmi.battleOptions.endWithAutocombat.help": "{结束战斗}\n\n以自动战斗立即结束剩余战斗过程",
"vcmi.adventureMap.revisitObject.hover" : "重新访问", "vcmi.adventureMap.revisitObject.hover" : "重新访问",
"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。", "vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
@@ -183,17 +190,22 @@
"vcmi.battleWindow.damageEstimation.damage.1" : "%d 伤害", "vcmi.battleWindow.damageEstimation.damage.1" : "%d 伤害",
"vcmi.battleWindow.damageEstimation.kills" : "%d 将被消灭", "vcmi.battleWindow.damageEstimation.kills" : "%d 将被消灭",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭", "vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭",
"vcmi.battleWindow.killed" : "已消灭",
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s 死于精准射击",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s 死于精准射击",
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s 死于精准射击",
"vcmi.battleWindow.endWithAutocombat" : "您确定想以自动战斗立即结束吗?",
"vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果", "vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果",
"vcmi.tutorialWindow.title" : "触摸屏介绍", "vcmi.tutorialWindow.title" : "触摸屏介绍",
"vcmi.tutorialWindow.decription.RightClick" : "触摸并按住要右键单击的元素。 触摸可用区域以关闭。", "vcmi.tutorialWindow.decription.RightClick" : "长按要右键单击的元素。 触摸其他区域以关闭。",
"vcmi.tutorialWindow.decription.MapPanning" : "用一根手指触摸并拖动来移动地图。", "vcmi.tutorialWindow.decription.MapPanning" : "单指拖拽以移动地图。",
"vcmi.tutorialWindow.decription.MapZooming" : "用两根手指捏合可更改地图缩放比例。", "vcmi.tutorialWindow.decription.MapZooming" : "两指开合更改地图缩放比例。",
"vcmi.tutorialWindow.decription.RadialWheel" : "滑动可打开径向轮以执行各种操作,例如生物/英雄管理和城镇排序。", "vcmi.tutorialWindow.decription.RadialWheel" : "滑动可打开径向轮以执行各种操作,例如生物/英雄管理和城镇排序。",
"vcmi.tutorialWindow.decription.BattleDirection" : "要从特定方向攻击,请向要进行攻击的方向滑动。", "vcmi.tutorialWindow.decription.BattleDirection" : "要从特定方向攻击,请向要进行攻击的方向滑动。",
"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "如果手指距离足够远,可以取消攻击方向手势。", "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "将长触状态的手指拉远足够距离,可以取消攻击方向手势。",
"vcmi.tutorialWindow.decription.AbortSpell" : "触摸并按住可取消魔法。", "vcmi.tutorialWindow.decription.AbortSpell" : "长触以取消魔法。",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示可招募生物", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示可招募生物",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示可招募生物}\n\n在城镇摘要(城镇屏幕的左下角)中显示可招募的生物数量,而不是增长。", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示可招募生物}\n\n在城镇摘要(城镇屏幕的左下角)中显示可招募的生物数量,而不是增长。",
@@ -212,7 +224,7 @@
"vcmi.townHall.greetingDefence" : "在%s中稍待片刻,富有战斗经验的战士会教你防御技巧(防御力+1)。", "vcmi.townHall.greetingDefence" : "在%s中稍待片刻,富有战斗经验的战士会教你防御技巧(防御力+1)。",
"vcmi.townHall.hasNotProduced" : "本周%s并没有产生什么资源。", "vcmi.townHall.hasNotProduced" : "本周%s并没有产生什么资源。",
"vcmi.townHall.hasProduced" : "本周%s产生了%d个%s。", "vcmi.townHall.hasProduced" : "本周%s产生了%d个%s。",
"vcmi.townHall.greetingCustomBonus" : "当你的英雄访问%s 时,这个神奇的建筑使你的英雄 +%d %s%s。", "vcmi.townHall.greetingCustomBonus" : "%s 给予英雄 +%d %s%s。",
"vcmi.townHall.greetingCustomUntil" : "直到下一场战斗。", "vcmi.townHall.greetingCustomUntil" : "直到下一场战斗。",
"vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。", "vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。",
@@ -225,13 +237,15 @@
"vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面", "vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面",
"vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物", "vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物",
"vcmi.tavernWindow.inviteHero" : "邀请英雄",
"vcmi.commanderWindow.artifactMessage" : "你要把这个宝物还给英雄吗?", "vcmi.commanderWindow.artifactMessage" : "你要把这个宝物还给英雄吗?",
"vcmi.creatureWindow.showBonuses.hover" : "属性视图", "vcmi.creatureWindow.showBonuses.hover" : "属性视图",
"vcmi.creatureWindow.showBonuses.help" : "显示指挥官的所有属性增益", "vcmi.creatureWindow.showBonuses.help" : "显示指挥官的所有属性增益",
"vcmi.creatureWindow.showSkills.hover" : "技能视图", "vcmi.creatureWindow.showSkills.hover" : "技能视图",
"vcmi.creatureWindow.showSkills.help" : "显示指挥官的所有学习的技能", "vcmi.creatureWindow.showSkills.help" : "显示指挥官的所有学习的技能",
"vcmi.creatureWindow.returnArtifact.hover" : "交换宝物", "vcmi.creatureWindow.returnArtifact.hover" : "返还宝物",
"vcmi.creatureWindow.returnArtifact.help" : "点击这个按钮将宝物反还到英雄的背包里", "vcmi.creatureWindow.returnArtifact.help" : "点击这个按钮将宝物反还到英雄的背包里",
"vcmi.questLog.hideComplete.hover" : "隐藏完成任务", "vcmi.questLog.hideComplete.hover" : "隐藏完成任务",
@@ -321,6 +335,7 @@
"vcmi.map.victoryCondition.collectArtifacts.message" : "获得所有三件宝物", "vcmi.map.victoryCondition.collectArtifacts.message" : "获得所有三件宝物",
"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "祝贺你!你取得了天使联盟且消灭了所有敌人,取得了胜利!", "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "祝贺你!你取得了天使联盟且消灭了所有敌人,取得了胜利!",
"vcmi.map.victoryCondition.angelicAlliance.message" : "击败所有敌人并取得天使联盟", "vcmi.map.victoryCondition.angelicAlliance.message" : "击败所有敌人并取得天使联盟",
"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "功亏一篑,你已失去了天使联盟的一个组件。彻底的失败。",
// few strings from WoG used by vcmi // few strings from WoG used by vcmi
"vcmi.stackExperience.description" : "» 经 验 获 得 明 细 «\n\n生物类型 ................... : %s\n经验等级 ................. : %s (%i)\n经验点数 ............... : %i\n下一个等级所需经验 .. : %i\n每次战斗最大获得经验 ... : %i%% (%i)\n获得经验的生物数量 .... : %i\n最大招募数量\n不会丢失经验升级 .... : %i\n经验倍数 ........... : %.2f\n升级倍数 .............. : %.2f\n10级后经验值 ........ : %i\n最大招募数量下\n 升级到10级所需经验数量: %i", "vcmi.stackExperience.description" : "» 经 验 获 得 明 细 «\n\n生物类型 ................... : %s\n经验等级 ................. : %s (%i)\n经验点数 ............... : %i\n下一个等级所需经验 .. : %i\n每次战斗最大获得经验 ... : %i%% (%i)\n获得经验的生物数量 .... : %i\n最大招募数量\n不会丢失经验升级 .... : %i\n经验倍数 ........... : %.2f\n升级倍数 .............. : %.2f\n10级后经验值 ........ : %i\n最大招募数量下\n 升级到10级所需经验数量: %i",
@@ -374,8 +389,10 @@
"core.bonus.ENCHANTER.description": "每回合群体施放${subtype.spell}", "core.bonus.ENCHANTER.description": "每回合群体施放${subtype.spell}",
"core.bonus.ENCHANTED.name": "法术加持", "core.bonus.ENCHANTED.name": "法术加持",
"core.bonus.ENCHANTED.description": "永久处于${subtype.spell}影响", "core.bonus.ENCHANTED.description": "永久处于${subtype.spell}影响",
"core.bonus.ENEMY_ATTACK_REDUCTION.name": "忽略攻击 (${val}%)",
"core.bonus.ENEMY_ATTACK_REDUCTION.description": "被攻击时,进攻方${val}%的攻击力将被无视。",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)", "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "攻击时,目标生物${val}%的防御力将被无视。", "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "发动攻击时,防御方${val}%的防御力将被无视。",
"core.bonus.FIRE_IMMUNITY.name": "火系免疫", "core.bonus.FIRE_IMMUNITY.name": "火系免疫",
"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法。", "core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法。",
"core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)", "core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)",
@@ -386,7 +403,9 @@
"core.bonus.FEAR.description": "使得敌方一只部队恐惧", "core.bonus.FEAR.description": "使得敌方一只部队恐惧",
"core.bonus.FEARLESS.name": "无惧", "core.bonus.FEARLESS.name": "无惧",
"core.bonus.FEARLESS.description": "免疫恐惧特质", "core.bonus.FEARLESS.description": "免疫恐惧特质",
"core.bonus.FLYING.name": "飞行兵种", "core.bonus.FEROCITY.name": "凶猛追击",
"core.bonus.FEROCITY.description": "杀死任意生物后额外攻击${val}次",
"core.bonus.FLYING.name": "飞行能力",
"core.bonus.FLYING.description": "以飞行的方式移动(无视障碍)", "core.bonus.FLYING.description": "以飞行的方式移动(无视障碍)",
"core.bonus.FREE_SHOOTING.name": "近身射击", "core.bonus.FREE_SHOOTING.name": "近身射击",
"core.bonus.FREE_SHOOTING.description": "能在近战范围内进行射击", "core.bonus.FREE_SHOOTING.description": "能在近战范围内进行射击",
@@ -396,50 +415,52 @@
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "减少从远程和近战中遭受的物理伤害", "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "减少从远程和近战中遭受的物理伤害",
"core.bonus.HATE.name": "${subtype.creature}的死敌", "core.bonus.HATE.name": "${subtype.creature}的死敌",
"core.bonus.HATE.description": "对${subtype.creature}造成额外${val}%伤害", "core.bonus.HATE.description": "对${subtype.creature}造成额外${val}%伤害",
"core.bonus.HEALER.name": "治疗", "core.bonus.HEALER.name": "治疗",
"core.bonus.HEALER.description": "可以治疗友军单位", "core.bonus.HEALER.description": "可以治疗友军单位",
"core.bonus.HP_REGENERATION.name": "再生", "core.bonus.HP_REGENERATION.name": "再生",
"core.bonus.HP_REGENERATION.description": "每回合恢复${val}点生命值", "core.bonus.HP_REGENERATION.description": "每回合恢复${val}点生命值",
"core.bonus.JOUSTING.name": "冲锋", "core.bonus.JOUSTING.name": "勇士冲锋",
"core.bonus.JOUSTING.description": "每移动一格 +${val}%伤害", "core.bonus.JOUSTING.description": "每移动一格 +${val}%伤害",
"core.bonus.KING.name": "顶级怪物", "core.bonus.KING.name": "王牌",
"core.bonus.KING.description": "受${val}级或更高级屠戮成性影响", "core.bonus.KING.description": "受${val}级或更高级屠戮成性影响",
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "免疫1-${val}级魔法", "core.bonus.LEVEL_SPELL_IMMUNITY.name": "免疫1-${val}级魔法",
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "免疫1-${val}级的魔法", "core.bonus.LEVEL_SPELL_IMMUNITY.description": "免疫1-${val}级的魔法",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "受限射击距离", "core.bonus.LIMITED_SHOOTING_RANGE.name": "射程限制",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "无法${val}格外的单位为射击目标", "core.bonus.LIMITED_SHOOTING_RANGE.description": "无法瞄准${val}格外的单位",
"core.bonus.LIFE_DRAIN.name": "吸取生命 (${val}%)", "core.bonus.LIFE_DRAIN.name": "吸取生命 (${val}%)",
"core.bonus.LIFE_DRAIN.description": "吸取${val}%伤害回复自身", "core.bonus.LIFE_DRAIN.description": "吸取${val}%伤害回复自身",
"core.bonus.MANA_CHANNELING.name": "法虹吸${val}%", "core.bonus.MANA_CHANNELING.name": "法虹吸${val}%",
"core.bonus.MANA_CHANNELING.description": "使你的英雄有${val}%几率获得敌人施法的魔法值", "core.bonus.MANA_CHANNELING.description": "使你的英雄有${val}%几率获得敌人施法的魔法值",
"core.bonus.MANA_DRAIN.name": "吸取力", "core.bonus.MANA_DRAIN.name": "吸取力",
"core.bonus.MANA_DRAIN.description": "每回合吸取${val}魔法值", "core.bonus.MANA_DRAIN.description": "每回合吸取${val}魔法值",
"core.bonus.MAGIC_MIRROR.name": "魔法神镜 (${val}%)", "core.bonus.MAGIC_MIRROR.name": "魔法神镜 (${val}%)",
"core.bonus.MAGIC_MIRROR.description": "${val}%几率将进攻性魔法导向一个敌人单位", "core.bonus.MAGIC_MIRROR.description": "${val}%几率将进攻性魔法导向一个敌人单位",
"core.bonus.MAGIC_RESISTANCE.name": "魔法抵抗 (${val}%)", "core.bonus.MAGIC_RESISTANCE.name": "魔法抵抗 (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description": "${val}%几率抵抗敌人的魔法", "core.bonus.MAGIC_RESISTANCE.description": "${val}%几率抵抗敌人的魔法",
"core.bonus.MIND_IMMUNITY.name": "免疫心智", "core.bonus.MIND_IMMUNITY.name": "免疫心智魔法",
"core.bonus.MIND_IMMUNITY.description": "不受心智魔法影响", "core.bonus.MIND_IMMUNITY.description": "不受心智相关的魔法影响",
"core.bonus.NO_DISTANCE_PENALTY.name": "无视距离惩罚", "core.bonus.NO_DISTANCE_PENALTY.name": "无视射程惩罚",
"core.bonus.NO_DISTANCE_PENALTY.description": "任意距离均造成全额伤害", "core.bonus.NO_DISTANCE_PENALTY.description": "任意射程造成全额伤害",
"core.bonus.NO_MELEE_PENALTY.name": "无近战惩罚", "core.bonus.NO_MELEE_PENALTY.name": "无近战惩罚",
"core.bonus.NO_MELEE_PENALTY.description": "该生物没有近战伤害惩罚", "core.bonus.NO_MELEE_PENALTY.description": "该生物没有近战伤害惩罚",
"core.bonus.NO_MORALE.name": "无士气", "core.bonus.NO_MORALE.name": "无士气",
"core.bonus.NO_MORALE.description": "生物不受士气影响", "core.bonus.NO_MORALE.description": "生物不受士气影响",
"core.bonus.NO_WALL_PENALTY.name": "无城墙影响", "core.bonus.NO_WALL_PENALTY.name": "无城墙惩罚",
"core.bonus.NO_WALL_PENALTY.description": "攻城战中不被城墙阻挡造成全额伤害", "core.bonus.NO_WALL_PENALTY.description": "攻城战中无视城墙阻挡造成全额伤害",
"core.bonus.NON_LIVING.name": "无生命", "core.bonus.NON_LIVING.name": "无生命",
"core.bonus.NON_LIVING.description": "免疫大多数的效果", "core.bonus.NON_LIVING.description": "免疫大多数的效果",
"core.bonus.RANDOM_SPELLCASTER.name": "随机施法", "core.bonus.RANDOM_SPELLCASTER.name": "随机施法",
"core.bonus.RANDOM_SPELLCASTER.description": "可以施放随机魔法", "core.bonus.RANDOM_SPELLCASTER.description": "可以施放随机魔法",
"core.bonus.RANGED_RETALIATION.name": "远程反击", "core.bonus.RANGED_RETALIATION.name": "远程反击",
"core.bonus.RANGED_RETALIATION.description": "可以对远程攻击进行反击", "core.bonus.RANGED_RETALIATION.description": "可以对远程攻击进行反击",
"core.bonus.RECEPTIVE.name": "接", "core.bonus.RECEPTIVE.name": "接",
"core.bonus.RECEPTIVE.description": "不会免疫有益魔法", "core.bonus.RECEPTIVE.description": "不会免疫有益魔法",
"core.bonus.REBIRTH.name": "复生 (${val}%)", "core.bonus.REBIRTH.name": "复生 (${val}%)",
"core.bonus.REBIRTH.description": "当整支部队死亡后${val}%会复活", "core.bonus.REBIRTH.description": "当整支部队死亡后${val}%会复活",
"core.bonus.RETURN_AFTER_STRIKE.name": "攻击后返回", "core.bonus.RETURN_AFTER_STRIKE.name": "攻击后返回",
"core.bonus.RETURN_AFTER_STRIKE.description": "近战攻击后回到初始位置", "core.bonus.RETURN_AFTER_STRIKE.description": "近战攻击后回到初始位置",
"core.bonus.REVENGE.name": "复仇",
"core.bonus.REVENGE.description": "根据攻击者在战斗中失去的生命值造成额外伤害",
"core.bonus.SHOOTER.name": "远程攻击", "core.bonus.SHOOTER.name": "远程攻击",
"core.bonus.SHOOTER.description": "生物可以射击", "core.bonus.SHOOTER.description": "生物可以射击",
"core.bonus.SHOOTS_ALL_ADJACENT.name": "范围远程攻击", "core.bonus.SHOOTS_ALL_ADJACENT.name": "范围远程攻击",
@@ -471,7 +492,7 @@
"core.bonus.TRANSMUTATION.name": "变形术", "core.bonus.TRANSMUTATION.name": "变形术",
"core.bonus.TRANSMUTATION.description": "${val}%机会将被攻击单位变成其他生物", "core.bonus.TRANSMUTATION.description": "${val}%机会将被攻击单位变成其他生物",
"core.bonus.UNDEAD.name": "不死生物", "core.bonus.UNDEAD.name": "不死生物",
"core.bonus.UNDEAD.description": "该生物属于丧尸", "core.bonus.UNDEAD.description": "该生物属于不死生物",
"core.bonus.UNLIMITED_RETALIATIONS.name": "无限反击", "core.bonus.UNLIMITED_RETALIATIONS.name": "无限反击",
"core.bonus.UNLIMITED_RETALIATIONS.description": "每回合可以无限反击敌人", "core.bonus.UNLIMITED_RETALIATIONS.description": "每回合可以无限反击敌人",
"core.bonus.WATER_IMMUNITY.name": "水系免疫", "core.bonus.WATER_IMMUNITY.name": "水系免疫",

View File

@@ -63,7 +63,6 @@
"vcmi.mainMenu.serverClosing" : "Closing...", "vcmi.mainMenu.serverClosing" : "Closing...",
"vcmi.mainMenu.hostTCP" : "Host TCP/IP game", "vcmi.mainMenu.hostTCP" : "Host TCP/IP game",
"vcmi.mainMenu.joinTCP" : "Join TCP/IP game", "vcmi.mainMenu.joinTCP" : "Join TCP/IP game",
"vcmi.mainMenu.playerName" : "Player",
"vcmi.lobby.filepath" : "File path", "vcmi.lobby.filepath" : "File path",
"vcmi.lobby.creationDate" : "Creation date", "vcmi.lobby.creationDate" : "Creation date",
@@ -73,12 +72,33 @@
"vcmi.lobby.noUnderground" : "no underground", "vcmi.lobby.noUnderground" : "no underground",
"vcmi.lobby.sortDate" : "Sorts maps by change date", "vcmi.lobby.sortDate" : "Sorts maps by change date",
"vcmi.lobby.login.title" : "VCMI Lobby",
"vcmi.lobby.login.username" : "Username:",
"vcmi.lobby.login.connecting" : "Connecting...",
"vcmi.lobby.login.error" : "Connection error: %s",
"vcmi.lobby.login.create" : "New Account",
"vcmi.lobby.login.login" : "Login",
"vcmi.lobby.room.create" : "Create Room",
"vcmi.lobby.room.players.limit" : "Players Limit",
"vcmi.lobby.room.public" : "Public",
"vcmi.lobby.room.private" : "Private",
"vcmi.lobby.room.description.public" : "Any player can join public room.",
"vcmi.lobby.room.description.private" : "Only invited players can join private room.",
"vcmi.lobby.room.description.new" : "To start the game, select a scenario or set up a random map.",
"vcmi.lobby.room.description.load" : "To start the game, use one of your saved games.",
"vcmi.lobby.room.description.limit" : "Up to %d players can enter your room, including you.",
"vcmi.lobby.room.new" : "New Game",
"vcmi.lobby.room.load" : "Load Game",
"vcmi.lobby.room.type" : "Room Type",
"vcmi.lobby.room.mode" : "Game Mode",
"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s", "vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
"vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
"vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToEnable" : "{Following mods are required}",
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",
"vcmi.server.confirmReconnect" : "Do you want to reconnect to the last session?",
"vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n", "vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n",
"vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n", "vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n",
"vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!", "vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!",

View File

@@ -20,6 +20,7 @@
"vcmi.adventureMap.playerAttacked" : "El jugador ha sido atacado: %s", "vcmi.adventureMap.playerAttacked" : "El jugador ha sido atacado: %s",
"vcmi.adventureMap.moveCostDetails" : "Puntos de movimiento - Coste: %TURNS turnos + %POINTS puntos, Puntos restantes: %REMAINING", "vcmi.adventureMap.moveCostDetails" : "Puntos de movimiento - Coste: %TURNS turnos + %POINTS puntos, Puntos restantes: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Puntos de movimiento - Coste: %POINTS puntos, Puntos restantes: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Puntos de movimiento - Coste: %POINTS puntos, Puntos restantes: %REMAINING",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Disculpe, la repetición del turno del oponente aún no está implementada.",
"vcmi.capitalColors.0" : "Rojo", "vcmi.capitalColors.0" : "Rojo",
"vcmi.capitalColors.1" : "Azul", "vcmi.capitalColors.1" : "Azul",
@@ -70,7 +71,9 @@
"vcmi.lobby.mapPreview" : "Vista previa del mapa", "vcmi.lobby.mapPreview" : "Vista previa del mapa",
"vcmi.lobby.noPreview" : "sin vista previa", "vcmi.lobby.noPreview" : "sin vista previa",
"vcmi.lobby.noUnderground" : "sin subterráneo", "vcmi.lobby.noUnderground" : "sin subterráneo",
"vcmi.lobby.sortDate" : "Ordena los mapas por la fecha de modificación",
"vcmi.client.errors.invalidMap" : "{Mapa o Campaña invalido}\n\n¡No se pudo iniciar el juego! El mapa o la campaña seleccionados pueden no ser válidos o estar dañados. Motivo:\n%s",
"vcmi.client.errors.missingCampaigns" : "{Archivos de datos faltantes}\n\n¡No se encontraron los archivos de datos de las campañas! Quizás estés utilizando archivos de datos incompletos o dañados de Heroes 3. Por favor, reinstala los datos del juego.", "vcmi.client.errors.missingCampaigns" : "{Archivos de datos faltantes}\n\n¡No se encontraron los archivos de datos de las campañas! Quizás estés utilizando archivos de datos incompletos o dañados de Heroes 3. Por favor, reinstala los datos del juego.",
"vcmi.server.errors.existingProcess" : "Otro servidor VCMI está en ejecución. Por favor, termínalo antes de comenzar un nuevo juego.", "vcmi.server.errors.existingProcess" : "Otro servidor VCMI está en ejecución. Por favor, termínalo antes de comenzar un nuevo juego.",
"vcmi.server.errors.modsToEnable" : "{Se requieren los siguientes mods}", "vcmi.server.errors.modsToEnable" : "{Se requieren los siguientes mods}",
@@ -144,6 +147,8 @@
"vcmi.adventureOptions.mapScrollSpeed1.help": "Establece la velocidad de desplazamiento del mapa como muy lenta", "vcmi.adventureOptions.mapScrollSpeed1.help": "Establece la velocidad de desplazamiento del mapa como muy lenta",
"vcmi.adventureOptions.mapScrollSpeed5.help": "Establece la velocidad de desplazamiento del mapa como muy rápida", "vcmi.adventureOptions.mapScrollSpeed5.help": "Establece la velocidad de desplazamiento del mapa como muy rápida",
"vcmi.adventureOptions.mapScrollSpeed6.help": "Establece la velocidad de desplazamiento del mapa como instantánea.", "vcmi.adventureOptions.mapScrollSpeed6.help": "Establece la velocidad de desplazamiento del mapa como instantánea.",
"vcmi.adventureOptions.hideBackground.hover" : "Ocultar fondo",
"vcmi.adventureOptions.hideBackground.help" : "{Ocultar fondo}\n\nOculta el mapa de aventuras en el fondo y muestra una textura en su lugar..",
"vcmi.battleOptions.queueSizeLabel.hover": "Mostrar orden de turno de criaturas", "vcmi.battleOptions.queueSizeLabel.hover": "Mostrar orden de turno de criaturas",
"vcmi.battleOptions.queueSizeNoneButton.hover": "APAGADO", "vcmi.battleOptions.queueSizeNoneButton.hover": "APAGADO",
@@ -168,6 +173,8 @@
"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostrar ventanas de estadísticas de héroes}\n\nAlternar permanentemente las ventanas de estadísticas de héroes que muestran estadísticas primarias y puntos de hechizo.", "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostrar ventanas de estadísticas de héroes}\n\nAlternar permanentemente las ventanas de estadísticas de héroes que muestran estadísticas primarias y puntos de hechizo.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\nPermitir acciones durante la música de introducción que se reproduce al comienzo de cada batalla.", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\nPermitir acciones durante la música de introducción que se reproduce al comienzo de cada batalla.",
"vcmi.battleOptions.endWithAutocombat.hover": "Finaliza la batalla",
"vcmi.battleOptions.endWithAutocombat.help": "{Finaliza la batalla}\n\nAutomatiza la batalla y la finaliza al instante",
"vcmi.adventureMap.revisitObject.hover" : "Revisitar objeto", "vcmi.adventureMap.revisitObject.hover" : "Revisitar objeto",
"vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.", "vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.",
@@ -183,6 +190,11 @@
"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño", "vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán", "vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá", "vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá",
"vcmi.battleWindow.killed" : "Eliminados",
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s han sido eliminados por disparos certeros",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s ha sido eliminado por un disparo certero",
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s han sido eliminados por disparos certeros",
"vcmi.battleWindow.endWithAutocombat" : "¿Quieres finalizar la batalla con combate automatizado?",
"vcmi.battleResultsWindow.applyResultsLabel" : "Aplicar resultado de la batalla", "vcmi.battleResultsWindow.applyResultsLabel" : "Aplicar resultado de la batalla",
@@ -225,6 +237,8 @@
"vcmi.heroWindow.openBackpack.hover" : "Abrir ventana de mochila de artefactos", "vcmi.heroWindow.openBackpack.hover" : "Abrir ventana de mochila de artefactos",
"vcmi.heroWindow.openBackpack.help" : "Abre la ventana que facilita la gestión de la mochila de artefactos.", "vcmi.heroWindow.openBackpack.help" : "Abre la ventana que facilita la gestión de la mochila de artefactos.",
"vcmi.tavernWindow.inviteHero" : "Invitar heroe",
"vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?", "vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?",
"vcmi.creatureWindow.showBonuses.hover" : "Cambiar a vista de bonificaciones", "vcmi.creatureWindow.showBonuses.hover" : "Cambiar a vista de bonificaciones",
@@ -321,6 +335,7 @@
"vcmi.map.victoryCondition.collectArtifacts.message" : "Adquirir tres artefactos", "vcmi.map.victoryCondition.collectArtifacts.message" : "Adquirir tres artefactos",
"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "¡Felicidades! Todos tus enemigos han sido derrotados y tienes la Alianza Angelical. ¡La victoria es tuya!", "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "¡Felicidades! Todos tus enemigos han sido derrotados y tienes la Alianza Angelical. ¡La victoria es tuya!",
"vcmi.map.victoryCondition.angelicAlliance.message" : "Derrota a todos los enemigos y crea la Alianza Angelical", "vcmi.map.victoryCondition.angelicAlliance.message" : "Derrota a todos los enemigos y crea la Alianza Angelical",
"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Por desgracia, has perdido parte de la Alianza Angélica. Todo se ha perdido.",
// few strings from WoG used by vcmi // few strings from WoG used by vcmi
"vcmi.stackExperience.description" : "» D e t a l l e s d e E x p e r i e n c i a d e l G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i", "vcmi.stackExperience.description" : "» D e t a l l e s d e E x p e r i e n c i a d e l G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i",
@@ -374,6 +389,8 @@
"core.bonus.ENCHANTER.description": "Puede lanzar ${subtype.spell} masivo cada turno", "core.bonus.ENCHANTER.description": "Puede lanzar ${subtype.spell} masivo cada turno",
"core.bonus.ENCHANTED.name": "Encantado", "core.bonus.ENCHANTED.name": "Encantado",
"core.bonus.ENCHANTED.description": "Afectado por el hechizo permanente ${subtype.spell}", "core.bonus.ENCHANTED.description": "Afectado por el hechizo permanente ${subtype.spell}",
"core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorar ataque (${val}%)",
"core.bonus.ENEMY_ATTACK_REDUCTION.description": "Al ser atacado, ${val}% del daño del atacante es ignorado",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorar Defensa (${val}%)", "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorar Defensa (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignora una parte de la defensa al atacar", "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignora una parte de la defensa al atacar",
"core.bonus.FIRE_IMMUNITY.name": "Inmunidad al Fuego", "core.bonus.FIRE_IMMUNITY.name": "Inmunidad al Fuego",
@@ -386,6 +403,8 @@
"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo", "core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
"core.bonus.FEARLESS.name": "Inmune al miedo", "core.bonus.FEARLESS.name": "Inmune al miedo",
"core.bonus.FEARLESS.description": "Inmune a la habilidad de miedo", "core.bonus.FEARLESS.description": "Inmune a la habilidad de miedo",
"core.bonus.FEROCITY.name": "Ferocidad",
"core.bonus.FEROCITY.description": "Ataca ${val} veces adicionales en caso de eliminar a alguien",
"core.bonus.FLYING.name": "Volar", "core.bonus.FLYING.name": "Volar",
"core.bonus.FLYING.description": "Puede volar (ignora obstáculos)", "core.bonus.FLYING.description": "Puede volar (ignora obstáculos)",
"core.bonus.FREE_SHOOTING.name": "Disparo cercano", "core.bonus.FREE_SHOOTING.name": "Disparo cercano",
@@ -416,8 +435,8 @@
"core.bonus.MANA_DRAIN.description": "Drena ${val} de maná cada turno", "core.bonus.MANA_DRAIN.description": "Drena ${val} de maná cada turno",
"core.bonus.MAGIC_MIRROR.name": "Espejo mágico (${val}%)", "core.bonus.MAGIC_MIRROR.name": "Espejo mágico (${val}%)",
"core.bonus.MAGIC_MIRROR.description": "Tiene una probabilidad del ${val}% de redirigir un hechizo ofensivo al enemigo", "core.bonus.MAGIC_MIRROR.description": "Tiene una probabilidad del ${val}% de redirigir un hechizo ofensivo al enemigo",
"core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${MR}%)", "core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${MR}% de resistir el hechizo del enemigo", "core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${val}% de resistir el hechizo del enemigo",
"core.bonus.MIND_IMMUNITY.name": "Inmunidad a hechizos mentales", "core.bonus.MIND_IMMUNITY.name": "Inmunidad a hechizos mentales",
"core.bonus.MIND_IMMUNITY.description": "Inmune a hechizos de tipo mental", "core.bonus.MIND_IMMUNITY.description": "Inmune a hechizos de tipo mental",
"core.bonus.NO_DISTANCE_PENALTY.name": "Sin penalización por distancia", "core.bonus.NO_DISTANCE_PENALTY.name": "Sin penalización por distancia",
@@ -440,10 +459,12 @@
"core.bonus.REBIRTH.description": "El ${val}% del grupo resucitará después de la muerte", "core.bonus.REBIRTH.description": "El ${val}% del grupo resucitará después de la muerte",
"core.bonus.RETURN_AFTER_STRIKE.name": "Atacar y volver", "core.bonus.RETURN_AFTER_STRIKE.name": "Atacar y volver",
"core.bonus.RETURN_AFTER_STRIKE.description": "Regresa después de un ataque cuerpo a cuerpo", "core.bonus.RETURN_AFTER_STRIKE.description": "Regresa después de un ataque cuerpo a cuerpo",
"core.bonus.REVENGE.name": "Venganza",
"core.bonus.REVENGE.description": "Inflige daño adicional según la salud perdida del atacante en la batalla.",
"core.bonus.SHOOTER.name": "A distancia", "core.bonus.SHOOTER.name": "A distancia",
"core.bonus.SHOOTER.description": "La criatura puede disparar", "core.bonus.SHOOTER.description": "La criatura puede disparar",
"core.bonus.SHOOTS_ALL_ADJACENT.name": "Dispara en todas direcciones", "core.bonus.SHOOTS_ALL_ADJACENT.name": "Dispara en todas direcciones",
"core.bonus.SHOOTS_ALL_ADJACENT.description": "Los ataques a distancia de esta criatura impactan a todos los objetivos en un área pequeña", "core.bonus.SHOOTS_ALL_ADJACENT.description": "Los ataques a distancia de esta criatura impactan a todos los objetivos en un área reducida",
"core.bonus.SOUL_STEAL.name": "Roba almas", "core.bonus.SOUL_STEAL.name": "Roba almas",
"core.bonus.SOUL_STEAL.description": "Gana ${val} nuevas criaturas por cada enemigo eliminado", "core.bonus.SOUL_STEAL.description": "Gana ${val} nuevas criaturas por cada enemigo eliminado",
"core.bonus.SPELLCASTER.name": "Lanzador de hechizos", "core.bonus.SPELLCASTER.name": "Lanzador de hechizos",

View File

@@ -138,16 +138,7 @@ int main(int argc, char * argv[])
("nointro,i", "skips intro movies") ("nointro,i", "skips intro movies")
("donotstartserver,d","do not attempt to start server and just connect to it instead server") ("donotstartserver,d","do not attempt to start server and just connect to it instead server")
("serverport", po::value<si64>(), "override port specified in config file") ("serverport", po::value<si64>(), "override port specified in config file")
("savefrequency", po::value<si64>(), "limit auto save creation to each N days") ("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
("lobby", "parameters address, port, uuid to connect ro remote lobby session")
("lobby-address", po::value<std::string>(), "address to remote lobby")
("lobby-port", po::value<ui16>(), "port to remote lobby")
("lobby-host", "if this client hosts session")
("lobby-uuid", po::value<std::string>(), "uuid to the server")
("lobby-connections", po::value<ui16>(), "connections of server")
("lobby-username", po::value<std::string>(), "player name")
("lobby-gamemode", po::value<ui16>(), "use 0 for new game and 1 for load game")
("uuid", po::value<std::string>(), "uuid for the client");
if(argc > 1) if(argc > 1)
{ {
@@ -371,46 +362,6 @@ int main(int argc, char * argv[])
} }
std::vector<std::string> names; std::vector<std::string> names;
session["lobby"].Bool() = false;
if(vm.count("lobby"))
{
session["lobby"].Bool() = true;
session["host"].Bool() = false;
session["address"].String() = vm["lobby-address"].as<std::string>();
if(vm.count("lobby-username"))
session["username"].String() = vm["lobby-username"].as<std::string>();
else
session["username"].String() = settings["launcher"]["lobbyUsername"].String();
if(vm.count("lobby-gamemode"))
session["gamemode"].Integer() = vm["lobby-gamemode"].as<ui16>();
else
session["gamemode"].Integer() = 0;
CSH->uuid = vm["uuid"].as<std::string>();
session["port"].Integer() = vm["lobby-port"].as<ui16>();
logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid);
if(vm.count("lobby-host"))
{
session["host"].Bool() = true;
session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as<ui16>());
session["hostUuid"].String() = vm["lobby-uuid"].as<std::string>();
logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String());
}
//we should not reconnect to previous game in online mode
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = false;
//start lobby immediately
names.push_back(session["username"].String());
ESelectionScreen sscreen = session["gamemode"].Integer() == 0 ? ESelectionScreen::newGame : ESelectionScreen::loadGame;
CMM->openLobby(sscreen, session["host"].Bool(), &names, ELoadMode::MULTI);
}
// Restore remote session - start game immediately
if(settings["server"]["reconnect"].Bool())
{
CSH->restoreLastSession();
}
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
{ {
@@ -451,7 +402,6 @@ static void mainLoop()
while(1) //main SDL events loop while(1) //main SDL events loop
{ {
GH.input().fetchEvents(); GH.input().fetchEvents();
CSH->applyPacksOnLobbyScreen();
GH.renderFrame(); GH.renderFrame();
} }
} }

View File

@@ -95,6 +95,12 @@ set(client_SRCS
renderSDL/ScreenHandler.cpp renderSDL/ScreenHandler.cpp
renderSDL/SDL_Extensions.cpp renderSDL/SDL_Extensions.cpp
globalLobby/GlobalLobbyClient.cpp
globalLobby/GlobalLobbyLoginWindow.cpp
globalLobby/GlobalLobbyServerSetup.cpp
globalLobby/GlobalLobbyWidget.cpp
globalLobby/GlobalLobbyWindow.cpp
widgets/Buttons.cpp widgets/Buttons.cpp
widgets/CArtifactHolder.cpp widgets/CArtifactHolder.cpp
widgets/CComponent.cpp widgets/CComponent.cpp
@@ -270,6 +276,13 @@ set(client_HEADERS
renderSDL/SDL_Extensions.h renderSDL/SDL_Extensions.h
renderSDL/SDL_PixelAccess.h renderSDL/SDL_PixelAccess.h
globalLobby/GlobalLobbyClient.h
globalLobby/GlobalLobbyDefines.h
globalLobby/GlobalLobbyLoginWindow.h
globalLobby/GlobalLobbyServerSetup.h
globalLobby/GlobalLobbyWidget.h
globalLobby/GlobalLobbyWindow.h
widgets/Buttons.h widgets/Buttons.h
widgets/CArtifactHolder.h widgets/CArtifactHolder.h
widgets/CComponent.h widgets/CComponent.h

View File

@@ -1870,14 +1870,9 @@ void CPlayerInterface::proposeLoadingGame()
CGI->generaltexth->allTexts[68], CGI->generaltexth->allTexts[68],
[]() []()
{ {
GH.dispatchMainThread( CSH->endGameplay();
[]() GH.defActionsDef = 63;
{ CMM->menu->switchToTab("load");
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("load");
}
);
}, },
nullptr nullptr
); );

View File

@@ -16,6 +16,7 @@
#include "gui/CGuiHandler.h" #include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h" #include "gui/WindowHandler.h"
#include "globalLobby/GlobalLobbyClient.h"
#include "lobby/CSelectionBase.h" #include "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h" #include "lobby/CLobbyScreen.h"
#include "windows/InfoWindows.h" #include "windows/InfoWindows.h"
@@ -46,16 +47,15 @@
#include "../lib/mapObjects/MiscObjects.h" #include "../lib/mapObjects/MiscObjects.h"
#include "../lib/modding/ModIncompatibility.h" #include "../lib/modding/ModIncompatibility.h"
#include "../lib/rmg/CMapGenOptions.h" #include "../lib/rmg/CMapGenOptions.h"
#include "../lib/serializer/Connection.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h" #include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/CMemorySerializer.h" #include "../lib/serializer/CMemorySerializer.h"
#include "../lib/UnlockGuard.h" #include "../lib/UnlockGuard.h"
#include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp> #include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp> #include <boost/uuid/uuid_generators.hpp>
#include <boost/asio.hpp>
#include "../lib/serializer/Cast.h" #include "../lib/serializer/Cast.h"
#include "LobbyClientNetPackVisitors.h" #include "LobbyClientNetPackVisitors.h"
@@ -67,8 +67,6 @@
template<typename T> class CApplyOnLobby; template<typename T> class CApplyOnLobby;
const std::string CServerHandler::localhostAddress{"127.0.0.1"};
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) #if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
extern std::atomic_bool androidTestServerReadyFlag; extern std::atomic_bool androidTestServerReadyFlag;
#endif #endif
@@ -76,8 +74,8 @@ extern std::atomic_bool androidTestServerReadyFlag;
class CBaseForLobbyApply class CBaseForLobbyApply
{ {
public: public:
virtual bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const = 0; virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0;
virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const = 0; virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0;
virtual ~CBaseForLobbyApply(){}; virtual ~CBaseForLobbyApply(){};
template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr) template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
{ {
@@ -88,124 +86,137 @@ public:
template<typename T> class CApplyOnLobby : public CBaseForLobbyApply template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
{ {
public: public:
bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); auto & ref = static_cast<T&>(pack);
T * ptr = static_cast<T *>(pack);
ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); ApplyOnLobbyHandlerNetPackVisitor visitor(*handler);
logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ptr).name()); logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name());
ptr->visit(visitor); ref.visit(visitor);
return visitor.getResult(); return visitor.getResult();
} }
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
{ {
T * ptr = static_cast<T *>(pack); auto & ref = static_cast<T &>(pack);
ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby); ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby);
logNetwork->trace("\tApply on lobby from queue: %s", typeid(ptr).name()); logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name());
ptr->visit(visitor); ref.visit(visitor);
} }
}; };
template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
{ {
public: public:
bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
{ {
logGlobal->error("Cannot apply plain CPack!"); logGlobal->error("Cannot apply plain CPack!");
assert(0); assert(0);
return false; return false;
} }
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
{ {
logGlobal->error("Cannot apply plain CPack!"); logGlobal->error("Cannot apply plain CPack!");
assert(0); assert(0);
} }
}; };
static const std::string NAME_AFFIX = "client"; CServerHandler::~CServerHandler()
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name {
networkHandler->stop();
try
{
threadNetwork.join();
}
catch (const std::runtime_error & e)
{
logGlobal->error("Failed to shut down network thread! Reason: %s", e.what());
assert(0);
}
}
CServerHandler::CServerHandler() CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false) : networkHandler(INetworkHandler::createHandler())
, lobbyClient(std::make_unique<GlobalLobbyClient>())
, applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
, threadNetwork(&CServerHandler::threadRunNetwork, this)
, state(EClientState::NONE)
, serverPort(0)
, campaignStateToSend(nullptr)
, screenType(ESelectionScreen::unknown)
, serverMode(EServerMode::NONE)
, loadMode(ELoadMode::NONE)
, client(nullptr)
{ {
uuid = boost::uuids::to_string(boost::uuids::random_generator()()); uuid = boost::uuids::to_string(boost::uuids::random_generator()());
//read from file to restore last session
if(!settings["server"]["uuid"].isNull() && !settings["server"]["uuid"].String().empty())
uuid = settings["server"]["uuid"].String();
applier = std::make_shared<CApplier<CBaseForLobbyApply>>();
registerTypesLobbyPacks(*applier); registerTypesLobbyPacks(*applier);
} }
CServerHandler::~CServerHandler() = default; void CServerHandler::threadRunNetwork()
{
logGlobal->info("Starting network thread");
setThreadName("runNetwork");
networkHandler->run();
logGlobal->info("Ending network thread");
}
void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names) void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector<std::string> & names)
{ {
hostClientId = -1; hostClientId = -1;
state = EClientState::NONE; setState(EClientState::NONE);
serverMode = newServerMode;
mapToStart = nullptr; mapToStart = nullptr;
th = std::make_unique<CStopWatch>(); th = std::make_unique<CStopWatch>();
packsForLobbyScreen.clear(); logicConnection.reset();
c.reset();
si = std::make_shared<StartInfo>(); si = std::make_shared<StartInfo>();
playerNames.clear(); playerNames.clear();
si->difficulty = 1; si->difficulty = 1;
si->mode = mode; si->mode = mode;
screenType = screen;
myNames.clear(); myNames.clear();
if(names && !names->empty()) //if have custom set of player names - use it if(!names.empty()) //if have custom set of player names - use it
myNames = *names; myNames = names;
else else
myNames.push_back(settings["general"]["playerName"].String()); myNames.push_back(settings["general"]["playerName"].String());
} }
void CServerHandler::startLocalServerAndConnect() GlobalLobbyClient & CServerHandler::getGlobalLobby()
{ {
if(threadRunLocalServer) return *lobbyClient;
threadRunLocalServer->join(); }
INetworkHandler & CServerHandler::getNetworkHandler()
{
return *networkHandler;
}
void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
{
if(threadRunLocalServer.joinable())
threadRunLocalServer.join();
th->update(); th->update();
auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess");
try
{
CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid);
logNetwork->error("Port is busy, check if another instance of vcmiserver is working");
CInfoWindow::showInfoDialog(errorMsg, {});
return;
}
catch(std::runtime_error & error)
{
//no connection means that port is not busy and we can start local server
}
#if defined(SINGLE_PROCESS_APP) #if defined(SINGLE_PROCESS_APP)
boost::condition_variable cond; boost::condition_variable cond;
std::vector<std::string> args{"--uuid=" + uuid, "--port=" + std::to_string(getHostPort())}; std::vector<std::string> args{"--port=" + std::to_string(getLocalPort())};
if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool()) if(connectToLobby)
{ args.push_back("--lobby");
args.push_back("--lobby=" + settings["session"]["address"].String());
args.push_back("--connections=" + settings["session"]["hostConnections"].String()); threadRunLocalServer = boost::thread([&cond, args] {
args.push_back("--lobby-port=" + std::to_string(settings["session"]["port"].Integer()));
args.push_back("--lobby-uuid=" + settings["session"]["hostUuid"].String());
}
threadRunLocalServer = std::make_shared<boost::thread>([&cond, args, this] {
setThreadName("CVCMIServer"); setThreadName("CVCMIServer");
CVCMIServer::create(&cond, args); CVCMIServer::create(&cond, args);
onServerFinished();
}); });
threadRunLocalServer->detach();
#elif defined(VCMI_ANDROID) #elif defined(VCMI_ANDROID)
{ {
CAndroidVMHelper envHelper; CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true); envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);
} }
#else #else
threadRunLocalServer = std::make_shared<boost::thread>(&CServerHandler::threadRunServer, this); //runs server executable; threadRunLocalServer = boost::thread(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable;
#endif #endif
logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff()); logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff());
@@ -236,7 +247,7 @@ void CServerHandler::startLocalServerAndConnect()
while(!androidTestServerReadyFlag.load()) while(!androidTestServerReadyFlag.load())
{ {
logNetwork->info("still waiting..."); logNetwork->info("still waiting...");
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
} }
logNetwork->info("waiting for server finished..."); logNetwork->info("waiting for server finished...");
androidTestServerReadyFlag = false; androidTestServerReadyFlag = false;
@@ -245,151 +256,165 @@ void CServerHandler::startLocalServerAndConnect()
th->update(); //put breakpoint here to attach to server before it does something stupid th->update(); //put breakpoint here to attach to server before it does something stupid
justConnectToServer(localhostAddress, 0); connectToServer(getLocalHostname(), getLocalPort());
logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff());
} }
void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port) void CServerHandler::connectToServer(const std::string & addr, const ui16 port)
{ {
state = EClientState::CONNECTING; logNetwork->info("Establishing connection to %s:%d...", addr, port);
while(!c && state != EClientState::CONNECTION_CANCELLED) setState(EClientState::CONNECTING);
{ serverHostname = addr;
try serverPort = port;
{
logNetwork->info("Establishing connection...");
c = std::make_shared<CConnection>(
addr.size() ? addr : getHostAddress(),
port ? port : getHostPort(),
NAME, uuid);
nextClient = std::make_unique<CClient>(); if (!isServerLocal())
c->iser.cb = nextClient.get(); {
} Settings remoteAddress = settings.write["server"]["remoteHostname"];
catch(std::runtime_error & error) remoteAddress->String() = addr;
{
logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", error.what()); Settings remotePort = settings.write["server"]["remotePort"];
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); remotePort->Integer() = port;
}
} }
if(state == EClientState::CONNECTION_CANCELLED) networkHandler->connectToRemote(*this, addr, port);
}
void CServerHandler::onConnectionFailed(const std::string & errorMessage)
{
assert(getState() == EClientState::CONNECTING);
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if (isServerLocal())
{
// retry - local server might be still starting up
logNetwork->debug("\nCannot establish connection. %s. Retrying...", errorMessage);
networkHandler->createTimer(*this, std::chrono::milliseconds(100));
}
else
{
// remote server refused connection - show error message
setState(EClientState::NONE);
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
}
}
void CServerHandler::onTimer()
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if(getState() == EClientState::CONNECTION_CANCELLED)
{ {
logNetwork->info("Connection aborted by player!"); logNetwork->info("Connection aborted by player!");
return; return;
} }
c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this); assert(isServerLocal());
networkHandler->connectToRemote(*this, getLocalHostname(), getLocalPort());
if(!addr.empty() && addr != getHostAddress())
{
Settings serverAddress = settings.write["server"]["server"];
serverAddress->String() = addr;
}
if(port && port != getHostPort())
{
Settings serverPort = settings.write["server"]["port"];
serverPort->Integer() = port;
}
} }
void CServerHandler::applyPacksOnLobbyScreen() void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection)
{ {
if(!c || !c->handler) assert(getState() == EClientState::CONNECTING);
return;
boost::unique_lock<boost::recursive_mutex> lock(*mx); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
while(!packsForLobbyScreen.empty())
networkConnection = netConnection;
logNetwork->info("Connection established");
if (serverMode == EServerMode::LOBBY_GUEST)
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); // say hello to lobby to switch connection to proxy mode
CPackForLobby * pack = packsForLobbyScreen.front(); getGlobalLobby().sendProxyConnectionLogin(netConnection);
packsForLobbyScreen.pop_front();
CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
GH.windows().totalRedraw();
delete pack;
} }
logicConnection = std::make_shared<CConnection>(netConnection);
logicConnection->uuid = uuid;
logicConnection->enterLobbyConnectionMode();
sendClientConnecting();
} }
void CServerHandler::stopServerConnection() void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
{ {
if(c->handler) const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier
{ apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
while(!c->handler->timed_join(boost::chrono::milliseconds(50))) GH.windows().totalRedraw();
applyPacksOnLobbyScreen();
c->handler->join();
}
} }
std::set<PlayerColor> CServerHandler::getHumanColors() std::set<PlayerColor> CServerHandler::getHumanColors()
{ {
return clientHumanColors(c->connectionID); return clientHumanColors(logicConnection->connectionID);
} }
PlayerColor CServerHandler::myFirstColor() const PlayerColor CServerHandler::myFirstColor() const
{ {
return clientFirstColor(c->connectionID); return clientFirstColor(logicConnection->connectionID);
} }
bool CServerHandler::isMyColor(PlayerColor color) const bool CServerHandler::isMyColor(PlayerColor color) const
{ {
return isClientColor(c->connectionID, color); return isClientColor(logicConnection->connectionID, color);
} }
ui8 CServerHandler::myFirstId() const ui8 CServerHandler::myFirstId() const
{ {
return clientFirstId(c->connectionID); return clientFirstId(logicConnection->connectionID);
}
EClientState CServerHandler::getState() const
{
return state;
}
void CServerHandler::setState(EClientState newState)
{
state = newState;
} }
bool CServerHandler::isServerLocal() const bool CServerHandler::isServerLocal() const
{ {
if(threadRunLocalServer) return threadRunLocalServer.joinable();
return true;
return false;
} }
bool CServerHandler::isHost() const bool CServerHandler::isHost() const
{ {
return c && hostClientId == c->connectionID; return logicConnection && hostClientId == logicConnection->connectionID;
} }
bool CServerHandler::isGuest() const bool CServerHandler::isGuest() const
{ {
return !c || hostClientId != c->connectionID; return !logicConnection || hostClientId != logicConnection->connectionID;
} }
ui16 CServerHandler::getDefaultPort() const std::string & CServerHandler::getLocalHostname() const
{ {
return static_cast<ui16>(settings["server"]["port"].Integer()); return settings["server"]["localHostname"].String();
} }
std::string CServerHandler::getDefaultPortStr() ui16 CServerHandler::getLocalPort() const
{ {
return std::to_string(getDefaultPort()); return settings["server"]["localPort"].Integer();
} }
std::string CServerHandler::getHostAddress() const const std::string & CServerHandler::getRemoteHostname() const
{ {
if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool()) return settings["server"]["remoteHostname"].String();
return settings["server"]["server"].String();
if(settings["session"]["host"].Bool())
return localhostAddress;
return settings["session"]["address"].String();
} }
ui16 CServerHandler::getHostPort() const ui16 CServerHandler::getRemotePort() const
{ {
if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool()) return settings["server"]["remotePort"].Integer();
return getDefaultPort(); }
if(settings["session"]["host"].Bool()) const std::string & CServerHandler::getCurrentHostname() const
return getDefaultPort(); {
return serverHostname;
}
return settings["session"]["port"].Integer(); ui16 CServerHandler::getCurrentPort() const
{
return serverPort;
} }
void CServerHandler::sendClientConnecting() const void CServerHandler::sendClientConnecting() const
@@ -404,13 +429,16 @@ void CServerHandler::sendClientConnecting() const
void CServerHandler::sendClientDisconnecting() void CServerHandler::sendClientDisconnecting()
{ {
// FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server // FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server
if(state == EClientState::DISCONNECTING) if(getState() == EClientState::DISCONNECTING)
{
assert(0);
return; return;
}
state = EClientState::DISCONNECTING; setState(EClientState::DISCONNECTING);
mapToStart = nullptr; mapToStart = nullptr;
LobbyClientDisconnected lcd; LobbyClientDisconnected lcd;
lcd.clientId = c->connectionID; lcd.clientId = logicConnection->connectionID;
logNetwork->info("Connection has been requested to be closed."); logNetwork->info("Connection has been requested to be closed.");
if(isServerLocal()) if(isServerLocal())
{ {
@@ -422,18 +450,14 @@ void CServerHandler::sendClientDisconnecting()
logNetwork->info("Sent leaving signal to the server"); logNetwork->info("Sent leaving signal to the server");
} }
sendLobbyPack(lcd); sendLobbyPack(lcd);
networkConnection->close();
{ networkConnection.reset();
// Network thread might be applying network pack at this moment logicConnection.reset();
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
c->close();
c.reset();
}
} }
void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign) void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
{ {
state = EClientState::LOBBY_CAMPAIGN; setState(EClientState::LOBBY_CAMPAIGN);
LobbySetCampaign lsc; LobbySetCampaign lsc;
lsc.ourCampaign = newCampaign; lsc.ourCampaign = newCampaign;
sendLobbyPack(lsc); sendLobbyPack(lsc);
@@ -441,7 +465,7 @@ void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign
void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
{ {
if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
return; return;
LobbySetCampaignMap lscm; LobbySetCampaignMap lscm;
@@ -451,7 +475,7 @@ void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
void CServerHandler::setCampaignBonus(int bonusId) const void CServerHandler::setCampaignBonus(int bonusId) const
{ {
if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
return; return;
LobbySetCampaignBonus lscb; LobbySetCampaignBonus lscb;
@@ -575,9 +599,7 @@ void CServerHandler::sendRestartGame() const
{ {
GH.windows().createAndPushWindow<CLoadingScreen>(); GH.windows().createAndPushWindow<CLoadingScreen>();
LobbyEndGame endGame; LobbyRestartGame endGame;
endGame.closeConnection = false;
endGame.restart = true;
sendLobbyPack(endGame); sendLobbyPack(endGame);
} }
@@ -621,17 +643,18 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
GH.windows().createAndPushWindow<CLoadingScreen>(); GH.windows().createAndPushWindow<CLoadingScreen>();
LobbyPrepareStartGame lpsg;
sendLobbyPack(lpsg);
LobbyStartGame lsg; LobbyStartGame lsg;
if(client) if(client)
{ {
lsg.initializedStartInfo = std::make_shared<StartInfo>(* const_cast<StartInfo *>(client->getStartInfo(true))); lsg.initializedStartInfo = std::make_shared<StartInfo>(* const_cast<StartInfo *>(client->getStartInfo(true)));
lsg.initializedStartInfo->mode = StartInfo::NEW_GAME; lsg.initializedStartInfo->mode = EStartMode::NEW_GAME;
lsg.initializedStartInfo->seedToBeUsed = lsg.initializedStartInfo->seedPostInit = 0; lsg.initializedStartInfo->seedToBeUsed = lsg.initializedStartInfo->seedPostInit = 0;
* si = * lsg.initializedStartInfo; * si = * lsg.initializedStartInfo;
} }
sendLobbyPack(lsg); sendLobbyPack(lsg);
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
} }
void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to) void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to)
@@ -644,83 +667,54 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
if(CMM) if(CMM)
CMM->disable(); CMM->disable();
std::swap(client, nextClient);
highScoreCalc = nullptr; highScoreCalc = nullptr;
switch(si->mode) switch(si->mode)
{ {
case StartInfo::NEW_GAME: case EStartMode::NEW_GAME:
client->newGame(gameState); client->newGame(gameState);
break; break;
case StartInfo::CAMPAIGN: case EStartMode::CAMPAIGN:
client->newGame(gameState); client->newGame(gameState);
break; break;
case StartInfo::LOAD_GAME: case EStartMode::LOAD_GAME:
client->loadGame(gameState); client->loadGame(gameState);
break; break;
default: default:
throw std::runtime_error("Invalid mode"); throw std::runtime_error("Invalid mode");
} }
// After everything initialized we can accept CPackToClient netpacks // After everything initialized we can accept CPackToClient netpacks
c->enterGameplayConnectionMode(client->gameState()); logicConnection->enterGameplayConnectionMode(client->gameState());
state = EClientState::GAMEPLAY; setState(EClientState::GAMEPLAY);
//store settings to continue game
if(!isServerLocal() && isGuest())
{
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = true;
Settings saveUuid = settings.write["server"]["uuid"];
saveUuid->String() = uuid;
Settings saveNames = settings.write["server"]["names"];
saveNames->Vector().clear();
for(auto & name : myNames)
{
JsonNode jsonName;
jsonName.String() = name;
saveNames->Vector().push_back(jsonName);
}
}
} }
void CServerHandler::endGameplay(bool closeConnection, bool restart) void CServerHandler::endGameplay()
{ {
if(closeConnection) // Game is ending
{ // Tell the network thread to reach a stable state
// Game is ending CSH->sendClientDisconnecting();
// Tell the network thread to reach a stable state logNetwork->info("Closed connection.");
CSH->sendClientDisconnecting();
logNetwork->info("Closed connection.");
}
client->endGame(); client->endGame();
client.reset(); client.reset();
if(!restart) if(CMM)
{ {
if(CMM) GH.curInt = CMM.get();
{ CMM->enable();
GH.curInt = CMM.get();
CMM->enable();
}
else
{
GH.curInt = CMainMenu::create().get();
}
} }
else
if(c)
{ {
nextClient = std::make_unique<CClient>(); GH.curInt = CMainMenu::create().get();
c->iser.cb = nextClient.get();
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
} }
}
//reset settings void CServerHandler::restartGameplay()
Settings saveSession = settings.write["server"]["reconnect"]; {
saveSession->Bool() = false; client->endGame();
client.reset();
logicConnection->enterLobbyConnectionMode();
} }
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs) void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs)
@@ -741,7 +735,6 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
GH.dispatchMainThread([ourCampaign, this]() GH.dispatchMainThread([ourCampaign, this]()
{ {
CSH->campaignServerRestartLock.set(true);
CSH->endGameplay(); CSH->endGameplay();
auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog; auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
@@ -764,13 +757,14 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *highScoreCalc); GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *highScoreCalc);
} }
}; };
threadRunLocalServer.join();
if(epilogue.hasPrologEpilog) if(epilogue.hasPrologEpilog)
{ {
GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher); GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
} }
else else
{ {
CSH->campaignServerRestartLock.waitUntil(false);
finisher(); finisher();
} }
}); });
@@ -796,15 +790,15 @@ int CServerHandler::howManyPlayerInterfaces()
return playerInts; return playerInts;
} }
ui8 CServerHandler::getLoadMode() ELoadMode CServerHandler::getLoadMode()
{ {
if(loadMode != ELoadMode::TUTORIAL && state == EClientState::GAMEPLAY) if(loadMode != ELoadMode::TUTORIAL && getState() == EClientState::GAMEPLAY)
{ {
if(si->campState) if(si->campState)
return ELoadMode::CAMPAIGN; return ELoadMode::CAMPAIGN;
for(auto pn : playerNames) for(auto pn : playerNames)
{ {
if(pn.second.connection != c->connectionID) if(pn.second.connection != logicConnection->connectionID)
return ELoadMode::MULTI; return ELoadMode::MULTI;
} }
if(howManyPlayerInterfaces() > 1) //this condition will work for hotseat mode OR multiplayer with allowed more than 1 color per player to control if(howManyPlayerInterfaces() > 1) //this condition will work for hotseat mode OR multiplayer with allowed more than 1 color per player to control
@@ -815,48 +809,24 @@ ui8 CServerHandler::getLoadMode()
return loadMode; return loadMode;
} }
void CServerHandler::restoreLastSession()
{
auto loadSession = [this]()
{
uuid = settings["server"]["uuid"].String();
for(auto & name : settings["server"]["names"].Vector())
myNames.push_back(name.String());
resetStateForLobby(StartInfo::LOAD_GAME, &myNames);
screenType = ESelectionScreen::loadGame;
justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer());
};
auto cleanUpSession = []()
{
//reset settings
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = false;
};
CInfoWindow::showYesNoDialog(VLC->generaltexth->translate("vcmi.server.confirmReconnect"), {}, loadSession, cleanUpSession);
}
void CServerHandler::debugStartTest(std::string filename, bool save) void CServerHandler::debugStartTest(std::string filename, bool save)
{ {
logGlobal->info("Starting debug test with file: %s", filename); logGlobal->info("Starting debug test with file: %s", filename);
auto mapInfo = std::make_shared<CMapInfo>(); auto mapInfo = std::make_shared<CMapInfo>();
if(save) if(save)
{ {
resetStateForLobby(StartInfo::LOAD_GAME); resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOCAL, {});
mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME)); mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME));
screenType = ESelectionScreen::loadGame;
} }
else else
{ {
resetStateForLobby(StartInfo::NEW_GAME); resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOCAL, {});
mapInfo->mapInit(filename); mapInfo->mapInit(filename);
screenType = ESelectionScreen::newGame;
} }
if(settings["session"]["donotstartserver"].Bool()) if(settings["session"]["donotstartserver"].Bool())
justConnectToServer(localhostAddress, 3030); connectToServer(getLocalHostname(), getLocalPort());
else else
startLocalServerAndConnect(); startLocalServerAndConnect(false);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
@@ -899,91 +869,71 @@ public:
{ {
} }
virtual bool callTyped() override { return false; } bool callTyped() override { return false; }
virtual void visitForLobby(CPackForLobby & lobbyPack) override void visitForLobby(CPackForLobby & lobbyPack) override
{ {
handler.visitForLobby(lobbyPack); handler.visitForLobby(lobbyPack);
} }
virtual void visitForClient(CPackForClient & clientPack) override void visitForClient(CPackForClient & clientPack) override
{ {
handler.visitForClient(clientPack); handler.visitForClient(clientPack);
} }
}; };
void CServerHandler::threadHandleConnection() void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection> &, const std::vector<std::byte> & message)
{ {
setThreadName("handleConnection"); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
c->enterLobbyConnectionMode();
try if(getState() == EClientState::DISCONNECTING)
{ {
sendClientConnecting(); assert(0); //Should not be possible - socket must be closed at this point
while(c && c->connected) return;
{
while(state == EClientState::STARTING)
boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
CPack * pack = c->retrievePack();
if(state == EClientState::DISCONNECTING)
{
// FIXME: server shouldn't really send netpacks after it's tells client to disconnect
// Though currently they'll be delivered and might cause crash.
vstd::clear_pointer(pack);
}
else
{
ServerHandlerCPackVisitor visitor(*this);
pack->visit(visitor);
}
}
} }
//catch only asio exceptions
catch(const boost::system::system_error & e) CPack * pack = logicConnection->retrievePack(message);
ServerHandlerCPackVisitor visitor(*this);
pack->visit(visitor);
}
void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
{
if(getState() == EClientState::DISCONNECTING)
{ {
if(state == EClientState::DISCONNECTING) assert(networkConnection == nullptr);
{ // Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction
logNetwork->info("Successfully closed connection to server, ending listening thread!"); logNetwork->info("Successfully closed connection to server!");
} return;
else
{
if (e.code() == boost::asio::error::eof)
logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed");
else
logNetwork->error("Lost connection to server, ending listening thread! Reason: %s", e.what());
if(client)
{
state = EClientState::DISCONNECTING;
GH.dispatchMainThread([]()
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
});
}
else
{
auto lcd = new LobbyClientDisconnected();
lcd->clientId = c->connectionID;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(lcd);
}
}
} }
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
logNetwork->error("Lost connection to server! Connection has been closed");
if(client)
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
CSH->showServerError(CGI->generaltexth->translate("vcmi.server.errors.disconnected"));
}
else
{
LobbyClientDisconnected lcd;
lcd.clientId = logicConnection->connectionID;
applyPackOnLobbyScreen(lcd);
}
networkConnection.reset();
} }
void CServerHandler::visitForLobby(CPackForLobby & lobbyPack) void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
{ {
if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, &lobbyPack)) if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack))
{ {
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
{ applyPackOnLobbyScreen(lobbyPack);
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(&lobbyPack);
}
} }
} }
@@ -992,22 +942,16 @@ void CServerHandler::visitForClient(CPackForClient & clientPack)
client->handlePack(&clientPack); client->handlePack(&clientPack);
} }
void CServerHandler::threadRunServer() void CServerHandler::threadRunServer(bool connectToLobby)
{ {
#if !defined(VCMI_MOBILE) #if !defined(VCMI_MOBILE)
setThreadName("runServer"); setThreadName("runServer");
const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string(); const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string();
std::string comm = VCMIDirs::get().serverPath().string() std::string comm = VCMIDirs::get().serverPath().string()
+ " --port=" + std::to_string(getHostPort()) + " --port=" + std::to_string(getLocalPort())
+ " --run-by-client" + " --run-by-client";
+ " --uuid=" + uuid; if(connectToLobby)
if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool()) comm += " --lobby";
{
comm += " --lobby=" + settings["session"]["address"].String();
comm += " --connections=" + settings["session"]["hostConnections"].String();
comm += " --lobby-port=" + std::to_string(settings["session"]["port"].Integer());
comm += " --lobby-uuid=" + settings["session"]["hostUuid"].String();
}
comm += " > \"" + logName + '\"'; comm += " > \"" + logName + '\"';
logGlobal->info("Server command line: %s", comm); logGlobal->info("Server command line: %s", comm);
@@ -1035,22 +979,31 @@ void CServerHandler::threadRunServer()
} }
else else
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if (getState() == EClientState::CONNECTING)
{
showServerError(CGI->generaltexth->translate("vcmi.server.errors.existingProcess"));
setState(EClientState::CONNECTION_CANCELLED); // stop attempts to reconnect
}
logNetwork->error("Error: server failed to close correctly or crashed!"); logNetwork->error("Error: server failed to close correctly or crashed!");
logNetwork->error("Check %s for more info", logName); logNetwork->error("Check %s for more info", logName);
} }
onServerFinished();
#endif #endif
} }
void CServerHandler::onServerFinished()
{
threadRunLocalServer.reset();
if (CSH)
CSH->campaignServerRestartLock.setn(false);
}
void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
{ {
if(state != EClientState::STARTING) if(getState() != EClientState::STARTING)
c->sendPack(&pack); logicConnection->sendPack(&pack);
}
bool CServerHandler::inLobbyRoom() const
{
return CSH->serverMode == EServerMode::LOBBY_HOST || CSH->serverMode == EServerMode::LOBBY_GUEST;
}
bool CServerHandler::inGame() const
{
return logicConnection != nullptr;
} }

View File

@@ -11,6 +11,7 @@
#include "../lib/CStopWatch.h" #include "../lib/CStopWatch.h"
#include "../lib/network/NetworkInterface.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
#include "../lib/CondSh.h" #include "../lib/CondSh.h"
@@ -34,10 +35,14 @@ VCMI_LIB_NAMESPACE_END
class CClient; class CClient;
class CBaseForLobbyApply; class CBaseForLobbyApply;
class GlobalLobbyClient;
class HighScoreCalculation; class HighScoreCalculation;
class HighScoreParameter; class HighScoreParameter;
enum class ESelectionScreen : ui8;
enum class ELoadMode : ui8;
// TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet // TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet
enum class EClientState : ui8 enum class EClientState : ui8
{ {
@@ -49,7 +54,14 @@ enum class EClientState : ui8
STARTING, // Gameplay interfaces being created, we pause netpacks retrieving STARTING, // Gameplay interfaces being created, we pause netpacks retrieving
GAMEPLAY, // In-game, used by some UI GAMEPLAY, // In-game, used by some UI
DISCONNECTING, // We disconnecting, drop all netpacks DISCONNECTING, // We disconnecting, drop all netpacks
CONNECTION_FAILED // We could not connect to server };
enum class EServerMode : uint8_t
{
NONE = 0,
LOCAL, // no global lobby
LOBBY_HOST, // We are hosting global server available via global lobby
LOBBY_GUEST // Connecting to a remote server via proxy provided by global lobby
}; };
class IServerAPI class IServerAPI
@@ -80,63 +92,68 @@ public:
}; };
/// structure to handle running server and connecting to it /// structure to handle running server and connecting to it
class CServerHandler : public IServerAPI, public LobbyInfo class CServerHandler final : public IServerAPI, public LobbyInfo, public INetworkClientListener, public INetworkTimerListener, boost::noncopyable
{ {
friend class ApplyOnLobbyHandlerNetPackVisitor; friend class ApplyOnLobbyHandlerNetPackVisitor;
std::shared_ptr<CApplier<CBaseForLobbyApply>> applier; std::unique_ptr<INetworkHandler> networkHandler;
std::shared_ptr<INetworkConnection> networkConnection;
std::shared_ptr<boost::recursive_mutex> mx; std::unique_ptr<GlobalLobbyClient> lobbyClient;
std::list<CPackForLobby *> packsForLobbyScreen; //protected by mx std::unique_ptr<CApplier<CBaseForLobbyApply>> applier;
std::shared_ptr<CMapInfo> mapToStart; std::shared_ptr<CMapInfo> mapToStart;
std::vector<std::string> myNames; std::vector<std::string> myNames;
std::shared_ptr<HighScoreCalculation> highScoreCalc; std::shared_ptr<HighScoreCalculation> highScoreCalc;
/// temporary helper member that exists while game in lobby mode boost::thread threadRunLocalServer;
/// required to correctly deserialize gamestate using client-side game callback boost::thread threadNetwork;
std::unique_ptr<CClient> nextClient;
std::atomic<EClientState> state;
void threadRunNetwork();
void threadRunServer(bool connectToLobby);
void threadHandleConnection();
void threadRunServer();
void onServerFinished();
void sendLobbyPack(const CPackForLobby & pack) const override; void sendLobbyPack(const CPackForLobby & pack) const override;
void onPacketReceived(const NetworkConnectionPtr &, const std::vector<std::byte> & message) override;
void onConnectionFailed(const std::string & errorMessage) override;
void onConnectionEstablished(const NetworkConnectionPtr &) override;
void onDisconnected(const NetworkConnectionPtr &, const std::string & errorMessage) override;
void onTimer() override;
void applyPackOnLobbyScreen(CPackForLobby & pack);
std::string serverHostname;
ui16 serverPort;
bool isServerLocal() const;
public: public:
std::atomic<EClientState> state; /// High-level connection overlay that is capable of (de)serializing network data
std::shared_ptr<CConnection> logicConnection;
//////////////////// ////////////////////
// FIXME: Bunch of crutches to glue it all together // FIXME: Bunch of crutches to glue it all together
// For starting non-custom campaign and continue to next mission // For starting non-custom campaign and continue to next mission
std::shared_ptr<CampaignState> campaignStateToSend; std::shared_ptr<CampaignState> campaignStateToSend;
ui8 screenType; // To create lobby UI only after server is setup ESelectionScreen screenType; // To create lobby UI only after server is setup
ui8 loadMode; // For saves filtering in SelectionTab EServerMode serverMode;
ELoadMode loadMode; // For saves filtering in SelectionTab
//////////////////// ////////////////////
std::unique_ptr<CStopWatch> th; std::unique_ptr<CStopWatch> th;
std::shared_ptr<boost::thread> threadRunLocalServer;
std::shared_ptr<CConnection> c;
std::unique_ptr<CClient> client; std::unique_ptr<CClient> client;
CondSh<bool> campaignServerRestartLock;
static const std::string localhostAddress;
CServerHandler(); CServerHandler();
~CServerHandler(); ~CServerHandler();
std::string getHostAddress() const; void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector<std::string> & names);
ui16 getHostPort() const; void startLocalServerAndConnect(bool connectToLobby);
void connectToServer(const std::string & addr, const ui16 port);
void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr); GlobalLobbyClient & getGlobalLobby();
void startLocalServerAndConnect(); INetworkHandler & getNetworkHandler();
void justConnectToServer(const std::string & addr, const ui16 port);
void applyPacksOnLobbyScreen();
void stopServerConnection();
// Helpers for lobby state access // Helpers for lobby state access
std::set<PlayerColor> getHumanColors(); std::set<PlayerColor> getHumanColors();
@@ -144,12 +161,21 @@ public:
bool isMyColor(PlayerColor color) const; bool isMyColor(PlayerColor color) const;
ui8 myFirstId() const; // Used by chat only! ui8 myFirstId() const; // Used by chat only!
bool isServerLocal() const; EClientState getState() const;
void setState(EClientState newState);
bool isHost() const; bool isHost() const;
bool isGuest() const; bool isGuest() const;
bool inLobbyRoom() const;
bool inGame() const;
static ui16 getDefaultPort(); const std::string & getCurrentHostname() const;
static std::string getDefaultPortStr(); const std::string & getLocalHostname() const;
const std::string & getRemoteHostname() const;
ui16 getCurrentPort() const;
ui16 getLocalPort() const;
ui16 getRemotePort() const;
// Lobby server API for UI // Lobby server API for UI
void sendClientConnecting() const override; void sendClientConnecting() const override;
@@ -175,15 +201,14 @@ public:
void debugStartTest(std::string filename, bool save = false); void debugStartTest(std::string filename, bool save = false);
void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr); void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
void endGameplay(bool closeConnection = true, bool restart = false); void endGameplay();
void restartGameplay();
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {}); void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {});
void showServerError(const std::string & txt) const; void showServerError(const std::string & txt) const;
// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle // TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
int howManyPlayerInterfaces(); int howManyPlayerInterfaces();
ui8 getLoadMode(); ELoadMode getLoadMode();
void restoreLastSession();
void visitForLobby(CPackForLobby & lobbyPack); void visitForLobby(CPackForLobby & lobbyPack);
void visitForClient(CPackForClient & clientPack); void visitForClient(CPackForClient & clientPack);

View File

@@ -30,11 +30,12 @@
#include "../lib/UnlockGuard.h" #include "../lib/UnlockGuard.h"
#include "../lib/battle/BattleInfo.h" #include "../lib/battle/BattleInfo.h"
#include "../lib/serializer/BinaryDeserializer.h" #include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMapService.h"
#include "../lib/pathfinder/CGPathNode.h" #include "../lib/pathfinder/CGPathNode.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesClientPacks.h" #include "../lib/registerTypes/RegisterTypesClientPacks.h"
#include "../lib/serializer/Connection.h"
#include <memory> #include <memory>
#include <vcmi/events/EventBus.h> #include <vcmi/events/EventBus.h>
@@ -297,7 +298,7 @@ void CClient::serialize(BinaryDeserializer & h)
bool shouldResetInterface = true; bool shouldResetInterface = true;
// Client no longer handle this player at all // Client no longer handle this player at all
if(!vstd::contains(CSH->getAllClientPlayers(CSH->c->connectionID), pid)) if(!vstd::contains(CSH->getAllClientPlayers(CSH->logicConnection->connectionID), pid))
{ {
logGlobal->trace("Player %s is not belong to this client. Destroying interface", pid); logGlobal->trace("Player %s is not belong to this client. Destroying interface", pid);
} }
@@ -397,7 +398,7 @@ void CClient::initPlayerEnvironments()
{ {
playerEnvironments.clear(); playerEnvironments.clear();
auto allPlayers = CSH->getAllClientPlayers(CSH->c->connectionID); auto allPlayers = CSH->getAllClientPlayers(CSH->logicConnection->connectionID);
bool hasHumanPlayer = false; bool hasHumanPlayer = false;
for(auto & color : allPlayers) for(auto & color : allPlayers)
{ {
@@ -427,7 +428,7 @@ void CClient::initPlayerInterfaces()
for(auto & playerInfo : gs->scenarioOps->playerInfos) for(auto & playerInfo : gs->scenarioOps->playerInfos)
{ {
PlayerColor color = playerInfo.first; PlayerColor color = playerInfo.first;
if(!vstd::contains(CSH->getAllClientPlayers(CSH->c->connectionID), color)) if(!vstd::contains(CSH->getAllClientPlayers(CSH->logicConnection->connectionID), color))
continue; continue;
if(!vstd::contains(playerint, color)) if(!vstd::contains(playerint, color))
@@ -457,7 +458,7 @@ void CClient::initPlayerInterfaces()
installNewPlayerInterface(std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true); installNewPlayerInterface(std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true);
} }
if(CSH->getAllClientPlayers(CSH->c->connectionID).count(PlayerColor::NEUTRAL)) if(CSH->getAllClientPlayers(CSH->logicConnection->connectionID).count(PlayerColor::NEUTRAL))
installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL); installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL);
logNetwork->trace("Initialized player interfaces %d ms", CSH->th->getDiff()); logNetwork->trace("Initialized player interfaces %d ms", CSH->th->getDiff());
@@ -520,7 +521,6 @@ void CClient::handlePack(CPack * pack)
CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
if(apply) if(apply)
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
apply->applyOnClBefore(this, pack); apply->applyOnClBefore(this, pack);
logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name()); logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name());
gs->apply(pack); gs->apply(pack);
@@ -545,7 +545,7 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
waitingRequest.pushBack(requestID); waitingRequest.pushBack(requestID);
request->requestID = requestID; request->requestID = requestID;
request->player = player; request->player = player;
CSH->c->sendPack(request); CSH->logicConnection->sendPack(request);
if(vstd::contains(playerint, player)) if(vstd::contains(playerint, player))
playerint[player]->requestSent(request, requestID); playerint[player]->requestSent(request, requestID);

View File

@@ -173,7 +173,7 @@ public:
void showTeleportDialog(TeleportDialog * iw) override {}; void showTeleportDialog(TeleportDialog * iw) override {};
void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override {}; void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override {};
void giveResource(PlayerColor player, GameResID which, int val) override {}; void giveResource(PlayerColor player, GameResID which, int val) override {};
virtual void giveResources(PlayerColor player, TResources resources) override {}; void giveResources(PlayerColor player, TResources resources) override {};
void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {}; void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {};
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures) override {}; void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures) override {};

View File

@@ -118,16 +118,16 @@ public:
{ {
} }
virtual void visitChangeObjPos(ChangeObjPos & pack) override; void visitChangeObjPos(ChangeObjPos & pack) override;
virtual void visitRemoveObject(RemoveObject & pack) override; void visitRemoveObject(RemoveObject & pack) override;
virtual void visitTryMoveHero(TryMoveHero & pack) override; void visitTryMoveHero(TryMoveHero & pack) override;
virtual void visitGiveHero(GiveHero & pack) override; void visitGiveHero(GiveHero & pack) override;
virtual void visitBattleStart(BattleStart & pack) override; void visitBattleStart(BattleStart & pack) override;
virtual void visitBattleNextRound(BattleNextRound & pack) override; void visitBattleNextRound(BattleNextRound & pack) override;
virtual void visitBattleUpdateGateState(BattleUpdateGateState & pack) override; void visitBattleUpdateGateState(BattleUpdateGateState & pack) override;
virtual void visitBattleResult(BattleResult & pack) override; void visitBattleResult(BattleResult & pack) override;
virtual void visitBattleStackMoved(BattleStackMoved & pack) override; void visitBattleStackMoved(BattleStackMoved & pack) override;
virtual void visitBattleAttack(BattleAttack & pack) override; void visitBattleAttack(BattleAttack & pack) override;
virtual void visitStartAction(StartAction & pack) override; void visitStartAction(StartAction & pack) override;
virtual void visitSetObjectProperty(SetObjectProperty & pack) override; void visitSetObjectProperty(SetObjectProperty & pack) override;
}; };

View File

@@ -32,11 +32,12 @@ public:
bool getResult() const { return result; } bool getResult() const { return result; }
virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; void visitLobbyClientConnected(LobbyClientConnected & pack) override;
virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override;
virtual void visitLobbyEndGame(LobbyEndGame & pack) override; void visitLobbyRestartGame(LobbyRestartGame & pack) override;
virtual void visitLobbyStartGame(LobbyStartGame & pack) override; void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override;
virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; void visitLobbyStartGame(LobbyStartGame & pack) override;
void visitLobbyUpdateState(LobbyUpdateState & pack) override;
}; };
class ApplyOnLobbyScreenNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) class ApplyOnLobbyScreenNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
@@ -51,11 +52,11 @@ public:
{ {
} }
virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override;
virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override; void visitLobbyChatMessage(LobbyChatMessage & pack) override;
virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override; void visitLobbyGuiAction(LobbyGuiAction & pack) override;
virtual void visitLobbyStartGame(LobbyStartGame & pack) override; void visitLobbyStartGame(LobbyStartGame & pack) override;
virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) override; void visitLobbyLoadProgress(LobbyLoadProgress & pack) override;
virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; void visitLobbyUpdateState(LobbyUpdateState & pack) override;
virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override; void visitLobbyShowMessage(LobbyShowMessage & pack) override;
}; };

View File

@@ -27,8 +27,8 @@
#include "../CCallback.h" #include "../CCallback.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h" #include "../lib/filesystem/FileInfo.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/BinarySerializer.h" #include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/CGeneralTextHandler.h" #include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h" #include "../lib/CHeroHandler.h"
#include "../lib/VCMI_Lib.h" #include "../lib/VCMI_Lib.h"
@@ -424,7 +424,7 @@ void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface
cl.initPlayerEnvironments(); cl.initPlayerEnvironments();
initInterfaces(); initInterfaces();
} }
else if(pack.playerConnectionId == CSH->c->connectionID) else if(pack.playerConnectionId == CSH->logicConnection->connectionID)
{ {
plSettings.connectedPlayerIDs.insert(pack.playerConnectionId); plSettings.connectedPlayerIDs.insert(pack.playerConnectionId);
cl.playerint.clear(); cl.playerint.clear();

View File

@@ -19,9 +19,13 @@
#include "lobby/ExtraOptionsTab.h" #include "lobby/ExtraOptionsTab.h"
#include "lobby/SelectionTab.h" #include "lobby/SelectionTab.h"
#include "lobby/CBonusSelection.h" #include "lobby/CBonusSelection.h"
#include "globalLobby/GlobalLobbyWindow.h"
#include "globalLobby/GlobalLobbyServerSetup.h"
#include "globalLobby/GlobalLobbyClient.h"
#include "CServerHandler.h" #include "CServerHandler.h"
#include "CGameInfo.h" #include "CGameInfo.h"
#include "Client.h"
#include "gui/CGuiHandler.h" #include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h" #include "gui/WindowHandler.h"
#include "widgets/Buttons.h" #include "widgets/Buttons.h"
@@ -36,26 +40,50 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
result = false; result = false;
// Check if it's LobbyClientConnected for our client // Check if it's LobbyClientConnected for our client
if(pack.uuid == handler.c->uuid) if(pack.uuid == handler.logicConnection->uuid)
{ {
handler.c->connectionID = pack.clientId; handler.logicConnection->connectionID = pack.clientId;
if(handler.mapToStart) if(handler.mapToStart)
{
handler.setMapInfo(handler.mapToStart); handler.setMapInfo(handler.mapToStart);
}
else if(!settings["session"]["headless"].Bool()) else if(!settings["session"]["headless"].Bool())
GH.windows().createAndPushWindow<CLobbyScreen>(static_cast<ESelectionScreen>(handler.screenType)); {
handler.state = EClientState::LOBBY; if (GH.windows().topWindow<CSimpleJoinScreen>())
GH.windows().popWindows(1);
if (!GH.windows().findWindows<GlobalLobbyServerSetup>().empty())
{
assert(handler.serverMode == EServerMode::LOBBY_HOST);
// announce opened game room
// TODO: find better approach?
int roomType = settings["lobby"]["roomType"].Integer();
if (roomType != 0)
handler.getGlobalLobby().sendOpenPrivateRoom();
else
handler.getGlobalLobby().sendOpenPublicRoom();
}
while (!GH.windows().findWindows<GlobalLobbyWindow>().empty())
{
// if global lobby is open, pop all dialogs on top of it as well as lobby itself
GH.windows().popWindows(1);
}
GH.windows().createAndPushWindow<CLobbyScreen>(handler.screenType);
}
handler.setState(EClientState::LOBBY);
} }
} }
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{ {
if(pack.clientId != handler.c->connectionID) if(pack.clientId != handler.logicConnection->connectionID)
{ {
result = false; result = false;
return; return;
} }
handler.stopServerConnection();
} }
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
@@ -106,30 +134,31 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
} }
} }
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack)
{ {
if(handler.state == EClientState::GAMEPLAY) assert(handler.getState() == EClientState::GAMEPLAY);
{
handler.endGameplay(pack.closeConnection, pack.restart);
}
if(pack.restart) handler.restartGameplay();
{ handler.sendStartGame();
if (handler.validateGameStart()) }
handler.sendStartGame();
} void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack)
{
handler.client = std::make_unique<CClient>();
handler.logicConnection->enterLobbyConnectionMode();
handler.logicConnection->setCallback(handler.client.get());
} }
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
{ {
if(pack.clientId != -1 && pack.clientId != handler.c->connectionID) if(pack.clientId != -1 && pack.clientId != handler.logicConnection->connectionID)
{ {
result = false; result = false;
return; return;
} }
handler.state = EClientState::STARTING; handler.setState(EClientState::STARTING);
if(handler.si->mode != StartInfo::LOAD_GAME || pack.clientId == handler.c->connectionID) if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.logicConnection->connectionID)
{ {
auto modeBackup = handler.si->mode; auto modeBackup = handler.si->mode;
handler.si = pack.initializedStartInfo; handler.si = pack.initializedStartInfo;
@@ -174,7 +203,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
if(!lobby) //stub: ignore message for game mode if(!lobby) //stub: ignore message for game mode
return; return;
if(!lobby->bonusSel && handler.si->campState && handler.state == EClientState::LOBBY_CAMPAIGN) if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
{ {
lobby->bonusSel = std::make_shared<CBonusSelection>(); lobby->bonusSel = std::make_shared<CBonusSelection>();
GH.windows().pushWindow(lobby->bonusSel); GH.windows().pushWindow(lobby->bonusSel);

View File

@@ -852,12 +852,19 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
owner(owner) owner(owner)
{ {
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
uint32_t queueSize = QUEUE_SIZE_BIG;
if(embedded) if(embedded)
{ {
pos.w = QUEUE_SIZE * 41; int32_t queueSmallOutsideYOffset = 65;
bool queueSmallOutside = settings["battle"]["queueSmallOutside"].Bool() && (pos.y - queueSmallOutsideYOffset) >= 0;
queueSize = std::clamp(static_cast<int>(settings["battle"]["queueSmallSlots"].Float()), 1, queueSmallOutside ? GH.screenDimensions().x / 41 : 19);
pos.w = queueSize * 41;
pos.h = 49; pos.h = 49;
pos.x += parent->pos.w/2 - pos.w/2; pos.x += parent->pos.w/2 - pos.w/2;
pos.y += 10; pos.y += queueSmallOutside ? -queueSmallOutsideYOffset : 10;
icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL")); icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"));
stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL")); stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
@@ -878,7 +885,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
} }
stateIcons->preload(); stateIcons->preload();
stackBoxes.resize(QUEUE_SIZE); stackBoxes.resize(queueSize);
for (int i = 0; i < stackBoxes.size(); i++) for (int i = 0; i < stackBoxes.size(); i++)
{ {
stackBoxes[i] = std::make_shared<StackBox>(this); stackBoxes[i] = std::make_shared<StackBox>(this);

View File

@@ -239,7 +239,7 @@ class StackQueue : public CIntObject
std::optional<uint32_t> getBoundUnitID() const; std::optional<uint32_t> getBoundUnitID() const;
}; };
static const int QUEUE_SIZE = 10; static const int QUEUE_SIZE_BIG = 10;
std::shared_ptr<CFilledTexture> background; std::shared_ptr<CFilledTexture> background;
std::vector<std::shared_ptr<StackBox>> stackBoxes; std::vector<std::shared_ptr<StackBox>> stackBoxes;
BattleInterface & owner; BattleInterface & owner;

View File

@@ -145,7 +145,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
Settings full = settings.write["video"]["fullscreen"]; Settings full = settings.write["video"]["fullscreen"];
full->Bool() = !full->Bool(); full->Bool() = !full->Bool();
GH.onScreenResize(); GH.onScreenResize(false);
return; return;
} }
} }
@@ -163,7 +163,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
#ifndef VCMI_IOS #ifndef VCMI_IOS
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
GH.onScreenResize(); GH.onScreenResize(false);
} }
#endif #endif
break; break;

View File

@@ -16,6 +16,8 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h" #include "../gui/EventDispatcher.h"
#include "../gui/ShortcutHandler.h" #include "../gui/ShortcutHandler.h"
#include "../CServerHandler.h"
#include "../globalLobby/GlobalLobbyClient.h"
#include <SDL_clipboard.h> #include <SDL_clipboard.h>
#include <SDL_events.h> #include <SDL_events.h>
@@ -31,6 +33,8 @@ InputSourceKeyboard::InputSourceKeyboard()
void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key) void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
{ {
assert(key.state == SDL_PRESSED);
if (SDL_IsTextInputActive() == SDL_TRUE) if (SDL_IsTextInputActive() == SDL_TRUE)
{ {
if(key.keysym.sym == SDLK_v && isKeyboardCtrlDown()) if(key.keysym.sym == SDLK_v && isKeyboardCtrlDown())
@@ -51,7 +55,11 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
return; // ignore periodic event resends return; // ignore periodic event resends
} }
assert(key.state == SDL_PRESSED);
if(key.keysym.sym == SDLK_TAB && isKeyboardCtrlDown())
{
CSH->getGlobalLobby().activateInterface();
}
if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool()) if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
{ {

View File

@@ -0,0 +1,366 @@
/*
* GlobalLobbyClient.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 "GlobalLobbyClient.h"
#include "GlobalLobbyLoginWindow.h"
#include "GlobalLobbyWindow.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../windows/InfoWindows.h"
#include "../CServerHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/MetaString.h"
#include "../../lib/TextOperations.h"
GlobalLobbyClient::GlobalLobbyClient() = default;
GlobalLobbyClient::~GlobalLobbyClient() = default;
static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0)
{
// FIXME: better/unified way to format date
auto timeNowChrono = std::chrono::system_clock::now();
timeNowChrono += std::chrono::seconds(timeOffsetSeconds);
return TextOperations::getFormattedTimeLocal(std::chrono::system_clock::to_time_t(timeNowChrono));
}
void GlobalLobbyClient::onPacketReceived(const std::shared_ptr<INetworkConnection> &, const std::vector<std::byte> & message)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
JsonNode json(message.data(), message.size());
if(json["type"].String() == "accountCreated")
return receiveAccountCreated(json);
if(json["type"].String() == "operationFailed")
return receiveOperationFailed(json);
if(json["type"].String() == "loginSuccess")
return receiveLoginSuccess(json);
if(json["type"].String() == "chatHistory")
return receiveChatHistory(json);
if(json["type"].String() == "chatMessage")
return receiveChatMessage(json);
if(json["type"].String() == "activeAccounts")
return receiveActiveAccounts(json);
if(json["type"].String() == "activeGameRooms")
return receiveActiveGameRooms(json);
if(json["type"].String() == "joinRoomSuccess")
return receiveJoinRoomSuccess(json);
if(json["type"].String() == "inviteReceived")
return receiveInviteReceived(json);
logGlobal->error("Received unexpected message from lobby server: %s", json["type"].String());
}
void GlobalLobbyClient::receiveAccountCreated(const JsonNode & json)
{
auto loginWindowPtr = loginWindow.lock();
if(!loginWindowPtr || !GH.windows().topWindow<GlobalLobbyLoginWindow>())
throw std::runtime_error("lobby connection finished without active login window!");
{
Settings configID = settings.write["lobby"]["accountID"];
configID->String() = json["accountID"].String();
Settings configName = settings.write["lobby"]["displayName"];
configName->String() = json["displayName"].String();
Settings configCookie = settings.write["lobby"]["accountCookie"];
configCookie->String() = json["accountCookie"].String();
}
sendClientLogin();
}
void GlobalLobbyClient::receiveOperationFailed(const JsonNode & json)
{
auto loginWindowPtr = loginWindow.lock();
if(loginWindowPtr)
loginWindowPtr->onConnectionFailed(json["reason"].String());
// TODO: handle errors in lobby menu
}
void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json)
{
{
Settings configCookie = settings.write["lobby"]["accountCookie"];
configCookie->String() = json["accountCookie"].String();
Settings configName = settings.write["lobby"]["displayName"];
configName->String() = json["displayName"].String();
}
auto loginWindowPtr = loginWindow.lock();
if(!loginWindowPtr || !GH.windows().topWindow<GlobalLobbyLoginWindow>())
throw std::runtime_error("lobby connection finished without active login window!");
loginWindowPtr->onConnectionSuccess();
}
void GlobalLobbyClient::receiveChatHistory(const JsonNode & json)
{
for(const auto & entry : json["messages"].Vector())
{
std::string accountID = entry["accountID"].String();
std::string displayName = entry["displayName"].String();
std::string messageText = entry["messageText"].String();
int ageSeconds = entry["ageSeconds"].Integer();
std::string timeFormatted = getCurrentTimeFormatted(-ageSeconds);
auto lobbyWindowPtr = lobbyWindow.lock();
if(lobbyWindowPtr)
lobbyWindowPtr->onGameChatMessage(displayName, messageText, timeFormatted);
}
}
void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
{
std::string accountID = json["accountID"].String();
std::string displayName = json["displayName"].String();
std::string messageText = json["messageText"].String();
std::string timeFormatted = getCurrentTimeFormatted();
auto lobbyWindowPtr = lobbyWindow.lock();
if(lobbyWindowPtr)
lobbyWindowPtr->onGameChatMessage(displayName, messageText, timeFormatted);
}
void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
{
activeAccounts.clear();
for (auto const & jsonEntry : json["accounts"].Vector())
{
GlobalLobbyAccount account;
account.accountID = jsonEntry["accountID"].String();
account.displayName = jsonEntry["displayName"].String();
account.status = jsonEntry["status"].String();
activeAccounts.push_back(account);
}
auto lobbyWindowPtr = lobbyWindow.lock();
if(lobbyWindowPtr)
lobbyWindowPtr->onActiveAccounts(activeAccounts);
}
void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
{
activeRooms.clear();
for (auto const & jsonEntry : json["gameRooms"].Vector())
{
GlobalLobbyRoom room;
room.gameRoomID = jsonEntry["gameRoomID"].String();
room.hostAccountID = jsonEntry["hostAccountID"].String();
room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String();
room.description = jsonEntry["description"].String();
room.playersCount = jsonEntry["playersCount"].Integer();
room.playersLimit = jsonEntry["playersLimit"].Integer();
activeRooms.push_back(room);
}
auto lobbyWindowPtr = lobbyWindow.lock();
if(lobbyWindowPtr)
lobbyWindowPtr->onActiveRooms(activeRooms);
}
void GlobalLobbyClient::receiveInviteReceived(const JsonNode & json)
{
assert(0); //TODO
}
void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json)
{
Settings configRoom = settings.write["lobby"]["roomID"];
configRoom->String() = json["gameRoomID"].String();
if (json["proxyMode"].Bool())
{
CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_GUEST, {});
CSH->loadMode = ELoadMode::MULTI;
std::string hostname = settings["lobby"]["hostname"].String();
int16_t port = settings["lobby"]["port"].Integer();
CSH->connectToServer(hostname, port);
}
}
void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr<INetworkConnection> & connection)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
networkConnection = connection;
auto loginWindowPtr = loginWindow.lock();
if(!loginWindowPtr || !GH.windows().topWindow<GlobalLobbyLoginWindow>())
throw std::runtime_error("lobby connection established without active login window!");
loginWindowPtr->onConnectionSuccess();
}
void GlobalLobbyClient::sendClientRegister(const std::string & accountName)
{
JsonNode toSend;
toSend["type"].String() = "clientRegister";
toSend["displayName"].String() = accountName;
sendMessage(toSend);
}
void GlobalLobbyClient::sendClientLogin()
{
JsonNode toSend;
toSend["type"].String() = "clientLogin";
toSend["accountID"] = settings["lobby"]["accountID"];
toSend["accountCookie"] = settings["lobby"]["accountCookie"];
sendMessage(toSend);
}
void GlobalLobbyClient::onConnectionFailed(const std::string & errorMessage)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
auto loginWindowPtr = loginWindow.lock();
if(!loginWindowPtr || !GH.windows().topWindow<GlobalLobbyLoginWindow>())
throw std::runtime_error("lobby connection failed without active login window!");
logGlobal->warn("Connection to game lobby failed! Reason: %s", errorMessage);
loginWindowPtr->onConnectionFailed(errorMessage);
}
void GlobalLobbyClient::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
assert(connection == networkConnection);
networkConnection.reset();
while (!GH.windows().findWindows<GlobalLobbyWindow>().empty())
{
// if global lobby is open, pop all dialogs on top of it as well as lobby itself
GH.windows().popWindows(1);
}
CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {});
}
void GlobalLobbyClient::sendMessage(const JsonNode & data)
{
networkConnection->sendPacket(data.toBytes(true));
}
void GlobalLobbyClient::sendOpenPublicRoom()
{
JsonNode toSend;
toSend["type"].String() = "openGameRoom";
toSend["hostAccountID"] = settings["lobby"]["accountID"];
toSend["roomType"].String() = "public";
sendMessage(toSend);
}
void GlobalLobbyClient::sendOpenPrivateRoom()
{
JsonNode toSend;
toSend["type"].String() = "openGameRoom";
toSend["hostAccountID"] = settings["lobby"]["accountID"];
toSend["roomType"].String() = "private";
sendMessage(toSend);
}
void GlobalLobbyClient::connect()
{
std::string hostname = settings["lobby"]["hostname"].String();
int16_t port = settings["lobby"]["port"].Integer();
CSH->getNetworkHandler().connectToRemote(*this, hostname, port);
}
bool GlobalLobbyClient::isConnected() const
{
return networkConnection != nullptr;
}
std::shared_ptr<GlobalLobbyLoginWindow> GlobalLobbyClient::createLoginWindow()
{
auto loginWindowPtr = loginWindow.lock();
if(loginWindowPtr)
return loginWindowPtr;
auto loginWindowNew = std::make_shared<GlobalLobbyLoginWindow>();
loginWindow = loginWindowNew;
return loginWindowNew;
}
std::shared_ptr<GlobalLobbyWindow> GlobalLobbyClient::createLobbyWindow()
{
auto lobbyWindowPtr = lobbyWindow.lock();
if(lobbyWindowPtr)
return lobbyWindowPtr;
lobbyWindowPtr = std::make_shared<GlobalLobbyWindow>();
lobbyWindow = lobbyWindowPtr;
lobbyWindowLock = lobbyWindowPtr;
return lobbyWindowPtr;
}
const std::vector<GlobalLobbyAccount> & GlobalLobbyClient::getActiveAccounts() const
{
return activeAccounts;
}
const std::vector<GlobalLobbyRoom> & GlobalLobbyClient::getActiveRooms() const
{
return activeRooms;
}
void GlobalLobbyClient::activateInterface()
{
if (!GH.windows().findWindows<GlobalLobbyWindow>().empty())
return;
if (!GH.windows().findWindows<GlobalLobbyLoginWindow>().empty())
return;
if (isConnected())
GH.windows().pushWindow(createLobbyWindow());
else
GH.windows().pushWindow(createLoginWindow());
}
void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & netConnection)
{
JsonNode toSend;
toSend["type"].String() = "clientProxyLogin";
toSend["accountID"] = settings["lobby"]["accountID"];
toSend["accountCookie"] = settings["lobby"]["accountCookie"];
toSend["gameRoomID"] = settings["lobby"]["roomID"];
netConnection->sendPacket(toSend.toBytes(true));
}

View File

@@ -0,0 +1,70 @@
/*
* GlobalLobbyClient.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 "GlobalLobbyDefines.h"
#include "../../lib/network/NetworkInterface.h"
VCMI_LIB_NAMESPACE_BEGIN
class JsonNode;
VCMI_LIB_NAMESPACE_END
class GlobalLobbyLoginWindow;
class GlobalLobbyWindow;
class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyable
{
std::vector<GlobalLobbyAccount> activeAccounts;
std::vector<GlobalLobbyRoom> activeRooms;
std::shared_ptr<INetworkConnection> networkConnection;
std::weak_ptr<GlobalLobbyLoginWindow> loginWindow;
std::weak_ptr<GlobalLobbyWindow> lobbyWindow;
std::shared_ptr<GlobalLobbyWindow> lobbyWindowLock; // helper strong reference to prevent window destruction on closing
void onPacketReceived(const std::shared_ptr<INetworkConnection> &, const std::vector<std::byte> & message) override;
void onConnectionFailed(const std::string & errorMessage) override;
void onConnectionEstablished(const std::shared_ptr<INetworkConnection> &) override;
void onDisconnected(const std::shared_ptr<INetworkConnection> &, const std::string & errorMessage) override;
void receiveAccountCreated(const JsonNode & json);
void receiveOperationFailed(const JsonNode & json);
void receiveLoginSuccess(const JsonNode & json);
void receiveChatHistory(const JsonNode & json);
void receiveChatMessage(const JsonNode & json);
void receiveActiveAccounts(const JsonNode & json);
void receiveActiveGameRooms(const JsonNode & json);
void receiveJoinRoomSuccess(const JsonNode & json);
void receiveInviteReceived(const JsonNode & json);
std::shared_ptr<GlobalLobbyLoginWindow> createLoginWindow();
std::shared_ptr<GlobalLobbyWindow> createLobbyWindow();
public:
explicit GlobalLobbyClient();
~GlobalLobbyClient();
const std::vector<GlobalLobbyAccount> & getActiveAccounts() const;
const std::vector<GlobalLobbyRoom> & getActiveRooms() const;
/// Activate interface and pushes lobby UI as top window
void activateInterface();
void sendMessage(const JsonNode & data);
void sendClientRegister(const std::string & accountName);
void sendClientLogin();
void sendOpenPublicRoom();
void sendOpenPrivateRoom();
void sendProxyConnectionLogin(const NetworkConnectionPtr & netConnection);
void connect();
bool isConnected() const;
};

View File

@@ -0,0 +1,27 @@
/*
* GlobalLobbyClient.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
struct GlobalLobbyAccount
{
std::string accountID;
std::string displayName;
std::string status;
};
struct GlobalLobbyRoom
{
std::string gameRoomID;
std::string hostAccountID;
std::string hostAccountDisplayName;
std::string description;
int playersCount;
int playersLimit;
};

View File

@@ -0,0 +1,128 @@
/*
* GlobalLobbyLoginWindow.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 "GlobalLobbyLoginWindow.h"
#include "GlobalLobbyClient.h"
#include "GlobalLobbyWindow.h"
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/TextControls.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/MetaString.h"
GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
: CWindowObject(BORDERED)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.w = 284;
pos.h = 220;
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title"));
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username"));
backgroundUsername = std::make_shared<TransparentFilledRectangle>(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
inputUsername = std::make_shared<CTextInput>(Rect(15, 93, 260, 16), FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true);
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); });
buttonClose = std::make_shared<CButton>(Point(210, 180), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
labelStatus = std::make_shared<CTextBox>( "", Rect(15, 115, 255, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
auto buttonRegister = std::make_shared<CToggleButton>(Point(10, 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
auto buttonLogin = std::make_shared<CToggleButton>(Point(146, 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
buttonRegister->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.create"), EFonts::FONT_SMALL, Colors::YELLOW);
buttonLogin->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.login"), EFonts::FONT_SMALL, Colors::YELLOW);
toggleMode = std::make_shared<CToggleGroup>(nullptr);
toggleMode->addToggle(0, buttonRegister);
toggleMode->addToggle(1, buttonLogin);
toggleMode->setSelected(settings["lobby"]["roomType"].Integer());
toggleMode->addCallback([this](int index){onLoginModeChanged(index);});
if (settings["lobby"]["accountID"].String().empty())
{
buttonLogin->block(true);
toggleMode->setSelected(0);
}
else
toggleMode->setSelected(1);
filledBackground->playerColored(PlayerColor(1));
inputUsername->cb += [this](const std::string & text)
{
this->buttonLogin->block(text.empty());
};
center();
}
void GlobalLobbyLoginWindow::onLoginModeChanged(int value)
{
if (value == 0)
{
inputUsername->setText("");
}
else
{
inputUsername->setText(settings["lobby"]["displayName"].String());
}
}
void GlobalLobbyLoginWindow::onClose()
{
close();
// TODO: abort ongoing connection attempt, if any
}
void GlobalLobbyLoginWindow::onLogin()
{
labelStatus->setText(CGI->generaltexth->translate("vcmi.lobby.login.connecting"));
if(!CSH->getGlobalLobby().isConnected())
CSH->getGlobalLobby().connect();
else
onConnectionSuccess();
buttonClose->block(true);
}
void GlobalLobbyLoginWindow::onConnectionSuccess()
{
std::string accountID = settings["lobby"]["accountID"].String();
if(toggleMode->getSelected() == 0)
CSH->getGlobalLobby().sendClientRegister(inputUsername->getText());
else
CSH->getGlobalLobby().sendClientLogin();
}
void GlobalLobbyLoginWindow::onLoginSuccess()
{
close();
CSH->getGlobalLobby().activateInterface();
}
void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason)
{
MetaString formatter;
formatter.appendTextID("vcmi.lobby.login.error");
formatter.replaceRawString(reason);
labelStatus->setText(formatter.toString());
buttonClose->block(false);
}

View File

@@ -0,0 +1,45 @@
/*
* GlobalLobbyLoginWindow.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 "../windows/CWindowObject.h"
class CLabel;
class CTextBox;
class CTextInput;
class CToggleGroup;
class FilledTexturePlayerColored;
class TransparentFilledRectangle;
class CButton;
class GlobalLobbyLoginWindow : public CWindowObject
{
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
std::shared_ptr<CLabel> labelTitle;
std::shared_ptr<CLabel> labelUsername;
std::shared_ptr<CTextBox> labelStatus;
std::shared_ptr<TransparentFilledRectangle> backgroundUsername;
std::shared_ptr<CTextInput> inputUsername;
std::shared_ptr<CButton> buttonLogin;
std::shared_ptr<CButton> buttonClose;
std::shared_ptr<CToggleGroup> toggleMode; // create account or use existing
void onLoginModeChanged(int value);
void onClose();
void onLogin();
public:
GlobalLobbyLoginWindow();
void onConnectionSuccess();
void onLoginSuccess();
void onConnectionFailed(const std::string & reason);
};

View File

@@ -0,0 +1,142 @@
/*
* GlobalLobbyServerSetup.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 "GlobalLobbyServerSetup.h"
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/MetaString.h"
GlobalLobbyServerSetup::GlobalLobbyServerSetup()
: CWindowObject(BORDERED)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.w = 284;
pos.h = 340;
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.create"));
labelPlayerLimit = std::make_shared<CLabel>( pos.w / 2, 48, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.players.limit"));
labelRoomType = std::make_shared<CLabel>( pos.w / 2, 108, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.type"));
labelGameMode = std::make_shared<CLabel>( pos.w / 2, 158, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.mode"));
togglePlayerLimit = std::make_shared<CToggleGroup>(nullptr);
togglePlayerLimit->addToggle(2, std::make_shared<CToggleButton>(Point(10 + 39*0, 60), AnimationPath::builtin("RanNum2"), CButton::tooltip(), 0));
togglePlayerLimit->addToggle(3, std::make_shared<CToggleButton>(Point(10 + 39*1, 60), AnimationPath::builtin("RanNum3"), CButton::tooltip(), 0));
togglePlayerLimit->addToggle(4, std::make_shared<CToggleButton>(Point(10 + 39*2, 60), AnimationPath::builtin("RanNum4"), CButton::tooltip(), 0));
togglePlayerLimit->addToggle(5, std::make_shared<CToggleButton>(Point(10 + 39*3, 60), AnimationPath::builtin("RanNum5"), CButton::tooltip(), 0));
togglePlayerLimit->addToggle(6, std::make_shared<CToggleButton>(Point(10 + 39*4, 60), AnimationPath::builtin("RanNum6"), CButton::tooltip(), 0));
togglePlayerLimit->addToggle(7, std::make_shared<CToggleButton>(Point(10 + 39*5, 60), AnimationPath::builtin("RanNum7"), CButton::tooltip(), 0));
togglePlayerLimit->addToggle(8, std::make_shared<CToggleButton>(Point(10 + 39*6, 60), AnimationPath::builtin("RanNum8"), CButton::tooltip(), 0));
togglePlayerLimit->setSelected(settings["lobby"]["roomPlayerLimit"].Integer());
togglePlayerLimit->addCallback([this](int index){onPlayerLimitChanged(index);});
auto buttonPublic = std::make_shared<CToggleButton>(Point(10, 120), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
auto buttonPrivate = std::make_shared<CToggleButton>(Point(146, 120), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
buttonPublic->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.public"), EFonts::FONT_SMALL, Colors::YELLOW);
buttonPrivate->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.private"), EFonts::FONT_SMALL, Colors::YELLOW);
toggleRoomType = std::make_shared<CToggleGroup>(nullptr);
toggleRoomType->addToggle(0, buttonPublic);
toggleRoomType->addToggle(1, buttonPrivate);
toggleRoomType->setSelected(settings["lobby"]["roomType"].Integer());
toggleRoomType->addCallback([this](int index){onRoomTypeChanged(index);});
auto buttonNewGame = std::make_shared<CToggleButton>(Point(10, 170), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
auto buttonLoadGame = std::make_shared<CToggleButton>(Point(146, 170), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
buttonNewGame->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.new"), EFonts::FONT_SMALL, Colors::YELLOW);
buttonLoadGame->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.load"), EFonts::FONT_SMALL, Colors::YELLOW);
toggleGameMode = std::make_shared<CToggleGroup>(nullptr);
toggleGameMode->addToggle(0, buttonNewGame);
toggleGameMode->addToggle(1, buttonLoadGame);
toggleGameMode->setSelected(settings["lobby"]["roomMode"].Integer());
toggleGameMode->addCallback([this](int index){onGameModeChanged(index);});
labelDescription = std::make_shared<CTextBox>("", Rect(10, 195, pos.w - 20, 80), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
buttonCreate = std::make_shared<CButton>(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); });
buttonClose = std::make_shared<CButton>(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
filledBackground->playerColored(PlayerColor(1));
updateDescription();
center();
}
void GlobalLobbyServerSetup::updateDescription()
{
MetaString description;
description.appendRawString("%s %s %s");
if(toggleRoomType->getSelected() == 0)
description.replaceTextID("vcmi.lobby.room.description.public");
else
description.replaceTextID("vcmi.lobby.room.description.private");
if(toggleGameMode->getSelected() == 0)
description.replaceTextID("vcmi.lobby.room.description.new");
else
description.replaceTextID("vcmi.lobby.room.description.load");
description.replaceTextID("vcmi.lobby.room.description.limit");
description.replaceNumber(togglePlayerLimit->getSelected());
labelDescription->setText(description.toString());
}
void GlobalLobbyServerSetup::onPlayerLimitChanged(int value)
{
Settings config = settings.write["lobby"]["roomPlayerLimit"];
config->Integer() = value;
updateDescription();
}
void GlobalLobbyServerSetup::onRoomTypeChanged(int value)
{
Settings config = settings.write["lobby"]["roomType"];
config->Integer() = value;
updateDescription();
}
void GlobalLobbyServerSetup::onGameModeChanged(int value)
{
Settings config = settings.write["lobby"]["roomMode"];
config->Integer() = value;
updateDescription();
}
void GlobalLobbyServerSetup::onCreate()
{
if(toggleGameMode->getSelected() == 0)
CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_HOST, {});
else
CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOBBY_HOST, {});
CSH->loadMode = ELoadMode::MULTI;
CSH->startLocalServerAndConnect(true);
buttonCreate->block(true);
buttonClose->block(true);
}
void GlobalLobbyServerSetup::onClose()
{
close();
}

View File

@@ -0,0 +1,49 @@
/*
* GlobalLobbyServerSetup.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 "../windows/CWindowObject.h"
class CLabel;
class CTextBox;
class FilledTexturePlayerColored;
class CButton;
class CToggleGroup;
class GlobalLobbyServerSetup : public CWindowObject
{
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
std::shared_ptr<CLabel> labelTitle;
std::shared_ptr<CLabel> labelPlayerLimit;
std::shared_ptr<CLabel> labelRoomType;
std::shared_ptr<CLabel> labelGameMode;
std::shared_ptr<CToggleGroup> togglePlayerLimit; // 2-8
std::shared_ptr<CToggleGroup> toggleRoomType; // public or private
std::shared_ptr<CToggleGroup> toggleGameMode; // new game or load game
std::shared_ptr<CTextBox> labelDescription;
std::shared_ptr<CTextBox> labelStatus;
std::shared_ptr<CButton> buttonCreate;
std::shared_ptr<CButton> buttonClose;
void updateDescription();
void onPlayerLimitChanged(int value);
void onRoomTypeChanged(int value);
void onGameModeChanged(int value);
void onCreate();
void onClose();
public:
GlobalLobbyServerSetup();
};

View File

@@ -0,0 +1,154 @@
/*
* GlobalLobbyWidget.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 "GlobalLobbyWidget.h"
#include "GlobalLobbyClient.h"
#include "GlobalLobbyWindow.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../../lib/MetaString.h"
GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window)
: window(window)
{
addCallback("closeWindow", [](int) { GH.windows().popWindows(1); });
addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); });
addCallback("createGameRoom", [this](int) { this->window->doCreateGameRoom(); });
REGISTER_BUILDER("accountList", &GlobalLobbyWidget::buildAccountList);
REGISTER_BUILDER("roomList", &GlobalLobbyWidget::buildRoomList);
const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json"));
build(config);
}
std::shared_ptr<CIntObject> GlobalLobbyWidget::buildAccountList(const JsonNode & config) const
{
const auto & createCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
{
const auto & accounts = CSH->getGlobalLobby().getActiveAccounts();
if(index < accounts.size())
return std::make_shared<GlobalLobbyAccountCard>(this->window, accounts[index]);
return std::make_shared<CIntObject>();
};
auto position = readPosition(config["position"]);
auto itemOffset = readPosition(config["itemOffset"]);
auto sliderPosition = readPosition(config["sliderPosition"]);
auto sliderSize = readPosition(config["sliderSize"]);
size_t visibleSize = 4; // FIXME: how many items can fit into UI?
size_t totalSize = 4; //FIXME: how many items are there in total
int sliderMode = 1 | 4; // present, vertical, blue
int initialPos = 0;
return std::make_shared<CListBox>(createCallback, position, itemOffset, visibleSize, totalSize, initialPos, sliderMode, Rect(sliderPosition, sliderSize) );
}
std::shared_ptr<CIntObject> GlobalLobbyWidget::buildRoomList(const JsonNode & config) const
{
const auto & createCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
{
const auto & rooms = CSH->getGlobalLobby().getActiveRooms();
if(index < rooms.size())
return std::make_shared<GlobalLobbyRoomCard>(this->window, rooms[index]);
return std::make_shared<CIntObject>();
};
auto position = readPosition(config["position"]);
auto itemOffset = readPosition(config["itemOffset"]);
auto sliderPosition = readPosition(config["sliderPosition"]);
auto sliderSize = readPosition(config["sliderSize"]);
size_t visibleSize = 4; // FIXME: how many items can fit into UI?
size_t totalSize = 4; //FIXME: how many items are there in total
int sliderMode = 1 | 4; // present, vertical, blue
int initialPos = 0;
return std::make_shared<CListBox>(createCallback, position, itemOffset, visibleSize, totalSize, initialPos, sliderMode, Rect(sliderPosition, sliderSize) );
}
std::shared_ptr<CLabel> GlobalLobbyWidget::getAccountNameLabel()
{
return widget<CLabel>("accountNameLabel");
}
std::shared_ptr<CTextInput> GlobalLobbyWidget::getMessageInput()
{
return widget<CTextInput>("messageInput");
}
std::shared_ptr<CTextBox> GlobalLobbyWidget::getGameChat()
{
return widget<CTextBox>("gameChat");
}
std::shared_ptr<CListBox> GlobalLobbyWidget::getAccountList()
{
return widget<CListBox>("accountList");
}
std::shared_ptr<CListBox> GlobalLobbyWidget::getRoomList()
{
return widget<CListBox>("roomList");
}
GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
const auto & onInviteClicked = [window, accountID=accountDescription.accountID]()
{
window->doInviteAccount(accountID);
};
pos.w = 130;
pos.h = 40;
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
labelName = std::make_shared<CLabel>( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, accountDescription.displayName);
labelStatus = std::make_shared<CLabel>( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, accountDescription.status);
if (CSH->inLobbyRoom())
buttonInvite = std::make_shared<CButton>(Point(95, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onInviteClicked);
}
GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
const auto & onJoinClicked = [window, roomID=roomDescription.gameRoomID]()
{
window->doJoinRoom(roomID);
};
auto roomSizeText = MetaString::createFromRawString("%d/%d");
roomSizeText.replaceNumber(roomDescription.playersCount);
roomSizeText.replaceNumber(roomDescription.playersLimit);
pos.w = 230;
pos.h = 40;
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
labelName = std::make_shared<CLabel>( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
labelStatus = std::make_shared<CLabel>( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomDescription.description);
labelRoomSize = std::make_shared<CLabel>( 160, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomSizeText.toString());
if (!CSH->inGame())
buttonJoin = std::make_shared<CButton>(Point(195, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onJoinClicked);
}

View File

@@ -0,0 +1,57 @@
/*
* GlobalLobbyWidget.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/InterfaceObjectConfigurable.h"
class GlobalLobbyWindow;
struct GlobalLobbyAccount;
struct GlobalLobbyRoom;
class CListBox;
class GlobalLobbyWidget : public InterfaceObjectConfigurable
{
GlobalLobbyWindow * window;
std::shared_ptr<CIntObject> buildAccountList(const JsonNode &) const;
std::shared_ptr<CIntObject> buildRoomList(const JsonNode &) const;
public:
explicit GlobalLobbyWidget(GlobalLobbyWindow * window);
std::shared_ptr<CLabel> getAccountNameLabel();
std::shared_ptr<CTextInput> getMessageInput();
std::shared_ptr<CTextBox> getGameChat();
std::shared_ptr<CListBox> getAccountList();
std::shared_ptr<CListBox> getRoomList();
};
class GlobalLobbyAccountCard : public CIntObject
{
public:
GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription);
std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
std::shared_ptr<CLabel> labelName;
std::shared_ptr<CLabel> labelStatus;
std::shared_ptr<CButton> buttonInvite;
};
class GlobalLobbyRoomCard : public CIntObject
{
public:
GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription);
std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
std::shared_ptr<CLabel> labelName;
std::shared_ptr<CLabel> labelRoomSize;
std::shared_ptr<CLabel> labelStatus;
std::shared_ptr<CButton> buttonJoin;
};

View File

@@ -0,0 +1,105 @@
/*
* GlobalLobbyWindow.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 "GlobalLobbyWindow.h"
#include "GlobalLobbyClient.h"
#include "GlobalLobbyServerSetup.h"
#include "GlobalLobbyWidget.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../widgets/TextControls.h"
#include "../widgets/ObjectLists.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/MetaString.h"
GlobalLobbyWindow::GlobalLobbyWindow()
: CWindowObject(BORDERED)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
widget = std::make_shared<GlobalLobbyWidget>(this);
pos = widget->pos;
center();
widget->getAccountNameLabel()->setText(settings["lobby"]["displayName"].String());
}
void GlobalLobbyWindow::doSendChatMessage()
{
std::string messageText = widget->getMessageInput()->getText();
JsonNode toSend;
toSend["type"].String() = "sendChatMessage";
toSend["messageText"].String() = messageText;
CSH->getGlobalLobby().sendMessage(toSend);
widget->getMessageInput()->setText("");
}
void GlobalLobbyWindow::doCreateGameRoom()
{
GH.windows().createAndPushWindow<GlobalLobbyServerSetup>();
}
void GlobalLobbyWindow::doInviteAccount(const std::string & accountID)
{
JsonNode toSend;
toSend["type"].String() = "sendInvite";
toSend["accountID"].String() = accountID;
CSH->getGlobalLobby().sendMessage(toSend);
}
void GlobalLobbyWindow::doJoinRoom(const std::string & roomID)
{
JsonNode toSend;
toSend["type"].String() = "joinGameRoom";
toSend["gameRoomID"].String() = roomID;
CSH->getGlobalLobby().sendMessage(toSend);
}
void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when)
{
MetaString chatMessageFormatted;
chatMessageFormatted.appendRawString("[%s] {%s}: %s\n");
chatMessageFormatted.replaceRawString(when);
chatMessageFormatted.replaceRawString(sender);
chatMessageFormatted.replaceRawString(message);
chatHistory += chatMessageFormatted.toString();
widget->getGameChat()->setText(chatHistory);
}
void GlobalLobbyWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts)
{
widget->getAccountList()->reset();
}
void GlobalLobbyWindow::onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms)
{
widget->getRoomList()->reset();
}
void GlobalLobbyWindow::onJoinedRoom()
{
widget->getAccountList()->reset();
}
void GlobalLobbyWindow::onLeftRoom()
{
widget->getAccountList()->reset();
}

View File

@@ -0,0 +1,38 @@
/*
* GlobalLobbyWindow.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 "../windows/CWindowObject.h"
class GlobalLobbyWidget;
struct GlobalLobbyAccount;
struct GlobalLobbyRoom;
class GlobalLobbyWindow : public CWindowObject
{
std::string chatHistory;
std::shared_ptr<GlobalLobbyWidget> widget;
public:
GlobalLobbyWindow();
void doSendChatMessage();
void doCreateGameRoom();
void doInviteAccount(const std::string & accountID);
void doJoinRoom(const std::string & roomID);
void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when);
void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts);
void onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms);
void onJoinedRoom();
void onLeftRoom();
};

Some files were not shown because too many files have changed in this diff Show More