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 (®ions,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 = ®ions; + + 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*!|</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, '&'). + replace(/</g, '<'). + replace(/>/g, '>'); + }; + + 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> <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; + } +}