浏览代码

Entities redesign and a few ERM features

* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
AlexVinS 7 年之前
父节点
当前提交
ecaa9f5d0b
共有 100 个文件被更改,包括 1998 次插入736 次删除
  1. 1 1
      .travis.yml
  2. 1 4
      AI/BattleAI/BattleAI.cbp
  3. 50 58
      AI/BattleAI/BattleAI.cpp
  4. 3 2
      AI/BattleAI/BattleAI.h
  5. 2 1
      AI/BattleAI/PossibleSpellcast.h
  6. 127 4
      AI/BattleAI/StackWithBonuses.cpp
  7. 83 3
      AI/BattleAI/StackWithBonuses.h
  8. 11 1
      AI/EmptyAI/CEmptyAI.cpp
  9. 4 1
      AI/EmptyAI/CEmptyAI.h
  10. 1 4
      AI/EmptyAI/EmptyAI.cbp
  11. 6 8
      AI/StupidAI/StupidAI.cbp
  12. 8 17
      AI/StupidAI/StupidAI.cpp
  13. 3 5
      AI/StupidAI/StupidAI.h
  14. 1 1
      AI/VCAI/AIUtility.cpp
  15. 2 0
      AI/VCAI/FuzzyHelper.cpp
  16. 1 1
      AI/VCAI/Goals/AbstractGoal.cpp
  17. 7 7
      AI/VCAI/Goals/AdventureSpellCast.cpp
  18. 1 1
      AI/VCAI/Goals/GatherArmy.cpp
  19. 3 3
      AI/VCAI/Goals/GatherTroops.cpp
  20. 3 3
      AI/VCAI/MapObjectsEvaluator.cpp
  21. 1 1
      AI/VCAI/Pathfinding/AIPathfinder.cpp
  22. 1 4
      AI/VCAI/VCAI.cbp
  23. 5 4
      AI/VCAI/VCAI.cpp
  24. 1 1
      AI/VCAI/VCAI.h
  25. 4 10
      CCallback.cpp
  26. 3 3
      CCallback.h
  27. 1 0
      CI/linux/before_install.sh
  28. 1 1
      CI/mac/before_install.sh
  29. 6 5
      CI/mxe/before_install.sh
  30. 21 2
      CMakeLists.txt
  31. 0 18
      Global.h
  32. 0 40
      Mods/vcmi/Data/s/std.verm
  33. 0 14
      Mods/vcmi/Data/s/testy.erm
  34. 57 3
      client/CGameInfo.cpp
  35. 20 5
      client/CGameInfo.h
  36. 29 25
      client/CMT.cpp
  37. 0 2
      client/CMusicHandler.cpp
  38. 0 1
      client/CMusicHandler.h
  39. 35 33
      client/CPlayerInterface.cpp
  40. 9 4
      client/CPlayerInterface.h
  41. 2 0
      client/CServerHandler.cpp
  42. 157 46
      client/Client.cpp
  43. 63 23
      client/Client.h
  44. 28 62
      client/Graphics.cpp
  45. 4 1
      client/Graphics.h
  46. 19 27
      client/NetPacksClient.cpp
  47. 3 6
      client/VCMI_client.cbp
  48. 4 5
      client/battle/CBattleAnimations.cpp
  49. 75 91
      client/battle/CBattleInterface.cpp
  50. 6 7
      client/battle/CBattleInterface.h
  51. 3 3
      client/battle/CBattleInterfaceClasses.cpp
  52. 5 5
      client/gui/CGuiHandler.cpp
  53. 11 9
      client/lobby/CBonusSelection.cpp
  54. 21 22
      client/lobby/OptionsTab.cpp
  55. 20 36
      client/widgets/CArtifactHolder.cpp
  56. 12 12
      client/widgets/CComponent.cpp
  57. 15 15
      client/widgets/CGarrisonInt.cpp
  58. 1 1
      client/widgets/CGarrisonInt.h
  59. 4 4
      client/widgets/MiscWidgets.cpp
  60. 25 23
      client/windows/CCastleInterface.cpp
  61. 8 6
      client/windows/CCreatureWindow.cpp
  62. 0 1
      client/windows/CHeroWindow.h
  63. 3 3
      client/windows/CKingdomInterface.cpp
  64. 8 8
      client/windows/CSpellWindow.cpp
  65. 13 13
      client/windows/CTradeWindow.cpp
  66. 5 5
      client/windows/GUIClasses.cpp
  67. 50 0
      cmake_modules/FindLuaJIT.cmake
  68. 4 0
      config/filesystem.json
  69. 6 1
      config/schemas/mod.json
  70. 22 0
      config/schemas/script.json
  71. 27 0
      include/vcmi/Artifact.h
  72. 21 0
      include/vcmi/ArtifactService.h
  73. 45 0
      include/vcmi/Creature.h
  74. 21 0
      include/vcmi/CreatureService.h
  75. 42 0
      include/vcmi/Entity.h
  76. 32 0
      include/vcmi/EntityService.h
  77. 36 0
      include/vcmi/Environment.h
  78. 21 0
      include/vcmi/Faction.h
  79. 21 0
      include/vcmi/FactionService.h
  80. 22 0
      include/vcmi/HeroClass.h
  81. 21 0
      include/vcmi/HeroClassService.h
  82. 22 0
      include/vcmi/HeroType.h
  83. 21 0
      include/vcmi/HeroTypeService.h
  84. 30 0
      include/vcmi/Metatype.h
  85. 30 0
      include/vcmi/Player.h
  86. 44 0
      include/vcmi/ServerCallback.h
  87. 57 0
      include/vcmi/Services.h
  88. 22 0
      include/vcmi/Skill.h
  89. 21 0
      include/vcmi/SkillService.h
  90. 15 0
      include/vcmi/Team.h
  91. 13 0
      include/vcmi/events/AdventureEvents.h
  92. 45 0
      include/vcmi/events/ApplyDamage.h
  93. 13 0
      include/vcmi/events/BattleEvents.h
  94. 29 0
      include/vcmi/events/Event.h
  95. 44 0
      include/vcmi/events/EventBus.h
  96. 35 0
      include/vcmi/events/GameResumed.h
  97. 15 0
      include/vcmi/events/GenericEvents.h
  98. 40 0
      include/vcmi/events/ObjectVisitEnded.h
  99. 43 0
      include/vcmi/events/ObjectVisitStarted.h
  100. 41 0
      include/vcmi/events/PlayerGotTurn.h

+ 1 - 1
.travis.yml

@@ -29,7 +29,7 @@ matrix:
     env: VCMI_PLATFORM='mxe' MXE_TARGET=i686-w64-mingw32.shared VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
     sudo: required
   - os: osx
-    env: VCMI_PLATFORM='mac'
+    env: VCMI_PLATFORM='mac' VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
 
 addons:
   apt:

+ 1 - 4
AI/BattleAI/BattleAI.cbp

@@ -69,7 +69,7 @@
 			<Add option="-Wno-overloaded-virtual" />
 			<Add option="-DBOOST_ALL_DYN_LINK" />
 			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
-			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32_WINNT=0x0600" />
 			<Add option="-D_WIN32" />
 			<Add directory="$(#boost.include)" />
 			<Add directory="../../include" />
@@ -100,9 +100,6 @@
 		<Unit filename="common.h" />
 		<Unit filename="main.cpp" />
 		<Extensions>
-			<code_completion />
-			<envvars />
-			<debugger />
 			<lib_finder disable_auto="1" />
 		</Extensions>
 	</Project>

+ 50 - 58
AI/BattleAI/BattleAI.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "BattleAI.h"
 
-#include <vstd/RNG.h>
-
 #include "StackWithBonuses.h"
 #include "EnemyInfo.h"
 #include "../../lib/CStopWatch.h"
@@ -26,26 +24,6 @@
 #define LOGL(text) print(text)
 #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
 
-class RNGStub : public vstd::RNG
-{
-public:
-	vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override
-	{
-		return [=]()->int64_t
-		{
-			return (lower + upper)/2;
-		};
-	}
-
-	vstd::TRand getDoubleRange(double lower, double upper) override
-	{
-		return [=]()->double
-		{
-			return (lower + upper)/2;
-		};
-	}
-};
-
 enum class SpellTypes
 {
 	ADVENTURE, BATTLE, OTHER
@@ -53,10 +31,10 @@ enum class SpellTypes
 
 SpellTypes spellType(const CSpell * spell)
 {
-	if(!spell->isCombatSpell() || spell->isCreatureAbility())
+	if(!spell->isCombat() || spell->isCreatureAbility())
 		return SpellTypes::OTHER;
 
-	if(spell->isOffensiveSpell() || spell->hasEffects() || spell->hasBattleEffects())
+	if(spell->isOffensive() || spell->hasEffects() || spell->hasBattleEffects())
 		return SpellTypes::BATTLE;
 
 	return SpellTypes::OTHER;
@@ -83,7 +61,9 @@ std::vector<BattleHex> CBattleAI::getBrokenWallMoatHexes() const
 }
 
 CBattleAI::CBattleAI()
-	: side(-1), wasWaitingForRealize(false), wasUnlockingGs(false)
+	: side(-1),
+	wasWaitingForRealize(false),
+	wasUnlockingGs(false)
 {
 }
 
@@ -97,12 +77,13 @@ CBattleAI::~CBattleAI()
 	}
 }
 
-void CBattleAI::init(std::shared_ptr<CBattleCallback> CB)
+void CBattleAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
 {
 	setCbc(CB);
+	env = ENV;
 	cb = CB;
 	playerID = *CB->getPlayerID(); //TODO should be sth in callback
-	wasWaitingForRealize = cb->waitTillRealize;
+	wasWaitingForRealize = CB->waitTillRealize;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	CB->waitTillRealize = true;
 	CB->unlockGsWhenWaiting = false;
@@ -131,7 +112,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 
 		attemptCastingSpell();
 
-		if(auto ret = getCbc()->battleIsFinished())
+		if(auto ret = cb->battleIsFinished())
 		{
 			//spellcast may finish battle
 			//send special preudo-action
@@ -144,7 +125,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			return *action;
 		//best action is from effective owner point if view, we are effective owner as we received "activeStack"
 
-	
+
 		//evaluate casting spell for spellcasting stack
 		boost::optional<PossibleSpellcast> bestSpellcast(boost::none);
 		//TODO: faerie dragon type spell should be selected by server
@@ -174,7 +155,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			}
 		}
 
-		HypotheticBattle hb(getCbc());
+		HypotheticBattle hb(env.get(), cb);
 
 		PotentialTargets targets(stack, &hb);
 
@@ -199,7 +180,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 				);
 
 				return BattleAction::makeMeleeAttack(stack,	bestAttack.attack.defender->getPosition(), bestAttack.from);
-			}
+		}
 		}
 		else if(bestSpellcast.is_initialized())
 		{
@@ -210,7 +191,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			if(stack->waited())
 			{
 				//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
-				auto dists = getCbc()->getReachability(stack);
+				auto dists = cb->getReachability(stack);
 				if(!targets.unreachableEnemies.empty())
 				{
 					auto closestEnemy = vstd::minElementByFun(targets.unreachableEnemies, [&](const battle::Unit * enemy) -> int
@@ -242,7 +223,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 					return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
 				else
 					return goTowardsNearest(stack, brokenWallMoat);
-			}
+	}
 		}
 	}
 	catch(boost::thread_interrupted &)
@@ -279,10 +260,10 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<Battl
 
 		if(stack->coversPos(hex))
 		{
-			logAi->warn("Warning: already standing on neighbouring tile!");
-			//We shouldn't even be here...
-			return BattleAction::makeDefend(stack);
-		}
+		logAi->warn("Warning: already standing on neighbouring tile!");
+		//We shouldn't even be here...
+		return BattleAction::makeDefend(stack);
+	}
 	}
 
 	BattleHex bestNeighbor = hexes.front();
@@ -330,7 +311,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 	if(cb->battleGetGateState() == EGateState::CLOSED)
 	{
 		targetHex = cb->wallPartToBattleHex(EWallPart::GATE);
-	}
+}
 	else
 	{
 		EWallPart::EWallPart wallParts[] = {
@@ -381,9 +362,9 @@ void CBattleAI::attemptCastingSpell()
 	LOGL("Casting spells sounds like fun. Let's see...");
 	//Get all spells we can cast
 	std::vector<const CSpell*> possibleSpells;
-	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero](const CSpell *s) -> bool
+	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
 	{
-		return s->canBeCast(getCbc().get(), spells::Mode::HERO, hero);
+		return s->canBeCast(cb.get(), spells::Mode::HERO, hero);
 	});
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 
@@ -398,7 +379,7 @@ void CBattleAI::attemptCastingSpell()
 	std::vector<PossibleSpellcast> possibleCasts;
 	for(auto spell : possibleSpells)
 	{
-		spells::BattleCast temp(getCbc().get(), hero, spells::Mode::HERO, spell);
+		spells::BattleCast temp(cb.get(), hero, spells::Mode::HERO, spell);
 
 		for(auto & target : temp.findPotentialTargets())
 		{
@@ -500,8 +481,6 @@ void CBattleAI::attemptCastingSpell()
 		return ourTurnSpan >= minTurnSpan;
 	};
 
-	RNGStub rngStub;
-
 	ValueMap valueOfStack;
 	ValueMap healthOfStack;
 
@@ -536,7 +515,8 @@ void CBattleAI::attemptCastingSpell()
 	{
 		bool enemyHadTurn = false;
 
-		HypotheticBattle state(cb);
+		HypotheticBattle state(env.get(), cb);
+
 		evaluateQueue(valueOfStack, turnOrder, &state, 0, &enemyHadTurn);
 
 		if(!enemyHadTurn)
@@ -551,13 +531,17 @@ void CBattleAI::attemptCastingSpell()
 		}
 	}
 
-	auto evaluateSpellcast = [&] (PossibleSpellcast * ps)
+	struct ScriptsCache
+	{
+		//todo: re-implement scripts context cache
+	};
+
+	auto evaluateSpellcast = [&] (PossibleSpellcast * ps, std::shared_ptr<ScriptsCache>)
 	{
-		HypotheticBattle state(cb);
+		HypotheticBattle state(env.get(), cb);
 
 		spells::BattleCast cast(&state, hero, spells::Mode::HERO, ps->spell);
-		cast.target = ps->dest;
-		cast.cast(&state, rngStub);
+		cast.castEval(state.getServerCallback(), ps->dest);
 		ValueMap newHealthOfStack;
 		ValueMap newValueOfStack;
 
@@ -617,10 +601,12 @@ void CBattleAI::attemptCastingSpell()
 		}
 	};
 
-	std::vector<std::function<void()>> tasks;
+	using EvalRunner = ThreadPool<ScriptsCache>;
+
+	EvalRunner::Tasks tasks;
 
 	for(PossibleSpellcast & psc : possibleCasts)
-		tasks.push_back(std::bind(evaluateSpellcast, &psc));
+		tasks.push_back(std::bind(evaluateSpellcast, &psc, _1));
 
 	uint32_t threadCount = boost::thread::hardware_concurrency();
 
@@ -632,8 +618,15 @@ void CBattleAI::attemptCastingSpell()
 
 	CStopWatch timer;
 
-	CThreadHelper threadHelper(&tasks, threadCount);
-	threadHelper.run();
+	std::vector<std::shared_ptr<ScriptsCache>> scriptsPool;
+
+	for(uint32_t idx = 0; idx < threadCount; idx++)
+	{
+		scriptsPool.emplace_back();
+	}
+
+	EvalRunner runner(&tasks, scriptsPool);
+	runner.run();
 
 	LOGFL("Evaluation took %d ms", timer.getDiff());
 
@@ -666,9 +659,9 @@ void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcas
 	using ValueMap = PossibleSpellcast::ValueMap;
 
 	RNGStub rngStub;
-	HypotheticBattle state(getCbc());
-	TStacks all = getCbc()->battleGetAllStacks(false);
-	
+	HypotheticBattle state(env.get(), cb);
+	TStacks all = cb->battleGetAllStacks(false);
+
 	ValueMap healthOfStack;
 	ValueMap newHealthOfStack;
 
@@ -678,8 +671,7 @@ void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcas
 	}
 
 	spells::BattleCast cast(&state, stack, spells::Mode::CREATURE_ACTIVE, ps.spell);
-	cast.target = ps.dest;
-	cast.cast(&state, rngStub);
+	cast.castEval(state.getServerCallback(), ps.dest);
 
 	for(auto unit : all)
 	{
@@ -710,7 +702,7 @@ void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcas
 	}
 
 	ps.value = totalGain;
-};
+}
 
 void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
 {

+ 3 - 2
AI/BattleAI/BattleAI.h

@@ -51,6 +51,7 @@ class CBattleAI : public CBattleGameInterface
 {
 	int side;
 	std::shared_ptr<CBattleCallback> cb;
+	std::shared_ptr<Environment> env;
 
 	//Previous setting of cb
 	bool wasWaitingForRealize, wasUnlockingGs;
@@ -59,7 +60,7 @@ public:
 	CBattleAI();
 	~CBattleAI();
 
-	void init(std::shared_ptr<CBattleCallback> CB) override;
+	void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
 	void attemptCastingSpell();
 
 	void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
@@ -74,7 +75,7 @@ public:
 	//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
 	//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
-	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
+	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
 	//void battleEnd(const BattleResult *br) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;

+ 2 - 1
AI/BattleAI/PossibleSpellcast.h

@@ -10,8 +10,9 @@
 
 #pragma once
 
+#include <vcmi/spells/Magic.h>
+
 #include "../../lib/battle/Destination.h"
-#include "../../lib/spells/Magic.h"
 
 class CSpell;
 

+ 127 - 4
AI/BattleAI/StackWithBonuses.cpp

@@ -9,8 +9,14 @@
  */
 #include "StdInc.h"
 #include "StackWithBonuses.h"
-#include "../../lib/NetPacksBase.h"
+
+#include <vcmi/events/EventBus.h>
+
+#include "../../lib/NetPacks.h"
 #include "../../lib/CStack.h"
+#include "../../lib/ScriptHandler.h"
+
+using scripting::Pool;
 
 void actualizeEffect(TBonusListPtr target, const Bonus & ef)
 {
@@ -191,19 +197,27 @@ void StackWithBonuses::removeUnitBonus(const CSelector & selector)
 	vstd::erase_if(bonusesToUpdate, [&](const Bonus & b){return selector(&b);});
 }
 
-void StackWithBonuses::spendMana(const spells::PacketSender * server, const int spellCost) const
+void StackWithBonuses::spendMana(ServerCallback * server, const int spellCost) const
 {
 	//TODO: evaluate cast use
 }
 
-HypotheticBattle::HypotheticBattle(Subject realBattle)
+HypotheticBattle::HypotheticBattle(const Environment * ENV, Subject realBattle)
 	: BattleProxy(realBattle),
+	env(ENV),
 	bonusTreeVersion(1)
 {
 	auto activeUnit = realBattle->battleActiveUnit();
 	activeUnitId = activeUnit ? activeUnit->unitId() : -1;
 
-	nextId = 0xF0000000;
+	nextId = 0x00F00000;
+
+	eventBus.reset(new events::EventBus());
+
+	localEnvironment.reset(new HypotheticEnvironment(this, env));
+	serverCallback.reset(new HypotheticServerCallback(this));
+
+	pool.reset(new scripting::PoolImpl(localEnvironment.get(), serverCallback.get()));
 }
 
 bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const
@@ -348,6 +362,11 @@ void HypotheticBattle::removeUnit(uint32_t id)
 	}
 }
 
+void HypotheticBattle::updateUnit(uint32_t id, const JsonNode & data)
+{
+	//TODO:
+}
+
 void HypotheticBattle::addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
 {
 	getForUpdate(id)->addUnitBonus(bonus);
@@ -400,3 +419,107 @@ int64_t HypotheticBattle::getTreeVersion() const
 {
 	return getBattleNode()->getTreeVersion() + bonusTreeVersion;
 }
+
+Pool * HypotheticBattle::getContextPool() const
+{
+	return pool.get();
+}
+
+ServerCallback * HypotheticBattle::getServerCallback()
+{
+	return serverCallback.get();
+}
+
+HypotheticBattle::HypotheticServerCallback::HypotheticServerCallback(HypotheticBattle * owner_)
+	:owner(owner_)
+{
+
+}
+
+void HypotheticBattle::HypotheticServerCallback::complain(const std::string & problem)
+{
+	logAi->error(problem);
+}
+
+bool HypotheticBattle::HypotheticServerCallback::describeChanges() const
+{
+	return false;
+}
+
+vstd::RNG * HypotheticBattle::HypotheticServerCallback::getRNG()
+{
+	return &rngStub;
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient * pack)
+{
+	logAi->error("Package of type %s is not allowed in battle evaluation", typeid(pack).name());
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage * pack)
+{
+	pack->applyBattle(owner);
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved * pack)
+{
+	pack->applyBattle(owner);
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged * pack)
+{
+	pack->applyBattle(owner);
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect * pack)
+{
+	pack->applyBattle(owner);
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured * pack)
+{
+	pack->applyBattle(owner);
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged * pack)
+{
+	pack->applyBattle(owner);
+}
+
+void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack * pack)
+{
+	pack->applyBattle(owner);
+}
+
+HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)
+	: owner(owner_),
+	env(upperEnvironment)
+{
+
+}
+
+const Services * HypotheticBattle::HypotheticEnvironment::services() const
+{
+	return env->services();
+}
+
+const Environment::BattleCb * HypotheticBattle::HypotheticEnvironment::battle() const
+{
+	return owner;
+}
+
+const Environment::GameCb * HypotheticBattle::HypotheticEnvironment::game() const
+{
+	return env->game();
+}
+
+vstd::CLoggerBase * HypotheticBattle::HypotheticEnvironment::logger() const
+{
+	return env->logger();
+}
+
+events::EventBus * HypotheticBattle::HypotheticEnvironment::eventBus() const
+{
+	return owner->eventBus.get();
+}
+

+ 83 - 3
AI/BattleAI/StackWithBonuses.h

@@ -8,6 +8,12 @@
  *
  */
 #pragma once
+
+#include <vstd/RNG.h>
+
+#include <vcmi/Environment.h>
+#include <vcmi/ServerCallback.h>
+
 #include "../../lib/HeroBonus.h"
 #include "../../lib/battle/BattleProxy.h"
 #include "../../lib/battle/CUnitState.h"
@@ -15,10 +21,30 @@
 class HypotheticBattle;
 class CStack;
 
-class StackWithBonuses : public battle::CUnitState, public virtual IBonusBearer
+///Fake random generator, used by AI to evaluate random server behavior
+class RNGStub : public vstd::RNG
 {
 public:
+	vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override
+	{
+		return [=]()->int64_t
+		{
+			return (lower + upper)/2;
+		};
+	}
+
+	vstd::TRand getDoubleRange(double lower, double upper) override
+	{
+		return [=]()->double
+		{
+			return (lower + upper)/2;
+		};
+	}
+};
 
+class StackWithBonuses : public battle::CUnitState, public virtual IBonusBearer
+{
+public:
 	std::vector<Bonus> bonusesToAdd;
 	std::vector<Bonus> bonusesToUpdate;
 	std::set<std::shared_ptr<Bonus>> bonusesToRemove;
@@ -53,7 +79,7 @@ public:
 
 	void removeUnitBonus(const CSelector & selector);
 
-	void spendMana(const spells::PacketSender * server, const int spellCost) const override;
+	void spendMana(ServerCallback * server, const int spellCost) const override;
 
 private:
 	const IBonusBearer * origBearer;
@@ -72,7 +98,9 @@ class HypotheticBattle : public BattleProxy, public battle::IUnitEnvironment
 public:
 	std::map<uint32_t, std::shared_ptr<StackWithBonuses>> stackStates;
 
-	HypotheticBattle(Subject realBattle);
+	const Environment * env;
+
+	HypotheticBattle(const Environment * ENV, Subject realBattle);
 
 	bool unitHasAmmoCart(const battle::Unit * unit) const override;
 	PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override;
@@ -90,6 +118,7 @@ public:
 	void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override;
 	void moveUnit(uint32_t id, BattleHex destination) override;
 	void removeUnit(uint32_t id) override;
+	void updateUnit(uint32_t id, const JsonNode & data) override;
 
 	void addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
 	void updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
@@ -107,8 +136,59 @@ public:
 
 	int64_t getTreeVersion() const;
 
+	scripting::Pool * getContextPool() const override;
+
+	ServerCallback * getServerCallback();
+
 private:
+
+	class HypotheticServerCallback : public ServerCallback
+	{
+	public:
+		HypotheticServerCallback(HypotheticBattle * owner_);
+
+		void complain(const std::string & problem) override;
+		bool describeChanges() const override;
+
+		vstd::RNG * getRNG() override;
+
+		void apply(CPackForClient * pack) override;
+
+		void apply(BattleLogMessage * pack) override;
+		void apply(BattleStackMoved * pack) override;
+		void apply(BattleUnitsChanged * pack) override;
+		void apply(SetStackEffect * pack) override;
+		void apply(StacksInjured * pack) override;
+		void apply(BattleObstaclesChanged * pack) override;
+		void apply(CatapultAttack * pack) override;
+	private:
+		HypotheticBattle * owner;
+		RNGStub rngStub;
+	};
+
+	class HypotheticEnvironment : public Environment
+	{
+	public:
+		HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment);
+
+		const Services * services() const override;
+		const BattleCb * battle() const override;
+		const GameCb * game() const override;
+		vstd::CLoggerBase * logger() const override;
+		events::EventBus * eventBus() const override;
+
+	private:
+		HypotheticBattle * owner;
+		const Environment * env;
+	};
+
 	int32_t bonusTreeVersion;
 	int32_t activeUnitId;
 	mutable uint32_t nextId;
+
+	std::unique_ptr<HypotheticServerCallback> serverCallback;
+	std::unique_ptr<HypotheticEnvironment> localEnvironment;
+
+	mutable std::shared_ptr<scripting::Pool> pool;
+	mutable std::shared_ptr<events::EventBus> eventBus;
 };

+ 11 - 1
AI/EmptyAI/CEmptyAI.cpp

@@ -12,12 +12,22 @@
 
 #include "../../lib/CRandomGenerator.h"
 
-void CEmptyAI::init(std::shared_ptr<CCallback> CB)
+void CEmptyAI::saveGame(BinarySerializer & h, const int version)
+{
+}
+
+void CEmptyAI::loadGame(BinaryDeserializer & h, const int version)
+{
+}
+
+void CEmptyAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
 {
 	cb = CB;
+	env = ENV;
 	human=false;
 	playerID = *cb->getMyColor();
 }
+
 void CEmptyAI::yourTurn()
 {
 	cb->endTurn();

+ 4 - 1
AI/EmptyAI/CEmptyAI.h

@@ -19,7 +19,10 @@ class CEmptyAI : public CGlobalAI
 	std::shared_ptr<CCallback> cb;
 
 public:
-	void init(std::shared_ptr<CCallback> CB) override;
+	virtual void saveGame(BinarySerializer & h, const int version) override;
+	virtual void loadGame(BinaryDeserializer & h, const int version) override;
+
+	void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void yourTurn() override;
 	void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
 	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;

+ 1 - 4
AI/EmptyAI/EmptyAI.cbp

@@ -63,7 +63,7 @@
 			<Add option="-Wno-unused-parameter" />
 			<Add option="-Wno-overloaded-virtual" />
 			<Add option="-fpermissive" />
-			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32_WINNT=0x0600" />
 			<Add option="-D_WIN32" />
 			<Add option="-DBOOST_ALL_DYN_LINK" />
 			<Add directory="$(#boost.include)" />
@@ -80,9 +80,6 @@
 		</Unit>
 		<Unit filename="exp_funcs.cpp" />
 		<Extensions>
-			<code_completion />
-			<envvars />
-			<debugger />
 			<lib_finder disable_auto="1" />
 		</Extensions>
 	</Project>

+ 6 - 8
AI/StupidAI/StupidAI.cbp

@@ -18,6 +18,7 @@
 				<Linker>
 					<Add option="-lboost_system$(#boost.libsuffix32)" />
 					<Add option="-lVCMI_lib" />
+					<Add directory="$(#boost.lib32)" />
 				</Linker>
 			</Target>
 			<Target title="Release-win32">
@@ -34,6 +35,7 @@
 					<Add option="-s" />
 					<Add option="-lboost_system$(#boost.libsuffix32)" />
 					<Add option="-lVCMI_lib" />
+					<Add directory="$(#boost.lib32)" />
 				</Linker>
 			</Target>
 			<Target title="Debug-win64">
@@ -41,10 +43,11 @@
 				<Option output="../StupidAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
 				<Option object_output="obj/Debug/x64" />
 				<Option type="3" />
-				<Option compiler="gcc" />
+				<Option compiler="gnu_gcc_compiler_x64" />
 				<Linker>
 					<Add option="-lboost_system$(#boost.libsuffix64)" />
 					<Add option="-lVCMI_lib" />
+					<Add directory="$(#boost.lib64)" />
 				</Linker>
 			</Target>
 		</Build>
@@ -61,13 +64,12 @@
 			<Add option="-Wno-overloaded-virtual" />
 			<Add option="-DBOOST_ALL_DYN_LINK" />
 			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
-			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32_WINNT=0x0600" />
 			<Add option="-D_WIN32" />
 			<Add directory="$(#boost.include)" />
 			<Add directory="../../include" />
 		</Compiler>
 		<Linker>
-			<Add directory="$(#boost.lib32)" />
 			<Add directory="../.." />
 		</Linker>
 		<Unit filename="StdInc.h">
@@ -77,10 +79,6 @@
 		<Unit filename="StupidAI.cpp" />
 		<Unit filename="StupidAI.h" />
 		<Unit filename="main.cpp" />
-		<Extensions>
-			<code_completion />
-			<envvars />
-			<debugger />
-		</Extensions>
+		<Extensions />
 	</Project>
 </CodeBlocks_project_file>

+ 8 - 17
AI/StupidAI/StupidAI.cpp

@@ -28,9 +28,10 @@ CStupidAI::~CStupidAI()
 	print("destroyed");
 }
 
-void CStupidAI::init(std::shared_ptr<CBattleCallback> CB)
+void CStupidAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
 {
 	print("init called, saving ptr to IBattleCallback");
+	env = ENV;
 	cbc = cb = CB;
 }
 
@@ -74,10 +75,12 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 	int shooters[2] = {0}; //count of shooters on hexes
 
 	for(int i = 0; i < 2; i++)
+	{
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
-			if(const CStack *s = cbc->battleGetStackByPos(neighbour))
-				if(s->getCreature()->isShooting())
-						shooters[i]++;
+			if(const CStack * s = cbc->battleGetStackByPos(neighbour))
+				if(s->isShooter())
+					shooters[i]++;
+	}
 
 	return shooters[0] < shooters[1];
 }
@@ -173,7 +176,7 @@ void CStupidAI::battleAttack(const BattleAttack *ba)
 	print("battleAttack called");
 }
 
-void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
+void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
 {
 	print("battleStacksAttacked called");
 }
@@ -293,15 +296,3 @@ BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> h
 		}
 	}
 }
-
-void CStupidAI::saveGame(BinarySerializer & h, const int version)
-{
-	//TODO to be implemented with saving/loading during the battles
-	assert(0);
-}
-
-void CStupidAI::loadGame(BinaryDeserializer & h, const int version)
-{
-	//TODO to be implemented with saving/loading during the battles
-	assert(0);
-}

+ 3 - 5
AI/StupidAI/StupidAI.h

@@ -18,19 +18,20 @@ class CStupidAI : public CBattleGameInterface
 {
 	int side;
 	std::shared_ptr<CBattleCallback> cb;
+	std::shared_ptr<Environment> env;
 
 	void print(const std::string &text) const;
 public:
 	CStupidAI();
 	~CStupidAI();
 
-	void init(std::shared_ptr<CBattleCallback> CB) override;
+	void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
 	void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
 	void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
 	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
 
 	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
-	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
+	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
 	void battleEnd(const BattleResult *br) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
@@ -42,9 +43,6 @@ public:
 	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
 
-	virtual void saveGame(BinarySerializer & h, const int version) override;
-	virtual void loadGame(BinaryDeserializer & h, const int version) override;
-
 private:
 	BattleAction goTowards(const CStack * stack, std::vector<BattleHex> hexes) const;
 };

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -279,7 +279,7 @@ creInfo infoFromDC(const dwellingContent & dc)
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	if (ci.creID != -1)
 	{
-		ci.cre = VLC->creh->creatures[ci.creID];
+		ci.cre = VLC->creh->objects[ci.creID];
 		ci.level = ci.cre->level; //this is cretaure tier, while tryRealize expects dwelling level. Ignore.
 	}
 	else

+ 2 - 0
AI/VCAI/FuzzyHelper.cpp

@@ -139,7 +139,9 @@ float FuzzyHelper::evaluate(Goals::VisitHero & g)
 {
 	auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar
 	if(!obj)
+	{
 		return -100; //hero died in the meantime
+	}
 	else
 	{
 		g.setpriority(Goals::VisitTile(obj->visitablePos()).sethero(g.hero).accept(this));

+ 1 - 1
AI/VCAI/Goals/AbstractGoal.cpp

@@ -91,7 +91,7 @@ std::string AbstractGoal::name() const //TODO: virtualize
 	}
 	break;
 	case GET_ART_TYPE:
-		desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
+		desc = "GET ARTIFACT OF TYPE " + VLC->artifacts()->getByIndex(aid)->getName();
 		break;
 	case VISIT_TILE:
 		desc = "VISIT TILE " + tile.toString();

+ 7 - 7
AI/VCAI/Goals/AdventureSpellCast.cpp

@@ -33,16 +33,16 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 
 	auto spell = getSpell();
 
-	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name);
+	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->name);
 
-	if(!spell->isAdventureSpell())
-		throw cannotFulfillGoalException(spell->name + " is not an adventure spell.");
+	if(!spell->isAdventure())
+		throw cannotFulfillGoalException(spell->getName() + " is not an adventure spell.");
 
 	if(!hero->canCastThisSpell(spell))
-		throw cannotFulfillGoalException("Hero can not cast " + spell->name);
+		throw cannotFulfillGoalException("Hero can not cast " + spell->getName());
 
 	if(hero->mana < hero->getSpellCost(spell))
-		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name);
+		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getName());
 
 	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
 		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name);
@@ -75,10 +75,10 @@ void AdventureSpellCast::accept(VCAI * ai)
 
 std::string AdventureSpellCast::name() const
 {
-	return "AdventureSpellCast " + spellID.toSpell()->name;
+	return "AdventureSpellCast " + getSpell()->getName();
 }
 
 std::string AdventureSpellCast::completeMessage() const
 {
-	return "Spell casted successfully  " + spellID.toSpell()->name;
+	return "Spell cast successfully " + getSpell()->getName();
 }

+ 1 - 1
AI/VCAI/Goals/GatherArmy.cpp

@@ -158,7 +158,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 						{
 							for(auto & creatureID : creLevel.second)
 							{
-								auto creature = VLC->creh->creatures[creatureID];
+								auto creature = VLC->creh->objects[creatureID];
 								if(ai->ah->freeResources().canAfford(creature->cost))
 									objs.push_back(obj); //TODO: reserve resources?
 							}

+ 3 - 3
AI/VCAI/Goals/GatherTroops.cpp

@@ -93,7 +93,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 			continue;
 		}
 
-		auto creature = VLC->creh->creatures[objid];
+		auto creature = VLC->creh->objects[objid];
 		if(t->subID == creature->faction) //TODO: how to force AI to build unupgraded creatures? :O
 		{
 			auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1);
@@ -110,7 +110,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 				solutions.push_back(sptr(BuyArmy(t, creature->AIValue * this->value).setobjid(objid)));
 			}
 			/*else //disable random building requests for now - this code needs to know a lot of town/resource context to do more good than harm
-			{	
+			{
 				return sptr(BuildThis(bid, t).setpriority(priority));
 			}*/
 		}
@@ -129,7 +129,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 			{
 				for(auto type : creature.second)
 				{
-					if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->creatures[type]->cost))
+					if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->objects[type]->cost))
 						vstd::concatenate(solutions, ai->ah->howToVisitObj(obj));
 				}
 			}

+ 3 - 3
AI/VCAI/MapObjectsEvaluator.cpp

@@ -41,7 +41,7 @@ MapObjectsEvaluator::MapObjectsEvaluator()
 					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0;
 				}
 			}
-		}	
+		}
 	}
 }
 
@@ -62,7 +62,7 @@ boost::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance
 	{
 		//special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes
 		auto hero = dynamic_cast<const CGHeroInstance*>(obj);
-		return getObjectValue(obj->ID, hero->type->heroClass->id);
+		return getObjectValue(obj->ID, hero->type->heroClass->getIndex());
 	}
 	else if(obj->ID == Obj::PRISON)
 	{
@@ -77,7 +77,7 @@ boost::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance
 		{
 			for(auto & creatureID : creLevel.second)
 			{
-				auto creature = VLC->creh->creatures[creatureID];
+				auto creature = VLC->creh->objects[creatureID];
 				aiValue += (creature->AIValue * creature->growth);
 			}
 		}

+ 1 - 1
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -60,7 +60,7 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 		cb->calculatePaths(config, hero);
 	};
 
-	std::vector<Task> calculationTasks;
+	std::vector<CThreadHelper::Task> calculationTasks;
 
 	for(HeroPtr hero : heroes)
 	{

+ 1 - 4
AI/VCAI/VCAI.cbp

@@ -78,7 +78,7 @@
 			<Add option="-Wno-overloaded-virtual" />
 			<Add option="-DBOOST_ALL_DYN_LINK" />
 			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
-			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32_WINNT=0x0600" />
 			<Add option="-D_WIN32" />
 			<Add option="-DFL_CPP11" />
 			<Add directory="$(#boost.include)" />
@@ -185,9 +185,6 @@
 		<Unit filename="VCAI.h" />
 		<Unit filename="main.cpp" />
 		<Extensions>
-			<code_completion />
-			<envvars />
-			<debugger />
 			<lib_finder disable_auto="1" />
 		</Extensions>
 	</Project>

+ 5 - 4
AI/VCAI/VCAI.cpp

@@ -579,9 +579,10 @@ void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions)
 	NET_EVENT_HANDLER;
 }
 
-void VCAI::init(std::shared_ptr<CCallback> CB)
+void VCAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
 {
 	LOG_TRACE(logAi);
+	env = ENV;
 	myCb = CB;
 	cbc = CB;
 
@@ -868,7 +869,7 @@ void VCAI::mainLoop()
 		goalsToRemove.clear();
 		elementarGoals.clear();
 		ultimateGoalsFromBasic.clear();
-		
+
 		ah->updatePaths(getMyHeroes());
 
 		logAi->debug("Main loop: decomposing %i basic goals", basicGoals.size());
@@ -1082,7 +1083,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArme
 						&& (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature))
 					{
 						auto weakest = ah->getWeakestCreature(bestArmy);
-						
+
 						if(weakest->creature == targetCreature)
 						{
 							if(1 == source->getStackCount(j))
@@ -1233,7 +1234,7 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
 		int count = d->creatures[i].first;
 		CreatureID creID = d->creatures[i].second.back();
 
-		vstd::amin(count, ah->freeResources() / VLC->creh->creatures[creID]->cost);
+		vstd::amin(count, ah->freeResources() / VLC->creh->objects[creID]->cost);
 		if(count > 0)
 			cb->recruitCreatures(d, recruiter, creID, count, i);
 	}

+ 1 - 1
AI/VCAI/VCAI.h

@@ -139,7 +139,7 @@ public:
 
 	std::string getBattleAIName() const override;
 
-	void init(std::shared_ptr<CCallback> CB) override;
+	void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void yourTurn() override;
 
 	void heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID) override; //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id

+ 4 - 10
CCallback.cpp

@@ -22,7 +22,6 @@
 #include "lib/CHeroHandler.h"
 #include "lib/NetPacks.h"
 #include "client/mapHandler.h"
-#include "lib/spells/CSpellHandler.h"
 #include "lib/CArtHandler.h"
 #include "lib/GameConstants.h"
 #include "lib/CPlayerState.h"
@@ -328,24 +327,19 @@ int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance
 		return swapCreatures(s1, s2, p1, p2);
 }
 
-void CCallback::registerGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents)
-{
-	cl->additionalPlayerInts[*player].push_back(gameEvents);
-}
-
 void CCallback::registerBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents)
 {
 	cl->additionalBattleInts[*player].push_back(battleEvents);
 }
 
-void CCallback::unregisterGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents)
+void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents)
 {
-	cl->additionalPlayerInts[*player] -= gameEvents;
+	cl->additionalBattleInts[*player] -= battleEvents;
 }
 
-void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents)
+scripting::Pool * CBattleCallback::getContextPool() const
 {
-	cl->additionalBattleInts[*player] -= battleEvents;
+	return cl->getGlobalContextPool();
 }
 
 CBattleCallback::CBattleCallback(boost::optional<PlayerColor> Player, CClient *C )

+ 3 - 3
CCallback.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "lib/CGameInfoCallback.h"
+#include "lib/battle/CPlayerBattleCallback.h"
 #include "lib/int3.h" // for int3
 
 class CGHeroInstance;
@@ -92,11 +93,12 @@ public:
 	int battleMakeAction(const BattleAction * action) override;//for casting spells by hero - DO NOT use it for moving active stack
 	bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions
 
+	scripting::Pool * getContextPool() const override;
+
 	friend class CCallback;
 	friend class CClient;
 };
 
-class CPlayerInterface;
 class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
 {
 public:
@@ -111,9 +113,7 @@ public:
 	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);
 
 	//Set of metrhods that allows adding more interfaces for this player that'll receive game event call-ins.
-	void registerGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents);
 	void registerBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
-	void unregisterGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents);
 	void unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
 
 //commands

+ 1 - 0
CI/linux/before_install.sh

@@ -20,6 +20,7 @@ sudo apt-get install -qq cmake ninja-build libboost1.54-all-dev zlib1g-dev
 sudo apt-get install -qq libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
 sudo apt-get install -qq libavformat-dev libswscale-dev
 sudo apt-get install -qq qt57declarative
+sudo apt-get install -qq libluajit-5.1-dev
 
 #setup compiler
 source /opt/qt57/bin/qt57-env.sh

+ 1 - 1
CI/mac/before_install.sh

@@ -1,7 +1,7 @@
 #!/bin/sh
 
 brew update
-brew install smpeg2 libpng freetype sdl2 sdl2_ttf sdl2_image qt5 ffmpeg ninja
+brew install smpeg2 libpng freetype sdl2 sdl2_ttf sdl2_image qt5 ffmpeg ninja luajit
 brew install sdl2_mixer
 
 export CMAKE_PREFIX_PATH="/usr/local/opt/qt5:$CMAKE_PREFIX_PATH"

+ 6 - 5
CI/mxe/before_install.sh

@@ -4,17 +4,17 @@
 sudo apt-get install -qq nsis ninja-build
 
 # MXE repository was too slow for Travis far too often
-wget https://github.com/vcmi/vcmi-deps-mxe/releases/download/2018-02-10/mxe-$MXE_TARGET-2018-02-10.tar
-tar -xvf mxe-$MXE_TARGET-2018-02-10.tar
+wget https://github.com/vcmi/vcmi-deps-mxe/releases/download/2019-06-28/mxe-i686-w64-mingw32.shared-2019-06-28.tar
+tar -xvf mxe-i686-w64-mingw32.shared-2019-06-28.tar
 sudo dpkg -i mxe-*.deb
 sudo apt-get install -f --yes
 
 if false; then
 # Add MXE repository and key
-echo "deb http://pkg.mxe.cc/repos/apt/debian wheezy main" \
+echo "deb http://pkg.mxe.cc/repos/apt trusty main" \
     | sudo tee /etc/apt/sources.list.d/mxeapt.list
 
-sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D43A795B73B16ABE9643FE1AFD8FFF16DB45C6AB
+sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 86B72ED9
 
 # Install needed packages
 sudo apt-get update -qq
@@ -30,7 +30,8 @@ mxe-$MXE_TARGET-sdl2-mixer \
 mxe-$MXE_TARGET-sdl2-ttf \
 mxe-$MXE_TARGET-ffmpeg \
 mxe-$MXE_TARGET-qt \
-mxe-$MXE_TARGET-qtbase
+mxe-$MXE_TARGET-qtbase \
+mxe-i686-w64-mingw32.static-luajit
 
 fi # Disable
 

+ 21 - 2
CMakeLists.txt

@@ -44,7 +44,8 @@ set(VCMI_VERSION_MAJOR 0)
 set(VCMI_VERSION_MINOR 99)
 set(VCMI_VERSION_PATCH 0)
 
-option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
+option(ENABLE_ERM "Enable compilation of ERM scripting module" ON)
+option(ENABLE_LUA "Enable compilation of LUA scripting module" ON)
 option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
 option(ENABLE_TEST "Enable compilation of unit tests" ON)
 option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
@@ -122,7 +123,6 @@ if(APPLE)
 endif(APPLE)
 
 if(WIN32)
-	add_definitions(-DBOOST_THREAD_USE_LIB)
 	# Windows Vista or newer for FuzzyLite 6 to compile
 	add_definitions(-D_WIN32_WINNT=0x0600)
 
@@ -231,6 +231,21 @@ if(ENABLE_LAUNCHER)
 	find_package(Qt5Network REQUIRED)
 endif()
 
+if(ENABLE_LUA)
+	# MXE paths hardcoded for current dependencies pack - tried and could not make it work another way
+	if((MINGW) AND (${CMAKE_CROSSCOMPILING}) AND (DEFINED MSYS))
+		set(LUA_INCLUDE_DIR "/usr/lib/mxe/usr/i686-w64-mingw32.static/include/luajit-2.0")
+		set(LUA_LIBRARY "/usr/lib/mxe/usr/i686-w64-mingw32.static/lib/libluajit-5.1.a")
+	endif()
+	find_package(LuaJIT)
+	if(LUAJIT_FOUND)
+		message(STATUS "Using LuaJIT provided by system")
+	else()
+		message(STATUS "Cannot find LuaJIT! Fallback to using usual Lua.")
+		find_package(Lua REQUIRED)
+	endif()
+endif()
+
 ############################################
 #        Output directories                #
 ############################################
@@ -294,6 +309,9 @@ set(SCRIPTING_LIB_DIR "${LIB_DIR}/scripting")
 if(ENABLE_ERM)
 	add_subdirectory(scripting/erm)
 endif()
+if(ENABLE_LUA)
+	add_subdirectory(scripting/lua)
+endif()
 if(NOT MINIZIP_FOUND)
 	add_subdirectory_with_folder("3rdparty" lib/minizip)
 	set(MINIZIP_LIBRARIES minizip)
@@ -315,6 +333,7 @@ endif()
 #######################################
 
 install(DIRECTORY config DESTINATION ${DATA_DIR})
+install(DIRECTORY scripts DESTINATION ${DATA_DIR})
 install(DIRECTORY Mods DESTINATION ${DATA_DIR})
 
 # that script is useless for Windows

+ 0 - 18
Global.h

@@ -727,24 +727,6 @@ namespace vstd
 	}
 
 	using boost::math::round;
-
-	static std::pair<std::string, std::string> splitStringToPair(std::string input, char separator)
-	{
-		std::pair<std::string, std::string> ret;
-		size_t splitPos = input.find(separator);
-
-		if (splitPos == std::string::npos)
-		{
-			ret.first.clear();
-			ret.second = input;
-		}
-		else
-		{
-			ret.first = input.substr(0, splitPos);
-			ret.second = input.substr(splitPos + 1);
-		}
-		return ret;
-	}
 }
 using vstd::operator-=;
 using vstd::make_unique;

+ 0 - 40
Mods/vcmi/Data/s/std.verm

@@ -1,40 +0,0 @@
-VERM
-; standard verm file, global engine things should be put here
-
-!?PI;
-; example 1 --- Hello World
-![print ^Hello world!^]
-
-; example 2 --- simple arithmetics
-![defun add [x y] [+ x y]]
-![print [add 2 3]]
-
-; example 3 --- semantic macros
-![defmacro do-n-times [times body]
-	`[progn
-		[setq do-counter 0]
-		[setq do-max ,times]
-		[do [< do-counter do-max]
-			[progn 
-				[setq do-counter [+ do-counter 1]]
-				,body
-			]
-		]
-	]
-]
-![do-n-times 4 [print ^tekst\n^]]
-
-
-; example 4 --- conditional expression
-![if [> 2 1] [print ^Wieksze^] [print ^Mniejsze^]]
-
-; example 5 --- lambda expressions
-![[lambda [x y] [if [> x y] [print ^wieksze^] [print ^mniejsze^]]] 2 3]
-
-; example 6 --- resursion
-![defun factorial [n]
-	[if [= n 0] 1
-		[* n [factorial [- n 1]]]
-	]
-]
-![print [factorial 8]]

+ 0 - 14
Mods/vcmi/Data/s/testy.erm

@@ -1,14 +0,0 @@
-ZVSE
-!?PI;
-	!!VRv2777:S4;
-	!!DO1/0/5/1&v2777<>1:P0;
-
-!?FU1;
-	!!VRv2778:Sx16%2;
-	!!IF&x16>3:M^Hello world number %X16! To duza liczba^;
-	!!IF&v2778==0&x16<=3:M^Hello world number %X16! To mala parzysta liczba^;
-	!!IF&v2778==1&x16<=3:M^Hello world number %X16! To mala nieparzysta liczba^;
-
-!?PI;
-	!!VRz10:S^Composed hello ^;
-	!!IF:M^%Z10%%world%%, v2777=%V2777, v2778=%V2778!^;

+ 57 - 3
client/CGameInfo.cpp

@@ -9,8 +9,6 @@
  */
 #include "StdInc.h"
 #include "CGameInfo.h"
-#include "../lib/CSkillHandler.h"
-#include "../lib/CGeneralTextHandler.h"
 
 #include "../lib/VCMI_Lib.h"
 
@@ -24,13 +22,14 @@ CGameInfo::CGameInfo()
 	generaltexth = nullptr;
 	mh = nullptr;
 	townh = nullptr;
+	globalServices = nullptr;
 }
 
 void CGameInfo::setFromLib()
 {
+	globalServices = VLC;
 	modh = VLC->modh;
 	generaltexth = VLC->generaltexth;
-	arth = VLC->arth;
 	creh = VLC->creh;
 	townh = VLC->townh;
 	heroh = VLC->heroh;
@@ -39,3 +38,58 @@ void CGameInfo::setFromLib()
 	skillh = VLC->skillh;
 	objtypeh = VLC->objtypeh;
 }
+
+const ArtifactService * CGameInfo::artifacts() const
+{
+	return globalServices->artifacts();
+}
+
+const CreatureService * CGameInfo::creatures() const
+{
+	return globalServices->creatures();
+}
+
+const FactionService * CGameInfo::factions() const
+{
+	return globalServices->factions();
+}
+
+const HeroClassService * CGameInfo::heroClasses() const
+{
+	return globalServices->heroClasses();
+}
+
+const HeroTypeService * CGameInfo::heroTypes() const
+{
+	return globalServices->heroTypes();
+}
+
+const scripting::Service * CGameInfo::scripts()  const
+{
+	return globalServices->scripts();
+}
+
+const spells::Service * CGameInfo::spells()  const
+{
+	return globalServices->spells();
+}
+
+const SkillService * CGameInfo::skills() const
+{
+	return globalServices->skills();
+}
+
+void CGameInfo::updateEntity(Metatype metatype, int32_t index, const JsonNode & data)
+{
+	logGlobal->error("CGameInfo::updateEntity call is not expected.");
+}
+
+spells::effects::Registry * CGameInfo::spellEffects()
+{
+	return nullptr;
+}
+
+const spells::effects::Registry * CGameInfo::spellEffects() const
+{
+	return globalServices->spellEffects();
+}

+ 20 - 5
client/CGameInfo.h

@@ -9,11 +9,12 @@
  */
 #pragma once
 
+#include <vcmi/Services.h>
+
 #include "../lib/ConstTransitivePtr.h"
 
 class CModHandler;
 class CMapHandler;
-class CArtHandler;
 class CHeroHandler;
 class CCreatureHandler;
 class CSpellHandler;
@@ -48,11 +49,25 @@ extern CClientState * CCS;
 
 /// CGameInfo class
 /// for allowing different functions for accessing game informations
-class CGameInfo
+class CGameInfo : public Services
 {
 public:
+	const ArtifactService * artifacts() const override;
+	const CreatureService * creatures() const override;
+	const FactionService * factions() const override;
+	const HeroClassService * heroClasses() const override;
+	const HeroTypeService * heroTypes() const override;
+	const scripting::Service * scripts() const override;
+	const spells::Service * spells() const override;
+	const SkillService * skills() const override;
+
+	void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
+
+	const spells::effects::Registry * spellEffects() const override;
+	spells::effects::Registry * spellEffects() override;
+
+
 	ConstTransitivePtr<CModHandler> modh; //public?
-	ConstTransitivePtr<CArtHandler> arth;
 	ConstTransitivePtr<CHeroHandler> heroh;
 	ConstTransitivePtr<CCreatureHandler> creh;
 	ConstTransitivePtr<CSpellHandler> spellh;
@@ -65,8 +80,8 @@ public:
 
 	void setFromLib();
 
-	friend class CClient;
-
 	CGameInfo();
+private:
+	const Services * globalServices;
 };
 extern const CGameInfo* CGI;

+ 29 - 25
client/CMT.cpp

@@ -14,6 +14,8 @@
 
 #include <boost/program_options.hpp>
 
+#include <vcmi/scripting/Service.h>
+
 #include "gui/SDL_Extensions.h"
 #include "CGameInfo.h"
 #include "mapHandler.h"
@@ -46,9 +48,9 @@
 #include "../lib/NetPacks.h"
 #include "CMessage.h"
 #include "../lib/CModHandler.h"
+#include "../lib/ScriptHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CArtHandler.h"
-#include "../lib/CScriptingModule.h"
 #include "../lib/GameConstants.h"
 #include "gui/CGuiHandler.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
@@ -95,14 +97,13 @@ SDL_Surface *screen = nullptr, //main screen surface
 	*screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer)
 	*screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
 
-std::queue<SDL_Event> events;
+std::queue<SDL_Event> SDLEventsQueue;
 boost::mutex eventsM;
 
 static po::variables_map vm;
 
 //static bool setResolution = false; //set by event handling thread after resolution is adjusted
 
-static bool ermInteractiveMode = false; //structurize when time is right
 void processCommand(const std::string &message);
 static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true);
 void playIntro();
@@ -576,29 +577,10 @@ void processCommand(const std::string &message)
 //	if(LOCPLINT && LOCPLINT->cingconsole)
 //		LOCPLINT->cingconsole->print(message);
 
-	if(ermInteractiveMode)
-	{
-		if(cn == "exit")
-		{
-			ermInteractiveMode = false;
-			return;
-		}
-		else
-		{
-			if(CSH->client && CSH->client->erm)
-				CSH->client->erm->executeUserCommand(message);
-			std::cout << "erm>";
-		}
-	}
-	else if(message==std::string("die, fool"))
+	if(message==std::string("die, fool"))
 	{
 		exit(EXIT_SUCCESS);
 	}
-	else if(cn == "erm")
-	{
-		ermInteractiveMode = true;
-		std::cout << "erm>";
-	}
 	else if(cn==std::string("activate"))
 	{
 		int what;
@@ -725,6 +707,28 @@ void processCommand(const std::string &message)
 		std::cout << "\rExtracting done :)\n";
 		std::cout << " Extracted files can be found in " << outPath << " directory\n";
 	}
+	else if(message=="get scripts")
+	{
+		std::cout << "Command accepted.\t";
+
+		const bfs::path outPath =
+			VCMIDirs::get().userCachePath() / "extracted" / "scripts";
+
+		bfs::create_directories(outPath);
+
+		for(auto & kv : VLC->scriptHandler->objects)
+		{
+			std::string name = kv.first;
+			boost::algorithm::replace_all(name,":","_");
+
+			const scripting::ScriptImpl * script = kv.second.get();
+			bfs::path filePath = outPath / (name + ".lua");
+			bfs::ofstream file(filePath);
+			file << script->getSource();
+		}
+		std::cout << "\rExtracting done :)\n";
+		std::cout << " Extracted files can be found in " << outPath << " directory\n";
+	}
 	else if(message=="get txt")
 	{
 		std::cout << "Command accepted.\t";
@@ -891,7 +895,7 @@ void processCommand(const std::string &message)
 	{
 		YourTurn yt;
 		yt.player = player;
-		yt.daysWithoutCastle = CSH->client->getPlayer(player)->daysWithoutCastle;
+		yt.daysWithoutCastle = CSH->client->getPlayerState(player)->daysWithoutCastle;
 		yt.applyCl(CSH->client);
 	};
 
@@ -1360,7 +1364,7 @@ static void handleEvent(SDL_Event & ev)
 
 	{
 		boost::unique_lock<boost::mutex> lock(eventsM);
-		events.push(ev);
+		SDLEventsQueue.push(ev);
 	}
 
 }

+ 0 - 2
client/CMusicHandler.cpp

@@ -13,8 +13,6 @@
 #include "CMusicHandler.h"
 #include "CGameInfo.h"
 #include "SDLRWwrapper.h"
-#include "../lib/CCreatureHandler.h"
-#include "../lib/spells/CSpellHandler.h"
 #include "../lib/JsonNode.h"
 #include "../lib/GameConstants.h"
 #include "../lib/filesystem/Filesystem.h"

+ 0 - 1
client/CMusicHandler.h

@@ -12,7 +12,6 @@
 #include "../lib/CConfigHandler.h"
 #include "../lib/CSoundBase.h"
 
-class CSpell;
 struct _Mix_Music;
 struct SDL_RWops;
 typedef struct _Mix_Music Mix_Music;

+ 35 - 33
client/CPlayerInterface.cpp

@@ -8,6 +8,9 @@
  *
  */
 #include "StdInc.h"
+
+#include <vcmi/Artifact.h>
+
 #include "windows/CAdvmapInterface.h"
 #include "battle/CBattleInterface.h"
 #include "battle/CBattleInterfaceClasses.h"
@@ -82,7 +85,7 @@ using namespace CSDL_Ext;
 
 void processCommand(const std::string &message, CClient *&client);
 
-extern std::queue<SDL_Event> events;
+extern std::queue<SDL_Event> SDLEventsQueue;
 extern boost::mutex eventsM;
 boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
 
@@ -144,9 +147,10 @@ CPlayerInterface::~CPlayerInterface()
 	if (LOCPLINT == this)
 		LOCPLINT = nullptr;
 }
-void CPlayerInterface::init(std::shared_ptr<CCallback> CB)
+void CPlayerInterface::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
 {
 	cb = CB;
+	env = ENV;
 	initializeHeroTownList();
 
 	// always recreate advmap interface to avoid possible memory-corruption bugs
@@ -372,10 +376,10 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 	//check if user cancelled movement
 	{
 		boost::unique_lock<boost::mutex> un(eventsM);
-		while(!events.empty())
+		while(!SDLEventsQueue.empty())
 		{
-			SDL_Event ev = events.front();
-			events.pop();
+			SDL_Event ev = SDLEventsQueue.front();
+			SDLEventsQueue.pop();
 			switch(ev.type)
 			{
 			case SDL_MOUSEBUTTONDOWN:
@@ -692,7 +696,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 	if (settings["adventure"]["quickCombat"].Bool())
 	{
 		autofightingAI = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
-		autofightingAI->init(cb);
+		autofightingAI->init(env, cb);
 		autofightingAI->battleStart(army1, army2, int3(0,0,0), hero1, hero2, side);
 		isAutoFightOn = true;
 		cb->registerBattleInterface(autofightingAI);
@@ -707,7 +711,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 	BATTLE_EVENT_POSSIBLE_RETURN;
 }
 
-void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
+void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -769,7 +773,6 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
 	}
 
 	battleInt->displayCustomEffects(customEffects);
-	battleInt->displayBattleLog(battleLog);
 }
 
 void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
@@ -919,6 +922,14 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
 	battleInt->battleFinished(*br);
 }
 
+void CPlayerInterface::battleLogMessage(const std::vector<MetaString> & lines)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+	BATTLE_EVENT_POSSIBLE_RETURN;
+
+	battleInt->displayBattleLog(lines);
+}
+
 void CPlayerInterface::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -948,7 +959,7 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
 	RETURN_IF_QUICK_COMBAT;
 	battleInt->battleTriggerEffect(bte);
 }
-void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
+void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -977,7 +988,7 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
 		StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
 		arg.push_back(to_put);
 	}
-	battleInt->stacksAreAttacked(arg, battleLog);
+	battleInt->stacksAreAttacked(arg);
 }
 void CPlayerInterface::battleAttack(const BattleAttack * ba)
 {
@@ -1424,29 +1435,20 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
  * Shows the dialog that appears when right-clicking an artifact that can be assembled
  * into a combinational one on an artifact screen. Does not require the combination of
  * artifacts to be legal.
- * @param artifactID ID of a constituent artifact.
- * @param assembleTo ID of artifact to assemble a constituent into, not used when assemble
- * is false.
- * @param assemble True if the artifact is to be assembled, false if it is to be disassembled.
  */
-void CPlayerInterface::showArtifactAssemblyDialog (ui32 artifactID, ui32 assembleTo, bool assemble, CFunctionList<bool()> onYes, CFunctionList<bool()> onNo)
+void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes)
 {
-	const CArtifact &artifact = *CGI->arth->artifacts[artifactID];
-	std::string text = artifact.Description();
+	std::string text = artifact->getDescription();
 	text += "\n\n";
 	std::vector<std::shared_ptr<CComponent>> scs;
 
-	if(assemble)
+	if(assembledArtifact)
 	{
-		const CArtifact &assembledArtifact = *CGI->arth->artifacts[assembleTo];
-
 		// You possess all of the components to...
-		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact.Name());
+		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getName());
 
 		// Picture of assembled artifact at bottom.
-		auto sc = std::make_shared<CComponent>(CComponent::artifact, assembledArtifact.id, 0);
-		//sc->description = assembledArtifact.Description();
-		//sc->subtitle = assembledArtifact.Name();
+		auto sc = std::make_shared<CComponent>(CComponent::artifact, assembledArtifact->getIndex(), 0);
 		scs.push_back(sc);
 	}
 	else
@@ -1455,7 +1457,7 @@ void CPlayerInterface::showArtifactAssemblyDialog (ui32 artifactID, ui32 assembl
 		text += CGI->generaltexth->allTexts[733];
 	}
 
-	showYesNoDialog(text, onYes, onNo, scs);
+	showYesNoDialog(text, onYes, nullptr, scs);
 }
 
 void CPlayerInterface::requestRealized( PackageApplied *pa )
@@ -1612,8 +1614,8 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
 {
 	if(reason == PlayerBlocked::EReason::UPCOMING_BATTLE)
 	{
-		if(CSH->howManyPlayerInterfaces() > 1 && LOCPLINT != this && LOCPLINT->makingTurn == false) 
-		{ 
+		if(CSH->howManyPlayerInterfaces() > 1 && LOCPLINT != this && LOCPLINT->makingTurn == false)
+		{
 			//one of our players who isn't last in order got attacked not by our another player (happens for example in hotseat mode)
 			boost::unique_lock<boost::mutex> lock(eventsM); //TODO: copied from yourTurn, no idea if it's needed
 			LOCPLINT = this;
@@ -2236,13 +2238,13 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 	if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
 		eraseCurrentPathOf(caster, false);
 
-	const CSpell * spell = CGI->spellh->objects.at(spellID);
+	const spells::Spell * spell = CGI->spells()->getByIndex(spellID);
 
 	if(spellID == SpellID::VIEW_EARTH)
 	{
 		//TODO: implement on server side
-		int level = caster->getSpellSchoolLevel(spell);
-		adventureInt->worldViewOptions.showAllTerrain = (level>2);
+		const auto level = caster->getSpellSchoolLevel(spell);
+		adventureInt->worldViewOptions.showAllTerrain = (level > 2);
 	}
 
 	auto castSoundPath = spell->getCastSound();
@@ -2360,7 +2362,7 @@ void CPlayerInterface::acceptTurn()
 		components.push_back(Component(Component::FLAG, playerColor.getNum(), 0, 0));
 		MetaString text;
 
-		const auto & optDaysWithoutCastle = cb->getPlayer(playerColor)->daysWithoutCastle;
+		const auto & optDaysWithoutCastle = cb->getPlayerState(playerColor)->daysWithoutCastle;
 
 		if(optDaysWithoutCastle)
 		{
@@ -2646,9 +2648,9 @@ bool CPlayerInterface::capturedAllEvents()
 	if (ignoreEvents)
 	{
 		boost::unique_lock<boost::mutex> un(eventsM);
-		while(!events.empty())
+		while(!SDLEventsQueue.empty())
 		{
-			events.pop();
+			SDLEventsQueue.pop();
 		}
 		return true;
 	}

+ 9 - 4
client/CPlayerInterface.h

@@ -19,6 +19,8 @@
 #define sprintf_s snprintf
 #endif
 
+class Artifact;
+
 class CButton;
 class CToggleGroup;
 struct TryMoveHero;
@@ -62,7 +64,9 @@ namespace boost
 class CPlayerInterface : public CGameInterface, public IUpdateable
 {
 	const CArmedInstance * currentSelection;
+
 public:
+	std::shared_ptr<Environment> env;
 	ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
 	int3 destinationTeleportPos;
 
@@ -183,21 +187,22 @@ public:
 	void battleEnd(const BattleResult *br) override; //end of battle
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
+	void battleLogMessage(const std::vector<MetaString> & lines) override;
 	void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
 	void battleSpellCast(const BattleSpellCast *sc) override;
 	void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
 	void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
-	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override;
+	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
 	void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
 	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog) override;
+	void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects) override;
 	void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
 	void battleGateStateChanged(const EGateState state) override;
 	void yourTacticPhase(int distance) override;
 
 	//-------------//
-	void showArtifactAssemblyDialog(ui32 artifactID, ui32 assembleTo, bool assemble, CFunctionList<bool()> onYes, CFunctionList<bool()> onNo);
+	void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes);
 	void garrisonsChanged(std::vector<const CGObjectInstance *> objs);
 	void garrisonChanged(const CGObjectInstance * obj);
 	void heroKilled(const CGHeroInstance* hero);
@@ -210,7 +215,7 @@ public:
 	void openTownWindow(const CGTownInstance * town); //shows townscreen
 	void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero
 	void updateInfo(const CGObjectInstance * specific);
-	void init(std::shared_ptr<CCallback> CB) override;
+	void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
 	void activateForSpectator(); // TODO: spectator probably need own player interface class
 

+ 2 - 0
client/CServerHandler.cpp

@@ -45,6 +45,8 @@
 #include <boost/uuid/uuid_generators.hpp>
 #include "../lib/serializer/Cast.h"
 
+#include <vcmi/events/EventBus.h>
+
 template<typename T> class CApplyOnLobby;
 
 #ifdef VCMI_ANDROID

+ 157 - 46
client/Client.cpp

@@ -44,11 +44,12 @@
 #include "lobby/CBonusSelection.h"
 #include "battle/CBattleInterface.h"
 #include "../lib/CThreadHelper.h"
-#include "../lib/CScriptingModule.h"
 #include "../lib/registerTypes/RegisterTypes.h"
 #include "gui/CGuiHandler.h"
 #include "CMT.h"
 #include "CServerHandler.h"
+#include "../lib/ScriptHandler.h"
+#include <vcmi/events/EventBus.h>
 
 #ifdef VCMI_ANDROID
 #include "lib/CAndroidVMHelper.h"
@@ -105,6 +106,39 @@ public:
 	}
 };
 
+CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
+	: player(player_),
+	cl(cl_),
+	mainCallback(mainCallback_)
+{
+
+}
+
+const Services * CPlayerEnvironment::services() const
+{
+	return VLC;
+}
+
+vstd::CLoggerBase * CPlayerEnvironment::logger() const
+{
+	return logGlobal;
+}
+
+events::EventBus * CPlayerEnvironment::eventBus() const
+{
+	return cl->eventBus();//always get actual value
+}
+
+const CPlayerEnvironment::BattleCb * CPlayerEnvironment::battle() const
+{
+	return mainCallback.get();
+}
+
+const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
+{
+	return mainCallback.get();
+}
+
 
 CClient::CClient()
 {
@@ -114,7 +148,36 @@ CClient::CClient()
 	registerTypesClientPacks2(*applier);
 	IObjectInterface::cb = this;
 	gs = nullptr;
-	erm = nullptr;
+}
+
+CClient::~CClient()
+{
+	IObjectInterface::cb = nullptr;
+}
+
+const Services * CClient::services() const
+{
+	return VLC; //todo: this should be CGI
+}
+
+const CClient::BattleCb * CClient::battle() const
+{
+	return this;
+}
+
+const CClient::GameCb * CClient::game() const
+{
+	return this;
+}
+
+vstd::CLoggerBase * CClient::logger() const
+{
+	return logGlobal;
+}
+
+events::EventBus * CClient::eventBus() const
+{
+	return clientEventBus.get();
 }
 
 void CClient::newGame()
@@ -122,11 +185,14 @@ void CClient::newGame()
 	CSH->th->update();
 	CMapService mapService;
 	gs = new CGameState();
+	gs->preInit(VLC);
 	logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
 	gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool());
 	logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff());
 
 	initMapHandler();
+	reinitScripting();
+	initPlayerEnvironments();
 	initPlayerInterfaces();
 }
 
@@ -168,10 +234,16 @@ void CClient::loadGame()
 		throw; //obviously we cannot continue here
 	}
 	logNetwork->trace("Loaded common part of save %d ms", CSH->th->getDiff());
-
+	gs->preInit(VLC);
 	gs->updateOnLoad(CSH->si.get());
 	initMapHandler();
+
+	reinitScripting();
+
+	initPlayerEnvironments();
+
 	serialize(loader->serializer, loader->serializer.fileVersion);
+
 	initPlayerInterfaces();
 }
 
@@ -190,6 +262,13 @@ void CClient::serialize(BinarySerializer & h, const int version)
 		h & i->second->human;
 		i->second->saveGame(h, version);
 	}
+
+	if(version >= 800)
+	{
+		JsonNode scriptsState;
+		clientScripts->serializeState(h.saving, scriptsState);
+		h & scriptsState;
+	}
 }
 
 void CClient::serialize(BinaryDeserializer & h, const int version)
@@ -253,6 +332,17 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
 		}
 		nInt.reset();
 	}
+
+	{
+		JsonNode scriptsState;
+		if(version >= 800)
+		{
+			h & scriptsState;
+		}
+
+		clientScripts->serializeState(h.saving, scriptsState);
+	}
+
 	logNetwork->trace("Loaded client part of save %d ms", CSH->th->getDiff());
 }
 
@@ -270,6 +360,8 @@ void CClient::save(const std::string & fname)
 
 void CClient::endGame()
 {
+	clientScripts.reset();
+
 	//suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
 	for(auto & i : playerint)
 		i.second->finish();
@@ -296,8 +388,8 @@ void CClient::endGame()
 
 	playerint.clear();
 	battleints.clear();
-	callbacks.clear();
 	battleCallbacks.clear();
+	playerEnvironments.clear();
 	logNetwork->info("Deleted playerInts.");
 	logNetwork->info("Client stopped.");
 }
@@ -319,6 +411,24 @@ void CClient::initMapHandler()
 	pathCache.clear();
 }
 
+void CClient::initPlayerEnvironments()
+{
+	playerEnvironments.clear();
+
+	auto allPlayers = CSH->getAllClientPlayers(CSH->c->connectionID);
+
+	for(auto & color : allPlayers)
+	{
+		logNetwork->info("Preparing environment for player %s", color.getStr());
+		playerEnvironments[color] = std::make_shared<CPlayerEnvironment>(color, this, std::make_shared<CCallback>(gs, color, this));
+	}
+
+	if(settings["session"]["spectate"].Bool())
+	{
+		playerEnvironments[PlayerColor::SPECTATOR] = std::make_shared<CPlayerEnvironment>(PlayerColor::SPECTATOR, this, std::make_shared<CCallback>(gs, boost::none, this));
+	}
+}
+
 void CClient::initPlayerInterfaces()
 {
 	for(auto & elem : gs->scenarioOps->playerInfos)
@@ -327,19 +437,20 @@ void CClient::initPlayerInterfaces()
 		if(!vstd::contains(CSH->getAllClientPlayers(CSH->c->connectionID), color))
 			continue;
 
-		if(vstd::contains(playerint, color))
-			continue;
-
-		logNetwork->trace("Preparing interface for player %s", color.getStr());
-		if(elem.second.isControlledByAI())
-		{
-			auto AiToGive = aiNameForPlayer(elem.second, false);
-			logNetwork->info("Player %s will be lead by %s", color, AiToGive);
-			installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
-		}
-		else
+		if(!vstd::contains(playerint, color))
 		{
-			installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
+			logNetwork->info("Preparing interface for player %s", color.getStr());
+			if(elem.second.isControlledByAI())
+			{
+				auto AiToGive = aiNameForPlayer(elem.second, false);
+				logNetwork->info("Player %s will be lead by %s", color.getStr(), AiToGive);
+				installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
+			}
+			else
+			{
+				logNetwork->info("Player %s will be lead by human", color.getStr());
+				installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
+			}
 		}
 	}
 
@@ -379,41 +490,32 @@ std::string CClient::aiNameForPlayer(bool battleAI)
 	return goodAI;
 }
 
-void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb)
+void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, PlayerColor color, bool battlecb)
 {
 	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
-
-	if(!color)
-		privilegedGameEventReceivers.push_back(gameInterface);
 
-	playerint[colorUsed] = gameInterface;
+	playerint[color] = gameInterface;
 
-	logGlobal->trace("\tInitializing the interface for player %s", colorUsed);
+	logGlobal->trace("\tInitializing the interface for player %s", color.getStr());
 	auto cb = std::make_shared<CCallback>(gs, color, this);
-	callbacks[colorUsed] = cb;
-	battleCallbacks[colorUsed] = cb;
-	gameInterface->init(cb);
+	battleCallbacks[color] = cb;
+	gameInterface->init(playerEnvironments.at(color), cb);
 
 	installNewBattleInterface(gameInterface, color, battlecb);
 }
 
-void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback)
+void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, PlayerColor color, bool needCallback)
 {
 	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
 
-	if(!color)
-		privilegedBattleEventReceivers.push_back(battleInterface);
-
-	battleints[colorUsed] = battleInterface;
+	battleints[color] = battleInterface;
 
 	if(needCallback)
 	{
-		logGlobal->trace("\tInitializing the battle interface for player %s", *color);
+		logGlobal->trace("\tInitializing the battle interface for player %s", color.getStr());
 		auto cbc = std::make_shared<CBattleCallback>(color, this);
-		battleCallbacks[colorUsed] = cbc;
-		battleInterface->init(cbc);
+		battleCallbacks[color] = cbc;
+		battleInterface->init(playerEnvironments.at(color), cbc);
 	}
 }
 
@@ -437,14 +539,6 @@ void CClient::handlePack(CPack * pack)
 	delete pack;
 }
 
-void CClient::commitPackage(CPackForClient * pack)
-{
-	CommitPackage cp;
-	cp.freePack = false;
-	cp.packToCommit = pack;
-	sendRequest(&cp, PlayerColor::NEUTRAL);
-}
-
 int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
 {
 	static ui32 requestCounter = 0;
@@ -464,6 +558,7 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
 
 void CClient::battleStarted(const BattleInfo * info)
 {
+	setBattle(info);
 	for(auto & battleCb : battleCallbacks)
 	{
 		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })
@@ -472,9 +567,6 @@ void CClient::battleStarted(const BattleInfo * info)
 			battleCb.second->setBattle(info);
 		}
 	}
-// 	for(ui8 side : info->sides)
-// 		if(battleCallbacks.count(side))
-// 			battleCallbacks[side]->setBattle(info);
 
 	std::shared_ptr<CPlayerInterface> att, def;
 	auto & leftSide = info->sides[0], & rightSide = info->sides[1];
@@ -552,6 +644,8 @@ void CClient::battleFinished()
 
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
 		battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);
+
+	setBattle(nullptr);
 }
 
 void CClient::startPlayerBattleAction(PlayerColor color)
@@ -645,6 +739,23 @@ PlayerColor CClient::getLocalPlayer() const
 	return getCurrentPlayer();
 }
 
+scripting::Pool * CClient::getGlobalContextPool() const
+{
+	return clientScripts.get();
+}
+
+scripting::Pool * CClient::getContextPool() const
+{
+	return clientScripts.get();
+}
+
+void CClient::reinitScripting()
+{
+	clientEventBus = make_unique<events::EventBus>();
+	clientScripts.reset(new scripting::PoolImpl(this));
+}
+
+
 #ifdef VCMI_ANDROID
 extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
 {

+ 63 - 23
client/Client.h

@@ -9,8 +9,11 @@
  */
 #pragma once
 
+#include <vcmi/Environment.h>
+
 #include "../lib/IGameCallback.h"
 #include "../lib/battle/BattleAction.h"
+#include "../lib/battle/CBattleInfoCallback.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/int3.h"
 #include "../lib/CondSh.h"
@@ -28,7 +31,6 @@ class CGameInterface;
 class CCallback;
 class BattleAction;
 class CClient;
-class CScriptingModule;
 struct CPathsInfo;
 class BinaryDeserializer;
 class BinarySerializer;
@@ -37,6 +39,16 @@ namespace boost { class thread; }
 template<typename T> class CApplier;
 class CBaseForCLApply;
 
+namespace scripting
+{
+	class PoolImpl;
+}
+
+namespace events
+{
+	class EventBus;
+}
+
 template<typename T>
 class ThreadSafeVector
 {
@@ -95,32 +107,40 @@ public:
 	}
 };
 
-/// Class which handles client - server logic
-class CClient : public IGameCallback
+class CPlayerEnvironment : public Environment
 {
-	std::shared_ptr<CApplier<CBaseForCLApply>> applier;
-
-	mutable boost::mutex pathCacheMutex;
-	std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
-
-	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
-	void waitForMoveAndSend(PlayerColor color);
+public:
+	PlayerColor player;
+	CClient * cl;
+	std::shared_ptr<CCallback> mainCallback;
+
+	CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_);
+	const Services * services() const override;
+	vstd::CLoggerBase * logger() const override;
+	events::EventBus * eventBus() const override;
+	const BattleCb * battle() const override;
+	const GameCb * game() const override;
+};
 
+/// Class which handles client - server logic
+class CClient : public IGameCallback, public CBattleInfoCallback, public Environment
+{
 public:
-	std::map<PlayerColor, std::shared_ptr<CCallback>> callbacks; //callbacks given to player interfaces
-	std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces
-	std::vector<std::shared_ptr<IGameEventsReceiver>> privilegedGameEventReceivers; //scripting modules, spectator interfaces
-	std::vector<std::shared_ptr<IBattleEventsReceiver>> privilegedBattleEventReceivers; //scripting modules, spectator interfaces
 	std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
 	std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;
 
-	std::map<PlayerColor, std::vector<std::shared_ptr<IGameEventsReceiver>>> additionalPlayerInts;
 	std::map<PlayerColor, std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
 
 	boost::optional<BattleAction> curbaction;
 
-	CScriptingModule * erm;
 	CClient();
+	~CClient();
+
+	const Services * services() const override;
+	const BattleCb * battle() const override;
+	const GameCb * game() const override;
+	vstd::CLoggerBase * logger() const override;
+	events::EventBus * eventBus() const override;
 
 	void newGame();
 	void loadGame();
@@ -131,17 +151,16 @@ public:
 	void endGame();
 
 	void initMapHandler();
+	void initPlayerEnvironments();
 	void initPlayerInterfaces();
 	std::string aiNameForPlayer(const PlayerSettings & ps, bool battleAI); //empty means no AI -> human
 	std::string aiNameForPlayer(bool battleAI);
-	void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb = false);
-	void installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback = true);
-
+	void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, PlayerColor color, bool battlecb = false);
+	void installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, PlayerColor color, bool needCallback = true);
 
 	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
 
 	void handlePack(CPack * pack); //applies the given pack and deletes it
-	void commitPackage(CPackForClient * pack) override;
 	int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
 
 	void battleStarted(const BattleInfo * info);
@@ -160,7 +179,6 @@ public:
 
 	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> & spells) override {};
 	bool removeObject(const CGObjectInstance * obj) override {return false;};
-	void setBlockVis(ObjectInstanceID objid, bool bv) override {};
 	void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {};
 	void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs = false) override {};
 	void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs = false) override {};
@@ -190,7 +208,6 @@ public:
 	void putArtifact(const ArtifactLocation & al, const CArtifactInstance * a) override {};
 	void removeArtifact(const ArtifactLocation & al) override {};
 	bool moveArtifact(const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;};
-	void synchronizeArtifactHandlerLists() override {};
 
 	void showCompInfo(ShowInInfobox * comp) override {};
 	void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
@@ -199,7 +216,6 @@ public:
 	void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
 	void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
 	void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
-	void setAmount(ObjectInstanceID objid, ui32 val) override {};
 	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
 	void giveHeroBonus(GiveBonus * bonus) override {};
 	void setMovePoints(SetMovePoints * smp) override {};
@@ -211,4 +227,28 @@ public:
 
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
 	void changeFogOfWar(std::unordered_set<int3, ShashInt3> & tiles, PlayerColor player, bool hide) override {}
+
+	void setObjProperty(ObjectInstanceID objid, int prop, si64 val) override {}
+
+	void showInfoDialog(InfoWindow * iw) override {};
+	void showInfoDialog(const std::string & msg, PlayerColor player) override {};
+
+	scripting::Pool * getGlobalContextPool() const override;
+	scripting::Pool * getContextPool() const override;
+private:
+	std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces
+	std::map<PlayerColor, std::shared_ptr<CPlayerEnvironment>> playerEnvironments;
+
+	std::shared_ptr<scripting::PoolImpl> clientScripts;
+	std::unique_ptr<events::EventBus> clientEventBus;
+
+	std::shared_ptr<CApplier<CBaseForCLApply>> applier;
+
+	mutable boost::mutex pathCacheMutex;
+	std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
+
+	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
+
+	void waitForMoveAndSend(PlayerColor color);
+	void reinitScripting();
 };

+ 28 - 62
client/Graphics.cpp

@@ -10,6 +10,14 @@
 #include "StdInc.h"
 #include "Graphics.h"
 
+#include <vcmi/Entity.h>
+#include <vcmi/ArtifactService.h>
+#include <vcmi/CreatureService.h>
+#include <vcmi/FactionService.h>
+#include <vcmi/HeroTypeService.h>
+#include <vcmi/SkillService.h>
+#include <vcmi/spells/Service.h>
+
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CBinaryReader.h"
 #include "gui/SDL_Extensions.h"
@@ -19,19 +27,15 @@
 #include "CGameInfo.h"
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
-#include "../lib/CHeroHandler.h"
-#include "../lib/CTownHandler.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "../lib/CCreatureHandler.h"
 #include "CBitmapHandler.h"
-#include "../lib/CSkillHandler.h"
-#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CGameState.h"
 #include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/mapObjects/CObjectHandler.h"
+#include "../lib/CHeroHandler.h"
 
 using namespace CSDL_Ext;
 
@@ -163,9 +167,9 @@ void Graphics::load()
 
 void Graphics::loadHeroAnimations()
 {
-	for(auto & elem : CGI->heroh->classes.heroClasses)
+	for(auto & elem : CGI->heroh->classes.objects)
 	{
-		for (auto & templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->id)->getTemplates())
+		for (auto & templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->getIndex())->getTemplates())
 		{
 			if (!heroAnimations.count(templ.animationFile))
 				heroAnimations[templ.animationFile] = loadHeroAnimation(templ.animationFile);
@@ -423,74 +427,36 @@ void Graphics::loadErmuToPicture()
 	assert (etp_idx == 44);
 }
 
-void Graphics::addImageListEntry(size_t index, std::string listName, std::string imageName)
+void Graphics::addImageListEntry(size_t index, const std::string & listName, const std::string & imageName)
 {
 	if (!imageName.empty())
 	{
 		JsonNode entry;
-		entry["frame"].Float() = static_cast<double>(index);
+		entry["frame"].Integer() = index;
 		entry["file"].String() = imageName;
 
 		imageLists["SPRITES/" + listName]["images"].Vector().push_back(entry);
 	}
 }
 
-void Graphics::initializeImageLists()
+void Graphics::addImageListEntries(const EntityService * service)
 {
-	for(const CCreature * creature : CGI->creh->creatures)
-	{
-		addImageListEntry(creature->iconIndex, "CPRSMALL", creature->smallIconName);
-		addImageListEntry(creature->iconIndex, "TWCRPORT", creature->largeIconName);
-	}
+	auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3);
 
-	for(const CHero * hero : CGI->heroh->heroes)
+	auto loopCb = [&](const Entity * entity, bool & stop)
 	{
-		addImageListEntry(hero->imageIndex, "UN32", hero->iconSpecSmall);
-		addImageListEntry(hero->imageIndex, "UN44", hero->iconSpecLarge);
-		addImageListEntry(hero->imageIndex, "PORTRAITSLARGE", hero->portraitLarge);
-		addImageListEntry(hero->imageIndex, "PORTRAITSSMALL", hero->portraitSmall);
-	}
-
-	for(const CArtifact * art : CGI->arth->artifacts)
-	{
-		addImageListEntry(art->iconIndex, "ARTIFACT", art->image);
-		addImageListEntry(art->iconIndex, "ARTIFACTLARGE", art->large);
-	}
-
-	for(const CFaction * faction : CGI->townh->factions)
-	{
-		if (faction->town)
-		{
-			auto & info = faction->town->clientInfo;
-			addImageListEntry(info.icons[0][0], "ITPT", info.iconLarge[0][0]);
-			addImageListEntry(info.icons[0][1], "ITPT", info.iconLarge[0][1]);
-			addImageListEntry(info.icons[1][0], "ITPT", info.iconLarge[1][0]);
-			addImageListEntry(info.icons[1][1], "ITPT", info.iconLarge[1][1]);
-
-			addImageListEntry(info.icons[0][0] + 2, "ITPA", info.iconSmall[0][0]);
-			addImageListEntry(info.icons[0][1] + 2, "ITPA", info.iconSmall[0][1]);
-			addImageListEntry(info.icons[1][0] + 2, "ITPA", info.iconSmall[1][0]);
-			addImageListEntry(info.icons[1][1] + 2, "ITPA", info.iconSmall[1][1]);
-		}
-	}
+		entity->registerIcons(cb);
+	};
 
-	for(const CSpell * spell : CGI->spellh->objects)
-	{
-		addImageListEntry(spell->id, "SPELLS", spell->iconBook);
-		addImageListEntry(spell->id+1, "SPELLINT", spell->iconEffect);
-		addImageListEntry(spell->id, "SPELLBON", spell->iconScenarioBonus);
-		addImageListEntry(spell->id, "SPELLSCR", spell->iconScroll);
-	}
+	service->forEachBase(loopCb);
+}
 
-	for(const CSkill * skill : CGI->skillh->objects)
-	{
-		for(int level = 1; level <= 3; level++)
-		{
-			int frame = 2 + level + 3 * skill->id;
-			const CSkill::LevelInfo & skillAtLevel = skill->at(level);
-			addImageListEntry(frame, "SECSK32", skillAtLevel.iconSmall);
-			addImageListEntry(frame, "SECSKILL", skillAtLevel.iconMedium);
-			addImageListEntry(frame, "SECSK82", skillAtLevel.iconLarge);
-		}
-	}
+void Graphics::initializeImageLists()
+{
+	addImageListEntries(CGI->creatures());
+	addImageListEntries(CGI->heroTypes());
+	addImageListEntries(CGI->artifacts());
+	addImageListEntries(CGI->factions());
+	addImageListEntries(CGI->spells());
+	addImageListEntries(CGI->skills());
 }

+ 4 - 1
client/Graphics.h

@@ -23,6 +23,7 @@ struct InfoAboutTown;
 class CGObjectInstance;
 class ObjectTemplate;
 class CAnimation;
+class EntityService;
 
 enum EFonts
 {
@@ -32,7 +33,9 @@ enum EFonts
 /// Handles fonts, hero images, town images, various graphics
 class Graphics
 {
-	void addImageListEntry(size_t index, std::string listName, std::string imageName);
+	void addImageListEntry(size_t index, const std::string & listName, const std::string & imageName);
+
+	void addImageListEntries(const EntityService * service);
 
 	void initializeBattleGraphics();
 	void loadPaletteAndColors();

+ 19 - 27
client/NetPacksClient.cpp

@@ -45,12 +45,6 @@
 
 // TODO: as Tow suggested these template should all be part of CClient
 // This will require rework spectator interface properly though
-template<typename T, typename ... Args, typename ... Args2>
-void callPrivilegedInterfaces(CClient * cl, void (T::*ptr)(Args...), Args2 && ...args)
-{
-	for(auto &ger : cl->privilegedGameEventReceivers)
-		((*ger).*ptr)(std::forward<Args2>(args)...);
-}
 
 template<typename T, typename ... Args, typename ... Args2>
 bool callOnlyThatInterface(CClient * cl, PlayerColor player, void (T::*ptr)(Args...), Args2 && ...args)
@@ -67,7 +61,6 @@ template<typename T, typename ... Args, typename ... Args2>
 bool callInterfaceIfPresent(CClient * cl, PlayerColor player, void (T::*ptr)(Args...), Args2 && ...args)
 {
 	bool called = callOnlyThatInterface(cl, player, ptr, std::forward<Args2>(args)...);
-	callPrivilegedInterfaces(cl, ptr, std::forward<Args2>(args)...);
 	return called;
 }
 
@@ -84,18 +77,10 @@ void callOnlyThatBattleInterface(CClient * cl, PlayerColor player, void (T::*ptr
 	}
 }
 
-template<typename T, typename ... Args, typename ... Args2>
-void callPrivilegedBattleInterfaces(CClient * cl, void (T::*ptr)(Args...), Args2 && ...args)
-{
-	for(auto & ber : cl->privilegedBattleEventReceivers)
-		((*ber).*ptr)(std::forward<Args2>(args)...);
-}
-
 template<typename T, typename ... Args, typename ... Args2>
 void callBattleInterfaceIfPresent(CClient * cl, PlayerColor player, void (T::*ptr)(Args...), Args2 && ...args)
 {
 	callOnlyThatInterface(cl, player, ptr, std::forward<Args2>(args)...);
-	callPrivilegedBattleInterfaces(cl, ptr, std::forward<Args2>(args)...);
 }
 
 //calls all normal interfaces and privileged ones, playerints may be updated when iterating over it, so we need a copy
@@ -116,10 +101,8 @@ void callBattleInterfaceIfPresentForBothSides(CClient * cl, void (T::*ptr)(Args.
 	{
 		callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, ptr, std::forward<Args2>(args)...);
 	}
-	callPrivilegedBattleInterfaces(cl, ptr, std::forward<Args2>(args)...);
 }
 
-
 void SetResources::applyCl(CClient *cl)
 {
 	//todo: inform on actual resource set transfered
@@ -307,7 +290,7 @@ void GiveBonus::applyCl(CClient *cl)
 		break;
 	case PLAYER:
 		{
-			const PlayerState *p = GS(cl)->getPlayer(PlayerColor(id));
+			const PlayerState *p = GS(cl)->getPlayerState(PlayerColor(id));
 			callInterfaceIfPresent(cl, PlayerColor(id), &IGameEventsReceiver::playerBonusChanged, *p->getBonusList().back(), true);
 		}
 		break;
@@ -351,7 +334,7 @@ void RemoveBonus::applyCl(CClient *cl)
 		break;
 	case PLAYER:
 		{
-			//const PlayerState *p = GS(cl)->getPlayer(id);
+			//const PlayerState *p = GS(cl)->getPlayerState(id);
 			callInterfaceIfPresent(cl, PlayerColor(id), &IGameEventsReceiver::playerBonusChanged, bonus, false);
 		}
 		break;
@@ -370,7 +353,7 @@ void RemoveObject::applyFirstCl(CClient *cl)
 	{
 		//below line contains little cheat for AI so it will be aware of deletion of enemy heroes that moved or got re-covered by FoW
 		//TODO: loose requirements as next AI related crashes appear, for example another player collects object that got re-covered by FoW, unsure if AI code workarounds this
-		if(GS(cl)->isVisible(o, i->first) || (!cl->getPlayer(i->first)->human && o->ID == Obj::HERO && o->tempOwner != i->first))
+		if(GS(cl)->isVisible(o, i->first) || (!cl->getPlayerState(i->first)->human && o->ID == Obj::HERO && o->tempOwner != i->first))
 			i->second->objectRemoved(o);
 	}
 }
@@ -387,7 +370,7 @@ void TryMoveHero::applyFirstCl(CClient *cl)
 	//check if playerint will have the knowledge about movement - if not, directly update maphandler
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	{
-		auto ps = GS(cl)->getPlayer(i->first);
+		auto ps = GS(cl)->getPlayerState(i->first);
 		if(ps && (GS(cl)->isVisible(start - int3(1, 0, 0), i->first) || GS(cl)->isVisible(end - int3(1, 0, 0), i->first)))
 		{
 			if(ps->human)
@@ -611,8 +594,6 @@ void BattleStart::applyFirstCl(CClient *cl)
 		info->tile, info->sides[0].hero, info->sides[1].hero);
 	callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, &IBattleEventsReceiver::battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
 		info->tile, info->sides[0].hero, info->sides[1].hero);
-	callPrivilegedBattleInterfaces(cl, &IBattleEventsReceiver::battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
-		info->tile, info->sides[0].hero, info->sides[1].hero);
 }
 
 void BattleStart::applyCl(CClient *cl)
@@ -651,6 +632,11 @@ void BattleSetActiveStack::applyCl(CClient *cl)
 	cl->startPlayerBattleAction(playerToCall);
 }
 
+void BattleLogMessage::applyCl(CClient * cl)
+{
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleLogMessage, lines);
+}
+
 void BattleTriggerEffect::applyCl(CClient * cl)
 {
 	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, *this);
@@ -680,7 +666,7 @@ void BattleAttack::applyFirstCl(CClient *cl)
 
 void BattleAttack::applyCl(CClient *cl)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa, battleLog);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
 }
 
 void StartAction::applyFirstCl(CClient *cl)
@@ -702,7 +688,7 @@ void SetStackEffect::applyCl(CClient *cl)
 
 void StacksInjured::applyCl(CClient *cl)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks, battleLog);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks);
 }
 
 void BattleResultsApplied::applyCl(CClient *cl)
@@ -714,7 +700,7 @@ void BattleResultsApplied::applyCl(CClient *cl)
 
 void BattleUnitsChanged::applyCl(CClient * cl)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects, battleLog);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects);
 }
 
 void BattleObstaclesChanged::applyCl(CClient *cl)
@@ -795,7 +781,7 @@ void PlayerMessageClient::applyCl(CClient *cl)
 	if(player.isSpectator())
 		str << "Spectator: " << text;
 	else
-		str << cl->getPlayer(player)->nodeName() <<": " << text;
+		str << cl->getPlayerState(player)->nodeName() <<": " << text;
 	if(LOCPLINT)
 		LOCPLINT->cingconsole->print(str.str());
 }
@@ -918,3 +904,9 @@ void SetAvailableArtifacts::applyCl(CClient *cl)
 		callInterfaceIfPresent(cl, cl->getTile(bm->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::availableArtifactsChanged, bm);
 	}
 }
+
+
+void EntitiesChanged::applyCl(CClient *cl)
+{
+	cl->invalidatePaths();
+}

+ 3 - 6
client/VCMI_client.cbp

@@ -4,6 +4,7 @@
 	<Project>
 		<Option title="VCMI_client" />
 		<Option pch_mode="2" />
+		<Option default_target="Debug-win64" />
 		<Option compiler="gcc" />
 		<Build>
 			<Target title="Debug-win32">
@@ -13,7 +14,6 @@
 				<Option object_output="../obj/Client/Debug/x86" />
 				<Option type="1" />
 				<Option compiler="gcc" />
-				<Option parameters="--testmap=Maps/Reclamation --spectate --spectate-skip-battle --spectate-ignore-hero" />
 				<Compiler>
 					<Add option="-Og" />
 					<Add option="-g" />
@@ -91,7 +91,7 @@
 			<Add option="-isystem $(#boost.include)" />
 			<Add option="-DBOOST_ALL_DYN_LINK" />
 			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
-			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32_WINNT=0x0600" />
 			<Add option="-D_WIN32" />
 			<Add option="-DBOOST_UUID_RANDOM_PROVIDER_FORCE_WINCRYPT" />
 			<Add directory="../client" />
@@ -246,10 +246,6 @@
 		<Unit filename="windows/QuickRecruitmentWindow.cpp" />
 		<Unit filename="windows/QuickRecruitmentWindow.h" />
 		<Extensions>
-			<code_completion />
-			<envvars />
-			<debugger />
-			<lib_finder disable_auto="1" />
 			<DoxyBlocks>
 				<comment_style block="0" line="0" />
 				<doxyfile_project />
@@ -259,6 +255,7 @@
 				<doxyfile_dot />
 				<general />
 			</DoxyBlocks>
+			<lib_finder disable_auto="1" />
 		</Extensions>
 	</Project>
 </CodeBlocks_project_file>

+ 4 - 5
client/battle/CBattleAnimations.cpp

@@ -29,7 +29,6 @@
 #include "../../lib/CStack.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
-#include "../../lib/spells/CSpellHandler.h"
 
 CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
 	: owner(_owner), ID(_owner->animIDhelper++)
@@ -762,15 +761,15 @@ bool CShootingAnimation::init()
 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
 	{
 		int creID = owner->siegeH->town->town->clientInfo.siegeShooter;
-		shooterInfo = CGI->creh->creatures[creID];
+		shooterInfo = CGI->creh->operator[](creID);
 	}
 	if(!shooterInfo->animation.missleFrameAngles.size())
 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..."
 			, shooterInfo->nameSing);
-	
-	auto & angles = shooterInfo->animation.missleFrameAngles.size() 
+
+	auto & angles = shooterInfo->animation.missleFrameAngles.size()
 		? shooterInfo->animation.missleFrameAngles
-		: CGI->creh->creatures[CreatureID::ARCHER]->animation.missleFrameAngles;
+		: CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles;
 
 	ProjectileInfo spi;
 	spi.shotDone = false;

+ 75 - 91
client/battle/CBattleInterface.cpp

@@ -38,6 +38,8 @@
 #include "../../lib/CondSh.h"
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/spells/CSpellHandler.h"
+#include "../../lib/spells/ISpellMechanics.h"
+#include "../../lib/spells/Problem.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"
@@ -398,7 +400,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	for(auto hex : bfield)
 		addChild(hex.get());
 
-	if (tacticsMode)
+	if(tacticsMode)
 		bTacticNextStack();
 
 	CCS->musich->stopMusic();
@@ -415,7 +417,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	};
 
 	CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
-	memset(stackCountOutsideHexes, 1, GameConstants::BFIELD_SIZE *sizeof(bool)); //initialize array with trues
 
 	currentAction = PossiblePlayerBattleAction::INVALID;
 	selectedAction = PossiblePlayerBattleAction::INVALID;
@@ -579,7 +580,7 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
 		if(!battleActionsStarted)
 			CCS->soundh->stopSound(battleIntroSoundChannel);
 		else
-			endCastingSpell();
+		endCastingSpell();
 	}
 }
 void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
@@ -794,7 +795,7 @@ void CBattleInterface::bSurrenderf()
 	if (cost >= 0)
 	{
 		std::string enemyHeroName = curInt->cb->battleGetEnemyHero().name;
-		if(enemyHeroName.empty())
+		if (enemyHeroName.empty())
 		{
 			logGlobal->warn("Surrender performed without enemy hero, should not happen!");
 			enemyHeroName = "#ENEMY#";
@@ -871,7 +872,7 @@ void CBattleInterface::bAutofightf()
 		blockUI(true);
 
 		auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
-		ai->init(curInt->cb);
+		ai->init(curInt->env, curInt->cb);
 		ai->battleStart(army1, army2, int3(0,0,0), attackingHeroInstance, defendingHeroInstance, curInt->cb->battleGetMySide());
 		curInt->autofightingAI = ai;
 		curInt->cb->registerBattleInterface(ai);
@@ -910,14 +911,14 @@ void CBattleInterface::bSpellf()
 
 		if (blockingBonus->source == Bonus::ARTIFACT)
 		{
-			const int artID = blockingBonus->sid;
+			const int32_t artID = blockingBonus->sid;
 			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
 			//TODO check who *really* is source of bonus
 			std::string heroName = myHero->hasArt(artID) ? myHero->name : enemyHero().name;
 
 			//%s wields the %s, an ancient artifact which creates a p dead to all magic.
 			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
-										% heroName % CGI->arth->artifacts[artID]->Name()));
+										% heroName % CGI->artifacts()->getByIndex(artID)->getName()));
 		}
 	}
 }
@@ -964,7 +965,7 @@ void CBattleInterface::unitAdded(const CStack * stack)
 
 	if(stack->initialPosition < 0) //turret
 	{
-		const CCreature *turretCreature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter];
+		const CCreature *turretCreature = CGI->creh->objects[siegeH->town->town->clientInfo.siegeShooter];
 
 		creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
 
@@ -1012,7 +1013,7 @@ void CBattleInterface::initStackProjectile(const CStack * stack)
 {
 	const CCreature * creature;//creature whose shots should be loaded
 	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
-		creature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter];
+		creature = CGI->creh->objects[siegeH->town->town->clientInfo.siegeShooter];
 	else
 		creature = stack->getCreature();
 
@@ -1062,7 +1063,7 @@ void CBattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> de
 	waitForAnims();
 }
 
-void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos, const std::vector<MetaString> & battleLog)
+void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
 {
 	for(auto & attackedInfo : attackedInfos)
 	{
@@ -1089,8 +1090,6 @@ void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attacked
 		killedBySide.at(side) += attackedInfo.amountKilled;
 	}
 
-	int killed = killedBySide[0] + killedBySide[1];
-
 	for(ui8 side = 0; side < 2; side++)
 	{
 		if(killedBySide.at(side) > killedBySide.at(1-side))
@@ -1106,11 +1105,6 @@ void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attacked
 		if (attackedInfo.cloneKilled)
 			stackRemoved(attackedInfo.defender->ID);
 	}
-
-	if(!battleLog.empty())
-		displayBattleLog(battleLog);
-	else
-		printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, (targets > 1)); //creatures perish
 }
 
 void CBattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
@@ -1377,9 +1371,6 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc)
 			displayEffect(elem.effect, stack->getPosition());
 	}
 
-	//displaying message in console
-	displayBattleLog(sc->battleLog);
-
 	waitForAnims();
 	//mana absorption
 	if (sc->manaGained > 0)
@@ -1393,9 +1384,6 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc)
 
 void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
 {
-	for(const MetaString & line : sse.battleLog)
-		console->addText(line.toString());
-
 	if(activeStack != nullptr)
 		redrawBackgroundWithHexes(activeStack);
 }
@@ -1474,28 +1462,19 @@ void CBattleInterface::displayEffect(ui32 effect, BattleHex destTile)
 	addNewAnim(new CEffectAnimation(this, customAnim, destTile));
 }
 
-void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile)
-{
-	if (animation.pause > 0)
-	{
-		addNewAnim(new CDummyAnimation(this, animation.pause));
-	}
-	else
-	{
-		addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
-	}
-}
-
 void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
 {
-	const CSpell *spell = spellID.toSpell();
+	const CSpell * spell = spellID.toSpell();
 
-	if (spell == nullptr)
+	if(spell == nullptr)
 		return;
 
-	for (const CSpell::TAnimation & animation : spell->animationInfo.cast)
+	for(const CSpell::TAnimation & animation : spell->animationInfo.cast)
 	{
-		displaySpellAnimation(animation, destinationTile);
+		if(animation.pause > 0)
+			addNewAnim(new CDummyAnimation(this, animation.pause));
+		else
+			addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
 	}
 }
 
@@ -1503,25 +1482,32 @@ void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destination
 {
 	const CSpell *spell = spellID.toSpell();
 
-	if (spell == nullptr)
+	if(spell == nullptr)
 		return;
 
-	for (const CSpell::TAnimation & animation : spell->animationInfo.affect)
+	for(const CSpell::TAnimation & animation : spell->animationInfo.affect)
 	{
-		displaySpellAnimation(animation, destinationTile);
+		if(animation.pause > 0)
+			addNewAnim(new CDummyAnimation(this, animation.pause));
+		else
+			addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
+
 	}
 }
 
 void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
 {
-	const CSpell *spell = spellID.toSpell();
+	const CSpell * spell = spellID.toSpell();
 
-	if (spell == nullptr)
+	if(spell == nullptr)
 		return;
 
-	for (const CSpell::TAnimation & animation : spell->animationInfo.hit)
+	for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
 	{
-		displaySpellAnimation(animation, destinationTile);
+		if(animation.pause > 0)
+			addNewAnim(new CDummyAnimation(this, animation.pause));
+		else
+			addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
 	}
 }
 
@@ -1712,9 +1698,19 @@ void CBattleInterface::enterCreatureCastingMode()
 
 	if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
 	{
-		const spells::Caster *caster = activeStack;
-		const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
-		const bool isCastingPossible = spell->canBeCastAt(curInt->cb.get(), spells::Mode::CREATURE_ACTIVE, caster, BattleHex::INVALID);
+		const spells::Caster * caster = activeStack;
+		const CSpell * spell = SpellID(creatureSpellToCast).toSpell();
+
+		spells::Target target;
+		target.emplace_back();
+
+
+		spells::BattleCast cast(curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
+
+		auto m = spell->battleMechanics(&cast);
+		spells::detail::ProblemImpl ignored;
+
+		const bool isCastingPossible = m->canBeCastAt(ignored, target);
 
 		if (isCastingPossible)
 		{
@@ -1732,7 +1728,7 @@ void CBattleInterface::enterCreatureCastingMode()
 		auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
 		{
 			return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
-				(x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) && 
+				(x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
 				(x != PossiblePlayerBattleAction::OBSTACLE);
 		};
 
@@ -1766,7 +1762,7 @@ void CBattleInterface::reorderPossibleActionsPriority(const CStack * stack, Mous
 		case PossiblePlayerBattleAction::OBSTACLE:
 			if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
 				return 1;
-			else
+					else
 				return 100;//bottom priority
 			break;
 		case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
@@ -1784,9 +1780,9 @@ void CBattleInterface::reorderPossibleActionsPriority(const CStack * stack, Mous
 		case PossiblePlayerBattleAction::MOVE_STACK:
 			return 8; break;
 		case PossiblePlayerBattleAction::CATAPULT:
-			return 9; break;		
+			return 9; break;
 		case PossiblePlayerBattleAction::HEAL:
-			return 10; break;	
+			return 10; break;
 		default:
 			return 200; break;
 		}
@@ -1800,39 +1796,6 @@ void CBattleInterface::reorderPossibleActionsPriority(const CStack * stack, Mous
 	std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
 }
 
-void CBattleInterface::printConsoleAttacked(const CStack * defender, int dmg, int killed, const CStack * attacker, bool multiple)
-{
-	std::string formattedText;
-	if(attacker) //ignore if stacks were killed by spell
-	{
-		MetaString text;
-		attacker->addText(text, MetaString::GENERAL_TXT, 376);
-		attacker->addNameReplacement(text);
-		text.addReplacement(dmg);
-		formattedText = text.toString();
-	}
-
-	if(killed > 0)
-	{
-		if(attacker)
-			formattedText.append(" ");
-
-		boost::format txt;
-		if(killed > 1)
-		{
-			txt = boost::format(CGI->generaltexth->allTexts[379]) % killed % (multiple ? CGI->generaltexth->allTexts[43] : defender->getCreature()->namePl); // creatures perish
-		}
-		else //killed == 1
-		{
-			txt = boost::format(CGI->generaltexth->allTexts[378]) % (multiple ? CGI->generaltexth->allTexts[42] : defender->getCreature()->nameSing); // creature perishes
-		}
-		std::string trimmed = boost::to_string(txt);
-		boost::algorithm::trim(trimmed); // these default h3 texts have unnecessary new lines, so get rid of them before displaying
-		formattedText.append(trimmed);
-	}
-	console->addText(formattedText);
-}
-
 void CBattleInterface::endAction(const BattleAction* action)
 {
 	const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
@@ -2571,7 +2534,16 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack
 		else
 		{
 			const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
-			isCastingPossible = sp->canBeCastAt(curInt->cb.get(), mode, caster, myNumber);
+
+			spells::Target target;
+			target.emplace_back(myNumber);
+
+			spells::BattleCast cast(curInt->cb.get(), caster, mode, sp);
+
+			auto m = sp->battleMechanics(&cast);
+			spells::detail::ProblemImpl problem; //todo: display problem in status bar
+
+			isCastingPossible = m->canBeCastAt(problem, target);
 		}
 	}
 	else
@@ -3050,7 +3022,7 @@ void CBattleInterface::show(SDL_Surface *to)
 	showProjectiles(to);
 
 	if(battleActionsStarted)
-		updateBattleAnimations();
+	updateBattleAnimations();
 
 	SDL_SetClipRect(to, &buf); //restoring previous clip_rect
 
@@ -3064,7 +3036,7 @@ void CBattleInterface::show(SDL_Surface *to)
 		//we may have changed active interface (another side in hot-seat),
 		// so we can't continue drawing with old setting.
 		show(to);
-	}		
+	}
 }
 
 void CBattleInterface::showBackground(SDL_Surface *to)
@@ -3172,7 +3144,9 @@ void CBattleInterface::showHighlightedHexes(SDL_Surface *to)
 				if(caster && spell) //when casting spell
 				{
 					// printing shaded hex(es)
-					auto shaded = spell->rangeInHexes(curInt->cb.get(), mode, caster, currentlyHoveredHex);
+					spells::BattleCast event(curInt->cb.get(), caster, mode, spell);
+					auto shaded = spell->battleMechanics(&event)->rangeInHexes(currentlyHoveredHex);
+
 					for(BattleHex shadedHex : shaded)
 					{
 						if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
@@ -3691,7 +3665,17 @@ void CBattleInterface::redrawBackgroundWithHexes(const CStack *activeStack)
 	attackableHexes.clear();
 	if (activeStack)
 		occupyableHexes = curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
-	curInt->cb->battleGetStackCountOutsideHexes(stackCountOutsideHexes);
+
+	auto fillStackCountOutsideHexes = [&]()
+	{
+		auto accessibility = curInt->cb->getAccesibility();
+
+		for(int i = 0; i < accessibility.size(); i++)
+			stackCountOutsideHexes.at(i) = (accessibility[i] == EAccessibility::ACCESSIBLE);
+	};
+
+	fillStackCountOutsideHexes();
+
 	//prepare background graphic with hexes and shaded hexes
 	blitAt(background, 0, 0, backgroundWithHexes);
 

+ 6 - 7
client/battle/CBattleInterface.h

@@ -9,7 +9,9 @@
  */
 #pragma once
 
-#include "../../lib/ConstTransitivePtr.h" //may be reundant
+#include <vcmi/spells/Magic.h>
+
+#include "../../lib/ConstTransitivePtr.h" //may be redundant
 #include "../../lib/GameConstants.h"
 
 #include "CBattleAnimations.h"
@@ -50,6 +52,7 @@ class CBattleGameInterface;
 struct CustomEffectInfo;
 class CAnimation;
 class IImage;
+class CSpell;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -153,7 +156,7 @@ private:
 	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
 	std::vector<BattleHex> occupyableHexes, //hexes available for active stack
 		attackableHexes; //hexes attackable by active stack
-	bool stackCountOutsideHexes[GameConstants::BFIELD_SIZE]; // hexes that when in front of a unit cause it's amount box to move back
+	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes; // hexes that when in front of a unit cause it's amount box to move back
 	BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago
 	BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon)
 	int attackingHex; //hex from which the stack would perform attack with current cursor
@@ -187,8 +190,6 @@ private:
 	//force active stack to cast a spell if possible
 	void enterCreatureCastingMode();
 
-	void printConsoleAttacked(const CStack *defender, int dmg, int killed, const CStack *attacker, bool Multiple);
-
 	std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
 	void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
 	void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
@@ -340,7 +341,7 @@ public:
 	void stackActivated(const CStack *stack); //active stack has been changed
 	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
 	void waitForAnims();
-	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos, const std::vector<MetaString> & battleLog); //called when a certain amount of stacks has been attacked
+	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
 	void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
 	void newRoundFirst( int round );
 	void newRound(int number); //caled when round is ended; number is the number of round
@@ -361,8 +362,6 @@ public:
 	void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
 	void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
 
-	void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile);
-
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 	void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
 	void endAction(const BattleAction* action);

+ 3 - 3
client/battle/CBattleInterfaceClasses.cpp

@@ -448,8 +448,8 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
 
 			if(best != stacks.end()) //should be always but to be safe...
 			{
-				icons.push_back(std::make_shared<CAnimImage>("TWCRPORT", (*best)->type->idNumber+2, 0, xs[i], 38));
-				sideNames[i] = CGI->creh->creatures[(*best)->type->idNumber]->namePl;
+				icons.push_back(std::make_shared<CAnimImage>("TWCRPORT", (*best)->type->getIconIndex(), 0, xs[i], 38));
+				sideNames[i] = (*best)->type->getPluralName();
 			}
 		}
 	}
@@ -471,7 +471,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
 			int yPos = 344 + step * 97;
 			for(auto & elem : br.casualties[step])
 			{
-				icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", CGI->creh->creatures[elem.first]->iconIndex, 0, xPos, yPos));
+				icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", CGI->creatures()->getByIndex(elem.first)->getIconIndex(), 0, xPos, yPos));
 				std::ostringstream amount;
 				amount<<elem.second;
 				labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, CENTER, Colors::WHITE, amount.str()));

+ 5 - 5
client/gui/CGuiHandler.cpp

@@ -23,7 +23,7 @@
 #include "../CPlayerInterface.h"
 #include "../battle/CBattleInterface.h"
 
-extern std::queue<SDL_Event> events;
+extern std::queue<SDL_Event> SDLEventsQueue;
 extern boost::mutex eventsM;
 
 boost::thread_specific_ptr<bool> inGuiThread;
@@ -188,18 +188,18 @@ void CGuiHandler::handleEvents()
 		return;
 
 	boost::unique_lock<boost::mutex> lock(eventsM);
-	while(!events.empty())
+	while(!SDLEventsQueue.empty())
 	{
 		continueEventHandling = true;
-		SDL_Event ev = events.front();
+		SDL_Event ev = SDLEventsQueue.front();
 		current = &ev;
-		events.pop();
+		SDLEventsQueue.pop();
 
 		// In a sequence of mouse motion events, skip all but the last one.
 		// This prevents freezes when every motion event takes longer to handle than interval at which
 		// the events arrive (like dragging on the minimap in world view, with redraw at every event)
 		// so that the events would start piling up faster than they can be processed.
-		if ((ev.type == SDL_MOUSEMOTION) && !events.empty() && (events.front().type == SDL_MOUSEMOTION))
+		if ((ev.type == SDL_MOUSEMOTION) && !SDLEventsQueue.empty() && (SDLEventsQueue.front().type == SDL_MOUSEMOTION))
 			continue;
 
 		handleCurrentEvent();

+ 11 - 9
client/lobby/CBonusSelection.cpp

@@ -10,6 +10,10 @@
 #include "StdInc.h"
 
 #include "CBonusSelection.h"
+
+#include <vcmi/spells/Spell.h>
+#include <vcmi/spells/Service.h>
+
 #include "CSelectionBase.h"
 
 #include "../CGameInfo.h"
@@ -34,9 +38,7 @@
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/CGeneralTextHandler.h"
 
-#include "../../lib/CArtHandler.h"
 #include "../../lib/CBuildingHandler.h"
-#include "../../lib/spells/CSpellHandler.h"
 
 #include "../../lib/CSkillHandler.h"
 #include "../../lib/CTownHandler.h"
@@ -176,13 +178,13 @@ void CBonusSelection::createBonusesIcons()
 		{
 		case CScenarioTravel::STravelBonus::SPELL:
 			desc = CGI->generaltexth->allTexts[715];
-			boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
+			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getName());
 			break;
 		case CScenarioTravel::STravelBonus::MONSTER:
 			picNumber = bonDescs[i].info2 + 2;
 			desc = CGI->generaltexth->allTexts[717];
 			boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
-			boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
+			boost::algorithm::replace_first(desc, "%s", CGI->creatures()->getByIndex(bonDescs[i].info2)->getPluralName());
 			break;
 		case CScenarioTravel::STravelBonus::BUILDING:
 		{
@@ -202,18 +204,18 @@ void CBonusSelection::createBonusesIcons()
 			picName = graphics->ERMUtoPicture[faction][buildID];
 			picNumber = -1;
 
-			if(vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
-				desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
+			if(vstd::contains((*CGI->townh)[faction]->town->buildings, buildID))
+				desc = (*CGI->townh)[faction]->town->buildings.find(buildID)->second->Name();
 
 			break;
 		}
 		case CScenarioTravel::STravelBonus::ARTIFACT:
 			desc = CGI->generaltexth->allTexts[715];
-			boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
+			boost::algorithm::replace_first(desc, "%s", CGI->artifacts()->getByIndex(bonDescs[i].info2)->getName());
 			break;
 		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
 			desc = CGI->generaltexth->allTexts[716];
-			boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
+			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getName());
 			break;
 		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
 		{
@@ -318,7 +320,7 @@ void CBonusSelection::createBonusesIcons()
 			}
 			else
 			{
-				boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name);
+				boost::algorithm::replace_first(desc, "%s", CGI->heroh->objects[bonDescs[i].info2]->name);
 			}
 			break;
 		}

+ 21 - 22
client/lobby/OptionsTab.cpp

@@ -83,7 +83,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 		TOWN_RANDOM = 38,  TOWN_NONE = 39, // Special frames in ITPA
 		HERO_RANDOM = 163, HERO_NONE = 164 // Special frames in PortraitsSmall
 	};
-	auto factionIndex = settings.castle >= CGI->townh->factions.size() ? 0 : settings.castle;
+	auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle;
 
 	switch(type)
 	{
@@ -95,7 +95,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 		case PlayerSettings::RANDOM:
 			return TOWN_RANDOM;
 		default:
-			return CGI->townh->factions[factionIndex]->town->clientInfo.icons[true][false] + 2;
+			return (*CGI->townh)[factionIndex]->town->clientInfo.icons[true][false] + 2;
 		}
 	case HERO:
 		switch(settings.hero)
@@ -108,11 +108,10 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 		{
 			if(settings.heroPortrait >= 0)
 				return settings.heroPortrait;
-			auto index = settings.hero >= CGI->heroh->heroes.size() ? 0 : settings.hero;
-			return CGI->heroh->heroes[index]->imageIndex;
+			auto index = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
+			return (*CGI->heroh)[index]->imageIndex;
 		}
 		}
-
 	case BONUS:
 	{
 		switch(settings.bonus)
@@ -125,7 +124,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 			return GOLD;
 		case PlayerSettings::RESOURCE:
 		{
-			switch(CGI->townh->factions[factionIndex]->town->primaryRes)
+			switch((*CGI->townh)[factionIndex]->town->primaryRes)
 			{
 			case Res::WOOD_AND_ORE:
 				return WOOD_ORE;
@@ -181,11 +180,11 @@ std::string OptionsTab::CPlayerSettingsHelper::getName()
 			return CGI->generaltexth->allTexts[522];
 		default:
 		{
-			auto factionIndex = settings.castle >= CGI->townh->factions.size() ? 0 : settings.castle;
-			return CGI->townh->factions[factionIndex]->name;
-		}
+			auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle;
+			return (*CGI->townh)[factionIndex]->name;
 		}
 	}
+	}
 	case HERO:
 	{
 		switch(settings.hero)
@@ -198,8 +197,8 @@ std::string OptionsTab::CPlayerSettingsHelper::getName()
 		{
 			if(!settings.heroName.empty())
 				return settings.heroName;
-			auto index = settings.hero >= CGI->heroh->heroes.size() ? 0 : settings.hero;
-			return CGI->heroh->heroes[index]->name;
+			auto index = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
+			return (*CGI->heroh)[index]->name;
 		}
 		}
 	}
@@ -245,8 +244,8 @@ std::string OptionsTab::CPlayerSettingsHelper::getTitle()
 }
 std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
 {
-	auto factionIndex = settings.castle >= CGI->townh->factions.size() ? 0 : settings.castle;
-	auto heroIndex = settings.hero >= CGI->heroh->heroes.size() ? 0 : settings.hero;
+	auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle;
+	auto heroIndex = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
 
 	switch(type)
 	{
@@ -255,7 +254,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
 	case HERO:
 	{
 		if(settings.hero >= 0)
-			return getName() + " - " + CGI->heroh->heroes[heroIndex]->heroClass->name;
+			return getName() + " - " + (*CGI->heroh)[heroIndex]->heroClass->name;
 		return getName();
 	}
 
@@ -267,7 +266,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
 			return CGI->generaltexth->allTexts[87]; //500-1000
 		case PlayerSettings::RESOURCE:
 		{
-			switch(CGI->townh->factions[factionIndex]->town->primaryRes)
+			switch((*CGI->townh)[factionIndex]->town->primaryRes)
 			{
 			case Res::MERCURY:
 				return CGI->generaltexth->allTexts[694];
@@ -289,7 +288,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
 
 std::string OptionsTab::CPlayerSettingsHelper::getDescription()
 {
-	auto factionIndex = settings.castle >= CGI->townh->factions.size() ? 0 : settings.castle;
+	auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle;
 
 	switch(type)
 	{
@@ -309,7 +308,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getDescription()
 			return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
 		case PlayerSettings::RESOURCE:
 		{
-			switch(CGI->townh->factions[factionIndex]->town->primaryRes)
+			switch((*CGI->townh)[factionIndex]->town->primaryRes)
 			{
 			case Res::MERCURY:
 				return CGI->generaltexth->allTexts[690];
@@ -376,9 +375,9 @@ void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
 	pos = Rect(0, 0, 228, 290);
 	genHeader();
 	labelAssociatedCreatures = std::make_shared<CLabel>(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
-	auto factionIndex = settings.castle >= CGI->townh->factions.size() ? 0 : settings.castle;
+	auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle;
 	std::vector<std::shared_ptr<CComponent>> components;
-	const CTown * town = CGI->townh->factions[factionIndex]->town;
+	const CTown * town = (*CGI->townh)[factionIndex]->town;
 
 	for(auto & elem : town->creatures)
 	{
@@ -393,10 +392,10 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()
 	pos = Rect(0, 0, 292, 226);
 	genHeader();
 	labelHeroSpeciality = std::make_shared<CLabel>(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
-	auto heroIndex = settings.hero >= CGI->heroh->heroes.size() ? 0 : settings.hero;
+	auto heroIndex = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
 
-	imageSpeciality = std::make_shared<CAnimImage>("UN44", CGI->heroh->heroes[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134);
-	labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, CGI->heroh->heroes[heroIndex]->specName);
+	imageSpeciality = std::make_shared<CAnimImage>("UN44", (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134);
+	labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->specName);
 }
 
 void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()

+ 20 - 36
client/widgets/CArtifactHolder.cpp

@@ -25,7 +25,6 @@
 #include "../../CCallback.h"
 
 #include "../../lib/CArtHandler.h"
-#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -45,10 +44,11 @@ void CHeroArtPlace::createImage()
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 	si32 imageIndex = 0;
-	if(ourArt)
-		imageIndex = ourArt->artType->iconIndex;
+
 	if(locked)
 		imageIndex = ArtifactID::ART_LOCK;
+	else if(ourArt)
+		imageIndex = ourArt->artType->getIconIndex();
 
 	image = std::make_shared<CAnimImage>("artifact", imageIndex);
 	if(!ourArt)
@@ -68,7 +68,7 @@ void CHeroArtPlace::lockSlot(bool on)
 	if (on)
 		image->setFrame(ArtifactID::ART_LOCK);
 	else if (ourArt)
-		image->setFrame(ourArt->artType->iconIndex);
+		image->setFrame(ourArt->artType->getIconIndex());
 	else
 		image->setFrame(0);
 }
@@ -172,7 +172,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
 					else if(cur->isBig())
 					{
 						//war machines cannot go to backpack
-						LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name()));
+						LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->getName()));
 					}
 					else
 					{
@@ -197,18 +197,6 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
 				(!ourArt || ourOwner->curHero->tempOwner == LOCPLINT->playerID))
 			{
 				setMeAsDest();
-//
-// 				// Special case when the dest artifact can't be fit into the src slot.
-// 				//CGI->arth->unequipArtifact(ourOwner->curHero->artifWorn, slotID);
-// 				const CArtifactsOfHero* srcAOH = ourOwner->commonInfo->src.AOH;
-// 				ui16 srcSlotID = ourOwner->commonInfo->src.slotID;
-// 				if (ourArt && srcSlotID < 19 && !ourArt->canBePutAt(ArtifactLocation(srcAOH->curHero, srcSlotID)))
-// 				{
-// 					// Put dest artifact into owner's backpack.
-// 					ourOwner->commonInfo->src.AOH = ourOwner;
-// 					ourOwner->commonInfo->src.slotID = ourOwner->curHero->artifacts.size() + 19;
-// 				}
-
 				ourOwner->realizeCurrentTransaction();
 			}
 		}
@@ -226,14 +214,12 @@ bool CHeroArtPlace::askToAssemble(const CArtifactInstance *art, ArtifactPosition
 	for(const CArtifact *combination : assemblyPossibilities)
 	{
 		LOCPLINT->showArtifactAssemblyDialog(
-			art->artType->id,
-			combination->id,
-			true,
-			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->id),
-			0);
+			art->artType,
+			combination,
+			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->id));
 
 		if(assemblyPossibilities.size() > 2)
-			logGlobal->warn("More than one possibility of assembling on %s... taking only first", art->artType->Name());
+			logGlobal->warn("More than one possibility of assembling on %s... taking only first", art->artType->getName());
 		return true;
 	}
 	return false;
@@ -243,14 +229,14 @@ void CHeroArtPlace::clickRight(tribool down, bool previousState)
 {
 	if(ourArt && down && !locked && text.size() && !picked)  //if there is no description or it's a lock, do nothing ;]
 	{
-		if (slotID < GameConstants::BACKPACK_START)
+		if(slotID < GameConstants::BACKPACK_START)
 		{
 			if(ourOwner->allowedAssembling)
 			{
 				std::vector<const CArtifact *> assemblyPossibilities = ourArt->assemblyPossibilities(ourOwner->curHero);
 
 				// If the artifact can be assembled, display dialog.
-				if (askToAssemble(ourArt, slotID, ourOwner->curHero))
+				if(askToAssemble(ourArt, slotID, ourOwner->curHero))
 				{
 					return;
 				}
@@ -259,11 +245,9 @@ void CHeroArtPlace::clickRight(tribool down, bool previousState)
 				if(ourArt->canBeDisassembled())
 				{
 					LOCPLINT->showArtifactAssemblyDialog(
-						ourArt->artType->id,
-						0,
-						false,
-						std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, false, ArtifactID()),
-						0);
+						ourArt->artType,
+						nullptr,
+						std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, false, ArtifactID()));
 					return;
 				}
 			}
@@ -294,7 +278,7 @@ void CHeroArtPlace::select ()
 		}
 	}
 
-	CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", ourArt->artType->iconIndex));
+	CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", ourArt->artType->getIconIndex()));
 	ourOwner->commonInfo->src.setTo(this, false);
 	ourOwner->markPossibleSlots(ourArt);
 
@@ -389,7 +373,7 @@ void CHeroArtPlace::setArtifact(const CArtifactInstance *art)
 	}
 
 	image->enable();
-	image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->iconIndex);
+	image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->getIconIndex());
 
 	text = art->getEffectiveDescription(ourOwner->curHero);
 
@@ -414,7 +398,7 @@ void CHeroArtPlace::setArtifact(const CArtifactInstance *art)
 	if (locked) // Locks should appear as empty.
 		hoverText = CGI->generaltexth->allTexts[507];
 	else
-		hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->Name());
+		hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->getName());
 }
 
 void CArtifactsOfHero::SCommonPart::reset()
@@ -769,7 +753,7 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const Artifact
 			commonInfo->src.art = dst.getArt();
 			commonInfo->src.slotID = dst.slot;
 			assert(commonInfo->src.AOH);
-			CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", dst.getArt()->artType->iconIndex));
+			CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", dst.getArt()->artType->getIconIndex()));
 			markPossibleSlots(dst.getArt());
 		}
 	}
@@ -1020,7 +1004,7 @@ void CCommanderArtPlace::createImage()
 
 	int imageIndex = 0;
 	if(ourArt)
-		imageIndex = ourArt->artType->iconIndex;
+		imageIndex = ourArt->artType->getIconIndex();
 
 	image = std::make_shared<CAnimImage>("artifact", imageIndex);
 	if(!ourArt)
@@ -1055,7 +1039,7 @@ void CCommanderArtPlace::setArtifact(const CArtifactInstance * art)
 	}
 
 	image->enable();
-	image->setFrame(art->artType->iconIndex);
+	image->setFrame(art->artType->getIconIndex());
 
 	text = art->getEffectiveDescription();
 

+ 12 - 12
client/widgets/CComponent.cpp

@@ -112,7 +112,7 @@ const std::vector<std::string> CComponent::getFileName()
 	case spell:      return gen(spellsArr);
 	case morale:     return gen(moraleArr);
 	case luck:       return gen(luckArr);
-	case building:   return std::vector<std::string>(4, CGI->townh->factions[subtype]->town->clientInfo.buildingsIcons);
+	case building:   return std::vector<std::string>(4, (*CGI->townh)[subtype]->town->clientInfo.buildingsIcons);
 	case hero:       return gen(heroArr);
 	case flag:       return gen(flagArr);
 	}
@@ -127,8 +127,8 @@ size_t CComponent::getIndex()
 	case primskill:  return subtype;
 	case secskill:   return subtype*3 + 3 + val - 1;
 	case resource:   return subtype;
-	case creature:   return CGI->creh->creatures[subtype]->iconIndex;
-	case artifact:   return CGI->arth->artifacts[subtype]->iconIndex;
+	case creature:   return CGI->creatures()->getByIndex(subtype)->getIconIndex();
+	case artifact:   return CGI->artifacts()->getByIndex(subtype)->getIconIndex();
 	case experience: return 4;
 	case spell:      return subtype;
 	case morale:     return val+3;
@@ -159,15 +159,15 @@ std::string CComponent::getDescription()
 		}
 		else
 		{
-			art.reset(CArtifactInstance::createScroll(static_cast<SpellID>(val)));
+			art.reset(CArtifactInstance::createScroll(SpellID(val)));
 		}
 		return art->getEffectiveDescription();
 	}
 	case experience: return CGI->generaltexth->allTexts[241];
-	case spell:      return CGI->spellh->objects[subtype]->getLevelInfo(val).description;
+	case spell:      return CGI->spellh->objects[subtype]->getLevelDescription(val);
 	case morale:     return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
 	case luck:       return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
-	case building:   return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description();
+	case building:   return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->Description();
 	case hero:       return "";
 	case flag:       return "";
 	}
@@ -193,8 +193,8 @@ std::string CComponent::getSubtitleInternal()
 	case primskill:  return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387]));
 	case secskill:   return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->skillName(subtype);
 	case resource:   return boost::lexical_cast<std::string>(val);
-	case creature:   return (val? boost::lexical_cast<std::string>(val) + " " : "") + CGI->creh->creatures[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing);
-	case artifact:   return CGI->arth->artifacts[subtype]->Name();
+	case creature:   return (val? boost::lexical_cast<std::string>(val) + " " : "") + CGI->creh->objects[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing);
+	case artifact:   return CGI->artifacts()->getByIndex(subtype)->getName();
 	case experience:
 		{
 			if(subtype == 1) //+1 level - tree of knowledge
@@ -208,15 +208,15 @@ std::string CComponent::getSubtitleInternal()
 				return boost::lexical_cast<std::string>(val); //amount of experience OR level required for seer hut;
 			}
 		}
-	case spell:      return CGI->spellh->objects[subtype]->name;
+	case spell:      return CGI->spells()->getByIndex(subtype)->getName();
 	case morale:     return "";
 	case luck:       return "";
 	case building:
 		{
-			auto building = CGI->townh->factions[subtype]->town->buildings[BuildingID(val)];
+			auto building = (*CGI->townh)[subtype]->town->buildings[BuildingID(val)];
 			if(!building)
 			{
-				logGlobal->error("Town of faction %s has no building #%d", CGI->townh->factions[subtype]->town->faction->name, val);
+				logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[subtype]->town->faction->name, val);
 				return (boost::format("Missing building #%d") % val).str();
 			}
 			return building->Name();
@@ -224,7 +224,7 @@ std::string CComponent::getSubtitleInternal()
 	case hero:       return "";
 	case flag:       return CGI->generaltexth->capColors[subtype];
 	}
-	assert(0);
+	logGlobal->error("Invalid CComponent type: %d", (int)compType);
 	return "";
 }
 

+ 15 - 15
client/widgets/CGarrisonInt.cpp

@@ -355,7 +355,7 @@ void CGarrisonSlot::update()
 	if(creature)
 	{
 		creatureImage->enable();
-		creatureImage->setFrame(creature->iconIndex);
+		creatureImage->setFrame(creature->getIconIndex());
 
 		stackCount->enable();
 		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
@@ -367,12 +367,12 @@ void CGarrisonSlot::update()
 	}
 }
 
-CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGarrisonSlot::EGarrisonType Upg, const CStackInstance * Creature)
-	: ID(IID),
-	owner(Owner),
-	myStack(Creature),
-	creature(nullptr),
-	upg(Upg)
+CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGarrisonSlot::EGarrisonType Upg, const CStackInstance * creature_)
+    : ID(IID),
+    owner(Owner),
+    myStack(creature_),
+    creature(creature_ ? creature_->type : nullptr),
+    upg(Upg)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
@@ -382,7 +382,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGa
 	std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT";
 
 	creatureImage =  std::make_shared<CAnimImage>(imgName, 0);
-	creatureImage->disable();
+		creatureImage->disable();
 
 	selectionImage = std::make_shared<CAnimImage>(imgName, 1);
 	selectionImage->disable();
@@ -496,13 +496,13 @@ CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset,
 		const CArmedInstance * s1, const CArmedInstance * s2,
 		bool _removableUnits, bool smallImgs, bool _twoRows)
 	: highlighted(nullptr),
-	inSplittingMode(false),
-	interx(inx),
-	garOffset(garsOffset),
-	pb(false),
-	smallIcons(smallImgs),
-	removableUnits(_removableUnits),
-	twoRows(_twoRows)
+    inSplittingMode(false),
+    interx(inx),
+    garOffset(garsOffset),
+    pb(false),
+    smallIcons(smallImgs),
+    removableUnits(_removableUnits),
+    twoRows(_twoRows)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 

+ 1 - 1
client/widgets/CGarrisonInt.h

@@ -53,7 +53,7 @@ public:
 	void clickRight(tribool down, bool previousState) override;
 	void clickLeft(tribool down, bool previousState) override;
 	void update();
-	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, EGarrisonType Upg=EGarrisonType::UP, const CStackInstance * Creature=nullptr);
+	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, EGarrisonType Upg=EGarrisonType::UP, const CStackInstance * creature_ = nullptr);
 
 	void splitIntoParts(EGarrisonType type, int amount, int maxOfSplittedSlots);
 	void handleSplittingShortcuts();

+ 4 - 4
client/widgets/MiscWidgets.cpp

@@ -243,7 +243,7 @@ void CArmyTooltip::init(const InfoAboutArmy &army)
 			continue;
 		}
 
-		icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", slot.second.type->iconIndex, 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y));
+		icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", slot.second.type->getIconIndex(), 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y));
 
 		std::string subtitle;
 		if(army.army.isDetailed)
@@ -443,12 +443,12 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A
 
 	TFaction faction = cre->faction;
 
-	assert(CGI->townh->factions.size() > faction);
+	assert(CGI->townh->size() > faction);
 
 	if(Big)
-		bg = std::make_shared<CPicture>(CGI->townh->factions[faction]->creatureBg130);
+		bg = std::make_shared<CPicture>((*CGI->townh)[faction]->creatureBg130);
 	else
-		bg = std::make_shared<CPicture>(CGI->townh->factions[faction]->creatureBg120);
+		bg = std::make_shared<CPicture>((*CGI->townh)[faction]->creatureBg120);
 	anim = std::make_shared<CCreatureAnim>(0, 0, cre->animDefName);
 	anim->clipRect(cre->isDoubleWide()?170:150, 155, bg->pos.w, bg->pos.h);
 	anim->startPreview(cre->hasBonusOfType(Bonus::SIEGE_WEAPON));

+ 25 - 23
client/windows/CCastleInterface.cpp

@@ -112,11 +112,11 @@ void CBuildingRect::hover(bool on)
 void CBuildingRect::clickLeft(tribool down, bool previousState)
 {
 	if( previousState && getBuilding() && area && !down && (parent->selectedBuilding==this))
-		if (!CSDL_Ext::isTransparent(area, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y)) //inside building image
+		if (!CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) //inside building image
 		{
 			auto building = getBuilding();
 			parent->buildingClicked(building->bid, building->subId, building->upgrade);
-		}
+}
 }
 
 void CBuildingRect::clickRight(tribool down, bool previousState)
@@ -231,7 +231,7 @@ std::string CBuildingRect::getSubtitle()//hover text for building
 		if(availableCreatures.size())
 		{
 			int creaID = availableCreatures.back();//taking last of available creatures
-			return CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures.at(creaID)->namePl;
+			return CGI->generaltexth->allTexts[16] + " " + CGI->creh->objects.at(creaID)->namePl;
 		}
 		else
 		{
@@ -271,7 +271,7 @@ CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstanc
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	background->colorize(Town->tempOwner);
 
-	const CCreature * creature = CGI->creh->creatures.at(Town->creatures.at(level).second.back());
+	const CCreature * creature = CGI->creh->objects.at(Town->creatures.at(level).second.back());
 
 	title = std::make_shared<CLabel>(80, 30, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
 	animation = std::make_shared<CCreaturePic>(30, 44, creature, true, true);
@@ -607,7 +607,7 @@ void CCastleBuildings::recreate()
 		const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
 		{
 			return build->getDistance(a->building->bid)
-				 < build->getDistance(b->building->bid);
+			     < build->getDistance(b->building->bid);
 		});
 
 		buildings.push_back(std::make_shared<CBuildingRect>(this, town, toAdd));
@@ -731,7 +731,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
 
 				case BuildingSubID::FOUNTAIN_OF_FORTUNE:
 						enterFountain(building, subID, upgrades);
-						break;
+					break;
 
 				case BuildingSubID::FREELANCERS_GUILD:
 						if(getHero())
@@ -751,7 +751,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
 						if(upgrades == BuildingID::TAVERN)
 							LOCPLINT->showTavernWindow(town);
 						else
-							enterBuilding(building);
+						enterBuilding(building);
 						break;
 
 				case BuildingSubID::CASTLE_GATE:
@@ -794,9 +794,11 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
 		LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(BuildingID::BLACKSMITH)->second->Name()));
 		return;
 	}
-	int price = CGI->arth->artifacts[artifactID]->price;
+	auto art = artifactID.toArtifact(CGI->artifacts());
+
+	int price = art->getPrice();
 	bool possible = LOCPLINT->cb->getResourceAmount(Res::GOLD) >= price && !hero->hasArt(artifactID);
-	CreatureID cre = artifactID.toArtifact()->warMachine;
+	CreatureID cre = art->getWarMachine();
 	GH.pushIntT<CBlacksmithDialog>(possible, cre, artifactID, hero->id);
 }
 
@@ -844,7 +846,7 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
 
 void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades)
 {
-	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building,town->subID, building));
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building,town->subID,building));
 	std::string descr = town->town->buildings.find(building)->second->Description();
 	std::string hasNotProduced;
 	std::string hasProduced;
@@ -878,9 +880,9 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID:
 		else //Mystic Pond produced something;
 		{
 			descr += "\n\n" + hasProduced;
-			boost::algorithm::replace_first(descr, "%s", CGI->generaltexth->restypes[town->bonusValue.first]);
-			boost::algorithm::replace_first(descr, "%d", boost::lexical_cast<std::string>(town->bonusValue.second));
-		}
+		boost::algorithm::replace_first(descr,"%s",CGI->generaltexth->restypes[town->bonusValue.first]);
+		boost::algorithm::replace_first(descr,"%d",boost::lexical_cast<std::string>(town->bonusValue.second));
+	}
 	}
 	LOCPLINT->showInfoDialog(descr, comps);
 }
@@ -972,9 +974,9 @@ CCreaInfo::CCreaInfo(Point position, const CGTownInstance * Town, int Level, boo
 	addUsedEvents(LCLICK | RCLICK | HOVER);
 
 	ui32 creatureID = town->creatures[level].second.back();
-	creature = CGI->creh->creatures[creatureID];
+	creature = CGI->creh->objects[creatureID];
 
-	picture = std::make_shared<CAnimImage>("CPRSMALL", creature->iconIndex, 0, 8, 0);
+	picture = std::make_shared<CAnimImage>("CPRSMALL", creature->getIconIndex(), 0, 8, 0);
 
 	std::string value;
 	if(showAvailable)
@@ -1627,7 +1629,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 		Rect sizes(287, 4, 96, 18);
 		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->getAttack(false)));
 		sizes.y+=20;
-		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->getDefence(false)));
+		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->getDefense(false)));
 		sizes.y+=21;
 		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(false), getMyCreature()->getMaxDamage(false)));
 		sizes.y+=20;
@@ -1642,9 +1644,9 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 const CCreature * CFortScreen::RecruitArea::getMyCreature()
 {
 	if(!town->creatures.at(level).second.empty()) // built
-		return VLC->creh->creatures[town->creatures.at(level).second.back()];
+		return VLC->creh->objects[town->creatures.at(level).second.back()];
 	if(!town->town->creatures.at(level).empty()) // there are creatures on this level
-		return VLC->creh->creatures[town->town->creatures.at(level).front()];
+		return VLC->creh->objects[town->town->creatures.at(level).front()];
 	return nullptr;
 }
 
@@ -1750,13 +1752,13 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
 void CMageGuildScreen::Scroll::clickLeft(tribool down, bool previousState)
 {
 	if(down)
-		LOCPLINT->showInfoDialog(spell->getLevelInfo(0).description, std::make_shared<CComponent>(CComponent::spell, spell->id));
+		LOCPLINT->showInfoDialog(spell->getLevelDescription(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
 }
 
 void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState)
 {
 	if(down)
-		CRClickPopup::createAndPush(spell->getLevelInfo(0).description, std::make_shared<CComponent>(CComponent::spell, spell->id));
+		CRClickPopup::createAndPush(spell->getLevelDescription(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
 }
 
 void CMageGuildScreen::Scroll::hover(bool on)
@@ -1781,15 +1783,15 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art
 	animBG = std::make_shared<CPicture>("TPSMITBK", 64, 50);
 	animBG->needRefresh = true;
 
-	const CCreature * creature = CGI->creh->creatures[creMachineID];
+	const CCreature * creature = CGI->creh->objects[creMachineID];
 	anim = std::make_shared<CCreatureAnim>(64, 50, creature->animDefName);
 	anim->clipRect(113,125,200,150);
 
 	title = std::make_shared<CLabel>(165, 28, FONT_BIG, CENTER, Colors::YELLOW,
-				boost::str(boost::format(CGI->generaltexth->allTexts[274]) % creature->nameSing));
+	            boost::str(boost::format(CGI->generaltexth->allTexts[274]) % creature->nameSing));
 	costText = std::make_shared<CLabel>(165, 218, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->jktexts[43]);
 	costValue = std::make_shared<CLabel>(165, 290, FONT_MEDIUM, CENTER, Colors::WHITE,
-					boost::lexical_cast<std::string>(CGI->arth->artifacts[aid]->price));
+	                boost::lexical_cast<std::string>(aid.toArtifact(CGI->artifacts())->getPrice()));
 
 	std::string text = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % creature->nameSing);
 	buy = std::make_shared<CButton>(Point(42, 312), "IBUY30.DEF", CButton::tooltip(text), [&](){ close(); }, SDLK_RETURN);

+ 8 - 6
client/windows/CCreatureWindow.cpp

@@ -10,6 +10,9 @@
 #include "StdInc.h"
 #include "CCreatureWindow.h"
 
+#include <vcmi/spells/Spell.h>
+#include <vcmi/spells/Service.h>
+
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../widgets/Buttons.h"
@@ -26,7 +29,6 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CGameState.h"
 
 using namespace CSDL_Ext;
@@ -193,7 +195,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 	std::vector<si32> spells = battleStack->activeSpells();
 	for(si32 effect : spells)
 	{
-		const CSpell * sp = CGI->spellh->objects[effect];
+		const spells::Spell * spell = CGI->spells()->getByIndex(effect);
 
 		std::string spellText;
 
@@ -204,7 +206,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 		if (hasGraphics)
 		{
 			spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
-			boost::replace_first(spellText, "%s", sp->name);
+			boost::replace_first(spellText, "%s", spell->getName());
 			//FIXME: support permanent duration
 			int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
 			boost::replace_first(spellText, "%d", boost::lexical_cast<std::string>(duration));
@@ -320,7 +322,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
 			};
 			auto upgradeBtn = std::make_shared<CButton>(Point(221 + (int)buttonIndex * 40, 5), "stackWindow/upgradeButton", CGI->generaltexth->zelp[446], onClick, SDLK_1);
 
-			upgradeBtn->addOverlay(std::make_shared<CAnimImage>("CPRSMALL", VLC->creh->creatures[upgradeInfo.info.newID[buttonIndex]]->iconIndex));
+			upgradeBtn->addOverlay(std::make_shared<CAnimImage>("CPRSMALL", VLC->creh->objects[upgradeInfo.info.newID[buttonIndex]]->iconIndex));
 
 			upgrade[buttonIndex] = upgradeBtn;
 		}
@@ -520,7 +522,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 	if(battleStack != nullptr) // in battle
 	{
 		addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(battleStack->isShooter()), battleStack->getAttack(battleStack->isShooter()));
-		addStatLabel(EStat::DEFENCE, parent->info->creature->getDefence(battleStack->isShooter()), battleStack->getDefence(battleStack->isShooter()));
+		addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(battleStack->isShooter()), battleStack->getDefense(battleStack->isShooter()));
 		addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply);
 		addStatLabel(EStat::HEALTH, parent->info->creature->MaxHealth(), battleStack->MaxHealth());
 		addStatLabel(EStat::SPEED, parent->info->creature->Speed(), battleStack->Speed());
@@ -540,7 +542,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		const bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
 
 		addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter));
-		addStatLabel(EStat::DEFENCE, parent->info->creature->getDefence(shooter), parent->info->stackNode->getDefence(shooter));
+		addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter));
 		addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply);
 		addStatLabel(EStat::HEALTH, parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth());
 		addStatLabel(EStat::SPEED, parent->info->creature->Speed(), parent->info->stackNode->Speed());

+ 0 - 1
client/windows/CHeroWindow.h

@@ -16,7 +16,6 @@
 class CButton;
 struct SDL_Surface;
 class CGHeroInstance;
-class CArtifact;
 class CHeroWindow;
 class LClickableAreaHero;
 class LRClickableAreaWText;

+ 3 - 3
client/windows/CKingdomInterface.cpp

@@ -163,7 +163,7 @@ std::string InfoBoxAbstractHeroData::getNameText()
 	case HERO_EXPERIENCE:
 		return CGI->generaltexth->jktexts[6];
 	case HERO_SPECIAL:
-		return CGI->heroh->heroes[getSubID()]->specName;
+		return CGI->heroh->objects[getSubID()]->specName;
 	case HERO_SECONDARY_SKILL:
 		if (getValue())
 			return CGI->skillh->skillName(getSubID());
@@ -229,7 +229,7 @@ size_t InfoBoxAbstractHeroData::getImageIndex()
 	switch (type)
 	{
 	case HERO_SPECIAL:
-		return VLC->heroh->heroes[getSubID()]->imageIndex;
+		return CGI->heroh->objects[getSubID()]->imageIndex;
 	case HERO_PRIMARY_SKILL:
 		return getSubID();
 	case HERO_MANA:
@@ -256,7 +256,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr
 	switch (type)
 	{
 	case HERO_SPECIAL:
-		text = CGI->heroh->heroes[getSubID()]->specDescr;
+		text = CGI->heroh->objects[getSubID()]->specDescr;
 		break;
 	case HERO_PRIMARY_SKILL:
 		text = CGI->generaltexth->arraytxt[2+getSubID()];

+ 8 - 8
client/windows/CSpellWindow.cpp

@@ -120,7 +120,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
 
 	for(const auto spell : mySpells)
 	{
-		int * sitesPerOurTab = spell->isCombatSpell() ? sitesPerTabBattle : sitesPerTabAdv;
+		int * sitesPerOurTab = spell->isCombat() ? sitesPerTabBattle : sitesPerTabAdv;
 
 		++sitesPerOurTab[4];
 
@@ -332,7 +332,7 @@ void CSpellWindow::computeSpellsPerArea()
 	spellsCurSite.reserve(mySpells.size());
 	for(const CSpell * spell : mySpells)
 	{
-		if(spell->isCombatSpell() ^ !battleSpellsOnly
+		if(spell->isCombat() ^ !battleSpellsOnly
 			&& ((selectedTab == 4) || spell->school.at((ESpellSchool)selectedTab))
 			)
 		{
@@ -522,7 +522,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 {
 	if(mySpell && !down)
 	{
-		int spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero);
+		auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero);
 		if(spellCost > owner->myHero->mana) //insufficient mana
 		{
 			owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana));
@@ -531,8 +531,8 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 
 		//anything that is not combat spell is adventure spell
 		//this not an error in general to cast even creature ability with hero
-		const bool combatSpell = mySpell->isCombatSpell();
-		if(mySpell->isCombatSpell() != !mySpell->isAdventureSpell())
+		const bool combatSpell = mySpell->isCombat();
+		if(combatSpell == mySpell->isAdventure())
 		{
 			logGlobal->error("Spell have invalid flags");
 			return;
@@ -545,7 +545,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 		if((combatSpell ^ inCombat) || inCastle)
 		{
 			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(CComponent::spell, mySpell->id, 0));
-			owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
+			owner->myInt->showInfoDialog(mySpell->getLevelDescription(schoolLevel), hlp);
 		}
 		else if(combatSpell)
 		{
@@ -600,7 +600,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
 			boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(causedDmg));
 		}
 
-		CRClickPopup::createAndPush(mySpell->getLevelInfo(schoolLevel).description + dmgInfo, std::make_shared<CComponent>(CComponent::spell, mySpell->id));
+		CRClickPopup::createAndPush(mySpell->getLevelDescription(schoolLevel) + dmgInfo, std::make_shared<CComponent>(CComponent::spell, mySpell->id));
 	}
 }
 
@@ -625,7 +625,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
 	mySpell = spell;
 	if(mySpell)
 	{
-		int whichSchool = 0; //0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
+		int32_t whichSchool = 0; //0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
 		schoolLevel = owner->myHero->getSpellSchoolLevel(mySpell, &whichSchool);
 		auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero);
 

+ 13 - 13
client/windows/CTradeWindow.cpp

@@ -114,9 +114,9 @@ int CTradeWindow::CTradeableItem::getIndex()
 	case ARTIFACT_TYPE:
 	case ARTIFACT_INSTANCE:
 	case ARTIFACT_PLACEHOLDER:
-		return VLC->arth->artifacts[id]->iconIndex;
+		return CGI->artifacts()->getByIndex(id)->getIconIndex();
 	case CREATURE:
-		return VLC->creh->creatures[id]->iconIndex;
+		return CGI->creatures()->getByIndex(id)->getIconIndex();
 	default:
 		return -1;
 	}
@@ -244,13 +244,13 @@ void CTradeWindow::CTradeableItem::hover(bool on)
 	{
 	case CREATURE:
 	case CREATURE_PLACEHOLDER:
-		GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl));
+		GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
 		break;
 	case ARTIFACT_PLACEHOLDER:
 		if(id < 0)
 			GH.statusbar->setText(CGI->generaltexth->zelp[582].first);
 		else
-			GH.statusbar->setText(CGI->arth->artifacts[id]->Name());
+			GH.statusbar->setText(CGI->artifacts()->getByIndex(id)->getName());
 		break;
 	}
 }
@@ -263,13 +263,13 @@ void CTradeWindow::CTradeableItem::clickRight(tribool down, bool previousState)
 		{
 		case CREATURE:
 		case CREATURE_PLACEHOLDER:
-			//GH.statusbar->print(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl));
+			//GH.statusbar->print(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
 			break;
 		case ARTIFACT_TYPE:
 		case ARTIFACT_PLACEHOLDER:
 			//TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription.
 			if(id >= 0)
-				adventureInt->handleRightClick(CGI->arth->artifacts[id]->Description(), down);
+				adventureInt->handleRightClick(CGI->artifacts()->getByIndex(id)->getDescription(), down);
 			break;
 		}
 	}
@@ -285,14 +285,14 @@ std::string CTradeWindow::CTradeableItem::getName(int number) const
 		return CGI->generaltexth->restypes[id];
 	case CREATURE:
 		if(number == 1)
-			return CGI->creh->creatures[id]->nameSing;
+			return CGI->creh->objects[id]->nameSing;
 		else
-			return CGI->creh->creatures[id]->namePl;
+			return CGI->creh->objects[id]->namePl;
 	case ARTIFACT_TYPE:
 	case ARTIFACT_INSTANCE:
-		return CGI->arth->artifacts[id]->Name();
+		return CGI->artifacts()->getByIndex(id)->getName();
 	}
-	assert(0);
+	logGlobal->error("Invalid trade item type: %d", (int)type);
 	return "";
 }
 
@@ -668,14 +668,14 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 		switch (mode)
 		{
 		case EMarketMode::CREATURE_RESOURCE:
-			title = CGI->townh->factions[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->Name();
+			title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->Name();
 			break;
 		case EMarketMode::RESOURCE_ARTIFACT:
-			title = CGI->townh->factions[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name();
+			title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name();
 			sliderNeeded = false;
 			break;
 		case EMarketMode::ARTIFACT_RESOURCE:
-			title = CGI->townh->factions[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name();
+			title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name();
 			sliderNeeded = false;
 			break;
 		default:

+ 5 - 5
client/windows/GUIClasses.cpp

@@ -159,7 +159,7 @@ void CRecruitmentWindow::buy()
 		if(dst->ID == Obj::HERO)
 		{
 			txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them.
-			boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->creatures[crid]->namePl : CGI->creh->creatures[crid]->nameSing);
+			boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->namePl : CGI->creh->objects[crid]->nameSing);
 		}
 		else
 		{
@@ -249,7 +249,7 @@ void CRecruitmentWindow::availableCreaturesChanged()
 
 		//create new cards
 		for(auto & creature : boost::adaptors::reverse(dwelling->creatures[i].second))
-			cards.push_back(std::make_shared<CCreatureCard>(this, CGI->creh->creatures[creature], amount));
+			cards.push_back(std::make_shared<CCreatureCard>(this, CGI->creh->objects[creature], amount));
 	}
 
 	const int creatureWidth = 102;
@@ -1040,7 +1040,7 @@ CPuzzleWindow::CPuzzleWindow(const int3 & GrailPos, double discoveredRatio)
 
 	int faction = LOCPLINT->cb->getStartInfo()->playerInfos.find(LOCPLINT->playerID)->second.castle;
 
-	auto & puzzleMap = CGI->townh->factions[faction]->puzzleMap;
+	auto & puzzleMap = (*CGI->townh)[faction]->puzzleMap;
 
 	for(auto & elem : puzzleMap)
 	{
@@ -1276,10 +1276,10 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket
 		{
 			auto faction = town->town->faction->index;
 			auto bid = town->town->getSpecialBuilding(BuildingSubID::MAGIC_UNIVERSITY)->bid;
-			titlePic = std::make_shared<CAnimImage>(CGI->townh->factions[faction]->town->clientInfo.buildingsIcons, bid);
+			titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, bid);
 		}
 		else
-			titlePic = std::make_shared<CAnimImage>(CGI->townh->factions[ETownType::CONFLUX]->town->clientInfo.buildingsIcons, BuildingID::MAGIC_UNIVERSITY);
+			titlePic = std::make_shared<CAnimImage>((*CGI->townh)[ETownType::CONFLUX]->town->clientInfo.buildingsIcons, BuildingID::MAGIC_UNIVERSITY);
 	}
 	else
 		titlePic = std::make_shared<CPicture>("UNIVBLDG");

+ 50 - 0
cmake_modules/FindLuaJIT.cmake

@@ -0,0 +1,50 @@
+# Locate LuaJIT library
+# This module defines
+#  LUAJIT_FOUND, if false, do not try to link to Lua
+#  LUA_INCLUDE_DIR, where to find lua.h
+#  LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
+#
+# This module is similar to FindLua51.cmake except that it finds LuaJit instead.
+
+FIND_PATH(LUA_INCLUDE_DIR luajit.h
+	HINTS
+	$ENV{LUA_DIR}
+	PATH_SUFFIXES include/luajit include/luajit-2.1 include/luajit-2.0 include/luajit-5_1-2.1 include/luajit-5_1-2.0 include
+	PATHS
+	~/Library/Frameworks
+	/Library/Frameworks
+	/sw # Fink
+	/opt/local # DarwinPorts
+	/opt/csw # Blastwave
+	/opt
+)
+
+FIND_LIBRARY(LUA_LIBRARY
+	NAMES luajit-5.1 lua51
+	HINTS
+	$ENV{LUA_DIR}
+	PATH_SUFFIXES lib64 lib
+	PATHS
+	~/Library/Frameworks
+	/Library/Frameworks
+	/sw
+	/opt/local
+	/opt/csw
+	/opt
+)
+
+IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/luajit.h")
+	FILE(STRINGS "${LUA_INCLUDE_DIR}/luajit.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"LuaJIT .+\"")
+
+	STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"LuaJIT ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
+	UNSET(lua_version_str)
+ENDIF()
+
+INCLUDE(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LUAJIT_FOUND to TRUE if
+# all listed variables are TRUE
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJit
+	REQUIRED_VARS LUA_LIBRARY LUA_INCLUDE_DIR
+	VERSION_VAR LUA_VERSION_STRING)
+
+MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARY LUA_MATH_LIBRARY)

+ 4 - 0
config/filesystem.json

@@ -48,6 +48,10 @@
 		"MODS/":
 		[
 			{"type" : "dir",  "path" : "Mods", "depth": 1}
+		],
+		"SCRIPTS/":
+		[
+			{"type" : "dir",  "path" : "scripts"}
 		]
 	}
 }

+ 6 - 1
config/schemas/mod.json

@@ -92,6 +92,11 @@
 			"description": "List of configuration files for objects",
 			"items": { "type":"string", "format" : "textFile" }
 		},
+		"scripts": {
+			"type":"array",
+			"description": "List of configuration files for scripts",
+			"items": { "type":"string", "format" : "textFile" }
+		},
 		"spells": {
 			"type":"array",
 			"description": "List of configuration files for spells",
@@ -106,7 +111,7 @@
 			"type":"array",
 			"description": "List of configuration files for RMG templates",
 			"items": { "type":"string", "format" : "textFile" }
-		
+
 		},
 
 		"changelog" : {

+ 22 - 0
config/schemas/script.json

@@ -0,0 +1,22 @@
+{
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-04/schema",
+
+	"title" : "VCMI script format",
+	"description" : "Format used to configure script environment",
+
+
+	"required" : ["source"],
+
+	"properties" : {
+		"source" : {
+			"type": "string",
+			"description": "Full VFS path to script source (extension required)"
+		},
+		"implements" :{
+			"type": "string",
+			"enum": ["ANYTHING", "BATTLE_EFFECT"],
+			"description": ""
+		}
+	}
+}

+ 27 - 0
include/vcmi/Artifact.h

@@ -0,0 +1,27 @@
+/*
+ * Artifact.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 "Entity.h"
+
+class ArtifactID;
+class CreatureID;
+
+class DLL_LINKAGE Artifact : public EntityWithBonuses<ArtifactID>
+{
+public:
+	virtual bool isBig() const = 0;
+	virtual bool isTradable() const = 0;
+	virtual const std::string & getDescription() const = 0;
+	virtual const std::string & getEventText() const = 0;
+	virtual uint32_t getPrice() const = 0;
+	virtual CreatureID getWarMachine() const = 0;
+};

+ 21 - 0
include/vcmi/ArtifactService.h

@@ -0,0 +1,21 @@
+/*
+ * ArtifactService.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 "EntityService.h"
+
+class ArtifactID;
+class Artifact;
+
+class DLL_LINKAGE ArtifactService : public EntityServiceT<ArtifactID, Artifact>
+{
+public:
+};

+ 45 - 0
include/vcmi/Creature.h

@@ -0,0 +1,45 @@
+/*
+ * Creature.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 "Entity.h"
+
+class CreatureID;
+
+class DLL_LINKAGE Creature : public EntityWithBonuses<CreatureID>
+{
+public:
+	virtual const std::string & getPluralName() const = 0;
+	virtual const std::string & getSingularName() const = 0;
+	virtual uint32_t getMaxHealth() const = 0;
+
+	virtual int32_t getAdvMapAmountMin() const = 0;
+	virtual int32_t getAdvMapAmountMax() const = 0;
+	virtual int32_t getAIValue() const = 0;
+	virtual int32_t getFightValue() const = 0;
+	virtual int32_t getLevel() const = 0;
+	virtual int32_t getGrowth() const = 0;
+	virtual int32_t getHorde() const = 0;
+	virtual int32_t getFactionIndex() const = 0;
+
+	virtual int32_t getBaseAttack() const = 0;
+	virtual int32_t getBaseDefense() const = 0;
+	virtual int32_t getBaseDamageMin() const = 0;
+	virtual int32_t getBaseDamageMax() const = 0;
+	virtual int32_t getBaseHitPoints() const = 0;
+	virtual int32_t getBaseSpellPoints() const = 0;
+	virtual int32_t getBaseSpeed() const = 0;
+	virtual int32_t getBaseShots() const = 0;
+
+	virtual int32_t getCost(int32_t resIndex) const = 0;
+
+	virtual bool isDoubleWide() const = 0;
+};

+ 21 - 0
include/vcmi/CreatureService.h

@@ -0,0 +1,21 @@
+/*
+ * CreatureService.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 "EntityService.h"
+
+class CreatureID;
+class Creature;
+
+class DLL_LINKAGE CreatureService : public EntityServiceT<CreatureID, Creature>
+{
+public:
+};

+ 42 - 0
include/vcmi/Entity.h

@@ -0,0 +1,42 @@
+/*
+ * Entity.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
+
+class IBonusBearer;
+
+class DLL_LINKAGE Entity
+{
+public:
+	using IconRegistar = std::function<void(int32_t index, const std::string & listName, const std::string & imageName)>;
+
+	virtual ~Entity() = default;
+
+	virtual int32_t getIndex() const = 0;
+	virtual int32_t getIconIndex() const = 0;
+	virtual const std::string & getJsonKey() const = 0;
+	virtual const std::string & getName() const = 0;
+
+	virtual void registerIcons(const IconRegistar & cb) const = 0;
+};
+
+template <typename IdType>
+class DLL_LINKAGE EntityT : public Entity
+{
+public:
+	virtual IdType getId() const = 0;
+};
+
+template <typename IdType>
+class DLL_LINKAGE EntityWithBonuses : public EntityT<IdType>
+{
+public:
+	virtual const IBonusBearer * accessBonuses() const = 0;
+};

+ 32 - 0
include/vcmi/EntityService.h

@@ -0,0 +1,32 @@
+/*
+ * EntityService.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
+
+class Entity;
+
+class DLL_LINKAGE EntityService
+{
+public:
+	virtual ~EntityService() = default;
+
+	virtual const Entity * getBaseByIndex(const int32_t index) const = 0;
+	virtual void forEachBase(const std::function<void(const Entity * entity, bool & stop)> & cb) const = 0;
+};
+
+template <typename IdType, typename EntityType>
+class DLL_LINKAGE EntityServiceT : public EntityService
+{
+public:
+	virtual const EntityType * getById(const IdType & id) const = 0;
+	virtual const EntityType * getByIndex(const int32_t index) const = 0;
+
+	virtual void forEach(const std::function<void(const EntityType * entity, bool & stop)> & cb) const = 0;
+};

+ 36 - 0
include/vcmi/Environment.h

@@ -0,0 +1,36 @@
+/*
+ * Environment.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
+
+class Services;
+
+class IGameInfoCallback;
+class IBattleInfoCallback;
+
+namespace events
+{
+	class EventBus;
+}
+
+class DLL_LINKAGE Environment
+{
+public:
+	using BattleCb = ::IBattleInfoCallback;
+	using GameCb = ::IGameInfoCallback;
+
+	virtual ~Environment() = default;
+
+	virtual const Services * services() const = 0;
+	virtual const BattleCb * battle() const = 0;
+	virtual const GameCb * game() const = 0;
+	virtual vstd::CLoggerBase * logger() const = 0;
+	virtual events::EventBus * eventBus() const = 0;
+};

+ 21 - 0
include/vcmi/Faction.h

@@ -0,0 +1,21 @@
+/*
+ * Faction.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 "Entity.h"
+
+class FactionID;
+
+class DLL_LINKAGE Faction : public EntityT<FactionID>
+{
+public:
+	virtual bool hasTown() const = 0;
+};

+ 21 - 0
include/vcmi/FactionService.h

@@ -0,0 +1,21 @@
+/*
+ * FactionService.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 "EntityService.h"
+
+class FactionID;
+class Faction;
+
+class DLL_LINKAGE FactionService : public EntityServiceT<FactionID, Faction>
+{
+public:
+};

+ 22 - 0
include/vcmi/HeroClass.h

@@ -0,0 +1,22 @@
+/*
+ * HeroClass.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 "Entity.h"
+
+class HeroClassID;
+
+class DLL_LINKAGE HeroClass : public EntityT<HeroClassID>
+{
+public:
+
+};
+

+ 21 - 0
include/vcmi/HeroClassService.h

@@ -0,0 +1,21 @@
+/*
+ * HeroClassService.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 "EntityService.h"
+
+class HeroClassID;
+class HeroClass;
+
+class DLL_LINKAGE HeroClassService : public EntityServiceT<HeroClassID, HeroClass>
+{
+public:
+};

+ 22 - 0
include/vcmi/HeroType.h

@@ -0,0 +1,22 @@
+/*
+ * HeroType.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 "Entity.h"
+
+class HeroTypeID;
+
+class DLL_LINKAGE HeroType : public EntityT<HeroTypeID>
+{
+public:
+
+};
+

+ 21 - 0
include/vcmi/HeroTypeService.h

@@ -0,0 +1,21 @@
+/*
+ * HeroTypeService.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 "EntityService.h"
+
+class HeroTypeID;
+class HeroType;
+
+class DLL_LINKAGE HeroTypeService : public EntityServiceT<HeroTypeID, HeroType>
+{
+public:
+};

+ 30 - 0
include/vcmi/Metatype.h

@@ -0,0 +1,30 @@
+/*
+ * Metatype.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
+
+enum class Metatype : uint32_t
+{
+	UNKNOWN = 0,
+	ARTIFACT,
+	ARTIFACT_INSTANCE,
+	CREATURE,
+	CREATURE_INSTANCE,
+	FACTION,
+	HERO_CLASS,
+	HERO_TYPE,
+	HERO_INSTANCE,
+	MAP_OBJECT_GROUP,
+	MAP_OBJECT_TYPE,
+	MAP_OBJECT_INSTANCE,
+	SKILL,
+	SPELL
+};
+

+ 30 - 0
include/vcmi/Player.h

@@ -0,0 +1,30 @@
+/*
+ * Player.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
+
+class PlayerColor;
+class TeamID;
+class IBonusBearer;
+
+class DLL_LINKAGE Player
+{
+public:
+	virtual PlayerColor getColor() const = 0;
+	virtual TeamID getTeam() const = 0;
+	virtual bool isHuman() const = 0;
+	virtual const IBonusBearer * accessBonuses() const = 0;
+	virtual int getResourceAmount(int type) const = 0;
+};
+
+
+
+
+

+ 44 - 0
include/vcmi/ServerCallback.h

@@ -0,0 +1,44 @@
+/*
+ * ServerCallback.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
+
+namespace vstd
+{
+	class RNG;
+}
+
+struct CPackForClient;
+struct BattleLogMessage;
+struct BattleStackMoved;
+struct BattleUnitsChanged;
+struct SetStackEffect;
+struct StacksInjured;
+struct BattleObstaclesChanged;
+struct CatapultAttack;
+
+class DLL_LINKAGE ServerCallback
+{
+public:
+	virtual void complain(const std::string & problem) = 0;
+	virtual bool describeChanges() const = 0;
+
+	virtual vstd::RNG * getRNG() = 0;
+
+	virtual void apply(CPackForClient * pack) = 0;
+
+	virtual void apply(BattleLogMessage * pack) = 0;
+	virtual void apply(BattleStackMoved * pack) = 0;
+	virtual void apply(BattleUnitsChanged * pack) = 0;
+	virtual void apply(SetStackEffect * pack) = 0;
+	virtual void apply(StacksInjured * pack) = 0;
+	virtual void apply(BattleObstaclesChanged * pack) = 0;
+	virtual void apply(CatapultAttack * pack) = 0;
+};

+ 57 - 0
include/vcmi/Services.h

@@ -0,0 +1,57 @@
+/*
+ * Services.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 "Metatype.h"
+
+class ArtifactService;
+class CreatureService;
+class FactionService;
+class HeroClassService;
+class HeroTypeService;
+class SkillService;
+class JsonNode;
+
+namespace spells
+{
+	class Service;
+
+	namespace effects
+	{
+		class Registry;
+	}
+}
+
+namespace scripting
+{
+	class Service;
+}
+
+class DLL_LINKAGE Services
+{
+public:
+	virtual ~Services() = default;
+
+	virtual const ArtifactService * artifacts() const = 0;
+	virtual const CreatureService * creatures() const = 0;
+	virtual const FactionService * factions() const = 0;
+	virtual const HeroClassService * heroClasses() const = 0;
+	virtual const HeroTypeService * heroTypes() const = 0;
+	virtual const scripting::Service * scripts() const = 0;
+	virtual const spells::Service * spells() const = 0;
+	virtual const SkillService * skills() const = 0;
+
+	virtual void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) = 0;
+
+	virtual const spells::effects::Registry * spellEffects() const = 0;
+	virtual spells::effects::Registry * spellEffects() = 0;
+	//TODO: put map object types registry access here
+};

+ 22 - 0
include/vcmi/Skill.h

@@ -0,0 +1,22 @@
+/*
+ * Skill.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 "Entity.h"
+
+class SecondarySkill;
+
+class DLL_LINKAGE Skill : public EntityT<SecondarySkill>
+{
+public:
+
+};
+

+ 21 - 0
include/vcmi/SkillService.h

@@ -0,0 +1,21 @@
+/*
+ * SkillService.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 "EntityService.h"
+
+class SecondarySkill;
+class Skill;
+
+class DLL_LINKAGE SkillService : public EntityServiceT<SecondarySkill, Skill>
+{
+public:
+};

+ 15 - 0
include/vcmi/Team.h

@@ -0,0 +1,15 @@
+/*
+ * Team.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
+
+
+
+

+ 13 - 0
include/vcmi/events/AdventureEvents.h

@@ -0,0 +1,13 @@
+/*
+ * AdventureEvents.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 "ObjectVisitEnded.h"
+#include "ObjectVisitStarted.h"

+ 45 - 0
include/vcmi/events/ApplyDamage.h

@@ -0,0 +1,45 @@
+/*
+ * ApplyDamage.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 "Event.h"
+#include "SubscriptionRegistry.h"
+
+struct BattleStackAttacked;
+
+namespace battle
+{
+	class Unit;
+}
+
+namespace events
+{
+
+class DLL_LINKAGE ApplyDamage : public Event
+{
+public:
+	using Sub = SubscriptionRegistry<ApplyDamage>;
+
+	using PreHandler = Sub::PreHandler;
+	using PostHandler = Sub::PostHandler;
+	using ExecHandler = Sub::ExecHandler;
+
+	static Sub * getRegistry();
+
+	virtual int64_t getInitalDamage() const = 0;
+	virtual int64_t getDamage() const = 0;
+	virtual void setDamage(int64_t value) = 0;
+	virtual const battle::Unit * getTarget() const = 0;
+
+	friend class SubscriptionRegistry<ApplyDamage>;
+};
+
+}

+ 13 - 0
include/vcmi/events/BattleEvents.h

@@ -0,0 +1,13 @@
+/*
+ * BattleEvents.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 "ApplyDamage.h"

+ 29 - 0
include/vcmi/events/Event.h

@@ -0,0 +1,29 @@
+/*
+ * Event.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
+
+namespace events
+{
+
+class EventBus;
+
+template <typename T>
+class SubscriptionRegistry;
+
+class DLL_LINKAGE Event
+{
+public:
+	virtual bool isEnabled() const = 0;
+
+};
+
+}
+

+ 44 - 0
include/vcmi/events/EventBus.h

@@ -0,0 +1,44 @@
+/*
+ * EventBus.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 "SubscriptionRegistry.h"
+
+class Environment;
+
+namespace events
+{
+
+class DLL_LINKAGE EventBus : public boost::noncopyable
+{
+public:
+	template <typename E>
+	std::unique_ptr<EventSubscription> subscribeBefore(typename E::PreHandler && cb)
+	{
+		auto registry = E::getRegistry();
+		return registry->subscribeBefore(this, std::move(cb));
+	}
+
+	template <typename E>
+	std::unique_ptr<EventSubscription> subscribeAfter(typename E::PostHandler && cb)
+	{
+		auto registry = E::getRegistry();
+		return registry->subscribeAfter(this, std::move(cb));
+	}
+
+	template <typename E>
+	void executeEvent(E & event, const typename E::ExecHandler & execHandler = typename E::ExecHandler()) const
+	{
+		auto registry = E::getRegistry();
+		registry->executeEvent(this, event, execHandler);
+	}
+};
+}

+ 35 - 0
include/vcmi/events/GameResumed.h

@@ -0,0 +1,35 @@
+/*
+ * GameResumed.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 "Event.h"
+#include "SubscriptionRegistry.h"
+
+namespace events
+{
+
+class DLL_LINKAGE GameResumed : public Event
+{
+public:
+	using Sub = SubscriptionRegistry<GameResumed>;
+
+	using PreHandler = Sub::PreHandler;
+	using PostHandler = Sub::PostHandler;
+	using ExecHandler = Sub::ExecHandler;
+
+	static Sub * getRegistry();
+
+	static void defaultExecute(const EventBus * bus);
+
+	friend class SubscriptionRegistry<GameResumed>;
+};
+
+}

+ 15 - 0
include/vcmi/events/GenericEvents.h

@@ -0,0 +1,15 @@
+/*
+ * GenericEvents.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 "GameResumed.h"
+#include "PlayerGotTurn.h"
+#include "TurnStarted.h"

+ 40 - 0
include/vcmi/events/ObjectVisitEnded.h

@@ -0,0 +1,40 @@
+/*
+ * ObjectVisitEnded.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 "Event.h"
+#include "SubscriptionRegistry.h"
+
+class PlayerColor;
+class ObjectInstanceID;
+
+namespace events
+{
+
+class DLL_LINKAGE ObjectVisitEnded : public Event
+{
+public:
+	using Sub = SubscriptionRegistry<ObjectVisitEnded>;
+	using PreHandler = Sub::PreHandler;
+	using PostHandler = Sub::PostHandler;
+	using ExecHandler = Sub::ExecHandler;
+
+	virtual PlayerColor getPlayer() const = 0;
+	virtual ObjectInstanceID getHero() const = 0;
+
+	static Sub * getRegistry();
+	static void defaultExecute(const EventBus * bus, const ExecHandler & execHandler,
+		const PlayerColor & player, const ObjectInstanceID & heroId);
+
+	friend class SubscriptionRegistry<ObjectVisitEnded>;
+};
+
+}

+ 43 - 0
include/vcmi/events/ObjectVisitStarted.h

@@ -0,0 +1,43 @@
+/*
+ * ObjectVisitStarted.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 "Event.h"
+#include "SubscriptionRegistry.h"
+
+class PlayerColor;
+class ObjectInstanceID;
+
+namespace events
+{
+
+class DLL_LINKAGE ObjectVisitStarted : public Event
+{
+public:
+	using Sub = SubscriptionRegistry<ObjectVisitStarted>;
+	using PreHandler = Sub::PreHandler;
+	using PostHandler = Sub::PostHandler;
+	using ExecHandler = Sub::ExecHandler;
+
+	virtual PlayerColor getPlayer() const = 0;
+	virtual ObjectInstanceID getHero() const = 0;
+	virtual ObjectInstanceID getObject() const = 0;
+
+	virtual void setEnabled(bool enable) = 0;
+
+	static Sub * getRegistry();
+	static void defaultExecute(const EventBus * bus, const ExecHandler & execHandler,
+		const PlayerColor & player, const ObjectInstanceID & heroId, const ObjectInstanceID & objId);
+
+	friend class SubscriptionRegistry<ObjectVisitStarted>;
+};
+
+}

+ 41 - 0
include/vcmi/events/PlayerGotTurn.h

@@ -0,0 +1,41 @@
+/*
+ * PlayerGotTurn.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 "Event.h"
+#include "SubscriptionRegistry.h"
+
+class PlayerColor;
+
+namespace events
+{
+
+class DLL_LINKAGE PlayerGotTurn : public Event
+{
+public:
+	using Sub = SubscriptionRegistry<PlayerGotTurn>;
+	using PreHandler = Sub::PreHandler;
+	using PostHandler = Sub::PostHandler;
+	using ExecHandler = Sub::ExecHandler;
+
+	static Sub * getRegistry();
+	static void defaultExecute(const EventBus * bus, const ExecHandler & execHandler, PlayerColor & player);
+
+	virtual PlayerColor getPlayer() const = 0;
+	virtual void setPlayer(const PlayerColor & value) = 0;
+
+	virtual int32_t getPlayerIndex() const = 0;
+	virtual void setPlayerIndex(int32_t value) = 0;
+
+	friend class SubscriptionRegistry<PlayerGotTurn>;
+};
+
+}

部分文件因为文件数量过多而无法显示