/* Implementation of the transfer protocol for networked Xconq.
   Copyright (C) 1996-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING. */

/* This is the implementation of the high-level protocol. */

#include "conq.h"
#include "kernel.h"
#include "kpublic.h"

extern long randstate;

/* Iteration over all remote programs, by rid. */

#define for_all_remotes(rid) \
  for ((rid) = 1; (rid) <= numremotes; ++(rid)) \
    if (online[rid])

/* Definitions of special characters in the protocol. */

#define STARTPKT '$'
#define ENDPKT '^'
#define ESCAPEPKT '!'

#define STARTPKTESC '%'
#define ENDPKTESC '&'
#define ESCAPEPKTESC '@'

static void broadcast_command_5(char *cmd, int val, int val2, int val3,
				int val4, int val5);
static void broadcast_side_property(Side *side, char *prop, int val);
static void broadcast_side_property_2(Side *side, char *prop, int val,
				      int val2);
static void broadcast_side_str_property(Side *side, char *prop, char *val);
static void broadcast_unit_property(Side *side, Unit *unit, char *prop,
				    int val);
static void broadcast_unit_property_2(Side *side, Unit *unit, char *prop,
				      int val, int val2);
static void broadcast_unit_property_5(Side *side, Unit *unit, char *prop,
				      int val, int val2, int val3, int val4,
				      int val5);
static void broadcast_unit_str_property(Side *side, Unit *unit, char *prop,
					char *val);
static void broadcast_add_task(Unit *unit, int pos, Task *task);
static void broadcast_layer_change(char *layername, Side *side, int x, int y,
				   int a1, int a2, int a3, int a4);
static void broadcast_packet(char *buf);
static void save_outgoing_packet(int id, char *inbuf);
static void save_incoming_packet(int id, char *inbuf);
static void remove_chars(char *buf, int n);
static void receive_net_message(char *str);
static void receive_command(char *str);
static void receive_action(char *str);
static void receive_player_prop(char *str);
static void receive_quit(char *str);
static void receive_side_prop(char *str);
static void receive_task(char *str);
static void receive_unit_prop(char *str);
static void receive_world_prop(char *str);
static void receive_run_game(char *str);
static void receive_game_checksum(char *str);
static void receive_error(int id, char *str);
static void receive_remote_program(char *str);
static void receive_chat(char *str);
static void receive_variant_setting(char *str);
static void receive_assignment_setting(char *str);
static int tohex(int x);
static int fromhex(int x);
static void flush_incoming_queue(void);
static void send_game_checksum_error(int rid, int my_csum, int master_csum);

static int send_packet(int id, char *buf);
static void broadcast_game_checksum(void);
static void send_assignment(int id, Side *side, Player *player);
static void send_randstate(int id);
static void broadcast_next_action(Unit *unit);
static void receive_packet(int id, char *buf);
static void add_remote_program(int rid, char *name);
static void download_game_module(int rid);
static void send_remote_id(int rid);

/* This is true if the program expects to host the game and also be the
   master when all the remote programs are hooked up. */

int hosting;

/* This is the total number of programs in the game. */

int numremotes;

int numremotewaiting;

/* The remote id of this program. */

int my_rid;

/* The remote id of the current master program. */

int master_rid;

/* The program to which we are currently downloading. */

int tmprid;

char *remote_player_specs[100];

int online[100];

int expecting_ack;

int timeout_warnings = TRUE;

int sendnow;

/* Flag indicating that we're in the middle of downloading. */

int downloading;

char *downloadbuf;

int dlbufend;

long new_randstate;

void (*update_variant_callback)(int which) = NULL;

void (*update_assignment_callback)(int n) = NULL;

int quitter;

/* If a side is being played from a remote program, we don't need the
   full user interface structure, just make a placeholder that records
   the program's id. */

void
init_remote_ui(Side *side)
{
    if (side->rui == NULL) {
	side->rui = (RUI *) xmalloc(sizeof(RUI));
    }
    /* At present, rui->rid is unused. */
    side->rui->rid = side->player->rid;
}

/* For a new remote program with given remote id and player spec,
   record it locally and also broadcast to all remotes currently
   online. */

void
add_remote_program(int rid, char *name)
{
    int rid2;

    numremotes = max(rid, numremotes);
    online[rid] = TRUE;
    /* Inform our new program of all the existing programs. */
    for_all_remotes(rid2) {
	if (rid2 != rid) {
	    sprintf(spbuf, "p%d %s", rid2, remote_player_specs[rid2]);
	    send_packet(rid, spbuf);
	}
    }
    remote_player_specs[rid] = copy_string(name);
    add_remote_locally(rid, name);
    /* Now broadcast info about the new program to everybody. */
    sprintf(spbuf, "p%d %s", rid, name);
    broadcast_packet(spbuf);
}

/* For each remote program (including us), add a player corresponding
   to the spec supplied by that program. */

void
add_remote_players(void)
{
    int rid;
    Player *player;

    for_all_remotes(rid) {
	/* First look for an existing player object that's waiting to
	   be associated with a remote program. */
	player = NULL;
	for_all_players(player) {
	    if (!empty_string(player->name)
		&& strcmp(player->name, "?") == 0)
	      break;
	}
	/* Otherwise create a new player. */
	if (player == NULL)
	  player = add_player();
	parse_player_spec(player, remote_player_specs[rid]);
	canonicalize_player(player);
	player->rid = rid;
    }
}

/* Send a message asking to join the game.  Return TRUE if the host
   actually acknowledged us, FALSE if no response. */

int
send_join(char *str)
{
    int tries = 100, successful;

    sprintf(spbuf, "j%s", str);
    /* Keep trying until host responds. */
    while (--tries > 0) {
	timeout_warnings = FALSE;
    	successful = send_packet(0, spbuf);
	timeout_warnings = TRUE;
    	if (successful)
    	  return TRUE;
    }
    init_warning("No response from host program");
    return FALSE;
}

/* Tell the remote program what its id will be during this game. */

void
send_remote_id(int rid)
{
    sprintf(spbuf, "r%d", rid);
    send_packet(rid, spbuf);
}

void
net_send_chat(int rid, char *str)
{
    if (my_rid == master_rid) {
	send_chat(rid, str);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "c%d %s", rid, str);
	broadcast_packet(spbuf);
    }
}

void
send_version(int rid)
{
    /* In order to guarantee that the kernels can actually stay in
       sync, we require that the program versions match exactly. */
    sprintf(spbuf, "V%s", version_string());
    send_packet(rid, spbuf);
}

void
net_set_variant_value(int which, int v1, int v2, int v3)
{
    if (my_rid == master_rid) {
	set_variant_value(which, v1, v2, v3);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "v %d %d %d %d", which, v1, v2, v3);
	broadcast_packet(spbuf);
    }
}

void
net_set_player_advantage(int n, int newadv)
{
    if (my_rid == master_rid) {
	set_player_advantage(n, newadv);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "a%d advantage %d", n, newadv);
	broadcast_packet(spbuf);
    }
}

int
net_add_side_and_player(void)
{
    int rslt = -1;

    if (my_rid == master_rid) {
	rslt = add_side_and_player();
    }
    if (numremotes > 0) {
	sprintf(spbuf, "a0 add");
	broadcast_packet(spbuf);
    }
    return rslt;
}

void
net_rename_side_for_player(int n, int which)
{
    if (my_rid == master_rid) {
	rename_side_for_player(n, which);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "a%d rename %d", n, which);
	broadcast_packet(spbuf);
    }
}

void
net_set_ai_for_player(int n, char *aitype)
{
    if (my_rid == master_rid) {
	set_ai_for_player(n, aitype);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "a%d ai %s", n, (aitype ? aitype : ""));
	broadcast_packet(spbuf);
    }
}

int
net_exchange_players(int n, int n2)
{
    int rslt = -1;

    if (my_rid == master_rid) {
	rslt = exchange_players(n, n2);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "a%d exchange %d", n, n2);
	broadcast_packet(spbuf);
    }
    return rslt;
}

void
send_assignment(int rid, Side *side, Player *player)
{
    sprintf(spbuf, "Passign %d %d", side->id, player->id);
    send_packet(rid, spbuf);
}

/* (this routine is almost certainly incorrect now - only used on Macs) */
void
download_to_player(Player *player)
{
    int i;

    download_game_module(player->rid);
    /* Send it the current list of side/player assignments. */
    for (i = 0; i < numsides; ++i) {
	send_assignment(player->rid, assignments[i].side,
			assignments[i].player);
    }
    /* Make it run everything up to the player setup phase. */
    send_randstate(player->rid);
    /* If errors, should set player rid back to zero */
}

void
net_update_player(Player *player)
{
    sprintf(spbuf, "a%d update %s", player->id, player_desig(player));
    broadcast_packet(spbuf);
}

/* Given the id of a remote program that has just connected,
   download our own current state, as a module. */

void
download_game_module(int rid)
{
    int rslt;
    Module *module;

    if (1 /* sending name only */) {
	sprintf(spbuf, "g %s", mainmodule->name);
	send_packet(rid, spbuf);
	/* (should send some sort of state checksum also) */
	return;
    }
    /* The indepside might not have been filled in yet (such as when
       connecting before sides/players have been set up), but we need
       it to be correct before going into write_game_module. */
    if (numtotsides == 0)
      create_side();
    /* Record the rid in a global so the module-writing code knows
       where to send all the packets. */
    tmprid = rid;
    send_packet(rid, "gameModule");
    module = create_game_module("*download*");
    copy_module(module, mainmodule);
    /* This module is not associated with a file. */
    module->filename = NULL;
    /* The module will sent in its entirety, so suppress any attempt
       to load a base module. */
    module->basemodulename = NULL;
    module->compress_tables = TRUE;
    module->compress_layers = TRUE;
    module->def_all = TRUE; /* for now */
    downloading = TRUE;
    rslt = write_game_module(module);
    downloading = FALSE;
    send_packet(rid, "\neludoMemag\n");
    tmprid = 0;
}

#ifndef MAC
#define DOWNLOADPACKETSIZE 200
#else
#define DOWNLOADPACKETSIZE 40
#endif

static char *notherbuf;

void
add_to_packet(char *str)
{
    if (notherbuf == NULL)
      notherbuf = xmalloc(DOWNLOADPACKETSIZE + 10);
    if (strlen(str) + strlen(notherbuf) > DOWNLOADPACKETSIZE) {
	/* No room in the packet, send it and start on a new one. */
	send_packet(tmprid, notherbuf);
	notherbuf[0] = '\0';
	/* If the string is long, break it into multiple packets. */
	while (strlen(str) > DOWNLOADPACKETSIZE) {
	    strncpy(notherbuf, str, DOWNLOADPACKETSIZE);
	    notherbuf[DOWNLOADPACKETSIZE] = '\0';
	    send_packet(tmprid, notherbuf);
	    notherbuf[0] = '\0';
	    str += DOWNLOADPACKETSIZE;
	}
    }
    strcat(notherbuf, str);
}

void
flush_write(void)
{
    if (!empty_string(notherbuf)) {
	send_packet(tmprid, notherbuf);
	notherbuf[0] = '\0';
    }
}

/* Make sure every remote program has the same game that we do. */

void
broadcast_game_module(void)
{
    int rid;

    for_all_remotes(rid) {
	if (rid != my_rid) {
	    download_game_module(rid);
	}
    }
}

void
broadcast_start_variant_setup(void)
{
    sprintf(spbuf, "vSTART");
    broadcast_packet(spbuf);
}

void
broadcast_variants_chosen(void)
{
    /* Once variants have been chosen, programs will advance to making
       trial assignments immediately, so make sure randstates are in
       sync first. */
    sprintf(spbuf, "R%ld", randstate);
    broadcast_packet(spbuf); 
    sprintf(spbuf, "vOK");
    broadcast_packet(spbuf);
}

void
broadcast_start_player_setup(void)
{
    sprintf(spbuf, "aSTART");
    broadcast_packet(spbuf);
}

void
broadcast_players_assigned(void)
{
    sprintf(spbuf, "aOK");
    broadcast_packet(spbuf);
}

void
send_randstate(int rid)
{
    sprintf(spbuf, "R%ld", randstate);
    send_packet(rid, spbuf); 
}

void
broadcast_game_checksum(void)
{
    sprintf(spbuf, "Z%d %d", my_rid, game_checksum());
    broadcast_packet(spbuf); 
}

void
send_game_checksum_error(int rid, int my_csum, int master_csum)
{
    sprintf(spbuf, "Echecksum %d %d %d", my_rid, my_csum, master_csum);
    send_packet(rid, spbuf); 
}

time_t last_checksum_time;

int
net_run_game(int maxactions)
{
    int oldsernum, oldstate, sendcheck, oldcsum, newcsum, numdone = 0;
    time_t now;

    if (my_rid == master_rid) {
	if (numremotes > 0) {
	    oldsernum = g_run_serial_number();
	    oldstate = randstate;
	    /* Send occasional checksums at random times. */
	    sendcheck = FALSE;
	    if (0 /* for now */) {
		time(&now);
		sendcheck = (idifftime(now, last_checksum_time) >= 2);
		if (sendcheck)
		  last_checksum_time = now;
	    }
	    if (sendcheck)
	      broadcast_game_checksum();
	    oldcsum = game_checksum();
	}
	/* This is where the master's real run_game call happens. */
	numdone = run_game(maxactions);
	if (numremotes > 0) {
	    if (numdone > 0) {
		sprintf(spbuf, "Za%d %d", my_rid, oldcsum);
		broadcast_packet(spbuf); 
		sprintf(spbuf, "X%d %d %d", maxactions, oldsernum, oldstate);
		broadcast_packet(spbuf);
		broadcast_game_checksum();
	    } else {
		newcsum = game_checksum();
		if (newcsum != oldcsum)
		  notify_all("Checksum changed %d -> %d when numdone == 0",
			      oldcsum, newcsum);
	    }
	}
    }
    return numdone;
}

void
net_request_additional_side(char *playerspec)
{
    if (my_rid == master_rid) {
	request_additional_side(playerspec);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "Padd %s", (playerspec ? playerspec : ""));
	broadcast_packet(spbuf);
    }
}

void
net_send_message(Side *side, SideMask sidemask, char *str)
{
    if (my_rid == master_rid) {
	send_message(side, sidemask, str);
    }
    if (numremotes > 0) {
	sprintf(spbuf, "M%d %d %s", side_number(side), sidemask, str);
	broadcast_packet(spbuf);
    }
}

static void
broadcast_command_5(char *cmd, int a1, int a2, int a3, int a4, int a5)
{
    sprintf(spbuf, "C%s %d %d %d %d %d", cmd, a1, a2, a3, a4, a5);
    broadcast_packet(spbuf);
}

void
net_resign_game(Side *side, Side *side2)
{
    if (my_rid == master_rid) {
	resign_game(side, side2);
    }
    if (numremotes > 0) {
      broadcast_side_property(side, "resign",
			      (side2 ? side_number(side2) : 0));
    }
}

/* Side property tweaking. */

void
net_set_side_name(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_name(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "name", newname);
    }
}

void
net_set_side_longname(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_longname(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "longname", newname);
    }
}

void
net_set_side_shortname(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_shortname(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "shortname", newname);
    }
}

void
net_set_side_noun(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_noun(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "noun", newname);
    }
}

void
net_set_side_pluralnoun(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_pluralnoun(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "pluralnoun", newname);
    }
}

void
net_set_side_adjective(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_adjective(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "adjective", newname);
    }
}

void
net_set_side_emblemname(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_emblemname(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "emblemname", newname);
    }
}

void
net_set_side_colorscheme(Side *side, Side *side2, char *newname)
{
    if (my_rid == master_rid) {
	set_side_colorscheme(side, side2, newname);
    }
    if (numremotes > 0)  {
	broadcast_side_str_property(side2, "colorscheme", newname);
    }
}

void
net_finish_turn(Side *side)
{
    if (my_rid == master_rid) {
	finish_turn(side);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "fin", 0);
    }
}

void
net_set_trust(Side *side, Side *side2, int val)
{
    if (my_rid == master_rid) {
	set_trust(side, side2, val);
    }
    if (numremotes > 0)  {
	broadcast_side_property_2(side, "trust", side_number(side2), val);
    }
}

void
net_set_controlled_by(Side *side, Side *side2, int val)
{
    if (my_rid == master_rid) {
	set_controlled_by(side, side2, val);
    }
    if (numremotes > 0)  {
	broadcast_side_property_2(side, "controlledby", side_number(side2), val);
    }
}

void
net_set_autofinish(Side *side, int value)
{
    if (my_rid == master_rid) {
	set_autofinish(side, value);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "af", value);
    }
}

void
net_set_autoresearch(Side *side, int value)
{
    if (my_rid == master_rid) {
	set_autoresearch(side, value);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "ar", value);
    }
}

void
net_set_willing_to_save(Side *side, int flag)
{
    if (my_rid == master_rid) {
	set_willing_to_save(side, flag);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "save", flag);
    }
}

void
net_set_willing_to_draw(Side *side, int flag)
{
    if (my_rid == master_rid) {
	set_willing_to_draw(side, flag);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "draw", flag);
    }
}

void
net_set_side_self_unit(Side *side, Unit *unit)
{
    if (my_rid == master_rid) {
	set_side_self_unit(side, unit);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "self", (unit ? unit->id : 0));
    }
}

void
net_set_side_ai(Side *side, char *typename)
{
    if (my_rid == master_rid) {
	set_side_ai(side, typename);
    }
    if (numremotes > 0)  {
	if (typename == NULL)
	  typename = "";
	broadcast_side_str_property(side, "ai", typename);
    }
}

void
net_set_doctrine(Side *side, char *spec)
{
    if (my_rid == master_rid) {
	set_doctrine(side, spec);
    }
    if (numremotes > 0)  {
	if (spec == NULL)
	  spec = "";
	broadcast_side_str_property(side, "doctrine", spec);
    }
}

void
net_set_side_research(Side *side, int a)
{
    if (my_rid == master_rid) {
	set_side_research(side, a);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "research", a);
    }
}

#ifdef DESIGNERS

void
net_become_designer(Side *side)
{
    if (my_rid == master_rid) {
	become_designer(side);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "designer", TRUE);
    }
}

void
net_become_nondesigner(Side *side)
{
    if (my_rid == master_rid) {
	become_nondesigner(side);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "designer", FALSE);
    }
}

void
net_paint_view(Side *side, int x, int y, int r, int tview, int uview)
{
    if (my_rid == master_rid) {
	paint_view(side, x, y, r, tview, uview);
    }
    if (numremotes > 0)  {
	sprintf(spbuf, "D%d view %d %d %d %d %d %d",
		side_number(side), side_number(side), x, y, r, tview, uview);
	broadcast_packet(spbuf);
    }
}

#endif /* DESIGNERS */

static void
broadcast_side_property(Side *side, char *prop, int val)
{
    sprintf(spbuf, "S%d %s %d", side_number(side), prop, val);
    broadcast_packet(spbuf);
}

static void
broadcast_side_property_2(Side *side, char *prop, int val, int val2)
{
    sprintf(spbuf, "S%d %s %d %d", side_number(side), prop, val, val2);
    broadcast_packet(spbuf);
}

static void
broadcast_side_str_property(Side *side, char *prop, char *val)
{
    sprintf(spbuf, "S%d %s %s", side_number(side), prop, val);
    broadcast_packet(spbuf);
}

/* Unit property tweaking. */

void
net_set_unit_name(Side *side, Unit *unit, char *newname)
{
    if (my_rid == master_rid) {
	set_unit_name(side, unit, newname);
    }
    if (numremotes > 0) {
	broadcast_unit_str_property(side, unit, "name", newname);
    }
}

void
net_set_unit_plan_type(Side *side, Unit *unit, int type)
{
    if (my_rid == master_rid) {
	set_unit_plan_type(side, unit, type);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "plan", type);
    }
}

void
net_set_unit_asleep(Side *side, Unit *unit, int flag, int recurse)
{
    if (my_rid == master_rid) {
	set_unit_asleep(side, unit, flag, recurse);
    }
    if (numremotes > 0) {
	broadcast_unit_property_2(side, unit, "sleep", flag, recurse);
    }
}

void
net_set_unit_reserve(Side *side, Unit *unit, int flag, int recurse)
{
    if (my_rid == master_rid) {
	set_unit_reserve(side, unit, flag, recurse);
    }
    if (numremotes > 0) {
	broadcast_unit_property_2(side, unit, "resv", flag, recurse);
    }
}

void
net_set_unit_main_goal(Side *side, Unit *unit, Goal *goal)
{
    if (my_rid == master_rid) {
	set_unit_main_goal(side, unit, goal);
    }
    if (numremotes > 0) {
	broadcast_unit_property_5(side, unit, "maingoal",
				  (goal ? goal->type : 0),
				  (goal ? goal->args[0] : 0),
				  (goal ? goal->args[1] : 0),
				  (goal ? goal->args[2] : 0),
				  (goal ? goal->args[3] : 0));
    }
}

void
net_set_unit_curadvance(Side *side, Unit *unit, int a)
{
    if (my_rid == master_rid) {
	set_unit_curadvance(side, unit, a);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "curadvance", a);
    }
}

void
net_set_unit_autoplan(Side *side, Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	set_unit_autoplan(side, unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "autoplan", flag);
    }
}

void
net_set_unit_autoresearch(Side *side, Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	set_unit_autoresearch(side, unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "autoresearch", flag);
    }
}

void
net_set_unit_autobuild(Side *side, Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	set_unit_autobuild(side, unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "autobuild", flag);
    }
}

void
net_set_unit_buildingdone(Side *side, Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	set_unit_buildingdone(side, unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "buildingdone", flag);
    }
}

void
net_set_unit_researchdone(Side *side, Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	set_unit_researchdone(side, unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "researchdone", flag);
    }
}

void
net_set_unit_waiting_for_transport(Side *side, Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	set_unit_waiting_for_transport(side, unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "waittrans", flag);
    }
}

void
net_wake_unit(Side *side, Unit *unit, int wakeocc)
{
    if (my_rid == master_rid) {
	wake_unit(side, unit, wakeocc);
    }
    if (numremotes > 0) {
	broadcast_command_5("wake-unit", side_number(side), unit->id, wakeocc, 0, 0);
    }
}

void
net_wake_area(Side *side, int x, int y, int n, int occs)
{
    if (my_rid == master_rid) {
	wake_area(side, x, y, n, occs);
    }
    if (numremotes > 0) {
	broadcast_command_5("wake-area", side_number(side), x, y, n, occs);
    }
}

void
net_set_unit_ai_control(Side *side, Unit *unit, int flag, int recurse)
{
    if (my_rid == master_rid) {
	set_unit_ai_control(side, unit, flag, recurse);
    }
    if (numremotes > 0) {
	broadcast_unit_property_2(side, unit, "ai", flag, recurse);
    }
}

int
net_clear_task_agenda(Side *side, Unit *unit)
{
    if (my_rid == master_rid) {
	clear_task_agenda(unit->plan);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "clra", 0);
    }
    return 0;
}

void
net_force_replan(Side *side, Unit *unit, int passive_only)
{
    if (my_rid == master_rid) {
	force_replan(side, unit, passive_only);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "forcereplan", passive_only);
    }
}

int
net_disband_unit(Side *side, Unit *unit)
{
    if (my_rid == master_rid) {
	return disband_unit(side, unit);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "disband", 0);
    }
    return 0;
}

void
net_set_formation(Unit *unit, Unit *leader, int x, int y, int dist, int flex)
{
    if (my_rid == master_rid) {
	set_formation(unit, leader, x, y, dist, flex);
    }
    if (numremotes > 0) {
	broadcast_unit_property_5(unit->side, unit, "formation",
				  (leader ? leader->id : 0), x, y, dist, flex);
    }
}

void
net_delay_unit(Unit *unit, int flag)
{
    if (my_rid == master_rid) {
	delay_unit(unit, flag);
    }
    if (numremotes > 0) {
	broadcast_unit_property(unit->side, unit, "delay", flag);
    }
}

#ifdef DESIGNERS

/* A designer can call this to create an arbitrary unit during the game. */

Unit *
net_designer_create_unit(Side *side, int u, int s, int x, int y)
{
    Unit *rslt = NULL;

    if (my_rid == master_rid) {
	rslt = designer_create_unit(side, u, s, x, y);
    }
    if (numremotes > 0) {
	broadcast_command_5("designer-create-unit", side_number(side), u, s, x, y);
    }
    return rslt;
}

/* Move a unit to a given location instantly, with all sides observing.
   This is for use by designers only! */

int
net_designer_teleport(Unit *unit, int x, int y, Unit *other)
{
    int rslt = 0;

    if (my_rid == master_rid) {
	rslt = designer_teleport(unit, x, y, other);
    }
    if (numremotes > 0) {
	broadcast_command_5("designer-teleport", unit->id, x, y, (other ? other->id : 0), 0);
    }
    return rslt;
}

int
net_designer_change_side(Unit *unit, Side *side)
{
    int rslt = 0;

    if (my_rid == master_rid) {
	rslt = designer_change_side(unit, side);
    }
    if (numremotes > 0) {
	broadcast_command_5("designer-change-side", unit->id,
			    side_number(side), 0, 0, 0);
    }
    return rslt;
}

int
net_designer_disband(Unit *unit)
{
    int rslt = 0;

    if (my_rid == master_rid) {
	rslt = designer_disband(unit);
    }
    if (numremotes > 0) {
	broadcast_command_5("designer-disband", unit->id, 0, 0, 0, 0);
    }
    return rslt;
}

#endif /* DESIGNERS */

Feature *
net_create_feature(char *typename, char *name)
{
    Feature *rslt = NULL;

    if (my_rid == master_rid) {
	rslt = create_feature(typename, name);
    }
    if (numremotes > 0) {
	run_error("net");
    }
    return rslt;
}

void
net_set_feature_type_name(Feature *feature, char *typename)
{
    if (my_rid == master_rid) {
	set_feature_type_name(feature, typename);
    }
    if (numremotes > 0) {
	run_error("net");
    }
}

void
net_set_feature_name(Feature *feature, char *name)
{
    if (my_rid == master_rid) {
	set_feature_name(feature, name);
    }
    if (numremotes > 0) {
	run_error("net");
    }
}

void
net_destroy_feature(Feature *feature)
{
    if (my_rid == master_rid) {
	destroy_feature(feature);
    }
    if (numremotes > 0) {
	run_error("net");
    }
}

void
net_renumber_features(void)
{
    if (my_rid == master_rid) {
	renumber_features();
    }
    if (numremotes > 0) {
	run_error("net");
    }
}


static void
broadcast_unit_property(Side *side, Unit *unit, char *prop, int val)
{
    char buf[BUFSIZE];

    sprintf(buf, "U%d %d %s %d", side_number(side), unit->id, prop, val);
    broadcast_packet(buf);
}

static void
broadcast_unit_property_2(Side *side, Unit *unit, char *prop, int val, int val2)
{
    char buf[BUFSIZE];

    sprintf(buf, "U%d %d %s %d %d", side_number(side), unit->id, prop, val, val2);
    broadcast_packet(buf);
}

static void
broadcast_unit_property_5(Side *side, Unit *unit, char *prop,
			  int val, int val2, int val3, int val4, int val5)
{
    char buf[BUFSIZE];

    sprintf(buf, "U%d %d %s %d %d %d %d %d", side_number(side), unit->id, prop,
	    val, val2, val3, val4, val5);
    broadcast_packet(buf);
}

static void
broadcast_unit_str_property(Side *side, Unit *unit, char *prop, char *val)
{
    char buf[BUFSIZE];

    sprintf(buf, "U%d %d %s %s", side_number(side), unit->id, prop, val);
    broadcast_packet(buf);
}

/* Action networking. */

void
broadcast_next_action(Unit *unit)
{
    int j, atype, n;
    char buf[BUFSIZE];

    if (numremotes <= 0)
      return;
    atype = unit->act->nextaction.type;
    sprintf(buf, "A%d", unit->id);
    if (unit->id != unit->act->nextaction.actee) {
	tprintf(buf, "/%d", unit->act->nextaction.actee);
    }
    tprintf(buf, " %d", unit->act->nextaction.type);
    n = strlen(actiondefns[atype].argtypes);
    for (j = 0; j < n; ++j) {
	tprintf(buf, " %d", unit->act->nextaction.args[j]);
    }
    broadcast_packet(buf);
    if (my_rid != master_rid) {
	/* Bash the local action, wait for master to send it back in
	   the correct order. */
	unit->act->nextaction.type = ACTION_NONE;
    }
}

/* From actions.c. */

int
net_prep_none_action(Unit *unit, Unit *unit2)
{
    int rslt;

    rslt = prep_none_action(unit, unit2);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_produce_action(unit, unit2, m, n)
Unit *unit, *unit2;
int m, n;
{
    int rslt;

    rslt = prep_produce_action(unit, unit2, m, n);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_extract_action(unit, unit2, x, y, m, n)
Unit *unit, *unit2;
int x, y, m, n;
{
    int rslt;

    rslt = prep_extract_action(unit, unit2, x, y, m, n);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_develop_action(Unit *unit, Unit *unit2, int u3)
{
    int rslt;

    rslt = prep_develop_action(unit, unit2, u3);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_toolup_action(Unit *unit, Unit *unit2, int u3)
{
    int rslt;

    rslt = prep_toolup_action(unit, unit2, u3);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_create_in_action(Unit *unit, Unit *unit2, int u3, Unit *dest)
{
    int rslt;

    rslt = prep_create_in_action(unit, unit2, u3, dest);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_create_at_action(Unit *unit, Unit *unit2, int u3, int x, int y, int z)
{
    int rslt;

    rslt = prep_create_at_action(unit, unit2, u3, x, y, z);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_build_action(Unit *unit, Unit *unit2, Unit *newunit)
{
    int rslt;

    rslt = prep_build_action(unit, unit2, newunit);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_repair_action(unit, unit2, unit3)
Unit *unit, *unit2, *unit3;
{
    int rslt;

    rslt = prep_repair_action(unit, unit2, unit3);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_disband_action(unit, unit2)
Unit *unit, *unit2;
{
    int rslt;

    rslt = prep_disband_action(unit, unit2);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_transfer_part_action(unit, unit2, parts, unit3)
Unit *unit, *unit2, *unit3;
int parts;
{
    int rslt;

    rslt = prep_transfer_part_action(unit, unit2, parts, unit3);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_change_type_action(unit, unit2, u3)
Unit *unit, *unit2;
int u3;
{
    int rslt;

    rslt = prep_change_type_action(unit, unit2, u3);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_change_side_action(unit, unit2, side)
Unit *unit, *unit2;
Side *side;
{
    int rslt;

    rslt = prep_change_side_action(unit, unit2, side);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_alter_cell_action(unit, unit2, x, y, t)
Unit *unit, *unit2;
int x, y, t;
{
    int rslt;

    rslt = prep_alter_cell_action(unit, unit2, x, y, t);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_add_terrain_action(unit, unit2, x, y, dir, t)
Unit *unit, *unit2;
int x, y, dir, t;
{
    int rslt;

    rslt = prep_add_terrain_action(unit, unit2, x, y, dir, t);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_remove_terrain_action(unit, unit2, x, y, dir, t)
Unit *unit, *unit2;
int x, y, dir, t;
{
    int rslt;

    rslt = prep_remove_terrain_action(unit, unit2, x, y, dir, t);
    broadcast_next_action(unit);
    return rslt;
}

/* From combat.c. */

int
net_prep_attack_action(Unit *unit, Unit *unit2, Unit *defender, int n)
{
    int rslt;

    rslt = prep_attack_action(unit, unit2, defender, n);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_overrun_action(Unit *unit, Unit *unit2, int x, int y, int z, int n)
{
    int rslt;

    rslt = prep_overrun_action(unit, unit2, x, y, z, n);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_fire_at_action(Unit *unit, Unit *unit2, Unit *unit3, int m)
{
    int rslt;

    rslt = prep_fire_at_action(unit, unit2, unit3, m);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_fire_into_action(Unit *unit, Unit *unit2, int x, int y, int z, int m)
{
    int rslt;

    rslt = prep_fire_into_action(unit, unit2, x, y, z, m);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_capture_action(Unit *unit, Unit *unit2, Unit *unit3)
{
    int rslt;

    rslt = prep_capture_action(unit, unit2, unit3);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_detonate_action(Unit *unit, Unit *unit2, int x, int y, int z)
{
    int rslt;

    rslt = prep_detonate_action(unit, unit2, x, y, z);
    broadcast_next_action(unit);
    return rslt;
}

/* From move.c. */

int
net_prep_move_action(Unit *unit, Unit *unit2, int x, int y, int z)
{
    int rslt;

    rslt = prep_move_action(unit, unit2, x, y, z);
    broadcast_next_action(unit);
    return rslt;
}

int
net_prep_enter_action(Unit *unit, Unit *unit2, Unit *unit3)
{
    int rslt;

    rslt = prep_enter_action(unit, unit2, unit3);
    broadcast_next_action(unit);
    return rslt;
}

/* Task networking. */

#define CLEAR_AGENDA 99

static Task *tmptask;

/* (should remove, never used) */
void
net_add_task(unit, pos, task)
Unit *unit;
int pos;
Task *task;
{
    if (my_rid == master_rid) {
	add_task(unit, pos, task);
    }
    if (numremotes > 0) {
	broadcast_add_task(unit, pos, task);
    }
}

void
net_set_capture_task(Unit *unit, int x, int y, int u, int s)
{
    if (my_rid == master_rid) {
	set_capture_task(unit, x, y, u, s);
    }
    if (numremotes > 0) {
	tmptask = create_capture_task(unit, x, y, u, s);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_set_occupy_task(Unit *unit, Unit *transport)
{
    if (my_rid == master_rid) {
	set_occupy_task(unit, transport);
    }
    if (numremotes > 0) {
	tmptask = create_occupy_task(unit, transport);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_set_collect_task(Unit *unit, int m, int x, int y)
{
    if (my_rid == master_rid) {
	set_collect_task(unit, m, x, y);
    }
    if (numremotes > 0) {
	tmptask = create_collect_task(unit, m, x, y);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_set_move_to_task(Unit *unit, int x, int y, int dist)
{
    if (my_rid == master_rid) {
	set_move_to_task(unit, x, y, dist);
    }
    if (numremotes > 0) {
	tmptask = create_move_to_task(unit, x, y, dist);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_push_move_to_task(Unit *unit, int x, int y, int dist)
{
    if (my_rid == master_rid) {
	push_move_to_task(unit, x, y, dist);
    }
    if (numremotes > 0) {
	tmptask = create_move_to_task(unit, x, y, dist);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_set_move_dir_task(Unit *unit, int dir, int n)
{
    if (my_rid == master_rid) {
	set_move_dir_task(unit, dir, n);
    }
    if (numremotes > 0) {
	tmptask = create_move_dir_task(unit, dir, n);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_set_build_task(Unit *unit, int u2, int run, int x, int y)
{
    if (my_rid == master_rid) {
	set_build_task(unit, u2, run, x, y);
    }
    if (numremotes > 0) {
	tmptask = create_build_task(unit, u2, run, x, y);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

/* Same as above but handles cases where we resume building an
   incomplete occ. */

void
net_resume_build_task(Unit *unit, Unit *unit2, int run, int x, int y)
{
    if (my_rid == master_rid) {
	resume_build_task(unit, unit2, run, x, y);
    }
    if (numremotes > 0) {
	tmptask = create_build_task(unit, unit2->type, run, x, y);
	tmptask->args[1] = unit2->id;		
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_push_build_task(Unit *unit, int u2, int run, int x, int y)
{
    if (my_rid == master_rid) {
	push_build_task(unit, u2, run, x, y);
    }
    if (numremotes > 0) {
	tmptask = create_build_task(unit, u2, run, x, y);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_push_develop_task(Unit *unit, int u2, int n)
{
    if (my_rid == master_rid) {
	push_develop_task(unit, u2, n);
    }
    if (numremotes > 0) {
	tmptask = create_develop_task(unit, u2, n);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_push_hit_unit_task(Unit *unit, int x, int y, int u, int s)
{
    if (my_rid == master_rid) {
	push_hit_unit_task(unit, x, y, u, s);
    }
    if (numremotes > 0) {
	tmptask = create_hit_unit_task(unit, x, y, u, s);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_set_hit_unit_task(Unit *unit, int x, int y, int u, int s)
{
    if (my_rid == master_rid) {
	set_hit_unit_task(unit, x, y, u, s);
    }
    if (numremotes > 0) {
	tmptask = create_hit_unit_task(unit, x, y, u, s);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_set_disband_task(Unit *unit)
{
    if (my_rid == master_rid) {
	set_disband_task(unit);
    }
    if (numremotes > 0) {
	tmptask = create_task(TASK_DISBAND);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_set_resupply_task(Unit *unit, int m)
{
    if (my_rid == master_rid) {
	set_resupply_task(unit, m);
    }
    if (numremotes > 0) {
	tmptask = create_resupply_task(unit, m);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_push_occupy_task(Unit *unit, Unit *transport)
{
    if (my_rid == master_rid) {
	push_occupy_task(unit, transport);
    }
    if (numremotes > 0) {
	tmptask = create_occupy_task(unit, transport);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_push_pickup_task(Unit *unit, Unit *occ)
{
    if (my_rid == master_rid) {
	push_pickup_task(unit, occ);
    }
    if (numremotes > 0) {
	tmptask = create_pickup_task(unit, occ);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_push_produce_task(Unit *unit, int m, int n)
{
    if (my_rid == master_rid) {
	push_produce_task(unit, m, n);
    }
    if (numremotes > 0) {
	tmptask = create_produce_task(unit, m, n);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_set_sentry_task(Unit *unit, int n)
{
    if (my_rid == master_rid) {
	set_sentry_task(unit, n);
    }
    if (numremotes > 0) {
	tmptask = create_sentry_task(unit, n);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

void
net_push_sentry_task(Unit *unit, int n)
{
    if (my_rid == master_rid) {
	push_sentry_task(unit, n);
    }
    if (numremotes > 0) {
	tmptask = create_sentry_task(unit, n);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

static void
broadcast_add_task(Unit *unit, int pos, Task *task)
{
    int numargs, i;
    char buf[BUFSIZE], *argtypes;

    sprintf(buf, "T%d %d %d %d %d",
	    unit->id, pos, task->type, task->execnum, task->retrynum);
    argtypes = taskdefns[task->type].argtypes;
    numargs = strlen(argtypes);
    for (i = 0; i < numargs; ++i)
      tprintf(buf, " %d", task->args[i]);
    broadcast_packet(buf);
}

/* World tweaking. */

#ifdef DESIGNERS

void
net_paint_cell(Side *side, int x, int y, int r, int t)
{
    if (my_rid == master_rid) {
	paint_cell(side, x, y, r, t);
    }
    if (numremotes > 0) {
	broadcast_layer_change("cell", side, x, y, r, t, 0, 0);
    }
}

void
net_paint_border(Side *side, int x, int y, int dir, int t, int mode)
{
    if (my_rid == master_rid) {
	paint_border(side, x, y, dir, t, mode);
    }
    if (numremotes > 0) {
	broadcast_layer_change("bord", side, x, y, dir, t, mode, 0);
    }
}

void
net_paint_connection(Side *side, int x, int y, int dir, int t, int mode)
{
    if (my_rid == master_rid) {
	paint_connection(side, x, y, dir, t, mode);
    }
    if (numremotes > 0) {
	broadcast_layer_change("conn", side, x, y, dir, t, mode, 0);
    }
}

void
net_paint_coating(Side *side, int x, int y, int r, int t, int depth)
{
    if (my_rid == master_rid) {
	paint_coating(side, x, y, r, t, depth);
    }
    if (numremotes > 0) {
	broadcast_layer_change("coat", side, x, y, r, t, depth, 0);
    }
}

void
net_paint_people(Side *side, int x, int y, int r, int s)
{
    if (my_rid == master_rid) {
	paint_people(side, x, y, r, s);
    }
    if (numremotes > 0) {
	broadcast_layer_change("peop", side, x, y, r, s, 0, 0);
    }
}

void
net_paint_control(Side *side, int x, int y, int r, int s)
{
    if (my_rid == master_rid) {
	paint_control(side, x, y, r, s);
    }
    if (numremotes > 0) {
	broadcast_layer_change("ctrl", side, x, y, r, s, 0, 0);
    }
}

void
net_paint_feature(Side *side, int x, int y, int r, int f)
{
    if (my_rid == master_rid) {
	paint_feature(side, x, y, r, f);
    }
    if (numremotes > 0) {
	broadcast_layer_change("feat", side, x, y, r, f, 0, 0);
    }
}

void
net_paint_elevation(Side *side, int x, int y, int r, int code, int elev,
		    int vary)
{
    if (my_rid == master_rid) {
	paint_elevation(side, x, y, r, code, elev, vary);
    }
    if (numremotes > 0) {
	broadcast_layer_change("elev", side, x, y, r, code, elev, vary);
    }
}

void
net_paint_temperature(Side *side, int x, int y, int r, int temp)
{
    if (my_rid == master_rid) {
	paint_temperature(side, x, y, r, temp);
    }
    if (numremotes > 0) {
	broadcast_layer_change("temp", side, x, y, r, temp, 0, 0);
    }
}

void
net_paint_material(Side *side, int x, int y, int r, int m, int amt)
{
    if (my_rid == master_rid) {
	paint_material(side, x, y, r, m, amt);
    }
    if (numremotes > 0) {
	broadcast_layer_change("m", side, x, y, r, m, amt, 0);
    }
}

void
net_paint_clouds(Side *side, int x, int y, int r, int cloudtype, int bot,
		 int hgt)
{
    if (my_rid == master_rid) {
	paint_clouds(side, x, y, r, cloudtype, bot, hgt);
    }
    if (numremotes > 0) {
	broadcast_layer_change("clouds", side, x, y, r, cloudtype, 0, 0);
	broadcast_layer_change("cloud-bottoms", side, x, y, r, bot, 0, 0);
	broadcast_layer_change("cloud-heights", side, x, y, r, hgt, 0, 0);
    }
}

void
net_paint_winds(Side *side, int x, int y, int r, int dir, int force)
{
    if (my_rid == master_rid) {
	paint_winds(side, x, y, r, dir, force);
    }
    if (numremotes > 0) {
	broadcast_layer_change("wind", side, x, y, r, dir, force, 0);
    }
}

static void
broadcast_layer_change(char *layername, Side *side, int x, int y,
		       int a1, int a2, int a3, int a4)
{
    char buf[BUFSIZE];

    sprintf(buf, "W%s %d %d %d %d %d %d %d",
	    layername, side->id, x, y, a1, a2, a3, a4);
    broadcast_packet(buf);
}

#endif /* DESIGNERS */

void
send_quit(void)
{
    /* (should have more error-handling, since may be called in bad
       situations) */
    sprintf(spbuf, "Q%d", my_rid);
    broadcast_packet(spbuf);
    flush_outgoing_queue();
}

/* Given a buffer with a packet in it, send it all the places that
   it should go. */

static void
broadcast_packet(char *buf)
{
    int rid;
    extern int in_run_game;
 
    if (in_run_game) {
	notify_all("Attempting to send \"%s\" while in run_game", buf);
    }
    if (my_rid == master_rid) {
	for_all_remotes(rid) {
	    if (rid != my_rid) {
		send_packet(rid, buf);
	    }
	}
    } else {
	/* Send to master; master will echo back eventually. */
	send_packet(master_rid, buf);
    }
}

/* Given a packet to be sent, package it up (add header, embed escape
   chars, add checksum), send, and maybe wait for acknowledgement. */

int
send_packet(int id, char *inbuf)
{
    int i, j, csum, numtimeouts;
    char buf[BUFSIZE];

    if (my_rid != master_rid && !sendnow) {
	/* Add packet to the outgoing queue. */
	save_outgoing_packet(id, inbuf);
	Dprintf("OutQ: %d \"%s\"\n", id, inbuf);
	return TRUE;
    }
    Dprintf("Send: %d \"%s\"\n", id, inbuf);
    j = 0;
    csum = 0;
    buf[j++] = STARTPKT;
    /* Copy the input buffer into the packet, changing any special
       characters into escape sequences along the way. */
    for (i = 0; inbuf[i] != '\0'; ++i) {
	if (inbuf[i] == STARTPKT) {
	    buf[j++] = ESCAPEPKT;
	    buf[j++] = STARTPKTESC;
	} else if (inbuf[i] == ENDPKT) {
	    buf[j++] = ESCAPEPKT;
	    buf[j++] = ENDPKTESC;
	} else if (inbuf[i] == ESCAPEPKT) {
	    buf[j++] = ESCAPEPKT;
	    buf[j++] = ESCAPEPKTESC;
	} else {
	    buf[j++] = inbuf[i];
	}
	csum += (unsigned char) inbuf[i];
    }
    buf[j++] = ENDPKT;
    /* Add on the checksum, in hex. */
    buf[j++] = tohex ((csum >> 4) & 0xf);
    buf[j++] = tohex (csum & 0xf);
    buf[j++] = '\0';
    low_send(id, buf);
    /* Wait for the packet's receipt to be acknowledged. */
    numtimeouts = 0;
    expecting_ack = TRUE;
    while (expecting_ack) {
	receive_data(30);
	if (!expecting_ack)
	  break;
	if (timeout_warnings)
	  notify_all("Timed out waiting for ack");
	else
	  notify_all("Timed out waiting for ack");
	++numtimeouts;
	if (hosting && my_rid == master_rid) {
	    /* If we're the master, we MUST be assured that our
	       packets have been received, since we've already changed
	       our own state and can't undo it. So we will loop here
	       for a long time, can only get out early if run_warning
	       includes a quit option. */
	    if (numtimeouts > 200)
	      run_error("Timed out %d times", numtimeouts);
	} else {
	    /* If we're not the master, then we haven't actually
	       changed our own state, and so we can continue on
	       safely.  The user will have to retry whatever state
	       change was desired. */
	    expecting_ack = FALSE;
	    return FALSE;
	}
    }
    return TRUE;
}

struct q_entry {
  int id;
  char *buf;
  struct q_entry *next;
} *outgoing, *outgoing_last, *incoming, *incoming_last;

static void
save_outgoing_packet(int id, char *inbuf)
{
    struct q_entry *entry;

    entry = (struct q_entry *) xmalloc (sizeof (struct q_entry));
    entry->id = id;
    entry->buf = copy_string(inbuf);
    if (outgoing_last) {
	outgoing_last->next = entry;
    } else {
	outgoing = entry;
    }
    outgoing_last = entry;
}

void
flush_outgoing_queue(void)
{
    struct q_entry *entry;

    if (my_rid != master_rid) {
	sendnow = TRUE;
	for (entry = outgoing; entry != NULL; entry = entry->next) {
	    send_packet(entry->id, entry->buf);
	}
	outgoing = outgoing_last = NULL;
	sendnow = FALSE;
    }
}

static void
save_incoming_packet(int id, char *inbuf)
{
    struct q_entry *entry;

    entry = (struct q_entry *) xmalloc (sizeof (struct q_entry));
    entry->id = id;
    entry->buf = copy_string(inbuf);
    if (incoming_last) {
	incoming_last->next = entry;
    } else {
	incoming = entry;
    }
    incoming_last = entry;
}

void
flush_incoming_queue(void)
{
    struct q_entry *entry;

    for (entry = incoming; entry != NULL; entry = entry->next) {
	receive_packet(entry->id, entry->buf);
    }
    incoming = incoming_last = NULL;
}

static char *packetbuf;

static char *rsltbuf;

static int rsltid;

#define PACKETBUFSIZE 1000

static time_t nothing_start_time;

static int nothing_count;

static int nothing_timeout;

static void
remove_chars(char *buf, int n)
{
    int i;

    for (i = 0; i < PACKETBUFSIZE - n; ++i)
      buf[i] = buf[i + n];
}

/* Handle the basic process of receiving packets. */

void
receive_data(int timeout)
{
    char buf[2000], *pktend;
    int retry = 10, gotsome, i, j, len, csum, pktcsum;

    /* (should have buffers for each remote prog) */
    if (packetbuf == NULL)
      packetbuf = xmalloc(PACKETBUFSIZE);
    if (rsltbuf == NULL)
      rsltbuf = xmalloc(BUFSIZE);
    if (incoming != NULL && !expecting_ack) {
	flush_incoming_queue();
	return;
    }
    while (retry > 0) {
	if (packetbuf[0] == STARTPKT) {
	    pktend = strchr(packetbuf, ENDPKT);
	    if (pktend != NULL
		&& strlen(packetbuf) >= (pktend - packetbuf + 3)
		) {
		/* We have accumulated a whole packet. */
		len = pktend - packetbuf - 1;
		/* Copy out the packet's contents, handling escape
		   chars and computing checksum along the way. */
		j = 0;
		csum = 0;
		for (i = 1; i < len + 1; ++i) {
		    if (packetbuf[i] == ESCAPEPKT
			&& packetbuf[i+1] == STARTPKTESC) {
			rsltbuf[j++] = STARTPKT;
			++i;
		    } else if (packetbuf[i] == ESCAPEPKT
			       && packetbuf[i+1] == ENDPKTESC) {
			rsltbuf[j++] = ENDPKT;
			++i;
		    } else if (packetbuf[i] == ESCAPEPKT
			       && packetbuf[i+1] == ESCAPEPKTESC) {
			rsltbuf[j++] = ESCAPEPKT;
			++i;
		    } else {
			rsltbuf[j++] = packetbuf[i];
		    }
		    csum += (unsigned char) rsltbuf[j-1];
		}
		rsltbuf[j++] = '\0';
		csum &= 0xff;
		/* Extract the checksum that was sent. */
		pktcsum = fromhex(packetbuf[len + 2]) << 4;
		pktcsum |= fromhex(packetbuf[len + 3]);
		/* Remove the packet from the buffer. */
		remove_chars(packetbuf, len + 4);
		if (csum != pktcsum && pktcsum != 0) {
		    run_warning("checksum error, received %x, calced %x",
				pktcsum, csum);
		    /* what to do with the packet? should require resend */
		}
		if (expecting_ack) {
		    Dprintf("Packet received while expecting ack, saving \"%s\"\n", rsltbuf);
		    save_incoming_packet(rsltid, rsltbuf);
		    Dprintf("Sending ack to unexpected packet\n");
		    low_send(rsltid, "+");
		    goto foo;
		}
		if (rsltid == 0) {
		    run_warning("Packet from rid == 0, discarded \"%s\"",
				rsltbuf);
		    goto foo;
		}
		Dprintf("Sending ack\n");
		low_send(rsltid, "+");
		/* The interpretation process needs to be last,
		   because it may in turn cause more packets to be
		   sent. */
		receive_packet(rsltid, rsltbuf);
		retry = 0;
	    } else {
		/* Part of a packet, but not the whole thing -
                   continue waiting. */
		--retry;
		goto foo;
	    }
	} else if (packetbuf[0] == '+') {
	    expecting_ack = FALSE;
	    Dprintf("Ack received\n");
	    remove_chars(packetbuf, 1);
	    retry = 0;
	} else if (strlen(packetbuf) > 0) {
	    Dprintf("Removing garbage char '%c' (0x%x)\n",
		    packetbuf[0], packetbuf[0]);
	    remove_chars(packetbuf, 1);
	} else {
	foo:
	    gotsome = low_receive(&rsltid, buf, BUFSIZE, timeout);
	    /* Debugging printout. */
	    if (gotsome) {
		if (nothing_count > 0) {
		    Dprintf("Rcvd nothing %d times (timeout %d secs)\n",
			    nothing_count, nothing_timeout);
		    nothing_count = 0;
		}
		Dprintf("Rcvd: %d \"%s\"\n", rsltid, (buf ? buf : "<null>"));
	    } else {
		time_t now;

		if (nothing_count == 0)
		  time(&nothing_start_time);
		time(&now);
		if (idifftime(now, nothing_start_time) > 0
		    || timeout != nothing_timeout) {
		    Dprintf("Rcvd nothing %d times (timeout %d secs)\n",
			    nothing_count, nothing_timeout);
		    nothing_count = 0;
		} else {
		    ++nothing_count;
		    nothing_timeout = timeout;
		}
		nothing_timeout = timeout;
	    }
	    if (gotsome) {
		if (strlen(packetbuf) + strlen(buf) > PACKETBUFSIZE) {
		    run_warning("packet buffer overflow");
		    return;
		}
		strcat(packetbuf, buf);
		/* Go around and see if we have something now. */
		--retry;
	    } else {
		retry = 0;
	    }
	}
    }
}

/* Given a received packet, take it apart and do what it says. */

void
receive_packet(int id, char *buf)
{
    int should_rebroadcast;

    Dprintf("From %d: \"%s\"\n", id, buf);
    if (downloading) {
	if (strcmp(buf, "\neludoMemag\n") == 0) {
	    /* We've seen the end-of-module marker, now interpret. */
	    downloading = FALSE;
	    mainmodule = get_game_module("*download*");
	    mainmodule->contents = downloadbuf;
	    /* The sequence below is similar to that in load_game_module. */
	    open_module(mainmodule, FALSE);
	    read_forms(mainmodule);
	    close_module(mainmodule);
	    /* Make all the cross-references right. */
	    patch_object_references();
	    check_game_validity();
	    start_variant_setup_stage();
	} else {
	    /* Make sure we have space with which to concatenate. */
	    if (strlen(downloadbuf) + strlen(buf) >= 200000) {
		init_error("Exceeded download buffer size");
		return;
	    }
	    /* Concatenate this packet to the others. */
	    strcat(downloadbuf, buf);
	}
    } else {
	should_rebroadcast = TRUE;
	switch (buf[0]) {
	  case 'A':
	    receive_action(buf + 1);
	    break;
	  case 'C':
	    receive_command(buf + 1);
	    break;
	  case 'E':
	    receive_error(id, buf + 1);
	    should_rebroadcast = FALSE;
	    break;
	  case 'M':
	    receive_net_message(buf + 1);
	    break;
	  case 'P':
	    receive_player_prop(buf + 1);
	    break;
	  case 'Q':
	    receive_quit(buf + 1);
	    break;
	  case 'R':
	    new_randstate = atoi(buf + 1);
	    if (new_randstate != randstate) {
		Dprintf("Rand state change: %d -> %d\n",
			randstate, new_randstate);
	    } else {
		Dprintf("Rand state matches\n");
	    }
	    randstate = new_randstate;
	    /* This is only issued by the master, no need to rebroadcast. */
	    should_rebroadcast = FALSE;
	    break;
	  case 'S':
	    receive_side_prop(buf + 1);
	    break;
	  case 'T':
	    receive_task(buf + 1);
	    break;
	  case 'U':
	    receive_unit_prop(buf + 1);
	    break;
	  case 'V':
	    if (strcmp(buf + 1, version_string()) != 0) {
		init_warning("Xconq versions \"%s\" and \"%s\" should not link up",
			     buf + 1, version_string());
	    }
	    should_rebroadcast = FALSE;
	    break;
	  case 'W':
	    receive_world_prop(buf + 1);
	    break;
	  case 'X':
	    receive_run_game(buf + 1);
	    /* This is only issued by the master, no need to rebroadcast. */
	    should_rebroadcast = FALSE;
	    break;
	  case 'Z':
	    receive_game_checksum(buf + 1);
	    /* Checksums may be broadcast, but not automatically. */
	    should_rebroadcast = FALSE;
	    break;
	  case 'a':
	    if (buf[1] == 'S' && buf[2] == 'T') {
		start_player_setup_stage();
		/* This is only issued by the master, no need to
                   rebroadcast. */
		should_rebroadcast = FALSE;
	    } else if (buf[1] == 'O' && buf[2] == 'K') {
		start_game_ready_stage();
		/* This is only issued by the master, no need to
                   rebroadcast. */
		should_rebroadcast = FALSE;
	    } else {
		receive_assignment_setting(buf + 1);
	    }
	    break;
	  case 'c':
	    receive_chat(buf + 1);
	    break;
	  case 'g':
	    if (buf[1] == ' ') {
		Module *module = get_game_module(buf + 2);
		mainmodule = module;
		load_game_module(mainmodule, TRUE);
	    } else {
		downloading = TRUE;
		/* (should use obstack here) */
		if (downloadbuf == NULL)
		  downloadbuf = xmalloc(200000);
		downloadbuf[0] = '\0';
	    }
	    should_rebroadcast = FALSE;
	    break;
	  case 'j':
	    send_remote_id(id);
	    add_remote_program(id, buf + 1);
	    /* Don't rebroadcast, the master will handle specially. */
	    should_rebroadcast = FALSE;
	    break;
	  case 'p':
	    receive_remote_program(buf + 1);
	    start_game_load_stage();  /* seems wrong? */
	    /* This is only issued by the master, no need to rebroadcast. */
	    should_rebroadcast = FALSE;
	    break;
	  case 'r':	
	    my_rid = atoi(buf + 1);
	    /* This is only issued by the master, no need to rebroadcast. */
	    should_rebroadcast = FALSE;
	    break;
	  case 'v':
	    if (buf[1] == 'S' && buf[2] == 'T') {
		start_variant_setup_stage();
		/* This is only issued by the master, no need to
                   rebroadcast. */
		should_rebroadcast = FALSE;
	    } else if (buf[1] == 'O' && buf[2] == 'K') {
		start_player_pre_setup_stage();
		/* This is only issued by the master, no need to
                   rebroadcast. */
		should_rebroadcast = FALSE;
	    } else {
		receive_variant_setting(buf + 1);
	    }
	    break;
	  default:
	    /* Since the protocol's purpose is to keep multiple
	       executables' state in sync, every kind of packet must
	       be understood. */
	    run_warning("Packet not recognized: \"%s\"\n", buf);
	    break;
	}
	/* The master should rebroadcast most types of packets
	   automatically, so all programs stay in sync. */
	if (my_rid == master_rid && should_rebroadcast)
	  broadcast_packet(buf);
    }
}

static void
receive_net_message(char *str)
{
    int id;
    char *nstr;
    SideMask sidemask;
    Side *side;

    id = strtol(str, &nstr, 10);
    side = side_n(id);
    str = nstr;
    sidemask = strtol(str, &nstr, 10);
    str = nstr;
    ++str;
    /* Note that this is the internal form of "send", which just copies
       to all local recipients. */
    send_message(side, sidemask, str);
}

static void
receive_command(char *str)
{
    int i, args[5];
    char *nstr, *argstr;
    Side *side;
    Unit *unit, *unit2;

    argstr = strchr(str, ' ');
    i = 0;
    while (*argstr != '\0' && i < 5) {
	args[i++] = strtol(argstr, &nstr, 10);
	argstr = nstr;
    }
    if (strncmp(str, "wake-unit ", 9) == 0) {
	side = side_n(args[0]);
	if (side == NULL) {
	    return;
	}
	unit = find_unit(args[1]);
	if (unit == NULL) {
	    return;
	}
	wake_unit(side, unit, args[2]);
    } else if (strncmp(str, "wake-area ", 9) == 0) {
	side = side_n(args[0]);
	if (side == NULL) {
	    return;
	}
	wake_area(side, args[1], args[2], args[3], args[4]);
    } else if (strncmp(str, "designer-create-unit ", strlen("designer-create-unit ")) == 0) {
	side = side_n(args[0]);
	if (side == NULL) {
	    return;
	}
	designer_create_unit(side, args[1], args[2], args[3], args[4]);		
    } else if (strncmp(str, "designer-teleport ", strlen("designer-teleport ")) == 0) {
	unit = find_unit(args[0]);
	if (unit == NULL) {
	    return;
	}
	unit2 = NULL;
	if (args[3] > 0) {
	    unit2 = find_unit(args[3]);
	    if (unit2 == NULL) {
		return;
	    }
	}
	designer_teleport(unit, args[1], args[2], unit2);		
    } else if (strncmp(str, "designer-change-side ", strlen("designer-change-side ")) == 0) {
	unit = find_unit(args[0]);
	if (unit == NULL) {
	    return;
	}
	side = side_n(args[1]);
	designer_change_side(unit, side);		
    } else if (strncmp(str, "designer-disband ", strlen("designer-disband ")) == 0) {
	unit = find_unit(args[0]);
	if (unit == NULL) {
	    return;
	}
	designer_disband(unit);		
    } else {
	run_warning("Unknown C packet \"%s\", ignoring", str);
    }
}

static void
receive_action(char *str)
{
    int unitid, acteeid, i;
    char *nstr;
    Unit *unit;
    Action tmpaction;

    unitid = strtol(str, &nstr, 10);
    str = nstr;
    if (*str == '/') {
	++str;
	acteeid = strtol(str, &nstr, 10);
	str = nstr;
    } else {
	acteeid = unitid;
    }
    tmpaction.actee = acteeid;
    tmpaction.type = strtol(str, &nstr, 10);
    str = nstr;
    i = 0;
    while (*nstr != '\0' && i < 10) {
	tmpaction.args[i++] = strtol(str, &nstr, 10);
	str = nstr;
    }
    unit = find_unit(unitid);
    if (unit == NULL) {
	run_warning("Packet A refers to missing unit #%d, ignoring", unitid);
	return;
    }
    if (unit->act == NULL) {
	run_warning("Packet A refers to non-acting unit %s, ignoring",
		    unit_desig(unit));
	return;
    }
    /* Lay the action over what is already there; the next run_game
       should actually do it. */
    unit->act->nextaction = tmpaction;
}

static void
receive_task(char *str)
{
    int unitid, pos, tasktype, i;
    char *nstr;
    Unit *unit;
    Task *task;

    unitid = strtol(str, &nstr, 10);
    str = nstr;
    pos = strtol(str, &nstr, 10);
    str = nstr;
    tasktype = strtol(str, &nstr, 10);
    str = nstr;
    task = create_task(tasktype);
    task->execnum = strtol(str, &nstr, 10);
    str = nstr;
    task->retrynum = strtol(str, &nstr, 10);
    str = nstr;
    i = 0;
    while (*nstr != '\0' && i < 10) {
	task->args[i++] = strtol(str, &nstr, 10);
	str = nstr;
    }
    unit = find_unit(unitid);
    if (unit == NULL) {
	run_warning("Packet T refers to missing unit #%d, ignoring", unitid);
	return;
    }
    if (unit->plan == NULL) {
	run_warning("Packet T refers to non-planning unit %s, ignoring",
		    unit_desig(unit));
	return;
    }
    add_task(unit, pos, task);
}

static void
receive_player_prop(char *str)
{
    int val, val2;
    char *nstr;
    Side *side;

    if (strncmp(str, "add ", 4) == 0) {
	str += 4;
	request_additional_side(str);
    } else if (strncmp(str, "assign ", 7) == 0) {
	str += 7;
	val = strtol(str, &nstr, 10);
	str = nstr;
	val2 = strtol(str, &nstr, 10);
	side = side_n(val);
	if (side != NULL)
	  side->player = find_player(val2);
    } else {
	run_warning("Unknown P packet \"%s\", ignoring", str);
    }
}

static void
receive_quit(char *str)
{
    int rid;
    char *nstr;

    rid = strtol(str, &nstr, 10);
    Dprintf("Received quit from %d\n", rid);
    online[rid] = FALSE;
    quitter = rid;
}

static void
receive_side_prop(char *str)
{
    int id, sn, val;
    char *nstr;
    Side *side;
    Unit *unit;

    id = strtol(str, &nstr, 10);
    side = side_n(id);
    if (side == NULL)
      return;
    str = nstr;
    ++str;
    if (strncmp(str, "adjective ", 10) == 0) {
	set_side_adjective(side, side, str + 10);
    } else if (strncmp(str, "af ", 3) == 0) {
	str += 3;
	val = strtol(str, &nstr, 10);
	set_autofinish(side, val);
    } else if (strncmp(str, "ar ", 3) == 0) {
	str += 3;
	val = strtol(str, &nstr, 10);
	set_autoresearch(side, val);
    } else if (strncmp(str, "ai ", 3) == 0) {
	str += 3;
	set_side_ai(side, str);
    } else if (strncmp(str, "colorscheme ", 12) == 0) {
	set_side_colorscheme(side, side, str + 12);
    } else if (strncmp(str, "controlledby ", 13) == 0) {
	str += 13;
	sn = strtol(str, &nstr, 10);
	str = nstr;
	val = strtol(str, &nstr, 10);
	set_controlled_by(side, side_n(sn), val);
    } else if (strncmp(str, "designer ", 9) == 0) {
	str += 9;
	val = strtol(str, &nstr, 10);
	if (val)
	  become_designer(side);
	else
	  become_nondesigner(side);
    } else if (strncmp(str, "doctrine ", 9) == 0) {
	str += 9;
	set_doctrine(side, str);
    } else if (strncmp(str, "draw ", 5) == 0) {
	str += 5;
	val = strtol(str, &nstr, 10);
	set_willing_to_draw(side, val);
    } else if (strncmp(str, "emblemname ", 11) == 0) {
	set_side_emblemname(side, side, str + 11);
    } else if (strncmp(str, "fin ", 4) == 0) {
	finish_turn(side);
    } else if (strncmp(str, "longname ", 9) == 0) {
	set_side_longname(side, side, str + 9);
    } else if (strncmp(str, "name ", 5) == 0) {
	set_side_name(side, side, str + 5);
    } else if (strncmp(str, "noun ", 5) == 0) {
	set_side_noun(side, side, str + 5);
    } else if (strncmp(str, "pluralnoun ", 11) == 0) {
	set_side_pluralnoun(side, side, str + 11);
    } else if (strncmp(str, "resign ", 7) == 0) {
	str += 7;
	val = strtol(str, &nstr, 10);
	resign_game(side, (val ? side_n(val) : NULL));
    } else if (strncmp(str, "save ", 5) == 0) {
	str += 5;
	val = strtol(str, &nstr, 10);
	set_willing_to_save(side, val);
    } else if (strncmp(str, "self ", 5) == 0) {
	str += 5;
	val = strtol(str, &nstr, 10);
	unit = find_unit(val);
	set_side_self_unit(side, unit);
    } else if (strncmp(str, "research ", 9) == 0) {
	str += 9;
	val = strtol(str, &nstr, 10);
	set_side_research(side, val);
    } else if (strncmp(str, "shortname ", 10) == 0) {
	set_side_shortname(side, side, str + 10);
    } else if (strncmp(str, "trust ", 6) == 0) {
	str += 6;
	sn = strtol(str, &nstr, 10);
	str = nstr;
	val = strtol(str, &nstr, 10);
	set_trust(side, side_n(sn), val);
    } else {
	run_warning("Unknown S packet \"%s\", ignoring", str);
    }
}

static void
receive_unit_prop(char *str)
{
    int sid, uid, val, val2;
    char *nstr;
    Unit *unit;
    Side *side;

    /* Collect the side. */
    sid = strtol(str, &nstr, 10);
    side = side_n(sid);
    /* Note that the side may be NULL. */
    str = nstr;
    /* Collect the unit. */
    uid = strtol(str, &nstr, 10);
    unit = find_unit(uid);
    if (unit == NULL) {
	run_warning("Packet with invalid unit id %d, ignoring packet", uid);
	return;
    }
    str = nstr;
    ++str;
    /* Decode the property being set. */
    if (strncmp(str, "ai ", 3) == 0) {
	val = strtol(str + 3, &nstr, 10);
	str = nstr;
	val2 = strtol(str, &nstr, 10);
	set_unit_ai_control(side, unit, val, val2);
    } else if (strncmp(str, "clra ", 5) == 0) {
	clear_task_agenda(unit->plan);
    } else if (strncmp(str, "delay ", 6) == 0) {
	val = strtol(str + 6, &nstr, 10);
	delay_unit(unit, val);
    } else if (strncmp(str, "disband ", 8) == 0) {
	disband_unit(side, unit);
    } else if (strncmp(str, "forcereplan ", 12) == 0) {
	val = strtol(str + 12, &nstr, 10);
	force_replan(side, unit, val);
    } else if (strncmp(str, "formation ", 10) == 0) {
	run_warning("need to interp formation packet");
    } else if (strncmp(str, "maingoal ", 9) == 0) {
	Goal *goal;
	int i;

	str += 9;
	val = strtol(str, &nstr, 10);
	if (val > 0) {
	    str = nstr;
	    goal = create_goal(val, side, TRUE);
	    for (i = 0; i < 4; ++i) {
		val = strtol(str, &nstr, 10);
		str = nstr;
		goal->args[i] = val;
	    }
	} else {
	    goal = NULL;
	}
	set_unit_main_goal(side, unit, goal);
    } else if (strncmp(str, "curadvance ", 11) == 0) {
	val = strtol(str + 11, &nstr, 10);
	set_unit_curadvance(side, unit, val);
    } else if (strncmp(str, "autoplan ", 9) == 0) {
	val = strtol(str + 9, &nstr, 10);
	set_unit_autoplan(side, unit, val);
    } else if (strncmp(str, "autoresearch ", 13) == 0) {
	val = strtol(str + 13, &nstr, 10);
	set_unit_autoresearch(side, unit, val);
    } else if (strncmp(str, "autobuild ", 10) == 0) {
	val = strtol(str + 10, &nstr, 10);
	set_unit_autobuild(side, unit, val);
    } else if (strncmp(str, "buildingdone ", 13) == 0) {
	val = strtol(str + 13, &nstr, 10);
	set_unit_autobuild(side, unit, val);
    } else if (strncmp(str, "researchdone ", 13) == 0) {
	val = strtol(str + 13, &nstr, 10);
	set_unit_autobuild(side, unit, val);
    } else if (strncmp(str, "name ", 5) == 0) {
	set_unit_name(side, unit, str + 5);
    } else if (strncmp(str, "plan ", 5) == 0) {
	val = strtol(str + 5, &nstr, 10);
	set_unit_plan_type(side, unit, val);
    } else if (strncmp(str, "resv ", 5) == 0) {
	str += 5;
	val = strtol(str, &nstr, 10);
	str = nstr;
	val2 = strtol(str, &nstr, 10);
	set_unit_reserve(side, unit, val, val2);
    } else if (strncmp(str, "sleep ", 6) == 0) {
	str += 6;
	val = strtol(str, &nstr, 10);
	str = nstr;
	val2 = strtol(str, &nstr, 10);
	set_unit_asleep(side, unit, val, val2);
    } else if (strncmp(str, "waittrans ", 10) == 0) {
	str += 10;
	val = strtol(str, &nstr, 10);
	set_unit_waiting_for_transport(side, unit, val);
    } else {
	run_warning("Unknown U packet \"%s\", ignoring", str);
    }
}

static void
receive_world_prop(char *str)
{
    int id, x, y, a1, a2, a3, a4;
    char *str2, *nstr;
    Side *side;

    str2 = strchr(str, ' ');
    if (str2 == NULL) {
	return;
    }
    *str2 = '\0';
    ++str2;
    id = strtol(str2, &nstr, 10);
    str2 = nstr;
    x = strtol(str2, &nstr, 10);
    str2 = nstr;
    y = strtol(str2, &nstr, 10);
    str2 = nstr;
    a1 = strtol(str2, &nstr, 10);
    str2 = nstr;
    a2 = strtol(str2, &nstr, 10);
    str2 = nstr;
    a3 = strtol(str2, &nstr, 10);
    str2 = nstr;
    a4 = strtol(str2, &nstr, 10);
    side = side_n(id);
    if (strcmp(str, "cell") == 0) {
	paint_cell(side, x, y, a1, a2);
    } else if (strcmp(str, "bord") == 0) {
	paint_border(side, x, y, a1, a2, a3);
    } else if (strcmp(str, "conn") == 0) {
	paint_connection(side, x, y, a1, a2, a3);
    } else if (strcmp(str, "coat") == 0) {
	paint_coating(side, x, y, a1, a2, a3);
    } else if (strcmp(str, "peop") == 0) {
	paint_people(side, x, y, a1, a2);
    } else if (strcmp(str, "ctrl") == 0) {
	paint_control(side, x, y, a1, a2);
    } else if (strcmp(str, "feat") == 0) {
	paint_feature(side, x, y, a1, a2);
    } else if (strcmp(str, "elev") == 0) {
	paint_elevation(side, x, y, a1, a2, a3, a4);
    } else if (strcmp(str, "temp") == 0) {
	paint_temperature(side, x, y, a1, a2);
    } else if (strcmp(str, "m") == 0) {
	paint_material(side, x, y, a1, a2, a3);
    } else if (strcmp(str, "wind") == 0) {
	paint_winds(side, x, y, a1, a2, a3);
    } else {
	run_warning("Unknown W packet \"%s\", ignoring", str);
    }
}

static void
receive_run_game(char *str)
{
    int maxactions, newsernum;
    char *reststr, *nreststr;

    maxactions = strtol(str, &reststr, 10);
    newsernum = strtol(reststr, &nreststr, 10);
    reststr = nreststr;
    new_randstate = strtol(reststr, &nreststr, 10);
    set_g_run_serial_number(newsernum);
    if (new_randstate != randstate) {
	Dprintf("Rand state change: %d -> %d\n", randstate, new_randstate);
    } else {
	Dprintf("Rand state matches\n");
    }
    randstate = new_randstate;
    /* This where non-masters actually call run_game. */
    run_game(maxactions);
}

static void
receive_game_checksum(char *str)
{
    int before_run = FALSE, other_rid, other_csum, our_csum;
    char *reststr, *nreststr;

    if (*str == 'a') {
	before_run = TRUE;
	++str;
    }
    other_rid = strtol(str, &reststr, 10);
    other_csum = strtol(reststr, &nreststr, 10);
    our_csum = game_checksum();
    if (our_csum != other_csum) {
	/* If we're about to lose, make sure the master knows about it. */
	if (my_rid != master_rid)
	  send_game_checksum_error(master_rid, our_csum, other_csum);
	/* This would be a warning normally, but being out of sync
	   isn't always a real problem apparently... */
	notify_all("Game is out of sync! (received %d from %d, computed %d%s)",
		    other_csum, other_rid, our_csum,
		    (before_run ? ", before run_game" : ""));
    }
}

/* Compute a single integer derived from the contents of the game state. */

#define add_int_to_checksum(cs, x) ((cs) += (x));

int
game_checksum(void)
{
    int csum, i;
    Side *side;
    Unit *unit;
    Plan *plan;
    Task *task;

    csum = 0;
    for_all_sides(side) {
	add_int_to_checksum(csum, side->id);
	add_int_to_checksum(csum, side->self_unit_id);
	add_int_to_checksum(csum, side->controlled_by_id);
	add_int_to_checksum(csum, side->ingame);
	add_int_to_checksum(csum, side->everingame);
	add_int_to_checksum(csum, side->status);
	add_int_to_checksum(csum, side->willingtodraw);
	add_int_to_checksum(csum, side->autofinish);
	add_int_to_checksum(csum, side->finishedturn);
	add_int_to_checksum(csum, side->advantage);
    }
    for_all_units(unit) {
	add_int_to_checksum(csum, unit->id);
	add_int_to_checksum(csum, unit->number);
	add_int_to_checksum(csum, unit->x);
	add_int_to_checksum(csum, unit->y);
	add_int_to_checksum(csum, unit->z);
	add_int_to_checksum(csum, unit->hp);
	if (unit->transport) {
	    add_int_to_checksum(csum, unit->transport->id);
	}
	plan = unit->plan;
	if (unit->plan) {
	    add_int_to_checksum(csum, plan->type);
	    add_int_to_checksum(csum, plan->asleep);
	    add_int_to_checksum(csum, plan->reserve);
	    add_int_to_checksum(csum, plan->delayed);
	    add_int_to_checksum(csum, plan->waitingfortasks);
	    add_int_to_checksum(csum, plan->aicontrol);
	    for_all_tasks(plan, task) {
		add_int_to_checksum(csum, task->type);
		for (i = 0; i < MAXTASKARGS; ++i)
		  add_int_to_checksum(csum, task->args[i]);
		add_int_to_checksum(csum, task->execnum);
		add_int_to_checksum(csum, task->retrynum);
	    }
	}
    }
    return csum;
}

static void
receive_error(int id, char *str)
{
    /* (should decode specific types of errors?) */
    notify_all("Error from #%d, \"%s\"", id, str);
    Dprintf("Error from #%d, \"%s\"\n", id, str);
}

static void
receive_remote_program(char *str)
{
    int rid;
    char *nstr;

    rid = strtol(str, &nstr, 10);
    str = nstr + 1;
    numremotes = max(rid, numremotes);
    online[rid] = TRUE;
    remote_player_specs[rid] = copy_string(str);
    /* (should do with a function pointer) */
    add_remote_locally(rid, str);
}

static void
receive_chat(char *str)
{
    int rid;
    char *nstr;

    rid = strtol(str, &nstr, 10);
    str = nstr + 1;
    /* (should do with a function pointer) */
    send_chat(rid, str);
}

static void
receive_variant_setting(char *str)
{
    int which, v1, v2, v3;
    char *nstr;

    ++str;
    which = strtol(str, &nstr, 10);
    str = nstr + 1;
    v1 = strtol(str, &nstr, 10);
    str = nstr + 1;
    v2 = strtol(str, &nstr, 10);
    str = nstr + 1;
    v3 = strtol(str, &nstr, 10);
    set_variant_value(which, v1, v2, v3);
    if (update_variant_callback)
      (*update_variant_callback)(which);
}

static void
receive_assignment_setting(char *str)
{
    int n, n2, val;
    char *nstr, *aitype;
    Player *player;

    n = strtol(str, &nstr, 10);
    str = nstr;
    ++str;
    if (strncmp(str, "add", 3) == 0) {
	n = add_side_and_player();
    } else if (strncmp(str, "advantage ", 10) == 0) {
	val = strtol(str + 10, NULL, 10);
	player = assignments[n].player;
	if (player != NULL) {
	    player->advantage = val;
	}
    } else if (strncmp(str, "ai ", 3) == 0) {
	aitype = NULL;
	if (str[3] != '\0')
	  aitype = copy_string(str + 3);
	set_ai_for_player(n, aitype);
    } else if (strncmp(str, "exchange ", 9) == 0) {
	n2 = strtol(str + 9, NULL, 10);
	n2 = exchange_players(n, n2);
	if (update_assignment_callback)
	  (*update_assignment_callback)(n2);
    } else if (strncmp(str, "rename ", 7) == 0) {
	n2 = strtol(str + 7, NULL, 10);
	rename_side_for_player(n, n2);
    } else if (strncmp(str, "update ", 7) == 0) {
	nstr = str + 7;
	player = find_player(n);
	if (player == NULL)
	  player = add_player();
	parse_player_spec(player, nstr);
	return; /* a hack, need to handle callback below when windows not up */
    } else {
	run_error("assignment packet \"%s\" not recognized", str);
    }
    if (n >= 0) {
	/* Most settings affect the same assignment, so always update it
	   here. */
	if (update_assignment_callback)
	  (*update_assignment_callback)(n);
    }
}

/* Convert hex digit A to a number.  */

static int
fromhex(int a)
{
    if (a >= '0' && a <= '9')
      return a - '0';
    else if (a >= 'a' && a <= 'f')
      return a - 'a' + 10;
    else 
      run_warning ("Reply contains invalid hex digit %d", a);
    return 0;
}

/* Convert number NIB to a hex digit.  */

static int
tohex(int nib)
{
    if (nib < 10)
      return '0' + nib;
    else
      return 'a' + nib - 10;
}

/* The following "connection method" is for debugging. */

static char *inport_filename;
static char *outport_filename;
static FILE *inport;
static FILE *outport;

static char *sendbuf;

void
init_file_port(int ishost)
{
    /* Just use wired-in filenames, since only for debugging. */
    if (ishost) {
	inport_filename = "server.in";
	outport_filename = "server.out";
    } else {
	inport_filename = "client.in";
	outport_filename = "client.out";
    }
    inport = open_file(inport_filename, "r");
    outport = open_file(outport_filename, "w");
    if (inport == NULL || outport == NULL)
      run_warning("connection files opening failed");
    /* (should be able to get all the rids from file) */
    master_rid = 1;
    if (ishost) {
	my_rid = 1;
    } else {
	my_rid = 2;
    }
    numremotes = 2;
}

void
low_file_send(int rid, char *buf)
{
    int i, j, len;

    if (outport) {
	if (sendbuf == NULL)
	  sendbuf = xmalloc(1000);
	j = 0;
	len = strlen(buf);
	for (i = 0; i <= len; ++i) {
	    if (buf[i] == '\n') {
		sendbuf[j++] = '<';
		sendbuf[j++] = 'n';
		sendbuf[j++] = 'l';
		sendbuf[j++] = '>';
	    } else {
		sendbuf[j++] = buf[i];
	    }
	}
	fprintf(outport, "%d->%d: %s\n", my_rid, rid, sendbuf);
	fflush(outport);
    } else {
	run_warning("No place to write \"%s\"", (buf ? buf : "<null>"));
    }
}

int
low_file_receive(int *idp, char *buf, int maxchars, int timeout)
{
    int rslt = FALSE, i, j, len;
    char readbuf[BUFSIZE], *str;

    if (inport) {
	/* Read until we find a message addressed to us. */
	while (1) {
	    str = fgets(readbuf, maxchars, inport);
	    if (str == NULL)
	      return FALSE;
	    if (outport)
	      fputs(str, outport);
	    if (str[3] - '0' == my_rid)
	      break;
	}
	*idp = str[0] - '0';
	strcpy(buf, str + 6);
	len = strlen(buf);
	if (buf[len-1] == '\n') {
	    buf[len-1] = '\0';
	    --len;
	}
	if (strstr(buf, "<nl>")) {
	    j = 0;
	    for (i = 0; i <= len; ++i) {
		if (i + 4 <= len
		    && buf[i] == '<'
		    && buf[i+1] == 'n'
		    && buf[i+2] == 'l'
		    && buf[i+3] == '>'
		    ) {
		    buf[j++] = '\n';
		    i += 3;
		} else {
		    buf[j++] = buf[i];
		}
	    }
	}
	rslt = TRUE;
    } else {
	run_warning("No place to read");
    }
    return rslt;
}

void
close_file_port(void)
{
    fclose(inport);
    fclose(outport);
}

