/********************************************************************** Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ***********************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include /* utility */ #include "fcintl.h" #include "log.h" #include "mem.h" #include "registry.h" #include "shared.h" #include "support.h" /* common */ #include "base.h" #include "capability.h" #include "city.h" #include "effects.h" #include "game.h" #include "government.h" #include "map.h" #include "movement.h" #include "nation.h" #include "packets.h" #include "requirements.h" #include "specialist.h" #include "tech.h" #include "unit.h" /* server */ #include "citytools.h" #include "plrhand.h" #include "script.h" #include "aiunit.h" /* update_simple_ai_types */ #include "ruleset.h" #include "srv_main.h" /* RULESET_SUFFIX already used, no leading dot here */ #define RULES_SUFFIX "ruleset" #define SCRIPT_SUFFIX "lua" #define ADVANCE_SECTION_PREFIX "advance_" #define BUILDING_SECTION_PREFIX "building_" #define CITYSTYLE_SECTION_PREFIX "citystyle_" #define EFFECT_SECTION_PREFIX "effect_" #define GOVERNMENT_SECTION_PREFIX "government_" #define NATION_GROUP_SECTION_PREFIX "ngroup" /* without underscore? */ #define NATION_SECTION_PREFIX "nation" /* without underscore? */ #define RESOURCE_SECTION_PREFIX "resource_" #define BASE_SECTION_PREFIX "base_" #define SPECIALIST_SECTION_PREFIX "specialist_" #define TERRAIN_CAPABILITY "+1.9+2007.Oct.26" #define TERRAIN_SECTION_PREFIX "terrain_" #define UNIT_CLASS_SECTION_PREFIX "unitclass_" #define UNIT_SECTION_PREFIX "unit_" static const char name_too_long[] = "Name \"%s\" too long; truncating."; #define check_name(name) (check_strlen(name, MAX_LEN_NAME, name_too_long)) #define name_strlcpy(dst, src) \ ((void) sz_loud_strlcpy(dst, src, name_too_long)) /* avoid re-reading files */ #define MAX_SECTION_LABEL 64 #define section_strlcpy(dst, src) \ (void) loud_strlcpy(dst, src, MAX_SECTION_LABEL, name_too_long) static char *resource_sections = NULL; static char *terrain_sections = NULL; static char *base_sections = NULL; static void openload_ruleset_file(struct section_file *file, const char *whichset); static char *check_ruleset_capabilities(struct section_file *file, const char *us_capstr, const char *filename); static void load_tech_names(struct section_file *file); static void load_unit_names(struct section_file *file); static void load_building_names(struct section_file *file); static void load_government_names(struct section_file *file); static void load_terrain_names(struct section_file *file); static void load_citystyle_names(struct section_file *file); static void load_nation_names(struct section_file *file); static struct nation_city* load_city_name_list(struct section_file *file, const char *secfile_str1, const char *secfile_str2); static void load_ruleset_techs(struct section_file *file); static void load_ruleset_units(struct section_file *file); static void load_ruleset_buildings(struct section_file *file); static void load_ruleset_governments(struct section_file *file); static void load_ruleset_terrain(struct section_file *file); static void load_ruleset_cities(struct section_file *file); static void load_ruleset_effects(struct section_file *file); static void load_ruleset_game(void); static void send_ruleset_techs(struct conn_list *dest); static void send_ruleset_unit_classes(struct conn_list *dest); static void send_ruleset_units(struct conn_list *dest); static void send_ruleset_buildings(struct conn_list *dest); static void send_ruleset_terrain(struct conn_list *dest); static void send_ruleset_resources(struct conn_list *dest); static void send_ruleset_bases(struct conn_list *dest); static void send_ruleset_governments(struct conn_list *dest); static void send_ruleset_cities(struct conn_list *dest); static void send_ruleset_game(struct conn_list *dest); static bool nation_has_initial_tech(struct nation_type *pnation, struct advance *tech); static bool sanity_check_ruleset_data(void); /************************************************************************** Notifications about ruleset errors to clients. Especially important in case of internal server crashing. **************************************************************************/ static void ruleset_error(int loglevel, const char *format, ...) fc__attribute((__format__ (__printf__, 2, 3))); static void ruleset_error(int loglevel, const char *format, ...) { va_list args; va_start(args, format); vreal_freelog(loglevel, format, args); va_end(args); if (LOG_FATAL >= loglevel) { exit(EXIT_FAILURE); } } /************************************************************************** datafilename() wrapper: tries to match in two ways. Returns NULL on failure, the (statically allocated) filename on success. **************************************************************************/ static char *valid_ruleset_filename(const char *subdir, const char *name, const char *extension) { char filename[512], *dfilename; assert(subdir && name && extension); my_snprintf(filename, sizeof(filename), "%s/%s.%s", subdir, name, extension); freelog(LOG_VERBOSE, "Trying \"%s\".", filename); dfilename = datafilename(filename); if (dfilename) { return dfilename; } my_snprintf(filename, sizeof(filename), "default/%s.%s", name, extension); freelog(LOG_VERBOSE, "Trying \"%s\": default ruleset directory.", filename); dfilename = datafilename(filename); if (dfilename) { return dfilename; } my_snprintf(filename, sizeof(filename), "%s_%s.%s", subdir, name, extension); freelog(LOG_VERBOSE, "Trying \"%s\": alternative ruleset filename syntax.", filename); dfilename = datafilename(filename); if (dfilename) { return dfilename; } else { ruleset_error(LOG_FATAL, /* TRANS: message about an installation error. */ _("Could not find a readable \"%s.%s\" ruleset file."), name, extension); } return(NULL); } /************************************************************************** Do initial section_file_load on a ruleset file. "whichset" = "techs", "units", "buildings", "terrain", ... **************************************************************************/ static void openload_ruleset_file(struct section_file *file, const char *whichset) { char sfilename[512]; char *dfilename = valid_ruleset_filename(game.rulesetdir, whichset, RULES_SUFFIX); /* Need to save a copy of the filename for following message, since section_file_load() may call datafilename() for includes. */ sz_strlcpy(sfilename, dfilename); if (!section_file_load_nodup(file, sfilename)) { ruleset_error(LOG_FATAL, "\"%s\": could not load ruleset.", sfilename); } } /************************************************************************** Parse script file. **************************************************************************/ static void openload_script_file(const char *whichset) { char *dfilename = valid_ruleset_filename(game.rulesetdir, whichset, SCRIPT_SUFFIX); if (!script_do_file(dfilename)) { ruleset_error(LOG_FATAL, "\"%s\": could not load ruleset script.", dfilename); } } /************************************************************************** Ruleset files should have a capabilities string datafile.options This gets and returns that string, and checks that the required capabilities specified are satisified. **************************************************************************/ static char *check_ruleset_capabilities(struct section_file *file, const char *us_capstr, const char *filename) { char *datafile_options; datafile_options = secfile_lookup_str(file, "datafile.options"); if (!has_capabilities(us_capstr, datafile_options)) { freelog(LOG_FATAL, "\"%s\": ruleset datafile appears incompatible:", filename); freelog(LOG_FATAL, " datafile options: %s", datafile_options); freelog(LOG_FATAL, " supported options: %s", us_capstr); ruleset_error(LOG_FATAL, "Capability problem"); } if (!has_capabilities(datafile_options, us_capstr)) { freelog(LOG_FATAL, "\"%s\": ruleset datafile claims required option(s)" " that we don't support:", filename); freelog(LOG_FATAL, " datafile options: %s", datafile_options); freelog(LOG_FATAL, " supported options: %s", us_capstr); ruleset_error(LOG_FATAL, "Capability problem"); } return datafile_options; } /************************************************************************** Load a requirement list. The list is returned as a static vector (callers need not worry about freeing anything). **************************************************************************/ static struct requirement_vector *lookup_req_list(struct section_file *file, const char *sec, const char *sub, const char *rfor) { char *type, *name; int j; const char *filename; static struct requirement_vector list; filename = secfile_filename(file); requirement_vector_reserve(&list, 0); for (j = 0; (type = secfile_lookup_str_default(file, NULL, "%s.%s%d.type", sec, sub, j)); j++) { char *range; bool survives, negated; struct requirement req; name = secfile_lookup_str(file, "%s.%s%d.name", sec, sub, j); range = secfile_lookup_str(file, "%s.%s%d.range", sec, sub, j); survives = secfile_lookup_bool_default(file, FALSE, "%s.%s%d.survives", sec, sub, j); negated = secfile_lookup_bool_default(file, FALSE, "%s.%s%d.negated", sec, sub, j); req = req_from_str(type, range, survives, negated, name); if (VUT_LAST == req.source.kind) { /* Error. Log it, clear the req and continue. */ freelog(LOG_ERROR, "\"%s\" [%s] has unknown req: \"%s\" \"%s\".", filename, sec, type, name); req.source.kind = VUT_NONE; } requirement_vector_append(&list, &req); } if (j > MAX_NUM_REQS) { ruleset_error(LOG_FATAL, "Too many (%d) requirements for %s. Max is %d", j, rfor, MAX_NUM_REQS); } return &list; } /************************************************************************** Lookup a string prefix.entry in the file and return the corresponding advances pointer. If (!required), return A_NEVER for match "Never" or can't match. If (required), die when can't match. Note the first tech should have name "None" so that will always match. If description is not NULL, it is used in the warning message instead of prefix (eg pass unit->name instead of prefix="units2.u27") **************************************************************************/ static struct advance *lookup_tech(struct section_file *file, const char *prefix, const char *entry, int loglevel, const char *filename, const char *description) { char *sval; struct advance *padvance; sval = secfile_lookup_str_default(file, NULL, "%s.%s", prefix, entry); if (!sval || (LOG_FATAL < loglevel && strcmp(sval, "Never") == 0)) { padvance = A_NEVER; } else { padvance = find_advance_by_rule_name(sval); if (A_NEVER == padvance) { ruleset_error(loglevel, "\"%s\" %s %s: couldn't match \"%s\".", filename, (description ? description : prefix), entry, sval); /* ruleset_error returned only if error was not fatal. */ } } return padvance; } /************************************************************************** Lookup a string prefix.entry in the file and return the corresponding improvement pointer. If (!required), return B_NEVER for match "None" or can't match. If (required), die when can't match. If description is not NULL, it is used in the warning message instead of prefix (eg pass unit->name instead of prefix="units2.u27") **************************************************************************/ static struct impr_type *lookup_building(struct section_file *file, const char *prefix, const char *entry, int loglevel, const char *filename, const char *description) { char *sval; struct impr_type *pimprove; sval = secfile_lookup_str_default(file, NULL, "%s.%s", prefix, entry); if (!sval || (LOG_FATAL < loglevel && strcmp(sval, "None") == 0)) { pimprove = B_NEVER; } else { pimprove = find_improvement_by_rule_name(sval); if (B_NEVER == pimprove) { ruleset_error(loglevel, "\"%s\" %s %s: couldn't match \"%s\".", filename, (description ? description : prefix), entry, sval); /* ruleset_error() returned only if error was not fatal */ } } return pimprove; } /************************************************************************** Lookup a prefix.entry string vector in the file and fill in the array, which should hold MAX_NUM_UNIT_LIST items. The output array is either U_LAST terminated or full (contains MAX_NUM_UNIT_LIST items). If the vector is not found and the required parameter is set, we report it as an error, otherwise we just punt. **************************************************************************/ static void lookup_unit_list(struct section_file *file, const char *prefix, const char *entry, int loglevel, struct unit_type **output, const char *filename) { char **slist; int i, nval; /* pre-fill with NULL: */ for(i = 0; i < MAX_NUM_UNIT_LIST; i++) { output[i] = NULL; } slist = secfile_lookup_str_vec(file, &nval, "%s.%s", prefix, entry); if (nval == 0) { if (LOG_FATAL >= loglevel) { ruleset_error(LOG_FATAL, "\"%s\": missing string vector %s.%s", filename, prefix, entry); } return; } if (nval > MAX_NUM_UNIT_LIST) { ruleset_error(LOG_FATAL, "\"%s\": string vector %s.%s too long (%d, max %d)", filename, prefix, entry, nval, MAX_NUM_UNIT_LIST); } if (nval == 1 && strcmp(slist[0], "") == 0) { free(slist); return; } for (i = 0; i < nval; i++) { char *sval = slist[i]; struct unit_type *punittype = find_unit_type_by_rule_name(sval); if (!punittype) { ruleset_error(LOG_FATAL, "\"%s\" %s.%s (%d): couldn't match \"%s\".", filename, prefix, entry, i, sval); } output[i] = punittype; freelog(LOG_DEBUG, "\"%s\" %s.%s (%d): %s (%d)", filename, prefix, entry, i, sval, utype_number(punittype)); } free(slist); return; } /************************************************************************** Lookup a prefix.entry string vector in the file and fill in the array, which should hold MAX_NUM_TECH_LIST items. The output array is either A_LAST terminated or full (contains MAX_NUM_TECH_LIST items). All valid entries of the output array are guaranteed to exist. There should be at least one value, but it may be "", meaning empty list. **************************************************************************/ static void lookup_tech_list(struct section_file *file, const char *prefix, const char *entry, int *output, const char *filename) { char **slist; int i, nval; /* pre-fill with A_LAST: */ for(i=0; iMAX_NUM_TECH_LIST) { ruleset_error(LOG_FATAL, "\"%s\": string vector %s.%s too long (%d, max %d)", filename, prefix, entry, nval, MAX_NUM_TECH_LIST); } if (nval==1 && strcmp(slist[0], "")==0) { free(slist); return; } for (i=0; i MAX_NUM_BUILDING_LIST) { ruleset_error(LOG_FATAL, "\"%s\": string vector %s.%s too long (%d, max %d)", filename, prefix, entry, nval, MAX_NUM_BUILDING_LIST); } if (nval == 1 && strcmp(slist[0], "") == 0) { free(slist); return; } for (i = 0; i < nval; i++) { char *sval = slist[i]; struct impr_type *pimprove = find_improvement_by_rule_name(sval); if (NULL == pimprove) { ruleset_error(LOG_FATAL, "\"%s\" %s.%s (%d): couldn't match \"%s\".", filename, prefix, entry, i, sval); } output[i] = improvement_number(pimprove); freelog(LOG_DEBUG, "%s.%s,%d %s %d", prefix, entry, i, sval, output[i]); } free(slist); } /************************************************************************** Lookup a string prefix.entry in the file and return the corresponding unit_type id. If (!required), return NULL if match "None" or can't match. If (required), die if can't match. If description is not NULL, it is used in the warning message instead of prefix (eg pass unit->name instead of prefix="units2.u27") **************************************************************************/ static struct unit_type *lookup_unit_type(struct section_file *file, const char *prefix, const char *entry, int loglevel, const char *filename, const char *description) { char *sval; struct unit_type *punittype; if (LOG_FATAL >= loglevel) { sval = secfile_lookup_str(file, "%s.%s", prefix, entry); } else { sval = secfile_lookup_str_default(file, "None", "%s.%s", prefix, entry); } if (strcmp(sval, "None")==0) { punittype = NULL; } else { punittype = find_unit_type_by_rule_name(sval); if (!punittype) { ruleset_error(loglevel, "\"%s\" %s %s: couldn't match \"%s\".", filename, (description ? description : prefix), entry, sval); /* We continue if error was not fatal. */ punittype = NULL; } } return punittype; } /************************************************************************** Lookup entry in the file and return the corresponding government index; dies if can't find/match. filename is for error message. **************************************************************************/ static struct government *lookup_government(struct section_file *file, const char *entry, const char *filename) { char *sval; struct government *gov; sval = secfile_lookup_str(file, "%s", entry); gov = find_government_by_rule_name(sval); if (!gov) { ruleset_error(LOG_FATAL, "\"%s\" %s: couldn't match \"%s\".", filename, entry, sval); } return gov; } /************************************************************************** Lookup entry in the file and return the corresponding move_type index; dies if can't find/match. filename is for error message. **************************************************************************/ static enum unit_move_type lookup_move_type(struct section_file *file, const char *entry, const char *filename) { char *sval; enum unit_move_type mt; sval = secfile_lookup_str(file, "%s", entry); mt = move_type_from_str(sval); if (mt == MOVETYPE_LAST) { ruleset_error(LOG_FATAL, "\"%s\" %s: couldn't match \"%s\".", filename, entry, sval); } return mt; } /************************************************************************** Lookup optional string, returning allocated memory or NULL. **************************************************************************/ static char *lookup_string(struct section_file *file, const char *prefix, const char *suffix) { char *sval; sval = secfile_lookup_str_default(file, NULL, "%s.%s", prefix, suffix); if (sval) { sval = skip_leading_spaces(sval); if (strlen(sval) > 0) { return mystrdup(sval); } } return NULL; } /************************************************************************** Lookup optional helptext, returning allocated memory or NULL. **************************************************************************/ static char *lookup_helptext(struct section_file *file, const char *prefix) { return lookup_string(file, prefix, "helptext"); } /************************************************************************** Look up the resource section name and return its pointer. **************************************************************************/ static struct resource *lookup_resource(const char *filename, const char *name, const char *jsection) { resource_type_iterate(presource) { const int i = resource_index(presource); const char *isection = &resource_sections[i * MAX_SECTION_LABEL]; if (0 == mystrcasecmp(isection, name)) { return presource; } } resource_type_iterate_end; ruleset_error(LOG_ERROR, "\"%s\" [%s] has unknown \"%s\".", filename, jsection, name); return NULL; } /************************************************************************** Look up the terrain section name and return its pointer. **************************************************************************/ static struct terrain *lookup_terrain(struct section_file *file, const char *item, struct terrain *pthis) { const int j = terrain_index(pthis); const char *jsection = &terrain_sections[j * MAX_SECTION_LABEL]; char *name = secfile_lookup_str(file, "%s.%s", jsection, item); if (NULL == name || *name == '\0' || (0 == strcmp(name, "none")) || (0 == strcmp(name, "no"))) { return T_NONE; } if (0 == strcmp(name, "yes")) { return pthis; } terrain_type_iterate(pterrain) { const int i = terrain_index(pterrain); const char *isection = &terrain_sections[i * MAX_SECTION_LABEL]; if (0 == mystrcasecmp(isection, name)) { return pterrain; } } terrain_type_iterate_end; ruleset_error(LOG_ERROR, "\"%s\" [%s] has unknown \"%s\".", secfile_filename(file), jsection, name); return T_NONE; } /************************************************************************** ... **************************************************************************/ static void load_tech_names(struct section_file *file) { char **sec; int num_techs; /* number of techs in the ruleset (means without A_NONE)*/ int i; const char *filename = secfile_filename(file); (void) section_file_lookup(file, "datafile.description"); /* unused */ /* The names: */ sec = secfile_get_secnames_prefix(file, ADVANCE_SECTION_PREFIX, &num_techs); freelog(LOG_VERBOSE, "%d advances (including possibly unused)", num_techs); if(num_techs == 0) { ruleset_error(LOG_FATAL, "\"%s\": No Advances?!?", filename); } if(num_techs + A_FIRST > A_LAST_REAL) { ruleset_error(LOG_FATAL, "\"%s\": Too many advances (%d, max %d)", filename, num_techs, A_LAST_REAL-A_FIRST); } game.control.num_tech_types = num_techs + A_FIRST; /* includes A_NONE */ i = 0; advance_iterate(A_FIRST, a) { char *name = secfile_lookup_str(file, "%s.name", sec[i]); name_strlcpy(a->name.vernacular, name); a->name.translated = NULL; i++; } advance_iterate_end; free(sec); } /************************************************************************** ... **************************************************************************/ static void load_ruleset_techs(struct section_file *file) { char **sec; int num_techs; /* number of techs in the ruleset (means without A_NONE)*/ int i; struct advance *a_none = advance_by_number(A_NONE); const char *filename = secfile_filename(file); (void) check_ruleset_capabilities(file, "+1.9", filename); sec = secfile_get_secnames_prefix(file, ADVANCE_SECTION_PREFIX, &num_techs); /* Initialize dummy tech A_NONE */ a_none->require[AR_ONE] = a_none; a_none->require[AR_TWO] = a_none; a_none->require[AR_ROOT] = A_NEVER; a_none->flags = 0; i = 0; advance_iterate(A_FIRST, a) { char *sval, **slist; int j,ival,nval; a->require[AR_ONE] = lookup_tech(file, sec[i], "req1", LOG_ERROR, filename, a->name.vernacular); a->require[AR_TWO] = lookup_tech(file, sec[i], "req2", LOG_ERROR, filename, a->name.vernacular); a->require[AR_ROOT] = lookup_tech(file, sec[i], "root_req", LOG_ERROR, filename, a->name.vernacular); if ((A_NEVER == a->require[AR_ONE] && A_NEVER != a->require[AR_TWO]) || (A_NEVER != a->require[AR_ONE] && A_NEVER == a->require[AR_TWO])) { freelog(LOG_ERROR, "\"%s\" [%s] \"%s\": \"Never\" with non-\"Never\".", filename, sec[i], a->name.vernacular); a->require[AR_ONE] = a->require[AR_TWO] = A_NEVER; } if (a_none == a->require[AR_ONE] && a_none != a->require[AR_TWO]) { freelog(LOG_ERROR, "\"%s\" [%s] \"%s\": should have \"None\" second.", filename, sec[i], a->name.vernacular); a->require[AR_ONE] = a->require[AR_TWO]; a->require[AR_TWO] = a_none; } a->flags = 0; slist = secfile_lookup_str_vec(file, &nval, "%s.flags", sec[i]); for(j=0; jname.vernacular, sval); } else { a->flags |= (1<graphic_str, secfile_lookup_str_default(file, "-", "%s.graphic", sec[i])); sz_strlcpy(a->graphic_alt, secfile_lookup_str_default(file, "-", "%s.graphic_alt", sec[i])); a->helptext = lookup_helptext(file, sec[i]); a->bonus_message = lookup_string(file, sec[i], "bonus_message"); a->preset_cost = secfile_lookup_int_default(file, -1, "%s.%s", sec[i], "cost"); a->num_reqs = 0; i++; } advance_iterate_end; /* Propagate a root tech up into the tech tree. Thus if a technology * X has Y has a root tech, then any technology requiring X also has * Y as a root tech. */ restart: advance_iterate(A_FIRST, a) { if (valid_advance(a) && A_NEVER != a->require[AR_ROOT]) { bool out_of_order = FALSE; /* Now find any tech depending on this technology and update its * root_req. */ advance_iterate(A_FIRST, b) { if (valid_advance(b) && A_NEVER == b->require[AR_ROOT] && (a == b->require[AR_ONE] || a == b->require[AR_TWO])) { b->require[AR_ROOT] = a->require[AR_ROOT]; if (b < a) { out_of_order = TRUE; } } } advance_iterate_end; if (out_of_order) { /* HACK: If we just changed the root_tech of a lower-numbered * technology, we need to go back so that we can propagate the * root_tech up to that technology's parents... */ goto restart; } } } advance_iterate_end; /* Now rename A_NEVER to A_NONE for consistency */ advance_iterate(A_NONE, a) { if (A_NEVER == a->require[AR_ROOT]) { a->require[AR_ROOT] = a_none; } } advance_iterate_end; /* Some more consistency checking: Non-removed techs depending on removed techs is too broken to fix by default, so die. */ advance_iterate(A_FIRST, a) { if (!valid_advance(a)) { /* We check for recursive tech loops later, * in build_required_techs_helper. */ if (!valid_advance(a->require[AR_ONE])) { ruleset_error(LOG_FATAL, "\"%s\" tech \"%s\": req1 leads to removed tech.", filename, advance_rule_name(a)); } if (!valid_advance(a->require[AR_TWO])) { ruleset_error(LOG_FATAL, "\"%s\" tech \"%s\": req2 leads to removed tech.", filename, advance_rule_name(a)); } } } advance_iterate_end; free(sec); section_file_check_unused(file, filename); section_file_free(file); } /************************************************************************** ... **************************************************************************/ static void load_unit_names(struct section_file *file) { char **sec; int nval; const char *filename = secfile_filename(file); (void) section_file_lookup(file, "datafile.description"); /* unused */ /* Unit classes */ sec = secfile_get_secnames_prefix(file, UNIT_CLASS_SECTION_PREFIX, &nval); freelog(LOG_VERBOSE, "%d unit classes", nval); if (nval == 0) { ruleset_error(LOG_FATAL, "\"%s\": No unit classes?!?", filename); } if(nval > UCL_LAST) { ruleset_error(LOG_FATAL, "\"%s\": Too many unit classes (%d, max %d)", filename, nval, UCL_LAST); } game.control.num_unit_classes = nval; unit_class_iterate(punitclass) { const int i = uclass_index(punitclass); char *name = secfile_lookup_str(file, "%s.name", sec[i]); name_strlcpy(punitclass->name.vernacular, name); punitclass->name.translated = NULL; } unit_class_iterate_end; /* The names: */ sec = secfile_get_secnames_prefix(file, UNIT_SECTION_PREFIX, &nval); freelog(LOG_VERBOSE, "%d unit types (including possibly unused)", nval); if(nval == 0) { ruleset_error(LOG_FATAL, "\"%s\": No unit types?!?", filename); } if(nval > U_LAST) { ruleset_error(LOG_FATAL, "\"%s\": Too many unit types (%d, max %d)", filename, nval, U_LAST); } game.control.num_unit_types = nval; unit_type_iterate(punittype) { const int i = utype_index(punittype); char *name = secfile_lookup_str(file, "%s.name", sec[i]); name_strlcpy(punittype->name.vernacular, name); punittype->name.translated = NULL; } unit_type_iterate_end; free(sec); } /************************************************************************** ... **************************************************************************/ static void load_ruleset_units(struct section_file *file) { struct unit_type *u; int i, j, ival, nval, vet_levels, vet_levels_default; char *sval, **slist, **sec, **csec; const char *filename = secfile_filename(file); char **vnlist, **def_vnlist; int *vblist, *def_vblist; (void) check_ruleset_capabilities(file, "+1.9", filename); /* * Load up expanded veteran system values. */ sec = secfile_get_secnames_prefix(file, UNIT_SECTION_PREFIX, &nval); #define CHECK_VETERAN_LIMIT(_count, _string) \ if (_count > MAX_VET_LEVELS) { \ ruleset_error(LOG_FATAL, "\"%s\": Too many " _string " entries (%d, max %d)", \ filename, _count, MAX_VET_LEVELS); \ } /* level names */ def_vnlist = secfile_lookup_str_vec(file, &vet_levels_default, "veteran_system.veteran_names"); CHECK_VETERAN_LIMIT(vet_levels_default, "veteran_names"); unit_type_iterate(u) { const int i = utype_index(u); vnlist = secfile_lookup_str_vec(file, &vet_levels, "%s.veteran_names", sec[i]); CHECK_VETERAN_LIMIT(vet_levels, "veteran_names"); if (vnlist) { /* unit has own veterancy settings */ for (j = 0; j < vet_levels; j++) { sz_strlcpy(u->veteran[j].name, vnlist[j]); } free(vnlist); } else { /* apply defaults */ for (j = 0; j < vet_levels_default; j++) { sz_strlcpy(u->veteran[j].name, def_vnlist[j]); } } /* We check for this value to determine how many veteran levels * a unit type has */ for (; j < MAX_VET_LEVELS; j++) { u->veteran[j].name[0] = '\0'; } } unit_type_iterate_end; free(def_vnlist); /* power factor */ def_vblist = secfile_lookup_int_vec(file, &vet_levels_default, "veteran_system.veteran_power_fact"); CHECK_VETERAN_LIMIT(vet_levels_default, "veteran_power_fact"); unit_type_iterate(u) { const int i = utype_index(u); vblist = secfile_lookup_int_vec(file, &vet_levels, "%s.veteran_power_fact", sec[i]); CHECK_VETERAN_LIMIT(vet_levels, "veteran_power_fact"); if (vblist) { for (j = 0; j < vet_levels; j++) { u->veteran[j].power_fact = ((double)vblist[j]) / 100; } free(vblist); } else { for (j = 0; j < vet_levels_default; j++) { u->veteran[j].power_fact = ((double)def_vblist[j]) / 100; } } } unit_type_iterate_end; if (def_vblist) { free(def_vblist); } /* raise chance */ def_vblist = secfile_lookup_int_vec(file, &vet_levels_default, "veteran_system.veteran_raise_chance"); CHECK_VETERAN_LIMIT(vet_levels_default, "veteran_raise_chance"); for (i = 0; i < vet_levels_default; i++) { game.veteran_chance[i] = def_vblist[i]; } for (; i < MAX_VET_LEVELS; i++) { game.veteran_chance[i] = 0; } if (def_vblist) { free(def_vblist); } /* work raise chance */ def_vblist = secfile_lookup_int_vec(file, &vet_levels_default, "veteran_system.veteran_work_raise_chance"); CHECK_VETERAN_LIMIT(vet_levels_default, "veteran_work_raise_chance"); for (i = 0; i < vet_levels_default; i++) { game.work_veteran_chance[i] = def_vblist[i]; } for (; i < MAX_VET_LEVELS; i++) { game.work_veteran_chance[i] = 0; } if (def_vblist) { free(def_vblist); } /* move bonus */ def_vblist = secfile_lookup_int_vec(file, &vet_levels_default, "veteran_system.veteran_move_bonus"); CHECK_VETERAN_LIMIT(vet_levels_default, "veteran_move_bonus"); unit_type_iterate(u) { const int i = utype_index(u); vblist = secfile_lookup_int_vec(file, &vet_levels, "%s.veteran_move_bonus", sec[i]); CHECK_VETERAN_LIMIT(vet_levels, "veteran_move_bonus"); if (vblist) { for (j = 0; j < vet_levels; j++) { u->veteran[j].move_bonus = vblist[j]; } free(vblist); } else { for (j = 0; j < vet_levels_default; j++) { u->veteran[j].move_bonus = def_vblist[j]; } } } unit_type_iterate_end; if (def_vblist) { free(def_vblist); } csec = secfile_get_secnames_prefix(file, UNIT_CLASS_SECTION_PREFIX, &nval); unit_class_iterate(ut) { int i = uclass_index(ut); char tmp[200] = "\0"; char *hut_str; mystrlcat(tmp, csec[i], 200); mystrlcat(tmp, ".move_type", 200); ut->move_type = lookup_move_type(file, tmp, filename); ut->min_speed = SINGLE_MOVE * secfile_lookup_int(file, "%s.min_speed", csec[i]); ut->hp_loss_pct = secfile_lookup_int(file,"%s.hp_loss_pct", csec[i]); hut_str = secfile_lookup_str_default(file, "Normal", "%s.hut_behavior", csec[i]); if (mystrcasecmp(hut_str, "Normal") == 0) { ut->hut_behavior = HUT_NORMAL; } else if (mystrcasecmp(hut_str, "Nothing") == 0) { ut->hut_behavior = HUT_NOTHING; } else if (mystrcasecmp(hut_str, "Frighten") == 0) { ut->hut_behavior = HUT_FRIGHTEN; } else { ruleset_error(LOG_FATAL, "\"%s\" unit_class \"%s\":" " Illegal hut behavior \"%s\".", filename, uclass_rule_name(ut), hut_str); } BV_CLR_ALL(ut->flags); slist = secfile_lookup_str_vec(file, &nval, "%s.flags", csec[i]); for(j = 0; j < nval; j++) { sval = slist[j]; if(strcmp(sval,"") == 0) { continue; } ival = find_unit_class_flag_by_rule_name(sval); if (ival == UCF_LAST) { freelog(LOG_ERROR, "\"%s\" unit_class \"%s\": bad flag name \"%s\".", filename, uclass_rule_name(ut), sval); ival = find_unit_flag_by_rule_name(sval); if (ival != F_LAST) { freelog(LOG_ERROR, "\"%s\" unit_class \"%s\": unit_type flag!", filename, uclass_rule_name(ut)); } } else if (ut->move_type == SEA_MOVING && ( ival == UCF_ROAD_NATIVE || ival == UCF_RIVER_NATIVE)) { freelog(LOG_ERROR, "\"%s\" unit_class \"%s\": cannot give \"%s\" flag" " to sea moving unit", filename, uclass_rule_name(ut), sval); } else { BV_SET(ut->flags, ival); } } free(slist); } unit_class_iterate_end; /* Tech and Gov requirements */ unit_type_iterate(u) { const int i = utype_index(u); u->require_advance = lookup_tech(file, sec[i], "tech_req", LOG_FATAL, filename, u->name.vernacular); if (section_file_lookup(file, "%s.gov_req", sec[i])) { char tmp[200] = "\0"; mystrlcat(tmp, sec[i], 200); mystrlcat(tmp, ".gov_req", 200); u->need_government = lookup_government(file, tmp, filename); } else { u->need_government = NULL; /* no requirement */ } } unit_type_iterate_end; unit_type_iterate(u) { const int i = utype_index(u); u->obsoleted_by = lookup_unit_type(file, sec[i], "obsolete_by", LOG_ERROR, filename, u->name.vernacular); } unit_type_iterate_end; /* main stats: */ unit_type_iterate(u) { const int i = utype_index(u); struct unit_class *pclass; u->need_improvement = lookup_building(file, sec[i], "impr_req", LOG_ERROR, filename, u->name.vernacular); sval = secfile_lookup_str(file, "%s.class", sec[i]); pclass = find_unit_class_by_rule_name(sval); if (!pclass) { ruleset_error(LOG_FATAL, "\"%s\" unit_type \"%s\":" " bad class \"%s\".", filename, utype_rule_name(u), sval); } u->uclass = pclass; sz_strlcpy(u->sound_move, secfile_lookup_str_default(file, "-", "%s.sound_move", sec[i])); sz_strlcpy(u->sound_move_alt, secfile_lookup_str_default(file, "-", "%s.sound_move_alt", sec[i])); sz_strlcpy(u->sound_fight, secfile_lookup_str_default(file, "-", "%s.sound_fight", sec[i])); sz_strlcpy(u->sound_fight_alt, secfile_lookup_str_default(file, "-", "%s.sound_fight_alt", sec[i])); sz_strlcpy(u->graphic_str, secfile_lookup_str(file,"%s.graphic", sec[i])); sz_strlcpy(u->graphic_alt, secfile_lookup_str_default(file, "-", "%s.graphic_alt", sec[i])); u->build_cost = secfile_lookup_int(file,"%s.build_cost", sec[i]); u->pop_cost = secfile_lookup_int(file,"%s.pop_cost", sec[i]); u->attack_strength = secfile_lookup_int(file,"%s.attack", sec[i]); u->defense_strength = secfile_lookup_int(file,"%s.defense", sec[i]); u->move_rate = SINGLE_MOVE*secfile_lookup_int(file,"%s.move_rate", sec[i]); u->vision_radius_sq = secfile_lookup_int(file,"%s.vision_radius_sq", sec[i]); u->transport_capacity = secfile_lookup_int(file,"%s.transport_cap", sec[i]); u->hp = secfile_lookup_int(file,"%s.hitpoints", sec[i]); u->firepower = secfile_lookup_int(file,"%s.firepower", sec[i]); if (u->firepower <= 0) { ruleset_error(LOG_FATAL, "\"%s\" unit_type \"%s\":" " firepower is %d," " but must be at least 1. " " If you want no attack ability," " set the unit's attack strength to 0.", filename, utype_rule_name(u), u->firepower); } u->fuel = secfile_lookup_int(file,"%s.fuel", sec[i]); u->happy_cost = secfile_lookup_int(file, "%s.uk_happy", sec[i]); output_type_iterate(o) { u->upkeep[o] = secfile_lookup_int_default(file, 0, "%s.uk_%s", sec[i], get_output_identifier(o)); } output_type_iterate_end; slist = secfile_lookup_str_vec(file, &nval, "%s.cargo", sec[i]); BV_CLR_ALL(u->cargo); for (j = 0; j < nval; j++) { struct unit_class *class = find_unit_class_by_rule_name(slist[j]); if (!class) { ruleset_error(LOG_FATAL, "\"%s\" unit_type \"%s\":" "has unknown unit class %s as cargo.", filename, utype_rule_name(u), slist[j]); } BV_SET(u->cargo, uclass_index(class)); } free(slist); slist = secfile_lookup_str_vec(file, &nval, "%s.targets", sec[i]); BV_CLR_ALL(u->targets); for (j = 0; j < nval; j++) { struct unit_class *class = find_unit_class_by_rule_name(slist[j]); if (!class) { ruleset_error(LOG_FATAL, "\"%s\" unit_type \"%s\":" "has unknown unit class %s as target.", filename, utype_rule_name(u), slist[j]); } BV_SET(u->targets, uclass_index(class)); } free(slist); /* Set also all classes that are never unreachable as targets. */ unit_class_iterate(pclass) { if (!uclass_has_flag(pclass, UCF_UNREACHABLE)) { BV_SET(u->targets, uclass_index(pclass)); } } unit_class_iterate_end; u->helptext = lookup_helptext(file, sec[i]); u->paratroopers_range = secfile_lookup_int_default(file, 0, "%s.paratroopers_range", sec[i]); u->paratroopers_mr_req = SINGLE_MOVE * secfile_lookup_int_default(file, 0, "%s.paratroopers_mr_req", sec[i]); u->paratroopers_mr_sub = SINGLE_MOVE * secfile_lookup_int_default(file, 0, "%s.paratroopers_mr_sub", sec[i]); u->bombard_rate = secfile_lookup_int_default(file, 0, "%s.bombard_rate", sec[i]); u->city_size = secfile_lookup_int_default(file, 1, "%s.city_size", sec[i]); } unit_type_iterate_end; /* flags */ unit_type_iterate(u) { const int i = utype_index(u); BV_CLR_ALL(u->flags); assert(!utype_has_flag(u, F_LAST-1)); slist = secfile_lookup_str_vec(file, &nval, "%s.flags", sec[i]); for(j=0; jflags, ival); } assert(utype_has_flag(u, ival)); } free(slist); } unit_type_iterate_end; /* roles */ unit_type_iterate(u) { const int i = utype_index(u); BV_CLR_ALL(u->roles); slist = secfile_lookup_str_vec(file, &nval, "%s.roles", sec[i] ); for(j=0; juclass->move_type == LAND_MOVING) { freelog(LOG_ERROR, "\"%s\" unit_type \"%s\": role \"%s\" for land moving unit.", filename, utype_rule_name(u), sval); } else { BV_SET(u->roles, ival - L_FIRST); } assert(utype_has_role(u, ival)); } free(slist); } unit_type_iterate_end; /* Some more consistency checking: */ unit_type_iterate(u) { if (!valid_advance(u->require_advance)) { freelog(LOG_ERROR, "\"%s\" unit_type \"%s\": depends on removed tech \"%s\".", filename, utype_rule_name(u), advance_rule_name(u->require_advance)); u->require_advance = A_NEVER; } if (utype_has_flag(u, F_SETTLERS) && u->city_size <= 0) { ruleset_error(LOG_ERROR, "\"%s\": Unit %s would build size %d cities", filename, utype_rule_name(u), u->city_size); u->city_size = 1; } } unit_type_iterate_end; /* Setup roles and flags pre-calcs: */ role_unit_precalcs(); /* Check some required flags and roles etc: */ if(num_role_units(F_CITIES)==0) { ruleset_error(LOG_FATAL, "\"%s\": No flag=cities units?", filename); } if(num_role_units(F_SETTLERS)==0) { ruleset_error(LOG_FATAL, "\"%s\": No flag=settler units?", filename); } if(num_role_units(L_EXPLORER)==0) { ruleset_error(LOG_FATAL, "\"%s\": No role=explorer units?", filename); } if(num_role_units(L_FERRYBOAT)==0) { ruleset_error(LOG_FATAL, "\"%s\": No role=ferryboat units?", filename); } if(num_role_units(L_FIRSTBUILD)==0) { ruleset_error(LOG_FATAL, "\"%s\": No role=firstbuild units?", filename); } if (num_role_units(L_BARBARIAN) == 0 && game.info.barbarianrate > 0) { ruleset_error(LOG_FATAL, "\"%s\": No role=barbarian units?", filename); } if (num_role_units(L_BARBARIAN_LEADER) == 0 && game.info.barbarianrate > 0) { ruleset_error(LOG_FATAL, "\"%s\": No role=barbarian leader units?", filename); } if (num_role_units(L_BARBARIAN_BUILD) == 0 && game.info.barbarianrate > 0) { ruleset_error(LOG_FATAL, "\"%s\": No role=barbarian build units?", filename); } if (num_role_units(L_BARBARIAN_BOAT) == 0 && game.info.barbarianrate > 0) { ruleset_error(LOG_FATAL, "\"%s\": No role=barbarian ship units?", filename); } else if (num_role_units(L_BARBARIAN_BOAT) > 0) { u = get_role_unit(L_BARBARIAN_BOAT,0); if(utype_move_type(u) != SEA_MOVING) { ruleset_error(LOG_FATAL, "\"%s\": Barbarian boat (%s) needs to be a sea unit.", filename, utype_rule_name(u)); } } if (num_role_units(L_BARBARIAN_SEA) == 0 && game.info.barbarianrate > 0) { ruleset_error(LOG_FATAL, "\"%s\": No role=sea raider barbarian units?", filename); } update_simple_ai_types(); free(csec); free(sec); section_file_check_unused(file, filename); section_file_free(file); } /************************************************************************** ... **************************************************************************/ static void load_building_names(struct section_file *file) { char **sec; int i, nval; const char *filename = secfile_filename(file); (void) section_file_lookup(file, "datafile.description"); /* unused */ /* The names: */ sec = secfile_get_secnames_prefix(file, BUILDING_SECTION_PREFIX, &nval); freelog(LOG_VERBOSE, "%d improvement types (including possibly unused)", nval); if (nval == 0) { ruleset_error(LOG_FATAL, "\"%s\": No improvements?!?", filename); } if (nval > B_LAST) { ruleset_error(LOG_FATAL, "\"%s\": Too many improvements (%d, max %d)", filename, nval, B_LAST); } game.control.num_impr_types = nval; for (i = 0; i < nval; i++) { char *name = secfile_lookup_str(file, "%s.name", sec[i]); struct impr_type *b = improvement_by_number(i); name_strlcpy(b->name.vernacular, name); b->name.translated = NULL; } ruleset_cache_init(); free(sec); } /************************************************************************** ... **************************************************************************/ static void load_ruleset_buildings(struct section_file *file) { char **sec, *item; int i, nval; const char *filename = secfile_filename(file); (void) check_ruleset_capabilities(file, "+1.10.1", filename); sec = secfile_get_secnames_prefix(file, BUILDING_SECTION_PREFIX, &nval); for (i = 0; i < nval; i++) { struct impr_type *b = improvement_by_number(i); struct requirement_vector *reqs = lookup_req_list(file, sec[i], "reqs", improvement_rule_name(b)); char *sval, **slist; int j, nflags, ival; item = secfile_lookup_str(file, "%s.genus", sec[i]); b->genus = find_genus_by_rule_name(item); if (b->genus == IG_LAST) { ruleset_error(LOG_FATAL, "\"%s\" improvement \"%s\": couldn't match genus \"%s\".", filename, improvement_rule_name(b), item); } slist = secfile_lookup_str_vec(file, &nflags, "%s.flags", sec[i]); b->flags = 0; for(j=0; jflags |= (1<reqs, reqs); b->obsolete_by = lookup_tech(file, sec[i], "obsolete_by", LOG_ERROR, filename, b->name.vernacular); if (advance_by_number(A_NONE) == b->obsolete_by) { /* * The ruleset can specify "None" for a never-obsoleted * improvement. Currently this means A_NONE, which is an * unnecessary special-case. We use A_NEVER to flag a * never-obsoleted improvement in the code instead. * (Test for valid_advance() later.) */ b->obsolete_by = A_NEVER; } b->replaced_by = lookup_building(file, sec[i], "replaced_by", LOG_ERROR, filename, b->name.vernacular); b->build_cost = secfile_lookup_int(file, "%s.build_cost", sec[i]); b->upkeep = secfile_lookup_int(file, "%s.upkeep", sec[i]); b->sabotage = secfile_lookup_int(file, "%s.sabotage", sec[i]); sz_strlcpy(b->graphic_str, secfile_lookup_str_default(file, "-", "%s.graphic", sec[i])); sz_strlcpy(b->graphic_alt, secfile_lookup_str_default(file, "-", "%s.graphic_alt", sec[i])); sz_strlcpy(b->soundtag, secfile_lookup_str_default(file, "-", "%s.sound", sec[i])); sz_strlcpy(b->soundtag_alt, secfile_lookup_str_default(file, "-", "%s.sound_alt", sec[i])); b->helptext = lookup_helptext(file, sec[i]); b->allows_units = FALSE; unit_type_iterate(ut) { if (ut->need_improvement == b) { b->allows_units = TRUE; break; } } unit_type_iterate_end; } /* Some more consistency checking: */ improvement_iterate(b) { if (valid_improvement(b)) { if (A_NEVER != b->obsolete_by && !valid_advance(b->obsolete_by)) { freelog(LOG_ERROR, "\"%s\" improvement \"%s\": obsoleted by removed tech \"%s\".", filename, improvement_rule_name(b), advance_rule_name(b->obsolete_by)); b->obsolete_by = A_NEVER; } } } improvement_iterate_end; free(sec); section_file_check_unused(file, filename); section_file_free(file); } /************************************************************************** ... **************************************************************************/ static void load_terrain_names(struct section_file *file) { int nval; char **sec; const char *filename = secfile_filename(file); (void) section_file_lookup(file, "datafile.description"); /* unused */ /* terrain names */ sec = secfile_get_secnames_prefix(file, TERRAIN_SECTION_PREFIX, &nval); if (nval == 0) { ruleset_error(LOG_FATAL, "\"%s\": ruleset doesn't have any terrains.", filename); } if (nval > MAX_NUM_TERRAINS) { ruleset_error(LOG_FATAL, "\"%s\": Too many terrains (%d, max %d)", filename, nval, MAX_NUM_TERRAINS); } game.control.terrain_count = nval; /* avoid re-reading files */ if (terrain_sections) { free(terrain_sections); } terrain_sections = fc_calloc(nval, MAX_SECTION_LABEL); terrain_type_iterate(pterrain) { const int i = terrain_index(pterrain); char *name = secfile_lookup_str(file, "%s.name", sec[i]); name_strlcpy(pterrain->name.vernacular, name); if (0 == strcmp(pterrain->name.vernacular, "unused")) { pterrain->name.vernacular[0] = '\0'; } pterrain->name.translated = NULL; section_strlcpy(&terrain_sections[i * MAX_SECTION_LABEL], sec[i]); } terrain_type_iterate_end; free(sec); /* resource names */ sec = secfile_get_secnames_prefix(file, RESOURCE_SECTION_PREFIX, &nval); if (nval > MAX_NUM_RESOURCES) { ruleset_error(LOG_FATAL, "\"%s\": Too many resources (%d, max %d)", filename, nval, MAX_NUM_RESOURCES); } game.control.resource_count = nval; /* avoid re-reading files */ if (resource_sections) { free(resource_sections); } resource_sections = fc_calloc(nval, MAX_SECTION_LABEL); resource_type_iterate(presource) { const int i = resource_index(presource); char *name = secfile_lookup_str(file, "%s.name", sec[i]); name_strlcpy(presource->name.vernacular, name); if (0 == strcmp(presource->name.vernacular, "unused")) { presource->name.vernacular[0] = '\0'; } presource->name.translated = NULL; section_strlcpy(&resource_sections[i * MAX_SECTION_LABEL], sec[i]); } resource_type_iterate_end; free(sec); /* base names */ sec = secfile_get_secnames_prefix(file, BASE_SECTION_PREFIX, &nval); if (nval > MAX_BASE_TYPES) { ruleset_error(LOG_FATAL, "\"%s\": Too many base types (%d, max %d)", filename, nval, MAX_BASE_TYPES); } game.control.num_base_types = nval; if (base_sections) { free(base_sections); } base_sections = fc_calloc(nval, MAX_SECTION_LABEL); base_type_iterate(pbase) { const int i = base_index(pbase); char *name = secfile_lookup_str(file, "%s.name", sec[i]); name_strlcpy(pbase->name.vernacular, name); pbase->name.translated = NULL; section_strlcpy(&base_sections[i * MAX_SECTION_LABEL], sec[i]); } base_type_iterate_end; free(sec); } /************************************************************************** ... **************************************************************************/ static void load_ruleset_terrain(struct section_file *file) { int nval; int j; char **res; const char *filename = secfile_filename(file); /* char *datafile_options = */ (void) check_ruleset_capabilities(file, TERRAIN_CAPABILITY, filename); /* options */ terrain_control.may_road = secfile_lookup_bool_default(file, TRUE, "options.may_road"); terrain_control.may_irrigate = secfile_lookup_bool_default(file, TRUE, "options.may_irrigate"); terrain_control.may_mine = secfile_lookup_bool_default(file, TRUE, "options.may_mine"); terrain_control.may_transform = secfile_lookup_bool_default(file, TRUE, "options.may_transform"); /* parameters */ terrain_control.ocean_reclaim_requirement_pct = secfile_lookup_int_default(file, 101, "parameters.ocean_reclaim_requirement"); terrain_control.land_channel_requirement_pct = secfile_lookup_int_default(file, 101, "parameters.land_channel_requirement"); terrain_control.lake_max_size = secfile_lookup_int_default(file, 0, "parameters.lake_max_size"); map.ocean_resources = secfile_lookup_bool_default(file, FALSE, "parameters.ocean_resources"); terrain_control.river_move_mode = secfile_lookup_int_default(file, RMV_FAST_STRICT, "parameters.river_move_mode"); terrain_control.river_defense_bonus = secfile_lookup_int_default(file, 50, "parameters.river_defense_bonus"); terrain_control.river_trade_incr = secfile_lookup_int_default(file, 1, "parameters.river_trade_incr"); { char *s = secfile_lookup_str_default(file, "", "parameters.river_help_text"); sz_strlcpy(terrain_control.river_help_text, s); } terrain_control.road_superhighway_trade_bonus = secfile_lookup_int_default(file, 50, "parameters.road_superhighway_trade_bonus"); output_type_iterate(o) { terrain_control.rail_tile_bonus[o] = secfile_lookup_int_default(file, 0, "parameters.rail_%s_bonus", get_output_identifier(o)); terrain_control.pollution_tile_penalty[o] = secfile_lookup_int_default(file, 50, "parameters.pollution_%s_penalty", get_output_identifier(o)); terrain_control.fallout_tile_penalty[o] = secfile_lookup_int_default(file, 50, "parameters.fallout_%s_penalty", get_output_identifier(o)); } output_type_iterate_end; /* terrain details */ terrain_type_iterate(pterrain) { char **slist; const int i = terrain_index(pterrain); const char *tsection = &terrain_sections[i * MAX_SECTION_LABEL]; sz_strlcpy(pterrain->graphic_str, secfile_lookup_str(file,"%s.graphic", tsection)); sz_strlcpy(pterrain->graphic_alt, secfile_lookup_str(file,"%s.graphic_alt", tsection)); pterrain->identifier = secfile_lookup_str(file, "%s.identifier", tsection)[0]; if ('\0' == pterrain->identifier) { ruleset_error(LOG_FATAL, "\"%s\" [%s] identifier missing value.", filename, tsection); } if (TERRAIN_UNKNOWN_IDENTIFIER == pterrain->identifier) { ruleset_error(LOG_FATAL, "\"%s\" [%s] cannot use '%c' as an identifier;" " it is reserved for unknown terrain.", filename, tsection, pterrain->identifier); } for (j = T_FIRST; j < i; j++) { if (pterrain->identifier == terrain_by_number(j)->identifier) { ruleset_error(LOG_FATAL, "\"%s\" [%s] has the same identifier as [%s].", filename, tsection, &terrain_sections[j * MAX_SECTION_LABEL]); } } pterrain->movement_cost = secfile_lookup_int(file, "%s.movement_cost", tsection); pterrain->defense_bonus = secfile_lookup_int(file, "%s.defense_bonus", tsection); output_type_iterate(o) { pterrain->output[o] = secfile_lookup_int_default(file, 0, "%s.%s", tsection, get_output_identifier(o)); } output_type_iterate_end; res = secfile_lookup_str_vec(file, &nval, "%s.resources", tsection); pterrain->resources = fc_calloc(nval + 1, sizeof(*pterrain->resources)); for (j = 0; j < nval; j++) { pterrain->resources[j] = lookup_resource(filename, res[j], tsection); } pterrain->resources[nval] = NULL; free(res); res = NULL; pterrain->road_trade_incr = secfile_lookup_int(file, "%s.road_trade_incr", tsection); pterrain->road_time = secfile_lookup_int(file, "%s.road_time", tsection); pterrain->irrigation_result = lookup_terrain(file, "irrigation_result", pterrain); pterrain->irrigation_food_incr = secfile_lookup_int(file, "%s.irrigation_food_incr", tsection); pterrain->irrigation_time = secfile_lookup_int(file, "%s.irrigation_time", tsection); pterrain->mining_result = lookup_terrain(file, "mining_result", pterrain); pterrain->mining_shield_incr = secfile_lookup_int(file, "%s.mining_shield_incr", tsection); pterrain->mining_time = secfile_lookup_int(file, "%s.mining_time", tsection); pterrain->transform_result = lookup_terrain(file, "transform_result", pterrain); pterrain->transform_time = secfile_lookup_int(file, "%s.transform_time", tsection); pterrain->rail_time = secfile_lookup_int_default(file, 3, "%s.rail_time", tsection); pterrain->clean_pollution_time = secfile_lookup_int_default(file, 3, "%s.clean_pollution_time", tsection); pterrain->clean_fallout_time = secfile_lookup_int_default(file, 3, "%s.clean_fallout_time", tsection); pterrain->warmer_wetter_result = lookup_terrain(file, "warmer_wetter_result", pterrain); pterrain->warmer_drier_result = lookup_terrain(file, "warmer_drier_result", pterrain); pterrain->cooler_wetter_result = lookup_terrain(file, "cooler_wetter_result", pterrain); pterrain->cooler_drier_result = lookup_terrain(file, "cooler_drier_result", pterrain); slist = secfile_lookup_str_vec(file, &nval, "%s.flags", tsection); BV_CLR_ALL(pterrain->flags); for (j = 0; j < nval; j++) { const char *sval = slist[j]; enum terrain_flag_id flag = find_terrain_flag_by_rule_name(sval); if (flag == TER_LAST) { ruleset_error(LOG_FATAL, "\"%s\" [%s] has unknown flag \"%s\".", filename, tsection, sval); } else { BV_SET(pterrain->flags, flag); } } free(slist); for (j = 0; j < MG_LAST; j++) { const char *mg_names[] = { "mountainous", "green", "foliage", "tropical", "temperate", "cold", "frozen", "wet", "dry", "ocean_depth" }; assert(ARRAY_SIZE(mg_names) == MG_LAST); pterrain->property[j] = secfile_lookup_int_default(file, 0, "%s.property_%s", tsection, mg_names[j]); } slist = secfile_lookup_str_vec(file, &nval, "%s.native_to", tsection); BV_CLR_ALL(pterrain->native_to); for (j = 0; j < nval; j++) { struct unit_class *class = find_unit_class_by_rule_name(slist[j]); if (!class) { ruleset_error(LOG_FATAL, "\"%s\" [%s] is native to unknown unit class \"%s\".", filename, tsection, slist[j]); } else if (is_ocean(pterrain) && class->move_type == LAND_MOVING) { ruleset_error(LOG_FATAL, "\"%s\" oceanic [%s] is native to land units.", filename, tsection); } else if (!is_ocean(pterrain) && class->move_type == SEA_MOVING) { ruleset_error(LOG_FATAL, "\"%s\" non-oceanic [%s] is native to sea units.", filename, tsection); } else { BV_SET(pterrain->native_to, uclass_index(class)); } } free(slist); pterrain->helptext = lookup_helptext(file, tsection); } terrain_type_iterate_end; /* resource details */ resource_type_iterate(presource) { char identifier[MAX_LEN_NAME]; const int i = resource_index(presource); const char *rsection = &resource_sections[i * MAX_SECTION_LABEL]; output_type_iterate (o) { presource->output[o] = secfile_lookup_int_default(file, 0, "%s.%s", rsection, get_output_identifier(o)); } output_type_iterate_end; sz_strlcpy(presource->graphic_str, secfile_lookup_str(file,"%s.graphic", rsection)); sz_strlcpy(presource->graphic_alt, secfile_lookup_str(file,"%s.graphic_alt", rsection)); sz_strlcpy(identifier, secfile_lookup_str(file,"%s.identifier", rsection)); presource->identifier = identifier[0]; if (RESOURCE_NULL_IDENTIFIER == presource->identifier) { ruleset_error(LOG_FATAL, "\"%s\" [%s] identifier missing value.", filename, rsection); } if (RESOURCE_NONE_IDENTIFIER == presource->identifier) { ruleset_error(LOG_FATAL, "\"%s\" [%s] cannot use '%c' as an identifier;" " it is reserved.", filename, rsection, presource->identifier); } for (j = 0; j < i; j++) { if (presource->identifier == resource_by_number(j)->identifier) { ruleset_error(LOG_FATAL, "\"%s\" [%s] has the same identifier as [%s].", filename, rsection, &resource_sections[j * MAX_SECTION_LABEL]); } } } resource_type_iterate_end; /* base details */ base_type_iterate(pbase) { BV_CLR_ALL(pbase->conflicts); } base_type_iterate_end; base_type_iterate(pbase) { const char *section = &base_sections[base_index(pbase) * MAX_SECTION_LABEL]; int j; char **slist; struct requirement_vector *reqs; char *gui_str; pbase->buildable = secfile_lookup_bool_default(file, TRUE, "%s.buildable", section); pbase->pillageable = secfile_lookup_bool_default(file, TRUE, "%s.pillageable", section); sz_strlcpy(pbase->graphic_str, secfile_lookup_str_default(file, "-", "%s.graphic", section)); sz_strlcpy(pbase->graphic_alt, secfile_lookup_str_default(file, "-", "%s.graphic_alt", section)); sz_strlcpy(pbase->activity_gfx, secfile_lookup_str_default(file, "-", "%s.activity_gfx", section)); reqs = lookup_req_list(file, section, "reqs", base_rule_name(pbase)); requirement_vector_copy(&pbase->reqs, reqs); slist = secfile_lookup_str_vec(file, &nval, "%s.native_to", section); BV_CLR_ALL(pbase->native_to); for (j = 0; j < nval; j++) { struct unit_class *class = find_unit_class_by_rule_name(slist[j]); if (!class) { ruleset_error(LOG_FATAL, "\"%s\" base \"%s\" is native to unknown unit class \"%s\".", filename, base_rule_name(pbase), slist[j]); } else { BV_SET(pbase->native_to, uclass_index(class)); } } free(slist); gui_str = secfile_lookup_str(file,"%s.gui_type", section); pbase->gui_type = base_gui_type_from_str(gui_str); if (pbase->gui_type == BASE_GUI_LAST) { ruleset_error(LOG_FATAL, "\"%s\" base \"%s\": unknown gui_type \"%s\".", filename, base_rule_name(pbase), gui_str); } pbase->build_time = secfile_lookup_int(file, "%s.build_time", section); pbase->border_sq = secfile_lookup_int_default(file, -1, "%s.border_sq", section); pbase->vision_sq = secfile_lookup_int_default(file, -1, "%s.vision_sq", section); pbase->defense_bonus = secfile_lookup_int_default(file, 0, "%s.defense_bonus", section); slist = secfile_lookup_str_vec(file, &nval, "%s.flags", section); BV_CLR_ALL(pbase->flags); for (j = 0; j < nval; j++) { const char *sval = slist[j]; enum base_flag_id flag = base_flag_from_str(sval); if (flag == BF_LAST) { ruleset_error(LOG_FATAL, "\"%s\" base \"%s\": unknown flag \"%s\".", filename, base_rule_name(pbase), sval); } else { BV_SET(pbase->flags, flag); } } free(slist); slist = secfile_lookup_str_vec(file, &nval, "%s.conflicts", section); for (j = 0; j < nval; j++) { const char *sval = slist[j]; struct base_type *pbase2 = find_base_type_by_rule_name(sval); if (pbase2 == NULL) { ruleset_error(LOG_FATAL, "\"%s\" base \"%s\": unknown conflict base \"%s\".", filename, base_rule_name(pbase), sval); } else { BV_SET(pbase->conflicts, base_index(pbase2)); BV_SET(pbase2->conflicts, base_index(pbase)); } } free(slist); if (territory_claiming_base(pbase)) { base_type_iterate(pbase2) { if (pbase2 > pbase && territory_claiming_base(pbase2)) { BV_SET(pbase->conflicts, base_index(pbase2)); BV_SET(pbase2->conflicts, base_index(pbase)); } } base_type_iterate_end; } } base_type_iterate_end; section_file_check_unused(file, filename); section_file_free(file); } /************************************************************************** ... **************************************************************************/ static void load_government_names(struct section_file *file) { int nval; char **sec; const char *filename = secfile_filename(file); (void) section_file_lookup(file, "datafile.description"); /* unused */ sec = secfile_get_secnames_prefix(file, GOVERNMENT_SECTION_PREFIX, &nval); if (nval == 0) { ruleset_error(LOG_FATAL, "\"%s\": No governments?!?", filename); } else if(nval > G_MAGIC) { /* upper limit is really about 255 for 8-bit id values, but use G_MAGIC elsewhere as a sanity check, and should be plenty big enough --dwp */ ruleset_error(LOG_FATAL, "\"%s\": Too many governments (%d, max %d)", filename, nval, G_MAGIC); } governments_alloc(nval); /* Government names are needed early so that get_government_by_name will * work. */ government_iterate(gov) { char *name = secfile_lookup_str(file, "%s.name", sec[government_index(gov)]); name_strlcpy(gov->name.vernacular, name); gov->name.translated = NULL; } government_iterate_end; free(sec); } /************************************************************************** This loads information from given governments.ruleset **************************************************************************/ static void load_ruleset_governments(struct section_file *file) { int nval; char **sec; const char *filename = secfile_filename(file); (void) check_ruleset_capabilities(file, "+1.9", filename); sec = secfile_get_secnames_prefix(file, GOVERNMENT_SECTION_PREFIX, &nval); game.government_during_revolution = lookup_government(file, "governments.during_revolution", filename); game.info.government_during_revolution_id = government_number(game.government_during_revolution); /* easy ones: */ government_iterate(g) { const int i = government_index(g); struct requirement_vector *reqs = lookup_req_list(file, sec[i], "reqs", government_rule_name(g)); if (section_file_lookup(file, "%s.ai_better", sec[i])) { char entry[100]; my_snprintf(entry, sizeof(entry), "%s.ai_better", sec[i]); g->ai.better = lookup_government(file, entry, filename); } else { g->ai.better = NULL; } requirement_vector_copy(&g->reqs, reqs); sz_strlcpy(g->graphic_str, secfile_lookup_str(file, "%s.graphic", sec[i])); sz_strlcpy(g->graphic_alt, secfile_lookup_str(file, "%s.graphic_alt", sec[i])); g->helptext = lookup_helptext(file, sec[i]); } government_iterate_end; /* titles */ government_iterate(g) { struct ruler_title *title; const int i = government_index(g); g->num_ruler_titles = 1; g->ruler_titles = fc_calloc(1, sizeof(*g->ruler_titles)); title = &(g->ruler_titles[0]); title->nation = DEFAULT_TITLE; sz_strlcpy(title->male.vernacular, secfile_lookup_str(file, "%s.ruler_male_title", sec[i])); title->male.translated = NULL; sz_strlcpy(title->female.vernacular, secfile_lookup_str(file, "%s.ruler_female_title", sec[i])); title->female.translated = NULL; } government_iterate_end; free(sec); section_file_check_unused(file, filename); section_file_free(file); } /************************************************************************** Send information in packet_ruleset_control (numbers of units etc, and other miscellany) to specified connections. The client assumes that exactly one ruleset control packet is sent as a part of each ruleset send. So after sending this packet we have to resend every other part of the rulesets (and none of them should be is-info in the network code!). The client frees ruleset data when receiving this packet and then re-initializes as it receives the individual ruleset packets. See packhand.c. **************************************************************************/ static void send_ruleset_control(struct conn_list *dest) { struct packet_ruleset_control packet; packet = game.control; lsend_packet_ruleset_control(dest, &packet); } /************************************************************************** This checks if nations[pos] leader names are not already defined in any previous nation, or twice in its own leader name table. If not return NULL, if yes return pointer to name which is repeated and id of a conflicting nation as second parameter. **************************************************************************/ static char *check_leader_names(Nation_type_id nation, Nation_type_id *conflict_nation) { int k; struct nation_type *pnation = nation_by_number(nation); for (k = 0; k < pnation->leader_count; k++) { char *leader = pnation->leaders[k].name; int i; Nation_type_id nation2; for (i = 0; i < k; i++) { if (0 == strcmp(leader, pnation->leaders[i].name)) { *conflict_nation = nation; return leader; } } for (nation2 = 0; nation2 < nation; nation2++) { struct nation_type *pnation2 = nation_by_number(nation2); for (i = 0; i < pnation2->leader_count; i++) { if (0 == strcmp(leader, pnation2->leaders[i].name)) { *conflict_nation = nation2; return leader; } } } } return NULL; } /************************************************************************** ... **************************************************************************/ static void load_nation_names(struct section_file *file) { char **sec; int j; (void) section_file_lookup(file, "datafile.description"); /* unused */ sec = secfile_get_secnames_prefix(file, NATION_SECTION_PREFIX, &game.control.nation_count); nations_alloc(game.control.nation_count); nations_iterate(pl) { const int i = nation_index(pl); char *adjective = secfile_lookup_str(file, "%s.name", sec[i]); char *noun_plural = secfile_lookup_str(file, "%s.plural", sec[i]); name_strlcpy(pl->adjective.vernacular, adjective); pl->adjective.translated = NULL; name_strlcpy(pl->noun_plural.vernacular, noun_plural); pl->noun_plural.translated = NULL; /* Check if nation name is already defined. */ for(j = 0; j < i; j++) { struct nation_type *n2 = nation_by_number(j); if (0 == strcmp(n2->adjective.vernacular, pl->adjective.vernacular) || 0 == strcmp(n2->noun_plural.vernacular, pl->noun_plural.vernacular)) { ruleset_error(LOG_FATAL, "%s nation (the %s) defined twice; " "in section nation%d and section nation%d", adjective, noun_plural, j, i); } } } nations_iterate_end; free(sec); } /************************************************************************** This function loads a city name list from a section file. The file and two section names (which will be concatenated) are passed in. The malloc'ed city name list (which is all filled out) will be returned. **************************************************************************/ static struct nation_city* load_city_name_list(struct section_file *file, const char *secfile_str1, const char *secfile_str2) { int dim, j; struct nation_city *city_names; int value; /* First we read the strings from the section file (above). */ char **cities = secfile_lookup_str_vec(file, &dim, "%s.%s", secfile_str1, secfile_str2); /* * Now we allocate enough room in the city_names array to store * all the name data. The array is NULL-terminated by * having a NULL name at the end. */ city_names = fc_calloc(dim + 1, sizeof(*city_names)); city_names[dim].name = NULL; /* * Each string will be of the form * " (