forked from github/server
1159 lines
38 KiB
C
1159 lines
38 KiB
C
/*
|
|
+-------------------+
|
|
| | Enno Rehling <enno@eressea.de>
|
|
| Eressea PBEM host | Christian Schlittchen <corwin@amber.kn-bremen.de>
|
|
| (c) 1998 - 2004 | Katja Zedel <katze@felidae.kn-bremen.de>
|
|
| | Henning Peters <faroul@beyond.kn-bremen.de>
|
|
+-------------------+
|
|
|
|
This program may not be used, modified or distributed
|
|
without prior permission by the authors of Eressea.
|
|
*/
|
|
|
|
#include <platform.h>
|
|
#include "jsonconf.h"
|
|
|
|
/* kernel includes */
|
|
#include "kernel/building.h"
|
|
#include "kernel/calendar.h"
|
|
#include "kernel/config.h"
|
|
#include "kernel/equipment.h"
|
|
#include "kernel/item.h"
|
|
#include "kernel/messages.h"
|
|
#include "kernel/race.h"
|
|
#include "kernel/region.h"
|
|
#include "kernel/resources.h"
|
|
#include "kernel/ship.h"
|
|
#include "kernel/terrain.h"
|
|
#include "kernel/spell.h"
|
|
#include "kernel/spellbook.h"
|
|
|
|
/* util includes */
|
|
#include "kernel/attrib.h"
|
|
#include "util/crmessage.h"
|
|
#include "util/functions.h"
|
|
#include "util/keyword.h"
|
|
#include "util/language.h"
|
|
#include "util/log.h"
|
|
#include "util/message.h"
|
|
#include "util/nrmessage.h"
|
|
#include "util/path.h"
|
|
#include "util/pofile.h"
|
|
#include "util/strings.h"
|
|
|
|
/* game modules */
|
|
#include "direction.h"
|
|
#include "move.h"
|
|
#include "prefix.h"
|
|
#include "skill.h"
|
|
#include "exparse.h"
|
|
|
|
/* external libraries */
|
|
#include <cJSON.h>
|
|
|
|
/* libc includes */
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static int json_flags(cJSON *json, const char *flags[]) {
|
|
cJSON *entry;
|
|
int result = 0;
|
|
assert(json->type == cJSON_Array);
|
|
for (entry = json->child; entry; entry = entry->next) {
|
|
if (entry->type == cJSON_String) {
|
|
int i;
|
|
for (i = 0; flags[i]; ++i) {
|
|
if (strcmp(flags[i], entry->valuestring) == 0) {
|
|
result |= (1 << i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void json_requirements(cJSON *json, requirement **matp) {
|
|
cJSON *child;
|
|
int i;
|
|
requirement *mat = calloc(1 + cJSON_GetArraySize(json), sizeof(requirement));
|
|
if (!mat) abort();
|
|
for (i = 0, child = json->child; child; child = child->next, ++i) {
|
|
mat[i].number = child->valueint;
|
|
mat[i].rtype = rt_get_or_create(child->string);
|
|
}
|
|
*matp = mat;
|
|
}
|
|
|
|
static void json_maintenance_i(cJSON *json, maintenance *mt) {
|
|
cJSON *child;
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "amount") == 0) {
|
|
mt->number = child->valueint;
|
|
}
|
|
else {
|
|
log_error("maintenance contains unknown attribute %s", child->string);
|
|
}
|
|
break;
|
|
case cJSON_String:
|
|
if (strcmp(child->string, "type") == 0) {
|
|
mt->rtype = rt_get_or_create(child->valuestring);
|
|
}
|
|
else {
|
|
log_error("maintenance contains unknown attribute %s", child->string);
|
|
}
|
|
break;
|
|
case cJSON_Array:
|
|
if (strcmp(child->string, "flags") == 0) {
|
|
const char * flags[] = { "variable", 0 };
|
|
mt->flags = json_flags(child, flags);
|
|
}
|
|
else {
|
|
log_error("maintenance contains unknown array %s", child->string);
|
|
}
|
|
break;
|
|
default:
|
|
log_error("maintenance contains unknown attribute %s of type %d", child->string, child->type);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_maintenance(cJSON *json, maintenance **mtp) {
|
|
cJSON *child;
|
|
maintenance *mt;
|
|
int size = 1;
|
|
|
|
if (json->type == cJSON_Array) {
|
|
size = cJSON_GetArraySize(json);
|
|
}
|
|
else if (json->type != cJSON_Object) {
|
|
log_error("maintenance is not a json object or array (%d)", json->type);
|
|
return;
|
|
}
|
|
*mtp = mt = (struct maintenance *) calloc(size + 1, sizeof(struct maintenance));
|
|
if (json->type == cJSON_Array) {
|
|
int i;
|
|
for (i = 0, child = json->child; child; child = child->next, ++i) {
|
|
if (child->type == cJSON_Object) {
|
|
json_maintenance_i(child, mt + i);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
json_maintenance_i(json, mt);
|
|
}
|
|
}
|
|
|
|
static void json_construction(cJSON *json, construction **consp) {
|
|
cJSON *child;
|
|
construction * cons;
|
|
|
|
if (json->type != cJSON_Object) {
|
|
log_error("construction %s is not a json object: %d", json->string, json->type);
|
|
return;
|
|
}
|
|
cons = (construction *)calloc(1, sizeof(construction));
|
|
if (!cons) abort();
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Object:
|
|
if (strcmp(child->string, "materials") == 0) {
|
|
json_requirements(child, &cons->materials);
|
|
}
|
|
break;
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "maxsize") == 0) {
|
|
cons->maxsize = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "reqsize") == 0) {
|
|
cons->reqsize = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "minskill") == 0) {
|
|
cons->minskill = child->valueint;
|
|
}
|
|
break;
|
|
default:
|
|
log_error("construction %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
}
|
|
*consp = cons;
|
|
}
|
|
|
|
static void json_terrain_production(cJSON *json, terrain_production *prod) {
|
|
cJSON *child;
|
|
assert(json->type == cJSON_Object);
|
|
for (child = json->child; child; child = child->next) {
|
|
char **dst = 0;
|
|
switch (child->type) {
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "chance") == 0) {
|
|
prod->chance = (float)child->valuedouble;
|
|
}
|
|
else {
|
|
log_error("terrain_production %s contains unknown number %s", json->string, child->string);
|
|
}
|
|
break;
|
|
case cJSON_String:
|
|
if (strcmp(child->string, "base") == 0) {
|
|
dst = &prod->base;
|
|
}
|
|
else if (strcmp(child->string, "level") == 0) {
|
|
dst = &prod->startlevel;
|
|
}
|
|
else if (strcmp(child->string, "div") == 0) {
|
|
dst = &prod->divisor;
|
|
}
|
|
else {
|
|
log_error("terrain_production %s contains unknown string %s", json->string, child->string);
|
|
}
|
|
break;
|
|
default:
|
|
log_error("terrain_production %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
if (dst) {
|
|
free(*dst);
|
|
assert(child->type == cJSON_String);
|
|
*dst = str_strdup(child->valuestring);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_terrain(cJSON *json, terrain_type *ter) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("terrain %s is not a json object: %d", json->string, json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Object:
|
|
if (strcmp(child->string, "production") == 0) {
|
|
cJSON *entry;
|
|
int size = cJSON_GetArraySize(child);
|
|
if (size > 0) {
|
|
int n;
|
|
ter->production = (terrain_production *)calloc(size + 1, sizeof(terrain_production));
|
|
if (!ter->production) abort();
|
|
ter->production[size].type = 0;
|
|
for (n = 0, entry = child->child; entry; entry = entry->next, ++n) {
|
|
ter->production[n].type = rt_get_or_create(entry->string);
|
|
if (entry->type != cJSON_Object) {
|
|
log_error("terrain %s contains invalid production %s", json->string, entry->string);
|
|
}
|
|
else {
|
|
json_terrain_production(entry, ter->production + n);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error("terrain %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
break;
|
|
case cJSON_Array:
|
|
if (strcmp(child->string, "flags") == 0) {
|
|
const char * flags[] = {
|
|
"land", "sea", "forest", "arctic", "cavalry", "forbidden", "sail", "fly", "swim", "walk", 0
|
|
};
|
|
ter->flags = json_flags(child, flags);
|
|
}
|
|
else if (strcmp(child->string, "herbs") == 0) {
|
|
cJSON *entry;
|
|
int size = cJSON_GetArraySize(child);
|
|
if (size > 0) {
|
|
int n;
|
|
free(ter->herbs);
|
|
ter->herbs = malloc(sizeof(const item_type *) * (size + 1));
|
|
if (!ter->herbs) abort();
|
|
ter->herbs[size] = 0;
|
|
for (n = 0, entry = child->child; entry; entry = entry->next) {
|
|
ter->herbs[n++] = it_get_or_create(rt_get_or_create(entry->valuestring));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error("terrain %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
break;
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "size") == 0) {
|
|
ter->size = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "road") == 0) {
|
|
ter->max_road = (short)child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "seed") == 0) {
|
|
ter->distribution = (short)child->valueint;
|
|
}
|
|
else {
|
|
log_error("terrain %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
break;
|
|
default:
|
|
log_error("terrain %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_stage(cJSON *json, building_stage *stage) {
|
|
cJSON *child;
|
|
|
|
if (json->type != cJSON_Object) {
|
|
log_error("building stages is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Object:
|
|
if (strcmp(child->string, "construction") == 0) {
|
|
json_construction(child, &stage->construction);
|
|
}
|
|
break;
|
|
case cJSON_String:
|
|
if (strcmp(child->string, "name") == 0) {
|
|
stage->name = str_strdup(child->valuestring);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_stages(cJSON *json, building_type *bt) {
|
|
cJSON *child;
|
|
building_stage *stage, **sp = &bt->stages;
|
|
int size = 0;
|
|
|
|
if (json->type != cJSON_Array) {
|
|
log_error("building stages is not a json array: %d", json->type);
|
|
return;
|
|
}
|
|
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Object:
|
|
stage = calloc(1, sizeof(building_stage));
|
|
if (!stage) abort();
|
|
json_stage(child, stage);
|
|
if (stage->construction->maxsize > 0) {
|
|
stage->construction->maxsize -= size;
|
|
size += stage->construction->maxsize;
|
|
}
|
|
*sp = stage;
|
|
sp = &stage->next;
|
|
break;
|
|
default:
|
|
log_error("building stage contains non-object type %d", child->type);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_building(cJSON *json, building_type *bt) {
|
|
cJSON *child;
|
|
const char *flags[] = {
|
|
"nodestroy", "nobuild", "unique", "decay", "dynamic", "magic", "oneperturn", "namechange", "fort", 0
|
|
};
|
|
if (json->type != cJSON_Object) {
|
|
log_error("building %s is not a json object: %d", json->string, json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Array:
|
|
if (strcmp(child->string, "stages") == 0) {
|
|
if (!bt->stages) {
|
|
json_stages(child, bt);
|
|
}
|
|
}
|
|
else if (strcmp(child->string, "maintenance") == 0) {
|
|
json_maintenance(child, &bt->maintenance);
|
|
}
|
|
else if (strcmp(child->string, "flags") == 0) {
|
|
json_flags(child, flags);
|
|
}
|
|
break;
|
|
case cJSON_Object:
|
|
if (strcmp(child->string, "construction") == 0) {
|
|
/* simple, single-stage building */
|
|
if (!bt->stages) {
|
|
building_stage *stage = calloc(1, sizeof(building_stage));
|
|
if (!stage) abort();
|
|
json_construction(child, &stage->construction);
|
|
bt->stages = stage;
|
|
}
|
|
}
|
|
if (strcmp(child->string, "maintenance") == 0) {
|
|
json_maintenance(child, &bt->maintenance);
|
|
}
|
|
break;
|
|
case cJSON_String:
|
|
log_error("building %s contains unknown attribute %s", json->string, child->string);
|
|
break;
|
|
default:
|
|
log_error("building %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_item(cJSON *json, item_type *itype) {
|
|
cJSON *child;
|
|
const char *flags[] = {
|
|
"herb", "cursed", "nodrop", "big", "animal", "vehicle", 0
|
|
};
|
|
if (json->type != cJSON_Object) {
|
|
log_error("ship %s is not a json object: %d", json->string, json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "weight") == 0) {
|
|
itype->weight = child->valueint;
|
|
break;
|
|
}
|
|
if (strcmp(child->string, "capacity") == 0) {
|
|
itype->capacity = child->valueint;
|
|
break;
|
|
}
|
|
log_error("item %s contains unknown attribute %s", json->string, child->string);
|
|
break;
|
|
case cJSON_Array:
|
|
if (strcmp(child->string, "flags") == 0) {
|
|
itype->flags = json_flags(child, flags);
|
|
break;
|
|
}
|
|
log_error("item %s contains unknown attribute %s", json->string, child->string);
|
|
case cJSON_Object:
|
|
default:
|
|
log_error("item %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_ship(cJSON *json, ship_type *st) {
|
|
cJSON *child, *iter;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("ship %s is not a json object: %d", json->string, json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
int i;
|
|
switch (child->type) {
|
|
case cJSON_Object:
|
|
if (strcmp(child->string, "construction") == 0) {
|
|
json_construction(child, &st->construction);
|
|
}
|
|
else {
|
|
log_error("ship %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
break;
|
|
case cJSON_Array:
|
|
st->coasts = (terrain_type **)
|
|
malloc(sizeof(terrain_type *) * (1 + cJSON_GetArraySize(child)));
|
|
if (!st->coasts) abort();
|
|
for (i = 0, iter = child->child; iter; iter = iter->next) {
|
|
if (iter->type == cJSON_String) {
|
|
terrain_type *ter = get_or_create_terrain(iter->valuestring);
|
|
if (ter) {
|
|
st->coasts[i++] = ter;
|
|
}
|
|
}
|
|
}
|
|
st->coasts[i] = 0;
|
|
break;
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "range") == 0) {
|
|
st->range = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "maxrange") == 0) {
|
|
st->range_max = child->valueint;
|
|
}
|
|
else {
|
|
log_error("ship %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
break;
|
|
default:
|
|
log_error("ship %s contains unknown attribute %s", json->string, child->string);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_race(cJSON *json, race *rc) {
|
|
cJSON *child;
|
|
const char *flags[] = {
|
|
"player", "killpeasants", "scarepeasants",
|
|
"nosteal", "moverandom", "cannotmove",
|
|
"learn", "fly", "swim", "walk", "nolearn",
|
|
"noteach", "horse", "desert",
|
|
"illusionary", "absorbpeasants", "noheal",
|
|
"noweapons", "shapeshift", "", "undead", "dragon",
|
|
"coastal", "", "cansail", NULL
|
|
};
|
|
const char *ecflags[] = {
|
|
"giveperson", "giveunit", "getitem", NULL
|
|
};
|
|
if (json->type != cJSON_Object) {
|
|
log_error("race %s is not a json object: %d", json->string, json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
switch (child->type) {
|
|
case cJSON_String:
|
|
if (strcmp(child->string, "damage") == 0) {
|
|
rc->def_damage = str_strdup(child->valuestring);
|
|
}
|
|
break;
|
|
case cJSON_Number:
|
|
if (strcmp(child->string, "magres") == 0) {
|
|
rc->magres = frac_make(child->valueint, 100);
|
|
}
|
|
else if (strcmp(child->string, "maxaura") == 0) {
|
|
rc->maxaura = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "regaura") == 0) {
|
|
rc->regaura = (float)child->valuedouble;
|
|
}
|
|
else if (strcmp(child->string, "speed") == 0) {
|
|
rc->speed = (float)child->valuedouble;
|
|
}
|
|
else if (strcmp(child->string, "recruitcost") == 0) {
|
|
rc->recruitcost = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "maintenance") == 0) {
|
|
rc->maintenance = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "weight") == 0) {
|
|
rc->weight = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "capacity") == 0) {
|
|
rc->capacity = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "income") == 0) {
|
|
rc->income = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "hp") == 0) {
|
|
rc->hitpoints = child->valueint;
|
|
}
|
|
else if (strcmp(child->string, "ac") == 0) {
|
|
rc->armor = child->valueint;
|
|
}
|
|
/* TODO: studyspeed (orcs only) */
|
|
break;
|
|
case cJSON_Array:
|
|
if (strcmp(child->string, "flags") == 0) {
|
|
rc->flags = json_flags(child, flags);
|
|
rc->ec_flags = json_flags(child, ecflags);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_prefixes(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Array) {
|
|
log_error("prefixes is not a json array: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
add_raceprefix(child->valuestring);
|
|
}
|
|
}
|
|
|
|
/** disable a feature.
|
|
* features are identified by one of:
|
|
* 1. the keyword for their orders,
|
|
* 2. the name of the skill they use,
|
|
* 3. a "module.enabled" flag in the settings
|
|
*/
|
|
static void disable_feature(const char *str) {
|
|
char name[32];
|
|
int k;
|
|
skill_t sk;
|
|
size_t len;
|
|
|
|
sk = findskill(str);
|
|
if (sk != NOSKILL) {
|
|
enable_skill(sk, false);
|
|
return;
|
|
}
|
|
k = findkeyword(str);
|
|
if (k!=NOKEYWORD) {
|
|
log_debug("disable keyword %s\n", str);
|
|
enable_keyword(k, false);
|
|
return;
|
|
}
|
|
len = strlen(str);
|
|
assert(len <= sizeof(name) - 9);
|
|
memcpy(name, str, len);
|
|
strcpy(name+len, ".enabled");
|
|
log_info("disable feature %s\n", name);
|
|
config_set(name, "0");
|
|
}
|
|
|
|
static void json_disable_features(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Array) {
|
|
log_error("disabled is not a json array: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
disable_feature(child->valuestring);
|
|
}
|
|
}
|
|
|
|
static void json_terrains(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("terrains is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
json_terrain(child, get_or_create_terrain(child->string));
|
|
}
|
|
}
|
|
|
|
static void json_buildings(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("buildings is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
json_building(child, bt_get_or_create(child->string));
|
|
}
|
|
}
|
|
|
|
static void json_spells(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("spells is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
if (child->type == cJSON_Object) {
|
|
spell *sp;
|
|
cJSON * item;
|
|
sp = create_spell(child->string);
|
|
for (item = child->child; item; item = item->next) {
|
|
if (strcmp(item->string, "index") == 0) {
|
|
continue;
|
|
}
|
|
else if (strcmp(item->string, "syntax") == 0) {
|
|
sp->syntax = str_strdup(item->valuestring);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_items(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("items is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
resource_type *rtype = rt_get_or_create(child->string);
|
|
item_type *itype = rtype->itype;
|
|
if (!itype) {
|
|
rtype->itype = itype = it_get_or_create(rtype);
|
|
}
|
|
json_item(child, itype);
|
|
}
|
|
}
|
|
|
|
static void json_ships(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("ships is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
json_ship(child, st_get_or_create(child->string));
|
|
}
|
|
}
|
|
|
|
static void json_locale(cJSON *json, struct locale *lang) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("strings is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
if (child->type == cJSON_String) {
|
|
locale_setstring(lang, child->string, child->valuestring);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_strings(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("strings is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
if (child->type == cJSON_Object) {
|
|
struct locale *lang = get_locale(child->string);
|
|
json_locale(child, lang);
|
|
}
|
|
else {
|
|
log_error("strings for locale `%s` are not a json object: %d", child->string, child->type);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_direction(cJSON *json, struct locale *lang) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("directions for locale `%s` not a json object: %d", locale_name(lang), json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
direction_t dir = finddirection(child->string);
|
|
if (dir != NODIRECTION) {
|
|
if (child->type == cJSON_String) {
|
|
init_direction(lang, dir, child->valuestring);
|
|
}
|
|
else if (child->type == cJSON_Array) {
|
|
cJSON *entry;
|
|
for (entry = child->child; entry; entry = entry->next) {
|
|
init_direction(lang, dir, entry->valuestring);
|
|
}
|
|
}
|
|
else {
|
|
log_error("invalid type %d for direction `%s`", child->type, child->string);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_calendar(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("calendar is not an object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
if (strcmp(child->string, "start") == 0) {
|
|
config_set_int("game.start", child->valueint);
|
|
}
|
|
else if (strcmp(child->string, "weeks") == 0) {
|
|
cJSON *entry;
|
|
int i;
|
|
if (child->type != cJSON_Array) {
|
|
log_error("calendar.weeks is not an array: %d", json->type);
|
|
return;
|
|
}
|
|
weeks_per_month = cJSON_GetArraySize(child);
|
|
free(weeknames);
|
|
weeknames = malloc(sizeof(char *) * weeks_per_month);
|
|
if (!weeknames) abort();
|
|
for (i = 0, entry = child->child; entry; entry = entry->next, ++i) {
|
|
if (entry->type == cJSON_String) {
|
|
weeknames[i] = str_strdup(entry->valuestring);
|
|
}
|
|
else {
|
|
log_error("calendar.weeks[%d] is not a string: %d", i, json->type);
|
|
free(weeknames);
|
|
weeknames = NULL;
|
|
return;
|
|
}
|
|
}
|
|
assert(i == weeks_per_month);
|
|
free(weeknames2);
|
|
weeknames2 = malloc(sizeof(char *) * weeks_per_month);
|
|
if (!weeknames2) abort();
|
|
for (i = 0; i != weeks_per_month; ++i) {
|
|
weeknames2[i] = malloc(strlen(weeknames[i]) + 3);
|
|
sprintf(weeknames2[i], "%s_d", weeknames[i]);
|
|
}
|
|
}
|
|
else if (strcmp(child->string, "months") == 0) {
|
|
cJSON *jmonth;
|
|
int i;
|
|
if (child->type != cJSON_Array) {
|
|
log_error("calendar.seasons is not an array: %d", json->type);
|
|
return;
|
|
}
|
|
free(month_season);
|
|
month_season = NULL;
|
|
free(storms);
|
|
months_per_year = cJSON_GetArraySize(child);
|
|
storms = malloc(sizeof(int) * months_per_year);
|
|
if (!storms) abort();
|
|
month_season = malloc(sizeof(int) * months_per_year);
|
|
if (!month_season) abort();
|
|
for (i = 0, jmonth = child->child; jmonth; jmonth = jmonth->next, ++i) {
|
|
if (jmonth->type == cJSON_Object) {
|
|
storms[i] = cJSON_GetObjectItem(jmonth, "storm")->valueint;
|
|
month_season[i] = cJSON_GetObjectItem(jmonth, "season")->valueint;
|
|
}
|
|
else {
|
|
log_error("calendar.months[%d] is not an object: %d", i, json->type);
|
|
free(storms);
|
|
storms = NULL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_directions(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("directions is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
struct locale * lang = get_locale(child->string);
|
|
json_direction(child, lang);
|
|
}
|
|
}
|
|
|
|
static void json_skill(cJSON *json, struct locale *lang) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("skill for locale `%s` not a json object: %d", locale_name(lang), json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
skill_t sk = findskill(child->string);
|
|
if (sk != NOSKILL) {
|
|
if (child->type == cJSON_String) {
|
|
init_skill(lang, sk, child->valuestring);
|
|
locale_setstring(lang, mkname("skill", skillnames[sk]), child->valuestring);
|
|
}
|
|
else if (child->type == cJSON_Array) {
|
|
cJSON *entry;
|
|
for (entry = child->child; entry; entry = entry->next) {
|
|
init_skill(lang, sk, entry->valuestring);
|
|
if (entry == child->child) {
|
|
locale_setstring(lang, mkname("skill", skillnames[sk]), entry->valuestring);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error("invalid type %d for skill `%s`", child->type, child->string);
|
|
}
|
|
}
|
|
else {
|
|
log_error("unknown skill `%s` for locale `%s`", child->string, locale_name(lang));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_keyword(cJSON *json, struct locale *lang) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("keywords for locale `%s` not a json object: %d", locale_name(lang), json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
keyword_t kwd = findkeyword(child->string);
|
|
if (kwd != NOKEYWORD && keywords[kwd]) {
|
|
if (child->type == cJSON_String) {
|
|
init_keyword(lang, kwd, child->valuestring);
|
|
locale_setstring(lang, mkname("keyword", keywords[kwd]), child->valuestring);
|
|
}
|
|
else if (child->type == cJSON_Array) {
|
|
cJSON *entry;
|
|
for (entry = child->child; entry; entry = entry->next) {
|
|
init_keyword(lang, kwd, entry->valuestring);
|
|
if (entry == child->child) {
|
|
locale_setstring(lang, mkname("keyword", keywords[kwd]), entry->valuestring);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error("invalid type %d for keyword `%s`", child->type, child->string);
|
|
}
|
|
}
|
|
else {
|
|
log_error("unknown keyword `%s` for locale `%s`", child->string, locale_name(lang));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_skills(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("skills is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
struct locale * lang = get_locale(child->string);
|
|
json_skill(child, lang);
|
|
}
|
|
}
|
|
|
|
static void json_keywords(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("keywords is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
struct locale * lang = get_locale(child->string);
|
|
json_keyword(child, lang);
|
|
}
|
|
}
|
|
|
|
static void json_settings(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("settings is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
if (config_get(child->string) == NULL) {
|
|
if (child->valuestring) {
|
|
config_set(child->string, child->valuestring);
|
|
}
|
|
else {
|
|
char value[32];
|
|
if (child->type == cJSON_Number && child->valuedouble && child->valueint < child->valuedouble) {
|
|
sprintf(value, "%f", child->valuedouble);
|
|
}
|
|
else {
|
|
sprintf(value, "%d", child->valueint);
|
|
}
|
|
config_set(child->string, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void json_races(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Object) {
|
|
log_error("races is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
json_race(child, rc_get_or_create(child->string));
|
|
}
|
|
}
|
|
|
|
const char * json_relpath;
|
|
|
|
/* TODO: much more configurable authority-to-file lookup */
|
|
static const char * authority_to_path(const char *authority, char *name, size_t size) {
|
|
/* source and destination cannot share the same buffer */
|
|
assert(authority < name || authority > name + size);
|
|
|
|
return join_path(json_relpath, authority, name, size);
|
|
}
|
|
|
|
static const char * uri_to_file(const char * uri, char *name, size_t size) {
|
|
const char *pos, *scheme, *path = uri;
|
|
|
|
/* source and destination cannot share the same buffer */
|
|
assert(uri < name || uri > name + size);
|
|
|
|
/* identify scheme */
|
|
scheme = uri;
|
|
pos = strstr(scheme, "://");
|
|
if (pos) {
|
|
size_t slen = pos - scheme;
|
|
if (strncmp(scheme, "config", slen) == 0) {
|
|
const char *authority = pos + 3;
|
|
/* authority */
|
|
pos = strstr(authority, "/");
|
|
if (pos) {
|
|
char buffer[16];
|
|
size_t alen = pos - authority;
|
|
assert(alen < sizeof(buffer));
|
|
memcpy(buffer, authority, alen);
|
|
buffer[alen] = 0;
|
|
|
|
path = authority_to_path(buffer, name, size);
|
|
path = path_join(path, pos + 1, name, size);
|
|
}
|
|
}
|
|
else {
|
|
log_fatal("unknown URI scheme: %s", uri);
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
static int include_json(const char *uri) {
|
|
FILE *F;
|
|
char name[PATH_MAX];
|
|
const char *filename = uri_to_file(uri, name, sizeof(name));
|
|
int result = -1;
|
|
|
|
F = fopen(filename, "r");
|
|
if (F) {
|
|
long pos;
|
|
fseek(F, 0, SEEK_END);
|
|
pos = ftell(F);
|
|
rewind(F);
|
|
if (pos > 0) {
|
|
cJSON *config;
|
|
char *data;
|
|
size_t sz;
|
|
|
|
data = malloc(pos + 1);
|
|
if (!data) abort();
|
|
sz = fread(data, 1, (size_t)pos, F);
|
|
data[sz] = 0;
|
|
config = cJSON_Parse(data);
|
|
free(data);
|
|
if (config) {
|
|
json_config(config);
|
|
cJSON_Delete(config);
|
|
result = 0;
|
|
}
|
|
else {
|
|
log_error("could not parse JSON from %s", uri);
|
|
result = -1;
|
|
}
|
|
}
|
|
fclose(F);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int include_xml(const char *uri) {
|
|
char name[PATH_MAX];
|
|
const char *filename = uri_to_file(uri, name, sizeof(name));
|
|
int err;
|
|
err = exparse_readfile(filename);
|
|
if (err != 0) {
|
|
log_error("could not parse XML from %s", uri);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int add_po_string(const char *msgid, const char *msgstr, const char *msgctxt, void *data) {
|
|
struct locale * lang = (struct locale *)data;
|
|
const char * key = msgid;
|
|
if (msgctxt) {
|
|
key = mkname(msgctxt, msgid);
|
|
}
|
|
locale_setstring(lang, key, msgstr);
|
|
return 0;
|
|
}
|
|
|
|
static int include_po(const char *uri) {
|
|
char name[PATH_MAX];
|
|
const char *filename = uri_to_file(uri, name, sizeof(name));
|
|
const char *pos = strstr(filename, ".po");
|
|
if (pos) {
|
|
size_t len;
|
|
const char *str = --pos;
|
|
char lname[8];
|
|
|
|
while (str > filename && *str != '.') --str;
|
|
len = (size_t)(pos - str);
|
|
if (len < sizeof(lname)) {
|
|
struct locale * lang;
|
|
memcpy(lname, str+1, len);
|
|
lname[len] = 0;
|
|
lang = get_or_create_locale(lname);
|
|
if (lang) {
|
|
int err = pofile_read(filename, add_po_string, lang);
|
|
if (err < 0) {
|
|
log_error("could not parse translations from %s", uri);
|
|
}
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void json_include(cJSON *json) {
|
|
cJSON *child;
|
|
if (json->type != cJSON_Array) {
|
|
log_error("config is not a json array: %d", json->type);
|
|
return;
|
|
}
|
|
for (child = json->child; child; child = child->next) {
|
|
const char *uri = child->valuestring;
|
|
int err;
|
|
|
|
if (strstr(uri, ".po") != NULL) {
|
|
err = include_po(uri);
|
|
}
|
|
else if (strstr(uri, ".xml") != NULL) {
|
|
err = include_xml(uri);
|
|
}
|
|
else {
|
|
err = include_json(uri);
|
|
}
|
|
if (err != 0) {
|
|
log_error("no data found in %s", uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
void json_config(cJSON *json) {
|
|
cJSON *child;
|
|
assert(json);
|
|
if (json->type != cJSON_Object) {
|
|
log_error("config is not a json object: %d", json->type);
|
|
return;
|
|
}
|
|
reset_locales();
|
|
for (child = json->child; child; child = child->next) {
|
|
if (strcmp(child->string, "races") == 0) {
|
|
json_races(child);
|
|
}
|
|
else if (strcmp(child->string, "items") == 0) {
|
|
json_items(child);
|
|
}
|
|
else if (strcmp(child->string, "include") == 0) {
|
|
json_include(child);
|
|
}
|
|
else if (strcmp(child->string, "ships") == 0) {
|
|
json_ships(child);
|
|
}
|
|
else if (strcmp(child->string, "strings") == 0) {
|
|
json_strings(child);
|
|
}
|
|
else if (strcmp(child->string, "calendar") == 0) {
|
|
json_calendar(child);
|
|
}
|
|
else if (strcmp(child->string, "directions") == 0) {
|
|
json_directions(child);
|
|
}
|
|
else if (strcmp(child->string, "keywords") == 0) {
|
|
json_keywords(child);
|
|
}
|
|
else if (strcmp(child->string, "settings") == 0) {
|
|
json_settings(child);
|
|
}
|
|
else if (strcmp(child->string, "skills") == 0) {
|
|
json_skills(child);
|
|
}
|
|
else if (strcmp(child->string, "buildings") == 0) {
|
|
json_buildings(child);
|
|
}
|
|
else if (strcmp(child->string, "spells") == 0) {
|
|
json_spells(child);
|
|
}
|
|
else if (strcmp(child->string, "prefixes") == 0) {
|
|
json_prefixes(child);
|
|
}
|
|
else if (strcmp(child->string, "disabled") == 0) {
|
|
json_disable_features(child);
|
|
}
|
|
else if (strcmp(child->string, "terrains") == 0) {
|
|
json_terrains(child);
|
|
init_terrains();
|
|
}
|
|
else {
|
|
log_error("config contains unknown attribute %s", child->string);
|
|
}
|
|
}
|
|
}
|
|
|