/* Toplevel code for the tcl/tk interface to Xconq.
   Copyright (C) 1998-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.  */

#include "conq.h"
#include "kpublic.h"
#include "tkconq.h"
#include "cmdline.h"

void update_world_mouseover(Map *map, int rawx, int rawy);
void autoscroll(Map *map, VP *vp, int which, int rawx, int rawy);

#include <sys/time.h>

void draw_unit_info(Map *map);

extern Tk_Window tmp_root_window;
extern int tmp_valid;

/* We only need one tcl interpreter, and this is it. */

Tcl_Interp *interp;

/* The one side that has a display in this program. */

Side *dside;

int research_popped_up;

/* This is the number of pixels around the edge of the map that will
   result in autoscrolling if the cursor is positioned within that
   distance from the edge. */

int autoscroll_width = 16;

/* Delay in ms between the detection of being in the autoscroll
   and the start of actual scrolling. */

int autoscroll_delay = 800;

/* Storage for values of user preferences. */

VP default_vp;
int default_draw_terrain_images;
int default_draw_terrain_patterns;
int default_draw_transitions;
char *default_font_family;
int default_font_size;

int use_stdio;

/* This flag is true while an runtime error or warning dialog is up;
   it is used to suppress attempts to draw, because they may have been
   the cause of the dialog popping up. */
  
int error_popped_up;

/* True if any variants are available. */

int any_variants;

/* These are true when a standard variant is available to be chosen. */

int vary_world_size;
int vary_real_time;

/* Tcl widgets are numbered 0-15 for checkboxes, -1 for world size
   dialog, -2 for real time dialog.  These map to and from variant
   indexes, which are defined by the order in which they're listed
   in module->variants. */

#define MAXCHECKBOXES 16

int numcheckboxes = MAXCHECKBOXES;

/* Indexes into the array of the main module's variants. */

int checkboxpos[MAXCHECKBOXES];
int worldsizepos = -1;
int realtimepos = -1;

/* Reverse lookup for tcltk widgets -> variants. */

int varrev[100];

/* Declarations of all the tcl commands defined in C code. */

typedef int TclCmdFn(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);

TclCmdFn tk_version_string;
TclCmdFn tk_copyright_string;
TclCmdFn tk_run_game;
TclCmdFn tk_run_game_idle;
TclCmdFn tk_animate_selection;
TclCmdFn tk_interp_key;
TclCmdFn tk_execute_long_command;
TclCmdFn tk_library_paths;
TclCmdFn tk_numgames;
TclCmdFn tk_side_lib_size;
TclCmdFn tk_side_lib_entry;
TclCmdFn tk_side_lib_entry_available;
TclCmdFn tk_interpret_variants;
TclCmdFn tk_set_variant_value;
TclCmdFn tk_numttypes;
TclCmdFn tk_numutypes;
TclCmdFn tk_numsides;
TclCmdFn tk_maxsides;
TclCmdFn tk_dside;
TclCmdFn tk_numtreasury;
TclCmdFn tk_numfeatures;
TclCmdFn tk_game_info;
TclCmdFn tk_ttype_name;
TclCmdFn tk_t_image_name;
TclCmdFn tk_utype_name;
TclCmdFn tk_u_image_name;
TclCmdFn tk_mtype_name;
TclCmdFn tk_side_name;
TclCmdFn tk_side_adjective;
TclCmdFn tk_side_emblem;
TclCmdFn tk_short_side_title;
TclCmdFn tk_side_ingame;
TclCmdFn tk_long_player_title;
TclCmdFn tk_player_advantage;
TclCmdFn tk_player_aitypename;
TclCmdFn tk_min_advantage;
TclCmdFn tk_max_advantage;
TclCmdFn tk_assigned_side;
TclCmdFn tk_assigned_player;
TclCmdFn tk_can_rename;
TclCmdFn tk_adjust_advantage;
TclCmdFn tk_add_side_and_player;
TclCmdFn tk_rename_side_for_player;
TclCmdFn tk_set_ai_for_player;
TclCmdFn tk_exchange_players;
TclCmdFn tk_set_indepside_options;
TclCmdFn tk_feature_desc;
TclCmdFn tk_feature_info;
TclCmdFn tk_set_feature_info;
TclCmdFn tk_start_new_game;
TclCmdFn tk_start_saved_game;
TclCmdFn tk_start_player_pre_setup_stage;
TclCmdFn tk_start_player_setup_stage;
TclCmdFn tk_launch_game;
TclCmdFn tk_try_host_game;
TclCmdFn tk_try_join_game;
TclCmdFn tk_send_chat;
TclCmdFn tk_set_unit_type;
TclCmdFn tk_mouse_down_cmd;
TclCmdFn tk_mouse_up_cmd;
TclCmdFn tk_mouse_over_cmd;
TclCmdFn tk_world_mouse_down_cmd;
TclCmdFn tk_world_mouse_up_cmd;
TclCmdFn tk_world_mouse_over_cmd;
TclCmdFn tk_save_prefs;
TclCmdFn tk_help_goto;
TclCmdFn tk_game_save;
TclCmdFn tk_set_design_tool;
TclCmdFn tk_set_design_data;
TclCmdFn tk_create_new_feature;
TclCmdFn tk_destroy_feature;
TclCmdFn tk_designer_fix;
TclCmdFn tk_designer_save;

TclCmdFn tk_numutypes_available;
TclCmdFn tk_utype_actual;
TclCmdFn tk_mtype_actual;
TclCmdFn tk_map_size_at_power;

TclCmdFn tk_center_on_unit;

TclCmdFn tk_available_advance;
TclCmdFn tk_set_side_research;

TclCmdFn tk_agreements;

TclCmdFn tk_get_scores;

TclCmdFn tk_reset_popup_flag;

TclCmdFn tk_exit_xconq;

TclCmdFn mapw_cmd;
TclCmdFn imfsample_cmd;

void update_mouseover(Map *map, int rawx, int rawy);

/* Declarations of local functions. */

static void update_side_progress_display(Side *side, Side *side2);
static void update_side_score_display(Side *side, Side *side2);

static void help_unit_type(Side *side, Map *map);
static void help_terrain_type(Side *side, Map *map);

static void init_unit_type_list(int u);
static void update_unit_type_list(int u);

static void ui_update_state(void);

static void update_variant_setting(int which);
static void update_assignment(int n);

void check_network(ClientData cldata);

/* Create the one global interpreter, add Xconq-specific commands to it. */

static void tcl_cmd(char *name, TclCmdFn tcmd);

static void
tcl_cmd(char *name, TclCmdFn tcmd)
{
    Tcl_CreateCommand(interp, name, tcmd,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
}

void
initial_ui_init(void)
{
    char pathbuf[500];
    int rslt;

    Tk_Window tkwin;

    Tcl_FindExecutable("xconq");

    interp = Tcl_CreateInterp();

    if (Tcl_Init(interp) == TCL_ERROR) {
	init_error("tcl init failed (%s), exiting", interp->result);
    }

    if (Tk_Init(interp) == TCL_ERROR) {
	init_error("tk init failed (%s), exiting", interp->result);
    }

    tcl_cmd("version_string", tk_version_string);
    tcl_cmd("copyright_string", tk_copyright_string);
    tcl_cmd("run_game", tk_run_game);
    tcl_cmd("run_game_idle", tk_run_game_idle);
    tcl_cmd("animate_selection", tk_animate_selection);
    tcl_cmd("interp_key", tk_interp_key);
    tcl_cmd("execute_long_command", tk_execute_long_command);
    tcl_cmd("xconq_library_paths", tk_library_paths);
    tcl_cmd("numgames", tk_numgames);
    tcl_cmd("side_lib_size", tk_side_lib_size);
    tcl_cmd("side_lib_entry", tk_side_lib_entry);
    tcl_cmd("side_lib_entry_available", tk_side_lib_entry_available);
    tcl_cmd("interpret_variants", tk_interpret_variants);
    tcl_cmd("set_variant_value", tk_set_variant_value);
    tcl_cmd("numttypes", tk_numttypes);
    tcl_cmd("numutypes", tk_numutypes);
    tcl_cmd("numsides", tk_numsides);
    tcl_cmd("maxsides", tk_maxsides);
    tcl_cmd("numtreasury", tk_numtreasury);
    tcl_cmd("numfeatures", tk_numfeatures);
    tcl_cmd("game_info", tk_game_info);
    tcl_cmd("ttype_name", tk_ttype_name);
    tcl_cmd("t_image_name", tk_t_image_name);
    tcl_cmd("utype_name", tk_utype_name);
    tcl_cmd("u_image_name", tk_u_image_name);
    tcl_cmd("mtype_name", tk_mtype_name);
    tcl_cmd("side_name", tk_side_name);
    tcl_cmd("side_adjective", tk_side_adjective);
    tcl_cmd("side_emblem", tk_side_emblem);
    tcl_cmd("short_side_title", tk_short_side_title);
    tcl_cmd("side_ingame", tk_side_ingame);
    tcl_cmd("long_player_title", tk_long_player_title);
    tcl_cmd("player_advantage", tk_player_advantage);
    tcl_cmd("player_aitypename", tk_player_aitypename);
    tcl_cmd("min_advantage", tk_min_advantage);
    tcl_cmd("max_advantage", tk_max_advantage);
    tcl_cmd("assigned_side", tk_assigned_side);
    tcl_cmd("assigned_player", tk_assigned_player);
    tcl_cmd("can_rename", tk_can_rename);
    tcl_cmd("adjust_advantage", tk_adjust_advantage);
    tcl_cmd("add_side_and_player", tk_add_side_and_player);
    tcl_cmd("rename_side_for_player", tk_rename_side_for_player);
    tcl_cmd("set_ai_for_player", tk_set_ai_for_player);
    tcl_cmd("exchange_players", tk_exchange_players);
    tcl_cmd("set_indepside_options", tk_set_indepside_options);
    tcl_cmd("dside", tk_dside);
    tcl_cmd("feature_desc", tk_feature_desc);
    tcl_cmd("feature_info", tk_feature_info);
    tcl_cmd("set_feature_info", tk_set_feature_info);
    tcl_cmd("start_new_game", tk_start_new_game);
    tcl_cmd("start_saved_game", tk_start_saved_game);
    tcl_cmd("start_player_pre_setup_stage", tk_start_player_pre_setup_stage);
    tcl_cmd("start_player_setup_stage", tk_start_player_setup_stage);
    tcl_cmd("launch_game", tk_launch_game);
    tcl_cmd("try_host_game", tk_try_host_game);
    tcl_cmd("try_join_game", tk_try_join_game);
    tcl_cmd("send_chat", tk_send_chat);
    tcl_cmd("set_unit_type", tk_set_unit_type);
    tcl_cmd("mouse_down_cmd", tk_mouse_down_cmd);
    tcl_cmd("mouse_up_cmd", tk_mouse_up_cmd);
    tcl_cmd("mouse_over_cmd", tk_mouse_over_cmd);
    tcl_cmd("world_mouse_down_cmd", tk_world_mouse_down_cmd);
    tcl_cmd("world_mouse_up_cmd", tk_world_mouse_up_cmd);
    tcl_cmd("world_mouse_over_cmd", tk_world_mouse_over_cmd);
    tcl_cmd("save_preferences", tk_save_prefs);
    tcl_cmd("help_goto", tk_help_goto);
    tcl_cmd("game_save", tk_game_save);
    tcl_cmd("set_design_tool", tk_set_design_tool);
    tcl_cmd("set_design_data", tk_set_design_data);
    tcl_cmd("create_new_feature", tk_create_new_feature);
    tcl_cmd("destroy_feature", tk_destroy_feature);
    tcl_cmd("designer_fix", tk_designer_fix);
    tcl_cmd("designer_save", tk_designer_save);
    tcl_cmd("numutypes_available", tk_numutypes_available);
    tcl_cmd("utype_actual", tk_utype_actual);
    tcl_cmd("mtype_actual", tk_mtype_actual);
    tcl_cmd("map_size_at_power", tk_map_size_at_power);
    tcl_cmd("center_on_unit", tk_center_on_unit);
    tcl_cmd("available_advance", tk_available_advance);
    tcl_cmd("set_side_research", tk_set_side_research);
    tcl_cmd("agreements", tk_agreements);
    tcl_cmd("get_scores", tk_get_scores);
    tcl_cmd("reset_popup_flag", tk_reset_popup_flag);
    tcl_cmd("exit_xconq", tk_exit_xconq);

    tkwin = Tk_MainWindow(interp);

    Tcl_CreateCommand(interp, "map", mapw_cmd,
		      (ClientData) tkwin, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "imfsample", imfsample_cmd,
		      (ClientData) tkwin, (Tcl_CmdDeleteProc *) NULL);

    {
	int loaded = FALSE;
	LibraryPath *p;
	FILE *fp;

	for_all_library_paths(p) {
	    make_pathname(p->path, "../tcltk/tkconq", "tcl", pathbuf);
	    if ((fp = fopen(pathbuf, "r")) != NULL) {
		fclose(fp);
		rslt = Tcl_EvalFile(interp, pathbuf);
		if (rslt == TCL_ERROR)
		  init_error("Error reading tcl from %s: %s",
			     pathbuf, interp->result);
		loaded = TRUE;
		break;
	    }
	    make_pathname(p->path, "../tkconq", "tcl", pathbuf);
	    if ((fp = fopen(pathbuf, "r")) != NULL) {
		fclose(fp);
		rslt = Tcl_EvalFile(interp, pathbuf);
		if (rslt == TCL_ERROR)
		  init_error("Error reading tcl from %s: %s",
			     pathbuf, interp->result);
		loaded = TRUE;
		break;
	    }
	}
	if (!loaded) {
	    /* List all the places we tried.  Would be spiffier to
	       collect from actual trials above. */
	    for_all_library_paths(p) {
		make_pathname(p->path, "../tcltk/tkconq", "tcl", pathbuf);
		init_warning("Failed to load tkconq from: %s\n", pathbuf);
		make_pathname(p->path, "../tkconq", "tcl", pathbuf);
		init_warning("Failed to load tkconq from: %s\n", pathbuf);
	    }
	    init_error("tkconq.tcl file could not be loaded");
	}
    }

    imf_interp_hook = tk_interp_imf;
    imf_load_hook = tk_load_imf;

    update_variant_callback = update_variant_setting;
    update_assignment_callback = update_assignment;

#if 0
    Tcl_DoWhenIdle(check_network, (ClientData) 0);
#endif
    Tcl_CreateTimerHandler(100, check_network, (ClientData) 0);
}

int
tk_library_paths(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char buf[500];
    LibraryPath *path;

    strcpy(buf, "");
    for_all_library_paths(path) {
	/* Use semicolons as separators, for the benefit of split. */
	/* (It would be clever to to use a char known not to be in any
	   of the paths, but only do that much work when it proves
	   necessary). */
	if (!empty_string(buf))
	  strcat(buf, ";");
	strcat(buf, path->path);
    }
    sprintf(interp->result, "%s", buf);
    return TCL_OK;
}

int
tk_numgames(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    collect_possible_games();
    sprintf(interp->result, "%d", numgames);
    return TCL_OK;
}

int
tk_side_lib_size(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", length(g_side_lib()));
    return TCL_OK;
}

int
tk_side_lib_entry(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int entryi;
    char *name;
    Obj *entry, *rest, *anelt;

    entryi = strtol(argv[1], NULL, 10);
    entry = elt(g_side_lib(), entryi);
    name = "?name?";
    for_all_list(entry, rest) {
	anelt = car(rest);
	if (consp(anelt) && stringp(cadr(anelt))) {
	    name = c_string(cadr(anelt));
	    break;
	}
    }
    sprintf(interp->result, "%s", name);
    return TCL_OK;
}

int
tk_side_lib_entry_available(ClientData cldata, Tcl_Interp *interp,
			    int argc, char *argv[])
{
    int entryi, rslt;
    char *name;
    Obj *entry, *rest, *anelt;

    entryi = strtol(argv[1], NULL, 10);
    entry = elt(g_side_lib(), entryi);
    name = "?name?";
    rslt = TRUE;
    for_all_list(entry, rest) {
	anelt = car(rest);
	if (consp(anelt) && stringp(cadr(anelt))) {
	    name = c_string(cadr(anelt));
	    if (name_in_use(NULL, name))
	      rslt = FALSE;
	    break;
	}
    }
    sprintf(interp->result, "%d", rslt);
    return TCL_OK;
}

int
tk_interpret_variants(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[])
{
    interpret_variants();
    return TCL_OK;
}

int
tk_set_variant_value(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[])
{
    int n, which;
    extern int checkboxpos[], worldsizepos, realtimepos;

    /* Translate tcl/tk index to variant number. */
    n = strtol(argv[1], NULL, 10);
    if (n == -1)
      which = worldsizepos;
    else if (n == -2)
      which = realtimepos;
    else
      which = checkboxpos[n];
    net_set_variant_value(which,
			  strtol(argv[2], NULL, 10),
			  strtol(argv[3], NULL, 10),
			  strtol(argv[4], NULL, 10));
    return TCL_OK;
}

int
tk_numttypes(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numttypes);
    return TCL_OK;
}

int
tk_numutypes(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numutypes);
    return TCL_OK;
}

int
tk_numsides(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numsides);
    return TCL_OK;
}

int
tk_maxsides(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", g_sides_max());
    return TCL_OK;
}

int
tk_numfeatures(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numfeatures);
    return TCL_OK;
}

int
tk_numtreasury(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int m, rslt = 0;

    for_all_material_types(m) {
	if (m_treasury(m))
	  ++rslt;
    }
    sprintf(interp->result, "%d", rslt);
    return TCL_OK;
}

int
tk_ttype_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int t;

    t = strtol(argv[1], NULL, 10);
    if (is_terrain_type(t))
      sprintf(interp->result, "%s", t_type_name(t));
    else
      sprintf(interp->result, "?t?");
    return TCL_OK;
}

int
tk_t_image_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int t;
    char *str;

    t = strtol(argv[1], NULL, 10);
    if (is_terrain_type(t)) {
	str = t_image_name(t);
	if (empty_string(str))
	  str = t_type_name(t);
	sprintf(interp->result, "%s", str);
    } else
      sprintf(interp->result, "?t?");
    return TCL_OK;
}

int
tk_utype_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int u;

    u = strtol(argv[1], NULL, 10);
    if (is_unit_type(u))
      sprintf(interp->result, "%s", u_type_name(u));
    else
      sprintf(interp->result, "?u?");
    return TCL_OK;
}

int
tk_u_image_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int u;
    char *str;

    u = strtol(argv[1], NULL, 10);
    if (is_unit_type(u)) {
	str = u_image_name(u);
	if (empty_string(str))
	  str = u_internal_name(u);
	sprintf(interp->result, "%s", str);
    } else
      sprintf(interp->result, "?u?");
    return TCL_OK;
}

int
tk_mtype_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int m;

    m = strtol(argv[1], NULL, 10);
    if (is_material_type(m))
      sprintf(interp->result, "%s", m_type_name(m));
    else
      sprintf(interp->result, "?m?");
    return TCL_OK;
}

int
tk_side_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      sprintf(interp->result, "%s", side_name(side_n(s)));
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_side_adjective(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      sprintf(interp->result, "%s", side_adjective(side_n(s)));
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_side_emblem(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (s == 0)
      sprintf(interp->result, indepside->emblemname);
    else if (between(1, s, numsides)) {
	/* Try to get the emblem that was actually set up - it may be
	   a default or something else different from the official
	   emblem name. */
	if (dside && dside->ui && dside->ui->eimages[s])
	  sprintf(interp->result, "%s", dside->ui->eimages[s]->name);
	else if (side_n(s)->emblemname)
	  sprintf(interp->result, "%s", (side_n(s))->emblemname);
	else
	  sprintf(interp->result, "null");
    } else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_short_side_title(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      /* (should handle empty string?) */
      sprintf(interp->result, "%s", short_side_title(side_n(s)));
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_side_ingame(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      sprintf(interp->result, "%d", side_n(s)->ingame);
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_long_player_title(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p;
    char abuf[300];
    Player *player;

    p = strtol(argv[1], NULL, 10);
    player = find_player(p);
    long_player_title(abuf, player, NULL);
    sprintf(interp->result, "%s", abuf);
    return TCL_OK;
}

int
tk_player_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p;
    Player *player;

    p = strtol(argv[1], NULL, 10);
    player = find_player(p);
    sprintf(interp->result, "%d", (player ? player->advantage : 0));
    return TCL_OK;
}

int
tk_player_aitypename(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p;
    Player *player;

    p = strtol(argv[1], NULL, 10);
    player = find_player(p);
    if (player->aitypename)
      sprintf(interp->result, "%s", player->aitypename);
    else
      strcpy(interp->result, "");
    return TCL_OK;
}

int
tk_min_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;
    Side *side;

    s = strtol(argv[1], NULL, 10);
    side = side_n(s);
    sprintf(interp->result, "%d", (side ? side->minadvantage : 0));
    return TCL_OK;
}

int
tk_max_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;
    Side *side;

    s = strtol(argv[1], NULL, 10);
    side = side_n(s);
    sprintf(interp->result, "%d", (side ? side->maxadvantage : 0));
    return TCL_OK;
}

int
tk_assigned_side(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int i;
    Side *side;

    i = strtol(argv[1], NULL, 10);
    if (between(0, i, numsides)) {
	side = assignments[i].side;
	sprintf(interp->result, "%d", side->id);
    } else {
	sprintf(interp->result, "?s?");
    }
    return TCL_OK;
}

int
tk_assigned_player(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[])
{
    int i;
    Player *player;

    i = strtol(argv[1], NULL, 10);
    if (between(0, i, numsides)) {
	player = assignments[i].player;
	sprintf(interp->result, "%d", (player ? player->id : 0));
    } else {
	sprintf(interp->result, "?s?");
    }
    return TCL_OK;
}

int
tk_can_rename(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;
    Side *side;

    s = strtol(argv[1], NULL, 10);
    side = side_n(s);
    sprintf(interp->result, "%d",
	    g_side_lib() != lispnil && side != NULL && !side->nameslocked);
    return TCL_OK;
}

int
tk_adjust_advantage(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[])
{
    int p, amt;
    Player *player;

    p = strtol(argv[1], NULL, 10);
    amt = strtol(argv[2], NULL, 10);
    player = assignments[p].player;
    net_set_player_advantage(p, player->advantage + amt);
    return TCL_OK;
}

int
tk_add_side_and_player(ClientData cldata, Tcl_Interp *interp,
		       int argc, char *argv[])
{
    int n;

    n = net_add_side_and_player();
    /* Return the position of the new side/player in the assignment array. */
    sprintf(interp->result, "%d", n);
    return TCL_OK;
}

int
tk_rename_side_for_player(ClientData cldata, Tcl_Interp *interp,
			  int argc, char *argv[])
{
    int n, n2;

    n = strtol(argv[1], NULL, 10);
    n2 = strtol(argv[2], NULL, 10);
    net_rename_side_for_player(n, n2);
    return TCL_OK;
}

int
tk_set_ai_for_player(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[])
{
    int n;
    char *aitype;
    Player *player;

    n = strtol(argv[1], NULL, 10);
    if (strcmp(argv[2], "-cycle") == 0) {
	player = assignments[n].player;
	aitype = next_ai_type_name(player->aitypename);
    } else {
	aitype = copy_string(argv[2]);
    }
    net_set_ai_for_player(n, aitype);
    return TCL_OK;
}

int
tk_exchange_players(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[])
{
    int n, n2;

    n = strtol(argv[1], NULL, 10);
    n2 = strtol(argv[2], NULL, 10);
    if (between(0, n, numsides)) {
	n2 = net_exchange_players(n, n2);
	sprintf(interp->result, "%d", n2);
    } else {
	sprintf(interp->result, "?s?");
    }
    return TCL_OK;
}

int
tk_set_indepside_options(ClientData cldata, Tcl_Interp *interp,
			 int argc, char *argv[])
{
    int n, n2, n3;

    n = strtol(argv[1], NULL, 10);
    n2 = strtol(argv[2], NULL, 10);
    n3 = strtol(argv[3], NULL, 10);
    set_g_indepside_can_build(n);
    set_g_indepside_can_research(n2);
    set_g_indepside_has_treasury(n3);
    return TCL_OK;
}

int
tk_dside(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", dside->id);
    return TCL_OK;
}

int
tk_feature_info(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int fid;
    Feature *feature;

    fid = strtol(argv[1], NULL, 10);
    feature = find_feature(fid);
    if (feature == NULL)
      sprintf(interp->result, "{\"%s\" \"%s\"}", "no %t", "feature");
    else if (feature->name != NULL || feature->typename != NULL)
      sprintf(interp->result, "{\"%s\" \"%s\"}",
	      (feature->name ? feature->name : ""),
	      (feature->typename ? feature->typename : ""));
    else
      sprintf(interp->result, "{?name? ?type?}");
    return TCL_OK;
}

int
tk_feature_desc(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char abuf[BUFSIZE], *fdesc;
    int fid;
    Feature *feature;

    fid = strtol(argv[1], NULL, 10);
    feature = find_feature(fid);
    fdesc = feature_desc(feature, abuf);
    /* The following is really only good for a menu entry, other uses
       of feature_desc might prefer something else. */
    if (fdesc == NULL)
      fdesc = "no feature";
    sprintf(interp->result, "%s", fdesc);
    return TCL_OK;
}

int
tk_set_feature_info(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[])
{
    int fid;
    char *fname, *ftypename;
    Feature *feature;

    fid = strtol(argv[1], NULL, 10);
    feature = find_feature(fid);
    fname = argv[2];
    ftypename = argv[3];
    if (feature != NULL) {
	net_set_feature_name(feature, fname);
	net_set_feature_type_name(feature, ftypename);
    }
    return TCL_OK;
}

int
tk_version_string(ClientData cldata, Tcl_Interp *interp, int argc,
		  char *argv[])
{
    sprintf(interp->result, "%s", version_string());
    return TCL_OK;
}

int
tk_copyright_string(ClientData cldata, Tcl_Interp *interp, int argc,
		  char *argv[])
{
    sprintf(interp->result, "%s", copyright_string());
    return TCL_OK;
}

int
tk_game_info(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int gamei;
    char *name, *title, *version, *blurb, *bname;

    gamei = strtol(argv[1], NULL, 10);
    name = title = version = blurb = bname = "";
    if (possible_games[gamei] != NULL) {
	name = possible_games[gamei]->name;
	title = possible_games[gamei]->title;
	if (title == NULL)
	  title = "";
	version = possible_games[gamei]->version;
	if (version == NULL)
	  version = "";
	blurb = possible_games[gamei]->blurb;
	if (empty_string(blurb))
	  blurb = "(no description)";
	bname = (possible_games[gamei]->basemodulename ? "-  " : "");
    }
    eval_tcl_cmd("set game_name(%d) \"%s\"", gamei, name);
    eval_tcl_cmd("set game_title(%d) \"%s\"", gamei, title);
    eval_tcl_cmd("set game_version(%d) \"%s\"", gamei, version);
    eval_tcl_cmd("set game_blurb(%d) \"%s\"", gamei, blurb);
    eval_tcl_cmd("set game_base_name(%d) \"%s\"", gamei, bname);
    return TCL_OK;
}

int
tk_start_new_game(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[])
{
    int gamei;

    gamei = strtol(argv[1], NULL, 10);
    mainmodule = possible_games[gamei];
    load_game_module(mainmodule, TRUE);
    check_game_validity();
    if (my_rid > 0 && my_rid == master_rid)
      broadcast_game_module();
    start_variant_setup_stage();
    return TCL_OK;
}

int
tk_start_saved_game(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[])
{
    /* Get an anonymous module. */
    mainmodule = get_game_module(NULL);
    mainmodule->filename = copy_string(argv[1]);
    load_game_module(mainmodule, TRUE);
    check_game_validity();
    if (my_rid > 0 && my_rid == master_rid)
      broadcast_game_module();
    /* (skip over?) */
    start_variant_setup_stage();
    return TCL_OK;
}

int
tk_start_player_pre_setup_stage(ClientData cldata, Tcl_Interp *interp,
				int argc, char *argv[])
{
    start_player_pre_setup_stage();
    return TCL_OK;
}

int
tk_start_player_setup_stage(ClientData cldata, Tcl_Interp *interp,
			    int argc, char *argv[])
{
    start_player_setup_stage();
    eval_tcl_cmd("set indepside_ai %d", g_indepside_has_ai());
    eval_tcl_cmd("set indepside_build %d", g_indepside_can_build());
    eval_tcl_cmd("set indepside_research %d", g_indepside_can_research());
    eval_tcl_cmd("set indepside_treasury %d", g_indepside_has_treasury());
    return TCL_OK;
}

int
tk_launch_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    launch_game();
    return TCL_OK;
}

int
tk_try_host_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    host_the_game(argv[1]);
    return TCL_OK;
}

int
tk_try_join_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int rslt;

    rslt = try_join_game(argv[1]);
    sprintf(interp->result, "%d", rslt);
    return TCL_OK;
}

int
tk_send_chat(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    net_send_chat(my_rid, argv[1]);
    return TCL_OK;
}

int
tk_run_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int maxactions, rslt = 0, interval;
    int cs1, cs2;

    if (argc != 2) {
	run_warning("wrong # args (%d) to run_game", argc - 1);
	return TCL_OK;
    }

    maxactions = strtol(argv[1], NULL, 10);

    /* Check for any input from remotes. */
    if (my_rid > 0) {
	flush_outgoing_queue();
	receive_data(0);
	++rslt;
    }
    cs1 = cs2 = 0;
    if (my_rid > 0 && my_rid != master_rid)
      cs1 = game_checksum();
    run_local_ai(1, 20);
    if (my_rid > 0 && my_rid != master_rid)
      cs2 = game_checksum();
    if (cs1 != cs2)
      run_warning("Checksum %d -> %d after run_local_ai\n", cs1, cs2);

    /* Run the kernel itself. */
    rslt += net_run_game(maxactions);
    
#if 0
    cs1 = cs2 = 0;
    if (my_rid > 0 && my_rid != master_rid)
      cs1 = game_checksum();
    run_local_ai(2, 20);
    if (my_rid > 0 && my_rid != master_rid)
      cs2 = game_checksum();
    if (cs1 != cs2)
      run_warning("Checksum %d -> %d after run_local_ai\n", cs1, cs2);
#endif

    /* Check for any input from remotes. */
    if (my_rid > 0) {
	flush_outgoing_queue();
	receive_data(0);
	++rslt;
    }
    /* Set up to call it again in a little while. */
    /* If things are happening, call again quickly, for responsiveness. */
    interval = 10;
    /* If nothing seems to be happening right now, do a few times/sec,
       just in case. */
    if (rslt == 0)
      interval = 250;
    sprintf(interp->result, "%d", interval);
    return TCL_OK;
}

int
tk_run_game_idle(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    Map *map;
    Unit *unit, *unit2;
    char *activity = "tk_rg_idle";

    if (!active_display(dside))
      return TCL_OK;
    record_activity_start(activity, 0);
    /* See if we should jump to another unit and make it current. */
    for_all_maps(map) {
	if (map->widget == NULL)
	  continue;
	unit = map->curunit;
	if (map->autoselect) {
	    if (in_play(unit)
		&& unit->id == map->curunit_id
		&& side_controls_unit(dside, unit)
		&& has_acp_left(unit)
		&& (unit->plan ? !unit->plan->asleep : TRUE)
		&& (unit->plan ? !unit->plan->reserve : TRUE)
		&& (unit->plan ? !unit->plan->delayed : TRUE)
		) {
		if (!in_middle(map, unit->x, unit->y)
		    && !map->scrolled_to_unit) {
		    put_on_screen(map, unit->x, unit->y);
		    map->scrolled_to_unit = TRUE;
		}
	    } else {
		unit2 = autonext_unit_inbox(dside, unit, widget_vp(map));
		if (unit2
		    && unit2->plan
		    && !unit2->plan->asleep
		    && !unit2->plan->reserve
		    && !unit2->plan->delayed
		    && unit2->plan->waitingfortasks) {
		    /* Use this unit */
		} else {
		    if (!in_play(unit)
			|| unit->id != map->curunit_id
			|| !side_controls_unit(dside, unit))
		      unit = NULL;
		    unit2 = autonext_unit(dside, unit);
		}
		if (unit2 != unit)
		  map->scrolled_to_unit = FALSE;
		if (unit2 != NULL)
		  set_current_unit(map, unit2);
	    }
	} else {
	    /* Even when not auto-selecting, the selected unit may pass
	       out of our control. */
	    if (!in_play(unit)
		|| unit->id != map->curunit_id
		|| !(side_controls_unit(dside, unit) || endofgame))
	      unit = NULL;
	    set_current_unit(map, unit);
	}
	update_mouseover(map, map->last_rawx[0], map->last_rawy[0]);
	update_world_mouseover(map, map->last_rawx[1], map->last_rawy[1]);
    }
    record_activity_end(activity, 0);
    return TCL_OK;
}

int
tk_animate_selection(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[])
{
    Map *map;
    char *activity = "tk_anim_sel";

    if (active_display(dside)) {
	for_all_maps(map) {
	    if (map->autoselect
		&& map->widget != NULL
		&& in_play(map->curunit)
		&& map->curunit->id == map->curunit_id) {
		record_activity_start(activity, map->curunit->id);
		map->anim_state = (map->anim_state + 1) % 8;
		update_at_unit(map, map->curunit);
		record_activity_end(activity, map->anim_state);
	    }
	}
    }
    return TCL_OK;
}

int
tk_set_unit_type(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int mapn, u;
    Map *map;

    mapn = strtol(argv[1], NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    u = strtol(argv[2], NULL, 10);
    map->inptype = u;
    return TCL_OK;
}

int
tk_mouse_down_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int mapn, rawx, rawy, button;
    Map *map;

    mapn = strtol(argv[1] + 2, NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    button = strtol(argv[4], NULL, 10);
    DGprintf("down map%d %d %d %d\n", mapn, rawx, rawy, button);
    handle_mouse_down(map, rawx, rawy, button);
    return TCL_OK;
}

int
tk_mouse_up_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int mapn, rawx, rawy, button;
    Map *map;

    mapn = strtol(argv[1] + 2, NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    button = strtol(argv[4], NULL, 10);
    DGprintf("up map%d %d %d %d\n", mapn, rawx, rawy, button);
    handle_mouse_up(map, rawx, rawy, button);
    return TCL_OK;
}

int
tk_mouse_over_cmd(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[])
{
    int mapn, rawx, rawy;
    Map *map;

    mapn = strtol(argv[1] + 2, NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    if (is_designer(dside))
      paint_on_drag(map, rawx, rawy, 0);
    update_mouseover(map, rawx, rawy);
    return TCL_OK;
}

int could_attack(Unit *unit, int u2, Side *side2, int x, int y);

void
update_mouseover(Map *map, int rawx, int rawy)
{
    int x, y, u;
    enum mapmode prevtmpmode;
    Unit *unit, *unit2;
    UnitView *uview;
    Side *side2;
    VP *vp;

    DGprintf("over map%d %d %d\n", map->number, rawx, rawy);
    tmpbuf[0] = '\0';
    if ((rawx >= 0 || rawy >= 0) && map->widget != NULL) {
	vp = widget_vp(map);
	oneliner(dside, vp, rawx, rawy);
	eval_tcl_cmd("update_mouseover %d \"%s\"", map->number, tmpbuf);
	prevtmpmode = map->tmp_mode;
	map->tmp_mode = no_tmp_mode;
	unit = map->curunit;
	/* Modify the cursor depending on the type of unit it is over. */
	if (unit != NULL && !is_designer(dside)) {
	    nearest_cell(vp, rawx, rawy, &x, &y, NULL, NULL);
	    nearest_unit_view(dside, vp, rawx, rawy, &uview);
	    if (in_area(x, y)) {
		if (vp->show_all) {
		    nearest_unit(dside, vp, rawx, rawy, &unit2);
		    if (unit2 != NULL) {
			if (could_attack(unit, unit2->type, unit2->side, x, y))
			  map->tmp_mode = attack_mode;
		    }
		} else if (uview != NULL) {
		    u = view_type(uview);
		    side2 = view_side(uview);
		    if (could_attack(unit, u, side2, x, y))
		      map->tmp_mode = attack_mode;
		}
	    }
	}
	if (map->tmp_mode != prevtmpmode)
	  set_tool_cursor(map, 0);
	autoscroll(map, vp, 0, rawx, rawy);
    }
    map->last_rawx[0] = rawx;  map->last_rawy[0] = rawy;
}

/* Return if it's possible for the given unit to attack a unit of the
   given type, side, and location. */
/* (should move to generic code) */
/* (should return immed/later, type of attack possible) */

int
could_attack(Unit *unit, int u2, Side *side2, int x, int y)
{
    int u = unit->type;

    if (trusted_side(unit->side, side2))
      return FALSE;
    switch (g_combat_model()) {
      case 0:
	/* See if a direct attack is possible. */
	if (uu_acp_to_attack(u, u2) > 0
	    && uu_hit(u, u2) > 0
#if 0 /* assume we can get there eventually */
	    && between(uu_attack_range_min(u, u2),
		       distance(unit->x, unit->y, x, y)
		       uu_attack_range(u, u2))
#endif
	    )
	  return TRUE;
	/* See if attack by firing is possible. */
	if (u_acp_to_fire(u) > 0
	    && (uu_fire_hit(u, u2) != -1
		? uu_fire_hit(u, u2) : uu_hit(u, u2)) > 0
	    /* (should use this to test blocking elev) */
	    )
	  return TRUE;
      case 1:
      default:
    }
    return FALSE;
}

int
tk_world_mouse_down_cmd(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[])
{
    int mapn, rawx, rawy, button;
    Map *map;

    mapn = strtol(argv[1] + 2, NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    button = strtol(argv[4], NULL, 10);
    DGprintf("world down map%d %d %d %d\n", mapn, rawx, rawy, button);
    handle_world_mouse_down(map, rawx, rawy, button);
    return TCL_OK;
}

int
tk_world_mouse_up_cmd(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[])
{
    int mapn, rawx, rawy, button;
    Map *map;

    mapn = strtol(argv[1] + 2, NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    button = strtol(argv[4], NULL, 10);
    DGprintf("world up map%d %d %d %d\n", mapn, rawx, rawy, button);
    handle_world_mouse_up(map, rawx, rawy, button);
    return TCL_OK;
}

int
tk_world_mouse_over_cmd(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[])
{
    int mapn, rawx, rawy;
    Map *map;

    mapn = strtol(argv[1] + 2, NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    update_world_mouseover(map, rawx, rawy);
    return TCL_OK;
}

void
update_world_mouseover(Map *map, int rawx, int rawy)
{
    DGprintf("over map%d world %d %d\n", map->number, rawx, rawy);
    if ((rawx >= 0 || rawy >= 0) && map->worldw != NULL) {
	autoscroll(map, worldw_vp(map), 1, rawx, rawy);
    }
    map->last_rawx[1] = rawx;  map->last_rawy[1] = rawy;
}

/* Autoscroll if the mouse is near the edge of a map. */

void
autoscroll(Map *map, VP *vp, int which, int rawx, int rawy)
{
    int xdelta, ydelta;
    int ms1, ms2, interval;
    enum mapmode prevscrollmode;
    struct timeval tmprealtime;

    prevscrollmode = map->scroll_mode[which];
    map->scroll_mode[which] = no_scroll_mode;
    xdelta = ydelta = 0;
    /* Note that rawx,rawy may be validly 0,0 here, so avoid that case
       - means that autoscrolling won't work on the exact edge of the
       map, but not likely to be a problem in practice. */
    /* Also note that we don't do diagonal autoscrolling - the code
       has to do two scrolls in different directions in quick
       succession, and the net effect is to "shake" the display;
       amusing, but it got old after two minutes of testing... */
    if (rawx > 0 && rawx < autoscroll_width && vp->sx > vp->sxmin) {
	map->scroll_mode[which] = scroll_left_mode;
	xdelta = -1;
    } else if (rawy > 0 && rawy < autoscroll_width && vp->sy > vp->symin) {
	map->scroll_mode[which] = scroll_up_mode;
	ydelta = -1;
    } else if (rawx > (vp->pxw - autoscroll_width) && vp->sx < vp->sxmax) {
	map->scroll_mode[which] = scroll_right_mode;
	xdelta = 1;
    } else if (rawy > (vp->pxh - autoscroll_width) && vp->sy < vp->symax) {
	map->scroll_mode[which] = scroll_down_mode;
	ydelta = 1;
    }
    if (xdelta != 0 || ydelta != 0) {
	if (map->autoscroll_delaying[which]) {
	    gettimeofday(&tmprealtime, NULL);
	    ms1 = (tmprealtime.tv_sec
		   - map->autoscroll_start_time[which].tv_sec) * 1000;
	    ms2 = (tmprealtime.tv_usec
		   - map->autoscroll_start_time[which].tv_usec) / 1000;
	    interval = ms1 + ms2;
	    /* Don't autoscroll for a brief time after the cursor is
	       in the autoscrolling area. */
#if 0 /* debug */
	    printf("%d interval is %d + %d = %d ms\n",
		   which, ms1, ms2, interval);
#endif
	    if (interval > autoscroll_delay)
	      eval_tcl_cmd("autoscroll %d %d %d %d",
			   map->number, which, xdelta, ydelta);
	} else {
	    gettimeofday(&(map->autoscroll_start_time[which]), NULL);
	    map->autoscroll_delaying[which] = TRUE;
#if 0 /* debug */
	    printf("delaying autoscroll\n");
#endif
	}
    } else {
	map->autoscroll_delaying[which] = FALSE;
#if 0 /* debug */
	printf("not delaying autoscroll\n");
#endif
    }
    /* Make the cursor change appearance immediately, even while
       delaying the autoscroll. */
    if (map->scroll_mode[which] != prevscrollmode)
      set_tool_cursor(map, which);
}

void
set_current_unit(Map *map, Unit *unit)
{
    Unit *oldunit = map->curunit;

    if (unit == oldunit)
      return;
    if (unit == NULL
	|| (in_play(unit) && side_controls_unit(dside, unit))
	|| endofgame) {
	map->curunit = unit;
	map->curunit_id = (unit ? unit->id : 0);
    }
    /* Make sure the unit is actually visible on-screen. */
    if (unit != NULL) {
	put_on_screen(map, unit->x, unit->y);
	map->scrolled_to_unit = TRUE;
    }
    /* (should only do this if map not scrolled) */
    if (oldunit)
      update_at_unit(map, oldunit);
    if (map->curunit)
      update_at_unit(map, map->curunit);
    draw_unit_info(map);
    update_action_controls_info(map);
}

static int empty_unit_info;

void
draw_unit_info(Map *map)
{
    char infobuf[BUFSIZE];
    int mapn = map->number, u, mrow, x = -1, y = -1;
    Unit *unit;
    Task *task;
    ImageFamily *uimf, *eimf;

    unit = map->curunit;
    if (!in_play(unit)) {
	if (!empty_unit_info) {
	    eval_tcl_cmd("update_unit_info %d curunit 0", mapn);
	    eval_tcl_cmd("update_unit_info %d handle \"(no unit)\"", mapn);
	    eval_tcl_cmd("update_unit_info %d loc \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d occ \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d hp \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d stack \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d s0 \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d s1 \"\"", mapn);
#if 0  /* too much screen real estate */
	    eval_tcl_cmd("update_unit_info %d s2 \"\"", mapn);
#endif
	    eval_tcl_cmd("update_unit_info %d aux0 \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d plan \"\"", mapn);
	    eval_tcl_cmd("update_unit_info %d t0 \"\"", mapn);
#if 0  /* too much screen real estate */
	    eval_tcl_cmd("update_unit_info %d t1 \"\"", mapn);
#endif
	    eval_tcl_cmd("update_unit_picture %d \"(no)\" \"(no)\"", mapn);
	    empty_unit_info = TRUE;
	}
	return;
    }
    empty_unit_info = FALSE;
    u = unit->type;
    eval_tcl_cmd("update_unit_info %d curunit %d", mapn, unit->id);
    /* Update the image displayed. */
    uimf = dside->ui->uimages[unit->type];
    eimf = dside->ui->eimages[unit->side->id];
    eval_tcl_cmd("update_unit_picture %d \"%s\" \"%s\"",
		 mapn,
		 (uimf ? uimf->name : "(no)"),
		 (eimf ? eimf->name : "(no)"));
    /* Say which unit this is. */
    strcpy(infobuf, unit_handle(dside, unit));
    capitalize(infobuf);
    eval_tcl_cmd("update_unit_info %d handle \"%s\"", mapn, infobuf);
    x = unit->x;  y = unit->y;
    location_desc(infobuf, dside, unit, u, x, y);
    eval_tcl_cmd("update_unit_info %d loc \"%s\"", mapn, infobuf);
    /* Very briefly list the numbers and types of the occupants. */
    occupants_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info %d occ \"%s\"", mapn, infobuf);
    /* Display the "important" parameters. */
    infobuf[0] = '\0';
    /* Size is most important, but only applies to advanced units. */
    if (u_advanced(u)) {
	size_desc(tmpbuf, unit, TRUE);
	strcat(infobuf, tmpbuf);
	strcat(infobuf, "   ");
    }
    /* (should say something about parts?) */
    hp_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    strcat(infobuf, "   ");
    acp_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    cxp_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    morale_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    point_value_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    /* Crude hack, should be replaced with something better */
    if (supply_system_in_use()) 
      sprintf(&infobuf[strlen(infobuf)],
	      "   Supply in flow=%d%% connectedness=%d%%",
	      supply_inflow(unit), supply_connectedness(unit));
    eval_tcl_cmd("update_unit_info %d hp \"%s\"", mapn, infobuf);
#if 0  /* doesn't seem so great in practice */
    if (unit->hp * 4 <= u_hp(unit->type))
      eval_tcl_cmd(".m1.leftside.uf.unitinfo itemconfig hp -fill red");
    else if (unit->hp * 2 <= u_hp(unit->type))
      eval_tcl_cmd(".m1.leftside.uf.unitinfo itemconfig hp -fill orange");
    else
      eval_tcl_cmd(".m1.leftside.uf.unitinfo itemconfig hp -fill black");
#endif
    /* List other stack members here. */
    others_here_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info %d stack \"%s\"", mapn, infobuf);
    /* Describe the state of all the supplies. */
    for (mrow = 0; mrow < 2; ++mrow) {
	supply_desc(infobuf, unit, mrow);
	eval_tcl_cmd("update_unit_info %d s%d \"%s\"", mapn, mrow, infobuf);
    }
    /* Share the last line between additional supply and tooling. */
    if (!supply_desc(infobuf, unit, 2))
      tooling_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info %d aux0 \"%s\"", mapn, infobuf);
    /* Describe the current plan and task agenda. */
    plan_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info %d plan \"%s\"", mapn, infobuf);
    infobuf[0] = '\0';
    task = NULL;
    if (unit->plan)
      task = unit->plan->tasks;
    if (task) {
	task_desc(infobuf, unit->side, unit, task);
	task = task->next;
	if (task) {
	    strcat(infobuf, "; then ");
	    task_desc(infobuf+strlen(infobuf), unit->side, unit, task);
	}
    }
    eval_tcl_cmd("update_unit_info %d t0 \"%s\"", mapn, infobuf);
}

void
ui_mainloop(void)
{
    while (Tk_GetNumMainWindows() > 0) {
        Tcl_DoOneEvent(0);
    }
}

enum setup_stage last_stage = initial_stage;

void
check_network(ClientData cldata)
{
    extern int quitter;

    if (hosting || my_rid > 0) {
	flush_outgoing_queue();
	receive_data(0);
	if (my_rid > 0
	    && my_rid != master_rid
	    && current_stage != last_stage) {
	    if (current_stage == game_load_stage) {
		eval_tcl_cmd("popup_game_dialog");
	    } else if (current_stage == variant_setup_stage) {
		eval_tcl_cmd("popup_variants_dialog");
	    } else if (current_stage == player_setup_stage) {
		eval_tcl_cmd("set_variants");
	    } else if (current_stage == game_ready_stage) {
		eval_tcl_cmd("set_players");
	    }
	    last_stage = current_stage;
	}
	if (quitter != 0) {
	    close_remote_connection(quitter);
	    eval_tcl_cmd("insert_chat_string %d %d \"has quit\"",
			 my_rid, quitter);
	    quitter = 0;
	}
    }
    /* (should detect when networking no longer possible, not resched calls) */
#if 0
    Tcl_DoWhenIdle(check_network, (ClientData) 0);
#endif
    Tcl_CreateTimerHandler(100, check_network, (ClientData) 0);
}

/* All update_xxx_display callbacks are here. */

void
update_area_display(Side *side)
{
    Map *map;

    if (!active_display(side))
      return;
    for_all_maps(map)
      redraw_map(map);
}

/* Draw an individual detailed hex, as a row of one, on all maps. */

void
update_cell_display(Side *side, int x, int y, int flags)
{
    int dir, x1, y1;
    Map *map;
    VP *vp;

    if (!active_display(side))
      return;
    for_all_maps(map) {
	update_at_cell(map, x, y, flags);
	if (flags & UPDATE_ADJ) {
	    vp = widget_vp(map);
	    if ((side->terrview != NULL && vp->hw > 10)
		|| vp->draw_people
		|| vp->draw_control
		|| vp->draw_feature_boundaries
		|| (vp->draw_elevations && numdesigners > 0)
		) {
		for_all_directions(dir) {
		    if (point_in_dir(x, y, dir, &x1, &y1)
			/* A totally unseen adjacent cell will have
			   nothing worth redrawing. */
			&& (terrain_view(dside, x1, y1) != UNSEEN
			    || numdesigners > 0)) {
			update_at_cell(map, x1, y1, flags);
		    }
		}
	    }
	}
    }
}

/* The kernel calls this to update info about the given side. */

void
update_side_display(Side *side, Side *side2, int rightnow)
{
    int m;
    char sidebuf[BUFSIZE];

    if (!active_display(side))
      return;
    if (side2 == NULL)
      return;

    /* Build up and write the textual description of the side. */
    sidebuf[0] = '\0';
    if (is_designer(side2))
      strcat(sidebuf, "(designer)");
    strcat(sidebuf, short_side_title(side2));
    if (side2->willingtodraw) {
	strcat(sidebuf, "[draw]");
    }
    if (side2->player) {
	strcat(sidebuf, "(");
	short_player_title(sidebuf+strlen(sidebuf), side2->player, NULL);
	strcat(sidebuf, ")");
    }
    eval_tcl_cmd("update_game_side_info %d {%s} %d %d %d",
		 side2->id, sidebuf, side2->everingame, side2->ingame,
		 side2->status);
    update_side_progress_display(side, side2);
    update_side_score_display(side, side2);
    for_all_material_types(m) {
	if (m_treasury(m)) {
	    eval_tcl_cmd("update_side_treasury %d %d %d",
			 side2->id, mtype_indexes[m], side2->treasury[m]);
	}
    }
    if (numatypes > 0
	&& g_side_can_research()
	&& dside->research_topic == NOADVANCE
	/* (should also test that any advances are available) */
	&& !research_popped_up) {
	eval_tcl_cmd("popup_research_dialog");
	research_popped_up = TRUE;
    }
}

/* For sides that are active in the game, update their progress
   display. */

static void
update_side_progress_display(Side *side, Side *side2)
{
    int totacp, percentleft, percentresv, activ;
    extern int curpriority;

    /* No progress display if no game action for this side. */
    if (!side2->ingame || endofgame)
      return;
    activ = FALSE;
    totacp = percentleft = percentresv = 0;
    totacp = side_initacp(side2);
    if (totacp > 0) {
	percentleft = (100 * side_acp(side2)) / totacp;
	percentleft = limitn(0, percentleft, 100);
	percentresv = (100 * side_acp_reserved(side2)) / totacp;
	percentresv = limitn(0, percentresv, percentleft);
    }
    activ = (!g_use_side_priority() || curpriority == side2->priority);
    eval_tcl_cmd("update_side_progress %d %d %d %d %d",
		 side2->id, activ, percentleft, percentresv,
		 side2->finishedturn);
}

static void
update_side_score_display(Side *side, Side *side2)
{
    int i;
    char *scoredesc;
    Scorekeeper *sk;

    if (keeping_score() && side2->everingame) {
	i = 0;
	for_all_scorekeepers(sk) {
	    scoredesc = side_score_desc(spbuf, side2, sk);
	    eval_tcl_cmd("update_game_side_score score%d_%d \"%s\"",
			 i, side2->id, scoredesc);
	    ++i;
	}
    }
}

/* The kernel calls this to update info about the given unit. */

void
update_unit_display(Side *side, Unit *unit, int rightnow)
{
    Map *map;

    if (!active_display(side))
      return;
    if (unit == NULL)
      return;
    if (inside_area(unit->x, unit->y)) {
	update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
    }
    for_all_maps(map) {
	if (unit == map->curunit) {
	    draw_unit_info(map);
	    update_action_controls_info(map);
	}
    }
    if (unit->side != NULL) {
	update_side_progress_display(side, unit->side);
	update_side_score_display(side, unit->side);
    }
    update_unit_type_list(unit->type);
}

void
update_unit_acp_display(Side *side, Unit *unit, int rightnow)
{
    /* There isn't really much to skip over from what regular unit
       display does, so just call it. */
    update_unit_display(side, unit, rightnow);
}

void
update_action_result_display(Side *side, Unit *unit, int rslt, int rightnow)
{
    Map *map;

    if (!active_display(side))
      return;

    update_unit_type_list(unit->type);

    for_all_maps(map) {
	if (unit == map->curunit)
	  map->scrolled_to_unit = FALSE;
    }
}

/* The kernel calls this to update the global game state. */

void
update_turn_display(Side *side, int rightnow)
{
    Map *map;
    Unit *unit;
    static int told_outcome = FALSE;

    if (!active_display(side))
      return;

    eval_tcl_cmd("update_game_state \"%s\"", curdatestr);
    for_all_maps(map) {
	if ((unit = map->curunit) != NULL) {
	    if (inside_area(unit->x, unit->y)) {
		update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
	    }
	    draw_unit_info(map);
	}
    }
    if (endofgame && !told_outcome && side == dside) {
	eval_tcl_cmd("update_show_all_info %d", dside->may_set_show_all);
	/* Force every map into a see-all/survey mode. */
	for_all_maps(map) {
	    set_show_all(map, TRUE);
	    redraw_map(map);
	    update_action_controls_info(map);
	    if (map->mode != survey_mode) {
		dside->ui->curmap = map;
		do_survey(side);
	    }
	}
	eval_tcl_cmd("set endofgame 1");
	if (side_won(dside)) {
	    eval_tcl_cmd("popup_game_over_dialog won");
	} else if (side_lost(dside)) {
	    eval_tcl_cmd("popup_game_over_dialog lost");
	} else {
	    eval_tcl_cmd("popup_game_over_dialog over");
	}
	told_outcome = TRUE;
    }
}

void
update_action_display(Side *side, int rightnow)
{
    Map *map;
    Unit *unit;

    if (!active_display(side))
      return;

    for_all_maps(map) {
	if ((unit = map->curunit) != NULL) {
	    if (inside_area(unit->x, unit->y)) {
		update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
	    }
	    draw_unit_info(map);
	}
	update_action_controls_info(map);
    }
}

void
update_event_display(Side *side, HistEvent *hevt, int rightnow)
{
}

void
update_fire_at_display(Side *side, Unit *unit, Unit *unit2, int m, int now)
{
    Map *map;

    if (!active_display(side))
      return;
    for_all_maps(map) {
	draw_fire_line(map, unit, unit2, 0, 0);
    }
}

/* This is for animation of fire-into actions. */

void
update_fire_into_display(Side *side, Unit *unit, int x, int y, int z, int m,
			 int rightnow)
{
    Map *map;

    if (!active_display(side))
      return;
    for_all_maps(map) {
	draw_fire_line(map, unit, NULL, x, y);
    }
}

/* Updates to clock need to be sure that display changes immediately. */

void
update_clock_display(Side *side, int rightnow)
{
}

void
update_message_display(Side *side, Side *sender, char *str, int rightnow)
{
    if (!active_display(side))
      return;

    notify(side, "From %s: \"%s\"",
	   (sender != NULL ? short_side_title(sender) : "<anon>"), str);
}

void
update_all_progress_displays(char *str, int s)
{
}

void
action_point(Side *side, int x, int y)
{
    Map *map;

    if (!active_display(side))
      return;
    if (!inside_area(x, y))
      return;

    for_all_maps(map) {
	if (map->follow_action && !in_middle(map, x, y)) {
	    put_on_screen(map, x, y);
	    map->scrolled_to_unit = TRUE;
	}
    }
}

void
flush_display_buffers(Side *side)
{
}

void
update_everything()
{
}

/* This is called by generic Unix crash-handling code, no need for
   us to do anything special. */

void
close_displays()
{
}

int
schedule_movie(Side *side, char *movie, ...)
{
    va_list ap;
    int i;
    enum movie_type itype;

    if (!active_display(side))
      return FALSE;
    if (side->ui->numscheduled >= 10)
      return FALSE;
    memset(&(side->ui->movies[side->ui->numscheduled]), 0, sizeof(struct a_movie));
    side->ui->movies[side->ui->numscheduled].type = movie;
    itype = movie_null;
    if (strcmp(movie, "miss") == 0)
      itype = movie_miss;
    else if (strcmp(movie, "hit") == 0)
      itype = movie_hit;
    else if (strcmp(movie, "hit-short") == 0)
      itype = movie_hit_short;
    else if (strcmp(movie, "death") == 0)
      itype = movie_death;
    else if (strcmp(movie, "nuke") == 0)
      itype = movie_nuke;
    else if (strcmp(movie, "sound") == 0)
      itype = movie_sound;
    else if (strcmp(movie, "flash") == 0)
      itype = movie_flash;
    else
      /* Do nothing. */
      return FALSE;
    side->ui->movies[side->ui->numscheduled].itype = itype;
    va_start(ap, movie);
    for (i = 0; i < 5; ++i)
      side->ui->movies[side->ui->numscheduled].args[i] = va_arg(ap, int);
    va_end(ap);
    ++side->ui->numscheduled;
    return TRUE;
}

void
play_movies(SideMask sidemask)
{
    int j, unitid, btype, dur, x, y;
    Map *map;
    Unit *unit;

    if (!active_display(dside))
      return;
    if (side_in_set(dside, sidemask)) {
	for (j = 0; j < dside->ui->numscheduled; ++j) {
	    btype = -1;
	    dur = 400;
	    switch (dside->ui->movies[j].itype) {
	      case movie_null:
		break;
	      case movie_miss:
		if (btype < 0)
		  btype = 0;
	      case movie_hit:
	      case movie_hit_short:
		if (btype < 0)
		  btype = 1;
		if (dside->ui->movies[j].itype == movie_hit_short)
		  dur = 100;
	      case movie_death:
		if (btype < 0)
		  btype = 2;
		unitid = dside->ui->movies[j].args[0];
		unit = find_unit(unitid);
		if (unit == NULL || !in_area(unit->x, unit->y))
		  continue;
		for_all_maps(map) {
		    draw_unit_blast(map, unit, btype, dur);
		}
		break;
	      case movie_nuke:
		x = dside->ui->movies[j].args[0];
		y = dside->ui->movies[j].args[1];
		for_all_maps(map) {
		    draw_cell_blast(map, x, y, 3, 800);
		}
		break;
	      case movie_flash:
		/* Flashing does the whole screen. */
		for_all_maps(map) {
		    draw_cell_blast(map, 0, 0, 10, 100);
		}
		break;
	      default:
		break;
	    }
	}
	dside->ui->numscheduled = 0;
    }
}

/* Beep the beeper! */

void
beep(Side *side)
{
    eval_tcl_cmd("bell");
}

void
low_notify(Side *side, char *str)
{
    eval_tcl_cmd("low_notify {%s\n}", str);
}

void
popup_game_dialog(void)
{
    eval_tcl_cmd("popup_splash_screen");
}

int last_num_units_in_play[MAXUTYPES];
int last_num_units_incomplete[MAXUTYPES];

void
init_redraws(void)
{
    int u;
    Side *side2;
    Map *map;

    if (dside->ui != NULL) {
	/* The moment of truth - up to now output has been suppressed. */
	dside->ui->active = TRUE;
    }

    for_all_sides(side2) {
	update_side_display(dside, side2, TRUE);
    }
    for_all_unit_types(u) {
	last_num_units_in_play[u] = last_num_units_incomplete[u] = -1;
	init_unit_type_list(u);
	update_unit_type_list(u);
    }
    update_turn_display(dside, TRUE);

    for_all_maps(map)
      set_tool_cursor(map, 0);
}

static void
init_unit_type_list(int u)
{
    int i;
    extern int longest_shortest;

    if (!between(0, u, numutypes))
      return;
    i = utype_indexes[u];
    /* Only show a char for the unit type if all have single chars,
       otherwise blank all. */
    eval_tcl_cmd("update_unitlist_char %d \"%c\"",
		 i, (longest_shortest == 1 ? *(shortest_unique_name(u)) : ' '));
    /* (should do other columns too) */
    eval_tcl_cmd("update_unitlist_name %d \"%s\"", i, u_type_name(u));
}

static void
update_unit_type_list(int u)
{
    int i, num;

    if (!between(0, u, numutypes))
      return;
    i = utype_indexes[u];
    /* Our unit total (right-justified) */
    num = num_units_in_play(dside, u);
    if (num != last_num_units_in_play[u]) {
	spbuf[0] = '\0';
	if (num > 0) {
	    sprintf(spbuf, "%d", num);
	}
	eval_tcl_cmd("update_unitlist_count %d \"%s\"", i, spbuf);
	last_num_units_in_play[u] = num;
    }
    /* Our units under construction (left-justified) */
    num = num_units_incomplete(dside, u);
    if (num != last_num_units_incomplete[u]) {
	spbuf[0] = '\0';
	if (num > 0) {
	    sprintf(spbuf, "(%d)", num);
	}
	eval_tcl_cmd("update_unitlist_incomplete %d \"%s\"", i, spbuf);
	last_num_units_incomplete[u] = num;
    }
    /* (should do other columns too) */
}

/* Transfer tcl preference settings to C code preferences. */

int
tk_save_prefs(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char *strval, prefbuf[5000];
    FILE *fp;

    strval = Tcl_GetVar2(interp, "prefs", "power", TCL_GLOBAL_ONLY);
    default_vp.power = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "grid", TCL_GLOBAL_ONLY);
    default_vp.draw_grid = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "coverage", TCL_GLOBAL_ONLY);
    default_vp.draw_cover = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "elevations", TCL_GLOBAL_ONLY);
    default_vp.draw_elevations = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "people", TCL_GLOBAL_ONLY);
    default_vp.draw_people = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "control", TCL_GLOBAL_ONLY);
    default_vp.draw_control = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "temperature", TCL_GLOBAL_ONLY);
    default_vp.draw_temperature = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "winds", TCL_GLOBAL_ONLY);
    default_vp.draw_winds = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "clouds", TCL_GLOBAL_ONLY);
    default_vp.draw_clouds = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "unit_names", TCL_GLOBAL_ONLY);
    default_vp.draw_names = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "feature_names", TCL_GLOBAL_ONLY);
    default_vp.draw_feature_names = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "feature_boundaries", TCL_GLOBAL_ONLY);
    default_vp.draw_feature_boundaries = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "meridians", TCL_GLOBAL_ONLY);
    default_vp.draw_meridians = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "meridian_interval", TCL_GLOBAL_ONLY);
    default_vp.meridian_interval = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "ai", TCL_GLOBAL_ONLY);
    default_vp.draw_ai = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "terrain_images", TCL_GLOBAL_ONLY);
    default_draw_terrain_images = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "terrain_patterns", TCL_GLOBAL_ONLY);
    default_draw_terrain_patterns = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "transitions", TCL_GLOBAL_ONLY);
    default_draw_transitions = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "font_family", TCL_GLOBAL_ONLY);
    default_font_family = copy_string(strval);
    strval = Tcl_GetVar2(interp, "prefs", "font_size", TCL_GLOBAL_ONLY);
    default_font_size = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "checkpoint_interval", TCL_GLOBAL_ONLY);
    checkpoint_interval = strtol(strval, NULL, 10);
    ui_update_state();
    sprintlisp(prefbuf, find_at_key(dside->uidata, "unix"), 5000);
    if ((fp = fopen(preferences_filename(), "w")) != NULL) {
	fputs(prefbuf, fp);
	fputs("\n", fp);
	fclose(fp);
    }
    return TCL_OK;
}

static void
ui_update_state(void)
{
    Obj *state = lispnil;

    push_binding(&state, intern_symbol("default-power"),
		 new_number(default_vp.power));
    push_binding(&state, intern_symbol("default-draw-grid"),
		 new_number(default_vp.draw_grid));
    push_binding(&state, intern_symbol("default-draw-coverage"),
		 new_number(default_vp.draw_cover));
    push_binding(&state, intern_symbol("default-draw-elevations"),
		 new_number(default_vp.draw_elevations));
    push_binding(&state, intern_symbol("default-draw-lighting"),
		 new_number(default_vp.draw_lighting));
    push_binding(&state, intern_symbol("default-draw-people"),
		 new_number(default_vp.draw_people));
    push_binding(&state, intern_symbol("default-draw-control"),
		 new_number(default_vp.draw_control));
    push_binding(&state, intern_symbol("default-draw-temperature"),
		 new_number(default_vp.draw_temperature));
    push_binding(&state, intern_symbol("default-draw-winds"),
		 new_number(default_vp.draw_winds));
    push_binding(&state, intern_symbol("default-draw-clouds"),
		 new_number(default_vp.draw_clouds));
    push_binding(&state, intern_symbol("default-draw-unit-names"),
		 new_number(default_vp.draw_names));
    push_binding(&state, intern_symbol("default-draw-feature-names"),
		 new_number(default_vp.draw_feature_names));
    push_binding(&state, intern_symbol("default-draw-feature-boundaries"),
		 new_number(default_vp.draw_feature_boundaries));
    push_binding(&state, intern_symbol("default-draw-meridians"),
		 new_number(default_vp.draw_meridians));
    push_binding(&state, intern_symbol("default-meridian-interval"),
		 new_number(default_vp.meridian_interval));
    push_binding(&state, intern_symbol("default-draw-ai"),
		 new_number(default_vp.draw_ai));
    push_binding(&state, intern_symbol("default-draw-terrain-images"),
		 new_number(default_draw_terrain_images));
    push_binding(&state, intern_symbol("default-draw-terrain-patterns"),
		 new_number(default_draw_terrain_patterns));
    push_binding(&state, intern_symbol("default-draw-transitions"),
		 new_number(default_draw_transitions));
    push_binding(&state, intern_symbol("default-font-family"),
		 new_string(default_font_family));
    push_binding(&state, intern_symbol("default-font-size"),
		 new_number(default_font_size));
    push_binding(&state, intern_symbol("checkpoint-interval"),
		 new_number(checkpoint_interval));

    dside->uidata = replace_at_key(dside->uidata, "unix", state);
}

static void interp_unix_ui_data(Obj *uispec);

void
get_preferences(void)
{
    int startlineno = 0, endlineno = 0;
    Obj *uispec;
    FILE *fp;

    if ((fp = fopen(preferences_filename(), "r")) != NULL) {
	uispec = read_form(fp, &startlineno, &endlineno);
	interp_unix_ui_data(uispec);
	fclose(fp);
    }
}

/* Given a list of preference specifications, decipher them and set
   appropriate C variables, also pass into tcl preferences code. */

static void
interp_unix_ui_data(Obj *uispec)
{
    int numval;
    char *strval = NULL;
    char *name;
    Obj *rest, *bdg;

    for_all_list(uispec, rest) {
	bdg = car(rest);
	if (!consp(bdg)) {
	    /* Don't complain out loud normally, confusing to users
	       because preferences are under Xconq and not user
	       control. */
	    Dprintf("Syntax error in preference binding?\n");
	    continue;
	}
	if (symbolp(car(bdg))) {
	    name = c_string(car(bdg));
	    strval = NULL;
	    numval = 0;
	    if (numberp(cadr(bdg))) {
		numval = c_number(cadr(bdg));
	    } else if (stringp(cadr(bdg))) {
		strval = c_string(cadr(bdg));
	    } else {
		Dprintf("Preference property `%s' not a number or string, setting to zero\n",
			name);
	    }
	    if (strcmp(name, "default-power") == 0) {
		default_vp.power = numval;
		eval_tcl_cmd("set_pref_value power %d", numval);
	    } else if (strcmp(name, "default-draw-grid") == 0) {
		default_vp.draw_grid = numval;
		eval_tcl_cmd("set_pref_value grid %d", numval);
	    } else if (strcmp(name, "default-draw-coverage") == 0) {
		default_vp.draw_cover = numval;
		eval_tcl_cmd("set_pref_value coverage %d", numval);
	    } else if (strcmp(name, "default-draw-elevations") == 0) {
		default_vp.draw_cover = numval;
		eval_tcl_cmd("set_pref_value elevations %d", numval);
	    } else if (strcmp(name, "default-draw-lighting") == 0) {
		default_vp.draw_lighting = numval;
		eval_tcl_cmd("set_pref_value lighting %d", numval);
	    } else if (strcmp(name, "default-draw-people") == 0) {
		default_vp.draw_people = numval;
		eval_tcl_cmd("set_pref_value people %d", numval);
	    } else if (strcmp(name, "default-draw-control") == 0) {
		default_vp.draw_control = numval;
		eval_tcl_cmd("set_pref_value control %d", numval);
	    } else if (strcmp(name, "default-draw-temperature") == 0) {
		default_vp.draw_temperature = numval;
		eval_tcl_cmd("set_pref_value temperature %d", numval);
	    } else if (strcmp(name, "default-draw-winds") == 0) {
		default_vp.draw_winds = numval;
		eval_tcl_cmd("set_pref_value winds %d", numval);
	    } else if (strcmp(name, "default-draw-clouds") == 0) {
		default_vp.draw_clouds = numval;
		eval_tcl_cmd("set_pref_value clouds %d", numval);
	    } else if (strcmp(name, "default-draw-unit-names") == 0) {
		default_vp.draw_names = numval;
		eval_tcl_cmd("set_pref_value unit_names %d", numval);
	    } else if (strcmp(name, "default-draw-feature-names") == 0) {
		default_vp.draw_feature_names = numval;
		eval_tcl_cmd("set_pref_value feature_names %d", numval);
	    } else if (strcmp(name, "default-draw-feature-boundaries") == 0) {
		default_vp.draw_feature_boundaries = numval;
		eval_tcl_cmd("set_pref_value feature_boundaries %d", numval);
	    } else if (strcmp(name, "default-draw-meridians") == 0) {
		default_vp.draw_meridians = numval;
		eval_tcl_cmd("set_pref_value meridians %d", numval);
	    } else if (strcmp(name, "default-meridian-interval") == 0) {
		default_vp.meridian_interval = numval;
		eval_tcl_cmd("set_pref_value meridian_interval %d", numval);
	    } else if (strcmp(name, "default-draw-ai") == 0) {
		default_vp.draw_ai = numval;
		eval_tcl_cmd("set_pref_value ai %d", numval);
	    } else if (strcmp(name, "default-draw-terrain-images") == 0) {
		default_draw_terrain_images = numval;
		eval_tcl_cmd("set_pref_value terrain_images %d", numval);
	    } else if (strcmp(name, "default-draw-terrain-patterns") == 0) {
		default_draw_terrain_patterns = numval;
		eval_tcl_cmd("set_pref_value terrain_patterns %d", numval);
	    } else if (strcmp(name, "default-draw-transitions") == 0) {
		default_draw_transitions = numval;
		eval_tcl_cmd("set_pref_value transitions %d", numval);
	    } else if (strcmp(name, "default-font-family") == 0) {
		default_font_family = copy_string(strval);
		eval_tcl_cmd("set_pref_value font_family \"%s\"", strval);
	    } else if (strcmp(name, "default-font-size") == 0) {
		default_font_size = numval;
		eval_tcl_cmd("set_pref_value font_size %d", numval);
	    } else if (strcmp(name, "checkpoint-interval") == 0) {
		checkpoint_interval = numval;
		eval_tcl_cmd("set_pref_value checkpoint_interval %d", numval);
	    } else {
		/* Note unrecognized properties, but don't bother the user. */
		Dprintf("Preference binding `%s' unrecognized\n", name);
	    }
	} else {
	    /* As with above comment. */
	    Dprintf("Syntax error in preference binding head?\n");
	}
    }
}

void
popup_help(Side *side, HelpNode *node)
{
    eval_tcl_cmd("popup_help_dialog");
}

HelpNode *cur_help_node;

#define NODESTACKSIZE 50

HelpNode **node_stack;

int node_stack_pos;

static void describe_map(int arg, char *key, TextBuffer *buf);

static void
describe_map(int arg, char *key, TextBuffer *buf)
{
    tbcat(buf, "** In MOVE mode (crosshair cursor):\n");
    tbcat(buf, "The next unit that can do anything will be selected ");
    tbcat(buf, "automatically.  It is indicated by a moving marquee.\n");
    tbcat(buf, "Left- or right-click anywhere in the map to move the selected unit there.\n");
    tbcat(buf, "When all units have moved, the turn ends and the next ");
    tbcat(buf, "turn starts immediately.\n");
    tbcat(buf, "\n");
    tbcat(buf, "** In SURVEY mode (magnifying glass cursor):\n");
    tbcat(buf, "Left-click on any of your units to make it the selected one.\n");
    tbcat(buf, "To move it, right-click anywhere in the map to move there; ");
    tbcat(buf, "or use the 'm'ove command, or 'z' to switch ");
    tbcat(buf, "to move mode.\n");
    tbcat(buf, "The turn ends only when you do <ret> or pick the menu item ");
    tbcat(buf, "Side->End This Turn.\n");
    tbcat(buf, "\n");
    tbcat(buf, "** In both modes:\n");
    tbcat(buf, "If the destination is far away, the unit will take as many ");
    tbcat(buf, "turns as it needs to get there.\n");
    tbcat(buf, "If the unit is adjacent to an enemy unit, and you click ");
    tbcat(buf, "on the enemy, your unit will attack.\n");
    tbcat(buf, "\n");
    tbcat(buf, "Nearly all keyboard and menu commands work on only ");
    tbcat(buf, "the selected unit.\n");
    tbcat(buf, "\n");
}

int
tk_help_goto(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char *arg = argv[1], *nclassname, tclbuf[15000];
    int i, rslt;
    HelpNode *node, *tmp, *prev;

    /* If this is the first time we came here, build up the table of
       contents. */
    if (cur_help_node == NULL) {
	key_commands_help_node =
	  add_help_node("commands", describe_key_commands, 0, first_help_node);
	long_commands_help_node =
	  add_help_node("long commands", describe_long_commands, 0,
			key_commands_help_node);
	add_help_node("map", describe_map, 0, first_help_node);
	eval_tcl_cmd("add_help_topic_key \"%s\"", first_help_node->key);
	for (tmp = first_help_node->next; tmp != first_help_node; tmp = tmp->next) {
	    eval_tcl_cmd("add_help_topic_key \"%s\"", tmp->key);
	}
	cur_help_node = first_help_node;
	/* Allocate the node stack. */
	if (node_stack == NULL)
	  node_stack =
	    (HelpNode **) xmalloc(NODESTACKSIZE * sizeof(HelpNode *));
	node_stack_pos = 0;
	
    }
    prev = cur_help_node;
    if (strcmp(arg, "help") == 0) {
	cur_help_node = first_help_node;
    } else if (strcmp(arg, "prev") == 0) {
	cur_help_node = cur_help_node->prev;
    } else if (strcmp(arg, "next") == 0) {
	cur_help_node = cur_help_node->next;
    } else if (strcmp(arg, "back") == 0) {
	/* Pop the last-visited node from the stack. */
	if (node_stack_pos > 0)
	  cur_help_node = node_stack[--node_stack_pos];
    } else {
	node = find_help_node(cur_help_node, arg);
	if (node)
	  cur_help_node = node;
	else
	  beep(dside);
    }
    /* Only add to the stack of nodes visited if we didn't do "back". */
    if (strcmp(arg, "back") != 0) {
	/* If the stack of nodes is full, move them all down by one. */
	if (node_stack_pos >= NODESTACKSIZE) {
	    for (i = 1; i < NODESTACKSIZE; ++i)
	      node_stack[i - 1] = node_stack[i];
	    node_stack_pos = NODESTACKSIZE - 1;
	}
	node_stack[node_stack_pos++] = prev;
    }
    get_help_text(cur_help_node);
    /* Make a string representing the node class. */
    switch (cur_help_node->nclass) {
      case utypenode:
	nclassname = "u";
	break;
      case mtypenode:
	nclassname = "m";
	break;
      case ttypenode:
	nclassname = "t";
	break;
      case atypenode:
	nclassname = "a";
	break;
      default:
	nclassname = "?";
    }
    sprintf (tclbuf, "update_help {%s} {%s} {%s} %d",
	     cur_help_node->key, cur_help_node->text, nclassname,
	     cur_help_node->arg);
    rslt = Tcl_Eval(interp, tclbuf);
    if (rslt == TCL_ERROR) {
	fprintf(stderr, "Error: %s\n", interp->result);
	fprintf(stderr, "Error: while updating help node %s\n",
		cur_help_node->key);
    }
    return TCL_OK;
}

/* Given a typed character, decide what to do with it. */

int
tk_interp_key(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int mapn, rawx, rawy, winx, winy, x, y, cancelled;
    Map *map;
    Tk_Window tkwin;
    void (*fn)(Side *sidex, Map *mapx, int cancelledx);

    /* Find the map indicated by the first arg. */
    mapn = strtol(argv[1], NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    map->inpch = *(argv[2]);
    rawx = strtol(argv[3], NULL, 10);  rawy = strtol(argv[4], NULL, 10);
    tkwin = Tk_CoordsToWindow(rawx, rawy, Tk_MainWindow(interp));
    map->inpsx = map->inpsy = -1;
    map->inpx = map->inpy = -1;
    if (tkwin && map->widget != NULL) {
	Tk_GetRootCoords(tkwin, &winx, &winy);
	map->inpsx = rawx - winx;  map->inpsy = rawy - winy;
	if (nearest_cell(widget_vp(map), map->inpsx, map->inpsy,
			 &x, &y, NULL, NULL)) {
	    map->inpx = x;  map->inpy = y;
	}
    }
    DGprintf("key %d %d -> %d %d\n",
	     map->inpsx, map->inpsy, map->inpx, map->inpy);
    /* Call the modal handler if defined, giving it side and cancel
       flag. */
    if (map->modalhandler) {
	fn = map->modalhandler;
	cancelled = (map->inpch == ESCAPE_CHAR);
	/* Remove the handler - will restore itself if needed. */
	map->modalhandler = NULL;
	(*fn)(dside, map, cancelled);
	if (cancelled) {
	    eval_tcl_cmd("clear_command_line %d", map->number);
	    notify(dside, "Cancelled.");
	}
    } else if (isdigit(map->inpch)) {
	/* Digits are assumed to be part of a command prefix, such as
	   a repetition count. */
	if (map->prefixarg < 0) {
	    map->prefixarg = 0;
	} else {
	    map->prefixarg *= 10;
	}
	map->prefixarg += (map->inpch - '0');
	/* Return the prefix arg so we can display it. */
	sprintf(interp->result, "%d", map->prefixarg);
	return TCL_OK;
    } else {
	/* In any other situation, the character is a single-letter
	   command. */
	dside->prefixarg = map->prefixarg;
	dside->ui->beepcount = 0;
	dside->ui->curmap = map;
	execute_command(dside, map->inpch);
    }
    /* Clear the command char, so menu selects and other issuers of
       commands aren't confused with keystroke commands. */
    map->inpch = '\0';
    /* Reset the prefix arg unless there is still more modal input
       to be read. */
    if (map->modalhandler == NULL)
      map->prefixarg = -1;
    /* Return the prefix arg so we can display it. */
    sprintf(interp->result, "%d", map->prefixarg);
    return TCL_OK;
}

int
tk_execute_long_command(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[])
{
    int mapn;
    char *cmd = argv[2], *ncmd;
    Map *map;

    /* Find the map indicated by the first arg. */
    mapn = strtol(argv[1], NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    /* Cheap fallback. */
    if (mapn == 0)
      map = dside->ui->maps;
    /* If the long command has a numeric prefix, take it to be the
       prefix argument and peel off.  */
    if (isdigit(*cmd)) {
        map->prefixarg = strtol(cmd, &ncmd, 10);
	cmd = ncmd;
    }
    dside->ui->curmap = map;
    execute_long_command(dside, cmd);
    return TCL_OK;
}

int
tk_game_save(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char *filename = argv[1];

    write_entire_game_state(filename);
    return TCL_OK;
}

int
tk_set_design_tool(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[])
{
    char *arg = argv[1];
    Map *map;

    if (strcmp(arg, "normal") == 0) {
	dside->ui->curdesigntool = survey_mode;
    } else if (strcmp(arg, "terrain") == 0) {
	switch (t_subtype(dside->ui->curttype)) {
	  case bordersubtype:
	    dside->ui->curdesigntool = bord_paint_mode;
	    break;
	  case connectionsubtype:
	    dside->ui->curdesigntool = conn_paint_mode;
	    break;
	  case coatingsubtype:
	    dside->ui->curdesigntool = coat_paint_mode;
	    break;
	  default:
	    dside->ui->curdesigntool = cell_paint_mode;
	    break;
	}
    } else if (strcmp(arg, "unit") == 0) {
	dside->ui->curdesigntool = unit_paint_mode;
    } else if (strcmp(arg, "people") == 0) {
	dside->ui->curdesigntool = people_paint_mode;
    } else if (strcmp(arg, "control") == 0) {
	dside->ui->curdesigntool = control_paint_mode;
    } else if (strcmp(arg, "feature") == 0) {
	dside->ui->curdesigntool = feature_paint_mode;
    } else if (strcmp(arg, "material") == 0) {
	dside->ui->curdesigntool = material_paint_mode;
    } else if (strcmp(arg, "elevation") == 0) {
	dside->ui->curdesigntool = elevation_paint_mode;
    } else if (strcmp(arg, "temperature") == 0) {
	dside->ui->curdesigntool = temperature_paint_mode;
    } else if (strcmp(arg, "clouds") == 0) {
	dside->ui->curdesigntool = clouds_paint_mode;
    } else if (strcmp(arg, "winds") == 0) {
	dside->ui->curdesigntool = winds_paint_mode;
    } else if (strcmp(arg, "view") == 0) {
	dside->ui->curdesigntool = view_paint_mode;
    } else {
	run_warning("bogus design tool %s, ignoring\n", arg);
	return TCL_OK;
    }
    for_all_maps(map) {
	map->mode = dside->ui->curdesigntool;
	set_tool_cursor(map, 0);
    }
    return TCL_OK;
}

/* This macro implements cycling of a variable through a set of consecutive
   values, with direction controlled by the shift key.  If the limit is 0,
   then the cycling part is not done. */

#define OPTION_CYCLE(var, lo, hi, n, dir)  \
  if ((hi) - (lo) > 0) {  \
    (var) = (((var) + ((dir) < 0 ? -(n) : (n)) - (lo) + ((hi) - (lo))) % ((hi) - (lo))) + (lo);  \
  } else {  \
    (var) = ((var) + ((dir) < 0 ? -(n) : (n)));  \
  }

int
tk_set_design_data(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[])
{
    int set, dir, val;
    char *type = argv[1];
    Map *map;

    dir = 0;
    if (strcmp(argv[2], "incr") == 0) {
	set = FALSE;
	dir = 1;
	if (argc == 4) {
	    dir *= strtol(argv[3], NULL, 10);
	}
    } else if (strcmp(argv[2], "decr") == 0) {
	set = FALSE;
	dir = -1;
	if (argc == 4) {
	    dir *= strtol(argv[3], NULL, 10);
	}
    } else {
	set = TRUE;
	val = strtol(argv[2], NULL, 10);
    }

    if (strcmp(type, "curttype") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curttype, 0, numttypes, 1, dir);
	    val = dside->ui->curttype;
	}
	dside->ui->curttype = val;
	switch (t_subtype(dside->ui->curttype)) {
	  case bordersubtype:
	    dside->ui->curdesigntool = bord_paint_mode;
	    break;
	  case connectionsubtype:
	    dside->ui->curdesigntool = conn_paint_mode;
	    break;
	  case coatingsubtype:
	    dside->ui->curdesigntool = coat_paint_mode;
	    break;
	  default:
	    dside->ui->curdesigntool = cell_paint_mode;
	    break;
	}
    } else if (strcmp(type, "curbgttype") == 0) {
	dside->ui->curbgttype = val;
    } else if (strcmp(type, "curutype") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curutype, 0, numutypes, 1, dir);
	    val = dside->ui->curutype;
	}
	dside->ui->curutype = val;
    } else if (strcmp(type, "curusidenumber") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curusidenumber, 0, numsides + 1, 1, dir);
	    val = dside->ui->curusidenumber;
	}
	dside->ui->curusidenumber = val;
    } else if (strcmp(type, "curpeoplenumber") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curpeoplenumber, 0, numsides + 1, 1, dir);
	    val = dside->ui->curpeoplenumber;
	}
	dside->ui->curpeoplenumber = val;
    } else if (strcmp(type, "curcontrolnumber") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curcontrolnumber, 0, numsides + 1, 1, dir);
	    val = dside->ui->curcontrolnumber;
	}
	dside->ui->curcontrolnumber = val;
    } else if (strcmp(type, "curfid") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curfid, 0, numfeatures, 1, dir);
	    val = dside->ui->curfid;
	}
	dside->ui->curfid = val;
    } else if (strcmp(type, "curelevation") == 0) {
	dside->ui->curelevation = (set ? val : dir);
	dside->ui->curelevationcode = (set ? 0 : 1);
    } else if (strcmp(type, "curelevation_vary") == 0) {
	dside->ui->curelevationvary = val;
    } else if (strcmp(type, "curwinddir") == 0) {
	dside->ui->curwinddir = val;
    } else if (strcmp(type, "curwindforce") == 0) {
	dside->ui->curwindforce = val;
    } else if (strcmp(type, "curbrushradius") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curbrushradius, 0, 11, 1, dir);
	    val = dside->ui->curbrushradius;
	}
	dside->ui->curbrushradius = val;
    }
    for_all_maps(map) {
	set_tool_cursor(map, 0);
    }
    sprintf(interp->result, "%d", val);
    return TCL_OK;
}

int
tk_create_new_feature(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[])
{
    extern int nextfid;
    Feature *feature;

    /* Default to using the next available id as the name.  Since
       deletion of a feature whose desc is a number only doesn't
       work right (menu gets confused), add a letter by default. */
    sprintf(spbuf, "f%d", nextfid);
    feature = net_create_feature("feature", copy_string(spbuf));
    dside->ui->legends = NULL;
    /* (this won't work if networking is on, feature might not
       exist yet) */
    sprintf(interp->result, "%d", (feature ? feature->id : 0));
    return TCL_OK;
}

int
tk_destroy_feature(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[])
{
    int fid;
    Feature *feature;

    fid = strtol(argv[1], NULL, 10);
    feature = find_feature(fid);
    if (feature != NULL)
      net_destroy_feature(feature);
    dside->ui->legends = NULL;
    /* (this won't work if networking is on, feature might not
       exist yet) */
    return TCL_OK;
}

int
tk_designer_fix(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int oldmin = area.minelev, oldmax = area.maxelev;

    fix_elevations();
    update_contour_intervals();
    if (oldmin != area.minelev || oldmax != area.maxelev)
      do_refresh(dside);
    return TCL_OK;
}

int
tk_designer_save(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[])
{
    char *options;
    Module *module;

    module = create_game_module(NULL);
    module->title = "Designer-saved data";
    module->compress_layers = TRUE;
    init_module_reshape(module);

    module->name = copy_string(argv[1]);
    module->filename = copy_string(argv[2]);
    options = argv[3];
    if (strstr(options, " !compress "))
      module->compress_layers = FALSE;
    if (strstr(options, " all "))
      module->def_all = TRUE;
    if (strstr(options, " types "))
      module->def_types = TRUE;
    if (strstr(options, " tables "))
      module->def_tables = TRUE;
    if (strstr(options, " globals "))
      module->def_globals = TRUE;
    if (strstr(options, " scoring "))
      module->def_scoring = TRUE;
    if (strstr(options, " world "))
      module->def_world = TRUE;
    if (strstr(options, " area "))
      module->def_areas = TRUE;
    if (strstr(options, " terrain "))
      module->def_area_terrain = TRUE;
    if (strstr(options, " areamisc "))
      module->def_area_misc = TRUE;
    if (strstr(options, " weather "))
      module->def_area_weather = TRUE;
    if (strstr(options, " material "))
      module->def_area_material = TRUE;
    if (strstr(options, " sides "))
      module->def_sides = TRUE;
    if (strstr(options, " views "))
      module->def_side_views = TRUE;
    if (strstr(options, " docts "))
      module->def_side_doctrines = TRUE;
    if (strstr(options, " players "))
      module->def_players = TRUE;
    if (strstr(options, " agreements "))
      module->def_agreements = TRUE;
    if (strstr(options, " units "))
      module->def_units = TRUE;
    if (strstr(options, " unitids "))
      module->def_unit_ids = TRUE;
    if (strstr(options, " unitprops "))
      module->def_unit_props = TRUE;
    if (strstr(options, " unitactions "))
      module->def_unit_acts = TRUE;
    if (strstr(options, " unitplans "))
      module->def_unit_plans = TRUE;
    if (strstr(options, " history "))
      module->def_history = TRUE;
    if (!write_game_module(module))
      run_warning("Could not write the module \"%s\"!", module->filename);
    return TCL_OK;
}

int
tk_numutypes_available(ClientData cldata, Tcl_Interp *interp,
		       int argc, char *argv[])
{
    int num, u;

    num = 0;
    for_all_unit_types(u) {
	if (utype_indexes[u] >=0)
	  ++num;
    }
    sprintf(interp->result, "%d", num);
    return TCL_OK;
}

int
tk_utype_actual(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n, u;

    n = strtol(argv[1], NULL, 10);
    for_all_unit_types(u) {
	if (utype_indexes[u] == n) {
	    sprintf(interp->result, "%d", u);
	    return TCL_OK;
	}
    }
    /* (should make error) */
    sprintf(interp->result, "%d", -1);
    return TCL_OK;
}

int
tk_mtype_actual(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n, m;

    n = strtol(argv[1], NULL, 10);
    for_all_material_types(m) {
	if (mtype_indexes[m] == n) {
	    sprintf(interp->result, "%d", m);
	    return TCL_OK;
	}
    }
    /* (should make error) */
    sprintf(interp->result, "%d", -1);
    return TCL_OK;
}

int
tk_map_size_at_power(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int pow;

    pow = strtol(argv[1], NULL, 10);
    sprintf(interp->result, "%d %d", area.width * hws[pow], 0);
    return TCL_OK;
}

int
tk_center_on_unit(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[])
{
    int mapn, n;
    Map *map;
    Unit *unit;

    /* Find the map indicated by the first arg. */
    mapn = strtol(argv[1], NULL, 10);
    for_all_maps(map) {
	if (map->number == mapn)
	  break;
    }
    /* Cheap fallback. */
    if (mapn == 0)
      map = dside->ui->maps;
    n = find_units_matching(dside, argv[2], &unit);
    if (n == 1 && in_play(unit)) {
	recenter(map, unit->x, unit->y);
    }
    sprintf(interp->result, "%d", n);
    return TCL_OK;
}

int
tk_available_advance(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[])
{
    int n, a, i;

    n = strtol(argv[1], NULL, 10);
    i = 0;
    for_all_advance_types(a) {
	if (side_can_research(dside, a)) {
	    if (i == n) {
		sprintf(interp->result, "%s", a_type_name(a));
		return TCL_OK;
	    } else {
		++i;
	    }
	}
    }
    sprintf(interp->result, "?");
    return TCL_OK;
}

extern int research_popped_up;

int
tk_set_side_research(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[])
{
    int n, a, i;

    if (strcmp(argv[1], "nothing") == 0) {
	net_set_side_research(dside, NONATYPE);
	research_popped_up = FALSE;
	return TCL_OK;
    }
    n = strtol(argv[1], NULL, 10);
    i = 0;
    for_all_advance_types(a) {
	if (side_can_research(dside, a)) {
	    if (i == n) {
		notify(dside,
		       "Your wise men will search for the secret of %s.",
		       a_type_name(a));
		net_set_side_research(dside, a);
		research_popped_up = FALSE;
		return TCL_OK;
	    } else {
		++i;
	    }
	}
    }
    return TCL_OK;
}

int
tk_agreements(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char listbuf[BUFSIZE];
    Agreement *ag;

    listbuf[0] = '\0';
    for_all_agreements(ag) {
	tprintf(listbuf, " %d", ag->id);
    }
    sprintf(interp->result, "%s", listbuf);
    return TCL_OK;
}

int
tk_get_scores(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    Tcl_SetResult(interp, get_scores(dside), TCL_VOLATILE);
    return TCL_OK;
}

int
tk_reset_popup_flag(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[])
{
    error_popped_up = FALSE;
    return TCL_OK;
}

int
tk_exit_xconq(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    exit_xconq(dside);
    return TCL_OK;
}

void
place_legends(Side *side)
{
    int nf = numfeatures;

    if (!features_defined() || nf <= 0)
      return;
    if (side->ui->legends == NULL)
      side->ui->legends = (Legend *) xmalloc((nf + 1) * sizeof(Legend));
    place_feature_legends(side->ui->legends, nf, side, 0, 1);
}

void
eval_tcl_cmd(char *fmt, ...)
{
    char tclbuf[500], backup[500];
    int rslt;
    va_list ap;

    if (empty_string(fmt))
      return;

    va_start(ap, fmt);
    vsprintf(tclbuf, fmt, ap);
    va_end(ap);
    strcpy(backup, tclbuf);
    rslt = Tcl_GlobalEval(interp, tclbuf);
    if (rslt == TCL_ERROR) {
	fprintf(stderr, "Error: %s\n", interp->result);
	fprintf(stderr, "Error: while evaluating `%s'\n", backup);
    }
}

/* Given a specification for the game to be hosted (such as a port
   number), set up to host and return success/failure. */

int
host_the_game(char *hostport)
{
    int i, rslt;
    Player *player;
    extern char *remote_player_specs[];
    extern int online[];

    hosting = TRUE;
    /* If we call this before a game is loaded, numsides will be zero
       and this won't do anything, which is what we want. */
    for (i = 0; i < numsides; ++i) {
	if (assignments[i].side && (assignments[i].side)->ingame) {
	    player = assignments[i].player;
	    if (player->displayname != NULL) {
		player->rid = 1;
	    } else if (player->name) {
		player->remotewanted = player->name;
	    }
	}
    }
    rslt = open_remote_connection(hostport, hosting);
    if (rslt == 0)
      return FALSE;
    /* As the host, we arbitrarily give ourselves remote id #1. */
    my_rid = master_rid = 1;
    /* Compose a plausible default player spec if necessary. */
    if (default_player_spec == NULL)
      make_default_player_spec();
    /* Record our online and active status. */
    online[my_rid] = TRUE;
    remote_player_specs[my_rid] = copy_string(default_player_spec);
    add_remote_locally(my_rid, default_player_spec);
    numremotes = 1;
    return TRUE;
}

/* Try to join a game at the given host and port. */

int
try_join_game(char *hostport)
{
    int rslt;

    hosting = FALSE;
    rslt = open_remote_connection(copy_string(hostport), hosting);
    /* If failure, return quietly and let caller handle interaction. */
    if (rslt == 0)
      return FALSE;
    sendnow = TRUE;
    /* Compose a player spec if necessary. */
    if (default_player_spec == NULL)
      make_default_player_spec();
    /* Send a join packet and then wait until we're officially added
       to the game. */
    rslt = send_join(default_player_spec);
    sendnow = FALSE;
    if (rslt) {
	master_rid = 1;
	while (my_rid == 0) {
	    /* (should have a timeout just in case) */
	    receive_data(0);
	}
    } else {
	close_remote_connection(0);
	return FALSE;
    }
    /* We've succeeded in getting connected.  Note that this just
       means that we're in the system of synchronized programs,
       not that we have a side/player in the game. */
    return TRUE;
}

/* Go through all the game's variants and set up appropriate tcl
   variables. */

static void interpret_checkbox(Variant *var, int which, int n);

void
interpret_variants(void)
{
    int i;
    char *vartypename;
    Variant *var;

    any_variants = FALSE;
    vary_world_size = vary_real_time = FALSE;
    /* Set default behavior for all the checkboxes. */
    for (i = 0; i < MAXCHECKBOXES; ++i) {
	eval_tcl_cmd("set varianttext(%d) Unused", i);
	eval_tcl_cmd("set variantstate(%d) disabled", i);
	eval_tcl_cmd("set varianthelp(%d) nothing", i);
    }
    if (mainmodule == NULL || mainmodule->variants == NULL)
      return;
    /* The first six checkboxes are assigned to common types of
       variants. */
    numcheckboxes = 6;
    for (i = 0; mainmodule->variants[i].id != lispnil; ++i) {
	var = &(mainmodule->variants[i]);
	any_variants = TRUE;
	vartypename = c_string(var->id);
	switch (keyword_code(vartypename)) {
	  case K_WORLD_SEEN:
	    interpret_checkbox(var, i, 0);
	    break;
	  case K_SEE_ALL:
	    interpret_checkbox(var, i, 1);
	    break;
	  case K_SEQUENTIAL:
	    interpret_checkbox(var, i, 2);
	    break;
	  case K_PEOPLE:
	    interpret_checkbox(var, i, 3);
	    break;
	  case K_ECONOMY:
	    interpret_checkbox(var, i, 4);
	    break;
	  case K_SUPPLY:
	    interpret_checkbox(var, i, 5);
	    break;
	  case K_WORLD_SIZE:
	    /* If the area is already set up, it's too late. */
	    /* (this seems wrong?) */
	    if (area.width > 0 || area.height > 0)
	      break;
	    vary_world_size = TRUE;
	    worldsizepos = i;
	    varrev[i] = -1;
	    break;
	  case K_REAL_TIME:
	    vary_real_time = TRUE;
	    realtimepos = i;
	    varrev[i] = -2;
	    break;
	  default:
	    if (numcheckboxes < MAXCHECKBOXES)
	      interpret_checkbox(var, i, numcheckboxes++);
	    else
	      init_warning("too many variants, can't set all of them");
	    break;
	}
	update_variant_setting(i);
    }
    /* Set flags that enable/disable subdialogs. */
    eval_tcl_cmd("set vary_world_size %d", vary_world_size);
    eval_tcl_cmd("set vary_real_time %d", vary_real_time);
}

static void
interpret_checkbox(Variant *var, int which, int n)
{
    eval_tcl_cmd("set variantstate(%d) active", n);
    eval_tcl_cmd("set varianttext(%d) \"%s\"", n, var->name);
    eval_tcl_cmd("set varianthelp(%d) \"%s\"", n, var->help);
    checkboxpos[n] = which;
    varrev[which] = n;
}

void
init_all_displays(void)
{
    int numdisplays;
    Side *side;

    numdisplays = 0;
    for_all_sides(side) {
	if (side_has_display(side)) {
	    if (side_has_local_display(side))
	      init_display();
	    ++numdisplays;
	}
    }
    if (numdisplays == 0) {
	if (use_stdio)
	  fprintf(stderr, "Must have at least one display to start.\n");
	exit(0);
    }
}

int mapn = 1;

void
create_map(void)
{
    int u;
    Map *map;
    Side *side2;

    DGprintf("Creating map\n");
    map = (Map *) xmalloc(sizeof(Map));

    map->number = mapn++;

    map->mode = move_mode;
    map->autoselect = TRUE;
    map->move_on_click = TRUE;

    map->prefixarg = -1;
    map->inptype = NONUTYPE;

    /* Newest map goes on the front of the list. */
    map->next = dside->ui->maps;
    dside->ui->maps = map;
    /* Make this be the current map.  (a little dubious) */
    dside->ui->curmap = map;

    eval_tcl_cmd("create_map_window %d", map->number);

    /* Inherit the side's show_all setting. */
    set_show_all(map, dside->show_all);

    for_all_sides(side2) {
	update_side_display(dside, side2, TRUE);
    }
    for_all_unit_types(u) {
	last_num_units_in_play[u] = last_num_units_incomplete[u] = -1;
	init_unit_type_list(u);
	update_unit_type_list(u);
    }
    update_turn_display(dside, TRUE);

    set_tool_cursor(map, 0);
    eval_tcl_cmd("update_mode %d move", map->number);
}

static void enable_in_unit_type_list(Side *side, Map *map, int u, int flag);

static void
enable_in_unit_type_list(Side *side, Map *map, int u, int flag)
{
    eval_tcl_cmd("enable_unitlist %d %d %d",
		 map->number, utype_indexes[u], flag);
}

/* Prompt for a type of a unit from player, maybe only allowing some
   types to be accepted.  Also allow specification of no unit type.
   We do this by scanning the vector, building a string of chars and a
   vector of unit types, so as to be able to map back when done. */

int
ask_unit_type(Side *side, Map *map, char *prompt, int *possibles,
	      void (*handler)(Side *side, Map *map, int cancelled))
{
    int u, numtypes = 0;

    for_all_unit_types(u) {
	if (possibles == NULL || possibles[u]) {
	    map->uvec[numtypes] = u;
	    map->ustr[numtypes] = unitchars[u];
	    ++numtypes;
	    enable_in_unit_type_list(side, map, u, 1);
	} else {
	    enable_in_unit_type_list(side, map, u, -1);
	}
    }
    map->ustr[numtypes] = '\0';
    if (numtypes > 1) {
	eval_tcl_cmd("ask_unit_type_mode %d {%s [%s]}",
		     map->number, prompt, map->ustr);
	map->modalhandler = handler;
    }
    return numtypes;
}

/* Do something with the char or unit type that the player entered. */

int
grok_unit_type(Side *side, Map *map, int *typep)
{
    int i, u;

    *typep = NONUTYPE;
    if (map->inptype != NONUTYPE) {
	/* Collect the type saved from a mouse click or other input. */
	*typep = map->inptype;
	/* Reset so doesn't affect subsequent unit type queries. */
	map->inptype = NONUTYPE;
    } else if (map->inpch != '\0') {
	if (map->inpch == '?') {
	    help_unit_type(side, map);
	    return FALSE;
	}
	i = iindex(map->inpch, map->ustr);
	if (i >= 0) {
	    *typep = map->uvec[i];
	} else {
	    notify(side, "Must type a unit type char from the list, or <esc>");
	    return FALSE;
	}
    } else {
	notify(side, "weird");
	return FALSE;
    }
    eval_tcl_cmd("ask_unit_type_done %d", map->number);
    /* Reset the appearance of the unit type list. */
    for_all_unit_types(u) {
	enable_in_unit_type_list(side, map, u, 0);
    }
    /* Make the unit type string be empty. */
    map->ustr[0] = '\0';
    return TRUE;
}

void
cancel_unit_type(Side *side, Map *map)
{
    int u;

    /* Reset the appearance of the unit type list. */
    for_all_unit_types(u) {
	enable_in_unit_type_list(side, map, u, 0);
    }
}

static void
help_unit_type(Side *side, Map *map)
{
    int i;
    char helpbuf[BUFSIZE];

    helpbuf[0] = '\0';
    for (i = 0; map->ustr[i] != '\0'; ++i) {
	/* Put out several types on each line. */
	if (i % 4 == 0) {
	    if (i > 0) {
		notify(side, "%s", helpbuf);
	    }
	    /* Indent each line a bit (also avoids notify's
	       auto-capitalization). */
	    strcpy(helpbuf, "  ");
	}
	tprintf(helpbuf, "%c %s, ", map->ustr[i], u_type_name(map->uvec[i]));
    }
    /* Add an extra helpful comment, then dump any leftovers. */
    tprintf(helpbuf, "? for this help info"); 
    notify(side, "%s", helpbuf);
}

int
ask_terrain_type(Side *side, Map *map, char *prompt, int *possibles,
		 void (*handler)(Side *side, Map *map, int cancelled))
{
    int numtypes = 0, t;

    for_all_terrain_types(t) {
	if (possibles == NULL || possibles[t]) {
	    map->tvec[numtypes] = t;
	    map->tstr[numtypes] = terrchars[t];
	    ++numtypes;
	}
    }
    map->tstr[numtypes] = '\0';
    if (numtypes > 1) {
	eval_tcl_cmd("ask_terrain_type_mode %d {%s [%s]}",
		     map->number, prompt, map->tstr);
	map->modalhandler = handler;
    }
    return numtypes;
}

/* Do something with the char or terrain type that the player entered. */

int
grok_terrain_type(Side *side, Map *map, int *typep)
{
    int i;

    *typep = NONTTYPE;
    if (map->inpch == '?') {
	help_terrain_type(dside, map);
	return FALSE;
    }
    i = iindex(map->inpch, map->tstr);
    if (i >= 0) {
	*typep = map->tvec[i];
	eval_tcl_cmd("ask_terrain_type_done %d", map->number);
	return TRUE;
    } else {
	notify(dside, "Must type a terrain type char or <esc>");
	return FALSE;
    }
}

static void
help_terrain_type(Side *side, Map *map)
{
    int i;
    char helpbuf[BUFSIZE];

    for (i = 0; map->tstr[i] != '\0'; ++i) {
	/* Put out several types on each line. */
	if (i % 4 == 0) {
	    if (i > 0) {
		notify(side, "%s", helpbuf);
	    }
	    /* Indent each line a bit (also avoids confusion due to
	       notify's capitalization). */
	    strcpy(helpbuf, "  ");
	}
	tprintf(helpbuf, "%c %s, ", map->tstr[i], t_type_name(map->tvec[i]));
    }
    /* Add an extra helpful comment, then dump any leftovers. */
    tprintf(helpbuf, "? for this help info"); 
    notify(side, "%s", helpbuf);
}

/* User is asked to pick a position on map.  This will iterate until the
   space bar designates the final position. */

/* (should change the cursor temporarily) */

void
ask_position(Side *side, Map *map, char *prompt,
	     void (*handler)(Side *side, Map *map, int cancel))
{
    eval_tcl_cmd("ask_position_mode %d {%s [click to set]}",
		 map->number, prompt);
    map->answer[0] = '\0';
    map->modalhandler = handler;
}

int
grok_position(Side *side, Map *map, int *xp, int *yp, Unit **unitp)
{
    if (in_area(map->inpx, map->inpy)) {
	*xp = map->inpx;  *yp = map->inpy;
	if (unitp != NULL)
	  *unitp = map->inpunit;
	eval_tcl_cmd("ask_position_done %d", map->number);
	return TRUE;
    } else {
	/* Make any possible usage attempts fail. */
	*xp = *yp = -1;
	if (unitp != NULL)
	  *unitp = NULL;
	return FALSE;
    }
}

/* Prompt for a yes/no answer with a settable default. */

void
ask_bool(Side *side, Map *map, char *question, int dflt,
	 void (*handler)(Side *side, Map *map, int cancelled))
{
    eval_tcl_cmd("ask_bool_mode %d {%s} %d", map->number, question, dflt);
    map->answer[0] = '\0';
    map->tmpint = dflt;
    map->modalhandler = handler;
}

/* Figure out what the answer actually is, keeping the default in mind. */

int
grok_bool(Side *side, Map *map)
{
    int dflt = map->tmpint;
    char ch = map->inpch;

    if (dflt ? (lowercase(ch) == 'n') : (lowercase(ch) == 'y'))
      dflt = !dflt;
    eval_tcl_cmd("ask_bool_done %d", map->number);
    return dflt;
}

/* Read a string from the prompt window.  Deletion is allowed, and a
   text cursor (an underscore) is displayed. */

void
ask_string(Side *side, Map *map, char *prompt, char *dflt,
	   void (*handler)(Side *side, Map *map, int cancelled))
{
    /* Default must be non-NULL. */
    if (dflt == NULL)
      dflt = "";
    sprintf(map->answer, "%s", dflt);
    eval_tcl_cmd("ask_string_mode %d {%s} {%s}",
		 map->number, prompt, map->answer);
    map->modalhandler = handler;
}

/* Dig a character from the input and add it into the string.
   Keep returning FALSE until we get something, then make a copy
   of the result string and return TRUE. */

int
grok_string(Side *side, Map *map, char **strp)
{
    char ch = map->inpch;
    int len;

    /* Note that we can't use '?' for help here, might be a part of
       the string to be returned. */
    if (ch == '\r' || ch == '\n') {
	*strp = copy_string(map->answer);
	eval_tcl_cmd("ask_string_done %d", map->number);
	return TRUE;
    } else {
	len = strlen(map->answer);
	if (ch == BACKSPACE_CHAR || ch == DELETE_CHAR) {
	    if (len > 0)
	      --len;
	} else {
	    map->answer[len++] = ch;
	}
	map->answer[len] = '\0';
	eval_tcl_cmd("update_string_mode %d {%s}", map->number, map->answer);
	return FALSE;
    }
}

void
ask_side(Side *side, Map *map, char *prompt, Side *dfltside,
	 void (*handler)(Side *side, Map *map, int cancelled))
{
    char *dfltstr;

    dfltstr = (dfltside == NULL ? "nobody" : side_name(dfltside));
    strcpy(map->answer, dfltstr);
    eval_tcl_cmd("ask_side_mode %d {%s} {%s}",
		 map->number, prompt, map->answer);
    map->modalhandler = handler;
}

int
grok_side(Side *side, Map *map, Side **side2p)
{
    char ch = map->inpch;
    int len, sidenum;
    Side *side3;

    *side2p = NULL;
    if (ch == '?') {
	notify(side, "  Type in the name or number of a side,");
	notify(side, "  or \"nobody\" to answer with no side.");
	/* A flag to suppress complaints... */
	*side2p = side;
	return FALSE;
    } else if (ch == '\r' || ch == '\n') {
	if (empty_string(map->answer)
	    || strcmp(map->answer, "nobody") == 0) {
#if 0 /* experimental */
	    *side2p = indepside;
#endif
	    eval_tcl_cmd("ask_side_done %d", map->number);
	    return TRUE;
	}
	if (isdigit(*(map->answer))) {
	    sidenum = strtol(map->answer, NULL, 10);
	    side3 = side_n(sidenum);
	    if (side3) {
		*side2p = side3;
		eval_tcl_cmd("ask_side_done %d", map->number);
		return TRUE;
	    }
	    beep(side);
	    notify(side, "\"%s\" is not a valid side number", map->answer);
	    return FALSE;
	}
	for_all_sides(side3) {
	    if ((!empty_string(side3->name)
		 && strstr(side3->name, map->answer) == 0)
		|| (!empty_string(side3->noun)
		    && strstr(side3->noun, map->answer) == 0)
		|| (!empty_string(side3->adjective)
		    && strstr(side3->adjective, map->answer) == 0)) {
		*side2p = side3;
		eval_tcl_cmd("ask_side_done %d", map->number);
		return TRUE;
	    }
	}
	beep(side);
	notify(side, "\"%s\" is not recognized as a side name", map->answer);
	return FALSE;
    } else {
	len = strlen(map->answer);
	if (ch == BACKSPACE_CHAR || ch == DELETE_CHAR) {
	    if (len > 0)
	      --len;
	} else {
	    map->answer[len++] = ch;
	}
	map->answer[len] = '\0';
	eval_tcl_cmd("update_side_mode %d {%s}", map->number, map->answer);
	return FALSE;
    }
}

void
add_remote_locally(int rid, char *str)
{
    eval_tcl_cmd("set master_rid %d", master_rid);
    eval_tcl_cmd("add_program %d %d {%s}", my_rid, rid, str);
}

void
send_chat(int rid, char *str)
{
    eval_tcl_cmd("insert_chat_string %d %d {%s}", my_rid, rid, str);
}

/* Given a set of numbers that represent the current desired value of
   a given variant, update the tcl values for that variant (which has
   the side effect of updating the displayed widgets). */

static void
update_variant_setting(int which)
{
    int rev = varrev[which];
    Variant *var = &(mainmodule->variants[which]);

    if (rev == -1) {
	eval_tcl_cmd("set new_width %d", var->newvalues[0]);
	eval_tcl_cmd("set new_height %d", var->newvalues[1]);
	eval_tcl_cmd("set new_circumference %d", var->newvalues[2]);
    } else if (rev == -2) {
	eval_tcl_cmd("set new_time_for_game %d", var->newvalues[0]);
	eval_tcl_cmd("set new_time_per_side %d", var->newvalues[1]);
	eval_tcl_cmd("set new_time_per_turn %d", var->newvalues[2]);
    } else {
	eval_tcl_cmd("set variantvalue(%d) %d", rev, var->newvalues[0]);
    }
}

/* Update the display of the given side/player assignment. */

static void
update_assignment(int n)
{
    eval_tcl_cmd("update_player_entry %d", n);
    eval_tcl_cmd("update_allplayer_buttons");
}

/* Reading is usually pretty fast, so don't do anything special here. */

void
announce_read_progress(void)
{
}

int announced = FALSE;

char *announcemsg = NULL;

/* Announce the start of a time-consuming computation. */

void
announce_lengthy_process(char *msg)
{
    n_seconds_elapsed(0);
    announcemsg = copy_string(msg);
    if (announcemsg && use_stdio) {
	printf("%s;", announcemsg);
	free(announcemsg);
	announcemsg = NULL;
	fflush(stdout);
	announced = TRUE;
    }
}

/* Announce the making of progress on the computation. */

void
announce_progress(int percentdone)
{
    if (n_seconds_elapsed(2) && use_stdio) {
	printf(" %d%%,", percentdone);
	fflush(stdout);
	announced = TRUE;
    }
}

/* Announce the end of the time-consuming computation. */

void
finish_lengthy_process(void)
{
    if (announced && use_stdio) {
	printf(" done.\n");
	announced = FALSE;
    }
}

/* An init error needs to have the command re-run. */

void
low_init_error(char *str)
{
    if (use_stdio) {
	fprintf(stderr, "Error: %s.\n", str);
	fflush(stderr);
    } else {
	eval_tcl_cmd("popup_init_error_dialog {%s}", str);
    }
    exit(1);
}

/* A warning just gets displayed, no other action is taken. */

void
low_init_warning(char *str)
{
    if (use_stdio) {
	fprintf(stderr, "Warning: %s.\n", str);
	fflush(stderr);
    } else {
	eval_tcl_cmd("popup_init_warning_dialog {%s}", str);
    }
}

/* Run errors are fatal, so we try to save state and get out. */

void
low_run_error(char *str)
{
    if (use_stdio) {
	fprintf(stderr, "Error: %s.\n", str);
	fflush(stderr);
	fprintf(stderr, "Saving the game...");
	close_displays();
	write_entire_game_state(saved_game_filename());
	fprintf(stderr, " done.\n");
	exit(1);
    }
    error_popped_up = TRUE;
    /* It might be that the game is already over, make sure the
       tcl code knows that. */
    if (endofgame)
      eval_tcl_cmd("set endofgame 1");
    eval_tcl_cmd("popup_run_error_dialog {%s}", str);
}

/* Runtime warnings are for when it's important to bug the players,
   usually a problem with Xconq or a game design. */

void
low_run_warning(char *str)
{
    /* Dump to console, just for the record. */
    if (use_stdio) {
	fprintf(stderr, "Warning: %s.\n", str);
	fflush(stderr);
    }
    error_popped_up = TRUE;
    /* It might be that the game is already over, make sure the
       tcl code knows that. */
    if (endofgame)
      eval_tcl_cmd("set endofgame 1");
    eval_tcl_cmd("popup_run_warning_dialog {%s}", str);
}

/* This should be called before any sort of normal exit. */

void
exit_xconq(Side *side)
{
    if (numremotes > 0) {
	/* Let the master know we're getting out. */
	send_quit();
    }
    close_displays();
    exit(0);
}

/* Run all the steps that bring a game up once all the players and
   sides have been decided on. */

int
launch_game(void)
{
    /* Tell all the other programs it's time to launch too. */
    start_game_ready_stage();
    /* (should resync RNG just in case?) */
    /* Do the time-consuming part of setup calculations. */
    calculate_globals();
    run_synth_methods();
    final_init();
    assign_players_to_sides();
    run_game(0);
    eval_tcl_cmd("do_initial_setup");
    place_legends(dside);
    /* Get the displays set up, but don't draw anything yet. */
    init_all_displays();
    /* Now bring up the init data on each display. */
    init_redraws();
    /* Set up the signal handlers. */
    init_signal_handlers();
#ifdef UNIX
    init_x_signal_handlers();
#endif
    /* (should notify players of all the game options in effect) */
    notify_instructions();
    return TRUE;
}

void
unit_research_dialog(Unit *unit)
{
    auto_pick_unit_research(unit);
}

void
print_form(Obj *form)
{
    print_form_and_value(stdout, form);
}

void
end_printing_forms(void)
{
}
