diff --git a/xtrn/atlantis/atlantis1.c b/xtrn/atlantis/atlantis1.c
new file mode 100644
index 0000000000000000000000000000000000000000..48e2f678fa627cf72f3bd9a728cc1c64a410a601
--- /dev/null
+++ b/xtrn/atlantis/atlantis1.c
@@ -0,0 +1,6988 @@
+
+/*	Atlantis v1.0  13 September 1993
+	Copyright 1993 by Russell Wallace
+
+	This program may be freely used, modified and distributed.  It may not be
+	sold or used commercially without prior written permission from the author.
+*/
+
+#include	<sys/stat.h>
+#include	<stdio.h>
+#include	<stdlib.h>
+#include	<string.h>
+#include	<math.h>
+#include	<ctype.h>
+#include	<assert.h>
+#include	<time.h>
+#include	<stddef.h>
+#include	<limits.h>
+#include	<glob.h>
+
+#define	NAMESIZE					81
+#define	DISPLAYSIZE				161
+#define	addlist2(l,p)			(*l = p, l = &p->next)
+#define	min(a,b)					((a) < (b) ? (a) : (b))
+#define	max(a,b)					((a) > (b) ? (a) : (b))
+#define	xisdigit(c)				((c) == '-' || ((c) >= '0' && (c) <= '9'))
+#define	addptr(p,i)				((void *)(((char *)p) + i))
+
+#define	ORDERGAP					4
+#define	BLOCKSIZE				7
+#define	BLOCKBORDER				1
+#define	MAINTENANCE				10
+#define	STARTMONEY				5000
+#define	RECRUITCOST				50
+#define	RECRUITFRACTION		4
+#define	ENTERTAININCOME		20
+#define	ENTERTAINFRACTION		20
+#define	TAXINCOME				200
+#define	COMBATEXP				10
+#define	PRODUCEEXP				10
+#define	TEACHNUMBER				10
+#define	STUDYCOST				200
+#define	POPGROWTH				5
+#define	PEASANTMOVE				5
+
+enum
+{
+	T_OCEAN,
+	T_PLAIN,
+	T_MOUNTAIN,
+	T_FOREST,
+	T_SWAMP,
+};
+
+enum
+{
+	SH_LONGBOAT,
+	SH_CLIPPER,
+	SH_GALLEON,
+};
+
+enum
+{
+	SK_MINING,
+	SK_LUMBERJACK,
+	SK_QUARRYING,
+	SK_HORSE_TRAINING,
+	SK_WEAPONSMITH,
+	SK_ARMORER,
+	SK_BUILDING,
+	SK_SHIPBUILDING,
+	SK_ENTERTAINMENT,
+	SK_STEALTH,
+	SK_OBSERVATION,
+	SK_TACTICS,
+	SK_RIDING,
+	SK_SWORD,
+	SK_CROSSBOW,
+	SK_LONGBOW,
+	SK_MAGIC,
+	MAXSKILLS
+};
+
+enum
+{
+	I_IRON,
+	I_WOOD,
+	I_STONE,
+	I_HORSE,
+	I_SWORD,
+	I_CROSSBOW,
+	I_LONGBOW,
+	I_CHAIN_MAIL,
+	I_PLATE_ARMOR,
+	I_AMULET_OF_DARKNESS,
+	I_AMULET_OF_DEATH,
+	I_AMULET_OF_HEALING,
+	I_AMULET_OF_TRUE_SEEING,
+	I_CLOAK_OF_INVULNERABILITY,
+	I_RING_OF_INVISIBILITY,
+	I_RING_OF_POWER,
+	I_RUNESWORD,
+	I_SHIELDSTONE,
+	I_STAFF_OF_FIRE,
+	I_STAFF_OF_LIGHTNING,
+	I_WAND_OF_TELEPORTATION,
+	MAXITEMS
+};
+
+enum
+{
+	SP_BLACK_WIND,
+	SP_CAUSE_FEAR,
+	SP_CONTAMINATE_WATER,
+	SP_DAZZLING_LIGHT,
+	SP_FIREBALL,
+	SP_HAND_OF_DEATH,
+	SP_HEAL,
+	SP_INSPIRE_COURAGE,
+	SP_LIGHTNING_BOLT,
+	SP_MAKE_AMULET_OF_DARKNESS,
+	SP_MAKE_AMULET_OF_DEATH,
+	SP_MAKE_AMULET_OF_HEALING,
+	SP_MAKE_AMULET_OF_TRUE_SEEING,
+	SP_MAKE_CLOAK_OF_INVULNERABILITY,
+	SP_MAKE_RING_OF_INVISIBILITY,
+	SP_MAKE_RING_OF_POWER,
+	SP_MAKE_RUNESWORD,
+	SP_MAKE_SHIELDSTONE,
+	SP_MAKE_STAFF_OF_FIRE,
+	SP_MAKE_STAFF_OF_LIGHTNING,
+	SP_MAKE_WAND_OF_TELEPORTATION,
+	SP_SHIELD,
+	SP_SUNFIRE,
+	SP_TELEPORT,
+	MAXSPELLS
+};
+
+enum
+{
+	K_ACCEPT,
+	K_ADDRESS,
+	K_ADMIT,
+	K_ALLY,
+	K_ATTACK,
+	K_BEHIND,
+	K_BOARD,
+	K_BUILD,
+	K_BUILDING,
+	K_CAST,
+	K_CLIPPER,
+	K_COMBAT,
+	K_DEMOLISH,
+	K_DISPLAY,
+	K_EAST,
+	K_END,
+	K_ENTER,
+	K_ENTERTAIN,
+	K_FACTION,
+	K_FIND,
+	K_FORM,
+	K_GALLEON,
+	K_GIVE,
+	K_GUARD,
+	K_LEAVE,
+	K_LONGBOAT,
+	K_MOVE,
+	K_NAME,
+	K_NORTH,
+	K_PAY,
+	K_PRODUCE,
+	K_PROMOTE,
+	K_QUIT,
+	K_RECRUIT,
+	K_RESEARCH,
+	K_RESHOW,
+	K_SAIL,
+	K_SHIP,
+	K_SINK,
+	K_SOUTH,
+	K_STUDY,
+	K_TAX,
+	K_TEACH,
+	K_TRANSFER,
+	K_UNIT,
+	K_WEST,
+	K_WORK,
+	MAXKEYWORDS
+};
+
+typedef struct list
+{
+	struct list *next;
+} list;
+
+typedef struct strlist
+{
+	struct strlist *next;
+	char s[1];
+} strlist;
+
+struct unit;
+typedef struct unit unit;
+
+typedef struct building
+{
+	struct building *next;
+	int no;
+	char name[NAMESIZE];
+	char display[DISPLAYSIZE];
+	int size;
+	int sizeleft;
+} building;
+
+typedef struct ship
+{
+	struct ship *next;
+	int no;
+	char name[NAMESIZE];
+	char display[DISPLAYSIZE];
+	char type;
+	int left;
+} ship;
+
+typedef struct region
+{
+	struct region *next;
+	int x,y;
+	char name[NAMESIZE];
+	struct region *connect[4];
+	char terrain;
+	int peasants;
+	int money;
+	building *buildings;
+	ship *ships;
+	unit *units;
+	int immigrants;
+} region;
+
+struct faction;
+
+typedef struct rfaction
+{
+	struct rfaction *next;
+	struct faction *faction;
+	int factionno;
+} rfaction;
+
+typedef struct faction
+{
+	struct faction *next;
+	int no;
+	char name[NAMESIZE];
+	char addr[NAMESIZE];
+	int lastorders;
+	char seendata[MAXSPELLS];
+	char showdata[MAXSPELLS];
+	rfaction *accept;
+	rfaction *admit;
+	rfaction *allies;
+	strlist *mistakes;
+	strlist *messages;
+	strlist *battles;
+	strlist *events;
+	char alive;
+	char attacking;
+	char seesbattle;
+	char dh;
+	int nunits;
+	int number;
+	int money;
+} faction;
+
+struct unit
+{
+	struct unit *next;
+	int no;
+	char name[NAMESIZE];
+	char display[DISPLAYSIZE];
+	int number;
+	int money;
+	faction *faction;
+	building *building;
+	ship *ship;
+	char owner;
+	char behind;
+	char guard;
+	char thisorder[NAMESIZE];
+	char lastorder[NAMESIZE];
+	char combatspell;
+	int skills[MAXSKILLS];
+	int items[MAXITEMS];
+	char spells[MAXSPELLS];
+	strlist *orders;
+	int alias;
+	int dead;
+	int learning;
+	int n;
+	int *litems;
+	char side;
+	char isnew;
+};
+
+typedef struct order
+{
+	struct order *next;
+	unit *unit;
+	int qty;
+} order;
+
+typedef struct troop
+{
+	struct troop *next;
+	unit *unit;
+	int lmoney;
+	char status;
+	char side;
+	char attacked;
+	char weapon;
+	char missile;
+	char skill;
+	char armor;
+	char behind;
+	char inside;
+	char reload;
+	char canheal;
+	char runesword;
+	char invulnerable;
+	char power;
+	char shieldstone;
+	char demoralized;
+	char dazzled;
+} troop;
+
+unsigned rndno;
+char buf[10240];
+char buf2[256];
+FILE *F;
+
+int turn;
+region *regions;
+faction *factions;
+
+char *keywords[] =
+{
+	"accept",
+	"address",
+	"admit",
+	"ally",
+	"attack",
+	"behind",
+	"board",
+	"build",
+	"building",
+	"cast",
+	"clipper",
+	"combat",
+	"demolish",
+	"display",
+	"east",
+	"end",
+	"enter",
+	"entertain",
+	"faction",
+	"find",
+	"form",
+	"galleon",
+	"give",
+	"guard",
+	"leave",
+	"longboat",
+	"move",
+	"name",
+	"north",
+	"pay",
+	"produce",
+	"promote",
+	"quit",
+	"recruit",
+	"research",
+	"reshow",
+	"sail",
+	"ship",
+	"sink",
+	"south",
+	"study",
+	"tax",
+	"teach",
+	"transfer",
+	"unit",
+	"west",
+	"work",
+};
+
+char *terrainnames[] =
+{
+	"ocean",
+	"plain",
+	"mountain",
+	"forest",
+	"swamp",
+};
+
+char *regionnames[] =
+{
+	"Aberaeron",
+	"Aberdaron",
+	"Aberdovey",
+	"Abernethy",
+	"Abersoch",
+	"Abrantes",
+	"Adrano",
+	"AeBrey",
+	"Aghleam",
+	"Akbou",
+	"Aldan",
+	"Alfaro",
+	"Alghero",
+	"Almeria",
+	"Altnaharra",
+	"Ancroft",
+	"Anshun",
+	"Anstruther",
+	"Antor",
+	"Arbroath",
+	"Arcila",
+	"Ardfert",
+	"Ardvale",
+	"Arezzo",
+	"Ariano",
+	"Arlon",
+	"Avanos",
+	"Aveiro",
+	"Badalona",
+	"Baechahoela",
+	"Ballindine",
+	"Balta",
+	"Banlar",
+	"Barika",
+	"Bastak",
+	"Bayonne",
+	"Bejaia",
+	"Benlech",
+	"Beragh",
+	"Bergland",
+	"Berneray",
+	"Berriedale",
+	"Binhai",
+	"Birde",
+	"Bocholt",
+	"Bogmadie",
+	"Braga",
+	"Brechlin",
+	"Brodick",
+	"Burscough",
+	"Calpio",
+	"Canna",
+	"Capperwe",
+	"Caprera",
+	"Carahue",
+	"Carbost",
+	"Carnforth",
+	"Carrigaline",
+	"Caserta",
+	"Catrianchi",
+	"Clatter",
+	"Coilaco",
+	"Corinth",
+	"Corofin",
+	"Corran",
+	"Corwen",
+	"Crail",
+	"Cremona",
+	"Crieff",
+	"Cromarty",
+	"Cumbraes",
+	"Daingean",
+	"Darm",
+	"Decca",
+	"Derron",
+	"Derwent",
+	"Deveron",
+	"Dezhou",
+	"Doedbygd",
+	"Doramed",
+	"Dornoch",
+	"Drammes",
+	"Dremmer",
+	"Drense",
+	"Drimnin",
+	"Drumcollogher",
+	"Drummore",
+	"Dryck",
+	"Drymen",
+	"Dunbeath",
+	"Duncansby",
+	"Dunfanaghy",
+	"Dunkeld",
+	"Dunmanus",
+	"Dunster",
+	"Durness",
+	"Duucshire",
+	"Elgomaar",
+	"Ellesmere",
+	"Ellon",
+	"Enfar",
+	"Erisort",
+	"Eskerfan",
+	"Ettrick",
+	"Fanders",
+	"Farafra",
+	"Ferbane",
+	"Fetlar",
+	"Flock",
+	"Florina",
+	"Formby",
+	"Frainberg",
+	"Galloway",
+	"Ganzhou",
+	"Geal Charn",
+	"Gerr",
+	"Gifford",
+	"Girvan",
+	"Glenagallagh",
+	"Glenanane",
+	"Glin",
+	"Glomera",
+	"Glormandia",
+	"Gluggby",
+	"Gnackstein",
+	"Gnoelhaala",
+	"Golconda",
+	"Gourock",
+	"Graevbygd",
+	"Grandola",
+	"Gresberg",
+	"Gresir",
+	"Greverre",
+	"Griminish",
+	"Grisbygd",
+	"Groddland",
+	"Grue",
+	"Gurkacre",
+	"Haikou",
+	"Halkirk",
+	"Handan",
+	"Hasmerr",
+	"Helmsdale",
+	"Helmsley",
+	"Helsicke",
+	"Helvete",
+	"Hoersalsveg",
+	"Hullevala",
+	"Ickellund",
+	"Inber",
+	"Inverie",
+	"Jaca",
+	"Jahrom",
+	"Jeormel",
+	"Jervbygd",
+	"Jining",
+	"Jotel",
+	"Kaddervar",
+	"Karand",
+	"Karothea",
+	"Kashmar",
+	"Keswick",
+	"Kielder",
+	"Killorglin",
+	"Kinbrace",
+	"Kintore",
+	"Kirriemuir",
+	"Klen",
+	"Knesekt",
+	"Kobbe",
+	"Komarken",
+	"Kovel",
+	"Krod",
+	"Kursk",
+	"Lagos",
+	"Lamlash",
+	"Langholm",
+	"Larache",
+	"Larkanth",
+	"Larmet",
+	"Lautaro",
+	"Leighlin",
+	"Lervir",
+	"Leven",
+	"Licata",
+	"Limavady",
+	"Lingen",
+	"Lintan",
+	"Liscannor",
+	"Locarno",
+	"Lochalsh",
+	"Lochcarron",
+	"Lochinver",
+	"Lochmaben",
+	"Lom",
+	"Lorthalm",
+	"Louer",
+	"Lurkabo",
+	"Luthiir",
+	"Lybster",
+	"Lynton",
+	"Mallaig",
+	"Mataro",
+	"Melfi",
+	"Melvaig",
+	"Menter",
+	"Methven",
+	"Moffat",
+	"Monamolin",
+	"Monzon",
+	"Morella",
+	"Morgel",
+	"Mortenford",
+	"Mullaghcarn",
+	"Mulle",
+	"Murom",
+	"Nairn",
+	"Navenby",
+	"Nephin Beg",
+	"Niskby",
+	"Nolle",
+	"Nork",
+	"Olenek",
+	"Oloron",
+	"Oranmore",
+	"Ormgryte",
+	"Orrebygd",
+	"Palmi",
+	"Panyu",
+	"Partry",
+	"Pauer",
+	"Penhalolen",
+	"Perkel",
+	"Perski",
+	"Planken",
+	"Plattland",
+	"Pleagne",
+	"Pogelveir",
+	"Porthcawl",
+	"Portimao",
+	"Potenza",
+	"Praestbygd",
+	"Preetsome",
+	"Presu",
+	"Prettstern",
+	"Rantlu",
+	"Rappbygd",
+	"Rath Luire",
+	"Rethel",
+	"Riggenthorpe",
+	"Rochfort",
+	"Roddendor",
+	"Roin",
+	"Roptille",
+	"Roter",
+	"Rueve",
+	"Sagunto",
+	"Saklebille",
+	"Salen",
+	"Sandwick",
+	"Sarab",
+	"Sarkanvale",
+	"Scandamia",
+	"Scarinish",
+	"Scourie",
+	"Serov",
+	"Shanyin",
+	"Siegen",
+	"Sinan",
+	"Sines",
+	"Skim",
+	"Skokholm",
+	"Skomer",
+	"Skottskog",
+	"Sledmere",
+	"Sorisdale",
+	"Spakker",
+	"Stackforth",
+	"Staklesse",
+	"Stinchar",
+	"Stoer",
+	"Strichen",
+	"Stroma",
+	"Stugslett",
+	"Suide",
+	"Tabuk",
+	"Tarraspan",
+	"Tetuan",
+	"Thurso",
+	"Tiemcen",
+	"Tiksi",
+	"Tolsta",
+	"Toppola",
+	"Torridon",
+	"Trapani",
+	"Tromeforth",
+	"Tudela",
+	"Turia",
+	"Uxelberg",
+	"Vaila",
+	"Valga",
+	"Verguin",
+	"Vernlund",
+	"Victoria",
+	"Waimer",
+	"Wett",
+	"Xontormia",
+	"Yakleks",
+	"Yuci",
+	"Zaalsehuur",
+	"Zamora",
+	"Zapulla",
+};
+
+char foodproductivity[] =
+{
+	0,
+	15,
+	12,
+	12,
+	12,
+};
+
+int maxfoodoutput[] =
+{
+	0,
+	100000,
+	20000,
+	20000,
+	10000,
+};
+
+char productivity[][4] =
+{
+	0,0,0,0,
+	0,0,0,1,
+	1,0,1,0,
+	0,1,0,0,
+	0,1,0,0,
+};
+
+int maxoutput[][4] =
+{
+	  0,  0,  0,  0,
+	  0,  0,  0,200,
+	200,  0,200,  0,
+	  0,200,  0,  0,
+	  0,100,  0,  0,
+};
+
+char *shiptypenames[] =
+{
+	"longboat",
+	"clipper",
+	"galleon",
+};
+
+int shipcapacity[] =
+{
+	200,
+	800,
+	1800,
+};
+
+int shipcost[] =
+{
+	100,
+	200,
+	300,
+};
+
+char *skillnames[] =
+{
+	"mining",
+	"lumberjack",
+	"quarrying",
+	"horse training",
+	"weaponsmith",
+	"armorer",
+	"building",
+	"shipbuilding",
+	"entertainment",
+	"stealth",
+	"observation",
+	"tactics",
+	"riding",
+	"sword",
+	"crossbow",
+	"longbow",
+	"magic",
+};
+
+char *itemnames[][MAXITEMS] =
+{
+	"iron",
+	"wood",
+	"stone",
+	"horse",
+	"sword",
+	"crossbow",
+	"longbow",
+	"chain mail",
+	"plate armor",
+	"Amulet of Darkness",
+	"Amulet of Death",
+	"Amulet of Healing",
+	"Amulet of True Seeing",
+	"Cloak of Invulnerability",
+	"Ring of Invisibility",
+	"Ring of Power",
+	"Runesword",
+	"Shieldstone",
+	"Staff of Fire",
+	"Staff of Lightning",
+	"Wand of Teleportation",
+
+	"iron",
+	"wood",
+	"stone",
+	"horses",
+	"swords",
+	"crossbows",
+	"longbows",
+	"chain mail",
+	"plate armor",
+	"Amulets of Darkness",
+	"Amulets of Death",
+	"Amulets of Healing",
+	"Amulets of True Seeing",
+	"Cloaks of Invulnerability",
+	"Rings of Invisibility",
+	"Rings of Power",
+	"Runeswords",
+	"Shieldstones",
+	"Staffs of Fire",
+	"Staffs of Lightning",
+	"Wands of Teleportation",
+};
+
+char itemskill[] =
+{
+	SK_MINING,
+	SK_LUMBERJACK,
+	SK_QUARRYING,
+	SK_HORSE_TRAINING,
+	SK_WEAPONSMITH,
+	SK_WEAPONSMITH,
+	SK_WEAPONSMITH,
+	SK_ARMORER,
+	SK_ARMORER,
+};
+
+char rawmaterial[] =
+{
+	0,
+	0,
+	0,
+	0,
+	I_IRON,
+	I_WOOD,
+	I_WOOD,
+	I_IRON,
+	I_IRON,
+};
+
+char *spellnames[] =
+{
+	"Black Wind",
+	"Cause Fear",
+	"Contaminate Water",
+	"Dazzling Light",
+	"Fireball",
+	"Hand of Death",
+	"Heal",
+	"Inspire Courage",
+	"Lightning Bolt",
+	"Make Amulet of Darkness",
+	"Make Amulet of Death",
+	"Make Amulet of Healing",
+	"Make Amulet of True Seeing",
+	"Make Cloak of Invulnerability",
+	"Make Ring of Invisibility",
+	"Make Ring of Power",
+	"Make Runesword",
+	"Make Shieldstone",
+	"Make Staff of Fire",
+	"Make Staff of Lightning",
+	"Make Wand of Teleportation",
+	"Shield",
+	"Sunfire",
+	"Teleport",
+};
+
+char spelllevel[] =
+{
+	4,
+	2,
+	1,
+	1,
+	2,
+	3,
+	2,
+	2,
+	1,
+	5,
+	4,
+	3,
+	3,
+	3,
+	3,
+	4,
+	3,
+	4,
+	3,
+	2,
+	4,
+	3,
+	5,
+	3,
+};
+
+char iscombatspell[] =
+{
+	1,
+	1,
+	0,
+	1,
+	1,
+	1,
+	0,
+	1,
+	1,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	1,
+	1,
+	0,
+};
+
+char *spelldata[] =
+{
+	"This spell creates a black whirlwind of energy which destroys all life, "
+	"leaving frozen corpses with faces twisted into expressions of horror. Cast "
+	"in battle, it kills from 2 to 1250 enemies.",
+
+	"This spell creates an aura of fear which causes enemy troops in battle to "
+	"panic. Each time it is cast, it demoralizes between 2 and 100 troops for "
+	"the duration of the battle. Demoralized troops are at a -1 to their "
+	"effective skill.",
+
+	"This ritual spell causes pestilence to contaminate the water supply of the "
+	"region in which it is cast. It causes from 2 to 50 peasants to die from "
+	"drinking the contaminated water. Any units which end the month in the "
+	"affected region will know about the deaths, however only units which have "
+	"Observation skill higher than the caster's Stealth skill will know who "
+	"was responsible. The spell costs $50 to cast.",
+
+	"This spell, cast in battle, creates a flash of light which dazzles from 2 "
+	"to 50 enemy troops. Dazzled troops are at a -1 to their effective skill "
+	"for their next attack.",
+
+	"This spell enables the caster to hurl balls of fire. Each time it is cast "
+	"in battle, it will incinerate between 2 and 50 enemy troops.",
+
+	"This spell disrupts the metabolism of living organisms, sending an "
+	"invisible wave of death across a battlefield. Each time it is cast, it "
+	"will kill between 2 and 250 enemy troops.",
+
+	"This spell enables the caster to attempt to heal the injured after a "
+	"battle. It is used automatically, and does not require use of either the "
+	"COMBAT or CAST command. If one's side wins a battle, a number of  "
+	"casualties on one's side, between 2 and 50, will be healed. (If this "
+	"results in all the casualties on the winning side being healed, the winner "
+	"is still eligible for combat experience.)",
+
+	"This spell boosts the morale of one's troops in battle. Each time it is "
+	"cast, it cancels the effect of the Cause Fear spell on a number of one's "
+	"own troops ranging from 2 to 100.",
+
+	"This spell enables the caster to throw bolts of lightning to strike down "
+	"enemies in battle. It kills from 2 to 10 enemies.",
+
+	"This spell allows one to create an Amulet of Darkness. This amulet allows "
+	"its possessor to cast the Black Wind spell in combat, without having to "
+	"know the spell; the only requirement is that the user must have the Magic "
+	"skill at 1 or higher. The Black Wind spell creates a black whirlwind of "
+	"energy which destroys all life. Cast in battle, it kills from 2 to 1250 "
+	"people. The amulet costs $1000 to make.",
+
+	"This spell allows one to create an Amulet of Death. This amulet allows its "
+	"possessor to cast the Hand of Death spell in combat, without having to "
+	"know the spell; the only requirement is that the user must have the Magic "
+	"skill at 1 or higher. The Hand of Death spell disrupts the metabolism of "
+	"living organisms, sending an invisible wave of death across a battlefield. "
+	"Each time it is cast, it will kill between 2 and 250 enemy troops. The "
+	"amulet costs $800 to make.",
+
+	"This spell allows one to create an Amulet of Healing. This amulet allows "
+	"its possessor to attempt to heal the injured after a battle. It is used "
+	"automatically, and does not require the use of either the COMBAT or CAST "
+	"command; the only requirement is that the user must have the Magic skill "
+	"at 1 or higher. If the user's side wins a battle, a number of casualties "
+	"on that side, between 2 and 50, will be healed. (If this results in all "
+	"the casualties on the winning side being healed, the winner is still "
+	"eligible for combat experience.) The amulet costs $600 to make.",
+
+	"This spell allows one to create an Amulet of True Seeing. This allows its "
+	"possessor to see units which are hidden by Rings of Invisibility. (It has "
+	"no effect against units which are hidden by the Stealth skill.) The amulet "
+	"costs $600 to make.",
+
+	"This spell allows one to create a Cloak of Invulnerability. This cloak "
+	"protects its wearer from injury in battle; any attack with a normal weapon "
+	"which hits the wearer has a 99.99% chance of being deflected. This benefit "
+	"is gained instead of, rather than as well as, the protection of any armor "
+	"worn; and the cloak confers no protection against magical attacks. The "
+	"cloak costs $600 to make.",
+
+	"This spell allows one to create a Ring of Invisibility. This ring renders "
+	"its wearer invisible to all units not in the same faction, regardless of "
+	"Observation skill. For a unit of many people to remain invisible, it must "
+	"possess a Ring of Invisibility for each person. The ring costs $600 to "
+	"make.",
+
+	"This spell allows one to create a Ring of Power. This ring doubles the "
+	"effectiveness of any spell the wearer casts in combat, or any magic item "
+	"the wearer uses in combat. The ring costs $800 to make.",
+
+	"This spell allows one to create a Runesword. This is a black sword with "
+	"magical runes etched along the blade. To use it, one must have both the "
+	"Sword and Magic skills at 1 or higher. It confers a bonus of 2 to the "
+	"user's Sword skill in battle, and also projects an aura of power that has "
+	"a 50% chance of cancelling any Fear spells cast by an enemy magician. The "
+	"sword costs $600 to make.",
+
+	"This spell allows one to create a Shieldstone. This is a small black "
+	"stone, engraved with magical runes, that creates an invisible shield of "
+	"energy that deflects hostile magic in battle. The stone is used "
+	"automatically, and does not require the use of either the COMBAT or CAST "
+	"commands; the only requirement is that the user must have the Magic skill "
+	"at 1 or higher. Each round of combat, it adds one layer to the shielding "
+	"around one's own side. When a hostile magician casts a spell, provided "
+	"there is at least one layer of shielding present, there is a 50% chance of "
+	"the spell being deflected. If the spell is deflected, nothing happens. If "
+	"it is not, then it has full effect, and one layer of shielding is removed. "
+	"The stone costs $800 to make.",
+
+	"This spell allows one to create a Staff of Fire. This staff allows its "
+	"possessor to cast the Fireball spell in combat, without having to know the "
+	"spell; the only requirement is that the user must have the Magic skill at "
+	"1 or higher. The Fireball spell enables the caster to hurl balls of fire. "
+	"Each time it is cast in battle, it will incinerate between 2 and 50 enemy "
+	"troops. The staff costs $600 to make.",
+
+	"This spell allows one to create a Staff of Lightning. This staff allows "
+	"its possessor to cast the Lightning Bolt spell in combat, without having "
+	"to know the spell; the only requirement is that the user must have the "
+	"Magic skill at 1 or higher. The Lightning Bolt spell enables the caster to "
+	"throw bolts of lightning to strike down enemies. It kills from 2 to 10 "
+	"enemies. The staff costs $400 to make.",
+
+	"This spell allows one to create a Wand of Teleportation. This wand allows "
+	"its possessor to cast the Teleport spell, without having to know the "
+	"spell; the only requirement is that the user must have the Magic skill at "
+	"1 or higher. The Teleport spell allows the caster to move himself and "
+	"others across vast distances without traversing the intervening space. The "
+	"command to use it is CAST TELEPORT target-unit unit-no ... The target unit "
+	"is a unit in the region to which the teleport is to occur. If the target "
+	"unit is not in your faction, it must be in a faction which has issued an "
+	"ADMIT command for you that month. After the target unit comes a list of "
+	"one or more units to be teleported into the target unit's region (this may "
+	"optionally include the caster). Any units to be teleported, not in your "
+	"faction, must be in a faction which has issued an ACCEPT command for you "
+	"that month. The total weight of all units to be teleported (including "
+	"people, equipment and horses) must not exceed 10000. If the target unit is "
+	"in a building or on a ship, the teleported units will emerge there, "
+	"regardless of who owns the building or ship. The caster spends the month "
+	"preparing the spell and the teleport occurs at the end of the month, so "
+	"any other units to be transported can spend the month doing something "
+	"else. The wand costs $800 to make, and $50 to use.",
+
+	"This spell creates an invisible shield of energy that deflects hostile "
+	"magic. Each round that it is cast in battle, it adds one layer to the "
+	"shielding around one's own side. When a hostile magician casts a spell, "
+	"provided there is at least one layer of shielding present, there is a 50% "
+	"chance of the spell being deflected. If the spell is deflected, nothing "
+	"happens. If it is not, then it has full effect, and one layer of shielding "
+	"is removed.",
+
+	"This spell allows the caster to incinerate whole armies with fire brighter "
+	"than the sun. Each round it is cast, it kills from 2 to 6250 enemies.",
+
+	"This spell allows the caster to move himself and others across vast "
+	"distances without traversing the intervening space. The command to use it "
+	"is CAST TELEPORT target-unit unit-no ... The target unit is a unit in the "
+	"region to which the teleport is to occur. If the target unit is not in "
+	"your faction, it must be in a faction which has issued an ADMIT command "
+	"for you that month. After the target unit comes a list of one or more "
+	"units to be teleported into the target unit's region (this may optionally "
+	"include the caster). Any units to be teleported, not in your faction, must "
+	"be in a faction which has issued an ACCEPT command for you that month. The "
+	"total weight of all units to be teleported (including people, equipment "
+	"and horses) must not exceed 10000. If the target unit is in a building or "
+	"on a ship, the teleported units will emerge there, regardless of who owns "
+	"the building or ship. The caster spends the month preparing the spell and "
+	"the teleport occurs at the end of the month, so any other units to be "
+	"transported can spend the month doing something else. The spell costs $50 "
+	"to cast.",
+};
+
+atoip (char *s)
+{
+	int n;
+
+	n = atoi (s);
+
+	if (n < 0)
+		n = 0;
+
+	return n;
+}
+
+void nstrcpy (char *to,char *from,int n)
+{
+	n--;
+
+	do
+		if ((*to++ = *from++) == 0)
+			return;
+	while (--n);
+
+	*to = 0;
+}
+
+void scat (char *s)
+{
+	strcat (buf,s);
+}
+
+void icat (int n)
+{
+	char s[20];
+
+	sprintf (s,"%d",n);
+	scat (s);
+}
+
+void *cmalloc (int n)
+{
+	void *p;
+
+	if (n == 0)
+		n = 1;
+
+	p = malloc (n);
+
+	if (p == 0)
+	{
+		puts ("Out of memory.");
+		exit (1);
+	}
+
+	return p;
+}
+
+rnd (void)
+{
+	rndno = rndno * 1103515245 + 12345;
+	return (rndno >> 16) & 0x7FFF;
+}
+
+void cfopen (char *filename,char *mode)
+{
+	F = fopen (filename,mode);
+
+	if (F == 0)
+	{
+		printf ("Can't open file %s in mode %s.\n",filename,mode);
+		exit (1);
+	}
+}
+
+void getbuf (void)
+{
+	int i;
+	int c;
+
+	i = 0;
+
+	for (;;)
+	{
+		c = fgetc (F);
+
+		if (c == EOF)
+		{
+			buf[0] = EOF;
+			return;
+		}
+
+		if (c == '\n')
+		{
+			buf[i] = 0;
+			return;
+		}
+
+		if (i == sizeof buf - 1)
+		{
+			buf[i] = 0;
+			while (c != EOF && c != '\n')
+				c = fgetc (F);
+			if (c == EOF)
+				buf[0] = EOF;
+			return;
+		}
+
+		buf[i++] = c;
+	}
+}
+
+void addlist (void *l1,void *p1)
+{
+	list **l;
+	list *p,*q;
+
+	l = l1;
+	p = p1;
+
+	p->next = 0;
+
+	if (*l)
+	{
+		for (q = *l; q->next; q = q->next)
+			;
+		q->next = p;
+	}
+	else
+		*l = p;
+}
+
+void choplist (list **l,list *p)
+{
+	list *q;
+
+	if (*l == p)
+		*l = p->next;
+	else
+	{
+		for (q = *l; q->next != p; q = q->next)
+			assert (q);
+		q->next = p->next;
+	}
+}
+
+void translist (void *l1,void *l2,void *p)
+{
+	choplist (l1,p);
+	addlist (l2,p);
+}
+
+void removelist (void *l,void *p)
+{
+	choplist (l,p);
+	free (p);
+}
+
+void freelist (void *p1)
+{
+	list *p,*p2;
+
+	p = p1;
+
+	while (p)
+	{
+		p2 = p->next;
+		free (p);
+		p = p2;
+	}
+}
+
+listlen (void *l)
+{
+	int i;
+	list *p;
+
+	for (p = l, i = 0; p; p = p->next, i++)
+		;
+	return i;
+}
+
+effskill (unit *u,int i)
+{
+	int n,j,result;
+
+	n = 0;
+	if (u->number)
+		n = u->skills[i] / u->number;
+	j = 30;
+	result = 0;
+
+	while (j <= n)
+	{
+		n -= j;
+		j += 30;
+		result++;
+	}
+
+	return result;
+}
+
+ispresent (faction *f,region *r)
+{
+	unit *u;
+
+	for (u = r->units; u; u = u->next)
+		if (u->faction == f)
+			return 1;
+
+	return 0;
+}
+
+cansee (faction *f,region *r,unit *u)
+{
+	int n,o;
+	int cansee;
+	unit *u2;
+
+	if (u->faction == f)
+		return 2;
+
+	cansee = 0;
+	if (u->guard || u->building || u->ship)
+		cansee = 1;
+
+	n = effskill (u,SK_STEALTH);
+
+	for (u2 = r->units; u2; u2 = u2->next)
+	{
+		if (u2->faction != f)
+			continue;
+
+		if (u->items[I_RING_OF_INVISIBILITY] &&
+			 u->items[I_RING_OF_INVISIBILITY] == u->number &&
+			 !u2->items[I_AMULET_OF_TRUE_SEEING])
+			continue;
+
+		o = effskill (u2,SK_OBSERVATION);
+		if (o > n)
+			return 2;
+		if (o >= n)
+			cansee = 1;
+	}
+
+	return cansee;
+}
+
+char *igetstr (char *s1)
+{
+	int i;
+	static char *s;
+	static char buf[256];
+
+	if (s1)
+		s = s1;
+	while (*s == ' ')
+		s++;
+	i = 0;
+
+	while (*s && *s != ' ')
+	{
+		buf[i] = *s;
+		if (*s == '_')
+			buf[i] = ' ';
+		s++;
+		i++;
+	}
+
+	buf[i] = 0;
+	return buf;
+}
+
+char *getstr (void)
+{
+	return igetstr (0);
+}
+
+geti (void)
+{
+	return atoip (getstr ());
+}
+
+faction *findfaction (int n)
+{
+	faction *f;
+
+	for (f = factions; f; f = f->next)
+		if (f->no == n)
+			return f;
+
+	return 0;
+}
+
+faction *getfaction (void)
+{
+	return findfaction (atoi (getstr ()));
+}
+
+region *findregion (int x,int y)
+{
+	region *r;
+
+	for (r = regions; r; r = r->next)
+		if (r->x == x && r->y == y)
+			return r;
+
+	return 0;
+}
+
+building *findbuilding (int n)
+{
+	region *r;
+	building *b;
+
+	for (r = regions; r; r = r->next)
+		for (b = r->buildings; b; b = b->next)
+			if (b->no == n)
+				return b;
+
+	return 0;
+}
+
+building *getbuilding (region *r)
+{
+	int n;
+	building *b;
+
+	n = geti ();
+
+	for (b = r->buildings; b; b = b->next)
+		if (b->no == n)
+			return b;
+
+	return 0;
+}
+
+ship *findship (int n)
+{
+	region *r;
+	ship *sh;
+
+	for (r = regions; r; r = r->next)
+		for (sh = r->ships; sh; sh = sh->next)
+			if (sh->no == n)
+				return sh;
+
+	return 0;
+}
+
+ship *getship (region *r)
+{
+	int n;
+	ship *sh;
+
+	n = geti ();
+
+	for (sh = r->ships; sh; sh = sh->next)
+		if (sh->no == n)
+			return sh;
+
+	return 0;
+}
+
+unit *findunitg (int n)
+{
+	region *r;
+	unit *u;
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			if (u->no == n)
+				return u;
+
+	return 0;
+}
+
+unit *getnewunit (region *r,unit *u)
+{
+	int n;
+	unit *u2;
+
+	n = geti ();
+
+	if (n == 0)
+		return 0;
+
+	for (u2 = r->units; u2; u2 = u2->next)
+		if (u2->faction == u->faction && u2->alias == n)
+			return u2;
+
+	return 0;
+}
+
+unit *getunitg (region *r,unit *u)
+{
+	char *s;
+
+	s = getstr ();
+
+	if (!strcmp (s,"new"))
+		return getnewunit (r,u);
+
+	return findunitg (atoi (s));
+}
+
+int getunit0;
+int getunitpeasants;
+
+unit *getunit (region *r,unit *u)
+{
+	int n;
+	char *s;
+	unit *u2;
+
+	getunit0 = 0;
+	getunitpeasants = 0;
+
+	s = getstr ();
+
+	if (!strcmp (s,"new"))
+		return getnewunit (r,u);
+
+	if (r->terrain != T_OCEAN && !strcmp (s,"peasants"))
+	{
+		getunitpeasants = 1;
+		return 0;
+	}
+
+	n = atoi (s);
+
+	if (n == 0)
+	{
+		getunit0 = 1;
+		return 0;
+	}
+
+	for (u2 = r->units; u2; u2 = u2->next)
+		if (u2->no == n && cansee (u->faction,r,u2) && !u2->isnew)
+			return u2;
+
+	return 0;
+}
+
+int strpcmp (const void *s1,const void *s2)
+{
+	return strcmp (*(char **)s1,*(char **)s2);
+}
+
+findkeyword (char *s)
+{
+	char **sp;
+
+	if (!strcmp (s,"describe"))
+		return K_DISPLAY;
+	if (!strcmp (s,"n"))
+		return K_NORTH;
+	if (!strcmp (s,"s"))
+		return K_SOUTH;
+	if (!strcmp (s,"e"))
+		return K_EAST;
+	if (!strcmp (s,"w"))
+		return K_WEST;
+
+	sp = bsearch (&s,keywords,MAXKEYWORDS,sizeof s,strpcmp);
+	if (sp == 0)
+		return -1;
+	return sp - keywords;
+}
+
+igetkeyword (char *s)
+{
+	return findkeyword (igetstr (s));
+}
+
+getkeyword (void)
+{
+	return findkeyword (getstr ());
+}
+
+findstr (char **v,char *s,int n)
+{
+	int i;
+
+	for (i = 0; i != n; i++)
+		if (!strcmp (v[i],s))
+			return i;
+
+	return -1;
+}
+
+findskill (char *s)
+{
+	if (!strcmp (s,"horse"))
+		return SK_HORSE_TRAINING;
+	if (!strcmp (s,"entertain"))
+		return SK_ENTERTAINMENT;
+
+	return findstr (skillnames,s,MAXSKILLS);
+}
+
+getskill (void)
+{
+	return findskill (getstr ());
+}
+
+finditem (char *s)
+{
+	int i;
+
+	if (!strcmp (s,"chain"))
+		return I_CHAIN_MAIL;
+	if (!strcmp (s,"plate"))
+		return I_PLATE_ARMOR;
+
+	i = findstr (itemnames[0],s,MAXITEMS);
+	if (i >= 0)
+		return i;
+
+	return findstr (itemnames[1],s,MAXITEMS);
+}
+
+getitem (void)
+{
+	return finditem (getstr ());
+}
+
+findspell (char *s)
+{
+	return findstr (spellnames,s,MAXSPELLS);
+}
+
+getspell (void)
+{
+	return findspell (getstr ());
+}
+
+unit *createunit (region *r1)
+{
+	int i,n;
+	region *r;
+	unit *u,*u2;
+	char v[1000];
+
+	u = cmalloc (sizeof (unit));
+	memset (u,0,sizeof (unit));
+
+	strcpy (u->lastorder,"work");
+	u->combatspell = -1;
+
+	for (n = 0;; n += 1000)
+	{
+		memset (v,0,sizeof v);
+
+		if (n == 0)
+			v[0] = 1;
+
+		for (r = regions; r; r = r->next)
+			for (u2 = r->units; u2; u2 = u2->next)
+				if (u2->no >= n && u2->no < n + 1000)
+					v[u2->no - n] = 1;
+
+		for (i = 0; i != 1000; i++)
+			if (!v[i])
+			{
+				u->no = n + i;
+				sprintf (u->name,"Unit %d",u->no);
+				addlist (&r1->units,u);
+				return u;
+			}
+	}
+}
+
+int scramblecmp (const void *p1,const void *p2)
+{
+	return *((long *)p1) - *((long *)p2);
+}
+
+void scramble (void *v1,int n,int width)
+{
+	int i;
+	void *v;
+
+	v = cmalloc (n * (width + 4));
+
+	for (i = 0; i != n; i++)
+	{
+		*(long *)addptr (v,i * (width + 4)) = rnd ();
+		memcpy (addptr (v,i * (width + 4) + 4),addptr (v1,i * width),width);
+	}
+
+	qsort (v,n,width + 4,scramblecmp);
+
+	for (i = 0; i != n; i++)
+		memcpy (addptr (v1,i * width),addptr (v,i * (width + 4) + 4),width);
+
+	free (v);
+}
+
+region *inputregion (void)
+{
+	int x,y;
+	region *r;
+
+LOOP:
+	printf ("X? ");
+	gets (buf);
+	if (buf[0] == 0)
+		return 0;
+	x = atoi (buf);
+
+	printf ("Y? ");
+	gets (buf);
+	if (buf[0] == 0)
+		return 0;
+	y = atoi (buf);
+
+	r = findregion (x,y);
+
+	if (!r)
+	{
+		puts ("No such region.");
+		goto LOOP;
+	}
+}
+
+void addplayers (void)
+{
+	region *r;
+	faction *f;
+	unit *u;
+
+	r = inputregion ();
+
+	if (!r)
+		return;
+
+	printf ("Name of players file? ");
+	gets (buf);
+
+	if (!buf[0])
+		return;
+
+	cfopen (buf,"r");
+
+	for (;;)
+	{
+		getbuf ();
+
+		if (buf[0] == 0 || buf[0] == EOF)
+		{
+			fclose (F);
+			break;
+		}
+
+		f = cmalloc (sizeof (faction));
+		memset (f,0,sizeof (faction));
+
+		nstrcpy (f->addr,buf,NAMESIZE);
+		f->lastorders = turn;
+		f->alive = 1;
+
+		do
+		{
+			f->no++;
+			sprintf (f->name,"Faction %d",f->no);
+		}
+		while (findfaction (f->no));
+
+		addlist (&factions,f);
+
+		u = createunit (r);
+		u->number = 1;
+		u->money = STARTMONEY;
+		u->faction = f;
+		u->isnew = 1;
+	}
+}
+
+void connecttothis (region *r,int x,int y,int from,int to)
+{
+	region *r2;
+
+	r2 = findregion (x,y);
+
+	if (r2)
+	{
+		r->connect[from] = r2;
+		r2->connect[to] = r;
+	}
+}
+
+void connectregions (void)
+{
+	region *r;
+
+	for (r = regions; r; r = r->next)
+	{
+		if (!r->connect[0])
+			connecttothis (r,r->x,r->y - 1,0,1);
+		if (!r->connect[1])
+			connecttothis (r,r->x,r->y + 1,1,0);
+		if (!r->connect[2])
+			connecttothis (r,r->x + 1,r->y,2,3);
+		if (!r->connect[3])
+			connecttothis (r,r->x - 1,r->y,3,2);
+	}
+}
+
+char newblock[BLOCKSIZE][BLOCKSIZE];
+
+void transmute (int from,int to,int n,int count)
+{
+	int i,x,y;
+
+	do
+	{
+		i = 0;
+
+		do
+		{
+			x = rnd () % BLOCKSIZE;
+			y = rnd () % BLOCKSIZE;
+			i += count;
+		}
+		while (i <= 10 &&
+				 !(newblock[x][y] == from &&
+					((x != 0 && newblock[x - 1][y] == to) ||
+					 (x != BLOCKSIZE - 1 && newblock[x + 1][y] == to) ||
+					 (y != 0 && newblock[x][y - 1] == to) ||
+					 (y != BLOCKSIZE - 1 && newblock[x][y + 1] == to))));
+
+		if (i > 10)
+			break;
+
+		newblock[x][y] = to;
+	}
+	while (--n);
+}
+
+void seed (int to,int n)
+{
+	int x,y;
+
+	do
+	{
+		x = rnd () % BLOCKSIZE;
+		y = rnd () % BLOCKSIZE;
+	}
+	while (newblock[x][y] != T_PLAIN);
+
+	newblock[x][y] = to;
+	transmute (T_PLAIN,to,n,1);
+}
+
+regionnameinuse (char *s)
+{
+	region *r;
+
+	for (r = regions; r; r = r->next)
+		if (!strcmp (r->name,s))
+			return 1;
+
+	return 0;
+}
+
+blockcoord (int x)
+{
+	return (x / (BLOCKSIZE + BLOCKBORDER*2)) * (BLOCKSIZE + BLOCKBORDER*2);
+}
+
+void makeblock (int x1,int y1)
+{
+	int i;
+	int n;
+	int x,y;
+	region *r,*r2;
+
+	if (x1 < 0)
+		while (x1 != blockcoord (x1))
+			x1--;
+
+	if (y1 < 0)
+		while (y1 != blockcoord (y1))
+			y1--;
+
+	x1 = blockcoord (x1);
+	y1 = blockcoord (y1);
+
+	memset (newblock,T_OCEAN,sizeof newblock);
+	newblock[BLOCKSIZE / 2][BLOCKSIZE / 2] = T_PLAIN;
+	transmute (T_OCEAN,T_PLAIN,31,0);
+	seed (T_MOUNTAIN,1);
+	seed (T_MOUNTAIN,1);
+	seed (T_FOREST,1);
+	seed (T_FOREST,1);
+	seed (T_SWAMP,1);
+	seed (T_SWAMP,1);
+
+	for (x = 0; x != BLOCKSIZE + BLOCKBORDER*2; x++)
+		for (y = 0; y != BLOCKSIZE + BLOCKBORDER*2; y++)
+		{
+			r = cmalloc (sizeof (region));
+			memset (r,0,sizeof (region));
+
+			r->x = x1 + x;
+			r->y = y1 + y;
+
+			if (x >= BLOCKBORDER && x < BLOCKBORDER + BLOCKSIZE &&
+				 y >= BLOCKBORDER && y < BLOCKBORDER + BLOCKSIZE)
+			{
+				r->terrain = newblock[x - BLOCKBORDER][y - BLOCKBORDER];
+
+				if (r->terrain != T_OCEAN)
+				{
+					n = 0;
+					for (r2 = regions; r2; r2 = r2->next)
+						if (r2->name[0])
+							n++;
+
+					i = rnd () % (sizeof regionnames / sizeof (char *));
+					if (n < sizeof regionnames / sizeof (char *))
+						while (regionnameinuse (regionnames[i]))
+							i = rnd () % (sizeof regionnames / sizeof (char *));
+
+					strcpy (r->name,regionnames[i]);
+					r->peasants = maxfoodoutput[r->terrain] / 50;
+				}
+			}
+
+			addlist (&regions,r);
+		}
+
+	connectregions ();
+}
+
+char *gamedate (void)
+{
+	static char buf[40];
+	static char *monthnames[] =
+	{
+		"January",
+		"February",
+		"March",
+		"April",
+		"May",
+		"June",
+		"July",
+		"August",
+		"September",
+		"October",
+		"November",
+		"December",
+	};
+
+	if (turn == 0)
+		strcpy (buf,"In the Beginning");
+	else
+		sprintf (buf,"%s, Year %d",monthnames[(turn - 1) % 12],((turn - 1) / 12) + 1);
+	return buf;
+}
+
+char *factionid (faction *f)
+{
+	static char buf[NAMESIZE + 20];
+
+	sprintf (buf,"%s (%d)",f->name,f->no);
+	return buf;
+}
+
+char *regionid (region *r)
+{
+	static char buf[NAMESIZE + 20];
+
+	if (r->terrain == T_OCEAN)
+		sprintf (buf,"(%d,%d)",r->x,r->y);
+	else
+		sprintf (buf,"%s (%d,%d)",r->name,r->x,r->y);
+	return buf;
+}
+
+char *buildingid (building *b)
+{
+	static char buf[NAMESIZE + 20];
+
+	sprintf (buf,"%s (%d)",b->name,b->no);
+	return buf;
+}
+
+char *shipid (ship *sh)
+{
+	static char buf[NAMESIZE + 20];
+
+	sprintf (buf,"%s (%d)",sh->name,sh->no);
+	return buf;
+}
+
+char *unitid (unit *u)
+{
+	static char buf[NAMESIZE + 20];
+
+	sprintf (buf,"%s (%d)",u->name,u->no);
+	return buf;
+}
+
+strlist *makestrlist (char *s)
+{
+	strlist *S;
+
+	S = cmalloc (sizeof (strlist) + strlen (s));
+	strcpy (S->s,s);
+	return S;
+}
+
+void addstrlist (strlist **SP,char *s)
+{
+	addlist (SP,makestrlist (s));
+}
+
+void catstrlist (strlist **SP,strlist *S)
+{
+	strlist *S2;
+
+	while (*SP)
+		SP = &((*SP)->next);
+
+	while (S)
+	{
+		S2 = makestrlist (S->s);
+		addlist2 (SP,S2);
+		S = S->next;
+	}
+
+	*SP = 0;
+}
+
+void sparagraph (strlist **SP,char *s,int indent,int mark)
+{
+	int i,j,width;
+	int firstline;
+	static char buf[128];
+
+	width = 79 - indent;
+	firstline = 1;
+
+	for (;;)
+	{
+		i = 0;
+
+		do
+		{
+			j = i;
+			while (s[j] && s[j] != ' ')
+				j++;
+			if (j > width)
+				break;
+			i = j + 1;
+		}
+		while (s[j]);
+
+		for (j = 0; j != indent; j++)
+			buf[j] = ' ';
+
+		if (firstline && mark)
+			buf[indent - 2] = mark;
+
+		for (j = 0; j != i - 1; j++)
+			buf[indent + j] = s[j];
+		buf[indent + j] = 0;
+
+		addstrlist (SP,buf);
+
+		if (s[i - 1] == 0)
+			break;
+
+		s += i;
+		firstline = 0;
+	}
+}
+
+void spskill (unit *u,int i,int *dh,int days)
+{
+	if (!u->skills[i])
+		return;
+
+	scat (", ");
+
+	if (!*dh)
+	{
+		scat ("skills: ");
+		*dh = 1;
+	}
+
+	scat (skillnames[i]);
+	scat (" ");
+	icat (effskill (u,i));
+
+	if (days)
+	{
+		assert (u->number);
+		scat (" [");
+		icat (u->skills[i] / u->number);
+		scat ("]");
+	}
+}
+
+void spunit (strlist **SP,faction *f,region *r,unit *u,int indent,int battle)
+{
+	int i;
+	int dh;
+
+	strcpy (buf,unitid (u));
+
+	if (cansee (f,r,u) == 2)
+	{
+		scat (", faction ");
+		scat (factionid (u->faction));
+	}
+
+	if (u->number != 1)
+	{
+		scat (", number: ");
+		icat (u->number);
+	}
+
+	if (u->behind && (u->faction == f || battle))
+		scat (", behind");
+
+	if (u->guard)
+		scat (", on guard");
+
+	if (u->faction == f && !battle && u->money)
+	{
+		scat (", $");
+		icat (u->money);
+	}
+
+	dh = 0;
+
+	if (battle)
+		for (i = SK_TACTICS; i <= SK_LONGBOW; i++)
+			spskill (u,i,&dh,0);
+	else if (u->faction == f)
+		for (i = 0; i != MAXSKILLS; i++)
+			spskill (u,i,&dh,1);
+
+	dh = 0;
+
+	for (i = 0; i != MAXITEMS; i++)
+		if (u->items[i])
+		{
+			scat (", ");
+
+			if (!dh)
+			{
+				scat ("has: ");
+				dh = 1;
+			}
+
+			if (u->items[i] == 1)
+				scat (itemnames[0][i]);
+			else
+			{
+				icat (u->items[i]);
+				scat (" ");
+				scat (itemnames[1][i]);
+			}
+		}
+
+	if (u->faction == f)
+	{
+		dh = 0;
+
+		for (i = 0; i != MAXSPELLS; i++)
+			if (u->spells[i])
+			{
+				scat (", ");
+
+				if (!dh)
+				{
+					scat ("spells: ");
+					dh = 1;
+				}
+
+				scat (spellnames[i]);
+			}
+
+		if (!battle)
+		{
+			scat (", default: \"");
+			scat (u->lastorder);
+			scat ("\"");
+		}
+
+		if (u->combatspell >= 0)
+		{
+			scat (", combat spell: ");
+			scat (spellnames[u->combatspell]);
+		}
+	}
+
+	i = 0;
+
+	if (u->display[0])
+	{
+		scat ("; ");
+		scat (u->display);
+
+		i = u->display[strlen (u->display) - 1];
+	}
+
+	if (i != '!' && i != '?')
+		scat (".");
+
+	sparagraph (SP,buf,indent,(u->faction == f) ? '*' : '-');
+}
+
+void mistake (faction *f,char *s,char *comment)
+{
+	static char buf[512];
+
+	sprintf (buf,"%s: %s.",s,comment);
+	sparagraph (&f->mistakes,buf,0,0);
+}
+
+void mistake2 (unit *u,strlist *S,char *comment)
+{
+	mistake (u->faction,S->s,comment);
+}
+
+void mistakeu (unit *u,char *comment)
+{
+	mistake (u->faction,u->thisorder,comment);
+}
+
+void addevent (faction *f,char *s)
+{
+	sparagraph (&f->events,s,0,0);
+}
+
+void addbattle (faction *f,char *s)
+{
+	sparagraph (&f->battles,s,0,0);
+}
+
+void reportevent (region *r,char *s)
+{
+	faction *f;
+	unit *u;
+
+	for (f = factions; f; f = f->next)
+		for (u = r->units; u; u = u->next)
+			if (u->faction == f && u->number)
+			{
+				addevent (f,s);
+				break;
+			}
+}
+
+void leave (region *r,unit *u)
+{
+	unit *u2;
+	building *b;
+	ship *sh;
+
+	if (u->building)
+	{
+		b = u->building;
+		u->building = 0;
+
+		if (u->owner)
+		{
+			u->owner = 0;
+
+			for (u2 = r->units; u2; u2 = u2->next)
+				if (u2->faction == u->faction && u2->building == b)
+				{
+					u2->owner = 1;
+					return;
+				}
+
+			for (u2 = r->units; u2; u2 = u2->next)
+				if (u2->building == b)
+				{
+					u2->owner = 1;
+					return;
+				}
+		}
+	}
+
+	if (u->ship)
+	{
+		sh = u->ship;
+		u->ship = 0;
+
+		if (u->owner)
+		{
+			u->owner = 0;
+
+			for (u2 = r->units; u2; u2 = u2->next)
+				if (u2->faction == u->faction && u2->ship == sh)
+				{
+					u2->owner = 1;
+					return;
+				}
+
+			for (u2 = r->units; u2; u2 = u2->next)
+				if (u2->ship == sh)
+				{
+					u2->owner = 1;
+					return;
+				}
+		}
+	}
+}
+
+void removeempty (void)
+{
+	int i;
+	faction *f;
+	region *r;
+	ship *sh,*sh2;
+	unit *u,*u2,*u3;
+
+	for (r = regions; r; r = r->next)
+	{
+		for (u = r->units; u;)
+		{
+			u2 = u->next;
+
+			if (!u->number)
+			{
+				leave (r,u);
+
+				for (u3 = r->units; u3; u3 = u3->next)
+					if (u3->faction == u->faction)
+					{
+						u3->money += u->money;
+						u->money = 0;
+						for (i = 0; i != MAXITEMS; i++)
+							u3->items[i] += u->items[i];
+						break;
+					}
+
+				if (r->terrain != T_OCEAN)
+					r->money += u->money;
+
+				freelist (u->orders);
+				removelist (&r->units,u);
+			}
+
+			u = u2;
+		}
+
+		if (r->terrain == T_OCEAN)
+			for (sh = r->ships; sh;)
+			{
+				sh2 = sh->next;
+
+				for (u = r->units; u; u = u->next)
+					if (u->ship == sh)
+						break;
+
+				if (!u)
+					removelist (&r->ships,sh);
+
+				sh = sh2;
+			}
+	}
+}
+
+void destroyfaction (faction *f)
+{
+	region *r;
+	unit *u;
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			if (u->faction == f)
+			{
+				if (r->terrain != T_OCEAN)
+					r->peasants += u->number;
+
+				u->number = 0;
+			}
+
+	f->alive = 0;
+}
+
+void togglerf (unit *u,strlist *S,rfaction **r)
+{
+	faction *f;
+	rfaction *rf;
+
+	f = getfaction ();
+
+	if (f)
+	{
+		if (f != u->faction)
+		{
+			for (rf = *r; rf; rf = rf->next)
+				if (rf->faction == f)
+					break;
+
+			if (rf)
+				removelist (r,rf);
+			else
+			{
+				rf = cmalloc (sizeof (rfaction));
+				rf->faction = f;
+				addlist (r,rf);
+			}
+		}
+	}
+	else
+		mistake2 (u,S,"Faction not found");
+}
+
+iscoast (region *r)
+{
+	int i;
+
+	for (i = 0; i != 4; i++)
+		if (r->connect[i]->terrain == T_OCEAN)
+			return 1;
+
+	return 0;
+}
+
+distribute (int old,int new,int n)
+{
+	int i;
+	int t;
+
+	assert (new <= old);
+
+	if (old == 0)
+		return 0;
+
+	t = (n / old) * new;
+	for (i = (n % old); i; i--)
+		if (rnd () % old < new)
+			t++;
+
+	return t;
+}
+
+armedmen (unit *u)
+{
+	int n;
+
+	n = 0;
+	if (effskill (u,SK_SWORD))
+		n += u->items[I_SWORD];
+	if (effskill (u,SK_CROSSBOW))
+		n += u->items[I_CROSSBOW];
+	if (effskill (u,SK_LONGBOW))
+		n += u->items[I_LONGBOW];
+	return min (n,u->number);
+}
+
+void getmoney (region *r,unit *u,int n)
+{
+	int i;
+	unit *u2;
+
+	n -= u->money;
+
+	for (u2 = r->units; u2 && n >= 0; u2 = u2->next)
+		if (u2->faction == u->faction && u2 != u)
+		{
+			i = min (u2->money,n);
+			u2->money -= i;
+			u->money += i;
+			n -= i;
+		}
+}
+
+int ntroops;
+troop *ta;
+troop attacker,defender;
+int initial[2];
+int left[2];
+int infront[2];
+int toattack[2];
+int shields[2];
+int runeswords[2];
+
+troop **maketroops (troop **tp,unit *u,int terrain)
+{
+	int i;
+	troop *t;
+	static skills[MAXSKILLS];
+	static items[MAXITEMS];
+
+	for (i = 0; i != MAXSKILLS; i++)
+		skills[i] = effskill (u,i);
+	memcpy (items,u->items,sizeof items);
+
+	left[u->side] += u->number;
+	if (!u->behind)
+		infront[u->side] += u->number;
+
+	for (i = u->number; i; i--)
+	{
+		t = cmalloc (sizeof (troop));
+		memset (t,0,sizeof (troop));
+
+		t->unit = u;
+		t->side = u->side;
+		t->skill = -2;
+		t->behind = u->behind;
+
+		if (u->combatspell >= 0)
+			t->missile = 1;
+		else if (items[I_RUNESWORD] && skills[SK_SWORD])
+		{
+			t->weapon = I_SWORD;
+			t->skill = skills[SK_SWORD] + 2;
+			t->runesword = 1;
+			items[I_RUNESWORD]--;
+			runeswords[u->side]++;
+
+			if (items[I_HORSE] && skills[SK_RIDING] >= 2 && terrain == T_PLAIN)
+			{
+				t->skill += 2;
+				items[I_HORSE]--;
+			}
+		}
+		else if (items[I_LONGBOW] && skills[SK_LONGBOW])
+		{
+			t->weapon = I_LONGBOW;
+			t->missile = 1;
+			t->skill = skills[SK_LONGBOW];
+			items[I_LONGBOW]--;
+		}
+		else if (items[I_CROSSBOW] && skills[SK_CROSSBOW])
+		{
+			t->weapon = I_CROSSBOW;
+			t->missile = 1;
+			t->skill = skills[SK_CROSSBOW];
+			items[I_CROSSBOW]--;
+		}
+		else if (items[I_SWORD] && skills[SK_SWORD])
+		{
+			t->weapon = I_SWORD;
+			t->skill = skills[SK_SWORD];
+			items[I_SWORD]--;
+
+			if (items[I_HORSE] && skills[SK_RIDING] >= 2 && terrain == T_PLAIN)
+			{
+				t->skill += 2;
+				items[I_HORSE]--;
+			}
+		}
+
+		if (u->spells[SP_HEAL] || items[I_AMULET_OF_HEALING] > 0)
+		{
+			t->canheal = 1;
+			items[I_AMULET_OF_HEALING]--;
+		}
+
+		if (items[I_RING_OF_POWER])
+		{
+			t->power = 1;
+			items[I_RING_OF_POWER]--;
+		}
+
+		if (items[I_SHIELDSTONE])
+		{
+			t->shieldstone = 1;
+			items[I_SHIELDSTONE]--;
+		}
+
+		if (items[I_CLOAK_OF_INVULNERABILITY])
+		{
+			t->invulnerable = 1;
+			items[I_CLOAK_OF_INVULNERABILITY]--;
+		}
+		else if (items[I_PLATE_ARMOR])
+		{
+			t->armor = 2;
+			items[I_PLATE_ARMOR]--;
+		}
+		else if (items[I_CHAIN_MAIL])
+		{
+			t->armor = 1;
+			items[I_CHAIN_MAIL]--;
+		}
+
+		if (u->building && u->building->sizeleft)
+		{
+			t->inside = 2;
+			u->building->sizeleft--;
+		}
+
+		addlist2 (tp,t);
+	}
+
+	return tp;
+}
+
+void battlerecord (char *s)
+{
+	faction *f;
+
+	for (f = factions; f; f = f->next)
+		if (f->seesbattle)
+			sparagraph (&f->battles,s,0,0);
+
+	if (s[0])
+		puts (s);
+}
+
+void battlepunit (region *r,unit *u)
+{
+	faction *f;
+
+	for (f = factions; f; f = f->next)
+		if (f->seesbattle)
+			spunit (&f->battles,f,r,u,4,1);
+}
+
+contest (int a,int d)
+{
+	int i;
+	static char table[] = { 10,25,40 };
+
+	i = a - d + 1;
+	if (i < 0)
+		return rnd () % 100 < 1;
+	if (i > 2)
+		return rnd () % 100 < 49;
+	return rnd () % 100 < table[i];
+}
+
+hits (void)
+{
+	int k;
+
+	if (defender.weapon == I_CROSSBOW || defender.weapon == I_LONGBOW)
+		defender.skill = -2;
+	defender.skill += defender.inside;
+	attacker.skill -= (attacker.demoralized + attacker.dazzled);
+	defender.skill -= (defender.demoralized + defender.dazzled);
+
+	switch (attacker.weapon)
+	{
+		case 0:
+		case I_SWORD:
+			k = contest (attacker.skill,defender.skill);
+			break;
+
+		case I_CROSSBOW:
+			k = contest (attacker.skill,0);
+			break;
+
+		case I_LONGBOW:
+			k = contest (attacker.skill,2);
+			break;
+	}
+
+	if (defender.invulnerable && rnd () % 10000)
+		k = 0;
+
+	if (rnd () % 3 < defender.armor)
+		k = 0;
+
+	return k;
+}
+
+validtarget (int i)
+{
+	return !ta[i].status &&
+			 ta[i].side == defender.side &&
+			 (!ta[i].behind || !infront[defender.side]);
+}
+
+canbedemoralized (int i)
+{
+	return validtarget (i) && !ta[i].demoralized;
+}
+
+canbedazzled (int i)
+{
+	return validtarget (i) && !ta[i].dazzled;
+}
+
+canberemoralized (int i)
+{
+	return !ta[i].status &&
+			 ta[i].side == attacker.side &&
+			 ta[i].demoralized;
+}
+
+selecttarget (void)
+{
+	int i;
+
+	do
+		i = rnd () % ntroops;
+	while (!validtarget (i));
+
+	return i;
+}
+
+void terminate (int i)
+{
+	if (!ta[i].attacked)
+	{
+		ta[i].attacked = 1;
+		toattack[defender.side]--;
+	}
+
+	ta[i].status = 1;
+	left[defender.side]--;
+	if (infront[defender.side])
+		infront[defender.side]--;
+	if (ta[i].runesword)
+		runeswords[defender.side]--;
+}
+
+lovar (int n)
+{
+	n /= 2;
+	return (rnd () % n + 1) + (rnd () % n + 1);
+}
+
+void dozap (int n)
+{
+	static char buf2[40];
+
+	n = lovar (n * (1 + attacker.power));
+	n = min (n,left[defender.side]);
+
+	sprintf (buf2,", inflicting %d %s",n,(n == 1) ? "casualty" : "casualties");
+	scat (buf2);
+
+	while (--n >= 0)
+		terminate (selecttarget ());
+}
+
+void docombatspell (int i)
+{
+	int j;
+	int z;
+	int n,m;
+
+	z = ta[i].unit->combatspell;
+	sprintf (buf,"%s casts %s",unitid (ta[i].unit),spellnames[z]);
+
+	if (shields[defender.side])
+		if (rnd () & 1)
+		{
+			scat (", and gets through the shield");
+			shields[defender.side] -= 1 + attacker.power;
+		}
+		else
+		{
+			scat (", but the spell is deflected by the shield!");
+			battlerecord (buf);
+			return;
+		}
+
+	switch (z)
+	{
+		case SP_BLACK_WIND:
+			dozap (1250);
+			break;
+
+		case SP_CAUSE_FEAR:
+			if (runeswords[defender.side] && (rnd () & 1))
+				break;
+
+			n = lovar (100 * (1 + attacker.power));
+
+			m = 0;
+			for (j = 0; j != ntroops; j++)
+				if (canbedemoralized (j))
+					m++;
+
+			n = min (n,m);
+
+			sprintf (buf2,", affecting %d %s",n,(n == 1) ? "person" : "people");
+			scat (buf2);
+
+			while (--n >= 0)
+			{
+				do
+					j = rnd () % ntroops;
+				while (!canbedemoralized (j));
+
+				ta[j].demoralized = 1;
+			}
+
+			break;
+
+		case SP_DAZZLING_LIGHT:
+			n = lovar (50 * (1 + attacker.power));
+
+			m = 0;
+			for (j = 0; j != ntroops; j++)
+				if (canbedazzled (j))
+					m++;
+
+			n = min (n,m);
+
+			sprintf (buf2,", dazzling %d %s",n,(n == 1) ? "person" : "people");
+			scat (buf2);
+
+			while (--n >= 0)
+			{
+				do
+					j = rnd () % ntroops;
+				while (!canbedazzled (j));
+
+				ta[j].dazzled = 1;
+			}
+
+			break;
+
+		case SP_FIREBALL:
+			dozap (50);
+			break;
+
+		case SP_HAND_OF_DEATH:
+			dozap (250);
+			break;
+
+		case SP_INSPIRE_COURAGE:
+			n = lovar (100 * (1 + attacker.power));
+
+			m = 0;
+			for (j = 0; j != ntroops; j++)
+				if (canberemoralized (j))
+					m++;
+
+			n = min (n,m);
+
+			sprintf (buf2,", affecting %d %s",n,(n == 1) ? "person" : "people");
+			scat (buf2);
+
+			while (--n >= 0)
+			{
+				do
+					j = rnd () % ntroops;
+				while (!canberemoralized (j));
+
+				ta[j].demoralized = 0;
+			}
+
+			break;
+
+		case SP_LIGHTNING_BOLT:
+			dozap (10);
+			break;
+
+		case SP_SHIELD:
+			shields[attacker.side] += 1 + attacker.power;
+			break;
+
+		case SP_SUNFIRE:
+			dozap (6250);
+			break;
+
+		default:
+			assert (0);
+	}
+
+	scat ("!");
+	battlerecord (buf);
+}
+
+void doshot (void)
+{
+	int ai,di;
+
+			/* Select attacker */
+
+	do
+		ai = rnd () % ntroops;
+	while (ta[ai].attacked);
+
+	attacker = ta[ai];
+	ta[ai].attacked = 1;
+	toattack[attacker.side]--;
+	defender.side = 1 - attacker.side;
+
+	ta[ai].dazzled = 0;
+
+	if (attacker.unit)
+	{
+		if (attacker.behind &&
+		 	infront[attacker.side] &&
+		 	!attacker.missile)
+			return;
+
+		if (attacker.shieldstone)
+			shields[attacker.side] += 1 + attacker.power;
+
+		if (attacker.unit->combatspell >= 0)
+		{
+			docombatspell (ai);
+			return;
+		}
+
+		if (attacker.reload)
+		{
+			ta[ai].reload--;
+			return;
+		}
+
+		if (attacker.weapon == I_CROSSBOW)
+			ta[ai].reload = 2;
+	}
+
+			/* Select defender */
+
+	di = selecttarget ();
+	defender = ta[di];
+	assert (defender.side == 1 - attacker.side);
+
+			/* If attack succeeds */
+
+	if (hits ())
+		terminate (di);
+}
+
+isallied (unit *u,unit *u2)
+{
+	rfaction *rf;
+
+	if (!u2)
+		return u->guard;
+
+	if (u->faction == u2->faction)
+		return 1;
+
+	for (rf = u->faction->allies; rf; rf = rf->next)
+		if (rf->faction == u2->faction)
+			return 1;
+
+	return 0;
+}
+
+accepts (unit *u,unit *u2)
+{
+	rfaction *rf;
+
+	if (isallied (u,u2))
+		return 1;
+
+	for (rf = u->faction->accept; rf; rf = rf->next)
+		if (rf->faction == u2->faction)
+			return 1;
+
+	return 0;
+}
+
+admits (unit *u,unit *u2)
+{
+	rfaction *rf;
+
+	if (isallied (u,u2))
+		return 1;
+
+	for (rf = u->faction->admit; rf; rf = rf->next)
+		if (rf->faction == u2->faction)
+			return 1;
+
+	return 0;
+}
+
+unit *buildingowner (region *r,building *b)
+{
+	unit *u;
+
+	for (u = r->units; u; u = u->next)
+		if (u->building == b && u->owner)
+			return u;
+
+	return 0;
+}
+
+unit *shipowner (region *r,ship *sh)
+{
+	unit *u;
+
+	for (u = r->units; u; u = u->next)
+		if (u->ship == sh && u->owner)
+			return u;
+
+	return 0;
+}
+
+mayenter (region *r,unit *u,building *b)
+{
+	unit *u2;
+
+	u2 = buildingowner (r,b);
+	return u2 == 0 || admits (u2,u);
+}
+
+mayboard (region *r,unit *u,ship *sh)
+{
+	unit *u2;
+
+	u2 = shipowner (r,sh);
+	return u2 == 0 || admits (u2,u);
+}
+
+void readorders (void)
+{
+	int i,j;
+	faction *f;
+	region *r;
+	unit *u;
+	strlist *S,**SP;
+
+	cfopen (buf,"r");
+	getbuf ();
+
+	while (buf[0] != EOF)
+	{
+		if (!strncasecmp (buf,"#atlantis",9))
+		{
+NEXTPLAYER:
+			igetstr (buf);
+			i = geti ();
+			f = findfaction (i);
+
+			if (f)
+			{
+				for (r = regions; r; r = r->next)
+					for (u = r->units; u; u = u->next)
+						if (u->faction == f)
+						{
+							freelist (u->orders);
+							u->orders = 0;
+						}
+
+				for (;;)
+				{
+					getbuf ();
+
+					if (!strncasecmp (buf,"#atlantis",9))
+						goto NEXTPLAYER;
+
+					if (buf[0] == EOF || buf[0] == '\f' || buf[0] == '#')
+						goto DONEPLAYER;
+
+					if (!strcmp (igetstr (buf),"unit"))
+					{
+NEXTUNIT:
+						i = geti ();
+						u = findunitg (i);
+
+						if (u && u->faction == f)
+						{
+							SP = &u->orders;
+							u->faction->lastorders = turn;
+
+							for (;;)
+							{
+								getbuf ();
+
+								if (!strcmp (igetstr (buf),"unit"))
+								{
+									*SP = 0;
+									goto NEXTUNIT;
+								}
+
+								if (!strncasecmp (buf,"#atlantis",9))
+								{
+									*SP = 0;
+									goto NEXTPLAYER;
+								}
+
+								if (buf[0] == EOF || buf[0] == '\f' || buf[0] == '#')
+								{
+									*SP = 0;
+									goto DONEPLAYER;
+								}
+
+								i = 0;
+								j = 0;
+
+								for (;;)
+								{
+									while (buf[i] == ' ' ||
+											 buf[i] == '\t')
+										i++;
+
+									if (buf[i] == 0 ||
+										 buf[i] == ';')
+										break;
+
+									if (buf[i] == '"')
+									{
+										i++;
+
+										for (;;)
+										{
+											while (buf[i] == '_' ||
+													 buf[i] == ' ' ||
+													 buf[i] == '\t')
+												i++;
+
+											if (buf[i] == 0 ||
+												 buf[i] == '"')
+												break;
+
+											while (buf[i] != 0 &&
+													 buf[i] != '"' &&
+													 buf[i] != '_' &&
+													 buf[i] != ' ' &&
+													 buf[i] != '\t')
+												buf2[j++] = buf[i++];
+
+											buf2[j++] = '_';
+										}
+
+										if (buf[i] != 0)
+											i++;
+
+										if (j && (buf2[j - 1] == '_'))
+											j--;
+									}
+									else
+									{
+										for (;;)
+										{
+											while (buf[i] == '_')
+												i++;
+
+											if (buf[i] == 0 ||
+												 buf[i] == ';' ||
+												 buf[i] == '"' ||
+												 buf[i] == ' ' ||
+												 buf[i] == '\t')
+												break;
+
+											while (buf[i] != 0 &&
+													 buf[i] != ';' &&
+													 buf[i] != '"' &&
+													 buf[i] != '_' &&
+													 buf[i] != ' ' &&
+													 buf[i] != '\t')
+												buf2[j++] = buf[i++];
+
+											buf2[j++] = '_';
+										}
+
+										if (j && (buf2[j - 1] == '_'))
+											j--;
+									}
+
+									buf2[j++] = ' ';
+								}
+
+								if (j)
+								{
+									buf2[j - 1] = 0;
+									S = makestrlist (buf2);
+									addlist2 (SP,S);
+								}
+							}
+						}
+						else
+						{
+							sprintf (buf,"Unit %d is not one of your units.",i);
+							addstrlist (&f->mistakes,buf);
+						}
+					}
+				}
+			}
+			else
+				printf ("Invalid faction number %d.\n",i);
+		}
+
+DONEPLAYER:
+		getbuf ();
+	}
+
+	fclose (F);
+}
+
+void writemap (void)
+{
+	int x,y,minx,miny,maxx,maxy;
+	region *r;
+
+	minx = INT_MAX;
+	maxx = INT_MIN;
+	miny = INT_MAX;
+	maxy = INT_MIN;
+
+	for (r = regions; r; r = r->next)
+	{
+		minx = min (minx,r->x);
+		maxx = max (maxx,r->x);
+		miny = min (miny,r->y);
+		maxy = max (maxy,r->y);
+	}
+
+	for (y = miny; y <= maxy; y++)
+	{
+		memset (buf,' ',sizeof buf);
+		buf[maxx - minx + 1] = 0;
+
+		for (r = regions; r; r = r->next)
+			if (r->y == y)
+				buf[r->x - minx] = ".+MFS"[r->terrain];
+
+		for (x = 0; buf[x]; x++)
+		{
+			fputc (' ',F);
+			fputc (buf[x],F);
+		}
+
+		fputc ('\n',F);
+	}
+}
+
+void writesummary (void)
+{
+	int inhabitedregions;
+	int peasants;
+	int peasantmoney;
+	int nunits;
+	int playerpop;
+	int playermoney;
+	faction *f;
+	region *r;
+	unit *u;
+
+	cfopen ("summary","w");
+	puts ("Writing summary file...");
+
+	inhabitedregions = 0;
+	peasants = 0;
+	peasantmoney = 0;
+
+	nunits = 0;
+	playerpop = 0;
+	playermoney = 0;
+
+	for (r = regions; r; r = r->next)
+		if (r->peasants || r->units)
+		{
+			inhabitedregions++;
+			peasants += r->peasants;
+			peasantmoney += r->money;
+
+			for (u = r->units; u; u = u->next)
+			{
+				nunits++;
+				playerpop += u->number;
+				playermoney += u->money;
+
+				u->faction->nunits++;
+				u->faction->number += u->number;
+				u->faction->money += u->money;
+			}
+		}
+
+	fprintf (F,"Summary file for Atlantis, %s\n\n",gamedate ());
+
+	fprintf (F,"Regions:            %d\n",listlen (regions));
+	fprintf (F,"Inhabited Regions:  %d\n\n",inhabitedregions);
+
+	fprintf (F,"Factions:           %d\n",listlen (factions));
+	fprintf (F,"Units:              %d\n\n",nunits);
+
+	fprintf (F,"Player Population:  %d\n",playerpop);
+	fprintf (F,"Peasants:           %d\n",peasants);
+	fprintf (F,"Total Population:   %d\n\n",playerpop + peasants);
+
+	fprintf (F,"Player Wealth:      $%d\n",playermoney);
+	fprintf (F,"Peasant Wealth:     $%d\n",peasantmoney);
+	fprintf (F,"Total Wealth:       $%d\n\n",playermoney + peasantmoney);
+
+	writemap ();
+
+	if (factions)
+		fputc ('\n',F);
+
+	for (f = factions; f; f = f->next)
+		fprintf (F,"%s, units: %d, number: %d, $%d, address: %s\n",factionid (f),
+					f->nunits,f->number,f->money,f->addr);
+
+	fclose (F);
+}
+
+int outi;
+char outbuf[256];
+
+void rnl (void)
+{
+	int i;
+	int rc,vc;
+
+	i = outi;
+	while (i && isspace (outbuf[i - 1]))
+		i--;
+	outbuf[i] = 0;
+
+	i = 0;
+	rc = 0;
+	vc = 0;
+
+	while (outbuf[i])
+	{
+		switch (outbuf[i])
+		{
+			case ' ':
+				vc++;
+				break;
+
+			case '\t':
+				vc = (vc & ~7) + 8;
+				break;
+
+			default:
+				while (rc / 8 != vc / 8)
+				{
+					if ((rc & 7) == 7)
+						fputc (' ',F);
+					else
+						fputc ('\t',F);
+					rc = (rc & ~7) + 8;
+				}
+
+				while (rc != vc)
+				{
+					fputc (' ',F);
+					rc++;
+				}
+
+				fputc (outbuf[i],F);
+				rc++;
+				vc++;
+		}
+
+		i++;
+	}
+
+	fputc ('\n',F);
+	outi = 0;
+}
+
+void rpc (int c)
+{
+	outbuf[outi++] = c;
+	assert (outi < sizeof outbuf);
+}
+
+void rps (char *s)
+{
+	while (*s)
+		rpc (*s++);
+}
+
+void centre (char *s)
+{
+	int i;
+
+	for (i = (79 - strlen (s)) / 2; i; i--)
+		rpc (' ');
+	rps (s);
+	rnl ();
+}
+
+void rpstrlist (strlist *S)
+{
+	while (S)
+	{
+		rps (S->s);
+		rnl ();
+		S = S->next;
+	}
+}
+
+void centrestrlist (char *s,strlist *S)
+{
+	if (S)
+	{
+		rnl ();
+		centre (s);
+		rnl ();
+
+		rpstrlist (S);
+	}
+}
+
+void rparagraph (char *s,int indent,int mark)
+{
+	strlist *S;
+
+	S = 0;
+	sparagraph (&S,s,indent,mark);
+	rpstrlist (S);
+	freelist (S);
+}
+
+void rpunit (faction *f,region *r,unit *u,int indent,int battle)
+{
+	strlist *S;
+
+	S = 0;
+	spunit (&S,f,r,u,indent,battle);
+	rpstrlist (S);
+	freelist (S);
+}
+
+void report (faction *f)
+{
+	int i;
+	int dh;
+	int anyunits;
+	rfaction *rf;
+	region *r;
+	building *b;
+	ship *sh;
+	unit *u;
+	strlist *S;
+
+	if (strcmp (f->addr,"n/a"))
+		sprintf (buf,"reports/%d.r",f->no);
+	else
+		sprintf (buf,"nreports/%d.r",f->no);
+	cfopen (buf,"w");
+
+	printf ("Writing report for %s...\n",factionid (f));
+
+	centre ("Atlantis Turn Report");
+	centre (factionid (f));
+	centre (gamedate ());
+
+	centrestrlist ("Mistakes",f->mistakes);
+	centrestrlist ("Messages",f->messages);
+
+	if (f->battles || f->events)
+	{
+		rnl ();
+		centre ("Events During Turn");
+		rnl ();
+
+		for (S = f->battles; S; S = S->next)
+		{
+			rps (S->s);
+			rnl ();
+		}
+
+		if (f->battles && f->events)
+			rnl ();
+
+		for (S = f->events; S; S = S->next)
+		{
+			rps (S->s);
+			rnl ();
+		}
+	}
+
+	for (i = 0; i != MAXSPELLS; i++)
+		if (f->showdata[i])
+			break;
+
+	if (i != MAXSPELLS)
+	{
+		rnl ();
+		centre ("Spells Acquired");
+
+		for (i = 0; i != MAXSPELLS; i++)
+			if (f->showdata[i])
+			{
+				rnl ();
+				centre (spellnames[i]);
+				sprintf (buf,"Level %d",spelllevel[i]);
+				centre (buf);
+				rnl ();
+
+				rparagraph (spelldata[i],0,0);
+			}
+	}
+
+	rnl ();
+	centre ("Current Status");
+
+	if (f->allies)
+	{
+		dh = 0;
+		strcpy (buf,"You are allied to ");
+
+		for (rf = f->allies; rf; rf = rf->next)
+		{
+			if (dh)
+				scat (", ");
+			dh = 1;
+			scat (factionid (rf->faction));
+		}
+
+		scat (".");
+		rnl ();
+		rparagraph (buf,0,0);
+	}
+
+	anyunits = 0;
+
+	for (r = regions; r; r = r->next)
+	{
+		for (u = r->units; u; u = u->next)
+			if (u->faction == f)
+				break;
+		if (!u)
+			continue;
+
+		anyunits = 1;
+
+		sprintf (buf,"%s, %s",regionid (r),terrainnames[r->terrain]);
+
+		if (r->peasants)
+		{
+			scat (", peasants: ");
+			icat (r->peasants);
+
+			if (r->money)
+			{
+				scat (", $");
+				icat (r->money);
+			}
+		}
+
+		scat (".");
+		rnl ();
+		rparagraph (buf,0,0);
+
+		dh = 0;
+
+		for (b = r->buildings; b; b = b->next)
+		{
+			sprintf (buf,"%s, size %d",buildingid (b),b->size);
+
+			if (b->display[0])
+			{
+				scat ("; ");
+				scat (b->display);
+			}
+
+			scat (".");
+
+			if (dh)
+				rnl ();
+
+			dh = 1;
+
+			rparagraph (buf,4,0);
+
+			for (u = r->units; u; u = u->next)
+				if (u->building == b && u->owner)
+				{
+					rpunit (f,r,u,8,0);
+					break;
+				}
+
+			for (u = r->units; u; u = u->next)
+				if (u->building == b && !u->owner)
+					rpunit (f,r,u,8,0);
+		}
+
+		for (sh = r->ships; sh; sh = sh->next)
+		{
+			sprintf (buf,"%s, %s",shipid (sh),shiptypenames[sh->type]);
+			if (sh->left)
+				scat (", under construction");
+
+			if (sh->display[0])
+			{
+				scat ("; ");
+				scat (sh->display);
+			}
+
+			scat (".");
+
+			if (dh)
+				rnl ();
+
+			dh = 1;
+
+			rparagraph (buf,4,0);
+
+			for (u = r->units; u; u = u->next)
+				if (u->ship == sh && u->owner)
+				{
+					rpunit (f,r,u,8,0);
+					break;
+				}
+
+			for (u = r->units; u; u = u->next)
+				if (u->ship == sh && !u->owner)
+					rpunit (f,r,u,8,0);
+		}
+
+		dh = 0;
+
+		for (u = r->units; u; u = u->next)
+			if (!u->building && !u->ship && cansee (f,r,u))
+			{
+				if (!dh && (r->buildings || r->ships))
+				{
+					rnl ();
+					dh = 1;
+				}
+
+				rpunit (f,r,u,4,0);
+			}
+	}
+
+	if (!anyunits)
+	{
+		rnl ();
+		rparagraph ("Unfortunately your faction has been wiped out. Please "
+						"contact the moderator if you wish to play again.",0,0);
+	}
+
+	fclose (F);
+}
+
+void reports (void)
+{
+	glob_t	fd;
+	int	i;
+	faction *f;
+
+	mkdir ("reports",(S_IRWXU|S_IRWXG|S_IRWXO));
+	mkdir ("nreports",(S_IRWXU|S_IRWXG|S_IRWXO));
+
+	if(glob("reports/*.*", GLOB_NOSORT, NULL, &fd)==0) {
+		for(i=0; i<fd.gl_pathc; i++)
+			unlink(fd.gl_pathv[i]);
+		globfree(&fd);
+	}
+
+	if(glob("nreports/*.*", GLOB_NOSORT, NULL, &fd)==0) {
+		for(i=0; i<fd.gl_pathc; i++)
+			unlink(fd.gl_pathv[i]);
+		globfree(&fd);
+	}
+
+	for (f = factions; f; f = f->next)
+		report (f);
+
+	cfopen ("send","w");
+	puts ("Writing send file...");
+
+	for (f = factions; f; f = f->next)
+		if (strcmp (f->addr,"n/a"))
+		{
+			fprintf (F,"mail %d.r\n",f->no);
+			fprintf (F,"in%%\"%s\"\n",f->addr);
+			fprintf (F,"Atlantis Report for %s\n",gamedate ());
+		}
+
+	fclose (F);
+
+	cfopen ("maillist","w");
+	puts ("Writing maillist file...");
+
+	for (f = factions; f; f = f->next)
+		if (strcmp (f->addr,"n/a"))
+			fprintf (F,"%s\n",f->addr);
+
+	fclose (F);
+}
+
+int reportcasualtiesdh;
+
+void reportcasualties (unit *u)
+{
+	if (!u->dead)
+		return;
+
+	if (!reportcasualtiesdh)
+	{
+		battlerecord ("");
+		reportcasualtiesdh = 1;
+	}
+
+	if (u->number == 1)
+		sprintf (buf,"%s is dead.",unitid (u));
+	else
+		if (u->dead == u->number)
+			sprintf (buf,"%s is wiped out.",unitid (u));
+		else
+			sprintf (buf,"%s loses %d.",unitid (u),u->dead);
+	battlerecord (buf);
+}
+
+int norders;
+order *oa;
+
+void expandorders (region *r,order *orders)
+{
+	int i,j;
+	unit *u;
+	order *o;
+
+	for (u = r->units; u; u = u->next)
+		u->n = -1;
+
+	norders = 0;
+
+	for (o = orders; o; o = o->next)
+		norders += o->qty;
+
+	oa = cmalloc (norders * sizeof (order));
+
+	i = 0;
+
+	for (o = orders; o; o = o->next)
+		for (j = o->qty; j; j--)
+		{
+			oa[i].unit = o->unit;
+			oa[i].unit->n = 0;
+			i++;
+		}
+
+	freelist (orders);
+
+	scramble (oa,norders,sizeof (order));
+}
+
+void removenullfactions (void)
+{
+	faction *f,*f2,*f3;
+	rfaction *rf,*rf2;
+
+	for (f = factions; f;)
+	{
+		f2 = f->next;
+
+		if (!f->alive)
+		{
+			printf ("Removing %s.\n",f->name);
+
+			for (f3 = factions; f3; f3 = f3->next)
+				for (rf = f3->allies; rf;)
+				{
+					rf2 = rf->next;
+
+					if (rf->faction == f)
+						removelist (&f3->allies,rf);
+
+					rf = rf2;
+				}
+
+			freelist (f->allies);
+			freelist (f->mistakes);
+			freelist (f->messages);
+			freelist (f->battles);
+			freelist (f->events);
+
+			removelist (&factions,f);
+		}
+
+		f = f2;
+	}
+}
+
+itemweight (unit *u)
+{
+	int i;
+	int n;
+
+	n = 0;
+
+	for (i = 0; i != MAXITEMS; i++)
+		switch (i)
+		{
+			case I_STONE:
+				n += u->items[i] * 50;
+				break;
+
+			case I_HORSE:
+				break;
+
+			default:
+				n += u->items[i];
+		}
+
+	return n;
+}
+
+horseweight (unit *u)
+{
+	int i;
+	int n;
+
+	n = 0;
+
+	for (i = 0; i != MAXITEMS; i++)
+		switch (i)
+		{
+			case I_HORSE:
+				n += u->items[i] * 50;
+				break;
+		}
+
+	return n;
+}
+
+canmove (unit *u)
+{
+	return itemweight (u) - horseweight (u) - (u->number * 5) <= 0;
+}
+
+canride (unit *u)
+{
+	return itemweight (u) - horseweight (u) + (u->number * 10) <= 0;
+}
+
+cansail (region *r,ship *sh)
+{
+	int n;
+	unit *u;
+
+	n = 0;
+
+	for (u = r->units; u; u = u->next)
+		if (u->ship == sh)
+			n += itemweight (u) + horseweight (u) + (u->number * 10);
+
+	return n <= shipcapacity[sh->type];
+}
+
+spellitem (int i)
+{
+	if (i < SP_MAKE_AMULET_OF_DARKNESS || i > SP_MAKE_WAND_OF_TELEPORTATION)
+		return -1;
+	return i - SP_MAKE_AMULET_OF_DARKNESS + I_AMULET_OF_DARKNESS;
+}
+
+cancast (unit *u,int i)
+{
+	if (u->spells[i])
+		return u->number;
+
+	if (!effskill (u,SK_MAGIC))
+		return 0;
+
+	switch (i)
+	{
+		case SP_BLACK_WIND:
+			return u->items[I_AMULET_OF_DARKNESS];
+
+		case SP_FIREBALL:
+			return u->items[I_STAFF_OF_FIRE];
+
+		case SP_HAND_OF_DEATH:
+			return u->items[I_AMULET_OF_DEATH];
+
+		case SP_LIGHTNING_BOLT:
+			return u->items[I_STAFF_OF_LIGHTNING];
+
+		case SP_TELEPORT:
+			return min (u->number,u->items[I_WAND_OF_TELEPORTATION]);
+	}
+
+	return 0;
+}
+
+magicians (faction *f)
+{
+	int n;
+	region *r;
+	unit *u;
+
+	n = 0;
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			if (u->skills[SK_MAGIC] && u->faction == f)
+				n += u->number;
+
+	return n;
+}
+
+region *movewhere (region *r)
+{
+	region *r2;
+
+	r2 = 0;
+
+	switch (getkeyword ())
+	{
+		case K_NORTH:
+			if (!r->connect[0])
+				makeblock (r->x,r->y - 1);
+			r2 = r->connect[0];
+			break;
+
+		case K_SOUTH:
+			if (!r->connect[1])
+				makeblock (r->x,r->y + 1);
+			r2 = r->connect[1];
+			break;
+
+		case K_EAST:
+			if (!r->connect[2])
+				makeblock (r->x + 1,r->y);
+			r2 = r->connect[2];
+			break;
+
+		case K_WEST:
+			if (!r->connect[3])
+				makeblock (r->x - 1,r->y);
+			r2 = r->connect[3];
+			break;
+	}
+
+	return r2;
+}
+
+char *strlwr(char *s)
+{
+	for(; *s; s++)
+		*s=tolower(*s);
+}
+
+void processorders (void)
+{
+	int i,j,k;
+	int n,m;
+	int nfactions;
+	int fno;
+	int winnercasualties;
+	int deadpeasants;
+	int taxed;
+	int availmoney;
+	int teaching;
+	int maxtactics[2];
+	int leader[2];
+	int lmoney;
+	int dh;
+	static litems[MAXITEMS];
+	char *s,*s2;
+	faction *f,*f2,**fa;
+	rfaction *rf;
+	region *r,*r2;
+	building *b;
+	ship *sh;
+	unit *u,*u2,*u3,*u4;
+	static unit *uv[100];
+	troop *t,*troops,**tp;
+	order *o,*taxorders,*recruitorders,*entertainorders,*workorders;
+	static order *produceorders[MAXITEMS];
+	strlist *S,*S2;
+
+			/* FORM orders */
+
+	puts ("Processing FORM orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S;)
+				switch (igetkeyword (S->s))
+				{
+					case K_FORM:
+						u2 = createunit (r);
+
+						u2->alias = geti ();
+						if (u2->alias == 0)
+							u2->alias = geti ();
+
+						u2->faction = u->faction;
+						u2->building = u->building;
+						u2->ship = u->ship;
+						u2->behind = u->behind;
+						u2->guard = u->guard;
+
+						S = S->next;
+
+						while (S)
+						{
+							if (igetkeyword (S->s) == K_END)
+							{
+								S = S->next;
+								break;
+							}
+
+							S2 = S->next;
+							translist (&u->orders,&u2->orders,S);
+							S = S2;
+						}
+
+						break;
+
+					default:
+						S = S->next;
+				}
+
+			/* Instant orders - diplomacy etc. */
+
+	puts ("Processing instant orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case -1:
+						mistake2 (u,S,"Order not recognized");
+						break;
+
+					case K_ACCEPT:
+						togglerf (u,S,&u->faction->accept);
+						break;
+
+					case K_ADDRESS:
+						s = getstr ();
+
+						if (!s[0])
+						{
+							mistake2 (u,S,"No address given");
+							break;
+						}
+
+						nstrcpy (u->faction->addr,s,NAMESIZE);
+						for (s = u->faction->addr; *s; s++)
+							if (*s == ' ')
+								*s = '_';
+
+						printf ("%s is changing address to %s.\n",u->faction->name,
+								  u->faction->addr);
+						break;
+
+					case K_ADMIT:
+						togglerf (u,S,&u->faction->admit);
+						break;
+
+					case K_ALLY:
+						f = getfaction ();
+
+						if (f == 0)
+						{
+							mistake2 (u,S,"Faction not found");
+							break;
+						}
+
+						if (f == u->faction)
+							break;
+
+						if (geti ())
+						{
+							for (rf = u->faction->allies; rf; rf = rf->next)
+								if (rf->faction == f)
+									break;
+
+							if (!rf)
+							{
+								rf = cmalloc (sizeof (rfaction));
+								rf->faction = f;
+								addlist (&u->faction->allies,rf);
+							}
+						}
+						else
+							for (rf = u->faction->allies; rf; rf = rf->next)
+								if (rf->faction == f)
+								{
+									removelist (&u->faction->allies,rf);
+									break;
+								}
+
+						break;
+
+					case K_BEHIND:
+						u->behind = geti ();
+						break;
+
+					case K_COMBAT:
+						s = getstr ();
+
+						if (!s[0])
+						{
+							u->combatspell = -1;
+							break;
+						}
+
+						i = findspell (s);
+
+						if (i < 0 || !cancast (u,i))
+						{
+							mistake2 (u,S,"Spell not found");
+							break;
+						}
+
+						if (!iscombatspell[i])
+						{
+							mistake2 (u,S,"Not a combat spell");
+							break;
+						}
+
+						u->combatspell = i;
+						break;
+
+					case K_DISPLAY:
+						s = 0;
+
+						switch (getkeyword ())
+						{
+							case K_BUILDING:
+								if (!u->building)
+								{
+									mistake2 (u,S,"Not in a building");
+									break;
+								}
+
+								if (!u->owner)
+								{
+									mistake2 (u,S,"Building not owned by you");
+									break;
+								}
+
+								s = u->building->display;
+								break;
+
+							case K_SHIP:
+								if (!u->ship)
+								{
+									mistake2 (u,S,"Not in a ship");
+									break;
+								}
+
+								if (!u->owner)
+								{
+									mistake2 (u,S,"Ship not owned by you");
+									break;
+								}
+
+								s = u->ship->display;
+								break;
+
+							case K_UNIT:
+								s = u->display;
+								break;
+
+							default:
+								mistake2 (u,S,"Order not recognized");
+								break;
+						}
+
+						if (!s)
+							break;
+
+						s2 = getstr ();
+
+						i = strlen (s2);
+						if (i && s2[i - 1] == '.')
+							s2[i - 1] = 0;
+
+						nstrcpy (s,s2,DISPLAYSIZE);
+						break;
+
+					case K_GUARD:
+						if (geti () == 0)
+							u->guard = 0;
+						break;
+
+					case K_NAME:
+						s = 0;
+
+						switch (getkeyword ())
+						{
+							case K_BUILDING:
+								if (!u->building)
+								{
+									mistake2 (u,S,"Not in a building");
+									break;
+								}
+
+								if (!u->owner)
+								{
+									mistake2 (u,S,"Building not owned by you");
+									break;
+								}
+
+								s = u->building->name;
+								break;
+
+							case K_FACTION:
+								s = u->faction->name;
+								break;
+
+							case K_SHIP:
+								if (!u->ship)
+								{
+									mistake2 (u,S,"Not in a ship");
+									break;
+								}
+
+								if (!u->owner)
+								{
+									mistake2 (u,S,"Ship not owned by you");
+									break;
+								}
+
+								s = u->ship->name;
+								break;
+
+							case K_UNIT:
+								s = u->name;
+								break;
+
+							default:
+								mistake2 (u,S,"Order not recognized");
+								break;
+						}
+
+						if (!s)
+							break;
+
+						s2 = getstr ();
+
+						if (!s2[0])
+						{
+							mistake2 (u,S,"No name given");
+							break;
+						}
+
+						for (i = 0; s2[i]; i++)
+							if (s2[i] == '(')
+								break;
+
+						if (s2[i])
+						{
+							mistake2 (u,S,"Names cannot contain brackets");
+							break;
+						}
+
+						nstrcpy (s,s2,NAMESIZE);
+						break;
+
+					case K_RESHOW:
+						i = getspell ();
+
+						if (i < 0 || !u->faction->seendata[i])
+						{
+							mistake2 (u,S,"Spell not found");
+							break;
+						}
+
+						u->faction->showdata[i] = 1;
+						break;
+				}
+
+			/* FIND orders */
+
+	puts ("Processing FIND orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_FIND:
+						f = getfaction ();
+
+						if (f == 0)
+						{
+							mistake2 (u,S,"Faction not found");
+							break;
+						}
+
+						sprintf (buf,"The address of %s is %s.",factionid (f),f->addr);
+						sparagraph (&u->faction->messages,buf,0,0);
+						break;
+				}
+
+			/* Leaving and entering buildings and ships */
+
+	puts ("Processing leaving and entering orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_BOARD:
+						sh = getship (r);
+
+						if (!sh)
+						{
+							mistake2 (u,S,"Ship not found");
+							break;
+						}
+
+						if (!mayboard (r,u,sh))
+						{
+							mistake2 (u,S,"Not permitted to board");
+							break;
+						}
+
+						leave (r,u);
+						u->ship = sh;
+						u->owner = 0;
+						if (shipowner (r,sh) == 0)
+							u->owner = 1;
+						break;
+
+					case K_ENTER:
+						b = getbuilding (r);
+
+						if (!b)
+						{
+							mistake2 (u,S,"Building not found");
+							break;
+						}
+
+						if (!mayenter (r,u,b))
+						{
+							mistake2 (u,S,"Not permitted to enter");
+							break;
+						}
+
+						leave (r,u);
+						u->building = b;
+						u->owner = 0;
+						if (buildingowner (r,b) == 0)
+							u->owner = 1;
+						break;
+
+					case K_LEAVE:
+						if (r->terrain == T_OCEAN)
+						{
+							mistake2 (u,S,"Ship is at sea");
+							break;
+						}
+
+						leave (r,u);
+						break;
+
+					case K_PROMOTE:
+						u2 = getunit (r,u);
+
+						if (!u2)
+						{
+							mistake2 (u,S,"Unit not found");
+							break;
+						}
+
+						if (!u->building && !u->ship)
+						{
+							mistake2 (u,S,"No building or ship to transfer ownership of");
+							break;
+						}
+
+						if (!u->owner)
+						{
+							mistake2 (u,S,"Not owned by you");
+							break;
+						}
+
+						if (!accepts (u2,u))
+						{
+							mistake2 (u,S,"Unit does not accept ownership");
+							break;
+						}
+
+						if (u->building)
+						{
+							if (u2->building != u->building)
+							{
+								mistake2 (u,S,"Unit not in same building");
+								break;
+							}
+						}
+						else
+							if (u2->ship != u->ship)
+							{
+								mistake2 (u,S,"Unit not on same ship");
+								break;
+							}
+
+						u->owner = 0;
+						u2->owner = 1;
+						break;
+				}
+
+			/* Combat */
+
+	puts ("Processing ATTACK orders...");
+
+	nfactions = listlen (factions);
+	fa = cmalloc (nfactions * sizeof (faction *));
+
+	for (r = regions; r; r = r->next)
+	{
+				/* Create randomly sorted list of factions */
+
+		for (f = factions, i = 0; f; f = f->next, i++)
+			fa[i] = f;
+		scramble (fa,nfactions,sizeof (faction *));
+
+				/* Handle each faction's attack orders */
+
+		for (fno = 0; fno != nfactions; fno++)
+		{
+			f = fa[fno];
+
+			for (u = r->units; u; u = u->next)
+				if (u->faction == f)
+					for (S = u->orders; S; S = S->next)
+						if (igetkeyword (S->s) == K_ATTACK)
+						{
+							u2 = getunit (r,u);
+
+							if (!u2 && !getunitpeasants)
+							{
+								mistake2 (u,S,"Unit not found");
+								continue;
+							}
+
+							if (u2 && u2->faction == f)
+							{
+								mistake2 (u,S,"One of your units");
+								continue;
+							}
+
+							if (isallied (u,u2))
+							{
+								mistake2 (u,S,"An allied unit");
+								continue;
+							}
+
+									/* Draw up troops for the battle */
+
+							for (b = r->buildings; b; b = b->next)
+								b->sizeleft = b->size;
+
+							troops = 0;
+							tp = &troops;
+							left[0] = left[1] = 0;
+							infront[0] = infront[1] = 0;
+
+									/* If peasants are defenders */
+
+							if (!u2)
+							{
+								for (i = r->peasants; i; i--)
+								{
+									t = cmalloc (sizeof (troop));
+									memset (t,0,sizeof (troop));
+									addlist2 (tp,t);
+								}
+
+								left[0] = r->peasants;
+								infront[0] = r->peasants;
+							}
+
+									/* What units are involved? */
+
+							for (f2 = factions; f2; f2 = f2->next)
+								f2->attacking = 0;
+
+							for (u3 = r->units; u3; u3 = u3->next)
+								for (S2 = u3->orders; S2; S2 = S2->next)
+									if (igetkeyword (S2->s) == K_ATTACK)
+									{
+										u4 = getunit (r,u3);
+
+										if ((getunitpeasants && !u2) ||
+											 (u4 && u4->faction == u2->faction &&
+											  !isallied (u3,u4)))
+										{
+											u3->faction->attacking = 1;
+											S2->s[0] = 0;
+											break;
+										}
+									}
+
+							for (u3 = r->units; u3; u3 = u3->next)
+							{
+								u3->side = -1;
+
+								if (!u3->number)
+									continue;
+
+								if (u3->faction->attacking)
+								{
+									u3->side = 1;
+									tp = maketroops (tp,u3,r->terrain);
+								}
+								else if (isallied (u3,u2))
+								{
+									u3->side = 0;
+									tp = maketroops (tp,u3,r->terrain);
+								}
+							}
+
+							*tp = 0;
+
+									/* If only one side shows up, cancel */
+
+							if (!left[0] || !left[1])
+							{
+								freelist (troops);
+								continue;
+							}
+
+									/* Set up array of troops */
+
+							ntroops = listlen (troops);
+							ta = cmalloc (ntroops * sizeof (troop));
+							for (t = troops, i = 0; t; t = t->next, i++)
+								ta[i] = *t;
+							freelist (troops);
+							scramble (ta,ntroops,sizeof (troop));
+
+							initial[0] = left[0];
+							initial[1] = left[1];
+							shields[0] = 0;
+							shields[1] = 0;
+							runeswords[0] = 0;
+							runeswords[1] = 0;
+
+							lmoney = 0;
+							memset (litems,0,sizeof litems);
+
+									/* Initial attack message */
+
+							for (f2 = factions; f2; f2 = f2->next)
+							{
+								f2->seesbattle = ispresent (f2,r);
+								if (f2->seesbattle && f2->battles)
+									addstrlist (&f2->battles,"");
+							}
+
+							if (u2)
+								strcpy (buf2,unitid (u2));
+							else
+								strcpy (buf2,"the peasants");
+							sprintf (buf,"%s attacks %s in %s!",unitid (u),buf2,regionid (r));
+							battlerecord (buf);
+
+									/* List sides */
+
+							battlerecord ("");
+
+							battlepunit (r,u);
+
+							for (u3 = r->units; u3; u3 = u3->next)
+								if (u3->side == 1 && u3 != u)
+									battlepunit (r,u3);
+
+							battlerecord ("");
+
+							if (u2)
+								battlepunit (r,u2);
+							else
+							{
+								sprintf (buf,"Peasants, number: %d",r->peasants);
+								for (f2 = factions; f2; f2 = f2->next)
+									if (f2->seesbattle)
+										sparagraph (&f2->battles,buf,4,'-');
+							}
+
+							for (u3 = r->units; u3; u3 = u3->next)
+								if (u3->side == 0 && u3 != u2)
+									battlepunit (r,u3);
+
+							battlerecord ("");
+
+									/* Does one side have an advantage in tactics? */
+
+							maxtactics[0] = 0;
+							maxtactics[1] = 0;
+
+							for (i = 0; i != ntroops; i++)
+								if (ta[i].unit)
+								{
+									j = effskill (ta[i].unit,SK_TACTICS);
+
+									if (maxtactics[ta[i].side] < j)
+									{
+										leader[ta[i].side] = i;
+										maxtactics[ta[i].side] = j;
+									}
+								}
+
+							attacker.side = -1;
+							if (maxtactics[0] > maxtactics[1])
+								attacker.side = 0;
+							if (maxtactics[1] > maxtactics[0])
+								attacker.side = 1;
+
+									/* Better leader gets free round of attacks */
+
+							if (attacker.side >= 0)
+							{
+										/* Note the fact in the battle report */
+
+								if (attacker.side)
+									sprintf (buf,"%s gets a free round of attacks!",unitid (u));
+								else
+									if (u2)
+										sprintf (buf,"%s gets a free round of attacks!",unitid (u2));
+									else
+										sprintf (buf,"The peasants get a free round of attacks!");
+								battlerecord (buf);
+
+										/* Number of troops to attack */
+
+								toattack[attacker.side] = 0;
+
+								for (i = 0; i != ntroops; i++)
+								{
+									ta[i].attacked = 1;
+
+									if (ta[i].side == attacker.side)
+									{
+										ta[i].attacked = 0;
+										toattack[attacker.side]++;
+									}
+								}
+
+										/* Do round of attacks */
+
+								do
+									doshot ();
+								while (toattack[attacker.side] && left[defender.side]);
+							}
+
+									/* Handle main body of battle */
+
+							toattack[0] = 0;
+							toattack[1] = 0;
+
+							while (left[defender.side])
+							{
+										/* End of a round */
+
+								if (toattack[0] == 0 && toattack[1] == 0)
+									for (i = 0; i != ntroops; i++)
+									{
+										ta[i].attacked = 1;
+
+										if (!ta[i].status)
+										{
+											ta[i].attacked = 0;
+											toattack[ta[i].side]++;
+										}
+									}
+
+								doshot ();
+							}
+
+									/* Report on winner */
+
+							if (attacker.side)
+								sprintf (buf,"%s wins the battle!",unitid (u));
+							else
+								if (u2)
+									sprintf (buf,"%s wins the battle!",unitid (u2));
+								else
+									sprintf (buf,"The peasants win the battle!");
+							battlerecord (buf);
+
+									/* Has winner suffered any casualties? */
+
+							winnercasualties = 0;
+
+							for (i = 0; i != ntroops; i++)
+								if (ta[i].side == attacker.side && ta[i].status)
+								{
+									winnercasualties = 1;
+									break;
+								}
+
+									/* Can wounded be healed? */
+
+							n = 0;
+
+							for (i = 0; i != ntroops &&
+											n != initial[attacker.side] -
+												  left[attacker.side]; i++)
+								if (!ta[i].status && ta[i].canheal)
+								{
+									k = lovar (50 * (1 + ta[i].power));
+									k = min (k,initial[attacker.side] -
+													  left[attacker.side] - n);
+
+									sprintf (buf,"%s heals %d wounded.",
+												unitid (ta[i].unit),k);
+									battlerecord (buf);
+
+									n += k;
+								}
+
+							while (--n >= 0)
+							{
+								do
+									i = rnd () % ntroops;
+								while (!ta[i].status || ta[i].side != attacker.side);
+
+								ta[i].status = 0;
+							}
+
+									/* Count the casualties */
+
+							deadpeasants = 0;
+
+							for (u3 = r->units; u3; u3 = u3->next)
+								u3->dead = 0;
+
+							for (i = 0; i != ntroops; i++)
+								if (ta[i].unit)
+									ta[i].unit->dead += ta[i].status;
+								else
+									deadpeasants += ta[i].status;
+
+									/* Report the casualties */
+
+							reportcasualtiesdh = 0;
+
+							if (attacker.side)
+							{
+								reportcasualties (u);
+
+								for (u3 = r->units; u3; u3 = u3->next)
+									if (u3->side == 1 && u3 != u)
+										reportcasualties (u3);
+							}
+							else
+							{
+								if (u2)
+									reportcasualties (u2);
+								else
+									if (deadpeasants)
+									{
+										battlerecord ("");
+										reportcasualtiesdh = 1;
+										sprintf (buf,"The peasants lose %d.",deadpeasants);
+										battlerecord (buf);
+									}
+
+								for (u3 = r->units; u3; u3 = u3->next)
+									if (u3->side == 0 && u3 != u2)
+										reportcasualties (u3);
+							}
+
+									/* Dead peasants */
+
+							k = r->peasants - deadpeasants;
+
+							j = distribute (r->peasants,k,r->money);
+							lmoney += r->money - j;
+							r->money = j;
+
+							r->peasants = k;
+
+									/* Adjust units */
+
+							for (u3 = r->units; u3; u3 = u3->next)
+							{
+								k = u3->number - u3->dead;
+
+										/* Redistribute items and skills */
+
+								if (u3->side == defender.side)
+								{
+									j = distribute (u3->number,k,u3->money);
+									lmoney += u3->money - j;
+									u3->money = j;
+
+									for (i = 0; i != MAXITEMS; i++)
+									{
+										j = distribute (u3->number,k,u3->items[i]);
+										litems[i] += u3->items[i] - j;
+										u3->items[i] = j;
+									}
+								}
+
+								for (i = 0; i != MAXSKILLS; i++)
+									u3->skills[i] = distribute (u3->number,k,u3->skills[i]);
+
+										/* Adjust unit numbers */
+
+								u3->number = k;
+
+										/* Need this flag cleared for reporting of loot */
+
+								u3->n = 0;
+							}
+
+									/* Distribute loot */
+
+							for (n = lmoney; n; n--)
+							{
+								do
+									j = rnd () % ntroops;
+								while (ta[j].status || ta[j].side != attacker.side);
+
+								if (ta[j].unit)
+								{
+									ta[j].unit->money++;
+									ta[j].unit->n++;
+								}
+								else
+									r->money++;
+							}
+
+							for (i = 0; i != MAXITEMS; i++)
+								for (n = litems[i]; n; n--)
+									if (i <= I_STONE || rnd () & 1)
+									{
+										do
+											j = rnd () % ntroops;
+										while (ta[j].status || ta[j].side != attacker.side);
+
+										if (ta[j].unit)
+										{
+											if (!ta[j].unit->litems)
+											{
+												ta[j].unit->litems = cmalloc (MAXITEMS *
+																						sizeof (int));
+												memset (ta[j].unit->litems,0,
+														  MAXITEMS * sizeof (int));
+											}
+
+											ta[j].unit->items[i]++;
+											ta[j].unit->litems[i]++;
+										}
+									}
+
+									/* Report loot */
+
+							for (f2 = factions; f2; f2 = f2->next)
+								f2->dh = 0;
+
+							for (u3 = r->units; u3; u3 = u3->next)
+								if (u3->n || u3->litems)
+								{
+									sprintf (buf,"%s finds ",unitid (u3));
+									dh = 0;
+
+									if (u3->n)
+									{
+										scat ("$");
+										icat (u3->n);
+										dh = 1;
+									}
+
+									if (u3->litems)
+									{
+										for (i = 0; i != MAXITEMS; i++)
+											if (u3->litems[i])
+											{
+												if (dh)
+													scat (", ");
+												dh = 1;
+
+												icat (u3->litems[i]);
+												scat (" ");
+
+												if (u3->litems[i] == 1)
+													scat (itemnames[0][i]);
+												else
+													scat (itemnames[1][i]);
+											}
+
+										free (u3->litems);
+										u3->litems = 0;
+									}
+
+									if (!u3->faction->dh)
+									{
+										addbattle (u3->faction,"");
+										u3->faction->dh = 1;
+									}
+
+									scat (".");
+									addbattle (u3->faction,buf);
+								}
+
+									/* Does winner get combat experience? */
+
+							if (winnercasualties)
+							{
+								if (maxtactics[attacker.side] &&
+									 !ta[leader[attacker.side]].status)
+									ta[leader[attacker.side]].unit->
+											skills[SK_TACTICS] += COMBATEXP;
+
+								for (i = 0; i != ntroops; i++)
+									if (ta[i].unit &&
+										 !ta[i].status &&
+										 ta[i].side == attacker.side)
+										switch (ta[i].weapon)
+										{
+											case I_SWORD:
+												ta[i].unit->skills[SK_SWORD] += COMBATEXP;
+												break;
+
+											case I_CROSSBOW:
+												ta[i].unit->skills[SK_CROSSBOW] += COMBATEXP;
+												break;
+
+											case I_LONGBOW:
+												ta[i].unit->skills[SK_LONGBOW] += COMBATEXP;
+												break;
+										}
+							}
+
+							free (ta);
+						}
+		}
+	}
+
+	free (fa);
+
+			/* Economic orders */
+
+	puts ("Processing economic orders...");
+
+	for (r = regions; r; r = r->next)
+	{
+		taxorders = 0;
+		recruitorders = 0;
+
+				/* DEMOLISH, GIVE, PAY, SINK orders */
+
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_DEMOLISH:
+						if (!u->building)
+						{
+							mistake2 (u,S,"Not in a building");
+							break;
+						}
+
+						if (!u->owner)
+						{
+							mistake2 (u,S,"Building not owned by you");
+							break;
+						}
+
+						b = u->building;
+
+						for (u2 = r->units; u2; u2 = u2->next)
+							if (u2->building == b)
+							{
+								u2->building = 0;
+								u2->owner = 0;
+							}
+
+						sprintf (buf,"%s demolishes %s.",unitid (u),buildingid (b));
+						reportevent (r,buf);
+
+						removelist (&r->buildings,b);
+						break;
+
+					case K_GIVE:
+						u2 = getunit (r,u);
+
+						if (!u2 && !getunit0)
+						{
+							mistake2 (u,S,"Unit not found");
+							break;
+						}
+
+						if (u2 && !accepts (u2,u))
+						{
+							mistake2 (u,S,"Unit does not accept your gift");
+							break;
+						}
+
+						s = getstr ();
+						i = findspell (s);
+
+						if (i >= 0)
+						{
+							if (!u2)
+							{
+								mistake2 (u,S,"Unit not found");
+								break;
+							}
+
+							if (!u->spells[i])
+							{
+								mistake2 (u,S,"Spell not found");
+								break;
+							}
+
+							if (spelllevel[i] > (effskill (u2,SK_MAGIC) + 1) / 2)
+							{
+								mistake2 (u,S,"Recipient is not able to learn that spell");
+								break;
+							}
+
+							u2->spells[i] = 1;
+
+							sprintf (buf,"%s gives ",unitid (u));
+							scat (unitid (u2));
+							scat (" the ");
+							scat (spellnames[i]);
+							scat (" spell.");
+							addevent (u->faction,buf);
+							if (u->faction != u2->faction)
+								addevent (u2->faction,buf);
+
+							if (!u2->faction->seendata[i])
+							{
+								u2->faction->seendata[i] = 1;
+								u2->faction->showdata[i] = 1;
+							}
+						}
+						else
+						{
+							n = atoip (s);
+							i = getitem ();
+
+							if (i < 0)
+							{
+								mistake2 (u,S,"Item not recognized");
+								break;
+							}
+
+							if (n > u->items[i])
+								n = u->items[i];
+
+							if (n == 0)
+							{
+								mistake2 (u,S,"Item not available");
+								break;
+							}
+
+							u->items[i] -= n;
+
+							if (!u2)
+							{
+								if (n == 1)
+									sprintf (buf,"%s discards 1 %s.",
+												unitid (u),itemnames[0][i]);
+								else
+									sprintf (buf,"%s discards %d %s.",
+												unitid (u),n,itemnames[1][i]);
+								addevent (u->faction,buf);
+								break;
+							}
+
+							u2->items[i] += n;
+
+							sprintf (buf,"%s gives ",unitid (u));
+							scat (unitid (u2));
+							scat (" ");
+							if (n == 1)
+							{
+								scat ("1 ");
+								scat (itemnames[0][i]);
+							}
+							else
+							{
+								icat (n);
+								scat (" ");
+								scat (itemnames[1][i]);
+							}
+							scat (".");
+							addevent (u->faction,buf);
+							if (u->faction != u2->faction)
+								addevent (u2->faction,buf);
+						}
+
+						break;
+
+					case K_PAY:
+						u2 = getunit (r,u);
+
+						if (!u2 && !getunit0 && !getunitpeasants)
+						{
+							mistake2 (u,S,"Unit not found");
+							break;
+						}
+
+						n = geti ();
+
+						if (n > u->money)
+							n = u->money;
+
+						if (n == 0)
+						{
+							mistake2 (u,S,"No money available");
+							break;
+						}
+
+						u->money -= n;
+
+						if (u2)
+						{
+							u2->money += n;
+
+							sprintf (buf,"%s pays ",unitid (u));
+							scat (unitid (u2));
+							scat (" $");
+							icat (n);
+							scat (".");
+							if (u->faction != u2->faction)
+								addevent (u2->faction,buf);
+						}
+						else
+							if (getunitpeasants)
+							{
+								r->money += n;
+
+								sprintf (buf,"%s pays the peasants $%d.",unitid (u),n);
+							}
+							else
+								sprintf (buf,"%s discards $%d.",unitid (u),n);
+
+						addevent (u->faction,buf);
+						break;
+
+					case K_SINK:
+						if (!u->ship)
+						{
+							mistake2 (u,S,"Not on a ship");
+							break;
+						}
+
+						if (!u->owner)
+						{
+							mistake2 (u,S,"Ship not owned by you");
+							break;
+						}
+
+						if (r->terrain == T_OCEAN)
+						{
+							mistake2 (u,S,"Ship is at sea");
+							break;
+						}
+
+						sh = u->ship;
+
+						for (u2 = r->units; u2; u2 = u2->next)
+							if (u2->ship == sh)
+							{
+								u2->ship = 0;
+								u2->owner = 0;
+							}
+
+						sprintf (buf,"%s sinks %s.",unitid (u),shipid (sh));
+						reportevent (r,buf);
+
+						removelist (&r->ships,sh);
+						break;
+				}
+
+				/* TRANSFER orders */
+
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_TRANSFER:
+						u2 = getunit (r,u);
+
+						if (u2)
+						{
+							if (!accepts (u2,u))
+							{
+								mistake2 (u,S,"Unit does not accept your gift");
+								break;
+							}
+						}
+						else if (!getunitpeasants)
+						{
+							mistake2 (u,S,"Unit not found");
+							break;
+						}
+
+						n = atoip (getstr ());
+
+						if (n > u->number)
+							n = u->number;
+
+						if (n == 0)
+						{
+							mistake2 (u,S,"No people available");
+							break;
+						}
+
+						if (u->skills[SK_MAGIC] && u2)
+						{
+							k = magicians (u2->faction);
+							if (u2->faction != u->faction)
+								k += n;
+							if (!u2->skills[SK_MAGIC])
+								k += u2->number;
+
+							if (k > 3)
+							{
+								mistake2 (u,S,"Only 3 magicians per faction");
+								break;
+							}
+						}
+
+						k = u->number - n;
+
+						for (i = 0; i != MAXSKILLS; i++)
+						{
+							j = distribute (u->number,k,u->skills[i]);
+							if (u2)
+								u2->skills[i] += u->skills[i] - j;
+							u->skills[i] = j;
+						}
+
+						u->number = k;
+
+						if (u2)
+						{
+							u2->number += n;
+
+							for (i = 0; i != MAXSPELLS; i++)
+								if (u->spells[i] && effskill (u2,SK_MAGIC) / 2 >= spelllevel[i])
+									u2->spells[i] = 1;
+
+							sprintf (buf,"%s transfers ",unitid (u));
+							if (k)
+							{
+								icat (n);
+								scat (" ");
+							}
+							scat ("to ");
+							scat (unitid (u2));
+							if (u->faction != u2->faction)
+								addevent (u2->faction,buf);
+						}
+						else
+						{
+							r->peasants += n;
+
+							if (k)
+								sprintf (buf,"%s disbands %d.",unitid (u),n);
+							else
+								sprintf (buf,"%s disbands.",unitid (u));
+						}
+
+						addevent (u->faction,buf);
+						break;
+				}
+
+				/* TAX orders */
+
+		for (u = r->units; u; u = u->next)
+		{
+			taxed = 0;
+
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_TAX:
+						if (taxed)
+							break;
+
+						n = armedmen (u);
+
+						if (!n)
+						{
+							mistake2 (u,S,"Unit is not armed and combat trained");
+							break;
+						}
+
+						for (u2 = r->units; u2; u2 = u2->next)
+							if (u2->guard && u2->number && !admits (u2,u))
+							{
+								sprintf (buf,"%s is on guard",u2->name);
+								mistake2 (u,S,buf);
+								break;
+							}
+
+						if (u2)
+							break;
+
+						o = cmalloc (sizeof (order));
+						o->qty = n * TAXINCOME;
+						o->unit = u;
+						addlist (&taxorders,o);
+						taxed = 1;
+						break;
+				}
+		}
+
+				/* Do taxation */
+
+		for (u = r->units; u; u = u->next)
+			u->n = -1;
+
+		norders = 0;
+
+		for (o = taxorders; o; o = o->next)
+			norders += o->qty / 10;
+
+		oa = cmalloc (norders * sizeof (order));
+
+		i = 0;
+
+		for (o = taxorders; o; o = o->next)
+			for (j = o->qty / 10; j; j--)
+			{
+				oa[i].unit = o->unit;
+				oa[i].unit->n = 0;
+				i++;
+			}
+
+		freelist (taxorders);
+
+		scramble (oa,norders,sizeof (order));
+
+		for (i = 0; i != norders && r->money > 10; i++, r->money -= 10)
+		{
+			oa[i].unit->money += 10;
+			oa[i].unit->n += 10;
+		}
+
+		free (oa);
+
+		for (u = r->units; u; u = u->next)
+			if (u->n >= 0)
+			{
+				sprintf (buf,"%s collects $%d in taxes.",unitid (u),u->n);
+				addevent (u->faction,buf);
+			}
+
+				/* GUARD 1, RECRUIT orders */
+
+		for (u = r->units; u; u = u->next)
+		{
+			availmoney = u->money;
+
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_GUARD:
+						if (geti ())
+							u->guard = 1;
+						break;
+
+					case K_RECRUIT:
+						if (availmoney < RECRUITCOST)
+							break;
+
+						n = geti ();
+
+						if (u->skills[SK_MAGIC] && magicians (u->faction) + n > 3)
+						{
+							mistake2 (u,S,"Only 3 magicians per faction");
+							break;
+						}
+
+						n = min (n,availmoney / RECRUITCOST);
+
+						o = cmalloc (sizeof (order));
+						o->qty = n;
+						o->unit = u;
+						addlist (&recruitorders,o);
+
+						availmoney -= o->qty * RECRUITCOST;
+						break;
+				}
+		}
+
+				/* Do recruiting */
+
+		expandorders (r,recruitorders);
+
+		for (i = 0, n = r->peasants / RECRUITFRACTION; i != norders && n; i++, n--)
+		{
+			oa[i].unit->number++;
+			r->peasants--;
+			oa[i].unit->money -= RECRUITCOST;
+			r->money += RECRUITCOST;
+			oa[i].unit->n++;
+		}
+
+		free (oa);
+
+		for (u = r->units; u; u = u->next)
+			if (u->n >= 0)
+			{
+				sprintf (buf,"%s recruits %d.",unitid (u),u->n);
+				addevent (u->faction,buf);
+			}
+	}
+
+			/* QUIT orders */
+
+	puts ("Processing QUIT orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_QUIT:
+						if (geti () != u->faction->no)
+						{
+							mistake2 (u,S,"Correct faction number not given");
+							break;
+						}
+
+						destroyfaction (u->faction);
+						break;
+				}
+
+			/* Remove players who haven't sent in orders */
+
+	for (f = factions; f; f = f->next)
+		if (turn - f->lastorders > ORDERGAP)
+			destroyfaction (f);
+
+			/* Clear away debris of destroyed factions */
+
+	removeempty ();
+	removenullfactions ();
+
+			/* Set production orders */
+
+	puts ("Setting production orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u; u = u->next)
+		{
+			strcpy (u->thisorder,u->lastorder);
+
+			for (S = u->orders; S; S = S->next)
+				switch (igetkeyword (S->s))
+				{
+					case K_BUILD:
+					case K_CAST:
+					case K_ENTERTAIN:
+					case K_MOVE:
+					case K_PRODUCE:
+					case K_RESEARCH:
+					case K_SAIL:
+					case K_STUDY:
+					case K_TEACH:
+					case K_WORK:
+						nstrcpy (u->thisorder,S->s,sizeof u->thisorder);
+						break;
+				}
+
+			switch (igetkeyword (u->thisorder))
+			{
+				case K_MOVE:
+				case K_SAIL:
+					break;
+
+				default:
+					strcpy (u->lastorder,u->thisorder);
+					strlwr (u->lastorder);
+			}
+		}
+
+			/* MOVE orders */
+
+	puts ("Processing MOVE orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u;)
+		{
+			u2 = u->next;
+
+			switch (igetkeyword (u->thisorder))
+			{
+				case K_MOVE:
+					r2 = movewhere (r);
+
+					if (!r2)
+					{
+						mistakeu (u,"Direction not recognized");
+						break;
+					}
+
+					if (r->terrain == T_OCEAN)
+					{
+						mistakeu (u,"Currently at sea");
+						break;
+					}
+
+					if (r2->terrain == T_OCEAN)
+					{
+						sprintf (buf,"%s discovers that (%d,%d) is ocean.",
+									unitid (u),r2->x,r2->y);
+						addevent (u->faction,buf);
+						break;
+					}
+
+					if (!canmove (u))
+					{
+						mistakeu (u,"Carrying too much weight to move");
+						break;
+					}
+
+					leave (r,u);
+					translist (&r->units,&r2->units,u);
+					u->thisorder[0] = 0;
+
+					sprintf (buf,"%s ",unitid (u));
+					if (canride (u))
+						scat ("rides");
+					else
+						scat ("walks");
+					scat (" from ");
+					scat (regionid (r));
+					scat (" to ");
+					scat (regionid (r2));
+					scat (".");
+					addevent (u->faction,buf);
+					break;
+			}
+
+			u = u2;
+		}
+
+			/* SAIL orders */
+
+	puts ("Processing SAIL orders...");
+
+	for (r = regions; r; r = r->next)
+		for (u = r->units; u;)
+		{
+			u2 = u->next;
+
+			switch (igetkeyword (u->thisorder))
+			{
+				case K_SAIL:
+					r2 = movewhere (r);
+
+					if (!r2)
+					{
+						mistakeu (u,"Direction not recognized");
+						break;
+					}
+
+					if (!u->ship)
+					{
+						mistakeu (u,"Not on a ship");
+						break;
+					}
+
+					if (!u->owner)
+					{
+						mistakeu (u,"Ship not owned by you");
+						break;
+					}
+
+					if (r2->terrain != T_OCEAN && !iscoast (r2))
+					{
+						sprintf (buf,"%s discovers that (%d,%d) is inland.",
+									unitid (u),r2->x,r2->y);
+						addevent (u->faction,buf);
+						break;
+					}
+
+					if (u->ship->left)
+					{
+						mistakeu (u,"Ship still under construction");
+						break;
+					}
+
+					if (!cansail (r,u->ship))
+					{
+						mistakeu (u,"Too heavily loaded to sail");
+						break;
+					}
+
+					translist (&r->ships,&r2->ships,u->ship);
+
+					for (u2 = r->units; u2;)
+					{
+						u3 = u2->next;
+
+						if (u2->ship == u->ship)
+						{
+							translist (&r->units,&r2->units,u2);
+							u2->thisorder[0] = 0;
+						}
+
+						u2 = u3;
+					}
+
+					u->thisorder[0] = 0;
+					break;
+			}
+
+			u = u2;
+		}
+
+			/* Do production orders */
+
+	puts ("Processing production orders...");
+
+	for (r = regions; r; r = r->next)
+	{
+		if (r->terrain == T_OCEAN)
+			continue;
+
+		entertainorders = 0;
+		workorders = 0;
+		memset (produceorders,0,sizeof produceorders);
+
+		for (u = r->units; u; u = u->next)
+			switch (igetkeyword (u->thisorder))
+			{
+				case K_BUILD:
+					switch (i = getkeyword ())
+					{
+						case K_BUILDING:
+							if (!effskill (u,SK_BUILDING))
+							{
+								mistakeu (u,"You don't have the skill");
+								break;
+							}
+
+							if (!u->items[I_STONE])
+							{
+								mistakeu (u,"No stone available");
+								break;
+							}
+
+							b = getbuilding (r);
+
+							if (!b)
+							{
+								b = cmalloc (sizeof (building));
+								memset (b,0,sizeof (building));
+
+								do
+								{
+									b->no++;
+									sprintf (b->name,"Building %d",b->no);
+								}
+								while (findbuilding (b->no));
+
+								addlist (&r->buildings,b);
+
+								leave (r,u);
+								u->building = b;
+								u->owner = 1;
+							}
+
+							n = u->number * effskill (u,SK_BUILDING);
+							n = min (n,u->items[I_STONE]);
+							b->size += n;
+							u->items[I_STONE] -= n;
+
+							u->skills[SK_BUILDING] += n * 10;
+
+							sprintf (buf,"%s adds %d to %s.",unitid (u),n,buildingid (b));
+							addevent (u->faction,buf);
+							break;
+
+						case K_SHIP:
+							if (!effskill (u,SK_SHIPBUILDING))
+							{
+								mistakeu (u,"You don't have the skill");
+								break;
+							}
+
+							if (!u->items[I_WOOD])
+							{
+								mistakeu (u,"No wood available");
+								break;
+							}
+
+							sh = getship (r);
+
+							if (sh == 0)
+							{
+								mistakeu (u,"Ship not found");
+								break;
+							}
+
+							if (!sh->left)
+							{
+								mistakeu (u,"Ship is already complete");
+								break;
+							}
+
+BUILDSHIP:
+							n = u->number * effskill (u,SK_SHIPBUILDING);
+							n = min (n,sh->left);
+							n = min (n,u->items[I_WOOD]);
+							sh->left -= n;
+							u->items[I_WOOD] -= n;
+
+							u->skills[SK_SHIPBUILDING] += n * 10;
+
+							sprintf (buf,"%s adds %d to %s.",unitid (u),n,shipid (sh));
+							addevent (u->faction,buf);
+							break;
+
+						case K_LONGBOAT:
+							i = SH_LONGBOAT;
+							goto CREATESHIP;
+
+						case K_CLIPPER:
+							i = SH_CLIPPER;
+							goto CREATESHIP;
+
+						case K_GALLEON:
+							i = SH_GALLEON;
+							goto CREATESHIP;
+
+CREATESHIP:
+							if (!effskill (u,SK_SHIPBUILDING))
+							{
+								mistakeu (u,"You don't have the skill");
+								break;
+							}
+
+							sh = cmalloc (sizeof (ship));
+							memset (sh,0,sizeof (ship));
+
+							sh->type = i;
+							sh->left = shipcost[i];
+
+							do
+							{
+								sh->no++;
+								sprintf (sh->name,"Ship %d",sh->no);
+							}
+							while (findship (sh->no));
+
+							addlist (&r->ships,sh);
+
+							leave (r,u);
+							u->ship = sh;
+							u->owner = 1;
+							goto BUILDSHIP;
+
+						default:
+							mistakeu (u,"Order not recognized");
+					}
+
+					break;
+
+				case K_ENTERTAIN:
+					o = cmalloc (sizeof (order));
+					o->unit = u;
+					o->qty = u->number * effskill (u,SK_ENTERTAINMENT) * ENTERTAININCOME;
+					addlist (&entertainorders,o);
+					break;
+
+				case K_PRODUCE:
+					i = getitem ();
+
+					if (i < 0 || i > I_PLATE_ARMOR)
+					{
+						mistakeu (u,"Item not recognized");
+						break;
+					}
+
+					n = effskill (u,itemskill[i]);
+
+					if (n == 0)
+					{
+						mistakeu (u,"You don't have the skill");
+						break;
+					}
+
+					if (i == I_PLATE_ARMOR)
+						n /= 3;
+
+					n *= u->number;
+
+					if (i < 4)
+					{
+						o = cmalloc (sizeof (order));
+						o->unit = u;
+						o->qty = n * productivity[r->terrain][i];
+						addlist (&produceorders[i],o);
+					}
+					else
+					{
+						n = min (n,u->items[rawmaterial[i]]);
+
+						if (n == 0)
+						{
+							mistakeu (u,"No material available");
+							break;
+						}
+
+						u->items[i] += n;
+						u->items[rawmaterial[i]] -= n;
+
+						if (n == 1)
+							sprintf (buf,"%s produces 1 %s.",unitid (u),itemnames[0][i]);
+						else
+							sprintf (buf,"%s produces %d %s.",unitid (u),n,itemnames[1][i]);
+						addevent (u->faction,buf);
+					}
+
+					u->skills[itemskill[i]] += u->number * PRODUCEEXP;
+					break;
+
+				case K_RESEARCH:
+					if (effskill (u,SK_MAGIC) < 2)
+					{
+						mistakeu (u,"Magic skill of at least 2 required");
+						break;
+					}
+
+					i = geti ();
+
+					if (i > effskill (u,SK_MAGIC) / 2)
+					{
+						mistakeu (u,"Insufficient Magic skill - highest available level researched");
+						i = 0;
+					}
+
+					if (i == 0)
+						i = effskill (u,SK_MAGIC) / 2;
+
+					k = 0;
+
+					for (j = 0; j != MAXSPELLS; j++)
+						if (spelllevel[j] == i && !u->spells[j])
+							k = 1;
+
+					if (k == 0)
+					{
+						if (u->money < 200)
+						{
+							mistakeu (u,"Insufficient funds");
+							break;
+						}
+
+						for (n = u->number; n; n--)
+							if (u->money >= 200)
+							{
+								u->money -= 200;
+								u->skills[SK_MAGIC] += 10;
+							}
+
+						sprintf (buf,"%s discovers that no more level %d spells exist.",
+									unitid (u),i);
+						addevent (u->faction,buf);
+						break;
+					}
+
+					for (n = u->number; n; n--)
+					{
+						if (u->money < 200)
+						{
+							mistakeu (u,"Insufficient funds");
+							break;
+						}
+
+						do
+							j = rnd () % MAXSPELLS;
+						while (spelllevel[j] != i || u->spells[j] == 1);
+
+						if (!u->faction->seendata[j])
+						{
+							u->faction->seendata[j] = 1;
+							u->faction->showdata[j] = 1;
+						}
+
+						if (u->spells[j] == 0)
+						{
+							sprintf (buf,"%s discovers the %s spell.",
+										unitid (u),spellnames[j]);
+							addevent (u->faction,buf);
+						}
+
+						u->spells[j] = 2;
+						u->skills[SK_MAGIC] += 10;
+					}
+
+					for (j = 0; j != MAXSPELLS; j++)
+						if (u->spells[j] == 2)
+							u->spells[j] = 1;
+					break;
+
+				case K_TEACH:
+					teaching = u->number * 30 * TEACHNUMBER;
+					m = 0;
+
+					do
+						uv[m++] = getunit (r,u);
+					while (!getunit0 && m != 100);
+
+					m--;
+
+					for (j = 0; j != m; j++)
+					{
+						u2 = uv[j];
+
+						if (!u2)
+						{
+							mistakeu (u,"Unit not found");
+							continue;
+						}
+
+						if (!accepts (u2,u))
+						{
+							mistakeu (u,"Unit does not accept teaching");
+							continue;
+						}
+
+						i = igetkeyword (u2->thisorder);
+
+						if (i != K_STUDY || (i = getskill ()) < 0)
+						{
+							mistakeu (u,"Unit not studying");
+							continue;
+						}
+
+						if (effskill (u,i) <= effskill (u2,i))
+						{
+							mistakeu (u,"Unit not studying a skill you can teach it");
+							continue;
+						}
+
+						n = (u2->number * 30) - u2->learning;
+						n = min (n,teaching);
+
+						if (n == 0)
+							continue;
+
+						u2->learning += n;
+						teaching -= u->number * 30;
+
+						strcpy (buf,unitid (u));
+						scat (" teaches ");
+						scat (unitid (u2));
+						scat (" ");
+						scat (skillnames[i]);
+						scat (".");
+
+						addevent (u->faction,buf);
+						if (u2->faction != u->faction)
+							addevent (u2->faction,buf);
+					}
+
+					break;
+
+				case K_WORK:
+					o = cmalloc (sizeof (order));
+					o->unit = u;
+					o->qty = u->number * foodproductivity[r->terrain];
+					addlist (&workorders,o);
+					break;
+			}
+
+				/* Entertainment */
+
+		expandorders (r,entertainorders);
+
+		for (i = 0, n = r->money / ENTERTAINFRACTION; i != norders && n; i++, n--)
+		{
+			oa[i].unit->money++;
+			r->money--;
+			oa[i].unit->n++;
+		}
+
+		free (oa);
+
+		for (u = r->units; u; u = u->next)
+			if (u->n >= 0)
+			{
+				sprintf (buf,"%s earns $%d entertaining.",unitid (u),u->n);
+				addevent (u->faction,buf);
+
+				u->skills[SK_ENTERTAINMENT] += 10 * u->number;
+			}
+
+				/* Food production */
+
+		expandorders (r,workorders);
+
+		for (i = 0, n = maxfoodoutput[r->terrain]; i != norders && n; i++, n--)
+		{
+			oa[i].unit->money++;
+			oa[i].unit->n++;
+		}
+
+		free (oa);
+
+		r->money += min (n,r->peasants * foodproductivity[r->terrain]);
+
+		for (u = r->units; u; u = u->next)
+			if (u->n >= 0)
+			{
+				sprintf (buf,"%s earns $%d performing manual labor.",unitid (u),u->n);
+				addevent (u->faction,buf);
+			}
+
+				/* Production of other primary commodities */
+
+		for (i = 0; i != 4; i++)
+		{
+			expandorders (r,produceorders[i]);
+
+			for (j = 0, n = maxoutput[r->terrain][i]; j != norders && n; j++, n--)
+			{
+				oa[j].unit->items[i]++;
+				oa[j].unit->n++;
+			}
+
+			free (oa);
+
+			for (u = r->units; u; u = u->next)
+				if (u->n >= 0)
+				{
+					if (u->n == 1)
+						sprintf (buf,"%s produces 1 %s.",unitid (u),itemnames[0][i]);
+					else
+						sprintf (buf,"%s produces %d %s.",unitid (u),u->n,itemnames[1][i]);
+					addevent (u->faction,buf);
+				}
+		}
+	}
+
+			/* Study skills */
+
+	puts ("Processing STUDY orders...");
+
+	for (r = regions; r; r = r->next)
+		if (r->terrain != T_OCEAN)
+			for (u = r->units; u; u = u->next)
+				switch (igetkeyword (u->thisorder))
+				{
+					case K_STUDY:
+						i = getskill ();
+
+						if (i < 0)
+						{
+							mistakeu (u,"Skill not recognized");
+							break;
+						}
+
+						if (i == SK_TACTICS || i == SK_MAGIC)
+						{
+							if (u->money < STUDYCOST * u->number)
+							{
+								mistakeu (u,"Insufficient funds");
+								break;
+							}
+
+							if (i == SK_MAGIC && !u->skills[SK_MAGIC] &&
+								 magicians (u->faction) + u->number > 3)
+							{
+								mistakeu (u,"Only 3 magicians per faction");
+								break;
+							}
+
+							u->money -= STUDYCOST * u->number;
+						}
+
+						sprintf (buf,"%s studies %s.",unitid (u),skillnames[i]);
+						addevent (u->faction,buf);
+
+						u->skills[i] += (u->number * 30) + u->learning;
+						break;
+				}
+
+			/* Ritual spells, and loss of spells where required */
+
+	puts ("Processing CAST orders...");
+
+	for (r = regions; r; r = r->next)
+	{
+		for (u = r->units; u; u = u->next)
+		{
+			for (i = 0; i != MAXSPELLS; i++)
+				if (u->spells[i] && spelllevel[i] > (effskill (u,SK_MAGIC) + 1) / 2)
+					u->spells[i] = 0;
+
+			if (u->combatspell >= 0 && !cancast (u,u->combatspell))
+				u->combatspell = -1;
+		}
+
+		if (r->terrain != T_OCEAN)
+			for (u = r->units; u; u = u->next)
+				switch (igetkeyword (u->thisorder))
+				{
+					case K_CAST:
+						i = getspell ();
+
+						if (i < 0 || !cancast (u,i))
+						{
+							mistakeu (u,"Spell not found");
+							break;
+						}
+
+						j = spellitem (i);
+
+						if (j >= 0)
+						{
+							if (u->money < 200 * spelllevel[i])
+							{
+								mistakeu (u,"Insufficient funds");
+								break;
+							}
+
+							n = min (u->number,u->money / (200 * spelllevel[i]));
+							u->items[j] += n;
+							u->money -= n * 200 * spelllevel[i];
+							u->skills[SK_MAGIC] += n * 10;
+
+							sprintf (buf,"%s casts %s.",unitid (u),spellnames[i]);
+							addevent (u->faction,buf);
+							break;
+						}
+
+						if (u->money < 50)
+						{
+							mistakeu (u,"Insufficient funds");
+							break;
+						}
+
+						switch (i)
+						{
+							case SP_CONTAMINATE_WATER:
+								n = cancast (u,SP_CONTAMINATE_WATER);
+								n = min (n,u->money / 50);
+
+								u->money -= n * 50;
+								u->skills[SK_MAGIC] += n * 10;
+
+								n = lovar (n * 50);
+								n = min (n,r->peasants);
+
+								if (!n)
+									break;
+
+								r->peasants -= n;
+
+								for (f = factions; f; f = f->next)
+								{
+									j = cansee (f,r,u);
+
+									if (j)
+									{
+										if (j == 2)
+											sprintf (buf,"%s contaminates the water supply "
+														"in %s, causing %d peasants to die.",
+														unitid (u),regionid (r),n);
+										else
+											sprintf (buf,"%d peasants die in %s from "
+														"drinking contaminated water.",
+														n,regionid (r));
+										addevent (f,buf);
+									}
+								}
+
+								break;
+
+							case SP_TELEPORT:
+								u2 = getunitg (r,u);
+
+								if (!u2)
+								{
+									mistakeu (u,"Unit not found");
+									break;
+								}
+
+								if (!admits (u2,u))
+								{
+									mistakeu (u,"Target unit does not provide vector");
+									break;
+								}
+
+								for (r2 = regions;; r2 = r2->next)
+								{
+									for (u3 = r2->units; u3; u3 = u3->next)
+										if (u3 == u2)
+											break;
+
+									if (u3)
+										break;
+								}
+
+								n = cancast (u,SP_TELEPORT);
+								n = min (n,u->money / 50);
+
+								u->money -= n * 50;
+								u->skills[SK_MAGIC] += n * 10;
+
+								n *= 10000;
+
+								for (;;)
+								{
+									u3 = getunit (r,u);
+
+									if (getunit0)
+										break;
+
+									if (!u3)
+									{
+										mistakeu (u,"Unit not found");
+										continue;
+									}
+
+									if (!accepts (u3,u))
+									{
+										mistakeu (u,"Unit does not accept teleportation");
+										continue;
+									}
+
+									i = itemweight (u3) + horseweight (u3) + (u->number * 10);
+
+									if (i > n)
+									{
+										mistakeu (u,"Unit too heavy");
+										continue;
+									}
+
+									leave (r,u3);
+									n -= i;
+									translist (&r->units,&r2->units,u3);
+									u3->building = u2->building;
+									u3->ship = u2->ship;
+								}
+
+								sprintf (buf,"%s casts Teleport.",unitid (u));
+								addevent (u->faction,buf);
+								break;
+
+							default:
+								mistakeu (u,"Spell not usable with CAST command");
+						}
+
+						break;
+				}
+	}
+
+			/* Population growth, dispersal and food consumption */
+
+	puts ("Processing demographics...");
+
+	for (r = regions; r; r = r->next)
+	{
+		if (r->terrain != T_OCEAN)
+		{
+			for (n = r->peasants; n; n--)
+				if (rnd () % 100 < POPGROWTH)
+					r->peasants++;
+
+			n = r->money / MAINTENANCE;
+			r->peasants = min (r->peasants,n);
+			r->money -= r->peasants * MAINTENANCE;
+
+			for (n = r->peasants; n; n--)
+				if (rnd () % 100 < PEASANTMOVE)
+				{
+					i = rnd () % 4;
+
+					if (r->connect[i]->terrain != T_OCEAN)
+					{
+						r->peasants--;
+						r->connect[i]->immigrants++;
+					}
+				}
+		}
+
+		for (u = r->units; u; u = u->next)
+		{
+			getmoney (r,u,u->number * MAINTENANCE);
+			n = u->money / MAINTENANCE;
+
+			if (u->number > n)
+			{
+				if (n)
+					sprintf (buf,"%s loses %d to starvation.",unitid (u),u->number - n);
+				else
+					sprintf (buf,"%s starves to death.",unitid (u));
+				addevent (u->faction,buf);
+
+				for (i = 0; i != MAXSKILLS; i++)
+					u->skills[i] = distribute (u->number,n,u->skills[i]);
+
+				u->number = n;
+			}
+
+			u->money -= u->number * MAINTENANCE;
+		}
+	}
+
+	removeempty ();
+
+	for (r = regions; r; r = r->next)
+		r->peasants += r->immigrants;
+
+			/* Warn players who haven't sent in orders */
+
+	for (f = factions; f; f = f->next)
+		if (turn - f->lastorders == ORDERGAP)
+			addstrlist (&f->messages,"Please send orders next turn if you wish to continue playing.");
+}
+
+int nextc;
+
+void rc (void)
+{
+	nextc = fgetc (F);
+}
+
+void rs (char *s)
+{
+	while (nextc != '"')
+	{
+		if (nextc == EOF)
+		{
+			puts ("Data file is truncated.");
+			exit (1);
+		}
+
+		rc ();
+	}
+
+	rc ();
+
+	while (nextc != '"')
+	{
+		if (nextc == EOF)
+		{
+			puts ("Data file is truncated.");
+			exit (1);
+		}
+
+		*s++ = nextc;
+		rc ();
+	}
+
+	rc ();
+	*s = 0;
+}
+
+ri (void)
+{
+	int i;
+	char buf[20];
+
+	i = 0;
+
+	while (!xisdigit (nextc))
+	{
+		if (nextc == EOF)
+		{
+			puts ("Data file is truncated.");
+			exit (1);
+		}
+
+		rc ();
+	}
+
+	while (xisdigit (nextc))
+	{
+		buf[i++] = nextc;
+		rc ();
+	}
+
+	buf[i] = 0;
+	return atoi (buf);
+}
+
+void rstrlist (strlist **SP)
+{
+	int n;
+	strlist *S;
+
+	n = ri ();
+
+	while (--n >= 0)
+	{
+		rs (buf);
+		S = makestrlist (buf);
+		addlist2 (SP,S);
+	}
+
+	*SP = 0;
+}
+
+void readgame (void)
+{
+	int i,n,n2;
+	faction *f,**fp;
+	rfaction *rf,**rfp;
+	region *r,**rp;
+	building *b,**bp;
+	ship *sh,**shp;
+	unit *u,**up;
+
+	sprintf (buf,"data/%d",turn);
+	cfopen (buf,"r");
+
+	printf ("Reading turn %d...\n",turn);
+
+	rc ();
+
+	turn = ri ();
+
+			/* Read factions */
+
+	n = ri ();
+	fp = &factions;
+
+	while (--n >= 0)
+	{
+		f = cmalloc (sizeof (faction));
+		memset (f,0,sizeof (faction));
+
+		f->no = ri ();
+		rs (f->name);
+		rs (f->addr);
+		f->lastorders = ri ();
+
+		for (i = 0; i != MAXSPELLS; i++)
+			f->showdata[i] = ri ();
+
+		n2 = ri ();
+		rfp = &f->allies;
+
+		while (--n2 >= 0)
+		{
+			rf = cmalloc (sizeof (rfaction));
+
+			rf->factionno = ri ();
+
+			addlist2 (rfp,rf);
+		}
+
+		*rfp = 0;
+
+		rstrlist (&f->mistakes);
+		rstrlist (&f->messages);
+		rstrlist (&f->battles);
+		rstrlist (&f->events);
+
+		addlist2 (fp,f);
+	}
+
+	*fp = 0;
+
+			/* Read regions */
+
+	n = ri ();
+	rp = &regions;
+
+	while (--n >= 0)
+	{
+		r = cmalloc (sizeof (region));
+		memset (r,0,sizeof (region));
+
+		r->x = ri ();
+		r->y = ri ();
+		rs (r->name);
+		r->terrain = ri ();
+		r->peasants = ri ();
+		r->money = ri ();
+
+		n2 = ri ();
+		bp = &r->buildings;
+
+		while (--n2 >= 0)
+		{
+			b = cmalloc (sizeof (building));
+
+			b->no = ri ();
+			rs (b->name);
+			rs (b->display);
+			b->size = ri ();
+
+			addlist2 (bp,b);
+		}
+
+		*bp = 0;
+
+		n2 = ri ();
+		shp = &r->ships;
+
+		while (--n2 >= 0)
+		{
+			sh = cmalloc (sizeof (ship));
+
+			sh->no = ri ();
+			rs (sh->name);
+			rs (sh->display);
+			sh->type = ri ();
+			sh->left = ri ();
+
+			addlist2 (shp,sh);
+		}
+
+		*shp = 0;
+
+		n2 = ri ();
+		up = &r->units;
+
+		addlist2 (rp,r);
+
+		while (--n2 >= 0)
+		{
+			u = cmalloc (sizeof (unit));
+			memset (u,0,sizeof (unit));
+
+			u->no = ri ();
+			rs (u->name);
+			rs (u->display);
+			u->number = ri ();
+			u->money = ri ();
+			u->faction = findfaction (ri ());
+			u->building = findbuilding (ri ());
+			u->ship = findship (ri ());
+			u->owner = ri ();
+			u->behind = ri ();
+			u->guard = ri ();
+			rs (u->lastorder);
+			u->combatspell = ri ();
+
+			for (i = 0; i != MAXSKILLS; i++)
+				u->skills[i] = ri ();
+
+			for (i = 0; i != MAXITEMS; i++)
+				u->items[i] = ri ();
+
+			for (i = 0; i != MAXSPELLS; i++)
+				u->spells[i] = ri ();
+
+			addlist2 (up,u);
+		}
+
+		*up = 0;
+	}
+
+	*rp = 0;
+
+			/* Get rid of stuff that was only relevant last turn */
+
+	for (f = factions; f; f = f->next)
+	{
+		memset (f->showdata,0,sizeof f->showdata);
+
+		freelist (f->mistakes);
+		freelist (f->messages);
+		freelist (f->battles);
+		freelist (f->events);
+
+		f->mistakes = 0;
+		f->messages = 0;
+		f->battles = 0;
+		f->events = 0;
+	}
+
+			/* Link rfaction structures */
+
+	for (f = factions; f; f = f->next)
+		for (rf = f->allies; rf; rf = rf->next)
+			rf->faction = findfaction (rf->factionno);
+
+	for (r = regions; r; r = r->next)
+	{
+				/* Initialize faction seendata values */
+
+		for (u = r->units; u; u = u->next)
+			for (i = 0; i != MAXSPELLS; i++)
+				if (u->spells[i])
+					u->faction->seendata[i] = 1;
+
+				/* Check for alive factions */
+
+		for (u = r->units; u; u = u->next)
+			u->faction->alive = 1;
+	}
+
+	connectregions ();
+	fclose (F);
+}
+
+void wc (int c)
+{
+	fputc (c,F);
+}
+
+void wsn (char *s)
+{
+	while (*s)
+		wc (*s++);
+}
+
+void wnl (void)
+{
+	wc ('\n');
+}
+
+void wspace (void)
+{
+	wc (' ');
+}
+
+void ws (char *s)
+{
+	wc ('"');
+	wsn (s);
+	wc ('"');
+}
+
+void wi (int n)
+{
+	sprintf (buf,"%d",n);
+	wsn (buf);
+}
+
+void wstrlist (strlist *S)
+{
+	wi (listlen (S));
+	wnl ();
+
+	while (S)
+	{
+		ws (S->s);
+		wnl ();
+		S = S->next;
+	}
+}
+
+void writegame (void)
+{
+	int i;
+	faction *f;
+	rfaction *rf;
+	region *r;
+	building *b;
+	ship *sh;
+	unit *u;
+
+	sprintf (buf,"data/%d",turn);
+	cfopen (buf,"w");
+	printf ("Writing turn %d...\n",turn);
+
+	wi (turn);
+	wnl ();
+
+			/* Write factions */
+
+	wi (listlen (factions));
+	wnl ();
+
+	for (f = factions; f; f = f->next)
+	{
+		wi (f->no);
+		wspace ();
+		ws (f->name);
+		wspace ();
+		ws (f->addr);
+		wspace ();
+		wi (f->lastorders);
+
+		for (i = 0; i != MAXSPELLS; i++)
+		{
+			wspace ();
+			wi (f->showdata[i]);
+		}
+
+		wnl ();
+
+		wi (listlen (f->allies));
+		wnl ();
+
+		for (rf = f->allies; rf; rf = rf->next)
+		{
+			wi (rf->faction->no);
+			wnl ();
+		}
+
+		wstrlist (f->mistakes);
+		wstrlist (f->messages);
+		wstrlist (f->battles);
+		wstrlist (f->events);
+	}
+
+			/* Write regions */
+
+	wi (listlen (regions));
+	wnl ();
+
+	for (r = regions; r; r = r->next)
+	{
+		wi (r->x);
+		wspace ();
+		wi (r->y);
+		wspace ();
+		ws (r->name);
+		wspace ();
+		wi (r->terrain);
+		wspace ();
+		wi (r->peasants);
+		wspace ();
+		wi (r->money);
+		wnl ();
+
+		wi (listlen (r->buildings));
+		wnl ();
+
+		for (b = r->buildings; b; b = b->next)
+		{
+			wi (b->no);
+			wspace ();
+			ws (b->name);
+			wspace ();
+			ws (b->display);
+			wspace ();
+			wi (b->size);
+			wnl ();
+		}
+
+		wi (listlen (r->ships));
+		wnl ();
+
+		for (sh = r->ships; sh; sh = sh->next)
+		{
+			wi (sh->no);
+			wspace ();
+			ws (sh->name);
+			wspace ();
+			ws (sh->display);
+			wspace ();
+			wi (sh->type);
+			wspace ();
+			wi (sh->left);
+			wnl ();
+		}
+
+		wi (listlen (r->units));
+		wnl ();
+
+		for (u = r->units; u; u = u->next)
+		{
+			wi (u->no);
+			wspace ();
+			ws (u->name);
+			wspace ();
+			ws (u->display);
+			wspace ();
+			wi (u->number);
+			wspace ();
+			wi (u->money);
+			wspace ();
+			wi (u->faction->no);
+			wspace ();
+			if (u->building)
+				wi (u->building->no);
+			else
+				wi (0);
+			wspace ();
+			if (u->ship)
+				wi (u->ship->no);
+			else
+				wi (0);
+			wspace ();
+			wi (u->owner);
+			wspace ();
+			wi (u->behind);
+			wspace ();
+			wi (u->guard);
+			wspace ();
+			ws (u->lastorder);
+			wspace ();
+			wi (u->combatspell);
+
+			for (i = 0; i != MAXSKILLS; i++)
+			{
+				wspace ();
+				wi (u->skills[i]);
+			}
+
+			for (i = 0; i != MAXITEMS; i++)
+			{
+				wspace ();
+				wi (u->items[i]);
+			}
+
+			for (i = 0; i != MAXSPELLS; i++)
+			{
+				wspace ();
+				wi (u->spells[i]);
+			}
+
+			wnl ();
+		}
+	}
+
+	fclose (F);
+}
+
+void addunits (void)
+{
+	int i,j;
+	region *r;
+	unit *u;
+
+	r = inputregion ();
+
+	if (!r)
+		return;
+
+	printf ("Name of units file? ");
+	gets (buf);
+
+	if (!buf[0])
+		return;
+
+	cfopen (buf,"r");
+
+	rc ();
+
+	for (;;)
+	{
+		rs (buf);
+
+		if (!strcmp (buf,"end"))
+			return;
+
+		u = createunit (r);
+
+		rs (u->name);
+		rs (u->display);
+		u->number = ri ();
+		u->money = ri ();
+		u->faction = findfaction (ri ());
+
+		for (;;)
+		{
+			rs (buf);
+
+			if (!strcmp (buf,"end"))
+				break;
+
+			j = ri ();
+
+			if ((i = findstr (skillnames,buf,MAXSKILLS)) >= 0)
+				u->skills[i] = j;
+			else if ((i = findstr (itemnames[0],buf,MAXITEMS)) >= 0)
+				u->items[i] = j;
+			else if ((i = findstr (spellnames,buf,MAXSPELLS)) >= 0)
+				u->spells[i] = j;
+			else
+				printf ("Attribute %s not recognized.\n",buf);
+		}
+	}
+}
+
+void initgame (void)
+{
+	int i,j;
+	glob_t	fd;
+
+	if(glob("data/*.*", GLOB_NOSORT, NULL, &fd)==0) {
+		turn = 0;
+
+		for(j=0; j<fd.gl_pathc; j++) {
+			i = atoi (fd.gl_pathv[j]);
+			if(i > turn)
+				turn = i;
+		}
+
+		readgame ();
+	}
+	else {
+		puts ("No data files found, creating game...");
+		mkdir ("data",(S_IRWXU|S_IRWXG|S_IRWXO));
+		makeblock (0,0);
+
+		writesummary ();
+		writegame ();
+	}
+}
+
+void processturn (void)
+{
+	printf ("Name of orders file? ");
+	gets (buf);
+	if (!buf[0])
+		return;
+	turn++;
+	readorders ();
+	processorders ();
+	reports ();
+	writesummary ();
+	writegame ();
+}
+
+void createcontinent (void)
+{
+	int x,y;
+
+LOOP:
+	printf ("X? ");
+	gets (buf);
+	if (buf[0] == 0)
+		return;
+	x = atoi (buf);
+
+	printf ("Y? ");
+	gets (buf);
+	if (buf[0] == 0)
+		return;
+	y = atoi (buf);
+
+	makeblock (x,y);
+
+	F = stdout;
+	writemap ();
+}
+
+int main (void)
+{
+	rndno = time (0);
+
+	puts ("Atlantis v1.0  " __DATE__ "\n"
+			"Copyright 1993 by Russell Wallace.\n"
+			"Type ? for list of commands.");
+
+	initgame ();
+
+	for (;;)
+	{
+		printf ("> ");
+		gets (buf);
+
+		switch (tolower (buf[0]))
+		{
+			case 'c':
+				createcontinent ();
+				break;
+
+			case 'a':
+				addplayers ();
+				break;
+
+			case 'u':
+				addunits ();
+				break;
+
+			case 'p':
+				processturn ();
+				return 0;
+
+			case 'q':
+				return 0;
+
+			default:
+				puts ("C - Create New Continent.\n"
+						"A - Add New Players.\n"
+						"U - Add New Units.\n"
+						"P - Process Game Turn.\n"
+						"Q - Quit.");
+		}
+	}
+}
+
+
diff --git a/xtrn/atlantis/building.js b/xtrn/atlantis/building.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b1641ac21cf952079ee5a4c5c4d2e3e05edd065
--- /dev/null
+++ b/xtrn/atlantis/building.js
@@ -0,0 +1,52 @@
+if(js.global.Region==undefined)
+	load(script_dir+'region.js');
+if(js.global.Unit==undefined)
+	load(script_dir+'unit.js');
+
+function Building()
+{
+	this.no=0;
+	this.name='';
+	this.display='';
+	this.size=0;
+	this.sizeleft=0;
+	this.region=null;	// TODO: Added
+	this.id getter=function() { return(this.name+' ('+this.no+')'); };
+	this.owner getter=function() {
+		var u;
+
+		for(u in this.region.units)
+			if(this.region.units[u].building.no==this.no)
+				return(this.region.units[u]);
+
+		return(null);
+	};
+
+	this.mayenter=function(u) {
+		var u2=this.owner;
+
+		if(u2 == undefined)
+			return(true);
+		return(u2.admits(u));
+	};
+}
+
+function findbuilding (no, region)
+{
+	var r,b;
+
+	if(region==undefined) {
+		for(r in regions)
+			for(b in regions[r].buildings)
+				if(regions[r].buildings[b].no==no)
+					return(regions[r].buildings[b]);
+	}
+	else {
+		for(b in region.buildings)
+			if(region.buildings[b].no==no)
+				return(region.buildings[b]);
+	}
+
+	return(null);
+}
+
diff --git a/xtrn/atlantis/engine.js b/xtrn/atlantis/engine.js
new file mode 100644
index 0000000000000000000000000000000000000000..06ea3633ad7740f65a1c6d097bd3d21a2158250c
--- /dev/null
+++ b/xtrn/atlantis/engine.js
@@ -0,0 +1,2768 @@
+
+/*	Atlantis v1.0  13 September 1993
+	Copyright 1993 by Russell Wallace
+
+	This program may be freely used, modified and distributed.  It may not be
+	sold or used commercially without prior written permission from the author.
+*/
+
+/*
+	Ported to Synchronet JavaScript by Stephen Hurd (Deuce)
+*/
+
+var script_dir='.';
+try { throw barfitty.barf(barf) } catch(e) { script_dir=e.fileName }
+script_dir=script_dir.replace(/[\/\\][^\/\\]*$/,'');
+script_dir=backslash(script_dir);
+
+load(script_dir+'gamedata.js');
+if(!js.global.scramble==undefined)
+	load(script_dir+'utilfuncs.js');
+if(!js.global.Troop==undefined)
+	load(script_dir+'troop.js');
+
+function reportcasualties(unit)
+{
+}
+
+function mistake2(order,str)
+{
+}
+
+function mistakeu(unit,str)
+{
+}
+
+function battlerecord(str)
+{
+}
+
+function battlepunit(unit)
+{
+}
+
+function sparagraph(battles,str,indent,istr)
+{
+}
+
+function doshot()
+{
+}
+
+function addbattle(faction,str)
+{
+}
+
+function lovar (n)
+{
+	n = parseInt(n/2);
+	return ((random(n) + 1) + (random(n) + 1));
+}
+
+function distribute (old,nw,n)
+{
+	var i,t;
+
+	if(!(nw <= old))
+		throw("distribute called with new ("+nw+") > old ("+old+")!");
+
+	if (old == 0)
+		return 0;
+
+	t = parseInt(n / old) * nw;
+	for (i = (n % old); i; i--)
+		if (random(old) < nw)
+			t++;
+
+	return t;
+}
+
+function process_form()
+{
+	var r,u,S,u2,S2;
+			/* FORM orders */
+
+	log("Processing FORM orders...");
+
+	for (r in regions)
+		/* We can't do for u in ... since we will potentially be growing the units array */
+		for (u=0; u<regions[r].units.length; u++)
+			for (S in regions[r].units[u].orders)
+				switch(regions[r].units[u].orders[S].command) {
+					case K_FORM:
+						u2 = new Unit(regions[r]);
+
+						u2.alias = regions[r].units[u].orders[S].args.shift();
+						if (u2.alias == 0)
+							u2.alias = regions[r].units[u].orders[S].args.shift();
+
+						u2.faction = u.faction;
+						u2.building = u.building;
+						u2.ship = u.ship;
+						u2.behind = u.behind;
+						u2.guard = u.guard;
+						u2.orders = regions[r].units[u].orders[S].suborders;
+						break;
+				}
+}
+
+function removenofromarray(array,obj)
+{
+	var i;
+
+	for(i in array) {
+		if(array[i].no==obj.no)
+			array.splice(i,1);
+	}
+}
+
+function addnotoarray(array,obj)
+{
+	/* If no is already in array, remove (then re-add) */
+	removenofromarray(array,obj);
+	array.push(obj);
+}
+
+function removefromarray(array,no)
+{
+	var i;
+
+	for(i in array) {
+		if(array[i]==no)
+			array.splice(i,1);
+	}
+}
+
+function addtoarray(array,no)
+{
+	/* If no is already in array, remove (then re-add) */
+	removefromarray(array,no);
+	array.push(no);
+}
+
+function getmoney (r,u,n)
+{
+	var i,u2,unit2;
+
+	n -= u.money;
+
+	for (u2 in r.units) {
+		unit2=r.units[u2];
+		if (unit2.faction.no == u.faction.no && u2.no != u.no)
+		{
+			i = Math.min (unit2.money,n);
+			unit2.money -= i;
+			u.money += i;
+			n -= i;
+		}
+	}
+}
+
+function maketroops (tp,u,terrain,left,infront,runeswords)
+{
+	var i;
+	var t;
+	var skills=new Array(MAXSKILLS);	// TODO: These were static!
+	var items=new Array(MAXITEMS);		// TODO: These were static!
+
+	for (i = 0; i < MAXSKILLS; i++)
+		skills[i] = u.effskill (i);
+	for (i = 0; i < MAXITEMS; i++)
+		items[i] = u.items[i];
+
+	left[u.side] += u.number;
+	if (!u.behind)
+		infront[u.side] += u.number;
+
+	for (i = u.number; i; i--)
+	{
+		t=new Troop();
+
+		t.unit = u;
+		t.side = u.side;
+		t.skill = -2;
+		t.behind = u.behind;
+
+		if (u.combatspell >= 0)
+			t.missile = true;
+		else if (items[I_RUNESWORD] && skills[SK_SWORD])
+		{
+			t.weapon = I_SWORD;
+			t.skill = skills[SK_SWORD] + 2;
+			t.runesword = true;
+			items[I_RUNESWORD]--;
+			runeswords[u.side]++;
+
+			if (items[I_HORSE] && skills[SK_RIDING] >= 2 && terrain == T_PLAIN)
+			{
+				t.skill += 2;
+				items[I_HORSE]--;
+			}
+		}
+		else if (items[I_LONGBOW] && skills[SK_LONGBOW])
+		{
+			t.weapon = I_LONGBOW;
+			t.missile = true;
+			t.skill = skills[SK_LONGBOW];
+			items[I_LONGBOW]--;
+		}
+		else if (items[I_CROSSBOW] && skills[SK_CROSSBOW])
+		{
+			t.weapon = I_CROSSBOW;
+			t.missile = true;
+			t.skill = skills[SK_CROSSBOW];
+			items[I_CROSSBOW]--;
+		}
+		else if (items[I_SWORD] && skills[SK_SWORD])
+		{
+			t.weapon = I_SWORD;
+			t.skill = skills[SK_SWORD];
+			items[I_SWORD]--;
+
+			if (items[I_HORSE] && skills[SK_RIDING] >= 2 && terrain == T_PLAIN)
+			{
+				t.skill += 2;
+				items[I_HORSE]--;
+			}
+		}
+
+		if (u.spells[SP_HEAL] || items[I_AMULET_OF_HEALING] > 0)
+		{
+			t.canheal = true;
+			items[I_AMULET_OF_HEALING]--;
+		}
+
+		if (items[I_RING_OF_POWER])
+		{
+			t.power = 1;
+			items[I_RING_OF_POWER]--;
+		}
+
+		if (items[I_SHIELDSTONE])
+		{
+			t.shieldstone = true;
+			items[I_SHIELDSTONE]--;
+		}
+
+		if (items[I_CLOAK_OF_INVULNERABILITY])
+		{
+			t.invulnerable = true;
+			items[I_CLOAK_OF_INVULNERABILITY]--;
+		}
+		else if (items[I_PLATE_ARMOR])
+		{
+			t.armor = 2;
+			items[I_PLATE_ARMOR]--;
+		}
+		else if (items[I_CHAIN_MAIL])
+		{
+			t.armor = 1;
+			items[I_CHAIN_MAIL]--;
+		}
+
+		if (u.building && u.building.sizeleft)
+		{
+			t.inside = 2;
+			u.building.sizeleft--;
+		}
+
+		tp.push(t);
+	}
+
+	return tp;
+}
+
+/* Instant orders - diplomacy etc. */
+function process_instant()
+{
+	var r,u,S,i,tmp;
+	var region,unit,order;
+
+	log ("Processing instant orders...");
+
+	for (r in regions) {
+		region=regions[r];
+		for (u in region.units) {
+			unit=region.units[u];
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case -1:
+						mistake2 (order,"Order not recognized");
+						break;
+
+					case K_ACCEPT:
+						tmp=findfaction(order.args.shift());
+						if(tmp != null)
+							addtoarray(unit.faction.accept, tmp.no);
+						break;
+
+					case K_ADDRESS:
+						/* TODO: Should we just remove this command? */
+						tmp=order.args.shift();
+						if (tmp.length==0)
+						{
+							mistake2 (order,"No address given");
+							break;
+						}
+						unit.faction.addr=tmp;
+
+						log(format("%s is changing address to %s.\n",unit.faction.name,
+								  unit.faction.addr));
+						break;
+
+					case K_ADMIT:
+						tmp=findfaction(order.args.shift());
+						if(tmp != null)
+							addtoarray(unit.faction.admin, tmp.no);
+						break;
+
+					case K_ALLY:
+						tmp = findfaction(order.args.shift());
+
+						if (tmp == null)
+						{
+							mistake2 (order,"Faction not found");
+							break;
+						}
+
+						if (tmp.no == unit.faction.no)
+							break;
+
+						if (parseInt(order.args.shift()))
+							addtoarray(unit.faction.allies, tmp.no);
+						else
+							removefromarray(unit.faction.allies, tmp.no);
+						break;
+
+					case K_BEHIND:
+						unit.behind=new Boolean(parseInt(order.args.shift()));
+						break;
+
+					case K_COMBAT:
+						if (order.args.length==0)
+						{
+							unit.combatspell = -1;
+							break;
+						}
+
+						i = order.args.shift();
+
+						if (i < 0 || !unit.cancast (i))
+						{
+							mistake2 (order,"Spell not found");
+							break;
+						}
+
+						if (!spelldata[i].iscombatspell)
+						{
+							mistake2 (order,"Not a combat spell");
+							break;
+						}
+
+						unit.combatspell = i;
+						break;
+
+					case K_DISPLAY:
+						switch (order.args.shift())
+						{
+							case K_BUILDING:
+								if (unit.building==null)
+								{
+									mistake2 (order,"Not in a building");
+									break;
+								}
+
+								if (!unit.owner)
+								{
+									mistake2 (order,"Building not owned by you");
+									break;
+								}
+								if(order.args.length > 0)
+									unit.building.display=order.args.shift();
+								break;
+
+							case K_SHIP:
+								if (unit.ship==null)
+								{
+									mistake2 (order,"Not in a ship");
+									break;
+								}
+
+								if (!unit.owner)
+								{
+									mistake2 (order,"Ship not owned by you");
+									break;
+								}
+								if(order.args.length > 0)
+									unit.ship.display=order.args.shift();
+								break;
+
+							case K_UNIT:
+								if(order.args.length > 0)
+									unit.display=order.args.shift();
+								break;
+
+							default:
+								mistake2 (order,"Order not recognized");
+								break;
+						}
+						break;
+
+					case K_GUARD:
+						/* MUST UNSHIFT THE ARG IF IT'S TRUE! */
+						tmp=order.args.shift();
+						if (tmp)
+							order.args.unshift(tmp);
+						else
+							unit.guard = 0;
+						break;
+
+					case K_NAME:
+						if(order.args.length==0)
+							break;
+						switch (parseInt(order.args.shift()))
+						{
+							case K_BUILDING:
+								if (unit.building==null)
+								{
+									mistake2 (order,"Not in a building");
+									break;
+								}
+
+								if (!unit.owner)
+								{
+									mistake2 (order,"Building not owned by you");
+									break;
+								}
+								unit.building.name=order.args.shift();
+								break;
+
+							case K_FACTION:
+								unit.faction.name=order.args.shift();
+								break;
+
+							case K_SHIP:
+								if (unit.ship==null)
+								{
+									mistake2 (order,"Not in a ship");
+								}
+
+								if (!unit.owner)
+								{
+									mistake2 (order,"Ship not owned by you");
+									break;
+								}
+								unit.ship.name=order.args.shift();
+								break;
+
+							case K_UNIT:
+								unit.name=order.args.shift();
+								break;
+
+							default:
+								mistake2 (order,"Order not recognized");
+								break;
+						}
+						// TODO: Previously, names could not contain ()
+						break;
+
+					case K_RESHOW:
+						i = parseInt(order.args.shift());
+
+						if (i < 0 || i >= MAXSPELLS || !unit.faction.seendata[i])
+						{
+							mistake2 (order,"Spell not found");
+							break;
+						}
+
+						unit.faction.showdata[i] = true;
+						break;
+				}
+			}
+		}
+	}
+}
+
+/* FIND orders */
+function process_find()
+{
+	/* TODO: Remove? */
+	var r,u,S,f,unit,order;
+
+	log ("Processing FIND orders...");
+
+	for (r in regions)
+		for (u in regions[r].units) {
+			unit=regions[r].units[u];
+			for (S in regions[r].units[u].orders) {
+				order=regions[r].units[u].orders[S];
+				switch (order.command)
+				{
+					case K_FIND:
+						f = findfaction (order.args.shift());
+
+						if (f == null)
+						{
+							mistake2 (unit,"Faction not found");
+							break;
+						}
+						sparagraph("The address of "+f.id+" is "+f.addr);
+						break;
+				}
+			}
+		}
+}
+
+function process_leaveEnter()
+{
+	var r,u,S,region,unit,order,sh,b,u2;
+
+			/* Leaving and entering buildings and ships */
+
+	log ("Processing leaving and entering orders...");
+
+	for (r in regions) {
+		region=regions[r];
+		for (u in region.units) {
+			unit=region.units[u];
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.args.shift())
+				{
+					case K_BOARD:
+						sh = findship (order.args.shift(), region);
+
+						if (sh==null)
+						{
+							mistake2 (order,"Ship not found");
+							break;
+						}
+
+						if (!sh.mayboard(u))
+						{
+							mistake2 (order,"Not permitted to board");
+							break;
+						}
+						unit.leave();
+						unit.ship=sh;
+						unit.owner=false;
+						if(sh.owner==null)
+							unit.owner=true;
+						break;
+
+					case K_ENTER:
+						b = findbuilding (order.args.shift(), region);
+
+						if (b==null)
+						{
+							mistake2 (order,"Building not found");
+							break;
+						}
+
+						if (!b.mayenter (u))
+						{
+							mistake2 (order,"Not permitted to enter");
+							break;
+						}
+
+						unit.leave();
+						unit.building = b;
+						unit.owner = false;
+						if (b.owner == null)
+							unit.owner = true;
+						break;
+
+					case K_LEAVE:
+						if (region.terrain == T_OCEAN)
+						{
+							mistake2 (order,"Ship is at sea");
+							break;
+						}
+						unit.leave();
+						break;
+
+					case K_PROMOTE:
+						u2 = unit.getunit(order.args);
+
+						if (u2==null || u2.no==undefined)
+						{
+							mistake2 (order,"Unit not found");
+							break;
+						}
+
+						if (!unit.building && !unit.ship)
+						{
+							mistake2 (order,"No building or ship to transfer ownership of");
+							break;
+						}
+
+						if (!unit.owner)
+						{
+							mistake2 (order,"Not owned by you");
+							break;
+						}
+
+						if (!u2.accepts(u))
+						{
+							mistake2 (order,"Unit does not accept ownership");
+							break;
+						}
+
+						if (unit.building)
+						{
+							if (u2.building.no != unit.building.no)
+							{
+								mistake2 (order,"Unit not in same building");
+								break;
+							}
+						}
+						else
+							if (u2.ship.no != u.ship.no)
+							{
+								mistake2 (order,"Unit not on same ship");
+								break;
+							}
+						
+						unit.owner=false;
+						u2.owner=true;
+						break;
+				}
+			}
+		}
+	}
+}
+
+/* Combat */
+function process_attack()
+{
+	var r, region;
+	var f, fno, faction, fa, f2;
+	var u, unit, u2, u3, unit3, u4; 
+	var b, S, order, S2, order2;
+	var initial=new Array(2);
+	var infront=new Array(2);
+	var maxtactics=new Array(2);
+	var left=new Array(2);
+	var shields=new Array(2);
+	var runeswords=new Array(2);
+	var leader=new Array(2);
+	var litems=new Array(MAXITEMS);
+	var toattack=new Array(MAXITEMS);
+	var troops;
+	var i,j,t,n,k;
+	var oldargs;
+	var tmp;
+	var lmoney;
+	var buf,buf2;
+	var attacker=new Troop();
+	var defender=new Troop();
+	var winnercasualties;
+	var deadpeasants;
+	var dh, reportcasualtiesdh;
+
+	log("Processing ATTACK orders...");
+
+	for (r in regions)
+	{
+		region=regions[r];
+		/* Create randomly sorted list of factions */
+		fa=new Array();
+
+		for (f in factions)
+			fa.push(factions[f]);
+		scramble (fa);
+
+		/* Handle each faction's attack orders */
+
+		for (fno in fa)
+		{
+			f = fa[fno];
+
+			for (u in region.units) {
+				unit=region.units[u];
+				if (unit.faction.no == f.no) {
+					for (S in unit.orders) {
+						order=unit.orders[S];
+						if (order.command == K_ATTACK)
+						{
+							u2 = unit.getunit (order.args);
+
+							if (u2.no==undefined && u2.getunitpeasants==undefined)
+							{
+								mistake2 (order,"Unit not found");
+								continue;
+							}
+
+							if (u2.no!=undefined && u2.faction.no == f.no)
+							{
+								mistake2 (order,"One of your units");
+								continue;
+							}
+
+							if (unit.isallied (u2))
+							{
+								mistake2 (order,"An allied unit");
+								continue;
+							}
+
+									/* Draw up troops for the battle */
+
+							for (b in region.buildings)
+								region.buildings[b].sizeleft = region.buildings[b].size;
+
+							troops = new Array();
+							left[0] = left[1] = 0;
+							infront[0] = infront[1] = 0;
+
+									/* If peasants are defenders */
+
+							if (u2.no==undefined)
+							{
+								for (i in region.peasants)
+								{
+									t=new Troop();
+									troops.push(t);
+								}
+
+								left[0] = region.peasants;
+								infront[0] = region.peasants;
+							}
+
+									/* What units are involved? */
+
+							for (f2 in factions)
+								factions[f2].attacking = false;
+
+							for (u3 in region.units) {
+								for (S2 in region.units[u3].orders) {
+									if (region.units[u3].orders[S2].command == K_ATTACK)
+									{
+										oldargs=new Array();
+										for(tmp in region.units[u3].orders[S2].args) {
+											oldargs.push(region.units[u3].orders[S2].args[tmp]);
+										}
+										u4 = region.units[u3].getunit (region.units[u3].orders[S2].args);
+
+										if ((u2 != undefined && u2.getunitpeasants != undefined) ||
+											 (u4.no != undefined && u4.faction.no == u2.faction.no &&
+											  !region.units[u3].isallied (u4)))
+										{
+											region.units[u3].faction.attacking = true;
+											region.units[u3].orders.splice(S2,1);
+											break;
+										}
+										else {
+											region.units[u3].orders.args=oldargs;
+										}
+									}
+								}
+							}
+
+							for (u3 in region.units)
+							{
+								unit3=region.units[u3];
+								unit3.side = -1;
+
+								if (!unit3.number)
+									continue;
+
+								if (unit3.faction.attacking)
+								{
+									unit3.side = 1;
+									maketroops (troops,unit3,region.terrain,left,infront,runeswords);
+								}
+								else if (unit3.isallied (u2))
+								{
+									unit3.side = 0;
+									maketroops (troops,unit3,region.terrain,left,infront,runeswords);
+								}
+							}
+
+							/* If only one side shows up, cancel */
+
+							if (!left[0] || !left[1])
+							{
+								troops=[];
+								continue;
+							}
+
+							/* Set up array of troops */
+
+							troops=scramble(troops);
+
+							initial[0] = left[0];
+							initial[1] = left[1];
+							shields[0] = 0;
+							shields[1] = 0;
+							runeswords[0] = 0;
+							runeswords[1] = 0;
+
+							lmoney = 0;
+							for(tmp in litems)
+								litems[tmp]=0;
+
+							/* Initial attack message */
+
+							for (f2 in factions)
+							{
+								factions[f2].seesbattle = factions[f2].ispresent(r);
+								if (factions[f2].seesbattle && factions[f2].battles)
+									factions[f2].battles.push('');
+							}
+
+							if (u2.no != undefined)
+								buf2=u2.id;
+							else
+								buf2="the peasants";
+							buf=unit.id+" attacks "+buf2+" in "+region.id;
+							battlerecord(buf);
+
+									/* List sides */
+
+							battlerecord ("");
+
+							battlepunit (u);
+
+							for (u3 in region.units)
+								if (region.units[u3].side == 1 && region.units[u3].no != unit.no)
+									battlepunit (region.units[u3]);
+
+							battlerecord ("");
+
+							if (u2.no != undefined)
+								battlepunit (u2);
+							else
+							{
+								buf="Peasants, number: "+region.peasants;
+								for (f2 in factions)
+									if (factions[f2].seesbattle)
+										sparagraph (factions[f2].battles,buf,4,'-');
+							}
+
+							for (u3 in region.units)
+								if (region.units[u3].side == 0 && region.units[u3].no != u2.no)
+									battlepunit (u3);
+
+							battlerecord ("");
+
+									/* Does one side have an advantage in tactics? */
+
+							maxtactics[0] = 0;
+							maxtactics[1] = 0;
+
+							for (i = 0; i < troops.length; i++)
+								if (troops[i].unit.no!=undefined)
+								{
+									j = troops[i].unit.effskill (SK_TACTICS);
+
+									if (maxtactics[troops[i].side] < j)
+									{
+										leader[troops[i].side] = i;
+										maxtactics[troops[i].side] = j;
+									}
+								}
+
+							attacker.side = -1;
+							if (maxtactics[0] > maxtactics[1])
+								attacker.side = 0;
+							if (maxtactics[1] > maxtactics[0])
+								attacker.side = 1;
+
+									/* Better leader gets free round of attacks */
+
+							if (attacker.side >= 0)
+							{
+										/* Note the fact in the battle report */
+
+								if (attacker.side)
+									battlerecord(unit.id+" gets a free round of attacks!");
+								else
+									if (u2.no != undefined)
+										battlerecord (u2.id+" gets a free round of attacks!");
+									else
+										battlerecord ("The peasants get a free round of attacks!");
+
+										/* Number of troops to attack */
+
+								toattack[attacker.side] = 0;
+
+								for (i = 0; i < troops.length; i++)
+								{
+									troops[i].attacked = true;
+
+									if (troops[i].side == attacker.side)
+									{
+										troops[i].attacked = false;
+										toattack[attacker.side]++;
+									}
+								}
+
+										/* Do round of attacks */
+
+								do
+									doshot ();
+								while (toattack[attacker.side] && left[defender.side]);
+							}
+
+									/* Handle main body of battle */
+
+							toattack[0] = 0;
+							toattack[1] = 0;
+
+							while (left[defender.side])
+							{
+										/* End of a round */
+
+								if (toattack[0] == 0 && toattack[1] == 0)
+									for (i = 0; i < troops.length; i++)
+									{
+										troops[i].attacked = true;
+
+										if (!troops[i].status)
+										{
+											troops[i].attacked = false;
+											toattack[troops[i].side]++;
+										}
+									}
+
+								doshot ();
+							}
+
+									/* Report on winner */
+
+							if (attacker.side)
+								battlerecord (unit.id+" wins the battle!");
+							else
+								if (u2.no != undefined)
+									battlerecord (u2.id+" wins the battle!");
+								else
+									battlerecord ("The peasants win the battle!");
+
+									/* Has winner suffered any casualties? */
+
+							winnercasualties = 0;
+
+							for (i = 0; i < troops.length; i++)
+								if (troops[i].side == attacker.side && troops[i].status)
+								{
+									winnercasualties = 1;
+									break;
+								}
+
+									/* Can wounded be healed? */
+
+							n = 0;
+
+							for (i = 0; i < troops.length &&
+											n != initial[attacker.side] -
+												  left[attacker.side]; i++)
+								if (!troops[i].status && troops[i].canheal)
+								{
+									k = lovar (50 * (1 + troops[i].power));
+									k = Math.min (k,initial[attacker.side] -
+													  left[attacker.side] - n);
+
+									battlerecord (troops[i].unit.id+" heals "+k+" wounded.");
+
+									n += k;
+								}
+
+							while (--n >= 0)
+							{
+								do
+									i = random(troops.length);
+								while (!troops[i].status || troops[i].side != attacker.side);
+
+								troops[i].status = 0;
+							}
+
+									/* Count the casualties */
+
+							deadpeasants = 0;
+
+							for (u3 in region.units)
+								region.units[u3].dead = 0;
+
+							for (i = 0; i < troops.length; i++)
+								if (troops[i].unit)
+									troops[i].unit.dead += troops[i].status;
+								else
+									deadpeasants += troops[i].status;
+
+									/* Report the casualties */
+
+							reportcasualtiesdh = 0;
+
+							if (attacker.side)
+							{
+								reportcasualties (unit);
+
+								for (u3 in region.units)
+									if (region.units[u3].side == 1 && region.units[u3].no != unit.no)
+										reportcasualties (region.units[u3]);
+							}
+							else
+							{
+								if (u2.no != undefined)
+									reportcasualties (u2);
+								else
+									if (deadpeasants)
+									{
+										battlerecord ("");
+										reportcasualtiesdh = 1;
+										battlerecord ("The peasants lose "+deadpeasants+".");
+									}
+
+								for (u3 in region.units)
+									if (region.units[u3].side == 0 && region.units[u3].no != u2.no)
+										reportcasualties (region.units[u3]);
+							}
+
+									/* Dead peasants */
+
+							k = region.peasants - deadpeasants;
+
+							j = distribute (region.peasants,k,region.money);
+							lmoney += region.money - j;
+							region.money = j;
+
+							region.peasants = k;
+
+									/* Adjust units */
+
+							for (u3 in region.units)
+							{
+								unit3=region.units[u3];
+								k = unit3.number - unit3.dead;
+
+										/* Redistribute items and skills */
+
+								if (unit3.side == defender.side)
+								{
+									j = distribute (unit3.number,k,unit3.money);
+									lmoney += unit3.money - j;
+									unit3.money = j;
+
+									for (i = 0; i < MAXITEMS; i++)
+									{
+										j = distribute (unit3.number,k,unit3.items[i]);
+										litems[i] += unit3.items[i] - j;
+										unit3.items[i] = j;
+									}
+								}
+
+								for (i = 0; i < MAXSKILLS; i++)
+									unit3.skills[i] = distribute (unit3.number,k,unit3.skills[i]);
+
+										/* Adjust unit numbers */
+
+								unit3.number = k;
+
+										/* Need this flag cleared for reporting of loot */
+
+								unit3.n = 0;
+							}
+
+									/* Distribute loot */
+
+							for (n = lmoney; n; n--)
+							{
+								do
+									j = random(troops.length);
+								while (troops[j].status || troops[j].side != attacker.side);
+
+								if (troops[j].unit)
+								{
+									troops[j].unit.money++;
+									troops[j].unit.n++;
+								}
+								else
+									region.money++;
+							}
+
+							for (i = 0; i < MAXITEMS; i++)
+								for (n = litems[i]; n; n--)
+									if (i <= I_STONE || random(2))
+									{
+										do
+											j = random(troops.length);
+										while (troops[j].status || troops[j].side != attacker.side);
+
+										if (troops[j].unit)
+										{
+											if (!troops[j].unit.litems)
+											{
+												troops[j].unit.litems = [];
+												for(tmp=0; tmp < MAXITEMS; tmp++)
+													troops[j].unit.litems.push(0);
+											}
+
+											troops[j].unit.items[i]++;
+											troops[j].unit.litems[i]++;
+										}
+									}
+
+									/* Report loot */
+
+							for (f2 in factions)
+								factions[f2].dh = 0;
+
+							for (u3 in region.units) {
+								unit3=region.units[u3];
+								
+								if (unit3.n || unit3.litems)
+								{
+									buf=unit3.id+" finds ";
+									dh = 0;
+
+									if (unit3.n)
+									{
+										buf += "$";
+										buf += unit3.n;
+										dh = 1;
+									}
+
+									if (unit3.litems)
+									{
+										for (i = 0; i != MAXITEMS; i++)
+											if (unit3.litems[i])
+											{
+												if (dh)
+													buf += ", ";
+												dh = 1;
+
+												buf += unit3.litems[i];
+												buf += " ";
+
+												if (unit3.litems[i] == 1)
+													buf += items[i].singular;
+												else
+													buf += items[i].plural;
+											}
+
+										unit3.litems = 0;
+									}
+
+									if (!unit3.faction.dh)
+									{
+										addbattle (unit3.faction,"");
+										unit3.faction.dh = 1;
+									}
+
+									buf += ".";
+									addbattle (unit3.faction,buf);
+								}
+							}
+
+									/* Does winner get combat experience? */
+
+							if (winnercasualties)
+							{
+								if (maxtactics[attacker.side] &&
+									 !troops[leader[attacker.side]].status)
+									troops[leader[attacker.side]].unit.skills[SK_TACTICS] += COMBATEXP;
+
+								for (i = 0; i != troops.length; i++)
+									if (troops[i].unit.no != undefined &&
+										 !troops[i].status &&
+										 troops[i].side == attacker.side)
+										switch (troops[i].weapon)
+										{
+											case I_SWORD:
+												troops[i].unit.skills[SK_SWORD] += COMBATEXP;
+												break;
+
+											case I_CROSSBOW:
+												troops[i].unit.skills[SK_CROSSBOW] += COMBATEXP;
+												break;
+
+											case I_LONGBOW:
+												troops[i].unit.skills[SK_LONGBOW] += COMBATEXP;
+												break;
+										}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+/* Economic orders */
+function process_economic()
+{
+	var r,region,u,unit,S,order,b,u2,event,tmp,i,j,k,n,sh;
+	var taxorders,recruitorders,taxed,norders,availmoney;
+
+	log ("Processing economic orders...");
+
+	for (r in regions)
+	{
+		region=regions[r];
+		taxorders = new Array();
+		recruitorders = new Array();
+
+		/* DEMOLISH, GIVE, PAY, SINK orders */
+
+		for (u in region.units) {
+			unit=region.units[u];
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case K_DEMOLISH:
+						if (unit.building==null)
+						{
+							mistake2 (order,"Not in a building");
+							break;
+						}
+
+						if (!unit.owner)
+						{
+							mistake2 (order,"Building not owned by you");
+							break;
+						}
+
+						b = unit.building;
+
+						for (u2 in region.units)
+							if (region.units[u2].building.no == b.no)
+							{
+								region.units[u2].building = null;
+								region.units[u2].owner = false;
+							}
+						
+						event=new Event();
+						event.text=unit.id+" demolishes "+b;
+						event.location=region;
+						event.unit=unit;
+						event.target=b;
+						region.reportevent(event);
+						removenofromarray(region.buildings,b);
+						break;
+
+					case K_GIVE:
+						u2 = unit.getunit(order.args);
+
+						if (u2==null || (u2.no==undefined && u2.getunit0==undefined))
+						{
+							mistake2 (order,"Unit not found");
+							break;
+						}
+
+						if (u2.no != undefined && !u2.accepts (u))
+						{
+							mistake2 (order,"Unit does not accept your gift");
+							break;
+						}
+						
+						tmp=order.args.shift();
+						i = findspell (tmp);
+
+						if (i >= 0)
+						{
+							if (u2==null)
+							{
+								mistake2 (order,"Unit not found");
+								break;
+							}
+
+							if (!unit.spells[i])
+							{
+								mistake2 (order,"Spell not found");
+								break;
+							}
+
+							if (spelldata[i].level > (u2.effskill(SK_MAGIC) + 1) / 2)
+							{
+								mistake2 (order,"Recipient is not able to learn that spell");
+								break;
+							}
+
+							u2.spells[i] = 1;
+
+							event=new Event();
+							event.text=unit.id+" gives "+u2.id+" the "+spelldata[i].name+" spell.";
+							event.unit=unit;
+							event.target=u2;
+							event.location=region;
+							unit.faction.events.push(event);
+							if(unit.faction.no != u2.faction.no)
+								u2.faction.events.push(event);
+						}
+						else
+						{
+							n = tmp;
+							i = finditem (order.args.shift());
+
+							if (i < 0)
+							{
+								mistake2 (order,"Item not recognized");
+								break;
+							}
+
+							if (n > unit.items[i])
+								n = unit.items[i];
+
+							if(n < 0)
+							{
+								mistake2 (order,"Negative amounts not allowed");
+								break;
+							}
+							
+							if (n == 0)
+							{
+								mistake2 (order,"Item not available");
+								break;
+							}
+
+							unit.items[i] -= n;
+
+							event=new Event();
+							event.location=region;
+							event.unit=unit;
+
+							if (u2.getunit0)
+							{
+								if (n == 1)
+									event.text=unit.id+" discards 1 "+items[i].singular+".";
+								else
+									event.text=unit.id+" discards "+n+" "+items[i].plural+".";
+
+								unit.faction.events.push(event);
+								break;
+							}
+
+							event.target=u2;
+							u2.items[i] += n;
+
+							if(n==1)
+								event.text=unit.id+" gives "+u2.id+" 1 "+items[i].singular+".";
+							else
+								event.text=unit.id+" gives "+u2.id+" "+n+" "+items[i].plural+".";
+
+							unit.faction.events.push(event);
+							if(unit.faction.no != u2.faction.no)
+								u2.faction.events.push(event);
+						}
+
+						break;
+
+					case K_PAY:
+						u2 = unit.getunit (order.args);
+
+						if (u2==null)
+						{
+							mistake2 (order,"Unit not found");
+							break;
+						}
+
+						n = order.args.shift();
+
+						if (n > unit.money)
+							n = unit.money;
+
+						if(n < 0)
+						{
+							mistake2 (order,"Negative amounts not allowed");
+							break;
+						}
+
+						if (n == 0)
+						{
+							mistake2 (order,"No money available");
+							break;
+						}
+
+						unit.money -= n;
+						event=new Event();
+						event.location=region;
+						event.unit=unit;
+						event.target=u2;
+
+						if (u2.no != undefined)
+						{
+							u2.money += n;
+							event.text=unit.id+" pays "+u2.id+" $"+n+".";
+							if(unit.faction.no != u2.faction.no)
+								u2.faction.events.push(event);
+						}
+						else {
+							if (u2.getunitpeasants != undefined)
+							{
+								region.money += n;
+								event.text=unit.id+" pays the peasants $"+n+".";
+							}
+							else
+								event.text=unit.id+" discards $"+n+".";
+						}
+						unit.faction.events.push(event);
+						break;
+
+					case K_SINK:
+						if (unit.ship==null)
+						{
+							mistake2 (order,"Not on a ship");
+							break;
+						}
+
+						if (!unit.owner)
+						{
+							mistake2 (order,"Ship not owned by you");
+							break;
+						}
+
+						if (region.terrain == T_OCEAN)
+						{
+							mistake2 (order,"Ship is at sea");
+							break;
+						}
+
+						sh = unit.ship;
+
+						for (u2 in region.units)
+							if (region.units[u2].ship.no == sh.no)
+							{
+								region.units[u2].ship = null;
+								region.units[u2].owner = false;
+							}
+						event=new Event();
+						event.location=region;
+						event.unit=unit;
+						event.target=sh;
+						event.text=unit.id+" sinks "+sh.id+".";
+						region.reportevent(event);
+
+						removenofromarray(region.ships,sh);
+						break;
+				}
+			}
+		}
+
+		/* TRANSFER orders */
+
+		for (u in region.units) {
+			unit=region.units[u];
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case K_TRANSFER:
+						u2 = unit.getunit (order.args);
+
+						if (u2 != null && u2.no != undefined)
+						{
+							if (!u2.accepts(unit))
+							{
+								mistake2 (order,"Unit does not accept your gift");
+								break;
+							}
+						}
+						else if (u2==null || u2.getunitpeasants==undefined)
+						{
+							mistake2 (order,"Unit not found");
+							break;
+						}
+
+						n = order.args.shift();
+
+						if(n < 0)
+						{
+							mistake2 (order,"Negative amounts not allowed");
+							break;
+						}
+
+						if (n > unit.number)
+							n = unit.number;
+
+						if (n == 0)
+						{
+							mistake2 (order,"No people available");
+							break;
+						}
+
+						if (unit.skills[SK_MAGIC] && u2.getunitpeasants != undefined)
+						{
+							k = u2.faction.magicians;
+							if (u2.faction.no != unit.faction.no)
+								k += n;
+							if (!u2.skills[SK_MAGIC])
+								k += u2.number;
+
+							if (k > 3)
+							{
+								mistake2 (order,"Only 3 magicians per faction");
+								break;
+							}
+						}
+
+						k = unit.number - n;
+
+						for (i = 0; i != MAXSKILLS; i++)
+						{
+							j = distribute (unit.number,k,unit.skills[i]);
+							if (u2.getunitpeasants==undefined)
+								u2.skills[i] += u.skills[i] - j;
+							u.skills[i] = j;
+						}
+
+						unit.number = k;
+
+						event=new Event();
+						event.unit=unit;
+						event.location=region;
+						event.target=u2;
+						if (u2.getunitpeasants==undefined)
+						{
+							u2.number += n;
+
+							for (i = 0; i != MAXSPELLS; i++)
+								if (unit.spells[i] && u2.effskill (SK_MAGIC) / 2 >= spelldata[i].level)
+									u2.spells[i] = 1;
+
+							event.text=unit.id+" transfers ";
+							if (k)
+								event.text += n+' ';
+							event.text += "to "+u2.id;
+							if (unit.faction.no != u2.faction.no)
+								u2.faction.events.push(event);
+						}
+						else
+						{
+							region.peasants += n;
+
+							if (k)
+								event.text=unit.id+" disbands "+n+".";
+							else
+								event.text=unit.id+" disbands.";
+						}
+
+						unit.faction.push(event);
+						break;
+				}
+			}
+		}
+
+		/* TAX orders */
+
+		for (u in region.units)
+		{
+			unit=region.units[u];
+			taxed = false;
+
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case K_TAX:
+						if (taxed)
+							break;
+						tmp=false;
+
+						n = unit.armedmen;
+
+						if (!n)
+						{
+							mistake2 (order,"Unit is not armed and combat trained");
+							break;
+						}
+
+						for (u2 in region.units)
+							if (region.units[u2].guard && region.units[u2].number && !region.units[u2].admits(u))
+							{
+								mistake2 (order,region.units[u2].name+" is on guard");
+								tmp=true;
+								break;
+							}
+
+						if (tmp)
+							break;
+
+						order.qty=n*TAXINCOME;
+						taxorders.push(order);
+						taxed = true;
+						break;
+				}
+			}
+		}
+
+		/* Do taxation */
+
+		for (u in region.units)
+			region.units[u].n = -1;
+
+		norders = 0;
+
+		for (S in taxorders)
+			norders += parseInt(taxorders[S].qty / 10);
+
+		i = 0;
+
+		for (S in taxorders) {
+			for (j = parseInt(taxorders[S].qty / 10); j; j--)
+			{
+				taxorders[S].n = 0;
+				i++;
+			}
+		}
+
+		scramble(taxorders);
+
+		for (i = 0; i < taxorders.length && region.money > 10; i++, region.money -= 10)
+		{
+			taxorders[i].unit.money += 10;
+			taxorders[i].unit.n += 10;
+		}
+
+		for (u in region.units) {
+			if (region.units[u].n >= 0)
+			{
+				event=new Event();
+				event.unit=region.units[u];
+				event.location=region;
+				event.text=region.units[u].id+" collects $"+region.units[u].n+" in taxes.";
+				region.units[u].faction.events.push(event);
+			}
+		}
+
+		/* GUARD 1, RECRUIT orders */
+
+		for (u in region.units)
+		{
+			unit=region.units[u];
+
+			availmoney = unit.money;
+
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case K_GUARD:
+						if (order.args.shift())
+							unit.guard = true;
+						break;
+
+					case K_RECRUIT:
+						if (availmoney < RECRUITCOST)
+							break;
+
+						n = order.args.shift();
+
+						if (unit.skills[SK_MAGIC] && unit.faction.magicians + n > 3)
+						{
+							mistake2 (order,"Only 3 magicians per faction");
+							break;
+						}
+
+						n = Math.min (n,parseInt(availmoney / RECRUITCOST));
+
+						order.qty=n;
+						recruitorders.push(order);
+
+						availmoney -= order.qty * RECRUITCOST;
+						break;
+				}
+			}
+		}
+
+		/* Do recruiting */
+
+		expandorders(region,recruitorders);
+
+		for (i = 0, n = parseInt(region.peasants / RECRUITFRACTION); i != recruitorders.length && n; i++, n--)
+		{
+			recruitorders[i].unit.number++;
+			region.peasants--;
+			recruitorders[i].unit.money -= RECRUITCOST;
+			region.money += RECRUITCOST;
+			recruitorders[i].unit.n++;
+		}
+
+		for(u in region.units) {
+			event=new Event();
+			event.location=region;
+			event.unit=region.units[u];
+			event.text=region.units[u].id+" recruits "+region.units[u].n+".";
+			region.units[u].faction.push(event);
+		}
+	}
+}
+
+/* QUIT orders */
+function process_quit()
+{
+	var r,u,S,unit,order;
+
+	log("Processing QUIT orders...");
+
+	for (r in regions)
+		for (u in regions[r].units) {
+			unit=regions[r].units[u];
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case K_QUIT:
+						if(unit.faction.no != order.args.shift())
+						{
+							mistake2 (order,"Correct faction number not given");
+							break;
+						}
+
+						unit.faction.destroy();
+						break;
+				}
+			}
+		}
+}
+
+/* Set production orders */
+function set_production()
+{
+	var r,region,u,unit,S,order,i;
+
+	log("Setting production orders...");
+
+	for (r in regions) {
+		region=regions[r];
+		for (u in region.units)
+		{
+			unit=region.units[u];
+			unit.thisorder=unit.lastorder;
+			unit.thisorder.args=new Array();
+			for(i in unit.lastorder.args)
+				unit.thisorder.args.push(unit.lastorder.args[i]);
+
+			for (S in unit.orders) {
+				order=unit.orders[S];
+				switch (order.command)
+				{
+					case K_BUILD:
+					case K_CAST:
+					case K_ENTERTAIN:
+					case K_MOVE:
+					case K_PRODUCE:
+					case K_RESEARCH:
+					case K_SAIL:
+					case K_STUDY:
+					case K_TEACH:
+					case K_WORK:
+						unit.thisorder=order;
+						break;
+				}
+			}
+
+			switch (unit.thisorder)
+			{
+				case K_MOVE:
+				case K_SAIL:
+					break;
+
+				default:
+					unit.lastorder=unit.thisorder;
+					unit.lastorder.args=new Array();
+					for(i in unit.thisorder.args)
+						unit.lastorder.args.push(unit.thisorder.args[i]);
+			}
+		}
+	}
+}
+
+/* MOVE orders */
+function process_move()
+{
+	var r,region,u,unit,S,order,r2,event;
+
+	log("Processing MOVE orders...");
+
+	for (r in regions) {
+		region=regions[r];
+		for (u=0; u<region.units.length; u++)	// TODO: This array changes as we work through it!
+		{
+			unit=region.units[u];
+			switch (unit.thisorder)
+			{
+				case K_MOVE:
+					r2 = region.movewhere(unit.thisorder.args.shift());
+
+					if (r2==null)
+					{
+						mistakeu (unit,"Direction not recognized");
+						break;
+					}
+
+					if (region.terrain == T_OCEAN)
+					{
+						mistakeu (unit,"Currently at sea");
+						break;
+					}
+
+					if (r2.terrain == T_OCEAN)
+					{
+						event=new Event();
+						event.location=region;
+						event.unit=unit;
+						event.target=r2;
+						event.text=unit.id+" discovers that ("+r2.x+","+r2.y+") is ocean.";
+						unit.faction.events.push(event);
+						break;
+					}
+
+					if (!unit.canmove)
+					{
+						mistakeu (unit,"Carrying too much weight to move");
+						break;
+					}
+
+					unit.leave();
+					removenofromarray(region.units,unit);
+					addnotoarray(r2,unit);
+					unit.region=r2;
+					unit.thisorder=null;
+
+					event=new Event();
+					event.location=region;
+					event.unit=unit;
+					event.target=r2;
+					event.text=unit.id+" ";
+					if(unit.canride)
+						event.text += "rides";
+					else
+						event.text += "walks";
+					event.text += " from "+region.id+" to "+r2.id+".";
+					unit.faction.events.push(event);
+					u--;
+					break;
+			}
+		}
+	}
+}
+
+/* SAIL orders */
+function process_sail()
+{
+	var r,r2,region,u,unit,event,u2,unit2,next;
+
+	log("Processing SAIL orders...");
+
+	for (r in regions) {
+		region=regions[r];
+		for (u=0; u<region.units.length; u++)	// TODO: This array changes as we work through it!
+		{
+			unit=region.units[u];
+			if(u < region.units.length)
+				next=region.units[u+1].no;
+			else
+				next=null;
+
+			switch (unit.thisorder.command)
+			{
+				case K_SAIL:
+					r2 = region.movewhere(unit.thisorder.args.shift());
+
+					if (r2==null)
+					{
+						mistakeu (unit,"Direction not recognized");
+						break;
+					}
+
+					if (unit.ship==null)
+					{
+						mistakeu (unit,"Not on a ship");
+						break;
+					}
+
+					if (unit.owner)
+					{
+						mistakeu (unit,"Ship not owned by you");
+						break;
+					}
+
+					if (r2.terrain != T_OCEAN && !r2.iscoast)
+					{
+						event=new Event();
+						event.unit=unit;
+						event.location=region;
+						event.target=r2;
+						event.text=unit.id+" discovers that ("+r2.x+","+r2.y+") is inland.";
+						unit.faction.events.push(event);
+						break;
+					}
+
+					if (unit.ship.left)
+					{
+						mistakeu (unit,"Ship still under construction");
+						break;
+					}
+
+					if (!unit.ship.cansail)
+					{
+						mistakeu (unit,"Too heavily loaded to sail");
+						break;
+					}
+
+					removenofromarray(region.ships, unit.ship);
+					addnotoarray(r2.ships, unit.ship);
+					unit.ship.region=r2;
+
+					for (u2=0; u2<region.units.length; u2++)	// TODO: This array changes!
+					{
+						unit2=region.units[u2];
+						if(unit2.ship.no==unit.ship.no) {
+							if(next != null) {
+								if(unit2.no==next)
+									u++;
+									if(u>region.units.length)
+										next=null;
+									else
+										next=region.units.length;
+							}
+							removenofromarray(region.units, unit2);
+							addnotoarray(r2.units, unit2);
+							unit2.thisorder=null;
+							unit2.region=r2;
+							if(next != null) {
+								while(region.units[u].no != next)
+									u--;
+							}
+							u2--;
+						}
+					}
+					break;
+			}
+		}
+	}
+}
+
+function buildship(region,unit,sh)
+{
+	var n,event;
+
+	n = unit.number * unit.effskill (SK_SHIPBUILDING);
+	n = Math.min (n,sh.left);
+	n = Math.min (n,unit.items[I_WOOD]);
+	sh.left -= n;
+	unit.items[I_WOOD] -= n;
+
+	unit.skills[SK_SHIPBUILDING] += n * 10;
+
+	event=new Event();
+	event.location=region;
+	event.unit=unit;
+	event.target=sh;
+	event.text=unit.id+" adds "+n+" to "+sh.id+".";
+	unit.faction.events.push(event);
+}
+
+function createship(region,unit,i)
+{
+	var sh;
+
+	if (!unit.effskill (SK_SHIPBUILDING))
+	{
+		mistakeu (unit,"You don't have the skill");
+		return;
+	}
+
+	sh = new Ship();
+
+	sh.type = i;
+	sh.left = shiptypes[i].cost;
+
+	do
+	{
+		sh.no++;
+		sh.name="Ship "+sh.no;
+	}
+	while (findship (sh.no)!=null);
+
+	region.ships.push(sh);
+
+	unit.leave ();
+	unit.ship = sh;
+	unit.owner = true;
+	buildship(region,unit,sh);
+}
+
+/* Do production orders */
+function process_production()
+{
+	var r,region,u,unit,i,b,sh,entertainorders,workorders,produceorders;
+	var n,event,o,j,teaching,teachunits,tmp,u2,k;
+
+	log("Processing production orders...");
+
+	for (r in regions)
+	{
+		region=regions[r];
+		if (region.terrain == T_OCEAN)
+			continue;
+
+		entertainorders = new Array();
+		workorders = new Array();
+		produceorders = new Array(4);
+		for(j=0; j<4; j++)
+			produceorders[j]=new Array();
+
+		for (u in region.units) {
+			unit=region.units[r];
+			switch (unit.thisorder.command)
+			{
+				case K_BUILD:
+					switch (unit.thisorder.args.shift())
+					{
+						case K_BUILDING:
+							if (!unit.effskill (SK_BUILDING))
+							{
+								mistakeu (unit,"You don't have the skill");
+								break;
+							}
+
+							if (!unit.items[I_STONE])
+							{
+								mistakeu (unit,"No stone available");
+								break;
+							}
+
+							b = findbuilding (unit.thisorder.args.shift(),r);
+
+							if (b==null)
+							{
+								b = new Building();
+
+								do
+								{
+									b.no++;
+									b.name="Building "+b.no;
+								}
+								while (findbuilding(b.no)!=null);
+								
+								region.buildings.push(b);
+
+								unit.leave();
+								unit.building = b;
+								unit.owner = true;
+							}
+
+							n = unit.number * unit.effskill (SK_BUILDING);
+							n = Math.min (n,unit.items[I_STONE]);
+							b.size += n;
+							unit.items[I_STONE] -= n;
+
+							unit.skills[SK_BUILDING] += n * 10;
+							
+							event=new Event();
+							event.location=region;
+							event.unit=unit;
+							event.target=b;
+							event.text=unit.id+" adds "+n+" to "+b.id+".";
+							unit.faction.events.push(event);
+							break;
+
+						case K_SHIP:
+							if (!unit.effskill (SK_SHIPBUILDING))
+							{
+								mistakeu (unit,"You don't have the skill");
+								break;
+							}
+
+							if (!unit.items[I_WOOD])
+							{
+								mistakeu (unit,"No wood available");
+								break;
+							}
+
+							sh = findship (unit.thisorder.args.shift(),r);
+
+							if (sh == null)
+							{
+								mistakeu (unit,"Ship not found");
+								break;
+							}
+
+							if (!sh.left)
+							{
+								mistakeu (unit,"Ship is already complete");
+								break;
+							}
+
+							buildship(region,unit,sh);
+							break;
+
+						case K_LONGBOAT:
+							createship(region,unit,SH_LONGBOAT);
+							break;
+
+						case K_CLIPPER:
+							createship(region,unit,SH_CLIPPER);
+							break;
+
+						case K_GALLEON:
+							createship(region,unit,SH_GALLEON);
+							break;
+
+						default:
+							mistakeu (unit,"Order not recognized");
+					}
+
+					break;
+
+				case K_ENTERTAIN:
+					o = new Order();
+					o.command=K_ENTERTAIN;
+					o.unit = unit;
+					o.qty = unit.number * unit.effskill (SK_ENTERTAINMENT) * ENTERTAININCOME;
+					entertainorders.push(o);
+					break;
+
+				case K_PRODUCE:
+					i = unit.thisorder.args.shift();
+
+					if (isNaN(i) || items[i].skill==undefined)
+					{
+						mistakeu (unit,"Item not recognized");
+						break;
+					}
+
+					n = unit.effskill(items[i].skill);
+
+					if (n == 0)
+					{
+						mistakeu (unit,"You don't have the skill");
+						break;
+					}
+
+					if (i == I_PLATE_ARMOR)
+						n /= 3;
+
+					n *= unit.number;
+
+					if (items[items[i].rawmaterial]==undefined)
+					{
+						o=new Order();
+						o.command=K_PRODUCE;
+						o.unit=unit;
+						o.qty=n * terrains[region.terrain].productivity[i];
+						o.args.push(i);
+						produceorders[i].push(o);
+					}
+					else
+					{
+						n = Math.min (n,unit.items[items[i].rawmaterial]);
+
+						if (n == 0)
+						{
+							mistakeu (unit,"No material available");
+							break;
+						}
+
+						unit.items[i] += n;
+						unit.items[items[i].rawmaterial] -= n;
+
+						event=new Event();
+						event.location=region;
+						event.unit=unit;
+						if (n == 1)
+							event.text=unit.id+" produces 1 "+items[i].singular+".";
+						else
+							event.text=unit.id+" produces "+n+" "+items[i].plural+".";
+						unit.faction.events.push(event);
+					}
+
+					unit.skills[items[i].skill] += unit.number * PRODUCEEXP;
+					break;
+
+				case K_RESEARCH:
+					if (unit.effskill (SK_MAGIC) < 2)
+					{
+						mistakeu (unit,"Magic skill of at least 2 required");
+						break;
+					}
+
+					i = unit.thisorder.args.shift();
+
+					if (i > unit.effskill (SK_MAGIC) / 2)
+					{
+						mistakeu (unit,"Insufficient Magic skill - highest available level researched");
+						i = 0;
+					}
+
+					if (i == 0)
+						i = unit.effskill (SK_MAGIC) / 2;
+
+					k = 0;
+
+					for (j = 0; j < MAXSPELLS; j++)
+						if (spelldata[j].level == i && !unit.spells[j])
+							k = 1;
+
+					if (k == 0)
+					{
+						if (unit.money < 200)
+						{
+							mistakeu (unit,"Insufficient funds");
+							break;
+						}
+
+						for (n = unit.number; n; n--)
+							if (unit.money >= 200)
+							{
+								unit.money -= 200;
+								unit.skills[SK_MAGIC] += 10;
+							}
+
+						event=new Event();
+						event.location=region;
+						event.unit=unit;
+						event.text=unit.id+" discovers that no more level "+i+" spells exist.";
+						unit.faction.events.push(event);
+						break;
+					}
+
+					for (n = unit.number; n; n--)
+					{
+						if (unit.money < 200)
+						{
+							mistakeu (unit,"Insufficient funds");
+							break;
+						}
+
+						do
+							j = random(MAXSPELLS);
+						while (spelldata[j].level != i || unit.spells[j] == 1);
+
+						if (!unit.faction.seendata[j])
+						{
+							unit.faction.seendata[j] = 1;
+							unit.faction.showdata[j] = 1;
+						}
+
+						if (unit.spells[j] == 0)
+						{
+							event=new Event();
+							event.location=region;
+							event.unit=unit;
+							event.text=unit.id+" discovers the "+spelldata[j].name+" spell.";
+							unit.faction.events.push(event);
+						}
+
+						unit.spells[j] = 2;
+						unit.skills[SK_MAGIC] += 10;
+					}
+
+					for (j = 0; j < MAXSPELLS; j++)
+						if (unit.spells[j] == 2)
+							unit.spells[j] = 1;
+					break;
+
+				case K_TEACH:
+					teaching = unit.number * 30 * TEACHNUMBER;
+					teachunits=new Array();
+
+					while(1) {
+						tmp=unit.getunit(unit.thisorder.args);
+						if(tmp==null)
+							break;
+						if(tmp.no != undefined)
+							teachunits.push(tmp);
+					}
+
+					for (j = 0; j < teachunits.length; j++)
+					{
+						u2 = teachunits[j];
+
+						if (u2==null)
+						{
+							mistakeu (unit,"Unit not found");
+							continue;
+						}
+
+						if (!u2.accepts(unit))
+						{
+							mistakeu (unit,"Unit does not accept teaching");
+							continue;
+						}
+
+						i = u2.thisorder.args[0];
+
+						if (u2.thisorder.command != K_STUDY || isNaN(i) || i < 0 || i >= MAXSKILLS)
+						{
+							mistakeu (unit,"Unit not studying");
+							continue;
+						}
+
+						if (unit.effskill(i) <= u2.effskill(i))
+						{
+							mistakeu (unit,"Unit not studying a skill you can teach it");
+							continue;
+						}
+
+						n = (u2.number * 30) - u2.learning;
+						n = Math.min (n,teaching);
+
+						if (n == 0)
+							continue;
+
+						u2.learning += n;
+						teaching -= unit.number * 30;
+
+						event=new Event();
+						event.location=region;
+						event.unit=unit;
+						event.text=unit.id+" teaches "+u2.id+" "+skillnames[i]+".";
+						unit.faction.events.push(event);
+						if (u2.faction != unit.faction)
+							u2.faction.events.push(event);
+					}
+
+					break;
+
+				case K_WORK:
+					o = new Order();
+					o.command=K_WORK;
+					o.unit = unit;
+					o.qty = unit.number * terrains[region.terrain].foodproductivity;
+					workorders.push(o);
+					break;
+			}
+		}
+
+				/* Entertainment */
+
+		expandorders(region,entertainorders);
+
+		for (i = 0, n = region.money / ENTERTAINFRACTION; i < entertainorders.length && n; i++, n--)
+		{
+			entertainorders[i].unit.money++;
+			region.money--;
+			entertainorders[i].unit.n++;
+		}
+
+		for (u in region.units)
+			if (region.units[u].n >= 0)
+			{
+				event=new Event();
+				event.location=region;
+				event.unit=region.units[u];
+				event.text=region.units[u].id+" earns $"+region.units[u].n+" entertaining.";
+				region.units[u].faction.events.push(event);
+
+				region.units[u].skills[SK_ENTERTAINMENT] += 10 * region.units[u].number;
+			}
+
+				/* Food production */
+
+		expandorders (region,workorders);
+
+		for (i = 0, n = terrains[region.terrain].maxfoodoutput; i < workorders.length && n; i++, n--)
+		{
+			workorders[i].money++;
+			workorders[i].n++;
+		}
+
+		region.money += Math.min (n,region.peasants * terrains[region.terrain].foodproductivity);
+
+		for (u in region.units) {
+			unit=region.units[u];
+			if (unit.n >= 0)
+			{
+				event=new Event();
+				event.location=region;
+				event.unit=region.units[u];
+				event.text=region.units[u].id+" earns $"+region.units[u].n+" performing manual labor.";
+				region.units[u].faction.events.push(event);
+			}
+		}
+
+				/* Production of other primary commodities */
+
+		for (i = 0; i != 4; i++)
+		{
+			expandorders (region,produceorders[i]);
+
+			for (j = 0, n = terrains[region.terrain].maxoutput[i]; j != produceorders[i].length && n; j++, n--)
+			{
+				produceorders[i][j].unit.items[i]++;
+				produceorders[i][j].unit.n++;
+			}
+
+			for (u in region.units) {
+				unit=region.units[u];
+				if (unit.n >= 0)
+				{
+					event=new Event();
+					event.location=region;
+					event.unit=region.units[u];
+					if(unit.n==1)
+						event.text=region.units[u].id+" produces 1 "+items[i].singular+".";
+					else
+						event.text=region.units[u].id+" produces "+region.units[u].n+" "+items[i].plural+".";
+					region.units[u].faction.events.push(event);
+				}
+			}
+		}
+	}
+}
+
+/* Study skills */
+function process_study()
+{
+	var r,region,u,unit,event,i;
+
+	log("Processing STUDY orders...");
+
+	for (r in regions) {
+		region=regions[r];
+		if (region.terrain != T_OCEAN) {
+			for (u in region.units) {
+				unit=region.units[u];
+				switch (unit.thisorder.command)
+				{
+					case K_STUDY:
+						i = unit.thisorder.args.ahift();
+
+						if (isNaN(i) || i < 0 || i>=MAXSKILLS)
+						{
+							mistakeu (unit,"Skill not recognized");
+							break;
+						}
+
+						if (i == SK_TACTICS || i == SK_MAGIC)
+						{
+							if (unit.money < STUDYCOST * unit.number)
+							{
+								mistakeu (unit,"Insufficient funds");
+								break;
+							}
+
+							if (i == SK_MAGIC && !unit.skills[SK_MAGIC] &&
+								 unit.faction.magicians + unit.number > 3)
+							{
+								mistakeu (unit,"Only 3 magicians per faction");
+								break;
+							}
+
+							unit.money -= STUDYCOST * unit.number;
+						}
+
+						event=new Event();
+						event.location=region;
+						event.unit=region.units[u];
+						event.text=unit.id+" studies "+skillnames[i]+".";
+						unit.faction.events.push(event);
+
+						unit.skills[i] += (unit.number * 30) + unit.learning;
+						break;
+				}
+			}
+		}
+	}
+}
+
+/* Ritual spells, and loss of spells where required */
+function process_cast()
+{
+	var r,region,u,unit,event,i,j,n,f,u2,r2,region2,u3;
+
+	log("Processing CAST orders...");
+
+	for (r in regions)
+	{
+		region=regions[r];
+		for (u in region.units)
+		{
+			unit=region.units[u];
+
+			for (i = 0; i < MAXSPELLS; i++)
+				if (unit.spells[i] && spelldata[i].level > (unit.effskill (SK_MAGIC) + 1) / 2)
+					unit.spells[i] = 0;
+
+			if (unit.combatspell >= 0 && !unit.cancast (unit.combatspell))
+				unit.combatspell = -1;
+		}
+
+		if (region.terrain != T_OCEAN) {
+			for (u in region.units) {
+				unit=region.units[u];
+				switch (unit.thisorder.command)
+				{
+					case K_CAST:
+						i = unit.thisorder.args.shift();
+
+						if (isNaN(i) || i < 0 || i>=MAXSPELLS || !unit.cancast (i))
+						{
+							mistakeu (unit,"Spell not found");
+							break;
+						}
+
+						j = spelldata[i].spellitem;
+
+						if (j != undefined && j >= 0)
+						{
+							if (unit.money < 200 * spelldata[i].level)
+							{
+								mistakeu (unit,"Insufficient funds");
+								break;
+							}
+
+							n = Math.min (unit.number,parseInt(unit.money / (200 * spelldata[i].level)));
+							unit.items[j] += n;
+							unit.money -= n * 200 * spelldata[i].level;
+							unit.skills[SK_MAGIC] += n * 10;
+
+							event=new Event();
+							event.location=region;
+							event.unit=unit;
+							event.text=unit.id+" casts "+spelldata[i].name+".";
+							unit.faction.events.push(event);
+							break;
+						}
+
+						if (unit.money < 50)
+						{
+							mistakeu (unit,"Insufficient funds");
+							break;
+						}
+
+						switch (i)
+						{
+							case SP_CONTAMINATE_WATER:
+								n = unit.cancast (SP_CONTAMINATE_WATER);
+								n = Math.min (n,parseInt(unit.money / 50));
+
+								unit.money -= n * 50;
+								unit.skills[SK_MAGIC] += n * 10;
+
+								n = lovar (n * 50);
+								n = Math.min (n,region.peasants);
+
+								if (!n)
+									break;
+
+								region.peasants -= n;
+
+								for (f in factions)
+								{
+									j = f.cansee (unit);
+
+									if (j)
+									{
+										event=new Event();
+										event.location=region;
+										event.unit=unit;
+										if (j == 2)
+											event.text=unit.id+" contaminates the water supply in "+region.id+", causing "+n+" peasants to die.";
+										else
+											event.text=n+" peasants die in "+region.id+" from drinking contaminated water.";
+										factions[f].events.push(event);
+									}
+								}
+
+								break;
+
+							case SP_TELEPORT:
+								u2 = unit.getunit(unit.thisorder.args);
+
+								if (u2==null || u2.no==undefined)
+								{
+									mistakeu (unit,"Unit not found");
+									break;
+								}
+
+								if (!u2.admits (unit))
+								{
+									mistakeu (u,"Target unit does not provide vector");
+									break;
+								}
+
+								regionloop:
+								for (r2 in regions)
+								{
+									region2=regions[r2];
+									for (u3 in regions[r2].units)
+										if (regions[r2].units[u3].no == u2.no)
+											break regionloop;
+								}
+
+								n = unit.cancast (SP_TELEPORT);
+								n = Math.min (n,parseInt(unit.money / 50));
+
+								unit.money -= n * 50;
+								unit.skills[SK_MAGIC] += n * 10;
+
+								n *= 10000;
+
+								for (;;)
+								{
+									u3 = unit.getunit(unit.thisorder.args);
+
+									if (u3 != null && u3.getunit0 != undefined)
+										break;
+
+									if (u3 == null || u3.no == undefined)
+									{
+										mistakeu (unit,"Unit not found");
+										continue;
+									}
+
+									if (!u3.accepts (unit))
+									{
+										mistakeu (unit,"Unit does not accept teleportation");
+										continue;
+									}
+
+									i = u3.itemweight + u3.horseweight + (u3.number * 10);
+
+									if (i > n)
+									{
+										mistakeu (unit,"Unit too heavy");
+										continue;
+									}
+
+									u3.leave ();
+									n -= i;
+									removenofromarray(region.units, u3);
+									addnotoarray(region2.units,u3);
+									u3.building = u2.building;
+									u3.ship = u2.ship;
+								}
+
+								event=new Event();
+								event.location=region;
+								event.unit=unit;
+								event.target=region2;
+								event.text=unit.id+" casts Teleport.";
+								unit.faction.events.push(event);
+								break;
+
+							default:
+								mistakeu (unit,"Spell not usable with CAST command");
+						}
+
+						break;
+				}
+			}
+		}
+	}
+}
+
+/* Population growth, dispersal and food consumption */
+function process_demographics()
+{
+	var r,region,n,u,unit,i,event;
+
+	log("Processing demographics...");
+
+	for (r in regions)
+	{
+		region=regions[r];
+		if (region.terrain != T_OCEAN)
+		{
+			for (n = region.peasants; n; n--)
+				if (random(100) < POPGROWTH)
+					region.peasants++;
+
+			n = parseInt(region.money / MAINTENANCE);
+			region.peasants = Math.min (region.peasants,n);
+			region.money -= region.peasants * MAINTENANCE;
+
+			for (n = region.peasants; n; n--)
+				if (random(100) < PEASANTMOVE)
+				{
+					i = random(4);
+
+					if (region.connect[i].terrain != T_OCEAN)
+					{
+						region.peasants--;
+						region.connect[i].immigrants++;
+					}
+				}
+		}
+
+		for (u in region.units)
+		{
+			unit=region.units[u];
+
+			getmoney (r,unit,unit.number * MAINTENANCE);
+			n = unit.money / MAINTENANCE;
+
+			if (unit.number > n)
+			{
+				event=new Event();
+				event.location=region;
+				event.unit=unit;
+			
+				if (n)
+					event.text=unit.id+" loses "+n+" to starvation.";
+				else
+					event.text=unit.id+" starves to death.";
+				unit.faction.events.push(event);
+
+				for (i = 0; i < MAXSKILLS; i++)
+					unit.skills[i] = distribute (unit.number,n,unit.skills[i]);
+
+				unit.number = n;
+			}
+
+			unit.money -= unit.number * MAINTENANCE;
+		}
+	}
+}
+
+function processorders()
+{
+	var f,r;
+
+	process_form();
+	process_instant();
+	process_find();
+	process_leaveEnter();
+	process_attack();		// TODO Combat!
+	process_economic();
+	process_quit();
+
+	/* Remove players who haven't sent in orders */
+
+	for (f in factions)
+		if (turn - factions[f].lastorders > ORDERGAP)
+			factions[f].destroy();
+
+	/* Clear away debris of destroyed factions */
+
+	removeempty();
+	removenullfactions();
+
+	set_production();
+	process_move();
+	process_sail();
+	process_production();
+	process_study();
+	process_cast();
+	process_demographics();
+
+	removeempty ();
+
+	for (r in regions)
+		regions[r].peasants += regions[r].immigrants;
+
+			/* Warn players who haven't sent in orders */
+
+	for (f in factions)
+		if (turn - factions[r].lastorders == ORDERGAP)
+			factions[r].messages.push("Please send orders next turn if you wish to continue playing.");
+}
+
+function writesummary()
+{
+	var sfile=new File(game_dir+"summary");
+	sfile.open("w");
+	sfile.write(gamesummary());
+	sfile.close();
+}
+
+initgame(argc > 0? argv[0] : '.');
+writesummary();
+writegame();
diff --git a/xtrn/atlantis/event.js b/xtrn/atlantis/event.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e8639ef905954a0fcad40be3f92d0fe3bc6e9ff
--- /dev/null
+++ b/xtrn/atlantis/event.js
@@ -0,0 +1,8 @@
+function Event()
+{
+	this.text='';
+	this.location=null;
+	this.unit=null;
+	this.target=null;
+	this.subject=null;
+}
diff --git a/xtrn/atlantis/faction.js b/xtrn/atlantis/faction.js
new file mode 100644
index 0000000000000000000000000000000000000000..cb1a9c6a106cd04817e736cf89f197cdcfab248b
--- /dev/null
+++ b/xtrn/atlantis/faction.js
@@ -0,0 +1,174 @@
+var factions=new Array();
+
+if(js.global.terrains==undefined)
+	load(script_dir+'gamedefs.js');
+if(js.global.Region==undefined)
+	load(script_dir+'region.js');
+if(js.global.Building==undefined)
+	load(script_dir+'building.js');
+if(js.global.Ship==undefined)
+	load(script_dir+'ship.js');
+if(js.global.Unit==undefined)
+	load(script_dir+'unit.js');
+if(js.global.Event==undefined)
+	load(script_dir+'event.js');
+
+function Faction()
+{
+	this.no=0;
+	this.name='';
+	this.addr='';
+	this.password='';
+	this.lastorders=0;
+	this.seendata=new Array();
+	this.showdata=new Array();
+	this.accept=new Array();
+	this.admit=new Array();
+	this.allies=new Array();
+	this.mistakes=new Array();
+	this.messages=new Array();
+	this.battles=new Array();
+	this.events=new Array();
+	this.alive=false;
+	this.attacking=false;
+	this.dh=false;
+	this.nunits=0;
+	this.number=0;
+	this.money=0;
+	this.id getter=function() { return(this.name+' ('+this.no+')'); };
+	this.magicians getter=function() {
+		var n,r,u;
+
+		n = 0;
+
+		for(r in regions)
+			for (u in regions[r].units)
+				if (regions[r].units[u].skills[SK_MAGIC] && regions[r].units[u].faction.no == this.no)
+					n += regions[r].units[u].number;
+
+		return n;
+	};
+
+	this.ispresent=function(region) {
+		var unit;
+
+		for (unit in region.units)
+			if(region.units[unit].faction.no==this.no)
+				return(true);
+
+		return(false);
+	};
+	this.cansee=function(unit) {
+		var cansee=0;
+		var stealth;
+		var observation;
+		var u;
+
+		/* You can always see your own units in detail */
+		if(unit.faction.no==this.no)
+			return(2);
+
+		/* You can always see units on guard, in a building, or on a ship */
+		if (unit.guard || unit.building || unit.ship)
+			cansee = 1;
+
+		/* Stealthiness */
+		stealth = unit.effskill(SK_STEALTH);
+
+		for(u in unit.region.units) {
+			/* Only your own units help see things. */
+			if(unit.region.units[u].faction.no != this.no)
+				continue;
+
+			/* If the unit is invisible and your unit doesn't have true seeing, your unit doesn't help */
+			if(unit.items[I_RING_OF_INVISIBILITY]
+					&& unit.items[I_RING_OF_INVISIBILITY] == unit.number
+					&& !unit.region.units[u].items[I_AMULET_OF_TRUE_SEEING])
+				continue;
+
+			observation=unit.region.units[u].effskill(SK_OBSERVATION);
+
+			/* If you're better at observation than they are at hiding, you can see their detail */
+			if(observation > stealth)
+				return(2);
+			/* If you're just as good, you can see them, but not the detail */
+			if(observation >= stealth)
+				cansee = 1;
+		}
+
+		return(cansee);
+	};
+	this.destroy=function()
+	{
+		var u,r;
+
+		for (r in regions)
+			for (u in regions[r].units)
+				if (regions[r].units[u].faction.no == this.no)
+				{
+					if (regions[r].terrain != T_OCEAN)
+						regions[r].peasants += regions[r].units[u].number;
+
+					regions[r].units[u].number = 0;
+				}
+
+		this.alive = false;
+	};
+}
+
+function findfaction(no)
+{
+	var f;
+
+	no=parseInt(no);
+	for(f in factions)
+		if(factions[f].no==no)
+			return(factions[f]);
+
+	return null;
+}
+
+function removenullfactions()
+{
+	var f,f2,f3;
+	var rf,rf2;
+
+	for (f in factions)
+	{
+		if (!factions[f].alive)
+		{
+			printf ("Removing %s.\n",factions[f].name);
+
+			for (f3 in factions)
+				for (rf in factions[f3].allies)
+				{
+					if (factions[f3].allies[rf].no == factions[f].no)
+						factions[f3].allies.splice(rf,1);
+				}
+
+			factions.splice(f,1);
+		}
+	}
+}
+
+function factionnameisunique(name)
+{
+	var f;
+
+	for(f in factions)
+		if(factions[f].name==name)
+			return(false);
+
+	return true;
+}
+
+function factionbyaddr(addr)
+{
+	var f;
+
+	for(f in factions)
+		if(factions[f].addr==addr)
+			return(factions[f]);
+
+	return null;
+}
diff --git a/xtrn/atlantis/fulljslint.js b/xtrn/atlantis/fulljslint.js
new file mode 100644
index 0000000000000000000000000000000000000000..f74f00144a7c84ec9650fc41c40a0a78473b66d2
--- /dev/null
+++ b/xtrn/atlantis/fulljslint.js
@@ -0,0 +1,4945 @@
+// jslint.js
+// 2009-01-26
+
+
+/*
+Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+    JSLINT is a global function. It takes two parameters.
+
+        var myResult = JSLINT(source, option);
+
+    The first parameter is either a string or an array of strings. If it is a
+    string, it will be split on '\n' or '\r'. If it is an array of strings, it
+    is assumed that each string represents one line. The source can be a
+    JavaScript text, or HTML text, or a Konfabulator text.
+
+    The second parameter is an optional object of options which control the
+    operation of JSLINT. Most of the options are booleans: They are all are
+    optional and have a default value of false.
+
+    If it checks out, JSLINT returns true. Otherwise, it returns false.
+
+    If false, you can inspect JSLINT.errors to find out the problems.
+    JSLINT.errors is an array of objects containing these members:
+
+    {
+        line      : The line (relative to 0) at which the lint was found
+        character : The character (relative to 0) at which the lint was found
+        reason    : The problem
+        evidence  : The text line in which the problem occurred
+        raw       : The raw message before the details were inserted
+        a         : The first detail
+        b         : The second detail
+        c         : The third detail
+        d         : The fourth detail
+    }
+
+    If a fatal error was found, a null will be the last element of the
+    JSLINT.errors array.
+
+    You can request a Function Report, which shows all of the functions
+    and the parameters and vars that they use. This can be used to find
+    implied global variables and other problems. The report is in HTML and
+    can be inserted in an HTML <body>.
+
+        var myReport = JSLINT.report(limited);
+
+    If limited is true, then the report will be limited to only errors.
+*/
+
+/*jslint evil: true, nomen: false, onevar: false, regexp: false */
+
+/*global JSLINT*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "\"", "%", "(begin)",
+    "(breakage)", "(context)", "(error)", "(global)", "(identifier)",
+    "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)",
+    "(verb)", ")", "++", "--", "\/", ADSAFE, Array, Boolean, COM, Canvas,
+    CustomAnimation, Date, Debug, E, Error, EvalError, FadeAnimation,
+    FormField, Frame, Function, HotKey, Image, JSON, LN10, LN2, LOG10E,
+    LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem, MoveAnimation,
+    NEGATIVE_INFINITY, Number, Object, Option, PI, POSITIVE_INFINITY, Point,
+    RangeError, ReferenceError, RegExp, ResizeAnimation, RotateAnimation,
+    SQRT1_2, SQRT2, ScrollBar, String, SyntaxError, System, Text, TextArea,
+    Timer, TypeError, URIError, URL, Window, XMLDOM, XMLHttpRequest, "\\",
+    "]", a, abbr, acronym, active, address, adsafe, after, alert, aliceblue,
+    animator, antiquewhite, appleScript, applet, apply, approved, aqua,
+    aquamarine, area, arguments, arity, autocomplete, azure, b, background,
+    "background-attachment", "background-color", "background-image",
+    "background-position", "background-repeat", base, bdo, beep, before,
+    beige, big, bisque, bitwise, black, blanchedalmond, block, blockquote,
+    blue, blueviolet, blur, body, border, "border-bottom",
+    "border-bottom-color", "border-bottom-style", "border-bottom-width",
+    "border-collapse", "border-color", "border-left", "border-left-color",
+    "border-left-style", "border-left-width", "border-right",
+    "border-right-color", "border-right-style", "border-right-width",
+    "border-spacing", "border-style", "border-top", "border-top-color",
+    "border-top-style", "border-top-width", "border-width", bottom, br,
+    brown, browser, burlywood, button, bytesToUIString, c, cadetblue, call,
+    callee, caller, canvas, cap, caption, "caption-side", cases, center,
+    charAt, charCodeAt, character, chartreuse, chocolate, chooseColor,
+    chooseFile, chooseFolder, cite, clear, clearInterval, clearTimeout,
+    clip, close, closeWidget, closed, cm, code, col, colgroup, color,
+    comment, condition, confirm, console, constructor, content,
+    convertPathToHFS, convertPathToPlatform, coral, cornflowerblue,
+    cornsilk, "counter-increment", "counter-reset", create, crimson, css,
+    cursor, cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen,
+    darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred,
+    darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise,
+    darkviolet, dd, debug, decodeURI, decodeURIComponent, deeppink,
+    deepskyblue, defaultStatus, defineClass, del, deserialize, dfn, dimgray,
+    dir, direction, display, div, dl, document, dodgerblue, dt, else, em,
+    embed, empty, "empty-cells", encodeURI, encodeURIComponent, entityify,
+    eqeqeq, errors, escape, eval, event, evidence, evil, ex, exec, exps,
+    fieldset, filesystem, firebrick, first, "first-child", "first-letter",
+    "first-line", float, floor, floralwhite, focus, focusWidget, font,
+    "font-face", "font-family", "font-size", "font-size-adjust",
+    "font-stretch", "font-style", "font-variant", "font-weight",
+    forestgreen, forin, form, fragment, frame, frames, frameset, from,
+    fromCharCode, fuchsia, fud, function, g, gainsboro, gc,
+    getComputedStyle, ghostwhite, gold, goldenrod, gray, green, greenyellow,
+    h1, h2, h3, h4, h5, h6, hasOwnProperty, head, height, help, history,
+    honeydew, hotpink, hover, hr, html, i, iTunes, id, identifier, iframe,
+    img, import, in, include, indent, indexOf, indianred, indigo, init,
+    input, ins, isAlpha, isApplicationRunning, isDigit, isFinite, isNaN,
+    ivory, join, kbd, khaki, konfabulatorVersion, label, labelled, lang,
+    lavender, lavenderblush, lawngreen, laxbreak, lbp, led, left, legend,
+    lemonchiffon, length, "letter-spacing", li, lightblue, lightcoral,
+    lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon,
+    lightseagreen, lightskyblue, lightslategray, lightsteelblue,
+    lightyellow, lime, limegreen, line, "line-height", linen, link,
+    "list-style", "list-style-image", "list-style-position",
+    "list-style-type", load, loadClass, location, log, m, magenta, map,
+    margin, "margin-bottom", "margin-left", "margin-right", "margin-top",
+    "marker-offset", maroon, match, "max-height", "max-width", media,
+    mediumaquamarine, mediumblue, mediumorchid, mediumpurple,
+    mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise,
+    mediumvioletred, menu, message, meta, midnightblue, "min-height",
+    "min-width", mintcream, mistyrose, mm, moccasin, moveBy, moveTo, name,
+    navajowhite, navigator, navy, new, newcap, noframes, nomen, noscript,
+    nud, object, ol, oldlace, olive, olivedrab, on, onblur, onerror, onevar,
+    onfocus, onload, onresize, onunload, opacity, open, openURL, opener,
+    opera, optgroup, option, orange, orangered, orchid, outer, outline,
+    "outline-color", "outline-style", "outline-width", overflow, p, padding,
+    "padding-bottom", "padding-left", "padding-right", "padding-top", page,
+    palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip,
+    param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru,
+    pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre,
+    predef, preferenceGroups, preferences, print, prompt, prototype, pt,
+    purple, push, px, q, quit, quotes, random, range, raw, reach, readFile,
+    readUrl, reason, red, regexp, reloadWidget, replace, report, reserved,
+    resizeBy, resizeTo, resolvePath, resumeUpdates, rhino, right, rosybrown,
+    royalblue, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp,
+    sandybrown, saveAs, savePreferences, screen, script, scroll, scrollBy,
+    scrollTo, seagreen, seal, search, seashell, select, self, serialize,
+    setInterval, setTimeout, shift, showWidgetPreferences, sidebar, sienna,
+    silver, skyblue, slateblue, slategray, sleep, slice, small, snow, sort,
+    span, spawn, speak, split, springgreen, src, status, steelblue, strict,
+    strong, style, styleproperty, sub, substr, sup, supplant,
+    suppressUpdates, sync, system, table, "table-layout", tan, tbody, td,
+    teal, tellWidget, test, "text-align", "text-decoration", "text-indent",
+    "text-shadow", "text-transform", textarea, tfoot, th, thead, thistle,
+    title, toLowerCase, toString, toUpperCase, toint32, token, tomato, top,
+    tr, tt, turquoise, type, u, ul, undef, unescape, "unicode-bidi",
+    unwatch, updateNow, value, valueOf, var, version, "vertical-align",
+    violet, visibility, visited, watch, wheat, white, "white-space",
+    whitesmoke, widget, width, window, "word-spacing", yahooCheckLogin,
+    yahooLogin, yahooLogout, yellow, yellowgreen, "z-index"
+*/
+
+// We build the application inside a function so that we produce only a single
+// global variable. The function will be invoked, its return value is the JSLINT
+// application itself.
+
+"use strict";
+
+JSLINT = function () {
+    var adsafe_id,      // The widget's ADsafe id.
+        adsafe_may,     // The widget may load approved scripts.
+        adsafe_went,    // ADSAFE.go has been called.
+        anonname,       // The guessed name for anonymous functions.
+        approved,       // ADsafe approved urls.
+
+        atrule = {
+            'import'   : true,
+            media      : true,
+            'font-face': true,
+            page       : true
+        },
+
+        badbreak = {
+            ')': true,
+            ']': true,
+            '++': true,
+            '--': true
+        },
+
+// These are members that should not be permitted in third party ads.
+
+        banned = {              // the member names that ADsafe prohibits.
+            apply           : true,
+            'arguments'     : true,
+            call            : true,
+            callee          : true,
+            caller          : true,
+            constructor     : true,
+            'eval'          : true,
+            prototype       : true,
+            unwatch         : true,
+            valueOf         : true,
+            watch           : true
+        },
+
+
+// These are the JSLint boolean options.
+
+        boolOptions = {
+            adsafe     : true, // if ADsafe should be enforced
+            bitwise    : true, // if bitwise operators should not be allowed
+            browser    : true, // if the standard browser globals should be predefined
+            cap        : true, // if upper case HTML should be allowed
+            css        : true, // if CSS workarounds should be tolerated
+            debug      : true, // if debugger statements should be allowed
+            eqeqeq     : true, // if === should be required
+            evil       : true, // if eval should be allowed
+            forin      : true, // if for in statements must filter
+            fragment   : true, // if HTML fragments should be allowed
+            laxbreak   : true, // if line breaks should not be checked
+            newcap     : true, // if constructor names must be capitalized
+            nomen      : true, // if names should be checked
+            on         : true, // if HTML event handlers should be allowed
+            onevar     : true, // if only one var statement per function should be allowed
+            passfail   : true, // if the scan should stop on first error
+            plusplus   : true, // if increment/decrement should not be allowed
+            regexp     : true, // if the . should not be allowed in regexp literals
+            rhino      : true, // if the Rhino environment globals should be predefined
+            undef      : true, // if variables should be declared before used
+            safe       : true, // if use of some browser features should be restricted
+            sidebar    : true, // if the System object should be predefined
+            strict     : true, // require the "use strict"; pragma
+            sub        : true, // if all forms of subscript notation are tolerated
+            white      : true, // if strict whitespace rules apply
+            widget     : true  // if the Yahoo Widgets globals should be predefined
+        },
+
+// browser contains a set of global names which are commonly provided by a
+// web browser environment.
+
+        browser = {
+            alert           : true,
+            blur            : true,
+            clearInterval   : true,
+            clearTimeout    : true,
+            close           : true,
+            closed          : true,
+            confirm         : true,
+            console         : true,
+            Debug           : true,
+            defaultStatus   : true,
+            document        : true,
+            event           : true,
+            focus           : true,
+            frames          : true,
+            getComputedStyle: true,
+            history         : true,
+            Image           : true,
+            length          : true,
+            location        : true,
+            moveBy          : true,
+            moveTo          : true,
+            name            : true,
+            navigator       : true,
+            onblur          : true,
+            onerror         : true,
+            onfocus         : true,
+            onload          : true,
+            onresize        : true,
+            onunload        : true,
+            open            : true,
+            opener          : true,
+            opera           : true,
+            Option          : true,
+            parent          : true,
+            print           : true,
+            prompt          : true,
+            resizeBy        : true,
+            resizeTo        : true,
+            screen          : true,
+            scroll          : true,
+            scrollBy        : true,
+            scrollTo        : true,
+            self            : true,
+            setInterval     : true,
+            setTimeout      : true,
+            status          : true,
+            top             : true,
+            window          : true,
+            XMLHttpRequest  : true
+        },
+
+        cssAttributeData,
+        cssAny,
+
+        cssColorData = {
+            "aliceblue": true,
+            "antiquewhite": true,
+            "aqua": true,
+            "aquamarine": true,
+            "azure": true,
+            "beige": true,
+            "bisque": true,
+            "black": true,
+            "blanchedalmond": true,
+            "blue": true,
+            "blueviolet": true,
+            "brown": true,
+            "burlywood": true,
+            "cadetblue": true,
+            "chartreuse": true,
+            "chocolate": true,
+            "coral": true,
+            "cornflowerblue": true,
+            "cornsilk": true,
+            "crimson": true,
+            "cyan": true,
+            "darkblue": true,
+            "darkcyan": true,
+            "darkgoldenrod": true,
+            "darkgray": true,
+            "darkgreen": true,
+            "darkkhaki": true,
+            "darkmagenta": true,
+            "darkolivegreen": true,
+            "darkorange": true,
+            "darkorchid": true,
+            "darkred": true,
+            "darksalmon": true,
+            "darkseagreen": true,
+            "darkslateblue": true,
+            "darkslategray": true,
+            "darkturquoise": true,
+            "darkviolet": true,
+            "deeppink": true,
+            "deepskyblue": true,
+            "dimgray": true,
+            "dodgerblue": true,
+            "firebrick": true,
+            "floralwhite": true,
+            "forestgreen": true,
+            "fuchsia": true,
+            "gainsboro": true,
+            "ghostwhite": true,
+            "gold": true,
+            "goldenrod": true,
+            "gray": true,
+            "green": true,
+            "greenyellow": true,
+            "honeydew": true,
+            "hotpink": true,
+            "indianred": true,
+            "indigo": true,
+            "ivory": true,
+            "khaki": true,
+            "lavender": true,
+            "lavenderblush": true,
+            "lawngreen": true,
+            "lemonchiffon": true,
+            "lightblue": true,
+            "lightcoral": true,
+            "lightcyan": true,
+            "lightgoldenrodyellow": true,
+            "lightgreen": true,
+            "lightpink": true,
+            "lightsalmon": true,
+            "lightseagreen": true,
+            "lightskyblue": true,
+            "lightslategray": true,
+            "lightsteelblue": true,
+            "lightyellow": true,
+            "lime": true,
+            "limegreen": true,
+            "linen": true,
+            "magenta": true,
+            "maroon": true,
+            "mediumaquamarine": true,
+            "mediumblue": true,
+            "mediumorchid": true,
+            "mediumpurple": true,
+            "mediumseagreen": true,
+            "mediumslateblue": true,
+            "mediumspringgreen": true,
+            "mediumturquoise": true,
+            "mediumvioletred": true,
+            "midnightblue": true,
+            "mintcream": true,
+            "mistyrose": true,
+            "moccasin": true,
+            "navajowhite": true,
+            "navy": true,
+            "oldlace": true,
+            "olive": true,
+            "olivedrab": true,
+            "orange": true,
+            "orangered": true,
+            "orchid": true,
+            "palegoldenrod": true,
+            "palegreen": true,
+            "paleturquoise": true,
+            "palevioletred": true,
+            "papayawhip": true,
+            "peachpuff": true,
+            "peru": true,
+            "pink": true,
+            "plum": true,
+            "powderblue": true,
+            "purple": true,
+            "red": true,
+            "rosybrown": true,
+            "royalblue": true,
+            "saddlebrown": true,
+            "salmon": true,
+            "sandybrown": true,
+            "seagreen": true,
+            "seashell": true,
+            "sienna": true,
+            "silver": true,
+            "skyblue": true,
+            "slateblue": true,
+            "slategray": true,
+            "snow": true,
+            "springgreen": true,
+            "steelblue": true,
+            "tan": true,
+            "teal": true,
+            "thistle": true,
+            "tomato": true,
+            "turquoise": true,
+            "violet": true,
+            "wheat": true,
+            "white": true,
+            "whitesmoke": true,
+            "yellow": true,
+            "yellowgreen": true
+        },
+
+        cssBorderStyle,
+
+        cssLengthData = {
+            '%': true,
+            'cm': true,
+            'em': true,
+            'ex': true,
+            'in': true,
+            'mm': true,
+            'pc': true,
+            'pt': true,
+            'px': true
+        },
+
+        escapes = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '/' : '\\/',
+            '\\': '\\\\'
+        },
+
+        funct,          // The current function
+        functions,      // All of the functions
+
+        global,         // The global scope
+        htmltag = {
+            a:        {},
+            abbr:     {},
+            acronym:  {},
+            address:  {},
+            applet:   {},
+            area:     {empty: true, parent: ' map '},
+            b:        {},
+            base:     {empty: true, parent: ' head '},
+            bdo:      {},
+            big:      {},
+            blockquote: {},
+            body:     {parent: ' html noframes '},
+            br:       {empty: true},
+            button:   {},
+            canvas:   {parent: ' body p div th td '},
+            caption:  {parent: ' table '},
+            center:   {},
+            cite:     {},
+            code:     {},
+            col:      {empty: true, parent: ' table colgroup '},
+            colgroup: {parent: ' table '},
+            dd:       {parent: ' dl '},
+            del:      {},
+            dfn:      {},
+            dir:      {},
+            div:      {},
+            dl:       {},
+            dt:       {parent: ' dl '},
+            em:       {},
+            embed:    {},
+            fieldset: {},
+            font:     {},
+            form:     {},
+            frame:    {empty: true, parent: ' frameset '},
+            frameset: {parent: ' html frameset '},
+            h1:       {},
+            h2:       {},
+            h3:       {},
+            h4:       {},
+            h5:       {},
+            h6:       {},
+            head:     {parent: ' html '},
+            html:     {parent: '*'},
+            hr:       {empty: true},
+            i:        {},
+            iframe:   {},
+            img:      {empty: true},
+            input:    {empty: true},
+            ins:      {},
+            kbd:      {},
+            label:    {},
+            legend:   {parent: ' fieldset '},
+            li:       {parent: ' dir menu ol ul '},
+            link:     {empty: true, parent: ' head '},
+            map:      {},
+            menu:     {},
+            meta:     {empty: true, parent: ' head noframes noscript '},
+            noframes: {parent: ' html body '},
+            noscript: {parent: ' body head noframes '},
+            object:   {},
+            ol:       {},
+            optgroup: {parent: ' select '},
+            option:   {parent: ' optgroup select '},
+            p:        {},
+            param:    {empty: true, parent: ' applet object '},
+            pre:      {},
+            q:        {},
+            samp:     {},
+            script:   {empty: true, parent: ' body div frame head iframe p pre span '},
+            select:   {},
+            small:    {},
+            span:     {},
+            strong:   {},
+            style:    {parent: ' head ', empty: true},
+            sub:      {},
+            sup:      {},
+            table:    {},
+            tbody:    {parent: ' table '},
+            td:       {parent: ' tr '},
+            textarea: {},
+            tfoot:    {parent: ' table '},
+            th:       {parent: ' tr '},
+            thead:    {parent: ' table '},
+            title:    {parent: ' head '},
+            tr:       {parent: ' table tbody thead tfoot '},
+            tt:       {},
+            u:        {},
+            ul:       {},
+            'var':    {}
+        },
+
+        ids,            // HTML ids
+        implied,        // Implied globals
+        inblock,
+        indent,
+        jsonmode,
+        lines,
+        lookahead,
+        member,
+        membersOnly,
+        nexttoken,
+        noreach,
+        option,
+        predefined,     // Global variables defined by option
+        prereg,
+        prevtoken,
+
+        pseudorule = {
+            'first-child': true,
+            link         : true,
+            visited      : true,
+            hover        : true,
+            active       : true,
+            focus        : true,
+            lang         : true,
+            'first-letter' : true,
+            'first-line' : true,
+            before       : true,
+            after        : true
+        },
+
+        rhino = {
+            defineClass : true,
+            deserialize : true,
+            gc          : true,
+            help        : true,
+            load        : true,
+            loadClass   : true,
+            print       : true,
+            quit        : true,
+            readFile    : true,
+            readUrl     : true,
+            runCommand  : true,
+            seal        : true,
+            serialize   : true,
+            spawn       : true,
+            sync        : true,
+            toint32     : true,
+            version     : true
+        },
+
+        scope,      // The current scope
+
+        sidebar = {
+            System      : true
+        },
+
+        src,
+        stack,
+
+// standard contains the global names that are provided by the
+// ECMAScript standard.
+
+        standard = {
+            Array               : true,
+            Boolean             : true,
+            Date                : true,
+            decodeURI           : true,
+            decodeURIComponent  : true,
+            encodeURI           : true,
+            encodeURIComponent  : true,
+            Error               : true,
+            'eval'              : true,
+            EvalError           : true,
+            Function            : true,
+            isFinite            : true,
+            isNaN               : true,
+            JSON                : true,
+            Math                : true,
+            Number              : true,
+            Object              : true,
+            parseInt            : true,
+            parseFloat          : true,
+            RangeError          : true,
+            ReferenceError      : true,
+            RegExp              : true,
+            String              : true,
+            SyntaxError         : true,
+            TypeError           : true,
+            URIError            : true
+        },
+
+        standard_member = {
+            E                   : true,
+            LN2                 : true,
+            LN10                : true,
+            LOG2E               : true,
+            LOG10E              : true,
+            PI                  : true,
+            SQRT1_2             : true,
+            SQRT2               : true,
+            MAX_VALUE           : true,
+            MIN_VALUE           : true,
+            NEGATIVE_INFINITY   : true,
+            POSITIVE_INFINITY   : true
+        },
+
+        syntax = {},
+        tab,
+        token,
+        urls,
+        warnings,
+
+// widget contains the global names which are provided to a Yahoo
+// (fna Konfabulator) widget.
+
+        widget = {
+            alert                   : true,
+            animator                : true,
+            appleScript             : true,
+            beep                    : true,
+            bytesToUIString         : true,
+            Canvas                  : true,
+            chooseColor             : true,
+            chooseFile              : true,
+            chooseFolder            : true,
+            closeWidget             : true,
+            COM                     : true,
+            convertPathToHFS        : true,
+            convertPathToPlatform   : true,
+            CustomAnimation         : true,
+            escape                  : true,
+            FadeAnimation           : true,
+            filesystem              : true,
+            focusWidget             : true,
+            form                    : true,
+            FormField               : true,
+            Frame                   : true,
+            HotKey                  : true,
+            Image                   : true,
+            include                 : true,
+            isApplicationRunning    : true,
+            iTunes                  : true,
+            konfabulatorVersion     : true,
+            log                     : true,
+            MenuItem                : true,
+            MoveAnimation           : true,
+            openURL                 : true,
+            play                    : true,
+            Point                   : true,
+            popupMenu               : true,
+            preferenceGroups        : true,
+            preferences             : true,
+            print                   : true,
+            prompt                  : true,
+            random                  : true,
+            reloadWidget            : true,
+            ResizeAnimation         : true,
+            resolvePath             : true,
+            resumeUpdates           : true,
+            RotateAnimation         : true,
+            runCommand              : true,
+            runCommandInBg          : true,
+            saveAs                  : true,
+            savePreferences         : true,
+            screen                  : true,
+            ScrollBar               : true,
+            showWidgetPreferences   : true,
+            sleep                   : true,
+            speak                   : true,
+            suppressUpdates         : true,
+            system                  : true,
+            tellWidget              : true,
+            Text                    : true,
+            TextArea                : true,
+            Timer                   : true,
+            unescape                : true,
+            updateNow               : true,
+            URL                     : true,
+            widget                  : true,
+            Window                  : true,
+            XMLDOM                  : true,
+            XMLHttpRequest          : true,
+            yahooCheckLogin         : true,
+            yahooLogin              : true,
+            yahooLogout             : true
+        },
+
+//  xmode is used to adapt to the exceptions in html parsing.
+//  It can have these states:
+//      false   .js script file
+//      html
+//      outer
+//      script
+//      style
+//      scriptstring
+//      styleproperty
+
+        xmode,
+        xquote,
+
+// unsafe comment or string
+        ax = /@cc|<\/?|script|\]*s\]|<\s*!|&lt/i,
+// unsafe characters that are silently deleted by one or more browsers
+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+// token
+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(global|extern|jslint|member|members)?|=|\/)?|\*[\/=]?|\+[+=]?|-[\-=]?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,
+// html token
+        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--|.)/,
+// outer html token
+        ox = /[>&]|<[\/!]?|--/,
+// star slash
+        lx = /\*\/|\/\*/,
+// identifier
+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,
+// javascript url
+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,
+// url badness
+        ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i,
+// style
+        sx = /^\s*([{:#*%.=,>+\[\]@()"';*]|[a-zA-Z0-9_][a-zA-Z0-9_\-]*|<\/|\/\*)/,
+        ssx = /^\s*([@#!"'};:\-\/%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\d+(?:\.\d+)?|<\/)/,
+// query characters
+        qx = /[\[\]\/\\"'*<>.&:(){}+=#_]/,
+// query characters for ids
+        dx = /[\[\]\/\\"'*<>.&:(){}+=#]/,
+
+        rx = {
+            outer: hx,
+            html: hx,
+            style: sx,
+            styleproperty: ssx
+        };
+
+    function F() {}
+
+    if (typeof Object.create !== 'function') {
+        Object.create = function (o) {
+            F.prototype = o;
+            return new F();
+        };
+    }
+    function combine(t, o) {
+        var n;
+        for (n in o) {
+            if (o.hasOwnProperty(n)) {
+                t[n] = o[n];
+            }
+        }
+    }
+
+    String.prototype.entityify = function () {
+        return this.
+            replace(/&/g, '&amp;').
+            replace(/</g, '&lt;').
+            replace(/>/g, '&gt;');
+    };
+
+    String.prototype.isAlpha = function () {
+        return (this >= 'a' && this <= 'z\uffff') ||
+            (this >= 'A' && this <= 'Z\uffff');
+    };
+
+
+    String.prototype.isDigit = function () {
+        return (this >= '0' && this <= '9');
+    };
+
+
+    String.prototype.supplant = function (o) {
+        return this.replace(/\{([^{}]*)\}/g, function (a, b) {
+            var r = o[b];
+            return typeof r === 'string' || typeof r === 'number' ? r : a;
+        });
+    };
+
+    String.prototype.name = function () {
+
+// If the string looks like an identifier, then we can return it as is.
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can simply slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe
+// sequences.
+
+
+        if (ix.test(this)) {
+            return this;
+        }
+        if (/[&<"\/\\\x00-\x1f]/.test(this)) {
+            return '"' + this.replace(/[&<"\/\\\x00-\x1f]/g, function (a) {
+                var c = escapes[a];
+                if (c) {
+                    return c;
+                }
+                c = a.charCodeAt();
+                return '\\u00' +
+                    Math.floor(c / 16).toString(16) +
+                    (c % 16).toString(16);
+            }) + '"';
+        }
+        return '"' + this + '"';
+    };
+
+
+    function assume() {
+        if (!option.safe) {
+            if (option.rhino) {
+                combine(predefined, rhino);
+            }
+            if (option.browser || option.sidebar) {
+                combine(predefined, browser);
+            }
+            if (option.sidebar) {
+                combine(predefined, sidebar);
+            }
+            if (option.widget) {
+                combine(predefined, widget);
+            }
+        }
+    }
+
+
+// Produce an error warning.
+
+    function quit(m, l, ch) {
+        throw {
+            name: 'JSLintError',
+            line: l,
+            character: ch,
+            message: m + " (" + Math.floor((l / lines.length) * 100) +
+                    "% scanned)."
+        };
+    }
+
+    function warning(m, t, a, b, c, d) {
+        var ch, l, w;
+        t = t || nexttoken;
+        if (t.id === '(end)') {  // `~
+            t = token;
+        }
+        l = t.line || 0;
+        ch = t.from || 0;
+        w = {
+            id: '(error)',
+            raw: m,
+            evidence: lines[l] || '',
+            line: l,
+            character: ch,
+            a: a,
+            b: b,
+            c: c,
+            d: d
+        };
+        w.reason = m.supplant(w);
+        JSLINT.errors.push(w);
+        if (option.passfail) {
+            quit('Stopping. ', l, ch);
+        }
+        warnings += 1;
+        if (warnings === 50) {
+            quit("Too many errors.", l, ch);
+        }
+        return w;
+    }
+
+    function warningAt(m, l, ch, a, b, c, d) {
+        return warning(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+    function error(m, t, a, b, c, d) {
+        var w = warning(m, t, a, b, c, d);
+        quit("Stopping, unable to continue.", w.line, w.character);
+    }
+
+    function errorAt(m, l, ch, a, b, c, d) {
+        return error(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+
+
+// lexical analysis
+
+    var lex = function lex() {
+        var character, from, line, s;
+
+// Private lex methods
+
+        function nextLine() {
+            var at;
+            line += 1;
+            if (line >= lines.length) {
+                return false;
+            }
+            character = 0;
+            s = lines[line].replace(/\t/g, tab);
+            at = s.search(cx);
+            if (at >= 0) {
+                warningAt("Unsafe character.", line, at);
+            }
+            return true;
+        }
+
+// Produce a token object.  The token inherits from a syntax symbol.
+
+        function it(type, value) {
+            var i, t;
+            if (type === '(color)') {
+                t = {type: type};
+            } else if (type === '(punctuator)' ||
+                    (type === '(identifier)' && syntax.hasOwnProperty(value))) {
+                t = syntax[value] || syntax['(error)'];
+
+// Mozilla bug workaround.
+
+                if (!t.id) {
+                    t = syntax[type];
+                }
+            } else {
+                t = syntax[type];
+            }
+            t = Object.create(t);
+            if (type === '(string)' || type === '(range)') {
+                if (jx.test(value)) {
+                    warningAt("Script URL.", line, from);
+                }
+            }
+            if (type === '(identifier)') {
+                t.identifier = true;
+                if (option.nomen && value.charAt(0) === '_') {
+                    warningAt("Unexpected '_' in '{a}'.", line, from, value);
+                }
+            }
+            t.value = value;
+            t.line = line;
+            t.character = character;
+            t.from = from;
+            i = t.id;
+            if (i !== '(endline)') {
+                prereg = i &&
+                        (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
+                        i === 'return');
+            }
+            return t;
+        }
+
+// Public lex methods
+
+        return {
+            init: function (source) {
+                if (typeof source === 'string') {
+                    lines = source.
+                        replace(/\r\n/g, '\n').
+                        replace(/\r/g, '\n').
+                        split('\n');
+                } else {
+                    lines = source;
+                }
+                line = -1;
+                nextLine();
+                from = 0;
+            },
+
+            range: function (begin, end) {
+                var c, value = '';
+                from = character;
+                if (s.charAt(0) !== begin) {
+                    errorAt("Expected '{a}' and instead saw '{b}'.",
+                            line, character, begin, s.charAt(0));
+                }
+                for (;;) {
+                    s = s.slice(1);
+                    character += 1;
+                    c = s.charAt(0);
+                    switch (c) {
+                    case '':
+                        errorAt("Missing '{a}'.", line, character, c);
+                        break;
+                    case end:
+                        s = s.slice(1);
+                        character += 1;
+                        return it('(range)', value);
+                    case xquote:
+                    case '\\':
+                    case '\'':
+                    case '"':
+                        warningAt("Unexpected '{a}'.", line, character, c);
+                    }
+                    value += c;
+                }
+
+            },
+
+// token -- this is called by advance to get the next token.
+
+            token: function () {
+                var b, c, captures, d, depth, high, i, l, low, q, t;
+
+                function match(x) {
+                    var r = x.exec(s), r1;
+                    if (r) {
+                        l = r[0].length;
+                        r1 = r[1];
+                        c = r1.charAt(0);
+                        s = s.substr(l);
+                        character += l;
+                        from = character - r1.length;
+                        return r1;
+                    }
+                }
+
+                function string(x) {
+                    var c, j, r = '';
+
+                    if (jsonmode && x !== '"') {
+                        warningAt("Strings must use doublequote.",
+                                line, character);
+                    }
+
+                    if (xquote === x || (xmode === 'scriptstring' && !xquote)) {
+                        return it('(punctuator)', x);
+                    }
+
+                    function esc(n) {
+                        var i = parseInt(s.substr(j + 1, n), 16);
+                        j += n;
+                        if (i >= 32 && i <= 127 &&
+                                i !== 34 && i !== 92 && i !== 39) {
+                            warningAt("Unnecessary escapement.", line, character);
+                        }
+                        character += n;
+                        c = String.fromCharCode(i);
+                    }
+                    j = 0;
+                    for (;;) {
+                        while (j >= s.length) {
+                            j = 0;
+                            if (xmode !== 'html' || !nextLine()) {
+                                errorAt("Unclosed string.", line, from);
+                            }
+                        }
+                        c = s.charAt(j);
+                        if (c === x) {
+                            character += 1;
+                            s = s.substr(j + 1);
+                            return it('(string)', r, x);
+                        }
+                        if (c < ' ') {
+                            if (c === '\n' || c === '\r') {
+                                break;
+                            }
+                            warningAt("Control character in string: {a}.",
+                                    line, character + j, s.slice(0, j));
+                        } else if (c === xquote) {
+                            warningAt("Bad HTML string", line, character + j);
+                        } else if (c === '<') {
+                            if (option.safe && xmode === 'html') {
+                                warningAt("ADsafe string violation.",
+                                        line, character + j);
+                            } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) {
+                                warningAt("Expected '<\\/' and instead saw '</'.", line, character);
+                            } else if (s.charAt(j + 1) === '!' && (xmode || option.safe)) {
+                                warningAt("Unexpected '<!' in a string.", line, character);
+                            }
+                        } else if (c === '\\') {
+                            if (xmode === 'html') {
+                                if (option.safe) {
+                                    warningAt("ADsafe string violation.",
+                                            line, character + j);
+                                }
+                            } else if (xmode === 'styleproperty') {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                if (c !== x) {
+                                    warningAt("Escapement in style string.",
+                                            line, character + j);
+                                }
+                            } else {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                switch (c) {
+                                case xquote:
+                                    warningAt("Bad HTML string", line,
+                                        character + j);
+                                    break;
+                                case '\\':
+                                case '\'':
+                                case '"':
+                                case '/':
+                                    break;
+                                case 'b':
+                                    c = '\b';
+                                    break;
+                                case 'f':
+                                    c = '\f';
+                                    break;
+                                case 'n':
+                                    c = '\n';
+                                    break;
+                                case 'r':
+                                    c = '\r';
+                                    break;
+                                case 't':
+                                    c = '\t';
+                                    break;
+                                case 'u':
+                                    esc(4);
+                                    break;
+                                case 'v':
+                                    c = '\v';
+                                    break;
+                                case 'x':
+                                    if (jsonmode) {
+                                        warningAt("Avoid \\x-.", line, character);
+                                    }
+                                    esc(2);
+                                    break;
+                                default:
+                                    warningAt("Bad escapement.", line, character);
+                                }
+                            }
+                        }
+                        r += c;
+                        character += 1;
+                        j += 1;
+                    }
+                }
+
+                for (;;) {
+                    if (!s) {
+                        return it(nextLine() ? '(endline)' : '(end)', '');
+                    }
+                    while (xmode === 'outer') {
+                        i = s.search(ox);
+                        if (i === 0) {
+                            break;
+                        } else if (i > 0) {
+                            character += 1;
+                            s = s.slice(i);
+                            break;
+                        } else {
+                            if (!nextLine()) {
+                                return it('(end)', '');
+                            }
+                        }
+                    }
+                    t = match(rx[xmode] || tx);
+                    if (!t) {
+                        if (xmode === 'html') {
+                            return it('(error)', s.charAt(0));
+                        } else {
+                            t = '';
+                            c = '';
+                            while (s && s < '!') {
+                                s = s.substr(1);
+                            }
+                            if (s) {
+                                errorAt("Unexpected '{a}'.",
+                                        line, character, s.substr(0, 1));
+                            }
+                        }
+                    } else {
+
+    //      identifier
+
+                        if (c.isAlpha() || c === '_' || c === '$') {
+                            return it('(identifier)', t);
+                        }
+
+    //      number
+
+                        if (c.isDigit()) {
+                            if (xmode !== 'style' && !isFinite(Number(t))) {
+                                warningAt("Bad number '{a}'.",
+                                    line, character, t);
+                            }
+                            if (xmode !== 'styleproperty' && s.substr(0, 1).isAlpha()) {
+                                warningAt("Missing space after '{a}'.",
+                                        line, character, t);
+                            }
+                            if (c === '0') {
+                                d = t.substr(1, 1);
+                                if (d.isDigit()) {
+                                    if (token.id !== '.' && xmode !== 'styleproperty') {
+                                        warningAt("Don't use extra leading zeros '{a}'.",
+                                            line, character, t);
+                                    }
+                                } else if (jsonmode && (d === 'x' || d === 'X')) {
+                                    warningAt("Avoid 0x-. '{a}'.",
+                                            line, character, t);
+                                }
+                            }
+                            if (t.substr(t.length - 1) === '.') {
+                                warningAt(
+        "A trailing decimal point can be confused with a dot '{a}'.",
+                                        line, character, t);
+                            }
+                            return it('(number)', t);
+                        }
+                        switch (t) {
+
+    //      string
+
+                        case '"':
+                        case "'":
+                            return string(t);
+
+    //      // comment
+
+                        case '//':
+                            if (src || (xmode && xmode !== 'script')) {
+                                warningAt("Unexpected comment.", line, character);
+                            } else if (xmode === 'script' && /<\s*\//i.test(s)) {
+                                warningAt("Unexpected <\/ in comment.", line, character);
+                            } else if ((option.safe || xmode === 'script') && ax.test(s)) {
+                                warningAt("Dangerous comment.", line, character);
+                            }
+                            s = '';
+                            token.comment = true;
+                            break;
+
+    //      /* comment
+
+                        case '/*':
+                            if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) {
+                                warningAt("Unexpected comment.", line, character);
+                            }
+                            if (option.safe && ax.test(s)) {
+                                warningAt("ADsafe comment violation.", line, character);
+                            }
+                            for (;;) {
+                                i = s.search(lx);
+                                if (i >= 0) {
+                                    break;
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed comment.", line, character);
+                                } else {
+                                    if (option.safe && ax.test(s)) {
+                                        warningAt("ADsafe comment violation.", line, character);
+                                    }
+                                }
+                            }
+                            character += i + 2;
+                            if (s.substr(i, 1) === '/') {
+                                errorAt("Nested comment.", line, character);
+                            }
+                            s = s.substr(i + 2);
+                            token.comment = true;
+                            break;
+
+    //      /*global /*extern /*members /*jslint */
+
+                        case '/*global':
+                        case '/*extern':
+                        case '/*members':
+                        case '/*member':
+                        case '/*jslint':
+                        case '*/':
+                            return {
+                                value: t,
+                                type: 'special',
+                                line: line,
+                                character: character,
+                                from: from
+                            };
+
+                        case '':
+                            break;
+    //      /
+                        case '/':
+                            if (prereg) {
+                                depth = 0;
+                                captures = 0;
+                                l = 0;
+                                for (;;) {
+                                    b = true;
+                                    c = s.charAt(l);
+                                    l += 1;
+                                    switch (c) {
+                                    case '':
+                                        errorAt("Unclosed regular expression.", line, from);
+                                        return;
+                                    case '/':
+                                        if (depth > 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, '/');
+                                        }
+                                        c = s.substr(0, l - 1);
+                                        q = {
+                                            g: true,
+                                            i: true,
+                                            m: true
+                                        };
+                                        while (q[s.charAt(l)] === true) {
+                                            q[s.charAt(l)] = false;
+                                            l += 1;
+                                        }
+                                        character += l;
+                                        s = s.substr(l);
+                                        return it('(regexp)', c);
+                                    case '\\':
+                                        c = s.charAt(l);
+                                        if (c < ' ') {
+                                            warningAt("Unexpected control character in regular expression.", line, from + l);
+                                        } else if (c === '<') {
+                                            warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                        }
+                                        l += 1;
+                                        break;
+                                    case '(':
+                                        depth += 1;
+                                        b = false;
+                                        if (s.charAt(l) === '?') {
+                                            l += 1;
+                                            switch (s.charAt(l)) {
+                                            case ':':
+                                            case '=':
+                                            case '!':
+                                                l += 1;
+                                                break;
+                                            default:
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
+                                            }
+                                        } else {
+                                            captures += 1;
+                                        }
+                                        break;
+                                    case ')':
+                                        if (depth === 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, ')');
+                                        } else {
+                                            depth -= 1;
+                                        }
+                                        break;
+                                    case ' ':
+                                        q = 1;
+                                        while (s.charAt(l) === ' ') {
+                                            l += 1;
+                                            q += 1;
+                                        }
+                                        if (q > 1) {
+                                            warningAt("Spaces are hard to count. Use {{a}}.", line, from + l, q);
+                                        }
+                                        break;
+                                    case '[':
+                                        if (s.charAt(l) === '^') {
+                                            l += 1;
+                                        }
+                                        q = false;
+    klass:                              do {
+                                            c = s.charAt(l);
+                                            l += 1;
+                                            switch (c) {
+                                            case '[':
+                                            case '^':
+                                                warningAt("Unescaped '{a}'.", line, from + l, c);
+                                                q = true;
+                                                break;
+                                            case '-':
+                                                if (q) {
+                                                    q = false;
+                                                } else {
+                                                    warningAt("Unescaped '{a}'.", line, from + l, '-');
+                                                    q = true;
+                                                }
+                                                break;
+                                            case ']':
+                                                if (!q) {
+                                                    warningAt("Unescaped '{a}'.", line, from + l - 1, '-');
+                                                }
+                                                break klass;
+                                            case '\\':
+                                                c = s.charAt(l);
+                                                if (c < ' ') {
+                                                    warningAt("Unexpected control character in regular expression.", line, from + l);
+                                                } else if (c === '<') {
+                                                    warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                                }
+                                                l += 1;
+                                                q = true;
+                                                break;
+                                            case '/':
+                                                warningAt("Unescaped '{a}'.", line, from + l - 1, '/');
+                                                q = true;
+                                                break;
+                                            case '<':
+                                                if (xmode === 'script') {
+                                                    c = s.charAt(l);
+                                                    if (c === '!' || c === '/') {
+                                                        warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                                    }
+                                                }
+                                                q = true;
+                                                break;
+                                            default:
+                                                q = true;
+                                            }
+                                        } while (c);
+                                        break;
+                                    case '.':
+                                        if (option.regexp) {
+                                            warningAt("Unexpected '{a}'.", line, from + l, c);
+                                        }
+                                        break;
+                                    case ']':
+                                    case '?':
+                                    case '{':
+                                    case '}':
+                                    case '+':
+                                    case '*':
+                                        warningAt("Unescaped '{a}'.", line, from + l, c);
+                                        break;
+                                    case '<':
+                                        if (xmode === 'script') {
+                                            c = s.charAt(l);
+                                            if (c === '!' || c === '/') {
+                                                warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                    }
+                                    if (b) {
+                                        switch (s.charAt(l)) {
+                                        case '?':
+                                        case '+':
+                                        case '*':
+                                            l += 1;
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            break;
+                                        case '{':
+                                            l += 1;
+                                            c = s.charAt(l);
+                                            if (c < '0' || c > '9') {
+                                                warningAt("Expected a number and instead saw '{a}'.", line, from + l, c);
+                                            }
+                                            l += 1;
+                                            low = +c;
+                                            for (;;) {
+                                                c = s.charAt(l);
+                                                if (c < '0' || c > '9') {
+                                                    break;
+                                                }
+                                                l += 1;
+                                                low = +c + (low * 10);
+                                            }
+                                            high = low;
+                                            if (c === ',') {
+                                                l += 1;
+                                                high = Infinity;
+                                                c = s.charAt(l);
+                                                if (c >= '0' && c <= '9') {
+                                                    l += 1;
+                                                    high = +c;
+                                                    for (;;) {
+                                                        c = s.charAt(l);
+                                                        if (c < '0' || c > '9') {
+                                                            break;
+                                                        }
+                                                        l += 1;
+                                                        high = +c + (high * 10);
+                                                    }
+                                                }
+                                            }
+                                            if (s.charAt(l) !== '}') {
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
+                                            } else {
+                                                l += 1;
+                                            }
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            if (low > high) {
+                                                warningAt("'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+                                            }
+                                        }
+                                    }
+                                }
+                                c = s.substr(0, l - 1);
+                                character += l;
+                                s = s.substr(l);
+                                return it('(regexp)', c);
+                            }
+                            return it('(punctuator)', t);
+
+    //      punctuator
+
+                        case '#':
+                            if (xmode === 'html' || xmode === 'styleproperty') {
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    if ((c < '0' || c > '9') &&
+                                            (c < 'a' || c > 'f') &&
+                                            (c < 'A' || c > 'F')) {
+                                        break;
+                                    }
+                                    character += 1;
+                                    s = s.substr(1);
+                                    t += c;
+                                }
+                                if (t.length !== 4 && t.length !== 7) {
+                                    warningAt("Bad hex color '{a}'.", line,
+                                        from + l, t);
+                                }
+                                return it('(color)', t);
+                            }
+                            return it('(punctuator)', t);
+                        default:
+                            if (xmode === 'outer' && c === '&') {
+                                character += 1;
+                                s = s.substr(1);
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    character += 1;
+                                    s = s.substr(1);
+                                    if (c === ';') {
+                                        break;
+                                    }
+                                    if (!((c >= '0' && c <= '9') ||
+                                            (c >= 'a' && c <= 'z') ||
+                                            c === '#')) {
+                                        errorAt("Bad entity", line, from + l,
+                                        character);
+                                    }
+                                }
+                                break;
+                            }
+                            return it('(punctuator)', t);
+                        }
+                    }
+                }
+            }
+        };
+    }();
+
+
+    function addlabel(t, type) {
+
+        if (t === 'hasOwnProperty') {
+            error("'hasOwnProperty' is a really bad name.");
+        }
+        if (option.safe && funct['(global)']) {
+            warning('ADsafe global: ' + t + '.', token);
+        }
+
+// Define t in the current function in the current scope.
+
+        if (funct.hasOwnProperty(t)) {
+            warning(funct[t] === true ?
+                "'{a}' was used before it was defined." :
+                "'{a}' is already defined.",
+                nexttoken, t);
+        }
+        funct[t] = type;
+        if (type === 'label') {
+            scope[t] = funct;
+        } else if (funct['(global)']) {
+            global[t] = funct;
+            if (implied.hasOwnProperty(t)) {
+                warning("'{a}' was used before it was defined.", nexttoken, t);
+                delete implied[t];
+            }
+        } else {
+            funct['(scope)'][t] = funct;
+        }
+    }
+
+
+    function doOption() {
+        var b, obj, filter, o = nexttoken.value, t, v;
+        switch (o) {
+        case '*/':
+            error("Unbegun comment.");
+            break;
+        case '/*global':
+        case '/*extern':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = predefined;
+            break;
+        case '/*members':
+        case '/*member':
+            o = '/*members';
+            if (!membersOnly) {
+                membersOnly = {};
+            }
+            obj = membersOnly;
+            break;
+        case '/*jslint':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = option;
+            filter = boolOptions;
+        }
+        for (;;) {
+            t = lex.token();
+            if (t.id === ',') {
+                t = lex.token();
+            }
+            while (t.id === '(endline)') {
+                t = lex.token();
+            }
+            if (t.type === 'special' && t.value === '*/') {
+                break;
+            }
+            if (t.type !== '(string)' && t.type !== '(identifier)' &&
+                    o !== '/*members') {
+                error("Bad option.", t);
+            }
+            if (filter) {
+                if (filter[t.value] !== true) {
+                    error("Bad option.", t);
+                }
+                v = lex.token();
+                if (v.id !== ':') {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, ':', t.value);
+                }
+                v = lex.token();
+                if (v.value === 'true') {
+                    b = true;
+                } else if (v.value === 'false') {
+                    b = false;
+                } else {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, 'true', t.value);
+                }
+            } else {
+                b = true;
+            }
+            obj[t.value] = b;
+        }
+        if (filter) {
+            assume();
+        }
+    }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+//     for ( var i in ...
+// from
+//     for ( var i = ...
+
+    function peek(p) {
+        var i = p || 0, j = 0, t;
+
+        while (j <= i) {
+            t = lookahead[j];
+            if (!t) {
+                t = lookahead[j] = lex.token();
+            }
+            j += 1;
+        }
+        return t;
+    }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+    function advance(id, t) {
+        var l;
+        switch (token.id) {
+        case '(number)':
+            if (nexttoken.id === '.') {
+                warning(
+"A dot following a number can be confused with a decimal point.", token);
+            }
+            break;
+        case '-':
+            if (nexttoken.id === '-' || nexttoken.id === '--') {
+                warning("Confusing minusses.");
+            }
+            break;
+        case '+':
+            if (nexttoken.id === '+' || nexttoken.id === '++') {
+                warning("Confusing plusses.");
+            }
+            break;
+        }
+        if (token.type === '(string)' || token.identifier) {
+            anonname = token.value;
+        }
+
+        if (id && nexttoken.id !== id) {
+            if (t) {
+                if (nexttoken.id === '(end)') {
+                    warning("Unmatched '{a}'.", t, t.id);
+                } else {
+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+                            nexttoken, id, t.id, t.line + 1, nexttoken.value);
+                }
+            } else if (nexttoken.type !== '(identifier)' ||
+                            nexttoken.value !== id) {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, id, nexttoken.value);
+            }
+        }
+        prevtoken = token;
+        token = nexttoken;
+        for (;;) {
+            nexttoken = lookahead.shift() || lex.token();
+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                return;
+            }
+            if (nexttoken.type === 'special') {
+                doOption();
+            } else {
+                if (nexttoken.id !== '(endline)') {
+                    break;
+                }
+                l = !xmode && !option.laxbreak &&
+                    (token.type === '(string)' || token.type === '(number)' ||
+                    token.type === '(identifier)' || badbreak[token.id]);
+            }
+        }
+        if (!option.evil && nexttoken.value === 'eval') {
+            warning("eval is evil.", nexttoken);
+        }
+        if (l) {
+            switch (nexttoken.id) {
+            case '{':
+            case '}':
+            case ']':
+            case '.':
+                break;
+            case ')':
+                switch (token.id) {
+                case ')':
+                case '}':
+                case ']':
+                    break;
+                default:
+                    warning("Line breaking error '{a}'.", token, ')');
+                }
+                break;
+            default:
+                warning("Line breaking error '{a}'.",
+                        token, token.value);
+            }
+        }
+    }
+
+
+// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is
+// like nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define JavaScript. I retained Pratt's
+// nomenclature.
+
+// .nud     Null denotation
+// .fud     First null denotation
+// .led     Left denotation
+//  lbp     Left binding power
+//  rbp     Right binding power
+
+// They are key to the parsing method called Top Down Operator Precedence.
+
+    function parse(rbp, initial) {
+        var left, o;
+        if (nexttoken.id === '(end)') {
+            error("Unexpected early end of program.", token);
+        }
+        advance();
+        if (option.safe && predefined[token.value] === true &&
+                (nexttoken.id !== '(' && nexttoken.id !== '.')) {
+            warning('ADsafe violation.', token);
+        }
+        if (initial) {
+            anonname = 'anonymous';
+            funct['(verb)'] = token.value;
+        }
+        if (initial === true && token.fud) {
+            left = token.fud();
+        } else {
+            if (token.nud) {
+                o = token.exps;
+                left = token.nud();
+            } else {
+                if (nexttoken.type === '(number)' && token.id === '.') {
+                    warning(
+"A leading decimal point can be confused with a dot: '.{a}'.",
+                            token, nexttoken.value);
+                    advance();
+                    return token;
+                } else {
+                    error("Expected an identifier and instead saw '{a}'.",
+                            token, token.id);
+                }
+            }
+            while (rbp < nexttoken.lbp) {
+                o = nexttoken.exps;
+                advance();
+                if (token.led) {
+                    left = token.led(left);
+                } else {
+                    error("Expected an operator and instead saw '{a}'.",
+                        token, token.id);
+                }
+            }
+            if (initial && !o) {
+                warning(
+"Expected an assignment or function call and instead saw an expression.",
+                        token);
+            }
+        }
+        return left;
+    }
+
+
+// Functions for conformance of style.
+
+    function abut(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (left.line !== right.line || left.character !== right.from) {
+            warning("Unexpected space after '{a}'.", right, left.value);
+        }
+    }
+
+
+    function adjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white || xmode === 'styleproperty' || xmode === 'style') {
+            if (left.character !== right.from && left.line === right.line) {
+                warning("Unexpected space after '{a}'.", right, left.value);
+            }
+        }
+    }
+
+
+    function nospace(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && !left.comment) {
+            if (left.line === right.line) {
+                adjacent(left, right);
+            }
+        }
+    }
+
+
+    function nonadjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white) {
+            if (left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function indentation(bias) {
+        var i;
+        if (option.white && nexttoken.id !== '(end)') {
+            i = indent + (bias || 0);
+            if (nexttoken.from !== i) {
+                warning("Expected '{a}' to have an indentation of {b} instead of {c}.",
+                        nexttoken, nexttoken.value, i, nexttoken.from);
+            }
+        }
+    }
+
+    function nolinebreak(t) {
+        if (t.line !== nexttoken.line) {
+            warning("Line breaking error '{a}'.", t, t.id);
+        }
+    }
+
+
+// Parasitic constructors for making the symbols that will be inherited by
+// tokens.
+
+    function symbol(s, p) {
+        var x = syntax[s];
+        if (!x || typeof x !== 'object') {
+            syntax[s] = x = {
+                id: s,
+                lbp: p,
+                value: s
+            };
+        }
+        return x;
+    }
+
+
+    function delim(s) {
+        return symbol(s, 0);
+    }
+
+
+    function stmt(s, f) {
+        var x = delim(s);
+        x.identifier = x.reserved = true;
+        x.fud = f;
+        return x;
+    }
+
+
+    function blockstmt(s, f) {
+        var x = stmt(s, f);
+        x.block = true;
+        return x;
+    }
+
+
+    function reserveName(x) {
+        var c = x.id.charAt(0);
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+            x.identifier = x.reserved = true;
+        }
+        return x;
+    }
+
+
+    function prefix(s, f) {
+        var x = symbol(s, 150);
+        reserveName(x);
+        x.nud = (typeof f === 'function') ? f : function () {
+            if (option.plusplus && (this.id === '++' || this.id === '--')) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.right = parse(150);
+            this.arity = 'unary';
+            return this;
+        };
+        return x;
+    }
+
+
+    function type(s, f) {
+        var x = delim(s);
+        x.type = s;
+        x.nud = f;
+        return x;
+    }
+
+
+    function reserve(s, f) {
+        var x = type(s, f);
+        x.identifier = x.reserved = true;
+        return x;
+    }
+
+
+    function reservevar(s, v) {
+        return reserve(s, function () {
+            if (this.id === 'this') {
+                if (option.safe) {
+                    warning("ADsafe violation.", this);
+                }
+            }
+            return this;
+        });
+    }
+
+
+    function infix(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            this.left = left;
+            this.right = parse(p);
+            return this;
+        };
+        return x;
+    }
+
+
+    function relation(s, f) {
+        var x = symbol(s, 100);
+        x.led = function (left) {
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            var right = parse(100);
+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
+                warning("Use the isNaN function to compare with NaN.", this);
+            } else if (f) {
+                f.apply(this, [left, right]);
+            }
+            this.left = left;
+            this.right = right;
+            return this;
+        };
+        return x;
+    }
+
+
+    function isPoorRelation(node) {
+        return (node.type === '(number)' && !+node.value) ||
+               (node.type === '(string)' && !node.value) ||
+                node.type === 'true' ||
+                node.type === 'false' ||
+                node.type === 'undefined' ||
+                node.type === 'null';
+    }
+
+
+    function assignop(s, f) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left) {
+            var l;
+            this.left = left;
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (option.safe) {
+                l = left;
+                do {
+                    if (predefined[l.value] === true) {
+                        warning('ADsafe violation.', l);
+                    }
+                    l = l.left;
+                } while (l);
+            }
+            if (left) {
+                if (left.id === '.' || left.id === '[') {
+                    if (left.left.value === 'arguments') {
+                        warning('Bad assignment.', this);
+                    }
+                    this.right = parse(19);
+                    return this;
+                } else if (left.identifier && !left.reserved) {
+                    this.right = parse(19);
+                    return this;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", this);
+        }, 20);
+    }
+
+    function bitwise(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            this.left = left;
+            this.right = parse(p);
+            return this;
+        };
+        return x;
+    }
+
+    function bitwiseassignop(s) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (left) {
+                if (left.id === '.' || left.id === '[' ||
+                        (left.identifier && !left.reserved)) {
+                    parse(19);
+                    return left;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", this);
+        }, 20);
+    }
+
+
+    function suffix(s, f) {
+        var x = symbol(s, 150);
+        x.led = function (left) {
+            if (option.plusplus) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.left = left;
+            return this;
+        };
+        return x;
+    }
+
+
+    function optionalidentifier() {
+        if (nexttoken.reserved) {
+            warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+                    nexttoken, nexttoken.id);
+        }
+        if (nexttoken.identifier) {
+            advance();
+            return token.value;
+        }
+    }
+
+
+    function identifier() {
+        var i = optionalidentifier();
+        if (i) {
+            return i;
+        }
+        if (token.id === 'function' && nexttoken.id === '(') {
+            warning("Missing name in function statement.");
+        } else {
+            error("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    }
+
+    function reachable(s) {
+        var i = 0, t;
+        if (nexttoken.id !== ';' || noreach) {
+            return;
+        }
+        for (;;) {
+            t = peek(i);
+            if (t.reach) {
+                return;
+            }
+            if (t.id !== '(endline)') {
+                if (t.id === 'function') {
+                    warning(
+"Inner functions should be listed at the top of the outer function.", t);
+                    break;
+                }
+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+                break;
+            }
+            i += 1;
+        }
+    }
+
+
+    function statement(noindent) {
+        var i = indent, r, s = scope, t = nexttoken;
+
+// We don't like the empty statement.
+
+        if (t.id === ';') {
+            warning("Unnecessary semicolon.", t);
+            advance(';');
+            return;
+        }
+
+// Is this a labelled statement?
+
+        if (t.identifier && !t.reserved && peek().id === ':') {
+            advance();
+            advance(':');
+            scope = Object.create(s);
+            addlabel(t.value, 'label');
+            if (!nexttoken.labelled) {
+                warning("Label '{a}' on {b} statement.",
+                        nexttoken, t.value, nexttoken.value);
+            }
+            if (jx.test(t.value + ':')) {
+                warning("Label '{a}' looks like a javascript url.",
+                        t, t.value);
+            }
+            nexttoken.label = t.value;
+            t = nexttoken;
+        }
+
+// Parse the statement.
+
+        if (!noindent) {
+            indentation();
+        }
+        r = parse(0, true);
+
+// Look for the final semicolon.
+
+        if (!t.block) {
+            if (nexttoken.id !== ';') {
+                warningAt("Missing semicolon.", token.line,
+                        token.from + token.value.length);
+            } else {
+                adjacent(token, nexttoken);
+                advance(';');
+                nonadjacent(token, nexttoken);
+            }
+        }
+
+// Restore the indentation.
+
+        indent = i;
+        scope = s;
+        return r;
+    }
+
+
+    function statements(begin) {
+        var a = [];
+        if (begin) {
+            if (option.strict && nexttoken.type !== '(string)') {
+                warning('Missing "use strict" statement.',
+                    nexttoken);
+            }
+            if (nexttoken.type === '(string)' &&
+                    nexttoken.value === 'use strict') {
+                advance();
+                advance(';');
+            }
+        }
+        if (option.adsafe) {
+            switch (begin) {
+            case 'script':
+                if (!adsafe_may) {
+                    if (nexttoken.value !== 'ADSAFE' ||
+                            peek(0).id !== '.' ||
+                            (peek(1).value !== 'id' &&
+                            peek(1).value !== 'go')) {
+                        error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.',
+                            nexttoken);
+                    }
+                }
+                if (nexttoken.value === 'ADSAFE' &&
+                        peek(0).id === '.' &&
+                        peek(1).value === 'id') {
+                    if (adsafe_may) {
+                        error('ADsafe violation.', nexttoken);
+                    }
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('id');
+                    advance('(');
+                    if (nexttoken.value !== adsafe_id) {
+                        error('ADsafe violation: id does not match.', nexttoken);
+                    }
+                    advance('(string)');
+                    advance(')');
+                    advance(';');
+                    adsafe_may = true;
+                }
+                break;
+            case 'lib':
+                if (nexttoken.value === 'ADSAFE') {
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('lib');
+                    advance('(');
+                    advance('(string)');
+                    advance(',');
+                    parse(0);
+                    advance(')');
+                    advance(';');
+                    return a;
+                } else {
+                    error("ADsafe lib violation.");
+                }
+            }
+        }
+        while (!nexttoken.reach && nexttoken.id !== '(end)') {
+            if (nexttoken.id === ';') {
+                warning("Unnecessary semicolon.");
+                advance(';');
+            } else {
+                a.push(statement());
+            }
+        }
+        return a;
+    }
+
+
+    function block(f) {
+        var a, b = inblock, s = scope, t;
+        inblock = f;
+        if (f) {
+            scope = Object.create(scope);
+        }
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        if (nexttoken.id === '{') {
+            advance('{');
+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {
+                indent += option.indent;
+                if (!f && nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+                a = statements();
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', t);
+        } else {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '{', nexttoken.value);
+            noreach = true;
+            a = [statement()];
+            noreach = false;
+        }
+        funct['(verb)'] = null;
+        scope = s;
+        inblock = b;
+        return a;
+    }
+
+
+// An identity function, used by string and number tokens.
+
+    function idValue() {
+        return this;
+    }
+
+
+    function countMember(m) {
+        if (membersOnly && membersOnly[m] !== true) {
+            warning("Unexpected /*member '{a}'.", nexttoken, m);
+        }
+        if (typeof member[m] === 'number') {
+            member[m] += 1;
+        } else {
+            member[m] = 1;
+        }
+    }
+
+    function note_implied(token) {
+        var name = token.value, line = token.line + 1, a = implied[name];
+        if (!a) {
+            a = [line];
+            implied[name] = a;
+        } else if (a[a.length - 1] !== line) {
+            a.push(line);
+        }
+    }
+
+// CSS parsing.
+
+
+    function cssName() {
+        if (nexttoken.identifier) {
+            advance();
+            return true;
+        }
+    }
+
+    function cssNumber() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            advance('(number)');
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            return true;
+        }
+    }
+
+    function cssString() {
+        if (nexttoken.type === '(string)') {
+            advance();
+            return true;
+        }
+    }
+
+    function cssColor() {
+        var i, number;
+        if (nexttoken.identifier) {
+            if (nexttoken.value === 'rgb') {
+                advance();
+                advance('(');
+                for (i = 0; i < 3; i += 1) {
+                    number = nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0) {
+                        warning("Expected a positive number and instead saw '{a}'",
+                            nexttoken, number);
+                        advance();
+                    } else {
+                        advance();
+                        if (nexttoken.id === '%') {
+                            advance('%');
+                            if (number > 100) {
+                                warning("Expected a percentage and instead saw '{a}'",
+                                    token, number);
+                            }
+                        } else {
+                            if (number > 255) {
+                                warning("Expected a small number and instead saw '{a}'",
+                                    token, number);
+                            }
+                        }
+                    }
+                }
+                advance(')');
+                return true;
+            } else if (cssColorData[nexttoken.value] === true) {
+                advance();
+                return true;
+            }
+        } else if (nexttoken.type === '(color)') {
+            advance();
+            return true;
+        }
+        return false;
+    }
+
+    function cssLength() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            } else if (+token.value !== 0) {
+                warning("Expected a linear unit and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssLineHeight() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssWidth() {
+        if (nexttoken.identifier) {
+            switch (nexttoken.value) {
+            case 'thin':
+            case 'medium':
+            case 'thick':
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssMargin() {
+        if (nexttoken.identifier) {
+            if (nexttoken.value === 'auto') {
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssAttr() {
+        if (nexttoken.identifier && nexttoken.value === 'attr') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssCommaList() {
+        while (nexttoken.id !== ';') {
+            if (!cssName() && !cssString()) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            if (nexttoken.id !== ',') {
+                return true;
+            }
+            advance(',');
+        }
+    }
+
+    function cssCounter() {
+        if (nexttoken.identifier && nexttoken.value === 'counter') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                advance(',');
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        if (nexttoken.identifier && nexttoken.value === 'counters') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                advance(',');
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            if (nexttoken.id === ',') {
+                advance(',');
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+
+    function cssShape() {
+        var i;
+        if (nexttoken.identifier && nexttoken.value === 'rect') {
+            advance();
+            advance('(');
+            for (i = 0; i < 4; i += 1) {
+                if (!cssLength()) {
+                    warning("Expected a number and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                    break;
+                }
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssUrl() {
+        var url;
+        if (nexttoken.identifier && nexttoken.value === 'url') {
+            nexttoken = lex.range('(', ')');
+            url = nexttoken.value;
+            advance();
+            if (option.safe && ux.test(url)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(url);
+            return true;
+        }
+        return false;
+    }
+
+    cssAny = [cssUrl, function () {
+        for (;;) {
+            if (nexttoken.identifier) {
+                switch (nexttoken.value.toLowerCase()) {
+                case 'url':
+                    cssUrl();
+                    break;
+                case 'expression':
+                    warning("Unexpected expression '{a}'.",
+                        nexttoken, nexttoken.value);
+                    advance();
+                    break;
+                default:
+                    advance();
+                }
+            } else {
+                if (nexttoken.id === ';' || nexttoken.id === '!'  ||
+                        nexttoken.id === '(end)' || nexttoken.id === '}') {
+                    return true;
+                }
+                advance();
+            }
+        }
+    }];
+
+    cssBorderStyle = [
+        'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge',
+        'inset', 'outset'
+    ];
+
+    cssAttributeData = {
+        background: [
+            true, 'background-attachment', 'background-color',
+            'background-image', 'background-position', 'background-repeat'
+        ],
+        'background-attachment': ['scroll', 'fixed'],
+        'background-color': ['transparent', cssColor],
+        'background-image': ['none', cssUrl],
+        'background-position': [
+            2, [cssLength, 'top', 'bottom', 'left', 'right', 'center']
+        ],
+        'background-repeat': [
+            'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
+        ],
+        'border': [true, 'border-color', 'border-style', 'border-width'],
+        'border-bottom': [true, 'border-bottom-color', 'border-bottom-style', 'border-bottom-width'],
+        'border-bottom-color': cssColor,
+        'border-bottom-style': cssBorderStyle,
+        'border-bottom-width': cssWidth,
+        'border-collapse': ['collapse', 'separate'],
+        'border-color': ['transparent', 4, cssColor],
+        'border-left': [
+            true, 'border-left-color', 'border-left-style', 'border-left-width'
+        ],
+        'border-left-color': cssColor,
+        'border-left-style': cssBorderStyle,
+        'border-left-width': cssWidth,
+        'border-right': [
+            true, 'border-right-color', 'border-right-style', 'border-right-width'
+        ],
+        'border-right-color': cssColor,
+        'border-right-style': cssBorderStyle,
+        'border-right-width': cssWidth,
+        'border-spacing': [2, cssLength],
+        'border-style': [4, cssBorderStyle],
+        'border-top': [
+            true, 'border-top-color', 'border-top-style', 'border-top-width'
+        ],
+        'border-top-color': cssColor,
+        'border-top-style': cssBorderStyle,
+        'border-top-width': cssWidth,
+        'border-width': [4, cssWidth],
+        bottom: [cssLength, 'auto'],
+        'caption-side' : ['bottom', 'left', 'right', 'top'],
+        clear: ['both', 'left', 'none', 'right'],
+        clip: [cssShape, 'auto'],
+        color: cssColor,
+        content: [
+            'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote',
+            cssString, cssUrl, cssCounter, cssAttr
+        ],
+        'counter-increment': [
+            cssName, 'none'
+        ],
+        'counter-reset': [
+            cssName, 'none'
+        ],
+        cursor: [
+            cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move',
+            'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize',
+            'se-resize', 'sw-resize', 'w-resize', 'text', 'wait'
+        ],
+        direction: ['ltr', 'rtl'],
+        display: [
+            'block', 'compact', 'inline', 'inline-block', 'inline-table',
+            'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption',
+            'table-column', 'table-column-group', 'table-footer-group',
+            'table-header-group', 'table-row', 'table-row-group'
+        ],
+        'empty-cells': ['show', 'hide'],
+        'float': ['left', 'none', 'right'],
+        font: [
+            'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar',
+            true, 'font-size', 'font-style', 'font-weight', 'font-family'
+        ],
+        'font-family': cssCommaList,
+        'font-size': [
+            'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large',
+            'xx-large', 'larger', 'smaller', cssLength
+        ],
+        'font-size-adjust': ['none', cssNumber],
+        'font-stretch': [
+            'normal', 'wider', 'narrower', 'ultra-condensed',
+            'extra-condensed', 'condensed', 'semi-condensed',
+            'semi-expanded', 'expanded', 'extra-expanded'
+        ],
+        'font-style': [
+            'normal', 'italic', 'oblique'
+        ],
+        'font-variant': [
+            'normal', 'small-caps'
+        ],
+        'font-weight': [
+            'normal', 'bold', 'bolder', 'lighter', cssNumber
+        ],
+        height: [cssLength, 'auto'],
+        left: [cssLength, 'auto'],
+        'letter-spacing': ['normal', cssLength],
+        'line-height': ['normal', cssLineHeight],
+        'list-style': [
+            true, 'list-style-image', 'list-style-position', 'list-style-type'
+        ],
+        'list-style-image': ['none', cssUrl],
+        'list-style-position': ['inside', 'outside'],
+        'list-style-type': [
+            'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero',
+            'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha',
+            'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana',
+            'hiragana-iroha', 'katakana-oroha', 'none'
+        ],
+        margin: [4, cssMargin],
+        'margin-bottom': cssMargin,
+        'margin-left': cssMargin,
+        'margin-right': cssMargin,
+        'margin-top': cssMargin,
+        'marker-offset': [cssLength, 'auto'],
+        'max-height': [cssLength, 'none'],
+        'max-width': [cssLength, 'none'],
+        'min-height': cssLength,
+        'min-width': cssLength,
+        opacity: cssNumber,
+        outline: [true, 'outline-color', 'outline-style', 'outline-width'],
+        'outline-color': ['invert', cssColor],
+        'outline-style': [
+            'dashed', 'dotted', 'double', 'groove', 'inset', 'none',
+            'outset', 'ridge', 'solid'
+        ],
+        'outline-width': cssWidth,
+        overflow: ['auto', 'hidden', 'scroll', 'visible'],
+        padding: [4, cssLength],
+        'padding-bottom': cssLength,
+        'padding-left': cssLength,
+        'padding-right': cssLength,
+        'padding-top': cssLength,
+        position: ['absolute', 'fixed', 'relative', 'static'],
+        quotes: [8, cssString],
+        right: [cssLength, 'auto'],
+        'table-layout': ['auto', 'fixed'],
+        'text-align': ['center', 'justify', 'left', 'right'],
+        'text-decoration': ['none', 'underline', 'overline', 'line-through', 'blink'],
+        'text-indent': cssLength,
+        'text-shadow': ['none', 4, [cssColor, cssLength]],
+        'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'],
+        top: [cssLength, 'auto'],
+        'unicode-bidi': ['normal', 'embed', 'bidi-override'],
+        'vertical-align': [
+            'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle',
+            'text-bottom', cssLength
+        ],
+        visibility: ['visible', 'hidden', 'collapse'],
+        'white-space': ['normal', 'pre', 'nowrap'],
+        width: [cssLength, 'auto'],
+        'word-spacing': ['normal', cssLength],
+        'z-index': ['auto', cssNumber]
+    };
+
+    function styleAttribute() {
+        var v;
+        while (nexttoken.id === '*' || nexttoken.id === '#' || nexttoken.value === '_') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance();
+        }
+        if (nexttoken.id === '-') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance('-');
+            if (!nexttoken.identifier) {
+                warning("Expected a non-standard style attribute and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+            return cssAny;
+        } else {
+            if (!nexttoken.identifier) {
+                warning("Excepted a style attribute, and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            } else {
+                if (cssAttributeData.hasOwnProperty(nexttoken.value)) {
+                    v = cssAttributeData[nexttoken.value];
+                } else {
+                    v = cssAny;
+                    if (!option.css) {
+                        warning("Unrecognized style attribute '{a}'.",
+                                nexttoken, nexttoken.value);
+                    }
+                }
+            }
+            advance();
+            return v;
+        }
+    }
+
+    function styleValue(v) {
+        var i = 0,
+            n,
+            once,
+            match,
+            round,
+            start = 0,
+            vi;
+        switch (typeof v) {
+        case 'function':
+            return v();
+        case 'string':
+            if (nexttoken.identifier && nexttoken.value === v) {
+                advance();
+                return true;
+            }
+            return false;
+        }
+        for (;;) {
+            if (i >= v.length) {
+                return false;
+            }
+            vi = v[i];
+            i += 1;
+            if (vi === true) {
+                break;
+            } else if (typeof vi === 'number') {
+                n = vi;
+                vi = v[i];
+                i += 1;
+            } else {
+                n = 1;
+            }
+            match = false;
+            while (n > 0) {
+                if (styleValue(vi)) {
+                    match = true;
+                    n -= 1;
+                } else {
+                    break;
+                }
+            }
+            if (match) {
+                return true;
+            }
+        }
+        start = i;
+        once = [];
+        for (;;) {
+            round = false;
+            for (i = start; i < v.length; i += 1) {
+                if (!once[i]) {
+                    if (styleValue(cssAttributeData[v[i]])) {
+                        match = true;
+                        round = true;
+                        once[i] = true;
+                        break;
+                    }
+                }
+            }
+            if (!round) {
+                return match;
+            }
+        }
+    }
+
+    function substyle() {
+        var v;
+        for (;;) {
+            if (nexttoken.id === '}' || nexttoken.id === '(end)' ||
+                    xquote && nexttoken.id === xquote) {
+                return;
+            }
+            while (nexttoken.id === ';') {
+                warning("Misplaced ';'.");
+                advance(';');
+            }
+            v = styleAttribute();
+            advance(':');
+            if (nexttoken.identifier && nexttoken.value === 'inherit') {
+                advance();
+            } else {
+                styleValue(v);
+            }
+            while (nexttoken.id !== ';' && nexttoken.id !== '!' &&
+                    nexttoken.id !== '}' && nexttoken.id !== '(end)' &&
+                    nexttoken.id !== xquote) {
+                warning("Unexpected token '{a}'.", nexttoken, nexttoken.value);
+                advance();
+            }
+            if (nexttoken.id === '!') {
+                advance('!');
+                adjacent();
+                if (nexttoken.identifier && nexttoken.value === 'important') {
+                    advance();
+                } else {
+                    warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'important', nexttoken.value);
+                }
+            }
+            if (nexttoken.id === '}' || nexttoken.id === xquote) {
+                warning("Missing '{a}'.", nexttoken, ';');
+            } else {
+                advance(';');
+            }
+        }
+    }
+
+    function stylePattern() {
+        var name;
+        if (nexttoken.id === '{') {
+            warning("Expected a style pattern, and instead saw '{a}'.", nexttoken,
+                nexttoken.id);
+        } else if (nexttoken.id === '@') {
+            advance('@');
+            name = nexttoken.value;
+            if (nexttoken.identifier && atrule[name] === true) {
+                advance();
+                return name;
+            }
+            warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name);
+        }
+        for (;;) {
+            if (nexttoken.identifier) {
+                if (!htmltag.hasOwnProperty(nexttoken.value)) {
+                    warning("Expected a tagName, and instead saw {a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            } else {
+                switch (nexttoken.id) {
+                case '>':
+                case '+':
+                    advance();
+                    if (!nexttoken.identifier ||
+                            !htmltag.hasOwnProperty(nexttoken.value)) {
+                        warning("Expected a tagName, and instead saw {a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                    break;
+                case ':':
+                    advance(':');
+                    if (pseudorule[nexttoken.value] !== true) {
+                        warning("Expected a pseudo, and instead saw :{a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                    if (nexttoken.value === 'lang') {
+                        advance('(');
+                        if (!nexttoken.identifier) {
+                            warning("Expected a lang code, and instead saw :{a}.",
+                                nexttoken, nexttoken.value);
+                        }
+                        advance(')');
+                    }
+                    break;
+                case '#':
+                    advance('#');
+                    if (!nexttoken.identifier) {
+                        warning("Expected an id, and instead saw #{a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                    break;
+                case '*':
+                    advance('*');
+                    break;
+                case '.':
+                    advance('.');
+                    if (!nexttoken.identifier) {
+                        warning("Expected a class, and instead saw #.{a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                    break;
+                case '[':
+                    advance('[');
+                    if (!nexttoken.identifier) {
+                        warning("Expected an attribute, and instead saw [{a}].",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                    if (nexttoken.id === '=' || nexttoken.id === '~=' ||
+                            nexttoken.id === '|=') {
+                        advance();
+                        if (nexttoken.type !== '(string)') {
+                            warning("Expected a string, and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                        }
+                        advance();
+                    }
+                    advance(']');
+                    break;
+                default:
+                    error("Expected a CSS selector, and instead saw {a}.",
+                        nexttoken, nexttoken.value);
+                }
+            }
+            if (nexttoken.id === '</' || nexttoken.id === '{' ||
+                    nexttoken.id === '(end)') {
+                return '';
+            }
+            if (nexttoken.id === ',') {
+                advance(',');
+            }
+        }
+    }
+
+    function styles() {
+        while (nexttoken.id !== '</' && nexttoken.id !== '(end)') {
+            stylePattern();
+            xmode = 'styleproperty';
+            if (nexttoken.id === ';') {
+                advance(';');
+            } else {
+                advance('{');
+                substyle();
+                xmode = 'style';
+                advance('}');
+            }
+        }
+    }
+
+
+// HTML parsing.
+
+    function doBegin(n) {
+        if (n !== 'html' && !option.fragment) {
+            if (n === 'div' && option.adsafe) {
+                error("ADSAFE: Use the fragment option.");
+            } else {
+                error("Expected '{a}' and instead saw '{b}'.",
+                    token, 'html', n);
+            }
+        }
+        if (option.adsafe) {
+            if (n === 'html') {
+                error("Currently, ADsafe does not operate on whole HTML documents. It operates on <div> fragments and .js files.", token);
+            }
+            if (option.fragment) {
+                if (n !== 'div') {
+                    error("ADsafe violation: Wrap the widget in a div.", token);
+                }
+            } else {
+                error("Use the fragment option.", token);
+            }
+        }
+        option.browser = true;
+        assume();
+    }
+
+    function doAttribute(n, a, v) {
+        var u, x;
+        if (a === 'id') {
+            u = typeof v === 'string' ? v.toUpperCase() : '';
+            if (ids[u] === true) {
+                warning("Duplicate id='{a}'.", nexttoken, v);
+            }
+            if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    adsafe_id = v;
+                    if (!/^[A-Z]+_$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                }
+            }
+            x = v.search(dx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'class' || a === 'type' || a === 'name') {
+            x = v.search(qx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'href' || a === 'background' ||
+                a === 'content' || a === 'data' ||
+                a.indexOf('src') >= 0 || a.indexOf('url') >= 0) {
+            if (option.safe && ux.test(v)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(v);
+        } else if (a === 'for') {
+            if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    warning("ADSAFE violation: bad id.");
+                }
+            }
+        } else if (a === 'name') {
+            if (option.adsafe && v.indexOf('_') >= 0) {
+                warning("ADsafe name violation.");
+            }
+        }
+    }
+
+    function doTag(n, a) {
+        var i, t = htmltag[n], x;
+        src = false;
+        if (!t) {
+            error("Unrecognized tag '<{a}>'.",
+                    nexttoken,
+                    n === n.toLowerCase() ? n :
+                        n + ' (capitalization error)');
+        }
+        if (stack.length > 0) {
+            if (n === 'html') {
+                error("Too many <html> tags.", token);
+            }
+            x = t.parent;
+            if (x) {
+                if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) {
+                    error("A '<{a}>' must be within '<{b}>'.",
+                            token, n, x);
+                }
+            } else if (!option.adsafe && !option.fragment) {
+                i = stack.length;
+                do {
+                    if (i <= 0) {
+                        error("A '<{a}>' must be within '<{b}>'.",
+                                token, n, 'body');
+                    }
+                    i -= 1;
+                } while (stack[i].name !== 'body');
+            }
+        }
+        switch (n) {
+        case 'div':
+            if (option.adsafe && stack.length === 1 && !adsafe_id) {
+                warning("ADSAFE violation: missing ID_.");
+            }
+            break;
+        case 'script':
+            xmode = 'script';
+            advance('>');
+            indent = nexttoken.from;
+            if (a.lang) {
+                warning("lang is deprecated.", token);
+            }
+            if (option.adsafe && stack.length !== 1) {
+                warning("ADsafe script placement violation.", token);
+            }
+            if (a.src) {
+                if (option.adsafe && (!adsafe_may || !approved[a.src])) {
+                    warning("ADsafe unapproved script source.", token);
+                }
+                if (a.type) {
+                    warning("type is unnecessary.", token);
+                }
+            } else {
+                if (adsafe_went) {
+                    error("ADsafe script violation.", token);
+                }
+                statements('script');
+            }
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'script') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'script', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'style':
+            xmode = 'style';
+            advance('>');
+            styles();
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'style') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'style', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'input':
+            switch (a.type) {
+            case 'radio':
+            case 'checkbox':
+            case 'text':
+            case 'button':
+            case 'file':
+            case 'reset':
+            case 'submit':
+            case 'password':
+            case 'file':
+            case 'hidden':
+            case 'image':
+                break;
+            default:
+                warning("Bad input type.");
+            }
+            if (option.adsafe && a.autocomplete !== 'off') {
+                warning("ADsafe autocomplete violation.");
+            }
+            break;
+        case 'applet':
+        case 'body':
+        case 'embed':
+        case 'frame':
+        case 'frameset':
+        case 'head':
+        case 'iframe':
+        case 'img':
+        case 'noembed':
+        case 'noframes':
+        case 'object':
+        case 'param':
+            if (option.adsafe) {
+                warning("ADsafe violation: Disallowed tag: " + n);
+            }
+            break;
+        }
+    }
+
+
+    function closetag(n) {
+        return '</' + n + '>';
+    }
+
+    function html() {
+        var a, attributes, e, n, q, t, v, w = option.white, wmode;
+        xmode = 'html';
+        xquote = '';
+        stack = null;
+        for (;;) {
+            switch (nexttoken.value) {
+            case '<':
+                xmode = 'html';
+                advance('<');
+                attributes = {};
+                t = nexttoken;
+                if (!t.identifier) {
+                    warning("Bad identifier {a}.", t, t.value);
+                }
+                n = t.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                t.name = n;
+                advance();
+                if (!stack) {
+                    stack = [];
+                    doBegin(n);
+                }
+                v = htmltag[n];
+                if (typeof v !== 'object') {
+                    error("Unrecognized tag '<{a}>'.", t, n);
+                }
+                e = v.empty;
+                t.type = n;
+                for (;;) {
+                    if (nexttoken.id === '/') {
+                        advance('/');
+                        if (nexttoken.id !== '>') {
+                            warning("Expected '{a}' and instead saw '{b}'.",
+                                    nexttoken, '>', nexttoken.value);
+                        }
+                        break;
+                    }
+                    if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') {
+                        break;
+                    }
+                    if (!nexttoken.identifier) {
+                        if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                            error("Missing '>'.", nexttoken);
+                        }
+                        warning("Bad identifier.");
+                    }
+                    option.white = true;
+                    nonadjacent(token, nexttoken);
+                    a = nexttoken.value;
+                    option.white = w;
+                    advance();
+                    if (!option.cap && a !== a.toLowerCase()) {
+                        warning("Attribute '{a}' not all lower case.", nexttoken, a);
+                    }
+                    a = a.toLowerCase();
+                    xquote = '';
+                    if (attributes.hasOwnProperty(a)) {
+                        warning("Attribute '{a}' repeated.", nexttoken, a);
+                    }
+                    if (a.slice(0, 2) === 'on') {
+                        if (!option.on) {
+                            warning("Avoid HTML event handlers.");
+                        }
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xquote = q;
+                        wmode = option.white;
+                        option.white = false;
+                        advance(q);
+                        statements('on');
+                        option.white = wmode;
+                        if (nexttoken.id !== q) {
+                            error("Missing close quote on script attribute.");
+                        }
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else if (a === 'style') {
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xmode = 'styleproperty';
+                        xquote = q;
+                        advance(q);
+                        substyle();
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else {
+                        if (nexttoken.id === '=') {
+                            advance('=');
+                            v = nexttoken.value;
+                            if (!nexttoken.identifier &&
+                                    nexttoken.id !== '"' &&
+                                    nexttoken.id !== '\'' &&
+                                    nexttoken.type !== '(string)' &&
+                                    nexttoken.type !== '(number)' &&
+                                    nexttoken.type !== '(color)') {
+                                warning("Expected an attribute value and instead saw '{a}'.", token, a);
+                            }
+                            advance();
+                        } else {
+                            v = true;
+                        }
+                    }
+                    attributes[a] = v;
+                    doAttribute(n, a, v);
+                }
+                doTag(n, attributes);
+                if (!e) {
+                    stack.push(t);
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '</':
+                xmode = 'html';
+                advance('</');
+                if (!nexttoken.identifier) {
+                    warning("Bad identifier.");
+                }
+                n = nexttoken.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                advance();
+                if (!stack) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                t = stack.pop();
+                if (!t) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                if (t.name !== n) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, closetag(t.name), closetag(n));
+                }
+                if (nexttoken.id !== '>') {
+                    error("Missing '{a}'.", nexttoken, '>');
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '<!':
+                if (option.safe) {
+                    warning("ADsafe HTML violation.");
+                }
+                xmode = 'html';
+                for (;;) {
+                    advance();
+                    if (nexttoken.id === '>' || nexttoken.id === '(end)') {
+                        break;
+                    }
+                    if (nexttoken.id === '--') {
+                        warning("Unexpected --.");
+                    }
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '<!--':
+                xmode = 'html';
+                if (option.safe) {
+                    warning("ADsafe HTML violation.");
+                }
+                for (;;) {
+                    advance();
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '-->'.");
+                    }
+                    if (nexttoken.id === '<!' || nexttoken.id === '<!--') {
+                        error("Unexpected '<!' in HTML comment.");
+                    }
+                    if (nexttoken.id === '--') {
+                        advance('--');
+                        break;
+                    }
+                }
+                abut();
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '(end)':
+                return;
+            default:
+                if (nexttoken.id === '(end)') {
+                    error("Missing '{a}'.", nexttoken,
+                            '</' + stack[stack.length - 1].value + '>');
+                } else {
+                    advance();
+                }
+            }
+            if (stack && stack.length === 0 && (option.adsafe ||
+                    !option.fragment || nexttoken.id === '(end)')) {
+                break;
+            }
+        }
+        if (nexttoken.id !== '(end)') {
+            error("Unexpected material after the end.");
+        }
+    }
+
+
+// Build the syntax table by declaring the syntactic elements of the language.
+
+    type('(number)', idValue);
+    type('(string)', idValue);
+
+    syntax['(identifier)'] = {
+        type: '(identifier)',
+        lbp: 0,
+        identifier: true,
+        nud: function () {
+            var v = this.value,
+                s = scope[v];
+
+// The name is in scope and defined in the current function.
+
+            if (s && (s === funct || s === funct['(global)'])) {
+
+//      If we are not also in the global scope, change 'unused' to 'var',
+//      and reject labels.
+
+                if (!funct['(global)']) {
+                    switch (funct[v]) {
+                    case 'unused':
+                        funct[v] = 'var';
+                        break;
+                    case 'label':
+                        warning("'{a}' is a statement label.", token, v);
+                        break;
+                    }
+                }
+
+// The name is not defined in the function.  If we are in the global scope,
+// then we have an undefined variable.
+
+            } else if (funct['(global)']) {
+                if (option.undef) {
+                    warning("'{a}' is undefined.", token, v);
+                }
+                note_implied(token);
+
+// If the name is already defined in the current
+// function, but not as outer, then there is a scope error.
+
+            } else {
+                switch (funct[v]) {
+                case 'closure':
+                case 'function':
+                case 'var':
+                case 'unused':
+                    warning("'{a}' used out of scope.", token, v);
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                case 'outer':
+                case true:
+                    break;
+                default:
+
+// If the name is defined in an outer function, make an outer entry, and if
+// it was unused, make it var.
+
+                    if (s === true) {
+                        funct[v] = true;
+                    } else if (typeof s !== 'object') {
+                        if (option.undef) {
+                            warning("'{a}' is undefined.", token, v);
+                        } else {
+                            funct[v] = true;
+                        }
+                        note_implied(token);
+                    } else {
+                        switch (s[v]) {
+                        case 'function':
+                        case 'var':
+                        case 'unused':
+                            s[v] = 'closure';
+                            funct[v] = 'outer';
+                            break;
+                        case 'closure':
+                        case 'parameter':
+                            funct[v] = 'outer';
+                            break;
+                        case 'label':
+                            warning("'{a}' is a statement label.", token, v);
+                        }
+                    }
+                }
+            }
+            return this;
+        },
+        led: function () {
+            error("Expected an operator and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    };
+
+    type('(regexp)', function () {
+        return this;
+    });
+
+    delim('(endline)');
+    delim('(begin)');
+    delim('(end)').reach = true;
+    delim('</').reach = true;
+    delim('<!');
+    delim('<!--');
+    delim('-->');
+    delim('(error)').reach = true;
+    delim('}').reach = true;
+    delim(')');
+    delim(']');
+    delim('"').reach = true;
+    delim("'").reach = true;
+    delim(';');
+    delim(':').reach = true;
+    delim(',');
+    delim('#');
+    delim('@');
+    reserve('else');
+    reserve('case').reach = true;
+    reserve('catch');
+    reserve('default').reach = true;
+    reserve('finally');
+    reservevar('arguments');
+    reservevar('eval');
+    reservevar('false');
+    reservevar('Infinity');
+    reservevar('NaN');
+    reservevar('null');
+    reservevar('this');
+    reservevar('true');
+    reservevar('undefined');
+    assignop('=', 'assign', 20);
+    assignop('+=', 'assignadd', 20);
+    assignop('-=', 'assignsub', 20);
+    assignop('*=', 'assignmult', 20);
+    assignop('/=', 'assigndiv', 20).nud = function () {
+        error("A regular expression literal can be confused with '/='.");
+    };
+    assignop('%=', 'assignmod', 20);
+    bitwiseassignop('&=', 'assignbitand', 20);
+    bitwiseassignop('|=', 'assignbitor', 20);
+    bitwiseassignop('^=', 'assignbitxor', 20);
+    bitwiseassignop('<<=', 'assignshiftleft', 20);
+    bitwiseassignop('>>=', 'assignshiftright', 20);
+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
+    infix('?', function (left) {
+        this.left = left;
+        this.right = parse(10);
+        advance(':');
+        this['else'] = parse(10);
+        return this;
+    }, 30);
+
+    infix('||', 'or', 40);
+    infix('&&', 'and', 50);
+    bitwise('|', 'bitor', 70);
+    bitwise('^', 'bitxor', 80);
+    bitwise('&', 'bitand', 90);
+    relation('==', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '===', '==');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', right.value);
+        }
+        return this;
+    });
+    relation('===');
+    relation('!=', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '!==', '!=');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', right.value);
+        }
+        return this;
+    });
+    relation('!==');
+    relation('<');
+    relation('>');
+    relation('<=');
+    relation('>=');
+    bitwise('<<', 'shiftleft', 120);
+    bitwise('>>', 'shiftright', 120);
+    bitwise('>>>', 'shiftrightunsigned', 120);
+    infix('in', 'in', 120);
+    infix('instanceof', 'instanceof', 120);
+    infix('+', function (left) {
+        nonadjacent(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        var right = parse(130);
+        if (left && right && left.id === '(string)' && right.id === '(string)') {
+            left.value += right.value;
+            left.character = right.character;
+            if (jx.test(left.value)) {
+                warning("JavaScript URL.", left);
+            }
+            return left;
+        }
+        this.left = left;
+        this.right = right;
+        return this;
+    }, 130);
+    prefix('+', 'num');
+    infix('-', 'sub', 130);
+    prefix('-', 'neg');
+    infix('*', 'mult', 140);
+    infix('/', 'div', 140);
+    infix('%', 'mod', 140);
+
+    suffix('++', 'postinc');
+    prefix('++', 'preinc');
+    syntax['++'].exps = true;
+
+    suffix('--', 'postdec');
+    prefix('--', 'predec');
+    syntax['--'].exps = true;
+    prefix('delete', function () {
+        var p = parse(0);
+        if (p.id !== '.' && p.id !== '[') {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '.', nexttoken.value);
+        }
+    }).exps = true;
+
+
+    prefix('~', function () {
+        if (option.bitwise) {
+            warning("Unexpected '{a}'.", this, '~');
+        }
+        parse(150);
+        return this;
+    });
+    prefix('!', 'not');
+    prefix('typeof', 'typeof');
+    prefix('new', function () {
+        var c = parse(155), i;
+        if (c && c.id !== 'function') {
+            if (c.identifier) {
+                c['new'] = true;
+                switch (c.value) {
+                case 'Object':
+                    warning("Use the object literal notation {}.", token);
+                    break;
+                case 'Array':
+                    warning("Use the array literal notation [].", token);
+                    break;
+                case 'Number':
+                case 'String':
+                case 'Boolean':
+                case 'Math':
+                    warning("Do not use {a} as a constructor.", token, c.value);
+                    break;
+                case 'Function':
+                    if (!option.evil) {
+                        warning("The Function constructor is eval.");
+                    }
+                    break;
+                case 'Date':
+                case 'RegExp':
+                    break;
+                default:
+                    if (c.id !== 'function') {
+                        i = c.value.substr(0, 1);
+                        if (option.newcap && (i < 'A' || i > 'Z')) {
+                            warning(
+                    "A constructor name should start with an uppercase letter.",
+                                token);
+                        }
+                    }
+                }
+            } else {
+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
+                    warning("Bad constructor.", token);
+                }
+            }
+        } else {
+            warning("Weird construction. Delete 'new'.", this);
+        }
+        adjacent(token, nexttoken);
+        if (nexttoken.id !== '(') {
+            warning("Missing '()' invoking a constructor.");
+        }
+        this.first = c;
+        return this;
+    });
+    syntax['new'].exps = true;
+
+    infix('.', function (left) {
+        adjacent(prevtoken, token);
+        var t = this, m = identifier();
+        if (typeof m === 'string') {
+            countMember(m);
+        }
+        t.left = left;
+        t.right = m;
+        if (!option.evil && left && left.value === 'document' &&
+                (m === 'write' || m === 'writeln')) {
+            warning("document.write can be a form of eval.", left);
+        }
+        if (option.adsafe) {
+            if (left && left.value === 'ADSAFE') {
+                if (m === 'id' || m === 'lib') {
+                    warning("ADsafe violation.", this);
+                } else if (m === 'go') {
+                    if (xmode !== 'script') {
+                        warning("ADsafe violation.", this);
+                    } else if (adsafe_went || nexttoken.id !== '(' ||
+                            peek(0).id !== '(string)' ||
+                            peek(0).value !== adsafe_id ||
+                            peek(1).id !== ',') {
+                        error("ADsafe violation: go.", this);
+                    }
+                    adsafe_went = true;
+                    adsafe_may = false;
+                }
+            }
+        }
+        if (option.safe) {
+            for (;;) {
+                if (banned[m] === true) {
+                    warning("ADsafe restricted word '{a}'.", token, m);
+                }
+                if (predefined[left.value] !== true ||
+                        nexttoken.id === '(') {
+                    break;
+                }
+                if (standard_member[m] === true) {
+                    if (nexttoken.id === '.') {
+                        warning("ADsafe violation.", this);
+                    }
+                    break;
+                }
+                if (nexttoken.id !== '.') {
+                    warning("ADsafe violation.", this);
+                    break;
+                }
+                advance('.');
+                token.left = t;
+                token.right = m;
+                t = token;
+                m = identifier();
+                if (typeof m === 'string') {
+                    countMember(m);
+                }
+            }
+        }
+        return t;
+    }, 160);
+
+    infix('(', function (left) {
+        adjacent(prevtoken, token);
+        nospace();
+        var n = 0,
+            p = [];
+        if (left) {
+            if (left.type === '(identifier)') {
+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+                    if (left.value !== 'Number' && left.value !== 'String' &&
+                            left.value !== 'Boolean' && left.value !== 'Date') {
+                        if (left.value === 'Math') {
+                            warning("Math is not a function.", left);
+                        } else if (option.newcap) {
+                            warning("Missing 'new' prefix when invoking a constructor.",
+                                left);
+                        }
+                    }
+                }
+            } else if (left.id === '.') {
+                if (option.safe && left.left.value === 'Math' &&
+                        left.right === 'random') {
+                    warning("ADsafe violation.", left);
+                }
+            }
+        }
+        if (nexttoken.id !== ')') {
+            for (;;) {
+                p[p.length] = parse(10);
+                n += 1;
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                advance(',');
+                nonadjacent(token, nexttoken);
+            }
+        }
+        advance(')');
+        nospace(prevtoken, token);
+        if (typeof left === 'object') {
+            if (left.value === 'parseInt' && n === 1) {
+                warning("Missing radix parameter.", left);
+            }
+            if (!option.evil) {
+                if (left.value === 'eval' || left.value === 'Function' ||
+                        left.value === 'execScript') {
+                    warning("eval is evil.", left);
+                } else if (p[0] && p[0].id === '(string)' &&
+                       (left.value === 'setTimeout' ||
+                        left.value === 'setInterval')) {
+                    warning(
+    "Implied eval is evil. Pass a function instead of a string.", left);
+                }
+            }
+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
+                    left.id !== '?') {
+                warning("Bad invocation.", left);
+            }
+
+        }
+        this.left = left;
+        return this;
+    }, 155).exps = true;
+
+    prefix('(', function () {
+        nospace();
+        var v = parse(0);
+        advance(')', this);
+        nospace(prevtoken, token);
+        return v;
+    });
+
+    infix('[', function (left) {
+        nospace();
+        var e = parse(0), s;
+        if (e && e.type === '(string)') {
+            if (option.safe && banned[e.value] === true) {
+                warning("ADsafe restricted word '{a}'.", this, e.value);
+            }
+            countMember(e.value);
+            if (!option.sub && ix.test(e.value)) {
+                s = syntax[e.value];
+                if (!s || !s.reserved) {
+                    warning("['{a}'] is better written in dot notation.",
+                            e, e.value);
+                }
+            }
+        } else if (!e || (e.type !== '(number)' &&
+                (e.id !== '+' || e.arity !== 'unary'))) {
+            if (option.safe) {
+                warning('ADsafe subscripting.');
+            }
+        }
+        advance(']', this);
+        nospace(prevtoken, token);
+        this.left = left;
+        this.right = e;
+        return this;
+    }, 160);
+
+    prefix('[', function () {
+        if (nexttoken.id === ']') {
+            advance(']');
+            return;
+        }
+        var b = token.line !== nexttoken.line;
+        if (b) {
+            indent += option.indent;
+            if (nexttoken.from === indent + option.indent) {
+                indent += option.indent;
+            }
+        }
+        for (;;) {
+            if (b && token.line !== nexttoken.line) {
+                indentation();
+            }
+            parse(10);
+            if (nexttoken.id === ',') {
+                adjacent(token, nexttoken);
+                advance(',');
+                if (nexttoken.id === ',') {
+                    warning("Extra comma.", token);
+                } else if (nexttoken.id === ']') {
+                    warning("Extra comma.", token);
+                    break;
+                }
+                nonadjacent(token, nexttoken);
+            } else {
+                if (b) {
+                    indent -= option.indent;
+                    indentation();
+                }
+                break;
+            }
+        }
+        advance(']', this);
+        return;
+    }, 160);
+
+    (function (x) {
+        x.nud = function () {
+            var b, i, s, seen = {};
+            b = token.line !== nexttoken.line;
+            if (b) {
+                indent += option.indent;
+                if (nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+            }
+            for (;;) {
+                if (nexttoken.id === '}') {
+                    break;
+                }
+                if (b) {
+                    indentation();
+                }
+                i = optionalidentifier(true);
+                if (!i) {
+                    if (nexttoken.id === '(string)') {
+                        i = nexttoken.value;
+                        if (ix.test(i)) {
+                            s = syntax[i];
+                        }
+                        advance();
+                    } else if (nexttoken.id === '(number)') {
+                        i = nexttoken.value.toString();
+                        advance();
+                    } else {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                                nexttoken, '}', nexttoken.value);
+                    }
+                }
+                if (seen[i] === true) {
+                    warning("Duplicate member '{a}'.", nexttoken, i);
+                }
+                seen[i] = true;
+                countMember(i);
+                advance(':');
+                nonadjacent(token, nexttoken);
+                parse(10);
+                if (nexttoken.id === ',') {
+                    adjacent(token, nexttoken);
+                    advance(',');
+                    if (nexttoken.id === ',' || nexttoken.id === '}') {
+                        warning("Extra comma.", token);
+                    }
+                    nonadjacent(token, nexttoken);
+                } else {
+                    break;
+                }
+            }
+            if (b) {
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', this);
+            return;
+        };
+        x.fud = function () {
+            error("Expected to see a statement and instead saw a block.", token);
+        };
+    })(delim('{'));
+
+
+    function varstatement(prefix) {
+
+// JavaScript does not have block scope. It only has function scope. So,
+// declaring a variable in a block can have unexpected consequences.
+
+        if (funct['(onevar)'] && option.onevar) {
+            warning("Too many var statements.");
+        } else if (!funct['(global)']) {
+            funct['(onevar)'] = true;
+        }
+        for (;;) {
+            nonadjacent(token, nexttoken);
+            addlabel(identifier(), 'unused');
+            if (prefix) {
+                return;
+            }
+            if (nexttoken.id === '=') {
+                nonadjacent(token, nexttoken);
+                advance('=');
+                nonadjacent(token, nexttoken);
+                if (peek(0).id === '=') {
+                    error("Variable {a} was not declared correctly.",
+                            nexttoken, nexttoken.value);
+                }
+                parse(20);
+            }
+            if (nexttoken.id !== ',') {
+                return;
+            }
+            adjacent(token, nexttoken);
+            advance(',');
+            nonadjacent(token, nexttoken);
+        }
+    }
+
+
+    stmt('var', varstatement);
+
+    stmt('new', function () {
+        error("'new' should not be used as a statement.");
+    });
+
+
+    function functionparams() {
+        var i, t = nexttoken, p = [];
+        advance('(');
+        nospace();
+        if (nexttoken.id === ')') {
+            advance(')');
+            nospace(prevtoken, token);
+            return;
+        }
+        for (;;) {
+            i = identifier();
+            p.push(i);
+            addlabel(i, 'parameter');
+            if (nexttoken.id === ',') {
+                advance(',');
+                nonadjacent(token, nexttoken);
+            } else {
+                advance(')', t);
+                nospace(prevtoken, token);
+                return p.join(', ');
+            }
+        }
+    }
+
+    function doFunction(i) {
+        var s = scope;
+        scope = Object.create(s);
+        funct = {
+            '(name)'    : i || '"' + anonname + '"',
+            '(line)'    : nexttoken.line + 1,
+            '(context)' : funct,
+            '(breakage)': 0,
+            '(loopage)' : 0,
+            '(scope)'   : scope
+        };
+        functions.push(funct);
+        if (i) {
+            addlabel(i, 'function');
+        }
+        funct['(params)'] = functionparams();
+
+        block(false);
+        scope = s;
+        funct = funct['(context)'];
+    }
+
+
+    blockstmt('function', function () {
+        if (inblock) {
+            warning(
+"Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);
+
+        }
+        var i = identifier();
+        adjacent(token, nexttoken);
+        addlabel(i, 'unused');
+        doFunction(i);
+        if (nexttoken.id === '(' && nexttoken.line === token.line) {
+            error(
+"Function statements are not invocable. Wrap the function expression in parens.");
+        }
+    });
+
+    prefix('function', function () {
+        var i = optionalidentifier();
+        if (i) {
+            adjacent(token, nexttoken);
+        } else {
+            nonadjacent(token, nexttoken);
+        }
+        doFunction(i);
+        if (funct['(loopage)'] && nexttoken.id !== '(') {
+            warning("Be careful when making functions within a loop. Consider putting the function in a closure.");
+        }
+        return this;
+    });
+
+    blockstmt('if', function () {
+        var t = nexttoken;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        if (nexttoken.id === 'else') {
+            nonadjacent(token, nexttoken);
+            advance('else');
+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
+                statement(true);
+            } else {
+                block(true);
+            }
+        }
+        return this;
+    });
+
+    blockstmt('try', function () {
+        var b, e, s;
+        if (option.adsafe) {
+            warning("ADsafe try violation.", this);
+        }
+        block(false);
+        if (nexttoken.id === 'catch') {
+            advance('catch');
+            nonadjacent(token, nexttoken);
+            advance('(');
+            s = scope;
+            scope = Object.create(s);
+            e = nexttoken.value;
+            if (nexttoken.type !== '(identifier)') {
+                warning("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, e);
+            } else {
+                addlabel(e, 'unused');
+            }
+            advance();
+            advance(')');
+            block(false);
+            b = true;
+            scope = s;
+        }
+        if (nexttoken.id === 'finally') {
+            advance('finally');
+            block(false);
+            return;
+        } else if (!b) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, 'catch', nexttoken.value);
+        }
+    });
+
+    blockstmt('while', function () {
+        var t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+    }).labelled = true;
+
+    reserve('with');
+
+    blockstmt('switch', function () {
+        var t = nexttoken,
+            g = false;
+        funct['(breakage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        this.condition = parse(20);
+        advance(')', t);
+        nospace(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        advance('{');
+        nonadjacent(token, nexttoken);
+        indent += option.indent;
+        this.cases = [];
+        for (;;) {
+            switch (nexttoken.id) {
+            case 'case':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'case':
+                case 'continue':
+                case 'return':
+                case 'switch':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'case'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('case');
+                this.cases.push(parse(20));
+                g = true;
+                advance(':');
+                funct['(verb)'] = 'case';
+                break;
+            case 'default':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'continue':
+                case 'return':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'default'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('default');
+                g = true;
+                advance(':');
+                break;
+            case '}':
+                indent -= option.indent;
+                indentation();
+                advance('}', t);
+                if (this.cases.length === 1 || this.condition.id === 'true' ||
+                        this.condition.id === 'false') {
+                    warning("This 'switch' should be an 'if'.", this);
+                }
+                funct['(breakage)'] -= 1;
+                funct['(verb)'] = undefined;
+                return;
+            case '(end)':
+                error("Missing '{a}'.", nexttoken, '}');
+                return;
+            default:
+                if (g) {
+                    switch (token.id) {
+                    case ',':
+                        error("Each value should have its own case label.");
+                        return;
+                    case ':':
+                        statements();
+                        break;
+                    default:
+                        error("Missing ':' on a case clause.", token);
+                    }
+                } else {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'case', nexttoken.value);
+                }
+            }
+        }
+    }).labelled = true;
+
+    stmt('debugger', function () {
+        if (!option.debug) {
+            warning("All 'debugger' statements should be removed.");
+        }
+    });
+
+    stmt('do', function () {
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        block(true);
+        advance('while');
+        var t = nexttoken;
+        nonadjacent(token, t);
+        advance('(');
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+    }).labelled = true;
+
+    blockstmt('for', function () {
+        var s, t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
+            if (nexttoken.id === 'var') {
+                advance('var');
+                varstatement(true);
+            } else {
+                advance();
+            }
+            advance('in');
+            parse(20);
+            advance(')', t);
+            s = block(true);
+            if (!option.forin && (s.length > 1 || typeof s[0] !== 'object' ||
+                    s[0].value !== 'if')) {
+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);
+            }
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        } else {
+            if (nexttoken.id !== ';') {
+                if (nexttoken.id === 'var') {
+                    advance('var');
+                    varstatement();
+                } else {
+                    for (;;) {
+                        parse(0, 'for');
+                        if (nexttoken.id !== ',') {
+                            break;
+                        }
+                        advance(',');
+                    }
+                }
+            }
+            advance(';');
+            if (nexttoken.id !== ';') {
+                parse(20);
+                if (nexttoken.id === '=') {
+                    warning("Expected a conditional expression and instead saw an assignment.");
+                    advance('=');
+                    parse(20);
+                }
+            }
+            advance(';');
+            if (nexttoken.id === ';') {
+                error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, ')', ';');
+            }
+            if (nexttoken.id !== ')') {
+                for (;;) {
+                    parse(0, 'for');
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            block(true);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+        }
+    }).labelled = true;
+
+
+    stmt('break', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                advance();
+            }
+        }
+        reachable('break');
+    });
+
+
+    stmt('continue', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                advance();
+            }
+        }
+        reachable('continue');
+    });
+
+
+    stmt('return', function () {
+        nolinebreak(this);
+        if (nexttoken.id === '(regexp)') {
+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+        }
+        if (nexttoken.id !== ';' && !nexttoken.reach) {
+            nonadjacent(token, nexttoken);
+            parse(20);
+        }
+        reachable('return');
+    });
+
+
+    stmt('throw', function () {
+        nolinebreak(this);
+        nonadjacent(token, nexttoken);
+        parse(20);
+        reachable('throw');
+    });
+
+    reserve('void');
+
+//  Superfluous reserved words
+
+    reserve('class');
+    reserve('const');
+    reserve('enum');
+    reserve('export');
+    reserve('extends');
+    reserve('float');
+    reserve('goto');
+    reserve('import');
+    reserve('let');
+    reserve('super');
+
+    function jsonValue() {
+
+        function jsonObject() {
+            var t = nexttoken;
+            advance('{');
+            if (nexttoken.id !== '}') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '}' to match '{' from line {a}.",
+                                nexttoken, t.line + 1);
+                    } else if (nexttoken.id === '}') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    } else if (nexttoken.id !== '(string)') {
+                        warning("Expected a string and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                    }
+                    advance();
+                    advance(':');
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance('}');
+        }
+
+        function jsonArray() {
+            var t = nexttoken;
+            advance('[');
+            if (nexttoken.id !== ']') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing ']' to match '[' from line {a}.",
+                                nexttoken, t.line + 1);
+                    } else if (nexttoken.id === ']') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    }
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(']');
+        }
+
+        switch (nexttoken.id) {
+        case '{':
+            jsonObject();
+            break;
+        case '[':
+            jsonArray();
+            break;
+        case 'true':
+        case 'false':
+        case 'null':
+        case '(number)':
+        case '(string)':
+            advance();
+            break;
+        case '-':
+            advance('-');
+            if (token.character !== nexttoken.from) {
+                warning("Unexpected space after '-'.", token);
+            }
+            adjacent(token, nexttoken);
+            advance('(number)');
+            break;
+        default:
+            error("Expected a JSON value.", nexttoken);
+        }
+    }
+
+
+// The actual JSLINT function itself.
+
+    var itself = function (s, o) {
+        var a, i;
+        JSLINT.errors = [];
+        predefined = Object.create(standard);
+        if (o) {
+            a = o.predef;
+            if (a instanceof Array) {
+                for (i = 0; i < a.length; i += 1) {
+                    predefined[a[i]] = true;
+                }
+            }
+            if (o.adsafe) {
+                o.safe = true;
+            }
+            if (o.safe) {
+                o.browser = false;
+                o.css     = false;
+                o.debug   = false;
+                o.eqeqeq  = true;
+                o.evil    = false;
+                o.forin   = false;
+                o.nomen   = true;
+                o.on      = false;
+                o.rhino   = false;
+                o.safe    = true;
+                o.sidebar = false;
+                o.strict  = true;
+                o.sub     = false;
+                o.undef   = true;
+                o.widget  = false;
+                predefined.Date = false;
+                predefined['eval'] = false;
+                predefined.Function = false;
+                predefined.Object = false;
+                predefined.ADSAFE = true;
+            }
+            option = o;
+        } else {
+            option = {};
+        }
+        option.indent = option.indent || 4;
+        adsafe_id = '';
+        adsafe_may = false;
+        adsafe_went = false;
+        approved = {};
+        if (option.approved) {
+            for (i = 0; i < option.approved.length; i += 1) {
+                approved[option.approved[i]] = option.approved[i];
+            }
+        }
+        approved.test = 'test'; ///////////////////////////////////////
+        tab = '';
+        for (i = 0; i < option.indent; i += 1) {
+            tab += ' ';
+        }
+        indent = 0;
+        global = Object.create(predefined);
+        scope = global;
+        funct = {
+            '(global)': true,
+            '(name)': '(global)',
+            '(scope)': scope,
+            '(breakage)': 0,
+            '(loopage)': 0
+        };
+        functions = [];
+        ids = {};
+        urls = [];
+        src = false;
+        xmode = false;
+        stack = null;
+        member = {};
+        membersOnly = null;
+        implied = {};
+        inblock = false;
+        lookahead = [];
+        jsonmode = false;
+        warnings = 0;
+        lex.init(s);
+        prereg = true;
+
+        prevtoken = token = nexttoken = syntax['(begin)'];
+        assume();
+
+        try {
+            advance();
+            if (nexttoken.value.charAt(0) === '<') {
+                html();
+                if (option.adsafe && !adsafe_went) {
+                    warning("ADsafe violation: Missing ADSAFE.go.", this);
+                }
+            } else {
+                switch (nexttoken.id) {
+                case '{':
+                case '[':
+                    option.laxbreak = true;
+                    jsonmode = true;
+                    jsonValue();
+                    break;
+                case '@':
+                case '*':
+                case '#':
+                case '.':
+                case ':':
+                    xmode = 'style';
+                    advance();
+                    if (token.id !== '@' || !nexttoken.identifier ||
+                            nexttoken.value !== 'charset') {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    if (nexttoken.type !== '(string)' &&
+                            nexttoken.value !== 'UTF-8') {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    advance(';');
+                    styles();
+                    break;
+
+                default:
+                    if (option.adsafe && option.fragment) {
+                        warning("ADsafe violation.", this);
+                    }
+                    statements('lib');
+                }
+            }
+            advance('(end)');
+        } catch (e) {
+            if (e) {
+                JSLINT.errors.push({
+                    reason    : e.message,
+                    line      : e.line || nexttoken.line,
+                    character : e.character || nexttoken.from
+                }, null);
+            }
+        }
+        return JSLINT.errors.length === 0;
+    };
+
+    function to_array(o) {
+        var a = [], k;
+        for (k in o) {
+            if (o.hasOwnProperty(k)) {
+                a.push(k);
+            }
+        }
+        return a;
+    }
+
+// Report generator.
+
+    itself.report = function (option, sep) {
+        var a = [], c, e, f, i, k, l, m = '', n, o = [], s, v, cl, va, un, ou, gl, la;
+
+        function detail(h, s, sep) {
+            if (s.length) {
+                o.push('<div><i>' + h + '</i> ' +
+                        s.sort().join(sep || ', ') + '</div>');
+            }
+        }
+
+        s = to_array(implied);
+
+        k = JSLINT.errors.length;
+        if (k || s.length > 0) {
+            o.push('<div id=errors><i>Error:</i>');
+            if (s.length > 0) {
+                s.sort();
+                for (i = 0; i < s.length; i += 1) {
+                    s[i] = '<code>' + s[i] + '</code>&nbsp;<i>' +
+                        implied[s[i]].join(' ') +
+                        '</i>';
+                }
+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
+                c = true;
+            }
+            for (i = 0; i < k; i += 1) {
+                c = JSLINT.errors[i];
+                if (c) {
+                    e = c.evidence || '';
+                    o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' + (c.line + 1) +
+                            ' character ' + (c.character + 1) : '') +
+                            ': ' + c.reason.entityify() +
+                            '</p><p class=evidence>' +
+                            (e && (e.length > 80 ? e.slice(0, 77) + '...' :
+                            e).entityify()) + '</p>');
+                }
+            }
+            o.push('</div>');
+            if (!c) {
+                return o.join('');
+            }
+        }
+
+        if (!option) {
+
+            o.push('<br><div id=functions>');
+
+            if (urls.length > 0) {
+                detail("URLs<br>", urls, '<br>');
+            }
+
+            s = to_array(scope);
+            if (s.length === 0) {
+                if (jsonmode) {
+                    if (k === 0) {
+                        o.push('<p>JSON: good.</p>');
+                    } else {
+                        o.push('<p>JSON: bad.</p>');
+                    }
+                } else {
+                    o.push('<div><i>No new global variables introduced.</i></div>');
+                }
+            } else {
+                o.push('<div><i>Global</i> ' + s.sort().join(', ') + '</div>');
+            }
+
+            for (i = 0; i < functions.length; i += 1) {
+                f = functions[i];
+                cl = [];
+                va = [];
+                un = [];
+                ou = [];
+                gl = [];
+                la = [];
+                for (k in f) {
+                    if (f.hasOwnProperty(k) && k.charAt(0) !== '(') {
+                        v = f[k];
+                        switch (v) {
+                        case 'closure':
+                            cl.push(k);
+                            break;
+                        case 'var':
+                            va.push(k);
+                            break;
+                        case 'unused':
+                            un.push(k);
+                            break;
+                        case 'label':
+                            la.push(k);
+                            break;
+                        case 'outer':
+                            ou.push(k);
+                            break;
+                        case true:
+                            gl.push(k);
+                            break;
+                        }
+                    }
+                }
+                o.push('<br><div class=function><i>' + f['(line)'] + '</i> ' +
+                        (f['(name)'] || '') + '(' +
+                        (f['(params)'] || '') + ')</div>');
+                detail('Closure', cl);
+                detail('Variable', va);
+                detail('Outer', ou);
+                detail('Global', gl);
+                detail('<big><b>Unused</b></big>', un);
+                detail('Label', la);
+            }
+            a = [];
+            for (k in member) {
+                if (typeof member[k] === 'number') {
+                    a.push(k);
+                }
+            }
+            if (a.length) {
+                a = a.sort();
+                m = '<br><pre>/*members ';
+                l = 10;
+                for (i = 0; i < a.length; i += 1) {
+                    k = a[i];
+                    n = k.name();
+                    if (l + n.length > 72) {
+                        o.push(m + '<br>');
+                        m = '    ';
+                        l = 1;
+                    }
+                    l += n.length + 2;
+                    if (member[k] === 1) {
+                        n = '<i>' + n + '</i>';
+                    }
+                    if (i < a.length - 1) {
+                        n += ', ';
+                    }
+                    m += n;
+                }
+                o.push(m + '<br>*/</pre>');
+            }
+            o.push('</div>');
+        }
+        return o.join('');
+    };
+
+    return itself;
+
+}();
\ No newline at end of file
diff --git a/xtrn/atlantis/gamedata.js b/xtrn/atlantis/gamedata.js
new file mode 100644
index 0000000000000000000000000000000000000000..351bbaa730fdc018b6bc6b4891b328bc4c8608eb
--- /dev/null
+++ b/xtrn/atlantis/gamedata.js
@@ -0,0 +1,593 @@
+var turn=0;
+var game_dir=script_dir;
+
+if(js.global.terrains==undefined)
+	load(script_dir+'gamedefs.js');
+if(js.global.Region==undefined)
+	load(script_dir+'region.js');
+if(js.global.Faction==undefined)
+	load(script_dir+'faction.js');
+if(js.global.Building==undefined)
+	load(script_dir+'building.js');
+if(js.global.Ship==undefined)
+	load(script_dir+'ship.js');
+if(js.global.Unit==undefined)
+	load(script_dir+'unit.js');
+
+function makeblock (x1,y1)
+{
+	var i,n,x,y,r,r2;
+	var newblock=new Array(BLOCKSIZE);
+	
+	function transmute (from,to,n,count)
+	{
+		var i,x,y;
+
+		do
+		{
+			i = 0;
+
+			do
+			{
+
+				x = random(BLOCKSIZE);
+				y = random(BLOCKSIZE);
+				i += count;
+			}
+			while (i <= 10 &&
+					 !(newblock[x][y] == from &&
+						((x != 0 && newblock[x - 1][y] == to) ||
+						 (x != BLOCKSIZE - 1 && newblock[x + 1][y] == to) ||
+						 (y != 0 && newblock[x][y - 1] == to) ||
+						 (y != BLOCKSIZE - 1 && newblock[x][y + 1] == to))))
+						 ;
+
+			if (i > 10)
+				break;
+
+			newblock[x][y] = to;
+		}
+		while (--n);
+	}
+
+	function seed(to,n)
+	{
+		var x,y;
+
+		do
+		{
+			x = random(BLOCKSIZE);
+			y = random(BLOCKSIZE);
+		}
+		while (newblock[x][y] != T_PLAIN);
+
+		newblock[x][y] = to;
+		transmute (T_PLAIN,to,n,1);
+	}
+
+	function blockcoord (x)
+	{
+		return parseInt((x / (BLOCKSIZE + BLOCKBORDER*2)) * (BLOCKSIZE + BLOCKBORDER*2));
+	}
+
+
+	if (x1 < 0)
+		while (x1 != blockcoord (x1))
+			x1--;
+
+	if (y1 < 0)
+		while (y1 != blockcoord (y1))
+			y1--;
+
+	x1 = blockcoord (x1);
+	y1 = blockcoord (y1);
+
+	for(i=0; i<newblock.length; i++) {
+		newblock[i]=new Array(BLOCKSIZE);
+		for(n=0; n<newblock[i].length; n++) {
+			newblock[i][n]=T_OCEAN;
+		}
+	}
+
+	newblock[parseInt(BLOCKSIZE / 2)][parseInt(BLOCKSIZE / 2)] = T_PLAIN;
+	transmute (T_OCEAN,T_PLAIN,31,0);
+	seed (T_MOUNTAIN,1);
+	seed (T_MOUNTAIN,1);
+	seed (T_FOREST,1);
+	seed (T_FOREST,1);
+	seed (T_SWAMP,1);
+	seed (T_SWAMP,1);
+
+	for (x = 0; x != BLOCKSIZE + BLOCKBORDER*2; x++)
+		for (y = 0; y != BLOCKSIZE + BLOCKBORDER*2; y++)
+		{
+			r=new Region();
+
+			r.x = x1 + x;
+			r.y = y1 + y;
+
+			if (x >= BLOCKBORDER && x < BLOCKBORDER + BLOCKSIZE &&
+				 y >= BLOCKBORDER && y < BLOCKBORDER + BLOCKSIZE)
+			{
+				r.terrain = newblock[x - BLOCKBORDER][y - BLOCKBORDER];
+
+				if (r.terrain != T_OCEAN)
+				{
+					n = 0;
+					for (r2 in regions)
+						if (regions[r2].name[0]!='')
+							n++;
+
+					i = random(regionnames.length);
+					if (n < regionnames.length)
+						while(regionnameinuse(regionnames[i]))
+							i = random(regionnames.length);
+
+					r.name=regionnames[i];
+					r.peasants = terrains[r.terrain].maxfoodoutput / 50;
+				}
+			}
+
+			regions.push(r);
+		}
+
+	connectregions ();
+}
+
+function writegame ()
+{
+	var tmp,tmp2,i,f,r,u,b,s,prop,prop2,arr;
+	var gfile=new File(game_dir+"data/"+turn);
+
+	gfile.open("w");
+
+	arr=[];
+	/* Write factions */
+	for(f in factions) {
+		tmp=new Object();
+		for(prop in factions[f]) {
+			switch(prop) {
+				/* Keep unchanged */
+				case 'no':
+				case 'name':
+				case 'password':
+				case 'addr':
+				case 'lastorders':
+				case 'showdata':
+					tmp[prop]=factions[f][prop];
+					break;
+				/* Convert */
+				case 'allies':
+					/* Convert to just an array of numbers */
+					for(i in factions[f][prop])
+						tmp.allies[i]=factions[f][prop][i].no;
+					break;
+			}
+		}
+		arr.push(tmp);
+	}
+	gfile.writeln("factions = "+arr.toSource()+';');
+
+	arr=[];	
+	/* Write regions */
+	for(r in regions) {
+		tmp=new Object();
+		for(prop in regions[r]) {
+			switch(prop) {
+				case 'x':
+				case 'y':
+				case 'name':
+				case 'terrain':
+				case 'peasants':
+				case 'money':
+					tmp[prop]=regions[r][prop];
+					break;
+				case 'buildings':
+					tmp.buildings=new Array();
+					for(b in regions[r].buildings) {
+						tmp2=new Object();
+						for(prop2 in regions[r].buildings[b]) {
+							switch(prop2) {
+								case 'no':
+								case 'name':
+								case 'display':
+								case 'size':
+									tmp2[prop]=regions[r].buildings[b][prop];
+									break;
+							}
+						}
+						tmp.buildings[b]=tmp2;
+					}
+					break;
+				case 'ships':
+					tmp.ships=new Array();
+					for(s in regions[r].ships) {
+						tmp2=new Object();
+						for(prop2 in regions[r].ships[s]) {
+							switch(prop2) {
+								case 'no':
+								case 'name':
+								case 'display':
+								case 'type':
+								case 'left':
+									tmp2[prop]=regions[r].ships[s][prop];
+									break;
+							}
+						}
+						tmp.ships[s]=tmp2;
+					}
+					break;
+				case 'units':
+					tmp.units=new Array();
+					for(u in tmp.units) {
+						tmp2=new Object();
+						for(prop2 in tmp2) {
+							switch(prop2) {
+								case 'no':
+								case 'name':
+								case 'display':
+								case 'number':
+								case 'money':
+								case 'owner':
+								case 'behind':
+								case 'guard':
+								case 'lastorder':
+								case 'combatspell':
+									tmp2[prop]=regions[r].units[u][prop];
+									break;
+								case 'faction':
+									tmp2.faction=regions[r].units[u].faction.no;
+									break;
+								case 'building':
+									if(tmp2.building == null)
+										tmp2.building=0;
+									else
+										tmp2.building=regions[r].units[u].building.no;
+									break;
+								case 'ship':
+									if(tmp2.ship == null)
+										tmp2.ship=0;
+									else
+										tmp2.ship=regions[r].units[u].ship.no;
+									break;
+							}
+						}
+						tmp.units[u]=tmp2;
+					}
+					break;
+			}
+		}
+		arr.push(tmp);
+	}
+	gfile.writeln('regions = '+arr.toSource()+";");
+	gfile.close();
+}
+
+function readgame ()
+{
+	factions=[];
+	regions=[];
+	load(game_dir+"data/"+turn);
+
+	var f,a,i,r,u,b,s,n,prop,prop2;
+
+	/* Fixup factions */
+	for(f in factions) {
+		for(a in factions[f].allies)
+			factions[f].allies[a]=findfaction(factions[f].allies[a]);
+		n=new Faction();
+		for(prop in factions[f])
+			n[prop]=factions[f][prop];
+		factions[f]=n;
+	}
+
+	/* Read regions */
+	for(r in regions) {
+		n=new Region();
+		for(prop in regions[r])
+			n[prop]=regions[r][prop];
+		regions[r]=n;
+
+		for(b in regions[r].buildings) {
+			n=new Building();
+			for(prop in regions[r].buildings[b])
+				n[prop]=regions[r].buildings[b];
+			regions[r].buildings[b]=n;
+			regions[r].buildings[b].region=regions[r];
+		}
+		for(s in regions[r].ships) {
+			n=new Ship();
+			for(prop in regions[r].ships[s])
+				n[prop]=regions[r].ships[s];
+			regions[r].ships[s]=n;
+			regions[r].ships[s].region=regions[r];
+		}
+		for(u in regions[r].units) {
+			regions[r].units[u].region=regions[r];
+			regions[r].units[u].faction=findfaction(regions[r].units[u].faction);
+			regions[r].units[u].building=findbuilding(regions[r].units[u].building);
+			regions[r].units[u].ship=findship(regions[r].units[u].ship);
+
+			if(regions[r].units[u].faction.seendata == undefined)
+				regions[r].units[u].faction.seendata=new Array(MAXSPELLS);
+
+			/* Initialize faction seendata values */
+			for(i=0; i<MAXSPELLS; i++) {
+				if(regions[r].units[u].spells[i])
+					regions[r].units[u].faction.seendata[i]=true;
+				else
+					regions[r].units[u].faction.seendata[i]=false;
+			}
+			regions[r].units[u].faction.alive=true;
+		}
+	}
+
+	connectregions ();
+}
+
+function gamedate()
+{
+	var buf;
+	var monthnames=
+	[
+		"January",
+		"February",
+		"March",
+		"April",
+		"May",
+		"June",
+		"July",
+		"August",
+		"September",
+		"October",
+		"November",
+		"December",
+	];
+
+	if (turn == 0)
+		return("In the Beginning");
+	return(monthnames[(turn-1)%12]+", Year "+(parseInt((turn-1)/12)+1));
+}
+
+function gamemap()
+{
+	var x,y,minx,miny,maxx,maxy;
+	var r,buf;
+	var ret='';
+
+	minx = Number.MAX_VALUE;
+	maxx = Number.MIN_VALUE;
+	miny = Number.MAX_VALUE;
+	maxy = Number.MIN_VALUE;
+
+	for (r in regions)
+	{
+		minx = Math.min (minx,regions[r].x);
+		maxx = Math.max (maxx,regions[r].x);
+		miny = Math.min (miny,regions[r].y);
+		maxy = Math.max (maxy,regions[r].y);
+	}
+
+	for (y = miny; y <= maxy; y++)
+	{
+		buf=format("%*s",maxy-miny,'');
+
+		for (r in regions)
+			if (regions[r].y == y) {
+				buf=buf.substr(0, (regions[r].x-minx))+(".+MFS".charAt(regions[r].terrain))+buf.substr((regions[r].x-minx)+1);
+			}
+
+		for (x = 0; x<buf.length; x++)
+		{
+			ret += ' ';
+			ret += buf.charAt(x);
+		}
+
+		ret += '\n';
+	}
+	return(ret);
+}
+
+function gamesummary()
+{
+	var inhabitedregions;
+	var peasants;
+	var peasantmoney;
+	var nunits;
+	var playerpop;
+	var playermoney;
+	var f,r,u;
+	var sfile;
+	var ret='';
+
+	inhabitedregions = 0;
+	peasants = 0;
+	peasantmoney = 0;
+
+	nunits = 0;
+	playerpop = 0;
+	playermoney = 0;
+
+	for (r in regions)
+		if (regions[r].peasants || regions[r].units)
+		{
+			inhabitedregions++;
+			peasants += regions[r].peasants;
+			peasantmoney += regions[r].money;
+
+			for (u in regions[r].units)
+			{
+				nunits++;
+				playerpop += regions[r].units[u].number;
+				playermoney += regions[r].units[u].money;
+
+				regions[r].units[u].faction.nunits++;
+				regions[r].units[u].faction.number += regions[r].units[u].number;
+				regions[r].units[u].faction.money += regions[r].units[u].money;
+			}
+		}
+
+	ret += format("Summary file for Atlantis, %s\n\n",gamedate());
+
+	ret += format("Regions:            %d\n",regions.length);
+	ret += format("Inhabited Regions:  %d\n\n",inhabitedregions);
+
+	ret += format("Factions:           %d\n",factions.length);
+	ret += format("Units:              %d\n\n",nunits);
+
+	ret += format("Player Population:  %d\n",playerpop);
+	ret += format("Peasants:           %d\n",peasants);
+	ret += format("Total Population:   %d\n\n",playerpop + peasants);
+
+	ret += format("Player Wealth:      $%d\n",playermoney);
+	ret += format("Peasant Wealth:     $%d\n",peasantmoney);
+	ret += format("Total Wealth:       $%d\n\n",playermoney + peasantmoney);
+
+	ret += gamemap();
+
+	if (factions.length > 0)
+		ret += '\n';
+
+	for (f in factions)
+		ret += format("%s, units: %d, number: %d, $%d, address: %s\n",factions[f].id,
+					factions[f].nunits,factions[f].number,factions[f].money,factions[f].addr);
+
+	return(ret);
+}
+
+function getturn()
+{
+	var lastt=-1;
+	var d=directory(game_dir+'data/*');
+	var path;
+	var tmp;
+
+	if(d.length > 0) {
+		for(path in d) {
+			tmp=d[path].replace(/^.*[\\\/]/,'');
+			if(parseInt(tmp) > lastt)
+				lastt=parseInt(tmp);
+		}
+	}
+	return(lastt);
+}
+
+function addplayers()
+{
+	var r,f,u,i,region;
+	var newplayer;
+	var nf=directory(game_dir+'newplayers/*');
+	var minx,miny,maxx,maxy,x,y,done;
+
+	eachplayer:
+	for(i in nf) {
+		newplayer=new Object();
+		load(newplayer, nf[i]);
+		if(newplayer.addr==undefined)
+			continue;
+		for(f in factions)
+			if(factions[f].addr==newplayer.addr)
+				continue eachplayer;
+
+		if(newplayer.name != undefined)
+			if(!factionnameisunique(newplayer.name))
+				newplayer.name=undefined;
+
+		/* Find an empty plains region... */
+		region=undefined;
+		do {
+			for(r in regions) {
+				if(regions[r].terrain!=T_PLAIN)
+					continue;
+				if(regions[r].units.length != 0)
+					continue;
+				if(regions[r].buildings.length != 0)
+					continue;
+				if(regions[r].ships.length != 0)
+					continue;
+				region=regions[r];
+				break;
+			}
+			if(region==undefined) {
+				/* Make a new continent! */
+
+				/* Search for holes in the existing area first... */
+				minx = Number.MAX_VALUE;
+				maxx = Number.MIN_VALUE;
+				miny = Number.MAX_VALUE;
+				maxy = Number.MIN_VALUE;
+
+				for (r in regions)
+				{
+					minx = Math.min (minx,regions[r].x);
+					maxx = Math.max (maxx,regions[r].x);
+					miny = Math.min (miny,regions[r].y);
+					maxy = Math.max (maxy,regions[r].y);
+				}
+
+				for_loop:
+				for(x=minx; x<=maxy; x+=BLOCKSIZE+(BLOCKBORDER*2)) {
+					for(y=miny; y<=maxy; y+=BLOCKSIZE+(BLOCKBORDER*2)) {
+						if(findregion(x,y)==null) {
+							makeblock(x,y);
+							done=true;
+							break for_loop;
+						}
+					}
+				}
+
+				/* If there are no holes, find the longest direction */
+				/* And add to middle in direction opposite longest */
+				/* If there is no longest direction, add to middle right */
+				if(!done) {
+					if(maxx-minx < maxy-miny)
+						makeblock(maxx+1, parseInt((maxy+miny)/2));
+					else
+						makeblock(parseInt((maxx+minx)/2), maxy+1);
+				}
+			}
+		} while(region==undefined);
+		f=new Faction();
+		f.lastorders=turn;
+		f.alive=true;
+		do {
+			f.no++;
+			f.name="Faction "+f.no;
+		} while(findfaction(f.no)!=null);
+		if(newplayer.name != undefined)
+			f.name=newplayer.name;
+		if(newplayer.password != undefined)
+			f.password=newplayer.password;
+		if(newplayer.addr != undefined)
+			f.addr=newplayer.addr;
+		factions.push(f);
+		u=new Unit(region);
+		u.number=1;
+		u.money=STARTMONEY;
+		u.faction=f;
+		u.isnew=true;
+		u.region=region;
+		file_remove(nf[i]);
+	}
+}
+
+function initgame(dir)
+{
+	game_dir=backslash(script_dir+dir);
+	var d=directory(game_dir+'data/*');
+	var path;
+
+	if(d.length > 0) {
+		turn=getturn();
+		readgame();
+		addplayers();
+		writegame();
+	}
+	else {
+		log("No data files found, creating game...");
+		mkdir (game_dir+"data");
+		mkdir (game_dir+"newplayers");
+		makeblock (0,0);
+	}
+}
+
diff --git a/xtrn/atlantis/gamedefs.js b/xtrn/atlantis/gamedefs.js
new file mode 100644
index 0000000000000000000000000000000000000000..744a8ea7c39a03391fc8d6da649fc7108a7bd5d2
--- /dev/null
+++ b/xtrn/atlantis/gamedefs.js
@@ -0,0 +1,1095 @@
+const	NAMESIZE=			80;
+const	DISPLAYSIZE=		160;
+
+const	ORDERGAP=			4;
+const	BLOCKSIZE=			7;
+const	BLOCKBORDER=		1;
+const	MAINTENANCE=		10;
+const	STARTMONEY=			5000;
+const	RECRUITCOST=		50;
+const	RECRUITFRACTION=	4;
+const	ENTERTAININCOME=	20;
+const	ENTERTAINFRACTION=	20;
+const	TAXINCOME=			200;
+const	COMBATEXP=			10;
+const	PRODUCEEXP=			10;
+const	TEACHNUMBER=		10;
+const	STUDYCOST=			200;
+const	POPGROWTH=			5;
+const	PEASANTMOVE=		5;
+
+const	K_ACCEPT=		0;
+const	K_ADDRESS=		1;
+const	K_ADMIT=		2;
+const	K_ALLY=			3;
+const	K_ATTACK=		4;
+const	K_BEHIND=		5;
+const	K_BOARD=		6;
+const	K_BUILD=		7;
+const	K_BUILDING=		8;
+const	K_CAST=			9;
+const	K_CLIPPER=		10;
+const	K_COMBAT=		11;
+const	K_DEMOLISH=		12;
+const	K_DISPLAY=		13;
+const	K_EAST=			14;
+const	K_END=			15;
+const	K_ENTER=		16;
+const	K_ENTERTAIN=	17;
+const	K_FACTION=		18;
+const	K_FIND=			19;
+const	K_FORM=			20;
+const	K_GALLEON=		21;
+const	K_GIVE=			22;
+const	K_GUARD=		23;
+const	K_LEAVE=		24;
+const	K_LONGBOAT=		25;
+const	K_MOVE=			26;
+const	K_NAME=			27;
+const	K_NORTH=		28;
+const	K_PAY=			29;
+const	K_PRODUCE=		30;
+const	K_PROMOTE=		31;
+const	K_QUIT=			32;
+const	K_RECRUIT=		33;
+const	K_RESEARCH=		34;
+const	K_RESHOW=		35;
+const	K_SAIL=			36;
+const	K_SHIP=			37;
+const	K_SINK=			38;
+const	K_SOUTH=		39;
+const	K_STUDY=		40;
+const	K_TAX=			41;
+const	K_TEACH=		42;
+const	K_TRANSFER=		43;
+const	K_UNIT=			44;
+const	K_WEST=			45;
+const	K_WORK=			46;
+const	MAXKEYWORDS=	47;
+
+const keywords =
+[
+	"accept",
+	"address",
+	"admit",
+	"ally",
+	"attack",
+	"behind",
+	"board",
+	"build",
+	"building",
+	"cast",
+	"clipper",
+	"combat",
+	"demolish",
+	"display",
+	"east",
+	"end",
+	"enter",
+	"entertain",
+	"faction",
+	"find",
+	"form",
+	"galleon",
+	"give",
+	"guard",
+	"leave",
+	"longboat",
+	"move",
+	"name",
+	"north",
+	"pay",
+	"produce",
+	"promote",
+	"quit",
+	"recruit",
+	"research",
+	"reshow",
+	"sail",
+	"ship",
+	"sink",
+	"south",
+	"study",
+	"tax",
+	"teach",
+	"transfer",
+	"unit",
+	"west",
+	"work",
+];
+
+const regionnames =
+[
+	"Aberaeron",
+	"Aberdaron",
+	"Aberdovey",
+	"Abernethy",
+	"Abersoch",
+	"Abrantes",
+	"Adrano",
+	"AeBrey",
+	"Aghleam",
+	"Akbou",
+	"Aldan",
+	"Alfaro",
+	"Alghero",
+	"Almeria",
+	"Altnaharra",
+	"Ancroft",
+	"Anshun",
+	"Anstruther",
+	"Antor",
+	"Arbroath",
+	"Arcila",
+	"Ardfert",
+	"Ardvale",
+	"Arezzo",
+	"Ariano",
+	"Arlon",
+	"Avanos",
+	"Aveiro",
+	"Badalona",
+	"Baechahoela",
+	"Ballindine",
+	"Balta",
+	"Banlar",
+	"Barika",
+	"Bastak",
+	"Bayonne",
+	"Bejaia",
+	"Benlech",
+	"Beragh",
+	"Bergland",
+	"Berneray",
+	"Berriedale",
+	"Binhai",
+	"Birde",
+	"Bocholt",
+	"Bogmadie",
+	"Braga",
+	"Brechlin",
+	"Brodick",
+	"Burscough",
+	"Calpio",
+	"Canna",
+	"Capperwe",
+	"Caprera",
+	"Carahue",
+	"Carbost",
+	"Carnforth",
+	"Carrigaline",
+	"Caserta",
+	"Catrianchi",
+	"Clatter",
+	"Coilaco",
+	"Corinth",
+	"Corofin",
+	"Corran",
+	"Corwen",
+	"Crail",
+	"Cremona",
+	"Crieff",
+	"Cromarty",
+	"Cumbraes",
+	"Daingean",
+	"Darm",
+	"Decca",
+	"Derron",
+	"Derwent",
+	"Deveron",
+	"Dezhou",
+	"Doedbygd",
+	"Doramed",
+	"Dornoch",
+	"Drammes",
+	"Dremmer",
+	"Drense",
+	"Drimnin",
+	"Drumcollogher",
+	"Drummore",
+	"Dryck",
+	"Drymen",
+	"Dunbeath",
+	"Duncansby",
+	"Dunfanaghy",
+	"Dunkeld",
+	"Dunmanus",
+	"Dunster",
+	"Durness",
+	"Duucshire",
+	"Elgomaar",
+	"Ellesmere",
+	"Ellon",
+	"Enfar",
+	"Erisort",
+	"Eskerfan",
+	"Ettrick",
+	"Fanders",
+	"Farafra",
+	"Ferbane",
+	"Fetlar",
+	"Flock",
+	"Florina",
+	"Formby",
+	"Frainberg",
+	"Galloway",
+	"Ganzhou",
+	"Geal Charn",
+	"Gerr",
+	"Gifford",
+	"Girvan",
+	"Glenagallagh",
+	"Glenanane",
+	"Glin",
+	"Glomera",
+	"Glormandia",
+	"Gluggby",
+	"Gnackstein",
+	"Gnoelhaala",
+	"Golconda",
+	"Gourock",
+	"Graevbygd",
+	"Grandola",
+	"Gresberg",
+	"Gresir",
+	"Greverre",
+	"Griminish",
+	"Grisbygd",
+	"Groddland",
+	"Grue",
+	"Gurkacre",
+	"Haikou",
+	"Halkirk",
+	"Handan",
+	"Hasmerr",
+	"Helmsdale",
+	"Helmsley",
+	"Helsicke",
+	"Helvete",
+	"Hoersalsveg",
+	"Hullevala",
+	"Ickellund",
+	"Inber",
+	"Inverie",
+	"Jaca",
+	"Jahrom",
+	"Jeormel",
+	"Jervbygd",
+	"Jining",
+	"Jotel",
+	"Kaddervar",
+	"Karand",
+	"Karothea",
+	"Kashmar",
+	"Keswick",
+	"Kielder",
+	"Killorglin",
+	"Kinbrace",
+	"Kintore",
+	"Kirriemuir",
+	"Klen",
+	"Knesekt",
+	"Kobbe",
+	"Komarken",
+	"Kovel",
+	"Krod",
+	"Kursk",
+	"Lagos",
+	"Lamlash",
+	"Langholm",
+	"Larache",
+	"Larkanth",
+	"Larmet",
+	"Lautaro",
+	"Leighlin",
+	"Lervir",
+	"Leven",
+	"Licata",
+	"Limavady",
+	"Lingen",
+	"Lintan",
+	"Liscannor",
+	"Locarno",
+	"Lochalsh",
+	"Lochcarron",
+	"Lochinver",
+	"Lochmaben",
+	"Lom",
+	"Lorthalm",
+	"Louer",
+	"Lurkabo",
+	"Luthiir",
+	"Lybster",
+	"Lynton",
+	"Mallaig",
+	"Mataro",
+	"Melfi",
+	"Melvaig",
+	"Menter",
+	"Methven",
+	"Moffat",
+	"Monamolin",
+	"Monzon",
+	"Morella",
+	"Morgel",
+	"Mortenford",
+	"Mullaghcarn",
+	"Mulle",
+	"Murom",
+	"Nairn",
+	"Navenby",
+	"Nephin Beg",
+	"Niskby",
+	"Nolle",
+	"Nork",
+	"Olenek",
+	"Oloron",
+	"Oranmore",
+	"Ormgryte",
+	"Orrebygd",
+	"Palmi",
+	"Panyu",
+	"Partry",
+	"Pauer",
+	"Penhalolen",
+	"Perkel",
+	"Perski",
+	"Planken",
+	"Plattland",
+	"Pleagne",
+	"Pogelveir",
+	"Porthcawl",
+	"Portimao",
+	"Potenza",
+	"Praestbygd",
+	"Preetsome",
+	"Presu",
+	"Prettstern",
+	"Rantlu",
+	"Rappbygd",
+	"Rath Luire",
+	"Rethel",
+	"Riggenthorpe",
+	"Rochfort",
+	"Roddendor",
+	"Roin",
+	"Roptille",
+	"Roter",
+	"Rueve",
+	"Sagunto",
+	"Saklebille",
+	"Salen",
+	"Sandwick",
+	"Sarab",
+	"Sarkanvale",
+	"Scandamia",
+	"Scarinish",
+	"Scourie",
+	"Serov",
+	"Shanyin",
+	"Siegen",
+	"Sinan",
+	"Sines",
+	"Skim",
+	"Skokholm",
+	"Skomer",
+	"Skottskog",
+	"Sledmere",
+	"Sorisdale",
+	"Spakker",
+	"Stackforth",
+	"Staklesse",
+	"Stinchar",
+	"Stoer",
+	"Strichen",
+	"Stroma",
+	"Stugslett",
+	"Suide",
+	"Tabuk",
+	"Tarraspan",
+	"Tetuan",
+	"Thurso",
+	"Tiemcen",
+	"Tiksi",
+	"Tolsta",
+	"Toppola",
+	"Torridon",
+	"Trapani",
+	"Tromeforth",
+	"Tudela",
+	"Turia",
+	"Uxelberg",
+	"Vaila",
+	"Valga",
+	"Verguin",
+	"Vernlund",
+	"Victoria",
+	"Waimer",
+	"Wett",
+	"Xontormia",
+	"Yakleks",
+	"Yuci",
+	"Zaalsehuur",
+	"Zamora",
+	"Zapulla",
+];
+
+const	T_OCEAN=	0;
+const	T_PLAIN=	1;
+const	T_MOUNTAIN=	2;
+const	T_FOREST=	3;
+const	T_SWAMP=	4;
+
+const terrains =
+[
+	{
+		name:"ocean",
+		foodproductivity:0,
+		maxfoodoutput:0,
+		productivity:{
+			iron:0,
+			wood:0,
+			stone:0,
+			horse:0,
+		},
+		maxoutput:{
+			iron:0,
+			wood:0,
+			stone:0,
+			horse:0,
+		},
+	},
+	{
+		name:"plain",
+		foodproductivity:15,
+		maxfoodoutput:100000,
+		productivity:{
+			iron:0,
+			wood:0,
+			stone:0,
+			horse:1,
+		},
+		maxoutput:{
+			iron:0,
+			wood:0,
+			stone:0,
+			horse:200,
+		},
+	},
+	{
+		name:"mountain",
+		foodproductivity:12,
+		maxfoodoutput:20000,
+		productivity:{
+			iron:1,
+			wood:0,
+			stone:1,
+			horse:0,
+		},
+		maxoutput:{
+			iron:200,
+			wood:0,
+			stone:200,
+			horse:0,
+		},
+	},
+	{
+		name:"forest",
+		foodproductivity:12,
+		maxfoodoutput:20000,
+		productivity:{
+			iron:0,
+			wood:1,
+			stone:0,
+			horse:0,
+		},
+		maxoutput:{
+			iron:0,
+			wood:200,
+			stone:0,
+			horse:0,
+		},
+	},
+	{
+		name:"swamp",
+		foodproductivity:12,
+		maxfoodoutput:10000,
+		productivity:{
+			iron:0,
+			wood:1,
+			stone:0,
+			horse:0,
+		},
+		maxoutput:{
+			iron:0,
+			wood:100,
+			stone:0,
+			horse:0,
+		},
+	}
+];
+
+const	SH_LONGBOAT=	0;
+const	SH_CLIPPER=		1;
+const	SH_GALLEON=		2;
+
+const shiptypes=
+[
+	{
+		name:'longboat',
+		capacity:200,
+		cost:100,
+	},
+	{
+		name:'clipper',
+		capacity:800,
+		cost:200,
+	},
+	{
+		name:'galleon',
+		capacity:1800,
+		cost:300,
+	},
+];
+
+const	SK_MINING=			0;
+const	SK_LUMBERJACK=		1;
+const	SK_QUARRYING=		2;
+const	SK_HORSE_TRAINING=	3;
+const	SK_WEAPONSMITH=		4;
+const	SK_ARMORER=			5;
+const	SK_BUILDING=		6;
+const	SK_SHIPBUILDING=	7;
+const	SK_ENTERTAINMENT=	8;
+const	SK_STEALTH=			9;
+const	SK_OBSERVATION=		10;
+const	SK_TACTICS=			11;
+const	SK_RIDING=			12;
+const	SK_SWORD=			13;
+const	SK_CROSSBOW=		14;
+const	SK_LONGBOW=			15;
+const	SK_MAGIC=			16;
+const	MAXSKILLS=			17;
+
+const skillnames =
+[
+	"mining",
+	"lumberjack",
+	"quarrying",
+	"horse training",
+	"weaponsmith",
+	"armorer",
+	"building",
+	"shipbuilding",
+	"entertainment",
+	"stealth",
+	"observation",
+	"tactics",
+	"riding",
+	"sword",
+	"crossbow",
+	"longbow",
+	"magic",
+];
+
+const	I_IRON=						0;
+const	I_WOOD=						1;
+const	I_STONE=					2;
+const	I_HORSE=					3;
+const	I_SWORD=					4;
+const	I_CROSSBOW=					5;
+const	I_LONGBOW=					6;
+const	I_CHAIN_MAIL=				7;
+const	I_PLATE_ARMOR=				8;
+const	I_AMULET_OF_DARKNESS=		9;
+const	I_AMULET_OF_DEATH=			10;
+const	I_AMULET_OF_HEALING=		11;
+const	I_AMULET_OF_TRUE_SEEING=	12;
+const	I_CLOAK_OF_INVULNERABILITY=	13;
+const	I_RING_OF_INVISIBILITY=		14;
+const	I_RING_OF_POWER=			15;
+const	I_RUNESWORD=				16;
+const	I_SHIELDSTONE=				17;
+const	I_STAFF_OF_FIRE=			18;
+const	I_STAFF_OF_LIGHTNING=		19;
+const	I_WAND_OF_TELEPORTATION=	20;
+const	MAXITEMS=					21;
+
+const items = [
+	{
+		singular:"iron",
+		plural:"iron",
+		skill:SK_MINING,
+	},
+	{
+		singular:"wood",
+		plural:"wood",
+		skill:SK_LUMBERJACK,
+	},
+	{
+		singular:"stone",
+		plural:"stone",
+		skill:SK_QUARRYING,
+	},
+	{
+		singular:"horse",
+		plural:"horses",
+		skill:SK_HORSE_TRAINING,
+	},
+	{
+		singular:"sword",
+		plural:"swords",
+		skill:SK_WEAPONSMITH,
+		rawmaterial:I_IRON,
+	},
+	{
+		singular:"crossbow",
+		plural:"crossbows",
+		skill:SK_WEAPONSMITH,
+		rawmaterial:I_WOOD,
+	},
+	{
+		singular:"longbow",
+		plural:"longbows",
+		skill:SK_WEAPONSMITH,
+		rawmaterial:I_WOOD,
+	},
+	{
+		singular:"chain mail",
+		plural:"chain mail",
+		skill:SK_ARMORER,
+		rawmaterial:I_IRON,
+	},
+	{
+		singular:"plate armor",
+		plural:"plate armor",
+		skill:SK_ARMORER,
+		rawmaterial:I_IRON,
+	},
+	{
+		singular:"Amulet of Darkness",
+		plural:"Amulets of Darkness",
+	},
+	{
+		singular:"Amulet of Death",
+		plural:"Amulets of Death",
+	},
+	{
+		singular:"Amulet of Healing",
+		plural:"Amulets of Healing",
+	},
+	{
+		singular:"Amulet of True Seeing",
+		plural:"Amulets of True Seeing",
+	},
+	{
+		singular:"Cloak of Invulnerability",
+		plural:"Cloaks of Invulnerability",
+	},
+	{
+		singular:"Ring of Invisibility",
+		plural:"Rings of Invisibility",
+	},
+	{
+		singular:"Ring of Power",
+		plural:"Rings of Power",
+	},
+	{
+		singular:"Runesword",
+		plural:"Runeswords",
+	},
+	{
+		singular:"Shieldstone",
+		plural:"Shieldstones",
+	},
+	{
+		singular:"Staff of Fire",
+		plural:"Staffs of Fire",
+	},
+	{
+		singular:"Staff of Lightning",
+		plural:"Staffs of Lightning",
+	},
+	{
+		singular:"Wand of Teleportation",
+		plural:"Wands of Teleportation",
+	},
+];
+	
+const	SP_BLACK_WIND=				0;
+const	SP_CAUSE_FEAR=				1;
+const	SP_CONTAMINATE_WATER=			2;
+const	SP_DAZZLING_LIGHT=			3;
+const	SP_FIREBALL=				4;
+const	SP_HAND_OF_DEATH=			5;
+const	SP_HEAL=				6;
+const	SP_INSPIRE_COURAGE=			7;
+const	SP_LIGHTNING_BOLT=			8;
+const	SP_MAKE_AMULET_OF_DARKNESS=		9;
+const	SP_MAKE_AMULET_OF_DEATH=		10;
+const	SP_MAKE_AMULET_OF_HEALING=		11;
+const	SP_MAKE_AMULET_OF_TRUE_SEEING=		12;
+const	SP_MAKE_CLOAK_OF_INVULNERABILITY=	13;
+const	SP_MAKE_RING_OF_INVISIBILITY=		14;
+const	SP_MAKE_RING_OF_POWER=			15;
+const	SP_MAKE_RUNESWORD=			16;
+const	SP_MAKE_SHIELDSTONE=			17;
+const	SP_MAKE_STAFF_OF_FIRE=			18;
+const	SP_MAKE_STAFF_OF_LIGHTNING=		19;
+const	SP_MAKE_WAND_OF_TELEPORTATION=		20;
+const	SP_SHIELD=				21;
+const	SP_SUNFIRE=				22;
+const	SP_TELEPORT=				23;
+const	MAXSPELLS=				24;
+
+const spelldata =
+[
+	{
+		name:"Black Wind",
+		level:4,
+		iscombatspell:true,
+		data:"This spell creates a black whirlwind of energy which destroys all life, "
+		+"leaving frozen corpses with faces twisted into expressions of horror. Cast "
+		+"in battle, it kills from 2 to 1250 enemies.",
+	},
+	{
+		name:"Cause Fear",
+		level:2,
+		iscombatspell:true,
+		data:"This spell creates an aura of fear which causes enemy troops in battle to "
+		+"panic. Each time it is cast, it demoralizes between 2 and 100 troops for "
+		+"the duration of the battle. Demoralized troops are at a -1 to their "
+		+"effective skill.",
+	},
+	{
+		name:"Contaminate Water",
+		level:1,
+		iscombatspell:false,
+		data:"This ritual spell causes pestilence to contaminate the water supply of the "
+		+"region in which it is cast. It causes from 2 to 50 peasants to die from "
+		+"drinking the contaminated water. Any units which end the month in the "
+		+"affected region will know about the deaths, however only units which have "
+		+"Observation skill higher than the caster's Stealth skill will know who "
+		+"was responsible. The spell costs $50 to cast.",
+	},
+	{
+		name:"Dazzling Light",
+		level:1,
+		iscombatspell:true,
+		data:"This spell, cast in battle, creates a flash of light which dazzles from 2 "
+		+"to 50 enemy troops. Dazzled troops are at a -1 to their effective skill "
+		+"for their next attack.",
+	},
+	{
+		name:"Fireball",
+		level:2,
+		iscombatspell:true,
+		data:"This spell enables the caster to hurl balls of fire. Each time it is cast "
+		+"in battle, it will incinerate between 2 and 50 enemy troops.",
+	},
+	{
+		name:"Hand of Death",
+		level:3,
+		iscombatspell:true,
+		data:"This spell disrupts the metabolism of living organisms, sending an "
+		+"invisible wave of death across a battlefield. Each time it is cast, it "
+		+"will kill between 2 and 250 enemy troops.",
+	},
+	{
+		name:"Heal",
+		level:2,
+		iscombatspell:false,
+		data:"This spell enables the caster to attempt to heal the injured after a "
+		+"battle. It is used automatically, and does not require use of either the "
+		+"COMBAT or CAST command. If one's side wins a battle, a number of  "
+		+"casualties on one's side, between 2 and 50, will be healed. (If this "
+		+"results in all the casualties on the winning side being healed, the winner "
+		+"is still eligible for combat experience.)",
+	},
+	{
+		name:"Inspire Courage",
+		level:2,
+		iscombatspell:true,
+		data:"This spell boosts the morale of one's troops in battle. Each time it is "
+		+"cast, it cancels the effect of the Cause Fear spell on a number of one's "
+		+"own troops ranging from 2 to 100.",
+	},
+	{
+		name:"Lightning Bolt",
+		level:1,
+		iscombatspell:true,
+		data:"This spell enables the caster to throw bolts of lightning to strike down "
+		+"enemies in battle. It kills from 2 to 10 enemies.",
+	},
+	{
+		name:"Make Amulet of Darkness",
+		level:5,
+		iscombatspell:false,
+		spellitem:I_AMULET_OF_DARKNESS,
+		data:"This spell allows one to create an Amulet of Darkness. This amulet allows "
+		+"its possessor to cast the Black Wind spell in combat, without having to "
+		+"know the spell; the only requirement is that the user must have the Magic "
+		+"skill at 1 or higher. The Black Wind spell creates a black whirlwind of "
+		+"energy which destroys all life. Cast in battle, it kills from 2 to 1250 "
+		+"people. The amulet costs $1000 to make.",
+	},
+	{
+		name:"Make Amulet of Death",
+		level:4,
+		iscombatspell:false,
+		spellitem:I_AMULET_OF_DEATH,
+		data:"This spell allows one to create an Amulet of Death. This amulet allows its "
+		+"possessor to cast the Hand of Death spell in combat, without having to "
+		+"know the spell; the only requirement is that the user must have the Magic "
+		+"skill at 1 or higher. The Hand of Death spell disrupts the metabolism of "
+		+"living organisms, sending an invisible wave of death across a battlefield. "
+		+"Each time it is cast, it will kill between 2 and 250 enemy troops. The "
+		+"amulet costs $800 to make.",
+	},
+	{
+		name:"Make Amulet of Healing",
+		level:3,
+		iscombatspell:false,
+		spellitem:I_AMULET_OF_HEALING,
+		data:"This spell allows one to create an Amulet of Healing. This amulet allows "
+		+"its possessor to attempt to heal the injured after a battle. It is used "
+		+"automatically, and does not require the use of either the COMBAT or CAST "
+		+"command; the only requirement is that the user must have the Magic skill "
+		+"at 1 or higher. If the user's side wins a battle, a number of casualties "
+		+"on that side, between 2 and 50, will be healed. (If this results in all "
+		+"the casualties on the winning side being healed, the winner is still "
+		+"eligible for combat experience.) The amulet costs $600 to make.",
+	},
+	{
+		name:"Make Amulet of True Seeing",
+		level:3,
+		iscombatspell:false,
+		spellitem:I_AMULET_OF_TRUE_SEEING,
+		data:"This spell allows one to create an Amulet of True Seeing. This allows its "
+		+"possessor to see units which are hidden by Rings of Invisibility. (It has "
+		+"no effect against units which are hidden by the Stealth skill.) The amulet "
+		+"costs $600 to make.",
+	},
+	{
+		name:"Make Cloak of Invulnerability",
+		level:3,
+		iscombatspell:false,
+		spellitem:I_CLOAK_OF_INVULNERABILITY,
+		data:"This spell allows one to create a Cloak of Invulnerability. This cloak "
+		+"protects its wearer from injury in battle; any attack with a normal weapon "
+		+"which hits the wearer has a 99.99% chance of being deflected. This benefit "
+		+"is gained instead of, rather than as well as, the protection of any armor "
+		+"worn; and the cloak confers no protection against magical attacks. The "
+		+"cloak costs $600 to make.",
+	},
+	{
+		name:"Make Ring of Invisibility",
+		level:3,
+		iscombatspell:false,
+		spellitem:I_RING_OF_INVISIBILITY,
+		data:"This spell allows one to create a Ring of Invisibility. This ring renders "
+		+"its wearer invisible to all units not in the same faction, regardless of "
+		+"Observation skill. For a unit of many people to remain invisible, it must "
+		+"possess a Ring of Invisibility for each person. The ring costs $600 to "
+		+"make.",
+	},
+	{
+		name:"Make Ring of Power",
+		level:4,
+		iscombatspell:false,
+		spellitem:I_RING_OF_POWER,
+		data:"This spell allows one to create a Ring of Power. This ring doubles the "
+		+"effectiveness of any spell the wearer casts in combat, or any magic item "
+		+"the wearer uses in combat. The ring costs $800 to make.",
+	},
+	{
+		name:"Make Runesword",
+		level:3,
+		iscombatspell:false,
+		spellitem:I_RUNESWORD,
+		data:"This spell allows one to create a Runesword. This is a black sword with "
+		+"magical runes etched along the blade. To use it, one must have both the "
+		+"Sword and Magic skills at 1 or higher. It confers a bonus of 2 to the "
+		+"user's Sword skill in battle, and also projects an aura of power that has "
+		+"a 50% chance of cancelling any Fear spells cast by an enemy magician. The "
+		+"sword costs $600 to make.",
+	},
+	{
+		name:"Make Shieldstone",
+		level:4,
+		iscombatspell:false,
+		spellitem:I_SHIELDSTONE,
+		data:"This spell allows one to create a Shieldstone. This is a small black "
+		+"stone, engraved with magical runes, that creates an invisible shield of "
+		+"energy that deflects hostile magic in battle. The stone is used "
+		+"automatically, and does not require the use of either the COMBAT or CAST "
+		+"commands; the only requirement is that the user must have the Magic skill "
+		+"at 1 or higher. Each round of combat, it adds one layer to the shielding "
+		+"around one's own side. When a hostile magician casts a spell, provided "
+		+"there is at least one layer of shielding present, there is a 50% chance of "
+		+"the spell being deflected. If the spell is deflected, nothing happens. If "
+		+"it is not, then it has full effect, and one layer of shielding is removed. "
+		+"The stone costs $800 to make.",
+	},
+	{
+		name:"Make Staff of Fire",
+		level:3,
+		iscombatspell:false,
+		spellitem:I_STAFF_OF_FIRE,
+		data:"This spell allows one to create a Staff of Fire. This staff allows its "
+		+"possessor to cast the Fireball spell in combat, without having to know the "
+		+"spell; the only requirement is that the user must have the Magic skill at "
+		+"1 or higher. The Fireball spell enables the caster to hurl balls of fire. "
+		+"Each time it is cast in battle, it will incinerate between 2 and 50 enemy "
+		+"troops. The staff costs $600 to make.",
+	},
+	{
+		name:"Make Staff of Lightning",
+		level:2,
+		iscombatspell:false,
+		spellitem:I_STAFF_OF_LIGHTNING,
+		data:"This spell allows one to create a Staff of Lightning. This staff allows "
+		+"its possessor to cast the Lightning Bolt spell in combat, without having "
+		+"to know the spell; the only requirement is that the user must have the "
+		+"Magic skill at 1 or higher. The Lightning Bolt spell enables the caster to "
+		+"throw bolts of lightning to strike down enemies. It kills from 2 to 10 "
+		+"enemies. The staff costs $400 to make.",
+	},
+	{
+		name:"Make Wand of Teleportation",
+		level:4,
+		iscombatspell:false,
+		spellitem:I_WAND_OF_TELEPORTATION,
+		data:"This spell allows one to create a Wand of Teleportation. This wand allows "
+		+"its possessor to cast the Teleport spell, without having to know the "
+		+"spell; the only requirement is that the user must have the Magic skill at "
+		+"1 or higher. The Teleport spell allows the caster to move himself and "
+		+"others across vast distances without traversing the intervening space. The "
+		+"command to use it is CAST TELEPORT target-unit unit-no ... The target unit "
+		+"is a unit in the region to which the teleport is to occur. If the target "
+		+"unit is not in your faction, it must be in a faction which has issued an "
+		+"ADMIT command for you that month. After the target unit comes a list of "
+		+"one or more units to be teleported into the target unit's region (this may "
+		+"optionally include the caster). Any units to be teleported, not in your "
+		+"faction, must be in a faction which has issued an ACCEPT command for you "
+		+"that month. The total weight of all units to be teleported (including "
+		+"people, equipment and horses) must not exceed 10000. If the target unit is "
+		+"in a building or on a ship, the teleported units will emerge there, "
+		+"regardless of who owns the building or ship. The caster spends the month "
+		+"preparing the spell and the teleport occurs at the end of the month, so "
+		+"any other units to be transported can spend the month doing something "
+		+"else. The wand costs $800 to make, and $50 to use.",
+	},
+	{
+		name:"Shield",
+		level:3,
+		iscombatspell:true,
+		data:"This spell creates an invisible shield of energy that deflects hostile "
+		+"magic. Each round that it is cast in battle, it adds one layer to the "
+		+"shielding around one's own side. When a hostile magician casts a spell, "
+		+"provided there is at least one layer of shielding present, there is a 50% "
+		+"chance of the spell being deflected. If the spell is deflected, nothing "
+		+"happens. If it is not, then it has full effect, and one layer of shielding "
+		+"is removed.",
+	},
+	{
+		name:"Sunfire",
+		level:5,
+		iscombatspell:true,
+		data:"This spell allows the caster to incinerate whole armies with fire brighter "
+		+"than the sun. Each round it is cast, it kills from 2 to 6250 enemies.",
+	},
+	{
+		name:"Teleport",
+		level:3,
+		iscombatspell:false,
+		data:"This spell allows the caster to move himself and others across vast "
+		+"distances without traversing the intervening space. The command to use it "
+		+"is CAST TELEPORT target-unit unit-no ... The target unit is a unit in the "
+		+"region to which the teleport is to occur. If the target unit is not in "
+		+"your faction, it must be in a faction which has issued an ADMIT command "
+		+"for you that month. After the target unit comes a list of one or more "
+		+"units to be teleported into the target unit's region (this may optionally "
+		+"include the caster). Any units to be teleported, not in your faction, must "
+		+"be in a faction which has issued an ACCEPT command for you that month. The "
+		+"total weight of all units to be teleported (including people, equipment "
+		+"and horses) must not exceed 10000. If the target unit is in a building or "
+		+"on a ship, the teleported units will emerge there, regardless of who owns "
+		+"the building or ship. The caster spends the month preparing the spell and "
+		+"the teleport occurs at the end of the month, so any other units to be "
+		+"transported can spend the month doing something else. The spell costs $50 "
+		+"to cast.",
+	},
+];
+
+function findkeyword (word)
+{
+	var w;
+
+	switch(word) {
+		case 'describe':
+			return(K_DISPLAY);
+		case 'n':
+			return(K_NORTH);
+		case 's':
+			return(K_SOUTH);
+		case 'e':
+			return(K_EAST);
+		case 'w':
+			return(K_WEST);
+	}
+
+	for(w in keywords)
+		if(keywords[w]==word)
+			return(w);
+
+	return(-1);
+}
+
+function findskill(s)
+{
+	var w;
+
+	switch(s) {
+		case "horse":
+			return(SK_HORSE_TRAINING);
+		case "entertain":
+			return(SK_ENTERTAINMENT);
+	}
+
+	for(w in skillnames)
+		if(skillnames[w]==s)
+			return(w);
+
+	return(-1);
+}
+
+function finditem (s)
+{
+	var w;
+
+	switch(s) {
+		case "chain":
+			return(I_CHAIN_MAIL);
+		case "plate":
+			return(I_PLATE_ARMOR);
+	}
+
+	for(w in items)
+		if(items[w].singular==s || items[w].plural==s)
+			return(w);
+
+	return(-1);
+}
+
+function findspell (s)
+{
+	var w;
+
+	for(w in spelldata)
+		if(spelldata[w].name==s)
+			return(w);
+
+	return(-1);
+}
+
diff --git a/xtrn/atlantis/order.js b/xtrn/atlantis/order.js
new file mode 100644
index 0000000000000000000000000000000000000000..34a340d0603e529a577c7bf01127d58bb2075e35
--- /dev/null
+++ b/xtrn/atlantis/order.js
@@ -0,0 +1,40 @@
+if(!js.global.scramble==undefined)
+	load(script_dir+'utilfuncs.js');
+
+function Order()
+{
+	this.unit=null;
+	this.qty=0;
+	this.command=0;
+	this.args=new Array();
+	this.suborders=new Array();
+}
+
+function expandorders(r,orders)
+{
+	var i,o,prop,neworder,neworders,u;
+
+	for (u in r.units)
+		r.units[u].n = -1;
+
+	neworders=new Array();
+
+	for (o in orders) {
+		for(i=0; i<orders[o].qty; i++) {
+			neworder=new Object();
+
+			neworder.unit.n=0;
+			for(prop in orders[o]) {
+				if(prop != 'qty')
+					neworder[prop]=orders[o][prop];
+			}
+			neworders.push(neworder);
+		}
+	}
+
+	scramble(neworders);
+
+	orders.splice(0,orders.length);
+	while(neworders.length)
+		orders.push(neworders.shift());
+}
diff --git a/xtrn/atlantis/region.js b/xtrn/atlantis/region.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8ca21deed2750e4ea01d43f60bba4d3ffad26a5
--- /dev/null
+++ b/xtrn/atlantis/region.js
@@ -0,0 +1,132 @@
+var regions=new Array();
+
+if(js.global.terrains==undefined)
+	load(script_dir+'gamedefs.js');
+if(js.global.makeblock==undefined)
+	load(script_dir+'gamedata.js');
+if(js.global.Building==undefined)
+	load(script_dir+'building.js');
+if(js.global.Ship==undefined)
+	load(script_dir+'ship.js');
+if(js.global.Unit==undefined)
+	load(script_dir+'unit.js');
+if(js.global.Faction==undefined)
+	load(script_dir+'faction.js');
+
+function Region()
+{
+	this.x=0;
+	this.y=0;
+	this.name='';
+	this.connect=new Array(4);
+	this.terrain=0;
+	this.peasants=0;
+	this.money=0;
+	this.buildings=new Array();
+	this.ships=new Array();
+	this.units=new Array();
+	this.immigrants=0;
+	this.id getter=function() { return(this.name+' ('+this.x+','+this.y+')'); };
+
+	this.iscoast getter=function()
+	{
+		var i;
+
+		if(this.terrain == T_OCEAN)
+			return(false);
+
+		for(i in this.connect)
+			if(this.connect[i]!=null && this.connect[i].terrain==T_OCEAN)
+				return(true);
+		return(false);
+	};
+
+	this.movewhere=function(keyword) {
+		var r2;
+
+		switch (keyword)
+		{
+			case K_NORTH:
+				if (this.connect[0]==null)
+					makeblock (this.x,this.y - 1);
+				return(this.connect[0]);
+
+			case K_SOUTH:
+				if (this.connect[1]==null)
+					makeblock (this.x,this.y + 1);
+				return(this.connect[1]);
+
+			case K_EAST:
+				if (this.connect[2]==null)
+					makeblock (this.x + 1,this.y);
+				return(this.connect[2]);
+
+			case K_WEST:
+				if (this.connect[3]==null)
+					makeblock (this.x - 1,this.y);
+				return(this.connect[3]);
+		}
+
+		return null;
+	};
+	this.reportevent=function(event) {
+		var f,u;
+		
+		for(f in factions) {
+			for(u in this.units) {
+				if(this.units[u].faction.no==factions[f].no) {
+					factions[f].events.push(event);
+					break;
+				}
+			}
+		}
+	};
+}
+
+function findregion (x,y)
+{
+	var r;
+
+	for(r in regions)
+		if(regions[r].x==x && regions[r].y==y)
+			return(regions[r]);
+
+	return(null);
+}
+
+function connecttothis(r, x, y, from, to)
+{
+	var r2=findregion(x,y);
+
+	if(r2 != null) {
+		r.connect[from]=r2;
+		r2.connect[to]=r;
+	}
+}
+
+function connectregions()
+{
+	var r;
+
+	for (r in regions)
+	{
+		if (regions[r].connect[0]==undefined)
+			connecttothis (regions[r],regions[r].x,regions[r].y - 1,0,1);
+		if (regions[r].connect[1]==undefined)
+			connecttothis (regions[r],regions[r].x,regions[r].y + 1,1,0);
+		if (regions[r].connect[2]==undefined)
+			connecttothis (regions[r],regions[r].x + 1,regions[r].y,2,3);
+		if (regions[r].connect[3]==undefined)
+			connecttothis (regions[r],regions[r].x - 1,regions[r].y,3,2);
+	}
+}
+
+function regionnameinuse (s)
+{
+	var r;
+
+	for(r in regions)
+		if(regions[r].name==s)
+			return(true);
+	return(false);
+}
diff --git a/xtrn/atlantis/rules.v1.txt b/xtrn/atlantis/rules.v1.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d0ed0f4bce62802de767af615ec6622af8032ddd
--- /dev/null
+++ b/xtrn/atlantis/rules.v1.txt
@@ -0,0 +1,976 @@
+Return-Path: RWALLACE@vax1.tcd.ie
+Received: from cgl.ucsf.edu by celeste.mmwb.ucsf.EDU (920330.SGI/GSC4.22)
+	id AA09530; Mon, 14 Jun 93 01:07:23 -0700
+Received: from vax1.tcd.ie by cgl.ucsf.EDU (5.65/GSC4.22)
+	id AA17383 for troyer@celeste.mmwb.ucsf.EDU; Mon, 14 Jun 93 01:06:40 -0700
+Received: from vax1.tcd.ie by vax1.tcd.ie (PMDF #12050) id
+ <01GZD2WD5H3S004KXE@vax1.tcd.ie>; Mon, 14 Jun 1993 09:06 GMT
+Date: Mon, 14 Jun 1993 09:06 GMT
+From: RWALLACE@vax1.tcd.ie
+Subject: Atlantis rules update
+To: troyer@cgl.ucsf.edu
+Message-Id: <01GZD2WD5H3S004KXE@vax1.tcd.ie>
+X-Envelope-To: troyer@cgl.ucsf.edu
+X-Vms-To: IN%"troyer@cgl.ucsf.edu"
+Status: O
+
+			Rules for Atlantis v1.0
+			8 June 1993
+			Copyright 1993 by Russell Wallace
+
+
+Introduction
+------------
+
+Atlantis is an open-ended computer moderated fantasy game for any number of
+players.  Players may attempt to carve out huge empires, become master
+magicians, intrepid explorers, rich traders or any other career that comes to
+mind.  There is no declared winner of the game; players set their own
+objectives, and one can join at any time.
+
+A player's position is called a "faction".  Each faction has a name and a
+number (the number is assigned by the computer, and used for entering orders).
+Each faction is composed of a number of "units", each unit being a group of one
+or more people loyal to the faction.  You start the game with a single unit
+consisting of one character, plus a sum of money.  More people can be hired
+during the course of the game, and formed into more units.  (In these rules,
+the word "character" generally refers either to a unit consisting of only one
+person, or to a person within a larger unit.)
+
+An example faction is shown below, consisting of a starting character, Merlin
+the Magician, who has formed two more units, Merlin's Guards and Merlin's
+Workers.  Each unit is assigned a unit number by the computer (completely
+independent of the faction number), this is used for entering orders.  Here,
+the player has chosen to give his faction the same name ("Merlin the Magician")
+as his starting character.  Alternatively, you can call your faction something
+like "The Great Northern Mining Company" or whatever.
+
+  - Merlin the Magician (17), faction Merlin the Magician (27), $2300, default
+    order "study magic".
+  - Merlin's Guards (33), faction Merlin the Magician (27), number: 20, default
+    order "study sword".
+  - Merlin's Workers (34), faction Merlin the Magician (27), number: 50,
+    default order "work".
+
+A faction is considered destroyed, and the player knocked out of the game, if
+ever all its people are killed or disbanded (i.e. the faction has no units
+left).  The program does not consider your starting character to be special; if
+your starting character gets killed, you will probably have been thinking of
+that character as the leader of your faction, so some other character can be
+regarded as having taken the dead leader's place (assuming of course that you
+have at least one surviving unit!).  As far as the computer is concerned, as
+long as any unit of the faction survives, the faction is not wiped out.  (If
+your faction is wiped out, you can rejoin the game with a new starting
+character.)
+
+The reason for having units of many people, rather than keeping track of
+individuals, is to simplify the game play.  The computer does not keep track of
+individual names, possessions or skills for people in the same unit, and all
+the people in a particular unit must be in the same place at all times.  If you
+want to send people in the same unit to different places, you must split up the
+unit.  Apart from this, there is no difference between having one unit of 50
+people, or 50 units of one person each, except that the former is very much
+easier to handle.
+
+
+The World
+---------
+
+The Atlantis world is divided for game purposes into square regions, each 50
+miles on a side.  Each region can have a terrain type of Plain, Mountain,
+Forest, Swamp or Ocean.  Regions can contain units belonging to players, of
+course; they can also have populations of peasants, as well as buildings
+(usually constructed by players for the purpose of defense) and ships
+(constructed by players for crossing oceans).
+
+A region's location is given as X,Y coordinates, e.g. if you are in region 5,3
+then to your east is 6,3 and south is 5,4.  The computer provides these for the
+convenience of players; region coordinates are never entered in orders.  Each
+region also has a name; again, this is provided only for the convenience of
+players, and is never used in orders.
+
+A turn is equal to one game month; every month is assumed to have 30 days for
+the sake of simplicity.  (To maintain playability, the distance and time scales
+are not always entirely realistic.)  A unit can do many actions at the start of
+the month, that only take a matter of hours, such as buying and selling
+commodities, or fighting an opposing faction.  Each unit can also do exactly
+one action that takes up the entire month, such as harvesting timber or moving
+from one region to another.  The commands which take an entire month are BUILD,
+CAST, ENTERTAIN, MOVE, PRODUCE, RESEARCH, SAIL, STUDY, TEACH and WORK.
+
+Any unit which is not given a command for a particular month will repeat
+whatever it did last month.  Movement commands are the exception to this, so if
+a unit gets the command WORK in January, then MOVE NORTH in February, and no
+command in March, it will settle down to WORK again, rather than continue to
+MOVE NORTH.  All newly created units start off with WORK as the default
+command.
+
+
+Movement
+--------
+
+In one month, it is possible to move from a region to an adjacent region.  Each
+region has four adjacent regions, these being to the north, south, east and
+west.  Thus, to travel to a region immediately northeast would take two months
+(unless powerful magic is used).  Also, to enter an ocean region, one must be
+on board a ship.  (Ships can enter ocean regions, or coastal regions with an
+adjacent ocean region.  They are always constructed in coastal regions.)
+
+Movement can be prevented if the units are trying to carry too much weight.  If
+a ship is trying to sail from one region to another, the capacity of the ship
+is compared with the total weight on board.  If the weight is greater, the ship
+cannot move.  The costs and capacities of the different types of ships are as
+follows:
+
+Type		Cost		Capacity
+
+Longboat	100		200
+Clipper		200		800
+Galleon		300		1800
+
+People weigh 10 units each.  Money has negligible weight.  All other items
+weigh 1 unit each, except for stone and horses, which weigh 50 each.
+
+For movement on land, the weight of people and horses is not taken into
+account; instead, the capacity of a unit is equal to 5 per person, plus 50 per
+horse; this must be no less than the weight of other items being carried, or
+the unit will not be able to move.
+
+The rule for ship movement means that any number of characters can be on board
+a ship, but there is a limit to how many the ship can sail with.  The first
+unit listed under a ship in the report is the "owner", this is the unit that
+created the ship in the first place.  Only this unit can issue SAIL orders for
+the ship.
+
+The owner can PROMOTE another unit, which then becomes the new owner.  If the
+owner leaves without promoting anyone, then the first unit of the same faction
+on the ship is promoted.  If there are no other units of the same faction on
+board, then the first unit on board is promoted.  If there are no other units
+at all, and the ship is at sea, then it sinks.  Unoccupied ships in harbor
+(i.e. in a coastal region) become owned by the first unit to board them.
+
+Ownership of buildings is handled in the same way.  Any number of units can be
+listed as inside a building, but the number of characters that can gain
+protection from it in combat is limited by the building's size.
+
+
+Skills
+------
+
+The most important thing distinguishing one character from another in Atlantis
+is skills.  The skills available are as follows:
+
+Mining
+Lumberjack
+Quarrying
+Horse Training
+Weaponsmith
+Armorer
+Building
+Shipbuilding
+Entertainment
+Stealth
+Observation
+Tactics
+Riding
+Sword
+Crossbow
+Longbow
+Magic
+
+Each unit can have one or more skills.  These are gained by training (using the
+STUDY command) and also by experience.  It takes 1 month of study for a unit to
+attain level 1 in a skill, another 2 months to attain level 2, another 3 months
+to attain level 3 and so on.  (These don't have to be consecutive; to go from
+level 1 to level 2, you can study for a month, do something else for a month,
+and then go back and complete your second month of study.)
+
+Each month of studying Tactics or Magic costs $200 per person (in addition to
+the normal unit maintenance fee).  Study of other skills does not cost
+anything, apart from normal maintenance.
+
+If units are merged together, their skills are averaged out.  No rounding off
+is done; rather, the computer keeps track for each unit of how many total days
+of training that unit has in each skill.  When units are split up, these days
+are divided as evenly as possible among the people in the unit; but no days are
+ever lost.
+
+A unit with a teacher can learn twice as fast as normal.  The TEACH command is
+used to spend the month teaching one or more other units (your own or another
+faction's).  The unit doing the teaching must have a skill level greater than
+the units doing the studying (the units doing the studying simply issue the
+STUDY command as normal).  Each person can only teach up to 10 students in a
+month, so a unit of 20 trying to learn from a single teacher would gain a total
+of 900 days of training per month (rather than the usual 600 or the optimum
+1200).
+
+Note that it is quite possible for a single unit to teach two or more other
+units different skills in the same month, provided that the teacher has a
+higher skill level than each student in the skill that that student is
+studying, and that there are no more than 10 students per teacher.
+
+Mining, Lumberjack, Quarrying, Horse Training, Weaponsmith and Armorer are used
+to produce commodities; this is covered in the section on the economy.
+
+Building and Shipbuilding are used to construct buildings and ships,
+respectively.
+
+Entertainment is used to earn money by entertaining the populace.  Each month
+spent entertaining will earn the performer $20 per level of skill, as well as
+giving the performer the equivalent of 10 days training.  However, the money
+earned from entertainment is taken from the surplus available in the region,
+and no more than 1/20 of the amount available can be earned by entertainers in
+any one month; if the amount would be more than this, then the maximum of 1/20
+of the available money is distributed among the entertainers.
+
+Tactics, Riding, Sword, Crossbow and Longbow are combat skills, and are covered
+in the section on combat.
+
+The Magic skill is covered in the section on magic.
+
+Stealth is used to hide units.  A unit can only be seen if you have at least
+one unit in the same region, with an Observation skill at least as high as that
+unit's Stealth skill.  However, units are always visible when participating in
+combat; and a unit set on guard, or in a building, or on board a ship, is also
+always visible.  You can only see which faction a unit belongs to if you have a
+unit with Observation skill higher than that unit's Stealth skill.
+
+
+The Economy
+-----------
+
+The unit of currency in Atlantis is the silver piece, shown using the dollar
+sign ($) for convenience.  The dollar sign is shown on reports only to make
+them easier to read; the sign is never entered in orders, e.g. 500 silver
+pieces will be shown on the report as $500, but should be entered in orders as
+just 500.  The words "food", "silver" and "money" are here used
+interchangeably, as one of the main uses for money is of course to buy food,
+but for the sake of simplicity, the game does not bother to keep track of these
+separately.
+
+IMPORTANT: Each and every person (player character or peasant) in the Atlantis
+world requires $10 every month for maintenance.  Anyone who ends the month
+without this maintenance cost available will starve to death.  It is up to you
+to make sure that your people have enough available.  Food will be shared
+automatically between your units in the same region, if one is starving and
+another has more than enough; but this will not happen between units in
+different regions.
+
+The primary commodities are silver/food, iron, wood, stone, and horses; they
+are referred to as "primary commodities" because they do not require raw
+materials to produce, but only labor.
+
+The peasants in a region produce food each month; some of this is consumed
+directly, but some may be available as a surplus which players can collect in
+tax.
+
+Player units can also be ordered to produce food, with the WORK command. This
+requires no skill; a player character is equivalent to a peasant in this
+context.
+
+The other primary commodities do require skill to produce.  The PRODUCE command
+is used to order a unit to mine/quarry/etc. one of these commodities.  A level
+of at least 1 in the appropriate skill is required.  The amount produced is
+proportional to the skill level, e.g. a unit with skill 3 will produce three
+times as much as a unit with skill 1.  Of course, the amount produced is also
+proportional to the number of people in the unit.  The skill required for each
+type of primary commodity is as follows:
+
+Commodity	Skill
+
+Iron		Mining
+Wood		Lumberjack
+Stone		Quarrying
+Horse		Horse Training
+
+The amount produced also depends on the terrain type; different types of
+terrain are better places for different types of economic activity.  The
+productivity values (how many units of the commodity one person with skill 1
+can produce in a month) are as follows:
+
+Terrain		$	Iron	Wood	Stone	Horse
+
+Plain		15				1
+Mountain	12	1		1
+Forest		12		1
+Swamp		12		1
+
+Too many cooks spoil the broth; there is a limit to how many people can
+profitably work at producing the same commodity in a particular region.  These
+limits are as follows:
+
+Terrain		$	Iron	Wood	Stone	Horse
+
+Plain		100000				200
+Mountain	 20000	200		200
+Forest		 20000		200
+Swamp		 10000		100
+
+For example, if 20000 people (including peasants) were growing food in a Plain
+region, each would get on average 1/3 of the expected amount.  If the skill
+levels involved are high, it takes even fewer people to fully exploit the
+resources of a region.  (For food production, player characters always take
+priority over peasants, if the production limit is exceeded.)
+
+Each of the secondary commodities takes one unit of raw material to produce, as
+well as one month of work by someone with skill 1 in the appropriate skill.
+(With skill 2, two units can be produced per month, etc.)  The exception is
+plate armor, which takes a month of work by someone with Armorer skill of at
+least 3 to produce (skill at least 6 to produce 2, etc.).  The secondary
+commodities, raw materials and skills required to produce them are as follows:
+
+Commodity	Raw Material	Skill
+
+Sword		Iron		Weaponsmith
+Crossbow	Wood		Weaponsmith
+Longbow		Wood		Weaponsmith
+Chain Mail	Iron		Armorer
+Plate Armor	Iron		Armorer
+
+Anyone who spends a month producing any commodity (except food) gains the
+equivalent of 10 days of study of the appropriate skill.
+
+The construction of buildings works as follows: A new building can be created
+at any time.  All buildings start with a size of 0.  Each unit of construction
+work done on a building increases its size by 1, and requires one unit of
+stone.  A month of work by a character with Building skill of 1 can do one unit
+of construction work; a character with skill 2 can do two units, etc.
+
+The construction of ships works similarly.  A new ship can be created at any
+time.  However, until a number of units of construction work equal to the
+ship's cost are done, the ship will remain "under construction" and unable to
+move.  Each unit of construction work requires one unit of wood, and labor by
+characters with the Shipbuilding skill.
+
+It is possible for player units to collect taxes from the peasants.  If a unit
+issues the TAX command, the amount of income collected is equal to $200 for
+every armed, combat-trained man in the unit (i.e. a tax collector must have a
+sword, crossbow or longbow, plus a skill of at least 1 in the use of the
+weapon).  The amount of money that can be collected is of course limited by the
+amount available in the region (this is shown on the report for the region).
+
+To safeguard one's tax income from other players, the GUARD command can be
+used; if a unit is set on guard, this will prevent a unit of any other faction
+from collecting taxes in the region, unless the guarding faction has issued an
+ADMIT command for the taxing faction.  (Of course, a sufficiently powerful
+enemy can kill the guarding units, and then collect taxes.)
+
+Each month, the peasant population in each region grows by 5% (food supply
+permitting).  To be exact, each peasant has a 5% chance of producing another
+peasant.  Each month also, 5% of the peasants in each region migrate to a
+randomly selected neighbouring region.
+
+
+Magic
+-----
+
+One starts off a career as a magician in Atlantis by studying the Magic skill.
+This is studied like any other skill, with the exception that no faction may
+have more than three magicians at any one time; any command that would cause
+this limit to be exceeded simply fails.  The reason for this restriction is
+that it would completely change the atmosphere of the game to have players
+accumulating large armies of magicians.
+
+Having acquired the appropriate skill, one can then learn spells.  Each spell
+has a difficulty level.  It is possible to learn spells whose difficulty level
+is no more than half your Magic skill, rounded up, e.g. at skill level 3 you
+can start learning difficulty 2 spells.  (It is reasonable to assume that
+magicians have other abilities in the game world, but the spells provided are
+what is relevant in terms of game rules.)
+
+The basic method of acquiring spells is to research them.  Each month spent
+researching results in one previously unknown spell being acquired (the
+computer selects the spell at random from those available).  When issuing a
+RESEARCH command, it is necessary to specify the level to be researched (the
+default is the highest possible level).  If no more spells exist at that level,
+this will be noted on your report for next month.  Research costs $200 per
+month.
+
+Note, however, that while you can learn spells whose difficulty is no more than
+half your Magic skill, rounded up, you can only research spells whose
+difficulty is no more than half your Magic skill, rounded down.  Thus, you can
+start learning difficulty 2 spells at skill level 3, if given them by someone
+else; but you cannot research them yourself until skill level 4.
+
+An easier way to acquire spells is to be given copies of them.  A magician who
+knows a spell can give a copy to another magician with the appropriate skill.
+This does not take a significant amount of time, so an entire library of spells
+can be copied in a single month without interfering with research or other
+activities.
+
+Once spells are learned, they can be cast.  There are two basic types of spells
+in this regard.  One type takes a whole month of involved ritual to cast
+(spells for creating magic items, for example, fall into this category).  The
+CAST command is used for this.
+
+The other type of spell is cast in mere seconds; combat spells fall into this
+category.  The way to order a magician to cast these is with the COMBAT command
+which sets the combat magic spells a magician will use.  Once a magician has
+been ordered to have a particular combat spell prepared, he will from then on
+automatically use it whenever he is involved in fighting of any kind.  Casting
+combat spells in battle does not interfere with the casting of ritual spells
+that month.
+
+Each month spent either researching spells or casting a ritual spell gains the
+magician the equivalent of 10 days study of the Magic skill.
+
+Magic items can be created by means of certain ritual spells.  These are
+handled in the same way as ordinary items as regards things like the GIVE
+command.  Should you ever buy, steal or be given a magic item created by
+someone else, however, remember that possession of the item does not
+necessarily mean you know how to use it.
+
+
+Combat
+------
+
+Combat occurs when a unit issues an ATTACK command.  The computer then gathers
+together all the units on the attacking side, and all the units on the
+defending side, and the two sides fight until an outcome is reached.  In this
+section, "attacker" means the unit that originally issued the ATTACK command,
+and "defender" means the unit that was the target of the command.
+
+On the attacking side are all the units of the attacker's faction, that are in
+the region.  Also are all other factions, one of whose units issued an ATTACK
+command against the defender's faction.  This means that if several factions
+are attacking an enemy, each attacking faction need only have one of its units
+issue an ATTACK command against one of the target faction's units, and the
+attackers will automatically fight together.  You cannot attack your own units,
+or an ally's units.
+
+On the defending side are all the units of the defender's faction.  Also are
+all other factions which are allied to the defender.  If the defender is the
+peasants of the region, then any factions which have a unit on guard will fight
+on the side of the peasants.  (If several factions are planning to attack one,
+they should not only all issue ATTACK commands, but make sure they are allied
+as well (using the ALLY command), in case the enemy gets in an attack first,
+and picks them off piecemeal.)
+
+The computer first checks which unit on each side has the best Tactics skill;
+the unit with the best Tactics is assumed to be leading all the troops on that
+side.  If one side's leader has a higher Tactics skill, then that side gets a
+free round of attacks.
+
+In each combat round, the combatants each get to attack once, in a random
+order.  (In a free round of attacks, only one side's forces get to attack.)
+Each combatant will attempt to hit a randomly selected enemy.  If he hits, and
+the target has no armor, then the target is automatically killed.  Chain mail
+gives a 1/3 chance of surviving a hit, and plate armor gives a 2/3 chance.
+
+For a soldier using a sword against an enemy soldier similarly equipped,
+whether he hits or not is decided by a contest between the two Sword skills.
+Against an enemy with a bow, the contest is of Sword skill versus an effective
+skill of -2.  (It is difficult for an archer to defend himself against a
+swordsman in hand-to-hand combat.)
+
+For a crossbow, whether the target is hit is decided by a contest of Crossbow
+skill versus 0.  For a longbow, the contest is of Longbow skill versus 2 (a
+longbow is considerably more difficult to use than a crossbow).  This
+disadvantage is offset by the fact that a longbow can be fired every round,
+whereas a crossbow takes two rounds to reload after being fired (so it fires on
+the first, fourth, seventh etc. rounds).
+
+Unarmed combatants are assumed to be fighting with knives, pitchforks or other
+improvised weapons; they are treated as if they were using swords, but with an
+effective skill of -2.  (A combatant with a weapon, but without a skill of at
+least 1 in its use, is treated as unarmed.  Combatants with more than one
+weapon will select the first one with which they have a skill of at least 1.)
+
+A swordsman with a horse, and with Riding skill of at least 2, gains a bonus of
+2 to his effective Sword skill, provided that the region terrain is Plain;
+horses are of little use in battle in any other terrain.
+
+People inside buildings gain protection; there is a penalty of 2 for anyone to
+hit them.  (This is true even if they are on the attacking side.)  The maximum
+number of people who can gain protection from a single building is equal to the
+building's size.
+
+The contest of skills works as follows: The attacker's and defender's skills
+are compared (here "attacker" and "defender" mean the individual soldiers,
+without reference to who issued the original ATTACK command).  The chance of
+hitting is determined by which skill is higher, and by how much:
+
+Skill Diff.	Chance
+
+-1		10
+ 0		25
+ 1		40
+
+Example: if a soldier with a Sword skill of 3 is trying to hit one with a Sword
+skill of 2, there is a 40% chance of success.  If the attacker's skill is more
+than 1 greater than the defender's, there is a 49% chance of success; if the
+defender's skill is more than 1 greater, there is only a 1% chance.
+
+Particularly valuable or lightly-armed units can be protected using the BEHIND
+command.  Any unit with the BEHIND flag set is considered to be at the rear of
+the battle, and cannot be attacked by any means until all the troops in front
+on that side have been killed.  However, units at the back cannot use swords,
+only missile weapons.
+
+A magician using a combat spell is considered to be using a missile weapon, and
+can attack even while BEHIND.  A magician without a combat spell set will fight
+with normal weapons like everyone else.  The battle report will indicate which
+magicians cast what combat spells, if any.
+
+After the free round of attacks (if any), comes the main part of the battle.
+Combat rounds are fought until one side is wiped out.  (Realistically, it is
+reasonable to assume that some of the troops on the losing side have fled and
+dispersed into the countryside.  Either way, they are no longer with their
+faction.)
+
+Finally, the dead are counted.  Any money or items owned by dead combatants on
+either side are collected by the winning side; the loot is distributed evenly
+among the troops, so factions on the winning side will generally pick up loot
+in proportion to their number of surviving men.  However, any items other than
+iron, wood or stone, whose owners are killed, have a 50% chance of being
+destroyed in the fighting, e.g. if 50 swordsmen are killed, the winners will
+pick up about 25 swords.
+
+Every armed soldier on the winning side gains the equivalent of 10 days study
+with the weapon he was using.  Magicians who were using a combat spell gain the
+equivalent of 10 days study of the Magic skill.  In addition, the leader of the
+winning side gains the equivalent of 10 days study of Tactics.  However, these
+skill gains only happen if the winning side took at least one casualty (so that
+the victory was at least something of a challenge).
+
+Combat between units on board ships at sea is handled exactly like land combat,
+except that horses can't be used in naval battles.
+
+If you are expecting to fight an enemy who is carrying so much equipment that
+you would not be able to move after picking it up, and you want to move to
+another region later that month, it may be worth issuing some orders to drop
+items (with the GIVE 0 command) in case you win the battle!
+
+
+Order Format
+------------
+
+To enter orders for Atlantis, you should send a mail message (no particular
+subject line required), containing the following:
+
+#ATLANTIS faction-no
+
+UNIT unit-no
+...orders...
+
+UNIT unit-no
+...orders...
+
+#END
+
+For example, if your faction number (shown at the top of your report) is 27,
+and you have two units numbered 5 and 17:
+
+#ATLANTIS 27
+
+UNIT 5
+...orders...
+
+UNIT 17
+...orders...
+
+#END
+
+Thus, orders for each unit are given separately, and indicated with the UNIT
+keyword.  (In the case of orders, such as the command to rename your faction,
+that are not really for any particular unit, it does not matter which unit
+issues the command; but some particular unit must still issue it.)
+
+Several sets of orders may be sent for the same turn, and the last one received
+before the deadline will be used.
+
+IMPORTANT: You MUST use the correct #ATLANTIS line or else your orders will be
+silently ignored.
+
+Each type of order is designated by giving a keyword as the first non-blank
+item on a line.  Parameters are given after this, separated by spaces or tabs.
+Blank lines are permitted, as are comments; anything after a semicolon on a
+line is treated as a comment and ignored.
+
+The parser is not case sensitive, so all commands may be given in upper case,
+low case or a mixture of the two.  However, when supplying names containing
+spaces, the name must be surrounded by double quotes, or else underscore
+characters must be used in place of spaces in the name.  (These things apply to
+the #ATLANTIS and #END lines as well as to order lines.)
+
+Any player not sending in orders for four turns in a row is removed from the
+game, e.g. if your last set of orders was for turn 20, you will receive reports
+for turns 20, 21, 22, 23 and 24; your turn 24 report will contain a request to
+send orders; and you will not receive a turn 25 report.  Note that an empty
+order set still counts as sending in orders.
+
+IMPORTANT: If you are dropped from the game for not sending orders, your
+faction is completely and permanently destroyed, and is NOT retrievable.
+Please contact the moderator if you have difficulty getting orders through.
+
+
+Orders
+------
+
+
+ACCEPT  faction-no
+
+If any of your units issues this command, then all of your units will accept
+gifts from any units of the target faction for that month.  The ACCEPT status
+only lasts for a single month.  ACCEPT status is always assumed for allies.
+
+It is necessary to issue an ACCEPT command to receive things given with any of
+the following commands: GIVE, PROMOTE, TEACH, TRANSFER.
+
+
+ADDRESS  new-address
+
+Change the email address to which your reports are sent (up to 80 characters).
+
+
+ADMIT  faction-no
+
+If any of your units issues this command, then any unit of the target faction
+will be admitted into any building owned by you, or aboard any ship owned by
+you, and allowed to collect taxes in any region you are guarding, for the
+duration of the month.  The ADMIT status only lasts for a single month.  ADMIT
+status is always assumed for allies.
+
+
+ALLY  faction-no  flag
+
+If you ally with a faction, then your units will fight to defend units of that
+faction when they are attacked.  ALLY faction-no 1 allies with the indicated
+faction.  ALLY faction-no 0 cancels this.
+
+The ALLY status also implies ACCEPT and ADMIT status, i.e. you will always
+ACCEPT and ADMIT any faction you are allied to.
+
+Note that even if you are allied to a faction, this does not necessarily mean
+that this faction is allied to you.
+
+
+ATTACK  unit-no
+ATTACK  PEASANTS
+
+Attack either a target unit, or the peasants in the current region.
+
+
+BEHIND  flag
+
+BEHIND 1 sets the unit issuing the command to be behind other units in combat.
+BEHIND 0 cancels this.
+
+
+BOARD  ship-no
+
+The unit issuing the command will attempt to board the specified ship.  If
+issued from inside a building or aboard a ship, the unit will first
+leave the current building or ship.  The command will only work if the target
+ship is unoccupied, or is owned by a unit in your faction, or is owned by a
+unit in a faction which has issued an ADMIT command for you that month.
+
+
+BUILD  BUILDING  [building-no]
+BUILD  SHIP  ship-no
+BUILD  ship-type
+
+Perform building work on a building or ship.  BUILD BUILDING with a building
+number specified will perform work on that building.  BUILD BUILDING alone will
+create a new building and perform work on it.  BUILD SHIP with a ship number
+specified will perform work on that ship.  BUILD LONGBOAT, CLIPPER or GALLEON
+will create a ship of the indicated type and perform work on it.
+
+Note that since the number of a newly created building or ship is not known
+until next month, only the creating unit can work on it in the first month.
+
+
+CAST  spell
+
+Spend the month casting the indicated spell (must be a ritual magic spell which
+the unit knows).
+
+
+COMBAT  spell
+
+Set the unit's combat spell to the indicated value (must be a combat spell
+which the unit knows).  If no spell is specified, the unit's combat spell will
+be set to nothing (i.e. the magician will henceforth fight in hand-to-hand
+combat).
+
+
+DEMOLISH
+
+Demolish the building of which the unit is currently the owner.
+
+
+DISPLAY  UNIT  text
+DISPLAY  BUILDING  text
+DISPLAY  SHIP  text
+
+Set the display string for the unit itself, or a building or ship (of which the
+unit must currently be the owner).  This is a text string (up to 160
+characters) which will be shown after the entity's description on everyone's
+report.
+
+
+ENTER  building-no
+
+The unit issuing the command will attempt to enter the specified building.  If
+issued from inside a building or aboard a ship, the unit will first leave the
+building or ship.  The command will only work if the target building is
+unoccupied, or is owned by a unit in your faction, or is owned by a unit in a
+faction which has issued an ADMIT command for you that month.
+
+
+ENTERTAIN
+
+The unit issuing the command will spent the month entertaining the locals to
+earn money.
+
+
+FIND  faction-no
+
+Find the email address of the specified faction.
+
+
+FORM  alias
+
+Forms a new unit.  The newly created unit will be in your faction, in the same
+region as the unit which formed it, with the same BEHIND and GUARD status, and
+in the same building or ship, if any.  It will start off, however, with no
+people or items; you should, in the same month, issue orders to transfer people
+into the new unit, or have it recruit new members.
+
+The FORM command is followed by a list of commands for the newly created unit. 
+This list is terminated by the END keyword, after which commands for the unit
+issuing the FORM command start again.
+
+The purpose of the "alias" parameter is so that you can refer to the new unit. 
+You will not know the new unit's number until next month.  To refer to the new
+unit that month, pick an alias number (the only restriction on this is that it
+must be at least 1, and you cannot create two new units in the same region in
+the same month, with the same alias numbers).  The new unit can then be
+referred to as NEW alias in place of the regular unit number.
+
+Example:
+
+UNIT 17
+FORM 1
+    NAME UNIT "Merlin's Guards"
+    RECRUIT 20
+    STUDY SWORD
+END
+FORM 2
+    NAME UNIT "Merlin's Workers"
+    DISPLAY UNIT "wearing dirty overalls and carrying shovels"
+    RECRUIT 50
+END
+PAY NEW 1 1200
+PAY NEW 2 3000
+
+This set of orders for unit 17 would create two new units with alias numbers 1
+and 2, name them Merlin's Guards and Merlin's Workers, set the display string
+for Merlin's Workers, have both units recruit men, and have Merlin's Guards
+study swordsmanship.  Merlin's Workers will have the default command WORK, as
+all newly created units do.  The unit that created these two then pays them
+enough money (using the NEW keyword to refer to them by alias numbers) to cover
+the costs of recruitment and the month's maintenance.
+
+
+GIVE  unit-no  quantity  item
+GIVE  0  quantity  item
+GIVE  unit-no  spell
+
+The first form of the command gives a quantity of an item to another unit.  If
+the target unit is not a member of your faction, then its faction must have
+issued an ACCEPT command for you that month.
+
+The second form of the command discards a quantity of an item.  The items
+discarded are permanently lost.
+
+The third form of the command gives a copy of a spell to another unit.  The
+other unit must have sufficient Magic skill to understand the spell, and its
+faction must have issued an ACCEPT command for you that month.
+
+
+GUARD  flag
+
+GUARD 1 sets the unit issuing the command to guard the peasants of the region
+against attack, or taxation by other factions (except those for which one has
+issued an ADMIT command).  GUARD 0 cancels this.
+
+
+LEAVE
+
+Leave whatever building the unit is in, or ship it is on board.  The command
+cannot be used at sea.
+
+
+MOVE  direction
+
+The unit will move in the specified direction (NORTH, SOUTH, EAST or WEST). 
+This command is for use on land only; use SAIL to move in a ship.
+
+
+NAME  FACTION  new-name
+NAME  UNIT  new-name
+NAME  BUILDING  new-name
+NAME  SHIP  new-name
+
+Changes the name of your faction, or of the unit, or of a building or ship (of
+which the unit must currently be the owner).  The name can be up to 80
+characters.  All entities start off named "Unit 5", "Faction 23" or whatever
+until they are given proper names.  Names may not contain brackets (square
+brackets can be used instead if necessary).
+
+
+PAY  unit-no  amount
+PAY  PEASANTS  amount
+PAY  0  amount
+
+Pay another unit the specified amount of money (does not require the target
+unit's faction to have issued an ACCEPT command).  PAY PEASANTS will give the
+money to the peasants of the region, and PAY 0 will simply throw it away.
+
+
+PRODUCE  item
+
+Spend the month producing as much as possible of the indicated item.
+
+
+PROMOTE  unit
+
+Promote the specified unit to owner of the building or ship of which the unit
+issuing the command is currently the owner.  If the specified unit is not a
+member of your faction, then its faction must have issued an ACCEPT command for
+you that month.
+
+
+QUIT  faction-no
+
+Quit the game.  Your faction number must be provided as a check to prevent Quit
+orders being issued by accident.  On issuing this order, your faction will be
+completely and permanently destroyed.
+
+
+RECRUIT  number
+
+Attempt to recruit the specified number of people into the unit.  Each person
+recruited costs $50 (this is either paid as compensation to their previous
+employers, or paid to the new recruits as a signing-on fee, who then spend it
+in the local tavern celebrating their new employment status).
+
+The total number of people who can be recruited in a region in one month is 1/4
+of the number of peasants in the region.  If more RECRUIT orders than this are
+issued, the available recruits are distributed at random among the units
+issuing the orders.
+
+Since new recruits have no skills, a trained unit which takes in recruits will
+find its skills diluted.
+
+
+RESEARCH  [level]
+
+Spend the month researching a spell at the specified difficulty level.  For
+example, a magician with a magic skill of 4 or 5 can research spells of up to
+level 2.  If the level is not specified, the research will be at the highest
+possible level.
+
+
+RESHOW  spell
+
+When a unit in your faction researches, or is given a copy of, a new spell for
+the first time, your report will contain a paragraph of information on the
+spell.  The RESHOW command will show this information again, in case you lose
+the report that originally showed it.  This only works if there is still at
+least one unit in your faction that knows the spell.
+
+
+SAIL  direction
+
+The ship of which the unit is the owner will sail in the specified direction
+(NORTH, SOUTH, EAST or WEST).
+
+
+SINK
+
+Sink the ship of which the unit is currently the owner.  This can only be done
+in a coastal region, as sinking one's ship at sea would be suicide.
+
+
+STUDY  skill
+
+Spend the month studying the specified skill.
+
+
+TAX
+
+Attempt to raise tax income from the region.
+
+
+TEACH  unit-no  ...
+
+Attempt to teach the specified units whatever skill they are studying that
+month.  A list of several unit numbers may be specified.  If any of these units
+is not a member of your faction, then its faction must have issued an ACCEPT
+command for you that month.
+
+
+TRANSFER  unit-no  number
+TRANSFER  PEASANTS  number
+
+Transfer the specified number of people to another unit.  If the specified
+unit is not a member of your faction, then its faction must have issued an
+ACCEPT command for you that month.  The TRANSFER PEASANTS order will transfer
+the people into the peasant population (will work anywhere except in an ocean
+region).
+
+
+WORK
+
+Spend the month working in agriculture.
+
+
+Sequence of Events
+------------------
+
+Each turn, the following sequence of events occurs:
+
+FORM orders are processed.
+ACCEPT, ADDRESS, ADMIT, ALLY, BEHIND, COMBAT, DISPLAY, GUARD 0, NAME and RESHOW
+orders are processed.
+FIND orders are processed.
+BOARD, ENTER, LEAVE and PROMOTE orders are processed.
+ATTACK orders are processed.
+DEMOLISH, GIVE, PAY and SINK orders are processed.
+TRANSFER orders are processed.
+TAX orders are processed.
+GUARD 1 and RECRUIT orders are processed.
+QUIT orders are processed.
+Empty units are deleted.
+Unoccupied ships at sea are sunk.
+MOVE orders are processed.
+SAIL orders are processed.
+BUILD, ENTERTAIN, PRODUCE, RESEARCH, STUDY, TEACH and WORK orders are
+processed.
+CAST orders are processed.
+Peasant population growth occurs.
+Maintenance costs are assessed.
+Peasant migration occurs.
+
+
+Hints for New Players
+---------------------
+
+Make sure to use the correct #ATLANTIS and UNIT lines in your orders.
+
+Be careful not to lose any people through starvation.  It is a good idea to
+store a month's supply of money in each region in which you have units, as a
+safety measure.
+
+Don't try to do everything at the start; pick a particular occupation to
+specialize in, and trade with other players for anything else you need.  You
+can always diversify later, when you have the resources.
+
+Keep an eye on what the other players are doing, so as not to miss
+opportunities for profitable trade, or get caught in a surprise attack.
+Remember, in time of war there is safety in numbers.
+
+Remember to use ACCEPT and ADMIT where necessary.
+
diff --git a/xtrn/atlantis/service.js b/xtrn/atlantis/service.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc0e8d1e8542fee9a6f2d5f12a27b3b5a6d56dc5
--- /dev/null
+++ b/xtrn/atlantis/service.js
@@ -0,0 +1,192 @@
+var script_dir='.';
+try { throw barfitty.barf(barf) } catch(e) { script_dir=e.fileName }
+script_dir=script_dir.replace(/[\/\\][^\/\\]*$/,'');
+script_dir=backslash(script_dir);
+
+load("sockdefs.js");
+load(script_dir+"gamedata.js");
+
+function authenticated(password)
+{
+	var f;
+
+	for(f in factions) {
+		if(factions[f].addr==user && factions[f].password==password)
+			return(true);
+	}
+}
+
+function main()
+{
+	var allgames;
+	var game=undefined;
+	var user;
+	var password;
+	var f;
+	var i;
+	var tmp;
+	var fa;
+
+	f=new File(script_dir+"games.ini");
+	f.open("r");
+	allgames=f.iniGetAllObjects();
+	f.close();
+
+	do {
+		switch(readln().toLowerCase()) {
+			case 'help':
+				writeln("Commands available:");
+				writeln("quit        user        password    list_games  select_game get_summary");
+				writeln("get_report  get_orders  new_player  send_orders");
+				writeln("");
+				writeln("OK");
+				break;
+			case 'quit':
+				return;
+			case 'user':
+				write("User: ");
+				user=readln();
+				writeln("OK");
+				break;
+			case 'password':
+				write("Pass: ");
+				password=readln();
+				writeln("OK");
+				break;
+			case 'list_games':
+				for(i in allgames) {
+					writeln(allgames[i].title);
+				}
+				writeln('');
+				writeln('OK');
+				break;
+			case 'select_game':
+				write("Game: ");
+				game=undefined;
+				game_dir=undefined;
+				tmp=readln();
+				for(i in allgames) {
+					if(allgames[i].title==tmp)
+						game=allgames[i];
+				}
+				if(game==undefined)
+					writeln("Game not found!");
+				else {
+					game_dir=backslash(script_dir+game.dir);
+					readgame();
+					writeln('OK');
+				}
+				break;
+			case 'get_summary':
+				if(game==undefined) {
+					writeln("No game selected!");
+					break;
+				}
+				client.socket.sendfile(game_dir+"summary");
+				writeln("END");
+				writeln("OK");
+				break;
+			case 'new_player':
+				if(game==undefined) {
+					writeln("No game selected!");
+					break;
+				}
+				if(factionbyaddr(user)!=null) {
+					writeln("Already playing!");
+					break;
+				}
+				write("Desired Faction Name: ");
+				tmp=readln();
+				for(i=0; ;i++) {
+					f=new File(game_dir+"newplayers/"+i);
+
+					if(!f.exists) {
+						f.open("w");
+						f.writeln("var addr="+user.toSource());
+						f.writeln("var name="+tmp.toSource());
+						f.writeln("var password="+password.toSource());
+						f.close();
+						break;
+					}
+				}
+				writeln("OK");
+				break;
+			case 'get_report':
+				if(game==undefined) {
+					writeln("No game selected!");
+					break;
+				}
+				if(!authenticated(password)) {
+					writeln("Not authenticated!");
+					break;
+				}
+				write("Turn: ");
+				tmp=parseInt(readln());
+				if(isNaN(tmp)) {
+					writeln("Invalid turn!");
+					break;
+				}
+				if(tmp > getturn()) {
+					writeln("Please wait for the future to arrive.");
+					break;
+				}
+				if(!client.socket.sendfile(game_dir+'reports/'+fa.no+'.'+(tmp))) {
+					writeln("END");
+					writeln("Unable to read report!");
+					break;
+				}
+				writeln("END");
+				writeln("OK");
+				break;
+			case 'get_orders':
+				if(game==undefined) {
+					writeln("No game selected!");
+					break;
+				}
+				if(!authenticated(password)) {
+					writeln("Not authenticated!");
+					break;
+				}
+				write("Turn: ");
+				tmp=parseInt(readln());
+				if(isNaN(tmp)) {
+					writeln("Invalid turn!");
+					break;
+				}
+				if(tmp > getturn()) {
+					writeln("Please wait for the future to arrive.");
+					break;
+				}
+				if(!client.socket.sendfile(game_dir+'orders/'+fa.no+'.'+(tmp))) {
+					writeln("END");
+					writeln("Unable to read report!");
+					break;
+				}
+				writeln("END");
+				writeln("OK");
+				break;
+			case 'send_orders':
+				if(game==undefined) {
+					writeln("No game selected!");
+					break;
+				}
+				if(!authenticated(password)) {
+					writeln("Not authenticated!");
+					break;
+				}
+				fa=factionbyaddr(user);
+				f=new File(game_dir+'orders/'+fa.no+'.'+(turn+1));
+				writeln("Send orders now... end with \"END\" on a line by itself");
+				while((tmp=readln())!="END")
+					f.writeln(tmp);
+				f.close();
+				writeln("OK");
+				break;
+			default:
+				writeln("Unknown command!");
+				break;
+		}
+	} while(1);
+}
+
+main();
diff --git a/xtrn/atlantis/ship.js b/xtrn/atlantis/ship.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ac84bb367ff53e5391494c962706880bf428ea9
--- /dev/null
+++ b/xtrn/atlantis/ship.js
@@ -0,0 +1,63 @@
+if(js.global.Region==undefined)
+	load(script_dir+'region.js');
+if(js.global.Unit==undefined)
+	load(script_dir+'unit.js');
+
+function Ship()
+{
+	this.no=0;
+	this.name='';
+	this.display='';
+	this.type=0;
+	this.left=0;
+	this.region=null;	// TODO: Added
+	this.id getter=function() { return(this.name+' ('+this.no+')'); };
+	this.cansail getter=function() {
+		var n,u;
+
+		n = 0;
+
+		for(u in this.region.units)
+			if(this.region.units[u].ship.no==this.no)
+				n += this.region.units[u].itemweight + this.region.units[u].horseweight + (this.region.units[u].number * 10);
+
+		return n <= shiptypes[this.type].capacity;
+	};
+	this.owner getter=function() {
+		var u;
+
+		for(u in this.region.units)
+			if(this.region.units[u].ship.no==this.no)
+				return(this.region.units[u]);
+
+		return(null);
+	};
+
+	this.mayboard=function(u) {
+		var u2=this.owner;
+
+		if(u2 == undefined)
+			return(true);
+		return(u2.admits(u));
+	};
+}
+
+function findship (no,region)
+{
+	var r,s;
+
+	if(region==undefined) {
+		for(r in regions)
+			for(s in regions[r].ships)
+				if(regions[r].ships[s].no==no)
+					return(regions[r].ships[s].no);
+	}
+	else {
+		for(s in region)
+			if(region.ships[s].no==no)
+				return(region.ships[s].no);
+	}
+
+	return(null);
+}
+
diff --git a/xtrn/atlantis/troop.js b/xtrn/atlantis/troop.js
new file mode 100644
index 0000000000000000000000000000000000000000..266e4fad3e8971d8193a7cbeb58e3c65009c58f2
--- /dev/null
+++ b/xtrn/atlantis/troop.js
@@ -0,0 +1,23 @@
+function Troop()
+{
+	this.unit=null;
+	this.lmoney=0;
+	this.status=0;
+	this.side=0;
+	this.attacked=false;
+	this.weapon=0;
+	this.missile=false;
+	this.skill=0;
+	this.armor=0;
+	this.behind=false;
+	this.inside=0;
+	this.reload=0;
+	this.canheal=false;
+	this.runesword=false;
+	this.invulnerable=false;
+	this.power=0;
+	this.shieldstone=false;
+	this.demoralized=false;
+	this.dazzled=false;
+}
+
diff --git a/xtrn/atlantis/unit.js b/xtrn/atlantis/unit.js
new file mode 100644
index 0000000000000000000000000000000000000000..32fc5ad5f74e360225c45c65266697d4410f9ad3
--- /dev/null
+++ b/xtrn/atlantis/unit.js
@@ -0,0 +1,368 @@
+if(js.global.terrains==undefined)
+	load(script_dir+'gamedefs.js');
+if(js.global.Region==undefined)
+	load(script_dir+'region.js');
+if(js.global.Faction==undefined)
+	load(script_dir+'faction.js');
+if(js.global.Building==undefined)
+	load(script_dir+'building.js');
+if(js.global.Ship==undefined)
+	load(script_dir+'ship.js');
+if(js.global.Order==undefined)
+	load(script_dir+'order.js');
+
+function Unit(region)
+{
+	var i,n,v,r,u;
+
+	this.no=0;
+	this.name='';
+	this.display='';
+	this.number=0;
+	this.money=0;
+	this.faction=null;
+	this.building=null;
+	this.ship=null;
+	this.region=null;		// TODO: Added
+	this.owner=false;
+	this.behind=false;
+	this.guard=false;
+	this.thisorder=null;
+	this.lastorder=new Order();
+	this.lastorder.unit=this;
+	this.lastorder.command=K_WORK;
+	this.combatspell=-1;
+	this.skills=new Array(MAXSKILLS);
+	for(i in this.skills)
+		this.skills[i]=0;
+	this.items=new Array(MAXITEMS);
+	for(i in this.items)
+		this.items[i]=0;
+	this.spells=new Array(MAXSPELLS);
+	for(i in this.spells)
+		this.spells[i]=0;
+	this.orders=new Array();
+	this.alias=0;
+	this.dead=0;
+	this.leaning=0;
+	this.n=0;
+	this.litems=new Array();
+	this.side=0;
+	this.isnew=false;
+	this.id getter=function() { return(this.name+' ('+this.no+')'); };
+	this.itemweight getter=function() {
+		var i,n;
+
+		n = 0;
+
+		for (i = 0; i < MAXITEMS; i++) {
+			switch (i) {
+				case I_STONE:
+					n += this.items[i] * 50;
+					break;
+
+				case I_HORSE:
+					break;
+
+				default:
+					n += this.items[i];
+			}
+		}
+
+		return n;
+	};
+	this.horseweight getter=function () {
+		var i,n;
+
+		n = 0;
+
+		for (i = 0; i < MAXITEMS; i++)
+			switch (i)
+			{
+				case I_HORSE:
+					n += this.items[i] * 50;
+					break;
+			}
+
+		return n;
+	};
+	this.canmove getter=function() {
+		return this.itemweight - this.horseweight - (this.number * 5) <= 0;
+	};
+	this.canride getter=function() {
+		return this.itemweight - this.horseweight + (this.number * 10) <= 0;
+	};
+	this.armedmen getter=function() {
+		var n=0;
+
+		if (this.effskill (SK_SWORD))
+			n += this.items[I_SWORD];
+		if (this.effskill (SK_CROSSBOW))
+			n += this.items[I_CROSSBOW];
+		if (this.effskill (SK_LONGBOW))
+			n += this.items[I_LONGBOW];
+		return Math.min(n,this.number);
+	};
+
+	this.effskill=function(skill) {
+		var n,j,result;
+
+		n = 0;
+		if (this.number)
+			n = this.skills[skill] / this.number;
+
+		j = 30;
+		result = 0;
+
+		while (j <= n)
+		{
+			n -= j;
+			j += 30;
+			result++;
+		}
+
+		return result;
+	};
+	this.cancast=function(i) {
+		i=parseInt(i);
+		if(i<0 || i>=MAXSPELLS)
+			return 0;
+		if (this.spells[i])
+			return this.number;
+
+		if (!this.effskill(SK_MAGIC))
+			return 0;
+
+		switch (i)
+		{
+			case SP_BLACK_WIND:
+				return this.items[I_AMULET_OF_DARKNESS];
+
+			case SP_FIREBALL:
+				return this.items[I_STAFF_OF_FIRE];
+
+			case SP_HAND_OF_DEATH:
+				return this.items[I_AMULET_OF_DEATH];
+
+			case SP_LIGHTNING_BOLT:
+				return this.items[I_STAFF_OF_LIGHTNING];
+
+			case SP_TELEPORT:
+				return Math.min (this.number,this.items[I_WAND_OF_TELEPORTATION]);
+		}
+
+		return 0;
+	};
+	this.isallied=function(u2) {
+		var rf;
+
+		if (u2==null)
+			return this.guard;
+
+		if (u2.faction.no == this.faction.no)
+			return true;
+
+		for(rf in this.faction.allies)
+			if(this.faction.allies[rf].no==u2.faction.no)
+				return(true);
+
+		return(false);
+	};
+
+	this.accepts=function(u2) {
+		var rf;
+
+		if(this.isallied(u2))
+			return(true);
+
+		for(rf in this.faction.accept)
+			if(this.faction.accept[rf].no==u2.faction.no)
+				return(true);
+
+		return false;
+	};
+
+	this.admits=function(u2) {
+		var rf;
+
+		if(this.isallied(u2))
+			return(true);
+
+		for(rf in this.faction.admit)
+			if(this.faction.admit[rf].no==u2.faction.no)
+				return(true);
+
+		return false;
+	};
+	this.leave=function()
+	{
+		var u2,b,sh;
+
+		if (this.building!=null)
+		{
+			b = u.building;
+			u.building = null;
+
+			if (u.owner)
+			{
+				u.owner = false;
+
+				for (u2 in this.region.units)
+					if (region.units[u2].faction.no == this.faction.no && region.units[u2].building.no == b.no)
+					{
+						region.units[u2].owner = true;
+						return;
+					}
+
+				for (u2 in this.region.units)
+					if (region.units[u2].building.no == b.no)
+					{
+						region.units[u2].owner = true;
+						return;
+					}
+			}
+		}
+
+		if (this.ship!=null)
+		{
+			sh = this.ship;
+			this.ship = null;
+
+			if (this.owner)
+			{
+				this.owner = false;
+
+				for (u2 in this.region.units)
+					if (region.units[u2].faction.no == this.faction.no && region.units[u2].ship.no == sh.no)
+					{
+						region.units[u2].owner = true;
+						return;
+					}
+
+				for (u2 in this.region.units)
+					if (region.units[u2].ship.no == sh.no)
+					{
+						region.units[u2].owner = true;
+						return;
+					}
+			}
+		}
+	};
+
+	this.getnewunit=function(arg)
+	{
+		var u2;
+		var n=parseInt(arg);
+
+		if (isNaN(n) || n == 0)
+			return null;
+
+		for (u2 in this.region.units)
+			if (this.region.units[u2].faction.no == this.faction.no && this.region.units[u2].alias == n)
+				return this.region.units[u2];
+
+		return null;
+	};
+
+	this.getunit=function (args)
+	{
+		var u2;
+		var tmp;
+
+		tmp=args.shift();
+		if(tmp == "new")
+			return(this.getnewunit(args.shift()));
+		if(this.region.terrain != T_OCEAN && tmp=="peasants")
+			return {getunitpeasants:true};
+		if(tmp==0)
+			return {getunit0:true};
+		for(u2 in this.region.units) {
+			if(this.region.units[u2].no==tmp && this.cansee(this.region.units[u2])
+					&& !this.region.units[u2].isnew)
+				return(this.region.units[u2]);
+		}
+		return null;
+	};
+
+	/* Find an unused unit number... */
+	if(region != undefined) {
+		for(n=0;; n+=1000) {
+			v=new Array();
+			for(r in regions)
+				for(u in regions[r].units)
+					if(regions[r].units[u].no >= n && regions[r].units[u].no < n + 1000)
+						v[regions[r].units[u].no]=true;
+			for(i=0; i<1000; i++)
+				if(v[i]==undefined) {
+					this.no=n+i;
+					this.name="Unit "+this.no;
+					region.units.push(this);
+					return;
+				}
+		}
+	}
+}
+
+function findunit(no,region)
+{
+	var r,u;
+
+	if(region==undefined) {
+		for(r in regions)
+			for(u in regions[r].units)
+				if(regions[r].units[u].no==no)
+					return(regions[r].units[u]);
+	}
+	else {
+		for(u in region.units)
+			if(region.units[u].no==no)
+				return(region.units[u]);
+	}
+
+	return(null);
+}
+
+function removeempty()
+{
+	var i,f,r,sh,sh2,u,u2,u3;
+
+	for (r in regions)
+	{
+		for (u in regions[r].units)
+		{
+			if (!regions[r].units[u].number)
+			{
+				regions[r].units[u].leave();
+
+				for (u3 in regions[r].units)
+					if (regions[r].units[u3].faction.no == regions[r].units[u].faction.no)
+					{
+						regions[r].units[u3].money += regions[r].units[u].money;
+						regions[r].units[u].money = 0;
+						for (i = 0; i != MAXITEMS; i++)
+							regions[r].units[u3].items[i] += regions[r].units[u].items[i];
+						break;
+					}
+
+				if (regions[r].terrain != T_OCEAN)
+					regions[r].money += regions[r].units[u].money;
+
+				regions[r].units.splice(u,1);
+			}
+		}
+
+		if (regions[r].terrain == T_OCEAN)
+			for (sh in regions[r].ships)
+			{
+				f=false;
+				for (u in regions[r].units)
+					if (regions[r].units[u].ship.no == sh.no) {
+						f=true;
+						break;
+					}
+
+				if (f)
+					regions[r].ships.splice(sh,1);
+			}
+	}
+}
diff --git a/xtrn/atlantis/utilfuncs.js b/xtrn/atlantis/utilfuncs.js
new file mode 100644
index 0000000000000000000000000000000000000000..35561910605e05357edc4c1013c41b0dae2b6b09
--- /dev/null
+++ b/xtrn/atlantis/utilfuncs.js
@@ -0,0 +1,14 @@
+
+function scramble(array)
+{
+	var i;
+	var sw;
+	var tmp;
+
+	for(i=array.length-1; i>=0; i--) {
+		sw=random(i);
+		tmp=array[sw];
+		array[sw]=array[i];
+		array[i]=tmp;
+	}
+}