Browse Source

Banks now use new scheme as well

- Implemented Bank Constructor object.
- Merged Pyramid object into common Bank class. Banks can now grant
spells as part of their reward.
- Move bank config code to config/objects/creatureBanks.json. Note: WoG
banks are not updated yet, should be moved to WoG mod.
- Updated AI code so it can correctly evaluate bank danger (should be
generic enough for use with other objects)
- New files JsonRandom.* that contain routines for loading random
objects from Json (still WiP but should be stable)
Ivan Savenko 11 năm trước cách đây
mục cha
commit
ab475195ac

+ 3 - 2
AI/VCAI/AIUtility.cpp

@@ -5,6 +5,7 @@
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
+#include "../../lib/mapObjects/CBank.h"
 
 /*
  * AIUtility.cpp, part of VCMI engine
@@ -302,11 +303,11 @@ ui64 evaluateDanger(const CGObjectInstance *obj)
 	case Obj::SHIPWRECK: //shipwreck
 	case Obj::DERELICT_SHIP: //derelict ship
 //	case Obj::PYRAMID:
-		return fh->estimateBankDanger (VLC->objh->bankObjToIndex(obj));
+		return fh->estimateBankDanger (dynamic_cast<const CBank *>(obj));
 	case Obj::PYRAMID:
 		{
 		    if(obj->subID == 0)
-				return fh->estimateBankDanger (VLC->objh->bankObjToIndex(obj));
+				return fh->estimateBankDanger (dynamic_cast<const CBank *>(obj));
 			else
 				return 0;
 		}

+ 17 - 42
AI/VCAI/Fuzzy.cpp

@@ -3,6 +3,7 @@
 #include <limits>
 
 #include "../../lib/mapObjects/MapObjects.h"
+#include "../../lib/mapObjects/CommonConstructors.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/VCMI_Lib.h"
 #include "../../CCallback.h"
@@ -41,16 +42,6 @@ struct armyStructure
 	ui32 maxSpeed;
 };
 
-ui64 evaluateBankConfig (BankConfig * bc)
-{
-	ui64 danger = 0;
-	for (auto opt : bc->guards)
-	{
-		danger += VLC->creh->creatures[opt.first]->fightValue * opt.second;
-	}
-	return danger;
-}
-
 armyStructure evaluateArmyStructure (const CArmedInstance * army)
 {
 	ui64 totalStrenght = army->getArmyStrength();
@@ -208,42 +199,26 @@ void FuzzyHelper::initTacticalAdvantage()
 	}
 }
 
-ui64 FuzzyHelper::estimateBankDanger (int ID)
+ui64 FuzzyHelper::estimateBankDanger (const CBank * bank)
 {
-	std::vector <ConstTransitivePtr<BankConfig>> & configs = VLC->objh->banksInfo[ID];
+	auto info = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
+
 	ui64 val = std::numeric_limits<ui64>::max();
 	try
 	{
-		switch (configs.size())
-		{
-			case 4:
-				try
-				{
-					for (int i = 0; i < 4; ++i)
-					{
-						int bankVal = evaluateBankConfig (VLC->objh->banksInfo[ID][i]);
-						bankDanger->term("Bank" + boost::lexical_cast<std::string>(i))->setMinimum(bankVal * 0.5f);
-						bankDanger->term("Bank" + boost::lexical_cast<std::string>(i))->setMaximum(bankVal * 1.5f);
-					}
-					//comparison purposes
-					//int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5;
-					//dynamic_cast<fl::SingletonTerm*>(bankInput->term("SET"))->setValue(0.5);
-					bankInput->setInput (0.5);
-					engine.process (BANK_DANGER);
-					//engine.process();
-					val = bankDanger->output().defuzzify(); //some expected value of this bank
-				}
-				catch (fl::FuzzyException & fe)
-				{
-                    logAi->errorStream() << fe.name() << ": " << fe.message();
-				}
-				break;
-			case 1: //rare case - Pyramid
-				val = evaluateBankConfig (VLC->objh->banksInfo[ID][0]);
-				break;
-			default:
-                logAi->warnStream() << ("Uhnandled bank config!");
-		}
+		bankDanger->term("Bank0")->setMinimum(info->minGuards().totalStrength * 0.5f);
+		bankDanger->term("Bank0")->setMaximum(info->minGuards().totalStrength * 1.5f);
+
+		bankDanger->term("Bank1")->setMinimum(info->maxGuards().totalStrength * 0.5f);
+		bankDanger->term("Bank1")->setMaximum(info->maxGuards().totalStrength * 1.5f);
+
+		//comparison purposes
+		//int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5;
+		//dynamic_cast<fl::SingletonTerm*>(bankInput->term("SET"))->setValue(0.5);
+		bankInput->setInput (0.5);
+		engine.process (BANK_DANGER);
+		//engine.process();
+		val = bankDanger->output().defuzzify(); //some expected value of this bank
 	}
 	catch (fl::FuzzyException & fe)
 	{

+ 2 - 1
AI/VCAI/Fuzzy.h

@@ -14,6 +14,7 @@
 
 class VCAI;
 class CArmedInstance;
+class CBank;
 
 class FuzzyHelper
 {
@@ -72,7 +73,7 @@ public:
 	float evaluate (Goals::AbstractGoal & g);
 	void setPriority (Goals::TSubgoal & g);
 
-	ui64 estimateBankDanger (int ID);
+	ui64 estimateBankDanger (const CBank * bank);
 	float getTacticalAdvantage (const CArmedInstance *we, const CArmedInstance *enemy); //returns factor how many times enemy is stronger than us
 
 	Goals::TSubgoal chooseSolution (Goals::TGoalVec vec);

+ 2 - 683
config/bankconfig.json

@@ -1,675 +1,9 @@
 //Resources:  Wood, Mercury, Ore, Sulfur, Crystal, Gems, Gold
 //Artifacts:  Treasure, Minor, Major, Relic
+//NOTE: all H3M banks were moved to objects/creatureBanks.json
+//Remaining part should be moved to WoG mod
 {
 	"banks": [
-		{
-			"name" : "Cyclops Stockpile",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 20, "id": 94 } ],
-					"upgrade_chance": 50,
-					"combat_value": 506,
-					"reward_resources": 
-					{
-						"wood" : 4,
-						"mercury" : 4,
-						"ore" : 4,
-						"sulfur" : 4,
-						"crystal" : 4,
-						"gems" : 4,
-						"gold" : 0
-					},
-					"value": 10000,
-					"profitability": 20,
-					"easiest": 100
-				},
-
-				{
-					"chance": 30,
-					"guards": [ { "number": 30, "id": 94 } ],
-					"upgrade_chance": 50,
-					"combat_value": 760,
-					"reward_resources":
-					{
-						"wood" : 6,
-						"mercury" : 6,
-						"ore" : 6,
-						"sulfur" : 6,
-						"crystal" : 6,
-						"gems" : 6
-					},
-					"value": 15000,
-					"profitability": 20,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 40, "id": 94 } ],
-					"upgrade_chance": 50,
-					"combat_value": 1013,
-					"reward_resources":
-					{
-						"wood" : 8,
-						"mercury" : 8,
-						"ore" : 8,
-						"sulfur" : 8,
-						"crystal" : 8,
-						"gems" : 8
-					},
-					"value": 20000,
-					"profitability": 20,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 50, "id": 94 } ],
-					"upgrade_chance": 50,
-					"combat_value": 1266,
-					"reward_resources":
-					{
-						"wood" : 10,
-						"mercury" : 10,
-						"ore" : 10,
-						"sulfur" : 10,
-						"crystal" : 10,
-						"gems" : 10
-					},
-					"value": 25000,
-					"profitability": 20,
-					"easiest": 250
-				}
-			]
-		},
-
-		{
-			"name" : "Dwarven Treasury",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 50, "id": 16 } ],
-					"upgrade_chance": 50,
-					"combat_value": 194,
-					"reward_resources":
-					{
-						"crystal" : 2,
-						"gold" : 2500
-					},
-					"value": 3500,
-					"profitability": 18,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 75, "id": 16 } ],
-					"upgrade_chance": 50,
-					"combat_value": 291,
-					"reward_resources":
-					{
-						"crystal" : 3,
-						"gold" : 4000
-					},
-					"value": 5500,
-					"profitability": 19,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 100, "id": 16 } ],
-					"upgrade_chance": 50,
-					"combat_value": 388,
-					"reward_resources":
-					{
-						"crystal" : 5,
-						"gold" : 5000
-					},
-					"value": 7500,
-					"profitability": 19,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 150, "id": 16 } ],
-					"upgrade_chance": 50,
-					"combat_value": 582,
-					"reward_resources":
-					{
-						"crystal" : 10,
-						"gold" : 7500
-					},
-					"value": 12500,
-					"profitability": 21,
-					"easiest": 300
-				}
-			]
-		},
-
-		{
-			"name" : "Griffin Conservatory",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 50, "id": 4 } ],
-					"upgrade_chance": 50,
-					"combat_value": 351,
-					"reward_creatures": [ { "number": 1, "id": 12 } ],
-					"value": 3000,
-					"profitability": 9,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 100, "id": 4 } ],
-					"upgrade_chance": 50,
-					"combat_value": 702,
-					"reward_creatures": [ { "number": 2, "id": 12 } ],
-					"value": 6000,
-					"profitability": 9,
-					"easiest": 200
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 150, "id": 4 } ],
-					"upgrade_chance": 50,
-					"combat_value": 1053,
-					"reward_creatures": [ { "number": 3, "id": 12 } ],
-					"value": 9000,
-					"profitability": 9,
-					"easiest": 300
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 200, "id": 4 } ],
-					"upgrade_chance": 50,
-					"combat_value": 1404,
-					"reward_creatures": [ { "number": 4, "id": 12 } ],
-					"value": 12000,
-					"profitability": 9,
-					"easiest": 400
-				}
-			]
-		},
-
-		{
-			"name" : "Imp Cache",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 100, "id": 42 } ],
-					"upgrade_chance": 50,
-					"combat_value": 100,
-					"reward_resources":
-					{
-						"wood" : 0,
-						"mercury" : 2,
-						"ore" : 0,
-						"sulfur" : 0,
-						"crystal" : 0,
-						"gems" : 0,
-						"gold" : 1000
-					},
-					"value": 2000,
-					"profitability": 20,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 150, "id": 42 } ],
-					"upgrade_chance": 50,
-					"combat_value": 150,
-					"reward_resources":
-					{
-						"mercury" : 3,
-						"gold" : 1500
-					},
-					"value": 3000,
-					"profitability": 20,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 200, "id": 42 } ],
-					"upgrade_chance": 50,
-					"combat_value": 200,
-					"reward_resources":
-					{
-						"mercury" : 4,
-						"gold" : 2000
-					},
-					"value": 4000,
-					"profitability": 20,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 300, "id": 42 } ],
-					"upgrade_chance": 50,
-					"combat_value": 300,
-					"reward_resources":
-					{
-						"mercury" : 6,
-						"gold" : 3000
-					},
-					"value": 6000,
-					"profitability": 20,
-					"easiest": 300
-				}
-			]
-		},
-
-		{
-			"name" : "Medusa Stores",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 20, "id": 76 } ],
-					"upgrade_chance": 50,
-					"combat_value": 207,
-					"reward_resources":
-					{
-						"sulfur" : 5,
-						"gold" : 2000
-					},
-					"value": 4500,
-					"profitability": 22,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 30, "id": 76 } ],
-					"upgrade_chance": 50,
-					"combat_value": 310,
-					"reward_resources":
-					{
-						"sulfur" : 6,
-						"gold" : 3000
-					},
-					"value": 6000,
-					"profitability": 19,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 40, "id": 76 } ],
-					"upgrade_chance": 50,
-					"combat_value": 414,
-					"reward_resources":
-					{
-						"sulfur" : 8,
-						"gold" : 4000
-					},
-					"value": 8000,
-					"profitability": 19,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 50, "id": 76 } ],
-					"upgrade_chance": 50,
-					"combat_value": 517,
-					"reward_resources":
-					{
-						"sulfur" : 10,
-						"gold" : 5000
-					},
-					"value": 10000,
-					"profitability": 19,
-					"easiest": 250
-				}
-			]
-		},
-
-		{
-			"name" : "Naga Bank",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 10, "id": 38 } ],
-					"upgrade_chance": 50,
-					"combat_value": 403,
-					"reward_resources":
-					{
-						"gems" : 8,
-						"gold" : 4000
-					},
-					"value": 8000,
-					"profitability": 20,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 15, "id": 38 } ],
-					"upgrade_chance": 50,
-					"combat_value": 605,
-					"reward_resources":
-					{
-						"gems" : 12,
-						"gold" : 6000
-					},
-					"value": 12000,
-					"profitability": 20,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 20, "id": 38 } ],
-					"upgrade_chance": 50,
-					"combat_value": 806,
-					"reward_resources":
-					{
-						"gems" : 16,
-						"gold" : 8000
-					},
-					"value": 16000,
-					"profitability": 20,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 30, "id": 38 } ],
-					"upgrade_chance": 50,
-					"combat_value": 1210,
-					"reward_resources":
-					{
-						"gems" : 24,
-						"gold" : 12000
-					},
-					"value": 24000,
-					"profitability": 20,
-					"easiest": 300
-				}
-			]
-		},
-
-		{
-			"name" : "Dragon Fly Hive",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 30, "id": 105} ],
-					"upgrade_chance": 0,
-					"combat_value": 154,
-					"reward_creatures": [ { "number": 4, "id": 108 } ],
-					"value": 3200,
-					"profitability": 21,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 45, "id": 105 } ],
-					"upgrade_chance": 0,
-					"combat_value": 230,
-					"reward_creatures": [ { "number": 6, "id": 108 } ],
-					"value": 4800,
-					"profitability": 21,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 60, "id": 105 } ],
-					"upgrade_chance": 0,
-					"combat_value": 307,
-					"reward_creatures": [ { "number": 8, "id": 108 } ],
-					"value": 6400,
-					"profitability": 21,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 90, "id": 105 } ],
-					"upgrade_chance": 0,
-					"combat_value": 461,
-					"reward_creatures": [ { "number": 12, "id": 108 } ],
-					"value": 9600,
-					"profitability": 21,
-					"easiest": 300
-				}
-			]
-		},
-
-		{
-			"name" : "Shipwreck",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 10, "id": 60 } ],
-					"upgrade_chance": 0,
-					"combat_value": 31,
-					"reward_resources":
-					{
-						"gold" : 2000
-					},
-					"value": 2000,
-					"profitability": 65,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 15, "id": 60 } ],
-					"upgrade_chance": 0,
-					"combat_value": 46,
-					"reward_resources":
-					{
-						"gold" : 3000
-					},
-					"value": 3000,
-					"profitability": 65,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 25, "id": 60 } ],
-					"upgrade_chance": 0,
-					"combat_value": 77,
-					"reward_resources":
-					{
-						"gold" : 4000
-					},
-					"reward_artifacts": [ 1, 0, 0, 0 ],
-					"value": 5000,
-					"profitability": 65,
-					"easiest": 250
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 50, "id": 60 } ],
-					"upgrade_chance": 0,
-					"combat_value": 154,
-					"reward_resources":
-					{
-						"gold" : 5000
-					},
-					"reward_artifacts": [ 0, 1, 0, 0 ],
-					"value": 7000,
-					"profitability": 45,
-					"easiest": 500
-				}
-			]
-		},
-
-		{
-			"name" : "Derelict Ship",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 20, "id": 115 } ],
-					"upgrade_chance": 0,
-					"combat_value": 138,
-					"reward_resources":
-					{
-						"gold" : 3000
-					},
-					"value": 3000,
-					"profitability": 22,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 30, "id": 115 } ],
-					"upgrade_chance": 0,
-					"combat_value": 207,
-					"reward_resources":
-					{
-						"gold" : 3000
-					},
-					"reward_artifacts": [ 1, 0, 0, 0 ],
-					"value": 4000,
-					"profitability": 19,
-					"easiest": 150
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 40, "id": 115 } ],
-					"upgrade_chance": 0,
-					"combat_value": 276,
-					"reward_resources":
-					{
-						"gold" : 4000
-					},
-					"reward_artifacts": [ 1, 0, 0, 0 ],
-					"value": 5000,
-					"profitability": 18,
-					"easiest": 200
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 60, "id": 115 } ],
-					"upgrade_chance": 0,
-					"combat_value": 414,
-					"reward_resources":
-					{
-						"gold" : 6000
-					},
-					"reward_artifacts": [ 0, 1, 0, 0 ],
-					"value": 8000,
-					"profitability": 19,
-					"easiest": 300
-				}
-			]
-		},
-
-		{
-			"name" : "Crypt",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 30, "id": 56 }, { "number": 20, "id": 58 }, { "number": 0, "id": 60 } , { "number": 0, "id": 62 } ],
-					"upgrade_chance": 0,
-					"combat_value": 75,
-					"reward_resources":
-					{
-						"gold" : 1500
-					},
-					"value": 1500,
-					"profitability": 20,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 25, "id": 56 }, { "number": 20, "id": 58 }, { "number": 5, "id": 60 }, { "number": 0, "id": 62 } ],
-					"upgrade_chance": 0,
-					"combat_value": 94,
-					"reward_resources":
-					{
-						"gold" : 2000
-					},
-					"value": 2000,
-					"profitability": 21,
-					"easiest": 126
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 20, "id": 56 }, { "number": 20, "id": 58 }, { "number": 10, "id": 60 }, { "number": 5, "id": 62 } ],
-					"upgrade_chance": 0,
-					"combat_value": 169,
-					"reward_resources":
-					{
-						"gold" : 2500
-					},
-					"reward_artifacts": [ 1, 0, 0, 0 ],
-					"value": 3500,
-					"profitability": 21,
-					"easiest": 225
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 20, "id": 56 }, { "number": 20, "id": 58 }, { "number": 10, "id": 60 }, { "number": 10, "id": 62 } ],
-					"upgrade_chance": 0,
-					"combat_value": 225,
-					"reward_resources":
-					{
-						"gold" : 5000
-					},
-					"reward_artifacts": [ 1, 0, 0, 0 ],
-					"value": 6000,
-					"profitability": 27,
-					"easiest": 299
-				}
-			]
-		},
-
-		{
-			"name" : "Dragon Utopia",
-			"levels": [
-				{
-					"chance": 30,
-					"guards": [ { "number": 8, "id": 26 }, { "number": 5, "id": 82 }, { "number": 2, "id": 27 }, { "number": 1, "id": 83 }	],
-					"upgrade_chance": 0,
-					"combat_value": 769,
-					"reward_resources":
-					{
-						"gold" : 20000
-					},
-					"reward_artifacts": [ 1, 1, 1, 1 ],
-					"value": 38000,
-					"profitability": 21,
-					"easiest": 100
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 8, "id": 26 }, { "number": 6, "id": 82 }, { "number": 3, "id": 27 }, { "number": 2, "id": 83 }	],
-					"upgrade_chance": 0,
-					"combat_value": 209,
-					"reward_resources":
-					{
-						"gold" : 30000
-					},
-					"reward_artifacts": [ 0, 1, 1, 2 ],
-					"value": 57000,
-					"profitability": 26,
-					"easiest": 125
-				},
-				{
-					"chance": 30,
-					"guards": [ { "number": 8, "id": 26 }, { "number": 6, "id": 82 }, { "number": 4, "id": 27 }, { "number": 3, "id": 83 }	],
-					"upgrade_chance": 0,
-					"combat_value": 556,
-					"reward_resources":
-					{
-						"gold" : 40000
-					},
-					"reward_artifacts": [ 0, 0, 1, 3 ],
-					"value": 75000,
-					"profitability": 29,
-					"easiest": 145
-				},
-				{
-					"chance": 10,
-					"guards": [ { "number": 8, "id": 26 }, { "number": 7, "id": 82 }, { "number": 6, "id": 27 }, { "number": 5, "id": 83 } ],
-					"upgrade_chance": 0,
-					"combat_value": 343,
-					"reward_resources":
-					{
-						"gold" : 50000
-					},
-					"reward_artifacts": [ 0, 0, 0, 4 ],
-					"value": 90000,
-					"profitability": 27,
-					"easiest": 189
-				}
-			]
-		},
-
 		{
 			"name" : "Hunting Lodge",
 			"levels": [
@@ -1252,21 +586,6 @@
 					"easiest": 250
 				}
 			]
-		},
-
-		{
-			"name" : "Pyramid",
-			"levels": [
-				{
-					"chance": 100,
-					"guards": [	{ "number": 20, "id": 116 }, { "number": 10, "id": 117 }, { "number": 20, "id": 116 }, { "number": 10, "id": 117 } ],
-					"upgrade_chance": 0,
-					"combat_value": 786,
-					"value": 15000,
-					"profitability": 19,
-					"easiest": 100
-				}
-			]
 		}
 	]
 }

+ 1 - 0
config/gameConfig.json

@@ -49,6 +49,7 @@
 	[
 		"config/objects/generic.json",
 		"config/objects/moddables.json",
+		"config/objects/creatureBanks.json",
 		"config/objects/dwellings.json",
 		"config/objects/rewardable.json"
 	],

+ 975 - 0
config/objects/creatureBanks.json

@@ -0,0 +1,975 @@
+{
+	"creatureBank" : {
+		"index" :16,
+		"handler": "bank",
+		"types" : {
+			"cyclopsStockpile" :
+			{
+				"index" : 0,
+				"name" : "Cyclops Stockpile",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 4, "type": "cyclop" },
+							{ "amount": 4, "type": "cyclop" },
+							{ "amount": 4, "type": "cyclop", "upgradeChance": 50 },
+							{ "amount": 4, "type": "cyclop" },
+							{ "amount": 4, "type": "cyclop" }
+						],
+						
+						"combat_value": 506,
+						"reward" : {
+							"value": 10000,
+							"resources": 
+							{
+								"wood" : 4,
+								"mercury" : 4,
+								"ore" : 4,
+								"sulfur" : 4,
+								"crystal" : 4,
+								"gems" : 4,
+								"gold" : 0
+							}
+						}
+					},
+
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 6, "type": "cyclop" },
+							{ "amount": 6, "type": "cyclop" },
+							{ "amount": 6, "type": "cyclop", "upgradeChance": 50 },
+							{ "amount": 6, "type": "cyclop" },
+							{ "amount": 6, "type": "cyclop" }
+						],
+						"combat_value": 760,
+						"reward" : {
+							"value": 15000,
+							"resources": 
+							{
+								"wood" : 6,
+								"mercury" : 6,
+								"ore" : 6,
+								"sulfur" : 6,
+								"crystal" : 6,
+								"gems" : 6
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 8, "type": "cyclop" },
+							{ "amount": 8, "type": "cyclop" },
+							{ "amount": 8, "type": "cyclop", "upgradeChance": 50 },
+							{ "amount": 8, "type": "cyclop" },
+							{ "amount": 8, "type": "cyclop" }
+						],
+						"combat_value": 1013,
+						"reward" : {
+							"value": 20000
+							"resources":
+							{
+								"wood" : 8,
+								"mercury" : 8,
+								"ore" : 8,
+								"sulfur" : 8,
+								"crystal" : 8,
+								"gems" : 8
+							}
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 10, "type": "cyclop" },
+							{ "amount": 10, "type": "cyclop" },
+							{ "amount": 10, "type": "cyclop", "upgradeChance": 50 },
+							{ "amount": 10, "type": "cyclop" },
+							{ "amount": 10, "type": "cyclop" }
+						],
+						"combat_value": 1266,
+						"reward" : {
+							"value": 25000,
+							"resources":
+							{
+								"wood" : 10,
+								"mercury" : 10,
+								"ore" : 10,
+								"sulfur" : 10,
+								"crystal" : 10,
+								"gems" : 10
+							}
+						}
+					}
+				]
+			},
+			"dwarvenTreasury" : {
+				"index" : 1,
+				"resetDuraition" : 28,
+				"name" : "Dwarven Treasury",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 10, "type": "dwarf" },
+							{ "amount": 10, "type": "dwarf" },
+							{ "amount": 10, "type": "dwarf", "upgradeChance": 50 },
+							{ "amount": 10, "type": "dwarf" },
+							{ "amount": 10, "type": "dwarf" }
+						],
+						"combat_value": 194,
+						"reward" : {
+						"value": 3500,
+							"resources":
+							{
+								"crystal" : 2,
+								"gold" : 2500
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 15, "type": "dwarf" },
+							{ "amount": 15, "type": "dwarf" },
+							{ "amount": 15, "type": "dwarf", "upgradeChance": 50 },
+							{ "amount": 15, "type": "dwarf" },
+							{ "amount": 15, "type": "dwarf" }
+						],
+						"combat_value": 291,
+						"reward" : {
+							"value": 5500,
+							"resources":
+							{
+								"crystal" : 3,
+								"gold" : 4000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 20, "type": "dwarf" },
+							{ "amount": 20, "type": "dwarf" },
+							{ "amount": 20, "type": "dwarf", "upgradeChance": 50 },
+							{ "amount": 20, "type": "dwarf" },
+							{ "amount": 20, "type": "dwarf" }
+						],
+						"combat_value": 388,
+						"reward" : {
+							"value": 7500,
+							"resources":
+							{
+								"crystal" : 5,
+								"gold" : 5000
+							}
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 30, "type": "dwarf" },
+							{ "amount": 30, "type": "dwarf" },
+							{ "amount": 30, "type": "dwarf", "upgradeChance": 50 },
+							{ "amount": 30, "type": "dwarf" },
+							{ "amount": 30, "type": "dwarf" }
+						],
+						"combat_value": 582,
+						"reward" : {
+							"value": 12500,
+							"resources":
+							{
+								"crystal" : 10,
+								"gold" : 7500
+							}
+						}
+					}
+				]
+			},
+			"griffinConservatory" : {
+				"index" : 2,
+				"resetDuraition" : 28,
+				"name" : "Griffin Conservatory",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 10, "type": "griffin" },
+							{ "amount": 10, "type": "griffin" },
+							{ "amount": 10, "type": "griffin", "upgradeChance": 50 },
+							{ "amount": 10, "type": "griffin" },
+							{ "amount": 10, "type": "griffin" }
+						],
+						"combat_value": 351,
+						"reward" : {
+							"value": 3000,
+							"creatures": [ { "amount": 1, "type": "angel" } ]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 20, "type": "griffin" },
+							{ "amount": 20, "type": "griffin" },
+							{ "amount": 20, "type": "griffin", "upgradeChance": 50 },
+							{ "amount": 20, "type": "griffin" },
+							{ "amount": 20, "type": "griffin" }
+						],
+						"combat_value": 702,
+						"reward" : {
+							"value": 6000,
+							"creatures": [ { "amount": 2, "type": "angel" } ]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 30, "type": "griffin" },
+							{ "amount": 30, "type": "griffin" },
+							{ "amount": 30, "type": "griffin", "upgradeChance": 50 },
+							{ "amount": 30, "type": "griffin" },
+							{ "amount": 30, "type": "griffin" }
+						],
+						"combat_value": 1053,
+						"reward" : {
+							"value": 9000,
+							"creatures": [ { "amount": 3, "type": "angel" } ]
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 40, "type": "griffin" },
+							{ "amount": 40, "type": "griffin" },
+							{ "amount": 40, "type": "griffin", "upgradeChance": 50 },
+							{ "amount": 40, "type": "griffin" },
+							{ "amount": 40, "type": "griffin" }
+						],
+						"combat_value": 1404,
+						"reward" : {
+							"value": 12000,
+							"creatures": [ { "amount": 4, "type": "angel" } ]
+						}
+					}
+				]
+			},
+			"inpCache" :  {
+				"index" : 3,
+				"resetDuraition" : 28,
+				"name" : "Imp Cache",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 20, "type": "imp" },
+							{ "amount": 20, "type": "imp" },
+							{ "amount": 20, "type": "imp", "upgradeChance": 50 },
+							{ "amount": 20, "type": "imp" },
+							{ "amount": 20, "type": "imp" }
+						],
+						"combat_value": 100,
+						"reward" : {
+							"value": 2000,
+							"resources":
+							{
+								"wood" : 0,
+								"mercury" : 2,
+								"ore" : 0,
+								"sulfur" : 0,
+								"crystal" : 0,
+								"gems" : 0,
+								"gold" : 1000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 30, "type": "imp" },
+							{ "amount": 30, "type": "imp" },
+							{ "amount": 30, "type": "imp", "upgradeChance": 50 },
+							{ "amount": 30, "type": "imp" },
+							{ "amount": 30, "type": "imp" }
+						],
+						"combat_value": 150,
+						"reward" : {
+							"value": 3000,
+							"resources":
+							{
+								"mercury" : 3,
+								"gold" : 1500
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 40, "type": "imp" },
+							{ "amount": 40, "type": "imp" },
+							{ "amount": 40, "type": "imp", "upgradeChance": 50 },
+							{ "amount": 40, "type": "imp" },
+							{ "amount": 40, "type": "imp" }
+						],
+						"combat_value": 200,
+						"reward" : {
+							"value": 4000,
+							"resources":
+							{
+								"mercury" : 4,
+								"gold" : 2000
+							}
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 60, "type": "imp" },
+							{ "amount": 60, "type": "imp" },
+							{ "amount": 60, "type": "imp", "upgradeChance": 50 },
+							{ "amount": 60, "type": "imp" },
+							{ "amount": 60, "type": "imp" }
+						],
+						"combat_value": 300,
+						"reward" : {
+							"value": 6000,
+							"resources":
+							{
+								"mercury" : 6,
+								"gold" : 3000
+							}
+						}
+					}
+				]
+			},
+			"medusaStore" : {
+				"index" : 4,
+				"resetDuraition" : 28,
+				"name" : "Medusa Stores",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 4, "type": "medusa" },
+							{ "amount": 4, "type": "medusa" },
+							{ "amount": 4, "type": "medusa", "upgradeChance": 50 },
+							{ "amount": 4, "type": "medusa" },
+							{ "amount": 4, "type": "medusa" }
+						],
+						"combat_value": 207,
+						"reward" : {
+							"value": 4500,
+							"resources":
+							{
+								"sulfur" : 5,
+								"gold" : 2000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 6, "type": "medusa" },
+							{ "amount": 6, "type": "medusa" },
+							{ "amount": 6, "type": "medusa", "upgradeChance": 50 },
+							{ "amount": 6, "type": "medusa" },
+							{ "amount": 6, "type": "medusa" }
+						],
+						"combat_value": 310,
+						"reward" : {
+							"value": 6000,
+							"resources":
+							{
+								"sulfur" : 6,
+								"gold" : 3000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 8, "type": "medusa" },
+							{ "amount": 8, "type": "medusa" },
+							{ "amount": 8, "type": "medusa", "upgradeChance": 50 },
+							{ "amount": 8, "type": "medusa" },
+							{ "amount": 8, "type": "medusa" }
+						],
+						"combat_value": 414,
+						"reward" : {
+							"value": 8000,
+							"resources":
+							{
+								"sulfur" : 8,
+								"gold" : 4000
+							}
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 10, "type": "medusa" },
+							{ "amount": 10, "type": "medusa" },
+							{ "amount": 10, "type": "medusa", "upgradeChance": 50 },
+							{ "amount": 10, "type": "medusa" },
+							{ "amount": 10, "type": "medusa" }
+						],
+						"combat_value": 517,
+						"reward" : {
+							"value": 10000,
+							"resources":
+							{
+								"sulfur" : 10,
+								"gold" : 5000
+							}
+						}
+					}
+				]
+			},
+			"nagaBank" : {
+				"index" : 5,
+				"resetDuraition" : 28,
+				"name" : "Naga Bank",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 2, "type": "naga" },
+							{ "amount": 2, "type": "naga" },
+							{ "amount": 2, "type": "naga", "upgradeChance": 50 },
+							{ "amount": 2, "type": "naga" },
+							{ "amount": 2, "type": "naga" }
+						],
+						"combat_value": 403,
+						"reward" : {
+							"value": 8000,
+							"resources":
+							{
+								"gems" : 8,
+								"gold" : 4000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 3, "type": "naga" },
+							{ "amount": 3, "type": "naga" },
+							{ "amount": 3, "type": "naga", "upgradeChance": 50 },
+							{ "amount": 3, "type": "naga" },
+							{ "amount": 3, "type": "naga" }
+						],
+						"combat_value": 605,
+						"reward" : {
+							"value": 12000,
+							"resources":
+							{
+								"gems" : 12,
+								"gold" : 6000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 4, "type": "naga" },
+							{ "amount": 4, "type": "naga" },
+							{ "amount": 4, "type": "naga", "upgradeChance": 50 },
+							{ "amount": 4, "type": "naga" },
+							{ "amount": 4, "type": "naga" }
+						],
+						"combat_value": 806,
+						"reward" : {
+							"value": 16000,
+							"resources":
+							{
+								"gems" : 16,
+								"gold" : 8000
+							}
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 6, "type": "naga" },
+							{ "amount": 6, "type": "naga" },
+							{ "amount": 6, "type": "naga", "upgradeChance": 50 },
+							{ "amount": 6, "type": "naga" },
+							{ "amount": 6, "type": "naga" }
+						],
+						"combat_value": 1210,
+						"reward" : {
+							"value": 24000,
+							"resources":
+							{
+								"gems" : 24,
+								"gold" : 12000
+							}
+						}
+					}
+				]
+			},
+			"dragonflyHive" : {
+				"index" : 6,
+				"resetDuraition" : 28,
+				"name" : "Dragon Fly Hive",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 6, "type": "dragonFly" },
+							{ "amount": 6, "type": "dragonFly" },
+							{ "amount": 6, "type": "dragonFly" },
+							{ "amount": 6, "type": "dragonFly" },
+							{ "amount": 6, "type": "dragonFly" }
+						],
+						"combat_value": 154,
+						"reward" : {
+							"value": 3200,
+							"creatures": [ { "amount": 4, "type": "vyvern" } ]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 9, "type": "dragonFly" },
+							{ "amount": 9, "type": "dragonFly" },
+							{ "amount": 9, "type": "dragonFly" },
+							{ "amount": 9, "type": "dragonFly" },
+							{ "amount": 9, "type": "dragonFly" }
+						],
+						"combat_value": 230,
+						"reward" : {
+							"value": 4800,
+							"creatures": [ { "amount": 6, "type": "vyvern" } ]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 12, "type": "dragonFly" },
+							{ "amount": 12, "type": "dragonFly" },
+							{ "amount": 12, "type": "dragonFly" },
+							{ "amount": 12, "type": "dragonFly" },
+							{ "amount": 12, "type": "dragonFly" }
+						],
+						"combat_value": 307,
+						"reward" : {
+							"value": 6400,
+							"creatures": [ { "amount": 8, "type": "vyvern" } ]
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 18, "type": "dragonFly" },
+							{ "amount": 18, "type": "dragonFly" },
+							{ "amount": 18, "type": "dragonFly" },
+							{ "amount": 18, "type": "dragonFly" },
+							{ "amount": 18, "type": "dragonFly" }
+						],
+						"combat_value": 461,
+						"reward" : {
+							"value": 9600,
+							"creatures": [ { "amount": 12, "type": "vyvern" } ]
+						}
+					}
+				]
+			}
+		}
+	},
+	"shipwreck" : {
+		"index" :85,
+		"handler": "bank",
+		"types" : {
+			"shipwreck" : {
+				"index" : 0,
+				"resetDuraition" : 28,
+				"name" : "Shipwreck",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 2, "type": "wight" },
+							{ "amount": 2, "type": "wight" },
+							{ "amount": 2, "type": "wight" },
+							{ "amount": 2, "type": "wight" },
+							{ "amount": 2, "type": "wight" }
+						],
+						"combat_value": 31,
+						"reward" : {
+							"value": 2000,
+							"resources":
+							{
+								"gold" : 2000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 3, "type": "wight" },
+							{ "amount": 3, "type": "wight" },
+							{ "amount": 3, "type": "wight" },
+							{ "amount": 3, "type": "wight" },
+							{ "amount": 3, "type": "wight" }
+						],
+						"combat_value": 46,
+						"reward" : {
+							"value": 3000,
+							"resources":
+							{
+								"gold" : 3000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 5, "type": "wight" },
+							{ "amount": 5, "type": "wight" },
+							{ "amount": 5, "type": "wight" },
+							{ "amount": 5, "type": "wight" },
+							{ "amount": 5, "type": "wight" }
+						],
+						"combat_value": 77,
+						"reward" : {
+							"value": 5000,
+							"resources":
+							{
+								"gold" : 4000
+							},
+							"artifacts": [ { "class" : "TREASURE" } ]
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 10, "type": "wight" },
+							{ "amount": 10, "type": "wight" },
+							{ "amount": 10, "type": "wight" },
+							{ "amount": 10, "type": "wight" },
+							{ "amount": 10, "type": "wight" }
+						],
+						"combat_value": 154,
+						"reward" : {
+							"value": 7000,
+							"resources":
+							{
+								"gold" : 5000
+							},
+							"artifacts": [ { "class" : "MINOR" } ]
+						}
+					}
+				]
+			}
+		}
+	}
+	"derelictShip" : {
+		"index" :24,
+		"handler": "bank",
+		"types" : {
+			"derelictShip" : {
+				"index" : 0,
+				"resetDuraition" : 28,
+				"name" : "Derelict Ship",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 4, "type": "waterElemental" },
+							{ "amount": 4, "type": "waterElemental" },
+							{ "amount": 4, "type": "waterElemental" },
+							{ "amount": 4, "type": "waterElemental" },
+							{ "amount": 4, "type": "waterElemental" }
+						],
+						"combat_value": 138,
+						"reward" : {
+							"value": 3000,
+							"resources":
+							{
+								"gold" : 3000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 6, "type": "waterElemental" },
+							{ "amount": 6, "type": "waterElemental" },
+							{ "amount": 6, "type": "waterElemental" },
+							{ "amount": 6, "type": "waterElemental" },
+							{ "amount": 6, "type": "waterElemental" }
+						],
+						"combat_value": 207,
+						"reward" : {
+							"value": 4000,
+							"resources":
+							{
+								"gold" : 3000
+							},
+							"artifacts": [ { "class" : "TREASURE" } ]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 8, "type": "waterElemental" },
+							{ "amount": 8, "type": "waterElemental" },
+							{ "amount": 8, "type": "waterElemental" },
+							{ "amount": 8, "type": "waterElemental" },
+							{ "amount": 8, "type": "waterElemental" }
+						],
+						"combat_value": 276,
+						"reward" : {
+							"value": 5000,
+							"resources":
+							{
+								"gold" : 4000
+							},
+							"artifacts": [ { "class" : "TREASURE" } ]
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 12, "type": "waterElemental" },
+							{ "amount": 12, "type": "waterElemental" },
+							{ "amount": 12, "type": "waterElemental" },
+							{ "amount": 12, "type": "waterElemental" },
+							{ "amount": 12, "type": "waterElemental" }
+						],
+						"combat_value": 414,
+						"reward" : {
+							"value": 8000,
+							"resources":
+							{
+								"gold" : 6000
+							},
+							"artifacts": [ { "class" : "MINOR" } ]
+						}
+					}
+				]
+			}
+		}
+	},
+	"crypt" : {
+		"index" :84,
+		"handler": "bank",
+		"types" : {
+			"crypt" : {
+				"index" : 0,
+				"resetDuraition" : 28,
+				"name" : "Crypt",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 10, "type": "skeleton" },
+							{ "amount": 10, "type": "walkingDead" },
+							{ "amount": 10, "type": "walkingDead" },
+							{ "amount": 10, "type": "skeleton" },
+							{ "amount": 10, "type": "skeleton" }
+						],
+						"combat_value": 75,
+						"reward" : {
+							"value": 1500,
+							"resources":
+							{
+								"gold" : 1500
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 13, "type": "skeleton" },
+							{ "amount": 10, "type": "walkingDead" },
+							{ "amount": 5, "type": "wight" },
+							{ "amount": 10, "type": "walkingDead" },
+							{ "amount": 12, "type": "skeleton" }
+						],
+						"combat_value": 94,
+						"reward" : {
+							"value": 2000,
+							"resources":
+							{
+								"gold" : 2000
+							}
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 20, "type": "skeleton" },
+							{ "amount": 20, "type": "walkingDead" },
+							{ "amount": 10, "type": "wight" },
+							{ "amount": 5, "type": "vampire" }
+						],
+						"combat_value": 169,
+						"reward" : {
+							"value": 3500,
+							"resources":
+							{
+								"gold" : 2500
+							},
+							"artifacts": [ { "class" : "TREASURE" } ]
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 20, "type": "skeleton" },
+							{ "amount": 20, "type": "walkingDead" },
+							{ "amount": 10, "type": "wight" },
+							{ "amount": 10, "type": "vampire" }
+						],
+						"combat_value": 225,
+						"reward" : {
+							"value": 6000,
+							"resources":
+							{
+								"gold" : 5000
+							},
+							"artifacts": [ { "class" : "TREASURE" } ]
+						}
+					}
+				]
+			}
+		}
+	},
+	"dragonUtopia" : {
+		"index" :25,
+		"handler": "bank",
+		"types" : {
+			"dragonUtopia" : {
+				"index" : 0,
+				"resetDuraition" : 28,
+				"name" : "Dragon Utopia",
+				"levels": [
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 8, "type": "greenDragon" },
+							{ "amount": 5, "type": "redDragon" },
+							{ "amount": 2, "type": "goldDragon" },
+							{ "amount": 1, "type": "blackDragon" }
+						],
+						"combat_value": 769,
+						"reward" : {
+							"value": 38000,
+							"resources":
+							{
+								"gold" : 20000
+							},
+							"artifacts": [
+								{ "class" : "TREASURE" }
+								{ "class" : "MINOR" }
+								{ "class" : "MAJOR" }
+								{ "class" : "RELIC" }
+							]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 8, "type": "greenDragon" },
+							{ "amount": 6, "type": "redDragon" },
+							{ "amount": 3, "type": "goldDragon" },
+							{ "amount": 2, "type": "blackDragon" }
+						],
+						"combat_value": 209,
+						"reward" : {
+							"value": 57000,
+							"resources":
+							{
+								"gold" : 30000
+							},
+							"artifacts": [
+								{ "class" : "MINOR" }
+								{ "class" : "MAJOR" }
+								{ "class" : "RELIC" }
+								{ "class" : "RELIC" }
+							]
+						}
+					},
+					{
+						"chance": 30,
+						"guards": [
+							{ "amount": 8, "type": "greenDragon" },
+							{ "amount": 6, "type": "redDragon" },
+							{ "amount": 4, "type": "goldDragon" },
+							{ "amount": 3, "type": "blackDragon" }
+						],
+						"combat_value": 556,
+						"reward" : {
+							"value": 75000,
+							"resources":
+							{
+								"gold" : 40000
+							},
+							"artifacts": [ 0, 0, 1, 3 ]
+							"artifacts": [
+								{ "class" : "MAJOR" }
+								{ "class" : "RELIC" }
+								{ "class" : "RELIC" }
+								{ "class" : "RELIC" }
+							]
+						}
+					},
+					{
+						"chance": 10,
+						"guards": [
+							{ "amount": 8, "type": "greenDragon" },
+							{ "amount": 7, "type": "redDragon" },
+							{ "amount": 6, "type": "goldDragon" },
+							{ "amount": 5, "type": "blackDragon" }
+						],
+						"combat_value": 343,
+						"reward" : {
+							"value": 90000,
+							"resources":
+							{
+								"gold" : 50000
+							},
+							"artifacts": [ 0, 0, 0, 4 ]
+							"artifacts": [
+								{ "class" : "RELIC" }
+								{ "class" : "RELIC" }
+								{ "class" : "RELIC" }
+								{ "class" : "RELIC" }
+							]
+						}
+					}
+				]
+			}
+		}
+	},
+	"pyramid" : {
+		"index" :63,
+		"handler": "bank",
+		"types" : {
+			"pyramid" : {
+				"index" : 0,
+				"resetDuraition" : 28,
+				"name" : "Pyramid",
+				"levels": [
+					{
+						"chance": 100,
+						"guards": [
+							{ "amount": 20, "type": "goldGolem" },
+							{ "amount": 10, "type": "diamondGolem" },
+							{ "amount": 20, "type": "goldGolem" },
+							{ "amount": 10, "type": "diamondGolem" }
+						],
+						"combat_value": 786,
+						"reward" : {
+							"value": 15000,
+							"spells" : [ { "level" : 5 } ]
+						}
+					}
+				]
+			}
+		}
+	}
+}

+ 7 - 7
config/objects/dwellings.json

@@ -104,9 +104,9 @@
 					[ "fireElemental" ],
 					[ "earthElemental" ]
 				],
-				"guards" : {
-					"earthElemental" : 12
-				}
+				"guards" : [
+					{ "amount" : 12, "type" : "earthElemental" }
+				]
 			},
 			"golemFactory" : {
 				"index" : 1,
@@ -116,10 +116,10 @@
 					[ "goldGolem" ],
 					[ "diamondGolem" ]
 				],
-				"guards" : {
-					"goldGolem" : 9,
-					"diamondGolem" : 6
-				}
+				"guards" : [
+					{ "amount" : 9, "type" : "goldGolem" },
+					{ "amount" : 6, "type" : "diamondGolem" }
+				]
 			}
 		}
 	},

+ 0 - 6
config/objects/generic.json

@@ -16,11 +16,6 @@
 
 	"blackMarket"					: { "index" :7,  "handler": "blackMarket" },
 
-	"crypt"							: { "index" :84, "handler": "bank" },
-	"shipwreck"						: { "index" :85, "handler": "bank" },
-	"derelictShip"					: { "index" :24, "handler": "bank" },
-	"dragonUtopia"					: { "index" :25, "handler": "bank" },
-
 	"pandoraBox"					: { "index" :6,  "handler": "pandora" },
 	"event"							: { "index" :26, "handler": "event" },
 
@@ -45,7 +40,6 @@
 	"magicWell"						: { "index" :49, "handler": "magicWell" },
 	"obelisk"						: { "index" :57, "handler": "obelisk" },
 	"oceanBottle"					: { "index" :59, "handler": "sign" },
-	"pyramid"						: { "index" :63, "handler": "pyramid" },
 	"scholar"						: { "index" :81, "handler": "scholar" },
 	"shipyard"						: { "index" :87, "handler": "shipyard" },
 	"sign"							: { "index" :91, "handler": "sign" },

+ 0 - 3
config/objects/moddables.json

@@ -184,9 +184,6 @@
 		}
 	},
 
-	// subtype: different content
-	"creatureBank" : { "index" :16, "handler": "bank" },
-
 	// subtype: 0 = normal, 1 = anti-magic
 	"garrisonHorizontal"			: { "index" :33, "handler": "garrison" },
 	"garrisonVertical"				: { "index" :219, "handler": "garrison" },

+ 39 - 21
lib/CArtHandler.cpp

@@ -262,12 +262,21 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node)
 	return art;
 }
 
-void CArtHandler::addSlot(CArtifact * art, const std::string & slotID)
+ArtifactPosition CArtHandler::stringToSlot(std::string slotName)
 {
 #define ART_POS(x) ( #x, ArtifactPosition::x )
 	static const std::map<std::string, ArtifactPosition> artifactPositionMap = boost::assign::map_list_of ART_POS_LIST;
 #undef ART_POS
+	auto it = artifactPositionMap.find (slotName);
+	if (it != artifactPositionMap.end())
+		return it->second;
+
+	logGlobal->warnStream() << "Warning! Artifact slot " << slotName << " not recognized!";
+	return ArtifactPosition::PRE_FIRST;
+}
 
+void CArtHandler::addSlot(CArtifact * art, const std::string & slotID)
+{
 	if (slotID == "MISC")
 	{
 		art->possibleSlots[ArtBearer::HERO] += ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5;
@@ -278,14 +287,9 @@ void CArtHandler::addSlot(CArtifact * art, const std::string & slotID)
 	}
 	else
 	{
-		auto it = artifactPositionMap.find (slotID);
-		if (it != artifactPositionMap.end())
-		{
-			auto slot = it->second;
+		auto slot = stringToSlot(slotID);
+		if (slot != ArtifactPosition::PRE_FIRST)
 			art->possibleSlots[ArtBearer::HERO].push_back (slot);
-		}
-		else
-            logGlobal->warnStream() << "Warning! Artifact slot " << slotID << " not recognized!";
 	}
 }
 
@@ -303,7 +307,7 @@ void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node)
 	}
 }
 
-void CArtHandler::loadClass(CArtifact * art, const JsonNode & node)
+CArtifact::EartClass CArtHandler::stringToClass(std::string className)
 {
 	static const std::map<std::string, CArtifact::EartClass> artifactClassMap = boost::assign::map_list_of
 		("TREASURE", CArtifact::ART_TREASURE)
@@ -312,16 +316,17 @@ void CArtHandler::loadClass(CArtifact * art, const JsonNode & node)
 		("RELIC", CArtifact::ART_RELIC)
 		("SPECIAL", CArtifact::ART_SPECIAL);
 
-	auto it = artifactClassMap.find (node["class"].String());
+	auto it = artifactClassMap.find (className);
 	if (it != artifactClassMap.end())
-	{
-		art->aClass = it->second;
-	}
-	else
-	{
-        logGlobal->warnStream() << "Warning! Artifact rarity " << node["class"].String() << " not recognized!";
-		art->aClass = CArtifact::ART_SPECIAL;
-	}
+		return it->second;
+
+	logGlobal->warnStream() << "Warning! Artifact rarity " << className << " not recognized!";
+	return CArtifact::ART_SPECIAL;
+}
+
+void CArtHandler::loadClass(CArtifact * art, const JsonNode & node)
+{
+	art->aClass = stringToClass(node["class"].String());
 }
 
 void CArtHandler::loadType(CArtifact * art, const JsonNode & node)
@@ -424,7 +429,7 @@ CreatureID CArtHandler::machineIDToCreature(ArtifactID id)
 	return CreatureID::NONE; //this artifact is not a creature
 }
 
-ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags)
+ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts)
 {
 	auto getAllowedArts = [&](std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, CArtifact::EartClass flag)
 	{
@@ -433,8 +438,11 @@ ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags)
 
 		for (auto & arts_i : *arts)
 		{
-			CArtifact *art = arts_i;
-			out.push_back(art);
+			if (accepts(arts_i->id))
+			{
+				CArtifact *art = arts_i;
+				out.push_back(art);
+			}
 		}
 	};
 
@@ -469,6 +477,16 @@ ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags)
 	return artID;
 }
 
+ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, std::function<bool(ArtifactID)> accepts)
+{
+	return pickRandomArtifact(rand, 0xff, accepts);
+}
+
+ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags)
+{
+	return pickRandomArtifact(rand, flags, [](ArtifactID){ return true;});
+}
+
 Bonus *createBonus(Bonus::BonusType type, int val, int subtype, Bonus::ValueType valType, shared_ptr<ILimiter> limiter = shared_ptr<ILimiter>(), int additionalInfo = 0)
 {
 	auto added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype);

+ 8 - 2
lib/CArtHandler.h

@@ -203,11 +203,17 @@ public:
 
 	boost::optional<std::vector<CArtifact*>&> listFromClass(CArtifact::EartClass artifactClass);
 
+	ArtifactPosition stringToSlot(std::string slotName);
+	CArtifact::EartClass stringToClass(std::string className);
+
 	/// Gets a artifact ID randomly and removes the selected artifact from this handler.
 	ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags);
+	ArtifactID pickRandomArtifact(CRandomGenerator & rand, std::function<bool(ArtifactID)> accepts);
+	ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts);
+
 	bool legalArtifact(ArtifactID id);
-	void getAllowedArts(std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, int flag);
-	void getAllowed(std::vector<ConstTransitivePtr<CArtifact> > &out, int flags);
+	//void getAllowedArts(std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, int flag);
+	//void getAllowed(std::vector<ConstTransitivePtr<CArtifact> > &out, int flags);
 	bool isBigArtifact (ArtifactID artID) const {return bigArtifacts.find(artID) != bigArtifacts.end();}
 	void initAllowedArtifactsList(const std::vector<bool> &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed
 	static ArtifactID creatureToMachineID(CreatureID id);

+ 1 - 0
lib/CMakeLists.txt

@@ -31,6 +31,7 @@ set(lib_SRCS
 		mapObjects/CQuest.cpp
 		mapObjects/CRewardableConstructor.cpp
 		mapObjects/CRewardableObject.cpp
+		mapObjects/JsonRandom.cpp
 		mapObjects/MiscObjects.cpp
 		mapObjects/ObjectTemplate.cpp
 

+ 1 - 2
lib/NetPacks.h

@@ -1021,8 +1021,7 @@ namespace ObjProperty
 		BONUS_VALUE_FIRST, BONUS_VALUE_SECOND, //used in Rampart for special building that generates resources (storing resource type and quantity)
 
 		//creature-bank specific
-		BANK_DAYCOUNTER, BANK_CLEAR_ARTIFACTS, BANK_ADD_ARTIFACT, BANK_MULTIPLIER, BANK_CONFIG_PRESET, 
-		BANK_CLEAR_CONFIG, BANK_INIT_ARMY, BANK_RESET,
+		BANK_DAYCOUNTER, BANK_RESET, BANK_CLEAR,
 
 		//object with reward
 		REWARD_RESET, REWARD_SELECT

+ 2 - 2
lib/VCMI_Lib.cpp

@@ -45,11 +45,11 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console)
 
 DLL_LINKAGE void loadDLLClasses()
 {
-	try
+	//try
 	{
 		VLC->init();
 	}
-	HANDLE_EXCEPTION;
+	//HANDLE_EXCEPTION;
 }
 
 const IBonusTypeHandler * LibClasses::getBth() const

+ 157 - 271
lib/mapObjects/CBank.cpp

@@ -14,6 +14,8 @@
 #include "../NetPacks.h"
 #include "../CGeneralTextHandler.h"
 #include "../CSoundBase.h"
+#include "CommonConstructors.h"
+#include "../CSpellHandler.h"
 
 using namespace boost::assign;
 
@@ -24,134 +26,51 @@ static std::string & visitedTxt(const bool visited)
 	return VLC->generaltexth->allTexts[id];
 }
 
+CBank::CBank()
+{
+}
+
+CBank::~CBank()
+{
+}
+
 void CBank::initObj()
 {
-	index = VLC->objh->bankObjToIndex(this);
-	bc = nullptr;
 	daycounter = 0;
-	multiplier = 1;
+	resetDuration = 0;
+	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator());
 }
+
 const std::string & CBank::getHoverText() const
 {
 	bool visited = (bc == nullptr);
-	hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited);
+	hoverName = visitedTxt(visited); // FIXME: USE BANK_SPECIFIC NAMES
 	return hoverName;
 }
-void CBank::reset(ui16 var1) //prevents desync
-{
-	ui8 chance = 0;
-	for (auto & elem : VLC->objh->banksInfo[index])
-	{
-		if (var1 < (chance += elem->chance))
-		{
- 			bc = elem;
-			break;
-		}
-	}
-	artifacts.clear();
-}
 
-void CBank::initialize() const
+void CBank::setConfig(const BankConfig & config)
 {
-	cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset
-
-	for (ui8 i = 0; i <= 3; i++)
-	{
-		for (ui8 n = 0; n < bc->artifacts[i]; n++)
-		{
-			CArtifact::EartClass artClass;
-			switch(i)
-			{
-			case 0: artClass = CArtifact::ART_TREASURE; break;
-			case 1: artClass = CArtifact::ART_MINOR; break;
-			case 2: artClass = CArtifact::ART_MAJOR; break;
-			case 3: artClass = CArtifact::ART_RELIC; break;
-			default: assert(0); continue;
-			}
+	bc.reset(new BankConfig(config));
+	clear(); // remove all stacks, if any
 
-			int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass);
-			cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID);
-		}
-	}
-
-	cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army
+	for (auto & stack : config.guards)
+		setCreature (SlotID(stacksCount()), stack.type->idNumber, stack.count);
 }
+
 void CBank::setPropertyDer (ui8 what, ui32 val)
-/// random values are passed as arguments and processed identically on all clients
 {
 	switch (what)
 	{
 		case ObjProperty::BANK_DAYCOUNTER: //daycounter
-			if (val == 0)
-				daycounter = 1; //yes, 1
-			else
-				daycounter++;
-			break;
-		case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent
-			multiplier = val / 100.0;
-			break;
-		case 13: //bank preset
-			bc = VLC->objh->banksInfo[index][val];
+				daycounter+=val;
 			break;
 		case ObjProperty::BANK_RESET:
-			reset (val%100);
-			break;
-		case ObjProperty::BANK_CLEAR_CONFIG:
-			bc = nullptr;
+			initObj();
+			daycounter = 1; //yes, 1 since "today" daycounter won't be incremented
 			break;
-		case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship
-			artifacts.clear();
+		case ObjProperty::BANK_CLEAR:
+			bc.reset();
 			break;
-		case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army
-			{
-				int upgraded = 0;
-				if (val%100 < bc->upgradeChance) //once again anti-desync
-					upgraded = 1;
-				switch (bc->guards.size())
-				{
-					case 1:
-						for	(int i = 0; i < 4; ++i)
-							setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second  / 5 );
-						setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second  / 5 );
-						break;
-					case 4:
-					{
-						if (bc->guards.back().second) //all stacks are present
-						{
-							for (auto & elem : bc->guards)
-							{
-								setCreature (SlotID(stacksCount()), elem.first, elem.second);
-							}
-						}
-						else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt
-						{
-							setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second  / 2 );
-							setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2);
-							setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second);
-							setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 );
-							setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second  / 2) );
-
-						}
-						else //split both stacks
-						{
-							for	(int i = 0; i < 3; ++i) //skellies
-								setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second  / 3);
-							for	(int i = 0; i < 2; ++i) //zombies
-								setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second  / 2);
-						}
-					}
-						break;
-					default:
-                        logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found";
-						return;
-				}
-			}
-			break;
-		case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact
-		{
-			artifacts.push_back (val);
-			break;
-		}
 	}
 }
 
@@ -159,25 +78,19 @@ void CBank::newTurn() const
 {
 	if (bc == nullptr)
 	{
-		if (cb->getDate() == 1)
-			initialize(); //initialize on first day
-		else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries
+		if (resetDuration != 0)
 		{
-			initialize();
-			cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0
-			if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1)
-			{
-				cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once
-				cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0);
-			}
+			if (daycounter >= resetDuration)
+				cb->setObjProperty (id, ObjProperty::BANK_RESET, 0); //daycounter 0
+			else
+				cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++
 		}
-		else
-			cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++
 	}
 }
+
 bool CBank::wasVisited (PlayerColor player) const
 {
-	return !bc;
+	return !bc; //FIXME: player A should not know about visit done by player B
 }
 
 void CBank::onHeroVisit (const CGHeroInstance * h) const
@@ -185,6 +98,7 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const
 	if (bc)
 	{
 		int banktext = 0;
+		ui16 soundID = soundBase::ROGUE;
 		switch (ID)
 		{
 		case Obj::CREATURE_BANK:
@@ -202,13 +116,17 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const
 		case Obj::SHIPWRECK:
 			banktext = 122;
 			break;
+		case Obj::PYRAMID:
+			soundID = soundBase::MYSTERY;
+			banktext = 105;
+			break;
 		}
 		BlockingDialog bd (true, false);
 		bd.player = h->getOwner();
-		bd.soundID = soundBase::ROGUE;
-		bd.text.addTxt(MetaString::ADVOB_TXT,banktext);
-		if (ID == Obj::CREATURE_BANK)
-			bd.text.addReplacement(VLC->objh->creBanksNames[index]);
+		bd.soundID = soundID;
+		bd.text.addTxt(MetaString::ADVOB_TXT, banktext);
+		//if (ID == Obj::CREATURE_BANK)
+		//	bd.text.addReplacement(VLC->objh->creBanksNames[index]); // FIXME: USE BANK SPECIFIC NAMES
 		cb->showBlockingDialog (&bd);
 	}
 	else
@@ -216,105 +134,100 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const
 		InfoWindow iw;
 		iw.soundID = soundBase::GRAVEYARD;
 		iw.player = h->getOwner();
-		if (ID == Obj::CRYPT) //morale penalty for empty Crypt
+		if (ID == Obj::PYRAMID) // You come upon the pyramid ... pyramid is completely empty.
+		{
+			iw.text << VLC->generaltexth->advobtxt[107];
+			iw.components.push_back (Component (Component::LUCK, 0 , -2, 0));
+			GiveBonus gb;
+			gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]);
+			gb.id = h->id.getNum();
+			cb->giveHeroBonus(&gb);
+		}
+		else
+		{
+			iw.text << VLC->generaltexth->advobtxt[33];// This was X, now is completely empty
+			//iw.text.addReplacement(VLC->objh->creBanksNames[index]); // FIXME: USE BANK SPECIFIC NAMES
+		}
+		cb->showInfoDialog(&iw);
+	}
+}
+
+void CBank::doVisit(const CGHeroInstance * hero) const
+{
+	int textID = -1;
+	InfoWindow iw;
+	iw.player = hero->getOwner();
+	MetaString loot;
+
+	switch (ID)
+	{
+	case Obj::CREATURE_BANK:
+	case Obj::DRAGON_UTOPIA:
+		textID = 34;
+		break;
+	case Obj::DERELICT_SHIP:
+		if (!bc)
+			textID = 43;
+		else
 		{
 			GiveBonus gbonus;
-			gbonus.id = h->id.getNum();
+			gbonus.id = hero->id.getNum();
 			gbonus.bonus.duration = Bonus::ONE_BATTLE;
 			gbonus.bonus.source = Bonus::OBJECT;
 			gbonus.bonus.sid = ID;
-			gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98];
+			gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101];
 			gbonus.bonus.type = Bonus::MORALE;
 			gbonus.bonus.val = -1;
 			cb->giveHeroBonus(&gbonus);
-			iw.text << VLC->generaltexth->advobtxt[120];
+			textID = 42;
 			iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
 		}
+		break;
+	case Obj::CRYPT:
+		if (bc)
+			textID = 121;
 		else
 		{
-			iw.text << VLC->generaltexth->advobtxt[33];
-			iw.text.addReplacement(VLC->objh->creBanksNames[index]);
+			iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
+			GiveBonus gbonus;
+			gbonus.id = hero->id.getNum();
+			gbonus.bonus.duration = Bonus::ONE_BATTLE;
+			gbonus.bonus.source = Bonus::OBJECT;
+			gbonus.bonus.sid = ID;
+			gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID];
+			gbonus.bonus.type = Bonus::MORALE;
+			gbonus.bonus.val = -1;
+			cb->giveHeroBonus(&gbonus);
+			textID = 120;
+			iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
 		}
-		cb->showInfoDialog(&iw);
+		break;
+	case Obj::SHIPWRECK:
+		if (bc)
+			textID = 124;
+		else
+			textID = 123;
+		break;
+	case Obj::PYRAMID:
+		textID = 106;
 	}
-}
 
-void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if (result.winner == 0)
+	//grant resources
+	if (bc)
 	{
-		int textID = -1;
-		InfoWindow iw;
-		iw.player = hero->getOwner();
-		MetaString loot;
-
-		switch (ID)
-		{
-		case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA:
-			textID = 34;
-			break;
-		case Obj::DERELICT_SHIP:
-			if (multiplier)
-				textID = 43;
-			else
-			{
-				GiveBonus gbonus;
-				gbonus.id = hero->id.getNum();
-				gbonus.bonus.duration = Bonus::ONE_BATTLE;
-				gbonus.bonus.source = Bonus::OBJECT;
-				gbonus.bonus.sid = ID;
-				gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101];
-				gbonus.bonus.type = Bonus::MORALE;
-				gbonus.bonus.val = -1;
-				cb->giveHeroBonus(&gbonus);
-				textID = 42;
-				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-			}
-			break;
-		case Obj::CRYPT:
-			if (bc->resources.size() != 0)
-				textID = 121;
-			else
-			{
-				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-				GiveBonus gbonus;
-				gbonus.id = hero->id.getNum();
-				gbonus.bonus.duration = Bonus::ONE_BATTLE;
-				gbonus.bonus.source = Bonus::OBJECT;
-				gbonus.bonus.sid = ID;
-				gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID];
-				gbonus.bonus.type = Bonus::MORALE;
-				gbonus.bonus.val = -1;
-				cb->giveHeroBonus(&gbonus);
-				textID = 120;
-				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-			}
-			break;
-		case Obj::SHIPWRECK:
-			if (bc->resources.size())
-				textID = 124;
-			else
-				textID = 123;
-			break;
-		}
-
-		//grant resources
-		if (textID != 42) //empty derelict ship gives no cash
+		for (int it = 0; it < bc->resources.size(); it++)
 		{
-			for (int it = 0; it < bc->resources.size(); it++)
+			if (bc->resources[it] != 0)
 			{
-				if (bc->resources[it] != 0)
-				{
-					iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0));
-					loot << "%d %s";
-					loot.addReplacement(iw.components.back().val);
-					loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype);
-					cb->giveResource (hero->getOwner(), static_cast<Res::ERes>(it), bc->resources[it]);
-				}
+				iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0));
+				loot << "%d %s";
+				loot.addReplacement(iw.components.back().val);
+				loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype);
+				cb->giveResource (hero->getOwner(), static_cast<Res::ERes>(it), bc->resources[it]);
 			}
 		}
 		//grant artifacts
-		for (auto & elem : artifacts)
+		for (auto & elem : bc->artifacts)
 		{
 			iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0));
 			loot << "%s";
@@ -327,22 +240,52 @@ void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &resul
 			iw.text.addTxt (MetaString::ADVOB_TXT, textID);
 			if (textID == 34)
 			{
-				iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first);
+				const CCreature * strongest = boost::range::max_element(bc->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b)
+				{
+					return a.type->fightValue < b.type->fightValue;
+				})->type;
+
+				iw.text.addReplacement(MetaString::CRE_PL_NAMES, strongest->idNumber);
 				iw.text.addReplacement(loot.buildList());
 			}
 			cb->showInfoDialog(&iw);
 		}
+
+		if (!bc->spells.empty())
+		{
+			std::set<SpellID> spells;
+
+			bool noWisdom = false;
+			for (SpellID spell : bc->spells)
+			{
+				iw.text.addTxt (MetaString::SPELL_NAME, spell);
+				if (VLC->spellh->objects[spell]->level <= hero->getSecSkillLevel(SecondarySkill::WISDOM) + 2)
+				{
+					spells.insert(spell);
+					iw.components.push_back(Component (Component::SPELL, spell, 0, 0));
+				}
+				else
+					noWisdom = true;
+			}
+
+			if (!hero->getArt(ArtifactPosition::SPELLBOOK))
+				iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook
+			else if (noWisdom)
+				iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom
+			if (spells.empty())
+				cb->changeSpells (hero, true, spells);
+		}
 		loot.clear();
 		iw.components.clear();
 		iw.text.clear();
 
 		//grant creatures
 		CCreatureSet ourArmy;
-		for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++)
+		for (auto slot : bc->creatures)
 		{
-			SlotID slot = ourArmy.getSlotFor(it->first);
-			ourArmy.addToSlot(slot, it->first, it->second);
+			ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->idNumber), slot.type->idNumber, slot.count);
 		}
+
 		for (auto & elem : ourArmy.Slots())
 		{
 			iw.components.push_back(Component(*elem.second));
@@ -362,82 +305,25 @@ void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &resul
 			cb->showInfoDialog(&iw);
 			cb->giveCreatures(this, hero, ourArmy, false);
 		}
-		cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr
+	cb->setObjProperty (id, ObjProperty::BANK_CLEAR, 0); //bc = nullptr
 	}
 }
 
-void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if (answer)
-	{
-		cb->startBattleI(hero, this, true);
-	}
-}
-
-void CGPyramid::initObj()
-{
-	std::vector<SpellID> available;
-	cb->getAllowedSpells (available, 5);
-	if (available.size())
-	{
-		bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value?
-		spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator());
-	}
-	else
-	{
-        logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty.";
-	}
-	setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start
-}
-const std::string & CGPyramid::getHoverText() const
-{
-	hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr));
-	return hoverName;
-}
-void CGPyramid::onHeroVisit (const CGHeroInstance * h) const
+void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
 {
-	if (bc)
-	{
-		BlockingDialog bd (true, false);
-		bd.player = h->getOwner();
-		bd.soundID = soundBase::MYSTERY;
-		bd.text << VLC->generaltexth->advobtxt[105];
-		cb->showBlockingDialog(&bd);
-	}
-	else
+	if (result.winner == 0)
 	{
-		InfoWindow iw;
-		iw.player = h->getOwner();
-		iw.text << VLC->generaltexth->advobtxt[107];
-		iw.components.push_back (Component (Component::LUCK, 0 , -2, 0));
-		GiveBonus gb;
-		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]);
-		gb.id = h->id.getNum();
-		cb->giveHeroBonus(&gb);
-		cb->showInfoDialog(&iw);
+		doVisit(hero);
 	}
 }
 
-void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
 {
-	if (result.winner == 0)
+	if (answer)
 	{
-		InfoWindow iw;
-		iw.player = hero->getOwner();
-		iw.text.addTxt (MetaString::ADVOB_TXT, 106);
-		iw.text.addTxt (MetaString::SPELL_NAME, spell);
-		if (!hero->getArt(ArtifactPosition::SPELLBOOK))
-			iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook
-		else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3)
-			iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom
+		if (bc) // not looted bank
+			cb->startBattleI(hero, this, true);
 		else
-		{
-			std::set<SpellID> spells;
-			spells.insert (SpellID(spell));
-			cb->changeSpells (hero, true, spells);
-			iw.components.push_back(Component (Component::SPELL, spell, 0, 0));
-		}
-		cb->showInfoDialog(&iw);
-		cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0);
+			doVisit(hero);
 	}
 }

+ 16 - 26
lib/mapObjects/CBank.h

@@ -13,19 +13,26 @@
  *
  */
 
+class BankConfig;
+class CBankInstanceConstructor;
+
 class DLL_LINKAGE CBank : public CArmedInstance
 {
-	public:
-	int index; //banks have unusal numbering - see ZCRBANK.txt and initObj()
-	BankConfig *bc;
-	double multiplier; //for improved banks script
-	std::vector<ui32> artifacts; //fixed and deterministic
+	std::unique_ptr<BankConfig> bc;
 	ui32 daycounter;
+	ui32 resetDuration;
+
+	void setPropertyDer(ui8 what, ui32 val) override;
+	void doVisit(const CGHeroInstance * hero) const;
+
+public:
+	CBank();
+	~CBank();
+
+	void setConfig(const BankConfig & bc);
 
 	void initObj() override;
 	const std::string & getHoverText() const override;
-	void initialize() const;
-	void reset(ui16 var1);
 	void newTurn() const override;
 	bool wasVisited (PlayerColor player) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
@@ -35,25 +42,8 @@ class DLL_LINKAGE CBank : public CArmedInstance
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CArmedInstance&>(*this);
-		h & index & multiplier & artifacts & daycounter & bc;
+		h & daycounter & bc & resetDuration;
 	}
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-};
-class DLL_LINKAGE CGPyramid : public CBank
-{
-public:
-	ui16 spell;
 
-	void initObj() override;
-	const std::string & getHoverText() const override;
-	void newTurn() const override {}; //empty, no reset
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CBank&>(*this);
-		h & spell;
-	}
+	friend class CBankInstanceConstructor;
 };

+ 6 - 2
lib/mapObjects/CObjectClassesHandler.cpp

@@ -35,13 +35,13 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER_CLASS("dwelling", CDwellingInstanceConstructor);
 	SET_HANDLER_CLASS("hero", CHeroInstanceConstructor);
 	SET_HANDLER_CLASS("town", CTownInstanceConstructor);
+	SET_HANDLER_CLASS("bank", CBankInstanceConstructor);
 
 	SET_HANDLER_CLASS("static", CObstacleConstructor);
 	SET_HANDLER_CLASS("", CObstacleConstructor);
 
 	SET_HANDLER("generic", CGObjectInstance);
 	SET_HANDLER("market", CGMarket);
-	SET_HANDLER("bank", CBank);
 	SET_HANDLER("cartographer", CCartographer);
 	SET_HANDLER("artifact", CGArtifact);
 	SET_HANDLER("blackMarket", CGBlackMarket);
@@ -67,7 +67,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("pandora", CGPandoraBox);
 	SET_HANDLER("pickable", CGPickable);
 	SET_HANDLER("prison", CGHeroInstance);
-	SET_HANDLER("pyramid", CGPyramid);
 	SET_HANDLER("questGuard", CGQuestGuard);
 	SET_HANDLER("resource", CGResource);
 	SET_HANDLER("scholar", CGScholar);
@@ -130,6 +129,11 @@ si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID)
 
 void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj)
 {
+	if (!handlerConstructors.count(obj->handlerName))
+	{
+		logGlobal->errorStream() << "Handler with name " << obj->handlerName << " was not found!";
+		return;
+	}
 	auto handler = handlerConstructors.at(obj->handlerName)();
 	handler->init(entry);
 

+ 36 - 11
lib/mapObjects/CObjectClassesHandler.h

@@ -50,20 +50,45 @@ struct RandomMapInfo
 class IObjectInfo
 {
 public:
-	virtual bool givesResources() const = 0;
+	struct CArmyStructure
+	{
+		ui32 totalStrength;
+		ui32 shootersStrength;
+		ui32 flyersStrength;
+		ui32 walkersStrength;
+
+		CArmyStructure() :
+			totalStrength(0),
+			shootersStrength(0),
+			flyersStrength(0),
+			walkersStrength(0)
+		{}
+
+		bool operator <(const CArmyStructure & other)
+		{
+			return this->totalStrength < other.totalStrength;
+		}
+	};
+
+	/// Returns possible composition of guards. Actual guards would be
+	/// somewhere between these two values
+	virtual CArmyStructure minGuards() const { return CArmyStructure(); }
+	virtual CArmyStructure maxGuards() const { return CArmyStructure(); }
+
+	virtual bool givesResources() const { return false; }
 
-	virtual bool givesExperience() const = 0;
-	virtual bool givesMana() const = 0;
-	virtual bool givesMovement() const = 0;
+	virtual bool givesExperience() const { return false; }
+	virtual bool givesMana() const { return false; }
+	virtual bool givesMovement() const { return false; }
 
-	virtual bool givesPrimarySkills() const = 0;
-	virtual bool givesSecondarySkills() const = 0;
+	virtual bool givesPrimarySkills() const { return false; }
+	virtual bool givesSecondarySkills() const { return false; }
 
-	virtual bool givesArtifacts() const = 0;
-	virtual bool givesCreatures() const = 0;
-	virtual bool givesSpells() const = 0;
+	virtual bool givesArtifacts() const { return false; }
+	virtual bool givesCreatures() const { return false; }
+	virtual bool givesSpells() const { return false; }
 
-	virtual bool givesBonuses() const = 0;
+	virtual bool givesBonuses() const { return false; }
 };
 
 class CGObjectInstance;
@@ -118,7 +143,7 @@ public:
 	virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0;
 
 	/// Returns object configuration, if available. Othervice returns NULL
-	virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0;
+	virtual std::unique_ptr<IObjectInfo> getObjectInfo(ObjectTemplate tmpl) const = 0;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 0 - 101
lib/mapObjects/CObjectHandler.cpp

@@ -108,53 +108,6 @@ void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const
 void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const
 {}
 
-// Bank helper. Find the creature ID and their number, and store the
-// result in storage (either guards or reward creatures).
-static void readCreatures(const JsonNode &creature, std::vector< std::pair <CreatureID, ui32> > &storage)
-{
-	std::pair<CreatureID, si32> creInfo = std::make_pair(CreatureID::NONE, 0);
-
-	//TODO: replace numeric id's with mod-friendly string id's
-	creInfo.second = creature["number"].Float();
-	creInfo.first = CreatureID((si32)creature["id"].Float());
-	storage.push_back(creInfo);
-}
-
-// Bank helper. Process a bank level.
-static void readBankLevel(const JsonNode &level, BankConfig &bc)
-{
-	int idx;
-
-	bc.chance = level["chance"].Float();
-
-	for(const JsonNode &creature : level["guards"].Vector())
-	{
-		readCreatures(creature, bc.guards);
-	}
-
-	bc.upgradeChance = level["upgrade_chance"].Float();
-	bc.combatValue = level["combat_value"].Float();
-
-	bc.resources = Res::ResourceSet(level["reward_resources"]);
-
-	for(const JsonNode &creature : level["reward_creatures"].Vector())
-	{
-		readCreatures(creature, bc.creatures);
-	}
-
-	bc.artifacts.resize(4);
-	idx = 0;
-	for(const JsonNode &artifact : level["reward_artifacts"].Vector())
-	{
-		bc.artifacts[idx] = artifact.Float();
-		idx ++;
-	}
-
-	bc.value = level["value"].Float();
-	bc.rewardDifficulty = level["profitability"].Float();
-	bc.easiest = level["easiest"].Float();
-}
-
 CObjectHandler::CObjectHandler()
 {
     logGlobal->traceStream() << "\t\tReading resources prices ";
@@ -164,62 +117,8 @@ CObjectHandler::CObjectHandler()
 		resVals.push_back(price.Float());
 	}
     logGlobal->traceStream() << "\t\tDone loading resource prices!";
-
-    logGlobal->traceStream() << "\t\tReading banks configs";
-	const JsonNode config3(ResourceID("config/bankconfig.json"));
-	int bank_num = 0;
-	for(const JsonNode &bank : config3["banks"].Vector())
-	{
-		creBanksNames[bank_num] = bank["name"].String();
-
-		int level_num = 0;
-		for(const JsonNode &level : bank["levels"].Vector())
-		{
-			banksInfo[bank_num].push_back(new BankConfig);
-			BankConfig &bc = *banksInfo[bank_num].back();
-			bc.level = level_num;
-
-			readBankLevel(level, bc);
-			level_num ++;
-		}
-
-		bank_num ++;
-	}
-    logGlobal->traceStream() << "\t\tDone loading banks configs";
 }
 
-CObjectHandler::~CObjectHandler()
-{
-	for(auto & mapEntry : banksInfo)
-	{
-		for(auto & vecEntry : mapEntry.second)
-		{
-			vecEntry.dellNull();
-		}
-	}
-}
-
-int CObjectHandler::bankObjToIndex (const CGObjectInstance * obj)
-{
-	switch (obj->ID) //find appriopriate key
-	{
-	case Obj::CREATURE_BANK:
-		return obj->subID;
-	case Obj::DERELICT_SHIP:
-		return 8;
-	case Obj::DRAGON_UTOPIA:
-		return 10;
-	case Obj::CRYPT:
-		return 9;
-	case Obj::SHIPWRECK:
-		return 7;
-	case Obj::PYRAMID:
-		return 21;
-	default:
-        logGlobal->warnStream() << "Unrecognized Bank indetifier!";
-		return 0;
-	}
-}
 PlayerColor CGObjectInstance::getOwner() const
 {
 	//if (state)

+ 1 - 27
lib/mapObjects/CObjectHandler.h

@@ -163,41 +163,15 @@ private:
 	CGObjectInstance * obj;
 };
 
-struct BankConfig
-{
-	BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; };
-	ui8 level; //1 - 4, how hard the battle will be
-	ui8 chance; //chance for this level being chosen
-	ui8 upgradeChance; //chance for creatures to be in upgraded versions
-	std::vector< std::pair <CreatureID, ui32> > guards; //creature ID, amount
-	ui32 combatValue; //how hard are guards of this level
-	Res::ResourceSet resources; //resources given in case of victory
-	std::vector< std::pair <CreatureID, ui32> > creatures; //creatures granted in case of victory (creature ID, amount)
-	std::vector<ui16> artifacts; //number of artifacts given in case of victory [0] -> treasure, [1] -> minor [2] -> major [3] -> relic
-	ui32 value; //overall value of given things
-	ui32 rewardDifficulty; //proportion of reward value to difficulty of guards; how profitable is this creature Bank config
-	ui16 easiest; //?!?
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & level & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & rewardDifficulty & easiest;
-	}
-};
-
 class DLL_LINKAGE CObjectHandler
 {
 public:
-	std::map <ui32, std::vector < ConstTransitivePtr<BankConfig> > > banksInfo; //[index][preset]
-	std::map <ui32, std::string> creBanksNames; //[crebank index] -> name of this creature bank
 	std::vector<ui32> resVals; //default values of resources in gold
 
 	CObjectHandler();
-	~CObjectHandler();
-
-	int bankObjToIndex (const CGObjectInstance * obj);
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & banksInfo & creBanksNames & resVals;
+		h & resVals;
 	}
 };

+ 29 - 118
lib/mapObjects/CRewardableConstructor.cpp

@@ -4,6 +4,7 @@
 #include "../CRandomGenerator.h"
 #include "../StringConstants.h"
 #include "../CCreatureHandler.h"
+#include "JsonRandom.h"
 
 /*
  * CRewardableConstructor.cpp, part of VCMI engine
@@ -16,100 +17,6 @@
  */
 
 namespace {
-	si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0)
-	{
-		if (value.isNull())
-			return defaultValue;
-		if (value.getType() == JsonNode::DATA_FLOAT)
-			return value.Float();
-		si32 min = value["min"].Float();
-		si32 max = value["max"].Float();
-		return rng.getIntRange(min, max)();
-	}
-
-	TResources loadResources(const JsonNode & value, CRandomGenerator & rng)
-	{
-		TResources ret;
-		for (size_t i=0; i<GameConstants::RESOURCE_QUANTITY; i++)
-		{
-			ret[i] = loadValue(value[GameConstants::RESOURCE_NAMES[i]], rng);
-		}
-		return ret;
-	}
-
-	std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng)
-	{
-		std::vector<si32> ret;
-		for (auto & name : PrimarySkill::names)
-		{
-			ret.push_back(loadValue(value[name], rng));
-		}
-		return ret;
-	}
-
-	std::map<SecondarySkill, si32> loadSecondary(const JsonNode & value, CRandomGenerator & rng)
-	{
-		std::map<SecondarySkill, si32> ret;
-		for (auto & pair : value.Struct())
-		{
-			SecondarySkill id(VLC->modh->identifiers.getIdentifier(pair.second.meta, "skill", pair.first).get());
-			ret[id] = loadValue(pair.second, rng);
-		}
-		return ret;
-	}
-
-	std::vector<ArtifactID> loadArtifacts(const JsonNode & value, CRandomGenerator & rng)
-	{
-		std::vector<ArtifactID> ret;
-		for (const JsonNode & entry : value.Vector())
-		{
-			ArtifactID art(VLC->modh->identifiers.getIdentifier("artifact", entry).get());
-			ret.push_back(art);
-		}
-		return ret;
-	}
-
-	std::vector<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng)
-	{
-		std::vector<SpellID> ret;
-		for (const JsonNode & entry : value.Vector())
-		{
-			SpellID spell(VLC->modh->identifiers.getIdentifier("spell", entry).get());
-			ret.push_back(spell);
-		}
-		return ret;
-	}
-
-	std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng)
-	{
-		std::vector<CStackBasicDescriptor> ret;
-		for (auto & pair : value.Struct())
-		{
-			CStackBasicDescriptor stack;
-			stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier(pair.second.meta, "creature", pair.first).get()];
-			stack.count = loadValue(pair.second, rng);
-			ret.push_back(stack);
-		}
-		return ret;
-	}
-
-	std::vector<Bonus> loadBonuses(const JsonNode & value)
-	{
-		std::vector<Bonus> ret;
-		for (const JsonNode & entry : value.Vector())
-		{
-			Bonus * bonus = JsonUtils::parseBonus(entry);
-			ret.push_back(*bonus);
-			delete bonus;
-		}
-		return ret;
-	}
-
-	std::vector<Component> loadComponents(const JsonNode & value)
-	{
-		//TODO
-	}
-
 	MetaString loadMessage(const JsonNode & value)
 	{
 		MetaString ret;
@@ -167,38 +74,42 @@ void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRando
 		const JsonNode & limiter = reward["limiter"];
 		CVisitInfo info;
 		// load limiter
-		info.limiter.numOfGrants = loadValue(limiter["numOfGrants"], rng);
-		info.limiter.dayOfWeek = loadValue(limiter["dayOfWeek"], rng);
-		info.limiter.minLevel = loadValue(limiter["minLevel"], rng);
-		info.limiter.resources = loadResources(limiter["resources"], rng);
+		info.limiter.numOfGrants = JsonRandom::loadValue(limiter["numOfGrants"], rng);
+		info.limiter.dayOfWeek = JsonRandom::loadValue(limiter["dayOfWeek"], rng);
+		info.limiter.minLevel = JsonRandom::loadValue(limiter["minLevel"], rng);
+		info.limiter.resources = JsonRandom::loadResources(limiter["resources"], rng);
+
+		info.limiter.primary = JsonRandom::loadPrimary(limiter["primary"], rng);
+		info.limiter.secondary = JsonRandom::loadSecondary(limiter["secondary"], rng);
+		info.limiter.artifacts = JsonRandom::loadArtifacts(limiter["artifacts"], rng);
+		info.limiter.creatures = JsonRandom::loadCreatures(limiter["creatures"], rng);
 
-		info.limiter.primary = loadPrimary(limiter["primary"], rng);
-		info.limiter.secondary = loadSecondary(limiter["secondary"], rng);
-		info.limiter.artifacts = loadArtifacts(limiter["artifacts"], rng);
-		info.limiter.creatures = loadCreatures(limiter["creatures"], rng);
+		info.reward.resources = JsonRandom::loadResources(reward["resources"], rng);
 
-		info.reward.resources = loadResources(reward["resources"], rng);
+		info.reward.gainedExp = JsonRandom::loadValue(reward["gainedExp"], rng);
+		info.reward.gainedLevels = JsonRandom::loadValue(reward["gainedLevels"], rng);
 
-		info.reward.gainedExp = loadValue(reward["gainedExp"], rng);
-		info.reward.gainedLevels = loadValue(reward["gainedLevels"], rng);
+		info.reward.manaDiff = JsonRandom::loadValue(reward["manaPoints"], rng);
+		info.reward.manaPercentage = JsonRandom::loadValue(reward["manaPercentage"], rng, -1);
 
-		info.reward.manaDiff = loadValue(reward["manaPoints"], rng);
-		info.reward.manaPercentage = loadValue(reward["manaPercentage"], rng, -1);
+		info.reward.movePoints = JsonRandom::loadValue(reward["movePoints"], rng);
+		info.reward.movePercentage = JsonRandom::loadValue(reward["movePercentage"], rng, -1);
 
-		info.reward.movePoints = loadValue(reward["movePoints"], rng);
-		info.reward.movePercentage = loadValue(reward["movePercentage"], rng, -1);
+		info.reward.bonuses = JsonRandom::loadBonuses(reward["bonuses"]);
 
-		info.reward.bonuses = loadBonuses(reward["bonuses"]);
+		info.reward.primary = JsonRandom::loadPrimary(reward["primary"], rng);
+		info.reward.secondary = JsonRandom::loadSecondary(reward["secondary"], rng);
 
-		info.reward.primary = loadPrimary(reward["primary"], rng);
-		info.reward.secondary = loadSecondary(reward["secondary"], rng);
+		std::vector<SpellID> spells;
+		for (size_t i=0; i<6; i++)
+			IObjectInterface::cb->getAllowedSpells(spells, i);
 
-		info.reward.artifacts = loadArtifacts(reward["artifacts"], rng);
-		info.reward.spells = loadSpells(reward["spells"], rng);
-		info.reward.creatures = loadCreatures(reward["creatures"], rng);
+		info.reward.artifacts = JsonRandom::loadArtifacts(reward["artifacts"], rng);
+		info.reward.spells = JsonRandom::loadSpells(reward["spells"], rng, spells);
+		info.reward.creatures = JsonRandom::loadCreatures(reward["creatures"], rng);
 
 		info.message = loadMessage(reward["message"]);
-		info.selectChance = loadValue(reward["selectChance"], rng);
+		info.selectChance = JsonRandom::loadValue(reward["selectChance"], rng);
 	}
 
 	object->onSelect  = loadMessage(parameters["onSelectMessage"]);
@@ -284,7 +195,7 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG
 	objectInfo.configureObject(dynamic_cast<CRewardableObject*>(object), rng);
 }
 
-const IObjectInfo * CRewardableConstructor::getObjectInfo(ObjectTemplate tmpl) const
+std::unique_ptr<IObjectInfo> CRewardableConstructor::getObjectInfo(ObjectTemplate tmpl) const
 {
-	return &objectInfo;
+	return std::unique_ptr<IObjectInfo>(new CRandomRewardObjectInfo(objectInfo));
 }

+ 1 - 1
lib/mapObjects/CRewardableConstructor.h

@@ -54,5 +54,5 @@ public:
 
 	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
 
-	const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const override;
+	std::unique_ptr<IObjectInfo> getObjectInfo(ObjectTemplate tmpl) const override;
 };

+ 175 - 28
lib/mapObjects/CommonConstructors.cpp

@@ -3,9 +3,11 @@
 
 #include "CGTownInstance.h"
 #include "CGHeroInstance.h"
+#include "CBank.h"
 #include "../mapping/CMap.h"
 #include "../CHeroHandler.h"
 #include "../CCreatureHandler.h"
+#include "JsonRandom.h"
 
 /*
  * CommonConstructors.cpp, part of VCMI engine
@@ -176,33 +178,6 @@ CGObjectInstance * CDwellingInstanceConstructor::create(ObjectTemplate tmpl) con
 	return obj;
 }
 
-namespace
-{
-	si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0)
-	{
-		if (value.isNull())
-			return defaultValue;
-		if (value.getType() == JsonNode::DATA_FLOAT)
-			return value.Float();
-		si32 min = value["min"].Float();
-		si32 max = value["max"].Float();
-		return rng.getIntRange(min, max)();
-	}
-
-	std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng)
-	{
-		std::vector<CStackBasicDescriptor> ret;
-		for (auto & pair : value.Struct())
-		{
-			CStackBasicDescriptor stack;
-			stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier(pair.second.meta, "creature", pair.first).get()];
-			stack.count = loadValue(pair.second, rng);
-			ret.push_back(stack);
-		}
-		return ret;
-	}
-}
-
 void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator &rng) const
 {
 	CGDwelling * dwelling = dynamic_cast<CGDwelling*>(object);
@@ -221,7 +196,7 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR
 		const CCreature * crea = availableCreatures.at(0).at(0);
 		dwelling->putStack(SlotID(0), new CStackInstance(crea->idNumber, crea->growth * 3 ));
 	}
-	else for (auto & stack : loadCreatures(guards, rng))
+	else for (auto & stack : JsonRandom::loadCreatures(guards, rng))
 	{
 		dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count));
 	}
@@ -237,3 +212,175 @@ bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) cons
 	}
 	return false;
 }
+
+CBankInstanceConstructor::CBankInstanceConstructor()
+{
+}
+
+void CBankInstanceConstructor::initTypeData(const JsonNode & input)
+{
+	//TODO: name = input["name"].String();
+	levels = input["levels"].Vector();
+	bankResetDuration = input["resetDuration"].Float();
+}
+
+CGObjectInstance *CBankInstanceConstructor::create(ObjectTemplate tmpl) const
+{
+	return createTyped(tmpl);
+}
+
+BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRandomGenerator & rng) const
+{
+	BankConfig bc;
+
+	bc.chance = level["chance"].Float();
+
+	bc.guards = JsonRandom::loadCreatures(level["guards"], rng);
+	bc.upgradeChance = level["upgrade_chance"].Float();
+	bc.combatValue = level["combat_value"].Float();
+
+	std::vector<SpellID> spells;
+	for (size_t i=0; i<6; i++)
+		IObjectInterface::cb->getAllowedSpells(spells, i);
+
+	bc.resources = Res::ResourceSet(level["reward"]["resources"]);
+	bc.creatures = JsonRandom::loadCreatures(level["reward"]["creatures"], rng);
+	bc.artifacts = JsonRandom::loadArtifacts(level["reward"]["artifacts"], rng);
+	bc.spells    = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells);
+
+	bc.value = level["value"].Float();
+
+	return bc;
+}
+
+void CBankInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
+{
+	auto bank = dynamic_cast<CBank*>(object);
+
+	bank->resetDuration = bankResetDuration;
+
+	si32 totalChance = 0;
+	for (auto & node : levels)
+		totalChance += node["chance"].Float();
+
+	assert(totalChance != 0);
+
+	si32 selectedChance = rng.nextInt(totalChance - 1);
+
+	for (auto & node : levels)
+	{
+		if (selectedChance < node["chance"].Float())
+		{
+			 bank->setConfig(generateConfig(node, rng));
+		}
+		else
+		{
+			selectedChance -= node["chance"].Float();
+		}
+
+	}
+}
+
+CBankInfo::CBankInfo(JsonVector config):
+	config(config)
+{
+}
+
+static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature * crea, si32 amount)
+{
+	army.totalStrength += crea->fightValue * amount;
+
+	bool walker = true;
+	if (crea->hasBonusOfType(Bonus::SHOOTER))
+	{
+		army.shootersStrength += crea->fightValue * amount;
+		walker = false;
+	}
+	if (crea->hasBonusOfType(Bonus::FLYING))
+	{
+		army.flyersStrength += crea->fightValue * amount;
+		walker = false;
+	}
+	if (walker)
+		army.walkersStrength += crea->fightValue * amount;
+}
+
+IObjectInfo::CArmyStructure CBankInfo::minGuards() const
+{
+	std::vector<IObjectInfo::CArmyStructure> armies;
+	for (auto configEntry : config)
+	{
+		auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
+		IObjectInfo::CArmyStructure army;
+		for (auto & stack : stacks)
+		{
+			assert(!stack.allowedCreatures.empty());
+			auto weakest = boost::range::min_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b)
+			{
+				return a->fightValue < b->fightValue;
+			});
+			addStackToArmy(army, *weakest, stack.minAmount);
+		}
+		armies.push_back(army);
+	}
+	return *boost::range::min_element(armies);
+}
+
+IObjectInfo::CArmyStructure CBankInfo::maxGuards() const
+{
+	std::vector<IObjectInfo::CArmyStructure> armies;
+	for (auto configEntry : config)
+	{
+		auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
+		IObjectInfo::CArmyStructure army;
+		for (auto & stack : stacks)
+		{
+			assert(!stack.allowedCreatures.empty());
+			auto strongest = boost::range::max_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b)
+			{
+				return a->fightValue < b->fightValue;
+			});
+			addStackToArmy(army, *strongest, stack.maxAmount);
+		}
+		armies.push_back(army);
+	}
+	return *boost::range::max_element(armies);
+}
+
+bool CBankInfo::givesResources() const
+{
+	for (const JsonNode & node : config)
+		if (!node["reward"]["resources"].isNull())
+			return true;
+	return false;
+}
+
+bool CBankInfo::givesArtifacts() const
+{
+	for (const JsonNode & node : config)
+		if (!node["reward"]["artifacts"].isNull())
+			return true;
+	return false;
+}
+
+bool CBankInfo::givesCreatures() const
+{
+	for (const JsonNode & node : config)
+		if (!node["reward"]["creatures"].isNull())
+			return true;
+	return false;
+}
+
+bool CBankInfo::givesSpells() const
+{
+	for (const JsonNode & node : config)
+		if (!node["reward"]["spells"].isNull())
+			return true;
+	return false;
+}
+
+
+std::unique_ptr<IObjectInfo> CBankInstanceConstructor::getObjectInfo(ObjectTemplate tmpl) const
+{
+	return std::unique_ptr<IObjectInfo>(new CBankInfo(levels));
+}

+ 59 - 4
lib/mapObjects/CommonConstructors.h

@@ -19,6 +19,8 @@ class CGDwelling;
 //class CGArtifact;
 //class CGCreature;
 class CHeroClass;
+class CBank;
+class CStackBasicDescriptor;
 
 /// Class that is used for objects that do not have dedicated handler
 template<class ObjectType>
@@ -45,7 +47,7 @@ public:
 	{
 	}
 
-	virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const
+	virtual std::unique_ptr<IObjectInfo> getObjectInfo(ObjectTemplate tmpl) const
 	{
 		return nullptr;
 	}
@@ -63,6 +65,7 @@ class CTownInstanceConstructor : public CDefaultObjectTypeHandler<CGTownInstance
 	JsonNode filtersJson;
 protected:
 	bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
+	void initTypeData(const JsonNode & input);
 
 public:
 	CFaction * faction;
@@ -70,7 +73,6 @@ public:
 
 	CTownInstanceConstructor();
 	CGObjectInstance * create(ObjectTemplate tmpl) const;
-	void initTypeData(const JsonNode & input);
 	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const;
 	void afterLoadFinalization();
 
@@ -86,6 +88,7 @@ class CHeroInstanceConstructor : public CDefaultObjectTypeHandler<CGHeroInstance
 	JsonNode filtersJson;
 protected:
 	bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
+	void initTypeData(const JsonNode & input);
 
 public:
 	CHeroClass * heroClass;
@@ -93,7 +96,6 @@ public:
 
 	CHeroInstanceConstructor();
 	CGObjectInstance * create(ObjectTemplate tmpl) const;
-	void initTypeData(const JsonNode & input);
 	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const;
 	void afterLoadFinalization();
 
@@ -112,12 +114,12 @@ class CDwellingInstanceConstructor : public CDefaultObjectTypeHandler<CGDwelling
 
 protected:
 	bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
+	void initTypeData(const JsonNode & input);
 
 public:
 
 	CDwellingInstanceConstructor();
 	CGObjectInstance * create(ObjectTemplate tmpl) const;
-	void initTypeData(const JsonNode & input);
 	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const;
 
 	bool producesCreature(const CCreature * crea) const;
@@ -128,3 +130,56 @@ public:
 		h & static_cast<CDefaultObjectTypeHandler<CGDwelling>&>(*this);
 	}
 };
+
+struct BankConfig
+{
+	BankConfig() { chance = upgradeChance = combatValue = value = 0; };
+	ui32 value; //overall value of given things
+	ui32 chance; //chance for this level being chosen
+	ui32 upgradeChance; //chance for creatures to be in upgraded versions
+	ui32 combatValue; //how hard are guards of this level
+	std::vector<CStackBasicDescriptor> guards; //creature ID, amount
+	Res::ResourceSet resources; //resources given in case of victory
+	std::vector<CStackBasicDescriptor> creatures; //creatures granted in case of victory (creature ID, amount)
+	std::vector<ArtifactID> artifacts; //artifacts given in case of victory
+	std::vector<SpellID> spells; // granted spell(s), for Pyramid
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & spells;
+	}
+};
+
+class CBankInfo : public IObjectInfo
+{
+	JsonVector config;
+public:
+	CBankInfo(JsonVector config);
+
+	CArmyStructure minGuards() const;
+	CArmyStructure maxGuards() const;
+	bool givesResources() const;
+	bool givesArtifacts() const;
+	bool givesCreatures() const;
+	bool givesSpells() const;
+};
+
+class CBankInstanceConstructor : public CDefaultObjectTypeHandler<CBank>
+{
+	BankConfig generateConfig(const JsonNode & conf, CRandomGenerator & rng) const;
+
+	JsonVector levels;
+protected:
+	void initTypeData(const JsonNode & input);
+
+public:
+	// all banks of this type will be reset N days after clearing,
+	si32 bankResetDuration;
+
+	CBankInstanceConstructor();
+
+	CGObjectInstance *create(ObjectTemplate tmpl) const;
+	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const;
+
+	std::unique_ptr<IObjectInfo> getObjectInfo(ObjectTemplate tmpl) const;
+};

+ 225 - 0
lib/mapObjects/JsonRandom.cpp

@@ -0,0 +1,225 @@
+/*
+ *
+ * CRewardableObject.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "JsonRandom.h"
+
+#include "../JsonNode.h"
+#include "../CRandomGenerator.h"
+#include "../StringConstants.h"
+#include "../VCMI_Lib.h"
+#include "../CModHandler.h"
+#include "../CArtHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CCreatureSet.h"
+#include "../CSpellHandler.h"
+
+namespace JsonRandom
+{
+	si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue)
+	{
+		if (value.isNull())
+			return defaultValue;
+		if (value.getType() == JsonNode::DATA_FLOAT)
+			return value.Float();
+		if (!value["amount"].isNull())
+			return value["amount"].Float();
+		si32 min = value["min"].Float();
+		si32 max = value["max"].Float();
+		return rng.getIntRange(min, max)();
+	}
+
+	TResources loadResources(const JsonNode & value, CRandomGenerator & rng)
+	{
+		TResources ret;
+		for (size_t i=0; i<GameConstants::RESOURCE_QUANTITY; i++)
+		{
+			ret[i] = loadValue(value[GameConstants::RESOURCE_NAMES[i]], rng);
+		}
+		return ret;
+	}
+
+	std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::vector<si32> ret;
+		for (auto & name : PrimarySkill::names)
+		{
+			ret.push_back(loadValue(value[name], rng));
+		}
+		return ret;
+	}
+
+	std::map<SecondarySkill, si32> loadSecondary(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::map<SecondarySkill, si32> ret;
+		for (auto & pair : value.Struct())
+		{
+			SecondarySkill id(VLC->modh->identifiers.getIdentifier(pair.second.meta, "skill", pair.first).get());
+			ret[id] = loadValue(pair.second, rng);
+		}
+		return ret;
+	}
+
+	ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng)
+	{
+		if (value.getType() == JsonNode::DATA_STRING)
+			return ArtifactID(VLC->modh->identifiers.getIdentifier("artifact", value).get());
+
+		std::set<CArtifact::EartClass> allowedClasses;
+		std::set<ArtifactPosition> allowedPositions;
+		ui32 minValue = 0;
+		ui32 maxValue = std::numeric_limits<ui32>::max();
+
+		if (value["class"].getType() == JsonNode::DATA_STRING)
+			allowedClasses.insert(VLC->arth->stringToClass(value["class"].String()));
+		else
+			for (auto & entry : value["class"].Vector())
+				allowedClasses.insert(VLC->arth->stringToClass(entry.String()));
+
+		if (value["slot"].getType() == JsonNode::DATA_STRING)
+			allowedPositions.insert(VLC->arth->stringToSlot(value["class"].String()));
+		else
+			for (auto & entry : value["slot"].Vector())
+				allowedPositions.insert(VLC->arth->stringToSlot(entry.String()));
+
+		if (value["minValue"].isNull()) minValue = value["minValue"].Float();
+		if (value["maxValue"].isNull()) maxValue = value["maxValue"].Float();
+
+		return VLC->arth->pickRandomArtifact(rng, [=](ArtifactID artID) -> bool
+		{
+			CArtifact * art = VLC->arth->artifacts[artID];
+
+			if (!vstd::iswithin(art->price, minValue, maxValue))
+				return false;
+
+			if (!allowedClasses.empty() && !allowedClasses.count(art->aClass))
+				return false;
+
+			if (!allowedPositions.empty())
+			{
+				for (auto pos : art->possibleSlots[ArtBearer::HERO])
+				{
+					if (allowedPositions.count(pos))
+						return true;
+				}
+				return false;
+			}
+			return true;
+		});
+	}
+
+	std::vector<ArtifactID> loadArtifacts(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::vector<ArtifactID> ret;
+		for (const JsonNode & entry : value.Vector())
+		{
+			ret.push_back(loadArtifact(entry, rng));
+		}
+		return ret;
+	}
+
+	SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector<SpellID> spells)
+	{
+		if (value.getType() == JsonNode::DATA_STRING)
+			return SpellID(VLC->modh->identifiers.getIdentifier("spell", value).get());
+		if (value["type"].getType() == JsonNode::DATA_STRING)
+			return SpellID(VLC->modh->identifiers.getIdentifier("spell", value["type"]).get());
+
+		spells.erase(std::remove_if(spells.begin(), spells.end(), [=](SpellID spell)
+		{
+			return VLC->spellh->objects[spell]->level != si32(value["level"].Float());
+		}), spells.end());
+
+		return SpellID(*RandomGeneratorUtil::nextItem(spells, rng));
+	}
+
+	std::vector<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng, std::vector<SpellID> spells)
+	{
+		// possible extensions: (taken from spell json config)
+		// "type": "adventure",//"adventure", "combat", "ability"
+		// "school": {"air":true, "earth":true, "fire":true, "water":true},
+		// "level": 1,
+
+		std::vector<SpellID> ret;
+		for (const JsonNode & entry : value.Vector())
+		{
+			ret.push_back(loadSpell(entry, rng, spells));
+		}
+		return ret;
+	}
+
+	CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng)
+	{
+		CStackBasicDescriptor stack;
+		stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", value["type"]).get()];
+		stack.count = loadValue(value, rng);
+		if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty())
+		{
+			if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade
+			{
+				stack.type = VLC->creh->creatures[*RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)];
+			}
+		}
+		return stack;
+	}
+
+	std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng)
+	{
+		std::vector<CStackBasicDescriptor> ret;
+		for (const JsonNode & node : value.Vector())
+		{
+			ret.push_back(loadCreature(node, rng));
+		}
+		return ret;
+	}
+
+	std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value)
+	{
+		std::vector<RandomStackInfo> ret;
+		for (const JsonNode & node : value.Vector())
+		{
+			RandomStackInfo info;
+
+			if (!node["amount"].isNull())
+				info.minAmount = info.maxAmount = node["amount"].Float();
+			else
+			{
+				info.minAmount = node["min"].Float();
+				info.maxAmount = node["max"].Float();
+			}
+			const CCreature * crea = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", node["type"]).get()];
+			info.allowedCreatures.push_back(crea);
+			if (!node["upgradeChance"].Float() > 0)
+			{
+				for (auto creaID : crea->upgrades)
+					info.allowedCreatures.push_back(VLC->creh->creatures[creaID]);
+			}
+		}
+		return ret;
+	}
+
+	std::vector<Bonus> loadBonuses(const JsonNode & value)
+	{
+		std::vector<Bonus> ret;
+		for (const JsonNode & entry : value.Vector())
+		{
+			Bonus * bonus = JsonUtils::parseBonus(entry);
+			ret.push_back(*bonus);
+			delete bonus;
+		}
+		return ret;
+	}
+
+	std::vector<Component> loadComponents(const JsonNode & value)
+	{
+		//TODO
+	}
+}

+ 50 - 0
lib/mapObjects/JsonRandom.h

@@ -0,0 +1,50 @@
+#pragma once
+
+#include "../GameConstants.h"
+#include "../ResourceSet.h"
+
+/*
+ * JsonRandom.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
+ *
+ */
+
+class JsonNode;
+typedef std::vector<JsonNode> JsonVector;
+class CRandomGenerator;
+
+class Bonus;
+class Component;
+class CStackBasicDescriptor;
+
+namespace JsonRandom
+{
+	struct RandomStackInfo
+	{
+		std::vector<const CCreature *> allowedCreatures;
+		si32 minAmount;
+		si32 maxAmount;
+	};
+
+	si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0);
+	TResources loadResources(const JsonNode & value, CRandomGenerator & rng);
+	std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng);
+	std::map<SecondarySkill, si32> loadSecondary(const JsonNode & value, CRandomGenerator & rng);
+
+	ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng);
+	std::vector<ArtifactID> loadArtifacts(const JsonNode & value, CRandomGenerator & rng);
+
+	SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector<SpellID> spells);
+	std::vector<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng, std::vector<SpellID> spells);
+
+	CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng);
+	std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng);
+	std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value);
+
+	std::vector<Bonus> loadBonuses(const JsonNode & value);
+	std::vector<Component> loadComponents(const JsonNode & value);
+}

+ 1 - 1
lib/mapping/MapFormatH3M.cpp

@@ -1569,7 +1569,7 @@ void CMapLoaderH3M::readObjects()
 			{
 				if(objTempl.subid == 0)
 				{
-					nobj = new CGPyramid();
+					nobj = new CBank();
 				}
 				else
 				{

+ 1 - 3
lib/registerTypes/RegisterTypes.h

@@ -69,7 +69,6 @@ void registerTypesMapObjects1(Serializer &s)
 	s.template registerType<CArmedInstance, CGResource>();
 	s.template registerType<CArmedInstance, CGMine>();
 	s.template registerType<CArmedInstance, CBank>();
-		s.template registerType<CBank, CGPyramid>();
 	s.template registerType<CArmedInstance, CGSeerHut>(); s.template registerType<IQuestObject, CGSeerHut>();
 	s.template registerType<CGSeerHut, CGQuestGuard>();
 }
@@ -81,13 +80,13 @@ void registerTypesMapObjectTypes(Serializer &s)
 	s.template registerType<AObjectTypeHandler, CHeroInstanceConstructor>();
 	s.template registerType<AObjectTypeHandler, CTownInstanceConstructor>();
 	s.template registerType<AObjectTypeHandler, CDwellingInstanceConstructor>();
+	s.template registerType<AObjectTypeHandler, CBankInstanceConstructor>();
 	s.template registerType<AObjectTypeHandler, CObstacleConstructor>();
 
 #define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType<AObjectTypeHandler, CDefaultObjectTypeHandler<TYPENAME> >()
 
 	REGISTER_GENERIC_HANDLER(CGObjectInstance);
 	REGISTER_GENERIC_HANDLER(CGMarket);
-	REGISTER_GENERIC_HANDLER(CBank);
 	REGISTER_GENERIC_HANDLER(CCartographer);
 	REGISTER_GENERIC_HANDLER(CGArtifact);
 	REGISTER_GENERIC_HANDLER(CGBlackMarket);
@@ -113,7 +112,6 @@ void registerTypesMapObjectTypes(Serializer &s)
 	REGISTER_GENERIC_HANDLER(CGOnceVisitable);
 	REGISTER_GENERIC_HANDLER(CGPandoraBox);
 	REGISTER_GENERIC_HANDLER(CGPickable);
-	REGISTER_GENERIC_HANDLER(CGPyramid);
 	REGISTER_GENERIC_HANDLER(CGQuestGuard);
 	REGISTER_GENERIC_HANDLER(CGResource);
 	REGISTER_GENERIC_HANDLER(CGScholar);