/* Implementation of the transfer protocol for Xconq.
   Copyright (C) 1996, 1997, 1998 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"

void flush_outgoing_queue PARAMS ((void));
void flush_incoming_queue PARAMS ((void));

extern long randstate;

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

#define for_all_remotes(rid) for ((rid) = 1; (rid) <= numremotes; ++(rid))

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

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

static void broadcast_message PARAMS ((Side *side, SideMask sidemask,
				       char *str));
static void broadcast_command_5 PARAMS ((char *cmd, int val, int val2,
					 int val3, int val4, int val5));
static void broadcast_side_property PARAMS ((Side *side, char *prop, int val));
static void broadcast_side_property_2 PARAMS ((Side *side, char *prop,
					       int val, int val2));
static void broadcast_side_str_property PARAMS ((Side *side, char *prop,
						 char *val));
static void broadcast_unit_property PARAMS ((Side *side, Unit *unit,
					     char *prop, int val));
static void broadcast_unit_property_2 PARAMS ((Side *side, Unit *unit,
					       char *prop, int val, int val2));
static void broadcast_unit_property_5 PARAMS ((Side *side, Unit *unit,
					       char *prop, int val, int val2,
					       int val3, int val4, int val5));
static void broadcast_unit_str_property PARAMS ((Side *side, Unit *unit,
						 char *prop, char *val));
static void broadcast_add_task PARAMS ((Unit *unit, int pos, Task *task));
static void broadcast_layer_change PARAMS ((char *layername, Side *side,
					    int x, int y,
					    int a1, int a2, int a3));
static void broadcast_packet PARAMS ((char *buf));
static void save_outgoing_packet PARAMS ((int id, char *inbuf));
static void save_incoming_packet PARAMS ((int id, char *inbuf));
static void remove_chars PARAMS ((char *buf, int n));
static void receive_net_message PARAMS ((char *str));
static void receive_command PARAMS ((char *str));
static void receive_action PARAMS ((char *str));
static void receive_player_prop PARAMS ((char *str));
static void receive_quit PARAMS ((char *str));
static void receive_side_prop PARAMS ((char *str));
static void receive_task PARAMS ((char *str));
static void receive_unit_prop PARAMS ((char *str));
static void receive_world_prop PARAMS ((char *str));
static void receive_run_game PARAMS ((char *str));
static void receive_game_checksum PARAMS ((char *str));
static int game_checksum PARAMS ((void));
static int tohex PARAMS ((int x));
static int fromhex PARAMS ((int x));

int numremotes;

int numremotewaiting;

/* The remote id of this program. */

int my_rid;

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

int master_rid;

int tmprid;

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
init_remote_ui(side)
Side *side;
{
    if (side->rui == NULL) {
	side->rui = (RUI *) xmalloc(sizeof(RUI));
    }
    side->rui->rid = side->rid;
}

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

int
send_join(str)
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;
}

void
send_version(rid)
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
send_assignment(id, side, player)
int id;
Side *side;
Player *player;
{
    sprintf(spbuf, "Passign %d %d", side->id, player->id);
    send_packet(id, spbuf);
}

void
send_id(id)
int id;
{
    sprintf(spbuf, "Pyou %d", id);
    send_packet(id, spbuf);
}

void
download_to_player(player)
Player *player;
{
    int i, rslt;
    Module *module;

    tmprid = player->rid;
    send_packet(tmprid, "GameModule");
    module = create_game_module("*download*");
    /* Fill in some default slots. */
    module->startlineno = 1;
    module->endlineno = 1;
    module->compress_tables = TRUE;
    module->compress_layers = TRUE;
    module->def_all = TRUE; /* for now */
    if (mainmodule != NULL) {
	if (!empty_string(mainmodule->origmodulename)) {
	    module->origmodulename = mainmodule->origmodulename;
	} else if (!empty_string(mainmodule->name)) {
	    module->origmodulename = mainmodule->name;
	}
    }
    downloading = TRUE;
    rslt = write_game_module(module);
    downloading = FALSE;
    send_packet(tmprid, "\neludoMemaG\n");
    /* Send it the current list of side/player assignments. */
    for (i = 0; i < numsides; ++i) {
	send_assignment(tmprid, assignments[i].side, assignments[i].player);
    }
    send_id(player->id);
    /* Make it run everything up to the player setup phase. */
    send_randstate(tmprid);
    send_packet(tmprid, "Launch");
    /* If errors, should set player rid back to zero */
}

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

void
send_game_checksum(id)
int id;
{
    sprintf(spbuf, "Z%d %d", my_rid, game_checksum());
    send_packet(id, spbuf); 
}

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

time_t last_checksum_time;

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

    if (my_rid == master_rid) {
	if (numremotes > 0) {
	    oldsernum = g_run_serial_number();
	    oldstate = randstate;
	    time(&now);
	    if (idifftime(now, last_checksum_time) >= 2) {
		broadcast_game_checksum();
		last_checksum_time = now;
	    }
	}
	numdone = run_game(maxactions);
	if (numremotes > 0 && numdone > 0) {
	    sprintf(spbuf, "X%d %d %d", maxactions, oldsernum, oldstate);
	    broadcast_packet(spbuf);
	    broadcast_game_checksum();
	}
    }
    return numdone;
}

void
net_request_additional_side(playerspec)
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, sidemask, str)
Side *side;
SideMask sidemask;
char *str;
{
    if (my_rid == master_rid) {
	send_message(side, sidemask, str);
    }
    if (numremotes > 0) {
	broadcast_message(side, sidemask, str);
    }
}

static void
broadcast_message(side, sidemask, str)
Side *side;
SideMask sidemask;
char *str;
{
    sprintf(spbuf, "M%d %d %s", side_number(side), sidemask, str);
    broadcast_packet(spbuf);
}

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

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

/* Side property tweaking. */

void
net_set_side_name(side, side2, newname)
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, side2, newname)
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, side2, newname)
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, side2, newname)
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, side2, newname)
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, side2, newname)
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, side2, newname)
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, side2, newname)
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 *side;
{
    if (my_rid == master_rid) {
	finish_turn(side);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "fin", 0);
    }
}

void
net_set_trust(side, side2, val)
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_mutual_trust(side, side2, val)
Side *side, *side2;
int val;
{
    if (my_rid == master_rid) {
	set_mutual_trust(side, side2, val);
    }
    if (numremotes > 0)  {
	broadcast_side_property_2(side, "mutualtrust", side_number(side2), val);
    }
}

void
net_set_controlled_by(side, side2, val)
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, value)
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_willing_to_save(side, flag)
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, flag)
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, 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, typename)
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, spec)
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);
    }
}

#ifdef DESIGNERS

void
net_become_designer(side)
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 *side;
{
    if (my_rid == master_rid) {
	become_nondesigner(side);
    }
    if (numremotes > 0)  {
	broadcast_side_property(side, "designer", FALSE);
    }
}

void
net_paint_view(side, x, y, r, tview, uview)
Side *side;
int x, y, r, tview, 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, prop, val)
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, prop, val, val2)
Side *side;
char *prop;
int val, val2;
{
    sprintf(spbuf, "S%d %s %d %d", side_number(side), prop, val, val2);
    broadcast_packet(spbuf);
}

static void
broadcast_side_str_property(side, prop, val)
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, unit, newname)
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, unit, 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, unit, flag, recurse)
Side *side;
Unit *unit;
int flag, 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, unit, flag, recurse)
Side *side;
Unit *unit;
int flag, 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, unit, 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_waiting_for_transport(side, unit, flag)
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_reserve_unit(side, unit)
Side *side;
Unit *unit;
{
    if (my_rid == master_rid) {
	reserve_unit(side, unit);
    }
    if (numremotes > 0) {
	broadcast_unit_property(side, unit, "resunit", 0);
    }
}

void
net_wake_unit(side, unit, wakeocc)
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, x, y, n, occs)
Side *side;
int x, y, n, 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, unit, flag, recurse)
Side *side;
Unit *unit;
int flag, 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, unit)
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, unit, passive_only)
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, 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, leader, x, y, dist, flex)
Unit *unit, *leader;
int x, y, dist, 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, flag)
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, u, s, x, y)
Side *side;
int u, s, x, 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, x, y, other)
Unit *unit, *other;
int x, y;
{
    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, 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 *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(typename, name)
char *typename, *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, typename)
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, 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 *feature;
{
    if (my_rid == master_rid) {
	destroy_feature(feature);
    }
    if (numremotes > 0) {
	run_error("net");
    }
}

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


static void
broadcast_unit_property(side, unit, prop, val)
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, unit, prop, val, val2)
Side *side;
Unit *unit;
char *prop;
int val, 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, unit, prop, val, val2, val3, val4, val5)
Side *side;
Unit *unit;
char *prop;
int val, val2, val3, val4, 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, unit, prop, val)
Side *side;
Unit *unit;
char *prop, *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 *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;
    }
}

/* 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_collect_task(unit, m, x, y)
Unit *unit;
int m, x, 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, x, y, dist)
Unit *unit;
int x, y, 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, x, y, dist)
Unit *unit;
int x, y, 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, dir, n)
Unit *unit;
int dir, 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, u2, run)
Unit *unit;
int u2, run;
{
    if (my_rid == master_rid) {
	set_build_task(unit, u2, run);
    }
    if (numremotes > 0) {
	tmptask = create_build_task(unit, u2, run);
	broadcast_add_task(unit, CLEAR_AGENDA, tmptask);
	free_task(tmptask);
    }
}

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

void
net_push_research_task(unit, u2, n)
Unit *unit;
int u2, n;
{
    if (my_rid == master_rid) {
	push_research_task(unit, u2, n);
    }
    if (numremotes > 0) {
	tmptask = create_research_task(unit, u2, n);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_push_hit_unit_task(unit, x, y, u, s)
Unit *unit;
int x, y, u, 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, x, y, u, s)
Unit *unit;
int x, y, u, 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 *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, m)
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, transp)
Unit *unit, *transp;
{
    if (my_rid == master_rid) {
	push_occupy_task(unit, transp);
    }
    if (numremotes > 0) {
	tmptask = create_occupy_task(unit, transp);
	broadcast_add_task(unit, 0, tmptask);
	free_task(tmptask);
    }
}

void
net_push_pickup_task(unit, occ)
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, m, n)
Unit *unit;
int m, 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, n)
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);
    }
}

static void
broadcast_add_task(unit, pos, 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, x, y, r, t)
Side *side;
int x, y, r, 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);
    }
}

void
net_paint_border(side, x, y, dir, t, mode)
Side *side;
int x, y, dir, t, 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);
    }
}

void
net_paint_connection(side, x, y, dir, t, mode)
Side *side;
int x, y, dir, t, 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);
    }
}

void
net_paint_coating(side, x, y, r, t, depth)
Side *side;
int x, y, r, t, 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);
    }
}

void
net_paint_people(side, x, y, r, s)
Side *side;
int x, y, r, 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);
    }
}

void
net_paint_control(side, x, y, r, s)
Side *side;
int x, y, r, 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);
    }
}

void
net_paint_feature(side, x, y, r, f)
Side *side;
int x, y, r, 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);
    }
}

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

void
net_paint_temperature(side, x, y, r, temp)
Side *side;
int x, y, r, 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);
    }
}

void
net_paint_material(side, x, y, r, m, amt)
Side *side;
int x, y, r, m, 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);
    }
}

void
net_paint_clouds(side, x, y, r, cloudtype, bot, hgt)
Side *side;
int x, y, r, cloudtype, bot, 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);
	broadcast_layer_change("cloud-bottoms", side, x, y, r, bot, 0);
	broadcast_layer_change("cloud-heights", side, x, y, r, hgt, 0);
    }
}

void
net_paint_winds(side, x, y, r, dir, force)
Side *side;
int x, y, r, dir, 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);
    }
}

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

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

#endif /* DESIGNERS */

void
send_quit()
{
    broadcast_packet("Q");
}

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

static void
broadcast_packet(buf)
char *buf;
{
    int rid;
    extern int in_run_game;
 
    if (in_run_game) {
	run_warning("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 an acknowledgement. */

int
send_packet(id, inbuf)
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;
    }
    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)
	  run_warning("Timed out waiting for ack");
	++numtimeouts;
	if (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(id, inbuf)
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()
{
    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(id, inbuf)
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()
{
    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 int nothing_count;

static int nothing_timeout;

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

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

void
receive_data(timeout)
int timeout;
{
    char buf[BUFSIZE], *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 {
		if (nothing_count >= 1000 || 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;
	    }
	}
    }
}

void
receive_packet(id, buf)
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();
	    typesdefined = TRUE;
	    make_trial_assignments();
	} else {
	    /* Make sure we have space with which to concatenate. */
	    if (strlen(downloadbuf) + strlen(buf) >= 100000) {
		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 'G':
	    downloading = TRUE;
	    if (downloadbuf == NULL)
	      downloadbuf = xmalloc(100000);
	    downloadbuf[0] = '\0';
	    should_rebroadcast = FALSE;
	    break;
	  case 'L':
	    launch_game();
	    /* This is only issued by the master, no need to rebroadcast. */
	    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 'j':	
	    add_remote_player(buf + 1);
	    should_rebroadcast = FALSE;
	    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;
	}
	if (my_rid == master_rid && should_rebroadcast)
	  broadcast_packet(buf);
    }
}

static void
receive_net_message(str)
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(str)
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(str)
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(str)
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(str)
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 if (strncmp(str, "you ", 4) == 0) {
	str += 4;
	val = strtol(str, &nstr, 10);
	set_you_player(val);
	/* This only comes from master, no need to rebroadcast. */
    } else {
	run_warning("Unknown P packet \"%s\", ignoring", str);
    }
}

static void
receive_quit(str)
char *str;
{
    /* (should decide what kind of quit is happening) */
    run_warning("Q packet received, should quit now");
}

static void
receive_side_prop(str)
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, "af ", 3) == 0) {
	str += 3;
	val = strtol(str, &nstr, 10);
	set_autofinish(side, val);
    } else if (strncmp(str, "ai ", 3) == 0) {
	str += 3;
	set_side_ai(side, str);
    } 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, "fin ", 4) == 0) {
	finish_turn(side);
    } else if (strncmp(str, "mutualtrust ", 12) == 0) {
	str += 12;
	sn = strtol(str, &nstr, 10);
	str = nstr;
	val = strtol(str, &nstr, 10);
	set_mutual_trust(side, side_n(sn), val);
    } else if (strncmp(str, "resign ", 7) == 0) {
	str += 7;
	val = strtol(str, &nstr, 10);
	resign_game(side, side_n(val));
    } 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, "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 if (strncmp(str, "adjective ", 10) == 0) {
	set_side_adjective(side, side, str + 10);
    } else if (strncmp(str, "colorscheme ", 12) == 0) {
	set_side_colorscheme(side, side, str + 12);
    } else if (strncmp(str, "emblemname ", 11) == 0) {
	set_side_emblemname(side, side, str + 11);
    } 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, "shortname ", 10) == 0) {
	set_side_shortname(side, side, str + 10);
    } else {
	run_warning("Unknown S packet \"%s\", ignoring", str);
    }
}

static void
receive_unit_prop(str)
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, "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, "resunit ", 8) == 0) {
	reserve_unit(side, unit);
    } 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(str)
char *str;
{
    int id, x, y, a1, a2, a3;
    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);
    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);
    } 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(str)
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;
    run_game(maxactions);
}

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

    other_rid = strtol(str, &reststr, 10);
    other_csum = strtol(reststr, &nreststr, 10);
    our_csum = game_checksum();
    if (our_csum != other_csum) {
#if 0 /* should use a distinct message */
	/* If we're about to lose, make sure everybody else knows it. */
	if (my_rid != master_rid)
	  send_game_checksum(master_rid);
#endif
	run_warning("Game is out of sync! (received %d from %d, computed %d)",
		    other_csum, other_rid, our_csum);
    }
}

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

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

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

    csum = 0;
    for_all_sides_plus_indep(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;
}

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

static int
fromhex(a)
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(nib)
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(hosting)
int hosting;
{
    /* Just use wired-in filenames, since only for debugging. */
    if (hosting) {
	inport_filename = "server.in";
	outport_filename = "server.out";
    } else {
	inport_filename = "client.in";
	outport_filename = "client.out";
    }
    inport = fopen(inport_filename, "r");
    outport = fopen(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 (hosting) {
	my_rid = 1;
    } else {
	my_rid = 2;
    }
    numremotes = 2;
}

void
low_file_send(rid, buf)
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(idp, buf, maxchars, timeout)
int *idp;
char *buf;
int maxchars, 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()
{
    fclose(inport);
    fclose(outport);
}

