E3CommandParser

Aus Eressea
Zur Navigation springenZur Suche springen

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;

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)).teachAll("namespace");

// 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.

 
// created by magellan.plugin.BeanShellCleaner at Fri Jan 08 09:35:32 CET 2016
// from file ./src-test/magellan/plugin/extendedcommands/scripts/E3CommandParser.java

// 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.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
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.Item;
import magellan.library.LuxuryPrice;
import magellan.library.Order;
import magellan.library.Region;
import magellan.library.Rules;
import magellan.library.Ship;
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.RulesException;
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.Resources;
import magellan.library.utils.Utils;
import magellan.library.utils.logging.Logger;
import magellan.plugin.BeanShellCleaner;
import magellan.plugin.extendedcommands.ExtendedCommandsHelper;

// ---uncommented 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
 */
public class E3CommandParser {

  /**
   * 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;

  /**
   * 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 GIB_WENN_PRIORITY = DEFAULT_PRIORITY;
  /** 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 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
  /** 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 = "Schild";
  /** 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";
  /** 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();

  /** 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 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 Map currentFactions = CollectionFactory.createOrderedMap();
  private Region currentRegion;
  private Unit currentUnit;
//   private ArrayList<String> newOrders;
  private ArrayList newOrders;
  private String currentOrder;
  private int line;
  private int error;
  private boolean changedOrders;

  /**
   * The item/unit/need map. Stores all needed items.
   */
//   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;

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

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

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

  private int progress = -1;

  /**
   * 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) {
  public void execute(Collection 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) {
  public void executeFrom(Collection 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);

    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);

    for (Faction faction : factions) {
      for (Unit u : faction.units()) {
        if ("1".equals(getProperty(u, "confirm"))) {
          u.setOrdersConfirmed(true);
          // u.addOrder("; autoconfirmed");
        } 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);
  }

  /**
   * Returns the value of currentRegion.
   *
   * @return Returns currentRegion.
   */
  private Region getCurrentRegion() {
    return currentRegion;
  }

  /**
   * Sets the value of currentRegion.
   *
   * @param currentRegion The value for currentRegion.
   */
  private void setCurrentRegion(Region currentRegion) {
    this.currentRegion = currentRegion;
  }

  /**
   * Returns the value of currentUnit.
   *
   * @return Returns currentUnit.
   */
  private Unit getCurrentUnit() {
    return currentUnit;
  }

  /**
   * Sets the value of currentUnit.
   *
   * @param currentUnit The value for currentUnit.
   */
  private void setCurrentUnit(Unit currentUnit) {
    this.currentUnit = currentUnit;
  }

//   private void findSomeUnit(Map<Faction, Integer> factions) {
  private void findSomeUnit(Map 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!");

    setCurrentUnit(someUnit);
  }

  protected void execute(Region region) {
    try {
      setCurrentRegion(region);
      initSupply();

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

      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);
  }

  /**
   * Adds some statistic information to the orders of the first unit.
   *
   * @param region
   */
  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)) {
        u.addOrder(COMMENTOrder + " hat Skript", false);
        unitScripts++;
      }
    }
    for (Region r : world.getRegions()) {
      if (helper.hasScript(r)) {
        regionScripts++;
      }
    }

    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 />
   * <tt>// $cript +X text</tt> -- 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 [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 />
   * <code>// $cript Kommentar text</code> -- add ; comment<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!
    for (Order o : getCurrentUnit().getOrders2()) {
      ++line;
      currentOrder = o.getText();
      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
        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")) {
              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(getCurrentUnit(), false);
              }
            } else if (command.equals("Mannschaft")) {
              if (tokens.length < 3) {
                addNewError("zu wenige Argumente");
              } else {
                commandLearn(new String[] { "Lerne", tokens[1], tokens[2] });
                setConfirm(getCurrentUnit(), true);
              }
            } else if (command.equals("Quartiermeister")) {
              commandQuartermaster(tokens);
            } else if (command.equals("Sammler")) {
              commandCollector(tokens);
            } else if (command.equals("RekrutiereMax")) {
              commandRecruit(tokens);
            } else if (command.equals("Kommentar")) {
              commandComment(tokens);
            } else {
              addNewError("unbekannter Befehl: " + command);
            }
          }
          currentOrder = null;

        }
      }
    }
    if (changedOrders) {
      getCurrentUnit().setOrders(newOrders);
    }
    notifyMagellan(getCurrentUnit());

    newOrders = null;

  }

  /**
   * 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.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;
  }

  /**
   * <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)) {
        changedOrders = 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.
   *
   * @param tokens
   * @return <code>text</code>, if <code>rest==1</code>, otherwise <code>null</code>
   */
  protected String commandRepeat(String[] tokens) {
    StringBuilder result = null;
    try {
      int rest = Integer.parseInt(tokens[0]);
      int period = 0;
      int length = Integer.MAX_VALUE;
      int textIndex = 1;
      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) {
        result = new StringBuilder();
        if (period > 0) {
          rest = period + 1;
        }
        if (textIndex < tokens.length && tokens[textIndex].equals(scriptMarker)) {
          result.append(COMMENTOrder).append(" ");
        }
        for (int i = textIndex; i < tokens.length; ++i) {
          if (i > textIndex) {
            result.append(" ");
          }
          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;
  }

  /**
   * <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(getCurrentUnit(), true);
      addNewOrder(currentOrder, false);
    } else if (NOT.equalsIgnoreCase(tokens[1])) {
      setConfirm(getCurrentUnit(), 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(getCurrentUnit(), true);
        } else {
          setConfirm(getCurrentUnit(), 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>
   * <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]);

    // 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;
      }
    }

    // handle GIB xyz ALLES
    if (ALLOrder.equalsIgnoreCase(tokens[2])) {
      if (!testUnit(tokens[1], target, w))
        return;
      if (tokens.length == 3) {
        for (Item item : getCurrentUnit().getItems()) {
          if (item.getAmount() > 0) {
            addNewOrder(getGiveOrder(getCurrentUnit(), tokens[1], item.getOrderName(),
                Integer.MAX_VALUE, false), true);
          } else if (ADD_NOT_THERE_INFO) {
            addNewMessage("we have no " + item);
          }
        }
        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(tokens, target, w, new Filter() {

            public boolean approve(Item item) {
              return world.getRules().getItemCategory("herbs").equals(item.getItemType()
                  .getCategory());
            }
          });
        }
      }

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

            public boolean approve(Item item) {
              return world.getRules().getItemCategory("luxuries").equals(item.getItemType()
                  .getCategory());
            }
          });
        }
      }

      // handle GIB xyz TRANK
      if (TRANKOrder.equalsIgnoreCase(tokens[2])) {
        if (world.getRules().getItemCategory("potions") == null) {
          addNewError("Spiel kennt keine Tränke");
        } else {
          giveAll(tokens, target, w, new Filter() {
            public boolean approve(Item item) {
              return world.getRules().getItemCategory("potions").equals(item.getItemType()
                  .getCategory());
            }
          });
        }
      }

      return;
    }

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

    // get amount
    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);
      }
      amount = getItemCount(getCurrentUnit(), item);
    } else {
      try {
        amount = Integer.parseInt(tokens[2 + je]);
      } catch (NumberFormatException e) {
        amount = 0;
        addNewError("Zahl oder ALLES erwartet");
        return;
      }
    }

    if (!testUnit(tokens[1], target, w))
      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(getCurrentUnit(), item) < fullAmount) {
      if (w.contains(C_AMOUNT)) {
        addNewWarning("zu wenig " + item);
      } else {
        addNewMessage("zu wenig " + item);
      }
      amount = getItemCount(getCurrentUnit(), item);
      je = 0;
    }

    // make GIVE order
    if (amount > 0) {
      if (amount == getItemCount(getCurrentUnit(), item) && je != 1) {
        addNewOrder(getGiveOrder(getCurrentUnit(), tokens[1], item, Integer.MAX_VALUE, je == 1),
            true);
      } else {
        addNewOrder(getGiveOrder(getCurrentUnit(), tokens[1], item, amount, je == 1), true);
      }
      Supply supply = getSupply(item, getCurrentUnit());
      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) {
    if (testUnit(tokens[1], target, w)) {
      if (tokens.length > 3) {
        addNewError("zu viele Parameter");
      }

      for (Item item : getCurrentUnit().getItems())
        if (filter.approve(item)) {
          addNewOrder(getGiveOrder(getCurrentUnit(), tokens[1], item.getOrderName(),
              Integer.MAX_VALUE, false), true);
          Supply supply = getSupply(item.getOrderName(), getCurrentUnit());
          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);
  }

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

    return true;
  }

  /**
   * <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><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 = getCurrentUnit();
    Warning w = new Warning(true);

    String sOther = "???";
    if (tokens[0].equals("BenoetigeFremd")) {
      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(C_FOREIGN)) {
        w.add(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 > 5) {
      addNewError("falsche Anzahl Argumente");
      return;
    }

    if (!testUnit(sOther, unit, w, true))
      return;

    try {
      if (ALLOrder.equals(tokens[1])) {
        if (tokens.length > 2) {
          addNeed(tokens[2], unit, 0, Integer.MAX_VALUE, priority, w);
        } else {
          for (String item : supplyMap.keySet()) {
            addNeed(item, unit, 0, Integer.MAX_VALUE, priority, w);
          }
        }
      } else if (EACHOrder.equals(tokens[1])) {
        if (tokens.length != 4) {
          addNewError("ungültige Argumente für Benoetige JE x Ding");
        } else {
          int amount = (int) Math.ceil(unit.getPersons() * Double.parseDouble(tokens[2]));
          String item = tokens[3];
          addNeed(item, unit, amount, amount, 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 {
        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 />
   * 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 : getCurrentRegion().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", getCurrentUnit(), costs + zusatz1, costs + 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 : getCurrentUnit().getItems()) {
        Supply supply = getSupply(item.getOrderName(), getCurrentUnit());
        if (supply != null) {
          supply.priority = priority;
        }
      }
    } else {
      for (int i = 1; i < tokens.length - 1; ++i) {
        Supply supply = getSupply(tokens[i], getCurrentUnit());
        if (supply != null) {
          supply.priority = priority;
        }
      }
    }
  }

  /**
   * <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(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);
    }
  }

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

    for (int i = 1; i < tokens.length; i++) {
      try {
        if (i < tokens.length - 1) {
          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(getCurrentUnit(), 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 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) {
    String warning = tokens[tokens.length - 1];
    if (!(W_NEVER.equals(warning) || W_SKILL.equals(warning) || W_WEAPON.equals(warning) || W_SHIELD
        .equals(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 = W_WEAPON;
    }

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

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

  /**
   * <code>// $cript BerufBotschafter [Talent]</code> -- earn money if necessary, otherwise learn
   * skill<br />
   */
  protected void commandEmbassador(String[] tokens) {
    Skill skill = null;
    if (tokens.length > 1) {
      skill = getSkill(tokens[1], 10);
    } else {
      skill = getSkill(EresseaConstants.S_WAHRNEHMUNG.toString(), 10);
      if (skill == null) {
        skill = getSkill(EresseaConstants.S_AUSDAUER.toString(), 10);
      }
    }

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

    if (helper.getSilver(getCurrentUnit()) < 100) {
      if (hasEntertain() && getCurrentUnit().getSkill(EresseaConstants.S_UNTERHALTUNG) != null
          && getCurrentUnit().getSkill(EresseaConstants.S_UNTERHALTUNG).getLevel() > 0) {
        addNewOrder(ENTERTAINOrder, true);
      } else if (hasWork()) {
        addNewOrder(WORKOrder, true);
      } else {
        addNewWarning("Einheit verhungert");
      }
    } else if (skill == null) {
      StringBuilder order = new StringBuilder();
      if (tokens.length > 1) {
        order.append(tokens[1]);
      }
      for (int i = 2; i < tokens.length; ++i) {
        order.append(" ").append(tokens[i]);
      }
      addNewOrder(order.toString(), true);
    } else {
      learn(getCurrentUnit(), 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 : getCurrentRegion().units()) {
      if (u.getFaction() != getCurrentUnit().getFaction()) {
        if (!(allowedUnits.containsKey(u.getFaction()) && (allowedUnits.get(u.getFaction())
            .contains(u.getID()) || allowedUnits.get(u.getFaction()).contains(getCurrentRegion()
                .getZeroUnit().getID())))) {
          if (!(requiredUnits.containsKey(u.getFaction()) && requiredUnits.get(u.getFaction())
              .contains(u.getID()))) {
//             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);
          }
        }
      }
    }

//     for (Entry<Faction, List<Unit>> entry : warnings.entrySet()) {
    for (Entry 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() != getCurrentUnit().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;
    Map map;
    if (tokens[0].equals("Erlaube")) {
      map = allowedUnits;
    } else {
      map = requiredUnits;
    }
    if (faction == null) {
      addNewError("unbekannte Partei");
    } else {
//       Set<UnitID> set = map.get(faction);
      Set set = map.get(faction);
      if (set == null) {
//         set = new HashSet<UnitID>();
        set = new HashSet();
        map.put(faction, set);
      }
      if (ALLOrder.equals(tokens[2])) {
        set.add(getCurrentRegion().getZeroUnit().getID());
        if (tokens.length > 3) {
          addNewError("zu viele Argumente");
        }
      } else {
        for (int i = 2; i < tokens.length; ++i) {
          set.add(UnitID.createUnitID(tokens[i], world.base));
        }
      }
    }
  }

  /**
   * <code>// $cript Ernaehre [amount]</code> -- Earn as much money as possible (or the specified
   * amount), Versorge {@value #DEFAULT_EARN_PRIORITY}
   */
  protected void commandEarn(String[] tokens) {
    int amount = -1;
    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(
        getCurrentRegion()), 0);
    int workers = Math.min(maxWorkers, getCurrentRegion().getPeasants());
    Skill entertaining = getCurrentUnit().getModifiedSkill(world.getRules().getSkillType(
        EresseaConstants.S_UNTERHALTUNG));
    Skill taxing = getCurrentUnit().getModifiedSkill(world.getRules().getSkillType(
        EresseaConstants.S_STEUEREINTREIBEN));
    int entertain = 0, entertain2 = 0, tax = 0, tax2 = 0;
    if (entertaining != null && hasEntertain()) {
      entertain2 = 20 * entertaining.getLevel() * getCurrentUnit().getPersons();
      entertain = Math.max(0, Math.min(getCurrentRegion().maxEntertain(), entertain2));
    }
    if (taxing != null && isSoldier(getCurrentUnit())) {
      tax2 = 20 * taxing.getLevel() * getCurrentUnit().getPersons();
      tax = Math.min(getCurrentRegion().getSilver(), tax2);
    }

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

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

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

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

    int volume = getCurrentRegion().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 = getCurrentUnit().getModifiedSkill(world.getRules().getSkillType(
        EresseaConstants.S_HANDELN));
    if (buySkill == null) {
      addNewError("kein Handelstalent");
      return;
    }

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

    LuxuryPrice buyGood = null;
//     for (Entry<StringID, LuxuryPrice> entry : getCurrentRegion().getPrices().entrySet()) {
    for (Entry entry : getCurrentRegion().getPrices().entrySet()) {
      LuxuryPrice price = entry.getValue();
      if (price.getPrice() < 0) {
        buyGood = price;
      } else {
        if (getCurrentRegion().getOldPrices() != null && getCurrentRegion().getOldPrices().get(entry
            .getKey()) != null && price.getPrice() < getCurrentRegion().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>();
    LinkedList orders = new LinkedList();

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

    if (volume > 0 && buyGood != null) {
//       List<String> goods = new LinkedList<String>();
      List goods = new LinkedList();
      // Verkaufsbefehl setzen, wenn notwendig
      if (tokens.length > 2 && ALLOrder.equals(tokens[2])) {
        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; 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, getCurrentUnit(), ALLOrder.equals(tokens[2]) ? goodAmount : volume, volume,
            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", getCurrentUnit(), 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(C_SKILL)) {
      addNewError("Einheit hat zu wenig Handelstalent (min: " + maxAmount / 10 + " < " + (int) Math
          .ceil(skillNeeded / 10.0) + ")");
    }

    setConfirm(getCurrentUnit(), true);
  }

  /**
   * <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(getCurrentUnit(), Collections.singleton(new Skill(world.getRules().getSkillType(
        EresseaConstants.S_WAHRNEHMUNG), 30, 10, 1, true)));
    try {
      for (Item item : getCurrentUnit().getItems()) {
        boolean okay = false;
        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(getCurrentUnit(), okay);
      }
    } catch (NumberFormatException e) {
      addNewError("ungültige Zahl ");
    }
    setConfirm(getCurrentUnit(), 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 (getCurrentRegion().getRegionType().isOcean()) {
      addNewError("Sammeln nicht möglich!");
      return;
    }
    removeOrdersLike(MAKEOrder + " " + "[^T].*", true);
    removeOrdersLike(getResearchOrder() + ".*", true);
    if (modulo != Integer.MAX_VALUE && (world.getDate().getDate() % modulo == 0
        || getCurrentRegion().getHerbAmount() == null || (!getCurrentRegion().getHerbAmount()
            .equals("viele") && !getCurrentRegion().getHerbAmount().equals("sehr viele")))) {
      addNewOrder(getResearchOrder(), true);
    } else {
      addNewOrder(MAKEOrder + " " + getLocalizedOrder(EresseaConstants.OC_HERBS, "KRÄUTER"), true);
    }
  }

  /**
   * <code>KrautKontrolle [[[direction...] PAUSE]...]</code> move until the next PAUSE, if pause is
   * reached, research herbs.
   */
  protected void commandControl(String[] tokens) {
    for (Order o : getCurrentUnit().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(PCOMMENTOrder).append(" ").append(scriptMarker).append(" ").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 ? getCurrentUnit().getRace() : helper.getRace(race);
    if (effRace == null) {
      addNewError("Unbekannte Rasse");
      return;
    }

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

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

    if (getCurrentUnit().getPersons() + amount >= max) {
      addNewWarning("Rekrutierung fertig");
      amount = Math.min(max - getCurrentUnit().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 - getCurrentUnit().getPersons())
            * effRace.getRecruitmentCosts();
        addNeed("Silber", getCurrentUnit(), costs, maxCosts, DEFAULT_PRIORITY);
      } else {
        addNewWarning("Rekrutierungskosten unbekannt");
      }
      getRecruitOrder(amount, effRace);
      addNewOrder(getRecruitOrder(amount, effRace != getCurrentUnit().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 hasWork() {
    return world.getGameSpecificStuff().getOrderChanger().isLongOrder(getLocalizedOrder(
        EresseaConstants.OC_WORK, WORKOrder));
  }

  protected void initSupply() {
    if (needMap == null) {
//       needMap = new LinkedHashMap<String, Map<Integer, Map<Unit, Need>>>();
      needMap = new LinkedHashMap();
    } else {
      needMap.clear();
    }
    if (supplyMap == null) {
//       supplyMap = new LinkedHashMap<String, Map<Unit, Supply>>();
      supplyMap = new LinkedHashMap();
    } else {
      supplyMap.clear();
    }

    for (Unit u : getCurrentRegion().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) {
//     Map<Unit, Supply> itemSupplyMap = supplyMap.get(item);
    Map itemSupplyMap = supplyMap.get(item);
    if (itemSupplyMap == null) {
//       itemSupplyMap = new LinkedHashMap<Unit, Supply>();
      itemSupplyMap = new LinkedHashMap();
      supplyMap.put(item, itemSupplyMap);
    }
    Supply result = new Supply(unit, item, getItemCount(unit, item));
    itemSupplyMap.put(unit, result);
    return result;
  }

  /**
   * Tries to satisfy all needs in the current needMap by adding GIVE orders to supplyers.
   */
  protected void satisfyNeeds() {
    for (String item : needMap.keySet()) {
      // sort supplies by priority
//       Map<Unit, Supply> itemSupply = supplyMap.get(item);
      Map 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
        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();
        for (Supply supply : sorted) {
          itemSupply.put(supply.unit, supply);
        }
      }

//       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);
        }

        // try to satisfy minimum needs with GIVE
        for (Need need : nMap.values()) {
          giveNeed(need, true);
        }

        // add warnings for unsatisfied needs
        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
        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);
          }
        }

        // 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);
          }
        }
      }
      for (Unit u : reserves.keySet()) {

        int amount = reserves.get(u);
        if (amount > 0) {
          if (amount == u.getPersons()) {
            u.addOrder(getReserveOrder(u, item // + COMMENTOrder + need.toString()
                , 1, true), false);
          } else {
            u.addOrder(getReserveOrder(u, item, amount, false), false);
          }
        }
      }
    }

  }

  /**
   * Tries to satisfy (minimum) need by a RESERVE order
   *
   * @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
    amount = Math.min(amount, supply.getAmount());
    if (amount > 0) {
      need.reduceAmount(amount);
      need.reduceMinAmount(amount);
      supply.reduceAmount(amount);
      if (min) {
        if (reserves.containsKey(need.getUnit())) {
          amount += reserves.get(need.getUnit());
        }
        reserves.put(need.getUnit(), amount);
      }
    }
  }

  /**
   * Tries to satisfy (minimum) need by a give order from supplyers.
   *
   * @param need
   * @param min
   */
  protected void giveNeed(Need need, boolean min) {
    int amount = min ? need.getMinAmount() : need.getAmount();
    if (amount > 0) {
      if (!supplyMap.containsKey(need.getItem()))
        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;
        }
      }
    }
  }

  /**
   * 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) {
//     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;
  }

  /**
   * 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) {
//     Map<Unit, Supply> map = supplyMap.get(item);
    Map map = supplyMap.get(item);
    if (map == null)
      return null;
    return map.get(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 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 (need.getMinAmount() != Integer.MAX_VALUE) {
      if (minAmount == Integer.MAX_VALUE) {
        need.setMinAmount(minAmount);
      } else {
        need.reduceMinAmount(-minAmount);
      }
    }
  }

  /**
   * Returns a need of a unit for an item.
   *
   * @param item Order name of the required item
   * @param unit
   * @return The need or <code>null</code> if none has been registered.
   */
  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);
  }

  /**
   * 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 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();
        }
      }
      if (weaponSkill == null && !W_NEVER.equals(warning)) {
        addNewWarning("kein Kampftalent");
        return;
      }
    }

//     ArrayList<Item> weapons = new ArrayList<Item>();
    ArrayList weapons = new ArrayList();
    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");
    ArrayList shields = findItems(shield, u, "shield");
    if (shields.isEmpty()) {
      addNewError("keine Schilde bekannt");
    }

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

    if (weaponSkill != null) {
//       List<Skill> targetSkills = new LinkedList<Skill>();
      List targetSkills = new LinkedList();
      targetSkills.add(u.getSkill(weaponSkill));
      if (weaponSkill.getName().equals("Hiebwaffen") || weaponSkill.getName().equals(
          "Stangenwafen")) {
        targetSkills.add(getSkill(S_ENDURANCE, Math.round(ENDURANCERATIO_FRONT * getSkillLevel(u,
            weaponSkill))));
      } else {
        targetSkills.add(getSkill(S_ENDURANCE, Math.round(ENDURANCERATIO_BACK * getSkillLevel(u,
            weaponSkill))));
      }
      learn(u, targetSkills);
    }

    if (!NULL.equals(sWeapon) && !reserveEquipment(weapon, weapons, !W_NEVER.equals(warning)
        && !W_SKILL.equals(warning))) {
      addNewError("konnte Waffe nicht reservieren");
    }
    if (!NULL.equals(sShield) && !reserveEquipment(shield, shields, W_ARMOR.equals(warning)
        || W_SHIELD.equals(warning))) {
      addNewError("konnte Schilde nicht reservieren");
    }
    if (!NULL.equals(sArmor) && !reserveEquipment(armor, armors, 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<Skill> targetSkills) {
  protected void learn(Unit u, Collection targetSkills) {

    // find skill with maximum priority
    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);

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

  /**
   * 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
    Skill learningTarget = null;
    double maxMult = 0;

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

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

    if (maxMult > 0) {
      // calc max normalized learning weeks
      double maxWeeks = 0;
      for (Skill skill2 : targetSkills) {
        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;
        }
        // }
      }
      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) {
    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) {
  protected static ArrayList findItems(ItemType itemType, Unit u, String category) {
//     ArrayList<Item> items = new ArrayList<Item>(1);
    ArrayList items = new ArrayList(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()) {
      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) {
  protected boolean reserveEquipment(ItemType preferred, List 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(getCurrentUnit()
              .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
      // weapons
      Item w = ownStuff.get(0);
      String max = Integer.toString(Math.max(0, Math.min(getCurrentUnit().getPersons() - supply + w
          .getAmount(), getCurrentUnit().getPersons())));
      String min = warn ? max : Integer.toString(Math.min(getCurrentUnit().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
   */
  protected void initLocales() {
    if (getCurrentUnit() == null) {
      findSomeUnit(currentFactions);
    }
    if (getCurrentUnit() == null)
      throw new NullPointerException();

    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
      W_NEVER = "never";
      W_SKILL = "skill";
      W_WEAPON = "weapon";
      W_SHIELD = "shield";
      W_ARMOR = "armor";
      BEST = "best";
      NULL = "null";
      LUXUSOrder = "LUXURY";
      KRAUTOrder = "HERBS";
    }
  }

  protected String getLocalizedOrder(StringID orderKey, String fallback) {
    try {
      return world.getGameSpecificStuff().getOrderChanger().getOrderO(orderKey, getCurrentUnit()
          .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(getCurrentUnit().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, getCurrentUnit().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) {
    changedOrders = changedOrders || changed;
    if (!changed) {
      // remove old orders matching a removed order
      for (String pattern : removedOrderPatterns)
        if (order.matches(pattern))
          return;
    }

    newOrders.add(getParser().parse(order, getCurrentUnit().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();) {
      for (Iterator it = newOrders.iterator(); it.hasNext();) {
        String line2 = it.next();
        if (line2.matches(regEx)) {
          it.remove();
        }
      }
    }
    removedOrderPatterns.add(regEx);
  }

  /**
   * Adds an error line.
   *
   * @param line The current order line
   * @param hint
   */
  protected void addNewError(String hint) {
    error = line;
    // errMsg = hint;
    addNewOrder(COMMENTOrder + " TODO: " + hint + " (Fehler in Zeile " + error + ")", true);
    setConfirm(getCurrentUnit(), 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 line The current order line
   * @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(getCurrentUnit(), false);
  }

  // ///////////////////////////////////////////////////////
  // 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, "; -------------------------------------");
    helper.addOrder(unit, "; TODO: " + text);
    setConfirm(unit, false);
  }

  /**
   * 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();
    Collection 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 = PCOMMENTOrder + " " + scriptMarker + " ";
    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);
//         newOrders = new ArrayList<String>();
        newOrders = new ArrayList();
//         removedOrderPatterns = new ArrayList<String>();
        removedOrderPatterns = new ArrayList();
        changedOrders = false;
        error = -1;
        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;
        }

        if (changedOrders) {
          getCurrentUnit().setOrders(newOrders);
        }
        notifyMagellan(getCurrentUnit());

        newOrders = 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>();
            List result = new ArrayList();
            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>();
    List newOrders2 = new ArrayList();
    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);
//         newOrders = new ArrayList<String>();
        newOrders = new ArrayList();
//         removedOrderPatterns = new ArrayList<String>();
        removedOrderPatterns = new ArrayList();
        changedOrders = false;
        error = -1;

        // 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
              changedOrders = true;
            }
          }
        }
        if (changedOrders) {
          getCurrentUnit().setOrders(newOrders);
        }
        notifyMagellan(getCurrentUnit());

        newOrders = 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);
//         newOrders = new ArrayList<String>();
        newOrders = new ArrayList();
//         removedOrderPatterns = new ArrayList<String>();
        removedOrderPatterns = new ArrayList();
        changedOrders = false;
        error = -1;

        // 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);
            changedOrders = true;
          } else if (changedOrders && (currentOrder.startsWith("LERNE") || currentOrder.startsWith(
              "LEHRE"))) {
            // skip
          } else {
            addNewOrder(command.getText(), false);
          }
        }
        if (changedOrders) {
          getCurrentUnit().setOrders(newOrders);
        }
        notifyMagellan(getCurrentUnit());

        newOrders = 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);
//         newOrders = new ArrayList<String>();
        newOrders = new ArrayList();
//         removedOrderPatterns = new ArrayList<String>();
        removedOrderPatterns = new ArrayList();
        changedOrders = false;
        error = -1;

        int hasLong = 0;
        for (Order command : u.getOrders2()) {
          currentOrder = command.getText();
          if (command.isLong()) {
            if (++hasLong > 1) {
              addNewOrder(COMMENTOrder + " " + currentOrder, true);
              changedOrders = true;
            } else {
              addNewOrder(currentOrder, false);
            }
          } else {
            addNewOrder(currentOrder, false);
          }
        }
        if (changedOrders) {
          getCurrentUnit().setOrders(newOrders);
        }
        notifyMagellan(getCurrentUnit());

        newOrders = null;
      }
    }
  }

  /**
   * 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) {
  protected void teachRegion(Collection regions, String namespaces) {
//     ArrayList<Region> regions2 = new ArrayList<Region>();
    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..");
    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");
    }
  }

  /**
   * 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());
  }

// ---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
//   * @throws IOException
//   */
//  public static void main(String[] args) throws IOException {
//    System.out.println(new BeanShellCleaner().clean(new File(
//        "./src-test/magellan/plugin/extendedcommands/scripts/E3CommandParser.java")));
//
//  }
//
  // ---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(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) {
    if (order.startsWith("// $$L"))
      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(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;
  }
}

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) {
    minAmount = amount;
  }

  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 = { new Flag(true, E3CommandParser.W_AMOUNT,
      E3CommandParser.C_AMOUNT), 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) {
    /** Bean Shell does not know static initializers, so this is a bit awkward */
    if (!initialized) {
      initStatic();
    }
    parse(tokens);
  }

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

  protected void setAll() {
    for (Flag f : ALL_FLAGS)
      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)
      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(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