#include #include #include #include "save.h" #include "alchemy.h" #include "alliance.h" #include "ally.h" #include "building.h" #include "calendar.h" #include "connection.h" #include "faction.h" #include "group.h" #include "item.h" #include "magic.h" #include "messages.h" #include "move.h" #include "objtypes.h" #include "order.h" #include "pathfinder.h" #include "plane.h" #include "race.h" #include "region.h" #include "resources.h" #include "ship.h" #include "skill.h" #include "spell.h" #include "spellbook.h" #include "terrain.h" #include "terrainid.h" /* only for conversion code */ #include "unit.h" #include "lighthouse.h" /* attributes includes */ #include #include #include #include #include /* util includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* libc includes */ #include #include #include #include #include #include #define xisdigit(c) (((c) >= '0' && (c) <= '9') || (c) == '-') #define ESCAPE_FIX #define MAXORDERS 256 #define MAXPERSISTENT 128 /* exported symbols symbols */ int firstx = 0, firsty = 0; static void read_alliances(gamedata *data) { storage *store = data->store; char pbuf[8]; int id, terminator = 0; if (data->version < ALLIANCELEADER_VERSION) { terminator = atoi36("end"); READ_STR(store, pbuf, sizeof(pbuf)); id = atoi36(pbuf); } else { READ_INT(store, &id); } while (id != terminator) { char aname[128]; alliance *al; READ_STR(store, aname, sizeof(aname)); al = new_alliance(id, aname); if (data->version >= OWNER_2_VERSION) { READ_INT(store, &al->flags); } if (data->version >= ALLIANCELEADER_VERSION) { read_faction_reference(data, &al->_leader); READ_INT(store, &id); } else { READ_STR(store, pbuf, sizeof(pbuf)); id = atoi36(pbuf); } } } void read_planes(gamedata *data) { struct storage *store = data->store; int nread; char name[32]; /* Planes */ planes = NULL; READ_INT(store, &nread); while (--nread >= 0) { int id; plane *pl; READ_INT(store, &id); pl = getplanebyid(id); if (pl == NULL) { pl = calloc(1, sizeof(plane)); if (!pl) abort(); } else { log_warning("the plane with id=%d already exists.", id); } pl->id = id; READ_STR(store, name, sizeof(name)); pl->name = str_strdup(name); READ_INT(store, &pl->minx); READ_INT(store, &pl->maxx); READ_INT(store, &pl->miny); READ_INT(store, &pl->maxy); READ_INT(store, &pl->flags); /* read watchers */ if (data->version < FIX_WATCHERS_VERSION) { char rname[64]; /* before this version, watcher storage was pretty broken. we are incompatible and don't read them */ for (;;) { READ_TOK(store, rname, sizeof(rname)); if (strcmp(rname, "end") == 0) { break; /* this is most likely the end of the list */ } else { log_error( ("This datafile contains watchers, but we are unable to read them.")); } } } else { /* WATCHERS - eliminated in February 2016, ca. turn 966 */ if (data->version < NOWATCH_VERSION) { int fno; READ_INT(data->store, &fno); while (fno) { READ_INT(data->store, &fno); } } } read_attribs(data, &pl->attribs, pl); if (pl->id != 1094969858) { /* Regatta */ addlist(&planes, pl); } } } void write_planes(storage *store) { plane *pl; /* Write planes */ WRITE_INT(store, listlen(planes)); for (pl = planes; pl; pl = pl->next) { WRITE_INT(store, pl->id); WRITE_STR(store, pl->name); WRITE_INT(store, pl->minx); WRITE_INT(store, pl->maxx); WRITE_INT(store, pl->miny); WRITE_INT(store, pl->maxy); WRITE_INT(store, pl->flags); #if RELEASE_VERSION < NOWATCH_VERSION write_faction_reference(NULL, store); /* mark the end of pl->watchers (gone since T966) */ #endif a_write(store, pl->attribs, pl); WRITE_SECTION(store); } } void write_alliances(gamedata *data) { alliance *al = alliances; while (al) { if (al->_leader) { WRITE_INT(data->store, al->id); WRITE_STR(data->store, al->name); WRITE_INT(data->store, (int)al->flags); write_faction_reference(al->_leader, data->store); WRITE_SECTION(data->store); } al = al->next; } WRITE_INT(data->store, 0); WRITE_SECTION(data->store); } static void read_owner(gamedata *data, region_owner ** powner) { int since_turn; READ_INT(data->store, &since_turn); if (since_turn >= 0) { region_owner *owner = malloc(sizeof(region_owner)); if (!owner) abort(); owner->since_turn = since_turn; READ_INT(data->store, &owner->morale_turn); if (data->version >= MOURNING_VERSION) { READ_INT(data->store, &owner->flags); } else { owner->flags = 0; } if (data->version >= OWNER_3_VERSION) { read_faction_reference(data, &owner->last_owner); } else if (data->version >= OWNER_2_VERSION) { int id; alliance *a; READ_INT(data->store, &id); a = id ? findalliance(id) : NULL; /* don't know which faction, take the leader */ owner->last_owner = a ? a->_leader : NULL; } else { owner->last_owner = NULL; } read_faction_reference(data, &owner->owner); *powner = owner; } else { *powner = 0; } } static void write_owner(gamedata *data, region_owner * owner) { if (owner) { WRITE_INT(data->store, owner->since_turn); if (owner->since_turn >= 0) { faction *f = owner->last_owner; WRITE_INT(data->store, owner->morale_turn); WRITE_INT(data->store, owner->flags); write_faction_reference((f && f->_alive) ? f : NULL, data->store); f = owner->owner; write_faction_reference((f && f->_alive) ? f : NULL, data->store); } } else { WRITE_INT(data->store, -1); } } int current_turn(void) { char zText[PATH_MAX]; int cturn = 0; FILE *F; path_join(basepath(), "turn", zText, sizeof(zText)); F = fopen(zText, "r"); if (!F) { perror(zText); } else { int c = fscanf(F, "%4d\n", &cturn); fclose(F); if (c != 1) { return -1; } } return cturn; } static void writeorder(gamedata *data, const struct order *ord, const struct locale *lang) { char obuf[1024]; write_order(ord, lang, obuf, sizeof(obuf)); if (obuf[0]) WRITE_STR(data->store, obuf); } static void read_skill(gamedata *data, skill *sv) { int val; READ_INT(data->store, &val); assert(val < MAXSKILLS); sv->id = (skill_t)val; if (sv->id != NOSKILL) { READ_INT(data->store, &val); assert(val < CHAR_MAX); sv->old = sv->level = val; READ_INT(data->store, &val); assert(val < CHAR_MAX); sv->weeks = val; } } static int skill_cmp(const void *a, const void *b) { const skill * sa = (const skill *)a; const skill * sb = (const skill *)b; return sa->id - sb->id; } static void read_skills(gamedata *data, unit *u) { if (data->version < SKILLSORT_VERSION) { skill skills[MAXSKILLS], *sv = skills; u->skill_size = 0; for (;;) { read_skill(data, sv); if (sv->id == NOSKILL) break; if (sv->level > 0) { ++sv; ++u->skill_size; } } if (u->skill_size > 0) { size_t sz = u->skill_size * sizeof(skill); qsort(skills, u->skill_size, sizeof(skill), skill_cmp); u->skills = (skill *)malloc(sz); memcpy(u->skills, skills, sz); } } else { int i; READ_INT(data->store, &u->skill_size); u->skills = (skill *)malloc(sizeof(skill)*u->skill_size); for (i = 0; i != u->skill_size; ++i) { skill *sv = u->skills + i; read_skill(data, sv); } } } static void write_skills(gamedata *data, const unit *u) { int i; skill_t sk = NOSKILL; WRITE_INT(data->store, u->skill_size); for (i = 0; i != u->skill_size; ++i) { skill *sv = u->skills + i; #ifndef NDEBUG assert(sv->id > sk); sk = sv->id; assert(sv->weeks <= sv->level * 2 + 1); #endif WRITE_INT(data->store, sv->id); WRITE_INT(data->store, sv->level); WRITE_INT(data->store, sv->weeks); } } unit *read_unit(gamedata *data) { unit *u; const race *rc; int number, n, p, bn, sn; order **orderp; char obuf[DISPLAYSIZE]; faction *f; char rname[32]; static const struct race * rc_demon; static int config; if (rc_changed(&config)) { rc_demon = get_race(RC_DAEMON); } READ_INT(data->store, &n); if (n <= 0) { log_error("data contains invalid unit %d.", n); assert(n > 0); return 0; } u = findunit(n); if (u) { log_error("reading unit %s that already exists.", unitname(u)); while (u->attribs) { a_remove(&u->attribs, u->attribs); } while (u->items) { i_free(i_remove(&u->items, u->items)); } free(u->skills); u->skills = 0; u->skill_size = 0; u_setfaction(u, NULL); } else { u = unit_create(n); } READ_INT(data->store, &n); f = findfaction(n); if (f != u->faction) { u_setfaction(u, f); } if (!u->faction) { log_error("unit %s has faction == NULL", itoa36(u->no)); return 0; } READ_STR(data->store, obuf, sizeof(obuf)); if (unicode_utf8_trim(obuf)!=0) { log_warning("trim unit %s name to '%s'", itoa36(u->no), obuf); } unit_setname(u, obuf[0] ? obuf : NULL); READ_STR(data->store, obuf, sizeof(obuf)); if (unicode_utf8_trim(obuf)!=0) { log_warning("trim unit %s info to '%s'", itoa36(u->no), obuf); } unit_setinfo(u, obuf[0] ? obuf : NULL); READ_INT(data->store, &number); set_number(u, number); READ_INT(data->store, &n); u->age = n; READ_TOK(data->store, rname, sizeof(rname)); rc = rc_find(rname); assert(rc); u_setrace(u, rc); READ_TOK(data->store, rname, sizeof(rname)); if (rname[0]) u->irace = rc_find(rname); else u->irace = NULL; READ_INT(data->store, &bn); READ_INT(data->store, &sn); if (sn <= 0 && bn > 0) { building * b = findbuilding(bn); if (b) { u_set_building(u, b); if (fval(u, UFL_OWNER)) { building_set_owner(u); } } else { log_error("read_unit: unit in unkown building '%s'", itoa36(bn)); } } if (sn > 0) { ship * sh = findship(sn); if (sh && sh->number > 0) { u_set_ship(u, sh); if (fval(u, UFL_OWNER)) { ship_set_owner(u); } } else { log_error("read_unit: unit in unkown ship '%s'", itoa36(sn)); } } READ_INT(data->store, &n); unit_setstatus(u, (status_t)n); READ_INT(data->store, &u->flags); u->flags &= UFL_SAVEMASK; if ((u->flags & UFL_ANON_FACTION) && !rule_stealth_anon()) { /* if this rule is broken, then fix broken units */ u->flags -= UFL_ANON_FACTION; log_warning("%s was anonymous.", unitname(u)); } /* Persistente Befehle einlesen */ free_orders(&u->orders); READ_STR(data->store, obuf, sizeof(obuf)); p = n = 0; orderp = &u->orders; while (obuf[0]) { order *ord = parse_order(obuf, u->faction->locale); if (ord != NULL) { if (++n < MAXORDERS) { if (!is_persistent(ord) || ++p < MAXPERSISTENT) { *orderp = ord; orderp = &ord->next; ord = NULL; } else if (p == MAXPERSISTENT) { log_info("%s had %d or more persistent orders", unitname(u), MAXPERSISTENT); } } else if (n == MAXORDERS) { log_info("%s had %d or more orders", unitname(u), MAXORDERS); } if (ord != NULL) { free_order(ord); } } READ_STR(data->store, obuf, sizeof(obuf)); } set_order(&u->thisorder, NULL); assert(u_race(u)); read_skills(data, u); read_items(data->store, &u->items); READ_INT(data->store, &u->hp); if (u->hp < u->number) { log_error("Einheit %s hat %u Personen, und %u Trefferpunkte", itoa36(u->no), u->number, u->hp); u->hp = u->number; } read_attribs(data, &u->attribs, u); if (rc_demon && data->version < FIX_SHAPESHIFT_VERSION) { if (u_race(u) == rc_demon) { const char *zRace = get_racename(u->attribs); if (zRace) { const struct race *rc = rc_find(zRace); if (rc) { set_racename(&u->attribs, NULL); u->irace = rc; } } } } resolve_unit(u); return u; } void write_unit(gamedata *data, const unit * u) { const char *str; order *ord; int p = 0; unsigned int flags = u->flags & UFL_SAVEMASK; const race *irace = u_irace(u); write_unit_reference(u, data->store); assert(u->faction->_alive); write_faction_reference(u->faction, data->store); WRITE_STR(data->store, u->_name); str = unit_getinfo(u); WRITE_STR(data->store, str ? str : ""); WRITE_INT(data->store, u->number); WRITE_INT(data->store, u->age); WRITE_TOK(data->store, u_race(u)->_name); WRITE_TOK(data->store, (irace && irace != u_race(u)) ? irace->_name : ""); write_building_reference(u->building, data->store); write_ship_reference(u->ship, data->store); WRITE_INT(data->store, u->status); if (u->building && u == building_owner(u->building)) flags |= UFL_OWNER; if (u->ship && u == ship_owner(u->ship)) flags |= UFL_OWNER; WRITE_INT(data->store, flags); WRITE_SECTION(data->store); for (ord = u->old_orders; ord; ord = ord->next) { if (++p < MAXPERSISTENT) { writeorder(data, ord, u->faction->locale); } else { log_info("%s had %d or more persistent orders", unitname(u), MAXPERSISTENT); break; } } for (ord = u->orders; ord; ord = ord->next) { keyword_t kwd = getkeyword(ord); if (u->old_orders && is_repeated(kwd)) continue; /* has new defaults */ if (is_persistent(ord)) { if (++p < MAXPERSISTENT) { writeorder(data, ord, u->faction->locale); } else { log_info("%s had %d or more persistent orders", unitname(u), MAXPERSISTENT); break; } } } /* write an empty string to terminate the list */ WRITE_STR(data->store, ""); WRITE_SECTION(data->store); assert(u_race(u)); write_skills(data, u); write_items(data->store, u->items); WRITE_SECTION(data->store); if (u->hp == 0 && data->version < NORCSPELL_VERSION) { log_error("unit %s has 0 hitpoints, adjusting.", itoa36(u->no)); ((unit *)u)->hp = u->number; } WRITE_INT(data->store, u->hp); WRITE_SECTION(data->store); write_attribs(data->store, u->attribs, u); WRITE_SECTION(data->store); } static void read_regioninfo(gamedata *data, const region *r, char *info, size_t len) { READ_STR(data->store, info, len); if (unicode_utf8_trim(info) != 0) { log_warning("trim region %d info to '%s'", r->uid, info); } } static void fix_resource_levels(region *r) { struct terrain_production *p; for (p = r->terrain->production; p->type; ++p) { char *end; long start = (int)strtol(p->startlevel, &end, 10); if (*end == '\0') { rawmaterial *res; for (res = r->resources; res; res = res->next) { if (p->type == res->rtype) { if (start != res->startlevel) { log_debug("setting resource start level for %s in %s to %d", res->rtype->_name, regionname(r, NULL), start); res->startlevel = start; } } } } } } static void fix_resource_bases(region *r) { struct terrain_production *p; for (p = r->terrain->production; p->type; ++p) { char *end; long base = (int)strtol(p->base, &end, 10); if (*end == '\0') { rawmaterial *res; for (res = r->resources; res; res = res->next) { if (p->type == res->rtype) { if (base != res->base) { log_debug("setting resource base for %s in %s to %d", res->rtype->_name, regionname(r, NULL), base); res->base = base; } } } } } } static region *readregion(gamedata *data, int x, int y) { region *r; const terrain_type *terrain; char name[NAMESIZE]; char info[DISPLAYSIZE]; int uid = 0; int n; READ_INT(data->store, &uid); r = findregionbyid(uid); if (r == NULL) { r = region_create(uid); } else { /* make sure this was not read earlier */ assert(r->next == NULL); assert(r->attribs == NULL); assert(r->land == NULL); assert(r->resources == NULL); } /* add region to the global list: */ add_region(r, x, y); if (data->version < LANDDISPLAY_VERSION) { read_regioninfo(data, r, info, sizeof(info)); } else { info[0] = '\0'; } READ_STR(data->store, name, sizeof(name)); terrain = get_terrain(name); if (terrain == NULL) { log_error("Unknown terrain '%s'", name); assert(!"unknown terrain"); } r->terrain = terrain; READ_INT(data->store, &r->flags); READ_INT(data->store, &n); r->age = (unsigned short)n; if (fval(r->terrain, LAND_REGION)) { r->land = calloc(1, sizeof(land_region)); READ_STR(data->store, name, sizeof(name)); if (unicode_utf8_trim(name) != 0) { log_warning("trim region %d name to '%s'", uid, name); }; r->land->name = str_strdup(name); } if (r->land) { int i; rawmaterial **pres = &r->resources; if (data->version >= LANDDISPLAY_VERSION) { read_regioninfo(data, r, info, sizeof(info)); } region_setinfo(r, info); READ_INT(data->store, &i); if (i < 0) { log_error("number of trees in %s is %d.", regionname(r, NULL), i); i = 0; } rsettrees(r, 0, i); READ_INT(data->store, &i); if (i < 0) { log_error("number of young trees in %s is %d.", regionname(r, NULL), i); i = 0; } rsettrees(r, 1, i); READ_INT(data->store, &i); if (i < 0) { log_error("number of seeds in %s is %d.", regionname(r, NULL), i); i = 0; } rsettrees(r, 2, i); READ_INT(data->store, &i); rsethorses(r, i); assert(*pres == NULL); for (;;) { rawmaterial *res; READ_STR(data->store, name, sizeof(name)); if (strcmp(name, "end") == 0) break; res = malloc(sizeof(rawmaterial)); if (!res) abort(); res->rtype = rt_find(name); if (!res->rtype && strncmp("rm_", name, 3) == 0) { res->rtype = rt_find(name + 3); } if (!res->rtype || !res->rtype->raw) { log_error("invalid resourcetype %s in data.", name); } assert(res->rtype); READ_INT(data->store, &n); res->level = n; READ_INT(data->store, &n); res->amount = n; res->flags = 0; READ_INT(data->store, &n); res->startlevel = n; READ_INT(data->store, &n); res->base = n; READ_INT(data->store, &n); res->divisor = n; *pres = res; pres = &res->next; } *pres = NULL; READ_STR(data->store, name, sizeof(name)); if (strcmp(name, "noherb") != 0) { const resource_type *rtype = rt_find(name); assert(rtype && rtype->itype && fval(rtype->itype, ITF_HERB)); rsetherbtype(r, rtype->itype); } else { rsetherbtype(r, NULL); } READ_INT(data->store, &n); rsetherbs(r, n); READ_INT(data->store, &n); if (n < 0) { /* bug 2182 */ log_error("data has negative peasants: %d in %s", n, regionname(r, 0)); rsetpeasants(r, 0); } else { rsetpeasants(r, n); } READ_INT(data->store, &n); rsetmoney(r, n); } else { if (info[0]) { log_error("%s %d has a description: %s", r->terrain->_name, r->uid, info); } } assert(r->terrain != NULL); if (r->land) { int n; for (;;) { const struct resource_type *rtype; READ_STR(data->store, name, sizeof(name)); if (!strcmp(name, "end")) break; rtype = rt_find(name); assert(rtype && rtype->ltype); READ_INT(data->store, &n); r_setdemand(r, rtype->ltype, n); } if (!r->land->demands) { fix_demand(r); } if (data->version < NOLANDITEM_VERSION) { read_items(data->store, NULL); } if (data->version >= REGIONOWNER_VERSION) { READ_INT(data->store, &n); if (n < 0) n = 0; region_set_morale(r, n, -1); read_owner(data, &r->land->ownership); } } read_attribs(data, &r->attribs, r); if (r->resources) { if (data->version < FIX_STARTLEVEL_VERSION) { /* we had some badly made rawmaterials before this */ fix_resource_levels(r); } if (data->version < FIX_RES_BASE_VERSION) { /* we had some badly made rawmaterials before this */ fix_resource_bases(r); } } return r; } region *read_region(gamedata *data) { storage *store = data->store; region *r; int x, y; READ_INT(store, &x); READ_INT(store, &y); r = readregion(data, x, y); resolve_region(r); return r; } void writeregion(gamedata *data, const region * r) { assert(r); assert(data); WRITE_INT(data->store, r->uid); WRITE_TOK(data->store, r->terrain->_name); WRITE_INT(data->store, r->flags & RF_SAVEMASK); WRITE_INT(data->store, r->age); WRITE_SECTION(data->store); if (r->land) { const item_type *rht; struct demand *demand; rawmaterial *res = r->resources; WRITE_STR(data->store, (const char *)r->land->name); WRITE_STR(data->store, region_getinfo(r)); assert(rtrees(r, 0) >= 0); assert(rtrees(r, 1) >= 0); assert(rtrees(r, 2) >= 0); WRITE_INT(data->store, rtrees(r, 0)); WRITE_INT(data->store, rtrees(r, 1)); WRITE_INT(data->store, rtrees(r, 2)); WRITE_INT(data->store, rhorses(r)); while (res) { WRITE_TOK(data->store, res->rtype->_name); WRITE_INT(data->store, res->level); WRITE_INT(data->store, res->amount); WRITE_INT(data->store, res->startlevel); WRITE_INT(data->store, res->base); WRITE_INT(data->store, res->divisor); res = res->next; } WRITE_TOK(data->store, "end"); rht = rherbtype(r); if (rht) { WRITE_TOK(data->store, resourcename(rht->rtype, 0)); } else { WRITE_TOK(data->store, "noherb"); } WRITE_INT(data->store, rherbs(r)); WRITE_INT(data->store, rpeasants(r)); WRITE_INT(data->store, rmoney(r)); for (demand = r->land->demands; demand; demand = demand->next) { WRITE_TOK(data->store, resourcename(demand->type->itype->rtype, 0)); WRITE_INT(data->store, demand->value); } WRITE_TOK(data->store, "end"); WRITE_SECTION(data->store); WRITE_INT(data->store, region_get_morale(r)); write_owner(data, r->land->ownership); WRITE_SECTION(data->store); } write_attribs(data->store, r->attribs, r); WRITE_SECTION(data->store); } void write_region(gamedata *data, const region *r) { storage *store = data->store; WRITE_INT(store, r->x); WRITE_INT(store, r->y); writeregion(data, r); } int get_spell_level_faction(const spell * sp, void * cbdata) { static spellbook * common = 0; spellbook * book; faction * f = (faction *)cbdata; spellbook_entry * sbe; book = get_spellbook(magic_school[f->magiegebiet]); if (book) { sbe = spellbook_get(book, sp); if (sbe) return sbe->level; } if (!common) { common = get_spellbook(magic_school[M_COMMON]); } sbe = spellbook_get(common, sp); if (sbe) { return sbe->level; } else { log_error("read_spellbook: faction '%s' has a spell with unknown level: '%s'", factionname(f), sp->sname); } return 0; } static char * getpasswd(int fno) { FILE * F = fopen("passwords.txt", "r"); if (F) { const char *prefix = itoa36(fno); size_t len = strlen(prefix); while (!feof(F)) { char line[80]; fgets(line, sizeof(line), F); if (line[len] == ':' && strncmp(prefix, line, len) == 0) { size_t slen = strlen(line) - 1; assert(line[slen] == '\n'); line[slen] = 0; fclose(F); return str_strdup(line + len + 1); } } fclose(F); } return NULL; } static void read_password(gamedata *data, faction *f) { char name[128]; READ_STR(data->store, name, sizeof(name)); if (name[0] == '$' && data->version == BADCRYPT_VERSION) { char * pass = getpasswd(f->no); if (pass) { faction_setpassword(f, password_hash(pass, PASSWORD_DEFAULT)); free(pass); /* TODO: remove this allocation! */ } else { log_error("data version is BADCRYPT but %s not in password.txt", itoa36(f->no)); } } else { faction_setpassword(f, (data->version >= CRYPT_VERSION) ? name : password_hash(name, PASSWORD_DEFAULT)); } } void _test_read_password(gamedata *data, faction *f) { read_password(data, f); } static void write_password(gamedata *data, const faction *f) { WRITE_TOK(data->store, faction_getpassword(f)); } void _test_write_password(gamedata *data, const faction *f) { write_password(data, f); } faction *read_faction(gamedata * data) { int planes, n; faction *f; char name[DISPLAYSIZE]; READ_INT(data->store, &n); assert(n > 0); f = findfaction(n); if (f == NULL) { f = faction_create(n); } else { f->allies = NULL; /* FIXME: mem leak */ while (f->attribs) { a_remove(&f->attribs, f->attribs); } } READ_INT(data->store, &f->uid); if (data->version < FACTION_UID_VERSION) { f->uid = 0; } if (data->version >= SPELL_LEVEL_VERSION) { READ_INT(data->store, &f->max_spelllevel); } if (alliances || data->version >= OWNER_2_VERSION) { int allianceid; READ_INT(data->store, &allianceid); if (allianceid > 0) f->alliance = findalliance(allianceid); if (f->alliance) { alliance *al = f->alliance; if (al->flags & ALF_NON_ALLIED) { assert(!al->members || !"non-allied dummy-alliance has more than one member"); } selist_push(&al->members, f); } else if (rule_region_owners()) { /* compat fix for non-allied factions */ alliance *al = makealliance(0, NULL); setalliance(f, al); } if (data->version >= OWNER_2_VERSION) { READ_INT(data->store, &f->alliance_joindate); } else { f->alliance_joindate = turn - 10; /* we're guessing something safe here */ } } READ_STR(data->store, name, sizeof(name)); if (unicode_utf8_trim(name)!=0) { log_warning("trim faction %s name to '%s'", itoa36(f->no), name); }; f->name = str_strdup(name); READ_STR(data->store, name, sizeof(name)); if (unicode_utf8_trim(name)!=0) { log_warning("trim faction %s banner to '%s'", itoa36(f->no), name); }; faction_setbanner(f, name); log_debug(" - Lese Partei %s (%s)", f->name, itoa36(f->no)); READ_STR(data->store, name, sizeof(name)); if (check_email(name) == 0) { faction_setemail(f, name); } else if (name[0]) { log_warning("Invalid email address for faction %s: %s", itoa36(f->no), name); faction_setemail(f, NULL); } read_password(data, f); if (data->version < NOOVERRIDE_VERSION) { READ_STR(data->store, 0, 0); } READ_STR(data->store, name, sizeof(name)); f->locale = get_locale(name); if (!f->locale) f->locale = default_locale; READ_INT(data->store, &f->lastorders); READ_INT(data->store, &f->age); READ_STR(data->store, name, sizeof(name)); f->race = rc_find(name); if (!f->race) { log_error("unknown race in data: %s", name); } assert(f->race); READ_INT(data->store, &n); f->magiegebiet = (magic_t)n; if (data->version < FOSS_VERSION) { /* ignore karma */ READ_INT(data->store, &n); } READ_INT(data->store, &f->flags); if (data->version < INTFLAGS_VERSION) { if (f->no == 0 || f->no == 666) { f->flags = FFL_NPC | FFL_NOIDLEOUT; } } read_attribs(data, &f->attribs, f); read_items(data->store, &f->items); for (;;) { READ_TOK(data->store, name, sizeof(name)); if (strcmp("end", name) == 0) break; READ_INT(data->store, &n); /* there used to be a level here, which is now ignored */ } READ_INT(data->store, &planes); while (--planes >= 0) { int id, ux, uy; READ_INT(data->store, &id); READ_INT(data->store, &ux); READ_INT(data->store, &uy); faction_setorigin(f, id, ux, uy); } f->newbies = 0; READ_INT(data->store, &n); f->options = n; n = WANT_OPTION(O_REPORT) | WANT_OPTION(O_COMPUTER); if ((f->options & n) == 0) { /* Kein Report eingestellt, Fehler */ f->options |= n; } if (data->version < JSON_REPORT_VERSION) { /* mistakes were made in the past*/ f->options &= ~WANT_OPTION(O_JSON); } read_allies(data, &f->allies); read_groups(data, f); f->spellbook = 0; if (data->version >= REGIONOWNER_VERSION) { read_spellbook(FactionSpells() ? &f->spellbook : 0, data, get_spell_level_faction, (void *)f); } return f; } void write_faction(gamedata *data, const faction * f) { origin *ur; assert(f->_alive); assert(f->no > 0 && f->no <= MAX_UNIT_NR); WRITE_INT(data->store, f->no); WRITE_INT(data->store, f->uid); #if RELEASE_VERSION >= SPELL_LEVEL_VERSION WRITE_INT(data->store, f->max_spelllevel); #endif if (f->alliance) { WRITE_INT(data->store, f->alliance->id); if (f->alliance->flags & ALF_NON_ALLIED) { assert(f == f->alliance->_leader || !"non-allied faction is not leader of its own dummy-alliance."); } } else { WRITE_INT(data->store, 0); } WRITE_INT(data->store, f->alliance_joindate); WRITE_STR(data->store, f->name); WRITE_STR(data->store, faction_getbanner(f)); WRITE_STR(data->store, f->email?f->email:""); write_password(data, f); WRITE_TOK(data->store, locale_name(f->locale)); WRITE_INT(data->store, f->lastorders); WRITE_INT(data->store, f->age); WRITE_TOK(data->store, f->race->_name); WRITE_SECTION(data->store); WRITE_INT(data->store, f->magiegebiet); WRITE_INT(data->store, f->flags & FFL_SAVEMASK); write_attribs(data->store, f->attribs, f); WRITE_SECTION(data->store); write_items(data->store, f->items); WRITE_SECTION(data->store); WRITE_TOK(data->store, "end"); WRITE_SECTION(data->store); WRITE_INT(data->store, listlen(f->origin)); for (ur = f->origin; ur; ur = ur->next) { WRITE_INT(data->store, ur->id); WRITE_INT(data->store, ur->x); WRITE_INT(data->store, ur->y); } WRITE_SECTION(data->store); WRITE_INT(data->store, f->options & ~WANT_OPTION(O_DEBUG)); WRITE_SECTION(data->store); write_allies(data, f->allies); WRITE_SECTION(data->store); write_groups(data, f); write_spellbook(f->spellbook, data->store); } static int cb_sb_maxlevel(spellbook_entry *sbe, void *cbdata) { faction *f = (faction *)cbdata; if (sbe->level > f->max_spelllevel) { f->max_spelllevel = sbe->level; } return 0; } int readgame(const char *filename) { int n = -2, stream_version; char path[PATH_MAX]; gamedata gdata = { 0 }; storage store; stream strm; FILE *F; size_t sz; log_debug("- reading game data from %s", filename); path_join(datapath(), filename, path, sizeof(path)); F = fopen(path, "rb"); if (!F) { perror(path); return -1; } sz = fread(&gdata.version, sizeof(int), 1, F); if (sz == 1) { sz = fread(&stream_version, sizeof(int), 1, F); assert((sz == 1 && stream_version == STREAM_VERSION) || !"unsupported data format"); assert(gdata.version >= MIN_VERSION || !"unsupported data format"); assert(gdata.version <= MAX_VERSION || !"unsupported data format"); fstream_init(&strm, F); binstore_init(&store, &strm); gdata.store = &store; if (gdata.version >= BUILDNO_VERSION) { int build; READ_INT(&store, &build); log_debug("data in %s created with build %d.", filename, build); } n = read_game(&gdata); binstore_done(&store); fstream_done(&strm); } else { fclose(F); } return n; } void write_building(gamedata *data, const building *b) { storage *store = data->store; write_building_reference(b, store); WRITE_STR(store, b->name); WRITE_STR(store, b->display ? b->display : ""); assert(b->size > 0); WRITE_INT(store, b->size); WRITE_TOK(store, b->type->_name); write_attribs(store, b->attribs, b); } struct building *read_building(gamedata *data) { char name[DISPLAYSIZE]; building *b; storage * store = data->store; b = (building *)calloc(1, sizeof(building)); if (!b) abort(); READ_INT(store, &b->no); bhash(b); READ_STR(store, name, sizeof(name)); if (unicode_utf8_trim(name)!=0) { log_warning("trim building %s name to '%s'", itoa36(b->no), name); } b->name = str_strdup(name); READ_STR(store, name, sizeof(name)); if (unicode_utf8_trim(name)!=0) { log_warning("trim building %s info to '%s'", itoa36(b->no), name); } b->display = str_strdup(name); READ_INT(store, &b->size); if (b->size < 1) { log_warning("trim building %s had size %d", itoa36(b->no), b->size); b->size = 1; } READ_STR(store, name, sizeof(name)); b->type = bt_find(name); if (!b->type) { log_error("building %d has unknown type %s", b->no, name); b->type = bt_find("building"); assert(b->type); } read_attribs(data, &b->attribs, b); /* repairs, bug 2221: */ if (b->type->maxsize>0 && b->size>b->type->maxsize) { log_error("building too big: %s (%s size %d of %d), fixing.", buildingname(b), b->type->_name, b->size, b->type->maxsize); b->size = b->type->maxsize; } resolve_building(b); return b; } void write_ship(gamedata *data, const ship *sh) { storage *store = data->store; write_ship_reference(sh, store); WRITE_STR(store, (const char *)sh->name); WRITE_STR(store, sh->display ? (const char *)sh->display : ""); WRITE_TOK(store, sh->type->_name); assert(sh->number > 0); WRITE_INT(store, sh->number); WRITE_INT(store, sh->size); WRITE_INT(store, sh->damage); WRITE_INT(store, sh->flags & SFL_SAVEMASK); assert((sh->type->flags & SFL_NOCOAST) == 0 || sh->coast == NODIRECTION); WRITE_INT(store, sh->coast); write_attribs(store, sh->attribs, sh); } ship *read_ship(gamedata *data) { char name[DISPLAYSIZE]; ship *sh; int n; storage *store = data->store; sh = (ship *)calloc(1, sizeof(ship)); if (!sh) abort(); READ_INT(store, &sh->no); shash(sh); READ_STR(store, name, sizeof(name)); if (unicode_utf8_trim(name)!=0) { log_warning("trim ship %s name to '%s'", itoa36(sh->no), name); } sh->name = str_strdup(name); READ_STR(store, name, sizeof(name)); if (unicode_utf8_trim(name)!=0) { log_warning("trim ship %s info to '%s'", itoa36(sh->no), name); } sh->display = str_strdup(name); READ_STR(store, name, sizeof(name)); sh->type = st_find(name); if (sh->type == NULL) { /* old datafiles */ sh->type = st_find((const char *)LOC(default_locale, name)); } assert(sh->type || !"ship_type not registered!"); if (data->version < SHIP_NUMBER_VERSION) { sh->number = 1; } else { READ_INT(store, &sh->number); } READ_INT(store, &sh->size); READ_INT(store, &sh->damage); if (data->version >= FOSS_VERSION) { READ_INT(store, &sh->flags); } /* Attribute rekursiv einlesen */ READ_INT(store, &n); sh->coast = (direction_t)n; if (sh->type->flags & SFL_NOCOAST) { sh->coast = NODIRECTION; } read_attribs(data, &sh->attribs, sh); return sh; } static void fix_fam_triggers(unit *u) { attrib * a = a_find(u->attribs, &at_mage); attrib * am = a_find(u->attribs, &at_familiarmage); if (!am && a) { /* not a familiar, but magical */ attrib * ae = a_find(u->attribs, &at_eventhandler); if (ae) { trigger **tlist; tlist = get_triggers(ae, "destroy"); if (tlist) { trigger *t; unit *um = NULL; for (t = *tlist; t; t = t->next) { if (t->type == &tt_shock) { um = (unit *)t->data.v; break; } } if (um) { attrib *af = a_find(um->attribs, &at_familiar); log_error("%s seems to be a broken familiar of %s.", unitname(u), unitname(um)); if (af) { unit * uf = (unit *)af->data.v; log_error("%s already has a familiar: %s.", unitname(um), unitname(uf)); } else { set_familiar(um, u); } } else { log_error("%s seems to be a broken familiar with no trigger.", unitname(u)); } } } } } static void fix_clone(unit *uc) { attrib * a; assert(uc); assert(uc->number > 0); ADDMSG(&uc->faction->msgs, msg_message("dissolve_units_5", "unit region number race", uc, uc->region, uc->number, u_race(uc))); a_removeall(&uc->attribs, &at_clonemage); a = a_new(&at_unitdissolve); a->data.ca[0] = 0; a->data.ca[1] = 100; a_add(&uc->attribs, a); } static void fix_clone_mage(unit *um, const item_type *itype) { i_change(&um->items, itype, 1); change_maxspellpoints(um, 20); a_removeall(&um->attribs, &at_clone); } static void fix_clones(void) { const race *rc_clone = rc_find("clone"); const item_type *it_potion = it_find("lifepotion"); if (rc_clone && it_potion) { region *r; for (r = regions; r; r = r->next) { unit * u; for (u = r->units; u; u = u->next) { if (!fval(u, UFL_MARK)) { if (u_race(u) == rc_clone) { attrib *a = a_find(u->attribs, &at_clonemage); unit * um = NULL; fset(u, UFL_MARK); if (a) { um = (unit *)a->data.v; fset(um, UFL_MARK); } } else { attrib *a = a_find(u->attribs, &at_clone); if (a) { unit *uc = (unit *)a->data.v; fset(u, UFL_MARK); fset(uc, UFL_MARK); } } } } } for (r = regions; r; r = r->next) { unit * u; for (u = r->units; u; u = u->next) { if (fval(u, UFL_MARK)) { if (u_race(u) == rc_clone) { fix_clone(u); } else { fix_clone_mage(u, it_potion); } freset(u, UFL_MARK); } } } } } static void fix_familiars(void (*callback)(unit *)) { region *r; for (r = regions; r; r = r->next) { unit * u; for (u = r->units; u; u = u->next) { if (u->_race != u->faction->race && (u->_race->flags & RCF_FAMILIAR)) { /* unit is potentially a familiar */ callback(u); } } } } static void read_regions(gamedata *data) { storage * store = data->store; const struct building_type *bt_lighthouse = bt_find("lighthouse"); const struct race *rc_spell = rc_find("spell"); region *r; int nread; READ_INT(store, &nread); assert(nread < MAXREGIONS && nread >= 0); log_debug(" - Einzulesende Regionen: %d", nread); while (--nread >= 0) { unit **up; building **bp; ship **shp; int p; r = read_region(data); /* Burgen */ READ_INT(store, &p); if (p > 0 && !r->land) { log_debug("%s, uid=%d has %d %s", regionname(r, NULL), r->uid, p, (p == 1) ? "building" : "buildings"); } bp = &r->buildings; while (--p >= 0) { building *b = *bp = read_building(data); if (b->type == bt_lighthouse) { r->flags |= RF_LIGHTHOUSE; } b->region = r; bp = &b->next; } /* Schiffe */ READ_INT(store, &p); shp = &r->ships; while (--p >= 0) { ship *sh = *shp = read_ship(data); sh->region = r; shp = &sh->next; } *shp = 0; /* Einheiten */ READ_INT(store, &p); up = &r->units; while (--p >= 0) { unit *u = read_unit(data); if (data->version < NORCSPELL_VERSION && u_race(u) == rc_spell) { set_observer(r, u->faction, get_level(u, SK_PERCEPTION), u->age); u_setfaction(u, NULL); free_unit(u); } else { if (data->version < JSON_REPORT_VERSION) { if (u->_name && fval(u->faction, FFL_NPC)) { if (!u->_name[0] || unit_name_equals_race(u)) { unit_setname(u, NULL); } } } assert(u->region == NULL); u->region = r; *up = u; up = &u->next; update_interval(u->faction, r); } } } log_debug("updating area information for lighthouses."); for (r = regions; r; r = r->next) { if (r->flags & RF_LIGHTHOUSE) { building *b; for (b = r->buildings; b; b = b->next) { if (is_lighthouse(b->type)) { update_lighthouse(b); } } } } } static void init_factions(int data_version) { log_debug("marking factions as alive."); for (faction *f = factions; f; f = f->next) { if (f->flags & FFL_NPC) { f->_alive = true; f->magiegebiet = M_GRAY; if (f->no == 0) { int no = 666; while (findfaction(no)) ++no; log_warning("renum(monsters, %d)", no); renumber_faction(f, no); } } else { assert(f->units); for (unit *u = f->units; u; u = u->nextF) { if (data_version < SPELL_LEVEL_VERSION) { struct sc_mage *mage = get_mage(u); if (mage) { faction *f = u->faction; int skl = effskill(u, SK_MAGIC, NULL); if (f->magiegebiet == M_GRAY) { f->magiegebiet = mage_get_type(mage); log_error("faction %s had magic=gray, fixing (%s)", factionname(f), magic_school[f->magiegebiet]); } if (f->max_spelllevel < skl) { f->max_spelllevel = skl; } } } if (u->number > 0) { f->_alive = true; if (data_version >= SPELL_LEVEL_VERSION) { break; } } } if (data_version < SPELL_LEVEL_VERSION && f->spellbook) { spellbook_foreach(f->spellbook, cb_sb_maxlevel, f); } } } } static void read_factions(gamedata * data) { storage * store = data->store; int nread; faction **fp; READ_INT(store, &nread); log_debug(" - Einzulesende Parteien: %d\n", nread); fp = &factions; while (*fp) { fp = &(*fp)->next; } while (--nread >= 0) { faction *f = read_faction(data); *fp = f; fp = &f->next; } } int read_game(gamedata *data) { storage * store = data->store; if (data->version >= SAVEGAMEID_VERSION) { int gameid; READ_INT(store, &gameid); if (gameid != game_id()) { log_warning("game mismatch: datafile contains game %d, but config is for %d", gameid, game_id()); } } else { READ_STR(store, NULL, 0); } if (data->version < FIXATKEYS_VERSION) { attrib *a = NULL; read_attribs(data, &a, NULL); a_removeall(&a, NULL); } READ_INT(store, &turn); log_debug(" - reading turn %d", turn); rng_init(turn + config_get_int("game.seed", 0)); READ_INT(store, NULL); /* max_unique_id = ignore */ READ_INT(store, &nextborder); read_planes(data); read_alliances(data); read_factions(data); /* Regionen */ read_regions(data); read_borders(data); init_factions(data->version); if (data->version < FIX_CLONES_VERSION) { fix_clones(); } if (data->version < FAMILIAR_FIX_VERSION) { fix_familiars(fix_fam_triggers); } if (data->version < FAMILIAR_FIXSPELLBOOK_VERSION) { fix_familiars(fix_fam_spells); } if (data->version < FIX_MIGRANT_AURA_VERSION) { fix_familiars(fix_fam_migrant); } log_debug("Done loading turn %d.", turn); return 0; } static void clear_npc_orders(faction *f) { if (f) { unit *u; for (u = f->units; u; u = u->nextF) { free_orders(&u->orders); } } } int writegame(const char *filename) { int n; char path[PATH_MAX]; gamedata gdata; storage store; stream strm; FILE *F; create_directories(); path_join(datapath(), filename, path, sizeof(path)); /* make sure we don't overwrite an existing file (hard links) */ if (remove(path) != 0) { if (errno == ENOENT) { errno = 0; } } F = fopen(path, "wb"); if (!F) { perror(path); return -1; } gdata.store = &store; gdata.version = RELEASE_VERSION; fwrite(&gdata.version, sizeof(int), 1, F); n = STREAM_VERSION; fwrite(&n, sizeof(int), 1, F); fstream_init(&strm, F); binstore_init(&store, &strm); WRITE_INT(&store, version_no(eressea_version())); n = write_game(&gdata); binstore_done(&store); fstream_done(&strm); return n; } int write_game(gamedata *data) { storage * store = data->store; region *r; faction *f; int n; /* globale Variablen */ assert(data->version <= MAX_VERSION && data->version >= MIN_VERSION); WRITE_INT(store, game_id()); WRITE_SECTION(store); WRITE_INT(store, turn); WRITE_INT(store, 0 /* max_unique_id */); WRITE_INT(store, nextborder); write_planes(store); write_alliances(data); n = listlen(factions); WRITE_INT(store, n); WRITE_SECTION(store); log_debug(" - Schreibe %d Parteien...", n); for (f = factions; f; f = f->next) { if (fval(f, FFL_NPC)) { clear_npc_orders(f); } write_faction(data, f); WRITE_SECTION(store); } /* Write regions */ n = listlen(regions); WRITE_INT(store, n); WRITE_SECTION(store); log_debug(" - Schreibe Regionen: %d", n); for (r = regions; r; r = r->next, --n) { ship *sh; building *b; unit *u; /* plus leerzeile */ if ((n % 1024) == 0) { /* das spart extrem Zeit */ log_debug(" - Schreibe Regionen: %d", n); } WRITE_SECTION(store); write_region(data, r); WRITE_INT(store, listlen(r->buildings)); WRITE_SECTION(store); for (b = r->buildings; b; b = b->next) { assert(b->region == r); write_building(data, b); } WRITE_INT(store, listlen(r->ships)); WRITE_SECTION(store); for (sh = r->ships; sh; sh = sh->next) { assert(sh->region == r); write_ship(data, sh); } WRITE_INT(store, listlen(r->units)); WRITE_SECTION(store); for (u = r->units; u; u = u->next) { assert(u->region == r); write_unit(data, u); } } WRITE_SECTION(store); write_borders(store); WRITE_SECTION(store); return 0; }