E3CommandParser: Unterschied zwischen den Versionen

Aus Eressea
Zur Navigation springenZur Suche springen
(neue Version)
 
(3 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 13: Zeile 13:


   <nowiki>
   <nowiki>
E3CommandParser.UNIT_LIMIT = 2000;
E3CommandParser.DEFAULT_SUPPLY_PRIORITY = 0;
E3CommandParser.DEFAULT_SUPPLY_PRIORITY = 0;


// hier eigene Parteinummer eingeben
Faction faction = (Faction)helper.getFaction("pnum"));
Faction faction = (Faction)helper.getFaction("pnum"));
(new E3CommandParser(world, helper)).execute(faction);
(new E3CommandParser(world, helper)).execute(faction);


// falls man das TeachPlugin benutzt, kann man es hier direkt mit aufrufen:
// falls man das TeachPlugin benutzt, kann man es hier direkt mit aufrufen:
// (new E3CommandParser(world, helper)).teachAll("namespace");
// (new E3CommandParser(world, helper)).teachAuto(factions);


// falls während der Ausführung der Kommandos ein Fehler auftreten sollte, kann man nach dessen Behebung direkt so bei der Region weitermachen, wo der Fehler auftrat:
// falls während der Ausführung der Kommandos ein Fehler auftreten sollte, kann man nach dessen Behebung direkt so bei der Region weitermachen, wo der Fehler auftrat:
Zeile 30: Zeile 30:
* Dieses Skript speichern und ausführen
* Dieses Skript speichern und ausführen


== Die Befehle ==
== Einheitenbefehle ==


Alle Befehle fangen mit "// $cript" an. Aus der Sicht des Eresseaservers sind es anormale Kommentare und somit erscheinen sie, nachdem man die Befehle verschickt hat, in der nächsten Woche wieder im Report. Bei der Ausführung der Skripte ("execute(...)") werden die Skripte aller Einheiten gelesen und ausgeführt. Die meisten Befehle werden einfach unverändert gelassen und haben zusätzlich irgendeine Auswirkung (meist werden weitere Befehle zur Einheit hinzugefügt). Manchmal wird auch der Befehl selber verändert (wie zum Beispiel beim "auto"-Befehl.


== E3CommandPaser ==
=== auto ===
 
Der einfachste Befehl ist
  // $cript auto [NICHT]|[length [period]]
Damit wird eine Einheit automatisch bestätigt, falls andere Befehle keinen Fehler oder eine Warnung liefern.
  // $cript auto 10
bestätigt die Einheit und verringert die Zahl um 1. Nach der ersten Ausführung der Skripte steht dann also
  // $cript auto 9
da und die Einheit ist bestätigt. Wenn man also das Skript einmal pro Woche ausführt, wird 10 Wochen lang die Einheit bestätigt. Dann steht da
  // $cript auto 0
Wenn die Zahl 0 oder kleiner ist, wird die Einheit nicht bestätigt. "// $cript auto 10" bewirkt also, dass die Einheit 10 Wochen lang bestätigt wird, danach nicht mehr. Sehr praktisch für Einheiten, die zum Beispiel zehn Wochen einfach nur lernen und die Klappe halten sollen.
// $cript auto 10 10
bewirkt das selbe, nur dass nach den zehn Wochen die erste Zahl wieder auf die zweite Zahl gesetz wird. Also nacheinander auf
// $cript auto 9 10 ; bestaetigt
...
// $cript auto 1 10 ; bestaetigt
// $cript auto 0 10 ; nicht bestaetigt
// $cript auto 9 10 ; bestaetigt
Die Einheit wird also nur alle zehn Wochen nicht bestätigt. Praktisch für Einheiten, bei denen man nur ab und zu mal nach dem Rechten sehen will.
// $cript auto NICHT
bewirkt, dass die Einheit auf keinen Fall bestätigt wird.
 
 
=== Versorge ===
 
  // $cript Versorge 100
bewirkt, dass eine Einheit zum "Versorger" wird. Sie wird dann Gegenstände an andere Einheiten abgeben, wenn diese sie brauchen. Die Zahl 100 ist die Priorität. Versorger mit höherer Priorität werden ihre Gegenstände vor Versorgern mit niedrigerer Priorität abgeben. Man kann das auch auf bestimmte Gegenstände beschränken:
  // $crript Versorge Balsam 95
gibt nur Balsam weg. Manche Befehle bewirken implizit, dass Einheiten zu Versorgern werden, zum Beispiel "BerufDepotverwalter"
 
 
=== Benoetige ===
 
  // $cript Benoetige JE 1 Schwert
bewirkt, dass die Einheit 1 Schwert pro Person reserviert oder versucht zu bekommen. Versorger werden ihr diese falls möglich übergeben. Man kann auch eine minimale und eine maximale Menge angeben:
  // $cript Benoetige 10 100 Silber 120
bewirkt, dass die Einheit versucht, 10 Silber (die Minimalmenge) auf jeden Fall zu bekommen. Erst wenn der Bedarf aller anderen Einheiten in der Region erfüllt ist, versucht die Einheit, bis zu 100 Silber (die Maximalmenge) zu bekommen. Die letzte Zahl ist die Priorität. Die Einheit wird die 100 Silber also vor Einheiten mit niedrigerer Priorität bekommen, aber erst nachdem alle Minimalmengen in der Region erfüllt sind.
 
=== BenoetigeFremd ===
 
  // $cript BenoetigeFremd abc 15 Schwert Menge
bewirkt, dass die Einheit abc 15 Schwert benötigt. Der Parameter Menge bewirkt, dass es keine Warnung gibt, falls die Einheit abc nicht da ist. Es gibt aber eine Warnung, falls keine 15 Schwerter vorhanden sind. Der Befehl ist praktisch, um Transportern immer wenn sie da sind 15 Schwerter zu übergeben.
 
=== GibWenn ===
 
  // $cript GibWenn abc 15 Schwert Menge
ist im Grunde nur eine ältere Version von BenoetigeFremd. Ich benutze es nicht mehr, weil es unter bestimmten Umständen Probleme gab.
 
=== +X ===
 
  // $cript +3 Warnungstext
bewirkt, dass in 3 Wochen, eine Zeile mit dem Text "; TODO Warnungstext" in den Einheitenbefehlen erscheint. Der Scriptbefehl selber wird dann gelöscht. Praktisch, wenn man in X Wochen an etwas erinnert werden will.
 
 
=== Weitere nützliche Befehle ===
 
  // $cript Ueberwache
bewirkt, dass die Einheit auf unbekannte Einheiten anderer Parteien achtet. Sie gibt eine Warnung aus, wenn eine solche Einheit in der Region ist. Bekannte Einheiten kann man mit
  // $cript Erlaube pnr enr1 enr2 enr3
wieder ausblenden. Für die Partei mit der Nummer pnr sind in diesem Beispiel die Einheiten enr1, enr2 und enr3 erlaubt und erzeugen keine Warnung.
 
  // $cript Handel x3 ALLES
bewirkt, dass die Einheit (in E2) das 3-fache der Basismenge des angebotenen Luxusguts kauft. Außerdem versucht sie die Maximalmenge aller verfügbaren Luxusgüter zu verkaufen.
 
  // $cript Sammler 10
bewirkt, dass die Einheit MACHN Kräuter ausführt und alle 10 Wochen ein "FORSCHEN Kräuter". Stellt sie dabei fest, dass weniger als "viele" Kröuter vorhanden sind, wird das Sammeln eingestellt.
 
// $cript Ernaehre
veranlasst die Einheit, so gut sie kann Silber zu verdienen.
 
  // $cript Soldat
bewirkt, dass die Einheit Kampftalente und Ausdauer lernt und sich so gut wie möglich mit Waffen und Rüstungen versorgt.
 
=== Meta-Befehle ===
 
  // $cript 3 LERNEN Wahrnehmung
bewirkt erstmal nichts, außer, dass der Befehl zu <code>$cript 2 LERNEN Wahrnehmung</code> verändert wird.
  // $cript 1 LERNEN Wahrnehmung
bewirkt, dass "LERNEN Wahrnehmung" zu den Einheitenbefehlen hinzugefügt wird. Der script-Befehl selbst wird gelöscht.
  // $cript 3 LERNEN Wahrnehmung
bewirkt also, dass die Einheit in 3 Wochen Wahrnehmung lernen wird.
  // $cript 4 $cript Kommando
bewirkt, dass in 4 Wochen der Skriptbefehl Kommando einmal ausgeführt wird.
  // $cript 3 $cript Loeschen
bewirkt, dass die langen Befehle der Einheit in 3 Wochen gelöscht werden.
  // $cript 2 // $cript Kommando
bewirkt, dass in 2 Wochen der Skriptbefehl Kommando dauerhaft zu den Einheitenbefehlen hinzugefügt und ausgeführt wird.
  // $cript 5 5 15 NACH no
bewirkt, dass die Einheit alle 5 Wochen den Befehl "NACH no" bekommt. Nach insgesamt 15 Wochen verschwindet der Befehl.
 
== E3CommandParser ==
 
Und hier das komplette Skript in einer alten aber funktionierenden Version (neueste Version [https://github.com/magellan2/magellan2/tree/master/src-test/magellan/plugin/extendedcommands/scripts bei Github]). Es muss zunächst in die "Library" der ExtendedCommands kopiert werden wie oben beschrieben. 


   <nowiki>
   <nowiki>
// created by E3CommandParser.main() at Wed Apr 23 16:23:13 CEST 2014
// Author : stm
// Author : stm
//
//
Zeile 44: Zeile 135:
// the Free Software Foundation; either version 2 of the License, or
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// (at your option) any later version.
//  
//
// This program is distributed in the hope that it will be useful,
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// but WITHOUT ANY WARRANTY; without even the implied warranty of
Zeile 55: Zeile 146:
import java.io.IOException;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.ArrayList;
Zeile 61: Zeile 154:
import java.util.Collection;
import java.util.Collection;
import java.util.Collections;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
Zeile 67: Zeile 162:
import java.util.LinkedList;
import java.util.LinkedList;
import java.util.List;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.StringTokenizer;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.regex.Pattern;


Zeile 77: Zeile 175:
import magellan.library.Faction;
import magellan.library.Faction;
import magellan.library.GameData;
import magellan.library.GameData;
import magellan.library.Island;
import magellan.library.Item;
import magellan.library.Item;
import magellan.library.LuxuryPrice;
import magellan.library.LuxuryPrice;
import magellan.library.Order;
import magellan.library.Order;
import magellan.library.Orders;
import magellan.library.Region;
import magellan.library.Region;
import magellan.library.Rules;
import magellan.library.Rules;
import magellan.library.Ship;
import magellan.library.Ship;
import magellan.library.Sign;
import magellan.library.Skill;
import magellan.library.Skill;
import magellan.library.StringID;
import magellan.library.StringID;
import magellan.library.Unit;
import magellan.library.Unit;
import magellan.library.UnitID;
import magellan.library.completion.OrderParser;
import magellan.library.completion.OrderParser;
import magellan.library.gamebinding.EresseaConstants;
import magellan.library.gamebinding.EresseaConstants;
import magellan.library.gamebinding.EresseaRelationFactory;
import magellan.library.gamebinding.MovementEvaluator;
import magellan.library.gamebinding.RulesException;
import magellan.library.gamebinding.RulesException;
import magellan.library.rules.Date;
import magellan.library.rules.ItemCategory;
import magellan.library.rules.ItemCategory;
import magellan.library.rules.ItemType;
import magellan.library.rules.ItemType;
Zeile 94: Zeile 199:
import magellan.library.rules.SkillType;
import magellan.library.rules.SkillType;
import magellan.library.utils.CollectionFactory;
import magellan.library.utils.CollectionFactory;
import magellan.library.utils.MagellanFactory;
import magellan.library.utils.OrderToken;
import magellan.library.utils.OrderedHashtable;
import magellan.library.utils.Resources;
import magellan.library.utils.Resources;
import magellan.library.utils.Units;
import magellan.library.utils.Utils;
import magellan.library.utils.Utils;
import magellan.library.utils.logging.Logger;
import magellan.library.utils.logging.Logger;
import magellan.plugin.extendedcommands.ExtendedCommandsHelper;
import magellan.plugin.extendedcommands.ExtendedCommandsHelper;


// ---uncommented for BeanShell
// ---start uncomment for BeanShell
import magellan.library.*;
// import magellan.library.*;
import java.util.*;
// import java.util.*;
// ---stop uncomment for BeanShell
// ---stop uncomment for BeanShell


Zeile 111: Zeile 220:
  * <code>(new E3CommandParser(world, helper)).execute(helper.getFaction("drac"), (Region) container);</code>
  * <code>(new E3CommandParser(world, helper)).execute(helper.getFaction("drac"), (Region) container);</code>
  * " (for just this region).
  * " (for just this region).
  *  
  *
  * @author stm
  * @author stm
  */
  */
public class E3CommandParser {
class E3CommandParser {


   /** A standard soldier's endurance skill should be this fraction of his (first row) weapon skill */
   static class Flag {
  public static float ENDURANCERATIO_FRONT = .6f;
    boolean positive;
  /** A standard soldier's endurance skill should be this fraction of his (second row) weapon skill */
    String name;
  public static float ENDURANCERATIO_BACK = .35f;
    int value;
 
    public Flag(boolean positive, String name, int value) {
      this.positive = positive;
      this.name = name;
      this.value = value;
    }


  /** Unit limit, used to warn if we get too many units. */
    @Override
  public static int UNIT_LIMIT = 250;
    public String toString() {
      return name + "(" + positive + "," + value + ")";
    }


   /** If this is true, some more hints will be added to the orders if expected units are missing */
   }
  public static boolean ADD_NOT_THERE_INFO = false;


   /**
   static class Warning {
  * If this is > 0, all units are suppliers, otherwise suppliers must be set with Versorge (the
    // warning constants
  * default)
    /** The NEVER warning type token */
  */
    public static String W_NEVER = "nie";
  public static int DEFAULT_SUPPLY_PRIORITY = 0;
    /** The SKILL warning type token */
    public static String W_SKILL = "Talent";
    /** The WEAPON warning type token */
    public static String W_WEAPON = "Waffe";
    /** The SHIELD warning type token */
    public static String W_SHIELD = "Schilde";
    /** The ARMOR warning type token */
    public static String W_ARMOR = "Rüstung";
    /** The UNIT warning type token */
    public static final String W_UNIT = "Einheit";
    /** The AMOUNT warning type token */
    public static final String W_AMOUNT = "Menge";
    /** The HIDDEN warning type token */
    public static String W_HIDDEN = "versteckt";
    /** The ALWAYS warning type token */
    public static final String W_ALWAYS = "immer";
    /** The ALWAYS warning type token */
    public static final String W_FOREIGN = "fremd";
 
    /** warning constants */
    protected static final int C_AMOUNT = 1, C_UNIT = 1 << 1, C_HIDDEN = 1 << 2, C_FOREIGN = 1 << 3,
        C_WEAPON = 1 << 5, C_ARMOR = 1 << 6, C_SHIELD = 1 << 7, C_SKILL = 1 << 8;
 
    private List<Flag> flags = new ArrayList<Flag>();
 
    public static final Flag[] ALL_FLAGS = new Flag[8];
 
    private static void initFlags() {
      ALL_FLAGS[0] = new Flag(true, W_AMOUNT, C_AMOUNT);
      ALL_FLAGS[1] = new Flag(true, W_ARMOR, C_ARMOR);
      ALL_FLAGS[2] = new Flag(false, W_FOREIGN, C_FOREIGN);
      ALL_FLAGS[3] = new Flag(false, W_HIDDEN, C_HIDDEN);
      ALL_FLAGS[4] = new Flag(true, W_SHIELD, C_SHIELD);
      ALL_FLAGS[5] = new Flag(true, W_SKILL, C_SKILL);
      ALL_FLAGS[6] = new Flag(true, W_UNIT, C_UNIT);
      ALL_FLAGS[7] = new Flag(true, W_WEAPON, C_WEAPON);
    }
 
    public static boolean initialized = false;
    public static Map<String, Flag> NAMES;
 
    public Warning(boolean all) {
      this(new String[] { W_NEVER });
      if (all) {
        setAll();
      }
    }


  /** default need priority */
    public Warning(String[] tokens) {
  private static final int DEFAULT_PRIORITY = 100;
      /** Bean Shell does not know static initializers, so this is a bit awkward */
  /** need priority for GibWenn command */
      if (!initialized) {
  private static final int GIB_WENN_PRIORITY = DEFAULT_PRIORITY;
        initStatic();
  /** need priority for Depot command and silver */
      }
  private static final int DEPOT_SILVER_PRIORITY = 150;
      parse(tokens);
  /** need priority for earn command */
    }
  private static final int DEFAULT_EARN_PRIORITY = 200;
  /** need priority for Depot command and other items */
  private static final int DEPOT_PRIORITY = -1;
  /** need priority for traders */
  private static final int TRADE_PRIORITY = DEFAULT_PRIORITY;


  /** All script commands begin with this text. */
    protected void initStatic() {
  public static final String scriptMarker = "$cript";
      initialized = true;
  /** The GIVE order */
      initFlags();
  public static String GIVEOrder = "GIB";
      NAMES = new HashMap<String, Flag>();
  /** The RESERVE order */
      for (Flag f : ALL_FLAGS) {
  public static String RESERVEOrder = "RESERVIERE";
        NAMES.put(f.name, f);
  /** The EACH order parameter */
      }
  public static String EACHOrder = "JE";
    }
  /** The ALL order parameter */
  public static String ALLOrder = "ALLES";
  /** The KRÄUTER order parameter */
  public static String KRAUTOrder = "KRAUT";
  /** The LUXUS order parameter */
  public static String LUXUSOrder = "LUXUS";
  /** The LUXUS order parameter */
  public static String TRANKOrder = "TRANK";
  /** The persistent comment order */
  public static String PCOMMENTOrder = EresseaConstants.O_PCOMMENT;
  /** The persistent comment order */
  public static String COMMENTOrder = EresseaConstants.O_COMMENT;
  /** The LEARN order */
  public static String LEARNOrder = "LERNE";
  /** The TEACH order */
  public static String TEACHOrder = "LEHRE";
  /** The ENTERTAIN order */
  private static String ENTERTAINOrder = "UNTERHALTE";
  /** The TAX order */
  private static String TAXOrder = "TREIBE";
  /** The WORK order */
  private static String WORKOrder = "ARBEITE";
  /** The BUY order */
  private static String BUYOrder = "KAUFE";
  /** The SELL order */
  private static String SELLOrder = "VERKAUFE";
  /** The MAKE order */
  private static String MAKEOrder = "MACHE";
  /** The NACH order */
  private static String MOVEOrder = "NACH";
  /** The ROUTE order */
  private static String ROUTEOrder = "ROUTE";
  /** The PAUSE order */
  private static String PAUSEOrder = "PAUSE";
  /** The RESEARCH order */
  private static String RESEARCHOrder = "FORSCHE";
  /** The RECRUIT order */
  private static String RECRUITOrder = "REKRUTIERE";


  // warning constants
    protected void setAll() {
  /** The NEVER warning type token */
      for (Flag f : ALL_FLAGS) {
  public static String W_NEVER = "nie";
        add(f);
  /** The SKILL warning type token */
      }
  public static String W_SKILL = "Talent";
    }
  /** The WEAPON warning type token */
 
  public static String W_WEAPON = "Waffe";
    private void setAll(boolean positive) {
  /** The SHIELD warning type token */
      for (Flag f : ALL_FLAGS) {
  public static String W_SHIELD = "Schild";
        if (f.positive == positive) {
  /** The ARMOR warning type token */
          add(f);
  public static String W_ARMOR = "Rüstung";
        }
  /** The UNIT warning type token */
      }
  public static final String W_UNIT = "Einheit";
    }
  /** The AMOUNT warning type token */
  public static final String W_AMOUNT = "Menge";
  /** The HIDDEN warning type token */
  public static String W_HIDDEN = "versteckt";
  /** The ALWAYS warning type token */
  public static final String W_ALWAYS = "immer";
  /** The ALWAYS warning type token */
  public static final String W_FOREIGN = "fremd";
  /** The BEST token (for soldier) */
  public static String BEST = "best";
  /** The NULL token (for soldier) */
  public static String NULL = "null";
  /** The NOT token (for auto and others) */
  public static String NOT = "NICHT";


  /** The LONG token (for clear) */
    protected String[] parse(String[] tokens) {
  public static String LONG = "$lang";
      flags = new ArrayList<Flag>();
  /** The SHORT token (for clear) */
      if (tokens == null)
  public static String SHORT = "$kurz";
        return null;
  /** The COMMENT token (for clear) */
  public static String COMMENT = "$kommentar";


  private static String S_ENDURANCE = EresseaConstants.S_AUSDAUER.toString();
      if (tokens.length == 0) {
        for (Flag f : ALL_FLAGS) {
          add(f);
        }
      }


  /** warning constants */
      int i = tokens.length - 1;
  protected static final int C_AMOUNT = 1, C_UNIT = 1 << 1, C_HIDDEN = 1 << 2, C_FOREIGN = 1 << 3,
      boolean hasPositive = false;
      C_WEAPON = 1 << 5, C_ARMOR = 1 << 6, C_SHIELD = 1 << 7, C_SKILL = 1 << 8;
      setAll(false);
      for (; i >= 0; --i)
        if (NAMES.containsKey(tokens[i])) {
          Flag f = NAMES.get(tokens[i]);
          if (f.positive) {
            hasPositive = true;
            add(f);
          } else {
            remove(f);
          }
        } else if (W_ALWAYS.equals(tokens[i])) {
          flags = new ArrayList<Flag>();
          setAll();
        } else if (W_NEVER.equals(tokens[i])) {
          flags = new ArrayList<Flag>();
          hasPositive = true;
        } else {
          break;
        }
      if (!hasPositive) {
        setAll(true);
      }


  private OrderParser parser;
      if (i == tokens.length - 1)
  private Logger log;
        return tokens;
      return Arrays.copyOf(tokens, i + 1);
    }


  /**
    public void add(String name) {
  * Creates and initializes the parser.
      if (NAMES.containsKey(name)) {
  *
        add(NAMES.get(name));
  * @param world
      }
  * @param helper
    }
  */
  public E3CommandParser(GameData world, ExtendedCommandsHelper helper) {
    E3CommandParser.world = world;
    E3CommandParser.helper = helper;
    parser = world.getGameSpecificStuff().getOrderParser(world);
    log = Logger.getInstance("E3CommandParser");
  }


  protected OrderParser getParser() {
    public void add(Flag f) {
    return parser;
      if (!flags.contains(f)) {
  }
        flags.add(f);
      }
    }


  // variables available in scripts; this is here mainly to be able to test this class outside
    public void remove(Flag f) {
  // BeanShell
      flags.remove(f);
  static GameData world;
    }
  static ExtendedCommandsHelper helper;


  private Unit someUnit;
    public boolean contains(String w) {
      for (Flag f : flags)
        if (f.name.equals(w))
          return true;
      return false;
    }


  // variables for current script state
    public boolean contains(int flag) {
//  private Map<Faction, Integer> currentFactions = CollectionFactory.createOrderedMap();
      for (Flag f : flags)
  private Map currentFactions = CollectionFactory.createOrderedMap();
        if ((f.value & flag) != 0)
  private Region currentRegion;
          return true;
  private Unit currentUnit;
      return false;
//  private ArrayList<String> newOrders;
    }
  private ArrayList newOrders;
  private String currentOrder;
  private int line;
  private int error;
  private boolean changedOrders;


  /**
    @Override
  * The item/unit/need map. Stores all needed items.
    public String toString() {
  */
      return flags.toString();
//  protected Map<String, Map<Integer, Map<Unit, Need>>> needMap;
    }
  protected Map needMap;


   /**
   }
  * The item/unit/supply map. Stores all available items.
  */
//  protected Map<String, Map<Unit, Supply>> supplyMap;
  protected Map supplyMap;


   /**
   static class SupplyMap {
  * Lines matching these patterns should be removed.
    Map<String, Map<Unit, Supply>> supplyMap;
  */
//  protected List<String> removedOrderPatterns;
  protected List removedOrderPatterns;


  /**
    public SupplyMap(E3CommandParser parser) {
  * Lists of allowed units for Erlaube/Ueberwache
      supplyMap = new LinkedHashMap<String, Map<Unit, Supply>>();
  */
    }
//  protected Map<Faction, Set<Unit>> allowedUnits = new HashMap<Faction, Set<Unit>>();
  protected Map allowedUnits = new HashMap();
  /**
  * Lists of required units for Verlange/Ueberwache
  */
//  protected Map<Faction, Set<Unit>> requiredUnits = new HashMap<Faction, Set<Unit>>();
  protected Map requiredUnits = new HashMap();


  /**
    public Collection<String> items() {
  * Current state for the Loesche command
      return supplyMap.keySet();
  */
    }
  protected String clear = null;
  /** Current prefix for the Loesche command */
  protected String clearPrefix = "";


  private int progress = -1;
    public int getSupply(String item) {
      Map<Unit, Supply> map = supplyMap.get(item);
      int goodAmount = 0;
      if (map != null) {
        for (Supply s : map.values()) {
          if (s.hasPriority()) {
            goodAmount += s.getAmount();
          }
        }
      }
      return goodAmount;
    }


  /**
    public Supply get(String item, Unit unit) {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      Map<Unit, Supply> map = supplyMap.get(item);
  * container to execute all unit commands.
      if (map == null)
  *
        return null;
  * @param faction scripts for all units of this faction
      return map.get(unit);
  * @throws NullPointerException if <code>faction == null</code>
    }
  */
  public void execute(Faction faction) {
    executeFrom(Collections.singleton(faction), null, null);
  }


  /**
    public Collection<Supply> get(String item) {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      Map<Unit, Supply> result = supplyMap.get(item);
  * container to execute all unit commands.
      if (result == null)
  *
        return Collections.emptyList();
  * @param factions scripts for all units of all factions in this set
      return result.values();
  * @throws NullPointerException if <code>faction == null</code>
    }
  */
 
//  public void execute(Collection<Faction> factions) {
    public void sortByPriority() {
  public void execute(Collection factions) {
      for (String item : supplyMap.keySet()) {
    executeFrom(factions, null, null);
        // sort supplies by priority
  }
        Map<Unit, Supply> itemSupply = supplyMap.get(item);
        if (itemSupply == null) {
          itemSupply = Collections.emptyMap();
        } else {
          Supply[] sorted = null;
          sorted = itemSupply.values().toArray(new Supply[0]);


  /**
          // this causes problems in BeansShell; I don't know why
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
          // Arrays.sort(sorted);
  * container to execute all unit commands.
          // doing insertion sort instead
  *
          sort(sorted);
  * @param faction scripts for all units of this faction are executed
  * @param region only commands of unit in this region are executed
  * @throws NullPointerException if <code>faction == null</code>
  */
  public void execute(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();


    executeFrom(faction, region, region);
          itemSupply.clear();
  }
          for (Supply supply : sorted) {
            itemSupply.put(supply.getUnit(), supply);
          }
        }
      }
    }


  /**
    private void sort(Supply[] sorted) {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      for (int j = 1; j < sorted.length; ++j) {
  * container to execute all unit commands. Ignore regions before first (in the report order).
        for (int i = 0; i < j; ++i) {
  *
          if (sorted[j].compareTo(sorted[i]) < 0) {
  * @param faction scripts for all units of this faction are executed
            // if (current.priority > sorted[i].priority) {
  * @param region only commands of unit in this region are executed, may be <code>null</code> to
            Supply temp = sorted[i];
  *          execute for all regions.
            sorted[i] = sorted[j];
  * @param first First region to execute, may be <code>null</code> to not ignore any regions
            sorted[j] = temp;
  * @throws NullPointerException if <code>faction == null</code>
          }
  */
        }
  public void executeFrom(Faction faction, Region region, Region first) {
      }
    executeFrom(Collections.singleton(faction), region, first);
    }
  }
 
    public Supply put(String item, Unit unit, int amount, long serial) {
      Map<Unit, Supply> itemSupplyMap = supplyMap.get(item);
      if (itemSupplyMap == null) {
        itemSupplyMap = new LinkedHashMap<Unit, Supply>();
        supplyMap.put(item, itemSupplyMap);
      }
      Supply result = new Supply(unit, item, amount, serial);
      itemSupplyMap.put(unit, result);
      return result;
    }


  /**
    public void clear() {
  * Parses scripts and confirms units according to the "confirm" tag. Ignore regions before first
      supplyMap.clear();
  * (in the report order).
     }
  *
  * @param factions scripts for all units of all factions in this set are executed
  * @param region only commands of unit in this region are executed, may be <code>null</code> to
  *          execute for all regions.
  * @param first First region to execute, may be <code>null</code> to not ignore any regions
  * @throws NullPointerException if <code>faction == null</code>
  */
//  public void executeFrom(Collection<Faction> factions, Region region, Region first) {
  public void executeFrom(Collection factions, Region region, Region first) {
     if (factions == null || factions.isEmpty())
      throw new NullPointerException();


     currentFactions = CollectionFactory.createOrderedMap(1);
     @Override
     for (Faction f : factions) {
     public String toString() {
       currentFactions.put(f, null);
       return supplyMap.toString();
     }
     }
  }


    helper.getUI().setMaximum(world.getRegions().size() + 4);
  interface ReserveVisitor {
     helper.getUI().setProgress("init", ++progress);
     public void execute(Unit u, String item, int amount);
  }


    // comment out the following two lines if you don't have the newest nighthly build of Magellan
  static class Reserves {
    helper.getUI().setProgress("preprocessing", ++progress);


     findSomeUnit(currentFactions);
     Map<String, Map<Unit, Integer>> reserves = new HashMap<String, Map<Unit, Integer>>();


     initLocales();
     public void add(String item, Unit unit, int amount) {
      E3CommandParser.increaseMultiInv(reserves, item, unit, amount);
    }
 
    public void execute(ReserveVisitor reserveVisitor) {
      for (String item : reserves.keySet()) {
        Map<Unit, Integer> iMap = reserves.get(item);
        for (Unit u : iMap.keySet()) {
          reserveVisitor.execute(u, item, iMap.get(u));
        }


    for (Faction faction : factions) {
      if (faction.units().size() >= UNIT_LIMIT) {
        addWarning(someUnit, "Einheitenlimit erreicht (" + faction.units().size() + "/"
            + UNIT_LIMIT + ")! ");
      } else if (faction.units().size() * 1.1 > UNIT_LIMIT) {
        addWarning(someUnit, "Einheitenlimit fast erreicht (" + faction.units().size() + "/"
            + UNIT_LIMIT + ")! ");
       }
       }
     }
     }
  }


     collectStats();
  static class SkillSpec {
 
     public SkillType skill;
    public int level;
    public int max;
 
    public SkillSpec(SkillType skill, int level, int max) {
      if (skill == null || level <= 0 || max <= 0)
        throw new IllegalArgumentException("invalid SkillSpec " + skill + " " + level + " " + max);
      this.skill = skill;
      this.level = level;
      this.max = max;
    }
 
    @Override
    public String toString() {
      return skill.getName() + " " + level + "/" + max;
    }
  }
 
  public static class ItemSorter implements Comparator<Item> {
 
    private String[] priorities;
 
    public ItemSorter(String[] priorities) {
      this.priorities = priorities;
    }


     // Parses the orders of the units for commands of the form "// $cript ..." and
     public int compare(Item o1, Item o2) {
    // tries to execute them.
       int p1 = 0, p2 = 0;
    if (region == null) {
      String t1 = o1.getItemType().getOrderName();
       boolean go = first == null;
       for (String type : priorities) {
       for (Region r : world.getRegions()) {
         if (t1.equals(type)) {
         if (r == first) {
           break;
           go = true;
         }
         }
         if (go) {
         p1++;
          execute(r);
      }
      if (p1 == priorities.length) {
        p1 = -1;
      }
      String t2 = o2.getItemType().getOrderName();
      for (String type : priorities) {
        if (t2.equals(type)) {
          break;
         }
         }
        p2++;
       }
       }
    } else {
      if (p2 == priorities.length) {
      execute(region);
        p2 = -2;
      }
      if (p1 == p2)
        if (t1.compareTo(t2) == 0)
          return o2.getAmount() - o1.getAmount();
        else
          return t1.compareTo(t2);
      return p1 - p2;
     }
     }


    // comment out the following line if you don't have the newest nightly build of Magellan
  }
    helper.getUI().setProgress("postprocessing", ++progress);


    for (Faction faction : factions) {
  /**
      for (Unit u : faction.units()) {
  * A standard soldier's endurance skill should be this fraction of his (first row) weapon skill
        if ("1".equals(getProperty(u, "confirm"))) {
  */
          u.setOrdersConfirmed(true);
  public static float ENDURANCERATIO_FRONT = .6f;
          // u.addOrder("; autoconfirmed");
  /**
        } else if ("0".equals(getProperty(u, "confirm"))) {
  * A standard soldier's endurance skill should be this fraction of his (second row) weapon skill
          u.setOrdersConfirmed(false);
  */
        }
  public static float ENDURANCERATIO_BACK = .35f;
        notifyMagellan(u);
 
      }
  /** Unit limit, used to warn if we get too many units. */
    }
   public static int UNIT_LIMIT = 250;
    // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("ready", ++progress);
   }


//  private void findSomeUnit(Map<Faction, Integer> factions) {
   /** If this is true, some more hints will be added to the orders if expected units are missing */
   private void findSomeUnit(Map factions) {
  public static boolean ADD_NOT_THERE_INFO = false;
    // sometimes we need an arbitrary unit. This is a shorthand for it.
    for (Faction faction : factions.keySet())
      if (!faction.units().isEmpty()) {
        someUnit = faction.units().iterator().next();
        break;
      }


    if (someUnit == null)
  /** Set to something in order to use TeachPlugin */
      throw new RuntimeException("No units in report!");
  public static String TEACH_PREFIX = null;
  /** The skills learned by helmspeople, see {@link #commandHelmsman(String[])}. */
  public static String LEARN_HELMSMAN = "$tm$ 100.0 Segeln 25 12 Schiffbau 8 4 Unterhaltung 6 5";


    currentUnit = someUnit;
  /**
   }
  * soldier parameters for helmspeople, see {@link #commandHelmsman(String[])} and {@link #commandSoldier(String[])}
  */
  public static String SOLDIER_HELMSMAN = "best best null null";
  /** The skills learned by crew, see {@link #commandCrew(String[])}. */
  public static String LEARN_CREW =
      "$tm$ 100.0 Segeln 25 12 Schiffbau 10 4 Unterhaltung 9 5 Reiten 4 2 Holzfällen 4 2 Pferdedressur 4 2";
   /** soldier parameters for crew, see {@link #commandCrew(String[])} and {@link #commandSoldier(String[])} */
  public static String SOLDIER_CREW = "best best null null";
  /** the learn level set by commands like soldier */
  public static final int DEFAULT_LEARNLEVEL = 20;


   protected void execute(Region region) {
   /** preferred level for quartermasters */
    try {
  public static final int QM_TACTICS = 5;
      currentRegion = region;
      initSupply();


      for (Unit u : region.units()) {
  /**
        if (currentFactions.containsKey(u.getFaction())) {
  * If this is &gt; 0, all units are suppliers, otherwise suppliers must be set with Versorge (the
          currentUnit = u;
  * default)
          // comment out the following line if you don't have the newest nightly build of Magellan
  */
          helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
  public static int DEFAULT_SUPPLY_PRIORITY = 0;


          try {
  /** default need priority */
            parseScripts();
  private static final int DEFAULT_PRIORITY = 100;
          } catch (RuntimeException e) {
  /** need priority for GibWenn command */
            addWarning(u, "error " + e.getClass().getSimpleName());
  private static final int GIVE_IF_PRIORITY = 999999;
            log.error(e.getClass().getSimpleName() + " script error for " + u);
  /** need priority for Depot command and silver */
            throw new RuntimeException("script error for " + u, e);
  private static final int DEPOT_SILVER_PRIORITY = 150;
          }
  /** need priority for earn command */
        }
  private static final int DEFAULT_EARN_PRIORITY = 200;
      }
  /** need priority for Depot command and other items */
      // comment out the following line if you don't have the newest nightly build of Magellan
  private static final int DEPOT_PRIORITY = -1;
      helper.getUI().setProgress(region.toString() + " - postprocessing", ++progress);
  /** need priority for traders */
  private static final int TRADE_PRIORITY = DEFAULT_PRIORITY;


      satisfyNeeds();
  /** All script commands begin with this text. */
    } catch (RuntimeException e) {
  public static final String scriptMarker = "$cript";
      e.printStackTrace();
      // log.error(e.getClass().getSimpleName() + " script error for " + region);
      throw new RuntimeException("script error for " + region, e);
    }
    // refresh relations, just in case
    region.refreshUnitRelations(true);
  }


   /**
   /** The GIVE order */
  * Adds some statistic information to the orders of the first unit.
  public static String GIVEOrder = "GIB";
  *  
  /** The RESERVE order */
  * @param region
   public static String RESERVEOrder = "RESERVIERE";
  */
  /** The EACH order parameter */
   protected void collectStats() {
  public static String EACHOrder = "JE";
    int buildingScripts = 0;
  /** The ALL order parameter */
    int shipScripts = 0;
  public static String ALLOrder = "ALLES";
    int unitScripts = 0;
  /** The KRÄUTER order parameter */
    int regionScripts = 0;
  public static String KRAUTOrder = "KRAUT";
    // comment out the following lines if you don't have the newest nightly build of Magellan
  /** The LUXUS order parameter */
    for (Building b : world.getBuildings()) {
  public static String LUXUSOrder = "LUXUS";
      if (helper.hasScript(b)) {
  /** The LUXUS order parameter */
        buildingScripts++;
  public static String TRANKOrder = "TRANK";
      }
  /** The "on foot" order */
    }
  public static String FOOTOrder = "FUSS";
    for (Ship s : world.getShips()) {
  /** The "on horse" order */
      if (helper.hasScript(s)) {
  public static String HORSEOrder = "PFERD";
        shipScripts++;
  /** The "on ship" order */
      }
  public static String SHIPOrder = "SCHIFF";
    }
  /** The item "horses" */
    for (Unit u : world.getUnits()) {
  public static String HORSEItem = "Pferd";
      if (helper.hasScript(u)) {
   /** The persistent comment order */
        u.addOrder(COMMENTOrder + " hat Skript", false);
  public static String PCOMMENTOrder = EresseaConstants.O_PCOMMENT;
        unitScripts++;
   /** The persistent comment order */
      }
  public static String COMMENTOrder = EresseaConstants.O_COMMENT;
    }
  /** The LEARN order */
    for (Region r : world.getRegions()) {
  public static String LEARNOrder = "LERNE";
      if (helper.hasScript(r)) {
  /** The (TEACH) AUTO order */
        regionScripts++;
  public static String AUTOOrder = "AUTO";
      }
  /** The TEACH order */
    }
  public static String TEACHOrder = "LEHRE";
 
  /** The ENTERTAIN order */
    someUnit.addOrderAt(0, "; " + unitScripts + " unit scripts, " + buildingScripts
  private static String ENTERTAINOrder = "UNTERHALTE";
        + " building scripts, " + shipScripts + " ship scripts, " + regionScripts
  /** The TAX order */
        + " region scripts", true);
  private static String TAXOrder = "TREIBE";
   }
  /** The WORK order */
 
  private static String WORKOrder = "ARBEITE";
   /**
  /** The BUY order */
  * Parses the orders of the unit u for commands of the form "// $cript ..." and tries to execute
  private static String BUYOrder = "KAUFE";
  * them. Known commands:<br />
  /** The SELL order */
  * <tt>// $cript +X text</tt> -- If X<=1 then a warning containing text is added to the unit's
  private static String SELLOrder = "VERKAUFE";
  * orders. Otherwise X is decreased by one.<br />
  /** The MAKE order */
  * <code>// $cript [rest [period [length]] text</code> -- Adds text (or commands) to the orders<br />
   private static String MAKEOrder = "MACHE";
  * <code>// $cript auto [NICHT]|[length [period]]</code> -- autoconfirm orders<br />
  /** The NACH order */
  * <code>// $cript Loeschen [$kurz] [<prefix>]</code> -- clears orders except comments<br />
  private static String MOVEOrder = "NACH";
  * <code>// $cript GibWenn receiver [JE] amount item [warning]</code> -- add give order (if
  /** The ROUTE order */
  * possible)<br />
  private static String ROUTEOrder = "ROUTE";
  * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
  /** The PAUSE order */
  * <code>// $cript Benoetige JE amount item [priority]</code><br />
  private static String PAUSEOrder = "PAUSE";
  * <code>// $cript Benoetige ALLES [item] [priority]</code> -- acquire things from other units<br />
  /** The RESEARCH order */
  * <code>// $cript BenoetigeFremd unit [JE] minAmount [maxAmount] item [priority] [warning]</code><br />
  private static String RESEARCHOrder = "FORSCHE";
  * <code>// $cript Versorge [[item]...] priority</code> -- set supply priority.<br />
  /** The RECRUIT order */
  * <code>// $cript BerufDepotVerwalter [Zusatzbetrag]</code> Collects all free items in the
  private static String RECRUITOrder = "REKRUTIERE";
  * region, Versorge 100, calls Ueberwache<br />
  * <code>// $cript Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code>
  * -- learn skill and reserve equipment<br />
  * <code>// $cript Lerne Talent1 Stufe1 [[Talent2 Stufe2]...]</code> -- learn skills in given
  * ratio <br />
  * <code>// $cript BerufBotschafter [Talent]</code> -- earn money if necessary, otherwise learn
  * skill<br />
  * <code>// $cript Ueberwache</code> -- look out for unknown units<br />
  * <code>// $cript Erlaube faction unit [unit...]</code> -- allow units for Ueberwache<br />
  * <code>// $cript Verlange faction unit [unit...]</code> -- allow and require units for
  * Ueberwache<br />
  * <code>// $cript Ernaehre [amount]</code> -- earn money<br />
  * <code>// $cript Handel amount [ALLES | good...]</code> -- trade luxuries<br />
  * <code>// $cript Steuermann minSilver maxSilver</code> -- be responsible for ship<br />
  * <code>// $cript Mannschaft skill</code> -- be crew and learn<br />
  * <code>// $cript Quartiermeister [[amount item]...]</code> -- be lookout<br />
  * <code>// $cript Sammler [interval]</code> -- collect and research HERBS<br />
  * <code>// $cript KrautKontrolle [route]</code> -- FORSCHE KRÄUTER in several regions<br />
  * <code>// $cript RekrutiereMax</code> -- recruit as much as possible<br />
  */
   protected void parseScripts() {
//     newOrders = new ArrayList<String>();
    newOrders = new ArrayList();
//     removedOrderPatterns = new ArrayList<String>();
    removedOrderPatterns = new ArrayList();
    changedOrders = false;
    error = -1;
    // errMsg = null;
    line = 0;
    allowedUnits.clear();
    requiredUnits.clear();
    clear = null;


    // NOTE: must not change currentUnit's orders directly! Always change newOrders!
  /** The BEST token (for soldier) */
    for (Order o : currentUnit.getOrders2()) {
  public static String BEST = "best";
      ++line;
  /** The NULL token (for soldier) */
      currentOrder = o.getText();
  public static String NULL = "null";
      String[] tokens = detectScriptCommand(currentOrder);
  /** The NOT token (for auto and others) */
      if (tokens == null) {
  public static String NOT = "NICHT";
        // add order if
 
        if (shallClear(currentOrder)) {
  /** The LONG token (for clear) */
          addNewOrder(COMMENTOrder + " " + currentOrder, true);
  public static String LONG = "$lang";
        } else {
  /** The SHORT token (for clear) */
          addNewOrder(currentOrder, false);
  public static String SHORT = "$kurz";
        }
  /** The COMMENT token (for clear) */
        currentOrder = null;
  public static String COMMENT = "$kommentar";
      } else {
        // as of Java 7 the first character of an integer may be a '+' sign
        if (!tokens[0].startsWith("+")) {
          try {
            Integer.parseInt(tokens[0]);
            currentOrder = commandRepeat(tokens);
            if (currentOrder == null) {
              tokens = null;
            } else {
              tokens = detectScriptCommand(currentOrder);
              if (tokens == null) {
                addNewOrder(currentOrder, true);
                currentOrder = null;
              }
            }
          } catch (NumberFormatException e) {
            // not a repeating order
          }
        }
        if (tokens != null) {
          String command = tokens[0];
          if (command.startsWith("+")) {
            commandWarning(tokens);
            changedOrders = true;
          } else if (command.equals("KrautKontrolle")) {
            commandControl(tokens);
            changedOrders = true;
          } else if (command.equals("auto")) {
            commandAuto(tokens);
          } else {
            // order remains
            addNewOrder(currentOrder, false);


            if (command.equals("Loeschen")) {
  private static String S_ENDURANCE = EresseaConstants.S_AUSDAUER.toString();
              commandClear(tokens);
            } else if (command.equals("GibWenn")) {
              commandGiveIf(tokens);
            } else if (command.equals("Benoetige") || command.equals("BenoetigeFremd")) {
              commandNeed(tokens);
            } else if (command.equals("Versorge")) {
              commandSupply(tokens);
            } else if (command.equals("BerufDepotVerwalter")) {
              commandDepot(tokens);
            } else if (command.equals("Soldat")) {
              commandSoldier(tokens);
            } else if (command.equals("Lerne")) {
              commandLearn(tokens);
            } else if (command.equals("BerufBotschafter")) {
              commandEmbassador(tokens);
            } else if (command.equals("Ueberwache")) {
              commandMonitor(tokens);
            } else if (command.equals("Erlaube") || command.equals("Verlange")) {
              commandAllow(tokens);
            } else if (command.equals("Ernaehre")) {
              commandEarn(tokens);
            } else if (command.equals("Handel")) {
              commandTrade(tokens);
            } else if (command.equals("Steuermann")) {
              if (tokens.length < 3) {
                addNewError("zu wenige Argumente");
              } else {
                commandNeed(new String[] { "Benoetige", tokens[1], tokens[2], "Silber",
                    String.valueOf(DEFAULT_PRIORITY + 10) });
                setConfirm(currentUnit, false);
              }
            } else if (command.equals("Mannschaft")) {
              if (tokens.length < 3) {
                addNewError("zu wenige Argumente");
              } else {
                commandLearn(new String[] { "Lerne", tokens[1], tokens[2] });
                setConfirm(currentUnit, true);
              }
            } else if (command.equals("Quartiermeister")) {
              commandQuartermaster(tokens);
            } else if (command.equals("Sammler")) {
              commandCollector(tokens);
            } else if (command.equals("RekrutiereMax")) {
              commandRecruit(tokens);
            } else {
              addNewError("unbekannter Befehl: " + command);
            }
          }
          currentOrder = null;


        }
  private static final int TEACH_MULTI = 10;
      }
    }
    if (changedOrders) {
      currentUnit.setOrders(newOrders);
    }
    notifyMagellan(currentUnit);


    newOrders = null;
  private String[] WEAPON_PRIORITIES =
      new String[] {
          "Adamantiumaxt",
          "Laenschwert",
          "Kriegsaxt",
          "Bihänder",
          "Schwert",
          "Rostige~Kriegsaxt",
          "Rostiger~Bihänder",
          "Schartiges~Schwert",
          "Hellebarde",
          "Mallornspeer",
          "Speer",
          "Rostige~Hellebarde",
          "Mallornlanze",
          "Lanze",
          "Mallornarmbrust",
          "Armbrust",
          "Elfenbogen",
          "Mallornbogen",
          "Bogen"
      };
  private String[] SHIELD_PRIORITIES =
      new String[] {
          "Laenschild",
          "Schild",
          "Rostiger~Schild"
      };
  private String[] ARMOR_PRIORITIES =
      new String[] {
          "Adamantiumrüstung",
          "Laenkettenhemd",
          "Plattenpanzer",
          "Kettenhemd",
          "Rostiges~Kettenhemd"
      };


   }
   private OrderParser parser;
  private Logger log;


   /**
   /**
   * If order is a script command ("// $cript ..."), returns a List of the tokens. Otherwise returns
   * Creates and initializes the parser.
   * <code>null</code>. The first in the list is the first token after the "// $cript".
   *
  * @param world
  * @param helper
   */
   */
   protected String[] detectScriptCommand(String order) {
   public E3CommandParser(GameData world, ExtendedCommandsHelper helper) {
     StringTokenizer tokenizer = new StringTokenizer(order, " ");
     E3CommandParser.world = world;
     if (tokenizer.hasMoreTokens()) {
     E3CommandParser.helper = helper;
      String part = tokenizer.nextToken();
    parser = world.getGameSpecificStuff().getOrderParser(world);
      if (part.equals(PCOMMENTOrder) || part.equals(COMMENTOrder)) {
    log = Logger.getInstance("E3CommandParser");
        if (tokenizer.hasMoreTokens()) {
          part = tokenizer.nextToken();
          if (part.equals(scriptMarker)) {
//            List<String> result = new ArrayList<String>();
            List result = new ArrayList();
            while (tokenizer.hasMoreTokens()) {
              result.add(tokenizer.nextToken());
            }
            if (result.size() == 0)
              return null;
            return result.toArray(new String[] {});
          }
        }
      }
    }
    return null;
   }
   }


  /**
   protected OrderParser getParser() {
  * <code>// $cript Loeschen [$kurz] [<prefix>]</code><br />
     return parser;
  * Remove orders except comments from here on. If "$kurz", remove all orders, otherwise only long
  * and permanent (@) orders. If prefix is set, remove only orders starting with that prefix.
  *
  * @param tokens
  */
   protected void commandClear(String[] tokens) {
     clearPrefix = "";
    if (tokens.length == 1) {
      clear = LONG;
    } else {
      if (tokens[1].equalsIgnoreCase(SHORT)) {
        clear = ALLOrder;
        clearPrefix =
            currentOrder.substring(Math.min(currentOrder.length(), currentOrder.indexOf(SHORT)
                + SHORT.length() + 1));
      } else {
        clear = LONG;
        clearPrefix =
            currentOrder.substring(Math.min(currentOrder.length(), currentOrder.indexOf("Loeschen")
                + "Loeschen".length() + 1));
      }
    }
    for (int i = 0; i < newOrders.size(); i++) {
      String order = newOrders.get(i);
      if (shallClear(order)) {
        changedOrders = true;
        newOrders.set(i, COMMENTOrder + " " + order);
      }
    }
   }
   }


   protected boolean shallClear(String order) {
   // variables available in scripts; this is here mainly to be able to test this class outside
    if (clear == null)
  // BeanShell
      return false;
  static GameData world;
    String trimmed = order.trim();
  static ExtendedCommandsHelper helper;


    return !trimmed.startsWith(PCOMMENTOrder)
  private Unit someUnit;
        && !trimmed.startsWith(COMMENTOrder)
 
        && (clear == ALLOrder || (clear == LONG && world.getGameSpecificStuff().getOrderChanger()
  // variables for current script state
            .isLongOrder(order)))
  private Map<Faction, Integer> currentFactions = CollectionFactory.createOrderedMap();
        && (clearPrefix == null || clearPrefix.length() == 0 || trimmed.startsWith(clearPrefix));
  private Region currentRegion;
   }
  private Unit currentUnit;
  private ArrayList<String> newOrders;
  private String currentOrder;
  private int line;
  private int error;
   private boolean changedOrders;


   /**
   /**
   * <code>// $cript [rest [period [length]]] text</code><br />
   * The item/unit/need map. Stores all needed items.
  * Adds text (or commands) to the orders after rest rounds. If <code>rest==1</code>, text is added
   */
  * to the unit's orders after the current order. If text is a script order itself, it will be
  protected List<Need> needQueue;
   * executed. If period is set, rest will be reset to period and the modified order added instead
  /**
  * of the current order. If <code>rest>1</code>, it is decreased and the modified order added
   * The item/unit/supply map. Stores all available items.
  * instead of the current one. If length is given, the whole order will be removed after length
  * rounds.
   *  
  * @param tokens
  * @return <code>text</code>, if <code>rest==1</code>, otherwise <code>null</code>
   */
   */
   protected String commandRepeat(String[] tokens) {
   protected SupplyMap supplyMapp;
    StringBuilder result = null;
 
    try {
  protected Map<Unit, Integer> capacities;
      int rest = Integer.parseInt(tokens[0]);
 
      int period = 0;
  private Map<String, Unit> dummyUnits;
      int length = Integer.MAX_VALUE;
  private Map<Unit, Map<String, Integer>> transfersMap;
      int textIndex = 1;
  private List<Transfer> transferList;
      if (tokens.length >= 2) {
 
        try {
  /**
          period = Integer.parseInt(tokens[1]);
  * Lines matching these patterns should be removed.
          textIndex = 2;
  */
          if (tokens.length >= 3) {
  protected List<String> removedOrderPatterns;
            try {
 
              length = Integer.parseInt(tokens[2]);
  /**
              textIndex = 3;
  * Lists of allowed units for Erlaube/Ueberwache
            } catch (NumberFormatException nfe) {
  */
              // third argument not a number
  protected Map<Faction, Set<UnitID>> allowedUnits = new HashMap<Faction, Set<UnitID>>();
              length = Integer.MAX_VALUE;
  /**
              textIndex = 2;
  * Lists of required units for Verlange/Ueberwache
            }
  */
          }
  protected Map<Faction, Set<UnitID>> requiredUnits = new HashMap<Faction, Set<UnitID>>();
        } catch (NumberFormatException nfe) {
 
          // second argument not a number
  /**
          period = 0;
  * Current state for the Loesche command
          textIndex = 1;
  */
        }
  protected String clear = null;
      }
  /** Current prefix for the Loesche command */
      if (rest == 1) {
  protected String clearPrefix = "";
        result = new StringBuilder();
 
        if (period > 0) {
  private int progress = -1;
          rest = period + 1;
  private long supplySerial = 0;
        }
  private int showStats = 1;
        if (textIndex < tokens.length && tokens[textIndex].equals(scriptMarker)) {
  private String cachedScriptCommand;
          result.append(COMMENTOrder).append(" ");
  private Map<Unit, Collection<SkillSpec>> skills = new HashMap<Unit, Collection<SkillSpec>>();
        }
  private Map<String, Consumer<String[]>> commands;
        for (int i = textIndex; i < tokens.length; ++i) {
 
          if (i > textIndex) {
  public int getShowStats() {
            result.append(" ");
    return showStats;
          }
  }
          result.append(tokens[i]);
        }
      } else if (length == 0) {
        // return empty string to signal success
        result = new StringBuilder();
      }
      if ((rest > 1 || period > 0) && length > 0) {
        StringBuilder newOrder = new StringBuilder();
        newOrder.append(PCOMMENTOrder).append(" ").append(scriptMarker);
        newOrder.append(" ").append(rest - 1);
        if (period > 0) {
          newOrder.append(" ").append(period);
        }
        if (length != Integer.MAX_VALUE) {
          newOrder.append(" ").append(length - 1);
        }
        for (int i = textIndex; i < tokens.length; ++i) {
          newOrder.append(" ").append(tokens[i]);
        }
        addNewOrder(newOrder.toString(), true);
      }
    } catch (NumberFormatException e) {
      addNewOrder(currentOrder, false);
      addNewError("Zahl erwartet");
      result = null;
    }


     return result != null ? result.toString() : null;
  public void setShowStats(int showStats) {
     this.showStats = showStats;
   }
   }


   /**
   /**
   * <code>// $cript auto [NICHT]|[length [period]]</code><br />
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * Autoconfirms a unit (or prevents autoconfirmation if NICHT). If length is given, the unit is
  * container to execute all unit commands.
   * autoconfirmed for length rounds (length is decreased each round). If period is given, the unit
  *
   * is <em>not</em> confirmed every period rounds, otherwise confirmed.
  * @param faction scripts for all units of this faction
   *  
  * @throws NullPointerException if <code>faction == null</code>
   * @param tokens
   */
  public void execute(Faction faction) {
    executeFrom(Collections.singleton(faction), null, null);
  }
 
  /**
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * container to execute all unit commands.
   *
   * @param factions scripts for all units of all factions in this set
  * @throws NullPointerException if <code>faction == null</code>
   */
   */
   protected void commandAuto(String[] tokens) {
   public void execute(Collection<Faction> factions) {
    if (tokens.length > 4) {
     executeFrom(factions, null, null);
      addNewError("zu viele Argumente");
  }
      return;
 
    }
  /**
     if (tokens.length == 1) {
  * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
      setConfirm(currentUnit, true);
  * container to execute all unit commands.
      addNewOrder(currentOrder, false);
  *
    } else if (NOT.equalsIgnoreCase(tokens[1])) {
  * @param faction scripts for all units of this faction are executed
      setConfirm(currentUnit, false);
  * @param region only commands of unit in this region are executed
      addNewOrder(currentOrder, false);
  * @throws NullPointerException if <code>faction == null</code>
    } else {
  */
      int length = 0, period = 0;
  public void execute(Faction faction, Region region) {
      try {
    if (faction == null)
        length = Integer.parseInt(tokens[1]);
      throw new NullPointerException();
        if (tokens.length > 2) {
 
          period = Integer.parseInt(tokens[2]);
    executeFrom(faction, region, region);
        }
        if (length > 0) {
          setConfirm(currentUnit, true);
        } else {
          setConfirm(currentUnit, false);
          if (period > 0) {
            length = period;
          }
        }
        StringBuilder newOrder = new StringBuilder();
        newOrder.append(PCOMMENTOrder).append(" ").append(scriptMarker).append(" ").append(
            tokens[0]);
        newOrder.append(" ").append(length - 1);
        if (period > 0) {
          newOrder.append(" ").append(period);
        }
        addNewOrder(newOrder.toString(), true);
      } catch (NumberFormatException e) {
        addNewOrder(currentOrder, false);
        addNewError("Zahl erwartet");
        return;
      }
    }
   }
   }


   /**
   /**
   * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * <br />
  * container to execute all unit commands. Ignore regions before first (in the report order).
   * Adds a GIB order to the unit. Warning may be one of "immer" (the default), "Menge", "Einheit",
  *
   * "nie".
  * @param faction scripts for all units of this faction are executed
   */
  * @param region only commands of unit in this region are executed, may be <code>null</code> to
   protected void commandGiveIf(String[] tokens) {
  *          execute for all regions.
     Warning w = new Warning(true);
  * @param first First region to execute, may be <code>null</code> to not ignore any regions
    tokens = w.parse(tokens);
   * @throws NullPointerException if <code>faction == null</code>
   */
  public void executeFrom(Faction faction, Region region, Region first) {
    executeFrom(Collections.singleton(faction), region, first);
  }
 
  /**
  * Parses scripts and confirms units according to the "confirm" tag. Ignore regions before first
  * (in the report order).
  *
  * @param factions scripts for all units of all factions in this set are executed
  * @param region only commands of unit in this region are executed, may be <code>null</code> to
  *          execute for all regions.
  * @param first First region to execute, may be <code>null</code> to not ignore any regions
   * @throws NullPointerException if <code>faction == null</code>
   */
   public void executeFrom(Collection<Faction> factions, Region region, Region first) {
     if (factions == null || factions.isEmpty())
      throw new NullPointerException();


     if (tokens.length < 3 || tokens.length > 5) {
     currentFactions = CollectionFactory.createOrderedMap(1);
       addNewError("falsche Anzahl Argumente");
    for (Faction f : factions) {
      return;
       currentFactions.put(f, null);
     }
     }


     Unit target = helper.getUnit(tokens[1]);
     helper.getUI().setMaximum(world.getRegions().size() + 4);
    helper.getUI().setProgress("init", ++progress);
 
    // comment out the following two lines if you don't have the newest nighthly build of Magellan
    helper.getUI().setProgress("preprocessing", ++progress);
 
    EresseaRelationFactory relationFactory = ((EresseaRelationFactory) world.getGameSpecificStuff()
        .getRelationFactory());
    relationFactory.stopUpdating();


     // test if EACH is present
     findSomeUnit(currentFactions);
     int je = 0;
 
     if (EACHOrder.equals(tokens[2])) {
     initLocales();
      je = 1;
 
      if (ALLOrder.equals(tokens[3]) || KRAUTOrder.equals(tokens[3])
     for (Faction faction : factions) {
          || LUXUSOrder.equals(tokens[3]) || TRANKOrder.equals(tokens[3])) {
      if (faction.units().size() >= UNIT_LIMIT) {
         addNewError("JE " + tokens[3] + " geht nicht");
        addWarning(someUnit, "Einheitenlimit erreicht (" + faction.units().size() + "/"
        return;
            + UNIT_LIMIT + ")! ");
      } else if (faction.units().size() * 1.1 > UNIT_LIMIT) {
         addWarning(someUnit, "Einheitenlimit fast erreicht (" + faction.units().size() + "/"
            + UNIT_LIMIT + ")! ");
       }
       }
     }
     }


     // handle GIB xyz ALLES
     collectStats();
     if (ALLOrder.equalsIgnoreCase(tokens[2])) {
 
      if (!testUnit(tokens[1], target, w))
    // Parses the orders of the units for commands of the form "// $cript ..." and
        return;
     // tries to execute them.
       if (tokens.length == 3) {
    if (region == null) {
        for (Item item : currentUnit.getItems()) {
       boolean go = first == null;
          if (item.getAmount() > 0) {
      for (Region r : world.getRegions()) {
            addNewOrder(getGiveOrder(currentUnit, tokens[1], item.getOrderName(),
        if (r == first) {
                Integer.MAX_VALUE, false), true);
          go = true;
          } else if (ADD_NOT_THERE_INFO) {
        }
            addNewMessage("we have no " + item);
        if (go) {
          }
          execute(r);
         }
         }
        return;
       }
       }
    } else {
      execute(region);
     }
     }


     if (KRAUTOrder.equalsIgnoreCase(tokens[2]) || LUXUSOrder.equalsIgnoreCase(tokens[2])
     // comment out the following line if you don't have the newest nightly build of Magellan
        || TRANKOrder.equalsIgnoreCase(tokens[2])) {
    helper.getUI().setProgress("postprocessing", ++progress);
      // handle GIB xyz KRAUT
 
      if (KRAUTOrder.equalsIgnoreCase(tokens[2])) {
    relationFactory.restartUpdating();
        if (world.getRules().getItemCategory("herbs") == null) {
          addNewError("Spiel kennt keine Kräuter");
        } else {
          giveAll(tokens, target, w, new Filter() {


            public boolean approve(Item item) {
    for (Faction faction : factions) {
              return world.getRules().getItemCategory("herbs").equals(
      for (Unit u : faction.units()) {
                  item.getItemType().getCategory());
        if ("1".equals(getProperty(u, "confirm"))) {
            }
          u.setOrdersConfirmed(true);
           });
        } else if ("0".equals(getProperty(u, "confirm"))) {
           u.setOrdersConfirmed(false);
         }
         }
        notifyMagellan(u);
       }
       }
    }
    // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("ready", ++progress);
  }
  protected void execute(Region region) {
    try {
      currentRegion = region;
      skills.clear();
      initSupply();


       // handle GIB xyz LUXUS
      commands = new HashMap<String, Consumer<String[]>>();
       if (LUXUSOrder.equalsIgnoreCase(tokens[2])) {
      commands.put("KrautKontrolle", constructCommand(this::commandControl, false, true));
        if (world.getRules().getItemCategory("luxuries") == null) {
      commands.put("auto", constructCommand(this::commandAuto, false, false));
          addNewError("Spiel kennt keine Luxusgüter");
      commands.put("Mannschaft", constructCommand(this::commandCrew, false, false));
        } else {
      commands.put("Loeschen", constructCommand(this::commandClear));
          giveAll(tokens, target, w, new Filter() {
      commands.put("GibWenn", constructCommand(this::commandGiveIf));
      commands.put("Benoetige", constructCommand(this::commandNeed));
      commands.put("BenoetigeFremd", constructCommand(this::commandNeed));
      commands.put("Versorge", constructCommand(this::commandSupply));
      commands.put("Kapazitaet", constructCommand(this::commandCapacity));
      commands.put("BerufDepotVerwalter", constructCommand(this::commandDepot));
      commands.put("Soldat", constructCommand(this::commandSoldier));
      commands.put("Lerne", constructCommand(this::commandLearn));
       // commands.put("BerufBotschafter", constructCommand(this::commandEmbassador, () ->
       // addNewOrder(currentOrder, false), () -> ));
      commands.put("Ueberwache", constructCommand(this::commandMonitor));
      commands.put("Erlaube", constructCommand(this::commandAllow));
      commands.put("Verlange", constructCommand(this::commandAllow));
      commands.put("Ernaehre", constructCommand(this::commandEarn));
      commands.put("Handel", constructCommand(this::commandTrade));
      commands.put("Steuermann", constructCommand(this::commandHelmsman));
      commands.put("Quartiermeister", constructCommand(this::commandQuartermaster));
      commands.put("Sammler", constructCommand(this::commandCollector));
      commands.put("RekrutiereMax", constructCommand(this::commandRecruit));
      commands.put("Kommentar", constructCommand(this::commandComment));


            public boolean approve(Item item) {
      for (Unit u : region.units()) {
              return world.getRules().getItemCategory("luxuries").equals(
        if (currentFactions.containsKey(u.getFaction())) {
                  item.getItemType().getCategory());
            }
          });
        }
      }


      // handle GIB xyz TRANK
          setCurrentUnit(u);
      if (TRANKOrder.equalsIgnoreCase(tokens[2])) {
          // comment out the following line if you don't have the newest nightly build of Magellan
        if (world.getRules().getItemCategory("potions") == null) {
          helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
          addNewError("Spiel kennt keine Tränke");
 
        } else {
          try {
          giveAll(tokens, target, w, new Filter() {
            parseScripts();
             public boolean approve(Item item) {
          } catch (RuntimeException e) {
              return world.getRules().getItemCategory("potions").equals(
             addWarning(u, "error " + e.getClass().getSimpleName());
                  item.getItemType().getCategory());
            log.error(e.getClass().getSimpleName() + " script error for " + u);
             }
             throw new RuntimeException("script error for " + u, e);
           });
           }
         }
         }
       }
       }
      setCurrentUnit(null);


       return;
       // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setProgress(region.toString() + " - postprocessing", ++progress);
 
      teachUnits();
      satisfyNeeds();
    } catch (RuntimeException e) {
      e.printStackTrace();
      // log.error(e.getClass().getSimpleName() + " script error for " + region);
      throw new RuntimeException("script error for " + region, e);
     }
     }
    // refresh relations, just in case
    region.refreshUnitRelations(true);
  }


     if (tokens.length != 4 + je) {
  protected void updateCurrentOrders() {
       addNewError("zu viele Parameter");
     if (isChangedOrders()) {
      return;
       currentUnit.setOrders(newOrders);
     }
     }
    notifyMagellan(currentUnit);
  }


    // get amount
  protected void setCurrentUnit(Unit u) {
     String item = tokens[3 + je];
     if (u != null) {
    int amount = 0;
      currentUnit = u;
    if (ALLOrder.equalsIgnoreCase(tokens[2 + je])) {
      newOrders = new ArrayList<String>();
       if (KRAUTOrder.equals(item) || LUXUSOrder.equals(item)) {
       removedOrderPatterns = new ArrayList<String>();
        addNewError("GIB xyz ALLES " + item + " statt GIB xyz " + item);
      setChangedOrders(false);
       }
       error = -1;
      amount = getItemCount(currentUnit, item);
     } else {
     } else {
       try {
       currentUnit = null;
        amount = Integer.parseInt(tokens[2 + je]);
      newOrders = null;
      } catch (NumberFormatException e) {
    }
        amount = 0;
  }
        addNewError("Zahl oder ALLES erwartet");
 
        return;
  protected boolean isChangedOrders() {
      }
    return changedOrders;
    }
  }


    if (!testUnit(tokens[1], target, w))
  protected void setChangedOrders(boolean changedOrders) {
      return;
    this.changedOrders = changedOrders;
  }


    // get full amount (=amount * persons)
  protected Unit findUnit(String target) {
     int fullAmount = amount;
     Unit targetUnit;
     if (je == 1) {
     if ((targetUnit = helper.getUnit(target)) == null) {
      if (target == null) {
      targetUnit = dummyUnits.get(target);
        addNewMessage("Einheit nicht gefunden; kann Menge nicht überprüfen");
       if (targetUnit == null) {
       } else {
         dummyUnits.put(target, targetUnit = MagellanFactory.createUnit(UnitID.createUnitID(target,
         fullAmount = target.getModifiedPersons() * amount;
            world.base),
            world));
       }
       }
     }
     }
    return targetUnit;
  }


     // check availibility
  private void findSomeUnit(Map<Faction, Integer> factions) {
     if (getItemCount(currentUnit, item) < fullAmount) {
     // sometimes we need an arbitrary unit. This is a shorthand for it.
       if (w.contains(C_AMOUNT)) {
     for (Faction faction : factions.keySet())
         addNewWarning("zu wenig " + item);
       if (!faction.units().isEmpty()) {
      } else {
         someUnit = faction.units().iterator().next();
         addNewMessage("zu wenig " + item);
         break;
       }
       }
      amount = getItemCount(currentUnit, item);
      je = 0;
    }


    // make GIVE order
     if (someUnit == null)
     if (amount > 0) {
       throw new RuntimeException("No units in report!");
      if (amount == getItemCount(currentUnit, item) && je != 1) {
        addNewOrder(getGiveOrder(currentUnit, tokens[1], item, Integer.MAX_VALUE, je == 1), true);
      } else {
        addNewOrder(getGiveOrder(currentUnit, tokens[1], item, amount, je == 1), true);
      }
       Supply supply = getSupply(item, currentUnit);
      if (supply == null) {
        addNewWarning("supply 0 " + item);
        return;
      }
      supply.reduceAmount(amount);
      if (target != null) {
        addNeed(item, target, -amount, -amount, GIB_WENN_PRIORITY);
      }
    }
 
  }
 
  interface Filter {
 
    boolean approve(Item item);


   }
   }


   private void giveAll(String[] tokens, Unit target, Warning w, Filter filter) {
   protected boolean testUnit(String sOther, Unit other, Warning warning) {
    if (testUnit(tokens[1], target, w)) {
      if (tokens.length > 3) {
        addNewError("zu viele Parameter");
      }
 
      for (Item item : currentUnit.getItems())
        if (filter.approve(item)) {
          addNewOrder(getGiveOrder(currentUnit, tokens[1], item.getOrderName(), Integer.MAX_VALUE,
              false), true);
          Supply supply = getSupply(item.getOrderName(), currentUnit);
          if (supply == null) {
            addNewWarning("supply 0 " + item);
          } else {
            int amount = item.getAmount();
            supply.reduceAmount(amount);
            if (target != null) {
              addNeed(item.getOrderName(), target, -amount, -amount, GIB_WENN_PRIORITY);
            }
          }
        }
 
    }
  }
 
  private boolean testUnit(String sOther, Unit other, Warning warning) {
     return testUnit(sOther, other, warning, false);
     return testUnit(sOther, other, warning, false);
   }
   }


   private boolean testUnit(String sOther, Unit other, Warning w, boolean testFaction) {
   protected boolean testUnit(String sOther, Unit other, Warning w, boolean testFaction) {
     if (other == null || other.getRegion() != currentUnit.getRegion()) {
     if (other == null || other.getRegion() != currentUnit.getRegion()) {
       if (w.contains(C_UNIT) && !w.contains(C_HIDDEN)) {
       if (w.contains(Warning.C_UNIT) && w.contains(Warning.C_HIDDEN)) {
         addNewWarning(sOther + " nicht da");
         addNewWarning(sOther + " nicht da");
         return false;
         return false;
Zeile 1.090: Zeile 1.098:
         addNewOrder("; " + sOther + " nicht da", true);
         addNewOrder("; " + sOther + " nicht da", true);
       }
       }
       return w.contains(C_HIDDEN);
       return !w.contains(Warning.C_HIDDEN);
     } else if (testFaction && w.contains(C_FOREIGN)
     } else if (testFaction && w.contains(Warning.C_FOREIGN)
         && !currentFactions.containsKey(other.getFaction())) {
         && !currentFactions.containsKey(other.getFaction())) {
       addNewWarning("Einheit " + sOther + " gehört nicht zu uns");
       addNewWarning("Einheit " + sOther + " gehört nicht zu uns");
Zeile 1.100: Zeile 1.108:


   /**
   /**
  * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
   * Tries to init the constants according to the current faction's locale
  * <code>// $cript Benoetige JE amount item [priority]</code><br />
  * <code>// $cript Benoetige ALLES [item] [priority]</code><br />
   * Tries to transfer the maxAmount of item from other units to this unit. Issues warning if
  * minAmount cannot be supplied. <code>Benoetige JE</code> tries to reserve amount of item for
  * every person in the unit. Fractional amounts are possible and rounded up.
  * <code>Benoetige ALLES item</code> is equivalent to <code>Benoetige 0 infinity item</code>,
  * <code>Benoetige ALLES</code> is equivalent to <code>Benoetige ALLES</code> for every itemtype
  * in the region.<br/>
  * <code>Benoetige KRAUT</code> is the same for every herb type in the region.<br/>
  * <code>BenoetigeFremd unit (JE amount)|(minAmount [maxAmount]) item [priority] [warning...]</code>
  * <br />
  * <code>BenoetigeFremd</code> does the same, but for the given unit instead of the current unit.
  * Needs with higher priority are satisfied first. If no priority is given,
  * {@link #DEFAULT_PRIORITY} is used.
   */
   */
   protected void commandNeed(String[] tokens) {
   protected void initLocales() {
     Unit unit = currentUnit;
     if (currentUnit == null) {
     Warning w = new Warning(true);
      findSomeUnit(currentFactions);
     }
    if (someUnit == null)
      throw new NullPointerException();
    setCurrentUnit(someUnit);


     String sOther = "???";
     GIVEOrder = getLocalizedOrder(EresseaConstants.OC_GIVE, GIVEOrder).toString();
     if (tokens[0].equals("BenoetigeFremd")) {
    RESERVEOrder = getLocalizedOrder(EresseaConstants.OC_RESERVE, RESERVEOrder).toString();
      sOther = tokens[1];
    EACHOrder = getLocalizedOrder(EresseaConstants.OC_EACH, EACHOrder).toString();
      unit = helper.getUnit(tokens[1]);
    ALLOrder = getLocalizedOrder(EresseaConstants.OC_ALL, ALLOrder).toString();
    PCOMMENTOrder = "//";
     LEARNOrder = getLocalizedOrder(EresseaConstants.OC_LEARN, LEARNOrder).toString();
    TEACHOrder = getLocalizedOrder(EresseaConstants.OC_TEACH, TEACHOrder).toString();
    ENTERTAINOrder = getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder).toString();
    TAXOrder = getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder).toString();
    WORKOrder = getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder).toString();
    BUYOrder = getLocalizedOrder(EresseaConstants.OC_BUY, BUYOrder).toString();
    SELLOrder = getLocalizedOrder(EresseaConstants.OC_SELL, SELLOrder).toString();
    MAKEOrder = getLocalizedOrder(EresseaConstants.OC_MAKE, MAKEOrder).toString();
    MOVEOrder = getLocalizedOrder(EresseaConstants.OC_MOVE, MOVEOrder).toString();
    ROUTEOrder = getLocalizedOrder(EresseaConstants.OC_ROUTE, ROUTEOrder).toString();
    PAUSEOrder = getLocalizedOrder(EresseaConstants.OC_PAUSE, PAUSEOrder).toString();
    RESEARCHOrder = getLocalizedOrder(EresseaConstants.OC_RESEARCH, RESEARCHOrder).toString();
    RECRUITOrder = getLocalizedOrder(EresseaConstants.OC_RECRUIT, RECRUITOrder).toString();


       // erase unit token for easier processing afterwards
    if (currentFactions.keySet().iterator().next().getLocale().getLanguage() != "de") {
       tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
       // warning constants
       tokens[0] = "BenoetigeFremd";
       Warning.W_NEVER = "never";
 
      Warning.W_SKILL = "skill";
       // only benoetigefremd can have warnings!
      Warning.W_WEAPON = "weapon";
       tokens = w.parse(tokens);
       Warning.W_SHIELD = "shields";
       // foreign implies amount
       Warning.W_ARMOR = "armor";
       if (w.contains(C_FOREIGN)) {
       BEST = "best";
        w.add(W_AMOUNT);
       NULL = "null";
       }
       LUXUSOrder = "LUXURY";
       KRAUTOrder = "HERBS";
     }
     }
    setCurrentUnit(null);
  }


    int tokenCount = tokens.length;
  protected String getLocalizedOrder(StringID orderKey, String fallback) {
 
    int priority;
     try {
     try {
       priority = Integer.parseInt(tokens[tokenCount - 1]);
       return world.getGameSpecificStuff().getOrderChanger().getOrderO(orderKey,
      tokenCount--;
          currentUnit.getLocale()).getText();
     } catch (NumberFormatException e) {
     } catch (RulesException e) {
       priority = DEFAULT_PRIORITY;
       return fallback;
     }
     }
  }


     tokens = Arrays.copyOf(tokens, tokenCount);
  /**
  * Tries to translate the given order to the current locale.
  */
  protected String getLocalizedOrder(StringID orderKey, Object[] args) {
     return world.getGameSpecificStuff().getOrderChanger().getOrderO(currentUnit.getLocale(),
        orderKey, args).getText();
  }


     if (tokens.length < 2 || tokens.length > 5) {
  /**
       addNewError("falsche Anzahl Argumente");
  * Tries to translate the given order to the current locale.
      return;
  */
  protected String getLocalizedOrder(String orderKey, String fallBack) {
    String translation = Resources.getOrderTranslation(orderKey, currentUnit.getLocale());
     if (translation == orderKey)
      return fallBack;
    else
      return translation;
  }
 
  /**
  * Adss an order to the current unit's new orders.
  *
  * @param order The new order
  * @param changed Set to true if this is a change (not merely a copy of an old order)
  */
  protected void addNewOrder(String order, boolean changed) {
    setChangedOrders(isChangedOrders() || changed);
    if (!changed) {
       // remove old orders matching a removed order
      for (String pattern : removedOrderPatterns)
        if (order.matches(pattern))
          return;
     }
     }


     if (!testUnit(sOther, unit, w, true))
     newOrders.add(getParser().parse(order, currentUnit.getLocale()).getText());
      return;
  }


    try {
  /**
      if (ALLOrder.equals(tokens[1])) {
  * Registers a pattern. All lines matching this regular expression (case sensitive!) will be
        if (tokens.length > 2) {
  * removed from here on. If retroActively, also orders that are already in {@link #newOrders}.
          addNeed(tokens[2], unit, 0, Integer.MAX_VALUE, priority, w);
  *
         } else {
  * @param regEx
          for (String item : supplyMap.keySet()) {
  * @param retroActively
            addNeed(item, unit, 0, Integer.MAX_VALUE, priority, w);
  */
          }
  protected void removeOrdersLike(String regEx, boolean retroActively) {
    if (retroActively) {
      for (Iterator<String> it = newOrders.iterator(); it.hasNext();) {
         String line2 = it.next();
        if (line2.matches(regEx)) {
          it.remove();
         }
         }
       } else if (EACHOrder.equals(tokens[1])) {
       }
        if (tokens.length != 4) {
    }
          addNewError("ungültige Argumente für Benoetige JE x Ding");
    removedOrderPatterns.add(regEx);
        } else {
  }
          int amount = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[2]));
 
          String item = tokens[3];
  /**
          addNeed(item, unit, amount, amount, priority, w);
  * Adds an error line.
        }
  *
      } else if (KRAUTOrder.equals(tokens[1])) {
  * @param hint
        if (tokens.length > 2) {
  */
          addNewError("zu viele Parameter");
  protected void addNewError(String hint) {
        }
    error = line;
        if (world.getRules().getItemCategory("herbs") == null) {
    // errMsg = hint;
          addNewError("Spiel kennt keine Kräuter");
    addNewOrder(COMMENTOrder + " TODO: " + hint + " (Fehler in Zeile " + error + ")", true);
        } else {
    setConfirm(currentUnit, false);
          for (ItemType itemType : world.getRules().getItemTypes()) { // currentUnit.getItems()
            if (world.getRules().getItemCategory("herbs").equals(itemType.getCategory())) {
              addNeed(itemType.getOrderName(), unit, 0, Integer.MAX_VALUE, priority, w);
            }
          }
        }
      } else if (LUXUSOrder.equals(tokens[1])) {
        if (tokens.length > 2) {
          addNewError("zu viele Parameter");
        }
        if (world.getRules().getItemCategory("luxuries") == null) {
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          for (ItemType itemType : world.getRules().getItemTypes()) { // currentUnit.getItems()
            if (world.getRules().getItemCategory("luxuries").equals(itemType.getCategory())) {
              addNeed(itemType.getOrderName(), unit, 0, Integer.MAX_VALUE, priority, w);
            }
          }
        }
      } else if (tokens.length > 4) {
        addNewError("zu viele Parameter");
      } else {
        int minAmount = Integer.parseInt(tokens[1]);
        int maxAmount = tokens.length == 3 ? minAmount : Integer.parseInt(tokens[2]);
        String item = tokens[tokens.length - 1];
        addNeed(item, unit, minAmount, maxAmount, priority, w);
      }
    } catch (NumberFormatException exc) {
      addNewError("Ungültige Zahl in Benoetige: " + exc.getMessage());
    }
   }
   }


   /**
   /**
   * <code>// $cript BerufDepotVerwalter [[ZusatzMin] ZusatzMax]</code><br />
   * Adds a warning message (with a to do tag) to the new orders.
   * Collects all free items in the region, Versorge 100, calls Ueberwache
   *
  * @param text
   */
   */
   protected void commandDepot(String[] tokens) {
   protected void addNewMessage(String text) {
     if (tokens.length > 3) {
     addNewOrder(COMMENTOrder + " ----- " + text + " -----", true);
      addNewError("zu viele Argumente");
  }
    }
    commandMonitor(new String[] { "Ueberwache" });


    int costs = 0;
  /**
    for (Unit u : currentRegion.units()) {
  * Adds an error line to new orders.
      if (currentFactions.containsKey(u.getFaction())) {
  *
        costs += u.getRace().getMaintenance() * u.getPersons();
  * @param hint
      }
  */
    }
  protected void addNewWarning(String hint) {
    int zusatz1 = 0, zusatz2 = 0;
    addNewWarning(hint, true);
    if (tokens.length > 1) {
  }
      try {
        zusatz1 = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
          zusatz2 = Integer.parseInt(tokens[2]);
        }
      } catch (NumberFormatException e) {
        addNewError("Zahl erwartet");
      }
    }
    addNeed("Silber", currentUnit, costs + zusatz1, costs + zusatz2, DEPOT_SILVER_PRIORITY);


    commandNeed(new String[] { "Benoetige ", ALLOrder, String.valueOf(DEPOT_PRIORITY) });
  protected void addNewWarning(String hint, boolean addLine) {
     commandSupply(new String[] { "Versorge", "100" });
    error = line;
    // errMsg = hint;
    addNewOrder(COMMENTOrder + " TODO: " + hint
        + (addLine ? " (Warnung in Zeile " + error + ")" : ""), true);
     setConfirm(currentUnit, false);
   }
   }


   /**
   /**
   * <code>Versorge [[item1]...] priority</code> -- set supply priority. Units with negative
   * Adds some statistic information to the orders of the first unit.
  * priority only deliver for minimum needs. Needs are satisfied in descending order of priority.
   *
   * If no items are given, the priority is adjusted for alle the unit's items.
   */
   */
   protected void commandSupply(String[] tokens) {
   protected void collectStats() {
     int priority = 0;
     int buildingScripts = 0;
     if (tokens.length < 2) {
     int shipScripts = 0;
       addNewError("zu wenig Argumente");
    int unitScripts = 0;
    int regionScripts = 0;
    // comment out the following lines if you don't have the newest nightly build of Magellan
    for (Building b : world.getBuildings()) {
       if (helper.hasScript(b)) {
        buildingScripts++;
      }
     }
     }
     try {
     for (Ship s : world.getShips()) {
       priority = Integer.parseInt(tokens[tokens.length - 1]);
       if (helper.hasScript(s)) {
    } catch (NumberFormatException e) {
        shipScripts++;
      addNewError("Zahl erwartet");
       }
       return;
     }
     }
     if (tokens.length == 2) {
     for (Unit u : world.getUnits()) {
       for (Item item : currentUnit.getItems()) {
       if (helper.hasScript(u)) {
         Supply supply = getSupply(item.getOrderName(), currentUnit);
         addOrder(u, COMMENTOrder + " hat Skript", false);
         if (supply != null) {
         unitScripts++;
          supply.priority = priority;
        }
       }
       }
     } else {
     }
      for (int i = 1; i < tokens.length - 1; ++i) {
    for (Region r : world.getRegions()) {
        Supply supply = getSupply(tokens[i], currentUnit);
      if (helper.hasScript(r)) {
        if (supply != null) {
        regionScripts++;
          supply.priority = priority;
        }
       }
       }
    }
    if (showStats > 0) {
      someUnit.addOrderAt(0, "; " + unitScripts + " unit scripts, " + buildingScripts
          + " building scripts, " + shipScripts + " ship scripts, " + regionScripts
          + " region scripts", true);
     }
     }
   }
   }


   /**
   /**
   * <code>// $script +x [Arguments...]</code><br />
   * Parses the orders of the unit u for commands of the form "// $cript ..." and tries to execute
   * If x<=1, the rest of the line is added as a warning to this unit. Otherwise x is decreased by
  * them. Known commands:<br />
   * one. If x==1, the order is removed.
   * <kbd>// $cript +X text</kbd> -- If X<=1 then a warning containing text is added to the unit's
  * orders. Otherwise X is decreased by one.<br />
  * <code>// $cript [rest [period [length]] text</code> -- Adds text (or commands) to the
  * orders<br />
  * <code>// $cript auto [NICHT]|[length [period]]</code> -- autoconfirm orders<br />
  * <code>// $cript Loeschen [$kurz] [<prefix>]</code> -- clears orders except comments<br />
  * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
  * -- add give order (if possible)<br />
  * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
  * <code>// $cript Benoetige JE amount item [priority]</code><br />
  * <code>// $cript Benoetige ALLES [item] [priority]</code> -- acquire things from other
  * units<br />
  * <code>// $cript BenoetigeFremd unit [JE] minAmount [maxAmount] item [priority] [warning]</code><br
  * />
  * <code>// $cript Versorge [[item]...] priority</code> -- set supply priority.<br />
  * <code>// $cript BerufDepotVerwalter [Zusatzbetrag]</code> Collects all free items in the
  * region, Versorge 100, calls Ueberwache<br />
   * <code>// $cript Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code>
  * -- learn skill and reserve equipment<br />
  * <code>// $cript Lerne Talent1 Stufe1 [[Talent2 Stufe2]...]</code> -- learn skills in given
  * ratio <br />
  * <code>// $cript BerufBotschafter [minimum money] [Talent]</code> -- earn money if necessary,
  * otherwise learn skill<br />
  * <code>// $cript Ueberwache</code> -- look out for unknown units<br />
  * <code>// $cript Erlaube faction unit [unit...]</code> -- allow units for Ueberwache<br />
  * <code>// $cript Verlange faction unit [unit...]</code> -- allow and require units for
  * Ueberwache<br />
  * <code>// $cript Ernaehre [amount]</code> -- earn money<br />
  * <code>// $cript Handel Menge|xM [xR] [ALLES | Verkaufsgut...] Warnung</code> -- trade luxuries<br />
  * <code>// $cript Steuermann minSilver maxSilver [Talent ...]</code> -- be responsible for ship<br />
  * <code>// $cript Mannschaft [Talen ...]</code> -- be crew and learn<br />
  * <code>// $cript Quartiermeister [[amount item]...]</code> -- be lookout<br />
  * <code>// $cript Sammler [interval]</code> -- collect and research HERBS<br />
  * <code>// $cript KrautKontrolle [route]</code> -- FORSCHE KRÄUTER in several regions<br />
  * <code>// $cript RekrutiereMax</code> -- recruit as much as possible<br />
  * <code>// $cript Kommentar text</code> -- add ; comment<br />
   */
   */
   protected void commandWarning(String[] tokens) {
   protected void parseScripts() {
     int delay = -1;
     // errMsg = null;
     try {
     line = 0;
      delay = Integer.parseInt(tokens[0].substring(1));
     allowedUnits.clear();
     } catch (NumberFormatException e) {
     requiredUnits.clear();
      addNewError("Zahl erwartet");
     clear = null;
      return;
    }
     if (delay <= 1) {
      StringBuilder warning = new StringBuilder();
      String foo = currentOrder.substring(currentOrder.indexOf("+"));
      warning.append(foo.indexOf(" ") >= 0 ? foo.substring(foo.indexOf(" ") + 1) : "");
      addNewWarning(warning.toString(), false);
     }
    if (delay != 1) {
      StringBuilder newCommand =
          new StringBuilder(PCOMMENTOrder).append(" ").append(scriptMarker).append(" +");
      newCommand.append(Math.max(0, delay - 1));
      for (int i = 1; i < tokens.length; ++i) { // skip "+x"
        newCommand.append(" ").append(tokens[i]);
      }
      addNewOrder(newCommand.toString(), true);
    }
  }


  /**
    // NOTE: must not change currentUnit's orders directly! Always change newOrders!
  * <code>// $cript Lerne Talent1 Stufe1 [[Talent2 Stufe2]...]</code><br />
    Deque<String> queue = new LinkedList<String>();
  * Tries to learn skills in given ratio. For example,
    for (Order o : currentUnit.getOrders2()) {
  * <code>// $cript Lerne Hiebwaffen 10 Ausdauer 5</code> tries to learn to (Hiebwaffen 2, Ausdauer
      ++line;
  * 1), (Hiebwaffen 4, Ausdauer 2), and so forth.
      queue.add(o.getText());
  */
      while (!queue.isEmpty()) {
  protected void commandLearn(String[] tokens) {
        currentOrder = queue.poll();
    if (tokens.length < 3 || tokens.length % 2 != 1) {
        try {
      addNewError("falsche Anzahl Argumente");
          String[] tokens = detectScriptCommand(currentOrder);
      return;
          if (tokens == null) {
    }
            // add order if
//    List<Skill> targetSkills = new LinkedList<Skill>();
            if (shallClear(currentOrder)) {
    List targetSkills = new LinkedList();
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
            } else {
              addNewOrder(currentOrder, false);
            }
            currentOrder = null;
          } else {
            // as of Java 7 the first character of an integer may be a '+' sign
            boolean repeat = true;
            while (tokens != null && !tokens[0].startsWith("+") && repeat) {
              try {
                Integer.parseInt(tokens[0]);
                String[] nextOrders = commandRepeat(tokens);
                if (nextOrders == null) {
                  currentOrder = null;
                  tokens = null;
                } else {
                  currentOrder = nextOrders.length > 0 ? nextOrders[0] : "";
                  tokens = detectScriptCommand(currentOrder);
                  if (tokens == null) {
                    addNewOrder(currentOrder, true);
                    currentOrder = null;
                  }
                  for (int i = 1; i < nextOrders.length; ++i) {
                    queue.add(nextOrders[i]);
                  }
                }
              } catch (NumberFormatException e) {
                repeat = false;
                // not a repeating order
              }
            }
            if (tokens != null) {
              Consumer<String[]> interpreter = commands.get(tokens[0]);
              if (interpreter != null) {
                interpreter.accept(tokens);
              } else {
                String command = tokens[0];
                if (command.startsWith("+")) {
                  commandWarning(tokens);
                  setChangedOrders(true);
                } else if (command.equals("BerufBotschafter")) {
                  addNewOrder(currentOrder, false);
                  Collection<String> addedCommands = commandEmbassador(tokens);
                  Collections.reverse((List<?>) addedCommands);
                  for (String newOrder : addedCommands) {
                    queue.addFirst(newOrder);
                  }
                  setChangedOrders(true);
                } else {
                  addNewError("unbekannter Befehl: " + command);
                }
              }
              currentOrder = null;
            }
          }
        } catch (Throwable t) {
          StringWriter w = new StringWriter();


    for (int i = 1; i < tokens.length; i++) {
           t.printStackTrace(new PrintWriter(w));
      try {
           addNewError("Fehler bei der Ausführung von '" + currentOrder + "': " + w.toString());
        if (i < tokens.length - 1) {
           log.error(t);
           SkillType skill = world.getRules().getSkillType(tokens[i++]);
          int level = Integer.parseInt(tokens[i]);
           if (skill == null) {
            addNewError("unbekanntes Talent " + tokens[i - 1]);
          }
          targetSkills.add(new Skill(skill, 0, level, 1, true));
        } else {
           addNewError("unerwartetes Token " + tokens[i]);
         }
         }
      } catch (NumberFormatException e) {
        addNewError("ungültige Stufe " + tokens[i]);
       }
       }
     }
     }


     learn(currentUnit, targetSkills);
     updateCurrentOrders();
 
  }
 
  private Consumer<String[]> constructCommand(Consumer<String[]> command, Runnable before, Runnable after) {
    return (tokens) -> {
      before.run();
      command.accept(tokens);
      after.run();
    };
  }
 
  private Consumer<String[]> constructCommand(Consumer<String[]> command, boolean keep, boolean setChanged) {
    return constructCommand(command,
        () -> {
          if (keep) {
            addNewOrder(currentOrder, false);
          }
        },
        () -> {
          if (setChanged) {
            setChangedOrders(true);
          }
        });
  }
 
  private Consumer<String[]> constructCommand(Consumer<String[]> command) {
    return constructCommand(command, true, false);
   }
   }


   /**
   /**
   * <code>Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code><br />
   * If order is a script command ("// $cript ..."), returns a List of the tokens. Otherwise returns
  * Tries to learn best skill and acquire equipment. If no skill is given, best weapon skill is
   * <code>null</code>. The first in the list is the first token after the "// $cript".
  * selected. If no weapon is given, best matching weapon is acquired and so on. Preference is
  * always for RESERVEing stuff the unit already has. Waffe, Schild, Rüstung can be "null" if
   * nothing should be reserved or "best" (the default behaviour).<br />
  * Warning can be:<br />
  * nie: issues no warnings at all, Talent: only warns if no skill is given and no best skill
  * exists, Waffe: additionally warn if no weapon can be acquired, Schild: additionally warn if no
  * shield, Rüstung: additionally warn if no armor. Default warning level is "Waffe".
   */
   */
   protected void commandSoldier(String[] tokens) {
   protected String[] detectScriptCommand(String order) {
     String warning = tokens[tokens.length - 1];
     StringTokenizer tokenizer = new StringTokenizer(order, " ");
     if (!(W_NEVER.equals(warning) || W_SKILL.equals(warning) || W_WEAPON.equals(warning)
     if (tokenizer.hasMoreTokens()) {
         || W_SHIELD.equals(warning) || W_ARMOR.equals(warning))) {
      String part = tokenizer.nextToken();
      warning = null;
      if (part.equals(PCOMMENTOrder) || part.equals(COMMENTOrder)) {
    }
         if (tokenizer.hasMoreTokens()) {
    String skill = null;
          part = tokenizer.nextToken();
    if (tokens.length > 1 + (warning == null ? 0 : 1)) {
          if (part.startsWith(scriptMarker)) {
      skill = tokens[1];
            if (!part.equals(scriptMarker)) {
    }
              addNewWarning("lines starting with '" + part + "' not allowed");
    String weapon = null;
            } else {
    if (tokens.length > 2 + (warning == null ? 0 : 1)) {
              List<String> result = new ArrayList<String>();
      weapon = tokens[2];
              while (tokenizer.hasMoreTokens()) {
    }
                result.add(tokenizer.nextToken());
    String shield = null;
              }
    if (tokens.length > 3 + (warning == null ? 0 : 1)) {
              if (result.size() == 0)
      shield = tokens[3];
                return null;
    }
              return result.toArray(new String[] {});
    String armor = null;
            }
    if (tokens.length > 4 + (warning == null ? 0 : 1)) {
          }
      armor = tokens[4];
        }
      }
     }
     }
    return null;
  }


     if (warning == null) {
  /**
       warning = W_WEAPON;
  * <code>// $cript Loeschen [$kurz] [<prefix>]</code><br />
     }
  * Remove orders except comments from here on. If "$kurz", remove all orders, otherwise only long
 
  * and permanent (@) orders. If prefix is set, remove only orders starting with that prefix.
    if (BEST.equals(skill)) {
  *
      skill = null;
  * @param tokens
    }
  */
    if (BEST.equals(weapon)) {
  protected void commandClear(String[] tokens) {
      weapon = null;
    clearPrefix = "";
     if (tokens.length == 1) {
       clear = LONG;
     } else {
      if (tokens[1].equalsIgnoreCase(SHORT)) {
        clear = ALLOrder;
        clearPrefix =
            currentOrder.substring(Math.min(currentOrder.length(), currentOrder.indexOf(SHORT)
                + SHORT.length() + 1));
      } else {
        clear = LONG;
        clearPrefix =
            currentOrder.substring(Math.min(currentOrder.length(), currentOrder.indexOf("Loeschen")
                + "Loeschen".length() + 1));
      }
     }
     }
     if (BEST.equals(shield)) {
     for (int i = 0; i < newOrders.size(); i++) {
       shield = null;
       String order = newOrders.get(i);
    }
      if (shallClear(order)) {
    if (BEST.equals(armor)) {
        setChangedOrders(true);
       armor = null;
        newOrders.set(i, COMMENTOrder + " " + order);
       }
     }
     }
  }
  protected boolean shallClear(String order) {
    if (clear == null)
      return false;
    String trimmed = order.trim();


     soldier(currentUnit, skill, weapon, shield, armor, warning);
     return !trimmed.startsWith(PCOMMENTOrder)
        && !trimmed.startsWith(COMMENTOrder)
        && (clear == ALLOrder || (clear == LONG && world.getGameSpecificStuff().getOrderChanger()
            .isLongOrder(order)))
        && (clearPrefix == null || clearPrefix.length() == 0 || trimmed.startsWith(clearPrefix));
   }
   }


   /**
   /**
   * <code>// $cript BerufBotschafter [Talent]</code> -- earn money if necessary, otherwise learn
   * <code>// $cript [rest [period [length]]] text</code><br />
   * skill<br />
  * Adds text (or commands) to the orders after rest rounds. If <code>rest==1</code>, text is added
   */
  * to the unit's orders after the current order. If text is a script order itself, it will be
   protected void commandEmbassador(String[] tokens) {
  * executed. If period is set, rest will be reset to period and the modified order added instead
     Skill skill = null;
  * of the current order. If <code>rest>1</code>, it is decreased and the modified order added
     if (tokens.length > 1) {
  * instead of the current one. If length is given, the whole order will be removed after length
       skill = getSkill(tokens[1], 10);
  * rounds. <code>text</code> may contain '\n". If this is the case it is split into lines and the
    } else {
  * lines are executed or inserted after the current command. For example the line<br />
       skill = getSkill(EresseaConstants.S_WAHRNEHMUNG.toString(), 10);
  * "// $cript 1 10 MACHE TEMP a\nLERNE Hiebwaffen\n// $cript 2 GIB a 1 Silber\nENDE"<br />
       if (skill == null) {
  * will be replaced with<br />
        skill = getSkill(EresseaConstants.S_AUSDAUER.toString(), 10);
  * "// $cript 10 10 MACHE TEMP a\nLERNE Hiebwaffen\n// $cript 2 GIB a 1 Silber\nENDE",<br />
       }
  * "MACHE TEMP a",<br />
    }
  * "LERNE Hiebwaffen",<br />
   * "// $cript 1 GIB a 1 Silber",<br />
  * "ENDE".
  *
  * @param tokens
  * @return <code>text</code>, if <code>rest==1</code>, otherwise <code>null</code>
   */
   protected String[] commandRepeat(String[] tokens) {
     ArrayList<String> lines = null;
     try {
       int rest = Integer.parseInt(tokens[0]);
       int period = 0;
       int length = Integer.MAX_VALUE;
       int textIndex = 1;


    commandClear(new String[] { "Loeschen" });
      setChangedOrders(true);


    if (helper.getSilver(currentUnit) < 100) {
       if (tokens.length >= 2) {
       if (hasEntertain() && currentUnit.getSkill(EresseaConstants.S_UNTERHALTUNG) != null
         try {
          && currentUnit.getSkill(EresseaConstants.S_UNTERHALTUNG).getLevel() > 0) {
          period = Integer.parseInt(tokens[1]);
         addNewOrder(ENTERTAINOrder, true);
          textIndex = 2;
      } else if (hasWork()) {
          if (tokens.length >= 3) {
        addNewOrder(WORKOrder, true);
             try {
      } else {
               length = Integer.parseInt(tokens[2]);
        addNewWarning("Einheit verhungert");
              textIndex = 3;
      }
             } catch (NumberFormatException nfe) {
    } else if (skill == null) {
              // third argument not a number
      StringBuilder order = new StringBuilder();
               length = Integer.MAX_VALUE;
      if (tokens.length > 1) {
               textIndex = 2;
        order.append(tokens[1]);
      }
      for (int i = 2; i < tokens.length; ++i) {
        order.append(" ").append(tokens[i]);
      }
      addNewOrder(order.toString(), true);
    } else {
      learn(currentUnit, Collections.singleton(skill));
      if (tokens.length > 2) {
        addNewError("zu viele Argumente");
      }
    }
  }
 
  protected void commandMonitor(String[] tokens) {
    if (tokens.length > 1) {
      addNewError("zu viele Argumente");
    }
 
    // check if region units are allowed
//    Map<Faction, List<Unit>> warnings = new HashMap<Faction, List<Unit>>();
    Map warnings = new HashMap();
    for (Unit u : currentRegion.units()) {
      if (u.getFaction() != currentUnit.getFaction()) {
        if (!(allowedUnits.containsKey(u.getFaction()) && (allowedUnits.get(u.getFaction())
             .contains(u) || allowedUnits.get(u.getFaction()).contains(currentRegion.getZeroUnit())))) {
          if (!(requiredUnits.containsKey(u.getFaction()) && requiredUnits.get(u.getFaction())
               .contains(u))) {
//            List<Unit> list = warnings.get(u.getFaction());
            List list = warnings.get(u.getFaction());
             if (list == null) {
//              list = new LinkedList<Unit>();
               list = new LinkedList();
              warnings.put(u.getFaction(), list);
             }
             }
            list.add(u);
           }
           }
        } catch (NumberFormatException nfe) {
          // second argument not a number
          period = 0;
          textIndex = 1;
         }
         }
       }
       }
    }
      if (rest == 1) {
 
        lines = new ArrayList<String>(1);
//    for (Entry<Faction, List<Unit>> entry : warnings.entrySet()) {
        StringBuilder result = new StringBuilder();
    for (Entry entry : warnings.entrySet()) {
         if (period > 0) {
      StringBuilder sb = new StringBuilder();
           rest = period + 1;
      sb.append(entry.getKey()).append(" hat unerlaubte Einheiten:");
      int i = 0;
      for (Unit u : entry.getValue()) {
         if (++i < 4) {
           sb.append(" ");
          sb.append(u.toString());
         }
         }
        if (textIndex < tokens.length && tokens[textIndex].equals(scriptMarker)) {
          result.append(COMMENTOrder).append(" ");
        }
        for (int tokenIndex = textIndex; tokenIndex < tokens.length; ++tokenIndex) {
          if (tokenIndex > textIndex) {
            result.append(" ");
          }
          String[] subTokens = tokens[tokenIndex].split("\\\\n");
          result.append(subTokens[0]);
          for (int j = 1; j < subTokens.length; ++j) {
            lines.add(result.toString());
            result.setLength(0);
            result.append(subTokens[j]);
          }
          if (tokenIndex + 1 >= tokens.length) {
            lines.add(result.toString());
          }
        }
      } else if (length == 0) {
        // return empty string to signal success
        lines = new ArrayList<String>(1);
        lines.add("");
       }
       }
       entry.getValue().clear();
       if ((rest > 1 || period > 0) && length > 0) {
      addNewWarning(sb.toString());
        StringBuilder newOrder = new StringBuilder();
    }
        newOrder.append(createScriptCommand()).append(rest - 1);
 
        if (period > 0) {
    // check if required units are present
          newOrder.append(" ").append(period);
    for (Faction f : requiredUnits.keySet()) {
        }
      for (Unit u : requiredUnits.get(f)) {
        if (length != Integer.MAX_VALUE) {
         if (u.getRegion() != currentUnit.getRegion()) {
          newOrder.append(" ").append(length - 1);
           addNewWarning("Einheit " + u + " der Partei " + f + " nicht mehr da.");
        }
         for (int i = textIndex; i < tokens.length; ++i) {
           newOrder.append(" ").append(tokens[i]);
         }
         }
        addNewOrder(newOrder.toString(), true);
       }
       }
    } catch (NumberFormatException e) {
      addNewOrder(currentOrder, false);
      addNewError("Zahl erwartet");
     }
     }
    return lines != null ? lines.toArray(new String[0]) : null;
   }
   }


   protected void commandAllow(String[] tokens) {
  /**
     if (tokens.length < 3) {
  * <code>// $cript auto [NICHT]|[length [period]]</code><br />
       addNewError("zu wenige Argumente");
  * Autoconfirms a unit (or prevents autoconfirmation if NICHT). If length is given, the unit is
       return;
  * autoconfirmed for length rounds (length is decreased each round). If period is given, the unit
  * is <em>not</em> confirmed every period rounds, otherwise confirmed.
  *
  * @param tokens
  */
   protected void commandAuto(String[] tokens) {
     if (tokens.length > 4) {
       addNewError("zu viele Argumente");
       return;
     }
     }
 
     if (tokens.length == 1) {
     Faction faction = helper.getFaction(tokens[1]);
      setConfirm(currentUnit, true);
//    Map<Faction, Set<Unit>> map;
      addNewOrder(currentOrder, false);
    Map map;
     } else if (NOT.equalsIgnoreCase(tokens[1])) {
     if (tokens[0].equals("Erlaube")) {
       setConfirm(currentUnit, false);
       map = allowedUnits;
      addNewOrder(currentOrder, false);
     } else {
     } else {
       map = requiredUnits;
       int length = 0, period = 0;
    }
       try {
    if (faction == null) {
         length = Integer.parseInt(tokens[1]);
      addNewError("unbekannte Partei");
         if (tokens.length > 2) {
    } else {
           period = Integer.parseInt(tokens[2]);
//       Set<Unit> set = map.get(faction);
      Set set = map.get(faction);
      if (set == null) {
//         set = new HashSet<Unit>();
        set = new HashSet();
        map.put(faction, set);
      }
      if (ALLOrder.equals(tokens[2])) {
        set.add(currentRegion.getZeroUnit());
         if (tokens.length > 3) {
           addNewError("zu viele Argumente");
         }
         }
      } else {
        if (length > 0) {
        for (int i = 2; i < tokens.length; ++i) {
          setConfirm(currentUnit, true);
           Unit u = helper.getUnit(tokens[i]);
        } else {
           if (u != null) {
           setConfirm(currentUnit, false);
             set.add(u);
           if (period > 0) {
             length = period;
           }
           }
         }
         }
        StringBuilder newOrder = new StringBuilder();
        newOrder.append(createScriptCommand()).append(tokens[0]);
        newOrder.append(" ").append(length - 1);
        if (period > 0) {
          newOrder.append(" ").append(period);
        }
        addNewOrder(newOrder.toString(), true);
      } catch (NumberFormatException e) {
        addNewOrder(currentOrder, false);
        addNewError("Zahl erwartet");
        return;
       }
       }
     }
     }
  }
  private String createScriptCommand() {
    if (cachedScriptCommand == null) {
      cachedScriptCommand = PCOMMENTOrder + " " + scriptMarker + " ";
    }
    return cachedScriptCommand;
   }
   }


   /**
   /**
   * <code>// $cript Ernaehre [amount]</code> -- Earn as much money as possible (or the specified
   * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
   * amount), Versorge {@value #DEFAULT_EARN_PRIORITY}
  * <br />
  * Adds a GIB order to the unit. Warning may be one of "immer" (the default), "Menge", "Einheit",
   * "nie".
   */
   */
   protected void commandEarn(String[] tokens) {
   protected void commandGiveIf(String[] tokens) {
     int amount = -1;
     Warning w = new Warning(true);
     if (tokens.length > 1) {
     tokens = w.parse(tokens);
      try {
        amount = Integer.parseInt(tokens[1]);
      } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[1]);
        return;
      }
      if (tokens.length > 2) {
        addNewError("zu viele Argumente");
      }
    }


     // Ernaehre includes Versorge
     if (tokens.length < 3 || tokens.length > 5) {
    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
      addNewError("falsche Anzahl Argumente");
      return;
    }


     // remove previous orders
     Unit target = helper.getUnit(tokens[1]);
    removeOrdersLike(TAXOrder + ".*", true);
     String targetId = tokens[1];
    removeOrdersLike(ENTERTAINOrder + ".*", true);
     removeOrdersLike(WORKOrder + ".*", true);


     int maxWorkers =
    // test if EACH is present
        Utils.getIntValue(world.getGameSpecificRules().getMaxWorkers(currentRegion), 0);
     int je = 0;
     int workers = Math.min(maxWorkers, currentRegion.getPeasants());
     if (EACHOrder.equals(tokens[2])) {
    Skill entertaining =
      je = 1;
        currentUnit
      if (ALLOrder.equals(tokens[3]) || KRAUTOrder.equals(tokens[3])
            .getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_UNTERHALTUNG));
          || LUXUSOrder.equals(tokens[3]) || TRANKOrder.equals(tokens[3])) {
    Skill taxing =
        addNewError("JE " + tokens[3] + " geht nicht");
        currentUnit.getModifiedSkill(world.getRules().getSkillType(
        return;
            EresseaConstants.S_STEUEREINTREIBEN));
      }
    int entertain = 0, entertain2 = 0, tax = 0, tax2 = 0;
    if (entertaining != null && hasEntertain()) {
      entertain2 = 20 * entertaining.getLevel() * currentUnit.getPersons();
      entertain = Math.max(0, Math.min(currentRegion.maxEntertain(), entertain2));
    }
    if (taxing != null && isSoldier(currentUnit)) {
      tax2 = 20 * taxing.getLevel() * currentUnit.getPersons();
      tax = Math.min(currentRegion.getSilver(), tax2);
     }
     }


     if (tax > entertain) {
     if (!testUnit(targetId, target, w))
      addNewOrder(TAXOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " " + tax + ">"
       return;
          + entertain, true);
 
      if (tax >= currentRegion.getSilver() + workers * 10 - currentRegion.getPeasants() * 10) {
    // handle GIB xyz ALLES
        addNewWarning("Bauern verhungern");
     if (ALLOrder.equalsIgnoreCase(tokens[2])) {
      }
       if (tokens.length == 3) {
       if (tax2 > tax * 2) {
         giveAll(targetId, w, null, null);
        addNewWarning("Treiber unterbeschäftigt " + tax2 + ">" + tax);
         return;
        entertain = currentRegion.maxEntertain();
      }
     } else if (entertain > 0) {
      addNewOrder(ENTERTAINOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " "
          + entertain + ">" + tax, true);
      if (amount > currentRegion.maxEntertain()) {
        addNewWarning("zu viele Arbeiter");
       } else if (entertain2 > entertain * 2) {
         addNewWarning("Unterhalter unterbeschäftigt " + entertain2 + ">" + entertain);
        entertain = currentRegion.maxEntertain();
      }
    } else {
      addNewOrder(WORKOrder + " " + (amount > 0 ? amount : ""), true);
      if ((maxWorkers - workers) * 10 < Math.min(amount, currentUnit.getModifiedPersons() * 10)) {
         addNewWarning("zu viele Arbeiter");
       }
       }
     }
     }
    setConfirm(currentUnit, true);
  }


  /**
    if (KRAUTOrder.equalsIgnoreCase(tokens[2]) || LUXUSOrder.equalsIgnoreCase(tokens[2])
  * <code>// $cript Handel Menge [ALLES | Verkaufsgut...] Warnung</code>: trade luxuries, Versorge
        || TRANKOrder.equalsIgnoreCase(tokens[2])) {
  * {@value #DEFAULT_EARN_PRIORITY}. Menge may be a multipler ("x2" of the region maximum) Warnung
      // handle GIB xyz KRAUT
  * can be "Talent", "Menge", or "nie"<br />
      if (KRAUTOrder.equalsIgnoreCase(tokens[2])) {
  */
        if (world.getRules().getItemCategory("herbs") == null) {
  protected void commandTrade(String[] tokens) {
          addNewError("Spiel kennt keine Kräuter");
    if (tokens.length < 2) {
        } else {
      addNewError("zu wenige Argumente");
          giveAll(targetId, w, null, world.getRules().getItemCategory("herbs"));
    }
        }
      }


    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
      // handle GIB xyz LUXUS
      if (LUXUSOrder.equalsIgnoreCase(tokens[2])) {
        if (world.getRules().getItemCategory("luxuries") == null) {
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          giveAll(targetId, w, null, world.getRules().getItemCategory("luxuries"));
        }
      }


    Warning warning = new Warning(true);
      // handle GIB xyz TRANK
    tokens = warning.parse(tokens);
      if (TRANKOrder.equalsIgnoreCase(tokens[2])) {
 
        if (world.getRules().getItemCategory("potions") == null) {
    int volume = currentRegion.maxLuxuries();
          addNewError("Spiel kennt keine Tränke");
 
         } else {
    int amount = -1;
          giveAll(targetId, w, null, world.getRules().getItemCategory("potions"));
    try {
        }
      if (tokens[1].substring(0, 1).equalsIgnoreCase("x")) {
        amount = Integer.parseInt(tokens[1].substring(1));
         amount = volume * amount;
      } else {
        amount = Integer.parseInt(tokens[1]);
       }
       }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl " + tokens[1]);
      return;
    }


    Skill buySkill =
        currentUnit.getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_HANDELN));
    if (buySkill == null) {
      addNewError("kein Handelstalent");
       return;
       return;
     }
     }


     if (currentRegion.getPrices() == null) {
     if (tokens.length != 4 + je) {
       addNewError("kein Handel möglich");
       addNewError("zu viele Parameter");
       return;
       return;
     }
     }


     LuxuryPrice buyGood = null;
     // get amount
//     for (Entry<StringID, LuxuryPrice> entry : currentRegion.getPrices().entrySet()) {
    final String item = tokens[3 + je];
    for (Entry entry : currentRegion.getPrices().entrySet()) {
    int amount = 0;
      LuxuryPrice price = entry.getValue();
     if (ALLOrder.equalsIgnoreCase(tokens[2 + je])) {
       if (price.getPrice() < 0) {
      if (KRAUTOrder.equals(item) || LUXUSOrder.equals(item)) {
        buyGood = price;
        addNewError("GIB xyz ALLES " + item + " statt GIB xyz " + item);
      } else {
       }
         if (currentRegion.getOldPrices() != null
      giveAll(targetId, w, item, null);
            && currentRegion.getOldPrices().get(entry.getKey()) != null
      return;
            && price.getPrice() < currentRegion.getOldPrices().get(entry.getKey()).getPrice()) {
    } else {
          addNewWarning("Preis gesunken: " + price.getItemType());
      try {
         }
         amount = Integer.parseInt(tokens[2 + je]);
      } catch (NumberFormatException e) {
        amount = 0;
        addNewError("Zahl oder ALLES erwartet");
         return;
       }
       }
     }
     }


     removeOrdersLike(SELLOrder + ".*", true);
     // get full amount (=amount * persons)
    removeOrdersLike(BUYOrder + ".*", true);
     int fullAmount = amount;
 
     if (je == 1) {
    // Gueterzahl intitialisieren
      if (target == null) {
     int guetersumme = 0;
        addNewMessage("Einheit nicht gefunden; kann Menge nicht überprüfen");
 
      } else {
     if (amount > 0 && (volume <= 0 || buyGood == null)) {
        fullAmount = target.getModifiedPersons() * amount;
      addNewError("Kein Handel möglich");
      }
     }
     }


     // Soll eingekauft werden?
     // check availibility
     if (volume > 0 && amount > 0 && buyGood != null) {
     if (getItemCount(currentUnit, item) < fullAmount) {
      // Berechne noetige Geldmenge fuer Einkauf (einfacher: Modulorechnung, aber wegen
      if (w.contains(Warning.C_AMOUNT)) {
      // Rundungsfehler nicht umsetzbar)
        addNewWarning("zu wenig " + item);
      int hilfNochUebrig = amount;
      } else {
      int hilfFaktor = 1;
        addNewMessage("zu wenig " + item);
      int geldNoetig = 0;
      while (hilfNochUebrig > 0) {
        if (hilfNochUebrig > volume) {
          hilfNochUebrig -= volume;
          geldNoetig -= (volume * hilfFaktor * buyGood.getPrice()); // price is negative
          hilfFaktor++;
        } else {
          geldNoetig -= (hilfNochUebrig * hilfFaktor * buyGood.getPrice());
          hilfNochUebrig = 0;
        }
       }
       }
      fullAmount = getItemCount(currentUnit, item);
      je = 0;
    }


       addNeed("Silber", currentUnit, geldNoetig, geldNoetig, TRADE_PRIORITY);
    // make GIVE order
    if (fullAmount > 0) {
       giveTransfer(targetId, item, fullAmount, false);
    }
  }


      // Einkaufsbefehl setzen, wenn notwendig
  private void giveAll(String targetId, Warning w, String filterItem, ItemCategory filterCategory) {
      if (amount > 0) {
    for (Item item : getUnitItems(currentUnit, filterItem, filterCategory)) {
        addNewOrder(BUYOrder + " " + amount + " " + buyGood.getItemType().getOrderName(), true);
      String itemName = item.getOrderName();
        guetersumme += amount;
      int amount = getSupply(itemName, currentUnit).getAmount();
      }
      giveTransfer(targetId, itemName, amount, true);
     }
     }
  }


    if (volume > 0 && buyGood != null) {
  private Collection<Item> getUnitItems(Unit unit, String filterItem, ItemCategory filterCategory) {
//      List<String> goods = new LinkedList<String>();
    Collection<Item> items = new ArrayList<Item>();
      List goods = new LinkedList();
    for (Item item : unit.getItems()) {
      // Verkaufsbefehl setzen, wenn notwendig
       if ((filterItem != null && filterItem.equals(
       if (tokens.length > 2 && ALLOrder.equals(tokens[2])) {
          item.getOrderName()))
        if (world.getRules().getItemCategory("luxuries") == null) {
           ||
           addNewError("Spiel kennt keine Luxusgüter");
           (filterCategory != null && filterCategory.equals(
        } else {
              item.getItemType().getCategory()))
           for (ItemType luxury : world.getRules().getItemTypes()) {
          || (filterItem == null && filterCategory == null)) {
            if (!luxury.equals(buyGood.getItemType())
         items.add(item);
                && world.getRules().getItemCategory("luxuries").equals(luxury.getCategory())) {
              goods.add(luxury.getOrderName());
            }
          }
        }
      } else {
         for (int i = 2; i < tokens.length; ++i) {
          goods.add(tokens[i]);
        }
       }
       }
    }
    return items;
  }


      int maxAmount = buySkill.getLevel() * currentUnit.getPersons() * 10;
  /**
 
  * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
      boolean skillWarning = false;
  * <code>// $cript Benoetige JE amount item [priority]</code><br />
      for (String luxury : goods) {
  * <code>// $cript Benoetige FUSS|PFERD Pferd [priority]</code><br />
        int goodAmount = volume;
  * <code>// $cript Benoetige ALLES [item] [priority]</code><br />
        if (goodAmount > maxAmount - guetersumme) {
  * Tries to transfer the maxAmount of item from other units to this unit. Issues warning if
          goodAmount = maxAmount - guetersumme;
  * minAmount cannot be supplied. <code>Benoetige JE</code> tries to reserve amount of item for
          skillWarning = true;
  * every person in the unit. Fractional amounts are possible and rounded up.
        }
  * <code>Benoetige ALLES item</code> is equivalent to <code>Benoetige 0 infinity item</code>,
        if (goodAmount > getSupply(luxury))
  * <code>Benoetige ALLES</code> is equivalent to <code>Benoetige ALLES</code> for every itemtype
          if (ALLOrder.equals(tokens[2])) {
  * in the region.<br/>
            goodAmount = getSupply(luxury);
  * <code>Benoetige KRAUT</code> is the same for every herb type in the region.<br/>
          }
  * <code>BenoetigeFremd unit (JE amount)|(minAmount [maxAmount]) item [priority] [warning...]</code>
  * <br />
  * <code>BenoetigeFremd</code> does the same, but for the given unit instead of the current unit.
  * Needs with higher priority are satisfied first. If no priority is given,
  * {@link #DEFAULT_PRIORITY} is used.
  */
  protected void commandNeed(String[] tokens) {
    Unit unit = currentUnit;
    Warning w = new Warning(true);


        if (goodAmount > 0) {
    String sOther = "???";
          addNeed(luxury, currentUnit, goodAmount, goodAmount, TRADE_PRIORITY, warning);
    if (tokens[0].equals("BenoetigeFremd")) {
        }
      if (tokens.length < 3) {
 
        addNewError("zu wenig Argumente");
        if (goodAmount > 0) {
         return;
          if (goodAmount == volume) {
            addNewOrder(SELLOrder + " " + ALLOrder + " " + luxury, true);
          } else {
            addNewOrder(SELLOrder + " " + goodAmount + " " + luxury, true);
          }
        }
        guetersumme += goodAmount;
         if (skillWarning) {
          break;
        }
       }
       }
      sOther = tokens[1];
      unit = helper.getUnit(tokens[1]);


       // Einheit gut genug?
       // erase unit token for easier processing afterwards
       if (skillWarning && warning.contains(C_SKILL)) {
      tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
         addNewError("Einheit hat zu wenig Handelstalent!");
      tokens[0] = "BenoetigeFremd";
       }
 
      // only benoetigefremd can have warnings!
      tokens = w.parse(tokens);
      // foreign implies amount
       if (w.contains(Warning.C_FOREIGN)) { // FIXME test
         w.add(Warning.W_AMOUNT);
       }
     }
     }


     setConfirm(currentUnit, true);
     int tokenCount = tokens.length;
  }


  /**
     int priority;
  * <code>// $cript Quartiermeister [[Menge1 Gut 1]...]</code>: learn perception, allow listed
  * amount of goods. If other goods are detected, do not confirm orders.
  */
  protected void commandQuartermaster(String[] tokens) {
     learn(currentUnit, Collections.singleton(new Skill(world.getRules().getSkillType(
        EresseaConstants.S_WAHRNEHMUNG), 30, 10, 1, true)));
     try {
     try {
       for (Item item : currentUnit.getItems()) {
       priority = Integer.parseInt(tokens[tokenCount - 1]);
        boolean okay = false;
      tokenCount--;
        if (item.getItemType().getID().equals(EresseaConstants.I_USILVER)
            && item.getAmount() < 1000) {
          okay = true;
        }
        for (int i = 1; !okay && i < tokens.length - 1; i += 2) {
          if (item.getName().equals(tokens[i + 1])) {
            if (item.getAmount() <= Integer.parseInt(tokens[i])) {
              okay = true;
              break;
            }
          }
        }
        setConfirm(currentUnit, okay);
      }
     } catch (NumberFormatException e) {
     } catch (NumberFormatException e) {
       addNewError("ungültige Zahl ");
       priority = DEFAULT_PRIORITY;
     }
     }
    setConfirm(currentUnit, true);
  }


  /**
    tokens = Arrays.copyOf(tokens, tokenCount);
  * <code>// $cript Sammler [frequenz]</code>: collect herbs if there are at least "viele",
 
  * research herbs every frequenz rounds.
     if (tokens.length < 2 || tokens.length > 6) {
  */
       addNewError("falsche Anzahl Argumente");
  protected void commandCollector(String[] tokens) {
      return;
     if (tokens.length > 2) {
       addNewError("zu viele Argumente");
     }
     }
    int modulo = Integer.MAX_VALUE;
 
     if (tokens.length > 1) {
     if (!testUnit(sOther, unit, w, true))
       try {
       return;
        modulo = Integer.parseInt(tokens[1]);
    if (unit == null) {
       } catch (NumberFormatException e) {
       if (sOther == null) {
         addNewError("ungültige Zahl " + tokens[1]);
         addNewError("Einheitennummer fehlt");
         return;
         return;
       }
       }
      unit = findUnit(sOther);
     }
     }
 
    try {
    if (currentRegion.getRegionType().isOcean()) {
      if (ALLOrder.equals(tokens[1])) {
      addNewError("Sammeln nicht möglich!");
        if (tokens.length > 2) {
      return;
          addNeed(tokens[2], unit, 0, Integer.MAX_VALUE, priority, w);
    }
        } else {
    removeOrdersLike(MAKEOrder + " " + "[^T].*", true);
          for (String item : supplyMapp.items()) {
    removeOrdersLike(getResearchOrder() + ".*", true);
            addNeed(item, unit, 0, Integer.MAX_VALUE, priority, w);
    if (modulo != Integer.MAX_VALUE
          }
         && (world.getDate().getDate() % modulo == 0 || currentRegion.getHerbAmount() == null || (!currentRegion
         }
             .getHerbAmount().equals("viele") && !currentRegion.getHerbAmount().equals("sehr viele")))) {
      } else if (EACHOrder.equals(tokens[1])) {
      addNewOrder(getResearchOrder(), true);
        if (unit.getPersons() <= 0)
    } else {
          if (w.contains(Warning.C_AMOUNT)) {
      addNewOrder(MAKEOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER"), true);
             addNewWarning("Benoetige JE für leere Einheit");
    }
          } else {
  }
            addNewMessage("Benoetige JE für leere Einheit");
 
          }
  /**
        int amount = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[2]));
  * <code>KrautKontrolle [[[direction...] PAUSE]...]</code> move until the next PAUSE, if pause is
        int maxTokens = 4;
  * reached, research herbs.
        int amount2 = amount;
  */
        if (tokens.length > 4) {
  protected void commandControl(String[] tokens) {
          ++maxTokens;
    for (Order o : currentUnit.getOrders2()) {
          boolean each2 = false;
      String order = o.getText();
          int numberToken = 3;
       if (order.startsWith(ROUTEOrder)) {
          if (EACHOrder.equals(tokens[3])) {
         if (order.substring(order.indexOf(" ")).trim().startsWith(PAUSEOrder)) {
            each2 = true;
           // end of route, FORSCHE
            ++numberToken;
          addNewOrder(currentOrder, false);
            ++maxTokens;
          addNewOrder(getResearchOrder(), true);
            amount2 = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[4]));
          removeOrdersLike(ROUTEOrder + ".*", true);
          } else {
            String item = tokens[4];
            amount2 = getAmountWithHorse(unit, tokens[3], item);
          }
        }
        if (tokens.length > maxTokens) {
          addNewError("zu viele Parameter");
        }
        String item = tokens[maxTokens - 1];
        addNeed(item, unit, amount, amount2, priority, w);
       } else if (KRAUTOrder.equals(tokens[1])) {
         if (tokens.length > 2) {
          addNewError("zu viele Parameter");
        }
        if (world.getRules().getItemCategory("herbs") == null) {
           addNewError("Spiel kennt keine Kräuter");
         } else {
         } else {
           // continue on route
           for (ItemType itemType : world.getRules().getItemTypes()) { // currentUnit.getItems()
          addNewOrder(currentOrder, false);
            if (world.getRules().getItemCategory("herbs").equals(itemType.getCategory())) {
              addNeed(itemType.getOrderName(), unit, 0, Integer.MAX_VALUE, priority, w);
            }
          }
         }
         }
         return;
      } else if (LUXUSOrder.equals(tokens[1])) {
      }
         if (tokens.length > 2) {
    }
          addNewError("zu viele Parameter");
    // no route order -- create new one
        }
    removeOrdersLike(getResearchOrder() + ".*", true);
        if (world.getRules().getItemCategory("luxuries") == null) {
    StringBuilder newOrder = new StringBuilder();
          addNewError("Spiel kennt keine Luxusgüter");
    newOrder.append(PCOMMENTOrder).append(" ").append(scriptMarker).append(" ").append(tokens[0]);
        } else {
    StringBuilder moveOrder = new StringBuilder(ROUTEOrder);
          for (ItemType itemType : world.getRules().getItemTypes()) { // currentUnit.getItems()
    boolean pause = false;
            if (world.getRules().getItemCategory("luxuries").equals(itemType.getCategory())) {
    for (int i = 1; i < tokens.length; ++i) {
              addNeed(itemType.getOrderName(), unit, 0, Integer.MAX_VALUE, priority, w);
      if (!pause) {
            }
        // add move order until first PAUSE
          }
         moveOrder.append(" ").append(tokens[i]);
        }
      } else if (tokens.length > 4 || tokens.length < 3) {
         addNewError("falsche Anzahl Argumente");
       } else {
       } else {
         // add to $cript order after first PAUSE
         String item = tokens[tokens.length - 1];
         newOrder.append(" ").append(tokens[i]);
         int minAmount = getAmountWithHorse(unit, tokens[1], item);
      }
        int maxAmount = tokens.length == 3 ? minAmount : getAmountWithHorse(unit, tokens[2], item);
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
         addNeed(item, unit, minAmount, maxAmount, priority, w);
         pause = true;
       }
       }
    } catch (NumberFormatException exc) {
      addNewError("Ungültige Zahl in Benoetige: " + exc.getMessage());
     }
     }
     moveOrder.append(" ").append(PAUSEOrder);
  }
    pause = false;
 
     for (int i = 1; i < tokens.length; ++i) {
  private int getAmountWithHorse(Unit unit, String amount, String item) {
       if (!pause) {
     if (isHorse(item))
         // append movement until first PAUSE to back of $cript order
      if (HORSEOrder.equals(amount))
        newOrder.append(" ").append(tokens[i]);
        return world.getGameSpecificRules().getMaxHorsesRiding(unit);
      else if (FOOTOrder.equals(amount))
        return world.getGameSpecificRules().getMaxHorsesWalking(unit);
     return Integer.parseInt(amount);
  }
 
  /**
  * <code>// $cript BerufDepotVerwalter [[ZusatzMin] ZusatzMax]</code><br />
  * Collects all free items in the region, Versorge 100, calls Ueberwache
  */
  protected void commandDepot(String[] tokens) {
    if (tokens.length > 3) {
      addNewError("zu viele Argumente");
    }
    commandMonitor(new String[] { "Ueberwache" });
 
    int costs = 0;
    for (Unit u : currentRegion.units()) {
       if (currentFactions.containsKey(u.getFaction())) {
         costs += u.getRace().getMaintenance() * u.getPersons();
       }
       }
       if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
    }
         pause = true;
    int zusatz1 = 0, zusatz2 = 0;
    if (tokens.length > 1) {
       try {
        zusatz1 = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
          zusatz2 = Integer.parseInt(tokens[2]);
        }
      } catch (NumberFormatException e) {
         addNewError("Zahl erwartet");
       }
       }
     }
     }
     addNewOrder(newOrder.toString(), true);
     addNeed("Silber", currentUnit, costs + zusatz1, costs + zusatz1 + zusatz2,
     addNewOrder(moveOrder.toString(), true);
        DEPOT_SILVER_PRIORITY);
 
     commandNeed(new String[] { "Benoetige ", ALLOrder, String.valueOf(DEPOT_PRIORITY) });
    commandSupply(new String[] { "Versorge", "100" });
   }
   }


   /**
   /**
   * <code>// $cript RekrutiereMax [min [max]] [race]</code>: recruit as much as possible; warn if
   * <code>Versorge [[item1]...] priority</code> -- set supply priority. Units with negative
   * less than min are possible
  * priority only deliver for minimum needs. Needs are satisfied in descending order of priority.
   * If no items are given, the priority is adjusted for alle the unit's items.
   */
   */
   protected void commandRecruit(String[] tokens) {
   protected void commandSupply(String[] tokens) {
     if (tokens.length > 4) {
    int priority = 0;
       addNewError("zu viele Argumente");
     if (tokens.length < 2) {
       addNewError("zu wenig Argumente");
    }
    try {
      priority = Integer.parseInt(tokens[tokens.length - 1]);
    } catch (NumberFormatException e) {
      addNewError("Zahl erwartet");
      return;
     }
     }
    int min = 1;
     if (tokens.length == 2) {
    String race = null;
       for (Item item : currentUnit.getItems()) {
    int max = Integer.MAX_VALUE;
        Supply supply = getSupply(item.getOrderName(), currentUnit);
     if (tokens.length > 1) {
         if (supply != null) {
       try {
           supply.setPriority(priority);
        min = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
          try {
            max = Integer.parseInt(tokens[2]);
            if (tokens.length > 3) {
              race = tokens[3];
            }
          } catch (NumberFormatException e) {
            max = Integer.MAX_VALUE;
            race = tokens[2];
            if (tokens.length > 3) {
              addNewError("zu viele Argumente");
            }
          }
        }
      } catch (NumberFormatException e) {
        min = 1;
        race = tokens[1];
         if (tokens.length > 2) {
           addNewError("zu viele Argumente");
         }
         }
      }
    } else {
      for (int i = 1; i < tokens.length - 1; ++i) {
        setSupplyPriority(currentUnit, tokens[i], priority);
       }
       }
     }
     }
  }


     Race effRace = race == null ? currentUnit.getRace() : helper.getRace(race);
  private void setSupplyPriority(Unit unit, String itemToken, int priority) {
     if (effRace == null) {
     Collection<Item> items = null;
       addNewError("Unbekannte Rasse");
    if (KRAUTOrder.equalsIgnoreCase(itemToken)) {
       return;
      items = getCategoryItems(unit, "herbs", "Kräuter");
    } else if (LUXUSOrder.equalsIgnoreCase(itemToken)) {
      items = getCategoryItems(unit, "luxury", "Luxusgüter");
     } else if (TRANKOrder.equalsIgnoreCase(itemToken)) {
       items = getCategoryItems(unit, "potion", "Tränke");
    } else {
       items = getUnitItems(unit, itemToken, null);
     }
     }


     if (currentUnit.getPersons() != 0 && !effRace.equals(currentUnit.getRace())) {
     if (items != null) {
      addNewWarning("race != unit race");
      for (Item item : items) {
        Supply supply = getSupply(item.getOrderName(), currentUnit);
        if (supply != null) {
          supply.setPriority(priority);
        }
      }
     }
     }
  }


    int amount = world.getGameSpecificRules().getRecruitmentLimit(currentUnit, effRace);
  private Collection<Item> getCategoryItems(Unit unit, String category, String categoryName) {
 
     ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
     if (currentUnit.getPersons() + amount >= max) {
     if (itemCategory == null) {
      addNewWarning("Rekrutierungslimit erreicht");
       addNewError("Spiel kennt keine " + categoryName);
      amount = Math.min(max - currentUnit.getPersons(), amount);
       return Collections.emptyList();
    }
 
     if (amount < min) {
       addNewError("Nicht genug Rekruten");
    }
 
    if (effRace.getRecruitmentCosts() > 0) {
      int costs = amount * effRace.getRecruitmentCosts();
      addNeed("Silber", currentUnit, costs, costs, DEFAULT_PRIORITY);
    } else {
      addNewWarning("Rekrutierungskosten unbekannt");
    }
    if (amount > 0) {
      getRecruitOrder(amount, effRace);
       addNewOrder(getRecruitOrder(amount, effRace != currentUnit.getRace() ? effRace : null), true);
     }
     }
    return getUnitItems(unit, null, itemCategory);
   }
   }


   // ///////////////////////////////////////////////////////
   /**
   // HELPER functions
  * <code>Kapazitaet FUSS|PFERD|SCHIFF|amount</code> -- ensure that a unit's capacity is not
  // ///////////////////////////////////////////////////////
  * exceeded
  */
   protected void commandCapacity(String[] tokens) {
    int capacity = 0;
    int slack = 0;
    if (tokens.length < 2) {
      addNewError("zu wenig Argumente");
      return;
    } else if (tokens.length > 2) {
      if (tokens.length == 4 && "-".equals(tokens[2])) {
        try {
          slack = Integer.parseInt(tokens[3]);
        } catch (NumberFormatException e) {
          addNewError("Zahl erwartet");
        }
      } else {
        addNewError("zu viele Argumente");
      }
    }
    MovementEvaluator movement = world.getGameSpecificStuff().getMovementEvaluator();
    int load = movement.getModifiedLoad(currentUnit);
    try {
      capacity = Integer.parseInt(tokens[1]);
    } catch (NumberFormatException e) {
      if (HORSEOrder.equals(tokens[1])) {
        capacity = movement.getPayloadOnHorse(currentUnit);
      } else if (FOOTOrder.equals(tokens[1])) {
        capacity = movement.getPayloadOnFoot(currentUnit);
      } else if (SHIPOrder.equals(tokens[1])) {
        Ship ship = currentUnit.getModifiedShip();
        if (ship == null || ship.getOwnerUnit() != currentUnit) {
          capacity = Integer.MAX_VALUE;
          addNewWarning("Einheit ist nicht Kapitän.");
        } else {
          capacity = ship.getMaxCapacity() - ship.getModifiedLoad() + load;
        }
      } else {
        addNewError("Zahl erwartet");
        return;
      }
      if (isCapacityError(capacity)) {
        addNewWarning("Zu viele Pferde.");
        capacity = load;
      }
    }


  protected boolean hasEntertain() {
    // int load = movement.getModifiedLoad(currentUnit);
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
     capacities.put(currentUnit, capacity - slack - load);
        getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder));
   }
   }


   protected boolean hasWork() {
   private boolean isCapacityError(int capacity) {
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
     return capacity == MovementEvaluator.CAP_NO_HORSES
         getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder));
         || capacity == MovementEvaluator.CAP_UNSKILLED;
   }
   }


   protected void initSupply() {
  /**
     if (needMap == null) {
  * <code>// $script +x [Arguments...]</code><br />
//       needMap = new LinkedHashMap<String, Map<Integer, Map<Unit, Need>>>();
  * If x<=1, the rest of the line is added as a warning to this unit. Otherwise x is decreased by
       needMap = new LinkedHashMap();
  * one. If x==1, the order is removed.
    } else {
  */
       needMap.clear();
   protected void commandWarning(String[] tokens) {
     }
    int delay = -1;
     if (supplyMap == null) {
    try {
//       supplyMap = new LinkedHashMap<String, Map<Unit, Supply>>();
      delay = Integer.parseInt(tokens[0].substring(1));
       supplyMap = new LinkedHashMap();
    } catch (NumberFormatException e) {
    } else {
      addNewError("Zahl erwartet");
       supplyMap.clear();
      return;
    }
     if (delay <= 1) {
       StringBuilder warning = new StringBuilder();
      String foo = currentOrder.substring(currentOrder.indexOf("+"));
       warning.append(foo.indexOf(" ") >= 0 ? foo.substring(foo.indexOf(" ") + 1) : "");
       addNewWarning(warning.toString(), false);
     }
     if (delay != 1) {
       StringBuilder newCommand = new StringBuilder(createScriptCommand()).append("+");
      newCommand.append(Math.max(0, delay - 1));
       for (int i = 1; i < tokens.length; ++i) { // skip "+x"
        newCommand.append(" ").append(tokens[i]);
      }
       addNewOrder(newCommand.toString(), true);
     }
     }
  }


    for (Unit u : currentRegion.units()) {
  /**
      if (currentFactions.containsKey(u.getFaction())) {
  * <code>// $cript Lerne Talent1 Stufe1 [Max1] [[Talent2 Stufe2 [Max2]...]</code><br />
        for (Item item : u.getItems()) {
  * Tries to learn skills in given ratio. For example,
          putSupply(item.getOrderName(), u, item.getAmount());
  * <code>// $cript Lerne Hiebwaffen 10 99 Ausdauer 5 3</code> tries to learn to (Hiebwaffen 2, Ausdauer
        }
  * 1), (Hiebwaffen 4, Ausdauer 2), and so forth, but Ausdauer only until level 3 is reached.
  */
  protected void commandLearn(String[] tokens) {
    if (tokens.length < 3) {
      addNewError("falsche Anzahl Argumente");
      return;
    }
    List<SkillSpec> targetSkills = new LinkedList<SkillSpec>();


        // TODO take RESERVE or GIVE orders into account?
    for (int i = 1; i < tokens.length;) {
 
      int j = i;
        // // subtract reserved items from supply amount of unit
      try {
         // for (ReserveRelation relation : u.getRelations(ReserveRelation.class)) {
         if (i < tokens.length - 1) {
        // Supply supply = getSupply(relation.itemType.getName(), u);
          SkillType skill = world.getRules().getSkillType(tokens[j++]);
        // if (supply != null && relation.source == u) {
          int level = Integer.parseInt(tokens[j]), max = 99;
        // supply.setAmount(supply.getAmount() - relation.amount);
          j++; // no exception
        // }
          if (i < tokens.length - 2) {
        // }
            try {
        // // subtract transferred items (by GIVE orders) from supply amount of source unit (don't
              max = Integer.parseInt(tokens[j]);
        // add
              j++;
        // // to target unit)
            } catch (NumberFormatException e) {
        // for (ItemTransferRelation relation : u.getRelations(ItemTransferRelation.class)) {
              max = 99;
        // Supply supply = getSupply(relation.itemType.getName(), u);
            }
         // if (supply != null && relation.source == u) {
          }
         // supply.setAmount(supply.getAmount() - relation.amount);
          if (skill == null) {
        // }
            addNewError("unbekanntes Talent " + tokens[i]);
         // }
          } else {
            targetSkills.add(new SkillSpec(skill, level, max));
          }
        } else {
          addNewError("unerwartetes Token " + tokens[i]);
         }
      } catch (NumberFormatException e) {
         addNewError("ungültige Stufe " + tokens[i]);
      } finally {
         i = Math.max(j, i + 1);
       }
       }
     }
     }
    learn(currentUnit, targetSkills);
   }
   }


   /**
   /**
   * Adds a supply to the supplyMap
   * <code>Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code><br />
   *  
  * Tries to learn best skill and acquire equipment. If no skill is given, best weapon skill is
   * @param item
  * selected. If no weapon is given, best matching weapon is acquired and so on. Preference is
   * @param unit
  * always for RESERVEing stuff the unit already has. Waffe, Schild, Rüstung can be "null" if
   * @param amount
  * nothing should be reserved or "best" (the default behavior).<br />
  * Warning can be:<br />
  * nie: issues no warnings at all,
  * Talent: only warns if no skill is given and no best skill exists,
   * Waffe: additionally warn if no weapon can be acquired,
   * Schild: additionally warn if no shield,
   * Rüstung: additionally warn if no armor.
   * Default warning level is "Waffe".
   */
   */
   protected Supply putSupply(String item, Unit unit, int amount) {
   protected void commandSoldier(String[] tokens) {
//     Map<Unit, Supply> itemSupplyMap = supplyMap.get(item);
     String warning = tokens[tokens.length - 1];
     Map itemSupplyMap = supplyMap.get(item);
    if (!(Warning.W_NEVER.equals(warning) || Warning.W_SKILL.equals(warning) || Warning.W_WEAPON
     if (itemSupplyMap == null) {
        .equals(warning)
//       itemSupplyMap = new LinkedHashMap<Unit, Supply>();
        || Warning.W_SHIELD.equals(warning) || Warning.W_ARMOR.equals(warning))) {
       itemSupplyMap = new LinkedHashMap();
      warning = null;
       supplyMap.put(item, itemSupplyMap);
     }
    String skill = null;
    if (tokens.length > 1 + (warning == null ? 0 : 1)) {
      skill = tokens[1];
    }
    String weapon = null;
     if (tokens.length > 2 + (warning == null ? 0 : 1)) {
       weapon = tokens[2];
    }
    String shield = null;
    if (tokens.length > 3 + (warning == null ? 0 : 1)) {
      shield = tokens[3];
    }
    String armor = null;
    if (tokens.length > 4 + (warning == null ? 0 : 1)) {
       armor = tokens[4];
    }
 
    if (warning == null) {
       warning = Warning.W_WEAPON;
     }
     }
     Supply result = new Supply(unit, item, getItemCount(unit, item));
 
     itemSupplyMap.put(unit, result);
    // if (NULL.equals(skill)) {
     return result;
    // skill = null;
    // }
     // if (NULL.equals(weapon)) {
    // weapon = null;
    // }
    // if (NULL.equals(shield)) {
    // shield = null;
     // }
    // if (NULL.equals(armor)) {
    // armor = null;
     // }
 
    soldier(currentUnit, skill, weapon, shield, armor, warning);
   }
   }


   /**
   /**
   * Tries to satisfy all needs in the current needMap by adding GIVE orders to supplyers.
   * <code>// $cript BerufBotschafter [minimum money] [Talent|command]</code> -- if we have at least
  * minimum money (default 100), learn skill or execute command<br />
  *
   */
   */
   protected void satisfyNeeds() {
   protected Collection<String> commandEmbassador(String[] tokens) {
     for (String item : needMap.keySet()) {
     SkillType skill = null;
       // sort supplies by priority
    int minimum = 100;
//      Map<Unit, Supply> itemSupply = supplyMap.get(item);
    int actionToken = 1;
       Map itemSupply = supplyMap.get(item);
    if (tokens.length > 1) {
       if (itemSupply == null) {
       try {
         itemSupply = Collections.emptyMap();
        minimum = Integer.parseInt(tokens[1]);
        actionToken = 2;
       } catch (NumberFormatException e) {
        actionToken = 1;
      }
    }
    String mode = "";
    if (tokens.length > actionToken) {
      if (WORKOrder.equals(tokens[actionToken])) {
        mode = WORKOrder;
       } else if (ENTERTAINOrder.equals(tokens[actionToken])) {
         mode = ENTERTAINOrder;
      } else if (TAXOrder.equals(tokens[actionToken])) {
        mode = TAXOrder;
       } else {
       } else {
         Supply[] sorted = null;
         --actionToken;
         sorted = itemSupply.values().toArray(new Supply[0]);
      }
      ++actionToken;
    }
    if (tokens.length > actionToken) {
      skill = getSkillType(tokens[actionToken]);
    } else {
      skill = getSkillType(EresseaConstants.S_WAHRNEHMUNG.toString());
      if (skill == null) {
         skill = getSkillType(EresseaConstants.S_AUSDAUER.toString());
      }
    }


        // this causes problems in BeansShell; I don't know why
    commandClear(new String[] { "Loeschen" });
        // Arrays.sort(sorted);
        // doing insertion sort instead
        for (int j = 1; j < sorted.length; ++j) {
          for (int i = 0; i < j; ++i) {
            if (sorted[j].compareTo(sorted[i]) < 0) {
              // if (current.priority > sorted[i].priority) {
              Supply temp = sorted[i];
              sorted[i] = sorted[j];
              sorted[j] = temp;
            }
          }
        }


         itemSupply.clear();
    if (helper.getSilver(currentUnit) < minimum) {
        for (Supply supply : sorted) {
      if (mode == ENTERTAINOrder ||
           itemSupply.put(supply.unit, supply);
          (mode == "" && hasEntertain() && currentUnit.getSkill(EresseaConstants.S_UNTERHALTUNG) != null
              && currentUnit.getSkill(EresseaConstants.S_UNTERHALTUNG).getLevel() > 0)) {
         if (world.getGameSpecificRules().getMaxEntertain(currentRegion) < 20 * currentUnit.getSkill(
            EresseaConstants.S_UNTERHALTUNG).getLevel() * currentUnit.getPersons()) {
           addNewWarning("Bauern zu arm");
         }
         }
      }
        if (currentUnit.getShip() != null && isGuarded(currentRegion, currentUnit)) {
 
           addNewWarning("Region wird bewacht");
//      Map<Unit, Integer> reserves = new HashMap<Unit, Integer>();
      Map reserves = new HashMap();
 
//      Map<Integer, Map<Unit, Need>> pMap = needMap.get(item);
      Map pMap = needMap.get(item);
 
//      List<Integer> prios = new ArrayList<Integer>();
      List prios = new ArrayList();
      for (Integer key : pMap.keySet()) {
        prios.add(key);
      }
      Collections.sort(prios);
      Collections.reverse(prios);
 
      for (Integer prio : prios) {
//        Map<Unit, Need> nMap = pMap.get(prio);
        Map nMap = pMap.get(prio);
        // try to satisfy minimum need by own items
        for (Need need : nMap.values()) {
           reserveNeed(need, true, reserves);
         }
         }
 
        addNewOrder(ENTERTAINOrder, true);
        // try to satisfy minimum needs with GIVE
      } else if (mode == TAXOrder ||
         for (Need need : nMap.values()) {
          (mode == "" && hasTax() && currentUnit.getSkill(EresseaConstants.S_STEUEREINTREIBEN) != null
           giveNeed(need, true);
              && currentUnit.getSkill(EresseaConstants.S_STEUEREINTREIBEN).getLevel() > 0)) {
         if (currentRegion.getSilver() < 20 * currentUnit.getSkill(EresseaConstants.S_STEUEREINTREIBEN).getLevel()) {
           addNewWarning("Bauern zu arm");
         }
         }
 
         if (isGuarded(currentRegion, currentUnit)) {
         // add warnings for unsatisfied needs
           addNewWarning("Region wird bewacht");
        for (Need need : nMap.values()) {
           if (need.getMinAmount() > 0 && need.getWarning().contains(C_AMOUNT)) {
            addWarning(need.getUnit(), "braucht " + need.getMinAmount() + " mehr " + need.getItem());
          }
         }
         }


         // try to satisfy max needs, ignore infinite needs first
         addNewOrder(TAXOrder, true);
        for (Need need : nMap.values()) {
      } else if (mode == WORKOrder || (mode == "" && hasWork())) {
          if (need.getAmount() != Integer.MAX_VALUE) {
        if (world.getGameSpecificRules().getMaxWorkers(currentRegion) < currentUnit.getPersons()) {
            reserveNeed(need, false, reserves);
          addNewWarning("zu wenig Arbeitsplätze");
          }
         }
         }
 
         if (currentUnit.getShip() != null && isGuarded(currentRegion, currentUnit)) {
         for (Need need : nMap.values()) {
           addNewWarning("Region wird bewacht");
          if (need.getAmount() != Integer.MAX_VALUE) {
            giveNeed(need, false);
          }
        }
 
        // now, finally, satisfy infinite needs
        for (Need need : nMap.values()) {
          if (need.getAmount() == Integer.MAX_VALUE) {
            reserveNeed(need, false, reserves);
          }
        }
 
        for (Need need : nMap.values()) {
          if (need.getAmount() == Integer.MAX_VALUE) {
            giveNeed(need, false);
          }
        }
 
        // add messages for unsatisfied needs
        for (Need need : nMap.values()) {
           if (need.getMinAmount() <= 0 && need.getMaxAmount() > 0
              && need.getMaxAmount() != Integer.MAX_VALUE) {
            need.getUnit().addOrder("; braucht " + need.getMaxAmount() + " mehr " + need.getItem(),
                false);
          }
         }
         }
        addNewOrder(WORKOrder, true);
      } else {
        addNewWarning("Einheit kann nichts verdienen");
      }
    } else if (skill == null) {
      // other order
      StringBuilder order = new StringBuilder(createScriptCommand()).append("1 ");
      if (tokens.length > actionToken) {
        order.append(tokens[actionToken]);
      }
      for (int i = actionToken + 1; i < tokens.length; ++i) {
        order.append(" ").append(tokens[i]);
       }
       }
       for (Unit u : reserves.keySet()) {
       return Collections.singletonList(order.toString());
 
      // addNewOrder(order.toString(), true);
        int amount = reserves.get(u);
    } else {
        if (amount > 0) {
      learn(currentUnit, Collections.singleton(new SkillSpec(skill, 10, 99)));
          if (amount == u.getPersons()) {
      if (tokens.length > actionToken + 1) {
            u.addOrder(getReserveOrder(u, item // + COMMENTOrder + need.toString()
         addNewError("zu viele Argumente");
                , 1, true), false);
          } else {
            u.addOrder(getReserveOrder(u, item, amount, false), false);
          }
         }
       }
       }
     }
     }
    return Collections.emptyList();
  }


  private boolean isGuarded(Region currentRegion2, Unit currentUnit2) {
    for (Unit guard : currentRegion.getGuards()) {
      if (!Units.isAllied(guard.getFaction(), currentUnit.getFaction(),
          EresseaConstants.A_GUARD))
        return true;
    }
    return false;
   }
   }


  /**
   protected void commandMonitor(String[] tokens) {
  * Tries to satisfy (minimum) need by a RESERVE order
     if (tokens.length > 1) {
  *
      addNewError("zu viele Argumente");
  * @param need
     }
  * @param min
  * @param reserves
  */
//   protected void reserveNeed(Need need, boolean min, Map<Unit, Integer> reserves) {
  protected void reserveNeed(Need need, boolean min, Map reserves) {
     int amount = min ? need.getMinAmount() : need.getAmount();
    Supply supply = getSupply(need.getItem(), need.getUnit());
     if (supply == null)
      return;


     // only suppliers with positive priority serve maximum needs
     // check if region units are allowed
     amount = Math.min(amount, supply.getAmount());
     Map<Faction, List<Unit>> warnings = new HashMap<Faction, List<Unit>>();
     if (amount > 0) {
     for (Unit u : currentRegion.units()) {
       need.reduceAmount(amount);
       if (u.getFaction() != currentUnit.getFaction()) {
      need.reduceMinAmount(amount);
        if (!(allowedUnits.containsKey(u.getFaction()) && (allowedUnits.get(u.getFaction())
      supply.reduceAmount(amount);
            .contains(u.getID()) || allowedUnits.get(u.getFaction()).contains(
      if (min) {
                currentRegion.getZeroUnit().getID())))) {
        if (reserves.containsKey(need.getUnit())) {
          if (!(requiredUnits.containsKey(u.getFaction()) && requiredUnits.get(u.getFaction())
          amount += reserves.get(need.getUnit());
              .contains(u.getID()))) {
            List<Unit> list = warnings.get(u.getFaction());
            if (list == null) {
              list = new LinkedList<Unit>();
              warnings.put(u.getFaction(), list);
            }
            list.add(u);
          }
         }
         }
        reserves.put(need.getUnit(), amount);
       }
       }
     }
     }
  }


  /**
    for (Entry<Faction, List<Unit>> entry : warnings.entrySet()) {
  * Tries to satisfy (minimum) need by a give order from supplyers.
      StringBuilder sb = new StringBuilder();
  *
       sb.append(entry.getKey()).append(" hat unerlaubte Einheiten:");
  * @param need
      int i = 0;
  * @param min
       for (Unit u : entry.getValue()) {
  */
         if (++i < 4) {
  protected void giveNeed(Need need, boolean min) {
           sb.append(" ");
    int amount = min ? need.getMinAmount() : need.getAmount();
           sb.append(u.toString());
    if (amount > 0) {
        } else {
       if (!supplyMap.containsKey(need.getItem()))
          sb.append(" ...");
        return;
       for (Supply supply : supplyMap.get(need.getItem()).values()) {
         if (supply.getUnit() != need.getUnit() && (min || supply.priority > 0)) {
           int giveAmount = Math.min(amount, supply.getAmount());
           if (giveAmount > 0) {
            supply.getUnit().addOrder(
                getGiveOrder(supply.getUnit(), need.getUnit().getID().toString(), need.getItem(),
                    giveAmount, false)
                    + COMMENTOrder + need.toString(), false);
            need.reduceAmount(giveAmount);
            need.reduceMinAmount(giveAmount);
            supply.reduceAmount(giveAmount);
            amount -= giveAmount;
          }
        }
        if (amount <= 0) {
           break;
           break;
         }
         }
       }
       }
      entry.getValue().clear();
      addNewWarning(sb.toString());
     }
     }
  }


  /**
    // check if required units are present
  * Returns the total supply for an item.
    for (Faction f : requiredUnits.keySet()) {
  *
      for (UnitID id : requiredUnits.get(f)) {
  * @param item Order name of the supplied item
        Unit u = world.getUnit(id);
  * @return The supply or 0, if none has been registered.
        if (u == null || u.getRegion() != currentUnit.getRegion()) {
  */
          addNewWarning("Einheit " + id + " der Partei " + f + " nicht mehr da.");
  protected int getSupply(String item) {
        }
//    Map<Unit, Supply> map = supplyMap.get(item);
    Map map = supplyMap.get(item);
    int goodAmount = 0;
    if (map != null) {
      for (Supply s : map.values()) {
        goodAmount += s.getAmount();
       }
       }
     }
     }
    return goodAmount;
   }
   }


   /**
   protected void commandAllow(String[] tokens) {
  * Returns a supply of a unit for an item.
    if (tokens.length < 3) {
  *
      addNewError("zu wenige Argumente");
  * @param item Order name of the supplied item
      return;
  * @param unit
    }
  * @return The supply or null, if none has been registered.
 
  */
    Faction faction = helper.getFaction(tokens[1]);
  protected Supply getSupply(String item, Unit unit) {
     Map<Faction, Set<UnitID>> map;
//     Map<Unit, Supply> map = supplyMap.get(item);
    if (tokens[0].equals("Erlaube")) {
     Map map = supplyMap.get(item);
      map = allowedUnits;
    if (map == null)
     } else {
       return null;
      map = requiredUnits;
    return map.get(unit);
    }
    if (faction == null) {
      addNewError("unbekannte Partei");
    } else {
      Set<UnitID> set = map.get(faction);
      if (set == null) {
        set = new HashSet<UnitID>();
        map.put(faction, set);
      }
      if (ALLOrder.equals(tokens[2])) {
        set.add(currentRegion.getZeroUnit().getID());
        if (tokens.length > 3) {
          addNewError("zu viele Argumente");
        }
       } else {
        for (int i = 2; i < tokens.length; ++i) {
          try {
            set.add(UnitID.createUnitID(tokens[i], world.base));
          } catch (NumberFormatException exc) {
            addNewError("Ungültige Einheitennummer " + tokens[i]);
          }
        }
      }
    }
   }
   }


   /**
   /**
   * Adds the specified amounts to the need of a unit for an item to the needMap.
   * <code>// $cript Ernaehre [amount] [nie]</code> -- Earn as much money as possible (or the specified
  *
   * amount), Versorge {@value #DEFAULT_EARN_PRIORITY}
  * @param item Order name of the required item
  * @param unit
  * @param minAmount
   * @param maxAmount
   */
   */
   protected void addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority) {
   protected void commandEarn(String[] tokens) {
     addNeed(item, unit, minAmount, maxAmount, priority, new Warning(true));
     int amount = -1;
  }
     Warning warning = new Warning(true);
 
     tokens = warning.parse(tokens);
  /**
     if (tokens.length > 1) {
  * Adds the specified amounts to the need of a unit for an item to the needMap.
       try {
  *
        amount = Integer.parseInt(tokens[1]);
  * @param item Order name of the required item
       } catch (NumberFormatException e) {
  * @param unit
         addNewError("ungültige Zahl " + tokens[1]);
  * @param minAmount
         return;
  * @param maxAmount
  */
  protected void addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority,
      Warning w) {
//    Map<Integer, Map<Unit, Need>> map = needMap.get(item);
     Map map = needMap.get(item);
    if (map == null) {
//      map = new LinkedHashMap<Integer, Map<Unit, Need>>();
      map = new LinkedHashMap();
      needMap.put(item, map);
    }
//     Map<Unit, Need> pMap = map.get(priority);
    Map pMap = map.get(priority);
     if (pMap == null) {
//       pMap = new LinkedHashMap<Unit, Need>();
      pMap = new LinkedHashMap();
      map.put(priority, pMap);
    }
    Need need = pMap.get(unit);
    if (need == null) {
       need = new Need(unit, item, 0, 0, w);
      pMap.put(unit, need);
    }
 
    if (need.getAmount() != Integer.MAX_VALUE) {
      if (maxAmount == Integer.MAX_VALUE) {
         need.setAmount(maxAmount);
      } else {
         need.reduceAmount(-maxAmount);
       }
       }
    }
       if (tokens.length > 2) {
    if (need.getMinAmount() != Integer.MAX_VALUE) {
         addNewError("zu viele Argumente");
       if (minAmount == Integer.MAX_VALUE) {
         need.setMinAmount(minAmount);
      } else {
        need.reduceMinAmount(-minAmount);
       }
       }
     }
     }
  }


  /**
    // Ernaehre includes Versorge
  * Returns a need of a unit for an item.
    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
  *
 
  * @param item Order name of the required item
     // remove previous orders
  * @param unit
     removeOrdersLike(TAXOrder + ".*", true);
  * @return The need or <code>null</code> if none has been registered.
     removeOrdersLike(ENTERTAINOrder + ".*", true);
  */
     removeOrdersLike(WORKOrder + ".*", true);
  protected Need getNeed(String item, Unit unit, int priority) {
//    Map<Integer, Map<Unit, Need>> map = needMap.get(item);
    Map map = needMap.get(item);
     if (map == null)
      return null;
//    Map<Unit, Need> pMap = map.get(priority);
     Map pMap = map.get(priority);
     if (pMap == null)
      return null;


     return pMap.get(unit);
     int maxWorkers =
  }
        Utils.getIntValue(world.getGameSpecificRules().getMaxWorkers(currentRegion), 0);
    int workers = Math.min(maxWorkers, currentRegion.getPeasants());
    Skill entertaining =
        currentUnit
            .getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_UNTERHALTUNG));
    Skill taxing =
        currentUnit.getModifiedSkill(world.getRules().getSkillType(
            EresseaConstants.S_STEUEREINTREIBEN));
    int entertain = 0, entertain2 = 0, tax = 0, tax2 = 0;
    if (entertaining != null && hasEntertain()) {
      entertain2 = 20 * entertaining.getLevel() * currentUnit.getPersons();
      entertain = Math.max(0, Math.min(currentRegion.maxEntertain(), entertain2));
    }
    if (taxing != null && isSoldier(currentUnit)) {
      tax2 = 20 * taxing.getLevel() * currentUnit.getPersons();
      tax = Math.min(currentRegion.getSilver(), tax2);
    }


  /**
    if (tax > entertain) {
  * Marks the unit as soldier. Learns its best weapon skill. Reserves suitable weapon, armor, and
      addNewOrder(TAXOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " " + tax + ">"
  * shield if available.
          + entertain, true);
  *
      if (tax >= currentRegion.getSilver() + workers * 10 - currentRegion.getPeasants() * 10) {
  * @param u The unit in question
        addNewWarning("Bauern verhungern");
  * @param sWeaponSkill The desired skill. If <code>null</code>, the unit's best weapon skill is
       }
  *          used. If the unit knows no weapon skill, a warning is issued.
       if (tax2 * 2 > tax * 3) {
  * @param sWeapon The desired weapon that is reserved. If this is <code>null</code>, a weapon that
         if (warning.contains(Warning.W_AMOUNT)) {
  *          matches the weaponSkill is reserved.
           addNewWarning("Treiber unterbeschäftigt " + tax2 + ">" + tax);
  * @param sArmor The desired armor. If <code>null</code>, a suitable armor is reserved. If the
  *          unit has no armor at all, <em>no</em> warning is issued.
  * @param sShield The desired shield. If <code>null</code>, a suitable shield is reserved. If the
  *          unit has no shield at all, <em>no</em> warning is issued.
  * @param warnint Warnings for missing equipment are only issued if this is <code>true</code>.
  */
  protected void soldier(Unit u, String sWeaponSkill, String sWeapon, String sShield,
      String sArmor, String warning) {
 
    Rules rules = world.getRules();
 
    SkillType weaponSkill =
        sWeaponSkill == null ? null : rules.getSkillType(StringID.create(sWeaponSkill));
    ItemType weapon = sWeapon == null ? null : rules.getItemType(StringID.create(sWeapon));
    ItemType armor = sArmor == null ? null : rules.getItemType(StringID.create(sArmor));
    ItemType shield = sShield == null ? null : rules.getItemType(StringID.create(sShield));
 
    if (weaponSkill == null || BEST.equals(sWeaponSkill)) {
 
       int max = 0;
       for (Skill skill : u.getSkills()) {
         if (isWeaponSkill(skill) && skill.getLevel() > max) {
           max = skill.getLevel();
          weaponSkill = skill.getSkillType();
         }
         }
        entertain = currentRegion.maxEntertain();
       }
       }
       if (weaponSkill == null && !W_NEVER.equals(warning)) {
    } else if (entertain > 0) {
         addNewWarning("kein Kampftalent");
      addNewOrder(ENTERTAINOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " "
         return;
          + entertain + ">" + tax, true);
       if (amount > currentRegion.maxEntertain() * 1.1) {
        if (warning.contains(Warning.W_NEVER)) {
          addNewWarning("zu viele Arbeiter");
         }
      } else if (entertain2 * 2 > entertain * 3) {
        if (warning.contains(Warning.W_AMOUNT)) {
          addNewWarning("Unterhalter unterbeschäftigt " + entertain2 + ">" + entertain);
         }
        entertain = currentRegion.maxEntertain();
       }
       }
     }
     } else {
 
      addNewOrder(WORKOrder + " " + (amount > 0 ? amount : ""), true);
//    ArrayList<Item> weapons = new ArrayList<Item>();
      if ((maxWorkers - workers) * 10 < Math.min(amount, currentUnit.getModifiedPersons() * 10)) {
    ArrayList weapons = new ArrayList();
         if (warning.contains(Warning.W_AMOUNT)) {
    if (weapon == null) {
           addNewWarning("zu viele Arbeiter");
      for (Item item : u.getItems()) {
         if (isUsable(item, weaponSkill)) {
           weapons.add(item);
         }
         }
      }
      if (weapons.isEmpty()) {
        for (ItemType type : rules.getItemTypes()) {
          if (isUsable(type, weaponSkill)) {
            weapons.add(new Item(type, 0));
            break;
          }
        }
      }
      if (weapons.isEmpty()) {
        addNewError("keine passenden Waffen bekannt für " + weaponSkill);
      }
    } else {
      if (u.getItem(weapon) != null) {
        weapons.add(u.getItem(weapon));
      } else {
        addNewError("keine " + weapon);
       }
       }
     }
     }
    setConfirm(currentUnit, true);
  }


    // note that "shield" is a subcategory of "armour"
  /**
//     ArrayList<Item> shields = findItems(shield, u, "shield");
  * <code>// $cript Handel Menge|xM [xR] [ALLES | Verkaufsgut...] Warnung</code>: trade luxuries,
    ArrayList shields = findItems(shield, u, "shield");
  * Versorge {@value #DEFAULT_EARN_PRIORITY}. Menge M may be a multipler ("x2" of the region
     if (shields.isEmpty()) {
  * maximum). xR: reserve goods for R rounds. Warnung can be "Talent", "Menge", or "nie"<br />
       addNewError("keine Schilde bekannt");
  */
  protected void commandTrade(String[] tokens) {
     if (tokens.length < 2) {
       addNewError("zu wenige Argumente");
      return;
     }
     }


//     ArrayList<Item> armors = findItems(armor, u, "armour");
     commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });
     ArrayList armors = findItems(armor, u, "armour");
 
     if (armors.isEmpty()) {
     Warning warning = new Warning(true);
      addNewError("keine Rüstungn bekannt");
     tokens = warning.parse(tokens);
    }
 
    int volume = currentRegion.maxLuxuries();


     if (weaponSkill != null) {
     int buyAmount = -1;
//      List<Skill> targetSkills = new LinkedList<Skill>();
    try {
      List targetSkills = new LinkedList();
       if (tokens[1].substring(0, 1).equalsIgnoreCase("x")) {
      targetSkills.add(u.getSkill(weaponSkill));
        buyAmount = Integer.parseInt(tokens[1].substring(1));
       if (weaponSkill.getName().equals("Hiebwaffen")
         buyAmount = volume * buyAmount;
          || weaponSkill.getName().equals("Stangenwafen")) {
         targetSkills.add(getSkill(S_ENDURANCE, Math.round(ENDURANCERATIO_FRONT
            * getSkillLevel(u, weaponSkill))));
       } else {
       } else {
         targetSkills.add(getSkill(S_ENDURANCE, Math.round(ENDURANCERATIO_BACK
         buyAmount = Integer.parseInt(tokens[1]);
            * getSkillLevel(u, weaponSkill))));
       }
       }
       learn(u, targetSkills);
    } catch (NumberFormatException e) {
       addNewError("ungültige Zahl " + tokens[1]);
      return;
     }
     }


     if (!NULL.equals(sWeapon)
     Skill buySkill =
         && !reserveEquipment(weapon, weapons, !W_NEVER.equals(warning) && !W_SKILL.equals(warning))) {
         currentUnit.getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_HANDELN));
       addNewError("konnte Waffe nicht reservieren");
    if (buySkill == null) {
       addNewError("kein Handelstalent");
      return;
     }
     }
     if (!NULL.equals(sShield)
 
        && !reserveEquipment(shield, shields, W_ARMOR.equals(warning) || W_SHIELD.equals(warning))) {
     if (currentRegion.getPrices() == null) {
       addNewError("konnte Schilde nicht reservieren");
       addNewError("kein Handel möglich");
      return;
     }
     }
    if (!NULL.equals(sArmor) && !reserveEquipment(armor, armors, W_ARMOR.equals(warning))) {
      addNewError("konnte Rüstung nicht reservieren");
    }
  }


  /**
    LuxuryPrice buyGood = null;
  * Tries to learn the skills at the ratio reflected in the skills argument.
    for (Entry<StringID, LuxuryPrice> entry : currentRegion.getPrices().entrySet()) {
  *
      LuxuryPrice price = entry.getValue();
  * @param u
      if (price.getPrice() < 0) {
  * @param targetSkills
        buyGood = price;
  */
      } else {
//  protected void learn(Unit u, Collection<Skill> targetSkills) {
        if (currentRegion.getOldPrices() != null
  protected void learn(Unit u, Collection targetSkills) {
            && currentRegion.getOldPrices().get(entry.getKey()) != null
 
            && price.getPrice() < currentRegion.getOldPrices().get(entry.getKey()).getPrice()) {
    // find skill with maximum priority
          addNewWarning("Preis gesunken: " + price.getItemType());
    double maxWeight = 0;
         }
    Skill maxSkill = null;
    StringBuilder comment = new StringBuilder(";");
    for (Skill skill : targetSkills) {
      double weight = calcSkillWeight(u, skill, targetSkills);
      comment.append(" ").append(skill.toString()).append(" ").append(weight);
      if (weight > maxWeight) {
        maxWeight = weight;
         maxSkill = skill;
       }
       }
     }
     }


     addNewOrder(comment.toString(), true);
     removeOrdersLike(SELLOrder + ".*", true);
    removeOrdersLike(BUYOrder + ".*", true);
 
    // Gueterzahl intitialisieren
    int totalVolume = 0;


     if (maxSkill == null) {
     if (buyAmount > 0 && (volume <= 0 || buyGood == null)) {
       addNewError("kein Kampftalent");
       addNewError("kein Handel möglich");
    } else {
      removeOrdersLike(LEARNOrder + ".*", true);
      removeOrdersLike(TEACHOrder + ".*", true);
      addNewOrder(LEARNOrder + " " + maxSkill.getName(), true);
     }
     }
  }


  /**
    LinkedList<String> orders = new LinkedList<String>();
  * Returns a weight ("importance") of a skill according to target values and the unit's current
  * skill levels.
  */
  protected static double calcSkillWeight(Unit u, Skill learningSkill,
//      Collection<Skill> targetSkills) {
      Collection targetSkills) {
    if (learningSkill == null)
      return 0;
    double prio = 0.5;


     // calc max mult
     int maxAmount = buySkill.getLevel() * currentUnit.getPersons() * 10;
    Skill learningTarget = null;
     int skillNeeded = 0;
     double maxMult = 0;


     for (Skill skill2 : targetSkills) {
     if (volume > 0 && buyGood != null) {
       if (skill2.getSkillType().equals(learningSkill.getSkillType())) {
       int reserveMultiplier = 1, reserveToken = 0;
         learningTarget = skill2;
      try {
        if (tokens.length > 2 && tokens[2].substring(0, 1).equalsIgnoreCase("x")) {
          reserveToken = 1;
          reserveMultiplier = Integer.parseInt(tokens[2].substring(1));
        }
      } catch (NumberFormatException e) {
         addNewError("ungültige Zahl " + tokens[2]);
        return;
       }
       }
       int level = Math.max(1, getSkillLevel(u, skill2.getSkillType()));
 
      // if (lev < getMax(skill2)) {
       List<String> goods = new LinkedList<String>();
      double mult = level / (double) skill2.getLevel();
      // Verkaufsbefehl setzen, wenn notwendig
       if (maxMult < mult) {
      if (tokens.length > 2 + reserveToken && ALLOrder.equals(tokens[2 + reserveToken])) {
         maxMult = mult;
        if (world.getRules().getItemCategory("luxuries") == null) {
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          for (ItemType luxury : world.getRules().getItemTypes()) {
            if (!luxury.equals(buyGood.getItemType())
                && world.getRules().getItemCategory("luxuries").equals(luxury.getCategory())) {
              goods.add(luxury.getOrderName());
            }
          }
        }
       } else {
        for (int i = 2 + reserveToken; i < tokens.length; ++i) {
          goods.add(tokens[i]);
         }
       }
       }
      // }
    }


    if (learningTarget == null)
      for (String luxury : goods) {
      // learned skill is not in target skills
        int goodAmount = volume;
      return 0.01 * learningSkill.getLevel();
        if (goodAmount > getSupply(luxury)) {
          goodAmount = getSupply(luxury);
        }
        skillNeeded += goodAmount;
 
        if (goodAmount > maxAmount - totalVolume) {
          goodAmount = maxAmount - totalVolume;
        }
 
        addNeed(luxury, currentUnit, ALLOrder.equals(tokens[2 + reserveToken]) ? goodAmount
            : volume, volume
                * reserveMultiplier,
            TRADE_PRIORITY, warning);
        if (goodAmount > 0) {


    if (maxMult > 0) {
          if (goodAmount == volume) {
      // calc max normalized learning weeks
            orders.add(SELLOrder + " " + ALLOrder + " " + luxury);
      double maxWeeks = 0;
          } else {
      for (Skill skill2 : targetSkills) {
            orders.add(SELLOrder + " " + goodAmount + " " + luxury);
        int currentLevel = Math.max(1, getSkillLevel(u, skill2.getSkillType()));
           }
        // if (lev < getMax(skill2)) {
        double weeks = skill2.getLevel() - currentLevel / maxMult + 2;
        if (maxWeeks < weeks) {
           maxWeeks = weeks;
         }
         }
         // }
         totalVolume += goodAmount;
      }
      int level = Math.max(1, getSkillLevel(u, learningSkill.getSkillType()));
      // if (level >= getMax(skill))
      // prio = 0d;
      // else
      // prio should be between .4 and 1
      prio = .4 + .6 * (learningTarget.getLevel() - level / maxMult + 2) / maxWeeks;
      if (prio < 0) {
        prio = prio * 1.000001;
       }
       }
     }
     }
    return prio;
  }


  protected static int getSkillLevel(Unit u, SkillType skill) {
    // Soll eingekauft werden?
    Skill uskill = u.getSkill(skill);
    if (volume > 0 && buyAmount > 0 && buyGood != null) {
    return uskill == null ? 0 : uskill.getLevel();
      int remainingFromSkill = maxAmount - totalVolume + 1 - 1;
  }
      // Berechne noetige Geldmenge fuer Einkauf (einfacher: Modulorechnung, aber wegen
 
      // Rundungsfehler nicht umsetzbar)
  /**
      int remainingToBuy = buyAmount;
  * If <code>itemType==null</code> return all the unit's items of the given category. If no item is
      skillNeeded += buyAmount;
  * found, at least one item (with amount 0) is returned.
      if (remainingToBuy > remainingFromSkill) {
  */
        buyAmount += (remainingFromSkill - remainingToBuy);
//  protected static ArrayList<Item> findItems(ItemType itemType, Unit u, String category) {
        remainingToBuy = remainingFromSkill;
  protected static ArrayList findItems(ItemType itemType, Unit u, String category) {
      }
//    ArrayList<Item> items = new ArrayList<Item>(1);
      int priceFactor = 1;
    ArrayList items = new ArrayList(1);
      int money = 0;
    ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
      while (remainingToBuy > 0) {
    if (itemType == null) {
        if (remainingToBuy > volume) {
      for (Item item : u.getItems()) {
          remainingToBuy -= volume;
        if (itemCategory.equals(item.getItemType().getCategory())) {
          money -= (volume * priceFactor * buyGood.getPrice()); // price is negative
           items.add(item);
          priceFactor++;
        } else {
          money -= (remainingToBuy * priceFactor * buyGood.getPrice());
           remainingToBuy = 0;
         }
         }
       }
       }
    } else {
 
       if (u.getItem(itemType) != null) {
      addNeed("Silber", currentUnit, money, money, TRADE_PRIORITY);
         items.add(u.getItem(itemType));
 
      // Einkaufsbefehl setzen, wenn notwendig
       if (buyAmount > 0) {
         orders.addFirst(BUYOrder + " " + buyAmount + " " + buyGood.getItemType().getOrderName());
        totalVolume += buyAmount;
       }
       }
     }
     }
     if (items.isEmpty()) {
 
      for (Object o : itemCategory.getInstances()) {
     // add orders (buy orders first)
        ItemType type = (ItemType) o;
    for (String order : orders) {
        items.add(new Item(type, 0));
      addNewOrder(order, true);
        break;
      }
     }
     }
    return items;
  }


//   protected boolean reserveEquipment(ItemType preferred, List<Item> ownStuff, boolean warn) {
    // Einheit gut genug?
  protected boolean reserveEquipment(ItemType preferred, List ownStuff, boolean warn) {
     if (skillNeeded > maxAmount && warning.contains(Warning.C_SKILL)) {
     if (preferred != null) {
       addNewError("Einheit hat zu wenig Handelstalent (min: " + maxAmount / 10 + " < "
      // reserve requested weapon
          + (int) Math.ceil(skillNeeded / 10.0) + ")");
      commandNeed(new String[] { "Benoetige", EACHOrder, "1", preferred.getOrderName(),
    }
          String.valueOf(DEFAULT_PRIORITY) });
    } else if (!ownStuff.isEmpty()) {
       int supply = 0;
      for (Item w : ownStuff) {
        // reserve all matching weapons, except first one
        if (supply > 0) {
          commandNeed(new String[] { "Benoetige",
              Integer.toString(Math.min(currentUnit.getPersons() - supply, w.getAmount())),
              w.getOrderName(), String.valueOf(DEFAULT_PRIORITY) });
        }
        supply += w.getAmount();
      }


      // now reserve the first matching weapon with min=w.getAmount(), max=w.getPersons()-other
    setConfirm(currentUnit, true);
      // weapons
      Item w = ownStuff.get(0);
      String max =
          Integer.toString(Math.max(0, Math.min(currentUnit.getPersons() - supply + w.getAmount(),
              currentUnit.getPersons())));
      String min = warn ? max : Integer.toString(Math.min(currentUnit.getPersons(), w.getAmount()));
      commandNeed(new String[] { "Benoetige", min, max, w.getOrderName(),
          String.valueOf(DEFAULT_PRIORITY) });
    } else
      return false;
    return true;
   }
   }


   /**
   /**
   * Tries to init the constants according to the current faction's locale
   * <code>// $cript Steuermann minSilver maxSilver [Talent ...]</code> -- be responsible for ship
  *
  * Reserves silver for crew, maintains route commands, executes soldier command. If given, uses the same parameters as
  * commandSoldier, starting with Talent.
  * Otherwise uses parameters specified in {@link #SOLDIER_HELMSMAN}.
  *
  * If {@link #LEARN_HELMSMAN} is not null, adds those parameters to the teach plugin.
  *
  * @param tokens
   */
   */
   protected void initLocales() {
   protected void commandHelmsman(String[] tokens) {
     if (currentUnit == null) {
     if (tokens.length == 2) {
       findSomeUnit(currentFactions);
       addNewError("zu wenige Argumente");
      return;
     }
     }
     if (currentUnit == null)
    String min, max;
       throw new NullPointerException();
     if (tokens.length > 1) {
      min = tokens[1];
      max = tokens[2];
    } else {
      Ship ship = currentUnit.getShip();
      if (ship == null) {
        min = "300";
        max = "500";
      } else {
        int sail = 0, money = 0;
        for (Unit u : ship.units()) {
          if (u.getSkill(EresseaConstants.S_SEGELN) != null) {
            sail += u.getSkill(EresseaConstants.S_SEGELN).getLevel() * u.getPersons();
          }
          money += u.getPersons() * u.getRace().getMaintenance();
          if (sail >= ship.getShipType().getSailorSkillLevel() * ship.getAmount()) {
            break;
          }
        }
        min = String.valueOf((money * 6 - 1) / 100 * 100 + 100);
        max = String.valueOf((money * 11 - 1) / 100 * 100 + 100);
       }
    }
    commandNeed(new String[] { "Benoetige", min, max, "Silber",
        String.valueOf(DEFAULT_PRIORITY + 10) });
    setConfirm(currentUnit, false);


     GIVEOrder = getLocalizedOrder(EresseaConstants.OC_GIVE, GIVEOrder);
     for (Order order : currentUnit.getOrders2()) {
    RESERVEOrder = getLocalizedOrder(EresseaConstants.OC_RESERVE, RESERVEOrder);
      String text = order.getText();
    EACHOrder = getLocalizedOrder(EresseaConstants.OC_EACH, EACHOrder);
      if (text.startsWith(ROUTEOrder + " " + PAUSEOrder)) {
    ALLOrder = getLocalizedOrder(EresseaConstants.OC_ALL, ALLOrder);
        addNewWarning("Route beendet");
    PCOMMENTOrder = "//";
      }
    LEARNOrder = getLocalizedOrder(EresseaConstants.OC_LEARN, LEARNOrder);
     }
    TEACHOrder = getLocalizedOrder(EresseaConstants.OC_TEACH, TEACHOrder);
     removeOrdersLike(ROUTEOrder + " " + PAUSEOrder + ".*", true);
    ENTERTAINOrder = getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder);
    TAXOrder = getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder);
    WORKOrder = getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder);
    BUYOrder = getLocalizedOrder(EresseaConstants.OC_BUY, BUYOrder);
    SELLOrder = getLocalizedOrder(EresseaConstants.OC_SELL, SELLOrder);
     MAKEOrder = getLocalizedOrder(EresseaConstants.OC_MAKE, MAKEOrder);
     MOVEOrder = getLocalizedOrder(EresseaConstants.OC_MOVE, MOVEOrder);
    ROUTEOrder = getLocalizedOrder(EresseaConstants.OC_ROUTE, ROUTEOrder);
    PAUSEOrder = getLocalizedOrder(EresseaConstants.OC_PAUSE, PAUSEOrder);
    RESEARCHOrder = getLocalizedOrder(EresseaConstants.OC_RESEARCH, RESEARCHOrder);
    RECRUITOrder = getLocalizedOrder(EresseaConstants.OC_RECRUIT, RECRUITOrder);


     if (currentFactions.keySet().iterator().next().getLocale().getLanguage() != "de") {
    String[] soldierTokens;
       // warning constants
     if (tokens.length > 3) {
       W_NEVER = "never";
       // Soldat [Talent [Waffe [Schild [Rüstung]]]]
       W_SKILL = "skill";
       soldierTokens = new String[tokens.length - 1];
       W_WEAPON = "weapon";
       soldierTokens[0] = tokens[0];
      W_SHIELD = "shield";
       for (int i = 1; i < tokens.length - 2; ++i) {
      W_ARMOR = "armor";
        soldierTokens[i] = tokens[i + 2];
       BEST = "best";
       }
      NULL = "null";
    } else {
       LUXUSOrder = "LUXURY";
       soldierTokens = (tokens[0] + " " + SOLDIER_HELMSMAN).split(" ");
      KRAUTOrder = "HERBS";
     }
     }
  }
    if (LEARN_HELMSMAN != null) {
 
       addToTeachPlugin(currentUnit, parseLearn(LEARN_HELMSMAN));
  protected String getLocalizedOrder(StringID orderKey, String fallback) {
    try {
       return world.getGameSpecificStuff().getOrderChanger().getOrder(orderKey,
          currentUnit.getLocale());
    } catch (RulesException e) {
      return fallback;
     }
     }
    commandSoldier(soldierTokens);
   }
   }


   /**
   /**
   * Tries to translate the given order to the current locale.
   * <code>// $cript Mannschaft [Talent ...]</code> -- be crew and learn<br />
  *
  * If given, uses the same parameters as
  * commandSoldier, starting with Talent.
  * Otherwise uses parameters specified in {@link #SOLDIER_CREW}.
  *
  * If {@link #LEARN_CREW} is not null, adds those parameters to the teach plugin.
  *
  * @param tokens
   */
   */
   protected String getLocalizedOrder(StringID orderKey, Object[] args) {
   protected void commandCrew(String[] tokens) {
     return world.getGameSpecificStuff().getOrderChanger().getOrder(currentUnit.getLocale(),
    String[] soldierTokens;
        orderKey, args);
    boolean legacy = false;
  }
    if (tokens.length == 3) {
      try {
        Integer.parseInt(tokens[2]);
        addNewOrder(createScriptCommand() + tokens[0], true);
        legacy = true;
      } catch (NumberFormatException e) {
        //
      }
    }
     if (!legacy) {
      addNewOrder(currentOrder, false);
    }
    if (tokens.length > 1 && !legacy) {
      soldierTokens = new String[tokens.length];
      soldierTokens[0] = tokens[0];
      for (int i = 1; i < tokens.length; ++i) {
        soldierTokens[i] = tokens[i];
      }
    } else {
      soldierTokens = (tokens[0] + " " + SOLDIER_CREW).split(" ");
    }
    if (LEARN_CREW != null) {
      addToTeachPlugin(currentUnit, parseLearn(LEARN_CREW));
    }
    commandSoldier(soldierTokens);


  /**
     setConfirm(currentUnit, true);
  * Tries to translate the given order to the current locale.
  */
  protected String getLocalizedOrder(String orderKey, String fallBack) {
     String translation = Resources.getOrderTranslation(orderKey, currentUnit.getLocale());
    if (translation == orderKey)
      return fallBack;
    else
      return translation;
   }
   }


   /**
   private Collection<SkillSpec> parseLearn(String learnLine) {
  * Adss an order to the current unit's new orders.
    OrderedHashtable<SkillSpec, Object> parsed = new OrderedHashtable<SkillSpec, Object>();
  *  
    StringTokenizer tokens = new StringTokenizer(learnLine);
  * @param order The new order
    while (tokens.hasMoreTokens()) {
  * @param changed Set to true if this is a change (not merely a copy of an old order)
      String t = tokens.nextToken();
  */
      if (t.matches("[A-Z].*")) {
  protected void addNewOrder(String order, boolean changed) {
        int level = -1, max = -1;
    changedOrders = changedOrders || changed;
        SkillType skill = getSkillType(t);
    if (!changed) {
        try {
      // remove old orders matching a removed order
          level = Integer.parseInt(tokens.nextToken());
      for (String pattern : removedOrderPatterns)
          max = Integer.parseInt(tokens.nextToken());
         if (order.matches(pattern))
        } catch (NumberFormatException e) {
           return;
          //
        } catch (NoSuchElementException e) {
          //
        }
         if (skill != null && level > 0 && max > 0) {
          parsed.put(new SkillSpec(skill, level, max), "");
        } else {
           addNewWarning("error in skillLine :" + learnLine);
        }
      }
     }
     }
 
     return parsed.keySet();
     newOrders.add(getParser().parse(order, currentUnit.getLocale()).getText());
   }
   }


   /**
   /**
   * Registers a pattern. All lines matching this regular expression (case sensitive!) will be
   * <code>// $cript Quartiermeister [[Menge1 Gut 1]...]</code>: learn perception, allow listed
   * removed from here on. If retroActively, also orders that are already in {@link #newOrders}.
   * amount of goods. If other goods are detected, do not confirm orders.
  *
  * @param regEx
  * @param retroActively
   */
   */
   protected void removeOrdersLike(String regEx, boolean retroActively) {
   protected void commandQuartermaster(String[] tokens) {
     if (retroActively) {
     int silver = 0;
//       for (Iterator<String> it = newOrders.iterator(); it.hasNext();) {
    try {
      for (Iterator it = newOrders.iterator(); it.hasNext();) {
       for (Item item : currentUnit.getItems()) {
         String line2 = it.next();
        boolean okay = false;
        if (line2.matches(regEx)) {
        if (item.getItemType().getID().equals(EresseaConstants.I_USILVER)) {
          it.remove();
          silver = item.getAmount();
          if (silver < 2000) {
            okay = true;
          }
        }
         for (int i = 1; !okay && i < tokens.length - 1; i += 2) {
          if (item.getName().equals(tokens[i + 1]) || item.getOrderName().equals(tokens[i + 1])) {
            if (item.getAmount() <= Integer.parseInt(tokens[i])) {
              okay = true;
              break;
            }
          }
         }
         }
        setConfirm(currentUnit, okay);
       }
       }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl ");
     }
     }
     removedOrderPatterns.add(regEx);
 
     ArrayList<SkillSpec> qskills = new ArrayList<SkillSpec>(2);
    qskills.add(new SkillSpec(world.getRules().getSkillType(EresseaConstants.S_WAHRNEHMUNG), 10, 99));
    SkillType tactics = world.getRules().getSkillType(EresseaConstants.S_TAKTIK);
    if (tactics != null) {
      Skill utactics = currentUnit.getSkill(tactics);
      if ((utactics == null || utactics.getLevel() < QM_TACTICS) && silver > tactics.getCost(utactics == null ? 0
          : utactics.getLevel())) {
        qskills.add(new SkillSpec(tactics, QM_TACTICS * 2, QM_TACTICS));
      }
    }
    learn(currentUnit, qskills);
 
    setConfirm(currentUnit, true);
   }
   }


   /**
   /**
   * Adds an error line.
   * <code>// $cript Sammler [frequenz]</code>: collect herbs if there are at least "viele",
   *  
   * research herbs every frequenz rounds.
  * @param line The current order line
  * @param hint
   */
   */
   protected void addNewError(String hint) {
   protected void commandCollector(String[] tokens) {
     error = line;
     if (tokens.length > 2) {
     // errMsg = hint;
      addNewError("zu viele Argumente");
     addNewOrder(COMMENTOrder + " TODO: " + hint + " (Fehler in Zeile " + error + ")", true);
    }
     setConfirm(currentUnit, false);
    int modulo = Integer.MAX_VALUE;
     if (tokens.length > 1) {
      try {
        modulo = Integer.parseInt(tokens[1]);
      } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[1]);
        return;
      }
    }
 
    if (currentRegion.getRegionType().isOcean()) {
      addNewError("Sammeln nicht möglich!");
      return;
    }
     removeOrdersLike(MAKEOrder + " " + "[^T].*", true);
    removeOrdersLike(getResearchOrder() + ".*", true);
    Date dateBefore = world.getDate().clone();
    dateBefore.setDate(world.getDate().getDate() - 1);
    if ((dateBefore.getSeason() == Date.WINTER && world.getDate().getSeason() == Date.WINTER)) {
      modulo = 2;
    }
    if ((modulo != Integer.MAX_VALUE && world.getDate().getDate() % modulo == 0)
        || currentRegion.getHerbAmount() == null
        || (!currentRegion.getHerbAmount().equals("viele") && !currentRegion.getHerbAmount().equals("sehr viele"))) {
      addNewOrder(getResearchOrder(), true);
    } else {
      addNewOrder(MAKEOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER"), true);
     }
    commandSupply(new String[] { "Versorge", KRAUTOrder, "100" });
   }
   }


   /**
   /**
   * Adds a warning message (with a to do tag) to the new orders.
   * <code>KrautKontrolle [[[direction...] PAUSE]...]</code> move until the next PAUSE, if pause is
   *  
   * reached, research herbs.
  * @param text
   */
   */
   protected void addNewMessage(String text) {
   protected void commandControl(String[] tokens) {
     addNewOrder(COMMENTOrder + " ----- " + text + " -----", true);
     for (Order o : currentUnit.getOrders2()) {
  }
      String order = o.getText();
 
      if (order.startsWith(ROUTEOrder)) {
  /**
        if (order.substring(order.indexOf(" ")).trim().startsWith(PAUSEOrder)) {
  * Adds an error line to new orders.
          // end of route, FORSCHE
  *
          addNewOrder(currentOrder, false);
  * @param line The current order line
          addNewOrder(getResearchOrder(), true);
  * @param hint
          removeOrdersLike(ROUTEOrder + ".*", true);
  */
        } else {
  protected void addNewWarning(String hint) {
          // continue on route
    addNewWarning(hint, true);
          addNewOrder(currentOrder, false);
  }
        }
 
        return;
  protected void addNewWarning(String hint, boolean addLine) {
      }
     error = line;
    }
     // errMsg = hint;
    // no route order -- create new one
     addNewOrder(COMMENTOrder + " TODO: " + hint
    removeOrdersLike(getResearchOrder() + ".*", true);
         + (addLine ? " (Warnung in Zeile " + error + ")" : ""), true);
    StringBuilder newOrder = new StringBuilder();
     setConfirm(currentUnit, false);
    newOrder.append(createScriptCommand()).append(tokens[0]);
   }
    StringBuilder moveOrder = new StringBuilder(ROUTEOrder);
 
    boolean pause = false;
  // ///////////////////////////////////////////////////////
    for (int i = 1; i < tokens.length; ++i) {
  // HELPER functions
      if (!pause) {
  // ///////////////////////////////////////////////////////
        // add move order until first PAUSE
        moveOrder.append(" ").append(tokens[i]);
      } else {
        // add to $cript order after first PAUSE
        newOrder.append(" ").append(tokens[i]);
      }
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
        pause = true;
      }
    }
     moveOrder.append(" ").append(PAUSEOrder);
     pause = false;
     for (int i = 1; i < tokens.length; ++i) {
      if (!pause) {
        // append movement until first PAUSE to back of $cript order
         newOrder.append(" ").append(tokens[i]);
      }
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
        pause = true;
      }
    }
    addNewOrder(newOrder.toString(), true);
     addNewOrder(moveOrder.toString(), true);
   }


   /**
   /**
   * Adds a warning message (with a to do tag) directly to the unit's orders.
   * <code>// $cript RekrutiereMax [min [max]] [race]</code>: recruit as much as possible; warn if
   *  
   * less than min are possible
  * @param unit
  * @param text
   */
   */
   public static void addWarning(Unit unit, String text) {
   protected void commandRecruit(String[] tokens) {
     // helper.addOrder(unit, "; -------------------------------------");
     if (tokens.length > 4) {
     helper.addOrder(unit, "; TODO: " + text);
      addNewError("zu viele Argumente");
    setConfirm(unit, false);
     }
  }
    int min = 1;
 
    String race = null;
  /**
    int max = Integer.MAX_VALUE;
  * If confirm is false, mark unit as not confirmable. If confirm is true, mark it as confirmable
    if (tokens.length > 1) {
  * if it has not been marked as unconfirmable before (unconfirm always overrides confirm).
      try {
  *
        min = Integer.parseInt(tokens[1]);
  * @param u
        if (tokens.length > 2) {
  * @param confirm
          try {
  */
            max = Integer.parseInt(tokens[2]);
  public static void setConfirm(Unit u, boolean confirm) {
            if (tokens.length > 3) {
    String tag = getProperty(u, "confirm");
              race = tokens[3];
    if (confirm) {
            }
      if (tag.length() == 0) {
          } catch (NumberFormatException e) {
        setProperty(u, "confirm", "1");
            max = Integer.MAX_VALUE;
       }
            race = tokens[2];
     } else if (!confirm) {
            if (tokens.length > 3) {
       setProperty(u, "confirm", "0");
              addNewError("zu viele Argumente");
            }
          }
        }
      } catch (NumberFormatException e) {
        min = 1;
        race = tokens[1];
        if (tokens.length > 2) {
          addNewError("zu viele Argumente");
        }
       }
     }
 
    Race effRace = race == null ? currentUnit.getRace() : helper.getRace(race);
    if (effRace == null) {
       addNewError("Unbekannte Rasse");
      return;
     }
     }
  }
  /**
  * Add a property (in form of a tag) to the unit.
  *
  * @param u
  * @param tagName
  * @param value
  * @see #getProperty(Unit, String)
  */
  public static void setProperty(Unit u, String tagName, String value) {
    u.putTag(scriptMarker + "." + tagName, value);
    // u.addOrder("; $cript " + tagName + ":" + value, false, 0);
  }


  /**
    if (currentUnit.getPersons() != 0 && !effRace.equals(currentUnit.getRace())) {
  * Returns a property value (from a tag) from the unit.
      addNewWarning("race != unit race");
  *
    }
  * @param u
 
  * @param tagName
    int amount = world.getGameSpecificRules().getRecruitmentLimit(currentUnit, effRace);
  * @return The value of property <code>tagName</code> or "" if the property has not been set.
 
  */
    if (currentUnit.getPersons() + amount >= max) {
  public static String getProperty(Unit u, String tagName) {
      addNewWarning("Rekrutierung fertig");
    String tag = u.getTag(scriptMarker + "." + tagName);
      amount = Math.min(max - currentUnit.getPersons(), amount);
    return tag == null ? "" : tag;
    }
 
    if (amount < min) {
      addNewError("Nicht genug Rekruten");
    }
 
    if (amount > 0) {
      if (effRace.getRecruitmentCosts() > 0) {
        int costs = amount * effRace.getRecruitmentCosts();
        int maxCosts =
            max == Integer.MAX_VALUE ? costs : (max - currentUnit.getPersons())
                * effRace.getRecruitmentCosts();
        addNeed("Silber", currentUnit, costs, maxCosts, DEFAULT_PRIORITY);
      } else {
        addNewWarning("Rekrutierungskosten unbekannt");
      }
      getRecruitOrder(amount, effRace);
      addNewOrder(getRecruitOrder(amount, effRace != currentUnit.getRace() ? effRace : null), true);
    }
   }
   }


   /**
   /**
   * Returns the ItemType in the rules matching <code>name</code>.
   * <code>// $cript Kommentar text</code>: add text after a semicolon
  *
  * @param name A item type name, like "Silber"
  * @return The ItemType corresponding to name or <code>null</code> if this ItemType does not
  *        exist.
   */
   */
   public static ItemType getItemType(String name) {
   protected void commandComment(String[] tokens) {
     return world.getRules().getItemType(name);
     String rest = currentOrder.substring(currentOrder.indexOf(tokens[0]) + tokens[0].length());
    addNewOrder(";" + rest, true);
 
   }
   }


   /**
   // ///////////////////////////////////////////////////////
  * Returns the SkillType in the rules matching <code>name</code>.
  // HELPER functions
  *
  // ///////////////////////////////////////////////////////
  * @param name A skill name, like "Ausdauer"
 
  * @return The SkillType corresponding to name or <code>null</code> if this SkillType does not
   protected boolean hasEntertain() {
  *        exist.
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
  */
        getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder));
   public static SkillType getSkillType(String name) {
     return world.getRules().getSkillType(name);
   }
   }


   /**
   protected boolean hasTax() {
  * Returns a skill with the given name and level.
     return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
  *
        getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder));
  * @param name
  * @param level
  * @return A skill with the given level or <code>null</code> if this SkillType does not exist.
  */
  public static Skill getSkill(String name, int level) {
     if (getSkillType(name) != null)
      return new Skill(getSkillType(name), 0, level, 1, true);
    else
      return null;
   }
   }


   /**
   protected boolean hasWork() {
  * Notifies the interface that the unit should be updated.
    return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
  *
        getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder));
  * @param u This unit is updated in the UI
  * @deprecated I don't think this is needed any more.
  */
//  @Deprecated
  public static void notifyMagellan(Unit u) {
    helper.updateUnit(u);
   }
   }


   /**
   protected void initSupply() {
  * Adds a GIVE order to the unit's orders, like <code>GIVE receiver [EACH] amount item</code>.
     if (needQueue == null) {
  *
      needQueue = new ArrayList<Need>();
  * @param unit The order is added to this unit's orders
    } else {
  * @param receiver
      needQueue.clear();
  * @param item
    }
  * @param amount
  * @param each
  */
  public static void addGiveOrder(Unit unit, Unit receiver, String item, int amount, boolean each) {
     helper.addOrder(unit, getGiveOrder(unit, receiver.getID().toString(), item, amount, each));
  }


  /**
    if (supplyMapp == null) {
  * Returns a line like <code>GIVE receiver [EACH] amount item</code>.
      supplyMapp = new SupplyMap(this);
  *
    } else {
  * @param unit
      supplyMapp.clear();
  * @param receiver
    }
  * @param item
    if (capacities == null) {
  * @param amount If <code>amount == Integer.MAX_VALUE</code>, amount is replaced by ALL
      capacities = new HashMap<Unit, Integer>();
  * @param each
    } else {
  * @return a line like <code>GIVE receiver [EACH] amount item</code>.
      capacities.clear();
  */
    }
  public static String getGiveOrder(Unit unit, String receiver, String item, int amount,
    if (transfersMap == null) {
      boolean each) {
      transfersMap = new HashMap<Unit, Map<String, Integer>>();
    return helper.getGiveOrder(unit, receiver, item, amount, each);
      transferList = new ArrayList<Transfer>();
  }
    } else {
      transfersMap.clear();
      transferList.clear();
    }
    if (dummyUnits == null) {
      dummyUnits = new HashMap<String, Unit>();
    } else {
      dummyUnits.clear();
    }


   /**
    for (Unit u : currentRegion.units()) {
   * Adds a RESERVE order to the unit's orders, like <code>RESERVE [EACH] amount item</code>.
      if (currentFactions.containsKey(u.getFaction())) {
  *
        for (Item item : u.getItems()) {
   * @param unit The order is added to this unit's orders
          putSupply(item.getOrderName(), u, item.getAmount());
        }
 
        // TODO take RESERVE or GIVE orders into account?
 
        // // subtract reserved items from supply amount of unit
        // for (ReserveRelation relation : u.getRelations(ReserveRelation.class)) {
        // Supply supply = getSupply(relation.itemType.getName(), u);
        // if (supply != null && relation.source == u) {
        // supply.setAmount(supply.getAmount() - relation.amount);
        // }
        // }
        // // subtract transferred items (by GIVE orders) from supply amount of source unit (don't
        // add
        // // to target unit)
        // for (ItemTransferRelation relation : u.getRelations(ItemTransferRelation.class)) {
        // Supply supply = getSupply(relation.itemType.getName(), u);
        // if (supply != null && relation.source == u) {
        // supply.setAmount(supply.getAmount() - relation.amount);
        // }
        // }
      }
    }
  }
 
   /**
   * Adds a supply to the supplyMap
   *
   * @param item
   * @param item
  * @param unit
   * @param amount
   * @param amount
  * @param each
   */
   */
   public static void addReserveOrder(Unit unit, String item, int amount, boolean each) {
   protected Supply putSupply(String item, Unit unit, int amount) {
     helper.addOrder(unit, getReserveOrder(unit, item, amount, each));
     return supplyMapp.put(item, unit, amount, ++supplySerial);
   }
   }


   /**
   /**
   * Returns a line like <code>RESERVE [EACH] amount item</code>.
   * Adds a supply to the supplyMap
   *  
  *
   * @param item
   * @param unit
   * @param unit
  * @param item
   * @param amount
   * @param amount
  * @param each
   */
  * @return a line like <code>GIVE receiver [EACH] amount item</code>.
   protected Supply putDummySupply(String item, Unit unit, int amount) {
   */
     return supplyMapp.put(item, unit, amount, Long.MAX_VALUE);
   public static String getReserveOrder(Unit unit, String item, int amount, boolean each) {
     return helper.getReserveOrder(unit, item, amount, each);
   }
   }


   /**
   /**
   * Returns a <code>RESEARCH HERBS</code> order.
   * Tries to satisfy all needs in the current needMap by adding GIVE orders to suppliers.
   */
   */
   protected String getResearchOrder() {
   protected void satisfyNeeds() {
     return RESEARCHOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER");
     supplyMapp.sortByPriority();
 
    Need[] needs = needQueue.toArray(new Need[] {});
    sort(needs);
    adjustAlreadyTransferred(needs);
    Reserves reserves = new Reserves();
 
    pack(needs, reserves, false);
    enforceCapacities();
    pack(needs, reserves, true);
 
    executeReserves(reserves);
    executeTransferOrders();
    warnNeeds();
    warnCapacities();
   }
   }


   /**
   private void pack(Need[] needs, Reserves reserves, boolean enforce) {
  * Returns a <code>RECRUIT amount race</code> order.
     for (int n = 0, prioStart = 0, state = 0; n < needs.length; ++n) {
  */
       Need need = needs[n];
  protected String getRecruitOrder(int amount, Race race) {
      if (need.getPriority() > needs[prioStart].getPriority())
     if (race != null)
        throw new RuntimeException("needs not sorted");
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount, race });
    else
       return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount });
    // return RECRUITOrder + " " + amount
    // + (race != null ? (" " + getLocalizedOrder("race." + race.getID(), race.getName())) : "");
  }


  /**
      switch (state) {
  * Return the amount of item that a unit has.
      case 0:
  *
        reserveNeed(need, true, reserves);
  * @param unit
        break;
  * @param item
  * @return The amount of item in the unit's items
  */
  public static int getItemCount(Unit unit, String item) {
    return helper.getItemCount(unit, item);
  }


  /**
      case 1:
  * Return the amount of silver of the unit.
        giveNeed(need, true);
  *
        break;
  * @param unit
      case 2:
  * @return The amount of silver in this unit's items
        if (need.getAmount() != Integer.MAX_VALUE) {
  */
          reserveNeed(need, false, reserves);
  public static int getSilber(Unit unit) {
        }
    return Math.max(getItemCount(unit, "Silver"), getItemCount(unit, "Silber"));
        break;
      case 3:
        if (need.getAmount() != Integer.MAX_VALUE) {
          giveNeed(need, false);
        }
        break;
      case 4:
        if (need.getAmount() == Integer.MAX_VALUE) {
          // now, finally, satisfy infinite needs
          reserveNeed(need, false, reserves);
        }
        break;
      case 5:
        if (need.getAmount() == Integer.MAX_VALUE) {
          giveNeed(need, false);
        }
        break;
      }
 
      if (n == needs.length - 1 || needs[n + 1].getPriority() < needs[prioStart].getPriority()) {
        if (state < 5) {
          n = prioStart - 1;
          ++state;
        } else {
          state = 0;
          prioStart = n + 1;
          if (enforce) {
            enforceCapacities();
          }
        }
      }
    }
   }
   }


   /**
   private void warnNeeds() {
  * Returns true if the item is usable with weaponSkill, false otherwise.
    for (Need need : needQueue) {
  */
      if (need.getMinAmount() > 0 && need.getWarning().contains(Warning.C_AMOUNT)) {
  protected static boolean isUsable(ItemType type, SkillType weaponSkill) {
        addWarning(need.getUnit(), "braucht " + need.getMinAmount()
    return (type.getUseSkill() != null && type.getUseSkill().getSkillType().equals(weaponSkill));
            + (need.getMaxAmount() != need.getMinAmount() ? ("/" + need.getMaxAmount()) : "")
            + " mehr " + need.getItem() + ", " + need.getMessage());
      }
 
      // add messages for unsatisfied needs
      if (need.getMinAmount() <= 0 && need.getMaxAmount() > 0
          && need.getMaxAmount() != Integer.MAX_VALUE) {
        addOrder(need.getUnit(), "; braucht " + need.getMaxAmount() + " mehr " + need.getItem()
            + ", "
            + need.getMessage(),
            false);
      }
    }
   }
   }


   /**
   private void warnCapacities() {
  * Returns true if the item is usable with weaponSkill, false otherwise.
    for (Entry<Unit, Integer> cap : capacities.entrySet()) {
  */
      if (cap.getValue() < 0) {
  protected static boolean isUsable(Item item, SkillType weaponSkill) {
        addWarning(cap.getKey(), "Kapazität überschritten um " + (-cap.getValue()));
    return isUsable(item.getItemType(), weaponSkill);
      }
    }
   }
   }


   /**
   private void sort(Need[] sorted) {
  * Returns true if skill is a weapon skill.
     for (int j = 1; j < sorted.length; ++j) {
  */
      for (int i = 0; i < j; ++i) {
  protected static boolean isWeaponSkill(Skill skill) {
         if (sorted[j].compareTo(sorted[i]) < 0) {
     return skill.getSkillType().getID().equals(EresseaConstants.S_HIEBWAFFEN)
          Need temp = sorted[i];
         || skill.getSkillType().getID().equals(EresseaConstants.S_STANGENWAFFEN)
          sorted[i] = sorted[j];
         || skill.getSkillType().getID().equals(EresseaConstants.S_BOGENSCHIESSEN)
          sorted[j] = temp;
        || skill.getSkillType().getID().equals(EresseaConstants.S_ARMBRUSTSCHIESSEN)
         }
        || skill.getSkillType().getID().equals(EresseaConstants.S_KATAPULTBEDIENUNG);
      }
    }
  }
 
  private void adjustAlreadyTransferred(Need[] needs) {
    for (Need need : needs) {
      adjustForTransfer(need);
    }
   }
   }


   /**
   private void enforceCapacities() {
  * This method tries to find out, if the unit has a weapon and a skill to use this weapon.
     for (int i = transferList.size() - 1; i >= 0; --i) {
  */
      Transfer transfer = transferList.get(i);
  public static boolean isSoldier(Unit unit) {
      Integer cap;
//     Collection<Item> items = unit.getItems();
      if ((cap = capacities.get(transfer.getTarget())) != null && cap < 0) {
    Collection items = unit.getItems();
        if (transfer.getUnit() != transfer.getTarget()) { // && transfer.isMin()
    ItemCategory weapons = world.getRules().getItemCategory(StringID.create("weapons"));
          int weight = getWeight(transfer.getItem());
    for (Item item : items) {
          if (weight > 0) {
      if (weapons.isInstance(item.getItemType())) {
            int delta = cap / weight;
        // ah, a weapon...
            if (delta * weight > cap) {
        Skill useSkill = item.getItemType().getUseSkill();
              --delta;
        if (useSkill != null) {
            }
          // okay, has the unit the skill?
            delta = Math.max(-transfer.getAmount(), delta);
          for (Skill skill : unit.getSkills()) {
             if (delta < 0) {
             if (useSkill.getSkillType().equals(skill.getSkillType()))
              undoTransfer(i, delta);
              return true;
              i = transferList.size();
            }
           }
           }
         }
         }
       }
       }
     }
     }
    return false;
   }
   }


   /**
   private void undoTransfer(int index, int delta) {
  * Parses all units in the region for orders like "; $xyz$sl" and adds a tag with name
    Transfer transfer = transferList.get(index);
  * "ejcTaggableComparator5" and value "xyz" for them.
    if (-delta < transfer.getAmount()) {
  *
      transfer.reduceAmount(-delta);
  * @param r
    } else {
  */
      transferList.remove(index);
  public static void parseShipLoaderTag(magellan.library.Region r) {
    }
     for (Unit u : r.units()) {
    if (-delta > getMulti(transfersMap, transfer.getTarget(), transfer.getItem())) {
       String name = null;
      addNewError("ungültiger Transfer " + transfer);
      for (Order order : u.getOrders2()) {
    }
        String line = order.getText();
    increaseMulti(transfersMap, transfer.getTarget(), transfer.getItem(), delta);
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]sl.*");
    changeCapacity(transfer.getUnit(), transfer.getTarget(), transfer.getItem(), delta);
        java.util.regex.Matcher m = p.matcher(line);
 
        if (m.matches()) {
    getSupply(transfer.getItem(), transfer.getUnit()).reduceAmount(delta);
          name = m.group(1);
     if (transfer.isMin()) {
        }
       transfer.getNeed().reduceMinAmount(delta);
    }
    transfer.getNeed().reduceMaxAmount(delta);
 
  }
 
  private void addTransfer(Unit giver, Unit receiver, String item, int amount, boolean min,
      boolean all,
      Need need) {
    transferList.add(new Transfer(giver, receiver, item, amount, min, all, need, ++supplySerial));
    increaseMulti(transfersMap, receiver, item, amount);
  }
 
  private void executeTransferOrders() {
    for (Transfer t : transferList) {
      if (t.getUnit() != t.getTarget()) {
        addOrder(t.getUnit(),
            getGiveOrder(t.getUnit(), t.getTarget().getID().toString(), t.getItem(),
                (t.isAll() ? Integer.MAX_VALUE : t.getAmount()), false)
                + COMMENTOrder + t.getMessage(), false);
       }
       }
       if (name != null) {
    }
         u.putTag("ejcTaggableComparator5", name);
  }
 
  private void transfer(Unit unit, Need need, int amount) {
    Supply supply = getSupply(need.getItem(), unit);
    if (supply.getAmount() < amount) {
       if (currentUnit == null) {
         addWarning(supply.getUnit(), "not enough " + need.getItem());
       } else {
       } else {
         u.removeTag("ejcTaggableComparator5");
         addNewWarning("not enough " + need.getItem() + " for " + supply.getUnit());
       }
       }
    }
    need.reduceMaxAmount(amount);
    need.reduceMinAmount(amount);
    supply.reduceAmount(amount);
    if (unit != need.getUnit()) {
      changeCapacity(supply.getUnit(), need.getUnit(), need.getItem(), amount);
     }
     }
   }
   }


   /**
   private boolean isHorse(String item) {
  * Parses all units in the region for orders like "; $xyz$verlassen" and adds a tag with name
    return HORSEItem.equals(item);
  * "ejcTaggableComparator5" and value "xyz" for them.
  }
  *
 
  * @param r
   private void giveTransfer(String targetId, String item, int amount, boolean all) {
  */
     Unit targetUnit = findUnit(targetId);
   public static void parseShipLoaderTag2(magellan.library.Region r) {
    Need dummyNeed = new Need(targetUnit, item, amount, amount, GIVE_IF_PRIORITY, new Warning(true),
     for (Unit u : r.units()) {
        ++supplySerial);
      String name = null;
    Supply supply = getSupply(item, currentUnit);
      for (Order line : u.getOrders2()) {
    all = all || (supply != null && supply.getAmount() == amount);
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]verlassen.*");
    addTransfer(currentUnit, targetUnit, item, amount, true, all, dummyNeed);
        java.util.regex.Matcher m = p.matcher(line.getText());
    transfer(currentUnit, dummyNeed, amount);
         if (m.matches()) {
  }
           name = m.group(1);
 
  public static class MyReserveVisitor implements ReserveVisitor {
 
    public void execute(Unit u, String item, int amount) {
      if (amount > 0) {
         if (amount == u.getPersons()) {
           addOrder(u, getReserveOrder(u, item // + COMMENTOrder + need.toString()
              , 1, true), false);
        } else {
          addOrder(u, getReserveOrder(u, item, amount, false), false);
         }
         }
      }
      if (name != null && name.equals("633")) {
        u.putTag("ejcTaggableComparator5", name);
      } else {
        u.removeTag("ejcTaggableComparator5");
       }
       }
     }
     }
   }
   }


   /**
   private void executeReserves(Reserves reserves) {
  * Converts some Vorlage commands to $cript commands. Call from the script of your faction with<br />
     reserves.execute(new MyReserveVisitor());
  * <code>(new E3CommandParser(world, helper)).convertVorlage((Faction) container, null);</code>
  }
  * (all regions) or from a region with<br />
  * <code>(new E3CommandParser(world, helper)).convertVorlage(helper.getFaction("1wpy"),
  * (Region) container);</code>.
  *
  * @param faction
  * @param region
  */
  public void convertVorlage(Faction faction, Region region) {
     if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);


     initLocales();
  private void changeCapacity(Unit source, Unit target, String item, int amount) {
     if (!isHorse(item)) {
      int weight = amount * getWeight(item);
      changeCapacity(source, weight);
      changeCapacity(target, -weight);
    }
  }


     if (region == null) {
  private Integer changeCapacity(Unit unit, int delta) {
       // comment out the following line if you don't have the newest nightly build of Magellan
    Integer c = capacities.get(unit);
      helper.getUI().setMaximum(world.getRegions().size());
     if (c != null) {
       capacities.put(unit, c = c + delta);
    }
    return c;
  }


      // call self for all regions
  /**
      for (Region r : world.getRegions()) {
  * Tries to satisfy (minimum) need by a RESERVE order
        // comment out the following line if you don't have the newest nightly build of Magellan
  *
        helper.getUI().setProgress(r.toString(), ++progress);
  * @param need
  * @param min
  * @param reserves
  */
  protected void reserveNeed(Need need, boolean min, Reserves reserves) {
    int amount = getNeedAmount(need, min);
    // amount = adjustForTransfer(need, amount);
 
    Supply supply = getSupply(need.getItem(), need.getUnit());
    if (supply == null)
      return;


         convertVorlage(faction, r);
    // only suppliers with positive priority serve maximum needs
    amount = Math.min(amount, supply.getAmount() - 0);
    if (amount > 0) {
      if (min) {
        addTransfer(need.getUnit(), need.getUnit(), need.getItem(), amount, min, false, need);
      }
      transfer(need.getUnit(), need, amount);
      if (min) {
         reserves.add(need.getItem(), need.getUnit(), amount);
       }
       }
      return;
     }
     }
  }


    // loop for all units of faction
  /**
     final String scriptStart = PCOMMENTOrder + " " + scriptMarker + " ";
  * Tries to satisfy (minimum) need by a give order from suppliers.
     for (Unit u : region.units())
  *
      if (faction.getID().equals(u.getFaction().getID())) {
  * @param need
        // comment out the following line if you don't have the newest nightly build of Magellan
  * @param min
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
  */
 
  protected void giveNeed(Need need, boolean min) {
        currentUnit = u;
     int amount = getNeedAmount(need, min);
//         newOrders = new ArrayList<String>();
     if (amount > 0) {
         newOrders = new ArrayList();
      // adjustForTransfer(need, amount);
//        removedOrderPatterns = new ArrayList<String>();
      for (Supply supply : supplyMapp.get(need.getItem())) {
        removedOrderPatterns = new ArrayList();
        if (supply.getUnit() != need.getUnit() && (min || supply.getPriority() > 0) && supply
        changedOrders = false;
            .hasPriority()) {
         error = -1;
          int giveAmount = Math.min(amount, supply.getAmount());
        StringBuilder lerneOrder = null;
          if (giveAmount > 0) {
 
            addTransfer(supply.getUnit(), need.getUnit(), need.getItem(), giveAmount, min, false,
        // loop over orders
                need);
        line = 0;
            transfer(supply.getUnit(), need, giveAmount);
        for (Order o : u.getOrders2()) {
            amount -= giveAmount;
          ++line;
          }
          currentOrder = o.getText();
         }
         if (amount <= 0) {
          break;
         }
      }
    }
  }
 
  private int getNeedAmount(Need need, boolean min) {
    int amount = min ? need.getMinAmount() : need.getAmount();
    return amount;
  }


          if (currentOrder.startsWith("// #")) {
  private int adjustForTransfer(Need need) {
            String[] tokens = detectVorlageCommand(currentOrder);
    int amount = need.getMaxAmount();
            if (tokens != null) {
    Integer transferred = getMulti(transfersMap, need.getUnit(), need.getItem());
              String command = tokens[0];
    if (transferred != null) {
      if (transferred > amount) {
        increaseMulti(transfersMap, need.getUnit(), need.getItem(), -amount);
        need.setMinAmount(0);
        need.setMaxAmount(0);
        amount = 0;
      } else {
        removeMulti(transfersMap, need.getUnit(), need.getItem());
        need.reduceMinAmount(transferred);
        need.reduceMaxAmount(transferred);
        amount -= transferred;
      }
    }
    return amount;
  }


              // add vorlage as comment
  protected int getWeight(String item) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
    return Math.round(world.getRules().getItemType(item).getWeight() * 100);
  }


              // detect commands
  /**
              if (command.equals("Ausruestung")) {
  * Returns the total supply for an item.
                for (int i = 1; i < tokens.length; ++i) {
  *
                  if (tokens[i].equals("Ausruestung")) {
  * @param item Order name of the supplied item
                    continue;
  * @return The supply or 0, if none has been registered.
                  }
  */
                  try {
  protected int getSupply(String item) {
                    int number = Integer.parseInt(tokens[i]);
    return supplyMapp.getSupply(item);
                    addNewOrder(scriptStart + "Benoetige " + number + " " + tokens[++i], true);
  }
                  } catch (Exception e) {
 
                    addNewOrder(scriptStart + "Benoetige JE 1 " + tokens[i], true);
  /**
                  }
  * Returns a supply of a unit for an item.
                }
  *
              } else if (command.equals("AutoBestaetigen")) {
  * @param item Order name of the supplied item
                addNewOrder(scriptStart + "auto " + (tokens[1].equals("an") ? "" : NOT), true);
  * @param unit
              } else if (command.equals("Benoetige")) {
  * @return The supply or null, if none has been registered.
                if (tokens.length > 3 && tokens[3].equals("aus")) {
  */
                  addNewOrder(scriptStart + "Benoetige 0 " + tokens[1] + " " + tokens[2], true);
  protected Supply getSupply(String item, Unit unit) {
                } else {
    return supplyMapp.get(item, unit);
                  addNewOrder(scriptStart + "Benoetige " + tokens[1] + " " + tokens[2], true);
  }
                }
 
              } else if (command.startsWith("BerufBotschafter")) { // also BerufBotschafterSTM
  /**
                addNewOrder(scriptStart + " BerufBotschafter "
  * Adds the specified amounts to the need of a unit for an item to the needMap.
                    + (tokens.length > 1 ? tokens[1] : ""), true);
  *
              } else if (command.equals("BerufDepotVerwalter")) {
  * @param item Order name of the required item
                int number = Integer.MIN_VALUE;
  * @param unit
                if (tokens.length > 1) {
  * @param minAmount
                  try {
  * @param maxAmount
                    number = Integer.parseInt(tokens[1]);
  */
                  } catch (Exception e) {
  protected void addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority) {
                    number = Integer.MIN_VALUE;
    addNeed(item, unit, minAmount, maxAmount, priority, new Warning(true));
                  }
  }
                }
 
                addNewOrder(scriptStart + "BerufDepotVerwalter " + (number > 0 ? number : ""), true);
  /**
              } else if (command.equals("BerufWahrnehmer")) {
  * Adds the specified amounts to the need of a unit for an item to the needMap.
                addNewOrder(scriptStart
  *
                    + "Lerne "
  * @param item Order name of the required item
                    + world.getRules().getSkillType(EresseaConstants.S_WAHRNEHMUNG.toString())
  * @param unit
                        .getName() + " 10", true);
  * @param minAmount
              } else if (command.equals("Depot")) {
  * @param maxAmount
                addNewOrder(scriptStart + "Benoetige " + ALLOrder, true);
  */
              } else if (command.equals("Versorge")) {
  protected Need addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority,
                addNewOrder(COMMENTOrder + " Versorge ignored", true);
      Warning w) {
              } else if (command.equals("Erlerne")) {
    Need result;
                // prepare Lerne order, but write it later to include several consecutive Erlerne
    if (world.getRules().getItemType(StringID.create(item)) == null && w.contains(~0)) {
                // orders
      addNewWarning("unknown item " + item);
                if (lerneOrder == null) {
    }
                  lerneOrder = new StringBuilder();
 
                  lerneOrder.append(scriptStart).append("Lerne");
    if (!currentFactions.containsKey(unit.getFaction())) {
                }
      int count;
                lerneOrder.append(" ").append(tokens[1]).append(" ").append(
      if (getSupply(item, unit) == null && (count = getItemCount(unit, item)) > 0) {
                    tokens.length > 2 ? tokens[2] : "10");
        putDummySupply(item, unit, count);
                // ignore warning argument "an/aus"
      }
              } else if (command.equals("Ernaehre")) {
    }
                StringBuilder order = new StringBuilder(scriptStart);
    if (minAmount > maxAmount) {
                for (String token : tokens) {
      addNewError("min amount " + minAmount + " > max amount " + maxAmount);
                  if (order.length() > 0) {
      maxAmount = minAmount;
                    order.append(" ");
    }
                  }
    needQueue.add(result = new Need(unit, item, minAmount, maxAmount, priority, w, ++supplySerial));
                  order.append(token);
 
                }
    return result;
                addNewOrder(order.toString(), true);
  }
              } else if (command.equals("GibKraeuter")) {
 
                addNewOrder(scriptStart + "GibWenn " + tokens[1] + " KRAUT", true);
  /**
              } else if (command.equals("GibWenn")) {
  * Marks the unit as soldier. Learns its best weapon skill. Reserves suitable weapon, armor, and
                StringBuilder order = new StringBuilder(scriptStart);
  * shield if available.
                for (int i = 0; i < tokens.length - 1; ++i) {
  *
                  if (order.length() > 0) {
  * @param u The unit in question
                    order.append(" ");
  * @param sWeaponSkill The desired skill. If <code>null</code>, the unit's best weapon skill is
                  }
  *          used. If the unit knows no weapon skill, a warning is issued.
                  order.append(tokens[i]);
  * @param sWeapon The desired weapon that is reserved. If this is <code>null</code>, a weapon that
                }
  *          matches the weaponSkill is reserved.
                if (tokens[tokens.length - 1].equals("aus")) {
  * @param sArmor The desired armor. If <code>null</code>, a suitable armor is reserved. If the
                  order.append(" Menge");
  *          unit has no armor at all, <em>no</em> warning is issued.
                }
  * @param sShield The desired shield. If <code>null</code>, a suitable shield is reserved. If the
                if (tokens[tokens.length - 1].equals("nie")) {
  *          unit has no shield at all, <em>no</em> warning is issued.
                  order.append(" nie");
  * @param warning Warnings for missing equipment are only issued if this is <code>true</code>.
                }
  */
                addNewOrder(order.toString(), true);
  protected void soldier(Unit u, String sWeaponSkill, String sWeapon, String sShield,
                if (command.equals("Steuermann") && tokens.length != 3) {
      String sArmor, String warning) {
                  addNewError("unzulässige Argumente");
 
                }
    Rules rules = world.getRules();
              } else if (command.equals("VersorgeFremd") || command.equals("BenoetigeFremd")) {
 
                if (tokens.length > 3 && tokens[3].equals("aus")) {
    SkillType weaponSkill =
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " 0 " + tokens[2] + " "
        sWeaponSkill == null ? null : rules.getSkillType(StringID.create(sWeaponSkill));
                      + tokens[3], true);
    ItemType weapon = sWeapon == null ? null : rules.getItemType(StringID.create(sWeapon));
                } else {
    ItemType armor = sArmor == null ? null : rules.getItemType(StringID.create(sArmor));
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " " + tokens[2] + " "
    ItemType shield = sShield == null ? null : rules.getItemType(StringID.create(sShield));
                      + tokens[3], true);
 
                }
    if (weaponSkill == null || BEST.equals(sWeaponSkill)) {
              } else if (command.equals("Warnung")) {
      int max = 0;
                StringBuilder order = new StringBuilder(scriptStart);
      for (Skill skill : u.getSkills()) {
                order.append("+0 ");
        if (isWeaponSkill(skill) && skill.getLevel() > max) {
                for (int i = 1; i < tokens.length; ++i) {
          max = skill.getLevel();
                  order.append(tokens[i]);
          weaponSkill = skill.getSkillType();
                }
        }
                addNewOrder(order.toString(), true);
      }
              } else if (command.equals("Warnung")) {
      if (weaponSkill == null && !Warning.W_NEVER.equals(warning)) {
                // ignore
        addNewWarning("kein Kampftalent");
              } else if (command.equals("Erlaube") || command.equals("Verlange")
        return;
                  || command.equals("Handel") || command.equals("Mannschaft")
      }
                  || command.equals("Quartiermeister") || command.equals("Steuermann")
    }
                  || command.equals("Ueberwache")) {
                // simply copy order
                StringBuilder order = new StringBuilder(scriptStart);
                for (String token : tokens) {
                  if (order.length() > scriptStart.length()) {
                    order.append(" ");
                  }
                  order.append(token);
                }
                addNewOrder(order.toString(), true);
                if (command.equals("Steuermann") && tokens.length != 3) {
                  addNewError("unzulässige Argumente");
                }


              } else {
    ArrayList<Item> weapons = new ArrayList<Item>();
                addNewOrder(currentOrder, false);
    if (weapon == null) {
                addNewError("unbekannter Befehl: " + command);
      for (Item item : u.getItems()) {
              }
        if (isUsable(item, weaponSkill)) {
            } else if (currentOrder.startsWith("// #forever { ")) {
          weapons.add(item);
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
        }
              addNewOrder("@" + currentOrder.substring(14, currentOrder.lastIndexOf("}") - 1), true);
      }
            } else if (currentOrder.startsWith("// #default")) {
      if (weapons.isEmpty()) {
              // ignore
        for (ItemType type : rules.getItemTypes()) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
          if (isUsable(type, weaponSkill)) {
            } else {
            weapons.add(new Item(type, 0));
              addNewOrder(currentOrder, false);
             break;
              addNewError("unbekannter Befehl: " + currentOrder);
              log.fine("unknown order " + currentOrder);
             }
          } else {
            addNewOrder(currentOrder, false);
           }
           }
          currentOrder = null;
         }
         }
        if (lerneOrder != null) { // unwritten Lerne order
          addNewOrder(lerneOrder.toString(), true);
          lerneOrder = null;
        }
        if (changedOrders) {
          currentUnit.setOrders(newOrders);
        }
        notifyMagellan(currentUnit);
        newOrders = null;
       }
       }
  }
      if (weapons.isEmpty()) {
 
        addNewError("keine passenden Waffen bekannt für " + weaponSkill);
  /**
      }
  * If order is a vorlage command ("// #call Name ..."), returns a List of the tokens. Otherwise
     } else {
  * returns <code>null</code>. The first in the list is the first token after the "// #call".
       if (u.getItem(weapon) != null) {
  */
         weapons.add(u.getItem(weapon));
  protected static String[] detectVorlageCommand(String order) {
      } else {
    StringTokenizer tokenizer = new StringTokenizer(order, " ");
        addNewError("keine " + weapon);
     if (tokenizer.hasMoreTokens()) {
      String part = tokenizer.nextToken();
       if (part.equals(PCOMMENTOrder)) {
         if (tokenizer.hasMoreTokens()) {
          part = tokenizer.nextToken();
          if (part.equals("#call")) {
//            List<String> result = new ArrayList<String>();
            List result = new ArrayList();
            while (tokenizer.hasMoreTokens()) {
              result.add(tokenizer.nextToken());
            }
            if (result.size() == 0)
              return null;
            return result.toArray(new String[] {});
          }
        }
       }
       }
     }
     }
    return null;
  }


  /**
    // note that "shield" is a subcategory of "armour"
  * @param u
    ArrayList<Item> shields = findItems(shield, u, "shield", true);
  * @param orderFilter
     if (shields.isEmpty()) {
  */
      addNewError("keine Schilde bekannt");
  protected boolean changeOrders(Unit u, OrderFilter orderFilter) {
     }
//     List<String> newOrders2 = new ArrayList<String>();
    List newOrders2 = new ArrayList();
     boolean changedOrders2 = false;


     // loop over orders
     ArrayList<Item> armors = findItems(armor, u, "armour", true);
     for (Order command : u.getOrders2()) {
     if (armors.isEmpty()) {
       if (orderFilter.changeOrder(command)) {
       addNewError("keine Rüstungen bekannt");
         changedOrders2 = true;
    }
         if (orderFilter.changedOrder() != null) {
 
          newOrders2.add(orderFilter.changedOrder());
    Collections.sort(weapons, new ItemSorter(WEAPON_PRIORITIES));
         }
    Collections.sort(shields, new ItemSorter(SHIELD_PRIORITIES));
    Collections.sort(armors, new ItemSorter(ARMOR_PRIORITIES));
 
    if (weaponSkill != null) {
      List<SkillSpec> targetSkills = new LinkedList<SkillSpec>();
      targetSkills.add(new SkillSpec(weaponSkill, DEFAULT_LEARNLEVEL, 99));
      if (weaponSkill.getName().equals("Hiebwaffen") || weaponSkill.getName().equals("Stangenwaffen")) {
         int weeks = (DEFAULT_LEARNLEVEL) * (DEFAULT_LEARNLEVEL + 1) / 2; // weeks for melee
         int weeks2 = (DEFAULT_LEARNLEVEL + 2) * (DEFAULT_LEARNLEVEL + 3) / 2; // weeks for melee + 2
        int maxskill = (int) (-.5 + Math.sqrt(.25 + 2 * (weeks2 - weeks)));
        targetSkills.add(new SkillSpec(getSkillType(S_ENDURANCE), maxskill, 99));
         // getSkillType(S_ENDURANCE), Math.round(ENDURANCERATIO_FRONT * DEFAULT_LEARNLEVEL), 99));
       } else {
       } else {
         newOrders2.add(command.getText());
         int weeks = (DEFAULT_LEARNLEVEL) * (DEFAULT_LEARNLEVEL + 1) / 2; // weeks for melee
        int weeks2 = (DEFAULT_LEARNLEVEL + 1) * (DEFAULT_LEARNLEVEL + 2) / 2; // weeks for melee + 1
        int maxskill = (int) (-.5 + Math.sqrt(.25 + 2 * (weeks2 - weeks)));
        targetSkills.add(new SkillSpec(getSkillType(S_ENDURANCE), maxskill, 99));
        // getSkillType(S_ENDURANCE), Math.round(ENDURANCERATIO_BACK * DEFAULT_LEARNLEVEL), 99));
       }
       }
      learn(u, targetSkills);
    }
    if (!NULL.equals(sWeapon)
        && !reserveEquipment(weapon, weapons, !Warning.W_NEVER.equals(warning) && !Warning.W_SKILL
            .equals(warning))) {
      addNewError("konnte Waffe nicht reservieren");
    }
    if (!NULL.equals(sShield)
        && !reserveEquipment(shield, shields, Warning.W_ARMOR.equals(warning) || Warning.W_SHIELD
            .equals(warning))) {
      addNewError("konnte Schilde nicht reservieren");
     }
     }
     if (changedOrders2) {
     if (!NULL.equals(sArmor) && !reserveEquipment(armor, armors, Warning.W_ARMOR.equals(warning))) {
       u.setOrders(newOrders2);
       addNewError("konnte Rüstung nicht reservieren");
     }
     }
    return changedOrders2;
   }
   }


   /**
   /**
   * Private utility script written to convert some of my faction's orders. Not interesting for the
   * Tries to learn the skills at the ratio reflected in the skills argument.
  * general public.
   *
   *  
   * @param u
   * @param faction
   * @param targetSkills
   * @param region
   */
   */
   public void cleanShortOrders(Faction faction, Region region) {
   protected void learn(Unit u, Collection<SkillSpec> targetSkills) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);


     initLocales();
     if (hasTeachPlugin()) {
      addToTeachPlugin(currentUnit, targetSkills);
      return;
    }


     if (region == null) {
     if (findLongOrder(u)) {
       // comment out the following line if you don't have the newest nightly build of Magellan
       addOrder(u, "; langer Befehl gefunden, Einheit wird nicht gelehrt", false);
       helper.getUI().setMaximum(world.getRegions().size());
       return;
    }


      // call self for all regions
    // find skill with maximum priority
      for (Region r : world.getRegions()) {
    double maxWeight = 0;
        // comment out the following line if you don't have the newest nightly build of Magellan
    SkillSpec maxSkill = null;
        helper.getUI().setProgress(r.toString(), ++progress);
    StringBuilder comment = new StringBuilder(";");
 
    for (SkillSpec skill : targetSkills) {
         cleanShortOrders(faction, r);
      double weight = calcSkillWeight(u, skill, targetSkills);
      comment.append(" ").append(skill.toString()).append(" ").append(weight);
      if (weight > maxWeight) {
        maxWeight = weight;
         maxSkill = skill;
       }
       }
      return;
     }
     }


     // loop for all units of faction
     addNewOrder(comment.toString(), true);
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);


        if (changeOrders(u, new ShortOrderFilter(world))) {
    if (maxSkill == null) {
          notifyMagellan(u);
      addNewError("kein Kampftalent");
        }
    } else {
       }
      removeOrdersLike(LEARNOrder + ".*", true);
      removeOrdersLike(TEACHOrder + ".*", true);
       addNewOrder(LEARNOrder + " " + maxSkill.skill.getName(), true);
     }
     }
   }
   }


   /**
   private boolean hasTeachPlugin() {
  * Private utility script written to convert some of my faction's orders. Not interesting for the
    return TEACH_PREFIX != null;
  * general public.
  }
  *
 
  * @param faction
   private void addToTeachPlugin(Unit unit, Collection<SkillSpec> targetSkills) {
  * @param region
    Collection<SkillSpec> oldskills = skills.get(unit);
  */
     if (oldskills != null) {
   public void cleanScripts(Faction faction, Region region) {
       Map<SkillType, SkillSpec> map = new OrderedHashtable<SkillType, SkillSpec>();
     if (faction == null)
      for (SkillSpec spec : targetSkills) {
       throw new NullPointerException();
        map.put(spec.skill, spec);
    currentFactions = Collections.singletonMap(faction, 1);
      }
      for (SkillSpec spec : oldskills) {
        map.put(spec.skill, spec);
      }
      for (SkillSpec spec : targetSkills) {
        map.put(spec.skill, spec);
      }


     initLocales();
      skills.put(unit, map.values());
     } else {
      skills.put(unit, targetSkills);
    }
  }


     if (region == null) {
  private void teachUnits() {
       // comment out the following line if you don't have the newest nightly build of Magellan
     for (Unit u : skills.keySet()) {
      helper.getUI().setMaximum(world.getRegions().size());
       if (!findLongOrder(u)) {
 
        StringBuilder learnOrder = new StringBuilder(String.format("; $%s$L 100.0", TEACH_PREFIX));
      // call self for all regions
        for (SkillSpec s : skills.get(u)) {
      for (Region r : world.getRegions()) {
          learnOrder.append(String.format(" %s %d %d", s.skill.getName(), s.level, s.max));
         // comment out the following line if you don't have the newest nightly build of Magellan
         }
         helper.getUI().setProgress(r.toString(), ++progress);
         addOrder(u, String.format("; $%s$T ALLES 0", TEACH_PREFIX), true);
 
        addOrder(u, learnOrder.toString(), true);
         cleanScripts(faction, r);
      } else {
         addOrder(u, "; langer Befehl gefunden, Einheit wird nicht gelehrt", false);
       }
       }
      return;
     }
     }
  }


     // loop for all units of faction
  private boolean findLongOrder(Unit u) {
     for (Unit u : region.units()) {
     boolean teach = true;
       if (faction.getID().equals(u.getFaction().getID())) {
     for (Order order : u.getOrders2()) {
         // comment out the following line if you don't have the newest nightly build of Magellan
       if (order.isLong() && !order.getText().startsWith(TEACHOrder) && !order.getText().startsWith(LEARNOrder))
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
         return true;
    }
    return false;
  }


        currentUnit = u;
  /**
//        newOrders = new ArrayList<String>();
  * Returns a weight ("importance") of a skill according to target values and the unit's current
        newOrders = new ArrayList();
  * skill levels.
//        removedOrderPatterns = new ArrayList<String>();
  */
        removedOrderPatterns = new ArrayList();
  protected static double calcSkillWeight(Unit u, SkillSpec learningSkill,
        changedOrders = false;
      Collection<SkillSpec> targetSkills) {
        error = -1;
    if (learningSkill == null)
      return 0;
    double prio = 0.5;


        // loop over orders
    // calc max mult
        line = 0;
    SkillSpec learningTarget = null;
        for (Order command : u.getOrders2()) {
    double maxMult = 0;
          currentOrder = command.getText();
 
          if (currentOrder.startsWith("@RESERVIEREN")) {
    for (SkillSpec skill2 : targetSkills) {
            addNewOrder("// $cript Benoetige " + currentOrder.substring(currentOrder.indexOf(" ")),
      if (skill2.skill.equals(learningSkill.skill)) {
                true);
        learningTarget = skill2;
            addNewOrder(currentOrder.substring(1), true);
      }
          } else {
      int level = Math.max(1, getSkillLevel(u, skill2.skill));
            if (command.isLong() || command.isPersistent() || command.getText().startsWith("//")) {
      if (level < skill2.max) {
              addNewOrder(command.getText(), false);
        double mult = level / (double) skill2.level;
            } else {
        if (maxMult < mult) {
              // omit
          maxMult = mult;
              changedOrders = true;
            }
          }
         }
         }
        if (changedOrders) {
          currentUnit.setOrders(newOrders);
        }
        notifyMagellan(currentUnit);
        newOrders = null;
       }
       }
     }
     }


  }
    if (learningTarget == null)
      // learned skill is not in target skills
      return 0.01 * learningSkill.level;


  /**
    if (maxMult > 0) {
  * Private utility script written to handle unwanted orders created by the TeachPlugin. Not
      // calc max normalized learning weeks
  * interesting for the general public.
      double maxWeeks = 0;
  *
       for (SkillSpec skill2 : targetSkills) {
  * @param faction
        int currentLevel = Math.max(1, getSkillLevel(u, skill2.skill));
  * @param region
        if (currentLevel < skill2.max) {
  */
          double weeks = skill2.level - currentLevel / maxMult + 2;
  public void undoTeaching(Faction faction, Region region) {
          if (maxWeeks < weeks) {
    if (faction == null)
            maxWeeks = weeks;
       throw new NullPointerException();
          }
    currentFactions = Collections.singletonMap(faction, 1);
        }
 
       }
    initLocales();
       int level = Math.max(1, getSkillLevel(u, learningSkill.skill));
 
       if (level >= learningSkill.max) {
    if (region == null) {
        prio = 0.01d;
       // comment out the following line if you don't have the newest nightly build of Magellan
      } else {
       helper.getUI().setMaximum(world.getRegions().size());
         // prio should be between .4 and 1
 
         prio = .4 + .6 * (learningTarget.level - level / maxMult + 2) / maxWeeks;
       // call self for all regions
      }
      for (Region r : world.getRegions()) {
      if (prio < 0) {
         // comment out the following line if you don't have the newest nightly build of Magellan
        prio = prio * 1.000001;
         helper.getUI().setProgress(r.toString(), ++progress);
 
        undoTeaching(faction, r);
       }
       }
      return;
     }
     }
    return prio;
  }


    // loop for all units of faction
  protected static int getSkillLevel(Unit u, SkillType skill) {
    for (Unit u : region.units()) {
    Skill uskill = u.getSkill(skill);
      if (faction.getID().equals(u.getFaction().getID())) {
    return uskill == null ? 0 : uskill.getLevel();
        // comment out the following line if you don't have the newest nightly build of Magellan
  }
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);


        currentUnit = u;
  /**
//        newOrders = new ArrayList<String>();
  * If <code>itemType==null</code> return all the unit's items of the given category. If no item is
        newOrders = new ArrayList();
  * found, at least one item (with amount 0) is returned.
//        removedOrderPatterns = new ArrayList<String>();
  */
        removedOrderPatterns = new ArrayList();
  protected static ArrayList<Item> findItems(ItemType itemType, Unit u, String category,
        changedOrders = false;
      boolean returnDummy) {
        error = -1;
    ArrayList<Item> items = new ArrayList<Item>(1);
 
    ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
        // loop over orders
    if (itemType == null) {
        line = 0;
      for (Item item : u.getItems()) {
        for (Order command : u.getOrders2()) {
        if (itemCategory.equals(item.getItemType().getCategory())) {
          currentOrder = command.getText();
           items.add(item);
          if (currentOrder.startsWith("; $$$ LE")) {
            addNewOrder(currentOrder.substring(currentOrder.indexOf("$$$") + 4) + " ; restored",
                true);
            changedOrders = true;
          } else if (changedOrders
              && (currentOrder.startsWith("LERNE") || currentOrder.startsWith("LEHRE"))) {
            // skip
           } else {
            addNewOrder(command.getText(), false);
          }
         }
         }
        if (changedOrders) {
      }
          currentUnit.setOrders(newOrders);
    } else {
        }
      if (u.getItem(itemType) != null) {
         notifyMagellan(currentUnit);
        items.add(u.getItem(itemType));
 
      }
         newOrders = null;
    }
    if (items.isEmpty() && returnDummy) {
      for (Object o : itemCategory.getInstances()) {
         ItemType type = (ItemType) o;
        items.add(new Item(type, 0));
         break;
       }
       }
     }
     }
    return items;
  }
  protected boolean reserveEquipment(ItemType preferred, List<Item> ownStuff, boolean warn) {
    if (preferred != null) {
      // reserve requested weapon
      commandNeed(new String[] { "Benoetige", EACHOrder, "1", preferred.getOrderName(),
          String.valueOf(DEFAULT_PRIORITY) });
    } else if (!ownStuff.isEmpty()) {
      int supply = 0;
      for (Item w : ownStuff) {
        // reserve all matching weapons, except first one
        if (supply > 0) {
          commandNeed(new String[] { "Benoetige",
              Integer.toString(Math.min(currentUnit.getPersons() - supply, w.getAmount())),
              w.getOrderName(), String.valueOf(DEFAULT_PRIORITY) });
        }
        supply += w.getAmount();
        if (supply >= currentUnit.getPersons()) {
          supply = currentUnit.getPersons();
          break;
        }
      }


   }
      // now reserve the first matching weapon with
 
      // min=w.getAmount(), max=w.getPersons()-other weapons
   /**
      Item w = ownStuff.get(0);
   * Again, a very specialized procedure
      String max =
   */
          Integer.toString(Math.max(0, Math.min(currentUnit.getPersons() - supply + w.getAmount(),
   protected void markTRound(int round) {
              currentUnit.getPersons())));
     if (world.getDate().getDate() == round) {
      String min = warn ? max : Integer.toString(Math.min(currentUnit.getPersons(), w.getAmount()));
       for (Unit u : world.getUnits()) {
      commandNeed(new String[] { "Benoetige", min, max, w.getOrderName(),
         changeOrders(u, new TeachRoundOrderFilter());
          String.valueOf(DEFAULT_PRIORITY) });
         u.addOrder("// $$L" + (round + 1));
    } else
       }
      return false;
    return true;
  }
 
  @SuppressWarnings("unused")
  private static Integer putMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2,
      Integer value) {
    Map<String, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<String, Integer>());
    }
    Integer old = inner.put(key2, value);
    return old;
  }
 
  private static void increaseMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2,
      int value) {
    Map<String, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<String, Integer>());
    }
    Integer old = inner.get(key2);
    if (old == null) {
      inner.put(key2, value);
    } else {
      inner.put(key2, old + value);
    }
  }
 
  static void increaseMultiInv(Map<String, Map<Unit, Integer>> map, String key1, Unit key2,
      int value) {
    Map<Unit, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<Unit, Integer>());
    }
    Integer old = inner.get(key2);
    if (old == null) {
      inner.put(key2, value);
    } else {
      inner.put(key2, old + value);
    }
  }
 
  @SuppressWarnings("unused")
  private static void appendMulti(Map<Object, Map<Object, Collection<Integer>>> map, Object key1,
      Object key2,
      Integer value) {
    Map<Object, Collection<Integer>> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<Object, Collection<Integer>>());
    }
    Collection<Integer> old = inner.get(key2);
    if (old == null) {
      inner.put(key2, old = new ArrayList<Integer>());
    }
    old.add(value);
  }
 
  private static Integer getMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2) {
    Map<String, Integer> inner = map.get(key1);
    if (inner != null)
      return inner.get(key2);
    return null;
  }
 
  private static Integer removeMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2) {
    Map<String, Integer> inner = map.get(key1);
    if (inner != null) {
      Integer val = inner.remove(key2);
      if (inner.isEmpty()) {
        map.remove(key1);
      }
      return val;
    }
    return null;
  }
 
  // ///////////////////////////////////////////////////////
  // HELPER functions
  // ///////////////////////////////////////////////////////
 
  /**
  * Adds a warning message (with a to do tag) directly to the unit's orders.
  *
  * @param unit
  * @param text
  */
  public static void addWarning(Unit unit, String text) {
    helper.addOrder(unit, "; TODO: " + text);
    setConfirm(unit, false);
  }
 
  public static void addOrder(Unit u, String order, boolean refresh) {
    u.addOrder(order, refresh);
  }
 
  /**
  * If confirm is false, mark unit as not confirmable. If confirm is true, mark it as confirmable
  * if it has not been marked as unconfirmable before (unconfirm always overrides confirm).
  *
  * @param u
  * @param confirm
  */
  public static void setConfirm(Unit u, boolean confirm) {
    String tag = getProperty(u, "confirm");
    if (confirm) {
      if (tag.length() == 0) {
        setProperty(u, "confirm", "1");
      }
    } else if (!confirm) {
      setProperty(u, "confirm", "0");
    }
  }
 
  /**
  * Add a property (in form of a tag) to the unit.
  *
  * @param u
  * @param tagName
  * @param value
  * @see #getProperty(Unit, String)
  */
  public static void setProperty(Unit u, String tagName, String value) {
    u.putTag(scriptMarker + "." + tagName, value);
    // u.addOrder("; $cript " + tagName + ":" + value, false, 0);
  }
 
  /**
  * Returns a property value (from a tag) from the unit.
  *
  * @param u
  * @param tagName
  * @return The value of property <code>tagName</code> or "" if the property has not been set.
  */
  public static String getProperty(Unit u, String tagName) {
    String tag = u.getTag(scriptMarker + "." + tagName);
    return tag == null ? "" : tag;
  }
 
  /**
  * Returns the ItemType in the rules matching <code>name</code>.
  *
  * @param name A item type name, like "Silber"
  * @return The ItemType corresponding to name or <code>null</code> if this ItemType does not
  *        exist.
  */
  public static ItemType getItemType(String name) {
    return world.getRules().getItemType(name);
  }
 
  /**
  * Returns the SkillType in the rules matching <code>name</code>.
  *
  * @param name A skill name, like "Ausdauer"
  * @return The SkillType corresponding to name or <code>null</code> if this SkillType does not
  *        exist.
  */
  public static SkillType getSkillType(String name) {
    return world.getRules().getSkillType(name);
  }
 
  /**
  * Returns a skill with the given name and level.
  *
  * @param name
  * @param level
  * @return A skill with the given level or <code>null</code> if this SkillType does not exist.
  */
  public static Skill getSkill(String name, int level) {
    if (getSkillType(name) != null)
      return new Skill(getSkillType(name), 0, level, 1, true);
    else
      return null;
  }
 
  /**
  * Notifies the interface that the unit should be updated.
  *
  * @param u This unit is updated in the UI
  * @deprecated I don't think this is needed any more.
  */
  @Deprecated
  public static void notifyMagellan(Unit u) {
    helper.updateUnit(u);
  }
 
  /**
  * Adds a GIVE order to the unit's orders, like <code>GIVE receiver [EACH] amount item</code>.
  *
  * @param unit The order is added to this unit's orders
  * @param receiver
  * @param item
  * @param amount
  * @param each
  */
  public static void addGiveOrder(Unit unit, Unit receiver, String item, int amount, boolean each) {
    helper.addOrder(unit, getGiveOrder(unit, receiver.getID().toString(), item, amount, each));
  }
 
  /**
  * Returns a line like <code>GIVE receiver [EACH] amount item</code>.
  *
  * @param unit
  * @param receiver
  * @param item
  * @param amount If <code>amount == Integer.MAX_VALUE</code>, amount is replaced by ALL
  * @param each
  * @return a line like <code>GIVE receiver [EACH] amount item</code>.
  */
  public static String getGiveOrder(Unit unit, String receiver, String item, int amount,
      boolean each) {
    return helper.getGiveOrder(unit, receiver, item, amount, each);
  }
 
  /**
  * Adds a RESERVE order to the unit's orders, like <code>RESERVE [EACH] amount item</code>.
  *
  * @param unit The order is added to this unit's orders
  * @param item
  * @param amount
  * @param each
  */
  public static void addReserveOrder(Unit unit, String item, int amount, boolean each) {
    helper.addOrder(unit, getReserveOrder(unit, item, amount, each));
  }
 
  /**
  * Returns a line like <code>RESERVE [EACH] amount item</code>.
  *
  * @param unit
  * @param item
  * @param amount
  * @param each
  * @return a line like <code>GIVE receiver [EACH] amount item</code>.
  */
  public static String getReserveOrder(Unit unit, String item, int amount, boolean each) {
    return helper.getReserveOrder(unit, item, amount, each);
  }
 
  /**
  * Returns a <code>RESEARCH HERBS</code> order.
  */
  protected String getResearchOrder() {
    return RESEARCHOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER");
  }
 
  /**
  * Returns a <code>RECRUIT amount race</code> order.
  */
  protected String getRecruitOrder(int amount, Race race) {
    if (race != null)
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount, race });
    else
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount });
    // return RECRUITOrder + " " + amount
    // + (race != null ? (" " + getLocalizedOrder("race." + race.getID(), race.getName())) : "");
  }
 
  /**
  * Return the amount of item that a unit has.
  *
  * @param unit
  * @param item
  * @return The amount of item in the unit's items
  */
  public static int getItemCount(Unit unit, String item) {
    return helper.getItemCount(unit, item);
  }
 
  /**
  * Return the amount of silver of the unit.
  *
  * @param unit
  * @return The amount of silver in this unit's items
  */
  public static int getSilber(Unit unit) {
    return Math.max(getItemCount(unit, "Silver"), getItemCount(unit, "Silber"));
  }
 
  /**
  * Returns true if the item is usable with weaponSkill, false otherwise.
  */
  protected static boolean isUsable(ItemType type, SkillType weaponSkill) {
    return (type.getUseSkill() != null && type.getUseSkill().getSkillType().equals(weaponSkill));
  }
 
  /**
  * Returns true if the item is usable with weaponSkill, false otherwise.
  */
  protected static boolean isUsable(Item item, SkillType weaponSkill) {
    return isUsable(item.getItemType(), weaponSkill);
  }
 
  /**
  * Returns true if skill is a weapon skill.
  */
  protected static boolean isWeaponSkill(Skill skill) {
    return skill.getSkillType().getID().equals(EresseaConstants.S_HIEBWAFFEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_STANGENWAFFEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_BOGENSCHIESSEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_ARMBRUSTSCHIESSEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_KATAPULTBEDIENUNG);
  }
 
  /**
  * This method tries to find out, if the unit has a weapon and a skill to use this weapon.
  */
  public static boolean isSoldier(Unit unit) {
    Collection<Item> items = unit.getItems();
    ItemCategory weapons = world.getRules().getItemCategory(StringID.create("weapons"));
    for (Item item : items) {
      if (weapons.isInstance(item.getItemType())) {
        // ah, a weapon...
        Skill useSkill = item.getItemType().getUseSkill();
        if (useSkill != null) {
          // okay, has the unit the skill?
          for (Skill skill : unit.getSkills()) {
            if (useSkill.getSkillType().equals(skill.getSkillType()))
              return true;
          }
        }
      }
    }
    return false;
  }
 
  /**
  * Parses all units in the region for orders like "; $xyz$sl" and adds a tag with name
  * "ejcTaggableComparator5" and value "xyz" for them.
  *
  * @param r
  */
  public static void parseShipLoaderTag(magellan.library.Region r) {
    for (Unit u : r.units()) {
      String name = null;
      for (Order order : u.getOrders2()) {
        String line = order.getText();
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]sl.*");
        java.util.regex.Matcher m = p.matcher(line);
        if (m.matches()) {
          name = m.group(1);
        }
      }
      if (name != null) {
        u.putTag("ejcTaggableComparator5", name);
      } else {
        u.removeTag("ejcTaggableComparator5");
      }
    }
  }
 
  /**
  * Parses all units in the region for orders like "; $xyz$verlassen" and adds a tag with name
  * "ejcTaggableComparator5" and value "xyz" for them.
  *
  * @param r
  */
  public static void parseShipLoaderTag2(magellan.library.Region r) {
    for (Unit u : r.units()) {
      String name = null;
      for (Order line : u.getOrders2()) {
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]verlassen.*");
        java.util.regex.Matcher m = p.matcher(line.getText());
        if (m.matches()) {
          name = m.group(1);
        }
      }
      if (name != null && name.equals("633")) {
        u.putTag("ejcTaggableComparator5", name);
      } else {
        u.removeTag("ejcTaggableComparator5");
      }
    }
  }
 
  /**
  * Converts some Vorlage commands to $cript commands. Call from the script of your faction
  * with<br />
  * <code>(new E3CommandParser(world, helper)).convertVorlage((Faction) container, null);</code>
  * (all regions) or from a region with<br />
  * <code>(new E3CommandParser(world, helper)).convertVorlage(helper.getFaction("1wpy"),
  * (Region) container);</code>.
  *
  * @param faction
  * @param region
  */
  public void convertVorlage(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);
 
    initLocales();
 
    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());
 
      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);
 
        convertVorlage(faction, r);
      }
      return;
    }
 
    // loop for all units of faction
    final String scriptStart = createScriptCommand();
    for (Unit u : region.units())
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
 
        setCurrentUnit(u);
 
        StringBuilder lerneOrder = null;
 
        // loop over orders
        line = 0;
        for (Order o : u.getOrders2()) {
          ++line;
          currentOrder = o.getText();
 
          if (currentOrder.startsWith("// #")) {
            String[] tokens = detectVorlageCommand(currentOrder);
            if (tokens != null) {
              String command = tokens[0];
 
              // add vorlage as comment
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
 
              // detect commands
              if (command.equals("Ausruestung")) {
                for (int i = 1; i < tokens.length; ++i) {
                  if (tokens[i].equals("Ausruestung")) {
                    continue;
                  }
                  try {
                    int number = Integer.parseInt(tokens[i]);
                    addNewOrder(scriptStart + "Benoetige " + number + " " + tokens[++i], true);
                  } catch (Exception e) {
                    addNewOrder(scriptStart + "Benoetige JE 1 " + tokens[i], true);
                  }
                }
              } else if (command.equals("AutoBestaetigen")) {
                addNewOrder(scriptStart + "auto " + (tokens[1].equals("an") ? "" : NOT), true);
              } else if (command.equals("Benoetige")) {
                if (tokens.length > 3 && tokens[3].equals("aus")) {
                  addNewOrder(scriptStart + "Benoetige 0 " + tokens[1] + " " + tokens[2], true);
                } else {
                  addNewOrder(scriptStart + "Benoetige " + tokens[1] + " " + tokens[2], true);
                }
              } else if (command.startsWith("BerufBotschafter")) { // also BerufBotschafterSTM
                addNewOrder(scriptStart + " BerufBotschafter "
                    + (tokens.length > 1 ? tokens[1] : ""), true);
              } else if (command.equals("BerufDepotVerwalter")) {
                int number = Integer.MIN_VALUE;
                if (tokens.length > 1) {
                  try {
                    number = Integer.parseInt(tokens[1]);
                  } catch (Exception e) {
                    number = Integer.MIN_VALUE;
                  }
                }
                addNewOrder(scriptStart + "BerufDepotVerwalter " + (number > 0 ? number : ""),
                    true);
              } else if (command.equals("BerufWahrnehmer")) {
                addNewOrder(scriptStart
                    + "Lerne "
                    + world.getRules().getSkillType(EresseaConstants.S_WAHRNEHMUNG.toString())
                        .getName() + " 10", true);
              } else if (command.equals("Depot")) {
                addNewOrder(scriptStart + "Benoetige " + ALLOrder, true);
              } else if (command.equals("Versorge")) {
                addNewOrder(COMMENTOrder + " Versorge ignored", true);
              } else if (command.equals("Erlerne")) {
                // prepare Lerne order, but write it later to include several consecutive Erlerne
                // orders
                if (lerneOrder == null) {
                  lerneOrder = new StringBuilder();
                  lerneOrder.append(scriptStart).append("Lerne");
                }
                lerneOrder.append(" ").append(tokens[1]).append(" ").append(
                    tokens.length > 2 ? tokens[2] : "10");
                // ignore warning argument "an/aus"
              } else if (command.equals("Ernaehre")) {
                StringBuilder order = new StringBuilder(scriptStart);
                for (String token : tokens) {
                  if (order.length() > 0) {
                    order.append(" ");
                  }
                  order.append(token);
                }
                addNewOrder(order.toString(), true);
              } else if (command.equals("GibKraeuter")) {
                addNewOrder(scriptStart + "GibWenn " + tokens[1] + " KRAUT", true);
              } else if (command.equals("GibWenn")) {
                StringBuilder order = new StringBuilder(scriptStart);
                for (int i = 0; i < tokens.length - 1; ++i) {
                  if (order.length() > 0) {
                    order.append(" ");
                  }
                  order.append(tokens[i]);
                }
                if (tokens[tokens.length - 1].equals("aus")) {
                  order.append(" Menge");
                }
                if (tokens[tokens.length - 1].equals("nie")) {
                  order.append(" nie");
                }
                addNewOrder(order.toString(), true);
                if (command.equals("Steuermann") && tokens.length != 3) {
                  addNewError("unzulässige Argumente");
                }
              } else if (command.equals("VersorgeFremd") || command.equals("BenoetigeFremd")) {
                if (tokens.length > 3 && tokens[3].equals("aus")) {
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " 0 " + tokens[2] + " "
                      + tokens[3], true);
                } else {
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " " + tokens[2] + " "
                      + tokens[3], true);
                }
              } else if (command.equals("Warnung")) {
                StringBuilder order = new StringBuilder(scriptStart);
                order.append("+0 ");
                for (int i = 1; i < tokens.length; ++i) {
                  order.append(tokens[i]);
                }
                addNewOrder(order.toString(), true);
              } else if (command.equals("Warnung")) {
                // ignore
              } else if (command.equals("Erlaube") || command.equals("Verlange")
                  || command.equals("Handel") || command.equals("Mannschaft")
                  || command.equals("Quartiermeister") || command.equals("Steuermann")
                  || command.equals("Ueberwache")) {
                // simply copy order
                StringBuilder order = new StringBuilder(scriptStart);
                for (String token : tokens) {
                  if (order.length() > scriptStart.length()) {
                    order.append(" ");
                  }
                  order.append(token);
                }
                addNewOrder(order.toString(), true);
                if (command.equals("Steuermann") && tokens.length != 3) {
                  addNewError("unzulässige Argumente");
                }
 
              } else {
                addNewOrder(currentOrder, false);
                addNewError("unbekannter Befehl: " + command);
              }
            } else if (currentOrder.startsWith("// #forever { ")) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
              addNewOrder("@" + currentOrder.substring(14, currentOrder.lastIndexOf("}") - 1),
                  true);
            } else if (currentOrder.startsWith("// #default")) {
              // ignore
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
            } else {
              addNewOrder(currentOrder, false);
              addNewError("unbekannter Befehl: " + currentOrder);
              log.fine("unknown order " + currentOrder);
            }
          } else {
            addNewOrder(currentOrder, false);
          }
          currentOrder = null;
 
        }
        if (lerneOrder != null) { // unwritten Lerne order
          addNewOrder(lerneOrder.toString(), true);
          lerneOrder = null;
        }
 
        updateCurrentOrders();
        setCurrentUnit(null);
      }
  }
 
  /**
  * If order is a vorlage command ("// #call Name ..."), returns a List of the tokens. Otherwise
  * returns <code>null</code>. The first in the list is the first token after the "// #call".
  */
  protected static String[] detectVorlageCommand(String order) {
    StringTokenizer tokenizer = new StringTokenizer(order, " ");
    if (tokenizer.hasMoreTokens()) {
      String part = tokenizer.nextToken();
      if (part.equals(PCOMMENTOrder)) {
        if (tokenizer.hasMoreTokens()) {
          part = tokenizer.nextToken();
          if (part.equals("#call")) {
            List<String> result = new ArrayList<String>();
            while (tokenizer.hasMoreTokens()) {
              result.add(tokenizer.nextToken());
            }
            if (result.size() == 0)
              return null;
            return result.toArray(new String[] {});
          }
        }
      }
    }
    return null;
  }
 
  /**
  * @param u
  * @param orderFilter
  */
  protected boolean changeOrders(Unit u, OrderFilter orderFilter) {
    List<String> newOrders2 = new ArrayList<String>();
    boolean changedOrders2 = false;
 
    // loop over orders
    for (Order command : u.getOrders2()) {
      if (orderFilter.changeOrder(command)) {
        changedOrders2 = true;
        if (orderFilter.changedOrder() != null) {
          newOrders2.add(orderFilter.changedOrder());
        }
      } else {
        newOrders2.add(command.getText());
      }
    }
    if (changedOrders2) {
      u.setOrders(newOrders2);
    }
    return changedOrders2;
  }
 
  /**
  * Private utility script written to convert some of my faction's orders. Not interesting for the
  * general public.
  *
  * @param faction
  * @param region
  */
  public void cleanShortOrders(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);
 
    initLocales();
 
    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());
 
      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);
 
        cleanShortOrders(faction, r);
      }
      return;
    }
 
    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
 
        if (changeOrders(u, new ShortOrderFilter(world))) {
          notifyMagellan(u);
        }
      }
    }
  }
 
  /**
  * Private utility script written to convert some of my faction's orders. Not interesting for the
  * general public.
  *
  * @param faction
  * @param region
  */
  public void cleanScripts(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);
 
    initLocales();
 
    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());
 
      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);
 
        cleanScripts(faction, r);
      }
      return;
    }
 
    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
 
        setCurrentUnit(u);
 
        // loop over orders
        line = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (currentOrder.startsWith("@RESERVIERE")) {
            addNewOrder("// $cript Benoetige " + currentOrder.substring(currentOrder.indexOf(" ")),
                true);
            addNewOrder(currentOrder.substring(1), true);
          } else {
            if (command.isLong() || command.isPersistent() || command.getText().startsWith("//")) {
              addNewOrder(command.getText(), false);
            } else {
              // omit
              setChangedOrders(true);
            }
          }
        }
        updateCurrentOrders();
        setCurrentUnit(null);
      }
    }
 
  }
 
  /**
  * Private utility script written to handle unwanted orders created by the TeachPlugin. Not
  * interesting for the general public.
  *
  * @param faction
  * @param region
  */
  public void undoTeaching(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);
 
    initLocales();
 
    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());
 
      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);
 
        undoTeaching(faction, r);
      }
      return;
    }
 
    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
 
        setCurrentUnit(u);
 
        // loop over orders
        line = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (currentOrder.startsWith("; $$$ LE")) {
            addNewOrder(currentOrder.substring(currentOrder.indexOf("$$$") + 4) + " ; restored",
                true);
            setChangedOrders(true);
          } else if (isChangedOrders()
              && (currentOrder.startsWith("LERNE") || currentOrder.startsWith("LEHRE"))) {
            // skip
          } else {
            addNewOrder(command.getText(), false);
          }
        }
        updateCurrentOrders();
        setCurrentUnit(null);
      }
    }
 
  }
 
  /**
  * Private utility script written to handle unwanted orders. Not interesting for the general
  * public.
  *
  * @param faction
  * @param region
  */
  public void fixTwoLongOrders(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);
 
    initLocales();
 
    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());
 
      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);
 
        fixTwoLongOrders(faction, r);
      }
      return;
    }
 
    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);
 
        setCurrentUnit(u);
 
        int hasLong = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (command.isLong()) {
            if (++hasLong > 1) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
              setChangedOrders(true);
            } else {
              addNewOrder(currentOrder, false);
            }
          } else {
            addNewOrder(currentOrder, false);
          }
        }
        updateCurrentOrders();
        setCurrentUnit(null);
      }
    }
  }
 
  /**
  * Private utility script to convert old crew logic to new.
  *
  */
  public static void correctCrewReserve(GameData data) {
    for (Unit u : data.getUnits()) {
      boolean found = false;
      for (Order o : u.getOrders2()) {
        if (o.getText().startsWith("// $cript Steuermann") || o.getText().startsWith("// $cript Mannschaft")) {
          found = true;
          break;
        }
      }
      if (found) {
        found = false;
        ArrayList<String> orders = new ArrayList<String>();
 
        for (Order o : u.getOrders2()) {
          String text = o.getText();
          if (!text.matches("// \\$cript Benoetige .* Schwert") && !text.matches(
              "// \\$cript Benoetige .* Schild")
              && !text.matches("// \\$cript Benoetige .* Speer")) {
            orders.add(text);
          } else {
            found = true;
            orders.add("; " + text);
          }
        }
        if (found) {
          u.setOrders(orders);
        }
      }
    }
  }
 
  /**
  * Deletes all signs, then creates a sign for every island.
  *
  * @param world
  */
  public static void signIslands(GameData world) {
 
    for (Region r : world.getRegions()) {
      r.clearSigns();
    }
    for (Island island : world.getIslands()) {
      if (island.regions().size() == 0) {
        continue;
      }
      if (island.regions().size() == 1) {
        Region r = island.regions().iterator().next();
        if (!r.getRegionType().isLand()) {
          continue;
        }
      }
      if (island.getModifiedName().matches("([0-9]+. )*[0-9]+")) {
        continue;
      }
 
      int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE;
      for (Region r : island.regions()) {
        if (r.getCoordinate().getZ() != 0) {
          continue;
        }
        minX = Math.min(minX, r.getCoordX());
        minY = Math.min(minY, r.getCoordY());
        maxX = Math.max(maxX, r.getCoordX());
        maxY = Math.max(maxY, r.getCoordY());
      }
      int minDist = Integer.MAX_VALUE;
      Region minR = null;
      for (Region r : island.regions()) {
        int x2 = (minX + maxX) / 2 - r.getCoordX();
        int y2 = (minY + maxY) / 2 - r.getCoordY();
        int dist2 = x2 * x2 + y2 * y2;
        if (minDist > dist2 || minR == null) {
          minDist = dist2;
          minR = r;
        }
      }
      if (minR != null) {
        minR.addSign(new Sign(island.getModifiedName()));
      }
 
    }
   }
 
   /**
   * Again, a very specialized procedure
   */
   protected void markTRound(int round) {
     if (world.getDate().getDate() == round) {
       for (Unit u : world.getUnits()) {
         changeOrders(u, new TeachRoundOrderFilter());
         u.addOrder("// $$L" + (round + 1));
       }
    }
  }
 
  private void setNamespaces(MagellanPlugIn plugin, String namespaces) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    ExtendedCommandsHelper.invoke(plugin, "setNamespaces", new Class[] { String.class },
        new Object[] { namespaces });
  }
 
  private String getNamespaces(MagellanPlugIn plugin) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    return (String) ExtendedCommandsHelper.invoke(plugin, "getNamespacesString", new Class[] {},
        new Object[] {});
  }
 
  protected void teachRegion(Collection<Region> regions, String namespaces) {
    ArrayList<Region> regions2 = new ArrayList<Region>();
    for (Region r : regions) {
      if (r != null) {
        regions2.add(r);
      }
    }
    if (regions2.isEmpty())
      return;
 
    log.info("trying to call TeachPlugin method..");
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
    if (plugin != null) {
      try {
        String oldNameSpaces = null;
        if (namespaces != null) {
          oldNameSpaces = getNamespaces(plugin);
          setNamespaces(plugin, namespaces);
        }
        ExtendedCommandsHelper.invoke(plugin, "doTeachUnits", new Class[] { Collection.class },
            new Collection[] { regions2 });
        if (oldNameSpaces != null) {
          setNamespaces(plugin, oldNameSpaces);
        }
      } catch (ClassCastException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (SecurityException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (NoSuchMethodException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalArgumentException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalAccessException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (InvocationTargetException e) {
        log.warn("error calling TeachPlugin", e);
      }
    } else {
      log.warn("TeachPlugin not found");
    }
  }
 
  protected void teachAll(String namespaces) {
    log.info("trying to call TeachPlugin method..");
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
    if (plugin != null) {
      try {
        String oldNamespaces = null;
        if (namespaces != null) {
          oldNamespaces = getNamespaces(plugin);
          setNamespaces(plugin, namespaces);
        }
        ExtendedCommandsHelper.invoke(plugin, "execute", new Class[] { String.class },
            new Object[] { "EXECUTE_ALL" });
        if (oldNamespaces != null) {
          setNamespaces(plugin, oldNamespaces);
        }
      } catch (ClassCastException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (SecurityException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (NoSuchMethodException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalArgumentException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalAccessException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (InvocationTargetException e) {
        log.warn("error calling TeachPlugin", e);
      }
    } else {
      log.warn("TeachPlugin not found");
    }
  }
 
  protected void teachAuto(Collection<Faction> factions) {
    teachAuto(factions, Collections.emptySet());
  }
 
  Map<Locale, Collection<String>> expensiveSkills = new HashMap<Locale, Collection<String>>();
 
  protected void initSkills(Locale loc) {
    // if (!expensiveSkills.containsKey(loc)) {
    // Set<String> exp;
    // expensiveSkills.put(loc, exp = new HashSet<String>());
    //
    // Arrays.asList(EresseaConstants.S_TAKTIK, EresseaConstants.S_KRAEUTERKUNDE, EresseaConstants.S_MAGIE,
    // EresseaConstants.S_ALCHEMIE, EresseaConstants.S_SPIONAGE).forEach((skill) -> {
    // try {
    // exp.add(
    // world.getGameSpecificStuff().getOrderChanger().getOrderO(skill, loc).getText());
    // } catch (RulesException e) {
    // e.printStackTrace();
    // // log.error(e.getClass().getSimpleName() + " script error for " + region);
    // throw new RuntimeException(e);
    // }
    // });
    // }
  }
 
  protected void teachAuto(Collection<Faction> factions, Collection<Region> regions) {
    Set<Region> filter = new HashSet<Region>(regions);
    Set<Unit> allStudents = new HashSet<Unit>();
    // for every teacher ...
    for (Faction faction : factions) {
      helper.getUI().setMaximum(faction.units().size());
      for (Unit teacher : faction.units()) {
        if (!filter.isEmpty() && !filter.contains(teacher.getRegion())) {
          continue;
        }
        if (!canTeach(teacher)) {
          continue;
        }
        int tIndex = 0;
        int studentCount = 0;
        helper.getUI().setProgress(teacher.toString(), tIndex);
        Orders teachOrders = teacher.getOrders2();
        for (Order teachOrder : teachOrders) {
          if (teachOrders.isToken(teachOrder, 0, EresseaConstants.OC_TEACH)) {
            // ... if all students learn the same skill
            int tokenIndex = 0;
            Map<Unit, Integer> students = new HashMap<Unit, Integer>();
            String skill = "";
            boolean temp = false;
            allStudents.add(teacher);
            for (OrderToken t : teachOrder.getTokens()) {
              if (tokenIndex++ > 0 && t.ttype == OrderToken.TT_ID) {
                String id = temp ? ("TEMP " + t.getText()) : t.getText();
                Unit student = helper.getUnit(id);
                allStudents.add(student);
                if (student != null && skill != null && student.getFaction() == teacher.getFaction() && canTeach(
                    student)) {
                  helper.getUI().setProgress(teacher.toString() + " - " + student.toString(), tIndex);
                  studentCount += student.getModifiedPersons();
                  Orders studentOrders = student.getOrders2();
                  int learnIndex = 0;
                  for (Order studentOrder : studentOrders) {
                    if (studentOrders.isToken(studentOrder, 0, EresseaConstants.OC_LEARN)) {
                      students.put(student, learnIndex);
                      String subject2 = studentOrder.getToken(1).getText();
 
                      if (skill.isEmpty() || skill.equals(subject2)) {
                        skill = subject2;
                      } else {
                        skill = null;
                      }
                    }
                    learnIndex++;
                  }
 
                } else {
                  skill = null;
                }
              }
            }
            // ... replace matching teach and learn orders with LEARN AUTO
            if (isAutoSkill(teacher, skill) && studentCount != teacher.getModifiedPersons() * TEACH_MULTI) {
              String autoT = LEARNOrder + " " + AUTOOrder + " " + skill + COMMENTOrder + " T " + teacher.getID();
              String autoL = LEARNOrder + " " + AUTOOrder + " " + skill + COMMENTOrder + " L " + studentCount;
              for (Unit s : students.keySet()) {
                autoL += " " + s.getID();
              }
              Order autoOrder = teacher.createOrder(autoL);
              teacher.replaceOrder(tIndex, autoOrder);
              autoOrder = teacher.createOrder(autoT);
              for (Unit s : students.keySet()) {
                s.replaceOrder(students.get(s), autoOrder);
              }
            }
          }
          ++tIndex;
        }
      }
    }
 
    // replace learn orders of students without teacher by LEARN AUTO
    int tIndex = 0;
    for (Faction faction : factions) {
      for (Unit unit : faction.units()) {
        if (!filter.isEmpty() && !filter.contains(unit.getRegion())) {
          continue;
        }
        helper.getUI().setProgress(unit.toString(), tIndex++);
        Orders orders = unit.getOrders2();
        int oIndex = 0;
        for (Order o : orders) {
          if (orders.isToken(o, 0, EresseaConstants.OC_LEARN) &&
              !orders.isToken(o, 1, EresseaConstants.OC_AUTO) &&
              !allStudents.contains(unit) &&
              isAutoSkill(unit, o.getToken(1).getText()) &&
              canTeach(unit)) {
            String newOrder = o.getText().replace(o.getToken(0).getText(), o.getToken(0).getText() + " " + AUTOOrder)
                + COMMENTOrder + " L-";
            unit.replaceOrder(oIndex, unit.createOrder(newOrder));
          }
          ++oIndex;
        }
      }
    }
  }
 
  private boolean isAutoSkill(Unit unit, String skillText) {
    SkillType skill = world.getRules().getSkillType(skillText);
    return skill != null && skill.getCost(1) == 0;
    // initSkills(unit.getLocale());
    // return expensiveSkills.get(unit.getLocale()).contains(skill);
  }
 
  private boolean canTeach(Unit unit) {
    // try {
    // world.getGameSpecificRules().getClass().getMethod("canTeach", Unit.class);
    // return world.getGameSpecificRules().canTeach(unit);
    // } catch (NoSuchMethodException e) {
    switch (unit.getRace().toString()) {
    case "Dämonen":
    case "Elfen":
    case "Goblins":
    case "Halblinge":
    case "Insekten":
    case "Katzen":
    case "Meermenschen":
    case "Menschen":
    case "Orks":
    case "Trolle":
    case "Zwerge":
      return true;
    default:
      return false;
    }
    // }
  }
 
  /**
  * Test classes for Beanshell
  */
  static class A {
 
    /**
    * @return "A.b"
    */
    public static String b() {
      return "A.b";
    }
  }
 
  class B {
 
    public String b() {
      return "B.b";
    }
 
  }
 
  void testA(Unit u) {
    helper.addOrder(u, A.b());
    helper.addOrder(u, (new B()).b());
  }
 
  // ---start comment for BeanShell
  /**
  * Makes this code usable as input to the CommandParser. Tries to read the source of this file and
  * strip it from generics and other advanced stuff that BeanShell doesn't understand.
  *
  * @param args this is ignored
  */
  public static void main(String[] args) {
    File file =
        new File("./src-test/magellan/plugin/extendedcommands/scripts/E3CommandParser.java");
    LineNumberReader reader = null;
    try {
      reader = new LineNumberReader(new FileReader(file));
 
      System.out.println("// created by E3CommandParser.main() at "
          + Calendar.getInstance().getTime());
      System.out.println();
 
      boolean commentMode = false;
      boolean unCommentMode = false;
      for (String line = reader.readLine(); line != null; line = reader.readLine()) {
        if (line.matches(" *// ---start comment for BeanShell")) {
          commentMode = true;
          System.out.println("// ---commented for BeanShell");
          continue;
        }
        if (line.matches(" *// ---stop comment for BeanShell")) {
          commentMode = false;
          System.out.println(line);
          continue;
        }
        if (line.matches(" *// ---start uncomment for BeanShell")) {
          unCommentMode = true;
          System.out.println("// ---uncommented for BeanShell");
          continue;
        }
        if (line.matches(" *// ---stop uncomment for BeanShell")) {
          unCommentMode = false;
          System.out.println(line);
          continue;
        }
        if (commentMode) {
          System.out.print("//");
          System.out.println(line);
          continue;
        }
        if (unCommentMode) {
          System.out.println(line.substring(line.indexOf("//") + 2));
          continue;
        }
        if (line.matches(" *package [a-z.]*;")) {
          continue;
        }
 
        // remove generics
        String lastLine = null, newLine = line;
        boolean changed = false;
        while (newLine.matches(".*<[A-Za-z0-9_, ]*>.*") && !isJavaComment(newLine)
            && !newLine.equals(lastLine)) {
          lastLine = newLine;
          newLine = newLine.replaceFirst("<[A-Za-z0-9_, ]*>", "");
          changed = true;
        }
        // remove @ directives
        if (!isJavaComment(newLine)) {
          newLine = newLine.replaceFirst(" +@.*", "// $0");
        }
        if (changed) {
          System.out.print("// ");
          System.out.println(line);
        }
        System.out.println(newLine);
      }
 
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (reader != null) {
        try {
          reader.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
 
  private static boolean isJavaComment(String line) {
    return line.matches("[ ]*//.*") || line.matches("[ ]*[*].*");
  }
  // ---stop comment for BeanShell
 
  static class UnitItemPriority {
    public UnitItemPriority(Unit unit, String item, int amount, int priority, long serial) {
      this.unit = unit;
      this.item = item;
      this.amount = amount;
      this.priority = priority;
      this.serial = serial;
    }
 
    Unit unit;
    /** The order name of the item. */
    String item;
    int amount;
    int priority;
    long serial;
 
    public static int reduceAmount(int amount, int change) {
      if (amount == Integer.MAX_VALUE || amount == Integer.MIN_VALUE)
        return amount;
      long newValue = (long) amount - change;
      if (newValue > Integer.MAX_VALUE)
        return Integer.MAX_VALUE;
      else if (newValue < Integer.MIN_VALUE)
        return Integer.MIN_VALUE;
      else
        return (int) newValue;
 
    }
 
    public int compareTo(UnitItemPriority uip) {
      int diff = uip.priority - priority;
      if (diff != 0)
        return diff;
      return serial > uip.serial ? 1 : serial < uip.serial ? -1 : 0;
    }
 
    @Override
    public String toString() {
      return unit + " " + amount + " " + item + " (" + priority + ")";
    }
  }
 
  static class Supply {
 
    UnitItemPriority uip;
 
    public Supply(Unit unit, String item, int amount, long serial) {
      super();
      if (unit == null)
        throw new NullPointerException();
      uip = new UnitItemPriority(unit, item, amount, E3CommandParser.DEFAULT_SUPPLY_PRIORITY, serial);
    }
 
    public boolean hasPriority() {
      return getPriority() != Long.MAX_VALUE;
    }
 
    public int getPriority() {
      return uip.priority;
    }
 
    public void setPriority(int priority) {
      uip.priority = priority;
    }
 
    public Unit getUnit() {
      return uip.unit;
    }
 
    public int getAmount() {
      return uip.amount;
    }
 
    public void setAmount(int i) {
      uip.amount = i;
    }
 
    public String getItem() {
      return uip.item;
    }
 
    public void reduceAmount(int change) {
      uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
    }
 
    @Override
    public String toString() {
      return uip.unit + " has " + uip.amount + " " + uip.item;
    }
 
    public int compareTo(Supply o) {
      return uip.compareTo(o.uip);
    }
  }
 
  interface OrderFilter {
 
    /**
    * @return true if the order should be changed or deleted
    */
    public boolean changeOrder(Order order);
 
    /**
    * @return the string that should replace order, <code>null</code> if the order should be be
    *        removed. If changeOrder returns <code>false</code>, the return value is undefined!
    */
    public String changedOrder();
  }
 
  static class TeachRoundOrderFilter implements OrderFilter {
    public boolean changeOrder(Order order) {
      return changeOrder(order.getText());
    }
 
    public boolean changeOrder(String order) {
      if (order.startsWith("// $$L"))
        return true;
      return false;
    }
 
    public String changedOrder() {
      return null;
    }
  }
 
  static class ShortOrderFilter implements OrderFilter {
 
    private String result;
    private GameData world;
 
    public ShortOrderFilter(GameData world) {
      this.world = world;
    }
 
    public boolean changeOrder(Order command) {
      if (world.getGameSpecificStuff().getOrderChanger().isLongOrder(command))
        return false;
      else {
        if (command.isLong() || command.isPersistent() || command.getText().startsWith("//"))
          return false;
        else {
          if (command.getText().startsWith(";")) {
            result = null;
            return true;
          } else {
            result = ";" + command.getText();
            return true;
          }
        }
      }
    }
 
    public String changedOrder() {
      return result;
    }
  }
 
  static class Need {
 
    private int minAmount;
    private Warning warning;
    UnitItemPriority uip;
    private String message;
 
    public Need(Unit unit, String item, int minAmount, int maxAmount, int priority,
        Warning warning, long serial) {
      uip = new UnitItemPriority(unit, item, maxAmount, priority, serial);
      this.minAmount = minAmount;
      this.warning = warning;
      message = createMessage();
    }
 
    public int compareTo(Need need) {
      return uip.compareTo(need.uip);
    }
 
    public int getPriority() {
      return uip.priority;
    }
 
    public Unit getUnit() {
      return uip.unit;
    }
 
    public int getAmount() {
      return uip.amount;
    }
 
    public void setAmount(int i) {
      uip.amount = i;
      message = createMessage();
    }
 
    public String getItem() {
      return uip.item;
    }
 
    public int getMinAmount() {
      return minAmount;
    }
 
    public void setMinAmount(int amount) {
      minAmount = amount;
      message = createMessage();
    }
 
    public void reduceMinAmount(int change) {
      minAmount = UnitItemPriority.reduceAmount(minAmount, change);
    }
 
    public int getMaxAmount() {
      return getAmount();
    }
 
    public void setMaxAmount(int amount) {
      setAmount(amount);
      message = createMessage();
    }
 
    public void reduceMaxAmount(int change) {
      uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
    }
 
    /**
    * Returns the value of warning.
    *
    * @return Returns warning.
    */
    public Warning getWarning() {
      return warning;
    }
 
    /**
    * Sets the value of warning.
    *
    * @param warning The value for warning.
    */
    public void setWarning(Warning warning) {
      this.warning = warning;
     }
     }
  }
  private void setNamespaces(MagellanPlugIn plugin, String namespaces) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    ExtendedCommandsHelper.invoke(plugin, "setNamespaces", new Class[] { String.class },
        new Object[] { namespaces });
  }
  private String getNamespaces(MagellanPlugIn plugin) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    return (String) ExtendedCommandsHelper.invoke(plugin, "getNamespacesString", new Class[] {},
        new Object[] {});
  }


//  protected void teachRegion(Collection<Region> regions, String namespaces) {
     @Override
  protected void teachRegion(Collection regions, String namespaces) {
     public String toString() {
//     ArrayList<Region> regions2 = new ArrayList<Region>();
       return createMessage();
     ArrayList regions2 = new ArrayList();
    for (Region r : regions) {
       if (r != null) {
        regions2.add(r);
      }
     }
     }
    if (regions2.isEmpty())
      return;


     log.info("trying to call TeachPlugin method..");
     private String createMessage() {
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
       return uip.unit + " needs " + minAmount + "/" + uip.amount + " " + uip.item + " ("
    if (plugin != null) {
          + uip.priority + ")";
      try {
        String oldNameSpaces = null;
        if (namespaces != null) {
          oldNameSpaces = getNamespaces(plugin);
          setNamespaces(plugin, namespaces);
        }
        ExtendedCommandsHelper.invoke(plugin, "doTeachUnits", new Class[] { Collection.class },
            new Collection[] { regions2 });
        if (oldNameSpaces != null) {
          setNamespaces(plugin, oldNameSpaces);
        }
       } catch (ClassCastException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (SecurityException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (NoSuchMethodException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalArgumentException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalAccessException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (InvocationTargetException e) {
        log.warn("error calling TeachPlugin", e);
      }
    } else {
      log.warn("TeachPlugin not found");
     }
     }
  }


  protected void teachAll(String namespaces) {
     public String getMessage() {
     log.info("trying to call TeachPlugin method..");
       return message;
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
    if (plugin != null) {
      try {
        String oldNamespaces = null;
        if (namespaces != null) {
          oldNamespaces = getNamespaces(plugin);
          setNamespaces(plugin, namespaces);
        }
        ExtendedCommandsHelper.invoke(plugin, "execute", new Class[] { String.class },
            new Object[] { "EXECUTE_ALL" });
        if (oldNamespaces != null) {
          setNamespaces(plugin, oldNamespaces);
        }
      } catch (ClassCastException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (SecurityException e) {
        log.warn("error calling TeachPlugin", e);
       } catch (NoSuchMethodException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalArgumentException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalAccessException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (InvocationTargetException e) {
        log.warn("error calling TeachPlugin", e);
      }
    } else {
      log.warn("TeachPlugin not found");
     }
     }
   }
   }


   /**
   static class Transfer {
  * Test classes for Beanshell
    private Unit target;
  */
    private boolean all;
  static class A {
    private Need need;
    private boolean min;
    private UnitItemPriority uip;


     /**
     public Transfer(Unit unit, Unit target, String item, int amount, boolean min, boolean all,
    * @return "A.b"
        Need need,
    */
        long serial) {
    public static String b() {
      if (item == null)
       return "A.b";
        throw new NullPointerException();
      uip = new UnitItemPriority(unit, item, amount, 0, serial);
      this.target = target;
      this.all = all;
      this.need = need;
       this.min = min;
     }
     }
  }


  class B {
     public void reduceAmount(int change) {
 
       uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
     public String b() {
       return "B.b";
     }
     }


  }
     public Unit getUnit() {
 
       return uip.unit;
  void testA(Unit u) {
    helper.addOrder(u, A.b());
    helper.addOrder(u, (new B()).b());
  }
 
// ---commented for BeanShell
//  /**
//  * Makes this code usable as input to the CommandParser. Tries to read the source of this file and
//  * strip it from generics and other advanced stuff that BeanShell doesn't understand.
//  *
//  * @param args this is ignored
//  */
//  public static void main(String[] args) {
//    File file =
//        new File("./src-test/magellan/plugin/extendedcommands/scripts/E3CommandParser.java");
//    try {
//      LineNumberReader reader = new LineNumberReader(new FileReader(file));
//
//      System.out.println("// created by E3CommandParser.main() at "
//          + Calendar.getInstance().getTime());
//      System.out.println();
//
//      boolean commentMode = false;
//      boolean unCommentMode = false;
//      for (String line = reader.readLine(); line != null; line = reader.readLine()) {
//        if (line.matches(" *// ---start comment for BeanShell")) {
//          commentMode = true;
//          System.out.println("// ---commented for BeanShell");
//          continue;
//        }
//        if (line.matches(" *// ---stop comment for BeanShell")) {
//          commentMode = false;
//          System.out.println(line);
//          continue;
//        }
//        if (line.matches(" *// ---start uncomment for BeanShell")) {
//          unCommentMode = true;
//          System.out.println("// ---uncommented for BeanShell");
//          continue;
//        }
//        if (line.matches(" *// ---stop uncomment for BeanShell")) {
//          unCommentMode = false;
//          System.out.println(line);
//          continue;
//        }
//        if (commentMode) {
//          System.out.print("//");
//          System.out.println(line);
//          continue;
//        }
//        if (unCommentMode) {
//          System.out.println(line.substring(line.indexOf("//") + 2));
//          continue;
//        }
//        if (line.matches(" *package [a-z.]*;")) {
//          continue;
//        }
//
//        // remove generics
//        String lastLine = null, newLine = line;
//        boolean changed = false;
//        while (newLine.matches(".*<[A-Za-z0-9_, ]*>.*") && !isJavaComment(newLine)
//            && !newLine.equals(lastLine)) {
//          lastLine = newLine;
//          newLine = newLine.replaceFirst("<[A-Za-z0-9_, ]*>", "");
//          changed = true;
//        }
//        // remove @ directives
//        if (!isJavaComment(newLine)) {
//          newLine = newLine.replaceFirst(" +@.*", "// $0");
//        }
//        if (changed) {
//          System.out.print("// ");
//          System.out.println(line);
//        }
//        System.out.println(newLine);
//      }
//
//    } catch (FileNotFoundException e) {
//      e.printStackTrace();
//    } catch (IOException e) {
//      e.printStackTrace();
//    }
//  }
//
//  private static boolean isJavaComment(String line) {
//    return line.matches("[ ]*//.*") || line.matches("[ ]*[*].*");
//  }
  // ---stop comment for BeanShell
 
}
 
// class Supply implements Comparable<Supply> {
class Supply implements Comparable {
 
  Unit unit;
  /** The order name of the item. */
  String item;
  int amount;
  int priority;
 
  public Supply(Unit unit, String item, int amount) {
     super();
    if (unit == null)
      throw new NullPointerException();
    this.unit = unit;
    this.item = item;
    this.amount = amount;
    priority = E3CommandParser.DEFAULT_SUPPLY_PRIORITY;
  }
 
  public Unit getUnit() {
    return unit;
  }
 
  public int getAmount() {
    return amount;
  }
 
  public void setAmount(int i) {
    amount = i;
  }
 
  public String getItem() {
    return item;
  }
 
  public void reduceAmount(int change) {
    if (amount == Integer.MAX_VALUE || amount == Integer.MIN_VALUE)
       return;
    long newValue = (long) amount - (long) change;
    if (newValue > Integer.MAX_VALUE) {
      amount = Integer.MAX_VALUE;
    } else if (newValue < Integer.MIN_VALUE) {
      amount = Integer.MIN_VALUE;
    } else {
      amount = (int) newValue;
     }
     }
  }
//  @Override
  public String toString() {
    return unit + " has " + amount + " " + item;
  }
  public int compareTo(Supply o) {
    return o.priority - priority;
  }
}
interface OrderFilter {
  /**
  * @return true if the order should be changed or deleted
  */
  public boolean changeOrder(String order);
  /**
  * @return true if the order should be changed or deleted
  */
  public boolean changeOrder(Order order);
  /**
  * @return the string that should replace order, <code>null</code> if the order should be be
  *        removed. If changeOrder returns <code>false</code>, the return value is undefined!
  */
  public String changedOrder();
}
class TeachRoundOrderFilter implements OrderFilter {
  public boolean changeOrder(Order order) {
    return changeOrder(order.getText());
  }


  public boolean changeOrder(String order) {
     public int getAmount() {
    if (order.startsWith("// $$L"))
       return uip.amount;
      return true;
    return false;
  }
 
  public String changedOrder() {
     return null;
  }
}
 
class ShortOrderFilter implements OrderFilter {
 
  private String result;
  private GameData world;
 
  public ShortOrderFilter(GameData world) {
    this.world = world;
  }
 
  public boolean changeOrder(String command) {
    if (world.getGameSpecificStuff().getOrderChanger().isLongOrder(command))
       return false;
    else {
      if (command.startsWith("@") || command.startsWith("//"))
        return false;
      else {
        if (command.startsWith(";")) {
          result = null;
          return true;
        } else {
          result = ";" + command;
          return true;
        }
      }
     }
     }
  }


  public boolean changeOrder(Order command) {
    public void setAmount(int i) {
    if (world.getGameSpecificStuff().getOrderChanger().isLongOrder(command))
       uip.amount = i;
      return false;
    else {
      if (command.isLong() || command.isPersistent() || command.getText().startsWith("//"))
        return false;
       else {
        if (command.getText().startsWith(";")) {
          result = null;
          return true;
        } else {
          result = ";" + command.getText();
          return true;
        }
      }
     }
     }
  }
  public String changedOrder() {
    return result;
  }
}
class Need extends Supply {
  private int minAmount;
  private Warning warning;
  public Need(Unit unit, String item, int minAmount, int maxAmount, Warning warning) {
    super(unit, item, maxAmount);
    this.minAmount = minAmount;
    this.warning = warning;
  }
  public int getMinAmount() {
    return minAmount;
  }


  public void setMinAmount(int amount) {
     public String getItem() {
     minAmount = amount;
       return uip.item;
  }
 
  public void reduceMinAmount(int change) {
    if (minAmount == Integer.MAX_VALUE || minAmount == Integer.MIN_VALUE)
       return;
    long newValue = (long) minAmount - (long) change;
    if (newValue > Integer.MAX_VALUE) {
      minAmount = Integer.MAX_VALUE;
    } else if (newValue < Integer.MIN_VALUE) {
      minAmount = Integer.MIN_VALUE;
    } else {
      minAmount = (int) newValue;
     }
     }
  }
  public int getMaxAmount() {
    return getAmount();
  }
  public void setMaxAmount(int amount) {
    setAmount(amount);
  }
  public void reduceMaxAmount(int change) {
    reduceAmount(change);
  }
  /**
  * Returns the value of warning.
  *
  * @return Returns warning.
  */
  public Warning getWarning() {
    return warning;
  }
  /**
  * Sets the value of warning.
  *
  * @param warning The value for warning.
  */
  public void setWarning(Warning warning) {
    this.warning = warning;
  }
//  @Override
  public String toString() {
    return unit + " needs " + minAmount + "/" + amount + " " + item;
  }
}
class Flag {
  boolean positive;
  String name;
  int value;
  public Flag(boolean positive, String name, int value) {
    this.positive = positive;
    this.name = name;
    this.value = value;
  }
//  @Override
  public String toString() {
    return name + "(" + positive + "," + value + ")";
  }
}
class Warning {
//  private List<Flag> flags = new ArrayList<Flag>();
  private List flags = new ArrayList();


  public static final Flag[] ALL_FLAGS = {
    public boolean isMin() {
      new Flag(true, E3CommandParser.W_AMOUNT, E3CommandParser.C_AMOUNT),
       return min;
      new Flag(true, E3CommandParser.W_ARMOR, E3CommandParser.C_ARMOR),
      new Flag(true, E3CommandParser.W_FOREIGN, E3CommandParser.C_FOREIGN),
      new Flag(true, E3CommandParser.W_SHIELD, E3CommandParser.C_SHIELD),
      new Flag(true, E3CommandParser.W_SKILL, E3CommandParser.C_SKILL),
      new Flag(true, E3CommandParser.W_UNIT, E3CommandParser.C_UNIT),
      new Flag(true, E3CommandParser.W_WEAPON, E3CommandParser.C_WEAPON),
      new Flag(false, E3CommandParser.W_FOREIGN, E3CommandParser.C_FOREIGN),
      new Flag(false, E3CommandParser.W_HIDDEN, E3CommandParser.C_HIDDEN) };
 
  public static boolean initialized = false;
//  public static Map<String, Flag> NAMES;
  public static Map NAMES;
 
  public Warning(boolean all) {
    this(new String[] { E3CommandParser.W_NEVER });
    if (all) {
       setAll();
     }
     }
  }


  public Warning(String[] tokens) {
    public Unit getTarget() {
    /** Bean Shell does not know static initializers, so this is a bit awkward */
       return target;
    if (!initialized) {
       initStatic();
     }
     }
    parse(tokens);
  }


  protected void initStatic() {
     public boolean isAll() {
    initialized = true;
       return all;
//    NAMES = new HashMap<String, Flag>();
    NAMES = new HashMap();
     for (Flag f : ALL_FLAGS) {
       NAMES.put(f.name, f);
     }
     }
  }


  protected void setAll() {
     public String getMessage() {
     for (Flag f : ALL_FLAGS)
       return need.getMessage();
      if (f.positive) {
        add(f);
      }
  }
 
  protected String[] parse(String[] tokens) {
//    flags = new ArrayList<Flag>();
    flags = new ArrayList();
    if (tokens == null)
       return null;
 
    int i = tokens.length - 1;
    boolean onlyNegative = true;
    for (; i >= 0; --i)
      if (NAMES.containsKey(tokens[i])) {
        Flag f = NAMES.get(tokens[i]);
        add(f);
        if (f.positive) {
          onlyNegative = false;
        }
      } else if (E3CommandParser.W_ALWAYS.equals(tokens[i])) {
//        flags = new ArrayList<Flag>();
        flags = new ArrayList();
        setAll();
        onlyNegative = false;
      } else if (E3CommandParser.W_NEVER.equals(tokens[i])) {
//        flags = new ArrayList<Flag>();
        flags = new ArrayList();
        onlyNegative = false;
      } else {
        break;
      }
    if (onlyNegative) {
      for (Flag f : ALL_FLAGS) {
        if (f.positive) {
          add(f);
        }
      }
     }
     }


     if (i == tokens.length - 1)
     @Override
      return tokens;
     public String toString() {
     return Arrays.copyOf(tokens, i + 1);
       return uip.unit + " gives " + target + " " + (all ? "ALL" : uip.amount) + " " + uip.item;
  }
 
  public void add(String name) {
    if (NAMES.containsKey(name)) {
       add(NAMES.get(name));
     }
     }
  }


  public void add(Flag f) {
    public Need getNeed() {
    if (!flags.contains(f)) {
       return need;
       flags.add(f);
     }
     }
  }


  public void remove(int f) {
    flags.remove(f);
   }
   }
  public boolean contains(String w) {
    for (Flag f : flags)
      if (f.name.equals(w))
        return true;
    return false;
  }
  public boolean contains(int flag) {
    for (Flag f : flags)
      if (f.value == flag)
        return true;
    return false;
  }
//  @Override
  public String toString() {
    return flags.toString();
  }
}
}


// ---uncommented for BeanShell
void soldier(Unit unit) {
E3CommandParser.soldier(unit);
}


void cleanOcean() {
for (Region r : world.regions().values()) {
if (r.getName() != null && r.getType().toString().contains("Ozean")
&& r.getName().contains("Leere")) {
r.setName(null);
r.setDescription(null);
}
}
}
// ---stop uncomment for BeanShell
</nowiki>
</nowiki>

Aktuelle Version vom 29. September 2024, 18:13 Uhr

E3CommandParser ist meine Skriptsammlung für die Automatisierung von großen Teilen meiner Partei. Anders als der Name vermuten lässt, funktionieren sie genauso gut mit E2 wie mit E3 und (zumindest zur Zeit der Erstellung dieser Anleitung) auch mit E4. Sie benutzt die ExtendedCommands, die mit Magellan mitgeliefert werden.

Kurzanleitung

Um sie zu nutzen sollte folgende Schritte nachvollziehen:

  • seinen Report in Magellan öffnen
  • Die ExtendedCommands-Bibliothek öffnen (am schnellsten mit Strg + 4)
  • Den Code des E3CommandParser hineinkopieren und anschließend speichern: #E3CommandParser
  • Einheitenbefehle mit Kommandos versehen. Beispiele siehe #Einheitenbefehle
  • In der Übersicht von Magellan seine Partei auswählen und die Befehle hierfür öffnen (mit Strg + Shift + 4 oder über das Kontextmenü)
  • Den folgenden Code hineinkopieren:
 
E3CommandParser.DEFAULT_SUPPLY_PRIORITY = 0;

// hier eigene Parteinummer eingeben
Faction faction = (Faction)helper.getFaction("pnum"));
(new E3CommandParser(world, helper)).execute(faction);

// falls man das TeachPlugin benutzt, kann man es hier direkt mit aufrufen:
// (new E3CommandParser(world, helper)).teachAuto(factions);

// falls während der Ausführung der Kommandos ein Fehler auftreten sollte, kann man nach dessen Behebung direkt so bei der Region weitermachen, wo der Fehler auftrat:
//(new E3CommandParser(world, helper)).
//    executeFrom(faction, null, 
//      helper.getRegion("Regionsname"));


  • Dieses Skript speichern und ausführen

Einheitenbefehle

Alle Befehle fangen mit "// $cript" an. Aus der Sicht des Eresseaservers sind es anormale Kommentare und somit erscheinen sie, nachdem man die Befehle verschickt hat, in der nächsten Woche wieder im Report. Bei der Ausführung der Skripte ("execute(...)") werden die Skripte aller Einheiten gelesen und ausgeführt. Die meisten Befehle werden einfach unverändert gelassen und haben zusätzlich irgendeine Auswirkung (meist werden weitere Befehle zur Einheit hinzugefügt). Manchmal wird auch der Befehl selber verändert (wie zum Beispiel beim "auto"-Befehl.

auto

Der einfachste Befehl ist

 // $cript auto [NICHT]|[length [period]]

Damit wird eine Einheit automatisch bestätigt, falls andere Befehle keinen Fehler oder eine Warnung liefern.

 // $cript auto 10

bestätigt die Einheit und verringert die Zahl um 1. Nach der ersten Ausführung der Skripte steht dann also

 // $cript auto 9

da und die Einheit ist bestätigt. Wenn man also das Skript einmal pro Woche ausführt, wird 10 Wochen lang die Einheit bestätigt. Dann steht da

 // $cript auto 0

Wenn die Zahl 0 oder kleiner ist, wird die Einheit nicht bestätigt. "// $cript auto 10" bewirkt also, dass die Einheit 10 Wochen lang bestätigt wird, danach nicht mehr. Sehr praktisch für Einheiten, die zum Beispiel zehn Wochen einfach nur lernen und die Klappe halten sollen.

// $cript auto 10 10

bewirkt das selbe, nur dass nach den zehn Wochen die erste Zahl wieder auf die zweite Zahl gesetz wird. Also nacheinander auf

// $cript auto 9 10 ; bestaetigt
...
// $cript auto 1 10 ; bestaetigt
// $cript auto 0 10 ; nicht bestaetigt
// $cript auto 9 10 ; bestaetigt

Die Einheit wird also nur alle zehn Wochen nicht bestätigt. Praktisch für Einheiten, bei denen man nur ab und zu mal nach dem Rechten sehen will.

// $cript auto NICHT

bewirkt, dass die Einheit auf keinen Fall bestätigt wird.


Versorge

 // $cript Versorge 100

bewirkt, dass eine Einheit zum "Versorger" wird. Sie wird dann Gegenstände an andere Einheiten abgeben, wenn diese sie brauchen. Die Zahl 100 ist die Priorität. Versorger mit höherer Priorität werden ihre Gegenstände vor Versorgern mit niedrigerer Priorität abgeben. Man kann das auch auf bestimmte Gegenstände beschränken:

 // $crript Versorge Balsam 95

gibt nur Balsam weg. Manche Befehle bewirken implizit, dass Einheiten zu Versorgern werden, zum Beispiel "BerufDepotverwalter"


Benoetige

 // $cript Benoetige JE 1 Schwert

bewirkt, dass die Einheit 1 Schwert pro Person reserviert oder versucht zu bekommen. Versorger werden ihr diese falls möglich übergeben. Man kann auch eine minimale und eine maximale Menge angeben:

 // $cript Benoetige 10 100 Silber 120

bewirkt, dass die Einheit versucht, 10 Silber (die Minimalmenge) auf jeden Fall zu bekommen. Erst wenn der Bedarf aller anderen Einheiten in der Region erfüllt ist, versucht die Einheit, bis zu 100 Silber (die Maximalmenge) zu bekommen. Die letzte Zahl ist die Priorität. Die Einheit wird die 100 Silber also vor Einheiten mit niedrigerer Priorität bekommen, aber erst nachdem alle Minimalmengen in der Region erfüllt sind.

BenoetigeFremd

 // $cript BenoetigeFremd abc 15 Schwert Menge

bewirkt, dass die Einheit abc 15 Schwert benötigt. Der Parameter Menge bewirkt, dass es keine Warnung gibt, falls die Einheit abc nicht da ist. Es gibt aber eine Warnung, falls keine 15 Schwerter vorhanden sind. Der Befehl ist praktisch, um Transportern immer wenn sie da sind 15 Schwerter zu übergeben.

GibWenn

 // $cript GibWenn abc 15 Schwert Menge

ist im Grunde nur eine ältere Version von BenoetigeFremd. Ich benutze es nicht mehr, weil es unter bestimmten Umständen Probleme gab.

+X

 // $cript +3 Warnungstext

bewirkt, dass in 3 Wochen, eine Zeile mit dem Text "; TODO Warnungstext" in den Einheitenbefehlen erscheint. Der Scriptbefehl selber wird dann gelöscht. Praktisch, wenn man in X Wochen an etwas erinnert werden will.


Weitere nützliche Befehle

 // $cript Ueberwache

bewirkt, dass die Einheit auf unbekannte Einheiten anderer Parteien achtet. Sie gibt eine Warnung aus, wenn eine solche Einheit in der Region ist. Bekannte Einheiten kann man mit

 // $cript Erlaube pnr enr1 enr2 enr3

wieder ausblenden. Für die Partei mit der Nummer pnr sind in diesem Beispiel die Einheiten enr1, enr2 und enr3 erlaubt und erzeugen keine Warnung.

 // $cript Handel x3 ALLES

bewirkt, dass die Einheit (in E2) das 3-fache der Basismenge des angebotenen Luxusguts kauft. Außerdem versucht sie die Maximalmenge aller verfügbaren Luxusgüter zu verkaufen.

 // $cript Sammler 10

bewirkt, dass die Einheit MACHN Kräuter ausführt und alle 10 Wochen ein "FORSCHEN Kräuter". Stellt sie dabei fest, dass weniger als "viele" Kröuter vorhanden sind, wird das Sammeln eingestellt.

// $cript Ernaehre

veranlasst die Einheit, so gut sie kann Silber zu verdienen.

 // $cript Soldat

bewirkt, dass die Einheit Kampftalente und Ausdauer lernt und sich so gut wie möglich mit Waffen und Rüstungen versorgt.

Meta-Befehle

 // $cript 3 LERNEN Wahrnehmung

bewirkt erstmal nichts, außer, dass der Befehl zu $cript 2 LERNEN Wahrnehmung verändert wird.

 // $cript 1 LERNEN Wahrnehmung

bewirkt, dass "LERNEN Wahrnehmung" zu den Einheitenbefehlen hinzugefügt wird. Der script-Befehl selbst wird gelöscht.

 // $cript 3 LERNEN Wahrnehmung

bewirkt also, dass die Einheit in 3 Wochen Wahrnehmung lernen wird.

 // $cript 4 $cript Kommando

bewirkt, dass in 4 Wochen der Skriptbefehl Kommando einmal ausgeführt wird.

 // $cript 3 $cript Loeschen

bewirkt, dass die langen Befehle der Einheit in 3 Wochen gelöscht werden.

 // $cript 2 // $cript Kommando

bewirkt, dass in 2 Wochen der Skriptbefehl Kommando dauerhaft zu den Einheitenbefehlen hinzugefügt und ausgeführt wird.

 // $cript 5 5 15 NACH no

bewirkt, dass die Einheit alle 5 Wochen den Befehl "NACH no" bekommt. Nach insgesamt 15 Wochen verschwindet der Befehl.

E3CommandParser

Und hier das komplette Skript in einer alten aber funktionierenden Version (neueste Version bei Github). Es muss zunächst in die "Library" der ExtendedCommands kopiert werden wie oben beschrieben.

 
// Author : stm
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Consumer;
import java.util.regex.Pattern;

import magellan.client.extern.MagellanPlugIn;
import magellan.library.Building;
import magellan.library.Faction;
import magellan.library.GameData;
import magellan.library.Island;
import magellan.library.Item;
import magellan.library.LuxuryPrice;
import magellan.library.Order;
import magellan.library.Orders;
import magellan.library.Region;
import magellan.library.Rules;
import magellan.library.Ship;
import magellan.library.Sign;
import magellan.library.Skill;
import magellan.library.StringID;
import magellan.library.Unit;
import magellan.library.UnitID;
import magellan.library.completion.OrderParser;
import magellan.library.gamebinding.EresseaConstants;
import magellan.library.gamebinding.EresseaRelationFactory;
import magellan.library.gamebinding.MovementEvaluator;
import magellan.library.gamebinding.RulesException;
import magellan.library.rules.Date;
import magellan.library.rules.ItemCategory;
import magellan.library.rules.ItemType;
import magellan.library.rules.Race;
import magellan.library.rules.SkillType;
import magellan.library.utils.CollectionFactory;
import magellan.library.utils.MagellanFactory;
import magellan.library.utils.OrderToken;
import magellan.library.utils.OrderedHashtable;
import magellan.library.utils.Resources;
import magellan.library.utils.Units;
import magellan.library.utils.Utils;
import magellan.library.utils.logging.Logger;
import magellan.plugin.extendedcommands.ExtendedCommandsHelper;

// ---start uncomment for BeanShell
// import magellan.library.*;
// import java.util.*;
// ---stop uncomment for BeanShell

/**
 * Call from the script of your faction with<br />
 * "<code>(new E3CommandParser(world, helper)).execute(container);</code>" or from the script of a
 * region with<br />
 * "
 * <code>(new E3CommandParser(world, helper)).execute(helper.getFaction("drac"), (Region) container);</code>
 * " (for just this region).
 *
 * @author stm
 */
class E3CommandParser {

  static class Flag {
    boolean positive;
    String name;
    int value;

    public Flag(boolean positive, String name, int value) {
      this.positive = positive;
      this.name = name;
      this.value = value;
    }

    @Override
    public String toString() {
      return name + "(" + positive + "," + value + ")";
    }

  }

  static class Warning {
    // warning constants
    /** The NEVER warning type token */
    public static String W_NEVER = "nie";
    /** The SKILL warning type token */
    public static String W_SKILL = "Talent";
    /** The WEAPON warning type token */
    public static String W_WEAPON = "Waffe";
    /** The SHIELD warning type token */
    public static String W_SHIELD = "Schilde";
    /** The ARMOR warning type token */
    public static String W_ARMOR = "Rüstung";
    /** The UNIT warning type token */
    public static final String W_UNIT = "Einheit";
    /** The AMOUNT warning type token */
    public static final String W_AMOUNT = "Menge";
    /** The HIDDEN warning type token */
    public static String W_HIDDEN = "versteckt";
    /** The ALWAYS warning type token */
    public static final String W_ALWAYS = "immer";
    /** The ALWAYS warning type token */
    public static final String W_FOREIGN = "fremd";

    /** warning constants */
    protected static final int C_AMOUNT = 1, C_UNIT = 1 << 1, C_HIDDEN = 1 << 2, C_FOREIGN = 1 << 3,
        C_WEAPON = 1 << 5, C_ARMOR = 1 << 6, C_SHIELD = 1 << 7, C_SKILL = 1 << 8;

    private List<Flag> flags = new ArrayList<Flag>();

    public static final Flag[] ALL_FLAGS = new Flag[8];

    private static void initFlags() {
      ALL_FLAGS[0] = new Flag(true, W_AMOUNT, C_AMOUNT);
      ALL_FLAGS[1] = new Flag(true, W_ARMOR, C_ARMOR);
      ALL_FLAGS[2] = new Flag(false, W_FOREIGN, C_FOREIGN);
      ALL_FLAGS[3] = new Flag(false, W_HIDDEN, C_HIDDEN);
      ALL_FLAGS[4] = new Flag(true, W_SHIELD, C_SHIELD);
      ALL_FLAGS[5] = new Flag(true, W_SKILL, C_SKILL);
      ALL_FLAGS[6] = new Flag(true, W_UNIT, C_UNIT);
      ALL_FLAGS[7] = new Flag(true, W_WEAPON, C_WEAPON);
    }

    public static boolean initialized = false;
    public static Map<String, Flag> NAMES;

    public Warning(boolean all) {
      this(new String[] { W_NEVER });
      if (all) {
        setAll();
      }
    }

    public Warning(String[] tokens) {
      /** Bean Shell does not know static initializers, so this is a bit awkward */
      if (!initialized) {
        initStatic();
      }
      parse(tokens);
    }

    protected void initStatic() {
      initialized = true;
      initFlags();
      NAMES = new HashMap<String, Flag>();
      for (Flag f : ALL_FLAGS) {
        NAMES.put(f.name, f);
      }
    }

    protected void setAll() {
      for (Flag f : ALL_FLAGS) {
        add(f);
      }
    }

    private void setAll(boolean positive) {
      for (Flag f : ALL_FLAGS) {
        if (f.positive == positive) {
          add(f);
        }
      }
    }

    protected String[] parse(String[] tokens) {
      flags = new ArrayList<Flag>();
      if (tokens == null)
        return null;

      if (tokens.length == 0) {
        for (Flag f : ALL_FLAGS) {
          add(f);
        }
      }

      int i = tokens.length - 1;
      boolean hasPositive = false;
      setAll(false);
      for (; i >= 0; --i)
        if (NAMES.containsKey(tokens[i])) {
          Flag f = NAMES.get(tokens[i]);
          if (f.positive) {
            hasPositive = true;
            add(f);
          } else {
            remove(f);
          }
        } else if (W_ALWAYS.equals(tokens[i])) {
          flags = new ArrayList<Flag>();
          setAll();
        } else if (W_NEVER.equals(tokens[i])) {
          flags = new ArrayList<Flag>();
          hasPositive = true;
        } else {
          break;
        }
      if (!hasPositive) {
        setAll(true);
      }

      if (i == tokens.length - 1)
        return tokens;
      return Arrays.copyOf(tokens, i + 1);
    }

    public void add(String name) {
      if (NAMES.containsKey(name)) {
        add(NAMES.get(name));
      }
    }

    public void add(Flag f) {
      if (!flags.contains(f)) {
        flags.add(f);
      }
    }

    public void remove(Flag f) {
      flags.remove(f);
    }

    public boolean contains(String w) {
      for (Flag f : flags)
        if (f.name.equals(w))
          return true;
      return false;
    }

    public boolean contains(int flag) {
      for (Flag f : flags)
        if ((f.value & flag) != 0)
          return true;
      return false;
    }

    @Override
    public String toString() {
      return flags.toString();
    }

  }

  static class SupplyMap {
    Map<String, Map<Unit, Supply>> supplyMap;

    public SupplyMap(E3CommandParser parser) {
      supplyMap = new LinkedHashMap<String, Map<Unit, Supply>>();
    }

    public Collection<String> items() {
      return supplyMap.keySet();
    }

    public int getSupply(String item) {
      Map<Unit, Supply> map = supplyMap.get(item);
      int goodAmount = 0;
      if (map != null) {
        for (Supply s : map.values()) {
          if (s.hasPriority()) {
            goodAmount += s.getAmount();
          }
        }
      }
      return goodAmount;
    }

    public Supply get(String item, Unit unit) {
      Map<Unit, Supply> map = supplyMap.get(item);
      if (map == null)
        return null;
      return map.get(unit);
    }

    public Collection<Supply> get(String item) {
      Map<Unit, Supply> result = supplyMap.get(item);
      if (result == null)
        return Collections.emptyList();
      return result.values();
    }

    public void sortByPriority() {
      for (String item : supplyMap.keySet()) {
        // sort supplies by priority
        Map<Unit, Supply> itemSupply = supplyMap.get(item);
        if (itemSupply == null) {
          itemSupply = Collections.emptyMap();
        } else {
          Supply[] sorted = null;
          sorted = itemSupply.values().toArray(new Supply[0]);

          // this causes problems in BeansShell; I don't know why
          // Arrays.sort(sorted);
          // doing insertion sort instead
          sort(sorted);

          itemSupply.clear();
          for (Supply supply : sorted) {
            itemSupply.put(supply.getUnit(), supply);
          }
        }
      }
    }

    private void sort(Supply[] sorted) {
      for (int j = 1; j < sorted.length; ++j) {
        for (int i = 0; i < j; ++i) {
          if (sorted[j].compareTo(sorted[i]) < 0) {
            // if (current.priority > sorted[i].priority) {
            Supply temp = sorted[i];
            sorted[i] = sorted[j];
            sorted[j] = temp;
          }
        }
      }
    }

    public Supply put(String item, Unit unit, int amount, long serial) {
      Map<Unit, Supply> itemSupplyMap = supplyMap.get(item);
      if (itemSupplyMap == null) {
        itemSupplyMap = new LinkedHashMap<Unit, Supply>();
        supplyMap.put(item, itemSupplyMap);
      }
      Supply result = new Supply(unit, item, amount, serial);
      itemSupplyMap.put(unit, result);
      return result;
    }

    public void clear() {
      supplyMap.clear();
    }

    @Override
    public String toString() {
      return supplyMap.toString();
    }
  }

  interface ReserveVisitor {
    public void execute(Unit u, String item, int amount);
  }

  static class Reserves {

    Map<String, Map<Unit, Integer>> reserves = new HashMap<String, Map<Unit, Integer>>();

    public void add(String item, Unit unit, int amount) {
      E3CommandParser.increaseMultiInv(reserves, item, unit, amount);
    }

    public void execute(ReserveVisitor reserveVisitor) {
      for (String item : reserves.keySet()) {
        Map<Unit, Integer> iMap = reserves.get(item);
        for (Unit u : iMap.keySet()) {
          reserveVisitor.execute(u, item, iMap.get(u));
        }

      }
    }
  }

  static class SkillSpec {

    public SkillType skill;
    public int level;
    public int max;

    public SkillSpec(SkillType skill, int level, int max) {
      if (skill == null || level <= 0 || max <= 0)
        throw new IllegalArgumentException("invalid SkillSpec " + skill + " " + level + " " + max);
      this.skill = skill;
      this.level = level;
      this.max = max;
    }

    @Override
    public String toString() {
      return skill.getName() + " " + level + "/" + max;
    }
  }

  public static class ItemSorter implements Comparator<Item> {

    private String[] priorities;

    public ItemSorter(String[] priorities) {
      this.priorities = priorities;
    }

    public int compare(Item o1, Item o2) {
      int p1 = 0, p2 = 0;
      String t1 = o1.getItemType().getOrderName();
      for (String type : priorities) {
        if (t1.equals(type)) {
          break;
        }
        p1++;
      }
      if (p1 == priorities.length) {
        p1 = -1;
      }
      String t2 = o2.getItemType().getOrderName();
      for (String type : priorities) {
        if (t2.equals(type)) {
          break;
        }
        p2++;
      }
      if (p2 == priorities.length) {
        p2 = -2;
      }
      if (p1 == p2)
        if (t1.compareTo(t2) == 0)
          return o2.getAmount() - o1.getAmount();
        else
          return t1.compareTo(t2);
      return p1 - p2;
    }

  }

  /**
   * A standard soldier's endurance skill should be this fraction of his (first row) weapon skill
   */
  public static float ENDURANCERATIO_FRONT = .6f;
  /**
   * A standard soldier's endurance skill should be this fraction of his (second row) weapon skill
   */
  public static float ENDURANCERATIO_BACK = .35f;

  /** Unit limit, used to warn if we get too many units. */
  public static int UNIT_LIMIT = 250;

  /** If this is true, some more hints will be added to the orders if expected units are missing */
  public static boolean ADD_NOT_THERE_INFO = false;

  /** Set to something in order to use TeachPlugin */
  public static String TEACH_PREFIX = null;
  /** The skills learned by helmspeople, see {@link #commandHelmsman(String[])}. */
  public static String LEARN_HELMSMAN = "$tm$ 100.0 Segeln 25 12 Schiffbau 8 4 Unterhaltung 6 5";

  /**
   * soldier parameters for helmspeople, see {@link #commandHelmsman(String[])} and {@link #commandSoldier(String[])}
   */
  public static String SOLDIER_HELMSMAN = "best best null null";
  /** The skills learned by crew, see {@link #commandCrew(String[])}. */
  public static String LEARN_CREW =
      "$tm$ 100.0 Segeln 25 12 Schiffbau 10 4 Unterhaltung 9 5 Reiten 4 2 Holzfällen 4 2 Pferdedressur 4 2";
  /** soldier parameters for crew, see {@link #commandCrew(String[])} and {@link #commandSoldier(String[])} */
  public static String SOLDIER_CREW = "best best null null";
  /** the learn level set by commands like soldier */
  public static final int DEFAULT_LEARNLEVEL = 20;

  /** preferred level for quartermasters */
  public static final int QM_TACTICS = 5;

  /**
   * If this is > 0, all units are suppliers, otherwise suppliers must be set with Versorge (the
   * default)
   */
  public static int DEFAULT_SUPPLY_PRIORITY = 0;

  /** default need priority */
  private static final int DEFAULT_PRIORITY = 100;
  /** need priority for GibWenn command */
  private static final int GIVE_IF_PRIORITY = 999999;
  /** need priority for Depot command and silver */
  private static final int DEPOT_SILVER_PRIORITY = 150;
  /** need priority for earn command */
  private static final int DEFAULT_EARN_PRIORITY = 200;
  /** need priority for Depot command and other items */
  private static final int DEPOT_PRIORITY = -1;
  /** need priority for traders */
  private static final int TRADE_PRIORITY = DEFAULT_PRIORITY;

  /** All script commands begin with this text. */
  public static final String scriptMarker = "$cript";

  /** The GIVE order */
  public static String GIVEOrder = "GIB";
  /** The RESERVE order */
  public static String RESERVEOrder = "RESERVIERE";
  /** The EACH order parameter */
  public static String EACHOrder = "JE";
  /** The ALL order parameter */
  public static String ALLOrder = "ALLES";
  /** The KRÄUTER order parameter */
  public static String KRAUTOrder = "KRAUT";
  /** The LUXUS order parameter */
  public static String LUXUSOrder = "LUXUS";
  /** The LUXUS order parameter */
  public static String TRANKOrder = "TRANK";
  /** The "on foot" order */
  public static String FOOTOrder = "FUSS";
  /** The "on horse" order */
  public static String HORSEOrder = "PFERD";
  /** The "on ship" order */
  public static String SHIPOrder = "SCHIFF";
  /** The item "horses" */
  public static String HORSEItem = "Pferd";
  /** The persistent comment order */
  public static String PCOMMENTOrder = EresseaConstants.O_PCOMMENT;
  /** The persistent comment order */
  public static String COMMENTOrder = EresseaConstants.O_COMMENT;
  /** The LEARN order */
  public static String LEARNOrder = "LERNE";
  /** The (TEACH) AUTO order */
  public static String AUTOOrder = "AUTO";
  /** The TEACH order */
  public static String TEACHOrder = "LEHRE";
  /** The ENTERTAIN order */
  private static String ENTERTAINOrder = "UNTERHALTE";
  /** The TAX order */
  private static String TAXOrder = "TREIBE";
  /** The WORK order */
  private static String WORKOrder = "ARBEITE";
  /** The BUY order */
  private static String BUYOrder = "KAUFE";
  /** The SELL order */
  private static String SELLOrder = "VERKAUFE";
  /** The MAKE order */
  private static String MAKEOrder = "MACHE";
  /** The NACH order */
  private static String MOVEOrder = "NACH";
  /** The ROUTE order */
  private static String ROUTEOrder = "ROUTE";
  /** The PAUSE order */
  private static String PAUSEOrder = "PAUSE";
  /** The RESEARCH order */
  private static String RESEARCHOrder = "FORSCHE";
  /** The RECRUIT order */
  private static String RECRUITOrder = "REKRUTIERE";

  /** The BEST token (for soldier) */
  public static String BEST = "best";
  /** The NULL token (for soldier) */
  public static String NULL = "null";
  /** The NOT token (for auto and others) */
  public static String NOT = "NICHT";

  /** The LONG token (for clear) */
  public static String LONG = "$lang";
  /** The SHORT token (for clear) */
  public static String SHORT = "$kurz";
  /** The COMMENT token (for clear) */
  public static String COMMENT = "$kommentar";

  private static String S_ENDURANCE = EresseaConstants.S_AUSDAUER.toString();

  private static final int TEACH_MULTI = 10;

  private String[] WEAPON_PRIORITIES =
      new String[] {
          "Adamantiumaxt",
          "Laenschwert",
          "Kriegsaxt",
          "Bihänder",
          "Schwert",
          "Rostige~Kriegsaxt",
          "Rostiger~Bihänder",
          "Schartiges~Schwert",
          "Hellebarde",
          "Mallornspeer",
          "Speer",
          "Rostige~Hellebarde",
          "Mallornlanze",
          "Lanze",
          "Mallornarmbrust",
          "Armbrust",
          "Elfenbogen",
          "Mallornbogen",
          "Bogen"
      };
  private String[] SHIELD_PRIORITIES =
      new String[] {
          "Laenschild",
          "Schild",
          "Rostiger~Schild"
      };
  private String[] ARMOR_PRIORITIES =
      new String[] {
          "Adamantiumrüstung",
          "Laenkettenhemd",
          "Plattenpanzer",
          "Kettenhemd",
          "Rostiges~Kettenhemd"
      };

  private OrderParser parser;
  private Logger log;

  /**
   * Creates and initializes the parser.
   *
   * @param world
   * @param helper
   */
  public E3CommandParser(GameData world, ExtendedCommandsHelper helper) {
    E3CommandParser.world = world;
    E3CommandParser.helper = helper;
    parser = world.getGameSpecificStuff().getOrderParser(world);
    log = Logger.getInstance("E3CommandParser");
  }

  protected OrderParser getParser() {
    return parser;
  }

  // variables available in scripts; this is here mainly to be able to test this class outside
  // BeanShell
  static GameData world;
  static ExtendedCommandsHelper helper;

  private Unit someUnit;

  // variables for current script state
  private Map<Faction, Integer> currentFactions = CollectionFactory.createOrderedMap();
  private Region currentRegion;
  private Unit currentUnit;
  private ArrayList<String> newOrders;
  private String currentOrder;
  private int line;
  private int error;
  private boolean changedOrders;

  /**
   * The item/unit/need map. Stores all needed items.
   */
  protected List<Need> needQueue;
  /**
   * The item/unit/supply map. Stores all available items.
   */
  protected SupplyMap supplyMapp;

  protected Map<Unit, Integer> capacities;

  private Map<String, Unit> dummyUnits;
  private Map<Unit, Map<String, Integer>> transfersMap;
  private List<Transfer> transferList;

  /**
   * Lines matching these patterns should be removed.
   */
  protected List<String> removedOrderPatterns;

  /**
   * Lists of allowed units for Erlaube/Ueberwache
   */
  protected Map<Faction, Set<UnitID>> allowedUnits = new HashMap<Faction, Set<UnitID>>();
  /**
   * Lists of required units for Verlange/Ueberwache
   */
  protected Map<Faction, Set<UnitID>> requiredUnits = new HashMap<Faction, Set<UnitID>>();

  /**
   * Current state for the Loesche command
   */
  protected String clear = null;
  /** Current prefix for the Loesche command */
  protected String clearPrefix = "";

  private int progress = -1;
  private long supplySerial = 0;
  private int showStats = 1;
  private String cachedScriptCommand;
  private Map<Unit, Collection<SkillSpec>> skills = new HashMap<Unit, Collection<SkillSpec>>();
  private Map<String, Consumer<String[]>> commands;

  public int getShowStats() {
    return showStats;
  }

  public void setShowStats(int showStats) {
    this.showStats = showStats;
  }

  /**
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * container to execute all unit commands.
   *
   * @param faction scripts for all units of this faction
   * @throws NullPointerException if <code>faction == null</code>
   */
  public void execute(Faction faction) {
    executeFrom(Collections.singleton(faction), null, null);
  }

  /**
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * container to execute all unit commands.
   *
   * @param factions scripts for all units of all factions in this set
   * @throws NullPointerException if <code>faction == null</code>
   */
  public void execute(Collection<Faction> factions) {
    executeFrom(factions, null, null);
  }

  /**
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * container to execute all unit commands.
   *
   * @param faction scripts for all units of this faction are executed
   * @param region only commands of unit in this region are executed
   * @throws NullPointerException if <code>faction == null</code>
   */
  public void execute(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();

    executeFrom(faction, region, region);
  }

  /**
   * Parses scripts and confirms units according to the "confirm" tag. Call this for the faction
   * container to execute all unit commands. Ignore regions before first (in the report order).
   *
   * @param faction scripts for all units of this faction are executed
   * @param region only commands of unit in this region are executed, may be <code>null</code> to
   *          execute for all regions.
   * @param first First region to execute, may be <code>null</code> to not ignore any regions
   * @throws NullPointerException if <code>faction == null</code>
   */
  public void executeFrom(Faction faction, Region region, Region first) {
    executeFrom(Collections.singleton(faction), region, first);
  }

  /**
   * Parses scripts and confirms units according to the "confirm" tag. Ignore regions before first
   * (in the report order).
   *
   * @param factions scripts for all units of all factions in this set are executed
   * @param region only commands of unit in this region are executed, may be <code>null</code> to
   *          execute for all regions.
   * @param first First region to execute, may be <code>null</code> to not ignore any regions
   * @throws NullPointerException if <code>faction == null</code>
   */
  public void executeFrom(Collection<Faction> factions, Region region, Region first) {
    if (factions == null || factions.isEmpty())
      throw new NullPointerException();

    currentFactions = CollectionFactory.createOrderedMap(1);
    for (Faction f : factions) {
      currentFactions.put(f, null);
    }

    helper.getUI().setMaximum(world.getRegions().size() + 4);
    helper.getUI().setProgress("init", ++progress);

    // comment out the following two lines if you don't have the newest nighthly build of Magellan
    helper.getUI().setProgress("preprocessing", ++progress);

    EresseaRelationFactory relationFactory = ((EresseaRelationFactory) world.getGameSpecificStuff()
        .getRelationFactory());
    relationFactory.stopUpdating();

    findSomeUnit(currentFactions);

    initLocales();

    for (Faction faction : factions) {
      if (faction.units().size() >= UNIT_LIMIT) {
        addWarning(someUnit, "Einheitenlimit erreicht (" + faction.units().size() + "/"
            + UNIT_LIMIT + ")! ");
      } else if (faction.units().size() * 1.1 > UNIT_LIMIT) {
        addWarning(someUnit, "Einheitenlimit fast erreicht (" + faction.units().size() + "/"
            + UNIT_LIMIT + ")! ");
      }
    }

    collectStats();

    // Parses the orders of the units for commands of the form "// $cript ..." and
    // tries to execute them.
    if (region == null) {
      boolean go = first == null;
      for (Region r : world.getRegions()) {
        if (r == first) {
          go = true;
        }
        if (go) {
          execute(r);
        }
      }
    } else {
      execute(region);
    }

    // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("postprocessing", ++progress);

    relationFactory.restartUpdating();

    for (Faction faction : factions) {
      for (Unit u : faction.units()) {
        if ("1".equals(getProperty(u, "confirm"))) {
          u.setOrdersConfirmed(true);
        } else if ("0".equals(getProperty(u, "confirm"))) {
          u.setOrdersConfirmed(false);
        }
        notifyMagellan(u);
      }
    }
    // comment out the following line if you don't have the newest nightly build of Magellan
    helper.getUI().setProgress("ready", ++progress);
  }

  protected void execute(Region region) {
    try {
      currentRegion = region;
      skills.clear();
      initSupply();

      commands = new HashMap<String, Consumer<String[]>>();
      commands.put("KrautKontrolle", constructCommand(this::commandControl, false, true));
      commands.put("auto", constructCommand(this::commandAuto, false, false));
      commands.put("Mannschaft", constructCommand(this::commandCrew, false, false));
      commands.put("Loeschen", constructCommand(this::commandClear));
      commands.put("GibWenn", constructCommand(this::commandGiveIf));
      commands.put("Benoetige", constructCommand(this::commandNeed));
      commands.put("BenoetigeFremd", constructCommand(this::commandNeed));
      commands.put("Versorge", constructCommand(this::commandSupply));
      commands.put("Kapazitaet", constructCommand(this::commandCapacity));
      commands.put("BerufDepotVerwalter", constructCommand(this::commandDepot));
      commands.put("Soldat", constructCommand(this::commandSoldier));
      commands.put("Lerne", constructCommand(this::commandLearn));
      // commands.put("BerufBotschafter", constructCommand(this::commandEmbassador, () ->
      // addNewOrder(currentOrder, false), () -> ));
      commands.put("Ueberwache", constructCommand(this::commandMonitor));
      commands.put("Erlaube", constructCommand(this::commandAllow));
      commands.put("Verlange", constructCommand(this::commandAllow));
      commands.put("Ernaehre", constructCommand(this::commandEarn));
      commands.put("Handel", constructCommand(this::commandTrade));
      commands.put("Steuermann", constructCommand(this::commandHelmsman));
      commands.put("Quartiermeister", constructCommand(this::commandQuartermaster));
      commands.put("Sammler", constructCommand(this::commandCollector));
      commands.put("RekrutiereMax", constructCommand(this::commandRecruit));
      commands.put("Kommentar", constructCommand(this::commandComment));

      for (Unit u : region.units()) {
        if (currentFactions.containsKey(u.getFaction())) {

          setCurrentUnit(u);
          // comment out the following line if you don't have the newest nightly build of Magellan
          helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);

          try {
            parseScripts();
          } catch (RuntimeException e) {
            addWarning(u, "error " + e.getClass().getSimpleName());
            log.error(e.getClass().getSimpleName() + " script error for " + u);
            throw new RuntimeException("script error for " + u, e);
          }
        }
      }
      setCurrentUnit(null);

      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setProgress(region.toString() + " - postprocessing", ++progress);

      teachUnits();
      satisfyNeeds();
    } catch (RuntimeException e) {
      e.printStackTrace();
      // log.error(e.getClass().getSimpleName() + " script error for " + region);
      throw new RuntimeException("script error for " + region, e);
    }
    // refresh relations, just in case
    region.refreshUnitRelations(true);
  }

  protected void updateCurrentOrders() {
    if (isChangedOrders()) {
      currentUnit.setOrders(newOrders);
    }
    notifyMagellan(currentUnit);
  }

  protected void setCurrentUnit(Unit u) {
    if (u != null) {
      currentUnit = u;
      newOrders = new ArrayList<String>();
      removedOrderPatterns = new ArrayList<String>();
      setChangedOrders(false);
      error = -1;
    } else {
      currentUnit = null;
      newOrders = null;
    }
  }

  protected boolean isChangedOrders() {
    return changedOrders;
  }

  protected void setChangedOrders(boolean changedOrders) {
    this.changedOrders = changedOrders;
  }

  protected Unit findUnit(String target) {
    Unit targetUnit;
    if ((targetUnit = helper.getUnit(target)) == null) {
      targetUnit = dummyUnits.get(target);
      if (targetUnit == null) {
        dummyUnits.put(target, targetUnit = MagellanFactory.createUnit(UnitID.createUnitID(target,
            world.base),
            world));
      }
    }
    return targetUnit;
  }

  private void findSomeUnit(Map<Faction, Integer> factions) {
    // sometimes we need an arbitrary unit. This is a shorthand for it.
    for (Faction faction : factions.keySet())
      if (!faction.units().isEmpty()) {
        someUnit = faction.units().iterator().next();
        break;
      }

    if (someUnit == null)
      throw new RuntimeException("No units in report!");

  }

  protected boolean testUnit(String sOther, Unit other, Warning warning) {
    return testUnit(sOther, other, warning, false);
  }

  protected boolean testUnit(String sOther, Unit other, Warning w, boolean testFaction) {
    if (other == null || other.getRegion() != currentUnit.getRegion()) {
      if (w.contains(Warning.C_UNIT) && w.contains(Warning.C_HIDDEN)) {
        addNewWarning(sOther + " nicht da");
        return false;
      } else if (ADD_NOT_THERE_INFO) {
        addNewOrder("; " + sOther + " nicht da", true);
      }
      return !w.contains(Warning.C_HIDDEN);
    } else if (testFaction && w.contains(Warning.C_FOREIGN)
        && !currentFactions.containsKey(other.getFaction())) {
      addNewWarning("Einheit " + sOther + " gehört nicht zu uns");
    }

    return true;
  }

  /**
   * Tries to init the constants according to the current faction's locale
   */
  protected void initLocales() {
    if (currentUnit == null) {
      findSomeUnit(currentFactions);
    }
    if (someUnit == null)
      throw new NullPointerException();
    setCurrentUnit(someUnit);

    GIVEOrder = getLocalizedOrder(EresseaConstants.OC_GIVE, GIVEOrder).toString();
    RESERVEOrder = getLocalizedOrder(EresseaConstants.OC_RESERVE, RESERVEOrder).toString();
    EACHOrder = getLocalizedOrder(EresseaConstants.OC_EACH, EACHOrder).toString();
    ALLOrder = getLocalizedOrder(EresseaConstants.OC_ALL, ALLOrder).toString();
    PCOMMENTOrder = "//";
    LEARNOrder = getLocalizedOrder(EresseaConstants.OC_LEARN, LEARNOrder).toString();
    TEACHOrder = getLocalizedOrder(EresseaConstants.OC_TEACH, TEACHOrder).toString();
    ENTERTAINOrder = getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder).toString();
    TAXOrder = getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder).toString();
    WORKOrder = getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder).toString();
    BUYOrder = getLocalizedOrder(EresseaConstants.OC_BUY, BUYOrder).toString();
    SELLOrder = getLocalizedOrder(EresseaConstants.OC_SELL, SELLOrder).toString();
    MAKEOrder = getLocalizedOrder(EresseaConstants.OC_MAKE, MAKEOrder).toString();
    MOVEOrder = getLocalizedOrder(EresseaConstants.OC_MOVE, MOVEOrder).toString();
    ROUTEOrder = getLocalizedOrder(EresseaConstants.OC_ROUTE, ROUTEOrder).toString();
    PAUSEOrder = getLocalizedOrder(EresseaConstants.OC_PAUSE, PAUSEOrder).toString();
    RESEARCHOrder = getLocalizedOrder(EresseaConstants.OC_RESEARCH, RESEARCHOrder).toString();
    RECRUITOrder = getLocalizedOrder(EresseaConstants.OC_RECRUIT, RECRUITOrder).toString();

    if (currentFactions.keySet().iterator().next().getLocale().getLanguage() != "de") {
      // warning constants
      Warning.W_NEVER = "never";
      Warning.W_SKILL = "skill";
      Warning.W_WEAPON = "weapon";
      Warning.W_SHIELD = "shields";
      Warning.W_ARMOR = "armor";
      BEST = "best";
      NULL = "null";
      LUXUSOrder = "LUXURY";
      KRAUTOrder = "HERBS";
    }
    setCurrentUnit(null);
  }

  protected String getLocalizedOrder(StringID orderKey, String fallback) {
    try {
      return world.getGameSpecificStuff().getOrderChanger().getOrderO(orderKey,
          currentUnit.getLocale()).getText();
    } catch (RulesException e) {
      return fallback;
    }
  }

  /**
   * Tries to translate the given order to the current locale.
   */
  protected String getLocalizedOrder(StringID orderKey, Object[] args) {
    return world.getGameSpecificStuff().getOrderChanger().getOrderO(currentUnit.getLocale(),
        orderKey, args).getText();
  }

  /**
   * Tries to translate the given order to the current locale.
   */
  protected String getLocalizedOrder(String orderKey, String fallBack) {
    String translation = Resources.getOrderTranslation(orderKey, currentUnit.getLocale());
    if (translation == orderKey)
      return fallBack;
    else
      return translation;
  }

  /**
   * Adss an order to the current unit's new orders.
   *
   * @param order The new order
   * @param changed Set to true if this is a change (not merely a copy of an old order)
   */
  protected void addNewOrder(String order, boolean changed) {
    setChangedOrders(isChangedOrders() || changed);
    if (!changed) {
      // remove old orders matching a removed order
      for (String pattern : removedOrderPatterns)
        if (order.matches(pattern))
          return;
    }

    newOrders.add(getParser().parse(order, currentUnit.getLocale()).getText());
  }

  /**
   * Registers a pattern. All lines matching this regular expression (case sensitive!) will be
   * removed from here on. If retroActively, also orders that are already in {@link #newOrders}.
   *
   * @param regEx
   * @param retroActively
   */
  protected void removeOrdersLike(String regEx, boolean retroActively) {
    if (retroActively) {
      for (Iterator<String> it = newOrders.iterator(); it.hasNext();) {
        String line2 = it.next();
        if (line2.matches(regEx)) {
          it.remove();
        }
      }
    }
    removedOrderPatterns.add(regEx);
  }

  /**
   * Adds an error line.
   *
   * @param hint
   */
  protected void addNewError(String hint) {
    error = line;
    // errMsg = hint;
    addNewOrder(COMMENTOrder + " TODO: " + hint + " (Fehler in Zeile " + error + ")", true);
    setConfirm(currentUnit, false);
  }

  /**
   * Adds a warning message (with a to do tag) to the new orders.
   *
   * @param text
   */
  protected void addNewMessage(String text) {
    addNewOrder(COMMENTOrder + " ----- " + text + " -----", true);
  }

  /**
   * Adds an error line to new orders.
   *
   * @param hint
   */
  protected void addNewWarning(String hint) {
    addNewWarning(hint, true);
  }

  protected void addNewWarning(String hint, boolean addLine) {
    error = line;
    // errMsg = hint;
    addNewOrder(COMMENTOrder + " TODO: " + hint
        + (addLine ? " (Warnung in Zeile " + error + ")" : ""), true);
    setConfirm(currentUnit, false);
  }

  /**
   * Adds some statistic information to the orders of the first unit.
   *
   */
  protected void collectStats() {
    int buildingScripts = 0;
    int shipScripts = 0;
    int unitScripts = 0;
    int regionScripts = 0;
    // comment out the following lines if you don't have the newest nightly build of Magellan
    for (Building b : world.getBuildings()) {
      if (helper.hasScript(b)) {
        buildingScripts++;
      }
    }
    for (Ship s : world.getShips()) {
      if (helper.hasScript(s)) {
        shipScripts++;
      }
    }
    for (Unit u : world.getUnits()) {
      if (helper.hasScript(u)) {
        addOrder(u, COMMENTOrder + " hat Skript", false);
        unitScripts++;
      }
    }
    for (Region r : world.getRegions()) {
      if (helper.hasScript(r)) {
        regionScripts++;
      }
    }

    if (showStats > 0) {
      someUnit.addOrderAt(0, "; " + unitScripts + " unit scripts, " + buildingScripts
          + " building scripts, " + shipScripts + " ship scripts, " + regionScripts
          + " region scripts", true);
    }
  }

  /**
   * Parses the orders of the unit u for commands of the form "// $cript ..." and tries to execute
   * them. Known commands:<br />
   * <kbd>// $cript +X text</kbd> -- If X<=1 then a warning containing text is added to the unit's
   * orders. Otherwise X is decreased by one.<br />
   * <code>// $cript [rest [period [length]] text</code> -- Adds text (or commands) to the
   * orders<br />
   * <code>// $cript auto [NICHT]|[length [period]]</code> -- autoconfirm orders<br />
   * <code>// $cript Loeschen [$kurz] [<prefix>]</code> -- clears orders except comments<br />
   * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
   * -- add give order (if possible)<br />
   * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
   * <code>// $cript Benoetige JE amount item [priority]</code><br />
   * <code>// $cript Benoetige ALLES [item] [priority]</code> -- acquire things from other
   * units<br />
   * <code>// $cript BenoetigeFremd unit [JE] minAmount [maxAmount] item [priority] [warning]</code><br
   * />
   * <code>// $cript Versorge [[item]...] priority</code> -- set supply priority.<br />
   * <code>// $cript BerufDepotVerwalter [Zusatzbetrag]</code> Collects all free items in the
   * region, Versorge 100, calls Ueberwache<br />
   * <code>// $cript Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code>
   * -- learn skill and reserve equipment<br />
   * <code>// $cript Lerne Talent1 Stufe1 [[Talent2 Stufe2]...]</code> -- learn skills in given
   * ratio <br />
   * <code>// $cript BerufBotschafter [minimum money] [Talent]</code> -- earn money if necessary,
   * otherwise learn skill<br />
   * <code>// $cript Ueberwache</code> -- look out for unknown units<br />
   * <code>// $cript Erlaube faction unit [unit...]</code> -- allow units for Ueberwache<br />
   * <code>// $cript Verlange faction unit [unit...]</code> -- allow and require units for
   * Ueberwache<br />
   * <code>// $cript Ernaehre [amount]</code> -- earn money<br />
   * <code>// $cript Handel Menge|xM [xR] [ALLES | Verkaufsgut...] Warnung</code> -- trade luxuries<br />
   * <code>// $cript Steuermann minSilver maxSilver [Talent ...]</code> -- be responsible for ship<br />
   * <code>// $cript Mannschaft [Talen ...]</code> -- be crew and learn<br />
   * <code>// $cript Quartiermeister [[amount item]...]</code> -- be lookout<br />
   * <code>// $cript Sammler [interval]</code> -- collect and research HERBS<br />
   * <code>// $cript KrautKontrolle [route]</code> -- FORSCHE KRÄUTER in several regions<br />
   * <code>// $cript RekrutiereMax</code> -- recruit as much as possible<br />
   * <code>// $cript Kommentar text</code> -- add ; comment<br />
   */
  protected void parseScripts() {
    // errMsg = null;
    line = 0;
    allowedUnits.clear();
    requiredUnits.clear();
    clear = null;

    // NOTE: must not change currentUnit's orders directly! Always change newOrders!
    Deque<String> queue = new LinkedList<String>();
    for (Order o : currentUnit.getOrders2()) {
      ++line;
      queue.add(o.getText());
      while (!queue.isEmpty()) {
        currentOrder = queue.poll();
        try {
          String[] tokens = detectScriptCommand(currentOrder);
          if (tokens == null) {
            // add order if
            if (shallClear(currentOrder)) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
            } else {
              addNewOrder(currentOrder, false);
            }
            currentOrder = null;
          } else {
            // as of Java 7 the first character of an integer may be a '+' sign
            boolean repeat = true;
            while (tokens != null && !tokens[0].startsWith("+") && repeat) {
              try {
                Integer.parseInt(tokens[0]);
                String[] nextOrders = commandRepeat(tokens);
                if (nextOrders == null) {
                  currentOrder = null;
                  tokens = null;
                } else {
                  currentOrder = nextOrders.length > 0 ? nextOrders[0] : "";
                  tokens = detectScriptCommand(currentOrder);
                  if (tokens == null) {
                    addNewOrder(currentOrder, true);
                    currentOrder = null;
                  }
                  for (int i = 1; i < nextOrders.length; ++i) {
                    queue.add(nextOrders[i]);
                  }
                }
              } catch (NumberFormatException e) {
                repeat = false;
                // not a repeating order
              }
            }
            if (tokens != null) {
              Consumer<String[]> interpreter = commands.get(tokens[0]);
              if (interpreter != null) {
                interpreter.accept(tokens);
              } else {
                String command = tokens[0];
                if (command.startsWith("+")) {
                  commandWarning(tokens);
                  setChangedOrders(true);
                } else if (command.equals("BerufBotschafter")) {
                  addNewOrder(currentOrder, false);
                  Collection<String> addedCommands = commandEmbassador(tokens);
                  Collections.reverse((List<?>) addedCommands);
                  for (String newOrder : addedCommands) {
                    queue.addFirst(newOrder);
                  }
                  setChangedOrders(true);
                } else {
                  addNewError("unbekannter Befehl: " + command);
                }
              }
              currentOrder = null;
            }
          }
        } catch (Throwable t) {
          StringWriter w = new StringWriter();

          t.printStackTrace(new PrintWriter(w));
          addNewError("Fehler bei der Ausführung von '" + currentOrder + "': " + w.toString());
          log.error(t);
        }
      }
    }

    updateCurrentOrders();

  }

  private Consumer<String[]> constructCommand(Consumer<String[]> command, Runnable before, Runnable after) {
    return (tokens) -> {
      before.run();
      command.accept(tokens);
      after.run();
    };
  }

  private Consumer<String[]> constructCommand(Consumer<String[]> command, boolean keep, boolean setChanged) {
    return constructCommand(command,
        () -> {
          if (keep) {
            addNewOrder(currentOrder, false);
          }
        },
        () -> {
          if (setChanged) {
            setChangedOrders(true);
          }
        });
  }

  private Consumer<String[]> constructCommand(Consumer<String[]> command) {
    return constructCommand(command, true, false);
  }

  /**
   * If order is a script command ("// $cript ..."), returns a List of the tokens. Otherwise returns
   * <code>null</code>. The first in the list is the first token after the "// $cript".
   */
  protected String[] detectScriptCommand(String order) {
    StringTokenizer tokenizer = new StringTokenizer(order, " ");
    if (tokenizer.hasMoreTokens()) {
      String part = tokenizer.nextToken();
      if (part.equals(PCOMMENTOrder) || part.equals(COMMENTOrder)) {
        if (tokenizer.hasMoreTokens()) {
          part = tokenizer.nextToken();
          if (part.startsWith(scriptMarker)) {
            if (!part.equals(scriptMarker)) {
              addNewWarning("lines starting with '" + part + "' not allowed");
            } else {
              List<String> result = new ArrayList<String>();
              while (tokenizer.hasMoreTokens()) {
                result.add(tokenizer.nextToken());
              }
              if (result.size() == 0)
                return null;
              return result.toArray(new String[] {});
            }
          }
        }
      }
    }
    return null;
  }

  /**
   * <code>// $cript Loeschen [$kurz] [<prefix>]</code><br />
   * Remove orders except comments from here on. If "$kurz", remove all orders, otherwise only long
   * and permanent (@) orders. If prefix is set, remove only orders starting with that prefix.
   *
   * @param tokens
   */
  protected void commandClear(String[] tokens) {
    clearPrefix = "";
    if (tokens.length == 1) {
      clear = LONG;
    } else {
      if (tokens[1].equalsIgnoreCase(SHORT)) {
        clear = ALLOrder;
        clearPrefix =
            currentOrder.substring(Math.min(currentOrder.length(), currentOrder.indexOf(SHORT)
                + SHORT.length() + 1));
      } else {
        clear = LONG;
        clearPrefix =
            currentOrder.substring(Math.min(currentOrder.length(), currentOrder.indexOf("Loeschen")
                + "Loeschen".length() + 1));
      }
    }
    for (int i = 0; i < newOrders.size(); i++) {
      String order = newOrders.get(i);
      if (shallClear(order)) {
        setChangedOrders(true);
        newOrders.set(i, COMMENTOrder + " " + order);
      }
    }
  }

  protected boolean shallClear(String order) {
    if (clear == null)
      return false;
    String trimmed = order.trim();

    return !trimmed.startsWith(PCOMMENTOrder)
        && !trimmed.startsWith(COMMENTOrder)
        && (clear == ALLOrder || (clear == LONG && world.getGameSpecificStuff().getOrderChanger()
            .isLongOrder(order)))
        && (clearPrefix == null || clearPrefix.length() == 0 || trimmed.startsWith(clearPrefix));
  }

  /**
   * <code>// $cript [rest [period [length]]] text</code><br />
   * Adds text (or commands) to the orders after rest rounds. If <code>rest==1</code>, text is added
   * to the unit's orders after the current order. If text is a script order itself, it will be
   * executed. If period is set, rest will be reset to period and the modified order added instead
   * of the current order. If <code>rest>1</code>, it is decreased and the modified order added
   * instead of the current one. If length is given, the whole order will be removed after length
   * rounds. <code>text</code> may contain '\n". If this is the case it is split into lines and the
   * lines are executed or inserted after the current command. For example the line<br />
   * "// $cript 1 10 MACHE TEMP a\nLERNE Hiebwaffen\n// $cript 2 GIB a 1 Silber\nENDE"<br />
   * will be replaced with<br />
   * "// $cript 10 10 MACHE TEMP a\nLERNE Hiebwaffen\n// $cript 2 GIB a 1 Silber\nENDE",<br />
   * "MACHE TEMP a",<br />
   * "LERNE Hiebwaffen",<br />
   * "// $cript 1 GIB a 1 Silber",<br />
   * "ENDE".
   *
   * @param tokens
   * @return <code>text</code>, if <code>rest==1</code>, otherwise <code>null</code>
   */
  protected String[] commandRepeat(String[] tokens) {
    ArrayList<String> lines = null;
    try {
      int rest = Integer.parseInt(tokens[0]);
      int period = 0;
      int length = Integer.MAX_VALUE;
      int textIndex = 1;

      setChangedOrders(true);

      if (tokens.length >= 2) {
        try {
          period = Integer.parseInt(tokens[1]);
          textIndex = 2;
          if (tokens.length >= 3) {
            try {
              length = Integer.parseInt(tokens[2]);
              textIndex = 3;
            } catch (NumberFormatException nfe) {
              // third argument not a number
              length = Integer.MAX_VALUE;
              textIndex = 2;
            }
          }
        } catch (NumberFormatException nfe) {
          // second argument not a number
          period = 0;
          textIndex = 1;
        }
      }
      if (rest == 1) {
        lines = new ArrayList<String>(1);
        StringBuilder result = new StringBuilder();
        if (period > 0) {
          rest = period + 1;
        }
        if (textIndex < tokens.length && tokens[textIndex].equals(scriptMarker)) {
          result.append(COMMENTOrder).append(" ");
        }
        for (int tokenIndex = textIndex; tokenIndex < tokens.length; ++tokenIndex) {
          if (tokenIndex > textIndex) {
            result.append(" ");
          }
          String[] subTokens = tokens[tokenIndex].split("\\\\n");
          result.append(subTokens[0]);
          for (int j = 1; j < subTokens.length; ++j) {
            lines.add(result.toString());
            result.setLength(0);
            result.append(subTokens[j]);
          }
          if (tokenIndex + 1 >= tokens.length) {
            lines.add(result.toString());
          }
        }
      } else if (length == 0) {
        // return empty string to signal success
        lines = new ArrayList<String>(1);
        lines.add("");
      }
      if ((rest > 1 || period > 0) && length > 0) {
        StringBuilder newOrder = new StringBuilder();
        newOrder.append(createScriptCommand()).append(rest - 1);
        if (period > 0) {
          newOrder.append(" ").append(period);
        }
        if (length != Integer.MAX_VALUE) {
          newOrder.append(" ").append(length - 1);
        }
        for (int i = textIndex; i < tokens.length; ++i) {
          newOrder.append(" ").append(tokens[i]);
        }
        addNewOrder(newOrder.toString(), true);
      }
    } catch (NumberFormatException e) {
      addNewOrder(currentOrder, false);
      addNewError("Zahl erwartet");
    }

    return lines != null ? lines.toArray(new String[0]) : null;
  }

  /**
   * <code>// $cript auto [NICHT]|[length [period]]</code><br />
   * Autoconfirms a unit (or prevents autoconfirmation if NICHT). If length is given, the unit is
   * autoconfirmed for length rounds (length is decreased each round). If period is given, the unit
   * is <em>not</em> confirmed every period rounds, otherwise confirmed.
   *
   * @param tokens
   */
  protected void commandAuto(String[] tokens) {
    if (tokens.length > 4) {
      addNewError("zu viele Argumente");
      return;
    }
    if (tokens.length == 1) {
      setConfirm(currentUnit, true);
      addNewOrder(currentOrder, false);
    } else if (NOT.equalsIgnoreCase(tokens[1])) {
      setConfirm(currentUnit, false);
      addNewOrder(currentOrder, false);
    } else {
      int length = 0, period = 0;
      try {
        length = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
          period = Integer.parseInt(tokens[2]);
        }
        if (length > 0) {
          setConfirm(currentUnit, true);
        } else {
          setConfirm(currentUnit, false);
          if (period > 0) {
            length = period;
          }
        }
        StringBuilder newOrder = new StringBuilder();
        newOrder.append(createScriptCommand()).append(tokens[0]);
        newOrder.append(" ").append(length - 1);
        if (period > 0) {
          newOrder.append(" ").append(period);
        }
        addNewOrder(newOrder.toString(), true);
      } catch (NumberFormatException e) {
        addNewOrder(currentOrder, false);
        addNewError("Zahl erwartet");
        return;
      }
    }
  }

  private String createScriptCommand() {
    if (cachedScriptCommand == null) {
      cachedScriptCommand = PCOMMENTOrder + " " + scriptMarker + " ";
    }
    return cachedScriptCommand;
  }

  /**
   * <code>// $cript GibWenn receiver [[JE] amount|ALLES|KRAUT|LUXUS|TRANK] [item] [warning...]</code>
   * <br />
   * Adds a GIB order to the unit. Warning may be one of "immer" (the default), "Menge", "Einheit",
   * "nie".
   */
  protected void commandGiveIf(String[] tokens) {
    Warning w = new Warning(true);
    tokens = w.parse(tokens);

    if (tokens.length < 3 || tokens.length > 5) {
      addNewError("falsche Anzahl Argumente");
      return;
    }

    Unit target = helper.getUnit(tokens[1]);
    String targetId = tokens[1];

    // test if EACH is present
    int je = 0;
    if (EACHOrder.equals(tokens[2])) {
      je = 1;
      if (ALLOrder.equals(tokens[3]) || KRAUTOrder.equals(tokens[3])
          || LUXUSOrder.equals(tokens[3]) || TRANKOrder.equals(tokens[3])) {
        addNewError("JE " + tokens[3] + " geht nicht");
        return;
      }
    }

    if (!testUnit(targetId, target, w))
      return;

    // handle GIB xyz ALLES
    if (ALLOrder.equalsIgnoreCase(tokens[2])) {
      if (tokens.length == 3) {
        giveAll(targetId, w, null, null);
        return;
      }
    }

    if (KRAUTOrder.equalsIgnoreCase(tokens[2]) || LUXUSOrder.equalsIgnoreCase(tokens[2])
        || TRANKOrder.equalsIgnoreCase(tokens[2])) {
      // handle GIB xyz KRAUT
      if (KRAUTOrder.equalsIgnoreCase(tokens[2])) {
        if (world.getRules().getItemCategory("herbs") == null) {
          addNewError("Spiel kennt keine Kräuter");
        } else {
          giveAll(targetId, w, null, world.getRules().getItemCategory("herbs"));
        }
      }

      // handle GIB xyz LUXUS
      if (LUXUSOrder.equalsIgnoreCase(tokens[2])) {
        if (world.getRules().getItemCategory("luxuries") == null) {
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          giveAll(targetId, w, null, world.getRules().getItemCategory("luxuries"));
        }
      }

      // handle GIB xyz TRANK
      if (TRANKOrder.equalsIgnoreCase(tokens[2])) {
        if (world.getRules().getItemCategory("potions") == null) {
          addNewError("Spiel kennt keine Tränke");
        } else {
          giveAll(targetId, w, null, world.getRules().getItemCategory("potions"));
        }
      }

      return;
    }

    if (tokens.length != 4 + je) {
      addNewError("zu viele Parameter");
      return;
    }

    // get amount
    final String item = tokens[3 + je];
    int amount = 0;
    if (ALLOrder.equalsIgnoreCase(tokens[2 + je])) {
      if (KRAUTOrder.equals(item) || LUXUSOrder.equals(item)) {
        addNewError("GIB xyz ALLES " + item + " statt GIB xyz " + item);
      }
      giveAll(targetId, w, item, null);
      return;
    } else {
      try {
        amount = Integer.parseInt(tokens[2 + je]);
      } catch (NumberFormatException e) {
        amount = 0;
        addNewError("Zahl oder ALLES erwartet");
        return;
      }
    }

    // get full amount (=amount * persons)
    int fullAmount = amount;
    if (je == 1) {
      if (target == null) {
        addNewMessage("Einheit nicht gefunden; kann Menge nicht überprüfen");
      } else {
        fullAmount = target.getModifiedPersons() * amount;
      }
    }

    // check availibility
    if (getItemCount(currentUnit, item) < fullAmount) {
      if (w.contains(Warning.C_AMOUNT)) {
        addNewWarning("zu wenig " + item);
      } else {
        addNewMessage("zu wenig " + item);
      }
      fullAmount = getItemCount(currentUnit, item);
      je = 0;
    }

    // make GIVE order
    if (fullAmount > 0) {
      giveTransfer(targetId, item, fullAmount, false);
    }
  }

  private void giveAll(String targetId, Warning w, String filterItem, ItemCategory filterCategory) {
    for (Item item : getUnitItems(currentUnit, filterItem, filterCategory)) {
      String itemName = item.getOrderName();
      int amount = getSupply(itemName, currentUnit).getAmount();
      giveTransfer(targetId, itemName, amount, true);
    }
  }

  private Collection<Item> getUnitItems(Unit unit, String filterItem, ItemCategory filterCategory) {
    Collection<Item> items = new ArrayList<Item>();
    for (Item item : unit.getItems()) {
      if ((filterItem != null && filterItem.equals(
          item.getOrderName()))
          ||
          (filterCategory != null && filterCategory.equals(
              item.getItemType().getCategory()))
          || (filterItem == null && filterCategory == null)) {
        items.add(item);
      }
    }
    return items;
  }

  /**
   * <code>// $cript Benoetige minAmount [maxAmount] item [priority]</code><br />
   * <code>// $cript Benoetige JE amount item [priority]</code><br />
   * <code>// $cript Benoetige FUSS|PFERD Pferd [priority]</code><br />
   * <code>// $cript Benoetige ALLES [item] [priority]</code><br />
   * Tries to transfer the maxAmount of item from other units to this unit. Issues warning if
   * minAmount cannot be supplied. <code>Benoetige JE</code> tries to reserve amount of item for
   * every person in the unit. Fractional amounts are possible and rounded up.
   * <code>Benoetige ALLES item</code> is equivalent to <code>Benoetige 0 infinity item</code>,
   * <code>Benoetige ALLES</code> is equivalent to <code>Benoetige ALLES</code> for every itemtype
   * in the region.<br/>
   * <code>Benoetige KRAUT</code> is the same for every herb type in the region.<br/>
   * <code>BenoetigeFremd unit (JE amount)|(minAmount [maxAmount]) item [priority] [warning...]</code>
   * <br />
   * <code>BenoetigeFremd</code> does the same, but for the given unit instead of the current unit.
   * Needs with higher priority are satisfied first. If no priority is given,
   * {@link #DEFAULT_PRIORITY} is used.
   */
  protected void commandNeed(String[] tokens) {
    Unit unit = currentUnit;
    Warning w = new Warning(true);

    String sOther = "???";
    if (tokens[0].equals("BenoetigeFremd")) {
      if (tokens.length < 3) {
        addNewError("zu wenig Argumente");
        return;
      }
      sOther = tokens[1];
      unit = helper.getUnit(tokens[1]);

      // erase unit token for easier processing afterwards
      tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
      tokens[0] = "BenoetigeFremd";

      // only benoetigefremd can have warnings!
      tokens = w.parse(tokens);
      // foreign implies amount
      if (w.contains(Warning.C_FOREIGN)) { // FIXME test
        w.add(Warning.W_AMOUNT);
      }
    }

    int tokenCount = tokens.length;

    int priority;
    try {
      priority = Integer.parseInt(tokens[tokenCount - 1]);
      tokenCount--;
    } catch (NumberFormatException e) {
      priority = DEFAULT_PRIORITY;
    }

    tokens = Arrays.copyOf(tokens, tokenCount);

    if (tokens.length < 2 || tokens.length > 6) {
      addNewError("falsche Anzahl Argumente");
      return;
    }

    if (!testUnit(sOther, unit, w, true))
      return;
    if (unit == null) {
      if (sOther == null) {
        addNewError("Einheitennummer fehlt");
        return;
      }
      unit = findUnit(sOther);
    }
    try {
      if (ALLOrder.equals(tokens[1])) {
        if (tokens.length > 2) {
          addNeed(tokens[2], unit, 0, Integer.MAX_VALUE, priority, w);
        } else {
          for (String item : supplyMapp.items()) {
            addNeed(item, unit, 0, Integer.MAX_VALUE, priority, w);
          }
        }
      } else if (EACHOrder.equals(tokens[1])) {
        if (unit.getPersons() <= 0)
          if (w.contains(Warning.C_AMOUNT)) {
            addNewWarning("Benoetige JE für leere Einheit");
          } else {
            addNewMessage("Benoetige JE für leere Einheit");
          }
        int amount = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[2]));
        int maxTokens = 4;
        int amount2 = amount;
        if (tokens.length > 4) {
          ++maxTokens;
          boolean each2 = false;
          int numberToken = 3;
          if (EACHOrder.equals(tokens[3])) {
            each2 = true;
            ++numberToken;
            ++maxTokens;
            amount2 = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[4]));
          } else {
            String item = tokens[4];
            amount2 = getAmountWithHorse(unit, tokens[3], item);
          }
        }
        if (tokens.length > maxTokens) {
          addNewError("zu viele Parameter");
        }
        String item = tokens[maxTokens - 1];
        addNeed(item, unit, amount, amount2, priority, w);
      } else if (KRAUTOrder.equals(tokens[1])) {
        if (tokens.length > 2) {
          addNewError("zu viele Parameter");
        }
        if (world.getRules().getItemCategory("herbs") == null) {
          addNewError("Spiel kennt keine Kräuter");
        } else {
          for (ItemType itemType : world.getRules().getItemTypes()) { // currentUnit.getItems()
            if (world.getRules().getItemCategory("herbs").equals(itemType.getCategory())) {
              addNeed(itemType.getOrderName(), unit, 0, Integer.MAX_VALUE, priority, w);
            }
          }
        }
      } else if (LUXUSOrder.equals(tokens[1])) {
        if (tokens.length > 2) {
          addNewError("zu viele Parameter");
        }
        if (world.getRules().getItemCategory("luxuries") == null) {
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          for (ItemType itemType : world.getRules().getItemTypes()) { // currentUnit.getItems()
            if (world.getRules().getItemCategory("luxuries").equals(itemType.getCategory())) {
              addNeed(itemType.getOrderName(), unit, 0, Integer.MAX_VALUE, priority, w);
            }
          }
        }
      } else if (tokens.length > 4 || tokens.length < 3) {
        addNewError("falsche Anzahl Argumente");
      } else {
        String item = tokens[tokens.length - 1];
        int minAmount = getAmountWithHorse(unit, tokens[1], item);
        int maxAmount = tokens.length == 3 ? minAmount : getAmountWithHorse(unit, tokens[2], item);
        addNeed(item, unit, minAmount, maxAmount, priority, w);
      }
    } catch (NumberFormatException exc) {
      addNewError("Ungültige Zahl in Benoetige: " + exc.getMessage());
    }
  }

  private int getAmountWithHorse(Unit unit, String amount, String item) {
    if (isHorse(item))
      if (HORSEOrder.equals(amount))
        return world.getGameSpecificRules().getMaxHorsesRiding(unit);
      else if (FOOTOrder.equals(amount))
        return world.getGameSpecificRules().getMaxHorsesWalking(unit);
    return Integer.parseInt(amount);
  }

  /**
   * <code>// $cript BerufDepotVerwalter [[ZusatzMin] ZusatzMax]</code><br />
   * Collects all free items in the region, Versorge 100, calls Ueberwache
   */
  protected void commandDepot(String[] tokens) {
    if (tokens.length > 3) {
      addNewError("zu viele Argumente");
    }
    commandMonitor(new String[] { "Ueberwache" });

    int costs = 0;
    for (Unit u : currentRegion.units()) {
      if (currentFactions.containsKey(u.getFaction())) {
        costs += u.getRace().getMaintenance() * u.getPersons();
      }
    }
    int zusatz1 = 0, zusatz2 = 0;
    if (tokens.length > 1) {
      try {
        zusatz1 = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
          zusatz2 = Integer.parseInt(tokens[2]);
        }
      } catch (NumberFormatException e) {
        addNewError("Zahl erwartet");
      }
    }
    addNeed("Silber", currentUnit, costs + zusatz1, costs + zusatz1 + zusatz2,
        DEPOT_SILVER_PRIORITY);

    commandNeed(new String[] { "Benoetige ", ALLOrder, String.valueOf(DEPOT_PRIORITY) });
    commandSupply(new String[] { "Versorge", "100" });
  }

  /**
   * <code>Versorge [[item1]...] priority</code> -- set supply priority. Units with negative
   * priority only deliver for minimum needs. Needs are satisfied in descending order of priority.
   * If no items are given, the priority is adjusted for alle the unit's items.
   */
  protected void commandSupply(String[] tokens) {
    int priority = 0;
    if (tokens.length < 2) {
      addNewError("zu wenig Argumente");
    }
    try {
      priority = Integer.parseInt(tokens[tokens.length - 1]);
    } catch (NumberFormatException e) {
      addNewError("Zahl erwartet");
      return;
    }
    if (tokens.length == 2) {
      for (Item item : currentUnit.getItems()) {
        Supply supply = getSupply(item.getOrderName(), currentUnit);
        if (supply != null) {
          supply.setPriority(priority);
        }
      }
    } else {
      for (int i = 1; i < tokens.length - 1; ++i) {
        setSupplyPriority(currentUnit, tokens[i], priority);
      }
    }
  }

  private void setSupplyPriority(Unit unit, String itemToken, int priority) {
    Collection<Item> items = null;
    if (KRAUTOrder.equalsIgnoreCase(itemToken)) {
      items = getCategoryItems(unit, "herbs", "Kräuter");
    } else if (LUXUSOrder.equalsIgnoreCase(itemToken)) {
      items = getCategoryItems(unit, "luxury", "Luxusgüter");
    } else if (TRANKOrder.equalsIgnoreCase(itemToken)) {
      items = getCategoryItems(unit, "potion", "Tränke");
    } else {
      items = getUnitItems(unit, itemToken, null);
    }

    if (items != null) {
      for (Item item : items) {
        Supply supply = getSupply(item.getOrderName(), currentUnit);
        if (supply != null) {
          supply.setPriority(priority);
        }
      }
    }
  }

  private Collection<Item> getCategoryItems(Unit unit, String category, String categoryName) {
    ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
    if (itemCategory == null) {
      addNewError("Spiel kennt keine " + categoryName);
      return Collections.emptyList();
    }
    return getUnitItems(unit, null, itemCategory);
  }

  /**
   * <code>Kapazitaet FUSS|PFERD|SCHIFF|amount</code> -- ensure that a unit's capacity is not
   * exceeded
   */
  protected void commandCapacity(String[] tokens) {
    int capacity = 0;
    int slack = 0;
    if (tokens.length < 2) {
      addNewError("zu wenig Argumente");
      return;
    } else if (tokens.length > 2) {
      if (tokens.length == 4 && "-".equals(tokens[2])) {
        try {
          slack = Integer.parseInt(tokens[3]);
        } catch (NumberFormatException e) {
          addNewError("Zahl erwartet");
        }
      } else {
        addNewError("zu viele Argumente");
      }
    }
    MovementEvaluator movement = world.getGameSpecificStuff().getMovementEvaluator();
    int load = movement.getModifiedLoad(currentUnit);
    try {
      capacity = Integer.parseInt(tokens[1]);
    } catch (NumberFormatException e) {
      if (HORSEOrder.equals(tokens[1])) {
        capacity = movement.getPayloadOnHorse(currentUnit);
      } else if (FOOTOrder.equals(tokens[1])) {
        capacity = movement.getPayloadOnFoot(currentUnit);
      } else if (SHIPOrder.equals(tokens[1])) {
        Ship ship = currentUnit.getModifiedShip();
        if (ship == null || ship.getOwnerUnit() != currentUnit) {
          capacity = Integer.MAX_VALUE;
          addNewWarning("Einheit ist nicht Kapitän.");
        } else {
          capacity = ship.getMaxCapacity() - ship.getModifiedLoad() + load;
        }
      } else {
        addNewError("Zahl erwartet");
        return;
      }
      if (isCapacityError(capacity)) {
        addNewWarning("Zu viele Pferde.");
        capacity = load;
      }
    }

    // int load = movement.getModifiedLoad(currentUnit);
    capacities.put(currentUnit, capacity - slack - load);
  }

  private boolean isCapacityError(int capacity) {
    return capacity == MovementEvaluator.CAP_NO_HORSES
        || capacity == MovementEvaluator.CAP_UNSKILLED;
  }

  /**
   * <code>// $script +x [Arguments...]</code><br />
   * If x<=1, the rest of the line is added as a warning to this unit. Otherwise x is decreased by
   * one. If x==1, the order is removed.
   */
  protected void commandWarning(String[] tokens) {
    int delay = -1;
    try {
      delay = Integer.parseInt(tokens[0].substring(1));
    } catch (NumberFormatException e) {
      addNewError("Zahl erwartet");
      return;
    }
    if (delay <= 1) {
      StringBuilder warning = new StringBuilder();
      String foo = currentOrder.substring(currentOrder.indexOf("+"));
      warning.append(foo.indexOf(" ") >= 0 ? foo.substring(foo.indexOf(" ") + 1) : "");
      addNewWarning(warning.toString(), false);
    }
    if (delay != 1) {
      StringBuilder newCommand = new StringBuilder(createScriptCommand()).append("+");
      newCommand.append(Math.max(0, delay - 1));
      for (int i = 1; i < tokens.length; ++i) { // skip "+x"
        newCommand.append(" ").append(tokens[i]);
      }
      addNewOrder(newCommand.toString(), true);
    }
  }

  /**
   * <code>// $cript Lerne Talent1 Stufe1 [Max1] [[Talent2 Stufe2 [Max2]...]</code><br />
   * Tries to learn skills in given ratio. For example,
   * <code>// $cript Lerne Hiebwaffen 10 99 Ausdauer 5 3</code> tries to learn to (Hiebwaffen 2, Ausdauer
   * 1), (Hiebwaffen 4, Ausdauer 2), and so forth, but Ausdauer only until level 3 is reached.
   */
  protected void commandLearn(String[] tokens) {
    if (tokens.length < 3) {
      addNewError("falsche Anzahl Argumente");
      return;
    }
    List<SkillSpec> targetSkills = new LinkedList<SkillSpec>();

    for (int i = 1; i < tokens.length;) {
      int j = i;
      try {
        if (i < tokens.length - 1) {
          SkillType skill = world.getRules().getSkillType(tokens[j++]);
          int level = Integer.parseInt(tokens[j]), max = 99;
          j++; // no exception
          if (i < tokens.length - 2) {
            try {
              max = Integer.parseInt(tokens[j]);
              j++;
            } catch (NumberFormatException e) {
              max = 99;
            }
          }
          if (skill == null) {
            addNewError("unbekanntes Talent " + tokens[i]);
          } else {
            targetSkills.add(new SkillSpec(skill, level, max));
          }
        } else {
          addNewError("unerwartetes Token " + tokens[i]);
        }
      } catch (NumberFormatException e) {
        addNewError("ungültige Stufe " + tokens[i]);
      } finally {
        i = Math.max(j, i + 1);
      }
    }
    learn(currentUnit, targetSkills);
  }

  /**
   * <code>Soldat [Talent [Waffe [Schild [Rüstung]]]] [nie|Talent|Waffe|Schild|Rüstung]</code><br />
   * Tries to learn best skill and acquire equipment. If no skill is given, best weapon skill is
   * selected. If no weapon is given, best matching weapon is acquired and so on. Preference is
   * always for RESERVEing stuff the unit already has. Waffe, Schild, Rüstung can be "null" if
   * nothing should be reserved or "best" (the default behavior).<br />
   * Warning can be:<br />
   * nie: issues no warnings at all,
   * Talent: only warns if no skill is given and no best skill exists,
   * Waffe: additionally warn if no weapon can be acquired,
   * Schild: additionally warn if no shield,
   * Rüstung: additionally warn if no armor.
   * Default warning level is "Waffe".
   */
  protected void commandSoldier(String[] tokens) {
    String warning = tokens[tokens.length - 1];
    if (!(Warning.W_NEVER.equals(warning) || Warning.W_SKILL.equals(warning) || Warning.W_WEAPON
        .equals(warning)
        || Warning.W_SHIELD.equals(warning) || Warning.W_ARMOR.equals(warning))) {
      warning = null;
    }
    String skill = null;
    if (tokens.length > 1 + (warning == null ? 0 : 1)) {
      skill = tokens[1];
    }
    String weapon = null;
    if (tokens.length > 2 + (warning == null ? 0 : 1)) {
      weapon = tokens[2];
    }
    String shield = null;
    if (tokens.length > 3 + (warning == null ? 0 : 1)) {
      shield = tokens[3];
    }
    String armor = null;
    if (tokens.length > 4 + (warning == null ? 0 : 1)) {
      armor = tokens[4];
    }

    if (warning == null) {
      warning = Warning.W_WEAPON;
    }

    // if (NULL.equals(skill)) {
    // skill = null;
    // }
    // if (NULL.equals(weapon)) {
    // weapon = null;
    // }
    // if (NULL.equals(shield)) {
    // shield = null;
    // }
    // if (NULL.equals(armor)) {
    // armor = null;
    // }

    soldier(currentUnit, skill, weapon, shield, armor, warning);
  }

  /**
   * <code>// $cript BerufBotschafter [minimum money] [Talent|command]</code> -- if we have at least
   * minimum money (default 100), learn skill or execute command<br />
   *
   */
  protected Collection<String> commandEmbassador(String[] tokens) {
    SkillType skill = null;
    int minimum = 100;
    int actionToken = 1;
    if (tokens.length > 1) {
      try {
        minimum = Integer.parseInt(tokens[1]);
        actionToken = 2;
      } catch (NumberFormatException e) {
        actionToken = 1;
      }
    }
    String mode = "";
    if (tokens.length > actionToken) {
      if (WORKOrder.equals(tokens[actionToken])) {
        mode = WORKOrder;
      } else if (ENTERTAINOrder.equals(tokens[actionToken])) {
        mode = ENTERTAINOrder;
      } else if (TAXOrder.equals(tokens[actionToken])) {
        mode = TAXOrder;
      } else {
        --actionToken;
      }
      ++actionToken;
    }
    if (tokens.length > actionToken) {
      skill = getSkillType(tokens[actionToken]);
    } else {
      skill = getSkillType(EresseaConstants.S_WAHRNEHMUNG.toString());
      if (skill == null) {
        skill = getSkillType(EresseaConstants.S_AUSDAUER.toString());
      }
    }

    commandClear(new String[] { "Loeschen" });

    if (helper.getSilver(currentUnit) < minimum) {
      if (mode == ENTERTAINOrder ||
          (mode == "" && hasEntertain() && currentUnit.getSkill(EresseaConstants.S_UNTERHALTUNG) != null
              && currentUnit.getSkill(EresseaConstants.S_UNTERHALTUNG).getLevel() > 0)) {
        if (world.getGameSpecificRules().getMaxEntertain(currentRegion) < 20 * currentUnit.getSkill(
            EresseaConstants.S_UNTERHALTUNG).getLevel() * currentUnit.getPersons()) {
          addNewWarning("Bauern zu arm");
        }
        if (currentUnit.getShip() != null && isGuarded(currentRegion, currentUnit)) {
          addNewWarning("Region wird bewacht");
        }
        addNewOrder(ENTERTAINOrder, true);
      } else if (mode == TAXOrder ||
          (mode == "" && hasTax() && currentUnit.getSkill(EresseaConstants.S_STEUEREINTREIBEN) != null
              && currentUnit.getSkill(EresseaConstants.S_STEUEREINTREIBEN).getLevel() > 0)) {
        if (currentRegion.getSilver() < 20 * currentUnit.getSkill(EresseaConstants.S_STEUEREINTREIBEN).getLevel()) {
          addNewWarning("Bauern zu arm");
        }
        if (isGuarded(currentRegion, currentUnit)) {
          addNewWarning("Region wird bewacht");
        }

        addNewOrder(TAXOrder, true);
      } else if (mode == WORKOrder || (mode == "" && hasWork())) {
        if (world.getGameSpecificRules().getMaxWorkers(currentRegion) < currentUnit.getPersons()) {
          addNewWarning("zu wenig Arbeitsplätze");
        }
        if (currentUnit.getShip() != null && isGuarded(currentRegion, currentUnit)) {
          addNewWarning("Region wird bewacht");
        }
        addNewOrder(WORKOrder, true);
      } else {
        addNewWarning("Einheit kann nichts verdienen");
      }
    } else if (skill == null) {
      // other order
      StringBuilder order = new StringBuilder(createScriptCommand()).append("1 ");
      if (tokens.length > actionToken) {
        order.append(tokens[actionToken]);
      }
      for (int i = actionToken + 1; i < tokens.length; ++i) {
        order.append(" ").append(tokens[i]);
      }
      return Collections.singletonList(order.toString());
      // addNewOrder(order.toString(), true);
    } else {
      learn(currentUnit, Collections.singleton(new SkillSpec(skill, 10, 99)));
      if (tokens.length > actionToken + 1) {
        addNewError("zu viele Argumente");
      }
    }
    return Collections.emptyList();
  }

  private boolean isGuarded(Region currentRegion2, Unit currentUnit2) {
    for (Unit guard : currentRegion.getGuards()) {
      if (!Units.isAllied(guard.getFaction(), currentUnit.getFaction(),
          EresseaConstants.A_GUARD))
        return true;
    }
    return false;
  }

  protected void commandMonitor(String[] tokens) {
    if (tokens.length > 1) {
      addNewError("zu viele Argumente");
    }

    // check if region units are allowed
    Map<Faction, List<Unit>> warnings = new HashMap<Faction, List<Unit>>();
    for (Unit u : currentRegion.units()) {
      if (u.getFaction() != currentUnit.getFaction()) {
        if (!(allowedUnits.containsKey(u.getFaction()) && (allowedUnits.get(u.getFaction())
            .contains(u.getID()) || allowedUnits.get(u.getFaction()).contains(
                currentRegion.getZeroUnit().getID())))) {
          if (!(requiredUnits.containsKey(u.getFaction()) && requiredUnits.get(u.getFaction())
              .contains(u.getID()))) {
            List<Unit> list = warnings.get(u.getFaction());
            if (list == null) {
              list = new LinkedList<Unit>();
              warnings.put(u.getFaction(), list);
            }
            list.add(u);
          }
        }
      }
    }

    for (Entry<Faction, List<Unit>> entry : warnings.entrySet()) {
      StringBuilder sb = new StringBuilder();
      sb.append(entry.getKey()).append(" hat unerlaubte Einheiten:");
      int i = 0;
      for (Unit u : entry.getValue()) {
        if (++i < 4) {
          sb.append(" ");
          sb.append(u.toString());
        } else {
          sb.append(" ...");
          break;
        }
      }
      entry.getValue().clear();
      addNewWarning(sb.toString());
    }

    // check if required units are present
    for (Faction f : requiredUnits.keySet()) {
      for (UnitID id : requiredUnits.get(f)) {
        Unit u = world.getUnit(id);
        if (u == null || u.getRegion() != currentUnit.getRegion()) {
          addNewWarning("Einheit " + id + " der Partei " + f + " nicht mehr da.");
        }
      }
    }
  }

  protected void commandAllow(String[] tokens) {
    if (tokens.length < 3) {
      addNewError("zu wenige Argumente");
      return;
    }

    Faction faction = helper.getFaction(tokens[1]);
    Map<Faction, Set<UnitID>> map;
    if (tokens[0].equals("Erlaube")) {
      map = allowedUnits;
    } else {
      map = requiredUnits;
    }
    if (faction == null) {
      addNewError("unbekannte Partei");
    } else {
      Set<UnitID> set = map.get(faction);
      if (set == null) {
        set = new HashSet<UnitID>();
        map.put(faction, set);
      }
      if (ALLOrder.equals(tokens[2])) {
        set.add(currentRegion.getZeroUnit().getID());
        if (tokens.length > 3) {
          addNewError("zu viele Argumente");
        }
      } else {
        for (int i = 2; i < tokens.length; ++i) {
          try {
            set.add(UnitID.createUnitID(tokens[i], world.base));
          } catch (NumberFormatException exc) {
            addNewError("Ungültige Einheitennummer " + tokens[i]);
          }
        }
      }
    }
  }

  /**
   * <code>// $cript Ernaehre [amount] [nie]</code> -- Earn as much money as possible (or the specified
   * amount), Versorge {@value #DEFAULT_EARN_PRIORITY}
   */
  protected void commandEarn(String[] tokens) {
    int amount = -1;
    Warning warning = new Warning(true);
    tokens = warning.parse(tokens);
    if (tokens.length > 1) {
      try {
        amount = Integer.parseInt(tokens[1]);
      } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[1]);
        return;
      }
      if (tokens.length > 2) {
        addNewError("zu viele Argumente");
      }
    }

    // Ernaehre includes Versorge
    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });

    // remove previous orders
    removeOrdersLike(TAXOrder + ".*", true);
    removeOrdersLike(ENTERTAINOrder + ".*", true);
    removeOrdersLike(WORKOrder + ".*", true);

    int maxWorkers =
        Utils.getIntValue(world.getGameSpecificRules().getMaxWorkers(currentRegion), 0);
    int workers = Math.min(maxWorkers, currentRegion.getPeasants());
    Skill entertaining =
        currentUnit
            .getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_UNTERHALTUNG));
    Skill taxing =
        currentUnit.getModifiedSkill(world.getRules().getSkillType(
            EresseaConstants.S_STEUEREINTREIBEN));
    int entertain = 0, entertain2 = 0, tax = 0, tax2 = 0;
    if (entertaining != null && hasEntertain()) {
      entertain2 = 20 * entertaining.getLevel() * currentUnit.getPersons();
      entertain = Math.max(0, Math.min(currentRegion.maxEntertain(), entertain2));
    }
    if (taxing != null && isSoldier(currentUnit)) {
      tax2 = 20 * taxing.getLevel() * currentUnit.getPersons();
      tax = Math.min(currentRegion.getSilver(), tax2);
    }

    if (tax > entertain) {
      addNewOrder(TAXOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " " + tax + ">"
          + entertain, true);
      if (tax >= currentRegion.getSilver() + workers * 10 - currentRegion.getPeasants() * 10) {
        addNewWarning("Bauern verhungern");
      }
      if (tax2 * 2 > tax * 3) {
        if (warning.contains(Warning.W_AMOUNT)) {
          addNewWarning("Treiber unterbeschäftigt " + tax2 + ">" + tax);
        }
        entertain = currentRegion.maxEntertain();
      }
    } else if (entertain > 0) {
      addNewOrder(ENTERTAINOrder + " " + (amount > 0 ? amount : "") + COMMENTOrder + " "
          + entertain + ">" + tax, true);
      if (amount > currentRegion.maxEntertain() * 1.1) {
        if (warning.contains(Warning.W_NEVER)) {
          addNewWarning("zu viele Arbeiter");
        }
      } else if (entertain2 * 2 > entertain * 3) {
        if (warning.contains(Warning.W_AMOUNT)) {
          addNewWarning("Unterhalter unterbeschäftigt " + entertain2 + ">" + entertain);
        }
        entertain = currentRegion.maxEntertain();
      }
    } else {
      addNewOrder(WORKOrder + " " + (amount > 0 ? amount : ""), true);
      if ((maxWorkers - workers) * 10 < Math.min(amount, currentUnit.getModifiedPersons() * 10)) {
        if (warning.contains(Warning.W_AMOUNT)) {
          addNewWarning("zu viele Arbeiter");
        }
      }
    }
    setConfirm(currentUnit, true);
  }

  /**
   * <code>// $cript Handel Menge|xM [xR] [ALLES | Verkaufsgut...] Warnung</code>: trade luxuries,
   * Versorge {@value #DEFAULT_EARN_PRIORITY}. Menge M may be a multipler ("x2" of the region
   * maximum). xR: reserve goods for R rounds. Warnung can be "Talent", "Menge", or "nie"<br />
   */
  protected void commandTrade(String[] tokens) {
    if (tokens.length < 2) {
      addNewError("zu wenige Argumente");
      return;
    }

    commandSupply(new String[] { "Versorge", String.valueOf(DEFAULT_EARN_PRIORITY) });

    Warning warning = new Warning(true);
    tokens = warning.parse(tokens);

    int volume = currentRegion.maxLuxuries();

    int buyAmount = -1;
    try {
      if (tokens[1].substring(0, 1).equalsIgnoreCase("x")) {
        buyAmount = Integer.parseInt(tokens[1].substring(1));
        buyAmount = volume * buyAmount;
      } else {
        buyAmount = Integer.parseInt(tokens[1]);
      }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl " + tokens[1]);
      return;
    }

    Skill buySkill =
        currentUnit.getModifiedSkill(world.getRules().getSkillType(EresseaConstants.S_HANDELN));
    if (buySkill == null) {
      addNewError("kein Handelstalent");
      return;
    }

    if (currentRegion.getPrices() == null) {
      addNewError("kein Handel möglich");
      return;
    }

    LuxuryPrice buyGood = null;
    for (Entry<StringID, LuxuryPrice> entry : currentRegion.getPrices().entrySet()) {
      LuxuryPrice price = entry.getValue();
      if (price.getPrice() < 0) {
        buyGood = price;
      } else {
        if (currentRegion.getOldPrices() != null
            && currentRegion.getOldPrices().get(entry.getKey()) != null
            && price.getPrice() < currentRegion.getOldPrices().get(entry.getKey()).getPrice()) {
          addNewWarning("Preis gesunken: " + price.getItemType());
        }
      }
    }

    removeOrdersLike(SELLOrder + ".*", true);
    removeOrdersLike(BUYOrder + ".*", true);

    // Gueterzahl intitialisieren
    int totalVolume = 0;

    if (buyAmount > 0 && (volume <= 0 || buyGood == null)) {
      addNewError("kein Handel möglich");
    }

    LinkedList<String> orders = new LinkedList<String>();

    int maxAmount = buySkill.getLevel() * currentUnit.getPersons() * 10;
    int skillNeeded = 0;

    if (volume > 0 && buyGood != null) {
      int reserveMultiplier = 1, reserveToken = 0;
      try {
        if (tokens.length > 2 && tokens[2].substring(0, 1).equalsIgnoreCase("x")) {
          reserveToken = 1;
          reserveMultiplier = Integer.parseInt(tokens[2].substring(1));
        }
      } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[2]);
        return;
      }

      List<String> goods = new LinkedList<String>();
      // Verkaufsbefehl setzen, wenn notwendig
      if (tokens.length > 2 + reserveToken && ALLOrder.equals(tokens[2 + reserveToken])) {
        if (world.getRules().getItemCategory("luxuries") == null) {
          addNewError("Spiel kennt keine Luxusgüter");
        } else {
          for (ItemType luxury : world.getRules().getItemTypes()) {
            if (!luxury.equals(buyGood.getItemType())
                && world.getRules().getItemCategory("luxuries").equals(luxury.getCategory())) {
              goods.add(luxury.getOrderName());
            }
          }
        }
      } else {
        for (int i = 2 + reserveToken; i < tokens.length; ++i) {
          goods.add(tokens[i]);
        }
      }

      for (String luxury : goods) {
        int goodAmount = volume;
        if (goodAmount > getSupply(luxury)) {
          goodAmount = getSupply(luxury);
        }
        skillNeeded += goodAmount;

        if (goodAmount > maxAmount - totalVolume) {
          goodAmount = maxAmount - totalVolume;
        }

        addNeed(luxury, currentUnit, ALLOrder.equals(tokens[2 + reserveToken]) ? goodAmount
            : volume, volume
                * reserveMultiplier,
            TRADE_PRIORITY, warning);
        if (goodAmount > 0) {

          if (goodAmount == volume) {
            orders.add(SELLOrder + " " + ALLOrder + " " + luxury);
          } else {
            orders.add(SELLOrder + " " + goodAmount + " " + luxury);
          }
        }
        totalVolume += goodAmount;
      }

    }

    // Soll eingekauft werden?
    if (volume > 0 && buyAmount > 0 && buyGood != null) {
      int remainingFromSkill = maxAmount - totalVolume + 1 - 1;
      // Berechne noetige Geldmenge fuer Einkauf (einfacher: Modulorechnung, aber wegen
      // Rundungsfehler nicht umsetzbar)
      int remainingToBuy = buyAmount;
      skillNeeded += buyAmount;
      if (remainingToBuy > remainingFromSkill) {
        buyAmount += (remainingFromSkill - remainingToBuy);
        remainingToBuy = remainingFromSkill;
      }
      int priceFactor = 1;
      int money = 0;
      while (remainingToBuy > 0) {
        if (remainingToBuy > volume) {
          remainingToBuy -= volume;
          money -= (volume * priceFactor * buyGood.getPrice()); // price is negative
          priceFactor++;
        } else {
          money -= (remainingToBuy * priceFactor * buyGood.getPrice());
          remainingToBuy = 0;
        }
      }

      addNeed("Silber", currentUnit, money, money, TRADE_PRIORITY);

      // Einkaufsbefehl setzen, wenn notwendig
      if (buyAmount > 0) {
        orders.addFirst(BUYOrder + " " + buyAmount + " " + buyGood.getItemType().getOrderName());
        totalVolume += buyAmount;
      }
    }

    // add orders (buy orders first)
    for (String order : orders) {
      addNewOrder(order, true);
    }

    // Einheit gut genug?
    if (skillNeeded > maxAmount && warning.contains(Warning.C_SKILL)) {
      addNewError("Einheit hat zu wenig Handelstalent (min: " + maxAmount / 10 + " < "
          + (int) Math.ceil(skillNeeded / 10.0) + ")");
    }

    setConfirm(currentUnit, true);
  }

  /**
   * <code>// $cript Steuermann minSilver maxSilver [Talent ...]</code> -- be responsible for ship
   * 
   * Reserves silver for crew, maintains route commands, executes soldier command. If given, uses the same parameters as
   * commandSoldier, starting with Talent.
   * Otherwise uses parameters specified in {@link #SOLDIER_HELMSMAN}.
   * 
   * If {@link #LEARN_HELMSMAN} is not null, adds those parameters to the teach plugin.
   * 
   * @param tokens
   */
  protected void commandHelmsman(String[] tokens) {
    if (tokens.length == 2) {
      addNewError("zu wenige Argumente");
      return;
    }
    String min, max;
    if (tokens.length > 1) {
      min = tokens[1];
      max = tokens[2];
    } else {
      Ship ship = currentUnit.getShip();
      if (ship == null) {
        min = "300";
        max = "500";
      } else {
        int sail = 0, money = 0;
        for (Unit u : ship.units()) {
          if (u.getSkill(EresseaConstants.S_SEGELN) != null) {
            sail += u.getSkill(EresseaConstants.S_SEGELN).getLevel() * u.getPersons();
          }
          money += u.getPersons() * u.getRace().getMaintenance();
          if (sail >= ship.getShipType().getSailorSkillLevel() * ship.getAmount()) {
            break;
          }
        }
        min = String.valueOf((money * 6 - 1) / 100 * 100 + 100);
        max = String.valueOf((money * 11 - 1) / 100 * 100 + 100);
      }
    }
    commandNeed(new String[] { "Benoetige", min, max, "Silber",
        String.valueOf(DEFAULT_PRIORITY + 10) });
    setConfirm(currentUnit, false);

    for (Order order : currentUnit.getOrders2()) {
      String text = order.getText();
      if (text.startsWith(ROUTEOrder + " " + PAUSEOrder)) {
        addNewWarning("Route beendet");
      }
    }
    removeOrdersLike(ROUTEOrder + " " + PAUSEOrder + ".*", true);

    String[] soldierTokens;
    if (tokens.length > 3) {
      // Soldat [Talent [Waffe [Schild [Rüstung]]]]
      soldierTokens = new String[tokens.length - 1];
      soldierTokens[0] = tokens[0];
      for (int i = 1; i < tokens.length - 2; ++i) {
        soldierTokens[i] = tokens[i + 2];
      }
    } else {
      soldierTokens = (tokens[0] + " " + SOLDIER_HELMSMAN).split(" ");
    }
    if (LEARN_HELMSMAN != null) {
      addToTeachPlugin(currentUnit, parseLearn(LEARN_HELMSMAN));
    }
    commandSoldier(soldierTokens);
  }

  /**
   * <code>// $cript Mannschaft [Talent ...]</code> -- be crew and learn<br />
   * 
   * If given, uses the same parameters as
   * commandSoldier, starting with Talent.
   * Otherwise uses parameters specified in {@link #SOLDIER_CREW}.
   * 
   * If {@link #LEARN_CREW} is not null, adds those parameters to the teach plugin.
   * 
   * @param tokens
   */
  protected void commandCrew(String[] tokens) {
    String[] soldierTokens;
    boolean legacy = false;
    if (tokens.length == 3) {
      try {
        Integer.parseInt(tokens[2]);
        addNewOrder(createScriptCommand() + tokens[0], true);
        legacy = true;
      } catch (NumberFormatException e) {
        //
      }
    }
    if (!legacy) {
      addNewOrder(currentOrder, false);
    }
    if (tokens.length > 1 && !legacy) {
      soldierTokens = new String[tokens.length];
      soldierTokens[0] = tokens[0];
      for (int i = 1; i < tokens.length; ++i) {
        soldierTokens[i] = tokens[i];
      }
    } else {
      soldierTokens = (tokens[0] + " " + SOLDIER_CREW).split(" ");
    }
    if (LEARN_CREW != null) {
      addToTeachPlugin(currentUnit, parseLearn(LEARN_CREW));
    }
    commandSoldier(soldierTokens);

    setConfirm(currentUnit, true);
  }

  private Collection<SkillSpec> parseLearn(String learnLine) {
    OrderedHashtable<SkillSpec, Object> parsed = new OrderedHashtable<SkillSpec, Object>();
    StringTokenizer tokens = new StringTokenizer(learnLine);
    while (tokens.hasMoreTokens()) {
      String t = tokens.nextToken();
      if (t.matches("[A-Z].*")) {
        int level = -1, max = -1;
        SkillType skill = getSkillType(t);
        try {
          level = Integer.parseInt(tokens.nextToken());
          max = Integer.parseInt(tokens.nextToken());
        } catch (NumberFormatException e) {
          //
        } catch (NoSuchElementException e) {
          //
        }
        if (skill != null && level > 0 && max > 0) {
          parsed.put(new SkillSpec(skill, level, max), "");
        } else {
          addNewWarning("error in skillLine :" + learnLine);
        }
      }
    }
    return parsed.keySet();
  }

  /**
   * <code>// $cript Quartiermeister [[Menge1 Gut 1]...]</code>: learn perception, allow listed
   * amount of goods. If other goods are detected, do not confirm orders.
   */
  protected void commandQuartermaster(String[] tokens) {
    int silver = 0;
    try {
      for (Item item : currentUnit.getItems()) {
        boolean okay = false;
        if (item.getItemType().getID().equals(EresseaConstants.I_USILVER)) {
          silver = item.getAmount();
          if (silver < 2000) {
            okay = true;
          }
        }
        for (int i = 1; !okay && i < tokens.length - 1; i += 2) {
          if (item.getName().equals(tokens[i + 1]) || item.getOrderName().equals(tokens[i + 1])) {
            if (item.getAmount() <= Integer.parseInt(tokens[i])) {
              okay = true;
              break;
            }
          }
        }
        setConfirm(currentUnit, okay);
      }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl ");
    }

    ArrayList<SkillSpec> qskills = new ArrayList<SkillSpec>(2);
    qskills.add(new SkillSpec(world.getRules().getSkillType(EresseaConstants.S_WAHRNEHMUNG), 10, 99));
    SkillType tactics = world.getRules().getSkillType(EresseaConstants.S_TAKTIK);
    if (tactics != null) {
      Skill utactics = currentUnit.getSkill(tactics);
      if ((utactics == null || utactics.getLevel() < QM_TACTICS) && silver > tactics.getCost(utactics == null ? 0
          : utactics.getLevel())) {
        qskills.add(new SkillSpec(tactics, QM_TACTICS * 2, QM_TACTICS));
      }
    }
    learn(currentUnit, qskills);

    setConfirm(currentUnit, true);
  }

  /**
   * <code>// $cript Sammler [frequenz]</code>: collect herbs if there are at least "viele",
   * research herbs every frequenz rounds.
   */
  protected void commandCollector(String[] tokens) {
    if (tokens.length > 2) {
      addNewError("zu viele Argumente");
    }
    int modulo = Integer.MAX_VALUE;
    if (tokens.length > 1) {
      try {
        modulo = Integer.parseInt(tokens[1]);
      } catch (NumberFormatException e) {
        addNewError("ungültige Zahl " + tokens[1]);
        return;
      }
    }

    if (currentRegion.getRegionType().isOcean()) {
      addNewError("Sammeln nicht möglich!");
      return;
    }
    removeOrdersLike(MAKEOrder + " " + "[^T].*", true);
    removeOrdersLike(getResearchOrder() + ".*", true);
    Date dateBefore = world.getDate().clone();
    dateBefore.setDate(world.getDate().getDate() - 1);
    if ((dateBefore.getSeason() == Date.WINTER && world.getDate().getSeason() == Date.WINTER)) {
      modulo = 2;
    }
    if ((modulo != Integer.MAX_VALUE && world.getDate().getDate() % modulo == 0)
        || currentRegion.getHerbAmount() == null
        || (!currentRegion.getHerbAmount().equals("viele") && !currentRegion.getHerbAmount().equals("sehr viele"))) {
      addNewOrder(getResearchOrder(), true);
    } else {
      addNewOrder(MAKEOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER"), true);
    }
    commandSupply(new String[] { "Versorge", KRAUTOrder, "100" });
  }

  /**
   * <code>KrautKontrolle [[[direction...] PAUSE]...]</code> move until the next PAUSE, if pause is
   * reached, research herbs.
   */
  protected void commandControl(String[] tokens) {
    for (Order o : currentUnit.getOrders2()) {
      String order = o.getText();
      if (order.startsWith(ROUTEOrder)) {
        if (order.substring(order.indexOf(" ")).trim().startsWith(PAUSEOrder)) {
          // end of route, FORSCHE
          addNewOrder(currentOrder, false);
          addNewOrder(getResearchOrder(), true);
          removeOrdersLike(ROUTEOrder + ".*", true);
        } else {
          // continue on route
          addNewOrder(currentOrder, false);
        }
        return;
      }
    }
    // no route order -- create new one
    removeOrdersLike(getResearchOrder() + ".*", true);
    StringBuilder newOrder = new StringBuilder();
    newOrder.append(createScriptCommand()).append(tokens[0]);
    StringBuilder moveOrder = new StringBuilder(ROUTEOrder);
    boolean pause = false;
    for (int i = 1; i < tokens.length; ++i) {
      if (!pause) {
        // add move order until first PAUSE
        moveOrder.append(" ").append(tokens[i]);
      } else {
        // add to $cript order after first PAUSE
        newOrder.append(" ").append(tokens[i]);
      }
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
        pause = true;
      }
    }
    moveOrder.append(" ").append(PAUSEOrder);
    pause = false;
    for (int i = 1; i < tokens.length; ++i) {
      if (!pause) {
        // append movement until first PAUSE to back of $cript order
        newOrder.append(" ").append(tokens[i]);
      }
      if (PAUSEOrder.equalsIgnoreCase(tokens[i])) {
        pause = true;
      }
    }
    addNewOrder(newOrder.toString(), true);
    addNewOrder(moveOrder.toString(), true);
  }

  /**
   * <code>// $cript RekrutiereMax [min [max]] [race]</code>: recruit as much as possible; warn if
   * less than min are possible
   */
  protected void commandRecruit(String[] tokens) {
    if (tokens.length > 4) {
      addNewError("zu viele Argumente");
    }
    int min = 1;
    String race = null;
    int max = Integer.MAX_VALUE;
    if (tokens.length > 1) {
      try {
        min = Integer.parseInt(tokens[1]);
        if (tokens.length > 2) {
          try {
            max = Integer.parseInt(tokens[2]);
            if (tokens.length > 3) {
              race = tokens[3];
            }
          } catch (NumberFormatException e) {
            max = Integer.MAX_VALUE;
            race = tokens[2];
            if (tokens.length > 3) {
              addNewError("zu viele Argumente");
            }
          }
        }
      } catch (NumberFormatException e) {
        min = 1;
        race = tokens[1];
        if (tokens.length > 2) {
          addNewError("zu viele Argumente");
        }
      }
    }

    Race effRace = race == null ? currentUnit.getRace() : helper.getRace(race);
    if (effRace == null) {
      addNewError("Unbekannte Rasse");
      return;
    }

    if (currentUnit.getPersons() != 0 && !effRace.equals(currentUnit.getRace())) {
      addNewWarning("race != unit race");
    }

    int amount = world.getGameSpecificRules().getRecruitmentLimit(currentUnit, effRace);

    if (currentUnit.getPersons() + amount >= max) {
      addNewWarning("Rekrutierung fertig");
      amount = Math.min(max - currentUnit.getPersons(), amount);
    }

    if (amount < min) {
      addNewError("Nicht genug Rekruten");
    }

    if (amount > 0) {
      if (effRace.getRecruitmentCosts() > 0) {
        int costs = amount * effRace.getRecruitmentCosts();
        int maxCosts =
            max == Integer.MAX_VALUE ? costs : (max - currentUnit.getPersons())
                * effRace.getRecruitmentCosts();
        addNeed("Silber", currentUnit, costs, maxCosts, DEFAULT_PRIORITY);
      } else {
        addNewWarning("Rekrutierungskosten unbekannt");
      }
      getRecruitOrder(amount, effRace);
      addNewOrder(getRecruitOrder(amount, effRace != currentUnit.getRace() ? effRace : null), true);
    }
  }

  /**
   * <code>// $cript Kommentar text</code>: add text after a semicolon
   */
  protected void commandComment(String[] tokens) {
    String rest = currentOrder.substring(currentOrder.indexOf(tokens[0]) + tokens[0].length());
    addNewOrder(";" + rest, true);

  }

  // ///////////////////////////////////////////////////////
  // HELPER functions
  // ///////////////////////////////////////////////////////

  protected boolean hasEntertain() {
    return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
        getLocalizedOrder(EresseaConstants.OC_ENTERTAIN, ENTERTAINOrder));
  }

  protected boolean hasTax() {
    return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
        getLocalizedOrder(EresseaConstants.OC_TAX, TAXOrder));
  }

  protected boolean hasWork() {
    return world.getGameSpecificStuff().getOrderChanger().isLongOrder(
        getLocalizedOrder(EresseaConstants.OC_WORK, WORKOrder));
  }

  protected void initSupply() {
    if (needQueue == null) {
      needQueue = new ArrayList<Need>();
    } else {
      needQueue.clear();
    }

    if (supplyMapp == null) {
      supplyMapp = new SupplyMap(this);
    } else {
      supplyMapp.clear();
    }
    if (capacities == null) {
      capacities = new HashMap<Unit, Integer>();
    } else {
      capacities.clear();
    }
    if (transfersMap == null) {
      transfersMap = new HashMap<Unit, Map<String, Integer>>();
      transferList = new ArrayList<Transfer>();
    } else {
      transfersMap.clear();
      transferList.clear();
    }
    if (dummyUnits == null) {
      dummyUnits = new HashMap<String, Unit>();
    } else {
      dummyUnits.clear();
    }

    for (Unit u : currentRegion.units()) {
      if (currentFactions.containsKey(u.getFaction())) {
        for (Item item : u.getItems()) {
          putSupply(item.getOrderName(), u, item.getAmount());
        }

        // TODO take RESERVE or GIVE orders into account?

        // // subtract reserved items from supply amount of unit
        // for (ReserveRelation relation : u.getRelations(ReserveRelation.class)) {
        // Supply supply = getSupply(relation.itemType.getName(), u);
        // if (supply != null && relation.source == u) {
        // supply.setAmount(supply.getAmount() - relation.amount);
        // }
        // }
        // // subtract transferred items (by GIVE orders) from supply amount of source unit (don't
        // add
        // // to target unit)
        // for (ItemTransferRelation relation : u.getRelations(ItemTransferRelation.class)) {
        // Supply supply = getSupply(relation.itemType.getName(), u);
        // if (supply != null && relation.source == u) {
        // supply.setAmount(supply.getAmount() - relation.amount);
        // }
        // }
      }
    }
  }

  /**
   * Adds a supply to the supplyMap
   *
   * @param item
   * @param unit
   * @param amount
   */
  protected Supply putSupply(String item, Unit unit, int amount) {
    return supplyMapp.put(item, unit, amount, ++supplySerial);
  }

  /**
   * Adds a supply to the supplyMap
   *
   * @param item
   * @param unit
   * @param amount
   */
  protected Supply putDummySupply(String item, Unit unit, int amount) {
    return supplyMapp.put(item, unit, amount, Long.MAX_VALUE);
  }

  /**
   * Tries to satisfy all needs in the current needMap by adding GIVE orders to suppliers.
   */
  protected void satisfyNeeds() {
    supplyMapp.sortByPriority();

    Need[] needs = needQueue.toArray(new Need[] {});
    sort(needs);
    adjustAlreadyTransferred(needs);
    Reserves reserves = new Reserves();

    pack(needs, reserves, false);
    enforceCapacities();
    pack(needs, reserves, true);

    executeReserves(reserves);
    executeTransferOrders();
    warnNeeds();
    warnCapacities();
  }

  private void pack(Need[] needs, Reserves reserves, boolean enforce) {
    for (int n = 0, prioStart = 0, state = 0; n < needs.length; ++n) {
      Need need = needs[n];
      if (need.getPriority() > needs[prioStart].getPriority())
        throw new RuntimeException("needs not sorted");

      switch (state) {
      case 0:
        reserveNeed(need, true, reserves);
        break;

      case 1:
        giveNeed(need, true);
        break;
      case 2:
        if (need.getAmount() != Integer.MAX_VALUE) {
          reserveNeed(need, false, reserves);
        }
        break;
      case 3:
        if (need.getAmount() != Integer.MAX_VALUE) {
          giveNeed(need, false);
        }
        break;
      case 4:
        if (need.getAmount() == Integer.MAX_VALUE) {
          // now, finally, satisfy infinite needs
          reserveNeed(need, false, reserves);
        }
        break;
      case 5:
        if (need.getAmount() == Integer.MAX_VALUE) {
          giveNeed(need, false);
        }
        break;
      }

      if (n == needs.length - 1 || needs[n + 1].getPriority() < needs[prioStart].getPriority()) {
        if (state < 5) {
          n = prioStart - 1;
          ++state;
        } else {
          state = 0;
          prioStart = n + 1;
          if (enforce) {
            enforceCapacities();
          }
        }
      }
    }
  }

  private void warnNeeds() {
    for (Need need : needQueue) {
      if (need.getMinAmount() > 0 && need.getWarning().contains(Warning.C_AMOUNT)) {
        addWarning(need.getUnit(), "braucht " + need.getMinAmount()
            + (need.getMaxAmount() != need.getMinAmount() ? ("/" + need.getMaxAmount()) : "")
            + " mehr " + need.getItem() + ", " + need.getMessage());
      }

      // add messages for unsatisfied needs
      if (need.getMinAmount() <= 0 && need.getMaxAmount() > 0
          && need.getMaxAmount() != Integer.MAX_VALUE) {
        addOrder(need.getUnit(), "; braucht " + need.getMaxAmount() + " mehr " + need.getItem()
            + ", "
            + need.getMessage(),
            false);
      }
    }
  }

  private void warnCapacities() {
    for (Entry<Unit, Integer> cap : capacities.entrySet()) {
      if (cap.getValue() < 0) {
        addWarning(cap.getKey(), "Kapazität überschritten um " + (-cap.getValue()));
      }
    }
  }

  private void sort(Need[] sorted) {
    for (int j = 1; j < sorted.length; ++j) {
      for (int i = 0; i < j; ++i) {
        if (sorted[j].compareTo(sorted[i]) < 0) {
          Need temp = sorted[i];
          sorted[i] = sorted[j];
          sorted[j] = temp;
        }
      }
    }
  }

  private void adjustAlreadyTransferred(Need[] needs) {
    for (Need need : needs) {
      adjustForTransfer(need);
    }
  }

  private void enforceCapacities() {
    for (int i = transferList.size() - 1; i >= 0; --i) {
      Transfer transfer = transferList.get(i);
      Integer cap;
      if ((cap = capacities.get(transfer.getTarget())) != null && cap < 0) {
        if (transfer.getUnit() != transfer.getTarget()) { // && transfer.isMin()
          int weight = getWeight(transfer.getItem());
          if (weight > 0) {
            int delta = cap / weight;
            if (delta * weight > cap) {
              --delta;
            }
            delta = Math.max(-transfer.getAmount(), delta);
            if (delta < 0) {
              undoTransfer(i, delta);
              i = transferList.size();
            }
          }
        }
      }
    }
  }

  private void undoTransfer(int index, int delta) {
    Transfer transfer = transferList.get(index);
    if (-delta < transfer.getAmount()) {
      transfer.reduceAmount(-delta);
    } else {
      transferList.remove(index);
    }
    if (-delta > getMulti(transfersMap, transfer.getTarget(), transfer.getItem())) {
      addNewError("ungültiger Transfer " + transfer);
    }
    increaseMulti(transfersMap, transfer.getTarget(), transfer.getItem(), delta);
    changeCapacity(transfer.getUnit(), transfer.getTarget(), transfer.getItem(), delta);

    getSupply(transfer.getItem(), transfer.getUnit()).reduceAmount(delta);
    if (transfer.isMin()) {
      transfer.getNeed().reduceMinAmount(delta);
    }
    transfer.getNeed().reduceMaxAmount(delta);

  }

  private void addTransfer(Unit giver, Unit receiver, String item, int amount, boolean min,
      boolean all,
      Need need) {
    transferList.add(new Transfer(giver, receiver, item, amount, min, all, need, ++supplySerial));
    increaseMulti(transfersMap, receiver, item, amount);
  }

  private void executeTransferOrders() {
    for (Transfer t : transferList) {
      if (t.getUnit() != t.getTarget()) {
        addOrder(t.getUnit(),
            getGiveOrder(t.getUnit(), t.getTarget().getID().toString(), t.getItem(),
                (t.isAll() ? Integer.MAX_VALUE : t.getAmount()), false)
                + COMMENTOrder + t.getMessage(), false);
      }
    }
  }

  private void transfer(Unit unit, Need need, int amount) {
    Supply supply = getSupply(need.getItem(), unit);
    if (supply.getAmount() < amount) {
      if (currentUnit == null) {
        addWarning(supply.getUnit(), "not enough " + need.getItem());
      } else {
        addNewWarning("not enough " + need.getItem() + " for " + supply.getUnit());
      }
    }

    need.reduceMaxAmount(amount);
    need.reduceMinAmount(amount);
    supply.reduceAmount(amount);
    if (unit != need.getUnit()) {
      changeCapacity(supply.getUnit(), need.getUnit(), need.getItem(), amount);
    }
  }

  private boolean isHorse(String item) {
    return HORSEItem.equals(item);
  }

  private void giveTransfer(String targetId, String item, int amount, boolean all) {
    Unit targetUnit = findUnit(targetId);
    Need dummyNeed = new Need(targetUnit, item, amount, amount, GIVE_IF_PRIORITY, new Warning(true),
        ++supplySerial);
    Supply supply = getSupply(item, currentUnit);
    all = all || (supply != null && supply.getAmount() == amount);
    addTransfer(currentUnit, targetUnit, item, amount, true, all, dummyNeed);
    transfer(currentUnit, dummyNeed, amount);
  }

  public static class MyReserveVisitor implements ReserveVisitor {

    public void execute(Unit u, String item, int amount) {
      if (amount > 0) {
        if (amount == u.getPersons()) {
          addOrder(u, getReserveOrder(u, item // + COMMENTOrder + need.toString()
              , 1, true), false);
        } else {
          addOrder(u, getReserveOrder(u, item, amount, false), false);
        }
      }
    }

  }

  private void executeReserves(Reserves reserves) {
    reserves.execute(new MyReserveVisitor());
  }

  private void changeCapacity(Unit source, Unit target, String item, int amount) {
    if (!isHorse(item)) {
      int weight = amount * getWeight(item);
      changeCapacity(source, weight);
      changeCapacity(target, -weight);
    }
  }

  private Integer changeCapacity(Unit unit, int delta) {
    Integer c = capacities.get(unit);
    if (c != null) {
      capacities.put(unit, c = c + delta);
    }
    return c;
  }

  /**
   * Tries to satisfy (minimum) need by a RESERVE order
   *
   * @param need
   * @param min
   * @param reserves
   */
  protected void reserveNeed(Need need, boolean min, Reserves reserves) {
    int amount = getNeedAmount(need, min);
    // amount = adjustForTransfer(need, amount);

    Supply supply = getSupply(need.getItem(), need.getUnit());
    if (supply == null)
      return;

    // only suppliers with positive priority serve maximum needs
    amount = Math.min(amount, supply.getAmount() - 0);
    if (amount > 0) {
      if (min) {
        addTransfer(need.getUnit(), need.getUnit(), need.getItem(), amount, min, false, need);
      }
      transfer(need.getUnit(), need, amount);
      if (min) {
        reserves.add(need.getItem(), need.getUnit(), amount);
      }
    }
  }

  /**
   * Tries to satisfy (minimum) need by a give order from suppliers.
   *
   * @param need
   * @param min
   */
  protected void giveNeed(Need need, boolean min) {
    int amount = getNeedAmount(need, min);
    if (amount > 0) {
      // adjustForTransfer(need, amount);
      for (Supply supply : supplyMapp.get(need.getItem())) {
        if (supply.getUnit() != need.getUnit() && (min || supply.getPriority() > 0) && supply
            .hasPriority()) {
          int giveAmount = Math.min(amount, supply.getAmount());
          if (giveAmount > 0) {
            addTransfer(supply.getUnit(), need.getUnit(), need.getItem(), giveAmount, min, false,
                need);
            transfer(supply.getUnit(), need, giveAmount);
            amount -= giveAmount;
          }
        }
        if (amount <= 0) {
          break;
        }
      }
    }
  }

  private int getNeedAmount(Need need, boolean min) {
    int amount = min ? need.getMinAmount() : need.getAmount();
    return amount;
  }

  private int adjustForTransfer(Need need) {
    int amount = need.getMaxAmount();
    Integer transferred = getMulti(transfersMap, need.getUnit(), need.getItem());
    if (transferred != null) {
      if (transferred > amount) {
        increaseMulti(transfersMap, need.getUnit(), need.getItem(), -amount);
        need.setMinAmount(0);
        need.setMaxAmount(0);
        amount = 0;
      } else {
        removeMulti(transfersMap, need.getUnit(), need.getItem());
        need.reduceMinAmount(transferred);
        need.reduceMaxAmount(transferred);
        amount -= transferred;
      }
    }
    return amount;
  }

  protected int getWeight(String item) {
    return Math.round(world.getRules().getItemType(item).getWeight() * 100);
  }

  /**
   * Returns the total supply for an item.
   *
   * @param item Order name of the supplied item
   * @return The supply or 0, if none has been registered.
   */
  protected int getSupply(String item) {
    return supplyMapp.getSupply(item);
  }

  /**
   * Returns a supply of a unit for an item.
   *
   * @param item Order name of the supplied item
   * @param unit
   * @return The supply or null, if none has been registered.
   */
  protected Supply getSupply(String item, Unit unit) {
    return supplyMapp.get(item, unit);
  }

  /**
   * Adds the specified amounts to the need of a unit for an item to the needMap.
   *
   * @param item Order name of the required item
   * @param unit
   * @param minAmount
   * @param maxAmount
   */
  protected void addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority) {
    addNeed(item, unit, minAmount, maxAmount, priority, new Warning(true));
  }

  /**
   * Adds the specified amounts to the need of a unit for an item to the needMap.
   *
   * @param item Order name of the required item
   * @param unit
   * @param minAmount
   * @param maxAmount
   */
  protected Need addNeed(String item, Unit unit, int minAmount, int maxAmount, int priority,
      Warning w) {
    Need result;
    if (world.getRules().getItemType(StringID.create(item)) == null && w.contains(~0)) {
      addNewWarning("unknown item " + item);
    }

    if (!currentFactions.containsKey(unit.getFaction())) {
      int count;
      if (getSupply(item, unit) == null && (count = getItemCount(unit, item)) > 0) {
        putDummySupply(item, unit, count);
      }
    }
    if (minAmount > maxAmount) {
      addNewError("min amount " + minAmount + " > max amount " + maxAmount);
      maxAmount = minAmount;
    }
    needQueue.add(result = new Need(unit, item, minAmount, maxAmount, priority, w, ++supplySerial));

    return result;
  }

  /**
   * Marks the unit as soldier. Learns its best weapon skill. Reserves suitable weapon, armor, and
   * shield if available.
   *
   * @param u The unit in question
   * @param sWeaponSkill The desired skill. If <code>null</code>, the unit's best weapon skill is
   *          used. If the unit knows no weapon skill, a warning is issued.
   * @param sWeapon The desired weapon that is reserved. If this is <code>null</code>, a weapon that
   *          matches the weaponSkill is reserved.
   * @param sArmor The desired armor. If <code>null</code>, a suitable armor is reserved. If the
   *          unit has no armor at all, <em>no</em> warning is issued.
   * @param sShield The desired shield. If <code>null</code>, a suitable shield is reserved. If the
   *          unit has no shield at all, <em>no</em> warning is issued.
   * @param warning Warnings for missing equipment are only issued if this is <code>true</code>.
   */
  protected void soldier(Unit u, String sWeaponSkill, String sWeapon, String sShield,
      String sArmor, String warning) {

    Rules rules = world.getRules();

    SkillType weaponSkill =
        sWeaponSkill == null ? null : rules.getSkillType(StringID.create(sWeaponSkill));
    ItemType weapon = sWeapon == null ? null : rules.getItemType(StringID.create(sWeapon));
    ItemType armor = sArmor == null ? null : rules.getItemType(StringID.create(sArmor));
    ItemType shield = sShield == null ? null : rules.getItemType(StringID.create(sShield));

    if (weaponSkill == null || BEST.equals(sWeaponSkill)) {
      int max = 0;
      for (Skill skill : u.getSkills()) {
        if (isWeaponSkill(skill) && skill.getLevel() > max) {
          max = skill.getLevel();
          weaponSkill = skill.getSkillType();
        }
      }
      if (weaponSkill == null && !Warning.W_NEVER.equals(warning)) {
        addNewWarning("kein Kampftalent");
        return;
      }
    }

    ArrayList<Item> weapons = new ArrayList<Item>();
    if (weapon == null) {
      for (Item item : u.getItems()) {
        if (isUsable(item, weaponSkill)) {
          weapons.add(item);
        }
      }
      if (weapons.isEmpty()) {
        for (ItemType type : rules.getItemTypes()) {
          if (isUsable(type, weaponSkill)) {
            weapons.add(new Item(type, 0));
            break;
          }
        }
      }
      if (weapons.isEmpty()) {
        addNewError("keine passenden Waffen bekannt für " + weaponSkill);
      }
    } else {
      if (u.getItem(weapon) != null) {
        weapons.add(u.getItem(weapon));
      } else {
        addNewError("keine " + weapon);
      }
    }

    // note that "shield" is a subcategory of "armour"
    ArrayList<Item> shields = findItems(shield, u, "shield", true);
    if (shields.isEmpty()) {
      addNewError("keine Schilde bekannt");
    }

    ArrayList<Item> armors = findItems(armor, u, "armour", true);
    if (armors.isEmpty()) {
      addNewError("keine Rüstungen bekannt");
    }

    Collections.sort(weapons, new ItemSorter(WEAPON_PRIORITIES));
    Collections.sort(shields, new ItemSorter(SHIELD_PRIORITIES));
    Collections.sort(armors, new ItemSorter(ARMOR_PRIORITIES));

    if (weaponSkill != null) {
      List<SkillSpec> targetSkills = new LinkedList<SkillSpec>();
      targetSkills.add(new SkillSpec(weaponSkill, DEFAULT_LEARNLEVEL, 99));
      if (weaponSkill.getName().equals("Hiebwaffen") || weaponSkill.getName().equals("Stangenwaffen")) {
        int weeks = (DEFAULT_LEARNLEVEL) * (DEFAULT_LEARNLEVEL + 1) / 2; // weeks for melee
        int weeks2 = (DEFAULT_LEARNLEVEL + 2) * (DEFAULT_LEARNLEVEL + 3) / 2; // weeks for melee + 2
        int maxskill = (int) (-.5 + Math.sqrt(.25 + 2 * (weeks2 - weeks)));
        targetSkills.add(new SkillSpec(getSkillType(S_ENDURANCE), maxskill, 99));
        // getSkillType(S_ENDURANCE), Math.round(ENDURANCERATIO_FRONT * DEFAULT_LEARNLEVEL), 99));
      } else {
        int weeks = (DEFAULT_LEARNLEVEL) * (DEFAULT_LEARNLEVEL + 1) / 2; // weeks for melee
        int weeks2 = (DEFAULT_LEARNLEVEL + 1) * (DEFAULT_LEARNLEVEL + 2) / 2; // weeks for melee + 1
        int maxskill = (int) (-.5 + Math.sqrt(.25 + 2 * (weeks2 - weeks)));
        targetSkills.add(new SkillSpec(getSkillType(S_ENDURANCE), maxskill, 99));
        // getSkillType(S_ENDURANCE), Math.round(ENDURANCERATIO_BACK * DEFAULT_LEARNLEVEL), 99));
      }
      learn(u, targetSkills);
    }

    if (!NULL.equals(sWeapon)
        && !reserveEquipment(weapon, weapons, !Warning.W_NEVER.equals(warning) && !Warning.W_SKILL
            .equals(warning))) {
      addNewError("konnte Waffe nicht reservieren");
    }
    if (!NULL.equals(sShield)
        && !reserveEquipment(shield, shields, Warning.W_ARMOR.equals(warning) || Warning.W_SHIELD
            .equals(warning))) {
      addNewError("konnte Schilde nicht reservieren");
    }
    if (!NULL.equals(sArmor) && !reserveEquipment(armor, armors, Warning.W_ARMOR.equals(warning))) {
      addNewError("konnte Rüstung nicht reservieren");
    }
  }

  /**
   * Tries to learn the skills at the ratio reflected in the skills argument.
   *
   * @param u
   * @param targetSkills
   */
  protected void learn(Unit u, Collection<SkillSpec> targetSkills) {

    if (hasTeachPlugin()) {
      addToTeachPlugin(currentUnit, targetSkills);
      return;
    }

    if (findLongOrder(u)) {
      addOrder(u, "; langer Befehl gefunden, Einheit wird nicht gelehrt", false);
      return;
    }

    // find skill with maximum priority
    double maxWeight = 0;
    SkillSpec maxSkill = null;
    StringBuilder comment = new StringBuilder(";");
    for (SkillSpec skill : targetSkills) {
      double weight = calcSkillWeight(u, skill, targetSkills);
      comment.append(" ").append(skill.toString()).append(" ").append(weight);
      if (weight > maxWeight) {
        maxWeight = weight;
        maxSkill = skill;
      }
    }

    addNewOrder(comment.toString(), true);

    if (maxSkill == null) {
      addNewError("kein Kampftalent");
    } else {
      removeOrdersLike(LEARNOrder + ".*", true);
      removeOrdersLike(TEACHOrder + ".*", true);
      addNewOrder(LEARNOrder + " " + maxSkill.skill.getName(), true);
    }
  }

  private boolean hasTeachPlugin() {
    return TEACH_PREFIX != null;
  }

  private void addToTeachPlugin(Unit unit, Collection<SkillSpec> targetSkills) {
    Collection<SkillSpec> oldskills = skills.get(unit);
    if (oldskills != null) {
      Map<SkillType, SkillSpec> map = new OrderedHashtable<SkillType, SkillSpec>();
      for (SkillSpec spec : targetSkills) {
        map.put(spec.skill, spec);
      }
      for (SkillSpec spec : oldskills) {
        map.put(spec.skill, spec);
      }
      for (SkillSpec spec : targetSkills) {
        map.put(spec.skill, spec);
      }

      skills.put(unit, map.values());
    } else {
      skills.put(unit, targetSkills);
    }
  }

  private void teachUnits() {
    for (Unit u : skills.keySet()) {
      if (!findLongOrder(u)) {
        StringBuilder learnOrder = new StringBuilder(String.format("; $%s$L 100.0", TEACH_PREFIX));
        for (SkillSpec s : skills.get(u)) {
          learnOrder.append(String.format(" %s %d %d", s.skill.getName(), s.level, s.max));
        }
        addOrder(u, String.format("; $%s$T ALLES 0", TEACH_PREFIX), true);
        addOrder(u, learnOrder.toString(), true);
      } else {
        addOrder(u, "; langer Befehl gefunden, Einheit wird nicht gelehrt", false);
      }
    }
  }

  private boolean findLongOrder(Unit u) {
    boolean teach = true;
    for (Order order : u.getOrders2()) {
      if (order.isLong() && !order.getText().startsWith(TEACHOrder) && !order.getText().startsWith(LEARNOrder))
        return true;
    }
    return false;
  }

  /**
   * Returns a weight ("importance") of a skill according to target values and the unit's current
   * skill levels.
   */
  protected static double calcSkillWeight(Unit u, SkillSpec learningSkill,
      Collection<SkillSpec> targetSkills) {
    if (learningSkill == null)
      return 0;
    double prio = 0.5;

    // calc max mult
    SkillSpec learningTarget = null;
    double maxMult = 0;

    for (SkillSpec skill2 : targetSkills) {
      if (skill2.skill.equals(learningSkill.skill)) {
        learningTarget = skill2;
      }
      int level = Math.max(1, getSkillLevel(u, skill2.skill));
      if (level < skill2.max) {
        double mult = level / (double) skill2.level;
        if (maxMult < mult) {
          maxMult = mult;
        }
      }
    }

    if (learningTarget == null)
      // learned skill is not in target skills
      return 0.01 * learningSkill.level;

    if (maxMult > 0) {
      // calc max normalized learning weeks
      double maxWeeks = 0;
      for (SkillSpec skill2 : targetSkills) {
        int currentLevel = Math.max(1, getSkillLevel(u, skill2.skill));
        if (currentLevel < skill2.max) {
          double weeks = skill2.level - currentLevel / maxMult + 2;
          if (maxWeeks < weeks) {
            maxWeeks = weeks;
          }
        }
      }
      int level = Math.max(1, getSkillLevel(u, learningSkill.skill));
      if (level >= learningSkill.max) {
        prio = 0.01d;
      } else {
        // prio should be between .4 and 1
        prio = .4 + .6 * (learningTarget.level - level / maxMult + 2) / maxWeeks;
      }
      if (prio < 0) {
        prio = prio * 1.000001;
      }
    }
    return prio;
  }

  protected static int getSkillLevel(Unit u, SkillType skill) {
    Skill uskill = u.getSkill(skill);
    return uskill == null ? 0 : uskill.getLevel();
  }

  /**
   * If <code>itemType==null</code> return all the unit's items of the given category. If no item is
   * found, at least one item (with amount 0) is returned.
   */
  protected static ArrayList<Item> findItems(ItemType itemType, Unit u, String category,
      boolean returnDummy) {
    ArrayList<Item> items = new ArrayList<Item>(1);
    ItemCategory itemCategory = world.getRules().getItemCategory(StringID.create(category));
    if (itemType == null) {
      for (Item item : u.getItems()) {
        if (itemCategory.equals(item.getItemType().getCategory())) {
          items.add(item);
        }
      }
    } else {
      if (u.getItem(itemType) != null) {
        items.add(u.getItem(itemType));
      }
    }
    if (items.isEmpty() && returnDummy) {
      for (Object o : itemCategory.getInstances()) {
        ItemType type = (ItemType) o;
        items.add(new Item(type, 0));
        break;
      }
    }
    return items;
  }

  protected boolean reserveEquipment(ItemType preferred, List<Item> ownStuff, boolean warn) {
    if (preferred != null) {
      // reserve requested weapon
      commandNeed(new String[] { "Benoetige", EACHOrder, "1", preferred.getOrderName(),
          String.valueOf(DEFAULT_PRIORITY) });
    } else if (!ownStuff.isEmpty()) {
      int supply = 0;
      for (Item w : ownStuff) {
        // reserve all matching weapons, except first one
        if (supply > 0) {
          commandNeed(new String[] { "Benoetige",
              Integer.toString(Math.min(currentUnit.getPersons() - supply, w.getAmount())),
              w.getOrderName(), String.valueOf(DEFAULT_PRIORITY) });
        }
        supply += w.getAmount();
        if (supply >= currentUnit.getPersons()) {
          supply = currentUnit.getPersons();
          break;
        }
      }

      // now reserve the first matching weapon with
      // min=w.getAmount(), max=w.getPersons()-other weapons
      Item w = ownStuff.get(0);
      String max =
          Integer.toString(Math.max(0, Math.min(currentUnit.getPersons() - supply + w.getAmount(),
              currentUnit.getPersons())));
      String min = warn ? max : Integer.toString(Math.min(currentUnit.getPersons(), w.getAmount()));
      commandNeed(new String[] { "Benoetige", min, max, w.getOrderName(),
          String.valueOf(DEFAULT_PRIORITY) });
    } else
      return false;
    return true;
  }

  @SuppressWarnings("unused")
  private static Integer putMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2,
      Integer value) {
    Map<String, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<String, Integer>());
    }
    Integer old = inner.put(key2, value);
    return old;
  }

  private static void increaseMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2,
      int value) {
    Map<String, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<String, Integer>());
    }
    Integer old = inner.get(key2);
    if (old == null) {
      inner.put(key2, value);
    } else {
      inner.put(key2, old + value);
    }
  }

  static void increaseMultiInv(Map<String, Map<Unit, Integer>> map, String key1, Unit key2,
      int value) {
    Map<Unit, Integer> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<Unit, Integer>());
    }
    Integer old = inner.get(key2);
    if (old == null) {
      inner.put(key2, value);
    } else {
      inner.put(key2, old + value);
    }
  }

  @SuppressWarnings("unused")
  private static void appendMulti(Map<Object, Map<Object, Collection<Integer>>> map, Object key1,
      Object key2,
      Integer value) {
    Map<Object, Collection<Integer>> inner = map.get(key1);
    if (inner == null) {
      map.put(key1, inner = new HashMap<Object, Collection<Integer>>());
    }
    Collection<Integer> old = inner.get(key2);
    if (old == null) {
      inner.put(key2, old = new ArrayList<Integer>());
    }
    old.add(value);
  }

  private static Integer getMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2) {
    Map<String, Integer> inner = map.get(key1);
    if (inner != null)
      return inner.get(key2);
    return null;
  }

  private static Integer removeMulti(Map<Unit, Map<String, Integer>> map, Unit key1, String key2) {
    Map<String, Integer> inner = map.get(key1);
    if (inner != null) {
      Integer val = inner.remove(key2);
      if (inner.isEmpty()) {
        map.remove(key1);
      }
      return val;
    }
    return null;
  }

  // ///////////////////////////////////////////////////////
  // HELPER functions
  // ///////////////////////////////////////////////////////

  /**
   * Adds a warning message (with a to do tag) directly to the unit's orders.
   *
   * @param unit
   * @param text
   */
  public static void addWarning(Unit unit, String text) {
    helper.addOrder(unit, "; TODO: " + text);
    setConfirm(unit, false);
  }

  public static void addOrder(Unit u, String order, boolean refresh) {
    u.addOrder(order, refresh);
  }

  /**
   * If confirm is false, mark unit as not confirmable. If confirm is true, mark it as confirmable
   * if it has not been marked as unconfirmable before (unconfirm always overrides confirm).
   *
   * @param u
   * @param confirm
   */
  public static void setConfirm(Unit u, boolean confirm) {
    String tag = getProperty(u, "confirm");
    if (confirm) {
      if (tag.length() == 0) {
        setProperty(u, "confirm", "1");
      }
    } else if (!confirm) {
      setProperty(u, "confirm", "0");
    }
  }

  /**
   * Add a property (in form of a tag) to the unit.
   *
   * @param u
   * @param tagName
   * @param value
   * @see #getProperty(Unit, String)
   */
  public static void setProperty(Unit u, String tagName, String value) {
    u.putTag(scriptMarker + "." + tagName, value);
    // u.addOrder("; $cript " + tagName + ":" + value, false, 0);
  }

  /**
   * Returns a property value (from a tag) from the unit.
   *
   * @param u
   * @param tagName
   * @return The value of property <code>tagName</code> or "" if the property has not been set.
   */
  public static String getProperty(Unit u, String tagName) {
    String tag = u.getTag(scriptMarker + "." + tagName);
    return tag == null ? "" : tag;
  }

  /**
   * Returns the ItemType in the rules matching <code>name</code>.
   *
   * @param name A item type name, like "Silber"
   * @return The ItemType corresponding to name or <code>null</code> if this ItemType does not
   *         exist.
   */
  public static ItemType getItemType(String name) {
    return world.getRules().getItemType(name);
  }

  /**
   * Returns the SkillType in the rules matching <code>name</code>.
   *
   * @param name A skill name, like "Ausdauer"
   * @return The SkillType corresponding to name or <code>null</code> if this SkillType does not
   *         exist.
   */
  public static SkillType getSkillType(String name) {
    return world.getRules().getSkillType(name);
  }

  /**
   * Returns a skill with the given name and level.
   *
   * @param name
   * @param level
   * @return A skill with the given level or <code>null</code> if this SkillType does not exist.
   */
  public static Skill getSkill(String name, int level) {
    if (getSkillType(name) != null)
      return new Skill(getSkillType(name), 0, level, 1, true);
    else
      return null;
  }

  /**
   * Notifies the interface that the unit should be updated.
   *
   * @param u This unit is updated in the UI
   * @deprecated I don't think this is needed any more.
   */
  @Deprecated
  public static void notifyMagellan(Unit u) {
    helper.updateUnit(u);
  }

  /**
   * Adds a GIVE order to the unit's orders, like <code>GIVE receiver [EACH] amount item</code>.
   *
   * @param unit The order is added to this unit's orders
   * @param receiver
   * @param item
   * @param amount
   * @param each
   */
  public static void addGiveOrder(Unit unit, Unit receiver, String item, int amount, boolean each) {
    helper.addOrder(unit, getGiveOrder(unit, receiver.getID().toString(), item, amount, each));
  }

  /**
   * Returns a line like <code>GIVE receiver [EACH] amount item</code>.
   *
   * @param unit
   * @param receiver
   * @param item
   * @param amount If <code>amount == Integer.MAX_VALUE</code>, amount is replaced by ALL
   * @param each
   * @return a line like <code>GIVE receiver [EACH] amount item</code>.
   */
  public static String getGiveOrder(Unit unit, String receiver, String item, int amount,
      boolean each) {
    return helper.getGiveOrder(unit, receiver, item, amount, each);
  }

  /**
   * Adds a RESERVE order to the unit's orders, like <code>RESERVE [EACH] amount item</code>.
   *
   * @param unit The order is added to this unit's orders
   * @param item
   * @param amount
   * @param each
   */
  public static void addReserveOrder(Unit unit, String item, int amount, boolean each) {
    helper.addOrder(unit, getReserveOrder(unit, item, amount, each));
  }

  /**
   * Returns a line like <code>RESERVE [EACH] amount item</code>.
   *
   * @param unit
   * @param item
   * @param amount
   * @param each
   * @return a line like <code>GIVE receiver [EACH] amount item</code>.
   */
  public static String getReserveOrder(Unit unit, String item, int amount, boolean each) {
    return helper.getReserveOrder(unit, item, amount, each);
  }

  /**
   * Returns a <code>RESEARCH HERBS</code> order.
   */
  protected String getResearchOrder() {
    return RESEARCHOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER");
  }

  /**
   * Returns a <code>RECRUIT amount race</code> order.
   */
  protected String getRecruitOrder(int amount, Race race) {
    if (race != null)
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount, race });
    else
      return getLocalizedOrder(EresseaConstants.OC_RECRUIT, new Object[] { amount });
    // return RECRUITOrder + " " + amount
    // + (race != null ? (" " + getLocalizedOrder("race." + race.getID(), race.getName())) : "");
  }

  /**
   * Return the amount of item that a unit has.
   *
   * @param unit
   * @param item
   * @return The amount of item in the unit's items
   */
  public static int getItemCount(Unit unit, String item) {
    return helper.getItemCount(unit, item);
  }

  /**
   * Return the amount of silver of the unit.
   *
   * @param unit
   * @return The amount of silver in this unit's items
   */
  public static int getSilber(Unit unit) {
    return Math.max(getItemCount(unit, "Silver"), getItemCount(unit, "Silber"));
  }

  /**
   * Returns true if the item is usable with weaponSkill, false otherwise.
   */
  protected static boolean isUsable(ItemType type, SkillType weaponSkill) {
    return (type.getUseSkill() != null && type.getUseSkill().getSkillType().equals(weaponSkill));
  }

  /**
   * Returns true if the item is usable with weaponSkill, false otherwise.
   */
  protected static boolean isUsable(Item item, SkillType weaponSkill) {
    return isUsable(item.getItemType(), weaponSkill);
  }

  /**
   * Returns true if skill is a weapon skill.
   */
  protected static boolean isWeaponSkill(Skill skill) {
    return skill.getSkillType().getID().equals(EresseaConstants.S_HIEBWAFFEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_STANGENWAFFEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_BOGENSCHIESSEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_ARMBRUSTSCHIESSEN)
        || skill.getSkillType().getID().equals(EresseaConstants.S_KATAPULTBEDIENUNG);
  }

  /**
   * This method tries to find out, if the unit has a weapon and a skill to use this weapon.
   */
  public static boolean isSoldier(Unit unit) {
    Collection<Item> items = unit.getItems();
    ItemCategory weapons = world.getRules().getItemCategory(StringID.create("weapons"));
    for (Item item : items) {
      if (weapons.isInstance(item.getItemType())) {
        // ah, a weapon...
        Skill useSkill = item.getItemType().getUseSkill();
        if (useSkill != null) {
          // okay, has the unit the skill?
          for (Skill skill : unit.getSkills()) {
            if (useSkill.getSkillType().equals(skill.getSkillType()))
              return true;
          }
        }
      }
    }
    return false;
  }

  /**
   * Parses all units in the region for orders like "; $xyz$sl" and adds a tag with name
   * "ejcTaggableComparator5" and value "xyz" for them.
   *
   * @param r
   */
  public static void parseShipLoaderTag(magellan.library.Region r) {
    for (Unit u : r.units()) {
      String name = null;
      for (Order order : u.getOrders2()) {
        String line = order.getText();
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]sl.*");
        java.util.regex.Matcher m = p.matcher(line);
        if (m.matches()) {
          name = m.group(1);
        }
      }
      if (name != null) {
        u.putTag("ejcTaggableComparator5", name);
      } else {
        u.removeTag("ejcTaggableComparator5");
      }
    }
  }

  /**
   * Parses all units in the region for orders like "; $xyz$verlassen" and adds a tag with name
   * "ejcTaggableComparator5" and value "xyz" for them.
   *
   * @param r
   */
  public static void parseShipLoaderTag2(magellan.library.Region r) {
    for (Unit u : r.units()) {
      String name = null;
      for (Order line : u.getOrders2()) {
        java.util.regex.Pattern p = Pattern.compile(".*[$]([^$]*)[$]verlassen.*");
        java.util.regex.Matcher m = p.matcher(line.getText());
        if (m.matches()) {
          name = m.group(1);
        }
      }
      if (name != null && name.equals("633")) {
        u.putTag("ejcTaggableComparator5", name);
      } else {
        u.removeTag("ejcTaggableComparator5");
      }
    }
  }

  /**
   * Converts some Vorlage commands to $cript commands. Call from the script of your faction
   * with<br />
   * <code>(new E3CommandParser(world, helper)).convertVorlage((Faction) container, null);</code>
   * (all regions) or from a region with<br />
   * <code>(new E3CommandParser(world, helper)).convertVorlage(helper.getFaction("1wpy"),
   * (Region) container);</code>.
   *
   * @param faction
   * @param region
   */
  public void convertVorlage(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);

    initLocales();

    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());

      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);

        convertVorlage(faction, r);
      }
      return;
    }

    // loop for all units of faction
    final String scriptStart = createScriptCommand();
    for (Unit u : region.units())
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);

        setCurrentUnit(u);

        StringBuilder lerneOrder = null;

        // loop over orders
        line = 0;
        for (Order o : u.getOrders2()) {
          ++line;
          currentOrder = o.getText();

          if (currentOrder.startsWith("// #")) {
            String[] tokens = detectVorlageCommand(currentOrder);
            if (tokens != null) {
              String command = tokens[0];

              // add vorlage as comment
              addNewOrder(COMMENTOrder + " " + currentOrder, true);

              // detect commands
              if (command.equals("Ausruestung")) {
                for (int i = 1; i < tokens.length; ++i) {
                  if (tokens[i].equals("Ausruestung")) {
                    continue;
                  }
                  try {
                    int number = Integer.parseInt(tokens[i]);
                    addNewOrder(scriptStart + "Benoetige " + number + " " + tokens[++i], true);
                  } catch (Exception e) {
                    addNewOrder(scriptStart + "Benoetige JE 1 " + tokens[i], true);
                  }
                }
              } else if (command.equals("AutoBestaetigen")) {
                addNewOrder(scriptStart + "auto " + (tokens[1].equals("an") ? "" : NOT), true);
              } else if (command.equals("Benoetige")) {
                if (tokens.length > 3 && tokens[3].equals("aus")) {
                  addNewOrder(scriptStart + "Benoetige 0 " + tokens[1] + " " + tokens[2], true);
                } else {
                  addNewOrder(scriptStart + "Benoetige " + tokens[1] + " " + tokens[2], true);
                }
              } else if (command.startsWith("BerufBotschafter")) { // also BerufBotschafterSTM
                addNewOrder(scriptStart + " BerufBotschafter "
                    + (tokens.length > 1 ? tokens[1] : ""), true);
              } else if (command.equals("BerufDepotVerwalter")) {
                int number = Integer.MIN_VALUE;
                if (tokens.length > 1) {
                  try {
                    number = Integer.parseInt(tokens[1]);
                  } catch (Exception e) {
                    number = Integer.MIN_VALUE;
                  }
                }
                addNewOrder(scriptStart + "BerufDepotVerwalter " + (number > 0 ? number : ""),
                    true);
              } else if (command.equals("BerufWahrnehmer")) {
                addNewOrder(scriptStart
                    + "Lerne "
                    + world.getRules().getSkillType(EresseaConstants.S_WAHRNEHMUNG.toString())
                        .getName() + " 10", true);
              } else if (command.equals("Depot")) {
                addNewOrder(scriptStart + "Benoetige " + ALLOrder, true);
              } else if (command.equals("Versorge")) {
                addNewOrder(COMMENTOrder + " Versorge ignored", true);
              } else if (command.equals("Erlerne")) {
                // prepare Lerne order, but write it later to include several consecutive Erlerne
                // orders
                if (lerneOrder == null) {
                  lerneOrder = new StringBuilder();
                  lerneOrder.append(scriptStart).append("Lerne");
                }
                lerneOrder.append(" ").append(tokens[1]).append(" ").append(
                    tokens.length > 2 ? tokens[2] : "10");
                // ignore warning argument "an/aus"
              } else if (command.equals("Ernaehre")) {
                StringBuilder order = new StringBuilder(scriptStart);
                for (String token : tokens) {
                  if (order.length() > 0) {
                    order.append(" ");
                  }
                  order.append(token);
                }
                addNewOrder(order.toString(), true);
              } else if (command.equals("GibKraeuter")) {
                addNewOrder(scriptStart + "GibWenn " + tokens[1] + " KRAUT", true);
              } else if (command.equals("GibWenn")) {
                StringBuilder order = new StringBuilder(scriptStart);
                for (int i = 0; i < tokens.length - 1; ++i) {
                  if (order.length() > 0) {
                    order.append(" ");
                  }
                  order.append(tokens[i]);
                }
                if (tokens[tokens.length - 1].equals("aus")) {
                  order.append(" Menge");
                }
                if (tokens[tokens.length - 1].equals("nie")) {
                  order.append(" nie");
                }
                addNewOrder(order.toString(), true);
                if (command.equals("Steuermann") && tokens.length != 3) {
                  addNewError("unzulässige Argumente");
                }
              } else if (command.equals("VersorgeFremd") || command.equals("BenoetigeFremd")) {
                if (tokens.length > 3 && tokens[3].equals("aus")) {
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " 0 " + tokens[2] + " "
                      + tokens[3], true);
                } else {
                  addNewOrder(scriptStart + "BenoetigeFremd " + tokens[1] + " " + tokens[2] + " "
                      + tokens[3], true);
                }
              } else if (command.equals("Warnung")) {
                StringBuilder order = new StringBuilder(scriptStart);
                order.append("+0 ");
                for (int i = 1; i < tokens.length; ++i) {
                  order.append(tokens[i]);
                }
                addNewOrder(order.toString(), true);
              } else if (command.equals("Warnung")) {
                // ignore
              } else if (command.equals("Erlaube") || command.equals("Verlange")
                  || command.equals("Handel") || command.equals("Mannschaft")
                  || command.equals("Quartiermeister") || command.equals("Steuermann")
                  || command.equals("Ueberwache")) {
                // simply copy order
                StringBuilder order = new StringBuilder(scriptStart);
                for (String token : tokens) {
                  if (order.length() > scriptStart.length()) {
                    order.append(" ");
                  }
                  order.append(token);
                }
                addNewOrder(order.toString(), true);
                if (command.equals("Steuermann") && tokens.length != 3) {
                  addNewError("unzulässige Argumente");
                }

              } else {
                addNewOrder(currentOrder, false);
                addNewError("unbekannter Befehl: " + command);
              }
            } else if (currentOrder.startsWith("// #forever { ")) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
              addNewOrder("@" + currentOrder.substring(14, currentOrder.lastIndexOf("}") - 1),
                  true);
            } else if (currentOrder.startsWith("// #default")) {
              // ignore
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
            } else {
              addNewOrder(currentOrder, false);
              addNewError("unbekannter Befehl: " + currentOrder);
              log.fine("unknown order " + currentOrder);
            }
          } else {
            addNewOrder(currentOrder, false);
          }
          currentOrder = null;

        }
        if (lerneOrder != null) { // unwritten Lerne order
          addNewOrder(lerneOrder.toString(), true);
          lerneOrder = null;
        }

        updateCurrentOrders();
        setCurrentUnit(null);
      }
  }

  /**
   * If order is a vorlage command ("// #call Name ..."), returns a List of the tokens. Otherwise
   * returns <code>null</code>. The first in the list is the first token after the "// #call".
   */
  protected static String[] detectVorlageCommand(String order) {
    StringTokenizer tokenizer = new StringTokenizer(order, " ");
    if (tokenizer.hasMoreTokens()) {
      String part = tokenizer.nextToken();
      if (part.equals(PCOMMENTOrder)) {
        if (tokenizer.hasMoreTokens()) {
          part = tokenizer.nextToken();
          if (part.equals("#call")) {
            List<String> result = new ArrayList<String>();
            while (tokenizer.hasMoreTokens()) {
              result.add(tokenizer.nextToken());
            }
            if (result.size() == 0)
              return null;
            return result.toArray(new String[] {});
          }
        }
      }
    }
    return null;
  }

  /**
   * @param u
   * @param orderFilter
   */
  protected boolean changeOrders(Unit u, OrderFilter orderFilter) {
    List<String> newOrders2 = new ArrayList<String>();
    boolean changedOrders2 = false;

    // loop over orders
    for (Order command : u.getOrders2()) {
      if (orderFilter.changeOrder(command)) {
        changedOrders2 = true;
        if (orderFilter.changedOrder() != null) {
          newOrders2.add(orderFilter.changedOrder());
        }
      } else {
        newOrders2.add(command.getText());
      }
    }
    if (changedOrders2) {
      u.setOrders(newOrders2);
    }
    return changedOrders2;
  }

  /**
   * Private utility script written to convert some of my faction's orders. Not interesting for the
   * general public.
   *
   * @param faction
   * @param region
   */
  public void cleanShortOrders(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);

    initLocales();

    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());

      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);

        cleanShortOrders(faction, r);
      }
      return;
    }

    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);

        if (changeOrders(u, new ShortOrderFilter(world))) {
          notifyMagellan(u);
        }
      }
    }
  }

  /**
   * Private utility script written to convert some of my faction's orders. Not interesting for the
   * general public.
   *
   * @param faction
   * @param region
   */
  public void cleanScripts(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);

    initLocales();

    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());

      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);

        cleanScripts(faction, r);
      }
      return;
    }

    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);

        setCurrentUnit(u);

        // loop over orders
        line = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (currentOrder.startsWith("@RESERVIERE")) {
            addNewOrder("// $cript Benoetige " + currentOrder.substring(currentOrder.indexOf(" ")),
                true);
            addNewOrder(currentOrder.substring(1), true);
          } else {
            if (command.isLong() || command.isPersistent() || command.getText().startsWith("//")) {
              addNewOrder(command.getText(), false);
            } else {
              // omit
              setChangedOrders(true);
            }
          }
        }
        updateCurrentOrders();
        setCurrentUnit(null);
      }
    }

  }

  /**
   * Private utility script written to handle unwanted orders created by the TeachPlugin. Not
   * interesting for the general public.
   *
   * @param faction
   * @param region
   */
  public void undoTeaching(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);

    initLocales();

    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());

      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);

        undoTeaching(faction, r);
      }
      return;
    }

    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);

        setCurrentUnit(u);

        // loop over orders
        line = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (currentOrder.startsWith("; $$$ LE")) {
            addNewOrder(currentOrder.substring(currentOrder.indexOf("$$$") + 4) + " ; restored",
                true);
            setChangedOrders(true);
          } else if (isChangedOrders()
              && (currentOrder.startsWith("LERNE") || currentOrder.startsWith("LEHRE"))) {
            // skip
          } else {
            addNewOrder(command.getText(), false);
          }
        }
        updateCurrentOrders();
        setCurrentUnit(null);
      }
    }

  }

  /**
   * Private utility script written to handle unwanted orders. Not interesting for the general
   * public.
   *
   * @param faction
   * @param region
   */
  public void fixTwoLongOrders(Faction faction, Region region) {
    if (faction == null)
      throw new NullPointerException();
    currentFactions = Collections.singletonMap(faction, 1);

    initLocales();

    if (region == null) {
      // comment out the following line if you don't have the newest nightly build of Magellan
      helper.getUI().setMaximum(world.getRegions().size());

      // call self for all regions
      for (Region r : world.getRegions()) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(r.toString(), ++progress);

        fixTwoLongOrders(faction, r);
      }
      return;
    }

    // loop for all units of faction
    for (Unit u : region.units()) {
      if (faction.getID().equals(u.getFaction().getID())) {
        // comment out the following line if you don't have the newest nightly build of Magellan
        helper.getUI().setProgress(region.toString() + " - " + u.toString(), progress);

        setCurrentUnit(u);

        int hasLong = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (command.isLong()) {
            if (++hasLong > 1) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
              setChangedOrders(true);
            } else {
              addNewOrder(currentOrder, false);
            }
          } else {
            addNewOrder(currentOrder, false);
          }
        }
        updateCurrentOrders();
        setCurrentUnit(null);
      }
    }
  }

  /**
   * Private utility script to convert old crew logic to new.
   *
   */
  public static void correctCrewReserve(GameData data) {
    for (Unit u : data.getUnits()) {
      boolean found = false;
      for (Order o : u.getOrders2()) {
        if (o.getText().startsWith("// $cript Steuermann") || o.getText().startsWith("// $cript Mannschaft")) {
          found = true;
          break;
        }
      }
      if (found) {
        found = false;
        ArrayList<String> orders = new ArrayList<String>();

        for (Order o : u.getOrders2()) {
          String text = o.getText();
          if (!text.matches("// \\$cript Benoetige .* Schwert") && !text.matches(
              "// \\$cript Benoetige .* Schild")
              && !text.matches("// \\$cript Benoetige .* Speer")) {
            orders.add(text);
          } else {
            found = true;
            orders.add("; " + text);
          }
        }
        if (found) {
          u.setOrders(orders);
        }
      }
    }
  }

  /**
   * Deletes all signs, then creates a sign for every island.
   * 
   * @param world
   */
  public static void signIslands(GameData world) {

    for (Region r : world.getRegions()) {
      r.clearSigns();
    }
    for (Island island : world.getIslands()) {
      if (island.regions().size() == 0) {
        continue;
      }
      if (island.regions().size() == 1) {
        Region r = island.regions().iterator().next();
        if (!r.getRegionType().isLand()) {
          continue;
        }
      }
      if (island.getModifiedName().matches("([0-9]+. )*[0-9]+")) {
        continue;
      }

      int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE;
      for (Region r : island.regions()) {
        if (r.getCoordinate().getZ() != 0) {
          continue;
        }
        minX = Math.min(minX, r.getCoordX());
        minY = Math.min(minY, r.getCoordY());
        maxX = Math.max(maxX, r.getCoordX());
        maxY = Math.max(maxY, r.getCoordY());
      }
      int minDist = Integer.MAX_VALUE;
      Region minR = null;
      for (Region r : island.regions()) {
        int x2 = (minX + maxX) / 2 - r.getCoordX();
        int y2 = (minY + maxY) / 2 - r.getCoordY();
        int dist2 = x2 * x2 + y2 * y2;
        if (minDist > dist2 || minR == null) {
          minDist = dist2;
          minR = r;
        }
      }
      if (minR != null) {
        minR.addSign(new Sign(island.getModifiedName()));
      }

    }
  }

  /**
   * Again, a very specialized procedure
   */
  protected void markTRound(int round) {
    if (world.getDate().getDate() == round) {
      for (Unit u : world.getUnits()) {
        changeOrders(u, new TeachRoundOrderFilter());
        u.addOrder("// $$L" + (round + 1));
      }
    }
  }

  private void setNamespaces(MagellanPlugIn plugin, String namespaces) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    ExtendedCommandsHelper.invoke(plugin, "setNamespaces", new Class[] { String.class },
        new Object[] { namespaces });
  }

  private String getNamespaces(MagellanPlugIn plugin) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    return (String) ExtendedCommandsHelper.invoke(plugin, "getNamespacesString", new Class[] {},
        new Object[] {});
  }

  protected void teachRegion(Collection<Region> regions, String namespaces) {
    ArrayList<Region> regions2 = new ArrayList<Region>();
    for (Region r : regions) {
      if (r != null) {
        regions2.add(r);
      }
    }
    if (regions2.isEmpty())
      return;

    log.info("trying to call TeachPlugin method..");
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
    if (plugin != null) {
      try {
        String oldNameSpaces = null;
        if (namespaces != null) {
          oldNameSpaces = getNamespaces(plugin);
          setNamespaces(plugin, namespaces);
        }
        ExtendedCommandsHelper.invoke(plugin, "doTeachUnits", new Class[] { Collection.class },
            new Collection[] { regions2 });
        if (oldNameSpaces != null) {
          setNamespaces(plugin, oldNameSpaces);
        }
      } catch (ClassCastException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (SecurityException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (NoSuchMethodException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalArgumentException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalAccessException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (InvocationTargetException e) {
        log.warn("error calling TeachPlugin", e);
      }
    } else {
      log.warn("TeachPlugin not found");
    }
  }

  protected void teachAll(String namespaces) {
    log.info("trying to call TeachPlugin method..");
    MagellanPlugIn plugin = helper.getPlugin("magellan.plugin.teacher.TeachPlugin");
    if (plugin != null) {
      try {
        String oldNamespaces = null;
        if (namespaces != null) {
          oldNamespaces = getNamespaces(plugin);
          setNamespaces(plugin, namespaces);
        }
        ExtendedCommandsHelper.invoke(plugin, "execute", new Class[] { String.class },
            new Object[] { "EXECUTE_ALL" });
        if (oldNamespaces != null) {
          setNamespaces(plugin, oldNamespaces);
        }
      } catch (ClassCastException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (SecurityException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (NoSuchMethodException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalArgumentException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (IllegalAccessException e) {
        log.warn("error calling TeachPlugin", e);
      } catch (InvocationTargetException e) {
        log.warn("error calling TeachPlugin", e);
      }
    } else {
      log.warn("TeachPlugin not found");
    }
  }

  protected void teachAuto(Collection<Faction> factions) {
    teachAuto(factions, Collections.emptySet());
  }

  Map<Locale, Collection<String>> expensiveSkills = new HashMap<Locale, Collection<String>>();

  protected void initSkills(Locale loc) {
    // if (!expensiveSkills.containsKey(loc)) {
    // Set<String> exp;
    // expensiveSkills.put(loc, exp = new HashSet<String>());
    //
    // Arrays.asList(EresseaConstants.S_TAKTIK, EresseaConstants.S_KRAEUTERKUNDE, EresseaConstants.S_MAGIE,
    // EresseaConstants.S_ALCHEMIE, EresseaConstants.S_SPIONAGE).forEach((skill) -> {
    // try {
    // exp.add(
    // world.getGameSpecificStuff().getOrderChanger().getOrderO(skill, loc).getText());
    // } catch (RulesException e) {
    // e.printStackTrace();
    // // log.error(e.getClass().getSimpleName() + " script error for " + region);
    // throw new RuntimeException(e);
    // }
    // });
    // }
  }

  protected void teachAuto(Collection<Faction> factions, Collection<Region> regions) {
    Set<Region> filter = new HashSet<Region>(regions);
    Set<Unit> allStudents = new HashSet<Unit>();
    // for every teacher ...
    for (Faction faction : factions) {
      helper.getUI().setMaximum(faction.units().size());
      for (Unit teacher : faction.units()) {
        if (!filter.isEmpty() && !filter.contains(teacher.getRegion())) {
          continue;
        }
        if (!canTeach(teacher)) {
          continue;
        }
        int tIndex = 0;
        int studentCount = 0;
        helper.getUI().setProgress(teacher.toString(), tIndex);
        Orders teachOrders = teacher.getOrders2();
        for (Order teachOrder : teachOrders) {
          if (teachOrders.isToken(teachOrder, 0, EresseaConstants.OC_TEACH)) {
            // ... if all students learn the same skill
            int tokenIndex = 0;
            Map<Unit, Integer> students = new HashMap<Unit, Integer>();
            String skill = "";
            boolean temp = false;
            allStudents.add(teacher);
            for (OrderToken t : teachOrder.getTokens()) {
              if (tokenIndex++ > 0 && t.ttype == OrderToken.TT_ID) {
                String id = temp ? ("TEMP " + t.getText()) : t.getText();
                Unit student = helper.getUnit(id);
                allStudents.add(student);
                if (student != null && skill != null && student.getFaction() == teacher.getFaction() && canTeach(
                    student)) {
                  helper.getUI().setProgress(teacher.toString() + " - " + student.toString(), tIndex);
                  studentCount += student.getModifiedPersons();
                  Orders studentOrders = student.getOrders2();
                  int learnIndex = 0;
                  for (Order studentOrder : studentOrders) {
                    if (studentOrders.isToken(studentOrder, 0, EresseaConstants.OC_LEARN)) {
                      students.put(student, learnIndex);
                      String subject2 = studentOrder.getToken(1).getText();

                      if (skill.isEmpty() || skill.equals(subject2)) {
                        skill = subject2;
                      } else {
                        skill = null;
                      }
                    }
                    learnIndex++;
                  }

                } else {
                  skill = null;
                }
              }
            }
            // ... replace matching teach and learn orders with LEARN AUTO
            if (isAutoSkill(teacher, skill) && studentCount != teacher.getModifiedPersons() * TEACH_MULTI) {
              String autoT = LEARNOrder + " " + AUTOOrder + " " + skill + COMMENTOrder + " T " + teacher.getID();
              String autoL = LEARNOrder + " " + AUTOOrder + " " + skill + COMMENTOrder + " L " + studentCount;
              for (Unit s : students.keySet()) {
                autoL += " " + s.getID();
              }
              Order autoOrder = teacher.createOrder(autoL);
              teacher.replaceOrder(tIndex, autoOrder);
              autoOrder = teacher.createOrder(autoT);
              for (Unit s : students.keySet()) {
                s.replaceOrder(students.get(s), autoOrder);
              }
            }
          }
          ++tIndex;
        }
      }
    }

    // replace learn orders of students without teacher by LEARN AUTO
    int tIndex = 0;
    for (Faction faction : factions) {
      for (Unit unit : faction.units()) {
        if (!filter.isEmpty() && !filter.contains(unit.getRegion())) {
          continue;
        }
        helper.getUI().setProgress(unit.toString(), tIndex++);
        Orders orders = unit.getOrders2();
        int oIndex = 0;
        for (Order o : orders) {
          if (orders.isToken(o, 0, EresseaConstants.OC_LEARN) &&
              !orders.isToken(o, 1, EresseaConstants.OC_AUTO) &&
              !allStudents.contains(unit) &&
              isAutoSkill(unit, o.getToken(1).getText()) &&
              canTeach(unit)) {
            String newOrder = o.getText().replace(o.getToken(0).getText(), o.getToken(0).getText() + " " + AUTOOrder)
                + COMMENTOrder + " L-";
            unit.replaceOrder(oIndex, unit.createOrder(newOrder));
          }
          ++oIndex;
        }
      }
    }
  }

  private boolean isAutoSkill(Unit unit, String skillText) {
    SkillType skill = world.getRules().getSkillType(skillText);
    return skill != null && skill.getCost(1) == 0;
    // initSkills(unit.getLocale());
    // return expensiveSkills.get(unit.getLocale()).contains(skill);
  }

  private boolean canTeach(Unit unit) {
    // try {
    // world.getGameSpecificRules().getClass().getMethod("canTeach", Unit.class);
    // return world.getGameSpecificRules().canTeach(unit);
    // } catch (NoSuchMethodException e) {
    switch (unit.getRace().toString()) {
    case "Dämonen":
    case "Elfen":
    case "Goblins":
    case "Halblinge":
    case "Insekten":
    case "Katzen":
    case "Meermenschen":
    case "Menschen":
    case "Orks":
    case "Trolle":
    case "Zwerge":
      return true;
    default:
      return false;
    }
    // }
  }

  /**
   * Test classes for Beanshell
   */
  static class A {

    /**
     * @return "A.b"
     */
    public static String b() {
      return "A.b";
    }
  }

  class B {

    public String b() {
      return "B.b";
    }

  }

  void testA(Unit u) {
    helper.addOrder(u, A.b());
    helper.addOrder(u, (new B()).b());
  }

  // ---start comment for BeanShell
  /**
   * Makes this code usable as input to the CommandParser. Tries to read the source of this file and
   * strip it from generics and other advanced stuff that BeanShell doesn't understand.
   *
   * @param args this is ignored
   */
  public static void main(String[] args) {
    File file =
        new File("./src-test/magellan/plugin/extendedcommands/scripts/E3CommandParser.java");
    LineNumberReader reader = null;
    try {
      reader = new LineNumberReader(new FileReader(file));

      System.out.println("// created by E3CommandParser.main() at "
          + Calendar.getInstance().getTime());
      System.out.println();

      boolean commentMode = false;
      boolean unCommentMode = false;
      for (String line = reader.readLine(); line != null; line = reader.readLine()) {
        if (line.matches(" *// ---start comment for BeanShell")) {
          commentMode = true;
          System.out.println("// ---commented for BeanShell");
          continue;
        }
        if (line.matches(" *// ---stop comment for BeanShell")) {
          commentMode = false;
          System.out.println(line);
          continue;
        }
        if (line.matches(" *// ---start uncomment for BeanShell")) {
          unCommentMode = true;
          System.out.println("// ---uncommented for BeanShell");
          continue;
        }
        if (line.matches(" *// ---stop uncomment for BeanShell")) {
          unCommentMode = false;
          System.out.println(line);
          continue;
        }
        if (commentMode) {
          System.out.print("//");
          System.out.println(line);
          continue;
        }
        if (unCommentMode) {
          System.out.println(line.substring(line.indexOf("//") + 2));
          continue;
        }
        if (line.matches(" *package [a-z.]*;")) {
          continue;
        }

        // remove generics
        String lastLine = null, newLine = line;
        boolean changed = false;
        while (newLine.matches(".*<[A-Za-z0-9_, ]*>.*") && !isJavaComment(newLine)
            && !newLine.equals(lastLine)) {
          lastLine = newLine;
          newLine = newLine.replaceFirst("<[A-Za-z0-9_, ]*>", "");
          changed = true;
        }
        // remove @ directives
        if (!isJavaComment(newLine)) {
          newLine = newLine.replaceFirst(" +@.*", "// $0");
        }
        if (changed) {
          System.out.print("// ");
          System.out.println(line);
        }
        System.out.println(newLine);
      }

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (reader != null) {
        try {
          reader.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  private static boolean isJavaComment(String line) {
    return line.matches("[ ]*//.*") || line.matches("[ ]*[*].*");
  }
  // ---stop comment for BeanShell

  static class UnitItemPriority {
    public UnitItemPriority(Unit unit, String item, int amount, int priority, long serial) {
      this.unit = unit;
      this.item = item;
      this.amount = amount;
      this.priority = priority;
      this.serial = serial;
    }

    Unit unit;
    /** The order name of the item. */
    String item;
    int amount;
    int priority;
    long serial;

    public static int reduceAmount(int amount, int change) {
      if (amount == Integer.MAX_VALUE || amount == Integer.MIN_VALUE)
        return amount;
      long newValue = (long) amount - change;
      if (newValue > Integer.MAX_VALUE)
        return Integer.MAX_VALUE;
      else if (newValue < Integer.MIN_VALUE)
        return Integer.MIN_VALUE;
      else
        return (int) newValue;

    }

    public int compareTo(UnitItemPriority uip) {
      int diff = uip.priority - priority;
      if (diff != 0)
        return diff;
      return serial > uip.serial ? 1 : serial < uip.serial ? -1 : 0;
    }

    @Override
    public String toString() {
      return unit + " " + amount + " " + item + " (" + priority + ")";
    }
  }

  static class Supply {

    UnitItemPriority uip;

    public Supply(Unit unit, String item, int amount, long serial) {
      super();
      if (unit == null)
        throw new NullPointerException();
      uip = new UnitItemPriority(unit, item, amount, E3CommandParser.DEFAULT_SUPPLY_PRIORITY, serial);
    }

    public boolean hasPriority() {
      return getPriority() != Long.MAX_VALUE;
    }

    public int getPriority() {
      return uip.priority;
    }

    public void setPriority(int priority) {
      uip.priority = priority;
    }

    public Unit getUnit() {
      return uip.unit;
    }

    public int getAmount() {
      return uip.amount;
    }

    public void setAmount(int i) {
      uip.amount = i;
    }

    public String getItem() {
      return uip.item;
    }

    public void reduceAmount(int change) {
      uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
    }

    @Override
    public String toString() {
      return uip.unit + " has " + uip.amount + " " + uip.item;
    }

    public int compareTo(Supply o) {
      return uip.compareTo(o.uip);
    }
  }

  interface OrderFilter {

    /**
     * @return true if the order should be changed or deleted
     */
    public boolean changeOrder(Order order);

    /**
     * @return the string that should replace order, <code>null</code> if the order should be be
     *         removed. If changeOrder returns <code>false</code>, the return value is undefined!
     */
    public String changedOrder();
  }

  static class TeachRoundOrderFilter implements OrderFilter {
    public boolean changeOrder(Order order) {
      return changeOrder(order.getText());
    }

    public boolean changeOrder(String order) {
      if (order.startsWith("// $$L"))
        return true;
      return false;
    }

    public String changedOrder() {
      return null;
    }
  }

  static class ShortOrderFilter implements OrderFilter {

    private String result;
    private GameData world;

    public ShortOrderFilter(GameData world) {
      this.world = world;
    }

    public boolean changeOrder(Order command) {
      if (world.getGameSpecificStuff().getOrderChanger().isLongOrder(command))
        return false;
      else {
        if (command.isLong() || command.isPersistent() || command.getText().startsWith("//"))
          return false;
        else {
          if (command.getText().startsWith(";")) {
            result = null;
            return true;
          } else {
            result = ";" + command.getText();
            return true;
          }
        }
      }
    }

    public String changedOrder() {
      return result;
    }
  }

  static class Need {

    private int minAmount;
    private Warning warning;
    UnitItemPriority uip;
    private String message;

    public Need(Unit unit, String item, int minAmount, int maxAmount, int priority,
        Warning warning, long serial) {
      uip = new UnitItemPriority(unit, item, maxAmount, priority, serial);
      this.minAmount = minAmount;
      this.warning = warning;
      message = createMessage();
    }

    public int compareTo(Need need) {
      return uip.compareTo(need.uip);
    }

    public int getPriority() {
      return uip.priority;
    }

    public Unit getUnit() {
      return uip.unit;
    }

    public int getAmount() {
      return uip.amount;
    }

    public void setAmount(int i) {
      uip.amount = i;
      message = createMessage();
    }

    public String getItem() {
      return uip.item;
    }

    public int getMinAmount() {
      return minAmount;
    }

    public void setMinAmount(int amount) {
      minAmount = amount;
      message = createMessage();
    }

    public void reduceMinAmount(int change) {
      minAmount = UnitItemPriority.reduceAmount(minAmount, change);
    }

    public int getMaxAmount() {
      return getAmount();
    }

    public void setMaxAmount(int amount) {
      setAmount(amount);
      message = createMessage();
    }

    public void reduceMaxAmount(int change) {
      uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
    }

    /**
     * Returns the value of warning.
     *
     * @return Returns warning.
     */
    public Warning getWarning() {
      return warning;
    }

    /**
     * Sets the value of warning.
     *
     * @param warning The value for warning.
     */
    public void setWarning(Warning warning) {
      this.warning = warning;
    }

    @Override
    public String toString() {
      return createMessage();
    }

    private String createMessage() {
      return uip.unit + " needs " + minAmount + "/" + uip.amount + " " + uip.item + " ("
          + uip.priority + ")";
    }

    public String getMessage() {
      return message;
    }
  }

  static class Transfer {
    private Unit target;
    private boolean all;
    private Need need;
    private boolean min;
    private UnitItemPriority uip;

    public Transfer(Unit unit, Unit target, String item, int amount, boolean min, boolean all,
        Need need,
        long serial) {
      if (item == null)
        throw new NullPointerException();
      uip = new UnitItemPriority(unit, item, amount, 0, serial);
      this.target = target;
      this.all = all;
      this.need = need;
      this.min = min;
    }

    public void reduceAmount(int change) {
      uip.amount = UnitItemPriority.reduceAmount(uip.amount, change);
    }

    public Unit getUnit() {
      return uip.unit;
    }

    public int getAmount() {
      return uip.amount;
    }

    public void setAmount(int i) {
      uip.amount = i;
    }

    public String getItem() {
      return uip.item;
    }

    public boolean isMin() {
      return min;
    }

    public Unit getTarget() {
      return target;
    }

    public boolean isAll() {
      return all;
    }

    public String getMessage() {
      return need.getMessage();
    }

    @Override
    public String toString() {
      return uip.unit + " gives " + target + " " + (all ? "ALL" : uip.amount) + " " + uip.item;
    }

    public Need getNeed() {
      return need;
    }

  }
}