/* Online help support for Xconq. Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs. Xconq 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. See the file COPYING. */ /* This is basically support code for interfaces, which handle the actual help interaction themselves. */ /* This file must also be translated (mostly) for non-English Xconq. */ #include "conq.h" int may_detonate(int u); int any_ut_capacity_x(int u); int any_mp_to_enter_unit(int u); int any_mp_to_leave_unit(int u); int any_enter_indep(int u); /* Obstack allocation and deallocation routines. */ #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free static void describe_help_system(int arg, char *key, TextBuffer *buf); static void describe_instructions(int arg, char *key, TextBuffer *buf); static void describe_synth_run(TextBuffer *buf, int methkey); static void describe_world(int arg, char *key, TextBuffer *buf); static int histogram_compare(const void *h1, const void *h2); static void describe_news(int arg, char *key, TextBuffer *buf); static void describe_concepts(int arg, char *key, TextBuffer *buf); static void describe_game_design(int arg, char *key, TextBuffer *buf); static void describe_utype(int u, char *key, TextBuffer *buf); static void describe_mtype(int m, char *key, TextBuffer *buf); static void describe_ttype(int t, char *key, TextBuffer *buf); static void describe_atype(int t, char *key, TextBuffer *buf); static void describe_scorekeepers(int arg, char *key, TextBuffer *buf); static void describe_setup(int arg, char *key, TextBuffer *buf); static void describe_game_modules(int arg, char *key, TextBuffer *buf); static void describe_game_module_aux(TextBuffer *buf, Module *module, int level); static void describe_module_notes(TextBuffer *buf, Module *module); static int u_property_not_default(int (*fn)(int i), int dflt); static int t_property_not_default(int (*fn)(int i), int dflt); static int uu_table_row_not_default(int u, int (*fn)(int i, int j), int dflt); static int ut_table_row_not_default(int u, int (*fn)(int i, int j), int dflt); static int um_table_row_not_default(int u, int (*fn)(int i, int j), int dflt); static int tt_table_row_not_default(int t, int (*fn)(int i, int j), int dflt); #if 0 static int tm_table_row_not_default(int t, int (*fn)(int i, int j), int dflt); #endif static int aa_table_row_not_default(int a1, int (*fn)(int i, int j), int dflt); static int uu_table_column_not_default(int u, int (*fn)(int i, int j), int dflt); static int aa_table_column_not_default(int a1, int (*fn)(int i, int j), int dflt); static void u_property_desc(TextBuffer *buf, int (*fn)(int), void (*formatter)(TextBuffer *, int)); static void t_property_desc(TextBuffer *buf, int (*fn)(int), void (*formatter)(TextBuffer *, int)); static void uu_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int), void (*formatter)(TextBuffer *, int), char *connect); static void uu_table_column_desc(TextBuffer *buf, int u, int (*fn)(int, int), void (*formatter)(TextBuffer *, int), char *connect); static void uu_table_rowcol_desc(TextBuffer *buf, int u, int (*fn)(int, int), void (*formatter)(TextBuffer *, int), char *connect, int rowcol); static void ut_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int), void (*formatter)(TextBuffer *, int), char *connect); static void um_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int), void (*formatter)(TextBuffer *, int)); static void tt_table_row_desc(TextBuffer *buf, int t1, int (*fn)(int, int), void (*formatter)(TextBuffer *, int)); #if 0 static void tm_table_row_desc(TextBuffer *buf, int t, int (*fn)(int, int), void (*formatter)(TextBuffer *, int)); #endif static void aa_table_row_desc(TextBuffer *buf, int a1, int (*fn)(int, int), void (*formatter)(TextBuffer *, int)); static void aa_table_column_desc(TextBuffer *buf, int a1, int (*fn)(int, int), void (*formatter)(TextBuffer *, int)); static void aa_table_rowcol_desc(TextBuffer *buf, int a1, int (*fn)(int, int), void (*formatter)(TextBuffer *, int), int rowcol); static void tb_value_desc(TextBuffer *buf, int val); static void tb_fraction_desc(TextBuffer *buf, int val); static void tb_percent_desc(TextBuffer *buf, int val); static void tb_percent_100th_desc(TextBuffer *buf, int val); static void tb_dice_desc(TextBuffer *buf, int val); static void tb_mult_desc(TextBuffer *buf, int val); static void tb_bool_desc(TextBuffer *buf, int val); #if 0 static void append_number(TextBuffer *buf, int value, int dflt); #endif static void append_help_phrase(TextBuffer *buf, char *phrase); static void append_notes(TextBuffer *buf, Obj *notes); /* The first help node in the chain. */ HelpNode *first_help_node; /* The last help node. */ HelpNode *last_help_node; /* The help node with copying and copyright info. */ HelpNode *copying_help_node; /* The help node with (non-)warranty info. */ HelpNode *warranty_help_node; HelpNode *default_prev_help_node; /* Create the initial help node and link it to itself. Subsequent nodes will be inserted later, after a game has been loaded. */ void init_help(void) { /* Note that we can't use add_help_node to set up the first help node. */ first_help_node = create_help_node(); first_help_node->key = "help system"; first_help_node->fn = describe_help_system; first_help_node->prev = first_help_node->next = first_help_node; last_help_node = first_help_node; copying_help_node = add_help_node("copyright", describe_copyright, 0, first_help_node); warranty_help_node = add_help_node("warranty", describe_warranty, 0, copying_help_node); /* Set the place for new nodes to appear normally. */ default_prev_help_node = copying_help_node; add_help_node("news", describe_news, 0, NULL); } /* This function creates the actual set of help nodes for the kernel. */ void create_game_help_nodes(void) { int u, m, t, a; char *name, *longname; HelpNode *node; add_help_node("instructions", describe_instructions, 0, NULL); add_help_node("game overview", describe_game_design, 0, NULL); add_help_node("scoring", describe_scorekeepers, 0, NULL); add_help_node("modules", describe_game_modules, 0, NULL); add_help_node("game setup", describe_setup, 0, NULL); add_help_node("world", describe_world, 0, NULL); for_all_unit_types(u) { longname = u_long_name(u); if (!empty_string(longname)) { sprintf(spbuf, "%s (%s)", longname, u_type_name(u)); name = copy_string(spbuf); } else { name = u_type_name(u); } node = add_help_node(name, describe_utype, u, NULL); node->nclass = utypenode; } for_all_material_types(m) { node = add_help_node(m_type_name(m), describe_mtype, m, NULL); node->nclass = mtypenode; } for_all_terrain_types(t) { node = add_help_node(t_type_name(t), describe_ttype, t, NULL); node->nclass = ttypenode; } for_all_advance_types(a) { node = add_help_node(a_type_name(a), describe_atype, a, NULL); node->nclass = atypenode; } add_help_node("general concepts", describe_concepts, 0, NULL); /* Invalidate any existing topics node. */ first_help_node->text = NULL; } /* Create an empty help node. */ HelpNode * create_help_node(void) { HelpNode *node = (HelpNode *) xmalloc(sizeof(HelpNode)); node->key = NULL; node->fn = NULL; node->nclass = miscnode; node->arg = 0; node->text = NULL; node->prev = node->next = NULL; return node; } /* Add a help node after the given node. */ HelpNode * add_help_node(char *key, void (*fn)(int t, char *key, TextBuffer *buf), int arg, HelpNode *prevnode) { HelpNode *node, *nextnode; if (empty_string(key)) { run_error("empty help key"); } node = create_help_node(); node->key = key; node->fn = fn; node->arg = arg; if (prevnode == NULL) prevnode = default_prev_help_node->prev; nextnode = prevnode->next; node->prev = prevnode; node->next = nextnode; prevnode->next = node; nextnode->prev = node; /* Might need to fix last help node. */ last_help_node = first_help_node->prev; return node; } /* Given a string and node, find the next node whose key matches. */ HelpNode * find_help_node(HelpNode *node, char *str) { HelpNode *tmp; /* Note that the search wraps around. */ for (tmp = node->next; tmp != node; tmp = tmp->next) { if (strcmp(tmp->key, str) == 0) return tmp; if (strstr(tmp->key, str) != NULL) return tmp; } return NULL; } /* Return the string containing the text of the help node, possibly computing it first. */ char * get_help_text(HelpNode *node) { TextBuffer tbuf; if (node != NULL) { /* Maybe calculate the text to display. */ if (node->text == NULL) { if (node->fn != NULL) { /* (should allow for variable-size allocation) */ obstack_begin(&(tbuf.ostack), 200); if (1) { node->textend = 0; (*(node->fn))(node->arg, node->key, &tbuf); obstack_1grow(&(tbuf.ostack), '\0'); node->text = copy_string(obstack_finish(&(tbuf.ostack))); obstack_free(&(tbuf.ostack), 0); node->textend = strlen(node->text); } else { /* Ran out of memory... (would never get here though!) */ } } else { /* Generate a default message if nothing to compute help. */ sprintf(spbuf, "%s: No info available.", node->key); node->text = copy_string(spbuf); node->textend = strlen(node->text); } Dprintf("Size of help node \"%s\" text is %d\n", node->key, node->textend); } return node->text; } else { return NULL; } } static void describe_help_system(int arg, char *key, TextBuffer *buf) { tbcat(buf, "This is the header node of the Xconq help system.\n"); tbcat(buf, "Go forward or backward from here to see the online help.\n"); } /* Create a raw list of help topics by just iterating through all the nodes, except for the topics node itself. */ void describe_topics(int arg, char *key, TextBuffer *buf) { HelpNode *topics, *tmp; topics = find_help_node(first_help_node, "topics"); /* Unlikely that we'll call this without the topics node existing already, but just in case... */ if (topics == NULL) return; for (tmp = topics->next; tmp != topics; tmp = tmp->next) { tbprintf(buf, "%s", tmp->key); tbcat(buf, "\n"); } } /* Get the news file and put it into text buffer. */ static void describe_news(int arg, char *key, TextBuffer *buf) { FILE *fp; fp = open_file(news_filename(), "r"); if (fp != NULL) { tbcat(buf, "XCONQ NEWS\n\n"); while (fgets(spbuf, BUFSIZE-1, fp) != NULL) { tbcat(buf, spbuf); } fclose(fp); } else { tbcat(buf, "(no news)"); } } /* Describe general game concepts in a general way. If a concept does not apply to the game in effect, then just say it's not part of this game (would be confusing if the online doc described things irrelevant to the specific game). */ static void describe_concepts(int arg, char *key, TextBuffer *buf) { tbcat(buf, "Hit points (HP) represent the overall condition of "); tbcat(buf, "the unit."); tbcat(buf, "\n"); tbcat(buf, "Action points (ACP) are what a unit needs to be able "); tbcat(buf, "to do anything at all. Typically a unit will use 1 ACP "); tbcat(buf, "to move 1 cell."); tbcat(buf, "\n"); tbcat(buf, "Movement points (MP) represent varying costs of movement "); tbcat(buf, "actions, such as a difficult-to-cross border. The number "); tbcat(buf, "of movement points is added up then divided by unit's speed "); tbcat(buf, "to get the total number of acp used up by a move."); tbcat(buf, "\n"); if (0) { } else { tbcat(buf, "No combat experience (CXP) in this game.\n"); } if (0) { } else { tbcat(buf, "No morale (MO) in this game.\n"); } tbcat(buf, "Each unit that can do anything has a plan, and a list of "); tbcat(buf, "tasks to perform.\n"); /* (should describe more general concepts) */ } static void describe_instructions(int arg, char *key, TextBuffer *buf) { Obj *instructions = mainmodule->instructions; if (instructions != lispnil) { append_notes(buf, instructions); } else { tbcat(buf, "(no instructions supplied)"); } } /* Spit out all the general game_design parameters in a readable fashion. */ static void describe_game_design(int arg, char *key, TextBuffer *buf) { int u, m, t, a; /* Replicate title and blurb? (should put title at head of windows, and pages if printed) */ tbprintf(buf, "*** %s ***\n", (mainmodule->title ? mainmodule->title : mainmodule->name)); tbcat(buf, "\n"); tbprintf(buf, "This game includes %d unit types and %d terrain types", numutypes, numttypes); if (nummtypes > 0) { tbprintf(buf, ", along with %d material types", nummtypes); } if (numatypes > 0) { tbprintf(buf, ", and it has %d types of advances", numatypes); } tbcat(buf, ".\n"); if (g_sides_min() == g_sides_max()) { tbprintf(buf, "Exactly %d sides may play.\n", g_sides_min()); } else { tbprintf(buf, "From %d up to %d sides may play.\n", g_sides_min(), g_sides_max()); } tbcat(buf, "\n"); if (g_advantage_min() == g_advantage_max()) tbprintf(buf, "Player advantages are fixed.\n"); else tbprintf(buf, "Player advantages may range from %d to %d, defaulting to %d.\n", g_advantage_min(), g_advantage_max(), g_advantage_default()); tbcat(buf, "\n"); if (g_see_all()) { tbcat(buf, "Everything is always seen by all sides.\n"); } else { if (g_see_terrain_always()) { tbcat(buf, "Terrain view is always accurate once seen.\n"); } /* (should only have if any weather to be seen) */ if ((any_temp_variation || any_wind_variation || any_clouds) && g_see_weather_always()) { tbcat(buf, "Weather view is always accurate once terrain seen.\n"); } if (g_terrain_seen()) { tbcat(buf, "World terrain is already seen by all sides.\n"); } } tbcat(buf, "\n"); if (g_last_turn() < 9999) { tbprintf(buf, "Game can go for up to %d turns", g_last_turn()); if (g_extra_turn() > 0) { tbprintf(buf, ", with %d%% chance of additional turn thereafter.", g_extra_turn()); } tbcat(buf, ".\n"); } if (g_rt_for_game() > 0) { tbprintf(buf, "Entire game can last up to %d minutes.\n", g_rt_for_game() / 60); } if (g_rt_per_turn() > 0) { tbprintf(buf, "Each turn can last up to %d minutes.\n", g_rt_per_turn() / 60); } if (g_rt_per_side() > 0) { tbprintf(buf, "Each side gets a total %d minutes to act.\n", g_rt_per_side() / 60); } if (g_units_in_game_max() >= 0) { tbprintf(buf, "Limited to no more than %d units in all.\n", g_units_in_game_max()); } if (g_units_per_side_max() >= 0) { tbprintf(buf, "Limited to no more than %d units per side.\n", g_units_per_side_max()); } if (g_use_side_priority()) { tbcat(buf, "Sides move sequentially, in priority order.\n"); } else { tbcat(buf, "Sides move simultaneously.\n"); } if (any_temp_variation) { tbprintf(buf, "Lowest possible temperature is %d, at an elevation of %d.\n", g_temp_floor(), g_temp_floor_elev()); tbprintf(buf, "Temperatures averaged to range %d.\n", g_temp_mod_range()); } tbcat(buf, "\nUnit Types:\n"); for_all_unit_types(u) { tbprintf(buf, " %s", u_type_name(u)); if (!empty_string(u_help(u))) tbprintf(buf, " (%s)", u_help(u)); tbcat(buf, "\n"); #ifdef DESIGNERS /* Show designers a bit more. */ if (numdesigners > 0) { tbcat(buf, " ["); if (!empty_string(u_uchar(u))) tbprintf(buf, "char '%s'", u_uchar(u)); else tbcat(buf, "no char"); if (!empty_string(u_gchar(u))) tbprintf(buf, "generic char '%s'", u_gchar(u)); else tbcat(buf, "no generic char"); if (!empty_string(u_image_name(u))) tbprintf(buf, ", image \"%s\"", u_image_name(u)); if (!empty_string(u_generic_name(u))) tbprintf(buf, ", generic name \"%s\"", u_generic_name(u)); if (u_desc_format(u) != lispnil) { tbcat(buf, ", special format"); } tbcat(buf, "]\n"); } #endif /* DESIGNERS */ } tbcat(buf, "\nTerrain Types:\n"); for_all_terrain_types(t) { tbprintf(buf, " %s", t_type_name(t)); if (!empty_string(t_help(t))) tbprintf(buf, " (%s)", t_help(t)); tbcat(buf, "\n"); #ifdef DESIGNERS /* Show designers a bit more. */ if (numdesigners > 0) { tbcat(buf, " ["); if (!empty_string(t_char(t))) tbprintf(buf, "char '%s'", t_char(t)); else tbcat(buf, "no char"); if (!empty_string(t_image_name(t))) tbprintf(buf, ", image \"%s\"", t_image_name(t)); tbcat(buf, "]\n"); } #endif /* DESIGNERS */ } if (nummtypes > 0) { tbcat(buf, "\nMaterial Types:\n"); for_all_material_types(m) { tbprintf(buf, " %s", m_type_name(m)); if (!empty_string(m_help(m))) tbprintf(buf, " (%s)", m_help(m)); tbcat(buf, "\n"); #ifdef DESIGNERS /* Show designers a bit more. */ if (numdesigners > 0) { tbcat(buf, " ["); if (!empty_string(m_char(m))) tbprintf(buf, "char '%s'", m_char(m)); else tbcat(buf, "no char"); if (!empty_string(m_image_name(m))) tbprintf(buf, ", image \"%s\"", m_image_name(m)); tbcat(buf, "]\n"); } #endif /* DESIGNERS */ } } if (numatypes > 0) { tbcat(buf, "\nAdvances:\n"); for_all_advance_types(a) { tbprintf(buf, " %s", a_type_name(a)); if (!empty_string(a_help(a))) tbprintf(buf, " (%s)", a_help(a)); tbcat(buf, "\n"); #ifdef DESIGNERS /* Show designers a bit more. */ if (numdesigners > 0) { tbcat(buf, " ["); if (!empty_string(a_image_name(a))) tbprintf(buf, ", image \"%s\"", a_image_name(a)); tbcat(buf, "]\n"); } #endif /* DESIGNERS */ } } #ifdef DESIGNERS /* Show designers a bit more. */ if (numdesigners > 0) { tbcat(buf, "FOR DESIGNERS:\n"); tbprintf(buf, "Unseen terrain char is \"%s\".\n", g_unseen_char()); tbprintf(buf, "Scorefile name is \"%s\".\n", g_scorefile_name()); } #endif /* DESIGNERS */ } /* Display game module info to a side. */ static void describe_game_modules(int arg, char *key, TextBuffer *buf) { if (mainmodule != NULL) { /* First put out basic module info. */ describe_game_module_aux(buf, mainmodule, 0); /* Now do the lengthy module notes (with no indentation). */ describe_module_notes(buf, mainmodule); } else { tbcat(buf, "(No game module information is available.)"); } } /* Recurse down through included modules to display docs on each. Indents each file by inclusion level. Note that modules cannot be loaded more than once, so each will be described only once here. */ static void describe_game_module_aux(TextBuffer *buf, Module *module, int level) { int i; char indentbuf[100]; char dashbuf[100]; Module *submodule; dashbuf[0] = '\0'; indentbuf[0] = '\0'; for (i = 0; i < level; ++i) { strcat(dashbuf, "-- "); strcat(indentbuf, " "); } tbprintf(buf, "%s\"%s\"", dashbuf, (module->title ? module->title : module->name)); /* Display the true name of the module if not the same as the title. */ if (module->title != NULL && strcmp(module->title, module->name) != 0) { tbprintf(buf, " (\"%s\")", module->name); } if (module->version != NULL) { tbprintf(buf, " (version \"%s\")", module->version); } tbcat(buf, "\n"); tbprintf(buf, " %s\n", (module->blurb ? module->blurb : "(no description)")); if (module->notes != lispnil) { tbprintf(buf, "Notes to \"%s\":\n", module->name); append_notes(buf, module->notes); tbprintf(buf, "\n\n"); } #if 0 if (module->notes != lispnil) { tbprintf(buf, "%s (See notes below)\n", indentbuf); } #endif /* Now describe any included modules. */ for_all_includes(module, submodule) { describe_game_module_aux(buf, submodule, level + 1); } } /* Dump the module designer's notes into the given buffer. When doing submodules, don't indent. */ static void describe_module_notes(TextBuffer *buf, Module *module) { Module *submodule; #ifdef DESIGNERS /* Only show design notes if any designers around. */ if (numdesigners > 0 && module->designnotes != lispnil) { tbprintf(buf, "\nDesign Notes to \"%s\":\n", module->name); append_notes(buf, module->designnotes); } #endif /* DESIGNERS */ for_all_includes(module, submodule) { describe_module_notes(buf, submodule); } } int any_ut_capacity_x(int u) { int t; for_all_terrain_types(t) { if (ut_capacity_x(u, t) != 0) return TRUE; } return FALSE; } int any_mp_to_enter_unit(int u) { int u2; for_all_unit_types(u2) { if (uu_mp_to_enter(u, u2) != 0) return TRUE; } return FALSE; } int any_mp_to_leave_unit(int u) { int u2; for_all_unit_types(u2) { if (uu_mp_to_leave(u, u2) != 0) return TRUE; } return FALSE; } int any_enter_indep(int u) { int u2; for_all_unit_types(u2) { if (uu_can_enter_indep(u, u2)) return TRUE; } return FALSE; } /* Full details on the given type of unit. */ /* (The defaults should come from the *.def defaults!!) */ static void describe_utype(int u, char *key, TextBuffer *buf) { char sidetmpbuf[BUFSIZE]; int m, first, speedvaries, usesm, a; Side *side; append_help_phrase(buf, u_help(u)); if (u_point_value(u) > 0) { tbprintf(buf, " (point value %d)\n", u_point_value(u)); } /* Display the designer's notes for this type. */ if (u_notes(u) != lispnil) { tbcat(buf, "Notes:\n"); append_notes(buf, u_notes(u)); tbcat(buf, "\n\n"); } if (u_can_be_self(u)) { tbcat(buf, "Can be self-unit"); if (u_self_changeable(u)) tbcat(buf, "; side may choose another to be self-unit"); if (u_self_resurrects(u)) tbcat(buf, "; if dies, another becomes self-unit"); if (g_self_required()) tbcat(buf, " (must always have a self-unit during game)"); tbcat(buf, ".\n"); } if (u_possible_sides(u) != lispnil) { tbcat(buf, "Possible sides in this game: "); first = TRUE; for_all_sides(side) { if (type_allowed_on_side(u, side)) { if (first) first = FALSE; else tbcat(buf, ", "); tbcat(buf, shortest_side_title(side, sidetmpbuf)); } } tbcat(buf, ".\n"); } if (u_type_in_game_max(u) >= 0) { tbprintf(buf, "At most %d allowed in a game.\n", u_type_in_game_max(u)); } if (u_type_per_side_max(u) >= 0) { tbprintf(buf, "At most %d allowed on each side in a game.\n", u_type_per_side_max(u)); } if (u_acp(u) > 0) { tbprintf(buf, "Gets %d action point%s (ACP) each turn", u_acp(u), (u_acp(u) == 1 ? "" : "s")); if (u_acp_min(u) != 0) { tbprintf(buf, ", can go down to %d ACP", u_acp_min(u)); } if (u_acp_max(u) != -1) { tbprintf(buf, ", can go up to %d ACP", u_acp_max(u)); } if (u_free_acp(u) != 0) { tbprintf(buf, ", %d free", u_free_acp(u)); } tbcat(buf, ".\n"); if (uu_table_row_not_default(u, uu_acp_occ_effect, 100)) { tbcat(buf, " Effect on transport's ACP: "); uu_table_row_desc(buf, u, uu_acp_occ_effect, tb_mult_desc, NULL); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_acp_night_effect, 100)) { tbcat(buf, " Night effect on ACP: "); ut_table_row_desc(buf, u, ut_acp_night_effect, tb_mult_desc, "in"); tbcat(buf, ".\n"); } } else { tbcat(buf, "Does not act.\n"); } if (!u_direct_control(u)) { tbcat(buf, "Cannot be controlled directly by side.\n"); } if (u_speed(u) > 0) { if (u_speed(u) != 100) { tbcat(buf, "Normal speed (MP/ACP ratio) is "); tb_fraction_desc(buf, u_speed(u)); tbcat(buf, ".\n"); } speedvaries = FALSE; if (u_speed_wind_effect(u) != lispnil) { /* (should add mech to describe in detail) */ tbcat(buf, "Wind affects speed.\n"); speedvaries = TRUE; } if (u_speed_damage_effect(u) != lispnil) { /* (should add mech to describe in detail) */ tbcat(buf, "Damage affects speed.\n"); speedvaries = TRUE; } /* (should only list variation limits if actually needed to clip) */ if (speedvaries) { tbcat(buf, "Speed variation limited to between "); tb_fraction_desc(buf, u_speed_min(u)); tbcat(buf, " and "); tb_fraction_desc(buf, u_speed_max(u)); tbcat(buf, ".\n"); } tbcat(buf, "MP to enter cell: "); ut_table_row_desc(buf, u, ut_mp_to_enter, NULL, NULL); tbcat(buf, ".\n"); if (ut_table_row_not_default(u, ut_mp_to_leave, 0)) { tbcat(buf, "MP to leave cell: "); ut_table_row_desc(buf, u, ut_mp_to_leave, NULL, NULL); tbcat(buf, ".\n"); } if (any_mp_to_enter_unit(u)) { tbcat(buf, "MP to enter unit: "); uu_table_row_desc(buf, u, uu_mp_to_enter, NULL, NULL); tbcat(buf, ".\n"); } if (any_mp_to_leave_unit(u)) { tbcat(buf, "MP to leave unit: "); uu_table_row_desc(buf, u, uu_mp_to_leave, NULL, NULL); tbcat(buf, ".\n"); } if (any_enter_indep(u)) { tbcat(buf, "Can enter indep unit: "); uu_table_row_desc(buf, u, uu_can_enter_indep, NULL, NULL); tbcat(buf, ".\n"); } if (u_mp_to_leave_world(u) >= 0) { tbprintf(buf, "%d MP to leave the world entirely.\n", u_mp_to_leave_world(u)); } if (u_free_mp(u) > 0) { tbprintf(buf, "Gets up to %d free MP if needed to move.\n", u_free_mp(u)); } if (u_acp_to_move(u) > 0) { tbprintf(buf, "Uses %d ACP to move.\n", u_acp_to_move(u)); } else { tbcat(buf, "Cannot move by self.\n"); } } else { tbcat(buf, "Does not move.\n"); } tbprintf(buf, "Hit Points (HP): %d.", u_hp_max(u)); if (u_parts(u) > 1) { tbprintf(buf, " Parts: %d.", u_parts(u)); } if (u_hp_recovery(u) != 0) { tbprintf(buf, " Recovers by "); tb_fraction_desc(buf, u_hp_recovery(u)); tbprintf(buf, " HP each turn"); if (u_hp_to_recover(u) > 0) tbprintf(buf, ", if over %d HP", u_hp_to_recover(u)); tbcat(buf, "."); } tbcat(buf, "\n"); if (ut_table_row_not_default(u, ut_vanishes_on, 0)) { tbprintf(buf, "Vanishes if in: "); ut_table_row_desc(buf, u, ut_vanishes_on, tb_bool_desc, ""); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_wrecks_on, 0)) { tbprintf(buf, "Immediate wreck if in: "); ut_table_row_desc(buf, u, ut_vanishes_on, tb_bool_desc, ""); tbcat(buf, ".\n"); } /* Describe unit's transport capabilities. */ if (u_capacity(u) > 0 || uu_table_row_not_default(u, uu_capacity_x, 0)) { if (u_capacity(u) > 0) { tbprintf(buf, "Generic capacity for units is %d.\n", u_capacity(u)); if (uu_table_column_not_default(u, uu_size, 1)) { tbcat(buf, "Relative sizes of occupants: "); uu_table_column_desc(buf, u, uu_size, NULL, NULL); tbcat(buf, ".\n"); } } if (uu_table_row_not_default(u, uu_capacity_x, 0)) { tbcat(buf, "Dedicated space for units: "); uu_table_row_desc(buf, u, uu_capacity_x, NULL, NULL); tbcat(buf, ".\n"); } /* (should only display if < capacities would allow?) */ if (uu_table_row_not_default(u, uu_occ_max, -1)) { tbcat(buf, "Maximum number of occupants: "); uu_table_row_desc(buf, u, uu_occ_max, NULL, NULL); tbcat(buf, ".\n"); } if (u_occ_total_max(u) >= 0) { tbprintf(buf, "Maximum total of %d for all types together.\n", u_occ_total_max(u)); } } if (any_ut_capacity_x(u)) { tbcat(buf, "Exclusive terrain capacity: "); ut_table_row_desc(buf, u, ut_capacity_x, NULL, NULL); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_capacity_neg, 0)) { tbcat(buf, "Presence in cell negates connection capacity: "); ut_table_row_desc(buf, u, ut_capacity_neg, tb_bool_desc, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_zoc_range, 0)) { tbcat(buf, "Exerts ZOC out to: "); uu_table_row_desc(buf, u, uu_zoc_range, NULL, NULL); tbcat(buf, ".\n"); if (ut_table_row_not_default(u, ut_zoc_into, 1)) { tbcat(buf, "Exerts ZOC into: "); ut_table_row_desc(buf, u, ut_zoc_into, tb_bool_desc, "in"); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_zoc_from_terrain, 100)) { tbcat(buf, "Effect of own terrain: "); ut_table_row_desc(buf, u, ut_zoc_from_terrain, tb_mult_desc, "in"); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_mp_to_enter_zoc, -1)) { tbcat(buf, "MP to enter ZOC: "); uu_table_row_desc(buf, u, uu_mp_to_enter_zoc, NULL, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_mp_to_leave_zoc, 0)) { tbcat(buf, "MP to leave ZOC: "); uu_table_row_desc(buf, u, uu_mp_to_leave_zoc, NULL, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_mp_to_traverse_zoc, 0)) { tbcat(buf, "MP to traverse ZOC: "); uu_table_row_desc(buf, u, uu_mp_to_traverse_zoc, NULL, NULL); tbcat(buf, ".\n"); } } if (u_cxp_max(u) != 0) { tbprintf(buf, "Combat experience (CXP) maximum: %d.\n", u_cxp_max(u)); } if (u_morale_max(u) != 0) { tbprintf(buf, "Morale maximum: %d", u_morale_max(u)); if (u_morale_recovery(u) != 0) tbprintf(buf, ", recover %s each turn", u_morale_recovery(u)); tbcat(buf, ".\n"); } if (u_cp(u) != 1) { tbprintf(buf, "Construction points (CP): %d.\n", u_cp(u)); } if (u_tech_to_see(u) != 0) { tbprintf(buf, "Tech to see: %d.\n", u_tech_to_see(u)); } if (u_tech_to_own(u) != 0) { tbprintf(buf, "Tech to own: %d.\n", u_tech_to_own(u)); } if (u_tech_to_use(u) != 0) { tbprintf(buf, "Tech to use: %d.\n", u_tech_to_use(u)); } if (u_tech_to_build(u) != 0) { tbprintf(buf, "Tech to build: %d.\n", u_tech_to_build(u)); } if (u_tech_max(u) != 0) { tbprintf(buf, "Tech max: %d.\n", u_tech_max(u)); } if (u_tech_max(u) != 0 && u_tech_per_turn_max(u) != PROPHI) { tbprintf(buf, "Tech increase per turn max: %d.\n", u_tech_per_turn_max(u)); } if (u_tech_from_ownership(u) != 0) { tbprintf(buf, "Tech guaranteed by ownership: %d.\n", u_tech_from_ownership(u)); } if (u_tech_leakage(u) != 0) { tbprintf(buf, "Tech leakage: %d.\n", u_tech_leakage(u)); } if (u_acp(u) > 0 && type_can_develop(u) > 0 ) { tbcat(buf, "\nDevelop:\n"); tbcat(buf, "ACP to develop: "); uu_table_row_desc(buf, u, uu_acp_to_develop, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, " Tech gained: "); uu_table_row_desc(buf, u, uu_tech_per_develop, tb_fraction_desc, NULL); tbcat(buf, ".\n"); } if (u_acp(u) > 0 && (type_can_create(u) > 0 || type_can_complete(u) > 0 )) { tbcat(buf, "\nConstruction:\n"); if (type_can_create(u) > 0) { tbcat(buf, "ACP to create: "); uu_table_row_desc(buf, u, uu_acp_to_create, NULL, NULL); tbcat(buf, ".\n"); if (uu_table_row_not_default(u, uu_create_range, 0)) { tbcat(buf, " Creation distance max: "); uu_table_row_desc(buf, u, uu_create_range, NULL, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_creation_cp, 1)) { tbcat(buf, " CP upon creation: "); uu_table_row_desc(buf, u, uu_creation_cp, NULL, NULL); tbcat(buf, ".\n"); } } if (type_can_complete(u) > 0) { tbcat(buf, "ACP to build: "); uu_table_row_desc(buf, u, uu_acp_to_build, NULL, NULL); tbcat(buf, ".\n"); if (uu_table_row_not_default(u, uu_cp_per_build, 1)) { tbcat(buf, " CP added per build: "); uu_table_row_desc(buf, u, uu_cp_per_build, NULL, NULL); tbcat(buf, ".\n"); } } if (u_cp_per_self_build(u) > 0) { tbprintf(buf, "Can finish building self at %d cp, will add %d cp per action.\n", u_cp_to_self_build(u), u_cp_per_self_build(u)); } if (uu_table_row_not_default(u, uu_build_range, 0)) { tbcat(buf, "Range at which can build: "); uu_table_row_desc(buf, u, uu_build_range, NULL, NULL); tbcat(buf, ".\n"); } else { tbcat(buf, "Can build at own location.\n"); } /* Toolup help. */ if (type_can_toolup(u)) { tbcat(buf, "ACP to toolup: "); uu_table_row_desc(buf, u, uu_acp_to_toolup, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, " TP/toolup action: "); uu_table_row_desc(buf, u, uu_tp_per_toolup, NULL, NULL); tbcat(buf, ".\n"); /* (should put these with type beING built...) */ tbcat(buf, " TP to build: "); uu_table_row_desc(buf, u, uu_tp_to_build, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, " TP max: "); uu_table_row_desc(buf, u, uu_tp_max, NULL, NULL); tbcat(buf, ".\n"); } } if ((u_acp(u) > 0 && (type_can_attack(u) > 0 || u_acp_to_fire(u) > 0 || type_can_capture(u) > 0 )) || may_detonate(u) || uu_table_row_not_default(u, uu_protection, 100) || uu_table_row_not_default(u, uu_retreat_chance, 0) || uu_table_row_not_default(u, uu_acp_retreat, 0) || u_wrecked_type(u) != NONUTYPE ) { tbcat(buf, "\nCombat:\n"); if (type_can_attack(u) > 0) { tbcat(buf, "Can attack (ACP "); uu_table_row_desc(buf, u, uu_acp_to_attack, NULL, "vs"); tbcat(buf, ").\n"); if (uu_table_row_not_default(u, uu_attack_range, 1)) { tbcat(buf, "Attack range is "); uu_table_row_desc(buf, u, uu_attack_range, NULL, "vs"); tbcat(buf, ".\n"); tbcat(buf, "Attack range min is "); uu_table_row_desc(buf, u, uu_attack_range_min, NULL, "vs"); tbcat(buf, ".\n"); } } if (u_acp_to_fire(u) > 0) { tbprintf(buf, "Can fire (%d ACP), at ranges", u_acp_to_fire(u)); if (u_range_min(u) > 0) { tbprintf(buf, " from %d", u_range_min(u)); } tbprintf(buf, " up to %d", u_range(u)); tbcat(buf, ".\n"); } tbcat(buf, "Hit chances are "); uu_table_row_desc(buf, u, uu_hit, tb_percent_desc, "vs"); tbcat(buf, ".\n"); if (u_acp_to_fire(u) > 0) { if (uu_table_row_not_default(u, uu_fire_hit, -1)) { tbcat(buf, "Hit chances if firing are "); uu_table_row_desc(buf, u, uu_fire_hit, tb_percent_desc, "vs"); tbcat(buf, ".\n"); } else { tbcat(buf, "Hit chances if firing same as for regular combat.\n"); } } if (ut_table_row_not_default(u, ut_attack_terrain_effect, 100)) { tbcat(buf, "Effect of attacker's terrain is "); ut_table_row_desc(buf, u, ut_attack_terrain_effect, tb_mult_desc, "in"); tbcat(buf, ".\n"); } tbcat(buf, "Damage is "); uu_table_row_desc(buf, u, uu_damage, tb_dice_desc, "vs"); tbcat(buf, ".\n"); if (uu_table_row_not_default(u, uu_tp_damage, 0)) { tbcat(buf, "Tooling damage is "); uu_table_row_desc(buf, u, uu_tp_damage, tb_dice_desc, NULL); tbcat(buf, ".\n"); } if (u_acp_to_fire(u) > 0) { if (uu_table_row_not_default(u, uu_fire_damage, -1)) { tbcat(buf, "Damage if firing is "); uu_table_row_desc(buf, u, uu_fire_damage, NULL, "vs"); tbcat(buf, ".\n"); } else { tbcat(buf, "Damages if firing same as for regular combat.\n"); } } if (uu_table_row_not_default(u, uu_acp_to_defend, 1)) { tbcat(buf, "If attacked, ACP to defend is "); uu_table_row_desc(buf, u, uu_acp_to_defend, NULL, "vs"); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_defend_terrain_effect, 100)) { tbcat(buf, "If attacked, effect of own terrain is "); ut_table_row_desc(buf, u, ut_defend_terrain_effect, tb_mult_desc, "in"); tbcat(buf, ".\n"); } if (um_table_row_not_default(u, um_hit_by, 0)) { tbcat(buf, "Ammo needed to hit unit: "); um_table_row_desc(buf, u, um_hit_by, NULL); tbcat(buf, ".\n"); } if (type_can_capture(u) > 0) { tbcat(buf, "Can capture (ACP "); uu_table_row_desc(buf, u, uu_acp_to_capture, NULL, "vs"); tbcat(buf, ").\n"); tbcat(buf, "Chance to capture: "); uu_table_row_desc(buf, u, uu_capture, tb_percent_desc, "vs"); tbcat(buf, ".\n"); if (uu_table_row_not_default(u, uu_indep_capture, -1)) { tbcat(buf, "Chance to capture indep: "); uu_table_row_desc(buf, u, uu_indep_capture, tb_percent_desc, "vs"); tbcat(buf, ".\n"); } } if (may_detonate(u)) { /* Display all the different ways that a unit might detonate. */ if (u_acp_to_detonate(u) > 0) { tbprintf(buf, "Can detonate self (%d ACP).\n", u_acp_to_detonate(u)); } if (u_detonate_on_death(u)) { tbprintf(buf, "%d%% chance to detonate if destroyed in combat.\n", u_detonate_on_death(u)); } if (uu_table_row_not_default(u, uu_detonate_on_hit, 0)) { tbcat(buf, "Chance to detonate upon being hit: "); uu_table_row_desc(buf, u, uu_detonate_on_hit, tb_percent_desc, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_detonate_on_capture, 0)) { tbcat(buf, "Chance to detonate upon capture: "); uu_table_row_desc(buf, u, uu_detonate_on_capture, tb_percent_desc, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_detonate_approach_range, -1)) { tbcat(buf, "Will detonate upon approach within range: "); uu_table_row_desc(buf, u, uu_detonate_approach_range, NULL, NULL); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_detonation_accident, 0)) { tbcat(buf, "Chance of accidental detonation: "); ut_table_row_desc(buf, u, ut_detonation_accident, tb_percent_desc, "in"); tbcat(buf, ".\n"); } tbcat(buf, "Detonation damage at ground zero is "); uu_table_row_desc(buf, u, uu_detonation_damage_at, NULL, NULL); tbcat(buf, ".\n"); /* (should only display if effect range > 0) */ tbcat(buf, "Detonation damage to adjacent units is "); uu_table_row_desc(buf, u, uu_detonation_damage_adj, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, "Range of detonation effect on units is "); uu_table_row_desc(buf, u, uu_detonation_range, NULL, NULL); tbcat(buf, ".\n"); /* Damage decreases as inverse square of distance. */ if (ut_table_row_not_default(u, ut_detonation_damage, 0)) { tbcat(buf, "Chance of detonation damage to terrain is "); ut_table_row_desc(buf, u, ut_detonation_damage, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, "Range of detonation effect on terrain is "); ut_table_row_desc(buf, u, ut_detonation_range, NULL, NULL); tbcat(buf, ".\n"); } if (u_hp_per_detonation(u) < u_hp_max(u)) { tbprintf(buf, "Loses %d HP per detonation.\n", u_hp_per_detonation(u)); } else { tbprintf(buf, "Always destroyed by detonation.\n"); } } if (uu_table_row_not_default(u, uu_hp_min, 0)) { tbcat(buf, "Combat never reduces defender's HP below "); uu_table_row_desc(buf, u, uu_hp_min, NULL, "vs"); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_protection, 100)) { tbcat(buf, "Protection of occupants/transport is "); uu_table_row_desc(buf, u, uu_protection, tb_mult_desc, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_occ_combat, 100)) { tbcat(buf, "Combat effectiveness as occupant is "); uu_table_row_desc(buf, u, uu_occ_combat, tb_percent_desc, "in"); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_retreat_chance, 0)) { tbcat(buf, "Chance to retreat from combat is "); uu_table_row_desc(buf, u, uu_retreat_chance, tb_percent_desc, "vs"); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_acp_retreat, 0)) { tbcat(buf, "Extra ACP for retreating is "); uu_table_row_desc(buf, u, uu_acp_retreat, NULL, "vs"); tbcat(buf, ".\n"); } if (u_wrecked_type(u) != NONUTYPE) { tbprintf(buf, "Becomes %s when destroyed.\n", u_type_name(u_wrecked_type(u))); } } if (u_acp(u) > 0 && (u_acp_to_change_side(u) > 0 || u_acp_to_disband(u) > 0 || u_acp_to_transfer_part(u) > 0 || uu_table_row_not_default(u, uu_acp_to_repair, 0) || uu_table_row_not_default(u, uu_acp_to_change_type, 0) || ut_table_row_not_default(u, ut_acp_to_add_terrain, 0) || ut_table_row_not_default(u, ut_acp_to_remove_terrain, 0) )) { tbcat(buf, "\nOther Actions:\n"); if (u_acp_to_change_side(u) > 0) { tbprintf(buf, "Can be given to another side (%d ACP).\n", u_acp_to_change_side(u)); } if (u_acp_to_disband(u) > 0) { tbprintf(buf, "Can be disbanded (%d ACP)", u_acp_to_disband(u)); if (u_hp_per_disband(u) < u_hp_max(u)) { tbprintf(buf, ", losing %d HP per action", u_hp_per_disband(u)); } tbcat(buf, ".\n"); if (um_table_row_not_default(u, um_supply_per_disband, 0)) { tbprintf(buf, "Supply yield per disband action: "); um_table_row_desc(buf, u, um_supply_per_disband, NULL); tbcat(buf, ".\n"); } if (um_table_row_not_default(u, um_recycleable, 0)) { tbprintf(buf, "Additional material when unit is gone: "); um_table_row_desc(buf, u, um_recycleable, NULL); tbcat(buf, ".\n"); } } if (u_acp_to_transfer_part(u) > 0) { tbprintf(buf, "Can transfer parts (%d ACP).\n", u_acp_to_transfer_part(u)); } if (uu_table_row_not_default(u, uu_acp_to_repair, 0)) { tbcat(buf, "ACP to repair is "); uu_table_row_desc(buf, u, uu_acp_to_repair, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, "Repair performance is "); uu_table_row_desc(buf, u, uu_repair, NULL, NULL); tbcat(buf, ".\n"); tbcat(buf, "Min HP to repair is "); uu_table_row_desc(buf, u, uu_hp_to_repair, NULL, NULL); tbcat(buf, ".\n"); } if (uu_table_row_not_default(u, uu_acp_to_change_type, 0)) { tbcat(buf, "ACP to change type is "); uu_table_row_desc(buf, u, uu_acp_to_change_type, NULL, NULL); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_acp_to_add_terrain, 0)) { tbcat(buf, "ACP to add/change terrain is "); ut_table_row_desc(buf, u, ut_acp_to_add_terrain, NULL, NULL); tbcat(buf, ".\n"); if (ut_table_row_not_default(u, ut_alter_range, 0)) { tbcat(buf, " Can alter up to range "); ut_table_row_desc(buf, u, ut_alter_range, NULL, NULL); tbcat(buf, ".\n"); } } if (ut_table_row_not_default(u, ut_acp_to_remove_terrain, 0)) { tbcat(buf, "ACP to remove terrain is "); ut_table_row_desc(buf, u, ut_acp_to_remove_terrain, NULL, NULL); tbcat(buf, ".\n"); if (ut_table_row_not_default(u, ut_alter_range, 0)) { tbcat(buf, " Can alter up to range "); ut_table_row_desc(buf, u, ut_alter_range, NULL, NULL); tbcat(buf, ".\n"); } } } if (!g_see_all()) { tbcat(buf, "\nVision:\n"); tbprintf(buf, "%d%% chance to be seen at outset of game.\n", u_already_seen(u)); tbprintf(buf, "%d%% chance to be seen at outset of game if independent.\n", u_already_seen_indep(u)); if (u_see_always(u)) tbcat(buf, "Always seen if terrain has been seen.\n"); else tbcat(buf, "Not always seen even if terrain has been seen.\n"); /* (should only say if can be an occupant) */ if (uu_table_row_not_default(u, uu_occ_vision, 100)) { tbcat(buf, "Vision effect when occupying: "); uu_table_row_desc(buf, u, uu_occ_vision, NULL, "in"); tbcat(buf, ".\n"); } /* (should only say if unit can have occupants) */ if (u_see_occupants(u)) tbcat(buf, "Occupants seen if unit has been seen.\n"); else tbcat(buf, "Occupants not seen even if unit has been seen.\n"); switch (u_vision_range(u)) { case -1: tbcat(buf, "Can never see other units.\n"); break; case 0: tbcat(buf, "Can see other units at own location.\n"); break; case 1: /* Default range, no need to say anything. */ break; default: tbprintf(buf, "Can see units up to %d cells away.\n", u_vision_range(u)); break; } if (u_vision_range(u) > 0 && u_vision_bend(u) < 100) { tbcat(buf, "Vision is line-of-sight"); if (u_vision_bend(u) > 0) tbprintf(buf, "bending by %d%%", u_vision_bend(u)); tbcat(buf, ".\n"); if (ut_table_row_not_default(u, ut_eye_height, 0)) { tbcat(buf, "Effective eye height is "); ut_table_row_desc(buf, u, ut_eye_height, NULL, "in"); tbcat(buf, ".\n"); } } if (ut_table_row_not_default(u, ut_body_height, 0)) { tbcat(buf, "Effective body height is "); ut_table_row_desc(buf, u, ut_body_height, NULL, "in"); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_weapon_height, 0)) { tbcat(buf, "Effective weapon height is "); ut_table_row_desc(buf, u, ut_weapon_height, NULL, "in"); tbcat(buf, ".\n"); } if (u_vision_range(u) >= 0 && uu_table_row_not_default(u, uu_see_at, 100)) { tbcat(buf, "Chance to see if in same cell is "); uu_table_row_desc(buf, u, uu_see_at, tb_percent_desc, NULL); tbcat(buf, ".\n"); } if (u_vision_range(u) >= 1 && uu_table_row_not_default(u, uu_see_adj, 100)) { tbcat(buf, "Chance to see if adjacent is "); uu_table_row_desc(buf, u, uu_see_adj, tb_percent_desc, NULL); tbcat(buf, ".\n"); } if (u_vision_range(u) >= 2 && uu_table_row_not_default(u, uu_see, 100)) { tbcat(buf, "Chance to see in general is "); uu_table_row_desc(buf, u, uu_see, tb_percent_desc, NULL); tbcat(buf, ".\n"); } if (u_see_terrain_captured(u) > 0) tbprintf(buf, "%d%% chance for enemy to see your terrain view if this type captured.\n", u_see_terrain_captured(u)); } if (ut_table_row_not_default(u, ut_accident_hit, 0) || ut_table_row_not_default(u, ut_accident_vanish, 0)) { tbcat(buf, "\nAccidents:\n"); if (ut_table_row_not_default(u, ut_accident_hit, 0)) { tbcat(buf, "Chance to be damaged in an accident: "); ut_table_row_desc(buf, u, ut_accident_hit, tb_percent_100th_desc, "in"); tbcat(buf, ".\n"); tbcat(buf, "Amount of damage: "); ut_table_row_desc(buf, u, ut_accident_damage, tb_dice_desc, "in"); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_accident_vanish, 0)) { tbcat(buf, "Chance to vanish in an accident: "); ut_table_row_desc(buf, u, ut_accident_vanish, tb_percent_100th_desc, "in"); tbcat(buf, ".\n"); } } if (ut_table_row_not_default(u, ut_attrition, 0)) { tbcat(buf, "\nAttrition:\n"); ut_table_row_desc(buf, u, ut_attrition, tb_percent_100th_desc, "in"); tbcat(buf, ".\n"); } /* Don't bother with this if economy code is not running. */ if (nummtypes > 0 && g_economy()) { tbcat(buf, "\nMaterial Handling:\n"); for_all_material_types(m) { usesm = FALSE; tbprintf(buf, " %s", m_type_name(m)); if (um_base_production(u, m) > 0) { tbprintf(buf, ", %d basic production", um_base_production(u, m)); usesm = TRUE; } if (um_occ_production(u, m) >= 0) { tbprintf(buf, ", %d basic production if occupant", um_occ_production(u, m)); usesm = TRUE; } if (um_acp_to_extract(u, m) > 0) { tbprintf(buf, ", %d ACP to extract", um_acp_to_extract(u, m)); usesm = TRUE; } if (um_storage_x(u, m) > 0) { tbprintf(buf, ", %d storage", um_storage_x(u, m)); if (um_initial(u, m) > 0) { tbprintf(buf, " (%d at start of game)", min(um_initial(u, m), um_storage_x(u, m))); } usesm = TRUE; } if (um_base_consumption(u, m) > 0) { tbprintf(buf, ", %d basic consumption", um_base_consumption(u, m)); if (um_consumption_as_occupant(u, m) != 100) { tbprintf(buf, ", times %d%% if occupant", um_consumption_as_occupant(u, m)); } usesm = TRUE; } if (um_to_act(u, m) != 0) { tbprintf(buf, ", needs %d to act at all", um_to_act(u, m)); usesm = TRUE; } if (um_to_move(u, m) != 0) { tbprintf(buf, ", needs %d to move", um_to_move(u, m)); usesm = TRUE; } if (um_consumption_per_move(u, m) != 0) { tbprintf(buf, ", %d consumed per move", um_consumption_per_move(u, m)); usesm = TRUE; } if (um_to_attack(u, m) != 0) { tbprintf(buf, ", needs %d to attack", um_to_attack(u, m)); usesm = TRUE; } if (um_consumption_per_attack(u, m) != 0) { tbprintf(buf, ", %d consumed per attack", um_consumption_per_attack(u, m)); usesm = TRUE; } if (u_acp_to_fire(u) > 0 && um_to_fire(u, m) != 0) { tbprintf(buf, ", needs %d to fire", um_to_fire(u, m)); usesm = TRUE; } if (um_to_create(u, m) != 0) { tbprintf(buf, ", builder needs %d to create", um_to_create(u, m)); usesm = TRUE; } if (um_consumption_on_creation(u, m) != 0) { tbprintf(buf, ", %d used in creation", um_consumption_on_creation(u, m)); usesm = TRUE; } if (um_to_build(u, m) != 0) { tbprintf(buf, ", builder needs %d to build", um_to_build(u, m)); usesm = TRUE; } if (um_consumption_per_build(u, m) != 0) { tbprintf(buf, ", %d used to add 1 CP", um_consumption_per_build(u, m)); usesm = TRUE; } if (um_to_repair(u, m) != 0) { tbprintf(buf, ", repairer needs %d to repair", um_to_repair(u, m)); usesm = TRUE; } if (um_consumption_per_repair(u, m) != 0) { tbprintf(buf, ", %d used to restore 1 HP", um_consumption_per_repair(u, m)); usesm = TRUE; } if (usesm) { if (um_inlength(u, m) > 0) { tbprintf(buf, ", receive from up to %d cells away", um_inlength(u, m)); } if (um_outlength(u, m) > 0) { tbprintf(buf, ", send up to %d cells away", um_outlength(u, m)); } } else { tbcat(buf, " (none)"); } tbcat(buf, "\n"); } /* Productivity adjustment due to terrain applies to all material types equally, but only display if unit produces. */ if (um_table_row_not_default(u, um_base_production, 0)) { if (ut_table_row_not_default(u, ut_productivity, 100)) { tbcat(buf, "Productivity adjustment for terrain is "); ut_table_row_desc(buf, u, ut_productivity, tb_mult_desc, "in"); tbcat(buf, ".\n"); } if (ut_table_row_not_default(u, ut_productivity_adj, 0)) { tbcat(buf, "Productivity adjustment for adjacent terrain is "); ut_table_row_desc(buf, u, ut_productivity_adj, tb_mult_desc, "in"); tbcat(buf, ".\n"); } if (um_table_row_not_default(u, um_productivity_min, 0)) { tbcat(buf, "Minimum net adjustment is "); um_table_row_desc(buf, u, um_productivity_min, tb_mult_desc); tbcat(buf, ".\n"); } if (um_table_row_not_default(u, um_productivity_max, TABHI)) { tbcat(buf, "Maximum net adjustment is "); um_table_row_desc(buf, u, um_productivity_max, tb_mult_desc); tbcat(buf, ".\n"); } } } if (numatypes > 0) { /* Now also prints "None" if no advances are required. */ int found = FALSE; tbcat(buf, "\nRequired advances to build: "); for_all_advance_types(a) { if (ua_needed_to_build(u, a) > 0) { if (found) tbprintf(buf, ", "); tbprintf(buf, "%s", a_type_name(a)); found = TRUE; } } if (found) tbprintf(buf, ".\n"); else tbprintf(buf, "None.\n"); } tbcat(buf, "\n"); /* (should display weather interaction here) */ if (uu_table_row_not_default(u, uu_auto_repair, 0)) { tbcat(buf, "Auto-repair of lost HP per turn: "); uu_table_row_desc(buf, u, uu_auto_repair, NULL, NULL); tbcat(buf, ".\n"); if (uu_table_row_not_default(u, uu_auto_repair_range, 0)) { tbcat(buf, "Range of auto-repair: "); uu_table_row_desc(buf, u, uu_auto_repair_range, NULL, NULL); tbcat(buf, ".\n"); } } if (u_spy_chance(u) > 0 /* and random event in use */) { tbprintf(buf, "%d%% chance to spy, on units up to %d away.", u_spy_chance(u), u_spy_range(u)); } if (u_revolt(u) > 0 /* and random event in use */) { tb_fraction_desc(buf, u_revolt(u)); tbcat(buf, "%% chance of revolt.\n"); } if (u_lost_wreck(u) > 0 || u_lost_vanish(u) > 0 || u_lost_revolt(u) > 0 || uu_table_row_not_default(u, uu_lost_surrender, 0)) { tbcat(buf, "\nFate if side loses:"); if (u_lost_wreck(u) > 0) { tbcat(buf, " "); tb_percent_100th_desc(buf, u_lost_wreck(u)); tbcat(buf, " chance to wreck"); } if (u_lost_wreck(u) < 10000 && u_lost_vanish(u) > 0) { tbcat(buf, " "); tb_percent_100th_desc(buf, u_lost_vanish(u)); tbcat(buf, " chance to vanish"); } if (u_lost_wreck(u) < 10000 && u_lost_vanish(u) < 10000 && u_lost_revolt(u) > 0) { tbcat(buf, " "); tb_percent_100th_desc(buf, u_lost_revolt(u)); tbcat(buf, " chance to revolt"); } if (u_lost_wreck(u) < 10000 && u_lost_vanish(u) < 10000 && u_lost_revolt(u) < 10000 && uu_table_row_not_default(u, uu_lost_surrender, 0)) { tbcat(buf, " chance to surrender to nearby unit, "); uu_table_row_desc(buf, u, uu_lost_surrender, tb_percent_100th_desc, "to"); } if (u_lost_wreck(u) == 0 && u_lost_vanish(u) == 0 && u_lost_revolt(u) == 0 && !uu_table_row_not_default(u, uu_lost_surrender, 0)) tbcat(buf, " survival"); tbcat(buf, ".\n"); } #ifdef DESIGNERS if (numdesigners > 0) { tbcat(buf, "FOR DESIGNERS:\n"); tbprintf(buf, "Internal name is \"%s\".\n", u_internal_name(u)); tbprintf(buf, "Short name is \"%s\".\n", u_short_name(u)); if (u_assign_number(u)) tbcat(buf, "New units get assigned a number.\n"); } #endif /* DESIGNERS */ } int may_detonate(int u) { return ((u_acp(u) > 0 && u_acp_to_detonate(u) > 0) || u_detonate_on_death(u) > 0 || uu_table_row_not_default(u, uu_detonate_on_hit, 0) || uu_table_row_not_default(u, uu_detonate_on_capture, 0) || uu_table_row_not_default(u, uu_detonate_approach_range, -1) || ut_table_row_not_default(u, ut_detonation_accident, 0) ); } static void describe_mtype(int m, char *key, TextBuffer *buf) { append_help_phrase(buf, m_help(m)); /* Display the designer's notes for this type. */ if (m_notes(m) != lispnil) { tbcat(buf, "\nNotes:\n"); append_notes(buf, m_notes(m)); tbcat(buf, "\n\n"); } if (m_people(m) > 0) { tbprintf(buf, "1 of this represents %d individuals.", m_people(m)); } /* (should display unit columns here) */ /* Add an extra space, to make sure the description isn't empty. */ tbcat(buf, " "); } static void describe_ttype(int t, char *key, TextBuffer *buf) { int m, ct; append_help_phrase(buf, t_help(t)); /* Display the subtype. */ switch (t_subtype(t)) { case cellsubtype: break; case bordersubtype: tbcat(buf, " (a border type)\n"); break; case connectionsubtype: tbcat(buf, " (a connection type)\n"); break; case coatingsubtype: tbcat(buf, " (a coating type)\n"); break; } /* Display the designer's notes for this type. */ if (t_notes(t) != lispnil) { tbcat(buf, "\nNotes:\n"); append_notes(buf, t_notes(t)); tbcat(buf, "\n\n"); } if (t_liquid(t)) tbcat(buf, "Represents water or other liquid.\n"); tbprintf(buf, "Generic unit capacity is %d.\n", t_capacity(t)); if (t_people_max(t) >= 0) { tbprintf(buf, "Up to %d people may live in this type of terrain.\n", t_people_max(t)); } if (any_elev_variation) { if (t_elev_min(t) == t_elev_max(t)) { tbprintf(buf, "Elevation is always %d.\n", t_elev_min(t)); } else { tbprintf(buf, "Elevations fall between %d and %d.\n", t_elev_min(t), t_elev_max(t)); } } if (t_thickness(t) > 0) { tbprintf(buf, "Thickness is %d.\n", t_thickness(t)); } if (any_temp_variation) { if (t_temp_min(t) == t_temp_max(t)) { tbprintf(buf, "Temperature is always %d.\n", t_temp_min(t)); } else { tbprintf(buf, "Temperatures fall between %d and %d, averaging %d.\n", t_temp_min(t), t_temp_max(t), t_temp_avg(t)); } if (t_temp_variability(t) > 0) { tbprintf(buf, "Temperature varies randomly, up to %d each turn.\n", t_temp_variability(t)); } } if (any_wind_variation) { if (t_wind_force_min(t) == t_wind_force_max(t)) { tbprintf(buf, "Wind force is always %d.\n", t_wind_force_min(t)); } else { tbprintf(buf, "Wind forces fall between %d and %d, averaging %d.\n", t_wind_force_min(t), t_wind_force_max(t), t_wind_force_avg(t)); } if (t_wind_force_variability(t) > 0) { tbprintf(buf, "%d%% chance each turn that wind force will change.\n", t_wind_force_variability(t)); } if (t_wind_variability(t) > 0) { tbprintf(buf, "%d%% chance each turn that wind direction will change.\n", t_wind_variability(t)); } } if (any_clouds) { if (t_clouds_min(t) == t_clouds_max(t)) { tbprintf(buf, "Cloud cover is always %d.\n", t_clouds_min(t)); } else { tbprintf(buf, "Cloud cover falls between %d and %d\n", t_clouds_min(t), t_clouds_max(t)); } } /* Display relationships with materials. */ if (nummtypes > 0) { for_all_material_types(m) { if (tm_storage_x(t, m) > 0 || tm_production(t, m) > 0 || tm_consumption(t, m) > 0) { tbprintf(buf, "%s:", m_type_name(m)); } if (tm_storage_x(t, m) > 0) { tbprintf(buf, " Can store up to %d", tm_storage_x(t, m)); tbprintf(buf, " (normally starts game with %d)", min(tm_initial(t, m), tm_storage_x(t, m))); tbcat(buf, ".\n"); tbprintf(buf, " Sides will%s always know current amount accurately.\n", (tm_see_always(t, m) ? "" : " not")); } if (tm_production(t, m) > 0 || tm_consumption(t, m) > 0) { tbprintf(buf, " Produces %d and consumes %d each turn.\n", tm_production(t, m), tm_consumption(t, m)); } } } /* Display relationships with any coating terrain types. */ if (numcoattypes > 0) { tbcat(buf, "Coatings:\n"); for_all_terrain_types(ct) { if (t_is_coating(ct)) { tbprintf(buf, "%s coats, depths %d up to %d", t_type_name(ct), tt_coat_min(ct, t), tt_coat_max(ct, t)); } } } /* Display damaged types. */ if (tt_table_row_not_default(t, tt_damaged_type, 0)) { tbcat(buf, " Type after being damaged: "); tt_table_row_desc(buf, t, tt_damaged_type, NULL); tbcat(buf, ".\n"); } /* Display exhaustion types. */ if (nummtypes > 0) { for_all_material_types(m) { if (tm_change_on_exhaust(t, m) > 0 && tm_exhaust_type(t, m) != NONTTYPE) { tbprintf(buf, "If exhausted of %s, %d%% chance to change to %s.\n", m_type_name(m), tm_change_on_exhaust(t, m), t_type_name(tm_exhaust_type(t, m)) ); } } } #ifdef DESIGNERS if (numdesigners > 0) { tbcat(buf, "\nFOR DESIGNERS:\n"); if (t_subtype_x(t) == c_number(symbol_value(intern_symbol(keyword_name(K_RIVER_X))))) { tbcat(buf, "Considered to be a type of river.\n"); } else if (t_subtype_x(t) == c_number(symbol_value(intern_symbol(keyword_name(K_ROAD_X))))) { tbcat(buf, "Considered to be a type of road.\n"); } if (tt_table_row_not_default(t, tt_drawable, 1)) { tbcat(buf, "Draw over terrain: "); tt_table_row_desc(buf, t, tt_drawable, NULL); tbcat(buf, ".\n"); } } #endif /* DESIGNERS */ } static void describe_atype(int a, char *key, TextBuffer *buf) { append_help_phrase(buf, a_help(a)); /* Display the designer's notes for this type. */ if (a_notes(a) != lispnil) { tbcat(buf, "\nNotes:\n"); append_notes(buf, a_notes(a)); tbcat(buf, "\n\n"); } tbprintf(buf, "Research points (RP) to discover: %d.\n", a_rp(a)); tbcat(buf, "\n"); if (aa_table_row_not_default(a, aa_needed_to_research, 0)) { tbcat(buf, "Prerequisite advances: "); aa_table_row_desc(buf, a, aa_needed_to_research, tb_bool_desc); tbcat(buf, ".\n"); } else { tbcat(buf, "No prerequisites.\n"); } if (aa_table_column_not_default(a, aa_needed_to_research, 0)) { tbcat(buf, "Enables: "); aa_table_column_desc(buf, a, aa_needed_to_research, tb_bool_desc); tbcat(buf, ".\n"); } else { tbcat(buf, "Enables no further advances.\n"); } } static void describe_scorekeepers(int arg, char *key, TextBuffer *buf) { int i = 1; Scorekeeper *sk; if (scorekeepers == NULL) { tbcat(buf, "No scores are being kept."); } else { for_all_scorekeepers(sk) { if (numscorekeepers > 1) { tbprintf(buf, "%d. ", i++); } if (symbolp(sk->body) && match_keyword(sk->body, K_LAST_SIDE_WINS)) { tbcat(buf, "The last side left in the game wins."); /* (should mention point values also) */ } else if (symbolp(sk->body) && match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) { tbcat(buf, "The last alliance left in the game wins."); /* (should mention point values also) */ } else { tbcat(buf, "(an indescribably complicated scorekeeper)"); } tbcat(buf, "\n"); } } } /* List each synthesis method and its parameters. */ static void describe_setup(int arg, char *key, TextBuffer *buf) { int u, t, t2, methkey; Obj *synthlist, *methods, *method; synthlist = g_synth_methods(); if (synthlist == lispnil) tbcat(buf, "No synthesis done when setting up this game.\n"); else tbcat(buf, "Synthesis done when setting up this game:\n"); for (methods = synthlist; methods != lispnil; methods = cdr(methods)) { method = car(methods); if (symbolp(method)) { methkey = keyword_code(c_string(method)); switch (methkey) { case K_MAKE_COUNTRIES: tbcat(buf, "\nCountries:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); tbprintf(buf, " %d cells across, between %d and %d cells apart.\n", 2 * g_radius_min() + 1, g_separation_min(), g_separation_max()); if (t_property_not_default(t_country_min, 0)) { tbcat(buf, " Minimum terrain in each country: "); t_property_desc(buf, t_country_min, NULL); tbcat(buf, ".\n"); } if (t_property_not_default(t_country_max, -1)) { tbcat(buf, " Maximum terrain in each country: "); t_property_desc(buf, t_country_max, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_start_with, 0)) { tbcat(buf, " Start with: "); u_property_desc(buf, u_start_with, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_indep_near_start, 0)) { tbcat(buf, " Independents nearby: "); u_property_desc(buf, u_indep_near_start, NULL); tbcat(buf, ".\n"); } tbcat(buf, " Favored terrain:\n"); for_all_unit_types(u) { if (u_start_with(u) > 0 || u_indep_near_start(u)) { tbprintf(buf, " %s: ", u_type_name(u)); ut_table_row_desc(buf, u, ut_favored, NULL, NULL); tbcat(buf, "\n"); } } if (g_radius_max() != 0) { tbcat(buf, "Country growth:\n"); if (g_radius_max() == -1) { tbcat(buf, " Up to entire world"); } else { tbprintf(buf, " Up to %d cells across", 2 * g_radius_max() + 1); } tbprintf(buf, ", %d chance to stop if blocked.\n", g_growth_stop()); if (t_property_not_default(t_country_growth, 100)) { tbcat(buf, " Growth chance, by terrain: "); t_property_desc(buf, t_country_max, tb_percent_desc); tbcat(buf, ".\n"); } if (t_property_not_default(t_country_takeover, 0)) { tbcat(buf, " Takeover chance, by terrain: "); t_property_desc(buf, t_country_takeover, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_unit_growth, 0)) { tbcat(buf, " Chance for additional unit: "); u_property_desc(buf, u_unit_growth, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_indep_growth, 0)) { tbcat(buf, " Chance for additional independent unit: "); u_property_desc(buf, u_indep_growth, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_unit_takeover, 0)) { tbcat(buf, " Chance to take over units: "); u_property_desc(buf, u_unit_takeover, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_indep_takeover, 0)) { tbcat(buf, " Chance to take over independent unit: "); u_property_desc(buf, u_indep_takeover, NULL); tbcat(buf, ".\n"); } if (u_property_not_default(u_country_units_max, -1)) { tbcat(buf, " Maximum units in country: "); u_property_desc(buf, u_country_units_max, NULL); tbcat(buf, ".\n"); } if (t_property_not_default(t_country_people, 0)) { tbcat(buf, " People takeover chance, by terrain: "); t_property_desc(buf, t_country_people, NULL); tbcat(buf, ".\n"); } if (t_property_not_default(t_indep_people, 0)) { tbcat(buf, " Independent people chance: "); t_property_desc(buf, t_indep_people, NULL); tbcat(buf, ".\n"); } } break; case K_MAKE_EARTHLIKE_TERRAIN: tbcat(buf, "\nEarthlike terrain:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); tbprintf(buf, " Terrain around edge is %s.\n", t_type_name(g_edge_terrain())); /* (should describe tt_adj_terr_effect workings here) */ break; case K_MAKE_FRACTAL_PTILE_TERRAIN: tbcat(buf, "\nFractal percentile terrain:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); tbprintf(buf, " Alt blobs density %d, size %d, height %d\n", g_alt_blob_density(), g_alt_blob_size(), g_alt_blob_height()); tbprintf(buf, " %d smoothing passes\n", g_alt_smoothing()); tbcat(buf, " Lower percentiles: "); t_property_desc(buf, t_alt_min, NULL); tbcat(buf, ".\n"); tbcat(buf, " Upper percentiles: "); t_property_desc(buf, t_alt_max, NULL); tbcat(buf, ".\n"); tbprintf(buf, " Wet blobs density %d, size %d, height %d\n", g_wet_blob_density(), g_wet_blob_size(), g_wet_blob_height()); tbprintf(buf, " %d smoothing passes\n", g_wet_smoothing()); tbcat(buf, " Lower percentiles: "); t_property_desc(buf, t_wet_min, NULL); tbcat(buf, ".\n"); tbcat(buf, " Upper percentiles: "); t_property_desc(buf, t_wet_max, NULL); tbcat(buf, ".\n"); tbprintf(buf, " Terrain around edge is %s.\n", t_type_name(g_edge_terrain())); /* (should describe tt_adj_terr_effect workings here) */ break; case K_MAKE_INDEPENDENT_UNITS: tbcat(buf, "\nIndependent units:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); for_all_unit_types(u) { if (ut_table_row_not_default(u, ut_indep_density, 0)) { tbprintf(buf, " Chance of independent %s:", u_type_name(u)); ut_table_row_desc(buf, u, ut_indep_density, tb_percent_100th_desc, "in"); tbcat(buf, ".\n"); } } /* (should show indep people) */ break; case K_MAKE_INITIAL_MATERIALS: tbcat(buf, "\nMaterials:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); /* (should show unit and terrain initial supply) */ break; case K_MAKE_MAZE_TERRAIN: tbcat(buf, "\nMaze terrain:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); tbprintf(buf, " %d.%2.2d%% of maze is room.\n", g_maze_room() / 100, g_maze_room() % 100); tbcat(buf, "Room terrain types will be"); for_all_terrain_types(t) { if (t_maze_room_occurrence(t) > 0) { tbprintf(buf, " %s(%d)", t_type_name(t), t_maze_room_occurrence(t)); } } tbcat(buf, ".\n"); tbprintf(buf, " %d.%2.2d%% of maze is passageway.\n", g_maze_passage() / 100, g_maze_passage() % 100); tbcat(buf, "Passageway terrain types will be"); for_all_terrain_types(t) { if (t_maze_passage_occurrence(t) > 0) { tbprintf(buf, " %s(%d)", t_type_name(t), t_maze_passage_occurrence(t)); } } tbcat(buf, ".\n"); tbprintf(buf, " Terrain around edge is %s.\n", t_type_name(g_edge_terrain())); break; case K_MAKE_RANDOM_DATE: tbcat(buf, "\nRandom date:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); break; case K_MAKE_RANDOM_TERRAIN: tbcat(buf, "\nRandom terrain:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); tbcat(buf, " Terrain types will be"); for_all_terrain_types(t) { if (t_occurrence(t) > 0) { tbprintf(buf, " %s(%d)", t_type_name(t), t_occurrence(t)); } } tbcat(buf, ".\n"); tbprintf(buf, " Terrain around edge is %s.\n", t_type_name(g_edge_terrain())); break; case K_MAKE_RIVERS: tbcat(buf, "\nRivers:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); if (t_property_not_default(t_river_chance, 0)) { tbcat(buf, " Chance to be river source: "); t_property_desc(buf, t_river_chance, tb_percent_100th_desc); tbcat(buf, ".\n"); if (g_river_sink_terrain() != NONTTYPE) tbprintf(buf, " Sink is %s.\n", t_type_name(g_river_sink_terrain())); else tbcat(buf, " No special sink terrain.\n"); } for_all_terrain_types(t) { if (tt_table_row_not_default(t, tt_adj_terr_effect, -1)) { tbprintf(buf, " River of type %s incompatible with", t_type_name(t)); for_all_terrain_types(t2) { if (tt_adj_terr_effect(t, t2) >= 0) tbprintf(buf, " %s", t_type_name(t2)); } } } break; case K_MAKE_ROADS: tbcat(buf, "\nRoads:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); tbcat(buf, " Chance to run:\n"); for_all_unit_types(u) { if (uu_table_row_not_default(u, uu_road_chance, 0)) { tbprintf(buf, " From %s: ", u_type_name(u)); uu_table_row_desc(buf, u, uu_road_chance, tb_percent_desc, "to"); tbcat(buf, "\n"); } if (u_spur_chance(u) > 0) { tbprintf(buf, " %d%% chance of spur, if within %d of road.\n", u_spur_chance(u), u_spur_range(u)); } if (u_road_to_edge_chance(u) > 0) { tbprintf(buf, " %d%% chance of road to edge.\n", u_road_to_edge_chance(u)); } } for_all_terrain_types(t) { if (t_subtype(t) == cellsubtype && tt_table_row_not_default(t, tt_road_into_chance, 0)) { tbprintf(buf, " Routing of road from %s: ", t_type_name(t)); /* Note: this is actually a weight, not a percent chance. */ tt_table_row_desc(buf, t, tt_road_into_chance, tb_value_desc /*, "to" */); tbcat(buf, ".\n"); } } if (g_edge_road_density() > 0) { tbcat(buf, " "); tb_percent_100th_desc(buf, g_edge_road_density()); tbcat(buf, " of edge gets road run to another edge.\n"); } break; case K_MAKE_WEATHER: tbcat(buf, "\nWeather:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); break; case K_NAME_GEOGRAPHICAL_FEATURES: tbcat(buf, "\nNames for geographical features:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); break; case K_NAME_UNITS_RANDOMLY: tbcat(buf, "\nNames for units:"); describe_synth_run(buf, methkey); tbcat(buf, "\n"); break; default: tbprintf(buf, "\n%s:", c_string(method)); describe_synth_run(buf, methkey); tbcat(buf, "\n"); break; } } else if (consp(method)) { /* (what?) */ } } tbcat(buf, "\n"); if (g_side_lib() != lispnil) tbprintf(buf, "%d choices in side library.\n", length(g_side_lib())); } static void describe_synth_run(TextBuffer *buf, int methkey) { int calls, runs; if (get_synth_method_uses(methkey, &calls, &runs)) { if (calls > 0) { if (calls == 1 && runs == 1) { tbcat(buf, " (was run)"); } else if (calls == 1 && runs == 0) { tbcat(buf, " (was not run)"); } else { tbprintf(buf, " (was called %d times, was run %d times)", calls, runs); } } else { tbcat(buf, " (not attempted)"); } } else { tbcat(buf, " (???)"); } } static void describe_world(int arg, char *key, TextBuffer *buf) { tbprintf(buf, "World circumference: %d.\n", world.circumference); tbcat(buf, "\n"); tbprintf(buf, "Area in world: %d wide x %d high", area.width, area.height); if (area.width == world.circumference) tbcat(buf, " (wraps completely around world). "); else tbcat(buf, ". "); tbprintf(buf, "Latitude: %d. Longitude: %d.\n", area.latitude, area.longitude); tbcat(buf, "\n"); if (elevations_defined()) { tbprintf(buf, "Elevations range from %d to %d, averaging %d\n", area.minelev, area.maxelev, area.avgelev); tbprintf(buf, "Cells are %d across.\n", area.cellwidth); } if (world.yearlength > 1) { tbprintf(buf, "Length of year: %d turns.\n", world.yearlength); } if (world.daylength != 1) { tbprintf(buf, "Length of day: %d turns.\n", world.daylength); tbprintf(buf, "Percentage daylight: %d%%.\n", world.daylight_fraction); tbprintf(buf, "Percentage twilight: %d%%.\n", world.twilight_fraction - world.daylight_fraction); } if (area.temp_year != lispnil) { /* (should describe temperature year cycle here) */ } #ifdef DESIGNERS if (numdesigners > 0) { tbcat(buf, "FOR DESIGNERS:\n"); tbprintf(buf, "Area projection is %d.\n", area.projection); tbprintf(buf, "Default contour line color is \"%s\".\n", g_contour_color()); tbprintf(buf, "Default country border color is \"%s\".\n", g_country_border_color()); tbprintf(buf, "Default frontline color is \"%s\".\n", g_frontline_color()); tbprintf(buf, "Default grid color is \"%s\".\n", g_grid_color()); tbprintf(buf, "Default meridian color is \"%s\".\n", g_meridian_color()); tbprintf(buf, "Default shoreline color is \"%s\".\n", g_shoreline_color()); tbprintf(buf, "Default unit name color is \"%s\".\n", g_unit_name_color()); tbprintf(buf, "Default unseen color is \"%s\".\n", g_unseen_color()); } #endif /* DESIGNERS */ } /* The following globals don't make sense for online help, but are listed here so that a cross-check of *.def and online help doesn't list them as undocumented: g_random_state g_run_serial_number g_turn. */ /* This describes a command (from cmd.def et al) in a way that all interfaces can use. */ void describe_command (int ch, char *name, char *help, int onechar, TextBuffer *buf) { if (onechar && ch != '\0') { if (ch < ' ' || ch > '~') { tbprintf(buf, "^%c ", (ch ^ 0x40)); } else if (ch == ' ') { tbprintf(buf, "'%c' ", ch); } else { tbprintf(buf, " %c ", ch); } } else if (!onechar && ch == '\0') { tbcat(buf, "\""); tbcat(buf, name); tbcat(buf, "\""); } else return; tbcat(buf, " "); tbcat(buf, help); tbcat(buf, "\n"); } static int u_property_not_default(int (*fn)(int i), int dflt) { int u, val; for_all_unit_types(u) { val = (*fn)(u); if (val != dflt) return TRUE; } return FALSE; } static int t_property_not_default(int (*fn)(int i), int dflt) { int t, val; for_all_terrain_types(t) { val = (*fn)(t); if (val != dflt) return TRUE; } return FALSE; } static int uu_table_row_not_default(u, fn, dflt) int u, dflt; int (*fn)(int i, int j); { int u2, val2; for_all_unit_types(u2) { val2 = (*fn)(u, u2); if (val2 != dflt) return TRUE; } return FALSE; } static int ut_table_row_not_default(u, fn, dflt) int u, dflt; int (*fn)(int i, int j); { int t, val2; for_all_terrain_types(t) { val2 = (*fn)(u, t); if (val2 != dflt) return TRUE; } return FALSE; } static int um_table_row_not_default(u, fn, dflt) int u, dflt; int (*fn)(int i, int j); { int m, val2; for_all_material_types(m) { val2 = (*fn)(u, m); if (val2 != dflt) return TRUE; } return FALSE; } static int tt_table_row_not_default(t1, fn, dflt) int t1, dflt; int (*fn)(int i, int j); { int t2, val2; for_all_terrain_types(t2) { val2 = (*fn)(t1, t2); if (val2 != dflt) return TRUE; } return FALSE; } #if 0 /* not used currently */ static int tm_table_row_not_default(t, fn, dflt) int t, dflt; int (*fn)(int i, int j); { int m, val2; for_all_material_types(m) { val2 = (*fn)(t, m); if (val2 != dflt) return TRUE; } return FALSE; } #endif static int aa_table_row_not_default(a1, fn, dflt) int a1, dflt; int (*fn)(int i, int j); { int a2, val2; for_all_advance_types(a2) { val2 = (*fn)(a1, a2); if (val2 != dflt) return TRUE; } return FALSE; } static int uu_table_column_not_default(u, fn, dflt) int u, dflt; int (*fn)(int i, int j); { int u2, val2; for_all_unit_types(u2) { val2 = (*fn)(u2, u); if (val2 != dflt) return TRUE; } return FALSE; } static int aa_table_column_not_default(a1, fn, dflt) int a1, dflt; int (*fn)(int i, int j); { int a2, val2; for_all_advance_types(a2) { val2 = (*fn)(a2, a1); if (val2 != dflt) return TRUE; } return FALSE; } struct histo { int val, num; }; /* This compare will sort histogram entries in *reverse* order (most common values first). */ static int histogram_compare(CONST void *h1, CONST void *h2) { if (((struct histo *) h2)->num != ((struct histo *) h1)->num) { return ((struct histo *) h2)->num - ((struct histo *) h1)->num; } else { return ((struct histo *) h2)->val - ((struct histo *) h1)->val; } } static struct histo *u_histogram; static void u_property_desc(TextBuffer *buf, int (*fn)(int i), void (*formatter)(TextBuffer *buf, int val)) { int val, u, val2, constant = TRUE, found; int i, numentries, first; if (formatter == NULL) formatter = tb_value_desc; if (u_histogram == NULL) u_histogram = (struct histo *) xmalloc(numutypes * sizeof(struct histo)); /* Compute a histogram of all the values for the given property. */ numentries = 0; val = (*fn)(0); u_histogram[numentries].val = val; u_histogram[numentries].num = 1; ++numentries; for_all_unit_types(u) { val2 = (*fn)(u); if (val2 == val) { ++(u_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == u_histogram[i].val) { ++(u_histogram[i].num); found = TRUE; break; } } if (!found) { u_histogram[numentries].val = val2; u_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbcat(buf, " for all unit types"); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (u_histogram[0].num * 2 >= numutypes) { (*formatter)(buf, u_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, u_histogram[i].val); tbcat(buf, " for "); first = TRUE; for_all_unit_types(u) { if ((*fn)(u) == u_histogram[i].val) { if (!first) /* For this and similar situations, we need a space in addition to the comma, so interface elements (such as the Mac's text display) can decide to add line breaks. */ tbcat(buf, ", "); else first = FALSE; tbcat(buf, u_type_name(u)); } } } } static struct histo *t_histogram; static void t_property_desc(TextBuffer *buf, int (*fn)(int i), void (*formatter)(TextBuffer *buf, int val)) { int val, t, val2, constant = TRUE, found; int i, numentries, first; if (formatter == NULL) formatter = tb_value_desc; if (t_histogram == NULL) t_histogram = (struct histo *) xmalloc(numttypes * sizeof(struct histo)); /* Compute a histogram of all the values for the given property. */ numentries = 0; val = (*fn)(0); t_histogram[numentries].val = val; t_histogram[numentries].num = 1; ++numentries; for_all_terrain_types(t) { val2 = (*fn)(t); if (val2 == val) { ++(t_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == t_histogram[i].val) { ++(t_histogram[i].num); found = TRUE; break; } } if (!found) { t_histogram[numentries].val = val2; t_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbcat(buf, " for all terrain types"); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(t_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (t_histogram[0].num * 2 >= numttypes) { (*formatter)(buf, t_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, t_histogram[i].val); tbcat(buf, " for "); first = TRUE; for_all_terrain_types(t) { if ((*fn)(t) == t_histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, t_type_name(t)); } } } } /* Generate a textual description of a single unit's interaction with all other unit types wrt a given table. */ static void uu_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val), char *connect) { uu_table_rowcol_desc(buf, u, fn, formatter, connect, 0); } static void uu_table_column_desc(TextBuffer *buf, int u, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val), char *connect) { uu_table_rowcol_desc(buf, u, fn, formatter, connect, 1); } static void uu_table_rowcol_desc(TextBuffer *buf, int u, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val), char *connect, int rowcol) { int val, val2, u2, constant = TRUE, found; int i, numentries, first; if (formatter == NULL) formatter = tb_value_desc; if (empty_string(connect)) connect = "for"; if (u_histogram == NULL) u_histogram = (struct histo *) xmalloc(numutypes * sizeof(struct histo)); val = (rowcol ? (*fn)(0, u) : (*fn)(u, 0)); /* Compute a histogram of all the values in the row of the table. */ numentries = 0; u_histogram[numentries].val = val; u_histogram[numentries].num = 1; ++numentries; for_all_unit_types(u2) { val2 = (rowcol ? (*fn)(u2, u) : (*fn)(u, u2)); if (val2 == val) { ++(u_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == u_histogram[i].val) { ++(u_histogram[i].num); found = TRUE; break; } } if (!found) { u_histogram[numentries].val = val2; u_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbprintf(buf, " %s all unit types", connect); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (u_histogram[0].num * 2 >= numutypes) { (*formatter)(buf, u_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, u_histogram[i].val); tbprintf(buf, " %s ", connect); first = TRUE; for_all_unit_types(u2) { val2 = (rowcol ? (*fn)(u2, u) : (*fn)(u, u2)); if (val2 == u_histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, u_type_name(u2)); } } } } /* Generate a textual description of a single unit's interaction with all terrain types wrt a given table. */ static void ut_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val), char *connect) { int val = (*fn)(u, 0), val2, t, constant = TRUE, found; int i, numentries, first; if (formatter == NULL) formatter = tb_value_desc; if (empty_string(connect)) connect = "for"; if (u_histogram == NULL) u_histogram = (struct histo *) xmalloc(numutypes * sizeof(struct histo)); /* Compute a histogram of all the values in the row of the table. */ numentries = 0; u_histogram[numentries].val = val; u_histogram[numentries].num = 1; ++numentries; for_all_terrain_types(t) { val2 = (*fn)(u, t); if (val2 == val) { ++(u_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == u_histogram[i].val) { ++(u_histogram[i].num); found = TRUE; break; } } if (!found) { u_histogram[numentries].val = val2; u_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbprintf(buf, " %s all terrain types", connect); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (u_histogram[0].num * 2 >= numttypes) { (*formatter)(buf, u_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, u_histogram[i].val); tbprintf(buf, " %s ", connect); first = TRUE; for_all_terrain_types(t) { if ((*fn)(u, t) == u_histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, t_type_name(t)); } } } } static void um_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val)) { int val = (*fn)(u, 0), val2, m, constant = TRUE, found; int i, numentries, first; char *connect = "vs"; if (formatter == NULL) formatter = tb_value_desc; if (u_histogram == NULL) u_histogram = (struct histo *) xmalloc(numutypes * sizeof(struct histo)); /* Compute a histogram of all the values in the row of the table. */ numentries = 0; u_histogram[numentries].val = val; u_histogram[numentries].num = 1; ++numentries; for_all_material_types(m) { val2 = (*fn)(u, m); if (val2 == val) { ++(u_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == u_histogram[i].val) { ++(u_histogram[i].num); found = TRUE; break; } } if (!found) { u_histogram[numentries].val = val2; u_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbprintf(buf, " %s all material types", connect); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (u_histogram[0].num * 2 >= nummtypes) { (*formatter)(buf, u_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, u_histogram[i].val); tbprintf(buf, " %s ", connect); first = TRUE; for_all_material_types(m) { if ((*fn)(u, m) == u_histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, m_type_name(m)); } } } } static void tt_table_row_desc(TextBuffer *buf, int t0, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val)) { int val = (*fn)(t0, 0), val2, t, constant = TRUE, found; int i, numentries, first; if (formatter == NULL) formatter = tb_value_desc; if (t_histogram == NULL) t_histogram = (struct histo *) xmalloc(numttypes * sizeof(struct histo)); /* Compute a histogram of all the values in the row of the table. */ numentries = 0; t_histogram[numentries].val = val; t_histogram[numentries].num = 1; ++numentries; for_all_terrain_types(t) { val2 = (*fn)(t0, t); if (val2 == val) { ++(t_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == t_histogram[i].val) { ++(t_histogram[i].num); found = TRUE; break; } } if (!found) { t_histogram[numentries].val = val2; t_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbcat(buf, " for all terrain types"); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(t_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (t_histogram[0].num * 2 >= numttypes) { (*formatter)(buf, t_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, t_histogram[i].val); tbcat(buf, " vs "); first = TRUE; for_all_terrain_types(t) { if ((*fn)(t0, t) == t_histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, t_type_name(t)); } } } } #if 0 /* not currently used */ static void tm_table_row_desc(buf, t0, fn, formatter) TextBuffer *buf; int t0; int (*fn)(int i, int j); void (*formatter)(TextBuffer *buf, int val); { int val = (*fn)(t0, 0), val2, m, constant = TRUE, found; int i, numentries, first; struct histo histogram[MAXUTYPES]; if (formatter == NULL) formatter = tb_value_desc; /* Compute a histogram of all the values in the row of the table. */ numentries = 0; histogram[numentries].val = val; histogram[numentries].num = 1; ++numentries; for_all_material_types(m) { val2 = (*fn)(t0, m); if (val2 == val) { ++(histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == histogram[i].val) { ++(histogram[i].num); found = TRUE; break; } } if (!found) { histogram[numentries].val = val2; histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbcat(buf, " for all material types"); return; } /* Not a constant row; sort the histogram and compose a description. */ qsort(histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (histogram[0].num * 2 >= nummtypes) { (*formatter)(buf, histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, histogram[i].val); tbcat(buf, " vs "); first = TRUE; for_all_material_types(m) { if ((*fn)(t0, m) == histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, m_type_name(m)); } } } } #endif static void aa_table_row_desc(TextBuffer *buf, int a0, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val)) { aa_table_rowcol_desc(buf, a0, fn, formatter, 0); } static void aa_table_column_desc(TextBuffer *buf, int a1, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val)) { aa_table_rowcol_desc(buf, a1, fn, formatter, 1); } static struct histo *a_histogram; static void aa_table_rowcol_desc(TextBuffer *buf, int a1, int (*fn)(int i, int j), void (*formatter)(TextBuffer *buf, int val), int rowcol) { int val = (*fn)(0, a1), val2, a, constant = TRUE, found; int i, numentries, first; if (formatter == NULL) formatter = tb_value_desc; if (a_histogram == NULL) a_histogram = (struct histo *) xmalloc(numatypes * sizeof(struct histo)); val = (rowcol ? (*fn)(0, a1) : (*fn)(a1, 0)); /* Compute a histogram of all the values in the row of the table. */ numentries = 0; a_histogram[numentries].val = val; a_histogram[numentries].num = 1; ++numentries; for_all_advance_types(a) { val2 = (rowcol ? (*fn)(a, a1) : (*fn)(a1, a)); if (val2 == val) { ++(a_histogram[0].num); } else { constant = FALSE; found = FALSE; for (i = 1; i < numentries; ++i) { if (val2 == a_histogram[i].val) { ++(a_histogram[i].num); found = TRUE; break; } } if (!found) { a_histogram[numentries].val = val2; a_histogram[numentries].num = 1; ++numentries; } } } /* The constant table/row case is easily disposed of. */ if (constant) { (*formatter)(buf, val); tbcat(buf, " for all advance types"); return; } /* Not a constant column; sort the histogram and compose a description. */ qsort(a_histogram, numentries, sizeof(struct histo), histogram_compare); /* Show a "by default" clause if at least half of the entries are all the same. */ if (a_histogram[0].num * 2 >= numttypes) { (*formatter)(buf, a_histogram[0].val); tbcat(buf, " by default"); i = 1; } else { i = 0; } for (; i < numentries; ++i) { if (i > 0) tbcat(buf, ", "); (*formatter)(buf, a_histogram[i].val); tbcat(buf, " vs "); first = TRUE; for_all_advance_types(a) { val2 = (rowcol ? (*fn)(a, a1) : (*fn)(a1, a)); if (val2 == a_histogram[i].val) { if (!first) tbcat(buf, ", "); else first = FALSE; tbcat(buf, a_type_name(a)); } } } } #if 0 /* not currently used */ /* A simple table-printing utility. Blanks out default values so they don't clutter the table. */ static void append_number(buf, value, dflt) TextBuffer *buf; int value, dflt; { if (value != dflt) { tbprintf(buf, "%5d ", value); } else { tbprintf(buf, " "); } } #endif static void append_help_phrase(TextBuffer *buf, char *phrase) { if (empty_string(phrase)) return; /* Extra new line makes display less cluttered. */ tbcat(buf, "----- "); tbcat(buf, phrase); tbcat(buf, " -----\n\n"); } static void append_notes(TextBuffer *buf, Obj *notes) { char *notestr; Obj *rest; if (stringp(notes)) { notestr = c_string(notes); if (strlen(notestr) > 0) { tbcat(buf, notestr); tbcat(buf, " "); } else { tbcat(buf, "\n"); } } else if (consp(notes)) { for_all_list(notes, rest) { append_notes(buf, car(rest)); } } else { run_warning("notes not list or strings, ignoring"); } } void notify_instructions(void) { Obj *instructions = mainmodule->instructions, *rest; if (instructions != lispnil) { if (stringp(instructions)) { notify_all("%s", c_string(instructions)); } else if (consp(instructions)) { for (rest = instructions; rest != lispnil; rest = cdr(rest)) { if (stringp(car(rest))) { notify_all("%s", c_string(car(rest))); } else { /* (should probably warn about this case too) */ } } } else { run_warning("Instructions are of wrong type"); } } else { notify_all("(no instructions supplied)"); } } /* Print the news file onto the console if there is anything to print. */ void print_any_news(void) { FILE *fp; fp = open_library_file(news_filename()); if (fp != NULL) { printf("\n XCONQ NEWS\n\n"); while (fgets(spbuf, BUFSIZE-1, fp) != NULL) { fputs(spbuf, stdout); } /* Add another blank line, to separate from init printouts. */ printf("\n"); fclose(fp); } } /* Generate a readable description of the game (design) being played. */ /* This works by writing out appropriate help nodes, along with some indexing material. This does *not* do interface-specific help, such as commands. */ void print_game_description_to_file(FILE *fp) { HelpNode *node; /* (need to work on which nodes to dump out) */ for (node = first_help_node; node != first_help_node; node = node->next) { get_help_text(node); if (node->text != NULL) { fprintf(fp, "\014\n%s\n", node->key); fprintf(fp, "%s\n", node->text); } } } static void tb_value_desc(TextBuffer *buf, int val) { char charbuf[30]; sprintf(charbuf, "%d", val); tbcat(buf, charbuf); } static void tb_fraction_desc(TextBuffer *buf, int val) { char charbuf[30]; if (val % 100 == 0) sprintf(charbuf, "%d", val / 100); else sprintf(charbuf, "%d.%2.2d", val / 100, val % 100); tbcat(buf, charbuf); } static void tb_percent_desc(TextBuffer *buf, int val) { tb_value_desc(buf, val); tbcat(buf, "%"); } static void tb_percent_100th_desc(TextBuffer *buf, int val) { tb_fraction_desc(buf, val); tbcat(buf, "%"); } static void tb_dice_desc(TextBuffer *buf, int val) { char charbuf[30]; dice_desc(charbuf, val); tbcat(buf, charbuf); } static void tb_mult_desc(TextBuffer *buf, int val) { char charbuf[30]; sprintf(charbuf, "*%d.%2.2d", val / 100, val % 100); tbcat(buf, charbuf); } static void tb_bool_desc(TextBuffer *buf, int val) { tbcat(buf, (val ? "true" : "false")); } void tbprintf(TextBuffer *buf, char *str, ...) { va_list ap; char line[300]; va_start(ap, str); vsprintf(line, str, ap); tbcat(buf, line); va_end(ap); } #undef bcopy #define bcopy(a,b,c) memcpy(b,a,c) void tbcat(buf, str) TextBuffer *buf; char *str; { obstack_grow(&(buf->ostack), str, strlen(str)); }