Explorar o código

Merge pull request #3714 from vcmi/biome_system

Biome system implementation
DjWarmonger hai 1 ano
pai
achega
0808a8b36f

+ 1 - 0
client/CGameInfo.cpp

@@ -37,6 +37,7 @@ void CGameInfo::setFromLib()
 	terrainTypeHandler = VLC->terrainTypeHandler;
 	battleFieldHandler = VLC->battlefieldsHandler;
 	obstacleHandler = VLC->obstacleHandler;
+	//TODO: biomeHandler?
 }
 
 const ArtifactService * CGameInfo::artifacts() const

+ 689 - 0
config/biomes.json

@@ -0,0 +1,689 @@
+{
+	"templateSet2":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLct1d0", "AVLct2d0", "AVLct3d0", "AVLct4d0", "AVLct5d0", "AVLctrd0"]
+	},
+	"dirtRedFlowers":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLfl1d0", "AVLfl6d0", "AVLfl7d0"]
+	},
+	"dirtLightFlowers":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLfl4d0", "AVLfl5d0"]
+	},
+	"dirtYellowFlowers":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLfl3d0", "AVLfl8d0"]
+	},
+	"dirtPurpleFlowers":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLfl2d0", "AVLfl9d0"]
+	},
+	"templateSet5":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlk1d0", "AVLlk2d0", "AVLlk3d0"]
+	},
+	"templateSet7":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLmd1d0", "AVLmd2d0"]
+	},
+	"templateSet8":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "mountain"
+		},
+		"templates" : ["avlmtdr1", "avlmtdr2", "avlmtdr3", "avlmtdr4", "avlmtdr5", "avlmtdr6", "avlmtdr7", "avlmtdr8"]
+	},
+	"templateSet9":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "tree"
+		},
+		"templates" : ["avlautr0", "avlautr1", "AVLAUTR2", "AVLAUTR3", "AVLAUTR4", "AVLAUTR5", "AVLautr6", "AVLautr7"]
+	},
+	"templateSet10":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLoc1d0", "AVLoc2d0", "AVLoc3d0"]
+	},
+	"templateSet11":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLPNTR0", "AVLPNTR1", "AVLPNTR2", "AVLPNTR3", "AVLPNTR4", "AVLPNTR5", "AVLpntr6", "AVLpntr7"]
+	},
+	"templateSet13":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "rock"
+		},
+		"templates" : ["AvLRD01", "AvLRD02", "AvLRD04", "AVLrk3d0", "AVLrk5d0"]
+	},
+	"templateSet14":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLsh1d0", "AVLsh2d0", "AVLsh3d0", "AVLsh4d0", "AVLsh5d0", "AVLsh6d0", "AVLsh7d0", "AVLsh8d0"]
+	},
+	"dirtStumps":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "plant"
+		},
+		"templates" : ["AvLdlog", "AvLStm1", "AvLStm2", "AvLStm3"]
+	},
+	"templateSet16":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLtr1d0", "AVLtr2d0", "AVLtr3d0"]
+	},
+	"templateSet17":{
+		"biome":{
+			"terrain" : "dirt",
+			"objectType" : "other"
+		},
+		"templates" : ["avlxdt00", "avlxdt01", "avlxdt02", "avlxdt03", "avlxdt04", "avlxdt05", "avlxdt06", "avlxdt07", "avlxdt08", "avlxdt09", "avlxdt10", "avlxdt11"]
+	},
+	"cactus":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLca010", "AVLca020", "AVLca030", "AVLca040", "AVLca050", "AVLca060", "AVLca070", "AVLca080", "AVLca090", "AVLca100", "AVLca110", "AVLca120", "AVLca130"]
+	},
+	"sandCraters":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLctds0", "AVLspit0"]
+	},
+	"templateSet32":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtds1", "AVLmtds2", "AVLmtds3", "AVLmtds4", "AVLmtds5", "AVLmtds6"]
+	},
+	"templateSet34":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLdun10", "AVLdun20", "AVLdun30"]
+	},
+	"templateSet36":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "animal"
+		},
+		"templates" : ["AVLskul0"]
+	},
+	"sandPalms":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLplm10", "AVLplm20", "AVLplm30", "AVLplm40", "AVLplm50"]
+	},
+	"sandYucca":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLyuc10", "AVLyuc20", "AVLyuc30"]
+	},
+	"templateSet38":{
+		"biome":{
+			"terrain" : "sand",
+			"objectType" : "other"
+		},
+		"templates" : ["avlxds01", "avlxds02", "avlxds03", "avlxds04", "avlxds05", "avlxds06", "avlxds07", "avlxds08", "avlxds09", "avlxds10", "avlxds11", "avlxds12"]
+	},
+	"templateSet50":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLct1g0", "AVLct2g0", "AVLct3g0", "AVLct4g0", "AVLct5g0", "AVLct6g0", "AVLctrg0"]
+	},
+	"grassRedFlowers":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLf01g0", "AVLf02g0", "AVLf07g0"]
+	},
+	"grassPurpleFlowers":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLf03g0", "AVLf08g0", "AVLf12g0"]
+	},
+	"grassYellowFlowers":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLf04g0", "AVLf05g0", "AVLf09g0", "AVLf10g0", "AVLf11g0"]
+	},
+	"grassWhiteFlowers":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLf06g0"]
+	},
+	"templateSet53":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlk1g0", "AVLlk2g0", "AVLlk3g0"]
+	},
+	"templateSet54":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AvLdlog"]
+	},
+	"templateSet55":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLmd1g0", "AVLmd2g0"]
+	},
+	"greyMountains":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtgn0", "AVLmtgn1", "AVLmtgn2", "AVLmtgn3", "AVLmtgn4", "AVLmtgn5"]
+	},
+	"brownMountains":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtgr1", "AVLmtgr2", "AVLmtgr3", "AVLmtgr4", "AVLmtgr5", "AVLmtgr6"]
+	},
+	"greenOakTrees":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLSPTR0", "AVLSPTR1", "AVLSPTR2", "AVLSPTR3", "AVLSPTR4", "AVLSPTR5", "AVLSPTR6", "AVLsptr7", "AVLsptr8"]
+	},
+	"autumnOakTrees":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "tree"
+		},
+		"templates" : ["avlautr0", "avlautr1", "AVLAUTR2", "AVLAUTR3", "AVLAUTR4", "AVLAUTR5", "AVLautr6", "AVLautr7"]
+	},
+	"templateSet58":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLoc1g0", "AVLoc2g0", "AVLoc3g0"]
+	},
+	"templateSet59":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLPNTR0", "AVLPNTR1", "AVLPNTR2", "AVLPNTR3", "AVLPNTR4", "AVLPNTR5", "AVLpntr6", "AVLpntr7"]
+	},
+	"templateSet61":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "rock"
+		},
+		"templates" : ["AvLRG01", "AvLRG02", "AvLRG03", "AvLRG04", "AvLRG05", "AvLRG06", "AvLRG07", "AvLRG08", "AvLRG09", "AvLRG10", "AvLRG11"]
+	},
+	"templateSet62":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLsh1g0", "AVLsh2g0", "AVLsh3g0", "AVLsh4g0", "AVLsh5g0", "AVLsh6g0"]
+	},
+	"templateSet63":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "plant"
+		},
+		"templates" : ["AvLStm1", "AvLStm2", "AvLStm3"]
+	},
+	"swampTreesOnGrass":{
+		"biome":{
+			"terrain" : "grass",
+			"faction" : "fortress",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLswmp0", "AVLswmp1", "AVLswmp2", "AVLswmp3", "AVLswmp4", "AVLswmp5", "AVLswmp6", "AVLswmp7", "AVLtr1d0", "AVLtr2d0", "AVLtr3d0", "AVLwlw10", "AVLwlw20", "AVLwlw30"]
+	},
+	"templateSet65":{
+		"biome":{
+			"terrain" : "grass",
+			"objectType" : "other"
+		},
+		"templates" : ["avlxgr01", "avlxgr02", "avlxgr03", "avlxgr04", "avlxgr05", "avlxgr06", "avlxgr07", "avlxgr08", "avlxgr09", "avlxgr10", "avlxgr11", "avlxgr12"]
+	},
+	"templateSet77":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLctsn0"]
+	},
+	"templateSet78":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLd1sn0", "AVLd2sn0", "AVLd3sn0", "AVLd4sn0", "AVLd5sn0", "AVLd6sn0", "AVLd7sn0", "AVLd8sn0", "AVLd9sn0", "AVLddsn0", "AVLddsn1", "AVLddsn2", "AVLddsn3", "AVLddsn4", "AVLddsn5", "AVLddsn6", "AVLddsn7"]
+	},
+	"templateSet79":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLflk10", "AVLflk20", "AVLflk30"]
+	},
+	"templateSet81":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtsn1", "AVLmtsn2", "AVLmtsn3", "AVLmtsn4", "AVLmtsn5", "AVLmtsn6"]
+	},
+	"templateSet82":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLo1sn0", "AVLo2sn0", "AVLo3sn0"]
+	},
+	"templateSet83":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLSNTR0", "AVLSNTR1", "AVLSNTR2", "AVLSNTR3", "AVLSNTR4", "AVLSNTR5", "AVLsntr6", "AVLsntr7"]
+	},
+	"templateSet85":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLr1sn0", "AVLr2sn0", "AVLr3sn0", "AVLr4sn0", "AVLr5sn0", "AVLr6sn0", "AVLr7sn0", "AVLr8sn0"]
+	},
+	"templateSet86":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLs1sn0", "AVLs2sn0", "AVLs3sn0"]
+	},
+	"templateSet87":{
+		"biome":{
+			"terrain" : "snow",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLp1sn0", "AVLp2sn0"]
+	},
+	"templateSet99":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLctrs0"]
+	},
+	"templateSet100":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLdead0", "AVLdead1", "AVLdead2", "AVLdead3", "AVLdead4", "AVLdead5", "AVLdead6", "AVLdead7", "AVLdt1s0", "AVLdt2s0", "AVLdt3s0", "AVLswp60", "AVLswp70"]
+	},
+	"templateSet102":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlk1s0", "AVLlk2s0", "AVLlk3s0", "AVLswp50"]
+	},
+	"templateSet103":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLman10", "AVLman20", "AVLman30", "AVLman40", "AVLman50"]
+	},
+	"templateSet104":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLmoss0"]
+	},
+	"templateSet105":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtsw1", "AVLmtsw2", "AVLmtsw3", "AVLmtsw4", "AVLmtsw5", "AVLmtsw6"]
+	},
+	"swampTrees":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLSPTR0", "AVLSPTR1", "AVLSPTR2", "AVLSPTR3", "AVLSPTR4", "AVLSPTR5", "AVLSPTR6", "AVLsptr7", "AVLsptr8"]
+	},
+	"templateSet108":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLrk1s0", "AVLrk2s0", "AVLrk3s0", "AVLrk4s0"]
+	},
+	"templateSet109":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLs01s0", "AVLs02s0", "AVLs03s0", "AVLs04s0", "AVLs05s0", "AVLs06s0", "AVLs07s0", "AVLs08s0", "AVLs09s0", "AVLs10s0", "AVLs11s0", "AVLswp10", "AVLswp20", "AVLswp30", "AVLswp40"]
+	},
+	"floodedPalms":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLswmp0", "AVLswmp1", "AVLswmp2", "AVLswmp3", "AVLswmp4", "AVLswmp5", "AVLswmp6", "AVLswmp7"]
+	},
+	"swampTrees2":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLtr1d0", "AVLtr2d0", "AVLtr3d0", "AVLwlw10", "AVLwlw20", "AVLwlw30"]
+	},
+	"swampPalms":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "tree"
+		},
+		"templates" : ["avlswtr0", "avlswtr1", "avlswtr2", "avlswtr3", "avlswtr4", "avlswtr5", "avlswtr6", "avlswtr7", "avlswtr8", "avlswtr9"]
+	},
+	"swampSinglePalms":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "plant"
+		},
+		"templates" : ["avlswt00", "avlswt01", "avlswt02", "avlswt03", "avlswt04", "avlswt05", "avlswt06", "avlswt07", "avlswt08", "avlswt09", "avlswt10", "avlswt11", "avlswt12", "avlswt13", "avlswt14", "avlswt15", "avlswt16", "avlswt17", "avlswt18", "avlswt19"]
+	},
+	"templateSet112":{
+		"biome":{
+			"terrain" : "swamp",
+			"objectType" : "other"
+		},
+		"templates" : ["avlxsw01", "avlxsw02", "avlxsw03", "avlxsw04", "avlxsw05", "avlxsw06", "avlxsw07", "avlxsw08", "avlxsw09", "avlxsw10", "avlxsw11"]
+	},
+	"templateSet124":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLca1r0", "AVLca2r0"]
+	},
+	"roughCraters":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLglly0", "AVLct1r0", "AVLct2r0", "AVLct3r0", "AVLct4r0", "AVLct5r0", "AVLct6r0", "AVLct7r0", "AVLct8r0", "AVLct9r0", "AVLctrr0"]
+	},
+	"templateSet129":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLmd1r0", "AVLmd2r0", "AVLmd3r0"]
+	},
+	"templateSet130":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "mountain"
+		},
+		"templates" : ["avlmtrf1", "avlmtrf2", "avlmtrf3", "avlmtrf4", "avlmtrf5", "avlmtrf6"]
+	},
+	"templateSet131":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLoc1r0", "AVLoc2r0", "AVLoc3r0", "AVLoc4r0"]
+	},
+	"templateSet133":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "rock"
+		},
+		"templates" : ["avlbuzr0", "AVLr02r0", "AVLr03r0", "AVLr04r0", "AVLr06r0", "AVLr07r0", "AVLr08r0", "AVLr09r0", "AVLr10r0", "AVLr11r0", "AVLr12r0", "AVLr13r0", "AVLr14r0", "AVLr15r0", "AvLRR01", "AvLRR05"]
+	},
+	"templateSet134":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLsh1r0", "AVLsh2r0", "AVLsh3r0", "avlsh4r0", "avlsh5r0", "avlsh6r0", "avlsh7r0", "avlsh8r0", "avlsh9r0"]
+	},
+	"templateSet135":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "animal"
+		},
+		"templates" : ["AVLskul0"]
+	},
+	"roughStumps":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "plant"
+		},
+		"templates" : ["AvLdlog", "AvLStm1", "AvLStm2", "AvLStm3"]
+	},
+	"roughSmallTree":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLroug0", "AVLroug1", "AVLroug2"]
+	},
+	"roughYucca":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLyuc10", "AVLyuc20", "AVLyuc30"]
+	},
+	"roughLake":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlk1r"]
+	},
+	"templateSet139":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "other"
+		},
+		"templates" : ["AVLtRo00", "AVLtRo01", "AVLtRo02", "AVLtRo03", "AVLtRo04", "AVLtRo05", "AVLtRo06", "AVLtRo07", "AVLtRo08", "AVLtRo09", "AVLtRo10", "AVLtRo11", "AVLtRo12", "AVLtrRo0", "AVLtrRo1", "AVLtrRo2", "AVLtrRo3", "AVLtrRo4", "AVLtrRo5", "AVLtrRo6", "AVLtrRo7"]
+	},
+	"templateSet140":{
+		"biome":{
+			"terrain" : "rough",
+			"objectType" : "other"
+		},
+		"templates" : ["avlxro01", "avlxro02", "avlxro03", "avlxro04", "avlxro05", "avlxro06", "avlxro07", "avlxro08", "avlxro09", "avlxro10", "avlxro11", "avlxro12"]
+	},
+	"templateSet152":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLct1u0", "AVLct2u0", "AVLct3u0", "AVLct4u0", "AVLct5u0"]
+	},
+	"templateSet153":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLdead0", "AVLdead1", "AVLdead2", "AVLdead3", "AVLdead4", "AVLdead5", "AVLdead6", "AVLdead7"]
+	},
+	"templateSet155":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlk1u0", "AVLlk2u0", "AVLlk3u0"]
+	},
+	"templateSet156":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlv1u0", "AVLlv2u0", "AVLlv3u0"]
+	},
+	"templateSet157":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLllk10", "AVLllk20"]
+	},
+	"templateSet158":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "plant"
+		},
+		"templates" : ["AVLms010", "AVLms020", "AVLms030", "AVLms040", "AVLms050", "AVLms060", "AVLms070", "AVLms080", "AVLms090", "AVLms100", "AVLms110", "AVLms120"]
+	},
+	"templateSet159":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtsb0", "AVLmtsb1", "AVLmtsb2", "AVLmtsb3", "AVLmtsb4", "AVLmtsb5"]
+	},
+	"templateSet160":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLoc1u0", "AVLoc2u0", "AVLoc3u0", "AVLoc4u0"]
+	},
+	"templateSet162":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLr01u0", "AVLr02u0", "AVLr03u0", "AVLr04u0", "AVLr05u0", "AVLr06u0", "AVLr07u0", "AVLr08u0", "AVLr09u0", "AVLr10u0", "AVLr11u0", "AVLr12u0", "AVLr13u0", "AVLr14u0", "AVLr15u0", "AVLr16u0", "AVLstg10", "AVLstg20", "AVLstg30", "AVLstg40", "AVLstg50", "AVLstg60"]
+	},
+	"templateSet163":{
+		"biome":{
+			"terrain" : "subterra",
+			"objectType" : "other"
+		},
+		"templates" : ["avlxsu01", "avlxsu02", "avlxsu03", "avlxsu04", "avlxsu05", "avlxsu06", "avlxsu07", "avlxsu08", "avlxsu09", "avlxsu10", "avlxsu11", "avlxsu12"]
+	},
+	"lavaChasm":{
+		"biome":{
+			"terrain" : "lava",
+			"objectType" : "crater"
+		},
+		"templates" : ["AVLc10l0", "AVLc11l0", "AVLc12l0", "AVLc13l0", "AVLc14l0", "AVLct1l0", "AVLct2l0", "AVLct3l0", "AVLct4l0", "AVLct5l0", "AVLct6l0", "AVLct7l0", "AVLct8l0", "AVLct9l0", "AVLctrl0"]
+	},
+	"lavaDeadTree":{
+		"biome":{
+			"terrain" : "lava",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLdead0", "AVLdead1", "AVLdead2", "AVLdead3", "AVLdead4", "AVLdead5", "AVLdead6", "AVLdead7"]
+	},
+	"lavaLake":{
+		"biome":{
+			"terrain" : "lava",
+			"objectType" : "lake"
+		},
+		"templates" : ["AVLlav10", "AVLlav20", "AVLlav30", "AVLlav40", "AVLlav50", "AVLlav60", "AVLlav70", "AVLlav80", "AVLlav90", "AVLlv100", "AVLlv110", "AVLlv120", "AVLlv130", "AVLlv140", "AVLlv150", "AVLlv160", "AVLlv170", "AVLlv180", "AVLlv190", "AVLlv200", "AVLlv210", "AVLlv220", "AVLlv230", "AVLlv240", "AVLlv250", "AVLlv260"]
+	},
+	"templateSet180":{
+		"biome":{
+			"terrain" : "lava",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLmtvo1", "AVLmtvo2", "AVLmtvo3", "AVLmtvo4", "AVLmtvo5", "AVLmtvo6"]
+	},
+	"volcanos":{
+		"biome":{
+			"terrain" : "lava",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLvol10", "AVLvol20", "AVLvol30", "AVLvol40", "AVLvol50"]
+	},
+	"templateSet192":{
+		"biome":{
+			"terrain" : "water",
+			"objectType" : "tree"
+		},
+		"templates" : ["AVLklp10", "AVLklp20"]
+	},
+	"templateSet193":{
+		"biome":{
+			"terrain" : "water",
+			"objectType" : "rock"
+		},
+		"templates" : ["AVLrk1w0", "AVLrk2w0", "AVLrk3w0", "AVLrk4w0"]
+	},
+	"templateSet194":{
+		"biome":{
+			"terrain" : "water",
+			"objectType" : "mountain"
+		},
+		"templates" : ["AVLref10", "AVLref20", "AVLref30", "AVLref40", "AVLref50", "AVLref60"]
+	}
+}

+ 5 - 0
config/gameConfig.json

@@ -67,6 +67,11 @@
 		"config/objects/witchHut.json"
 	],
 
+	"biomes" :
+	[
+		"config/biomes.json"
+	],
+
 	"artifacts" :
 	[
 		"config/artifacts.json"

+ 67 - 0
config/schemas/biome.json

@@ -0,0 +1,67 @@
+{
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-04/schema",
+	"title" : "VCMI map obstacle set format",
+	"description" : "Description of map object set, used only as sub-schema of object",
+	"required" : ["biome", "templates"],
+	"additionalProperties" : true, // may have type-dependant properties
+	"properties" : {
+		"biome" : {
+			"type" : "object",
+			"properties": {
+				"objectType" : {
+					"type" : "string",
+					"enmum": ["mountain", "tree", "lake", "crater", "rock", "plant", "structure", "animal", "other"],
+					"description" : "Type of the obstacle set"
+				},
+				"terrain" : {
+					"anyOf": [
+						{
+							"type" : "string",
+							"description" : "Terrain of the obstacle set"
+						},
+						{
+							"type" : "array",
+							"items" : { "type" : "string" },
+							"description" : "Terrains of the obstacle set"
+						}
+					]
+					
+				},
+				"faction" : {
+					"anyOf": [
+						{
+							"type" : "string",
+							"description" : "Faction of the zone"
+						},
+						{
+							"type" : "array",
+							"items" : { "type" : "string" },
+							"description" : "Factions of the zone"
+						}
+					]
+				},
+				"alignment" : {
+					"anyOf": [
+						{
+							"type" : "string",
+							"enum" : ["good", "evil", "neutral"],
+							"description" : "Alignment of faction of the zone"
+						},
+						{
+							"type" : "array",
+							"items" : { "type" : "string" },
+							"description" : "Alignment of faction of the zone"
+						}
+					]
+				}
+			}
+		},
+		"templates" : {
+			"type" : "array",
+			"items" : { "type" : "string" },
+			"description" : "Object templates of the obstacle set"
+		}
+	}
+}
+

+ 5 - 0
config/schemas/mod.json

@@ -255,6 +255,11 @@
 			"description" : "List of configuration files for objects",
 			"items" : { "type" : "string", "format" : "textFile" }
 		},
+		"biomes" : {
+			"type" : "array",
+			"description" : "List of configuration files for biomes",
+			"items" : { "type" : "string", "format" : "textFile" }
+		},
 		"bonuses" : {
 			"type" : "array",
 			"description" : "List of configuration files for bonuses",

+ 1 - 0
docs/modders/Readme.md

@@ -73,6 +73,7 @@ Other:
 - [Terrain](Entities_Format/Terrain_Format.md)
 - [River](Entities_Format/River_Format.md)
 - [Road](Entities_Format/Road_Format.md)
+- [Biome](Entities_Format/Biome_Format.md)
 - [Battlefield](Entities_Format/Battlefield_Format.md)
 - [Battle Obstacle](Entities_Format/Battle_Obstacle_Format.md)
 

+ 2 - 0
lib/CMakeLists.txt

@@ -121,6 +121,7 @@ set(lib_MAIN_SRCS
 	mapObjects/IObjectInterface.cpp
 	mapObjects/MiscObjects.cpp
 	mapObjects/ObjectTemplate.cpp
+	mapObjects/ObstacleSetHandler.cpp
 
 	mapping/CDrawRoadsOperation.cpp
 	mapping/CMap.cpp
@@ -482,6 +483,7 @@ set(lib_MAIN_HEADERS
 	mapObjects/MapObjects.h
 	mapObjects/MiscObjects.h
 	mapObjects/ObjectTemplate.h
+	mapObjects/ObstacleSetHandler.h
 
 	mapping/CDrawRoadsOperation.h
 	mapping/CMapDefines.h

+ 2 - 0
lib/VCMI_Lib.cpp

@@ -37,6 +37,7 @@
 #include "rmg/CRmgTemplateStorage.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"
+#include "mapObjects/ObstacleSetHandler.h"
 #include "mapping/CMapEditManager.h"
 #include "ScriptHandler.h"
 #include "BattleFieldHandler.h"
@@ -223,6 +224,7 @@ void LibClasses::init(bool onlyEssential)
 	createHandler(arth, "Artifact", pomtime);
 	createHandler(creh, "Creature", pomtime);
 	createHandler(townh, "Town", pomtime);
+	createHandler(biomeHandler, "Obstacle set", pomtime);
 	createHandler(objh, "Object", pomtime);
 	createHandler(objtypeh, "Object types information", pomtime);
 	createHandler(spellh, "Spell", pomtime);

+ 3 - 0
lib/VCMI_Lib.h

@@ -23,6 +23,7 @@ class CSkillHandler;
 class CBuildingHandler;
 class CObjectHandler;
 class CObjectClassesHandler;
+class ObstacleSetHandler;
 class CTownHandler;
 class CGeneralTextHandler;
 class CModHandler;
@@ -85,6 +86,7 @@ public:
 	std::shared_ptr<CCreatureHandler> creh;
 	std::shared_ptr<CSpellHandler> spellh;
 	std::shared_ptr<CSkillHandler> skillh;
+	// TODO: Remove ObjectHandler altogether?
 	std::shared_ptr<CObjectHandler> objh;
 	std::shared_ptr<CObjectClassesHandler> objtypeh;
 	std::shared_ptr<CTownHandler> townh;
@@ -99,6 +101,7 @@ public:
 	std::shared_ptr<BattleFieldHandler> battlefieldsHandler;
 	std::shared_ptr<ObstacleHandler> obstacleHandler;
 	std::shared_ptr<GameSettings> settingsHandler;
+	std::shared_ptr<ObstacleSetHandler> biomeHandler;
 
 #if SCRIPTING_ENABLED
 	std::shared_ptr<scripting::ScriptHandler> scriptHandler;

+ 40 - 0
lib/constants/EntityIdentifiers.h

@@ -457,7 +457,47 @@ public:
 		WHIRLPOOL = 111,
 		WINDMILL = 112,
 		WITCH_HUT = 113,
+		BRUSH = 114, // TODO: How does it look like?
+		BUSH = 115,
+		CACTUS = 116,
+		CANYON = 117,
+		CRATER = 118,
+		DEAD_VEGETATION = 119,
+		FLOWERS = 120,
+		FROZEN_LAKE = 121,
+		HEDGE = 122,
+		HILL = 123,
 		HOLE = 124,
+		KELP = 125,
+		LAKE = 126,
+		LAVA_FLOW = 127,
+		LAVA_LAKE = 128,
+		MUSHROOMS = 129,
+		LOG = 130,
+		MANDRAKE = 131,
+		MOSS = 132,
+		MOUND = 133,
+		MOUNTAIN = 134,
+		OAK_TREES = 135,
+		OUTCROPPING = 136,
+		PINE_TREES = 137,
+		PLANT = 138,
+		RIVER_DELTA = 143,
+		ROCK = 147,
+		SAND_DUNE = 148,
+		SAND_PIT = 149,
+		SHRUB = 150,
+		SKULL = 151,
+		STALAGMITE = 152,
+		STUMP = 153,
+		TAR_PIT = 154,
+		TREES = 155,
+		VINE = 156,
+		VOLCANIC_VENT = 157,
+		VOLCANO = 158,
+		WILLOW_TREES = 159,
+		YUCCA_TREES = 160,
+		REEF = 161,
 		RANDOM_MONSTER_L5 = 162,
 		RANDOM_MONSTER_L6 = 163,
 		RANDOM_MONSTER_L7 = 164,

+ 2 - 1
lib/constants/Enumerations.h

@@ -13,7 +13,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 enum class EAlignment : int8_t
 {
-	GOOD,
+	ANY = -1,
+	GOOD = 0,
 	EVIL,
 	NEUTRAL
 };

+ 19 - 0
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -34,6 +34,7 @@
 #include "../mapObjects/MiscObjects.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/CGTownInstance.h"
+#include "../mapObjects/ObstacleSetHandler.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/CModHandler.h"
 #include "../modding/ModScope.h"
@@ -211,10 +212,28 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
 	createdObject->subtype = index;
 	createdObject->init(entry);
 
+	bool staticObject = createdObject->isStaticObject();
+	if (staticObject)
+	{
+		for (auto & templ : createdObject->getTemplates())
+		{
+			// Register templates for new objects from mods
+			VLC->biomeHandler->addTemplate(scope, templ->stringID, templ);
+		}
+	}
+
 	auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index));
 	for (auto & templ : boost::make_iterator_range(range.first, range.second))
 	{
+		if (staticObject)
+		{
+			// Register legacy templates as "core"
+			// FIXME: Why does it clear stringID?
+			VLC->biomeHandler->addTemplate("core", templ.second->stringID, templ.second);
+		}
+
 		createdObject->addTemplate(templ.second);
+
 	}
 	legacyTemplates.erase(range.first, range.second);
 

+ 2 - 0
lib/mapObjectConstructors/CommonConstructors.h

@@ -14,6 +14,7 @@
 
 #include "../mapObjects/MiscObjects.h"
 #include "../mapObjects/CGCreature.h"
+#include "../mapObjects/ObstacleSetHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -33,6 +34,7 @@ class CObstacleConstructor : public CDefaultObjectTypeHandler<CGObjectInstance>
 {
 public:
 	bool isStaticObject() override;
+
 };
 
 class CreatureInstanceConstructor : public CDefaultObjectTypeHandler<CGCreature>

+ 470 - 0
lib/mapObjects/ObstacleSetHandler.cpp

@@ -0,0 +1,470 @@
+/*
+ * ObstacleSetHandler.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 "ObstacleSetHandler.h"
+
+#include "../modding/IdentifierStorage.h"
+#include "../constants/StringConstants.h"
+#include "../TerrainHandler.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ObstacleSet::ObstacleSet():
+	type(INVALID),
+	allowedTerrains({TerrainId::NONE})
+{
+}
+
+ObstacleSet::ObstacleSet(EObstacleType type, TerrainId terrain):
+	type(type),
+	allowedTerrains({terrain})
+{
+}
+
+void ObstacleSet::addObstacle(std::shared_ptr<const ObjectTemplate> obstacle)
+{
+	obstacles.push_back(obstacle);
+}
+
+void ObstacleSet::removeEmptyTemplates()
+{
+	vstd::erase_if(obstacles, [](const std::shared_ptr<const ObjectTemplate> &tmpl)
+	{
+		if (tmpl->getBlockedOffsets().empty())
+		{
+			logMod->warn("Obstacle template %s blocks no tiles, removing it", tmpl->stringID);
+			return true;
+		}
+		return false;
+	});
+}
+
+ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain = TerrainId::ANY_TERRAIN, FactionID faction = FactionID::ANY, EAlignment alignment = EAlignment::ANY):
+	allowedTypes(allowedTypes),
+	terrain(terrain),
+	faction(faction),
+	alignment(alignment)
+{
+}
+
+ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain = TerrainId::ANY_TERRAIN, FactionID faction = FactionID::ANY, EAlignment alignment = EAlignment::ANY):
+	allowedTypes({allowedType}),
+	terrain(terrain),
+	faction(faction),
+	alignment(alignment)
+{
+}
+
+bool ObstacleSetFilter::filter(const ObstacleSet &set) const
+{
+	if (terrain != TerrainId::ANY_TERRAIN && !vstd::contains(set.getTerrains(), terrain))
+	{
+		return false;
+	}
+
+	if (faction != FactionID::ANY)
+	{
+		auto factions = set.getFactions();
+		if (!factions.empty() && !vstd::contains(factions, faction))
+		{
+			return false;
+		}
+	}
+
+	// TODO: Also check specific factions
+	if (alignment != EAlignment::ANY)
+	{
+		auto alignments = set.getAlignments();
+		if (!alignments.empty() && !vstd::contains(alignments, alignment))
+		{
+			return false;
+		}
+	}
+
+	return true;
+}
+
+TerrainId ObstacleSetFilter::getTerrain() const
+{
+	return terrain;
+}
+
+std::set<TerrainId> ObstacleSet::getTerrains() const
+{
+	return allowedTerrains;
+}
+
+void ObstacleSet::setTerrain(TerrainId terrain)
+{
+	this->allowedTerrains = {terrain};
+}
+
+void ObstacleSet::setTerrains(const std::set<TerrainId> & terrains)
+{
+	this->allowedTerrains = terrains;
+}
+
+void ObstacleSet::addTerrain(TerrainId terrain)
+{
+	this->allowedTerrains.insert(terrain);
+}
+
+std::set<FactionID> ObstacleSet::getFactions() const
+{
+	return allowedFactions;
+}
+
+void ObstacleSet::addFaction(FactionID faction)
+{
+	this->allowedFactions.insert(faction);
+}
+
+void ObstacleSet::addAlignment(EAlignment alignment)
+{
+	this->allowedAlignments.insert(alignment);
+}
+
+std::set<EAlignment> ObstacleSet::getAlignments() const
+{
+	return allowedAlignments;
+}
+
+ObstacleSet::EObstacleType ObstacleSet::getType() const
+{
+	return type;
+}
+
+void ObstacleSet::setType(EObstacleType type)
+{
+	this->type = type;
+}
+
+std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleSet::getObstacles() const
+{
+	return obstacles;
+}
+
+ObstacleSet::EObstacleType ObstacleSetHandler::convertObstacleClass(MapObjectID id)
+{
+	switch (id)
+	{
+		case Obj::MOUNTAIN:
+		case Obj::VOLCANIC_VENT:
+		case Obj::VOLCANO:
+		case Obj::REEF:
+			return ObstacleSet::MOUNTAINS;
+		case Obj::OAK_TREES:
+		case Obj::PINE_TREES:
+		case Obj::TREES:
+		case Obj::DEAD_VEGETATION:
+		case Obj::HEDGE:
+		case Obj::KELP:
+		case Obj::WILLOW_TREES:
+		case Obj::YUCCA_TREES:
+			return ObstacleSet::TREES;
+		case Obj::FROZEN_LAKE:
+		case Obj::LAKE:
+		case Obj::LAVA_FLOW:
+		case Obj::LAVA_LAKE:
+			return ObstacleSet::LAKES;
+		case Obj::CANYON:
+		case Obj::CRATER:
+		case Obj::SAND_PIT:
+		case Obj::TAR_PIT:
+			return ObstacleSet::CRATERS;
+		case Obj::HILL:
+		case Obj::MOUND:
+		case Obj::OUTCROPPING:
+		case Obj::ROCK:
+		case Obj::SAND_DUNE:
+		case Obj::STALAGMITE:
+			return ObstacleSet::ROCKS;
+		case Obj::BUSH:
+		case Obj::CACTUS:
+		case Obj::FLOWERS:
+		case Obj::MUSHROOMS:
+		case Obj::LOG:
+		case Obj::MANDRAKE:
+		case Obj::MOSS:
+		case Obj::PLANT:
+		case Obj::SHRUB:
+		case Obj::STUMP:
+		case Obj::VINE:
+			return ObstacleSet::PLANTS;
+		case Obj::SKULL:
+			return ObstacleSet::ANIMALS;
+		default:
+			return ObstacleSet::OTHER;
+	}
+}
+
+ObstacleSet::EObstacleType ObstacleSet::typeFromString(const std::string &str)
+{
+	static const std::map<std::string, EObstacleType> OBSTACLE_TYPE_NAMES =
+	{
+		{"mountain", MOUNTAINS},
+		{"tree", TREES},
+		{"lake", LAKES},
+		{"crater", CRATERS},
+		{"rock", ROCKS},
+		{"plant", PLANTS},
+		{"structure", STRUCTURES},
+		{"animal", ANIMALS},
+		{"other", OTHER}
+	};
+
+	if (OBSTACLE_TYPE_NAMES.find(str) != OBSTACLE_TYPE_NAMES.end())
+	{
+		return OBSTACLE_TYPE_NAMES.at(str);
+	}
+
+	// TODO: How to handle that?
+	throw std::runtime_error("Invalid obstacle type: " + str);
+}
+
+std::string ObstacleSet::toString() const
+{
+	static const std::map<EObstacleType, std::string> OBSTACLE_TYPE_STRINGS =
+	{
+		{MOUNTAINS, "mountain"},
+		{TREES, "tree"},
+		{LAKES, "lake"},
+		{CRATERS, "crater"},
+		{ROCKS, "rock"},
+		{PLANTS, "plant"},
+		{STRUCTURES, "structure"},
+		{ANIMALS, "animal"},
+		{OTHER, "other"}
+	};
+
+	return OBSTACLE_TYPE_STRINGS.at(type);
+}
+
+std::vector<ObstacleSet::EObstacleType> ObstacleSetFilter::getAllowedTypes() const
+{
+	return allowedTypes;
+}
+
+void ObstacleSetFilter::setType(ObstacleSet::EObstacleType type)
+{
+	allowedTypes = {type};
+}
+
+void ObstacleSetFilter::setTypes(std::vector<ObstacleSet::EObstacleType> types)
+{
+	this->allowedTypes = types;
+}
+
+std::vector<JsonNode> ObstacleSetHandler::loadLegacyData()
+{
+	return {};
+}
+
+void ObstacleSetHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
+{
+	auto os = loadFromJson(scope, data, name, biomes.size());
+	if(os)
+	{
+		addObstacleSet(os);
+		// TODO: Define some const array of object types ("biome" etc.)
+		VLC->identifiersHandler->registerObject(scope, "biome", name, biomes.back()->id);
+	}
+	else
+	{
+		logMod->error("Failed to load obstacle set: %s", name);
+	}
+}
+
+void ObstacleSetHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
+{
+	//Unused
+	loadObject(scope, name, data);
+}
+
+std::shared_ptr<ObstacleSet> ObstacleSetHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index)
+{
+	auto os = std::make_shared<ObstacleSet>();
+	os->id = index;
+
+	auto biome = json["biome"].Struct();
+	os->setType(ObstacleSet::typeFromString(biome["objectType"].String()));
+
+	// TODO: Handle any (every) terrain option
+
+	if (biome["terrain"].isString())
+	{
+		auto terrainName = biome["terrain"].String();
+
+		VLC->identifiers()->requestIdentifier(scope, "terrain", terrainName, [os](si32 id)
+		{
+			os->setTerrain(TerrainId(id));
+		});
+	}
+	else if (biome["terrain"].isVector())
+	{
+		auto terrains = biome["terrain"].Vector();
+
+		for (const auto & terrain : terrains)
+		{
+			VLC->identifiers()->requestIdentifier(scope, "terrain", terrain.String(), [os](si32 id)
+			{
+				os->addTerrain(TerrainId(id));
+			});
+		}
+	}
+	else
+	{
+		logMod->error("No terrain specified for obstacle set %s", name);
+	}
+
+	auto handleFaction = [os, scope](const std::string & str)
+	{
+		VLC->identifiers()->requestIdentifier(scope, "faction", str, [os](si32 id)
+		{
+			os->addFaction(FactionID(id));
+		});
+	};
+
+	if (biome["faction"].isString())
+	{
+		auto factionName = biome["faction"].String();
+		handleFaction(factionName);
+	}
+	else if (biome["faction"].isVector())
+	{
+		auto factions = biome["faction"].Vector();
+		for (const auto & node : factions)
+		{
+			handleFaction(node.String());
+		}
+	}
+
+	// TODO: Move this parser to some utils
+	auto parseAlignment = [](const std::string & str) ->EAlignment
+	{
+		int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, str);
+		if (alignment == -1)
+		{
+			logMod->error("Incorrect alignment: ", str);
+			return EAlignment::ANY;
+		}
+		else
+		{
+			return static_cast<EAlignment>(alignment);
+		}
+	};
+
+	if (biome["alignment"].isString())
+	{
+		os->addAlignment(parseAlignment(biome["alignment"].String()));
+	}
+	else if (biome["alignment"].isVector())
+	{
+		auto alignments = biome["alignment"].Vector();
+		for (const auto & node : alignments)
+		{
+			os->addAlignment(parseAlignment(node.String()));
+		}
+	}
+
+	auto templates = json["templates"].Vector();
+	for (const auto & node : templates)
+	{
+		logMod->trace("Registering obstacle template: %s in scope %s", node.String(), scope);
+
+		auto identifier = boost::algorithm::to_lower_copy(node.String());
+		auto jsonName = JsonNode(identifier);
+
+		VLC->identifiers()->requestIdentifier(node.getModScope(), "obstacleTemplate", identifier, [this, os](si32 id)
+		{
+			logMod->trace("Resolved obstacle id: %d", id);
+			os->addObstacle(obstacleTemplates[id]);
+		});
+	}
+
+	return os;
+}
+
+void ObstacleSetHandler::addTemplate(const std::string & scope, const std::string &name, std::shared_ptr<const ObjectTemplate> tmpl)
+{
+	auto id = obstacleTemplates.size();
+
+	auto strippedName = boost::algorithm::to_lower_copy(name);
+	auto pos = strippedName.find(".def");
+	if(pos != std::string::npos)
+		strippedName.erase(pos, 4);
+
+	if (VLC->identifiersHandler->getIdentifier(scope, "obstacleTemplate", strippedName, true))
+	{
+		logMod->warn("Duplicate obstacle template: %s", strippedName);
+		return;
+	}
+	else
+	{
+		// Save by name
+		VLC->identifiersHandler->registerObject(scope, "obstacleTemplate", strippedName, id);
+
+		// Index by id
+		obstacleTemplates[id] = tmpl;
+	}
+}
+
+void ObstacleSetHandler::addObstacleSet(std::shared_ptr<ObstacleSet> os)
+{
+	biomes.push_back(os);
+}
+
+void ObstacleSetHandler::afterLoadFinalization()
+{
+	for (auto &os :biomes)
+	{
+		os->removeEmptyTemplates();
+	}
+	vstd::erase_if(biomes, [](const std::shared_ptr<ObstacleSet> &os)
+	{
+		if (os->getObstacles().empty())
+		{
+			logMod->warn("Obstacle set %d is empty, removing it", os->id);
+			return true;
+		}
+		return false;
+	});
+
+	// Populate map
+	for (auto &os : biomes)
+	{
+		obstacleSets[os->getType()].push_back(os);
+	}
+}
+
+TObstacleTypes ObstacleSetHandler::getObstacles( const ObstacleSetFilter &filter) const
+{
+	TObstacleTypes result;
+
+	for (const auto &allowedType : filter.getAllowedTypes())
+	{
+		auto it = obstacleSets.find(allowedType);
+		if(it != obstacleSets.end())
+		{
+			for (const auto &os : it->second)
+			{
+				if (filter.filter(*os))
+				{
+					result.push_back(os);
+				}
+			}
+		}
+	}
+	return result;
+}
+
+VCMI_LIB_NAMESPACE_END
+

+ 133 - 0
lib/mapObjects/ObstacleSetHandler.h

@@ -0,0 +1,133 @@
+/*
+ * ObstacleSetHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../GameConstants.h"
+#include "../constants/EntityIdentifiers.h"
+#include "../IHandlerBase.h"
+#include "../json/JsonNode.h"
+#include "ObjectTemplate.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE ObstacleSet
+{
+public:
+
+	// TODO: Create string constants for these
+
+	enum EObstacleType
+	{
+		INVALID = -1,
+		MOUNTAINS = 0,
+		TREES,
+		LAKES, // Inluding dry or lava lakes
+		CRATERS, // Chasms, Canyons, etc.
+		ROCKS,
+		PLANTS, // Flowers, cacti, mushrooms, logs, shrubs, etc.
+		STRUCTURES, // Buildings, ruins, etc.
+		ANIMALS, // Living, or bones
+		OTHER // Crystals, shipwrecks, barrels, etc.
+	};
+	ObstacleSet();
+	explicit ObstacleSet(EObstacleType type, TerrainId terrain);
+
+	void addObstacle(std::shared_ptr<const ObjectTemplate> obstacle);
+	void removeEmptyTemplates();
+	std::vector<std::shared_ptr<const ObjectTemplate>> getObstacles() const;
+
+	EObstacleType getType() const;
+	void setType(EObstacleType type);
+
+	std::set<TerrainId> getTerrains() const;
+	void setTerrain(TerrainId terrain);
+	void setTerrains(const std::set<TerrainId> & terrains);
+	void addTerrain(TerrainId terrain);
+	std::set<EAlignment> getAlignments() const;
+	void addAlignment(EAlignment alignment);
+	std::set<FactionID> getFactions() const;
+	void addFaction(FactionID faction);
+
+	static EObstacleType typeFromString(const std::string &str);
+	std::string toString() const;
+
+	si32 id;
+
+private:
+
+	EObstacleType type;
+	std::set<TerrainId> allowedTerrains; // Empty means all terrains
+	std::set<FactionID> allowedFactions; // Empty means all factions
+	std::set<EAlignment> allowedAlignments; // Empty means all alignments
+	std::vector<std::shared_ptr<const ObjectTemplate>> obstacles;
+};
+
+typedef std::vector<std::shared_ptr<ObstacleSet>> TObstacleTypes;
+
+class DLL_LINKAGE ObstacleSetFilter
+{
+public:
+	ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain, FactionID faction, EAlignment alignment);
+	ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain, FactionID faction, EAlignment alignment);
+
+	bool filter(const ObstacleSet &set) const;
+
+	void setType(ObstacleSet::EObstacleType type);
+	void setTypes(std::vector<ObstacleSet::EObstacleType> types);
+	std::vector<ObstacleSet::EObstacleType> getAllowedTypes() const;
+	TerrainId getTerrain() const;
+
+	void setAlignment(EAlignment alignment);
+
+private:
+	std::vector<ObstacleSet::EObstacleType> allowedTypes;
+	FactionID faction;
+	EAlignment alignment;
+// TODO: Filter by faction,  surface/underground, etc.
+	const TerrainId terrain;
+};
+
+// TODO: Instantiate ObstacleSetHandler
+class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase, boost::noncopyable
+{
+public:
+
+	ObstacleSetHandler() = default;
+	~ObstacleSetHandler() = default;
+
+	std::vector<JsonNode> loadLegacyData() override;
+	virtual void loadObject(std::string scope, std::string name, const JsonNode & data) override;
+	virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
+	std::shared_ptr<ObstacleSet> loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index);
+
+	ObstacleSet::EObstacleType convertObstacleClass(MapObjectID id);
+
+	// TODO: Populate obstacleSets with all the obstacle sets from the game data
+	void addTemplate(const std::string & scope, const std::string &name, std::shared_ptr<const ObjectTemplate> tmpl);
+
+	void addObstacleSet(std::shared_ptr<ObstacleSet> set);
+
+	void afterLoadFinalization() override;
+	
+	TObstacleTypes getObstacles(const ObstacleSetFilter &filter) const;
+
+private:
+
+	std::vector< std::shared_ptr<ObstacleSet> > biomes;
+
+	// TODO: Serialize?
+	std::map<si32, std::shared_ptr<const ObjectTemplate>> obstacleTemplates;
+
+		// FIXME: Store pointers?
+		std::map<ObstacleSet::EObstacleType, std::vector<std::shared_ptr<ObstacleSet>>> obstacleSets;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 161 - 0
lib/mapping/ObstacleProxy.cpp

@@ -15,6 +15,7 @@
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjects/CGObjectInstance.h"
 #include "../mapObjects/ObjectTemplate.h"
+#include "../mapObjects/ObstacleSetHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -36,6 +37,11 @@ void ObstacleProxy::collectPossibleObstacles(TerrainId terrain)
 			}
 		}
 	}
+	sortObstacles();
+}
+
+void ObstacleProxy::sortObstacles()
+{
 	for(const auto & o : obstaclesBySize)
 	{
 		possibleObstacles.emplace_back(o);
@@ -46,6 +52,161 @@ void ObstacleProxy::collectPossibleObstacles(TerrainId terrain)
 	});
 }
 
+bool ObstacleProxy::prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand)
+{
+	possibleObstacles.clear();
+
+	std::vector<std::shared_ptr<ObstacleSet>> obstacleSets;
+
+	size_t selectedSets = 0;
+	const size_t MINIMUM_SETS = 3; // Original Lava has only 4 types of sets
+	const size_t MAXIMUM_SETS = 9;
+	const size_t MIN_SMALL_SETS = 3;
+	const size_t MAX_SMALL_SETS = 5;
+
+	auto terrain = filter.getTerrain();
+	auto localFilter = filter;
+	localFilter.setType(ObstacleSet::EObstacleType::MOUNTAINS);
+
+	TObstacleTypes mountainSets = VLC->biomeHandler->getObstacles(localFilter);
+
+	if (!mountainSets.empty())
+	{
+		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(mountainSets, rand));
+		selectedSets++;
+		logGlobal->info("Mountain set added");
+	}
+	else
+	{
+		logGlobal->warn("No mountain sets found for terrain %s", TerrainId::encode(terrain.getNum()));
+		// FIXME: Do we ever want to generate obstacles without any mountains?
+	}
+
+	localFilter.setType(ObstacleSet::EObstacleType::TREES);
+	TObstacleTypes treeSets = VLC->biomeHandler->getObstacles(localFilter);
+
+	// 1 or 2 tree sets
+	size_t treeSetsCount = std::min<size_t>(treeSets.size(), rand.nextInt(1, 2));
+	for (size_t i = 0; i < treeSetsCount; i++)
+	{
+		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(treeSets, rand));
+		selectedSets++;
+	}
+	logGlobal->info("Added %d tree sets", treeSetsCount);
+
+	// Some obstacle types may be completely missing from water, but it's not a problem
+	localFilter.setTypes({ObstacleSet::EObstacleType::LAKES, ObstacleSet::EObstacleType::CRATERS});
+	TObstacleTypes largeSets = VLC->biomeHandler->getObstacles(localFilter);
+
+	// We probably don't want to have lakes and craters at the same time, choose one of them
+
+	if (!largeSets.empty())
+	{
+		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(largeSets, rand));
+		selectedSets++;
+
+		// TODO: Convert to string
+		logGlobal->info("Added large set of type %s", obstacleSets.back()->getType());
+	}
+
+	localFilter.setType(ObstacleSet::EObstacleType::ROCKS);
+	TObstacleTypes rockSets = VLC->biomeHandler->getObstacles(localFilter);
+
+	size_t rockSetsCount = std::min<size_t>(rockSets.size(), rand.nextInt(1, 2));
+	for (size_t i = 0; i < rockSetsCount; i++)
+	{
+		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(rockSets, rand));
+		selectedSets++;
+	}
+	logGlobal->info("Added %d rock sets", rockSetsCount);
+
+	localFilter.setType(ObstacleSet::EObstacleType::PLANTS);
+	TObstacleTypes plantSets = VLC->biomeHandler->getObstacles(localFilter);
+
+	// 1 or 2 sets (3 - rock sets)
+	size_t plantSetsCount = std::min<size_t>(plantSets.size(), rand.nextInt(1, std::max<size_t>(3 - rockSetsCount, 2)));
+	for (size_t i = 0; i < plantSetsCount; i++)
+	{
+		{
+			obstacleSets.push_back(*RandomGeneratorUtil::nextItem(plantSets, rand));
+			selectedSets++;
+		}
+	}
+	logGlobal->info("Added %d plant sets", plantSetsCount);
+
+	//3 to 5 of total small sets (rocks, plants, structures, animals and others)
+	//This gives total of 6 to 9 different sets
+
+	size_t maxSmallSets = std::min<size_t>(MAX_SMALL_SETS, std::max(MIN_SMALL_SETS, MAXIMUM_SETS - selectedSets));
+
+	size_t smallSets = rand.nextInt(MIN_SMALL_SETS, maxSmallSets);
+
+	localFilter.setTypes({ObstacleSet::EObstacleType::STRUCTURES, ObstacleSet::EObstacleType::ANIMALS});
+	TObstacleTypes smallObstacleSets = VLC->biomeHandler->getObstacles(localFilter);
+	RandomGeneratorUtil::randomShuffle(smallObstacleSets, rand);
+
+	localFilter.setType(ObstacleSet::EObstacleType::OTHER);
+	TObstacleTypes otherSets = VLC->biomeHandler->getObstacles(localFilter);
+	RandomGeneratorUtil::randomShuffle(otherSets, rand);
+
+	while (smallSets > 0)
+	{
+		if (!smallObstacleSets.empty())
+		{
+			obstacleSets.push_back(smallObstacleSets.back());
+			smallObstacleSets.pop_back();
+			selectedSets++;
+			smallSets--;
+			logGlobal->info("Added small set of type %s", obstacleSets.back()->getType());
+		}
+		else if(otherSets.empty())
+		{
+			logGlobal->warn("No other sets found for terrain %s", terrain.encode(terrain.getNum()));
+			break;
+		}
+
+		if (smallSets > 0)
+		{
+			// Fill with whatever's left
+			if (!otherSets.empty())
+			{
+				obstacleSets.push_back(otherSets.back());
+				otherSets.pop_back();
+				selectedSets++;
+				smallSets--;
+
+				logGlobal->info("Added set of other obstacles");
+			}
+		}
+	}
+
+	// Copy this set to our possible obstacles
+
+	if (selectedSets >= MINIMUM_SETS ||
+		(terrain == TerrainId::WATER && selectedSets > 0))
+	{
+		obstaclesBySize.clear();
+		for (const auto & os : obstacleSets)
+		{
+			for (const auto & temp : os->getObstacles())
+			{
+				if(temp->getBlockMapOffset().valid())
+				{
+					obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp);
+				}
+			}
+		}
+
+		sortObstacles();
+
+		return true;
+	}
+	else
+	{
+		return false; // Proceed with old method
+	}
+}
+
 void ObstacleProxy::addBlockedTile(const int3& tile)
 {
 	blockedArea.add(tile);

+ 3 - 0
lib/mapping/ObstacleProxy.h

@@ -20,6 +20,7 @@ class CGObjectInstance;
 class ObjectTemplate;
 class CRandomGenerator;
 class IGameCallback;
+class ObstacleSetFilter;
 
 class DLL_LINKAGE ObstacleProxy
 {
@@ -29,6 +30,7 @@ public:
 	virtual ~ObstacleProxy() = default;
 
 	void collectPossibleObstacles(TerrainId terrain);
+	bool prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand);
 
 	void addBlockedTile(const int3 & tile);
 
@@ -52,6 +54,7 @@ public:
 
 protected:
 	int getWeightedObjects(const int3& tile, CRandomGenerator& rand, IGameCallback * cb, std::list<rmg::Object>& allObjects, std::vector<std::pair<rmg::Object*, int3>>& weightedObjects);
+	void sortObstacles();
 
 	rmg::Area blockedArea;
 

+ 2 - 1
lib/modding/ContentTypeHandler.cpp

@@ -26,6 +26,7 @@
 #include "../IHandlerBase.h"
 #include "../Languages.h"
 #include "../ObstacleHandler.h"
+#include "../mapObjects/ObstacleSetHandler.h"
 #include "../RiverHandler.h"
 #include "../RoadHandler.h"
 #include "../ScriptHandler.h"
@@ -207,7 +208,7 @@ void CContentHandler::init()
 	handlers.insert(std::make_pair("rivers", ContentTypeHandler(VLC->riverTypeHandler.get(), "river")));
 	handlers.insert(std::make_pair("roads", ContentTypeHandler(VLC->roadTypeHandler.get(), "road")));
 	handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler.get(), "obstacle")));
-	//TODO: any other types of moddables?
+	handlers.insert(std::make_pair("biomes", ContentTypeHandler(VLC->biomeHandler.get(), "biome")));
 }
 
 bool CContentHandler::preloadModData(const std::string & modName, JsonNode modConfig, bool validate)

+ 16 - 1
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -25,6 +25,8 @@
 #include "../../mapping/CMap.h"
 #include "../../mapping/ObstacleProxy.h"
 #include "../../mapObjects/CGObjectInstance.h"
+#include "../../mapObjects/ObstacleSetHandler.h"
+#include "../../CTownHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -34,7 +36,20 @@ void ObstaclePlacer::process()
 	if(!manager)
 		return;
 
-	collectPossibleObstacles(zone.getTerrainType());
+	auto faction = zone.getTownType().toFaction();
+
+	ObstacleSetFilter filter(ObstacleSet::EObstacleType::INVALID, zone.getTerrainType(), faction->getId(), faction->alignment);
+
+	if (!prepareBiome(filter, zone.getRand()))
+	{
+		logGlobal->warn("Failed to prepare biome, using all possible obstacles");
+		// Use all if we fail to create proper biome
+		collectPossibleObstacles(zone.getTerrainType());
+	}
+	else
+	{
+		logGlobal->info("Biome prepared successfully for zone %d", zone.getId());
+	}
 	
 	{
 		auto area = zone.area();