mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +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:
parent
c8bb363a45
commit
c927913f5f
@ -308,7 +308,7 @@ int main(int argc, char** argv)
|
|||||||
tlog0 << "Fatal error: failed to load settings!\n";
|
tlog0 << "Fatal error: failed to load settings!\n";
|
||||||
tlog0 << "Possible reasons:\n";
|
tlog0 << "Possible reasons:\n";
|
||||||
tlog0 << "\tCorrupted local configuration file at " << VCMIDirs::get().localPath() << "/config/settings.json\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";
|
tlog0 << "VCMI will now exit...\n";
|
||||||
exit(EXIT_FAILURE);
|
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" : 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" : 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" : 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" :
|
"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->iconIndex = graphics["iconIndex"].Float();
|
||||||
art->image = graphics["image"].String();
|
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->large = art->image;
|
||||||
|
|
||||||
art->advMapDef = graphics["map"].String();
|
art->advMapDef = graphics["map"].String();
|
||||||
|
@ -59,8 +59,9 @@ void SettingsStorage::init()
|
|||||||
{
|
{
|
||||||
CResourceHandler::get()->createResource("config/settings.json");
|
CResourceHandler::get()->createResource("config/settings.json");
|
||||||
JsonNode(ResourceID("config/settings.json")).swap(config);
|
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)
|
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);
|
listener->nodeInvalidated(changedPath);
|
||||||
|
|
||||||
JsonNode savedConf = config;
|
JsonNode savedConf = config;
|
||||||
JsonNode schema(ResourceID("config/defaultSettings.json"));
|
JsonNode schema(ResourceID("config/schemas/settings.json"));
|
||||||
|
|
||||||
savedConf.Struct().erase("session");
|
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);
|
std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/settings.json")), std::ofstream::trunc);
|
||||||
file << savedConf;
|
file << savedConf;
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
typedef std::string TColor;
|
typedef std::string TColor;
|
||||||
#define CONSOLE_GREEN "\x1b[1;32m"
|
#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_MAGENTA "\x1b[1;35m"
|
||||||
#define CONSOLE_YELLOW "\x1b[1;32m"
|
#define CONSOLE_YELLOW "\x1b[1;33m"
|
||||||
#define CONSOLE_WHITE "\x1b[1;39m"
|
#define CONSOLE_WHITE "\x1b[1;37m"
|
||||||
#define CONSOLE_GRAY "\x1b[1;30m"
|
#define CONSOLE_GRAY "\x1b[1;30m"
|
||||||
#define CONSOLE_TEAL "\x1b[1;36m"
|
#define CONSOLE_TEAL "\x1b[1;36m"
|
||||||
#else
|
#else
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
void CIdentifierStorage::checkIdentifier(std::string & ID)
|
void CIdentifierStorage::checkIdentifier(std::string & ID)
|
||||||
{
|
{
|
||||||
if (boost::algorithm::ends_with(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
|
else
|
||||||
{
|
{
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
@ -33,7 +33,7 @@ void CIdentifierStorage::checkIdentifier(std::string & ID)
|
|||||||
{
|
{
|
||||||
if (std::tolower(ID[pos]) != ID[pos] ) //Not in camelCase
|
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
|
ID[pos] = std::tolower(ID[pos]);// Try to fix the ID
|
||||||
}
|
}
|
||||||
pos = ID.find('.', pos);
|
pos = ID.find('.', pos);
|
||||||
@ -53,7 +53,7 @@ void CIdentifierStorage::requestIdentifier(std::string name, const boost::functi
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(boost::algorithm::starts_with(name, "primSkill."))
|
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
|
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))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
|
|||||||
{
|
{
|
||||||
if (vstd::contains(input, conflicting))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,7 +269,7 @@ void CModHandler::initialize(std::vector<std::string> availableMods)
|
|||||||
|
|
||||||
if (!checkDependencies(detectedMods))
|
if (!checkDependencies(detectedMods))
|
||||||
{
|
{
|
||||||
tlog0 << "Critical error: failed to load mods! Exiting...\n";
|
tlog1 << "Critical error: failed to load mods! Exiting...\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,21 +282,21 @@ void CModHandler::initialize(std::vector<std::string> availableMods)
|
|||||||
file << modConfig;
|
file << modConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::string> CModHandler::getActiveMods()
|
std::vector<std::string> CModHandler::getActiveMods()
|
||||||
{
|
{
|
||||||
return activeMods;
|
return activeMods;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Handler>
|
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())
|
BOOST_FOREACH(auto & entry, config.Struct())
|
||||||
{
|
{
|
||||||
if (!entry.second.isNull()) // may happens if mod removed object by setting json entry to null
|
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);
|
handler->load(entry.first, entry.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,20 +306,21 @@ void CModHandler::loadActiveMods()
|
|||||||
{
|
{
|
||||||
BOOST_FOREACH(const TModID & modName, activeMods)
|
BOOST_FOREACH(const TModID & modName, activeMods)
|
||||||
{
|
{
|
||||||
tlog1 << "\t\tLoading mod ";
|
tlog0 << "\t\tLoading mod ";
|
||||||
tlog2 << allMods[modName].name << "\n";
|
tlog2 << allMods[modName].name << "\n";
|
||||||
|
|
||||||
std::string modFileName = "mods/" + modName + "/mod.json";
|
std::string modFileName = "mods/" + modName + "/mod.json";
|
||||||
|
|
||||||
const JsonNode config = JsonNode(ResourceID(modFileName));
|
const JsonNode config = JsonNode(ResourceID(modFileName));
|
||||||
|
JsonUtils::validate(config, "vcmi:mod", modName);
|
||||||
|
|
||||||
handleData(VLC->townh, config["factions"]);
|
handleData(VLC->townh, config, "factions", "vcmi:faction");
|
||||||
handleData(VLC->creh, config["creatures"]);
|
handleData(VLC->creh, config, "creatures", "vcmi:creature");
|
||||||
handleData(VLC->arth, config["artifacts"]);
|
handleData(VLC->arth, config, "artifacts", "vcmi:artifact");
|
||||||
//todo: spells
|
//todo: spells
|
||||||
|
|
||||||
handleData(&VLC->heroh->classes, config["heroClasses"]);
|
handleData(&VLC->heroh->classes, config,"heroClasses", "vcmi:heroClass");
|
||||||
handleData(VLC->heroh, config["heroes"]);
|
handleData(VLC->heroh, config, "heroes", "vcmi:hero");
|
||||||
}
|
}
|
||||||
|
|
||||||
VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded
|
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;
|
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
|
// - missing dependency mod
|
||||||
// - conflicting mod in load order
|
// - conflicting mod in load order
|
||||||
// - circular dependencies
|
// - 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
|
// 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)
|
// function assumes that input list is valid (checkDependencies returned true)
|
||||||
std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
|
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:
|
public:
|
||||||
|
|
||||||
CIdentifierStorage identifiers;
|
CIdentifierStorage identifiers;
|
||||||
|
|
||||||
/// receives list of available mods and trying to load mod.json from all of them
|
/// 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::GOOD : return 1;
|
||||||
case EAlignment::NEUTRAL : return 2;
|
case EAlignment::NEUTRAL : return 2;
|
||||||
}
|
}
|
||||||
|
assert(0);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CGTownInstance::getMarketEfficiency() const
|
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
|
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;
|
++iterator;
|
||||||
}
|
}
|
||||||
return foundMods;
|
return foundMods;
|
||||||
|
616
lib/JsonNode.cpp
616
lib/JsonNode.cpp
@ -11,6 +11,8 @@
|
|||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "JsonNode.h"
|
#include "JsonNode.h"
|
||||||
|
|
||||||
|
#include "ScopeGuard.h"
|
||||||
|
|
||||||
#include "HeroBonus.h"
|
#include "HeroBonus.h"
|
||||||
#include "Filesystem/CResourceLoader.h"
|
#include "Filesystem/CResourceLoader.h"
|
||||||
#include "VCMI_Lib.h" //for identifier resolution
|
#include "VCMI_Lib.h" //for identifier resolution
|
||||||
@ -34,8 +36,6 @@ JsonNode::JsonNode(const char *data, size_t datasize):
|
|||||||
{
|
{
|
||||||
JsonParser parser(data, datasize);
|
JsonParser parser(data, datasize);
|
||||||
*this = parser.parse("<unknown>");
|
*this = parser.parse("<unknown>");
|
||||||
|
|
||||||
JsonValidator validator(*this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode::JsonNode(ResourceID && fileURI):
|
JsonNode::JsonNode(ResourceID && fileURI):
|
||||||
@ -45,8 +45,6 @@ JsonNode::JsonNode(ResourceID && fileURI):
|
|||||||
|
|
||||||
JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
|
JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
|
||||||
*this = parser.parse(fileURI.getName());
|
*this = parser.parse(fileURI.getName());
|
||||||
|
|
||||||
JsonValidator validator(*this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode::JsonNode(const JsonNode ©):
|
JsonNode::JsonNode(const JsonNode ©):
|
||||||
@ -144,6 +142,11 @@ bool JsonNode::isNull() const
|
|||||||
return type == DATA_NULL;
|
return type == DATA_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JsonNode::clear()
|
||||||
|
{
|
||||||
|
setType(DATA_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
bool & JsonNode::Bool()
|
bool & JsonNode::Bool()
|
||||||
{
|
{
|
||||||
setType(DATA_BOOL);
|
setType(DATA_BOOL);
|
||||||
@ -231,6 +234,46 @@ const JsonNode & JsonNode::operator[](std::string child) const
|
|||||||
return it->second;
|
return it->second;
|
||||||
return nullNode;
|
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>
|
template<typename Iterator>
|
||||||
@ -516,7 +559,7 @@ bool JsonParser::extractNull(JsonNode &node)
|
|||||||
if (!extractLiteral("null"))
|
if (!extractLiteral("null"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
node.setType(JsonNode::DATA_NULL);
|
node.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,166 +739,370 @@ bool JsonParser::error(const std::string &message, bool warning)
|
|||||||
|
|
||||||
static const std::map<std::string, JsonNode::JsonType> stringToType =
|
static const std::map<std::string, JsonNode::JsonType> stringToType =
|
||||||
boost::assign::map_list_of
|
boost::assign::map_list_of
|
||||||
("null", JsonNode::DATA_NULL) ("bool", JsonNode::DATA_BOOL)
|
("null", JsonNode::DATA_NULL) ("boolean", JsonNode::DATA_BOOL)
|
||||||
("number", JsonNode::DATA_FLOAT) ("string", JsonNode::DATA_STRING)
|
("number", JsonNode::DATA_FLOAT) ("string", JsonNode::DATA_STRING)
|
||||||
("array", JsonNode::DATA_VECTOR) ("object", JsonNode::DATA_STRUCT);
|
("array", JsonNode::DATA_VECTOR) ("object", JsonNode::DATA_STRUCT);
|
||||||
|
|
||||||
//Check current schema entry for validness and converts "type" string to JsonType
|
std::string JsonValidator::validateEnum(const JsonNode &node, const JsonVector &enumeration)
|
||||||
bool JsonValidator::validateSchema(JsonNode::JsonType &type, const JsonNode &schema)
|
|
||||||
{
|
{
|
||||||
if (schema.isNull())
|
BOOST_FOREACH(auto & enumEntry, enumeration)
|
||||||
return addMessage("Missing schema for current entry!");
|
{
|
||||||
|
if (node == enumEntry)
|
||||||
const JsonNode &nodeType = schema["type"];
|
return "";
|
||||||
if (nodeType.isNull())
|
}
|
||||||
return addMessage("Entry type is not defined in schema!");
|
return fail("Key must have one of predefined values");
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Replaces node with default value if needed and calls type-specific validators
|
std::string JsonValidator::validatesSchemaList(const JsonNode &node, const JsonNode &schemas, std::string errorMsg, std::function<bool(size_t)> isValid)
|
||||||
bool JsonValidator::validateType(JsonNode &node, const JsonNode &schema, JsonNode::JsonType type)
|
|
||||||
{
|
{
|
||||||
if (node.isNull())
|
std::string errors;
|
||||||
|
if (!schemas.isNull())
|
||||||
{
|
{
|
||||||
const JsonNode & defaultValue = schema["default"];
|
size_t result = 0;
|
||||||
if (defaultValue.isNull())
|
|
||||||
return addMessage("Null entry without default entry!");
|
BOOST_FOREACH(auto & schema, schemas.Vector())
|
||||||
else
|
{
|
||||||
node = defaultValue;
|
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 count == schema["allOf"].Vector().size();
|
||||||
return false;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
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 count > 0;
|
||||||
return addMessage("Type mismatch!");
|
});
|
||||||
|
|
||||||
|
// 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");
|
||||||
}
|
}
|
||||||
|
// basic schema check
|
||||||
if (type == JsonNode::DATA_VECTOR)
|
if (!schema["type"].isNull())
|
||||||
return validateItems(node, schema["items"]);
|
{
|
||||||
|
JsonNode::JsonType type = stringToType.find(schema["type"].String())->second;
|
||||||
if (type == JsonNode::DATA_STRUCT)
|
if(type != node.getType())
|
||||||
return validateProperties(node, schema["properties"]);
|
errors += fail("Type mismatch!");
|
||||||
|
}
|
||||||
return true;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic checks common for any nodes
|
// 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;
|
assert(!schema.isNull()); // can this error be triggered?
|
||||||
if (!validateSchema(type, schema)
|
|
||||||
|| !validateType(node, schema, type))
|
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();
|
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())
|
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)
|
// othervice check against schema in additional items field
|
||||||
tlog3<<errors;
|
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):
|
//Checks "items" entry from schema (type-specific check for Vector)
|
||||||
minimize(Minimize)
|
std::string JsonValidator::validateVector(const JsonNode &node, const JsonNode &schema)
|
||||||
{
|
{
|
||||||
validateProperties(root, schema);
|
std::string errors;
|
||||||
if (schema.isNull())
|
auto & vector = node.Vector();
|
||||||
addMessage("Schema not found!");
|
|
||||||
tlog3<<errors;
|
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
|
///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)
|
void JsonUtils::merge(JsonNode & dest, JsonNode & source)
|
||||||
@ -1133,7 +1461,7 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source)
|
|||||||
|
|
||||||
switch (source.getType())
|
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_BOOL: std::swap(dest.Bool(), source.Bool());
|
||||||
break; case JsonNode::DATA_FLOAT: std::swap(dest.Float(), source.Float());
|
break; case JsonNode::DATA_FLOAT: std::swap(dest.Float(), source.Float());
|
||||||
break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String());
|
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;
|
||||||
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);
|
void setType(JsonType Type);
|
||||||
JsonType getType() const;
|
JsonType getType() const;
|
||||||
|
|
||||||
bool isNull() 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();
|
bool & Bool();
|
||||||
double & Float();
|
double & Float();
|
||||||
std::string & String();
|
std::string & String();
|
||||||
JsonVector & Vector();
|
JsonVector & Vector();
|
||||||
JsonMap & Struct();
|
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 bool & Bool() const;
|
||||||
const double & Float() const;
|
const double & Float() const;
|
||||||
const std::string & String() const;
|
const std::string & String() const;
|
||||||
const JsonVector & Vector() const;
|
const JsonVector & Vector() const;
|
||||||
const JsonMap & Struct() 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
|
/// convert json tree into specified type. Json tree must have same type as Type
|
||||||
/// Valid types: bool, string, any numeric, map and vector
|
/// Valid types: bool, string, any numeric, map and vector
|
||||||
/// example: convertTo< std::map< std::vector<int> > >();
|
/// example: convertTo< std::map< std::vector<int> > >();
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
Type convertTo() const;
|
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
|
//operator [], for structs only - get child node by name
|
||||||
JsonNode & operator[](std::string child);
|
JsonNode & operator[](std::string child);
|
||||||
const JsonNode & operator[](std::string child) const;
|
const JsonNode & operator[](std::string child) const;
|
||||||
@ -159,11 +159,28 @@ namespace JsonUtils
|
|||||||
*/
|
*/
|
||||||
DLL_LINKAGE JsonNode assembleFromFiles(std::vector<std::string> files);
|
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);
|
JsonNode parse(std::string fileName);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Internal class for Json validation, used automaticaly in JsonNode constructor. Behaviour:
|
//Internal class for Json validation. Mostly compilant with json-schema v4 draft
|
||||||
// - "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
|
|
||||||
class JsonValidator
|
class JsonValidator
|
||||||
{
|
{
|
||||||
std::string errors; // Contains description of all encountered errors
|
// path from root node to current one.
|
||||||
std::list<std::string> currentPath; // path from root node to current one
|
// JsonNode is used as variant - either string (name of node) or as float (index in list)
|
||||||
bool minimize;
|
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);
|
/// helpers for other validation methods
|
||||||
bool validateSchema(JsonNode::JsonType &type, const JsonNode &schema);
|
std::string validateVectorItem(const JsonVector items, const JsonNode & schema, const JsonNode & additional, size_t index);
|
||||||
bool validateNode(JsonNode &node, const JsonNode &schema, const std::string &name);
|
std::string validateStructItem(const JsonNode &node, const JsonNode &schema, const JsonNode & additional, std::string nodeName);
|
||||||
bool validateItems(JsonNode &node, const JsonNode &schema);
|
|
||||||
bool validateProperties(JsonNode &node, const JsonNode &schema);
|
|
||||||
|
|
||||||
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:
|
public:
|
||||||
// validate node with "schema" entry
|
|
||||||
JsonValidator(JsonNode &root, bool minimize=false);
|
/// returns true if parsed data is fully compilant with schema
|
||||||
// validate with external schema
|
bool validate(const JsonNode &root, std::string schemaName, std::string name);
|
||||||
JsonValidator(JsonNode &root, const JsonNode &schema, bool minimize=false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace JsonDetail
|
} // namespace JsonDetail
|
||||||
@ -377,11 +407,3 @@ Type JsonNode::convertTo() const
|
|||||||
{
|
{
|
||||||
return JsonDetail::JsonConverter<Type>::convert(*this);
|
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));
|
||||||
|
}
|
||||||
|
}
|
@ -92,7 +92,7 @@ template <class Handler> void createHandler(Handler *&handler, const std::string
|
|||||||
void LibClasses::init()
|
void LibClasses::init()
|
||||||
{
|
{
|
||||||
CStopWatch pomtime;
|
CStopWatch pomtime;
|
||||||
|
|
||||||
createHandler(bth, "Bonus type", pomtime);
|
createHandler(bth, "Bonus type", pomtime);
|
||||||
|
|
||||||
createHandler(generaltexth, "General text", pomtime);
|
createHandler(generaltexth, "General text", pomtime);
|
||||||
@ -111,7 +111,6 @@ void LibClasses::init()
|
|||||||
|
|
||||||
createHandler(spellh, "Spell", pomtime);
|
createHandler(spellh, "Spell", pomtime);
|
||||||
|
|
||||||
|
|
||||||
modh->loadActiveMods();
|
modh->loadActiveMods();
|
||||||
modh->reload();
|
modh->reload();
|
||||||
//FIXME: make sure that everything is ok after game restart
|
//FIXME: make sure that everything is ok after game restart
|
||||||
|
Loading…
Reference in New Issue
Block a user