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
/server/vcmiserver
/launcher/.lupdate
/launcher/vcmilauncher
/mapeditor/.lupdate
/launcher/vcmilauncher_automoc.cpp
/conan-*
build/
.cache/*
out/
/.qt
*.dll
*.exe
*.depend
@@ -42,6 +45,7 @@ VCMI_VS11.opensdf
.DS_Store
CMakeUserPresets.json
compile_commands.json
fuzzylite.pc
# Visual Studio
*.suo
@@ -62,5 +66,8 @@ compile_commands.json
/deps
.vs/
# Visual Studio Code
/.vscode/
# CLion
.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);
}

View File

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

View File

@@ -27,7 +27,7 @@ namespace Goals
virtual Goals::TGoalVec decompose() 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;
}

View File

@@ -26,7 +26,7 @@ namespace Goals
virtual Goals::TGoalVec decompose() 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;
}

View File

@@ -65,7 +65,7 @@ namespace Goals
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);

View File

@@ -28,10 +28,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
TGoalVec decompose() 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;
}

View File

@@ -32,7 +32,7 @@ namespace Goals
virtual Goals::TGoalVec decompose() 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;
}

View File

@@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
TGoalVec decompose() 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;
}

View File

@@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
TGoalVec decompose() 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;
}

View File

@@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
TGoalVec decompose() 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;
}

View File

@@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
TGoalVec decompose() 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;
}

View File

@@ -35,7 +35,7 @@ namespace Goals
void accept(AIGateway * ai) 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;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override
bool operator==(const Build & other) const override
{
return true;
}

View File

@@ -29,7 +29,7 @@ namespace Goals
void accept(AIGateway * ai) 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);
virtual bool operator==(const BuildThis & other) const override;
bool operator==(const BuildThis & other) const override;
virtual std::string toString() const override;
void accept(AIGateway * ai) override;
};

View File

@@ -36,11 +36,11 @@ namespace Goals
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 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;
}
virtual bool operator==(const AbstractGoal & g) const override
bool operator==(const AbstractGoal & g) const override
{
if(goalType != g.goalType)
return false;
@@ -54,7 +54,7 @@ namespace Goals
virtual bool operator==(const T & other) const = 0;
virtual TGoalVec decompose() const override
TGoalVec decompose() const override
{
TSubgoal single = decomposeSingle();
@@ -90,11 +90,11 @@ namespace Goals
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 int getHeroExchangeCount() const override { return 0; }
int getHeroExchangeCount() const override { return 0; }
};
}

View File

@@ -34,11 +34,11 @@ namespace Goals
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 std::string toString() const override;
virtual bool hasHash() const override { return true; }
virtual uint64_t getHash() const override;
bool hasHash() const override { return true; }
uint64_t getHash() const override;
};
}

View File

@@ -31,10 +31,10 @@ namespace Goals
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
virtual bool hasHash() const override { return true; }
virtual uint64_t getHash() const override;
bool hasHash() const override { return true; }
uint64_t getHash() const override;
virtual bool operator==(const CompleteQuest & other) const override;
bool operator==(const CompleteQuest & other) const override;
private:
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;
void accept(AIGateway * ai) override;
Composition & addNext(const AbstractGoal & goal);
Composition & addNext(TSubgoal goal);
Composition & addNextSequence(const TGoalVec & taskSequence);
virtual TGoalVec decompose() const override;
virtual bool isElementar() const override;
virtual int getHeroExchangeCount() const override;
TGoalVec decompose() const override;
bool isElementar() const override;
int getHeroExchangeCount() const override;
};
}

View File

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

View File

@@ -26,7 +26,7 @@ namespace Goals
void accept(AIGateway * ai) 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;
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; }
HeroLockedReason getLockingReason() const { return lockingReason; }

View File

@@ -30,10 +30,10 @@ namespace Goals
void accept(AIGateway * ai) 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; }
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
private:
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);

View File

@@ -36,7 +36,7 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() 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();
}
virtual bool operator==(const Invalid & other) const override
bool operator==(const Invalid & other) const override
{
return true;
}
@@ -42,7 +42,7 @@ namespace Goals
return "Invalid";
}
virtual void accept(AIGateway * ai) override
void accept(AIGateway * ai) override
{
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;
}

View File

@@ -28,7 +28,7 @@ namespace Goals
void accept(AIGateway * ai) 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:
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;
void accept(AIGateway * ai) override;
float getMovementWasted() const { return movementWasted; }

View File

@@ -34,7 +34,7 @@ namespace Goals
value = val;
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 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;
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 CGHeroInstance * defender);
virtual bool operator==(const DefendTown & other) const override;
bool operator==(const DefendTown & other) const override;
virtual std::string toString() const override;
const HitMapInfo & getTreat() const { return treat; }

View File

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

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ namespace AIPathfinding
~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:
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(
const CGHeroInstance * hero,
@@ -38,7 +38,7 @@ namespace AIPathfinding
AIPathNode * dstMode,
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;
};

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

View File

@@ -25,7 +25,7 @@ namespace AIPathfinding
class SummonBoatAction : public VirtualBoatAction
{
public:
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination(
const CGHeroInstance * hero,
@@ -34,9 +34,9 @@ namespace AIPathfinding
AIPathNode * dstMode,
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;
@@ -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 const ChainActor * getActor(const ChainActor * sourceActor) const override;
const ChainActor * getActor(const ChainActor * sourceActor) 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 void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) 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;
};

View File

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

View File

@@ -39,6 +39,6 @@ namespace Goals
void accept(VCAI * ai) override;
std::string name() 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;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override
bool operator==(const Build & other) const override
{
return true;
}

View File

@@ -32,6 +32,6 @@ namespace Goals
void accept(VCAI * ai) override;
std::string name() 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;
//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;
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;
}
virtual bool operator==(const AbstractGoal & g) const override
bool operator==(const AbstractGoal & g) const override
{
if(goalType != g.goalType)
return false;

View File

@@ -40,6 +40,6 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() 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 whatToDoToTrade();
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;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const CompleteQuest & other) const override;
bool operator==(const CompleteQuest & other) const override;
private:
TGoalVec tryCompleteQuest() const;

View File

@@ -27,6 +27,6 @@ namespace Goals
}
TGoalVec getAllPossibleSubgoals() 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();
}
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;
std::string completeMessage() const override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Explore & other) const override;
bool operator==(const Explore & other) const override;
private:
TSubgoal exploreNearestNeighbour(HeroPtr h) const;

View File

@@ -42,6 +42,6 @@ namespace Goals
}
TSubgoal whatToDoToAchieve() 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;
TSubgoal whatToDoToAchieve() 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;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const GatherTroops & other) const override;
bool operator==(const GatherTroops & other) const override;
private:
int getCreaturesCount(const CArmedInstance * army);

View File

@@ -35,6 +35,6 @@ namespace Goals
return TGoalVec();
}
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();
}
virtual bool operator==(const Invalid & other) const override
bool operator==(const Invalid & other) const override
{
return true;
}

View File

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

View File

@@ -33,6 +33,6 @@ namespace Goals
priority = 3; //trading is instant, but picking resources is free
}
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;
bool fulfillsMe(TSubgoal goal) 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;
bool fulfillsMe(TSubgoal goal) 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;
TSubgoal whatToDoToAchieve() 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;
virtual bool operator==(const Win & other) const override
bool operator==(const Win & other) const override
{
return true;
}

View File

@@ -99,7 +99,7 @@ public:
const PathfinderConfig * pathfinderConfig,
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;
void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);

View File

@@ -30,6 +30,6 @@ namespace AIPathfinding
~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 \
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 \
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 \
qtbase5-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)
endif()
set(buildLobby OFF)
set(singleProcess OFF)
set(staticAI OFF)
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)
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)
if(ENABLE_CCACHE)
find_program(CCACHE ccache REQUIRED)
@@ -475,6 +484,10 @@ if(TARGET SDL2_ttf::SDL2_ttf)
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
endif()
if(ENABLE_LOBBY)
find_package(SQLite3 REQUIRED)
endif()
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
# Widgets finds its own dependencies (QtGui and QtCore).
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
@@ -622,6 +635,9 @@ endif()
if(ENABLE_EDITOR)
add_subdirectory(mapeditor)
endif()
if(ENABLE_LOBBY)
add_subdirectory(lobby)
endif()
add_subdirectory(client)
add_subdirectory(server)
if(ENABLE_TEST)

View File

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

View File

@@ -63,7 +63,6 @@
"vcmi.mainMenu.serverClosing" : "Closing...",
"vcmi.mainMenu.hostTCP" : "Host TCP/IP game",
"vcmi.mainMenu.joinTCP" : "Join TCP/IP game",
"vcmi.mainMenu.playerName" : "Player",
"vcmi.lobby.filepath" : "File path",
"vcmi.lobby.creationDate" : "Creation date",
@@ -73,12 +72,33 @@
"vcmi.lobby.noUnderground" : "no underground",
"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.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.modsToEnable" : "{Following mods are required}",
"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.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!",

View File

@@ -20,6 +20,7 @@
"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.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.1" : "Azul",
@@ -70,7 +71,9 @@
"vcmi.lobby.mapPreview" : "Vista previa del mapa",
"vcmi.lobby.noPreview" : "sin vista previa",
"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.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}",
@@ -144,6 +147,8 @@
"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.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.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.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.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.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.kills" : "%d perecerán",
"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",
@@ -225,6 +237,8 @@
"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.tavernWindow.inviteHero" : "Invitar heroe",
"vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?",
"vcmi.creatureWindow.showBonuses.hover" : "Cambiar a vista de bonificaciones",
@@ -321,6 +335,7 @@
"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.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
"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.ENCHANTED.name": "Encantado",
"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.description": "Ignora una parte de la defensa al atacar",
"core.bonus.FIRE_IMMUNITY.name": "Inmunidad al Fuego",
@@ -386,6 +403,8 @@
"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
"core.bonus.FEARLESS.name": "Inmune al 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.description": "Puede volar (ignora obstáculos)",
"core.bonus.FREE_SHOOTING.name": "Disparo cercano",
@@ -416,8 +435,8 @@
"core.bonus.MANA_DRAIN.description": "Drena ${val} de maná cada turno",
"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_RESISTANCE.name": "Resistencia mágica (${MR}%)",
"core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${MR}% de resistir el hechizo del enemigo",
"core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${val}%)",
"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.description": "Inmune a hechizos de tipo mental",
"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.RETURN_AFTER_STRIKE.name": "Atacar y volver",
"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.description": "La criatura puede disparar",
"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.description": "Gana ${val} nuevas criaturas por cada enemigo eliminado",
"core.bonus.SPELLCASTER.name": "Lanzador de hechizos",

View File

@@ -138,16 +138,7 @@ int main(int argc, char * argv[])
("nointro,i", "skips intro movies")
("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")
("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");
("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
if(argc > 1)
{
@@ -371,46 +362,6 @@ int main(int argc, char * argv[])
}
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())
{
@@ -451,7 +402,6 @@ static void mainLoop()
while(1) //main SDL events loop
{
GH.input().fetchEvents();
CSH->applyPacksOnLobbyScreen();
GH.renderFrame();
}
}

View File

@@ -95,6 +95,12 @@ set(client_SRCS
renderSDL/ScreenHandler.cpp
renderSDL/SDL_Extensions.cpp
globalLobby/GlobalLobbyClient.cpp
globalLobby/GlobalLobbyLoginWindow.cpp
globalLobby/GlobalLobbyServerSetup.cpp
globalLobby/GlobalLobbyWidget.cpp
globalLobby/GlobalLobbyWindow.cpp
widgets/Buttons.cpp
widgets/CArtifactHolder.cpp
widgets/CComponent.cpp
@@ -270,6 +276,13 @@ set(client_HEADERS
renderSDL/SDL_Extensions.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/CArtifactHolder.h
widgets/CComponent.h

View File

@@ -1870,14 +1870,9 @@ void CPlayerInterface::proposeLoadingGame()
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
);

View File

@@ -16,6 +16,7 @@
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "globalLobby/GlobalLobbyClient.h"
#include "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h"
#include "windows/InfoWindows.h"
@@ -46,16 +47,15 @@
#include "../lib/mapObjects/MiscObjects.h"
#include "../lib/modding/ModIncompatibility.h"
#include "../lib/rmg/CMapGenOptions.h"
#include "../lib/serializer/Connection.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/CMemorySerializer.h"
#include "../lib/UnlockGuard.h"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/asio.hpp>
#include "../lib/serializer/Cast.h"
#include "LobbyClientNetPackVisitors.h"
@@ -67,8 +67,6 @@
template<typename T> class CApplyOnLobby;
const std::string CServerHandler::localhostAddress{"127.0.0.1"};
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
extern std::atomic_bool androidTestServerReadyFlag;
#endif
@@ -76,8 +74,8 @@ extern std::atomic_bool androidTestServerReadyFlag;
class CBaseForLobbyApply
{
public:
virtual bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const = 0;
virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const = 0;
virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0;
virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0;
virtual ~CBaseForLobbyApply(){};
template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
{
@@ -88,124 +86,137 @@ public:
template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
{
public:
bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
T * ptr = static_cast<T *>(pack);
auto & ref = static_cast<T&>(pack);
ApplyOnLobbyHandlerNetPackVisitor visitor(*handler);
logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ptr).name());
ptr->visit(visitor);
logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name());
ref.visit(visitor);
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);
logNetwork->trace("\tApply on lobby from queue: %s", typeid(ptr).name());
ptr->visit(visitor);
logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name());
ref.visit(visitor);
}
};
template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
{
public:
bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
{
logGlobal->error("Cannot apply plain CPack!");
assert(0);
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!");
assert(0);
}
};
static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CServerHandler::~CServerHandler()
{
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()
: 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()());
//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);
}
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;
state = EClientState::NONE;
setState(EClientState::NONE);
serverMode = newServerMode;
mapToStart = nullptr;
th = std::make_unique<CStopWatch>();
packsForLobbyScreen.clear();
c.reset();
logicConnection.reset();
si = std::make_shared<StartInfo>();
playerNames.clear();
si->difficulty = 1;
si->mode = mode;
screenType = screen;
myNames.clear();
if(names && !names->empty()) //if have custom set of player names - use it
myNames = *names;
if(!names.empty()) //if have custom set of player names - use it
myNames = names;
else
myNames.push_back(settings["general"]["playerName"].String());
}
void CServerHandler::startLocalServerAndConnect()
GlobalLobbyClient & CServerHandler::getGlobalLobby()
{
if(threadRunLocalServer)
threadRunLocalServer->join();
return *lobbyClient;
}
INetworkHandler & CServerHandler::getNetworkHandler()
{
return *networkHandler;
}
void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
{
if(threadRunLocalServer.joinable())
threadRunLocalServer.join();
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)
boost::condition_variable cond;
std::vector<std::string> args{"--uuid=" + uuid, "--port=" + std::to_string(getHostPort())};
if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool())
{
args.push_back("--lobby=" + settings["session"]["address"].String());
args.push_back("--connections=" + settings["session"]["hostConnections"].String());
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] {
std::vector<std::string> args{"--port=" + std::to_string(getLocalPort())};
if(connectToLobby)
args.push_back("--lobby");
threadRunLocalServer = boost::thread([&cond, args] {
setThreadName("CVCMIServer");
CVCMIServer::create(&cond, args);
onServerFinished();
});
threadRunLocalServer->detach();
#elif defined(VCMI_ANDROID)
{
CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);
}
#else
threadRunLocalServer = std::make_shared<boost::thread>(&CServerHandler::threadRunServer, this); //runs server executable;
threadRunLocalServer = boost::thread(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable;
#endif
logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff());
@@ -236,7 +247,7 @@ void CServerHandler::startLocalServerAndConnect()
while(!androidTestServerReadyFlag.load())
{
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...");
androidTestServerReadyFlag = false;
@@ -245,151 +256,165 @@ void CServerHandler::startLocalServerAndConnect()
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());
}
void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port)
void CServerHandler::connectToServer(const std::string & addr, const ui16 port)
{
state = EClientState::CONNECTING;
while(!c && state != EClientState::CONNECTION_CANCELLED)
{
try
{
logNetwork->info("Establishing connection...");
c = std::make_shared<CConnection>(
addr.size() ? addr : getHostAddress(),
port ? port : getHostPort(),
NAME, uuid);
logNetwork->info("Establishing connection to %s:%d...", addr, port);
setState(EClientState::CONNECTING);
serverHostname = addr;
serverPort = port;
nextClient = std::make_unique<CClient>();
c->iser.cb = nextClient.get();
}
catch(std::runtime_error & error)
{
logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", error.what());
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
}
if (!isServerLocal())
{
Settings remoteAddress = settings.write["server"]["remoteHostname"];
remoteAddress->String() = addr;
Settings remotePort = settings.write["server"]["remotePort"];
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!");
return;
}
c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
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;
}
assert(isServerLocal());
networkHandler->connectToRemote(*this, getLocalHostname(), getLocalPort());
}
void CServerHandler::applyPacksOnLobbyScreen()
void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection)
{
if(!c || !c->handler)
return;
assert(getState() == EClientState::CONNECTING);
boost::unique_lock<boost::recursive_mutex> lock(*mx);
while(!packsForLobbyScreen.empty())
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
networkConnection = netConnection;
logNetwork->info("Connection established");
if (serverMode == EServerMode::LOBBY_GUEST)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
CPackForLobby * pack = packsForLobbyScreen.front();
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;
// say hello to lobby to switch connection to proxy mode
getGlobalLobby().sendProxyConnectionLogin(netConnection);
}
logicConnection = std::make_shared<CConnection>(netConnection);
logicConnection->uuid = uuid;
logicConnection->enterLobbyConnectionMode();
sendClientConnecting();
}
void CServerHandler::stopServerConnection()
void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
{
if(c->handler)
{
while(!c->handler->timed_join(boost::chrono::milliseconds(50)))
applyPacksOnLobbyScreen();
c->handler->join();
}
const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier
apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
GH.windows().totalRedraw();
}
std::set<PlayerColor> CServerHandler::getHumanColors()
{
return clientHumanColors(c->connectionID);
return clientHumanColors(logicConnection->connectionID);
}
PlayerColor CServerHandler::myFirstColor() const
{
return clientFirstColor(c->connectionID);
return clientFirstColor(logicConnection->connectionID);
}
bool CServerHandler::isMyColor(PlayerColor color) const
{
return isClientColor(c->connectionID, color);
return isClientColor(logicConnection->connectionID, color);
}
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
{
if(threadRunLocalServer)
return true;
return false;
return threadRunLocalServer.joinable();
}
bool CServerHandler::isHost() const
{
return c && hostClientId == c->connectionID;
return logicConnection && hostClientId == logicConnection->connectionID;
}
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"]["server"].String();
if(settings["session"]["host"].Bool())
return localhostAddress;
return settings["session"]["address"].String();
return settings["server"]["remoteHostname"].String();
}
ui16 CServerHandler::getHostPort() const
ui16 CServerHandler::getRemotePort() const
{
if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool())
return getDefaultPort();
return settings["server"]["remotePort"].Integer();
}
if(settings["session"]["host"].Bool())
return getDefaultPort();
const std::string & CServerHandler::getCurrentHostname() const
{
return serverHostname;
}
return settings["session"]["port"].Integer();
ui16 CServerHandler::getCurrentPort() const
{
return serverPort;
}
void CServerHandler::sendClientConnecting() const
@@ -404,13 +429,16 @@ void CServerHandler::sendClientConnecting() const
void CServerHandler::sendClientDisconnecting()
{
// 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;
}
state = EClientState::DISCONNECTING;
setState(EClientState::DISCONNECTING);
mapToStart = nullptr;
LobbyClientDisconnected lcd;
lcd.clientId = c->connectionID;
lcd.clientId = logicConnection->connectionID;
logNetwork->info("Connection has been requested to be closed.");
if(isServerLocal())
{
@@ -422,18 +450,14 @@ void CServerHandler::sendClientDisconnecting()
logNetwork->info("Sent leaving signal to the server");
}
sendLobbyPack(lcd);
{
// Network thread might be applying network pack at this moment
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
c->close();
c.reset();
}
networkConnection->close();
networkConnection.reset();
logicConnection.reset();
}
void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
{
state = EClientState::LOBBY_CAMPAIGN;
setState(EClientState::LOBBY_CAMPAIGN);
LobbySetCampaign lsc;
lsc.ourCampaign = newCampaign;
sendLobbyPack(lsc);
@@ -441,7 +465,7 @@ void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign
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;
LobbySetCampaignMap lscm;
@@ -451,7 +475,7 @@ void CServerHandler::setCampaignMap(CampaignScenarioID mapId) 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;
LobbySetCampaignBonus lscb;
@@ -575,9 +599,7 @@ void CServerHandler::sendRestartGame() const
{
GH.windows().createAndPushWindow<CLoadingScreen>();
LobbyEndGame endGame;
endGame.closeConnection = false;
endGame.restart = true;
LobbyRestartGame endGame;
sendLobbyPack(endGame);
}
@@ -621,17 +643,18 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
if(!settings["session"]["headless"].Bool())
GH.windows().createAndPushWindow<CLoadingScreen>();
LobbyPrepareStartGame lpsg;
sendLobbyPack(lpsg);
LobbyStartGame lsg;
if(client)
{
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;
* si = * lsg.initializedStartInfo;
}
sendLobbyPack(lsg);
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
}
void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to)
@@ -644,83 +667,54 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
if(CMM)
CMM->disable();
std::swap(client, nextClient);
highScoreCalc = nullptr;
switch(si->mode)
{
case StartInfo::NEW_GAME:
case EStartMode::NEW_GAME:
client->newGame(gameState);
break;
case StartInfo::CAMPAIGN:
case EStartMode::CAMPAIGN:
client->newGame(gameState);
break;
case StartInfo::LOAD_GAME:
case EStartMode::LOAD_GAME:
client->loadGame(gameState);
break;
default:
throw std::runtime_error("Invalid mode");
}
// After everything initialized we can accept CPackToClient netpacks
c->enterGameplayConnectionMode(client->gameState());
state = 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);
}
}
logicConnection->enterGameplayConnectionMode(client->gameState());
setState(EClientState::GAMEPLAY);
}
void CServerHandler::endGameplay(bool closeConnection, bool restart)
void CServerHandler::endGameplay()
{
if(closeConnection)
{
// Game is ending
// Tell the network thread to reach a stable state
CSH->sendClientDisconnecting();
logNetwork->info("Closed connection.");
}
// Game is ending
// Tell the network thread to reach a stable state
CSH->sendClientDisconnecting();
logNetwork->info("Closed connection.");
client->endGame();
client.reset();
if(!restart)
if(CMM)
{
if(CMM)
{
GH.curInt = CMM.get();
CMM->enable();
}
else
{
GH.curInt = CMainMenu::create().get();
}
GH.curInt = CMM.get();
CMM->enable();
}
if(c)
else
{
nextClient = std::make_unique<CClient>();
c->iser.cb = nextClient.get();
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
GH.curInt = CMainMenu::create().get();
}
}
//reset settings
Settings saveSession = settings.write["server"]["reconnect"];
saveSession->Bool() = false;
void CServerHandler::restartGameplay()
{
client->endGame();
client.reset();
logicConnection->enterLobbyConnectionMode();
}
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]()
{
CSH->campaignServerRestartLock.set(true);
CSH->endGameplay();
auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
@@ -764,13 +757,14 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *highScoreCalc);
}
};
threadRunLocalServer.join();
if(epilogue.hasPrologEpilog)
{
GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
}
else
{
CSH->campaignServerRestartLock.waitUntil(false);
finisher();
}
});
@@ -796,15 +790,15 @@ int CServerHandler::howManyPlayerInterfaces()
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)
return ELoadMode::CAMPAIGN;
for(auto pn : playerNames)
{
if(pn.second.connection != c->connectionID)
if(pn.second.connection != logicConnection->connectionID)
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
@@ -815,48 +809,24 @@ ui8 CServerHandler::getLoadMode()
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)
{
logGlobal->info("Starting debug test with file: %s", filename);
auto mapInfo = std::make_shared<CMapInfo>();
if(save)
{
resetStateForLobby(StartInfo::LOAD_GAME);
resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOCAL, {});
mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME));
screenType = ESelectionScreen::loadGame;
}
else
{
resetStateForLobby(StartInfo::NEW_GAME);
resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOCAL, {});
mapInfo->mapInit(filename);
screenType = ESelectionScreen::newGame;
}
if(settings["session"]["donotstartserver"].Bool())
justConnectToServer(localhostAddress, 3030);
connectToServer(getLocalHostname(), getLocalPort());
else
startLocalServerAndConnect();
startLocalServerAndConnect(false);
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);
}
virtual void visitForClient(CPackForClient & clientPack) override
void visitForClient(CPackForClient & clientPack) override
{
handler.visitForClient(clientPack);
}
};
void CServerHandler::threadHandleConnection()
void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection> &, const std::vector<std::byte> & message)
{
setThreadName("handleConnection");
c->enterLobbyConnectionMode();
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
try
if(getState() == EClientState::DISCONNECTING)
{
sendClientConnecting();
while(c && c->connected)
{
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);
}
}
assert(0); //Should not be possible - socket must be closed at this point
return;
}
//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)
{
logNetwork->info("Successfully closed connection to server, ending listening thread!");
}
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);
}
}
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!");
return;
}
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)
{
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())
{
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(&lobbyPack);
}
applyPackOnLobbyScreen(lobbyPack);
}
}
@@ -992,22 +942,16 @@ void CServerHandler::visitForClient(CPackForClient & clientPack)
client->handlePack(&clientPack);
}
void CServerHandler::threadRunServer()
void CServerHandler::threadRunServer(bool connectToLobby)
{
#if !defined(VCMI_MOBILE)
setThreadName("runServer");
const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string();
std::string comm = VCMIDirs::get().serverPath().string()
+ " --port=" + std::to_string(getHostPort())
+ " --run-by-client"
+ " --uuid=" + uuid;
if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool())
{
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();
}
+ " --port=" + std::to_string(getLocalPort())
+ " --run-by-client";
if(connectToLobby)
comm += " --lobby";
comm += " > \"" + logName + '\"';
logGlobal->info("Server command line: %s", comm);
@@ -1035,22 +979,31 @@ void CServerHandler::threadRunServer()
}
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("Check %s for more info", logName);
}
onServerFinished();
#endif
}
void CServerHandler::onServerFinished()
{
threadRunLocalServer.reset();
if (CSH)
CSH->campaignServerRestartLock.setn(false);
}
void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
{
if(state != EClientState::STARTING)
c->sendPack(&pack);
if(getState() != EClientState::STARTING)
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/network/NetworkInterface.h"
#include "../lib/StartInfo.h"
#include "../lib/CondSh.h"
@@ -34,10 +35,14 @@ VCMI_LIB_NAMESPACE_END
class CClient;
class CBaseForLobbyApply;
class GlobalLobbyClient;
class HighScoreCalculation;
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
enum class EClientState : ui8
{
@@ -49,7 +54,14 @@ enum class EClientState : ui8
STARTING, // Gameplay interfaces being created, we pause netpacks retrieving
GAMEPLAY, // In-game, used by some UI
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
@@ -80,63 +92,68 @@ public:
};
/// 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;
std::shared_ptr<CApplier<CBaseForLobbyApply>> applier;
std::shared_ptr<boost::recursive_mutex> mx;
std::list<CPackForLobby *> packsForLobbyScreen; //protected by mx
std::unique_ptr<INetworkHandler> networkHandler;
std::shared_ptr<INetworkConnection> networkConnection;
std::unique_ptr<GlobalLobbyClient> lobbyClient;
std::unique_ptr<CApplier<CBaseForLobbyApply>> applier;
std::shared_ptr<CMapInfo> mapToStart;
std::vector<std::string> myNames;
std::shared_ptr<HighScoreCalculation> highScoreCalc;
/// temporary helper member that exists while game in lobby mode
/// required to correctly deserialize gamestate using client-side game callback
std::unique_ptr<CClient> nextClient;
boost::thread threadRunLocalServer;
boost::thread threadNetwork;
std::atomic<EClientState> state;
void threadRunNetwork();
void threadRunServer(bool connectToLobby);
void threadHandleConnection();
void threadRunServer();
void onServerFinished();
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:
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
// For starting non-custom campaign and continue to next mission
std::shared_ptr<CampaignState> campaignStateToSend;
ui8 screenType; // To create lobby UI only after server is setup
ui8 loadMode; // For saves filtering in SelectionTab
ESelectionScreen screenType; // To create lobby UI only after server is setup
EServerMode serverMode;
ELoadMode loadMode; // For saves filtering in SelectionTab
////////////////////
std::unique_ptr<CStopWatch> th;
std::shared_ptr<boost::thread> threadRunLocalServer;
std::shared_ptr<CConnection> c;
std::unique_ptr<CClient> client;
CondSh<bool> campaignServerRestartLock;
static const std::string localhostAddress;
CServerHandler();
~CServerHandler();
std::string getHostAddress() const;
ui16 getHostPort() const;
void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector<std::string> & names);
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);
void startLocalServerAndConnect();
void justConnectToServer(const std::string & addr, const ui16 port);
void applyPacksOnLobbyScreen();
void stopServerConnection();
GlobalLobbyClient & getGlobalLobby();
INetworkHandler & getNetworkHandler();
// Helpers for lobby state access
std::set<PlayerColor> getHumanColors();
@@ -144,12 +161,21 @@ public:
bool isMyColor(PlayerColor color) const;
ui8 myFirstId() const; // Used by chat only!
bool isServerLocal() const;
EClientState getState() const;
void setState(EClientState newState);
bool isHost() const;
bool isGuest() const;
bool inLobbyRoom() const;
bool inGame() const;
static ui16 getDefaultPort();
static std::string getDefaultPortStr();
const std::string & getCurrentHostname() const;
const std::string & getLocalHostname() const;
const std::string & getRemoteHostname() const;
ui16 getCurrentPort() const;
ui16 getLocalPort() const;
ui16 getRemotePort() const;
// Lobby server API for UI
void sendClientConnecting() const override;
@@ -175,15 +201,14 @@ public:
void debugStartTest(std::string filename, bool save = false);
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 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
int howManyPlayerInterfaces();
ui8 getLoadMode();
void restoreLastSession();
ELoadMode getLoadMode();
void visitForLobby(CPackForLobby & lobbyPack);
void visitForClient(CPackForClient & clientPack);

View File

@@ -30,11 +30,12 @@
#include "../lib/UnlockGuard.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/mapping/CMapService.h"
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesClientPacks.h"
#include "../lib/serializer/Connection.h"
#include <memory>
#include <vcmi/events/EventBus.h>
@@ -297,7 +298,7 @@ void CClient::serialize(BinaryDeserializer & h)
bool shouldResetInterface = true;
// 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);
}
@@ -397,7 +398,7 @@ void CClient::initPlayerEnvironments()
{
playerEnvironments.clear();
auto allPlayers = CSH->getAllClientPlayers(CSH->c->connectionID);
auto allPlayers = CSH->getAllClientPlayers(CSH->logicConnection->connectionID);
bool hasHumanPlayer = false;
for(auto & color : allPlayers)
{
@@ -427,7 +428,7 @@ void CClient::initPlayerInterfaces()
for(auto & playerInfo : gs->scenarioOps->playerInfos)
{
PlayerColor color = playerInfo.first;
if(!vstd::contains(CSH->getAllClientPlayers(CSH->c->connectionID), color))
if(!vstd::contains(CSH->getAllClientPlayers(CSH->logicConnection->connectionID), color))
continue;
if(!vstd::contains(playerint, color))
@@ -457,7 +458,7 @@ void CClient::initPlayerInterfaces()
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);
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
if(apply)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
apply->applyOnClBefore(this, pack);
logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name());
gs->apply(pack);
@@ -545,7 +545,7 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
waitingRequest.pushBack(requestID);
request->requestID = requestID;
request->player = player;
CSH->c->sendPack(request);
CSH->logicConnection->sendPack(request);
if(vstd::contains(playerint, player))
playerint[player]->requestSent(request, requestID);

View File

@@ -173,7 +173,7 @@ public:
void showTeleportDialog(TeleportDialog * iw) override {};
void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) 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 takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures) override {};

View File

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

View File

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

View File

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

View File

@@ -19,9 +19,13 @@
#include "lobby/ExtraOptionsTab.h"
#include "lobby/SelectionTab.h"
#include "lobby/CBonusSelection.h"
#include "globalLobby/GlobalLobbyWindow.h"
#include "globalLobby/GlobalLobbyServerSetup.h"
#include "globalLobby/GlobalLobbyClient.h"
#include "CServerHandler.h"
#include "CGameInfo.h"
#include "Client.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "widgets/Buttons.h"
@@ -36,26 +40,50 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
result = false;
// 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)
{
handler.setMapInfo(handler.mapToStart);
}
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)
{
if(pack.clientId != handler.c->connectionID)
if(pack.clientId != handler.logicConnection->connectionID)
{
result = false;
return;
}
handler.stopServerConnection();
}
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)
{
handler.endGameplay(pack.closeConnection, pack.restart);
}
assert(handler.getState() == EClientState::GAMEPLAY);
if(pack.restart)
{
if (handler.validateGameStart())
handler.sendStartGame();
}
handler.restartGameplay();
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)
{
if(pack.clientId != -1 && pack.clientId != handler.c->connectionID)
if(pack.clientId != -1 && pack.clientId != handler.logicConnection->connectionID)
{
result = false;
return;
}
handler.state = EClientState::STARTING;
if(handler.si->mode != StartInfo::LOAD_GAME || pack.clientId == handler.c->connectionID)
handler.setState(EClientState::STARTING);
if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.logicConnection->connectionID)
{
auto modeBackup = handler.si->mode;
handler.si = pack.initializedStartInfo;
@@ -174,7 +203,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
if(!lobby) //stub: ignore message for game mode
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>();
GH.windows().pushWindow(lobby->bonusSel);

View File

@@ -852,12 +852,19 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
owner(owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
uint32_t queueSize = QUEUE_SIZE_BIG;
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.x += parent->pos.w/2 - pos.w/2;
pos.y += 10;
pos.y += queueSmallOutside ? -queueSmallOutsideYOffset : 10;
icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"));
stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
@@ -878,7 +885,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
}
stateIcons->preload();
stackBoxes.resize(QUEUE_SIZE);
stackBoxes.resize(queueSize);
for (int i = 0; i < stackBoxes.size(); i++)
{
stackBoxes[i] = std::make_shared<StackBox>(this);

View File

@@ -239,7 +239,7 @@ class StackQueue : public CIntObject
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::vector<std::shared_ptr<StackBox>> stackBoxes;
BattleInterface & owner;

View File

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

View File

@@ -16,6 +16,8 @@
#include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h"
#include "../gui/ShortcutHandler.h"
#include "../CServerHandler.h"
#include "../globalLobby/GlobalLobbyClient.h"
#include <SDL_clipboard.h>
#include <SDL_events.h>
@@ -31,6 +33,8 @@ InputSourceKeyboard::InputSourceKeyboard()
void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
{
assert(key.state == SDL_PRESSED);
if (SDL_IsTextInputActive() == SDL_TRUE)
{
if(key.keysym.sym == SDLK_v && isKeyboardCtrlDown())
@@ -51,7 +55,11 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
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())
{

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