mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	- Improved json validation. Now it support most of features from latest json schema draft
- Set of schemas in config/schemas directory that are used to validate input from mods.
This commit is contained in:
		| @@ -308,7 +308,7 @@ int main(int argc, char** argv) | ||||
| 		tlog0 << "Fatal error: failed to load settings!\n"; | ||||
| 		tlog0 << "Possible reasons:\n"; | ||||
| 		tlog0 << "\tCorrupted local configuration file at " << VCMIDirs::get().localPath() << "/config/settings.json\n"; | ||||
| 		tlog0 << "\tMissing or corrupted global configuration file at " << VCMIDirs::get().dataPath() << "/config/defaultSettings.json\n"; | ||||
| 		tlog0 << "\tMissing or corrupted global configuration file at " << VCMIDirs::get().dataPath() << "/config/schemas/settings.json\n"; | ||||
| 		tlog0 << "VCMI will now exit...\n"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
|   | ||||
| @@ -1,126 +0,0 @@ | ||||
| // This is schema for checking game settings from settings.json | ||||
| // Any new settings should be added in this file for correct serialization and initialization | ||||
| { | ||||
| 	"general" : { | ||||
| 		"type" : "object", | ||||
| 		"properties" : { | ||||
| 			"classicCreatureWindow" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : false | ||||
| 			}, | ||||
| 			"playerName" : { | ||||
| 				"type":"string", | ||||
| 				"default" : "Player" | ||||
| 			}, | ||||
| 			"showfps" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : false | ||||
| 			}, | ||||
| 			"music" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 88 | ||||
| 			}, | ||||
| 			"sound" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 88 | ||||
|             } | ||||
| 		}, | ||||
| 		"default" : {} | ||||
| 	}, | ||||
| 	"video" : { | ||||
| 		"type" : "object", | ||||
| 		"properties" : { | ||||
| 			"screenRes" : { | ||||
| 				"type" : "object", | ||||
| 				"properties" : { | ||||
| 					"width"  : { "type" : "number" }, | ||||
| 					"height" : { "type" : "number" } | ||||
| 				}, | ||||
| 				"default": {"width" : 800, "height": 600 } | ||||
| 			}, | ||||
| 			"bitsPerPixel" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 24 | ||||
| 			}, | ||||
| 			"fullscreen" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : false | ||||
| 			} | ||||
| 		}, | ||||
| 		"default" : {} | ||||
| 	}, | ||||
| 	"adventure" : { | ||||
| 		"type" : "object", | ||||
| 		"properties" : { | ||||
| 			"heroSpeed" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 2 | ||||
| 			}, | ||||
| 			"enemySpeed" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 2 | ||||
| 			}, | ||||
| 			"scrollSpeed" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 1 | ||||
| 			}, | ||||
| 			"heroReminder" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : true | ||||
| 			} | ||||
| 		}, | ||||
| 		"default" : {} | ||||
| 	}, | ||||
| 	"battle" : { | ||||
| 		"type" : "object", | ||||
| 		"properties" : { | ||||
| 			"animationSpeed" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 2 | ||||
| 			}, | ||||
| 			"mouseShadow" : { | ||||
| 				"type":"bool", | ||||
| 				"default" : true | ||||
| 			}, | ||||
| 			"cellBorders" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : false | ||||
| 			}, | ||||
| 			"stackRange" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : true | ||||
| 			}, | ||||
| 			"showQueue" : { | ||||
| 				"type" : "bool", | ||||
| 				"default" : true | ||||
| 			} | ||||
| 		}, | ||||
| 		"default" : {} | ||||
| 	}, | ||||
| 	"server" : { | ||||
| 		"type" : "object", | ||||
| 		"properties" : { | ||||
| 			"server" : { | ||||
| 				"type":"string", | ||||
| 				"default" : "127.0.0.1" | ||||
| 			}, | ||||
| 			"port" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 3030 | ||||
| 			}, | ||||
| 			"localInformation" : { | ||||
| 				"type" : "number", | ||||
| 				"default" : 2 | ||||
| 			}, | ||||
| 			"playerAI" : { | ||||
| 				"type" : "string", | ||||
| 				"default" : "VCAI" | ||||
| 			}, | ||||
| 			"neutralAI" : { | ||||
| 				"type" : "string", | ||||
| 				"default" : "StupidAI" | ||||
| 			} | ||||
| 		}, | ||||
| 		"default" : {} | ||||
| 	} | ||||
| } | ||||
| @@ -112,8 +112,7 @@ | ||||
| 				{ "id" : 40, "animation" : "TBCSUP_3.def", "x" : 176, "y" : 85,  "border" : "TOCSSWD2.bmp", "area" : "TZCSSWD2.bmp" }, | ||||
| 				{ "id" : 41, "animation" : "TBCSUP_4.def", "x" : 563, "y" : 173, "z" : 1,  "border" : "TOCSMON2.bmp", "area" : "TZCSMON2.bmp" }, | ||||
| 				{ "id" : 42, "animation" : "TBCSUP_5.def", "x" : 160, "y" : 190, "z" : -1, "border" : "TOCSCAV2.bmp", "area" : "TZCSCAV2.bmp" }, | ||||
| 				{ "id" : 43, "animation" : "TBCSUP_6.def", "x" : 303, "y" : 0,   "z" : -1, "border" : "TOCSANG2.bmp", "area" : "TZCSANG2.bmp" }, | ||||
|  | ||||
| 				{ "id" : 43, "animation" : "TBCSUP_6.def", "x" : 303, "y" : 0,   "z" : -1, "border" : "TOCSANG2.bmp", "area" : "TZCSANG2.bmp" } | ||||
| 			], | ||||
| 			"icons" : | ||||
| 			{ | ||||
|   | ||||
							
								
								
									
										82
									
								
								config/schemas/artifact.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								config/schemas/artifact.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI artifact format", | ||||
| 	"description" : "Format used to define new artifacts in VCMI", | ||||
| 	"required" : [ "class", "graphics", "text", "type", "value" ], | ||||
| 	"properties":{ | ||||
| 		"bonuses": { | ||||
| 			"type":"array", | ||||
| 			"description": "Bonuses provided by this artifact using bonus system", | ||||
| 			"items": { "$ref" : "vcmi:bonus" } | ||||
| 		}, | ||||
| 		"class": { | ||||
| 			"type":"string", | ||||
| 			"enum" : [ "SPECIAL", "TREASURE", "MINOR", "MAJOR", "RELIC" ], | ||||
| 			"description": "Artifact class, treasure, minor, major or relic" | ||||
| 		}, | ||||
| 		"components": { | ||||
| 			"type":"array", | ||||
| 			"description": "Optional, list of components for combinational artifacts", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"graphics": { | ||||
| 			"type":"object", | ||||
| 			"description": "Graphical files associated with the artifact", | ||||
| 			"required" : [ "iconIndex", "image", "map" ], | ||||
| 			"properties":{ | ||||
| 				"iconIndex": { | ||||
| 					"type":"number", | ||||
| 					"description": "index of icon of this artifact in .def file" | ||||
| 				}, | ||||
| 				"image": { | ||||
| 					"type":"string", | ||||
| 					"description": "Base image for this artifact, used for example in hero screen" | ||||
| 				}, | ||||
| 				"large": { | ||||
| 					"type":"string", | ||||
| 					"description": "Large image, used for drag-and-drop and popup messages" | ||||
| 				}, | ||||
| 				"map": { | ||||
| 					"type":"string", | ||||
| 					"description": ".def file for adventure map" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"slot": { | ||||
| 			"type":"string", | ||||
| 			"description": "Slot to which this artifact can be put, if applicable" | ||||
| 		}, | ||||
| 		"text": { | ||||
| 			"type":"object", | ||||
| 			"description": "Texts associated with artifact", | ||||
| 			"required" : [ "description", "event", "name" ], | ||||
| 			"properties":{ | ||||
| 				"description": { | ||||
| 					"type":"string", | ||||
| 					"description": "Long description of this artifact" | ||||
| 				}, | ||||
| 				"event": { | ||||
| 					"type":"string", | ||||
| 					"description": "Text that visible on picking this artifact on map" | ||||
| 				}, | ||||
| 				"name": { | ||||
| 					"type":"string", | ||||
| 					"description": "Name of the artifact" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"type": { | ||||
| 			"type":"array", | ||||
| 			"description": "Type of this artifact - creature, hero or commander", | ||||
| 			"items": { | ||||
| 				"type":"string", | ||||
| 				"enum" : [ "HERO", "CREATURE", "COMMANDER" ] | ||||
| 			} | ||||
| 		}, | ||||
| 		"value": { | ||||
| 			"type":"number", | ||||
| 			"description": "Cost of this artifact, in gold" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										92
									
								
								config/schemas/bonus.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								config/schemas/bonus.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI bonus system format", | ||||
| 	"description" : "Subsection of several formats, used to add generic bonuses to objects", | ||||
| 	"required": ["type"], | ||||
| 	"properties":{ | ||||
| 		"addInfo": { | ||||
| 			"anyOf" : [ | ||||
| 				{ "type" : "string" }, | ||||
| 				{ "type" : "number" } | ||||
| 			], | ||||
| 			"description": "addInfo", | ||||
| 		}, | ||||
| 		"description": { | ||||
| 			"type":"string", | ||||
| 			"description": "description", | ||||
| 		}, | ||||
| 		"duration": { | ||||
| 			"type":"string", | ||||
| 			"description": "duration", | ||||
| 		}, | ||||
| 		"effectRange": { | ||||
| 			"type":"string", | ||||
| 			"description": "effectRange", | ||||
| 		}, | ||||
| 		"limiters": { | ||||
| 			"type":"array", | ||||
| 			"description": "limiters", | ||||
| 			"items": { | ||||
| 				"type":"object", | ||||
| 				"properties" : { | ||||
| 					"parameters": { | ||||
| 						"type":"array", | ||||
| 						"description" : "parameters", | ||||
| 						"additionalItems": true | ||||
| 					}, | ||||
| 					"type": { | ||||
| 						"type":"string", | ||||
| 						"description": "type", | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"propagator": { | ||||
| 			"description": "propagator", | ||||
| 			"anyOf" : [ | ||||
| 				{ | ||||
| 					"type" : "string" | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type":"array", | ||||
| 					"items": { | ||||
| 							"type":"string", | ||||
| 							"description": "0", | ||||
| 					} | ||||
| 				} | ||||
| 			], | ||||
| 		}, | ||||
| 		"sourceID": { | ||||
| 			"type":"number", | ||||
| 			"description": "sourceID", | ||||
| 		}, | ||||
| 		"sourceType": { | ||||
| 			"type":"string", | ||||
| 			"description": "sourceType", | ||||
| 		}, | ||||
| 		"subtype": { | ||||
| 			"anyOf" : [ | ||||
| 				{ "type" : "string" }, | ||||
| 				{ "type" : "number" } | ||||
| 			], | ||||
| 			"description": "subtype", | ||||
| 		}, | ||||
| 		"turns": { | ||||
| 			"type":"number", | ||||
| 			"description": "turns", | ||||
| 		}, | ||||
| 		"type": { | ||||
| 			"type":"string", | ||||
| 			"description": "type", | ||||
| 		}, | ||||
| 		"val": { | ||||
| 			"type":"number", | ||||
| 			"description": "val", | ||||
| 		}, | ||||
| 		"valueType": { | ||||
| 			"type":"string", | ||||
| 			"description": "valueType", | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										244
									
								
								config/schemas/creature.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								config/schemas/creature.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,244 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI creature format", | ||||
| 	"description": "Json format for defining new creatures in VCMI", | ||||
| 	"required" : [ | ||||
| 		"name", "faction", "cost", "level", "fightValue", "aiValue", | ||||
| 		"attack", "defense", "hitPoints", "speed", "damage", "advMapAmount", | ||||
| 		"graphics", "sound" | ||||
| 	], | ||||
| 	"properties":{ | ||||
| 		"name": { | ||||
| 			"type":"object", | ||||
| 			"description": "Translatable names for this creature", | ||||
| 			"required" : [ "singular", "plural" ], | ||||
| 			"properties":{ | ||||
| 				"singular": { | ||||
| 					"type":"string", | ||||
| 					"description": "Singular version" | ||||
| 				}, | ||||
| 				"plural": { | ||||
| 					"type":"string", | ||||
| 					"description": "Plural version" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"faction": { | ||||
| 			"type":"string", | ||||
| 			"description": "Faction this creature belongs to. Examples: castle, rampart" | ||||
| 		}, | ||||
| 		"cost": { | ||||
| 			"type":"object", | ||||
| 			"description": "Cost to recruit this creature", | ||||
| 			"properties":{ | ||||
| 				"wood":    { "type":"number"}, | ||||
| 				"mercury": { "type":"number"}, | ||||
| 				"ore":     { "type":"number"}, | ||||
| 				"sulfur":  { "type":"number"}, | ||||
| 				"crystal": { "type":"number"}, | ||||
| 				"gems":    { "type":"number"}, | ||||
| 				"gold":    { "type":"number"} | ||||
| 			} | ||||
| 		}, | ||||
| 		"special": { | ||||
| 			"type":"boolean", | ||||
| 			"description": "Marks this object as special and not available by default" | ||||
| 		}, | ||||
| 		"level": { "type":"number"}, | ||||
| 		"fightValue": { | ||||
| 			"type":"number", | ||||
| 			"description": " \"value\" of creature, used to determine for example army strength" | ||||
| 		}, | ||||
| 		"aiValue": { | ||||
| 			"type":"number", | ||||
| 			"description": "Describes how valuable this creature is to AI. Usually similar to fightValue" | ||||
| 		}, | ||||
| 		"growth": { | ||||
| 			"type":"number", | ||||
| 			"description": "Basic growth of this creature in town or in external dwellings" | ||||
| 		}, | ||||
| 		"horde": { | ||||
| 			"type":"number", | ||||
| 			"description": "Bonus growth of this creature from built horde" | ||||
| 		}, | ||||
| 		"attack":    { "type":"number" }, | ||||
| 		"defense":   { "type":"number" }, | ||||
| 		"hitPoints": { "type":"number" }, | ||||
| 		"speed":     { "type":"number" }, | ||||
| 		"shots":     { "type":"number" }, | ||||
|  | ||||
| 		"damage": { | ||||
| 			"type":"object", | ||||
| 			"properties":{ | ||||
| 				"max": { "type":"number" }, | ||||
| 				"min": { "type":"number" } | ||||
| 			} | ||||
| 		}, | ||||
| 		"spellPoints": { | ||||
| 			"type":"number", | ||||
| 			"description": "Spell points this creature has (usually equal to number of casts)" | ||||
| 		}, | ||||
| 		"advMapAmount": { | ||||
| 			"type":"object", | ||||
| 			"description" : "Initial size of random stacks on adventure map", | ||||
| 			"properties":{ | ||||
| 				"min": { "type":"number" }, | ||||
| 				"max": { "type":"number" } | ||||
| 			} | ||||
| 		}, | ||||
| 		"upgrades": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of creatures to which this one can be upgraded", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"doubleWide": { | ||||
| 			"type":"boolean", | ||||
| 			"description": "If set, creature will be two tiles wide on battlefield" | ||||
| 		}, | ||||
| 		"hasDoubleWeek": { | ||||
| 			"type":"boolean", | ||||
| 			"description": "creature may receive \"week of\" events" | ||||
| 		}, | ||||
| 		"abilities": { | ||||
| 			"type":"array", | ||||
| 			"description": "Creature abilities described using Bonus system", | ||||
| 			"items": { "$ref" : "vcmi:bonus" } | ||||
| 		}, | ||||
| 		"stackExperience": { | ||||
| 			"type":"array", | ||||
| 			"description": "Stack experience, using bonus system", | ||||
| 			"items":{ | ||||
| 				"type":"object", | ||||
| 				"required" : [ "bonus", "values" ], | ||||
| 				"description": "0", | ||||
| 				"properties":{ | ||||
| 					"bonus": {"$ref" : "vcmi:bonus" }, | ||||
| 					"values": { | ||||
| 						"type":"array", | ||||
| 						"minItems" : 10, | ||||
| 						"maxItems" : 10, | ||||
| 						"description": "Strength of the bonus", | ||||
| 						"additionalItems" : true, | ||||
| 						"anyof" : [ | ||||
| 							{ "items": { "type" : "number"  } }, | ||||
| 							{ "items": { "type" : "boolean" } } | ||||
| 						] | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"graphics": { | ||||
| 			"type":"object", | ||||
| 			"description": "Describes how this creature looks like during battles", | ||||
| 			"required" : [ | ||||
| 				"animationTime", "iconLarge", "iconSmall", "iconIndex", | ||||
| 				"map", "animation", "attackClimaxFrame", "timeBetweenFidgets" | ||||
| 			], | ||||
| 			"properties":{ | ||||
| 				"animationTime": { | ||||
| 					"type":"object", | ||||
| 					"required" : [ "attack", "flight", "walk" ], | ||||
| 					"description": "Length of several animations", | ||||
| 					"properties":{ | ||||
| 						"attack": { | ||||
| 							"type":"number", | ||||
| 							"description": "attack" | ||||
| 						}, | ||||
| 						"flight": { | ||||
| 							"type":"number", | ||||
| 							"description": "flight" | ||||
| 						}, | ||||
| 						"walk": { | ||||
| 							"type":"number", | ||||
| 							"description": "walk" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"iconLarge": { | ||||
| 					"type":"string", | ||||
| 					"description": "Large icon for this creature, used for example in town screen" | ||||
| 				}, | ||||
| 				"iconSmall": { | ||||
| 					"type":"string", | ||||
| 					"description": "Small icon for this creature, used for example in exchange screen" | ||||
| 				}, | ||||
| 				"iconIndex": { | ||||
| 					"type":"number", | ||||
| 					"description": "Index of icon of this creature in .def file" | ||||
| 				}, | ||||
|  | ||||
| 				"map": { | ||||
| 					"type":"string", | ||||
| 					"description": ".def file with animation of this creature on adventure map" | ||||
| 				}, | ||||
| 				"animation": { | ||||
| 					"type":"string", | ||||
| 					"description": ".def file with animation of this creature in battles" | ||||
| 				}, | ||||
| 				"attackClimaxFrame": { | ||||
| 					"type":"number", | ||||
| 					"description": "Frame from attack animation during which creature deals damage" | ||||
| 				}, | ||||
| 				"missile": { | ||||
| 					"type":"object", | ||||
| 					"required" : [ "projectile", "frameAngles", "offset" ], | ||||
| 					"description": "Missile description for archers", | ||||
| 					"properties":{ | ||||
| 						"projectile": { | ||||
| 							"type":"string", | ||||
| 							"description": "Path to projectile animation" | ||||
| 						}, | ||||
| 						"frameAngles": { | ||||
| 							"type":"array", | ||||
| 							"description": "Angles of missile images, should go from 90 to -90", | ||||
| 							"minItems" : 1, | ||||
| 							"items": { "type":"number" } | ||||
| 						}, | ||||
| 						"offset": { | ||||
| 							"type":"object", | ||||
| 							"required" : [ "lowerX", "lowerY", "middleX", "middleY", "upperX", "upperY" ], | ||||
| 							"description": "Position where projectile image appears during shooting in specific direction", | ||||
| 							"properties":{ | ||||
| 								"lowerX":  { "type":"number" }, | ||||
| 								"lowerY":  { "type":"number" }, | ||||
| 								"middleX": { "type":"number" }, | ||||
| 								"middleY": { "type":"number" }, | ||||
| 								"upperX":  { "type":"number" }, | ||||
| 								"upperY":  { "type":"number" } | ||||
| 							} | ||||
| 						}, | ||||
| 						"spinning": { | ||||
| 							"type":"boolean", | ||||
| 							"description": "Flying projectile spins. Has some requirements to .def file" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"timeBetweenFidgets": { | ||||
| 					"type":"number", | ||||
| 					"description": "How often creature will play idling animation" | ||||
| 				}, | ||||
| 				"troopCountLocationOffset": { | ||||
| 					"type":"number", | ||||
| 					"description": "Position of troop count label?" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"sound": { | ||||
| 			"type":"object", | ||||
| 			"description": "Various sound files associated with this creature", | ||||
| 			"properties":{ | ||||
| 				"attack":    { "type":"string" }, | ||||
| 				"defend":    { "type":"string" }, | ||||
| 				"killed":    { "type":"string" }, | ||||
| 				"moveEnd":   { "type":"string" }, | ||||
| 				"moveStart": { "type":"string" }, | ||||
| 				"move":      { "type":"string" }, | ||||
| 				"shoot":     { "type":"string" }, | ||||
| 				"wince":     { "type":"string" }, | ||||
| 				"ext1":      { "type":"string" }, | ||||
| 				"ext2":      { "type":"string" } | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										250
									
								
								config/schemas/faction.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								config/schemas/faction.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI faction format", | ||||
| 	"description": "Json format for defining new faction (aka towns) in VCMI", | ||||
| 	"required" : [ "name", "alignment", "creatureBackground" ], | ||||
| 	"dependencies" : { | ||||
| 		"town" : [ "puzzleMap", "commander" ] | ||||
| 	}, | ||||
| 	"properties":{ | ||||
| 		"name" : { | ||||
| 			"type" : "string", | ||||
| 			"description" : "Translatable name of town" | ||||
| 		}, | ||||
| 		"alignment": { | ||||
| 			"type":"string", | ||||
| 			"enum" : [ "good", "neutral", "evil" ], | ||||
| 			"description": "Town alignment, good, neutral or evil" | ||||
| 		}, | ||||
| 		"commander": { | ||||
| 			"type":"string", | ||||
| 			"description": "Identifier of creature that is used as commander by heroes" | ||||
| 		}, | ||||
| 		"creatureBackground": { | ||||
| 			"type":"object", | ||||
| 			"required" : [ "120px", "130px" ], | ||||
| 			"description": "Backgrounds for creature info card", | ||||
| 			"properties":{ | ||||
| 				"120px": { | ||||
| 					"type":"string", | ||||
| 					"description": "Version that is 120 pixels in height" | ||||
| 				}, | ||||
| 				"130px": { | ||||
| 					"type":"string", | ||||
| 					"description": "Version that is 130 pixels in height" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"nativeTerrain": { | ||||
| 			"type":"string", | ||||
| 			"description": "Native terrain for creatures. Creatures fighting on native terrain receive several bonuses", | ||||
| 		}, | ||||
| 		"puzzleMap": { | ||||
| 			"type":"object", | ||||
| 			"required" : [ "prefix", "pieces" ], | ||||
| 			"description": "Puzzle map from obelisks for this town. Must contain 48 pieces", | ||||
| 			"properties":{ | ||||
| 				"pieces": { | ||||
| 					"type":"array", | ||||
| 					"description": "Lits of pieces definitions", | ||||
| 					"minItems" : 48, | ||||
| 					"maxItems" : 48, | ||||
| 					"items": { | ||||
| 						"type":"object", | ||||
| 						"properties":{ | ||||
| 							"index": { "type":"number", "description" : "Order in which images will be opened" }, | ||||
| 							"x":     { "type":"number", "description" : "X coordinate on screen" }, | ||||
| 							"y":     { "type":"number", "description" : "X coordinate on screen" } | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"prefix": { | ||||
| 					"type":"string", | ||||
| 					"description": "Prefix for image names, e.g. \"PUZCAS\" for name \"PUZCAS12.png\"" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"town": { | ||||
| 			"type":"object", | ||||
| 			"required" : [ | ||||
| 				"adventureMap", "buildingsIcons", "buildings", "creatures", "guildWindow", "names", | ||||
| 				"hallBackground", "hallSlots", "horde", "icons", "mageGuild", "moatDamage", | ||||
| 				"musicTheme", "siege", "structures", "townBackground", "warMachine", "primaryResource" | ||||
| 			], | ||||
| 			"description": "town", | ||||
| 			"properties":{ | ||||
| 				"adventureMap": { | ||||
| 					"type":"object", | ||||
| 					"description": "Paths to images of object on adventure map", | ||||
| 					"required" : [ "capitol", "castle", "village" ], | ||||
| 					"properties":{ | ||||
| 						"capitol": { | ||||
| 							"type":"string", | ||||
| 							"description": "Town with capitol" | ||||
| 						}, | ||||
| 						"castle": { | ||||
| 							"type":"string", | ||||
| 							"description": "Town with built fort" | ||||
| 						}, | ||||
| 						"village": { | ||||
| 							"type":"string", | ||||
| 							"description": "Village without built fort" | ||||
| 						}, | ||||
| 						"dwellings" : { | ||||
| 							"type" : "array", | ||||
| 							"description" : "Dwellings on adventure map", | ||||
| 							"items" : { | ||||
| 								"type" : "object", | ||||
| 								"properties" : { | ||||
| 									"name":     { "type":"string" }, | ||||
| 									"graphics": { "type":"string" } | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"guildSpells" : { | ||||
| 					"type" : "object", | ||||
| 					"description" : "Spells available in mage guild. Value indicates chance for this spell to appear", | ||||
| 					"additionalProperties" : { | ||||
| 						"type" : "number" | ||||
| 					} | ||||
| 				}, | ||||
| 				"tavern" : { | ||||
| 					"type" : "object", | ||||
| 					"description" : "Hero classes available in tavern. Value indicates chance for hero of this class to appear", | ||||
| 					"additionalProperties" : { | ||||
| 						"type" : "number" | ||||
| 					} | ||||
| 				}, | ||||
| 				"names" : { | ||||
| 					"type" : "array", | ||||
| 					"description" : "Names for towns on adventure map", | ||||
| 					"minItems" : 1, | ||||
| 					"items" : { "type" : "string" } | ||||
| 				}, | ||||
| 				"buildingsIcons": { | ||||
| 					"type" : "string", | ||||
| 					"description": "Path to .def file with building icons" | ||||
| 				}, | ||||
| 				"buildings": { | ||||
| 					"type" : "array", | ||||
| 					"items" : { | ||||
| 						"$ref" : "vcmi:townBuilding" | ||||
| 					} | ||||
| 				}, | ||||
| 				"creatures": { | ||||
| 					"type":"array", | ||||
| 					"minItems" : 7, | ||||
| 					"maxItems" : 7, | ||||
| 					"description" : "List of creatures available for recruitment on each level", | ||||
| 					"items": { | ||||
| 						"type":"array", | ||||
| 						"items" : { "type" : "string" } | ||||
| 					} | ||||
| 				}, | ||||
| 				"guildWindow": { | ||||
| 					"type":"string", | ||||
| 					"description": "Image with small view on town from mage guild" | ||||
| 				}, | ||||
| 				"hallBackground": { | ||||
| 					"type":"string", | ||||
| 					"description": "background image for town hall" | ||||
| 				}, | ||||
| 				"hallSlots": { | ||||
| 					"type":"array", | ||||
| 					"description": "Description of town hall", | ||||
| 					"minItems" : 5, | ||||
| 					"maxItems" : 5, | ||||
| 					"items": { | ||||
| 						"type":"array", | ||||
| 						"minItems" : 1, | ||||
| 						"maxItems" : 4, | ||||
| 						"items" : { | ||||
| 							"type" : "array", | ||||
| 							"description" : "List of buildings available in one slot", | ||||
| 							"items" : { "type" : "number" } | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"horde": { | ||||
| 					"type":"array", | ||||
| 					"maxItems" : 2, | ||||
| 					"description": "Levels of creatures that have hordes in town", | ||||
| 					"items": { "type":"number" } | ||||
| 				}, | ||||
| 				"icons": { | ||||
| 					"type":"object", | ||||
| 					"description": "Town icons", | ||||
| 					"required" : [ "fort", "village" ], | ||||
| 					"properties":{ | ||||
| 						"fort": { | ||||
| 							"type":"object", | ||||
| 							"required" : [ "normal", "built" ], | ||||
| 							"description": "Icons for town with built fort", | ||||
| 							"properties":{ | ||||
| 								"built": { | ||||
| 									"type":"number", | ||||
| 									"description": "Icon used after player build something in town" | ||||
| 								}, | ||||
| 								"normal": { | ||||
| 									"type":"number", | ||||
| 									"description": "Icon used normally" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"village": { | ||||
| 							"type":"object", | ||||
| 							"required" : [ "normal", "built" ], | ||||
| 							"description": "Icons for town without fort", | ||||
| 							"properties":{ | ||||
| 								"built": { | ||||
| 									"type":"number", | ||||
| 									"description": "Icon used after player build something in town" | ||||
| 								}, | ||||
| 								"normal": { | ||||
| 									"type":"number", | ||||
| 									"description": "Icon used normally" | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"mageGuild": { | ||||
| 					"type":"number", | ||||
| 					"description": "Maximal level of mage guild" | ||||
| 				}, | ||||
| 				"moatDamage": { | ||||
| 					"type":"number", | ||||
| 					"description": "Damage dealt to creature that entered town moat during siege" | ||||
| 				}, | ||||
| 				"musicTheme": { | ||||
| 					"type":"string", | ||||
| 					"description": "Path to town music theme" | ||||
| 				}, | ||||
| 				"siege": { | ||||
| 					"$ref" : "vcmi:townSiege" | ||||
| 				}, | ||||
| 				"structures": { | ||||
| 					"type" : "array", | ||||
| 					"items" : { | ||||
| 						"$ref" : "vcmi:townStructure" | ||||
| 					} | ||||
| 				}, | ||||
| 				"townBackground": { | ||||
| 					"type":"string", | ||||
| 					"description": "Background for town screen" | ||||
| 				}, | ||||
| 				"primaryResource": { | ||||
| 					"type":"string", | ||||
| 					"description": "Primary resource for this town. Produced by Silo and offered as starting bonus" | ||||
| 				}, | ||||
| 				"warMachine": { | ||||
| 					"type":"string", | ||||
| 					"description": "Identifier of war machine produced by blacksmith in town" | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										151
									
								
								config/schemas/hero.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								config/schemas/hero.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI hero format", | ||||
| 	"description" : "Format used to define new heroes in VCMI", | ||||
| 	"required": [ "army", "class", "images", "skills", "texts" ], | ||||
| 	"properties":{ | ||||
| 		"army": { | ||||
| 			"type":"array", | ||||
| 			"description": "Initial hero army when recruited in tavern", | ||||
| 			"minItems" : 1, | ||||
| 			"maxItems" : 3, | ||||
| 			"items": { | ||||
| 				"type":"object", | ||||
| 				"required" : [ "creature", "min", "max" ], | ||||
| 				"properties":{ | ||||
| 					"creature": { | ||||
| 						"type":"string", | ||||
| 						"description": "creature", | ||||
| 					}, | ||||
| 					"max": { | ||||
| 						"type":"number", | ||||
| 						"description": "max", | ||||
| 					}, | ||||
| 					"min": { | ||||
| 						"type":"number", | ||||
| 						"description": "min", | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"special": { | ||||
| 				"type":"boolean", | ||||
| 				"description": "Marks this object as special and not available by default" | ||||
| 			}, | ||||
| 		"class": { | ||||
| 			"type":"string", | ||||
| 			"description": "Hero class, e.g. knight or battleMage" | ||||
| 		}, | ||||
| 		"female": { | ||||
| 			"type":"boolean", | ||||
| 			"description": "This hero is female by default" | ||||
| 		}, | ||||
| 		"images": { | ||||
| 			"type":"object", | ||||
| 			"description": "images", | ||||
| 			"required": [ "index", "large", "small", "specialtyLarge", "specialtySmall" ], | ||||
| 			"properties":{ | ||||
| 				"index": { | ||||
| 					"type":"number", | ||||
| 					"description": "Index of image in portraitsLarge/portraitsSmall files" | ||||
| 				}, | ||||
| 				"large": { | ||||
| 					"type":"string", | ||||
| 					"description": "Large version of portrait for use in hero screen" | ||||
| 				}, | ||||
| 				"small": { | ||||
| 					"type":"string", | ||||
| 					"description": "Small version of portrait for use on adventure map" | ||||
| 				}, | ||||
| 				"specialtyLarge": { | ||||
| 					"type":"string", | ||||
| 					"description": "Large image of hero specilty, used in hero screen" | ||||
| 				}, | ||||
| 				"specialtySmall": { | ||||
| 					"type":"string", | ||||
| 					"description": "Small image of hero specialty for use in exchange screen" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"skills": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of skills initially known by hero", | ||||
| 			"maxItems" : 8, | ||||
| 			"items": { | ||||
| 				"type":"object", | ||||
| 				"required" : [ "level", "skill" ], | ||||
| 				"properties":{ | ||||
| 					"level": { | ||||
| 						"type":"string", | ||||
| 						"description": "level", | ||||
| 						"enum" : [ "basic", "advanced", "expert" ] | ||||
| 					}, | ||||
| 					"skill": { | ||||
| 						"type":"string", | ||||
| 						"description": "skill" | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"specialty": { | ||||
| 			"type":"array", | ||||
| 			"description": "Description of hero specialty using bonus system",	 | ||||
| 			"items": { | ||||
| 				"type":"object", | ||||
| 				"required" : [ "bonuses" ], | ||||
| 				"properties":{ | ||||
| 					"growsWithLevel" : { | ||||
| 						"type" : "boolean", | ||||
| 						"description" : "Specialty growth with level, so far only SECONDARY_SKILL_PREMY and PRIMATY SKILL with creature limiter can grow" | ||||
| 					}, | ||||
| 					"bonuses": { | ||||
| 						"type":"array", | ||||
| 						"description": "List of bonuses", | ||||
| 						"items": { "$ref" : "vcmi:bonus" } | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"spellbook": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of starting spells, if available. This entry (even empty) will also grant spellbook", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"texts": { | ||||
| 			"type":"object", | ||||
| 			"description": "All translatable texts related to hero", | ||||
| 			"required" : [ "biography", "name", "specialty" ], | ||||
| 			"properties":{ | ||||
| 				"biography": { | ||||
| 					"type":"string", | ||||
| 					"description": "Hero biography" | ||||
| 				}, | ||||
| 				"name": { | ||||
| 					"type":"string", | ||||
| 					"description": "Hero name" | ||||
| 				}, | ||||
| 				"specialty": { | ||||
| 					"type":"object", | ||||
| 					"description": "Hero specialty information", | ||||
| 					"required" : [ "description", "name", "tooltip" ], | ||||
| 					"properties":{ | ||||
| 						"description": { | ||||
| 							"type":"string", | ||||
| 							"description": "Description visible when hovering over specialty icon" | ||||
| 						}, | ||||
| 						"name": { | ||||
| 							"type":"string", | ||||
| 							"description": "Name of the specialty" | ||||
| 						}, | ||||
| 						"tooltip": { | ||||
| 							"type":"string", | ||||
| 							"description": "Tooltip visible on clicking icon." | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										92
									
								
								config/schemas/heroClass.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								config/schemas/heroClass.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI hero class format", | ||||
| 	"description" : "Format used to define classes of heroes in VCMI", | ||||
| 	"required" : [ | ||||
| 		"animation", "faction", "highLevelChance", "lowLevelChance", | ||||
| 		"name", "primarySkills", "secondarySkills", "tavern" | ||||
| 	], | ||||
| 	"properties":{ | ||||
| 		"animation": { | ||||
| 			"type":"object", | ||||
| 			"description": "Files related to hero animation", | ||||
| 			"required": [ "battle", "map" ], | ||||
| 			"properties":{ | ||||
| 				"battle": { | ||||
| 					"type":"object", | ||||
| 					"description": "Hero animations for battle", | ||||
| 					"required": [ "female", "male" ], | ||||
| 					"properties":{ | ||||
| 						"female": { | ||||
| 							"type":"string", | ||||
| 							"description": "Female version" | ||||
| 						}, | ||||
| 						"male": { | ||||
| 							"type":"string", | ||||
| 							"description": "Male version" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"map": { | ||||
| 					"type":"object", | ||||
| 					"description": "Hero animations for adventure map", | ||||
| 					"required": [ "female", "male" ], | ||||
| 					"properties":{ | ||||
| 						"female": { | ||||
| 							"type":"string", | ||||
| 							"description": "Female version. Warning: not implemented!" | ||||
| 						}, | ||||
| 						"male": { | ||||
| 							"type":"string", | ||||
| 							"description": "Male version" | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"faction": { | ||||
| 			"type":"string", | ||||
| 			"description": "Faction this hero class belongs to" | ||||
| 		}, | ||||
| 		"highLevelChance": { | ||||
| 			"type":"object", | ||||
| 			"description": "Chance to get specific primary skill on level-up, applicable for levels starting from 10", | ||||
| 			"additionalProperties":{ | ||||
| 				"type":"number" | ||||
| 			} | ||||
| 		}, | ||||
| 		"lowLevelChance": { | ||||
| 			"type":"object", | ||||
| 			"description": "Chance to get specific primary skill on level-up, applicable for levels less than 10", | ||||
| 			"additionalProperties":{ | ||||
| 				"type":"number" | ||||
| 			} | ||||
| 		}, | ||||
| 		"name": { | ||||
| 			"type":"string", | ||||
| 			"description": "Translatable name of hero class" | ||||
| 		}, | ||||
| 		"primarySkills": { | ||||
| 			"type":"object", | ||||
| 			"description": "Initial primary skills of heroes", | ||||
| 			"additionalProperties":{ | ||||
| 				"type":"number" | ||||
| 			} | ||||
| 		}, | ||||
| 		"secondarySkills": { | ||||
| 			"type":"object", | ||||
| 			"description": "Chance to get specific secondary skill on level-up. All missing skills are considered to be banned", | ||||
| 			"additionalProperties":{ | ||||
| 				"type":"number" | ||||
| 			} | ||||
| 		}, | ||||
| 		"tavern": { | ||||
| 			"type":"object", | ||||
| 			"description": "Chance for this hero to appear in tavern of this factions. Reversed version of field \"tavern\" from town format", | ||||
| 			"additionalProperties":{ | ||||
| 				"type":"number" | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										78
									
								
								config/schemas/mod.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								config/schemas/mod.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI mod file format", | ||||
| 	"description" : "Format used to define main mod file (mod.json) in VCMI", | ||||
| 	"required" : [ "name", "description" ], | ||||
| 	"properties":{ | ||||
|  | ||||
| 		"name": { | ||||
| 			"type":"string", | ||||
| 			"description": "Short name of your mod. No more than 2-3 words" | ||||
| 		}, | ||||
| 		"description": { | ||||
| 			"type":"string", | ||||
| 			"description": "More lengthy description of mod. No hard limit" | ||||
| 		}, | ||||
|  | ||||
| 		"depends": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of mods that are required to run this one", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"conflicts": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of mods that can't be enabled in the same time as this one", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
|  | ||||
| 		"artifacts": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of configuration files for artifacts", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"creatures": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of configuration files for creatures", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"factions": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of configuration files for towns/factions", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"heroClasses": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of configuration files for hero classes", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
| 		"heroes": { | ||||
| 			"type":"array", | ||||
| 			"description": "List of configuration files for heroes", | ||||
| 			"items": { "type":"string" } | ||||
| 		}, | ||||
|  | ||||
| 		"filesystem": { | ||||
| 			"type":"object", | ||||
| 			"description": "Optional, description on how files are organized in your mod. In most cases you do not need to use this field", | ||||
| 			"additionalProperties":{ | ||||
| 				"type":"array", | ||||
| 				"description" : "list of data sources attached to this mount point", | ||||
| 				"items": { | ||||
| 					"type":"object", | ||||
| 					"properties":{ | ||||
| 						"path": { | ||||
| 							"type":"string", | ||||
| 							"description": "Path to data source" | ||||
| 						}, | ||||
| 						"type": { | ||||
| 							"type" : "string", | ||||
| 							"enum" : [ "dir", "lod", "snd", "vid" ], | ||||
| 							"description" : "Type of data source" | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										133
									
								
								config/schemas/settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								config/schemas/settings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| // This is schema for checking game settings from settings.json | ||||
| // Any new settings should be added in this file for correct serialization and initialization | ||||
| { | ||||
| 	"type" : "object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"required" : [ "general", "video", "adventure", "battle", "server" ], | ||||
| 	"properties": | ||||
| 	{ | ||||
| 		"general" : { | ||||
| 			"type" : "object", | ||||
| 			"required" : [ "classicCreatureWindow", "playerName", "showfps", "music", "sound" ], | ||||
| 			"properties" : { | ||||
| 				"classicCreatureWindow" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : false | ||||
| 				}, | ||||
| 				"playerName" : { | ||||
| 					"type":"string", | ||||
| 					"default" : "Player" | ||||
| 				}, | ||||
| 				"showfps" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : false | ||||
| 				}, | ||||
| 				"music" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 88 | ||||
| 				}, | ||||
| 				"sound" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 88 | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"video" : { | ||||
| 			"type" : "object", | ||||
| 			"required" : [ "screenRes", "bitsPerPixel", "fullscreen" ], | ||||
| 			"properties" : { | ||||
| 				"screenRes" : { | ||||
| 					"type" : "object", | ||||
| 					"required" : [ "width", "height" ], | ||||
| 					"properties" : { | ||||
| 						"width"  : { "type" : "number" }, | ||||
| 						"height" : { "type" : "number" } | ||||
| 					}, | ||||
| 					"default": {"width" : 800, "height": 600 } | ||||
| 				}, | ||||
| 				"bitsPerPixel" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 24 | ||||
| 				}, | ||||
| 				"fullscreen" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : false | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"adventure" : { | ||||
| 			"type" : "object", | ||||
| 			"required" : [ "heroSpeed", "enemySpeed", "scrollSpeed", "heroReminder" ], | ||||
| 			"properties" : { | ||||
| 				"heroSpeed" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 2 | ||||
| 				}, | ||||
| 				"enemySpeed" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 2 | ||||
| 				}, | ||||
| 				"scrollSpeed" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 1 | ||||
| 				}, | ||||
| 				"heroReminder" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : true | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"battle" : { | ||||
| 			"type" : "object", | ||||
| 			"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue" ], | ||||
| 			"properties" : { | ||||
| 				"animationSpeed" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 2 | ||||
| 				}, | ||||
| 				"mouseShadow" : { | ||||
| 					"type":"boolean", | ||||
| 					"default" : true | ||||
| 				}, | ||||
| 				"cellBorders" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : false | ||||
| 				}, | ||||
| 				"stackRange" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : true | ||||
| 				}, | ||||
| 				"showQueue" : { | ||||
| 					"type" : "boolean", | ||||
| 					"default" : true | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"server" : { | ||||
| 			"type" : "object", | ||||
| 			"required" : [ "server", "port", "localInformation", "playerAI", "neutralAI" ], | ||||
| 			"properties" : { | ||||
| 				"server" : { | ||||
| 					"type":"string", | ||||
| 					"default" : "127.0.0.1" | ||||
| 				}, | ||||
| 				"port" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 3030 | ||||
| 				}, | ||||
| 				"localInformation" : { | ||||
| 					"type" : "number", | ||||
| 					"default" : 2 | ||||
| 				}, | ||||
| 				"playerAI" : { | ||||
| 					"type" : "string", | ||||
| 					"default" : "VCAI" | ||||
| 				}, | ||||
| 				"neutralAI" : { | ||||
| 					"type" : "string", | ||||
| 					"default" : "StupidAI" | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										50
									
								
								config/schemas/townBuilding.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								config/schemas/townBuilding.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI town building format", | ||||
| 	"description" : "Format used to define town buildings in VCMI", | ||||
| 	"required": [ "id" ], | ||||
| 	"properties":{ | ||||
| 		"id": { | ||||
| 			"type":"number", | ||||
| 			"description" : "Numeric identifier of this building" | ||||
| 		}, | ||||
| 		"mode": { | ||||
| 			"type":"string", | ||||
| 			"enum" : [ "normal", "auto", "special", "grail" ], | ||||
| 			"description" : "Mode in which this building will be built" | ||||
| 		}, | ||||
| 		"requires": { | ||||
| 			"type":"array", | ||||
| 			"description" : "List of town buildings that must be built before this one", | ||||
| 			"items": { | ||||
| 				"type":"number" | ||||
| 			} | ||||
| 		}, | ||||
| 		"upgrades": { | ||||
| 			"description" : "If this building is upgrade, identifier of base building", | ||||
| 			"type":"number" | ||||
| 		}, | ||||
| 		"name" : { | ||||
| 			"description" : "Name of this building", | ||||
| 			"type" : "string" | ||||
| 		}, | ||||
| 		"description" : { | ||||
| 			"description" : "Full decsription of this building", | ||||
| 			"type" : "string" | ||||
| 		}, | ||||
| 		"cost": { | ||||
| 			"type":"object", | ||||
| 			"description": "Cost to build this building", | ||||
| 			"properties":{ | ||||
| 				"wood":    { "type":"number"}, | ||||
| 				"mercury": { "type":"number"}, | ||||
| 				"ore":     { "type":"number"}, | ||||
| 				"sulfur":  { "type":"number"}, | ||||
| 				"crystal": { "type":"number"}, | ||||
| 				"gems":    { "type":"number"}, | ||||
| 				"gold":    { "type":"number"} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										349
									
								
								config/schemas/townSiege.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								config/schemas/townSiege.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,349 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI town building format", | ||||
| 	"description" : "Format used to define town buildings in VCMI", | ||||
| 	"id": "#", | ||||
| 	"properties":{ | ||||
| 		"gate": { | ||||
| 			"type":"object", | ||||
| 			"id": "gate", | ||||
| 			"properties":{ | ||||
| 				"arch": { | ||||
| 					"type":"object", | ||||
| 					"id": "arch", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"gate": { | ||||
| 					"type":"object", | ||||
| 					"id": "gate", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"imagePrefix": { | ||||
| 			"type":"string", | ||||
| 			"id": "imagePrefix" | ||||
| 		}, | ||||
| 		"moat": { | ||||
| 			"type":"object", | ||||
| 			"id": "moat", | ||||
| 			"properties":{ | ||||
| 				"bank": { | ||||
| 					"type":"object", | ||||
| 					"id": "bank", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"moat": { | ||||
| 					"type":"object", | ||||
| 					"id": "moat", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"shooterHeight": { | ||||
| 			"type":"number", | ||||
| 			"id": "shooterHeight" | ||||
| 		}, | ||||
| 		"shooter": { | ||||
| 			"type":"string", | ||||
| 			"id": "shooter" | ||||
| 		}, | ||||
| 		"static": { | ||||
| 			"type":"object", | ||||
| 			"id": "static", | ||||
| 			"properties":{ | ||||
| 				"background": { | ||||
| 					"type":"object", | ||||
| 					"id": "background", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"bottom": { | ||||
| 					"type":"object", | ||||
| 					"id": "bottom", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"top": { | ||||
| 					"type":"object", | ||||
| 					"id": "top", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"towers": { | ||||
| 			"type":"object", | ||||
| 			"id": "towers", | ||||
| 			"properties":{ | ||||
| 				"bottom": { | ||||
| 					"type":"object", | ||||
| 					"id": "bottom", | ||||
| 					"properties":{ | ||||
| 						"battlement": { | ||||
| 							"type":"object", | ||||
| 							"id": "battlement", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"creature": { | ||||
| 							"type":"object", | ||||
| 							"id": "creature", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"tower": { | ||||
| 							"type":"object", | ||||
| 							"id": "tower", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"keep": { | ||||
| 					"type":"object", | ||||
| 					"id": "keep", | ||||
| 					"properties":{ | ||||
| 						"battlement": { | ||||
| 							"type":"object", | ||||
| 							"id": "battlement", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"creature": { | ||||
| 							"type":"object", | ||||
| 							"id": "creature", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"tower": { | ||||
| 							"type":"object", | ||||
| 							"id": "tower", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"top": { | ||||
| 					"type":"object", | ||||
| 					"id": "top", | ||||
| 					"properties":{ | ||||
| 						"battlement": { | ||||
| 							"type":"object", | ||||
| 							"id": "battlement", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"creature": { | ||||
| 							"type":"object", | ||||
| 							"id": "creature", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						"tower": { | ||||
| 							"type":"object", | ||||
| 							"id": "tower", | ||||
| 							"properties":{ | ||||
| 								"x": { | ||||
| 									"type":"number", | ||||
| 									"id": "x" | ||||
| 								}, | ||||
| 								"y": { | ||||
| 									"type":"number", | ||||
| 									"id": "y" | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"walls": { | ||||
| 			"type":"object", | ||||
| 			"id": "walls", | ||||
| 			"properties":{ | ||||
| 				"bottomMid": { | ||||
| 					"type":"object", | ||||
| 					"id": "bottomMid", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"bottom": { | ||||
| 					"type":"object", | ||||
| 					"id": "bottom", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"upperMid": { | ||||
| 					"type":"object", | ||||
| 					"id": "upperMid", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				}, | ||||
| 				"upper": { | ||||
| 					"type":"object", | ||||
| 					"id": "upper", | ||||
| 					"properties":{ | ||||
| 						"x": { | ||||
| 							"type":"number", | ||||
| 							"id": "x" | ||||
| 						}, | ||||
| 						"y": { | ||||
| 							"type":"number", | ||||
| 							"id": "y" | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										45
									
								
								config/schemas/townStructure.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								config/schemas/townStructure.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| { | ||||
| 	"type":"object", | ||||
| 	"$schema": "http://json-schema.org/draft-04/schema", | ||||
| 	"title" : "VCMI town structures format", | ||||
| 	"description" : "Format used to define structures visible on town screen in VCMI", | ||||
| 	"required": [ "animation", "x", "y"], | ||||
| 	"properties":{ | ||||
| 		"animation": { | ||||
| 			"type":"string", | ||||
| 			"description" : "Main animation file for this building" | ||||
| 		}, | ||||
| 		"area": { | ||||
| 			"type":"string", | ||||
| 			"description" : "Area that indicate when building is selected. Must be 8-bit image" | ||||
| 		}, | ||||
| 		"border": { | ||||
| 			"type":"string", | ||||
| 			"description" : "Golden border around building, displayed when building is selected" | ||||
| 		}, | ||||
| 		"builds": { | ||||
| 			"type":"number", | ||||
| 			"description" : "" | ||||
| 		}, | ||||
| 		"hidden": { | ||||
| 			"type":"boolean", | ||||
| 			"description" : "If upgrade, this building will replace parent animation but will not alter its behaviour" | ||||
| 		}, | ||||
| 		"id": { | ||||
| 			"type":"number", | ||||
| 			"description" : "Numeric identifier of building" | ||||
| 		}, | ||||
| 		"x": { | ||||
| 			"type":"number", | ||||
| 			"description" : "Position on screen" | ||||
| 		}, | ||||
| 		"y": { | ||||
| 			"type":"number", | ||||
| 			"description" : "Position on screen" | ||||
| 		}, | ||||
| 		"z": { | ||||
| 			"type":"number", | ||||
| 			"description" : "Position on screen. Buildings with higher value will be drawn on top of other buildings" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -250,7 +250,9 @@ CArtifact * CArtHandler::loadArtifact(const JsonNode & node) | ||||
| 	art->iconIndex = graphics["iconIndex"].Float(); | ||||
| 	art->image = graphics["image"].String(); | ||||
|  | ||||
| 	if (!graphics["large"].loadTo(art->large)) | ||||
| 	if (!graphics["large"].isNull()) | ||||
| 		art->large = graphics["large"].String(); | ||||
| 	else | ||||
| 		art->large = art->image; | ||||
|  | ||||
| 	art->advMapDef = graphics["map"].String(); | ||||
|   | ||||
| @@ -59,8 +59,9 @@ void SettingsStorage::init() | ||||
| { | ||||
| 	CResourceHandler::get()->createResource("config/settings.json"); | ||||
| 	JsonNode(ResourceID("config/settings.json")).swap(config); | ||||
| 	JsonNode schema(ResourceID("config/defaultSettings.json")); | ||||
| 	JsonUtils::validate(config, schema); | ||||
|  | ||||
| 	JsonUtils::maximize(config, "vcmi:settings"); | ||||
| 	JsonUtils::validate(config, "vcmi:settings", "settings"); | ||||
| } | ||||
|  | ||||
| void SettingsStorage::invalidateNode(const std::vector<std::string> &changedPath) | ||||
| @@ -69,10 +70,10 @@ void SettingsStorage::invalidateNode(const std::vector<std::string> &changedPath | ||||
| 		listener->nodeInvalidated(changedPath); | ||||
|  | ||||
| 	JsonNode savedConf = config; | ||||
| 	JsonNode schema(ResourceID("config/defaultSettings.json")); | ||||
| 	JsonNode schema(ResourceID("config/schemas/settings.json")); | ||||
|  | ||||
| 	savedConf.Struct().erase("session"); | ||||
| 	JsonUtils::minimize(savedConf, schema); | ||||
| 	JsonUtils::minimize(savedConf, "vcmi:settings"); | ||||
|  | ||||
| 	std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/settings.json")), std::ofstream::trunc); | ||||
| 	file << savedConf; | ||||
|   | ||||
| @@ -16,10 +16,10 @@ | ||||
| #ifndef _WIN32 | ||||
| 	typedef std::string TColor; | ||||
| 	#define CONSOLE_GREEN "\x1b[1;32m" | ||||
| 	#define CONSOLE_RED "\x1b[1;32m" | ||||
| 	#define CONSOLE_RED "\x1b[1;31m" | ||||
| 	#define CONSOLE_MAGENTA "\x1b[1;35m" | ||||
| 	#define CONSOLE_YELLOW "\x1b[1;32m" | ||||
| 	#define CONSOLE_WHITE "\x1b[1;39m" | ||||
| 	#define CONSOLE_YELLOW "\x1b[1;33m" | ||||
| 	#define CONSOLE_WHITE "\x1b[1;37m" | ||||
| 	#define CONSOLE_GRAY "\x1b[1;30m" | ||||
| 	#define CONSOLE_TEAL "\x1b[1;36m" | ||||
| #else | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
| void CIdentifierStorage::checkIdentifier(std::string & ID) | ||||
| { | ||||
| 	if (boost::algorithm::ends_with(ID, ".")) | ||||
| 		tlog0 << "BIG WARNING: identifier " << ID << " seems to be broken!\n"; | ||||
| 		tlog3 << "BIG WARNING: identifier " << ID << " seems to be broken!\n"; | ||||
| 	else | ||||
| 	{ | ||||
| 		size_t pos = 0; | ||||
| @@ -33,7 +33,7 @@ void CIdentifierStorage::checkIdentifier(std::string & ID) | ||||
| 		{ | ||||
| 			if (std::tolower(ID[pos]) != ID[pos] ) //Not in camelCase | ||||
| 			{ | ||||
| 				tlog0 << "Warning: identifier " << ID << " is not in camelCase!\n"; | ||||
| 				tlog3 << "Warning: identifier " << ID << " is not in camelCase!\n"; | ||||
| 				ID[pos] = std::tolower(ID[pos]);// Try to fix the ID | ||||
| 			} | ||||
| 			pos = ID.find('.', pos); | ||||
| @@ -53,7 +53,7 @@ void CIdentifierStorage::requestIdentifier(std::string name, const boost::functi | ||||
| 	else | ||||
| 	{ | ||||
| 		if(boost::algorithm::starts_with(name, "primSkill.")) | ||||
| 			tlog2 << "incorrect primSkill name requested\n"; | ||||
| 			tlog3 << "incorrect primSkill name requested\n"; | ||||
|  | ||||
| 		missingObjects[name].push_back(callback); // queue callback | ||||
| 	} | ||||
| @@ -168,7 +168,7 @@ bool CModHandler::checkDependencies(const std::vector <TModID> & input) const | ||||
| 		{ | ||||
| 			if (!vstd::contains(input, dep)) | ||||
| 			{ | ||||
| 				tlog0 << "Error: Mod " << mod.name << " requires missing " << dep << "!\n"; | ||||
| 				tlog1 << "Error: Mod " << mod.name << " requires missing " << dep << "!\n"; | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
| @@ -177,7 +177,7 @@ bool CModHandler::checkDependencies(const std::vector <TModID> & input) const | ||||
| 		{ | ||||
| 			if (vstd::contains(input, conflicting)) | ||||
| 			{ | ||||
| 				tlog0 << "Error: Mod " << mod.name << " conflicts with " << allMods.at(conflicting).name << "!\n"; | ||||
| 				tlog1 << "Error: Mod " << mod.name << " conflicts with " << allMods.at(conflicting).name << "!\n"; | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
| @@ -269,7 +269,7 @@ void CModHandler::initialize(std::vector<std::string> availableMods) | ||||
|  | ||||
| 	if (!checkDependencies(detectedMods)) | ||||
| 	{ | ||||
| 		tlog0 << "Critical error: failed to load mods! Exiting...\n"; | ||||
| 		tlog1 << "Critical error: failed to load mods! Exiting...\n"; | ||||
| 		exit(1); | ||||
| 	} | ||||
|  | ||||
| @@ -282,21 +282,21 @@ void CModHandler::initialize(std::vector<std::string> availableMods) | ||||
| 	file << modConfig; | ||||
| } | ||||
|  | ||||
|  | ||||
| std::vector<std::string> CModHandler::getActiveMods() | ||||
| { | ||||
| 	return activeMods; | ||||
| } | ||||
|  | ||||
| template<typename Handler> | ||||
| void handleData(Handler handler, const JsonNode & sourceList) | ||||
| void CModHandler::handleData(Handler handler, const JsonNode & source, std::string listName, std::string schemaName) | ||||
| { | ||||
| 	JsonNode config = JsonUtils::assembleFromFiles(sourceList.convertTo<std::vector<std::string> >()); | ||||
| 	JsonNode config = JsonUtils::assembleFromFiles(source[listName].convertTo<std::vector<std::string> >()); | ||||
|  | ||||
| 	BOOST_FOREACH(auto & entry, config.Struct()) | ||||
| 	{ | ||||
| 		if (!entry.second.isNull()) // may happens if mod removed object by setting json entry to null | ||||
| 		{ | ||||
| 			JsonUtils::validate(entry.second, schemaName, entry.first); | ||||
| 			handler->load(entry.first, entry.second); | ||||
| 		} | ||||
| 	} | ||||
| @@ -306,20 +306,21 @@ void CModHandler::loadActiveMods() | ||||
| { | ||||
| 	BOOST_FOREACH(const TModID & modName, activeMods) | ||||
| 	{ | ||||
| 		tlog1 << "\t\tLoading mod "; | ||||
| 		tlog0 << "\t\tLoading mod "; | ||||
| 		tlog2 << allMods[modName].name << "\n"; | ||||
|  | ||||
| 		std::string modFileName = "mods/" + modName + "/mod.json"; | ||||
|  | ||||
| 		const JsonNode config = JsonNode(ResourceID(modFileName)); | ||||
| 		JsonUtils::validate(config, "vcmi:mod", modName); | ||||
|  | ||||
| 		handleData(VLC->townh, config["factions"]); | ||||
| 		handleData(VLC->creh, config["creatures"]); | ||||
| 		handleData(VLC->arth, config["artifacts"]); | ||||
| 		handleData(VLC->townh, config, "factions", "vcmi:faction"); | ||||
| 		handleData(VLC->creh, config, "creatures", "vcmi:creature"); | ||||
| 		handleData(VLC->arth, config, "artifacts", "vcmi:artifact"); | ||||
| 		//todo: spells | ||||
|  | ||||
| 		handleData(&VLC->heroh->classes, config["heroClasses"]); | ||||
| 		handleData(VLC->heroh, config["heroes"]); | ||||
| 		handleData(&VLC->heroh->classes, config,"heroClasses", "vcmi:heroClass"); | ||||
| 		handleData(VLC->heroh, config, "heroes", "vcmi:hero"); | ||||
| 	} | ||||
|  | ||||
| 	VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded | ||||
|   | ||||
| @@ -75,7 +75,7 @@ class DLL_LINKAGE CModHandler | ||||
|  | ||||
| 	bool hasCircularDependency(TModID mod, std::set <TModID> currentList = std::set <TModID>()) const; | ||||
|  | ||||
| 	//returns false if mod list is incorrec and prints error to console. Possible errors are: | ||||
| 	//returns false if mod list is incorrect and prints error to console. Possible errors are: | ||||
| 	// - missing dependency mod | ||||
| 	// - conflicting mod in load order | ||||
| 	// - circular dependencies | ||||
| @@ -84,7 +84,12 @@ class DLL_LINKAGE CModHandler | ||||
| 	// returns load order in which all dependencies are resolved, e.g. loaded after required mods | ||||
| 	// function assumes that input list is valid (checkDependencies returned true) | ||||
| 	std::vector <TModID> resolveDependencies(std::vector<TModID> input) const; | ||||
|  | ||||
| 	// helper for loadActiveMods. Loads content from list of files | ||||
| 	template<typename Handler> | ||||
| 	void handleData(Handler handler, const JsonNode & source, std::string listName, std::string schemaName); | ||||
| public: | ||||
|  | ||||
| 	CIdentifierStorage identifiers; | ||||
|  | ||||
| 	/// receives list of available mods and trying to load mod.json from all of them | ||||
|   | ||||
| @@ -2299,6 +2299,8 @@ int CGTownInstance::getBoatType() const | ||||
| 	case EAlignment::GOOD : return 1; | ||||
| 	case EAlignment::NEUTRAL : return 2; | ||||
| 	} | ||||
| 	assert(0); | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| int CGTownInstance::getMarketEfficiency() const | ||||
|   | ||||
| @@ -420,7 +420,8 @@ std::vector<std::string> CResourceHandler::getAvailableMods() | ||||
|  | ||||
| 		name.erase(0, name.find_last_of('/') + 1);        //Remove path prefix | ||||
|  | ||||
| 		foundMods.push_back(name); | ||||
| 		if (!name.empty()) // this is also triggered for "ALL/MODS/" entry | ||||
| 			foundMods.push_back(name); | ||||
| 		++iterator; | ||||
| 	} | ||||
| 	return foundMods; | ||||
|   | ||||
							
								
								
									
										616
									
								
								lib/JsonNode.cpp
									
									
									
									
									
								
							
							
						
						
									
										616
									
								
								lib/JsonNode.cpp
									
									
									
									
									
								
							| @@ -11,6 +11,8 @@ | ||||
| #include "StdInc.h" | ||||
| #include "JsonNode.h" | ||||
|  | ||||
| #include "ScopeGuard.h" | ||||
|  | ||||
| #include "HeroBonus.h" | ||||
| #include "Filesystem/CResourceLoader.h" | ||||
| #include "VCMI_Lib.h" //for identifier resolution | ||||
| @@ -34,8 +36,6 @@ JsonNode::JsonNode(const char *data, size_t datasize): | ||||
| { | ||||
| 	JsonParser parser(data, datasize); | ||||
| 	*this = parser.parse("<unknown>"); | ||||
|  | ||||
| 	JsonValidator validator(*this); | ||||
| } | ||||
|  | ||||
| JsonNode::JsonNode(ResourceID && fileURI): | ||||
| @@ -45,8 +45,6 @@ JsonNode::JsonNode(ResourceID && fileURI): | ||||
|  | ||||
| 	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second); | ||||
| 	*this = parser.parse(fileURI.getName()); | ||||
|  | ||||
| 	JsonValidator validator(*this); | ||||
| } | ||||
|  | ||||
| JsonNode::JsonNode(const JsonNode ©): | ||||
| @@ -144,6 +142,11 @@ bool JsonNode::isNull() const | ||||
| 	return type == DATA_NULL; | ||||
| } | ||||
|  | ||||
| void JsonNode::clear() | ||||
| { | ||||
| 	setType(DATA_NULL); | ||||
| } | ||||
|  | ||||
| bool & JsonNode::Bool() | ||||
| { | ||||
| 	setType(DATA_BOOL); | ||||
| @@ -231,6 +234,46 @@ const JsonNode & JsonNode::operator[](std::string child) const | ||||
| 		return it->second; | ||||
| 	return nullNode; | ||||
| } | ||||
|  | ||||
| // to avoid duplicating const and non-const code | ||||
| template<typename Node> | ||||
| Node & resolvePointer(Node & in, const std::string & pointer) | ||||
| { | ||||
| 	if (pointer.empty()) | ||||
| 		return in; | ||||
| 	assert(pointer[0] == '/'); | ||||
|  | ||||
| 	size_t splitPos = pointer.find('/', 1); | ||||
|  | ||||
| 	std::string entry   =   pointer.substr(1, splitPos -1); | ||||
| 	std::string remainer =  splitPos == std::string::npos ? "" : pointer.substr(splitPos); | ||||
|  | ||||
| 	if (in.getType() == JsonNode::DATA_VECTOR) | ||||
| 	{ | ||||
| 		if (entry.find_first_not_of("0123456789") != std::string::npos) // non-numbers in string | ||||
| 			throw std::runtime_error("Invalid Json pointer"); | ||||
|  | ||||
| 		if (entry.size() > 1 && entry[0] == '0') // leading zeros are not allowed | ||||
| 			throw std::runtime_error("Invalid Json pointer"); | ||||
|  | ||||
| 		size_t index = boost::lexical_cast<size_t>(entry); | ||||
|  | ||||
| 		if (in.Vector().size() > index) | ||||
| 			return in.Vector()[index].resolvePointer(remainer); | ||||
| 	} | ||||
| 	return in[entry].resolvePointer(remainer); | ||||
| } | ||||
|  | ||||
| const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const | ||||
| { | ||||
| 	return ::resolvePointer(*this, jsonPointer); | ||||
| } | ||||
|  | ||||
| JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) | ||||
| { | ||||
| 	return ::resolvePointer(*this, jsonPointer); | ||||
| } | ||||
|  | ||||
| //////////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| template<typename Iterator> | ||||
| @@ -516,7 +559,7 @@ bool JsonParser::extractNull(JsonNode &node) | ||||
| 	if (!extractLiteral("null")) | ||||
| 		return false; | ||||
|  | ||||
| 	node.setType(JsonNode::DATA_NULL); | ||||
| 	node.clear(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -696,166 +739,370 @@ bool JsonParser::error(const std::string &message, bool warning) | ||||
|  | ||||
| static const std::map<std::string, JsonNode::JsonType> stringToType = | ||||
| 	boost::assign::map_list_of | ||||
| 		("null",   JsonNode::DATA_NULL)   ("bool",   JsonNode::DATA_BOOL) | ||||
| 		("number", JsonNode::DATA_FLOAT)  ("string", JsonNode::DATA_STRING) | ||||
| 		("array",  JsonNode::DATA_VECTOR) ("object", JsonNode::DATA_STRUCT); | ||||
| 		("null",   JsonNode::DATA_NULL)   ("boolean", JsonNode::DATA_BOOL) | ||||
| 		("number", JsonNode::DATA_FLOAT)  ("string",  JsonNode::DATA_STRING) | ||||
| 		("array",  JsonNode::DATA_VECTOR) ("object",  JsonNode::DATA_STRUCT); | ||||
|  | ||||
| //Check current schema entry for validness and converts "type" string to JsonType | ||||
| bool JsonValidator::validateSchema(JsonNode::JsonType &type, const JsonNode &schema) | ||||
| std::string JsonValidator::validateEnum(const JsonNode &node, const JsonVector &enumeration) | ||||
| { | ||||
| 	if (schema.isNull()) | ||||
| 		return addMessage("Missing schema for current entry!"); | ||||
|  | ||||
| 	const JsonNode &nodeType = schema["type"]; | ||||
| 	if (nodeType.isNull()) | ||||
| 		return addMessage("Entry type is not defined in schema!"); | ||||
|  | ||||
| 	if (nodeType.getType() != JsonNode::DATA_STRING) | ||||
| 		return addMessage("Entry type must be string!"); | ||||
|  | ||||
| 	std::map<std::string, JsonNode::JsonType>::const_iterator iter = stringToType.find(nodeType.String()); | ||||
|  | ||||
| 	if (iter == stringToType.end()) | ||||
| 		return addMessage("Unknown entry type found!"); | ||||
|  | ||||
| 	type = iter->second; | ||||
| 	return true; | ||||
| 	BOOST_FOREACH(auto & enumEntry, enumeration) | ||||
| 	{ | ||||
| 		if (node == enumEntry) | ||||
| 			return ""; | ||||
| 	} | ||||
| 	return fail("Key must have one of predefined values"); | ||||
| } | ||||
|  | ||||
| //Replaces node with default value if needed and calls type-specific validators | ||||
| bool JsonValidator::validateType(JsonNode &node, const JsonNode &schema, JsonNode::JsonType type) | ||||
| std::string JsonValidator::validatesSchemaList(const JsonNode &node, const JsonNode &schemas, std::string errorMsg, std::function<bool(size_t)> isValid) | ||||
| { | ||||
| 	if (node.isNull()) | ||||
| 	std::string errors; | ||||
| 	if (!schemas.isNull()) | ||||
| 	{ | ||||
| 		const JsonNode & defaultValue = schema["default"]; | ||||
| 		if (defaultValue.isNull()) | ||||
| 			return addMessage("Null entry without default entry!"); | ||||
| 		else | ||||
| 			node = defaultValue; | ||||
| 		size_t result = 0; | ||||
|  | ||||
| 		BOOST_FOREACH(auto & schema, schemas.Vector()) | ||||
| 		{ | ||||
| 			std::string error = validateNode(node, schema); | ||||
| 			if (error.empty()) | ||||
| 				result++; | ||||
| 			else | ||||
| 				errors += fail(error); | ||||
| 		} | ||||
| 		if (isValid(result)) | ||||
| 		{ | ||||
| 			return ""; | ||||
| 		} | ||||
| 		return fail(errorMsg) + errors; | ||||
| 	} | ||||
| 	if (minimize && node == schema["default"]) | ||||
| 	return ""; | ||||
| } | ||||
|  | ||||
| std::string JsonValidator::validateNodeType(const JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	std::string errors; | ||||
|  | ||||
| 	// data must be valid against all schemas in the list | ||||
| 	errors += validatesSchemaList(node, schema["allOf"], "Failed to pass all schemas", [&](size_t count) | ||||
| 	{ | ||||
| 		node.setType(JsonNode::DATA_NULL); | ||||
| 		return false; | ||||
| 	} | ||||
| 		return count == schema["allOf"].Vector().size(); | ||||
| 	}); | ||||
|  | ||||
| 	if (type != node.getType()) | ||||
| 	// data must be valid against any non-zero number of schemas in the list | ||||
| 	errors += validatesSchemaList(node, schema["anyOf"], "Failed to pass any schema", [&](size_t count) | ||||
| 	{ | ||||
| 		node.setType(JsonNode::DATA_NULL); | ||||
| 		return addMessage("Type mismatch!"); | ||||
| 		return count > 0; | ||||
| 	}); | ||||
|  | ||||
| 	// data must be valid against one and only one schema | ||||
| 	errors += validatesSchemaList(node, schema["oneOf"], "Failed to pass only one and only one schema", [&](size_t count) | ||||
| 	{ | ||||
| 		return count == 1; | ||||
| 	}); | ||||
|  | ||||
| 	// data must NOT be valid against schema | ||||
| 	if (!schema["not"].isNull()) | ||||
| 	{ | ||||
| 		if (validateNode(node, schema["not"]).empty()) | ||||
| 			errors += fail("Successful validation against negative check"); | ||||
| 	} | ||||
|  | ||||
| 	if (type == JsonNode::DATA_VECTOR) | ||||
| 		return validateItems(node, schema["items"]); | ||||
|  | ||||
| 	if (type == JsonNode::DATA_STRUCT) | ||||
| 		return validateProperties(node, schema["properties"]); | ||||
|  | ||||
| 	return true; | ||||
| 	// basic schema check | ||||
| 	if (!schema["type"].isNull()) | ||||
| 	{ | ||||
| 		JsonNode::JsonType type = stringToType.find(schema["type"].String())->second; | ||||
| 		if(type != node.getType()) | ||||
| 			errors += fail("Type mismatch!"); | ||||
| 	} | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| // Basic checks common for any nodes | ||||
| bool JsonValidator::validateNode(JsonNode &node, const JsonNode &schema, const std::string &name) | ||||
| std::string JsonValidator::validateNode(const JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	currentPath.push_back(name); | ||||
| 	std::string errors; | ||||
|  | ||||
| 	JsonNode::JsonType type = JsonNode::DATA_NULL; | ||||
| 	if (!validateSchema(type, schema) | ||||
| 	 || !validateType(node, schema, type)) | ||||
| 	assert(!schema.isNull()); // can this error be triggered? | ||||
|  | ||||
| 	if (!schema["$ref"].isNull()) | ||||
| 	{ | ||||
| 		std::string URI = schema["$ref"].String(); | ||||
| 		//node must be validated using schema pointed by this reference and not by data here | ||||
| 		//Local reference. Turn it into more easy to handle remote ref | ||||
| 		if (boost::algorithm::starts_with(URI, "#")) | ||||
| 			URI = usedSchemas.back() + URI; | ||||
|  | ||||
| 		return validateRoot(node, URI); | ||||
| 	} | ||||
|  | ||||
| 	errors += validateNodeType(node, schema); | ||||
|  | ||||
| 	// enumeration - data must be equeal to one of items in list | ||||
| 	if (!schema["enum"].isNull()) | ||||
| 		errors += validateEnum(node, schema["enum"].Vector()); | ||||
|  | ||||
| 	// try to run any type-specific checks | ||||
| 	if (node.getType() == JsonNode::DATA_VECTOR) errors += validateVector(node, schema); | ||||
| 	if (node.getType() == JsonNode::DATA_STRUCT) errors += validateStruct(node, schema); | ||||
| 	if (node.getType() == JsonNode::DATA_STRING) errors += validateString(node, schema); | ||||
| 	if (node.getType() == JsonNode::DATA_FLOAT)  errors += validateNumber(node, schema); | ||||
|  | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| std::string JsonValidator::validateVectorItem(const JsonVector items, const JsonNode & schema, const JsonNode & additional, size_t index) | ||||
| { | ||||
| 	currentPath.push_back(JsonNode()); | ||||
| 	currentPath.back().Float() = index; | ||||
| 	auto onExit = vstd::makeScopeGuard([&] | ||||
| 	{ | ||||
| 		node.setType(JsonNode::DATA_NULL); | ||||
| 		currentPath.pop_back(); | ||||
| 		return false; | ||||
| 	} | ||||
| 	currentPath.pop_back(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| //Checks "items" entry from schema (type-specific check for Vector) | ||||
| bool JsonValidator::validateItems(JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	JsonNode::JsonType type = JsonNode::DATA_NULL; | ||||
| 	if (!validateSchema(type, schema)) | ||||
| 		return false; | ||||
|  | ||||
| 	bool result = true; | ||||
| 	BOOST_FOREACH(JsonNode &entry, node.Vector()) | ||||
| 	{ | ||||
| 		if (!validateType(entry, schema, type)) | ||||
| 		{ | ||||
| 			result = false; | ||||
| 			entry.setType(JsonNode::DATA_NULL); | ||||
| 		} | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| //Checks "propertries" entry from schema (type-specific check for Struct) | ||||
| bool JsonValidator::validateProperties(JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	if (schema.isNull()) | ||||
| 		return addMessage("Properties entry is missing for struct in schema"); | ||||
|  | ||||
| 	BOOST_FOREACH(auto & schemaEntry, schema.Struct()) | ||||
| 	{ | ||||
| 		if (!validateNode(node[schemaEntry.first], schemaEntry.second, schemaEntry.first)) | ||||
| 			node.Struct().erase(schemaEntry.first); | ||||
| 	} | ||||
|  | ||||
| 	for (auto iter = node.Struct().begin(); iter!= node.Struct().end();) | ||||
| 	{ | ||||
| 		if (!vstd::contains(schema.Struct(), iter->first)) | ||||
| 		{ | ||||
| 			addMessage("Missing schema for entry " + iter->first + "!"); | ||||
| 			iter = node.Struct().erase(iter); | ||||
| 		} | ||||
| 		else | ||||
| 			iter++; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool JsonValidator::addMessage(const std::string &message) | ||||
| { | ||||
| 	std::ostringstream stream; | ||||
|  | ||||
| 	stream << "At "; | ||||
| 	BOOST_FOREACH(const std::string &path, currentPath) | ||||
| 		stream << path<<"/"; | ||||
| 	stream << "\t Error: " << message <<"\n"; | ||||
| 	errors += stream.str(); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| JsonValidator::JsonValidator(JsonNode &root, bool Minimize): | ||||
| 	minimize(Minimize) | ||||
| { | ||||
| 	if (root.getType() != JsonNode::DATA_STRUCT) | ||||
| 		return; | ||||
|  | ||||
| 	JsonNode schema; | ||||
| 	schema.swap(root["schema"]); | ||||
| 	root.Struct().erase("schema"); | ||||
| 	}); | ||||
|  | ||||
| 	if (!schema.isNull()) | ||||
| 	{ | ||||
| 		validateProperties(root, schema); | ||||
| 		// case 1: schema is vector. Validate items agaist corresponding items in vector | ||||
| 		if (schema.getType() == JsonNode::DATA_VECTOR) | ||||
| 		{ | ||||
| 			if (schema.Vector().size() > index) | ||||
| 				return validateNode(items[index], schema.Vector()[index]); | ||||
| 		} | ||||
| 		else // case 2: schema has to be struct. Apply it to all items, completely ignore additionalItems | ||||
| 		{ | ||||
| 			return validateNode(items[index], schema); | ||||
| 		} | ||||
| 	} | ||||
| 	//This message is quite annoying now - most files do not have schemas. May be re-enabled later | ||||
| 	//else | ||||
| 	//	addMessage("Schema not found!", true); | ||||
|  | ||||
| 	//TODO: better way to show errors (like printing file name as well) | ||||
| 	tlog3<<errors; | ||||
| 	// othervice check against schema in additional items field | ||||
| 	if (additional.getType() == JsonNode::DATA_STRUCT) | ||||
| 		return validateNode(items[index], additional); | ||||
|  | ||||
| 	// or, additionalItems field can be bool which indicates if such items are allowed | ||||
| 	// default = false, so case if additionalItems is not present will be handled as well | ||||
| 	if (!additional.Bool()) | ||||
| 		return fail("Unknown entry found"); | ||||
|  | ||||
| 	return ""; | ||||
| } | ||||
|  | ||||
| JsonValidator::JsonValidator(JsonNode &root, const JsonNode &schema, bool Minimize): | ||||
| 	minimize(Minimize) | ||||
| //Checks "items" entry from schema (type-specific check for Vector) | ||||
| std::string JsonValidator::validateVector(const JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	validateProperties(root, schema); | ||||
| 	if (schema.isNull()) | ||||
| 		addMessage("Schema not found!"); | ||||
| 	tlog3<<errors; | ||||
| 	std::string errors; | ||||
| 	auto & vector = node.Vector(); | ||||
|  | ||||
| 	for (size_t i=0; i<vector.size(); i++) | ||||
| 		errors += validateVectorItem(vector, schema["items"], schema["additionalItems"], i); | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "maxItems") && vector.size() > schema["maxItems"].Float()) | ||||
| 		errors += fail("Too many items in the list!"); | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "minItems") && vector.size() < schema["minItems"].Float()) | ||||
| 		errors += fail("Too few items in the list"); | ||||
|  | ||||
| 	if (schema["uniqueItems"].Bool()) | ||||
| 	{ | ||||
| 		for (auto itA = vector.begin(); itA != vector.end(); itA++) | ||||
| 		{ | ||||
| 			auto itB = itA; | ||||
| 			while (++itB != vector.end()) | ||||
| 			{ | ||||
| 				if (*itA == *itB) | ||||
| 					errors += fail("List must consist from unique items"); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| std::string JsonValidator::validateStructItem(const JsonNode &node, const JsonNode & schema, const JsonNode & additional, std::string nodeName) | ||||
| { | ||||
| 	currentPath.push_back(JsonNode()); | ||||
| 	currentPath.back().String() = nodeName; | ||||
| 	auto onExit = vstd::makeScopeGuard([&] | ||||
| 	{ | ||||
| 		currentPath.pop_back(); | ||||
| 	}); | ||||
|  | ||||
| 	// there is schema specifically for this item | ||||
| 	if (!schema[nodeName].isNull()) | ||||
| 		return validateNode(node, schema[nodeName]); | ||||
|  | ||||
| 	// try generic additionalItems schema | ||||
| 	if (additional.getType() == JsonNode::DATA_STRUCT) | ||||
| 		return validateNode(node, additional); | ||||
|  | ||||
| 	if (!additional.Bool()) | ||||
| 		return fail("Unknown entry found: " + nodeName); | ||||
|  | ||||
| 	return ""; | ||||
| } | ||||
|  | ||||
| //Checks "properties" entry from schema (type-specific check for Struct) | ||||
| std::string JsonValidator::validateStruct(const JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	std::string errors; | ||||
| 	auto & map = node.Struct(); | ||||
|  | ||||
| 	BOOST_FOREACH(auto & entry, map) | ||||
| 		errors += validateStructItem(entry.second, schema["properties"], schema["additionalProperties"], entry.first); | ||||
|  | ||||
| 	BOOST_FOREACH(auto & required, schema["required"].Vector()) | ||||
| 	{ | ||||
| 		if (!vstd::contains(map, required.String())) | ||||
| 			errors += fail("Required entry " + required.String() + " is missing"); | ||||
| 	} | ||||
|  | ||||
| 	//Copy-paste from vector code. yay! | ||||
| 	if (vstd::contains(schema.Struct(), "maxProperties") && map.size() > schema["maxProperties"].Float()) | ||||
| 		errors += fail("Too many items in the list!"); | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "minItems") && map.size() < schema["minItems"].Float()) | ||||
| 		errors += fail("Too few items in the list"); | ||||
|  | ||||
| 	if (schema["uniqueItems"].Bool()) | ||||
| 	{ | ||||
| 		for (auto itA = map.begin(); itA != map.end(); itA++) | ||||
| 		{ | ||||
| 			auto itB = itA; | ||||
| 			while (++itB != map.end()) | ||||
| 			{ | ||||
| 				if (itA->second == itB->second) | ||||
| 					errors += fail("List must consist from unique items"); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// dependencies. Format is object/struct where key is the name of key in data | ||||
| 	// and value is either: | ||||
| 	// a) array of fields that must be present | ||||
| 	// b) struct with schema against which data should be valid | ||||
| 	// These checks are triggered only if key is present | ||||
| 	BOOST_FOREACH(auto & deps, schema["dependencies"].Struct()) | ||||
| 	{ | ||||
| 		if (vstd::contains(map, deps.first)) | ||||
| 		{ | ||||
| 			if (deps.second.getType() == JsonNode::DATA_VECTOR) | ||||
| 			{ | ||||
| 				JsonVector depList = deps.second.Vector(); | ||||
| 				BOOST_FOREACH(auto & depEntry, depList) | ||||
| 				{ | ||||
| 					if (!vstd::contains(map, depEntry.String())) | ||||
| 						errors += fail("Property " + depEntry.String() + " required for " + deps.first + " is missing"); | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				if (!validateNode(node, deps.second).empty()) | ||||
| 					errors += fail("Requirements for " + deps.first + " are not fulfilled"); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// TODO: missing fields from draft v4 | ||||
| 	// patternProperties | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| std::string JsonValidator::validateString(const JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	std::string errors; | ||||
| 	auto & string = node.String(); | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "maxLength") && string.size() > schema["maxLength"].Float()) | ||||
| 		errors += fail("String too long"); | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "minLength") && string.size() < schema["minLength"].Float()) | ||||
| 		errors += fail("String too short"); | ||||
|  | ||||
| 	// TODO: missing fields from draft v4 | ||||
| 	// pattern | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| std::string JsonValidator::validateNumber(const JsonNode &node, const JsonNode &schema) | ||||
| { | ||||
| 	std::string errors; | ||||
| 	auto & value = node.Float(); | ||||
| 	if (vstd::contains(schema.Struct(), "maximum")) | ||||
| 	{ | ||||
| 		if (schema["exclusiveMaximum"].Bool()) | ||||
| 		{ | ||||
| 			if (value >= schema["maximum"].Float()) | ||||
| 				errors += fail("Value is too large"); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (value >  schema["maximum"].Float()) | ||||
| 				errors += fail("Value is too large"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "minimum")) | ||||
| 	{ | ||||
| 		if (schema["exclusiveMinimum"].Bool()) | ||||
| 		{ | ||||
| 			if (value <= schema["minimum"].Float()) | ||||
| 				errors += fail("Value is too small"); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (value <  schema["minimum"].Float()) | ||||
| 				errors += fail("Value is too small"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (vstd::contains(schema.Struct(), "multipleOf")) | ||||
| 	{ | ||||
| 		double result = value / schema["multipleOf"].Float(); | ||||
| 		if (floor(result) != result) | ||||
| 			errors += ("Value is not divisible"); | ||||
| 	} | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| //basic schema validation (like checking $schema entry). | ||||
| std::string JsonValidator::validateRoot(const JsonNode &node, std::string schemaName) | ||||
| { | ||||
| 	const JsonNode & schema = JsonUtils::getSchema(schemaName); | ||||
|  | ||||
| 	usedSchemas.push_back(schemaName.substr(0, schemaName.find('#'))); | ||||
| 	auto onExit = vstd::makeScopeGuard([&] | ||||
| 	{ | ||||
| 		usedSchemas.pop_back(); | ||||
| 	}); | ||||
|  | ||||
| 	if (!schema.isNull()) | ||||
| 		return validateNode(node, schema); | ||||
| 	else | ||||
| 		return fail("Schema not found!"); | ||||
| } | ||||
|  | ||||
| std::string JsonValidator::fail(const std::string &message) | ||||
| { | ||||
| 	std::string errors; | ||||
| 	errors += "At "; | ||||
| 	BOOST_FOREACH(const JsonNode &path, currentPath) | ||||
| 	{ | ||||
| 		errors += "/"; | ||||
| 		if (path.getType() == JsonNode::DATA_STRING) | ||||
| 			errors += path.String(); | ||||
| 		else | ||||
| 			errors += boost::lexical_cast<std::string>(static_cast<unsigned>(path.Float())); | ||||
| 	} | ||||
| 	errors += "\n\t Error: " + message + "\n"; | ||||
| 	return errors; | ||||
| } | ||||
|  | ||||
| bool JsonValidator::validate(const JsonNode &root, std::string schemaName, std::string name) | ||||
| { | ||||
| 	std::string errors = validateRoot(root, schemaName); | ||||
|  | ||||
| 	if (!errors.empty()) | ||||
| 	{ | ||||
| 		tlog3 << "Data in " << name << " is invalid!\n"; | ||||
| 		tlog3 << errors; | ||||
| 	} | ||||
|  | ||||
| 	return errors.empty(); | ||||
| } | ||||
|  | ||||
| ///JsonUtils | ||||
| @@ -1113,14 +1360,95 @@ void JsonUtils::unparseBonus( JsonNode &node, const Bonus * bonus ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void JsonUtils::minimize(JsonNode & node, const JsonNode& schema) | ||||
| void minimizeNode(JsonNode & node, const JsonNode & schema) | ||||
| { | ||||
| 	JsonValidator validator(node, schema, true); | ||||
| 	assert(schema["type"].String() == "object"); | ||||
|  | ||||
| 	BOOST_FOREACH(auto & entry, schema["required"].Vector()) | ||||
| 	{ | ||||
| 		std::string name = entry.String(); | ||||
|  | ||||
| 		if (node[name].getType() == JsonNode::DATA_STRUCT) | ||||
| 			minimizeNode(node[name], schema["properties"][name]); | ||||
|  | ||||
| 		if (vstd::contains(node.Struct(), name) && | ||||
| 		    node[name] == schema["properties"][name]["default"]) | ||||
| 		{ | ||||
| 			node.Struct().erase(name); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void JsonUtils::validate(JsonNode & node, const JsonNode& schema) | ||||
| void JsonUtils::minimize(JsonNode & node, std::string schemaName) | ||||
| { | ||||
| 	JsonValidator validator(node, schema, false); | ||||
| 	minimizeNode(node, getSchema(schemaName)); | ||||
| } | ||||
|  | ||||
| void maximizeNode(JsonNode & node, const JsonNode & schema) | ||||
| { | ||||
| 	assert(schema["type"].String() == "object"); | ||||
|  | ||||
| 	BOOST_FOREACH(auto & entry, schema["required"].Vector()) | ||||
| 	{ | ||||
| 		std::string name = entry.String(); | ||||
|  | ||||
| 		if (node[name].isNull() && | ||||
| 		    !schema["properties"][name]["default"].isNull()) | ||||
| 		{ | ||||
| 			node[name] = schema["properties"][name]["default"]; | ||||
| 		} | ||||
| 		if (node[name].getType() == JsonNode::DATA_STRUCT) | ||||
| 			maximizeNode(node[name], schema["properties"][name]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void JsonUtils::maximize(JsonNode & node, std::string schemaName) | ||||
| { | ||||
| 	maximizeNode(node, getSchema(schemaName)); | ||||
| } | ||||
|  | ||||
| bool JsonUtils::validate(const JsonNode &node, std::string schemaName, std::string dataName) | ||||
| { | ||||
| 	JsonValidator validator; | ||||
| 	return validator.validate(node, schemaName, dataName); | ||||
| } | ||||
|  | ||||
| const JsonNode & getSchemaByName(std::string name) | ||||
| { | ||||
| 	// cached schemas to avoid loading json data multiple times | ||||
| 	static std::map<std::string, JsonNode> loadedSchemas; | ||||
|  | ||||
| 	if (vstd::contains(loadedSchemas, name)) | ||||
| 		return loadedSchemas[name]; | ||||
|  | ||||
| 	std::string filename = "config/schemas/" + name + ".json"; | ||||
|  | ||||
| 	if (CResourceHandler::get()->existsResource(ResourceID(filename))) | ||||
| 	{ | ||||
| 		loadedSchemas[name] = JsonNode(ResourceID(filename)); | ||||
| 		return loadedSchemas[name]; | ||||
| 	} | ||||
|  | ||||
| 	tlog0 << "Error: missing schema with name " << name << "!\n"; | ||||
| 	assert(0); | ||||
| 	return nullNode; | ||||
| } | ||||
|  | ||||
| const JsonNode & JsonUtils::getSchema(std::string URI) | ||||
| { | ||||
| 	std::vector<std::string> segments; | ||||
|  | ||||
| 	boost::split(segments, URI, boost::is_any_of(":#")); | ||||
|  | ||||
| 	segments.resize(3);//in case if last section (after #) was not present | ||||
|  | ||||
| 	if (segments[0] != "vcmi") | ||||
| 	{ | ||||
| 		tlog0 << "Error: unsupported URI protocol for schema: " << segments[0] << "\n"; | ||||
| 		return nullNode; | ||||
| 	} | ||||
|  | ||||
| 	return getSchemaByName(segments[1]).resolvePointer(segments[2]); | ||||
| } | ||||
|  | ||||
| void JsonUtils::merge(JsonNode & dest, JsonNode & source) | ||||
| @@ -1133,7 +1461,7 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source) | ||||
|  | ||||
| 	switch (source.getType()) | ||||
| 	{ | ||||
| 		break; case JsonNode::DATA_NULL:   dest.setType(JsonNode::DATA_NULL); | ||||
| 		break; case JsonNode::DATA_NULL:   dest.clear(); | ||||
| 		break; case JsonNode::DATA_BOOL:   std::swap(dest.Bool(), source.Bool()); | ||||
| 		break; case JsonNode::DATA_FLOAT:  std::swap(dest.Float(), source.Float()); | ||||
| 		break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String()); | ||||
|   | ||||
							
								
								
									
										100
									
								
								lib/JsonNode.h
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								lib/JsonNode.h
									
									
									
									
									
								
							| @@ -63,38 +63,38 @@ public: | ||||
| 	bool operator == (const JsonNode &other) const; | ||||
| 	bool operator != (const JsonNode &other) const; | ||||
|  | ||||
| 	//Convert node to another type. Converting to NULL will clear all data | ||||
| 	/// Convert node to another type. Converting to NULL will clear all data | ||||
| 	void setType(JsonType Type); | ||||
| 	JsonType getType() const; | ||||
|  | ||||
| 	bool isNull() const; | ||||
| 	/// removes all data from node and sets type to null | ||||
| 	void clear(); | ||||
|  | ||||
| 	//non-const accessors, node will change type on type mismatch | ||||
| 	/// non-const accessors, node will change type on type mismatch | ||||
| 	bool & Bool(); | ||||
| 	double & Float(); | ||||
| 	std::string & String(); | ||||
| 	JsonVector & Vector(); | ||||
| 	JsonMap & Struct(); | ||||
|  | ||||
| 	//const accessors, will cause assertion failure on type mismatch | ||||
| 	/// const accessors, will cause assertion failure on type mismatch | ||||
| 	const bool & Bool() const; | ||||
| 	const double & Float() const; | ||||
| 	const std::string & String() const; | ||||
| 	const JsonVector & Vector() const; | ||||
| 	const JsonMap & Struct() const; | ||||
|  | ||||
| 	/// returns resolved "json pointer" (string in format "/path/to/node") | ||||
| 	const JsonNode & resolvePointer(const std::string & jsonPointer) const; | ||||
| 	JsonNode & resolvePointer(const std::string & jsonPointer); | ||||
|  | ||||
| 	/// convert json tree into specified type. Json tree must have same type as Type | ||||
| 	/// Valid types: bool, string, any numeric, map and vector | ||||
| 	/// example: convertTo< std::map< std::vector<int> > >(); | ||||
| 	template<typename Type> | ||||
| 	Type convertTo() const; | ||||
|  | ||||
| 	/// Similar to convertTo but will assign data only if node is not null | ||||
| 	/// Othervice original data will be preserved | ||||
| 	/// Returns true if data was assigned | ||||
| 	template<typename Type> | ||||
| 	bool loadTo(Type & data) const; | ||||
|  | ||||
| 	//operator [], for structs only - get child node by name | ||||
| 	JsonNode & operator[](std::string child); | ||||
| 	const JsonNode & operator[](std::string child) const; | ||||
| @@ -159,11 +159,28 @@ namespace JsonUtils | ||||
| 	 */ | ||||
| 	DLL_LINKAGE JsonNode assembleFromFiles(std::vector<std::string> files); | ||||
|  | ||||
| 	/// removes all nodes that are identical to default entry in schema | ||||
| 	DLL_LINKAGE void minimize(JsonNode & node, const JsonNode& schema); | ||||
| 	/** | ||||
| 	 * @brief removes all nodes that are identical to default entry in schema | ||||
| 	 * @param node - JsonNode to minimize | ||||
| 	 * @param schemaName - name of schema to use | ||||
| 	 * @note for minimizing data must be valid against given schema | ||||
| 	 */ | ||||
| 	DLL_LINKAGE void minimize(JsonNode & node, std::string schemaName); | ||||
| 	/// opposed to minimize, adds all missing, required entries that have default value | ||||
| 	DLL_LINKAGE void maximize(JsonNode & node, std::string schemaName); | ||||
|  | ||||
| 	/// check schema | ||||
| 	DLL_LINKAGE void validate(JsonNode & node, const JsonNode& schema); | ||||
| 	/** | ||||
| 	* @brief validate node against specified schema | ||||
| 	* @param node - JsonNode to check | ||||
| 	* @param schemaName - name of schema to use | ||||
| 	* @param dataName - some way to identify data (printed in console in case of errors) | ||||
| 	* @returns true if data in node fully compilant with schema | ||||
| 	*/ | ||||
| 	DLL_LINKAGE bool validate(const JsonNode & node, std::string schemaName, std::string dataName); | ||||
|  | ||||
| 	/// get schema by json URI: vcmi:<name of file in schemas directory>#<entry in file, optional> | ||||
| 	/// example: schema "vcmi:settings" is used to check user settings | ||||
| 	DLL_LINKAGE const JsonNode & getSchema(std::string URI); | ||||
| } | ||||
|  | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
| @@ -345,29 +362,42 @@ namespace JsonDetail | ||||
| 		JsonNode parse(std::string fileName); | ||||
| 	}; | ||||
|  | ||||
| 	//Internal class for Json validation, used automaticaly in JsonNode constructor. Behaviour: | ||||
| 	// - "schema" entry from root node is used for validation and will be removed | ||||
| 	// - any missing entries will be replaced with default value from schema (if present) | ||||
| 	// - if entry uses different type than defined in schema it will be removed | ||||
| 	// - entries nod described in schema will be kept unchanged | ||||
| 	//Internal class for Json validation. Mostly compilant with json-schema v4 draft | ||||
| 	class JsonValidator | ||||
| 	{ | ||||
| 		std::string errors;     // Contains description of all encountered errors | ||||
| 		std::list<std::string> currentPath; // path from root node to current one | ||||
| 		bool minimize; | ||||
| 		// path from root node to current one. | ||||
| 		// JsonNode is used as variant - either string (name of node) or as float (index in list) | ||||
| 		std::vector<JsonNode> currentPath; | ||||
| 		// Stack of used schemas. Last schema is the one used currently. | ||||
| 		// May contain multiple items in case if remote references were found | ||||
| 		std::vector<std::string> usedSchemas; | ||||
|  | ||||
| 		bool validateType(JsonNode &node, const JsonNode &schema, JsonNode::JsonType type); | ||||
| 		bool validateSchema(JsonNode::JsonType &type, const JsonNode &schema); | ||||
| 		bool validateNode(JsonNode &node, const JsonNode &schema, const std::string &name); | ||||
| 		bool validateItems(JsonNode &node, const JsonNode &schema); | ||||
| 		bool validateProperties(JsonNode &node, const JsonNode &schema); | ||||
| 		/// helpers for other validation methods | ||||
| 		std::string validateVectorItem(const JsonVector items, const JsonNode & schema, const JsonNode & additional, size_t index); | ||||
| 		std::string validateStructItem(const JsonNode &node, const JsonNode &schema, const JsonNode & additional, std::string nodeName); | ||||
|  | ||||
| 		bool addMessage(const std::string &message); | ||||
| 		std::string validateEnum(const JsonNode &node, const JsonVector &enumeration); | ||||
| 		std::string validateNodeType(const JsonNode &node, const JsonNode &schema); | ||||
| 		std::string validatesSchemaList(const JsonNode &node, const JsonNode &schemas, std::string errorMsg, std::function<bool(size_t)> isValid); | ||||
|  | ||||
| 		/// contains all type-independent checks | ||||
| 		std::string validateNode(const JsonNode &node, const JsonNode &schema); | ||||
|  | ||||
| 		/// type-specific checks | ||||
| 		std::string validateVector(const JsonNode &node, const JsonNode &schema); | ||||
| 		std::string validateStruct(const JsonNode &node, const JsonNode &schema); | ||||
| 		std::string validateString(const JsonNode &node, const JsonNode &schema); | ||||
| 		std::string validateNumber(const JsonNode &node, const JsonNode &schema); | ||||
|  | ||||
| 		/// validation of root node of both schema and input data | ||||
| 		std::string validateRoot(const JsonNode &node, std::string schemaName); | ||||
|  | ||||
| 		/// add error message to list and return false | ||||
| 		std::string fail(const std::string &message); | ||||
| 	public: | ||||
| 		// validate node with "schema" entry | ||||
| 		JsonValidator(JsonNode &root, bool minimize=false); | ||||
| 		// validate with external schema | ||||
| 		JsonValidator(JsonNode &root, const JsonNode &schema, bool minimize=false); | ||||
|  | ||||
| 		/// returns true if parsed data is fully compilant with schema | ||||
| 		bool validate(const JsonNode &root, std::string schemaName, std::string name); | ||||
| 	}; | ||||
|  | ||||
| } // namespace JsonDetail | ||||
| @@ -377,11 +407,3 @@ Type JsonNode::convertTo() const | ||||
| { | ||||
| 	return JsonDetail::JsonConverter<Type>::convert(*this); | ||||
| } | ||||
|  | ||||
| template<typename Type> | ||||
| bool JsonNode::loadTo(Type & data) const | ||||
| { | ||||
| 	if (!isNull()) | ||||
| 		data = convertTo<Type>(); | ||||
| 	return !isNull(); | ||||
| } | ||||
|   | ||||
							
								
								
									
										47
									
								
								lib/ScopeGuard.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								lib/ScopeGuard.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| /* | ||||
|  * ScopeGuard.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
|  | ||||
| namespace vstd | ||||
| { | ||||
| 	template<typename Func> | ||||
| 	class ScopeGuard | ||||
| 	{ | ||||
| 		bool fire; | ||||
| 		Func f; | ||||
|  | ||||
| 		explicit ScopeGuard(ScopeGuard&); | ||||
| 		ScopeGuard& operator=(ScopeGuard&); | ||||
| 	public: | ||||
| 		ScopeGuard(ScopeGuard &&other): | ||||
| 		    fire(false), | ||||
| 			f(other.f) | ||||
| 		{ | ||||
| 			std::swap(fire, other.fire); | ||||
| 		} | ||||
|  | ||||
| 		explicit ScopeGuard(Func && f): | ||||
| 			fire(true), | ||||
| 			f(std::forward<Func>(f)) | ||||
| 		{} | ||||
| 		~ScopeGuard() | ||||
| 		{ | ||||
| 			f(); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	template <typename Func> | ||||
| 	ScopeGuard<Func> makeScopeGuard(Func&& exitScope) | ||||
| 	{ | ||||
| 		return ScopeGuard<Func>(std::forward<Func>(exitScope)); | ||||
| 	} | ||||
| } | ||||
| @@ -111,7 +111,6 @@ void LibClasses::init() | ||||
|  | ||||
| 	createHandler(spellh, "Spell", pomtime); | ||||
|  | ||||
|  | ||||
| 	modh->loadActiveMods(); | ||||
| 	modh->reload(); | ||||
| 	//FIXME: make sure that everything is ok after game restart | ||||
|   | ||||
		Reference in New Issue
	
	Block a user